diff --git a/ArchiSteamFarm/Bot.cs b/ArchiSteamFarm/Bot.cs index 069e05e59fba4..0c0e27ff77bc9 100755 --- a/ArchiSteamFarm/Bot.cs +++ b/ArchiSteamFarm/Bot.cs @@ -74,16 +74,16 @@ internal sealed class Bot : IDisposable { internal bool IsPlayingPossible => !PlayingBlocked && (LibraryLockedBySteamID == 0); private readonly ArchiHandler ArchiHandler; - private readonly BotDatabase BotDatabase; + internal 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 HandledGifts = new ConcurrentHashSet(); @@ -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; @@ -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; @@ -930,16 +930,8 @@ internal async Task Response(ulong steamID, string message) { return null; case 1: switch (args[0].ToUpperInvariant()) { - case "2FA": - return await Response2FA(steamID).ConfigureAwait(false); - case "2FANO": - return await Response2FAConfirm(steamID, false).ConfigureAwait(false); - case "2FAOK": - 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": @@ -952,8 +944,6 @@ internal async Task Response(ulong steamID, string message) { return await ResponseLoot(steamID).ConfigureAwait(false); case "LOOT&": return ResponseLootSwitch(steamID); - case "PASSWORD": - return ResponsePassword(steamID); case "PAUSE": return await ResponsePause(steamID, true).ConfigureAwait(false); case "PAUSE~": @@ -962,33 +952,15 @@ internal async Task 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); } default: switch (args[0].ToUpperInvariant()) { - case "2FA": - return await Response2FA(steamID, Utilities.GetArgsAsText(args, 1, ",")).ConfigureAwait(false); - case "2FANO": - return await Response2FAConfirm(steamID, Utilities.GetArgsAsText(args, 1, ","), false).ConfigureAwait(false); - case "2FAOK": - return await Response2FAConfirm(steamID, Utilities.GetArgsAsText(args, 1, ","), true).ConfigureAwait(false); case "ADDLICENSE": if (args.Length > 2) { return await ResponseAddLicense(steamID, args[1], Utilities.GetArgsAsText(args, 2, ",")).ConfigureAwait(false); @@ -1083,8 +1055,6 @@ internal async Task Response(ulong steamID, string message) { } return (await ResponseOwns(steamID, args[1]).ConfigureAwait(false)).Response; - case "PASSWORD": - return await ResponsePassword(steamID, Utilities.GetArgsAsText(args, 1, ",")).ConfigureAwait(false); case "PAUSE": return await ResponsePause(steamID, Utilities.GetArgsAsText(args, 1, ","), true).ConfigureAwait(false); case "PAUSE~": @@ -1129,10 +1099,6 @@ internal async Task 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); @@ -1143,8 +1109,6 @@ internal async Task Response(ulong steamID, string message) { } goto default; - case "UNPACK": - return await ResponseUnpackBoosters(steamID, Utilities.GetArgsAsText(args, 1, ",")).ConfigureAwait(false); default: return ResponseUnknown(steamID); } @@ -1404,38 +1368,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; @@ -1601,7 +1533,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; @@ -1619,7 +1551,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; @@ -1628,7 +1560,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; @@ -1948,7 +1880,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) { @@ -1982,7 +1920,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) { @@ -2540,108 +2483,6 @@ private void ResetPlayingWasBlockedWithTimer() { StopPlayingWasBlockedTimer(); } - private async Task Response2FA(ulong steamID) { - if (steamID == 0) { - ArchiLogger.LogNullError(nameof(steamID)); - return null; - } - - if (!IsMaster(steamID)) { - return null; - } - - if (!HasMobileAuthenticator) { - return FormatBotResponse(Strings.BotNoASFAuthenticator); - } - - string token = await BotDatabase.MobileAuthenticator.GenerateToken().ConfigureAwait(false); - return FormatBotResponse(!string.IsNullOrEmpty(token) ? string.Format(Strings.BotAuthenticatorToken, token) : Strings.WarningFailed); - } - - private static async Task Response2FA(ulong steamID, string botNames) { - if ((steamID == 0) || string.IsNullOrEmpty(botNames)) { - ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames)); - return null; - } - - HashSet bots = GetBots(botNames); - if ((bots == null) || (bots.Count == 0)) { - return IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null; - } - - IEnumerable> tasks = bots.Select(bot => bot.Response2FA(steamID)); - ICollection results; - - switch (Program.GlobalConfig.OptimizationMode) { - case GlobalConfig.EOptimizationMode.MinMemoryUsage: - results = new List(bots.Count); - foreach (Task task in tasks) { - results.Add(await task.ConfigureAwait(false)); - } - - break; - default: - results = await Task.WhenAll(tasks).ConfigureAwait(false); - break; - } - - List responses = new List(results.Where(result => !string.IsNullOrEmpty(result))); - return responses.Count > 0 ? string.Join(Environment.NewLine, responses) : null; - } - - private async Task Response2FAConfirm(ulong steamID, bool confirm) { - if (steamID == 0) { - ArchiLogger.LogNullError(nameof(steamID)); - return null; - } - - if (!IsMaster(steamID)) { - return null; - } - - if (!IsConnectedAndLoggedOn) { - return FormatBotResponse(Strings.BotNotConnected); - } - - if (!HasMobileAuthenticator) { - return FormatBotResponse(Strings.BotNoASFAuthenticator); - } - - bool result = await AcceptConfirmations(confirm).ConfigureAwait(false); - return FormatBotResponse(result ? Strings.Success : Strings.WarningFailed); - } - - private static async Task Response2FAConfirm(ulong steamID, string botNames, bool confirm) { - if ((steamID == 0) || string.IsNullOrEmpty(botNames)) { - ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames)); - return null; - } - - HashSet bots = GetBots(botNames); - if ((bots == null) || (bots.Count == 0)) { - return IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null; - } - - IEnumerable> tasks = bots.Select(bot => bot.Response2FAConfirm(steamID, confirm)); - ICollection results; - - switch (Program.GlobalConfig.OptimizationMode) { - case GlobalConfig.EOptimizationMode.MinMemoryUsage: - results = new List(bots.Count); - foreach (Task task in tasks) { - results.Add(await task.ConfigureAwait(false)); - } - - break; - default: - results = await Task.WhenAll(tasks).ConfigureAwait(false); - break; - } - - List responses = new List(results.Where(result => !string.IsNullOrEmpty(result))); - return responses.Count > 0 ? string.Join(Environment.NewLine, responses) : null; - } - private async Task ResponseAddLicense(ulong steamID, IReadOnlyCollection gameIDs) { if ((steamID == 0) || (gameIDs == null) || (gameIDs.Count == 0)) { ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(gameIDs) + " || " + nameof(gameIDs.Count)); @@ -3101,27 +2942,6 @@ private static async Task 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 ResponseFarm(ulong steamID) { if (steamID == 0) { ArchiLogger.LogNullError(nameof(steamID)); @@ -3984,55 +3804,6 @@ private static async Task ResponseOwns(ulong steamID, string botNames, s return string.Join(Environment.NewLine, validResults.Select(result => result.Response).Concat(extraResponses)); } - private string ResponsePassword(ulong steamID) { - if (steamID == 0) { - ArchiLogger.LogNullError(nameof(steamID)); - return null; - } - - if (!IsMaster(steamID)) { - return null; - } - - if (string.IsNullOrEmpty(BotConfig.SteamPassword)) { - return FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(BotConfig.SteamPassword))); - } - - string response = FormatBotResponse(string.Format(Strings.BotEncryptedPassword, CryptoHelper.ECryptoMethod.AES, CryptoHelper.Encrypt(CryptoHelper.ECryptoMethod.AES, BotConfig.SteamPassword))) + FormatBotResponse(string.Format(Strings.BotEncryptedPassword, CryptoHelper.ECryptoMethod.ProtectedDataForCurrentUser, CryptoHelper.Encrypt(CryptoHelper.ECryptoMethod.ProtectedDataForCurrentUser, BotConfig.SteamPassword))); - return response; - } - - private static async Task ResponsePassword(ulong steamID, string botNames) { - if ((steamID == 0) || string.IsNullOrEmpty(botNames)) { - ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames)); - return null; - } - - HashSet bots = GetBots(botNames); - if ((bots == null) || (bots.Count == 0)) { - return IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null; - } - - IEnumerable> tasks = bots.Select(bot => Task.Run(() => bot.ResponsePassword(steamID))); - ICollection results; - - switch (Program.GlobalConfig.OptimizationMode) { - case GlobalConfig.EOptimizationMode.MinMemoryUsage: - results = new List(bots.Count); - foreach (Task task in tasks) { - results.Add(await task.ConfigureAwait(false)); - } - - break; - default: - results = await Task.WhenAll(tasks).ConfigureAwait(false); - break; - } - - List responses = new List(results.Where(result => !string.IsNullOrEmpty(result))); - return responses.Count > 0 ? string.Join(Environment.NewLine, responses) : null; - } - private async Task ResponsePause(ulong steamID, bool sticky, string timeout = null) { if (steamID == 0) { ArchiLogger.LogNullError(nameof(steamID)); @@ -4726,135 +4497,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 ResponseStatus(ulong steamID, string botNames) { - if ((steamID == 0) || string.IsNullOrEmpty(botNames)) { - ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames)); - return null; - } - - HashSet bots = GetBots(botNames); - if ((bots == null) || (bots.Count == 0)) { - return IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null; - } - - IEnumerable> 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 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 ResponseStop(ulong steamID, string botNames) { - if ((steamID == 0) || string.IsNullOrEmpty(botNames)) { - ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames)); - return null; - } - - HashSet bots = GetBots(botNames); - if ((bots == null) || (bots.Count == 0)) { - return IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null; - } - - IEnumerable> tasks = bots.Select(bot => Task.Run(() => bot.ResponseStop(steamID))); - ICollection results; - - switch (Program.GlobalConfig.OptimizationMode) { - case GlobalConfig.EOptimizationMode.MinMemoryUsage: - results = new List(bots.Count); - foreach (Task task in tasks) { - results.Add(await task.ConfigureAwait(false)); - } - - break; - default: - results = await Task.WhenAll(tasks).ConfigureAwait(false); - break; - } - - List responses = new List(results.Where(result => !string.IsNullOrEmpty(result))); - return responses.Count > 0 ? string.Join(Environment.NewLine, responses) : null; - } - private async Task 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)); @@ -5026,88 +4668,6 @@ private string ResponseUnknown(ulong steamID) { return IsOperator(steamID) ? FormatBotResponse(Strings.UnknownCommand) : null; } - private async Task ResponseUnpackBoosters(ulong steamID) { - if (steamID == 0) { - ArchiLogger.LogNullError(nameof(steamID)); - return null; - } - - if (!IsMaster(steamID)) { - return null; - } - - if (!IsConnectedAndLoggedOn) { - return FormatBotResponse(Strings.BotNotConnected); - } - - HashSet inventory = await ArchiWebHandler.GetInventory(CachedSteamID, wantedTypes: new HashSet { Steam.Asset.EType.BoosterPack }).ConfigureAwait(false); - if ((inventory == null) || (inventory.Count == 0)) { - return FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(inventory))); - } - - // It'd make sense here to actually check return code of ArchiWebHandler.UnpackBooster(), but it lies most of the time | https://github.com/JustArchi/ArchiSteamFarm/issues/704 - // It'd also make sense to run all of this in parallel, but it seems that Steam has a lot of problems with inventory-related parallel requests | https://steamcommunity.com/groups/ascfarm/discussions/1/3559414588264550284/ - foreach (Steam.Asset item in inventory) { - await ArchiWebHandler.UnpackBooster(item.RealAppID, item.AssetID).ConfigureAwait(false); - } - - return FormatBotResponse(Strings.Done); - } - - private static async Task ResponseUnpackBoosters(ulong steamID, string botNames) { - if ((steamID == 0) || string.IsNullOrEmpty(botNames)) { - ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames)); - return null; - } - - HashSet bots = GetBots(botNames); - if ((bots == null) || (bots.Count == 0)) { - return IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null; - } - - IEnumerable> tasks = bots.Select(bot => bot.ResponseUnpackBoosters(steamID)); - ICollection results; - - switch (Program.GlobalConfig.OptimizationMode) { - case GlobalConfig.EOptimizationMode.MinMemoryUsage: - results = new List(bots.Count); - foreach (Task task in tasks) { - results.Add(await task.ConfigureAwait(false)); - } - - break; - default: - results = await Task.WhenAll(tasks).ConfigureAwait(false); - break; - } - - List responses = new List(results.Where(result => !string.IsNullOrEmpty(result))); - return responses.Count > 0 ? string.Join(Environment.NewLine, responses) : null; - } - - private static async Task 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)); @@ -5220,4 +4780,4 @@ private enum ERedeemFlags : byte { SkipKeepMissingGames = 128 } } -} \ No newline at end of file +} diff --git a/ArchiSteamFarm/Commands.cs b/ArchiSteamFarm/Commands.cs new file mode 100644 index 0000000000000..eee6627897f70 --- /dev/null +++ b/ArchiSteamFarm/Commands.cs @@ -0,0 +1,529 @@ +using ArchiSteamFarm.Json; +using ArchiSteamFarm.Localization; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace ArchiSteamFarm { + internal sealed class Commands { + private static readonly Dictionary>> CommandDictionary = new Dictionary>>() { + { "2FA", Response2FA }, + { "2FANO", Response2FANO }, + { "2FAOK", Response2FAOK }, + { "EXIT", ResponseExit }, + { "SA", ResponseSA }, + { "STATUS", ResponseStatus }, + { "STOP", ResponseStop }, + { "UNPACK", ResponseUnpack }, + { "UPDATE", ResponseUpdate }, + { "VERSION", ResponseVersion } + }; + + private static IEnumerable AllCommands; + + //Because string-operations are quite heavy we don't want to use ToLowerInvariant() each time someone wants a list of all commands + internal static IEnumerable GetAllAvailableCommands() => AllCommands ?? (AllCommands = CommandDictionary.Keys.Select(key => key.ToLowerInvariant())); + + private static string FormatBotResponse(Bot bot, string response) { + if (bot == null || string.IsNullOrEmpty(response)) { + ASF.ArchiLogger.LogNullError(nameof(bot) + " || " + nameof(response)); + return null; + } + + return "<" + bot.BotName + "> " + response; + } + + private static string FormatStaticResponse(string response) { + if (string.IsNullOrEmpty(response)) { + ASF.ArchiLogger.LogNullError(nameof(response)); + return null; + } + + return "<" + SharedInfo.ASF + "> " + response; + } + + internal static async Task Parse(Bot bot, ulong steamID, string message) { + if (bot == null || steamID == 0 || string.IsNullOrEmpty(message)) { + ASF.ArchiLogger.LogNullError(nameof(bot) + " || " + nameof(steamID) + " || " + nameof(message)); + return null; + } + + if (!string.IsNullOrEmpty(Program.GlobalConfig.CommandPrefix)) { + if (!message.StartsWith(Program.GlobalConfig.CommandPrefix, StringComparison.Ordinal)) { + return null; + } + + message = message.Substring(Program.GlobalConfig.CommandPrefix.Length); + } + + string[] messageParts = message.Split((char[])null, StringSplitOptions.RemoveEmptyEntries); + + if (messageParts.Length == 0) { + ASF.ArchiLogger.LogNullError(nameof(messageParts)); + return null; + } + + string command = messageParts[0]; + string[] arguments = new string[messageParts.Length - 1]; + Array.Copy(messageParts, 1, arguments, 0, arguments.Length); + + if (CommandDictionary.TryGetValue(command.ToUpperInvariant(), out Func> func)) { + return await func(bot, steamID, arguments).ConfigureAwait(false); + } + + return null; + } + + private static async Task Response2FA(Bot bot, ulong steamID, string[] args) { + if (bot == null || steamID == 0 || args == null) { + ASF.ArchiLogger.LogNullError(nameof(bot) + " || " + nameof(steamID) + " || " + nameof(args)); + return null; + } + + if (args.Length == 0) { + return await Response2FA(bot, steamID).ConfigureAwait(false); + } + + string botNames = Utilities.GetArgsAsText(args, 0, ","); + HashSet bots = Bot.GetBots(botNames); + if (bots == null || bots.Count == 0) { + return Bot.IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null; + } + + IEnumerable> tasks = bots.Select(singleBot => Response2FA(singleBot, steamID)); + ICollection results; + + switch (Program.GlobalConfig.OptimizationMode) { + case GlobalConfig.EOptimizationMode.MinMemoryUsage: + results = new List(bots.Count); + foreach(Task currentTask in tasks) { + results.Add(await currentTask.ConfigureAwait(false)); + } + + break; + default: + results = await Task.WhenAll(tasks).ConfigureAwait(false); + break; + } + + List responses = new List(results.Where(result => !string.IsNullOrEmpty(result))); + if (responses.Count > 0) { + return string.Join(Environment.NewLine, responses); + } + + return null; + } + + private static async Task Response2FA(Bot bot, ulong steamID) { + if (bot == null || steamID == 0) { + ASF.ArchiLogger.LogNullError(nameof(bot) + " || " + nameof(steamID)); + return null; + } + + if (!bot.IsMaster(steamID)) { + return null; + } + + if (!bot.HasMobileAuthenticator) { + return FormatBotResponse(bot, Strings.BotNoASFAuthenticator); + } + + string token = await bot.BotDatabase.MobileAuthenticator.GenerateToken().ConfigureAwait(false); + return FormatBotResponse(bot, !string.IsNullOrEmpty(token) ? string.Format(Strings.BotAuthenticatorToken, token) : Strings.WarningFailed); + } + + private static async Task Response2FANO(Bot bot, ulong steamID, string[] args) => await Response2FAConfirm(bot, steamID, args, false).ConfigureAwait(false); + + private static async Task Response2FAOK(Bot bot, ulong steamID, string[] args) => await Response2FAConfirm(bot, steamID, args, true).ConfigureAwait(false); + + private static async Task Response2FAConfirm(Bot bot, ulong steamID, string[] args, bool confirm) { + if (bot == null || steamID == 0 || args == null) { + ASF.ArchiLogger.LogNullError(nameof(bot) + " || " + nameof(steamID) + " || " + nameof(args)); + return null; + } + + if (args.Length == 0) { + return await Response2FAConfirm(bot, steamID, confirm).ConfigureAwait(false); + } + + string botNames = Utilities.GetArgsAsText(args, 0, ","); + HashSet bots = Bot.GetBots(botNames); + if (bots == null || bots.Count == 0) { + return Bot.IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null; + } + + IEnumerable> tasks = bots.Select(singleBot => Response2FAConfirm(singleBot, steamID, confirm)); + ICollection results; + + switch (Program.GlobalConfig.OptimizationMode) { + case GlobalConfig.EOptimizationMode.MinMemoryUsage: + results = new List(bots.Count); + foreach (Task currentTask in tasks) { + results.Add(await currentTask.ConfigureAwait(false)); + } + + break; + default: + results = await Task.WhenAll(tasks).ConfigureAwait(false); + break; + } + + List responses = new List(results.Where(result => !string.IsNullOrEmpty(result))); + if (responses.Count > 0) { + return string.Join(Environment.NewLine, responses); + } + + return null; + } + + private static async Task Response2FAConfirm(Bot bot, ulong steamID, bool confirm) { + if (bot == null || steamID == 0) { + ASF.ArchiLogger.LogNullError(nameof(bot) + " || " + nameof(steamID)); + return null; + } + + if (!bot.IsMaster(steamID)) { + return null; + } + + if (!bot.IsConnectedAndLoggedOn) { + return FormatBotResponse(bot, Strings.BotNotConnected); + } + + if (!bot.HasMobileAuthenticator) { + return FormatBotResponse(bot, Strings.BotNoASFAuthenticator); + } + + if (await bot.AcceptConfirmations(confirm).ConfigureAwait(false)) { + return FormatBotResponse(bot, Strings.Success); + } + + return FormatBotResponse(bot, Strings.WarningFailed); + } + + private static async Task ResponseExit(Bot bot, ulong steamID, string[] args) { + if (steamID == 0) { + ASF.ArchiLogger.LogNullError(nameof(steamID)); + return null; + } + + if (!Bot.IsOwner(steamID)) { + return null; + } + + Utilities.InBackground( + async () => { + await Task.Delay(1000).ConfigureAwait(false); + await Program.Exit().ConfigureAwait(false); + } + ); + + return FormatStaticResponse(Strings.Done); + } + + private static async Task ResponsePassword(Bot bot, ulong steamID, string[] args) { + if(bot == null || steamID == 0 || args == null) { + ASF.ArchiLogger.LogNullError(nameof(bot) + " || " + nameof(steamID) + " || " + nameof(args)); + return null; + } + + if (args.Length == 0) { + return ResponsePassword(bot, steamID); + } + + string botNames = Utilities.GetArgsAsText(args, 0, ","); + HashSet bots = Bot.GetBots(botNames); + if(bots == null || bots.Count == 0) { + return Bot.IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null; + } + + IEnumerable> tasks = bots.Select(singleBot => Task.Run(() => ResponsePassword(singleBot, steamID))); + ICollection results; + + switch (Program.GlobalConfig.OptimizationMode) { + case GlobalConfig.EOptimizationMode.MinMemoryUsage: + results = new List(bots.Count); + foreach(Task currentTask in tasks) { + results.Add(await currentTask.ConfigureAwait(false)); + } + + break; + default: + results = await Task.WhenAll(tasks).ConfigureAwait(false); + break; + } + + List responses = new List(results.Where(result => !string.IsNullOrEmpty(result))); + if(responses.Count > 0) { + return string.Join(Environment.NewLine, responses); + } + + return null; + } + + private static string ResponsePassword(Bot bot, ulong steamID) { + if (bot == null || steamID == 0) { + ASF.ArchiLogger.LogNullError(nameof(bot) + " || " + nameof(steamID)); + return null; + } + + if (!bot.IsMaster(steamID)) { + return null; + } + + if (string.IsNullOrEmpty(bot.BotConfig.SteamPassword)) { + return FormatBotResponse(bot, string.Format(Strings.ErrorIsEmpty, nameof(BotConfig.SteamPassword))); + } + + return FormatBotResponse(bot, string.Format(Strings.BotEncryptedPassword, CryptoHelper.ECryptoMethod.AES, CryptoHelper.Encrypt(CryptoHelper.ECryptoMethod.AES, bot.BotConfig.SteamPassword))) + FormatBotResponse(bot, string.Format(Strings.BotEncryptedPassword, CryptoHelper.ECryptoMethod.ProtectedDataForCurrentUser, CryptoHelper.Encrypt(CryptoHelper.ECryptoMethod.ProtectedDataForCurrentUser, bot.BotConfig.SteamPassword))); + } + + private static async Task ResponseSA(Bot bot, ulong steamID, string[] args) => await ResponseStatus(bot, steamID, new string[] { SharedInfo.ASF }).ConfigureAwait(false); + + private static async Task ResponseStatus(Bot bot, ulong steamID, string[] args) { + if (bot == null || steamID == 0 || args == null) { + ASF.ArchiLogger.LogNullError(nameof(bot) + " || " + nameof(steamID) + " || " + nameof(args)); + return null; + } + + if (args.Length == 0) { + return ResponseStatus(bot, steamID); + } + + string botNames = Utilities.GetArgsAsText(args, 0, ","); + HashSet bots = Bot.GetBots(botNames); + if (bots == null || bots.Count == 0) { + return Bot.IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null; + } + + IEnumerable<(Bot Bot, Task Task)> tasks = bots.Select(singleBot => (singleBot, Task.Run(() => ResponseStatus(singleBot, steamID)))); + ICollection<(Bot Bot, string Response)> results; + + switch (Program.GlobalConfig.OptimizationMode) { + case GlobalConfig.EOptimizationMode.MinMemoryUsage: + results = new List<(Bot Bot, string Response)>(bots.Count); + foreach ((Bot currentBot, Task currentTask) in tasks) { + results.Add((currentBot, await currentTask.ConfigureAwait(false))); + } + + break; + default: + results = await Task.WhenAll(tasks.Select(async task => (task.Bot, await task.Task.ConfigureAwait(false)))).ConfigureAwait(false); + break; + } + + List<(Bot Bot, string Response)> validResults = new List<(Bot Bot, string Response)>(results.Where(result => !string.IsNullOrEmpty(result.Response))); + if (validResults.Count == 0) { + return null; + } + + HashSet botsRunning = validResults.Where(result => result.Bot.KeepRunning).Select(result => result.Bot).ToHashSet(); + + string extraResponse = string.Format(Strings.BotStatusOverview, botsRunning.Count, validResults.Count, botsRunning.Sum(singleBot => singleBot.CardsFarmer.GamesToFarm.Count), botsRunning.Sum(singleBot => singleBot.CardsFarmer.GamesToFarm.Sum(game => game.CardsRemaining))); + return string.Join(Environment.NewLine, validResults.Select(result => result.Response).Union(extraResponse.ToEnumerable())); + } + + private static string ResponseStatus(Bot bot, ulong steamID) { + if (bot == null || steamID == 0) { + ASF.ArchiLogger.LogNullError(nameof(bot) + " || " + nameof(steamID)); + return null; + } + + if (!bot.IsFamilySharing(steamID)) { + return null; + } + + if (!bot.IsConnectedAndLoggedOn) { + return FormatBotResponse(bot, bot.KeepRunning ? Strings.BotStatusConnecting : Strings.BotStatusNotRunning); + } + + if (bot.PlayingBlocked) { + return FormatBotResponse(bot, Strings.BotStatusPlayingNotAvailable); + } + + if (bot.CardsFarmer.Paused) { + return FormatBotResponse(bot, Strings.BotStatusPaused); + } + + if (bot.IsAccountLimited) { + return FormatBotResponse(bot, Strings.BotStatusLimited); + } + + if (bot.IsAccountLocked) { + return FormatBotResponse(bot, Strings.BotStatusLocked); + } + + if (!bot.CardsFarmer.NowFarming || bot.CardsFarmer.CurrentGamesFarming.Count == 0) { + return FormatBotResponse(bot, Strings.BotStatusNotIdling); + } + + if (bot.CardsFarmer.CurrentGamesFarming.Count > 1) { + return FormatBotResponse(bot, string.Format(Strings.BotStatusIdlingList, string.Join(", ", bot.CardsFarmer.CurrentGamesFarming.Select(game => game.AppID + " (" + game.GameName + ")")), bot.CardsFarmer.GamesToFarm.Count, bot.CardsFarmer.GamesToFarm.Sum(game => game.CardsRemaining), bot.CardsFarmer.TimeRemaining.ToHumanReadable())); + } + + CardsFarmer.Game soloGame = bot.CardsFarmer.CurrentGamesFarming.First(); + return FormatBotResponse(bot, string.Format(Strings.BotStatusIdling, soloGame.AppID, soloGame.GameName, soloGame.CardsRemaining, bot.CardsFarmer.GamesToFarm.Count, bot.CardsFarmer.GamesToFarm.Sum(game => game.CardsRemaining), bot.CardsFarmer.TimeRemaining.ToHumanReadable())); + } + + private static async Task ResponseStop(Bot bot, ulong steamID, string[] args) { + if (bot == null || steamID == 0 || args == null) { + ASF.ArchiLogger.LogNullError(nameof(bot) + " || " + nameof(steamID) + " || " + nameof(args)); + return null; + } + + if (args.Length == 0) { + return ResponseStop(bot, steamID); + } + + string botNames = Utilities.GetArgsAsText(args, 0, ","); + HashSet bots = Bot.GetBots(botNames); + if (bots == null || bots.Count == 0) { + return Bot.IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null; + } + + IEnumerable> tasks = bots.Select(singleBot => Task.Run(() => ResponseStop(singleBot, steamID))); + ICollection results; + + switch (Program.GlobalConfig.OptimizationMode) { + case GlobalConfig.EOptimizationMode.MinMemoryUsage: + results = new List(bots.Count); + foreach (Task task in tasks) { + results.Add(await task.ConfigureAwait(false)); + } + + break; + default: + results = await Task.WhenAll(tasks).ConfigureAwait(false); + break; + } + + List responses = new List(results.Where(result => !string.IsNullOrEmpty(result))); + if (responses.Count > 0) { + return string.Join(Environment.NewLine, responses); + } + + return null; + } + + private static string ResponseStop(Bot bot, ulong steamID) { + if (bot == null || steamID == 0) { + ASF.ArchiLogger.LogNullError(nameof(bot) + " || " + nameof(steamID)); + return null; + } + + if (!bot.IsMaster(steamID)) { + return null; + } + + if (!bot.KeepRunning) { + return FormatBotResponse(bot, Strings.BotAlreadyStopped); + } + + bot.Stop(); + return FormatBotResponse(bot, Strings.Done); + } + + private static async Task ResponseUnpack(Bot bot, ulong steamID, string[] args) { + if (bot == null || steamID == 0 || args == null) { + ASF.ArchiLogger.LogNullError(nameof(bot) + " || " + nameof(steamID) + " || " + nameof(args)); + return null; + } + + if (args.Length == 0) { + return await ResponseUnpack(bot, steamID).ConfigureAwait(false); + } + + string botNames = Utilities.GetArgsAsText(args, 0, ","); + HashSet bots = Bot.GetBots(botNames); + if ((bots == null) || (bots.Count == 0)) { + return Bot.IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null; + } + + IEnumerable> tasks = bots.Select(singleBot => ResponseUnpack(singleBot, steamID)); + ICollection results; + + switch (Program.GlobalConfig.OptimizationMode) { + case GlobalConfig.EOptimizationMode.MinMemoryUsage: + results = new List(bots.Count); + foreach (Task task in tasks) { + results.Add(await task.ConfigureAwait(false)); + } + + break; + default: + results = await Task.WhenAll(tasks).ConfigureAwait(false); + break; + } + + List responses = new List(results.Where(result => !string.IsNullOrEmpty(result))); + if(responses.Count > 0) { + return string.Join(Environment.NewLine, responses); + } + + return null; + } + + private static async Task ResponseUnpack(Bot bot, ulong steamID) { + if (bot == null || steamID == 0) { + ASF.ArchiLogger.LogNullError(nameof(bot) + " || " + nameof(steamID)); + return null; + } + + if (!bot.IsMaster(steamID)) { + return null; + } + + if (!bot.IsConnectedAndLoggedOn) { + return FormatBotResponse(bot, Strings.BotNotConnected); + } + + HashSet inventory = await bot.ArchiWebHandler.GetInventory(bot.CachedSteamID, wantedTypes: new HashSet { Steam.Asset.EType.BoosterPack }).ConfigureAwait(false); + if (inventory == null || inventory.Count == 0) { + return FormatBotResponse(bot, string.Format(Strings.ErrorIsEmpty, nameof(inventory))); + } + + // It'd make sense here to actually check return code of ArchiWebHandler.UnpackBooster(), but it lies most of the time | https://github.com/JustArchi/ArchiSteamFarm/issues/704 + // It'd also make sense to run all of this in parallel, but it seems that Steam has a lot of problems with inventory-related parallel requests | https://steamcommunity.com/groups/ascfarm/discussions/1/3559414588264550284/ + foreach (Steam.Asset item in inventory) { + await bot.ArchiWebHandler.UnpackBooster(item.RealAppID, item.AssetID).ConfigureAwait(false); + } + + return FormatBotResponse(bot, Strings.Done); + } + + private static async Task ResponseUpdate(Bot bot, ulong steamID, string[] args) { + if (steamID == 0) { + ASF.ArchiLogger.LogNullError(nameof(steamID)); + return null; + } + + if (!Bot.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 static async Task ResponseVersion(Bot bot, ulong steamID, string[] args) { + if (bot == null || steamID == 0) { + ASF.ArchiLogger.LogNullError(nameof(bot) + " || " + nameof(steamID)); + return null; + } + + if (bot.IsOperator(steamID)) { + return FormatBotResponse(bot, string.Format(Strings.BotVersion, SharedInfo.ASF, SharedInfo.Version)); + } + + return null; + } + + /* + + private static async Task Response(Bot bot, ulong steamID, string[] args) { + + } + //*/ + } +} diff --git a/ArchiSteamFarm/IPC.cs b/ArchiSteamFarm/IPC.cs index 21deef35e968b..317a46c97e5e1 100644 --- a/ArchiSteamFarm/IPC.cs +++ b/ArchiSteamFarm/IPC.cs @@ -454,6 +454,8 @@ private static async Task HandleApiCommand(HttpListenerRequest request, Ht } switch (request.HttpMethod) { + case HttpMethods.Get: + return await HandleApiCommandGet(request, response, arguments, argumentsIndex).ConfigureAwait(false); case HttpMethods.Post: return await HandleApiCommandPost(request, response, arguments, argumentsIndex).ConfigureAwait(false); default: @@ -462,6 +464,26 @@ private static async Task HandleApiCommand(HttpListenerRequest request, Ht } } + private static async Task HandleApiCommandGet(HttpListenerRequest request, HttpListenerResponse response, string[] arguments, byte argumentsIndex) { + if ((request == null) || (response == null) || (arguments == null) || (argumentsIndex == 0)) { + ASF.ArchiLogger.LogNullError(nameof(request) + " || " + nameof(response) + " || " + nameof(arguments) + " || " + nameof(argumentsIndex)); + return false; + } + + IEnumerable commands = Commands.GetAllAvailableCommands(); + if(commands == null) { + ASF.ArchiLogger.LogNullError(nameof(commands)); + return false; + } + + Dictionary> content = new Dictionary>() { + { "Commands", commands } + }; + + await ResponseJsonObject(request, response, new GenericResponse>>(true, "OK", content)).ConfigureAwait(false); + return true; + } + private static async Task HandleApiCommandPost(HttpListenerRequest request, HttpListenerResponse response, string[] arguments, byte argumentsIndex) { if ((request == null) || (response == null) || (arguments == null) || (argumentsIndex == 0)) { ASF.ArchiLogger.LogNullError(nameof(request) + " || " + nameof(response) + " || " + nameof(arguments) + " || " + nameof(argumentsIndex)); @@ -488,7 +510,7 @@ private static async Task HandleApiCommandPost(HttpListenerRequest request argument = Program.GlobalConfig.CommandPrefix + argument; } - string content = await targetBot.Response(Program.GlobalConfig.SteamOwnerID, argument).ConfigureAwait(false); + string content = await Commands.Parse(targetBot, Program.GlobalConfig.SteamOwnerID, argument).ConfigureAwait(false); await ResponseJsonObject(request, response, new GenericResponse(true, "OK", content)).ConfigureAwait(false); return true; @@ -1283,4 +1305,4 @@ internal TypeProperties(string baseType = null, HashSet customAttributes } } } -} \ No newline at end of file +}