Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Feature command rewrite #862

Closed
wants to merge 16 commits into from
Closed
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
252 changes: 21 additions & 231 deletions ArchiSteamFarm/Bot.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,13 @@ internal sealed class Bot : IDisposable {
private readonly BotDatabase BotDatabase;

[JsonProperty]
private readonly string BotName;
internal readonly string BotName;

private readonly CallbackManager CallbackManager;
private readonly SemaphoreSlim CallbackSemaphore = new SemaphoreSlim(1, 1);

[JsonProperty]
private readonly CardsFarmer CardsFarmer;
internal readonly CardsFarmer CardsFarmer;

private readonly SemaphoreSlim GamesRedeemerInBackgroundSemaphore = new SemaphoreSlim(1, 1);
private readonly ConcurrentHashSet<ulong> HandledGifts = new ConcurrentHashSet<ulong>();
Expand All @@ -102,7 +102,7 @@ internal sealed class Bot : IDisposable {
private string BotPath => Path.Combine(SharedInfo.ConfigDirectory, BotName);
private string ConfigFilePath => BotPath + SharedInfo.ConfigExtension;
private string DatabaseFilePath => BotPath + SharedInfo.DatabaseExtension;
private bool IsAccountLocked => AccountFlags.HasFlag(EAccountFlags.Lockdown);
internal bool IsAccountLocked => AccountFlags.HasFlag(EAccountFlags.Lockdown);
private string KeysToRedeemFilePath => BotPath + SharedInfo.KeysExtension;
private string KeysToRedeemUnusedFilePath => KeysToRedeemFilePath + SharedInfo.KeysUnusedExtension;
private string KeysToRedeemUsedFilePath => KeysToRedeemFilePath + SharedInfo.KeysUsedExtension;
Expand Down Expand Up @@ -146,7 +146,7 @@ internal sealed class Bot : IDisposable {
private ulong LibraryLockedBySteamID;
private bool LootingAllowed = true;
private bool LootingScheduled;
private bool PlayingBlocked;
internal bool PlayingBlocked;
private Timer PlayingWasBlockedTimer;
private bool ReconnectOnUserInitiated;
private Timer SendItemsTimer;
Expand Down Expand Up @@ -938,8 +938,6 @@ internal async Task<string> Response(ulong steamID, string message) {
return await Response2FAConfirm(steamID, true).ConfigureAwait(false);
case "BL":
return ResponseBlacklist(steamID);
case "EXIT":
return ResponseExit(steamID);
case "FARM":
return await ResponseFarm(steamID).ConfigureAwait(false);
case "HELP":
Expand All @@ -962,22 +960,12 @@ internal async Task<string> Response(ulong steamID, string message) {
return ResponseResume(steamID);
case "RESTART":
return ResponseRestart(steamID);
case "SA":
return await ResponseStatus(steamID, SharedInfo.ASF).ConfigureAwait(false);
case "START":
return ResponseStart(steamID);
case "STATS":
return ResponseStats(steamID);
case "STATUS":
return ResponseStatus(steamID).Response;
case "STOP":
return ResponseStop(steamID);
case "UNPACK":
return await ResponseUnpackBoosters(steamID).ConfigureAwait(false);
case "UPDATE":
return await ResponseUpdate(steamID).ConfigureAwait(false);
case "VERSION":
return ResponseVersion(steamID);
default:
return ResponseUnknown(steamID);
}
Expand Down Expand Up @@ -1129,10 +1117,6 @@ internal async Task<string> Response(ulong steamID, string message) {
return await ResponseResume(steamID, Utilities.GetArgsAsText(args, 1, ",")).ConfigureAwait(false);
case "START":
return await ResponseStart(steamID, Utilities.GetArgsAsText(args, 1, ",")).ConfigureAwait(false);
case "STATUS":
return await ResponseStatus(steamID, Utilities.GetArgsAsText(args, 1, ",")).ConfigureAwait(false);
case "STOP":
return await ResponseStop(steamID, Utilities.GetArgsAsText(args, 1, ",")).ConfigureAwait(false);
case "TRANSFER":
if (args.Length > 3) {
return await ResponseTransfer(steamID, args[1], args[2], Utilities.GetArgsAsText(args, 3, ",")).ConfigureAwait(false);
Expand Down Expand Up @@ -1404,38 +1388,6 @@ private void HandleCallbacks() {
}
}

private async Task HandleMessage(ulong chatGroupID, ulong chatID, ulong steamID, string message) {
if ((chatGroupID == 0) || (chatID == 0) || (steamID == 0) || string.IsNullOrEmpty(message)) {
ArchiLogger.LogNullError(nameof(chatGroupID) + " || " + nameof(chatID) + " || " + nameof(steamID) + " || " + nameof(message));
return;
}

string response = await Response(steamID, message).ConfigureAwait(false);

// We respond with null when user is not authorized (and similar)
if (string.IsNullOrEmpty(response)) {
return;
}

await SendMessage(chatGroupID, chatID, response).ConfigureAwait(false);
}

private async Task HandleMessage(ulong steamID, string message) {
if ((steamID == 0) || string.IsNullOrEmpty(message)) {
ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(message));
return;
}

string response = await Response(steamID, message).ConfigureAwait(false);

// We respond with null when user is not authorized (and similar)
if (string.IsNullOrEmpty(response)) {
return;
}

await SendMessage(steamID, response).ConfigureAwait(false);
}

private async Task HeartBeat() {
if (!KeepRunning || !IsConnectedAndLoggedOn || (HeartBeatFailures == byte.MaxValue)) {
return;
Expand Down Expand Up @@ -1601,7 +1553,7 @@ private void InitStart() {
Utilities.InBackground(Start);
}

private bool IsFamilySharing(ulong steamID) {
internal bool IsFamilySharing(ulong steamID) {
if (steamID == 0) {
ArchiLogger.LogNullError(nameof(steamID));
return false;
Expand All @@ -1619,7 +1571,7 @@ private bool IsMasterClanID(ulong steamID) {
return steamID == BotConfig.SteamMasterClanID;
}

private bool IsOperator(ulong steamID) {
internal bool IsOperator(ulong steamID) {
if (steamID == 0) {
ArchiLogger.LogNullError(nameof(steamID));
return false;
Expand All @@ -1628,7 +1580,7 @@ private bool IsOperator(ulong steamID) {
return IsOwner(steamID) || (GetSteamUserPermission(steamID) >= BotConfig.EPermission.Operator);
}

private static bool IsOwner(ulong steamID) {
internal static bool IsOwner(ulong steamID) {
if (steamID == 0) {
ASF.ArchiLogger.LogNullError(nameof(steamID));
return false;
Expand Down Expand Up @@ -1948,7 +1900,13 @@ private async Task OnIncomingChatMessage(CChatRoom_IncomingChatMessage_Notificat
}

ArchiLogger.LogChatMessage(false, message, notification.chat_group_id, notification.chat_id, notification.steamid_sender);
await HandleMessage(notification.chat_group_id, notification.chat_id, notification.steamid_sender, message).ConfigureAwait(false);

string response = await Commands.Parse(this, notification.steamid_sender, message).ConfigureAwait(false);
if (string.IsNullOrEmpty(response)) {
return;
}

await SendMessage(notification.chat_group_id, notification.chat_id, response).ConfigureAwait(false);
}

private async Task OnIncomingMessage(CFriendMessages_IncomingMessage_Notification notification) {
Expand Down Expand Up @@ -1982,7 +1940,12 @@ private async Task OnIncomingMessage(CFriendMessages_IncomingMessage_Notificatio
return;
}

await HandleMessage(notification.steamid_friend, message).ConfigureAwait(false);
string response = await Commands.Parse(this, notification.steamid_friend, message).ConfigureAwait(false);
if (string.IsNullOrEmpty(response)) {
return;
}

await SendMessage(notification.steamid_friend, response).ConfigureAwait(false);
}

private async void OnLicenseList(SteamApps.LicenseListCallback callback) {
Expand Down Expand Up @@ -3101,27 +3064,6 @@ private static async Task<string> ResponseBlacklistRemove(ulong steamID, string
return responses.Count > 0 ? string.Join(Environment.NewLine, responses) : null;
}

private static string ResponseExit(ulong steamID) {
if (steamID == 0) {
ASF.ArchiLogger.LogNullError(nameof(steamID));
return null;
}

if (!IsOwner(steamID)) {
return null;
}

// Schedule the task after some time so user can receive response
Utilities.InBackground(
async () => {
await Task.Delay(1000).ConfigureAwait(false);
await Program.Exit().ConfigureAwait(false);
}
);

return FormatStaticResponse(Strings.Done);
}

private async Task<string> ResponseFarm(ulong steamID) {
if (steamID == 0) {
ArchiLogger.LogNullError(nameof(steamID));
Expand Down Expand Up @@ -4726,135 +4668,6 @@ private string ResponseStats(ulong steamID) {
return FormatBotResponse(string.Format(Strings.BotStats, memoryInMegabytes));
}

private (string Response, Bot Bot) ResponseStatus(ulong steamID) {
if (steamID == 0) {
ArchiLogger.LogNullError(nameof(steamID));
return (null, this);
}

if (!IsFamilySharing(steamID)) {
return (null, this);
}

if (!IsConnectedAndLoggedOn) {
return (FormatBotResponse(KeepRunning ? Strings.BotStatusConnecting : Strings.BotStatusNotRunning), this);
}

if (PlayingBlocked) {
return (FormatBotResponse(Strings.BotStatusPlayingNotAvailable), this);
}

if (CardsFarmer.Paused) {
return (FormatBotResponse(Strings.BotStatusPaused), this);
}

if (IsAccountLimited) {
return (FormatBotResponse(Strings.BotStatusLimited), this);
}

if (IsAccountLocked) {
return (FormatBotResponse(Strings.BotStatusLocked), this);
}

if (!CardsFarmer.NowFarming || (CardsFarmer.CurrentGamesFarming.Count == 0)) {
return (FormatBotResponse(Strings.BotStatusNotIdling), this);
}

if (CardsFarmer.CurrentGamesFarming.Count > 1) {
return (FormatBotResponse(string.Format(Strings.BotStatusIdlingList, string.Join(", ", CardsFarmer.CurrentGamesFarming.Select(game => game.AppID + " (" + game.GameName + ")")), CardsFarmer.GamesToFarm.Count, CardsFarmer.GamesToFarm.Sum(game => game.CardsRemaining), CardsFarmer.TimeRemaining.ToHumanReadable())), this);
}

CardsFarmer.Game soloGame = CardsFarmer.CurrentGamesFarming.First();
return (FormatBotResponse(string.Format(Strings.BotStatusIdling, soloGame.AppID, soloGame.GameName, soloGame.CardsRemaining, CardsFarmer.GamesToFarm.Count, CardsFarmer.GamesToFarm.Sum(game => game.CardsRemaining), CardsFarmer.TimeRemaining.ToHumanReadable())), this);
}

private static async Task<string> ResponseStatus(ulong steamID, string botNames) {
if ((steamID == 0) || string.IsNullOrEmpty(botNames)) {
ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames));
return null;
}

HashSet<Bot> bots = GetBots(botNames);
if ((bots == null) || (bots.Count == 0)) {
return IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null;
}

IEnumerable<Task<(string Response, Bot Bot)>> tasks = bots.Select(bot => Task.Run(() => bot.ResponseStatus(steamID)));
ICollection<(string Response, Bot Bot)> results;

switch (Program.GlobalConfig.OptimizationMode) {
case GlobalConfig.EOptimizationMode.MinMemoryUsage:
results = new List<(string Response, Bot Bot)>(bots.Count);
foreach (Task<(string Response, Bot Bot)> task in tasks) {
results.Add(await task.ConfigureAwait(false));
}

break;
default:
results = await Task.WhenAll(tasks).ConfigureAwait(false);
break;
}

List<(string Response, Bot Bot)> validResults = new List<(string Response, Bot Bot)>(results.Where(result => !string.IsNullOrEmpty(result.Response)));
if (validResults.Count == 0) {
return null;
}

HashSet<Bot> botsRunning = validResults.Where(result => result.Bot.KeepRunning).Select(result => result.Bot).ToHashSet();

string extraResponse = string.Format(Strings.BotStatusOverview, botsRunning.Count, validResults.Count, botsRunning.Sum(bot => bot.CardsFarmer.GamesToFarm.Count), botsRunning.Sum(bot => bot.CardsFarmer.GamesToFarm.Sum(game => game.CardsRemaining)));
return string.Join(Environment.NewLine, validResults.Select(result => result.Response).Union(extraResponse.ToEnumerable()));
}

private string ResponseStop(ulong steamID) {
if (steamID == 0) {
ArchiLogger.LogNullError(nameof(steamID));
return null;
}

if (!IsMaster(steamID)) {
return null;
}

if (!KeepRunning) {
return FormatBotResponse(Strings.BotAlreadyStopped);
}

Stop();
return FormatBotResponse(Strings.Done);
}

private static async Task<string> ResponseStop(ulong steamID, string botNames) {
if ((steamID == 0) || string.IsNullOrEmpty(botNames)) {
ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames));
return null;
}

HashSet<Bot> bots = GetBots(botNames);
if ((bots == null) || (bots.Count == 0)) {
return IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null;
}

IEnumerable<Task<string>> tasks = bots.Select(bot => Task.Run(() => bot.ResponseStop(steamID)));
ICollection<string> results;

switch (Program.GlobalConfig.OptimizationMode) {
case GlobalConfig.EOptimizationMode.MinMemoryUsage:
results = new List<string>(bots.Count);
foreach (Task<string> task in tasks) {
results.Add(await task.ConfigureAwait(false));
}

break;
default:
results = await Task.WhenAll(tasks).ConfigureAwait(false);
break;
}

List<string> responses = new List<string>(results.Where(result => !string.IsNullOrEmpty(result)));
return responses.Count > 0 ? string.Join(Environment.NewLine, responses) : null;
}

private async Task<string> ResponseTransfer(ulong steamID, string mode, string botNameTo) {
if ((steamID == 0) || string.IsNullOrEmpty(botNameTo) || string.IsNullOrEmpty(mode)) {
ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(mode) + " || " + nameof(botNameTo));
Expand Down Expand Up @@ -5085,29 +4898,6 @@ private static async Task<string> ResponseUnpackBoosters(ulong steamID, string b
return responses.Count > 0 ? string.Join(Environment.NewLine, responses) : null;
}

private static async Task<string> ResponseUpdate(ulong steamID) {
if (steamID == 0) {
ASF.ArchiLogger.LogNullError(nameof(steamID));
return null;
}

if (!IsOwner(steamID)) {
return null;
}

Version version = await ASF.CheckAndUpdateProgram(true).ConfigureAwait(false);
return FormatStaticResponse(version != null ? (version > SharedInfo.Version ? Strings.Success : Strings.Done) : Strings.WarningFailed);
}

private string ResponseVersion(ulong steamID) {
if (steamID == 0) {
ArchiLogger.LogNullError(nameof(steamID));
return null;
}

return IsOperator(steamID) ? FormatBotResponse(string.Format(Strings.BotVersion, SharedInfo.ASF, SharedInfo.Version)) : null;
}

private void SetUserInput(ASF.EUserInputType inputType, string inputValue) {
if ((inputType == ASF.EUserInputType.Unknown) || string.IsNullOrEmpty(inputValue)) {
ArchiLogger.LogNullError(nameof(inputType) + " || " + nameof(inputValue));
Expand Down Expand Up @@ -5220,4 +5010,4 @@ private enum ERedeemFlags : byte {
SkipKeepMissingGames = 128
}
}
}
}
Loading