diff --git a/ArchiSteamFarm.sln.DotSettings b/ArchiSteamFarm.sln.DotSettings index 7880f129f..1f0b89f02 100644 --- a/ArchiSteamFarm.sln.DotSettings +++ b/ArchiSteamFarm.sln.DotSettings @@ -79,6 +79,7 @@ WARNING WARNING SUGGESTION + HINT SUGGESTION WARNING diff --git a/ArchiSteamFarm/ASF.cs b/ArchiSteamFarm/ASF.cs index 2a0e37261..f6ac382a0 100644 --- a/ArchiSteamFarm/ASF.cs +++ b/ArchiSteamFarm/ASF.cs @@ -227,6 +227,15 @@ namespace ArchiSteamFarm { FileSystemWatcher.EnableRaisingEvents = true; } + internal static bool IsOwner(ulong steamID) { + if (steamID == 0) { + ArchiLogger.LogNullError(nameof(steamID)); + return false; + } + + return (steamID == Program.GlobalConfig.SteamOwnerID) || (Debugging.IsDebugBuild && (steamID == SharedInfo.ArchiSteamID)); + } + private static bool IsValidBotName(string botName) { if (string.IsNullOrEmpty(botName)) { ArchiLogger.LogNullError(nameof(botName)); diff --git a/ArchiSteamFarm/Actions.cs b/ArchiSteamFarm/Actions.cs new file mode 100644 index 000000000..3165946ce --- /dev/null +++ b/ArchiSteamFarm/Actions.cs @@ -0,0 +1,247 @@ +// _ _ _ ____ _ _____ +// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___ +// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \ +// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | +// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| +// +// Copyright 2015-2018 Łukasz "JustArchi" Domeradzki +// Contact: JustArchi@JustArchi.net +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using ArchiSteamFarm.Collections; +using ArchiSteamFarm.Json; +using ArchiSteamFarm.Localization; +using SteamKit2; + +namespace ArchiSteamFarm { + internal sealed class Actions : IDisposable { + private static readonly SemaphoreSlim GiftCardsSemaphore = new SemaphoreSlim(1, 1); + private static readonly SemaphoreSlim GiftsSemaphore = new SemaphoreSlim(1, 1); + + private readonly Bot Bot; + private readonly ConcurrentHashSet HandledGifts = new ConcurrentHashSet(); + private readonly SemaphoreSlim LootingSemaphore = new SemaphoreSlim(1, 1); + + private bool LootingAllowed = true; + private bool LootingScheduled; + private bool ProcessingGiftsScheduled; + + internal Actions(Bot bot) => Bot = bot ?? throw new ArgumentNullException(nameof(bot)); + + public void Dispose() => LootingSemaphore.Dispose(); + + internal async Task AcceptConfirmations(bool accept, Steam.ConfirmationDetails.EType acceptedType = Steam.ConfirmationDetails.EType.Unknown, ulong acceptedSteamID = 0, IReadOnlyCollection acceptedTradeIDs = null) { + if (!Bot.HasMobileAuthenticator) { + return false; + } + + HashSet confirmations = await Bot.BotDatabase.MobileAuthenticator.GetConfirmations(acceptedType).ConfigureAwait(false); + if ((confirmations == null) || (confirmations.Count == 0)) { + return true; + } + + if ((acceptedSteamID == 0) && ((acceptedTradeIDs == null) || (acceptedTradeIDs.Count == 0))) { + return await Bot.BotDatabase.MobileAuthenticator.HandleConfirmations(confirmations, accept).ConfigureAwait(false); + } + + IEnumerable> tasks = confirmations.Select(Bot.BotDatabase.MobileAuthenticator.GetConfirmationDetails); + ICollection results; + + switch (Program.GlobalConfig.OptimizationMode) { + case GlobalConfig.EOptimizationMode.MinMemoryUsage: + results = new List(confirmations.Count); + foreach (Task task in tasks) { + results.Add(await task.ConfigureAwait(false)); + } + + break; + default: + results = await Task.WhenAll(tasks).ConfigureAwait(false); + break; + } + + foreach (MobileAuthenticator.Confirmation confirmation in results.Where(details => (details != null) && ((acceptedType != details.Type) || ((acceptedSteamID != 0) && (details.OtherSteamID64 != 0) && (acceptedSteamID != details.OtherSteamID64)) || ((acceptedTradeIDs != null) && (details.TradeOfferID != 0) && !acceptedTradeIDs.Contains(details.TradeOfferID)))).Select(details => details.Confirmation)) { + confirmations.Remove(confirmation); + if (confirmations.Count == 0) { + return true; + } + } + + return await Bot.BotDatabase.MobileAuthenticator.HandleConfirmations(confirmations, accept).ConfigureAwait(false); + } + + internal async Task AcceptDigitalGiftCards() { + lock (GiftCardsSemaphore) { + if (ProcessingGiftsScheduled) { + return; + } + + ProcessingGiftsScheduled = true; + } + + await GiftCardsSemaphore.WaitAsync().ConfigureAwait(false); + + try { + lock (GiftCardsSemaphore) { + ProcessingGiftsScheduled = false; + } + + HashSet giftCardIDs = await Bot.ArchiWebHandler.GetDigitalGiftCards().ConfigureAwait(false); + if ((giftCardIDs == null) || (giftCardIDs.Count == 0)) { + return; + } + + foreach (ulong giftCardID in giftCardIDs.Where(gid => !HandledGifts.Contains(gid))) { + HandledGifts.Add(giftCardID); + + Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.BotAcceptingGift, giftCardID)); + await LimitGiftsRequestsAsync().ConfigureAwait(false); + + bool result = await Bot.ArchiWebHandler.AcceptDigitalGiftCard(giftCardID).ConfigureAwait(false); + if (result) { + Bot.ArchiLogger.LogGenericInfo(Strings.Success); + } else { + Bot.ArchiLogger.LogGenericWarning(Strings.WarningFailed); + } + } + } finally { + GiftCardsSemaphore.Release(); + } + } + + internal async Task AcceptGuestPasses(IReadOnlyCollection guestPassIDs) { + if ((guestPassIDs == null) || (guestPassIDs.Count == 0)) { + Bot.ArchiLogger.LogNullError(nameof(guestPassIDs)); + return; + } + + foreach (ulong guestPassID in guestPassIDs.Where(guestPassID => !HandledGifts.Contains(guestPassID))) { + HandledGifts.Add(guestPassID); + + Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.BotAcceptingGift, guestPassID)); + await LimitGiftsRequestsAsync().ConfigureAwait(false); + + ArchiHandler.RedeemGuestPassResponseCallback response = await Bot.ArchiHandler.RedeemGuestPass(guestPassID).ConfigureAwait(false); + if (response != null) { + if (response.Result == EResult.OK) { + Bot.ArchiLogger.LogGenericInfo(Strings.Success); + } else { + Bot.ArchiLogger.LogGenericWarning(string.Format(Strings.WarningFailedWithError, response.Result)); + } + } else { + Bot.ArchiLogger.LogGenericWarning(Strings.WarningFailed); + } + } + } + + internal async Task<(bool Success, string Output)> Loot(uint appID = Steam.Asset.SteamAppID, byte contextID = Steam.Asset.SteamCommunityContextID, ulong targetSteamID = 0, IReadOnlyCollection wantedTypes = null, IReadOnlyCollection wantedRealAppIDs = null) { + if ((appID == 0) || (contextID == 0)) { + Bot.ArchiLogger.LogNullError(nameof(appID) + " || " + nameof(contextID)); + return (false, null); + } + + if (!Bot.IsConnectedAndLoggedOn) { + return (false, Strings.BotNotConnected); + } + + if (!LootingAllowed) { + return (false, Strings.BotLootingTemporarilyDisabled); + } + + if (Bot.BotConfig.LootableTypes.Count == 0) { + return (false, Strings.BotLootingNoLootableTypes); + } + + if (targetSteamID == 0) { + targetSteamID = GetFirstSteamMasterID(); + + if (targetSteamID == 0) { + return (false, Strings.BotLootingMasterNotDefined); + } + } + + if (targetSteamID == Bot.CachedSteamID) { + return (false, Strings.BotSendingTradeToYourself); + } + + lock (LootingSemaphore) { + if (LootingScheduled) { + return (false, Strings.BotLootingTemporarilyDisabled); + } + + LootingScheduled = true; + } + + await LootingSemaphore.WaitAsync().ConfigureAwait(false); + + try { + lock (LootingSemaphore) { + LootingScheduled = false; + } + + HashSet inventory = await Bot.ArchiWebHandler.GetInventory(Bot.CachedSteamID, appID, contextID, true, wantedTypes, wantedRealAppIDs).ConfigureAwait(false); + if ((inventory == null) || (inventory.Count == 0)) { + return (false, string.Format(Strings.ErrorIsEmpty, nameof(inventory))); + } + + if (!await Bot.ArchiWebHandler.MarkSentTrades().ConfigureAwait(false) || !await Bot.ArchiWebHandler.SendTradeOffer(targetSteamID, inventory, Bot.BotConfig.SteamTradeToken).ConfigureAwait(false)) { + return (false, Strings.BotLootingFailed); + } + + if (Bot.HasMobileAuthenticator) { + // Give Steam network some time to generate confirmations + await Task.Delay(3000).ConfigureAwait(false); + if (!await AcceptConfirmations(true, Steam.ConfirmationDetails.EType.Trade, targetSteamID).ConfigureAwait(false)) { + return (false, Strings.BotLootingFailed); + } + } + } finally { + LootingSemaphore.Release(); + } + + return (true, Strings.BotLootingSuccess); + } + + internal void OnDisconnected() => HandledGifts.Clear(); + + internal async Task RedeemKey(string key) { + await LimitGiftsRequestsAsync().ConfigureAwait(false); + + return await Bot.ArchiHandler.RedeemKey(key).ConfigureAwait(false); + } + + internal bool SwitchLootingAllowed() => LootingAllowed = !LootingAllowed; + + private ulong GetFirstSteamMasterID() => Bot.BotConfig.SteamUserPermissions.Where(kv => (kv.Key != 0) && (kv.Value == BotConfig.EPermission.Master)).Select(kv => kv.Key).OrderByDescending(steamID => steamID != Bot.CachedSteamID).ThenBy(steamID => steamID).FirstOrDefault(); + + private static async Task LimitGiftsRequestsAsync() { + if (Program.GlobalConfig.GiftsLimiterDelay == 0) { + return; + } + + await GiftsSemaphore.WaitAsync().ConfigureAwait(false); + Utilities.InBackground( + async () => { + await Task.Delay(Program.GlobalConfig.GiftsLimiterDelay * 1000).ConfigureAwait(false); + GiftsSemaphore.Release(); + } + ); + } + } +} diff --git a/ArchiSteamFarm/Bot.cs b/ArchiSteamFarm/Bot.cs index a1b1d56d2..7c728b8c9 100755 --- a/ArchiSteamFarm/Bot.cs +++ b/ArchiSteamFarm/Bot.cs @@ -28,12 +28,10 @@ using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Security.Cryptography; -using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using ArchiSteamFarm.Collections; -using ArchiSteamFarm.Json; using ArchiSteamFarm.Localization; using ArchiSteamFarm.NLog; using Newtonsoft.Json; @@ -44,11 +42,11 @@ using SteamKit2.Unified.Internal; namespace ArchiSteamFarm { public sealed class Bot : IDisposable { internal const ushort CallbackSleep = 500; // In milliseconds + internal const byte FamilySharingInactivityMinutes = 5; internal const ushort MaxMessagePrefixLength = MaxMessageLength - ReservedMessageLength - 2; // 2 for a minimum of 2 characters (escape one and real one) internal const byte MinPlayingBlockedTTL = 60; // Delay in seconds added when account was occupied during our disconnect, to not disconnect other Steam client session too soon private const char DefaultBackgroundKeysRedeemerSeparator = '\t'; - private const byte FamilySharingInactivityMinutes = 5; private const byte LoginCooldownInMinutes = 25; // Captcha disappears after around 20 minutes, so we make it 25 private const uint LoginID = 1242; // This must be the same for all ASF bots and all ASF processes private const ushort MaxMessageLength = 5000; // This is a limitation enforced by Steam @@ -59,55 +57,51 @@ namespace ArchiSteamFarm { internal static readonly ConcurrentDictionary Bots = new ConcurrentDictionary(); private static readonly SemaphoreSlim BotsSemaphore = new SemaphoreSlim(1, 1); - private static readonly SemaphoreSlim GiftCardsSemaphore = new SemaphoreSlim(1, 1); - private static readonly SemaphoreSlim GiftsSemaphore = new SemaphoreSlim(1, 1); private static readonly SemaphoreSlim LoginSemaphore = new SemaphoreSlim(1, 1); private static SteamConfiguration SteamConfiguration; + internal readonly Actions Actions; + internal readonly ArchiHandler ArchiHandler; internal readonly ArchiLogger ArchiLogger; internal readonly ArchiWebHandler ArchiWebHandler; + internal readonly BotDatabase BotDatabase; [JsonProperty] internal readonly string BotName; + [JsonProperty] + internal readonly CardsFarmer CardsFarmer; + + internal readonly Commands Commands; internal readonly ConcurrentDictionary OwnedPackageIDs = new ConcurrentDictionary(); + internal readonly SteamApps SteamApps; + internal readonly SteamFriends SteamFriends; internal bool CanReceiveSteamCards => !IsAccountLimited && !IsAccountLocked; internal bool HasMobileAuthenticator => BotDatabase?.MobileAuthenticator != null; internal bool IsAccountLimited => AccountFlags.HasFlag(EAccountFlags.LimitedUser) || AccountFlags.HasFlag(EAccountFlags.LimitedUserForce); + internal bool IsAccountLocked => AccountFlags.HasFlag(EAccountFlags.Lockdown); internal bool IsConnectedAndLoggedOn => SteamID != 0; [JsonProperty] internal bool IsPlayingPossible => !PlayingBlocked && (LibraryLockedBySteamID == 0); - private readonly ArchiHandler ArchiHandler; - private readonly BotDatabase BotDatabase; - private readonly Dictionary CachedGamesOwned = new Dictionary(); private readonly CallbackManager CallbackManager; private readonly SemaphoreSlim CallbackSemaphore = new SemaphoreSlim(1, 1); - - [JsonProperty] - private readonly CardsFarmer CardsFarmer; - private readonly SemaphoreSlim GamesRedeemerInBackgroundSemaphore = new SemaphoreSlim(1, 1); - private readonly ConcurrentHashSet HandledGifts = new ConcurrentHashSet(); private readonly Timer HeartBeatTimer; private readonly SemaphoreSlim InitializationSemaphore = new SemaphoreSlim(1, 1); - private readonly SemaphoreSlim LootingSemaphore = new SemaphoreSlim(1, 1); private readonly SemaphoreSlim PICSSemaphore = new SemaphoreSlim(1, 1); private readonly Statistics Statistics; - private readonly SteamApps SteamApps; private readonly SteamClient SteamClient; private readonly ConcurrentHashSet SteamFamilySharingIDs = new ConcurrentHashSet(); - private readonly SteamFriends SteamFriends; private readonly SteamUser SteamUser; private readonly Trading Trading; 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); private string KeysToRedeemFilePath => BotPath + SharedInfo.KeysExtension; private string KeysToRedeemUnusedFilePath => KeysToRedeemFilePath + SharedInfo.KeysUnusedExtension; private string KeysToRedeemUsedFilePath => KeysToRedeemFilePath + SharedInfo.KeysUsedExtension; @@ -128,7 +122,9 @@ namespace ArchiSteamFarm { [JsonProperty] internal bool KeepRunning { get; private set; } + internal bool PlayingBlocked { get; private set; } internal bool PlayingWasBlocked { get; private set; } + internal bool SkipFirstShutdown { private get; set; } [JsonProperty] private EAccountFlags AccountFlags; @@ -138,7 +134,6 @@ namespace ArchiSteamFarm { [JsonProperty] private string AvatarHash; - private Timer CardsFarmerResumeTimer; private Timer ConnectionFailureTimer; private string DeviceID; private Timer FamilySharingInactivityTimer; @@ -150,15 +145,10 @@ namespace ArchiSteamFarm { private EResult LastLogOnResult; private DateTime LastLogonSessionReplaced; private ulong LibraryLockedBySteamID; - private bool LootingAllowed = true; - private bool LootingScheduled; private ulong MasterChatGroupID; - private bool PlayingBlocked; private Timer PlayingWasBlockedTimer; - private bool ProcessingGiftsScheduled; private bool ReconnectOnUserInitiated; private Timer SendItemsTimer; - private bool SkipFirstShutdown; private SteamSaleEvent SteamSaleEvent; private uint TradesCount; private string TwoFactorCode; @@ -232,8 +222,10 @@ namespace ArchiSteamFarm { CallbackManager.Subscribe(OnUserNotifications); CallbackManager.Subscribe(OnVanityURLChangedCallback); + Actions = new Actions(this); ArchiWebHandler = new ArchiWebHandler(this); CardsFarmer = new CardsFarmer(this); + Commands = new Commands(this); Trading = new Trading(this); if (!Debugging.IsDebugBuild && Program.GlobalConfig.Statistics) { @@ -252,18 +244,17 @@ namespace ArchiSteamFarm { public void Dispose() { // Those are objects that are always being created if constructor doesn't throw exception + Actions.Dispose(); CallbackSemaphore.Dispose(); + Commands.Dispose(); GamesRedeemerInBackgroundSemaphore.Dispose(); - GiftCardsSemaphore.Dispose(); InitializationSemaphore.Dispose(); - LootingSemaphore.Dispose(); PICSSemaphore.Dispose(); // Those are objects that might be null and the check should be in-place ArchiWebHandler?.Dispose(); BotDatabase?.Dispose(); CardsFarmer?.Dispose(); - CardsFarmerResumeTimer?.Dispose(); ConnectionFailureTimer?.Dispose(); FamilySharingInactivityTimer?.Dispose(); GamesRedeemerInBackgroundTimer?.Dispose(); @@ -275,85 +266,6 @@ namespace ArchiSteamFarm { Trading?.Dispose(); } - internal async Task AcceptConfirmations(bool accept, Steam.ConfirmationDetails.EType acceptedType = Steam.ConfirmationDetails.EType.Unknown, ulong acceptedSteamID = 0, IReadOnlyCollection acceptedTradeIDs = null) { - if (!HasMobileAuthenticator) { - return false; - } - - HashSet confirmations = await BotDatabase.MobileAuthenticator.GetConfirmations(acceptedType).ConfigureAwait(false); - if ((confirmations == null) || (confirmations.Count == 0)) { - return true; - } - - if ((acceptedSteamID == 0) && ((acceptedTradeIDs == null) || (acceptedTradeIDs.Count == 0))) { - return await BotDatabase.MobileAuthenticator.HandleConfirmations(confirmations, accept).ConfigureAwait(false); - } - - IEnumerable> tasks = confirmations.Select(BotDatabase.MobileAuthenticator.GetConfirmationDetails); - ICollection results; - - switch (Program.GlobalConfig.OptimizationMode) { - case GlobalConfig.EOptimizationMode.MinMemoryUsage: - results = new List(confirmations.Count); - foreach (Task task in tasks) { - results.Add(await task.ConfigureAwait(false)); - } - - break; - default: - results = await Task.WhenAll(tasks).ConfigureAwait(false); - break; - } - - foreach (MobileAuthenticator.Confirmation confirmation in results.Where(details => (details != null) && ((acceptedType != details.Type) || ((acceptedSteamID != 0) && (details.OtherSteamID64 != 0) && (acceptedSteamID != details.OtherSteamID64)) || ((acceptedTradeIDs != null) && (details.TradeOfferID != 0) && !acceptedTradeIDs.Contains(details.TradeOfferID)))).Select(details => details.Confirmation)) { - confirmations.Remove(confirmation); - if (confirmations.Count == 0) { - return true; - } - } - - return await BotDatabase.MobileAuthenticator.HandleConfirmations(confirmations, accept).ConfigureAwait(false); - } - - private async Task AcceptDigitalGiftCards() { - lock (GiftCardsSemaphore) { - if (ProcessingGiftsScheduled) { - return; - } - - ProcessingGiftsScheduled = true; - } - - await GiftCardsSemaphore.WaitAsync().ConfigureAwait(false); - - try { - lock (GiftCardsSemaphore) { - ProcessingGiftsScheduled = false; - } - - HashSet giftCardIDs = await ArchiWebHandler.GetDigitalGiftCards().ConfigureAwait(false); - if ((giftCardIDs == null) || (giftCardIDs.Count == 0)) { - return; - } - - foreach (ulong giftCardID in giftCardIDs.Where(gid => !HandledGifts.Contains(gid))) { - HandledGifts.Add(giftCardID); - - ArchiLogger.LogGenericInfo(string.Format(Strings.BotAcceptingGift, giftCardID)); - await LimitGiftsRequestsAsync().ConfigureAwait(false); - - bool result = await ArchiWebHandler.AcceptDigitalGiftCard(giftCardID).ConfigureAwait(false); - if (result) { - ArchiLogger.LogGenericInfo(Strings.Success); - } else { - ArchiLogger.LogGenericWarning(Strings.WarningFailed); - } - } - } finally { - GiftCardsSemaphore.Release(); - } - } - internal async Task DeleteAllRelatedFiles() { await BotDatabase.MakeReadOnly().ConfigureAwait(false); @@ -680,6 +592,15 @@ namespace ArchiSteamFarm { return result; } + internal BotConfig.EPermission GetSteamUserPermission(ulong steamID) { + if (steamID == 0) { + ArchiLogger.LogNullError(nameof(steamID)); + return BotConfig.EPermission.None; + } + + return BotConfig.SteamUserPermissions.TryGetValue(steamID, out BotConfig.EPermission permission) ? permission : BotConfig.EPermission.None; + } + internal async Task GetTradeHoldDuration(ulong steamID, ulong tradeID) { if ((steamID == 0) || (tradeID == 0)) { ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(tradeID)); @@ -819,13 +740,22 @@ namespace ArchiSteamFarm { return BotDatabase.IsBlacklistedFromTrades(steamID); } + internal bool IsFamilySharing(ulong steamID) { + if (steamID == 0) { + ArchiLogger.LogNullError(nameof(steamID)); + return false; + } + + return ASF.IsOwner(steamID) || SteamFamilySharingIDs.Contains(steamID) || (GetSteamUserPermission(steamID) >= BotConfig.EPermission.FamilySharing); + } + internal bool IsMaster(ulong steamID) { if (steamID == 0) { ArchiLogger.LogNullError(nameof(steamID)); return false; } - return IsOwner(steamID) || (GetSteamUserPermission(steamID) >= BotConfig.EPermission.Master); + return ASF.IsOwner(steamID) || (GetSteamUserPermission(steamID) >= BotConfig.EPermission.Master); } internal bool IsPriorityIdling(uint appID) { @@ -837,19 +767,6 @@ namespace ArchiSteamFarm { return BotDatabase.IsPriorityIdling(appID); } - internal async Task LootIfNeeded() { - if (!IsConnectedAndLoggedOn || !BotConfig.SendOnFarmingFinished) { - return; - } - - ulong steamMasterID = GetFirstSteamMasterID(); - if (steamMasterID == 0) { - return; - } - - await ResponseLoot(steamMasterID).ConfigureAwait(false); - } - internal async Task OnConfigChanged(bool deleted) { if (deleted) { Destroy(); @@ -889,7 +806,10 @@ namespace ArchiSteamFarm { if (farmedSomething || !FirstTradeSent) { FirstTradeSent = true; - await LootIfNeeded().ConfigureAwait(false); + + if (BotConfig.SendOnFarmingFinished) { + await Actions.Loot().ConfigureAwait(false); + } } if (BotConfig.ShutdownOnFarmingFinished) { @@ -995,249 +915,6 @@ namespace ArchiSteamFarm { SteamFriends.RequestFriendInfo(CachedSteamID, EClientPersonaStateFlag.PlayerName | EClientPersonaStateFlag.Presence); } - internal async Task Response(ulong steamID, string message) { - if ((steamID == 0) || string.IsNullOrEmpty(message)) { - ArchiLogger.LogNullError(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[] args = message.Split((char[]) null, StringSplitOptions.RemoveEmptyEntries); - - switch (args.Length) { - case 0: - ArchiLogger.LogNullError(nameof(args)); - 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": - return ResponseHelp(steamID); - case "IB": - return ResponseIdleBlacklist(steamID); - case "IQ": - return ResponseIdleQueue(steamID); - case "LOOT": - 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~": - return await ResponsePause(steamID, false).ConfigureAwait(false); - case "RESUME": - 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); - } - - return await ResponseAddLicense(steamID, args[1]).ConfigureAwait(false); - case "BL": - return await ResponseBlacklist(steamID, Utilities.GetArgsAsText(args, 1, ",")).ConfigureAwait(false); - case "BLADD": - if (args.Length > 2) { - return await ResponseBlacklistAdd(steamID, args[1], Utilities.GetArgsAsText(args, 2, ",")).ConfigureAwait(false); - } - - return await ResponseBlacklistAdd(steamID, args[1]).ConfigureAwait(false); - case "BLRM": - if (args.Length > 2) { - return await ResponseBlacklistRemove(steamID, args[1], Utilities.GetArgsAsText(args, 2, ",")).ConfigureAwait(false); - } - - return await ResponseBlacklistRemove(steamID, args[1]).ConfigureAwait(false); - case "FARM": - return await ResponseFarm(steamID, Utilities.GetArgsAsText(args, 1, ",")).ConfigureAwait(false); - case "INPUT": - if (args.Length > 3) { - return await ResponseInput(steamID, args[1], args[2], Utilities.GetArgsAsText(message, 3)).ConfigureAwait(false); - } - - if (args.Length > 2) { - return ResponseInput(steamID, args[1], args[2]); - } - - goto default; - case "IB": - return await ResponseIdleBlacklist(steamID, Utilities.GetArgsAsText(args, 1, ",")).ConfigureAwait(false); - case "IBADD": - if (args.Length > 2) { - return await ResponseIdleBlacklistAdd(steamID, args[1], Utilities.GetArgsAsText(args, 2, ",")).ConfigureAwait(false); - } - - return await ResponseIdleBlacklistAdd(steamID, args[1]).ConfigureAwait(false); - case "IBRM": - if (args.Length > 2) { - return await ResponseIdleBlacklistRemove(steamID, args[1], Utilities.GetArgsAsText(args, 2, ",")).ConfigureAwait(false); - } - - return await ResponseIdleBlacklistRemove(steamID, args[1]).ConfigureAwait(false); - case "IQ": - return await ResponseIdleQueue(steamID, Utilities.GetArgsAsText(args, 1, ",")).ConfigureAwait(false); - case "IQADD": - if (args.Length > 2) { - return await ResponseIdleQueueAdd(steamID, args[1], Utilities.GetArgsAsText(args, 2, ",")).ConfigureAwait(false); - } - - return await ResponseIdleQueueAdd(steamID, args[1]).ConfigureAwait(false); - case "IQRM": - if (args.Length > 2) { - return await ResponseIdleQueueRemove(steamID, args[1], Utilities.GetArgsAsText(args, 2, ",")).ConfigureAwait(false); - } - - return await ResponseIdleQueueRemove(steamID, args[1]).ConfigureAwait(false); - case "LOOT": - return await ResponseLoot(steamID, Utilities.GetArgsAsText(args, 1, ",")).ConfigureAwait(false); - case "LOOT^": - if (args.Length > 3) { - return await ResponseAdvancedLoot(steamID, args[1], args[2], Utilities.GetArgsAsText(args, 3, ",")).ConfigureAwait(false); - } - - if (args.Length > 2) { - return await ResponseAdvancedLoot(steamID, args[1], args[2]).ConfigureAwait(false); - } - - goto default; - case "LOOT@": - if (args.Length > 2) { - return await ResponseLootByRealAppIDs(steamID, args[1], Utilities.GetArgsAsText(args, 2, ",")).ConfigureAwait(false); - } - - return await ResponseLootByRealAppIDs(steamID, args[1]).ConfigureAwait(false); - case "LOOT&": - return await ResponseLootSwitch(steamID, Utilities.GetArgsAsText(args, 1, ",")).ConfigureAwait(false); - case "NICKNAME": - if (args.Length > 2) { - return await ResponseNickname(steamID, args[1], Utilities.GetArgsAsText(message, 2)).ConfigureAwait(false); - } - - return ResponseNickname(steamID, args[1]); - case "OA": - return await ResponseOwns(steamID, SharedInfo.ASF, Utilities.GetArgsAsText(message, 1)).ConfigureAwait(false); - case "OWNS": - if (args.Length > 2) { - return await ResponseOwns(steamID, args[1], Utilities.GetArgsAsText(message, 2)).ConfigureAwait(false); - } - - 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~": - return await ResponsePause(steamID, Utilities.GetArgsAsText(args, 1, ","), false).ConfigureAwait(false); - case "PAUSE&": - if (args.Length > 2) { - return await ResponsePause(steamID, args[1], true, Utilities.GetArgsAsText(args, 2, ",")).ConfigureAwait(false); - } - - return await ResponsePause(steamID, true, args[1]).ConfigureAwait(false); - case "PLAY": - if (args.Length > 2) { - return await ResponsePlay(steamID, args[1], Utilities.GetArgsAsText(args, 2, ",")).ConfigureAwait(false); - } - - return await ResponsePlay(steamID, args[1]).ConfigureAwait(false); - case "PRIVACY": - if (args.Length > 2) { - return await ResponsePrivacy(steamID, args[1], Utilities.GetArgsAsText(args, 2, ",")).ConfigureAwait(false); - } - - return await ResponsePrivacy(steamID, args[1]).ConfigureAwait(false); - case "R": - case "REDEEM": - if (args.Length > 2) { - return await ResponseRedeem(steamID, args[1], Utilities.GetArgsAsText(args, 2, ",")).ConfigureAwait(false); - } - - return await ResponseRedeem(steamID, args[1]).ConfigureAwait(false); - case "R^": - case "REDEEM^": - if (args.Length > 3) { - return await ResponseAdvancedRedeem(steamID, args[1], args[2], Utilities.GetArgsAsText(args, 3, ",")).ConfigureAwait(false); - } - - if (args.Length > 2) { - return await ResponseAdvancedRedeem(steamID, args[1], args[2]).ConfigureAwait(false); - } - - goto default; - case "RESUME": - 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); - } - - if (args.Length > 2) { - return await ResponseTransfer(steamID, args[1], args[2]).ConfigureAwait(false); - } - - goto default; - case "UNPACK": - return await ResponseUnpackBoosters(steamID, Utilities.GetArgsAsText(args, 1, ",")).ConfigureAwait(false); - default: - return ResponseUnknown(steamID); - } - } - } - internal async Task SendMessage(ulong steamID, string message) { if ((steamID == 0) || string.IsNullOrEmpty(message)) { ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(message)); @@ -1312,13 +989,87 @@ namespace ArchiSteamFarm { return true; } + internal void SetUserInput(ASF.EUserInputType inputType, string inputValue) { + if ((inputType == ASF.EUserInputType.Unknown) || string.IsNullOrEmpty(inputValue)) { + ArchiLogger.LogNullError(nameof(inputType) + " || " + nameof(inputValue)); + } + + // This switch should cover ONLY bot properties + switch (inputType) { + case ASF.EUserInputType.DeviceID: + DeviceID = inputValue; + break; + case ASF.EUserInputType.Login: + if (BotConfig != null) { + BotConfig.SteamLogin = inputValue; + } + + break; + case ASF.EUserInputType.Password: + if (BotConfig != null) { + BotConfig.SteamPassword = inputValue; + } + + break; + case ASF.EUserInputType.SteamGuard: + AuthCode = inputValue; + break; + case ASF.EUserInputType.SteamParentalPIN: + if (BotConfig != null) { + BotConfig.SteamParentalPIN = inputValue; + } + + break; + case ASF.EUserInputType.TwoFactorAuthentication: + TwoFactorCode = inputValue; + break; + default: + ASF.ArchiLogger.LogGenericError(string.Format(Strings.WarningUnknownValuePleaseReport, nameof(inputType), inputType)); + break; + } + } + + internal async Task Start() { + if (KeepRunning) { + return; + } + + KeepRunning = true; + Utilities.InBackground(HandleCallbacks, true); + ArchiLogger.LogGenericInfo(Strings.Starting); + + // Support and convert 2FA files + if (!HasMobileAuthenticator && File.Exists(MobileAuthenticatorFilePath)) { + await ImportAuthenticator(MobileAuthenticatorFilePath).ConfigureAwait(false); + } + + if (File.Exists(KeysToRedeemFilePath)) { + await ImportKeysToRedeem(KeysToRedeemFilePath).ConfigureAwait(false); + } + + await Connect().ConfigureAwait(false); + } + + internal void StartFamilySharingInactivityTimer() { + if (FamilySharingInactivityTimer != null) { + return; + } + + FamilySharingInactivityTimer = new Timer( + async e => await CheckFamilySharingInactivity().ConfigureAwait(false), + null, + TimeSpan.FromMinutes(FamilySharingInactivityMinutes), // Delay + Timeout.InfiniteTimeSpan // Period + ); + } + internal void Stop(bool skipShutdownEvent = false) { if (!KeepRunning) { return; } - ArchiLogger.LogGenericInfo(Strings.BotStopping); KeepRunning = false; + ArchiLogger.LogGenericInfo(Strings.BotStopping); if (SteamClient.IsConnected) { Disconnect(); @@ -1329,6 +1080,15 @@ namespace ArchiSteamFarm { } } + internal void StopFamilySharingInactivityTimer() { + if (FamilySharingInactivityTimer == null) { + return; + } + + FamilySharingInactivityTimer.Dispose(); + FamilySharingInactivityTimer = null; + } + internal async Task ValidateAndAddGamesToRedeemInBackground(OrderedDictionary gamesToRedeemInBackground) { if ((gamesToRedeemInBackground == null) || (gamesToRedeemInBackground.Count == 0)) { ArchiLogger.LogNullError(nameof(gamesToRedeemInBackground)); @@ -1344,7 +1104,7 @@ namespace ArchiSteamFarm { if (string.IsNullOrEmpty(key)) { invalid = true; ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorIsInvalid, nameof(key))); - } else if (!IsValidCdKey(key)) { + } else if (!Utilities.IsValidCdKey(key)) { invalid = true; ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorIsInvalid, key)); } @@ -1450,26 +1210,6 @@ namespace ArchiSteamFarm { return message.Replace("\\", "\\\\").Replace("[", "\\["); } - private string FormatBotResponse(string response) { - if (string.IsNullOrEmpty(response)) { - ASF.ArchiLogger.LogNullError(nameof(response)); - return null; - } - - return "<" + BotName + "> " + response; - } - - private static string FormatStaticResponse(string response) { - if (string.IsNullOrEmpty(response)) { - ASF.ArchiLogger.LogNullError(nameof(response)); - return null; - } - - return "<" + SharedInfo.ASF + "> " + response; - } - - private ulong GetFirstSteamMasterID() => BotConfig.SteamUserPermissions.Where(kv => (kv.Key != 0) && (kv.Value == BotConfig.EPermission.Master)).Select(kv => kv.Key).OrderByDescending(steamID => steamID != CachedSteamID).ThenBy(steamID => steamID).FirstOrDefault(); - private async Task> GetKeysFromFile(string filePath) { if (string.IsNullOrEmpty(filePath)) { ArchiLogger.LogNullError(nameof(filePath)); @@ -1511,15 +1251,6 @@ namespace ArchiSteamFarm { } } - private BotConfig.EPermission GetSteamUserPermission(ulong steamID) { - if (steamID == 0) { - ArchiLogger.LogNullError(nameof(steamID)); - return BotConfig.EPermission.None; - } - - return BotConfig.SteamUserPermissions.TryGetValue(steamID, out BotConfig.EPermission permission) ? permission : BotConfig.EPermission.None; - } - private void HandleCallbacks() { TimeSpan timeSpan = TimeSpan.FromMilliseconds(CallbackSleep); while (KeepRunning || SteamClient.IsConnected) { @@ -1547,7 +1278,7 @@ namespace ArchiSteamFarm { return; } - string response = await Response(steamID, message).ConfigureAwait(false); + string response = await Commands.Response(steamID, message).ConfigureAwait(false); // We respond with null when user is not authorized (and similar) if (string.IsNullOrEmpty(response)) { @@ -1563,7 +1294,7 @@ namespace ArchiSteamFarm { return; } - string response = await Response(steamID, message).ConfigureAwait(false); + string response = await Commands.Response(steamID, message).ConfigureAwait(false); // We respond with null when user is not authorized (and similar) if (string.IsNullOrEmpty(response)) { @@ -1696,16 +1427,13 @@ namespace ArchiSteamFarm { SendItemsTimer = null; } - if (BotConfig.SendTradePeriod > 0) { - ulong steamMasterID = GetFirstSteamMasterID(); - if (steamMasterID != 0) { - SendItemsTimer = new Timer( - async e => await ResponseLoot(steamMasterID).ConfigureAwait(false), - null, - TimeSpan.FromHours(BotConfig.SendTradePeriod) + TimeSpan.FromSeconds(Program.LoadBalancingDelay * Bots.Count), // Delay - TimeSpan.FromHours(BotConfig.SendTradePeriod) // Period - ); - } + if ((BotConfig.SendTradePeriod > 0) && BotConfig.SteamUserPermissions.Values.Any(permission => permission >= BotConfig.EPermission.Master)) { + SendItemsTimer = new Timer( + async e => await Actions.Loot().ConfigureAwait(false), + null, + TimeSpan.FromHours(BotConfig.SendTradePeriod) + TimeSpan.FromSeconds(Program.LoadBalancingDelay * Bots.Count), // Delay + TimeSpan.FromHours(BotConfig.SendTradePeriod) // Period + ); } if (SteamSaleEvent != null) { @@ -1738,15 +1466,6 @@ namespace ArchiSteamFarm { Utilities.InBackground(Start); } - private bool IsFamilySharing(ulong steamID) { - if (steamID == 0) { - ArchiLogger.LogNullError(nameof(steamID)); - return false; - } - - return IsOwner(steamID) || SteamFamilySharingIDs.Contains(steamID) || (GetSteamUserPermission(steamID) >= BotConfig.EPermission.FamilySharing); - } - private bool IsMasterClanID(ulong steamID) { if (steamID == 0) { ArchiLogger.LogNullError(nameof(steamID)); @@ -1756,24 +1475,6 @@ namespace ArchiSteamFarm { return steamID == BotConfig.SteamMasterClanID; } - private bool IsOperator(ulong steamID) { - if (steamID == 0) { - ArchiLogger.LogNullError(nameof(steamID)); - return false; - } - - return IsOwner(steamID) || (GetSteamUserPermission(steamID) >= BotConfig.EPermission.Operator); - } - - private static bool IsOwner(ulong steamID) { - if (steamID == 0) { - ASF.ArchiLogger.LogNullError(nameof(steamID)); - return false; - } - - return (steamID == Program.GlobalConfig.SteamOwnerID) || (Debugging.IsDebugBuild && (steamID == SharedInfo.ArchiSteamID)); - } - private static bool IsRefundable(EPaymentMethod method) { if (method == EPaymentMethod.None) { ASF.ArchiLogger.LogNullError(nameof(method)); @@ -1795,15 +1496,6 @@ namespace ArchiSteamFarm { } } - private static bool IsValidCdKey(string key) { - if (string.IsNullOrEmpty(key)) { - ASF.ArchiLogger.LogNullError(nameof(key)); - return false; - } - - return Regex.IsMatch(key, @"^[0-9A-Z]{4,7}-[0-9A-Z]{4,7}-[0-9A-Z]{4,7}(?:(?:-[0-9A-Z]{4,7})?(?:-[0-9A-Z]{4,7}))?$", RegexOptions.CultureInvariant | RegexOptions.IgnoreCase); - } - private async Task JoinMasterChatGroupID() { if (BotConfig.SteamMasterClanID == 0) { return; @@ -1826,20 +1518,6 @@ namespace ArchiSteamFarm { await ArchiHandler.JoinChatRoomGroup(chatGroupID).ConfigureAwait(false); } - private static async Task LimitGiftsRequestsAsync() { - if (Program.GlobalConfig.GiftsLimiterDelay == 0) { - return; - } - - await GiftsSemaphore.WaitAsync().ConfigureAwait(false); - Utilities.InBackground( - async () => { - await Task.Delay(Program.GlobalConfig.GiftsLimiterDelay * 1000).ConfigureAwait(false); - GiftsSemaphore.Release(); - } - ); - } - private static async Task LimitLoginRequestsAsync() { if (Program.GlobalConfig.LoginLimiterDelay == 0) { return; @@ -1956,12 +1634,12 @@ namespace ArchiSteamFarm { ArchiLogger.LogGenericInfo(Strings.BotDisconnected); + Actions.OnDisconnected(); ArchiWebHandler.OnDisconnected(); CardsFarmer.OnDisconnected(); Trading.OnDisconnected(); FirstTradeSent = false; - HandledGifts.Clear(); // If we initiated disconnect, do not attempt to reconnect if (callback.UserInitiated && !ReconnectOnUserInitiated) { @@ -2046,23 +1724,12 @@ namespace ArchiSteamFarm { return; } - foreach (ulong gid in callback.GuestPasses.Select(guestPass => guestPass["gid"].AsUnsignedLong()).Where(gid => (gid != 0) && !HandledGifts.Contains(gid))) { - HandledGifts.Add(gid); - - ArchiLogger.LogGenericInfo(string.Format(Strings.BotAcceptingGift, gid)); - await LimitGiftsRequestsAsync().ConfigureAwait(false); - - ArchiHandler.RedeemGuestPassResponseCallback response = await ArchiHandler.RedeemGuestPass(gid).ConfigureAwait(false); - if (response != null) { - if (response.Result == EResult.OK) { - ArchiLogger.LogGenericInfo(Strings.Success); - } else { - ArchiLogger.LogGenericWarning(string.Format(Strings.WarningFailedWithError, response.Result)); - } - } else { - ArchiLogger.LogGenericWarning(Strings.WarningFailed); - } + HashSet guestPassIDs = callback.GuestPasses.Select(guestPass => guestPass["gid"].AsUnsignedLong()).Where(gid => gid != 0).ToHashSet(); + if (guestPassIDs.Count == 0) { + return; } + + await Actions.AcceptGuestPasses(guestPassIDs).ConfigureAwait(false); } private async Task OnIncomingChatMessage(CChatRoom_IncomingChatMessage_Notification notification) { @@ -2157,11 +1824,7 @@ namespace ArchiSteamFarm { } } - lock (CachedGamesOwned) { - CachedGamesOwned.Clear(); - CachedGamesOwned.TrimExcess(); - } - + Commands.OnNewLicenseList(); OwnedPackageIDs.Clear(); bool refreshData = !BotConfig.IdleRefundableGames || BotConfig.FarmingOrders.Contains(BotConfig.EFarmingOrder.RedeemDateTimesAscending) || BotConfig.FarmingOrders.Contains(BotConfig.EFarmingOrder.RedeemDateTimesDescending); @@ -2581,7 +2244,7 @@ namespace ArchiSteamFarm { if (newGifts && BotConfig.AcceptGifts) { ArchiLogger.LogGenericTrace(nameof(ArchiHandler.UserNotificationsCallback.EUserNotification.Gifts)); - Utilities.InBackground(AcceptDigitalGiftCards); + Utilities.InBackground(Actions.AcceptDigitalGiftCards); } break; @@ -2619,9 +2282,7 @@ namespace ArchiSteamFarm { break; } - await LimitGiftsRequestsAsync().ConfigureAwait(false); - - ArchiHandler.PurchaseResponseCallback result = await ArchiHandler.RedeemKey(key).ConfigureAwait(false); + ArchiHandler.PurchaseResponseCallback result = await Actions.RedeemKey(key).ConfigureAwait(false); if (result == null) { continue; } @@ -2712,2666 +2373,6 @@ namespace ArchiSteamFarm { 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)); - return null; - } - - if (!IsOperator(steamID)) { - return null; - } - - if (!IsConnectedAndLoggedOn) { - return FormatBotResponse(Strings.BotNotConnected); - } - - StringBuilder response = new StringBuilder(); - - foreach (uint gameID in gameIDs) { - await LimitGiftsRequestsAsync().ConfigureAwait(false); - - if (await ArchiWebHandler.AddFreeLicense(gameID).ConfigureAwait(false)) { - response.AppendLine(FormatBotResponse(string.Format(Strings.BotAddLicenseWithItems, gameID, EResult.OK, "sub/" + gameID))); - continue; - } - - SteamApps.FreeLicenseCallback callback; - - try { - callback = await SteamApps.RequestFreeLicense(gameID); - } catch (Exception e) { - ArchiLogger.LogGenericWarningException(e); - response.AppendLine(FormatBotResponse(string.Format(Strings.BotAddLicense, gameID, EResult.Timeout))); - break; - } - - if (callback == null) { - response.AppendLine(FormatBotResponse(string.Format(Strings.BotAddLicense, gameID, EResult.Timeout))); - break; - } - - response.AppendLine(FormatBotResponse((callback.GrantedApps.Count > 0) || (callback.GrantedPackages.Count > 0) ? string.Format(Strings.BotAddLicenseWithItems, gameID, callback.Result, string.Join(", ", callback.GrantedApps.Select(appID => "app/" + appID).Union(callback.GrantedPackages.Select(subID => "sub/" + subID)))) : string.Format(Strings.BotAddLicense, gameID, callback.Result))); - } - - return response.Length > 0 ? response.ToString() : null; - } - - private async Task ResponseAddLicense(ulong steamID, string targetGameIDs) { - if ((steamID == 0) || string.IsNullOrEmpty(targetGameIDs)) { - ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(targetGameIDs)); - return null; - } - - if (!IsOperator(steamID)) { - return null; - } - - if (!IsConnectedAndLoggedOn) { - return FormatBotResponse(Strings.BotNotConnected); - } - - string[] gameIDs = targetGameIDs.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - - if (gameIDs.Length == 0) { - return FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(gameIDs))); - } - - HashSet gamesToRedeem = new HashSet(); - - foreach (string game in gameIDs) { - if (!uint.TryParse(game, out uint gameID) || (gameID == 0)) { - return FormatBotResponse(string.Format(Strings.ErrorParsingObject, nameof(gameID))); - } - - gamesToRedeem.Add(gameID); - } - - return await ResponseAddLicense(steamID, gamesToRedeem).ConfigureAwait(false); - } - - private static async Task ResponseAddLicense(ulong steamID, string botNames, string targetGameIDs) { - if ((steamID == 0) || string.IsNullOrEmpty(botNames) || string.IsNullOrEmpty(targetGameIDs)) { - ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames) + " || " + nameof(targetGameIDs)); - 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.ResponseAddLicense(steamID, targetGameIDs)); - 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 ResponseAdvancedLoot(ulong steamID, string targetAppID, string targetContextID) { - if ((steamID == 0) || string.IsNullOrEmpty(targetAppID) || string.IsNullOrEmpty(targetContextID)) { - ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(targetAppID) + " || " + nameof(targetContextID)); - return null; - } - - if (!IsMaster(steamID)) { - return null; - } - - if (!IsConnectedAndLoggedOn) { - return FormatBotResponse(Strings.BotNotConnected); - } - - if (!uint.TryParse(targetAppID, out uint appID) || (appID == 0)) { - return FormatBotResponse(string.Format(Strings.ErrorIsInvalid, nameof(appID))); - } - - if (!byte.TryParse(targetContextID, out byte contextID) || (contextID == 0)) { - return FormatBotResponse(string.Format(Strings.ErrorIsInvalid, nameof(contextID))); - } - - if (!LootingAllowed) { - return FormatBotResponse(Strings.BotLootingTemporarilyDisabled); - } - - if (BotConfig.LootableTypes.Count == 0) { - return FormatBotResponse(Strings.BotLootingNoLootableTypes); - } - - ulong targetSteamMasterID = GetFirstSteamMasterID(); - if (targetSteamMasterID == 0) { - return FormatBotResponse(Strings.BotLootingMasterNotDefined); - } - - if (targetSteamMasterID == CachedSteamID) { - return FormatBotResponse(Strings.BotSendingTradeToYourself); - } - - await LootingSemaphore.WaitAsync().ConfigureAwait(false); - - try { - HashSet inventory = await ArchiWebHandler.GetInventory(CachedSteamID, appID, contextID, true).ConfigureAwait(false); - if ((inventory == null) || (inventory.Count == 0)) { - return FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(inventory))); - } - - if (!await ArchiWebHandler.MarkSentTrades().ConfigureAwait(false)) { - return FormatBotResponse(Strings.BotLootingFailed); - } - - if (!await ArchiWebHandler.SendTradeOffer(targetSteamMasterID, inventory, BotConfig.SteamTradeToken).ConfigureAwait(false)) { - return FormatBotResponse(Strings.BotLootingFailed); - } - - if (HasMobileAuthenticator) { - // Give Steam network some time to generate confirmations - await Task.Delay(3000).ConfigureAwait(false); - if (!await AcceptConfirmations(true, Steam.ConfirmationDetails.EType.Trade, targetSteamMasterID).ConfigureAwait(false)) { - return FormatBotResponse(Strings.BotLootingFailed); - } - } - } finally { - LootingSemaphore.Release(); - } - - return FormatBotResponse(Strings.BotLootingSuccess); - } - - private static async Task ResponseAdvancedLoot(ulong steamID, string botNames, string appID, string contextID) { - if ((steamID == 0) || string.IsNullOrEmpty(botNames) || string.IsNullOrEmpty(appID) || string.IsNullOrEmpty(contextID)) { - ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames) + " || " + nameof(appID) + " || " + nameof(contextID)); - 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.ResponseAdvancedLoot(steamID, appID, contextID)); - 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 ResponseAdvancedRedeem(ulong steamID, string options, string keys) { - if ((steamID == 0) || string.IsNullOrEmpty(options) || string.IsNullOrEmpty(keys)) { - ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(options) + " || " + nameof(keys)); - return null; - } - - if (!IsOperator(steamID)) { - return null; - } - - string[] flags = options.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - - if (flags.Length == 0) { - return FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(flags))); - } - - ERedeemFlags redeemFlags = ERedeemFlags.None; - - foreach (string flag in flags) { - switch (flag.ToUpperInvariant()) { - case "FD": - redeemFlags |= ERedeemFlags.ForceDistributing; - break; - case "FF": - redeemFlags |= ERedeemFlags.ForceForwarding; - break; - case "FKMG": - redeemFlags |= ERedeemFlags.ForceKeepMissingGames; - break; - case "SD": - redeemFlags |= ERedeemFlags.SkipDistributing; - break; - case "SF": - redeemFlags |= ERedeemFlags.SkipForwarding; - break; - case "SI": - redeemFlags |= ERedeemFlags.SkipInitial; - break; - case "SKMG": - redeemFlags |= ERedeemFlags.SkipKeepMissingGames; - break; - case "V": - redeemFlags |= ERedeemFlags.Validate; - break; - default: - return FormatBotResponse(string.Format(Strings.ErrorIsInvalid, flag)); - } - } - - return await ResponseRedeem(steamID, keys, redeemFlags).ConfigureAwait(false); - } - - private static async Task ResponseAdvancedRedeem(ulong steamID, string botNames, string options, string keys) { - if ((steamID == 0) || string.IsNullOrEmpty(botNames) || string.IsNullOrEmpty(options) || string.IsNullOrEmpty(keys)) { - ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames) + " || " + nameof(options) + " || " + nameof(keys)); - 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.ResponseAdvancedRedeem(steamID, options, keys)); - 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 string ResponseBlacklist(ulong steamID) { - if (steamID == 0) { - ArchiLogger.LogNullError(nameof(steamID)); - return null; - } - - if (!IsMaster(steamID)) { - return null; - } - - IReadOnlyCollection blacklist = BotDatabase.GetBlacklistedFromTradesSteamIDs(); - return FormatBotResponse(blacklist.Count > 0 ? string.Join(", ", blacklist) : string.Format(Strings.ErrorIsEmpty, nameof(blacklist))); - } - - private static async Task ResponseBlacklist(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.ResponseBlacklist(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 ResponseBlacklistAdd(ulong steamID, string targetSteamIDs) { - if ((steamID == 0) || string.IsNullOrEmpty(targetSteamIDs)) { - ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(targetSteamIDs)); - return null; - } - - if (!IsMaster(steamID)) { - return null; - } - - string[] targets = targetSteamIDs.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - - if (targets.Length == 0) { - return FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(targets))); - } - - HashSet targetIDs = new HashSet(); - - foreach (string target in targets) { - if (!ulong.TryParse(target, out ulong targetID) || (targetID == 0)) { - return FormatBotResponse(string.Format(Strings.ErrorParsingObject, nameof(targetID))); - } - - targetIDs.Add(targetID); - } - - await BotDatabase.AddBlacklistedFromTradesSteamIDs(targetIDs).ConfigureAwait(false); - return FormatBotResponse(Strings.Done); - } - - private static async Task ResponseBlacklistAdd(ulong steamID, string botNames, string targetSteamIDs) { - if ((steamID == 0) || string.IsNullOrEmpty(botNames) || string.IsNullOrEmpty(targetSteamIDs)) { - ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames) + " || " + nameof(targetSteamIDs)); - 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.ResponseBlacklistAdd(steamID, targetSteamIDs)); - 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 ResponseBlacklistRemove(ulong steamID, string targetSteamIDs) { - if ((steamID == 0) || string.IsNullOrEmpty(targetSteamIDs)) { - ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(targetSteamIDs)); - return null; - } - - if (!IsMaster(steamID)) { - return null; - } - - string[] targets = targetSteamIDs.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - - if (targets.Length == 0) { - return FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(targets))); - } - - HashSet targetIDs = new HashSet(); - - foreach (string target in targets) { - if (!ulong.TryParse(target, out ulong targetID) || (targetID == 0)) { - return FormatBotResponse(string.Format(Strings.ErrorParsingObject, nameof(targetID))); - } - - targetIDs.Add(targetID); - } - - await BotDatabase.RemoveBlacklistedFromTradesSteamIDs(targetIDs).ConfigureAwait(false); - return FormatBotResponse(Strings.Done); - } - - private static async Task ResponseBlacklistRemove(ulong steamID, string botNames, string targetSteamIDs) { - if ((steamID == 0) || string.IsNullOrEmpty(botNames) || string.IsNullOrEmpty(targetSteamIDs)) { - ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames) + " || " + nameof(targetSteamIDs)); - 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.ResponseBlacklistRemove(steamID, targetSteamIDs)); - 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 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)); - return null; - } - - if (!IsMaster(steamID)) { - return null; - } - - if (!IsConnectedAndLoggedOn) { - return FormatBotResponse(Strings.BotNotConnected); - } - - if (CardsFarmer.NowFarming) { - await CardsFarmer.StopFarming().ConfigureAwait(false); - } - - Utilities.InBackground(CardsFarmer.StartFarming); - return FormatBotResponse(Strings.Done); - } - - private static async Task ResponseFarm(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.ResponseFarm(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 string ResponseHelp(ulong steamID) { - if (steamID == 0) { - ArchiLogger.LogNullError(nameof(steamID)); - return null; - } - - return IsFamilySharing(steamID) ? FormatBotResponse("https://github.com/" + SharedInfo.GithubRepo + "/wiki/Commands") : null; - } - - private string ResponseIdleBlacklist(ulong steamID) { - if (steamID == 0) { - ArchiLogger.LogNullError(nameof(steamID)); - return null; - } - - if (!IsMaster(steamID)) { - return null; - } - - IReadOnlyCollection idleBlacklist = BotDatabase.GetIdlingBlacklistedAppIDs(); - return FormatBotResponse(idleBlacklist.Count > 0 ? string.Join(", ", idleBlacklist) : string.Format(Strings.ErrorIsEmpty, nameof(idleBlacklist))); - } - - private static async Task ResponseIdleBlacklist(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.ResponseIdleBlacklist(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 ResponseIdleBlacklistAdd(ulong steamID, string targetAppIDs) { - if ((steamID == 0) || string.IsNullOrEmpty(targetAppIDs)) { - ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(targetAppIDs)); - return null; - } - - if (!IsMaster(steamID)) { - return null; - } - - string[] targets = targetAppIDs.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - - if (targets.Length == 0) { - return FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(targets))); - } - - HashSet appIDs = new HashSet(); - - foreach (string target in targets) { - if (!uint.TryParse(target, out uint appID) || (appID == 0)) { - return FormatBotResponse(string.Format(Strings.ErrorParsingObject, nameof(appID))); - } - - appIDs.Add(appID); - } - - await BotDatabase.AddIdlingBlacklistedAppIDs(appIDs).ConfigureAwait(false); - return FormatBotResponse(Strings.Done); - } - - private static async Task ResponseIdleBlacklistAdd(ulong steamID, string botNames, string targetAppIDs) { - if ((steamID == 0) || string.IsNullOrEmpty(botNames) || string.IsNullOrEmpty(targetAppIDs)) { - ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames) + " || " + nameof(targetAppIDs)); - 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.ResponseIdleBlacklistAdd(steamID, targetAppIDs)); - 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 ResponseIdleBlacklistRemove(ulong steamID, string targetAppIDs) { - if ((steamID == 0) || string.IsNullOrEmpty(targetAppIDs)) { - ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(targetAppIDs)); - return null; - } - - if (!IsMaster(steamID)) { - return null; - } - - string[] targets = targetAppIDs.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - - if (targets.Length == 0) { - return FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(targets))); - } - - HashSet appIDs = new HashSet(); - - foreach (string target in targets) { - if (!uint.TryParse(target, out uint appID) || (appID == 0)) { - return FormatBotResponse(string.Format(Strings.ErrorParsingObject, nameof(appID))); - } - - appIDs.Add(appID); - } - - await BotDatabase.RemoveIdlingBlacklistedAppIDs(appIDs).ConfigureAwait(false); - return FormatBotResponse(Strings.Done); - } - - private static async Task ResponseIdleBlacklistRemove(ulong steamID, string botNames, string targetAppIDs) { - if ((steamID == 0) || string.IsNullOrEmpty(botNames) || string.IsNullOrEmpty(targetAppIDs)) { - ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames) + " || " + nameof(targetAppIDs)); - 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.ResponseIdleBlacklistRemove(steamID, targetAppIDs)); - 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 string ResponseIdleQueue(ulong steamID) { - if (steamID == 0) { - ArchiLogger.LogNullError(nameof(steamID)); - return null; - } - - if (!IsMaster(steamID)) { - return null; - } - - IReadOnlyCollection idleQueue = BotDatabase.GetIdlingPriorityAppIDs(); - return FormatBotResponse(idleQueue.Count > 0 ? string.Join(", ", idleQueue) : string.Format(Strings.ErrorIsEmpty, nameof(idleQueue))); - } - - private static async Task ResponseIdleQueue(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.ResponseIdleQueue(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 ResponseIdleQueueAdd(ulong steamID, string targetAppIDs) { - if ((steamID == 0) || string.IsNullOrEmpty(targetAppIDs)) { - ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(targetAppIDs)); - return null; - } - - if (!IsMaster(steamID)) { - return null; - } - - string[] targets = targetAppIDs.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - - if (targets.Length == 0) { - return FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(targets))); - } - - HashSet appIDs = new HashSet(); - - foreach (string target in targets) { - if (!uint.TryParse(target, out uint appID) || (appID == 0)) { - return FormatBotResponse(string.Format(Strings.ErrorParsingObject, nameof(appID))); - } - - appIDs.Add(appID); - } - - await BotDatabase.AddIdlingPriorityAppIDs(appIDs).ConfigureAwait(false); - return FormatBotResponse(Strings.Done); - } - - private static async Task ResponseIdleQueueAdd(ulong steamID, string botNames, string targetAppIDs) { - if ((steamID == 0) || string.IsNullOrEmpty(botNames) || string.IsNullOrEmpty(targetAppIDs)) { - ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames) + " || " + nameof(targetAppIDs)); - 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.ResponseIdleQueueAdd(steamID, targetAppIDs)); - 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 ResponseIdleQueueRemove(ulong steamID, string targetAppIDs) { - if ((steamID == 0) || string.IsNullOrEmpty(targetAppIDs)) { - ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(targetAppIDs)); - return null; - } - - if (!IsMaster(steamID)) { - return null; - } - - string[] targets = targetAppIDs.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - - if (targets.Length == 0) { - return FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(targets))); - } - - HashSet appIDs = new HashSet(); - - foreach (string target in targets) { - if (!uint.TryParse(target, out uint appID) || (appID == 0)) { - return FormatBotResponse(string.Format(Strings.ErrorParsingObject, nameof(appID))); - } - - appIDs.Add(appID); - } - - await BotDatabase.RemoveIdlingPriorityAppIDs(appIDs).ConfigureAwait(false); - return FormatBotResponse(Strings.Done); - } - - private static async Task ResponseIdleQueueRemove(ulong steamID, string botNames, string targetAppIDs) { - if ((steamID == 0) || string.IsNullOrEmpty(botNames) || string.IsNullOrEmpty(targetAppIDs)) { - ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames) + " || " + nameof(targetAppIDs)); - 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.ResponseIdleQueueRemove(steamID, targetAppIDs)); - 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 string ResponseInput(ulong steamID, string propertyName, string inputValue) { - if ((steamID == 0) || string.IsNullOrEmpty(propertyName) || string.IsNullOrEmpty(inputValue)) { - ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(propertyName) + " || " + nameof(inputValue)); - return null; - } - - if (!IsMaster(steamID)) { - return null; - } - - if (!Program.GlobalConfig.Headless) { - return FormatBotResponse(Strings.ErrorFunctionOnlyInHeadlessMode); - } - - if (!Enum.TryParse(propertyName, true, out ASF.EUserInputType inputType) || (inputType == ASF.EUserInputType.Unknown) || !Enum.IsDefined(typeof(ASF.EUserInputType), inputType)) { - return FormatBotResponse(string.Format(Strings.ErrorIsInvalid, nameof(inputType))); - } - - SetUserInput(inputType, inputValue); - return FormatBotResponse(Strings.Done); - } - - private static async Task ResponseInput(ulong steamID, string botNames, string propertyName, string inputValue) { - if ((steamID == 0) || string.IsNullOrEmpty(botNames) || string.IsNullOrEmpty(propertyName) || string.IsNullOrEmpty(inputValue)) { - ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames) + " || " + nameof(propertyName) + " || " + nameof(inputValue)); - 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.ResponseInput(steamID, propertyName, inputValue))); - 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 ResponseLoot(ulong steamID) { - if (steamID == 0) { - ArchiLogger.LogNullError(nameof(steamID)); - return null; - } - - if (!IsMaster(steamID)) { - return null; - } - - if (!IsConnectedAndLoggedOn) { - return FormatBotResponse(Strings.BotNotConnected); - } - - if (!LootingAllowed) { - return FormatBotResponse(Strings.BotLootingTemporarilyDisabled); - } - - if (BotConfig.LootableTypes.Count == 0) { - return FormatBotResponse(Strings.BotLootingNoLootableTypes); - } - - ulong targetSteamMasterID = GetFirstSteamMasterID(); - if (targetSteamMasterID == 0) { - return FormatBotResponse(Strings.BotLootingMasterNotDefined); - } - - if (targetSteamMasterID == CachedSteamID) { - return FormatBotResponse(Strings.BotSendingTradeToYourself); - } - - lock (LootingSemaphore) { - if (LootingScheduled) { - return FormatBotResponse(Strings.Done); - } - - LootingScheduled = true; - } - - await LootingSemaphore.WaitAsync().ConfigureAwait(false); - - try { - lock (LootingSemaphore) { - LootingScheduled = false; - } - - HashSet inventory = await ArchiWebHandler.GetInventory(CachedSteamID, tradable: true, wantedTypes: BotConfig.LootableTypes).ConfigureAwait(false); - if ((inventory == null) || (inventory.Count == 0)) { - return FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(inventory))); - } - - if (!await ArchiWebHandler.MarkSentTrades().ConfigureAwait(false)) { - return FormatBotResponse(Strings.BotLootingFailed); - } - - if (!await ArchiWebHandler.SendTradeOffer(targetSteamMasterID, inventory, BotConfig.SteamTradeToken).ConfigureAwait(false)) { - return FormatBotResponse(Strings.BotLootingFailed); - } - - if (HasMobileAuthenticator) { - // Give Steam network some time to generate confirmations - await Task.Delay(3000).ConfigureAwait(false); - if (!await AcceptConfirmations(true, Steam.ConfirmationDetails.EType.Trade, targetSteamMasterID).ConfigureAwait(false)) { - return FormatBotResponse(Strings.BotLootingFailed); - } - } - } finally { - LootingSemaphore.Release(); - } - - return FormatBotResponse(Strings.BotLootingSuccess); - } - - private static async Task ResponseLoot(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.ResponseLoot(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 ResponseLootByRealAppIDs(ulong steamID, string realAppIDsText) { - if ((steamID == 0) || string.IsNullOrEmpty(realAppIDsText)) { - ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(realAppIDsText)); - return null; - } - - if (!IsMaster(steamID)) { - return null; - } - - if (!IsConnectedAndLoggedOn) { - return FormatBotResponse(Strings.BotNotConnected); - } - - string[] appIDTexts = realAppIDsText.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - - if (appIDTexts.Length == 0) { - return FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(appIDTexts))); - } - - HashSet realAppIDs = new HashSet(); - - foreach (string appIDText in appIDTexts) { - if (!uint.TryParse(appIDText, out uint appID) || (appID == 0)) { - return FormatBotResponse(string.Format(Strings.ErrorIsInvalid, nameof(appID))); - } - - realAppIDs.Add(appID); - } - - if (!LootingAllowed) { - return FormatBotResponse(Strings.BotLootingTemporarilyDisabled); - } - - if (BotConfig.LootableTypes.Count == 0) { - return FormatBotResponse(Strings.BotLootingNoLootableTypes); - } - - ulong targetSteamMasterID = GetFirstSteamMasterID(); - if (targetSteamMasterID == 0) { - return FormatBotResponse(Strings.BotLootingMasterNotDefined); - } - - if (targetSteamMasterID == CachedSteamID) { - return FormatBotResponse(Strings.BotSendingTradeToYourself); - } - - await LootingSemaphore.WaitAsync().ConfigureAwait(false); - - try { - HashSet inventory = await ArchiWebHandler.GetInventory(CachedSteamID, tradable: true, wantedRealAppIDs: realAppIDs).ConfigureAwait(false); - if ((inventory == null) || (inventory.Count == 0)) { - return FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(inventory))); - } - - if (!await ArchiWebHandler.MarkSentTrades().ConfigureAwait(false)) { - return FormatBotResponse(Strings.BotLootingFailed); - } - - if (!await ArchiWebHandler.SendTradeOffer(targetSteamMasterID, inventory, BotConfig.SteamTradeToken).ConfigureAwait(false)) { - return FormatBotResponse(Strings.BotLootingFailed); - } - - if (HasMobileAuthenticator) { - // Give Steam network some time to generate confirmations - await Task.Delay(3000).ConfigureAwait(false); - if (!await AcceptConfirmations(true, Steam.ConfirmationDetails.EType.Trade, targetSteamMasterID).ConfigureAwait(false)) { - return FormatBotResponse(Strings.BotLootingFailed); - } - } - } finally { - LootingSemaphore.Release(); - } - - return FormatBotResponse(Strings.BotLootingSuccess); - } - - private static async Task ResponseLootByRealAppIDs(ulong steamID, string botNames, string realAppIDsText) { - if ((steamID == 0) || string.IsNullOrEmpty(botNames) || string.IsNullOrEmpty(realAppIDsText)) { - ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames) + " || " + nameof(realAppIDsText)); - 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.ResponseLootByRealAppIDs(steamID, realAppIDsText)); - 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 string ResponseLootSwitch(ulong steamID) { - if (steamID == 0) { - ArchiLogger.LogNullError(nameof(steamID)); - return null; - } - - if (!IsMaster(steamID)) { - return null; - } - - LootingAllowed = !LootingAllowed; - return FormatBotResponse(LootingAllowed ? Strings.BotLootingNowEnabled : Strings.BotLootingNowDisabled); - } - - private static async Task ResponseLootSwitch(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.ResponseLootSwitch(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 string ResponseNickname(ulong steamID, string nickname) { - if ((steamID == 0) || string.IsNullOrEmpty(nickname)) { - ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(nickname)); - return null; - } - - if (!IsMaster(steamID)) { - return null; - } - - if (!IsConnectedAndLoggedOn) { - return FormatBotResponse(Strings.BotNotConnected); - } - - SteamFriends.SetPersonaName(nickname); - return FormatBotResponse(Strings.Done); - } - - private static async Task ResponseNickname(ulong steamID, string botNames, string nickname) { - if ((steamID == 0) || string.IsNullOrEmpty(botNames) || string.IsNullOrEmpty(nickname)) { - ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames) + " || " + nameof(nickname)); - 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.ResponseNickname(steamID, nickname))); - 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<(string Response, HashSet OwnedGameIDs)> ResponseOwns(ulong steamID, string query) { - if ((steamID == 0) || string.IsNullOrEmpty(query)) { - ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(query)); - return (null, null); - } - - if (!IsOperator(steamID)) { - return (null, null); - } - - if (!IsConnectedAndLoggedOn) { - return (FormatBotResponse(Strings.BotNotConnected), null); - } - - Dictionary ownedGames = null; - - lock (CachedGamesOwned) { - if (CachedGamesOwned.Count > 0) { - ownedGames = new Dictionary(CachedGamesOwned); - } - } - - if (ownedGames == null) { - await LimitGiftsRequestsAsync().ConfigureAwait(false); - - ownedGames = await ArchiWebHandler.HasValidApiKey().ConfigureAwait(false) ? await ArchiWebHandler.GetOwnedGames(CachedSteamID).ConfigureAwait(false) : await ArchiWebHandler.GetMyOwnedGames().ConfigureAwait(false); - if ((ownedGames == null) || (ownedGames.Count == 0)) { - return (FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(ownedGames))), null); - } - - lock (CachedGamesOwned) { - if (CachedGamesOwned.Count == 0) { - foreach (KeyValuePair ownedGame in ownedGames) { - CachedGamesOwned[ownedGame.Key] = ownedGame.Value; - } - - CachedGamesOwned.TrimExcess(); - } - } - } - - StringBuilder response = new StringBuilder(); - HashSet ownedGameIDs = new HashSet(); - - if (query.Equals("*")) { - foreach (KeyValuePair ownedGame in ownedGames) { - ownedGameIDs.Add(ownedGame.Key); - response.AppendLine(FormatBotResponse(string.Format(Strings.BotOwnedAlreadyWithName, ownedGame.Key, ownedGame.Value))); - } - } else { - string[] games = query.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - - if (games.Length == 0) { - return (FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(games))), null); - } - - foreach (string game in games) { - // Check if this is gameID - if (uint.TryParse(game, out uint gameID) && (gameID != 0)) { - if (OwnedPackageIDs.ContainsKey(gameID)) { - ownedGameIDs.Add(gameID); - response.AppendLine(FormatBotResponse(string.Format(Strings.BotOwnedAlready, gameID))); - continue; - } - - if (ownedGames.TryGetValue(gameID, out string ownedName)) { - ownedGameIDs.Add(gameID); - response.AppendLine(FormatBotResponse(string.Format(Strings.BotOwnedAlreadyWithName, gameID, ownedName))); - } else { - response.AppendLine(FormatBotResponse(string.Format(Strings.BotNotOwnedYet, gameID))); - } - - continue; - } - - // This is a string, so check our entire library - foreach (KeyValuePair ownedGame in ownedGames.Where(ownedGame => ownedGame.Value.IndexOf(game, StringComparison.OrdinalIgnoreCase) >= 0)) { - ownedGameIDs.Add(ownedGame.Key); - response.AppendLine(FormatBotResponse(string.Format(Strings.BotOwnedAlreadyWithName, ownedGame.Key, ownedGame.Value))); - } - } - } - - return (response.Length > 0 ? response.ToString() : FormatBotResponse(string.Format(Strings.BotNotOwnedYet, query)), ownedGameIDs); - } - - private static async Task ResponseOwns(ulong steamID, string botNames, string query) { - if ((steamID == 0) || string.IsNullOrEmpty(botNames) || string.IsNullOrEmpty(query)) { - ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames) + " || " + nameof(query)); - return null; - } - - HashSet bots = GetBots(botNames); - if ((bots == null) || (bots.Count == 0)) { - return IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null; - } - - IEnumerable OwnedGameIDs)>> tasks = bots.Select(bot => bot.ResponseOwns(steamID, query)); - ICollection<(string Response, HashSet OwnedGameIDs)> results; - - switch (Program.GlobalConfig.OptimizationMode) { - case GlobalConfig.EOptimizationMode.MinMemoryUsage: - results = new List<(string Response, HashSet OwnedGameIDs)>(bots.Count); - foreach (Task<(string Response, HashSet OwnedGameIDs)> task in tasks) { - results.Add(await task.ConfigureAwait(false)); - } - - break; - default: - results = await Task.WhenAll(tasks).ConfigureAwait(false); - break; - } - - List<(string Response, HashSet OwnedGameIDs)> validResults = new List<(string Response, HashSet OwnedGameIDs)>(results.Where(result => !string.IsNullOrEmpty(result.Response))); - if (validResults.Count == 0) { - return null; - } - - Dictionary ownedGameCounts = new Dictionary(); - foreach (uint gameID in validResults.Where(validResult => (validResult.OwnedGameIDs != null) && (validResult.OwnedGameIDs.Count > 0)).SelectMany(validResult => validResult.OwnedGameIDs)) { - ownedGameCounts[gameID] = ownedGameCounts.TryGetValue(gameID, out ushort count) ? ++count : (ushort) 1; - } - - IEnumerable extraResponses = ownedGameCounts.Select(kv => FormatStaticResponse(string.Format(Strings.BotOwnsOverviewPerGame, kv.Value, validResults.Count, kv.Key))); - 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, ArchiCryptoHelper.ECryptoMethod.AES, ArchiCryptoHelper.Encrypt(ArchiCryptoHelper.ECryptoMethod.AES, BotConfig.SteamPassword))) + FormatBotResponse(string.Format(Strings.BotEncryptedPassword, ArchiCryptoHelper.ECryptoMethod.ProtectedDataForCurrentUser, ArchiCryptoHelper.Encrypt(ArchiCryptoHelper.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)); - return null; - } - - if (!IsFamilySharing(steamID)) { - return null; - } - - if (sticky && !IsOperator(steamID)) { - return FormatBotResponse(Strings.ErrorAccessDenied); - } - - if (!IsConnectedAndLoggedOn) { - return FormatBotResponse(Strings.BotNotConnected); - } - - if (CardsFarmer.Paused) { - return FormatBotResponse(Strings.BotAutomaticIdlingPausedAlready); - } - - ushort resumeInSeconds = 0; - - if (sticky && !string.IsNullOrEmpty(timeout)) { - if (!ushort.TryParse(timeout, out resumeInSeconds) || (resumeInSeconds == 0)) { - return FormatBotResponse(string.Format(Strings.ErrorIsInvalid, nameof(timeout))); - } - } - - await CardsFarmer.Pause(sticky).ConfigureAwait(false); - - if (!sticky && (BotConfig.GamesPlayedWhileIdle.Count > 0)) { - // We want to let family sharing users access our library, and in this case we must also stop GamesPlayedWhileIdle - // We add extra delay because OnFarmingStopped() also executes PlayGames() - // Despite of proper order on our end, Steam network might not respect it - await Task.Delay(CallbackSleep).ConfigureAwait(false); - await ArchiHandler.PlayGames(Enumerable.Empty(), BotConfig.CustomGamePlayedWhileIdle).ConfigureAwait(false); - } - - if (resumeInSeconds > 0) { - if (CardsFarmerResumeTimer != null) { - CardsFarmerResumeTimer.Dispose(); - CardsFarmerResumeTimer = null; - } - - CardsFarmerResumeTimer = new Timer( - e => ResponseResume(steamID), - null, - TimeSpan.FromSeconds(resumeInSeconds), // Delay - Timeout.InfiniteTimeSpan // Period - ); - } - - if (IsOperator(steamID)) { - return FormatBotResponse(Strings.BotAutomaticIdlingNowPaused); - } - - StartFamilySharingInactivityTimer(); - return FormatBotResponse(string.Format(Strings.BotAutomaticIdlingPausedWithCountdown, TimeSpan.FromMinutes(FamilySharingInactivityMinutes).ToHumanReadable())); - } - - private static async Task ResponsePause(ulong steamID, string botNames, bool sticky, string timeout = null) { - 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.ResponsePause(steamID, sticky, timeout)); - 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 ResponsePlay(ulong steamID, IEnumerable gameIDs, string gameName = null) { - if ((steamID == 0) || (gameIDs == null)) { - ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(gameIDs)); - return null; - } - - if (!IsMaster(steamID)) { - return null; - } - - if (!IsConnectedAndLoggedOn) { - return FormatBotResponse(Strings.BotNotConnected); - } - - if (!CardsFarmer.Paused) { - await CardsFarmer.Pause(false).ConfigureAwait(false); - } - - await ArchiHandler.PlayGames(gameIDs, gameName).ConfigureAwait(false); - return FormatBotResponse(Strings.Done); - } - - private async Task ResponsePlay(ulong steamID, string targetGameIDs) { - if ((steamID == 0) || string.IsNullOrEmpty(targetGameIDs)) { - ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(targetGameIDs)); - return null; - } - - if (!IsMaster(steamID)) { - return null; - } - - if (!IsConnectedAndLoggedOn) { - return FormatBotResponse(Strings.BotNotConnected); - } - - string[] games = targetGameIDs.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - - if (games.Length == 0) { - return FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(games))); - } - - HashSet gamesToPlay = new HashSet(); - StringBuilder gameName = new StringBuilder(); - - foreach (string game in games) { - if (!uint.TryParse(game, out uint gameID) || (gameID == 0)) { - gameName.AppendLine((gameName.Length > 0 ? " " : "") + game); - continue; - } - - if (gamesToPlay.Count >= ArchiHandler.MaxGamesPlayedConcurrently) { - continue; - } - - gamesToPlay.Add(gameID); - } - - return await ResponsePlay(steamID, gamesToPlay, gameName.ToString()).ConfigureAwait(false); - } - - private static async Task ResponsePlay(ulong steamID, string botNames, string targetGameIDs) { - if ((steamID == 0) || string.IsNullOrEmpty(botNames) || string.IsNullOrEmpty(targetGameIDs)) { - ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames) + " || " + nameof(targetGameIDs)); - 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.ResponsePlay(steamID, targetGameIDs)); - 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 ResponsePrivacy(ulong steamID, string privacySettingsText) { - if ((steamID == 0) || string.IsNullOrEmpty(privacySettingsText)) { - ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(privacySettingsText)); - return null; - } - - if (!IsMaster(steamID)) { - return null; - } - - if (!IsConnectedAndLoggedOn) { - return FormatBotResponse(Strings.BotNotConnected); - } - - string[] privacySettingsArgs = privacySettingsText.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - - if (privacySettingsArgs.Length == 0) { - return FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(privacySettingsArgs))); - } - - // There are only 6 privacy settings - if (privacySettingsArgs.Length > 6) { - return FormatBotResponse(string.Format(Strings.ErrorIsInvalid, nameof(privacySettingsArgs))); - } - - Steam.UserPrivacy.PrivacySettings.EPrivacySetting profile = Steam.UserPrivacy.PrivacySettings.EPrivacySetting.Private; - Steam.UserPrivacy.PrivacySettings.EPrivacySetting ownedGames = Steam.UserPrivacy.PrivacySettings.EPrivacySetting.Private; - Steam.UserPrivacy.PrivacySettings.EPrivacySetting playtime = Steam.UserPrivacy.PrivacySettings.EPrivacySetting.Private; - Steam.UserPrivacy.PrivacySettings.EPrivacySetting inventory = Steam.UserPrivacy.PrivacySettings.EPrivacySetting.Private; - Steam.UserPrivacy.PrivacySettings.EPrivacySetting inventoryGifts = Steam.UserPrivacy.PrivacySettings.EPrivacySetting.Private; - Steam.UserPrivacy.ECommentPermission comments = Steam.UserPrivacy.ECommentPermission.Private; - - // Converting digits to enum - for (byte index = 0; index < privacySettingsArgs.Length; index++) { - if (!Enum.TryParse(privacySettingsArgs[index], true, out Steam.UserPrivacy.PrivacySettings.EPrivacySetting privacySetting) || (privacySetting == Steam.UserPrivacy.PrivacySettings.EPrivacySetting.Unknown) || !Enum.IsDefined(typeof(Steam.UserPrivacy.PrivacySettings.EPrivacySetting), privacySetting)) { - return FormatBotResponse(string.Format(Strings.ErrorIsInvalid, nameof(privacySettingsArgs))); - } - - // Child setting can't be less restrictive than its parent - switch (index) { - case 0: // Profile - profile = privacySetting; - break; - case 1: // OwnedGames, child of Profile - if (profile < privacySetting) { - return FormatBotResponse(string.Format(Strings.ErrorIsInvalid, nameof(ownedGames))); - } - - ownedGames = privacySetting; - break; - case 2: // Playtime, child of OwnedGames - if (ownedGames < privacySetting) { - return FormatBotResponse(string.Format(Strings.ErrorIsInvalid, nameof(playtime))); - } - - playtime = privacySetting; - break; - case 3: // Inventory, child of Profile - if (profile < privacySetting) { - return FormatBotResponse(string.Format(Strings.ErrorIsInvalid, nameof(inventory))); - } - - inventory = privacySetting; - break; - case 4: // InventoryGifts, child of Inventory - if (inventory < privacySetting) { - return FormatBotResponse(string.Format(Strings.ErrorIsInvalid, nameof(inventoryGifts))); - } - - inventoryGifts = privacySetting; - break; - case 5: // Comments, child of Profile - if (profile < privacySetting) { - return FormatBotResponse(string.Format(Strings.ErrorIsInvalid, nameof(comments))); - } - - // Comments use different numbers than everything else, but we want to have this command consistent for end-user, so we'll map them - switch (privacySetting) { - case Steam.UserPrivacy.PrivacySettings.EPrivacySetting.FriendsOnly: - comments = Steam.UserPrivacy.ECommentPermission.FriendsOnly; - break; - case Steam.UserPrivacy.PrivacySettings.EPrivacySetting.Private: - comments = Steam.UserPrivacy.ECommentPermission.Private; - break; - case Steam.UserPrivacy.PrivacySettings.EPrivacySetting.Public: - comments = Steam.UserPrivacy.ECommentPermission.Public; - break; - default: - ArchiLogger.LogGenericError(string.Format(Strings.WarningUnknownValuePleaseReport, nameof(privacySetting), privacySetting)); - return FormatBotResponse(string.Format(Strings.ErrorIsInvalid, nameof(privacySetting))); - } - - break; - default: - ArchiLogger.LogGenericError(string.Format(Strings.WarningUnknownValuePleaseReport, nameof(index), index)); - return FormatBotResponse(string.Format(Strings.ErrorIsInvalid, nameof(index))); - } - } - - Steam.UserPrivacy userPrivacy = new Steam.UserPrivacy(new Steam.UserPrivacy.PrivacySettings(profile, ownedGames, playtime, inventory, inventoryGifts), comments); - return FormatBotResponse(await ArchiWebHandler.ChangePrivacySettings(userPrivacy).ConfigureAwait(false) ? Strings.Success : Strings.WarningFailed); - } - - private async Task ResponsePrivacy(ulong steamID, string botNames, string privacySettingsText) { - if ((steamID == 0) || string.IsNullOrEmpty(botNames) || string.IsNullOrEmpty(privacySettingsText)) { - ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames) + " || " + nameof(privacySettingsText)); - 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.ResponsePrivacy(steamID, privacySettingsText)); - 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; - } - - [SuppressMessage("ReSharper", "FunctionComplexityOverflow")] - private async Task ResponseRedeem(ulong steamID, string keysText, ERedeemFlags redeemFlags = ERedeemFlags.None) { - if ((steamID == 0) || string.IsNullOrEmpty(keysText)) { - ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(keysText)); - return null; - } - - if (!IsOperator(steamID)) { - return null; - } - - if (!IsConnectedAndLoggedOn) { - return FormatBotResponse(Strings.BotNotConnected); - } - - string[] keys = keysText.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - - if (keys.Length == 0) { - return FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(keys))); - } - - bool forward = !redeemFlags.HasFlag(ERedeemFlags.SkipForwarding) && (redeemFlags.HasFlag(ERedeemFlags.ForceForwarding) || BotConfig.RedeemingPreferences.HasFlag(BotConfig.ERedeemingPreferences.Forwarding)); - bool distribute = !redeemFlags.HasFlag(ERedeemFlags.SkipDistributing) && (redeemFlags.HasFlag(ERedeemFlags.ForceDistributing) || BotConfig.RedeemingPreferences.HasFlag(BotConfig.ERedeemingPreferences.Distributing)); - bool keepMissingGames = !redeemFlags.HasFlag(ERedeemFlags.SkipKeepMissingGames) && (redeemFlags.HasFlag(ERedeemFlags.ForceKeepMissingGames) || BotConfig.RedeemingPreferences.HasFlag(BotConfig.ERedeemingPreferences.KeepMissingGames)); - - HashSet pendingKeys = keys.ToHashSet(); - HashSet unusedKeys = pendingKeys.ToHashSet(); - - StringBuilder response = new StringBuilder(); - - using (HashSet.Enumerator keysEnumerator = pendingKeys.GetEnumerator()) { - HashSet rateLimitedBots = new HashSet(); - string key = keysEnumerator.MoveNext() ? keysEnumerator.Current : null; // Initial key - - while (!string.IsNullOrEmpty(key)) { - string startingKey = key; - - using (IEnumerator botsEnumerator = Bots.Where(bot => (bot.Value != this) && !rateLimitedBots.Contains(bot.Value) && bot.Value.IsConnectedAndLoggedOn && bot.Value.IsOperator(steamID)).OrderBy(bot => bot.Key).Select(bot => bot.Value).GetEnumerator()) { - Bot currentBot = this; - - while (!string.IsNullOrEmpty(key) && (currentBot != null)) { - if (redeemFlags.HasFlag(ERedeemFlags.Validate) && !IsValidCdKey(key)) { - key = keysEnumerator.MoveNext() ? keysEnumerator.Current : null; // Next key - continue; // Keep current bot - } - - if ((currentBot == this) && (redeemFlags.HasFlag(ERedeemFlags.SkipInitial) || rateLimitedBots.Contains(currentBot))) { - currentBot = null; // Either bot will be changed, or loop aborted - } else { - await LimitGiftsRequestsAsync().ConfigureAwait(false); - - ArchiHandler.PurchaseResponseCallback result = await currentBot.ArchiHandler.RedeemKey(key).ConfigureAwait(false); - if (result == null) { - response.AppendLine(FormatBotResponse(string.Format(Strings.BotRedeem, key, EPurchaseResultDetail.Timeout), currentBot.BotName)); - currentBot = null; // Either bot will be changed, or loop aborted - } else { - if (result.PurchaseResultDetail == EPurchaseResultDetail.CannotRedeemCodeFromClient) { - // If it's a wallet code, we try to redeem it first, then handle the inner result as our primary one - (EResult Result, EPurchaseResultDetail? PurchaseResult)? walletResult = await currentBot.ArchiWebHandler.RedeemWalletKey(key).ConfigureAwait(false); - - if (walletResult != null) { - result.Result = walletResult.Value.Result; - result.PurchaseResultDetail = walletResult.Value.PurchaseResult.GetValueOrDefault(walletResult.Value.Result == EResult.OK ? EPurchaseResultDetail.NoDetail : EPurchaseResultDetail.BadActivationCode); // BadActivationCode is our smart guess in this case - } else { - result.Result = EResult.Timeout; - result.PurchaseResultDetail = EPurchaseResultDetail.Timeout; - } - } - - switch (result.PurchaseResultDetail) { - case EPurchaseResultDetail.BadActivationCode: - case EPurchaseResultDetail.CannotRedeemCodeFromClient: - case EPurchaseResultDetail.DuplicateActivationCode: - case EPurchaseResultDetail.NoDetail: // OK - case EPurchaseResultDetail.Timeout: - if ((result.Items != null) && (result.Items.Count > 0)) { - response.AppendLine(FormatBotResponse(string.Format(Strings.BotRedeemWithItems, key, result.Result + "/" + result.PurchaseResultDetail, string.Join(", ", result.Items)), currentBot.BotName)); - } else { - response.AppendLine(FormatBotResponse(string.Format(Strings.BotRedeem, key, result.Result + "/" + result.PurchaseResultDetail), currentBot.BotName)); - } - - if ((result.Result != EResult.Timeout) && (result.PurchaseResultDetail != EPurchaseResultDetail.Timeout)) { - unusedKeys.Remove(key); - } - - key = keysEnumerator.MoveNext() ? keysEnumerator.Current : null; // Next key - - if (result.PurchaseResultDetail == EPurchaseResultDetail.NoDetail) { - break; // Next bot (if needed) - } - - continue; // Keep current bot - case EPurchaseResultDetail.AccountLocked: - case EPurchaseResultDetail.AlreadyPurchased: - case EPurchaseResultDetail.DoesNotOwnRequiredApp: - case EPurchaseResultDetail.RestrictedCountry: - if ((result.Items != null) && (result.Items.Count > 0)) { - response.AppendLine(FormatBotResponse(string.Format(Strings.BotRedeemWithItems, key, result.Result + "/" + result.PurchaseResultDetail, string.Join(", ", result.Items)), currentBot.BotName)); - } else { - response.AppendLine(FormatBotResponse(string.Format(Strings.BotRedeem, key, result.Result + "/" + result.PurchaseResultDetail), currentBot.BotName)); - } - - if (!forward || (keepMissingGames && (result.PurchaseResultDetail != EPurchaseResultDetail.AlreadyPurchased))) { - key = keysEnumerator.MoveNext() ? keysEnumerator.Current : null; // Next key - break; // Next bot (if needed) - } - - if (distribute) { - break; // Next bot, without changing key - } - - Dictionary items = result.Items ?? new Dictionary(); - - bool alreadyHandled = false; - foreach (Bot innerBot in Bots.Where(bot => (bot.Value != currentBot) && (!redeemFlags.HasFlag(ERedeemFlags.SkipInitial) || (bot.Value != this)) && !rateLimitedBots.Contains(bot.Value) && bot.Value.IsConnectedAndLoggedOn && bot.Value.IsOperator(steamID) && ((items.Count == 0) || items.Keys.Any(packageID => !bot.Value.OwnedPackageIDs.ContainsKey(packageID)))).OrderBy(bot => bot.Key).Select(bot => bot.Value)) { - await LimitGiftsRequestsAsync().ConfigureAwait(false); - - ArchiHandler.PurchaseResponseCallback otherResult = await innerBot.ArchiHandler.RedeemKey(key).ConfigureAwait(false); - if (otherResult == null) { - response.AppendLine(FormatBotResponse(string.Format(Strings.BotRedeem, key, EResult.Timeout + "/" + EPurchaseResultDetail.Timeout), innerBot.BotName)); - continue; - } - - switch (otherResult.PurchaseResultDetail) { - case EPurchaseResultDetail.BadActivationCode: - case EPurchaseResultDetail.DuplicateActivationCode: - case EPurchaseResultDetail.NoDetail: // OK - alreadyHandled = true; // This key is already handled, as we either redeemed it or we're sure it's dupe/invalid - unusedKeys.Remove(key); - break; - case EPurchaseResultDetail.RateLimited: - rateLimitedBots.Add(innerBot); - break; - } - - if ((otherResult.Items != null) && (otherResult.Items.Count > 0)) { - response.AppendLine(FormatBotResponse(string.Format(Strings.BotRedeemWithItems, key, otherResult.Result + "/" + otherResult.PurchaseResultDetail, string.Join(", ", otherResult.Items)), innerBot.BotName)); - } else { - response.AppendLine(FormatBotResponse(string.Format(Strings.BotRedeem, key, otherResult.Result + "/" + otherResult.PurchaseResultDetail), innerBot.BotName)); - } - - if (alreadyHandled) { - break; - } - - if (otherResult.Items == null) { - continue; - } - - foreach (KeyValuePair item in otherResult.Items.Where(item => !items.ContainsKey(item.Key))) { - items[item.Key] = item.Value; - } - } - - key = keysEnumerator.MoveNext() ? keysEnumerator.Current : null; // Next key - break; // Next bot (if needed) - case EPurchaseResultDetail.RateLimited: - rateLimitedBots.Add(currentBot); - goto case EPurchaseResultDetail.AccountLocked; - default: - ASF.ArchiLogger.LogGenericError(string.Format(Strings.WarningUnknownValuePleaseReport, nameof(result.PurchaseResultDetail), result.PurchaseResultDetail)); - - if ((result.Items != null) && (result.Items.Count > 0)) { - response.AppendLine(FormatBotResponse(string.Format(Strings.BotRedeemWithItems, key, result.Result + "/" + result.PurchaseResultDetail, string.Join(", ", result.Items)), currentBot.BotName)); - } else { - response.AppendLine(FormatBotResponse(string.Format(Strings.BotRedeem, key, result.Result + "/" + result.PurchaseResultDetail), currentBot.BotName)); - } - - unusedKeys.Remove(key); - - key = keysEnumerator.MoveNext() ? keysEnumerator.Current : null; // Next key - break; // Next bot (if needed) - } - } - } - - // We want to change bot in two cases: - // a) When we have distribution enabled, obviously - // b) When we're skipping initial bot AND we have forwarding enabled, otherwise we won't get down to other accounts - if (distribute || (forward && redeemFlags.HasFlag(ERedeemFlags.SkipInitial))) { - currentBot = botsEnumerator.MoveNext() ? botsEnumerator.Current : null; - } - } - } - - if (key == startingKey) { - // We ran out of bots to try for this key, so change it to avoid infinite loop - key = keysEnumerator.MoveNext() ? keysEnumerator.Current : null; // Next key - } - } - } - - if (unusedKeys.Count > 0) { - response.AppendLine(FormatBotResponse(string.Format(Strings.UnusedKeys, string.Join(", ", unusedKeys)))); - } - - return response.Length > 0 ? response.ToString() : null; - } - - private static async Task ResponseRedeem(ulong steamID, string botNames, string keys, ERedeemFlags redeemFlags = ERedeemFlags.None) { - if ((steamID == 0) || string.IsNullOrEmpty(botNames) || string.IsNullOrEmpty(keys)) { - ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames) + " || " + nameof(keys)); - 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.ResponseRedeem(steamID, keys, redeemFlags)); - 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 string ResponseRestart(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.Restart().ConfigureAwait(false); - } - ); - - return FormatStaticResponse(Strings.Done); - } - - private string ResponseResume(ulong steamID) { - if (steamID == 0) { - ArchiLogger.LogNullError(nameof(steamID)); - return null; - } - - if (!IsFamilySharing(steamID)) { - return null; - } - - if (!IsConnectedAndLoggedOn) { - return FormatBotResponse(Strings.BotNotConnected); - } - - if (!CardsFarmer.Paused) { - return FormatBotResponse(Strings.BotAutomaticIdlingResumedAlready); - } - - if (CardsFarmerResumeTimer != null) { - CardsFarmerResumeTimer.Dispose(); - CardsFarmerResumeTimer = null; - } - - StopFamilySharingInactivityTimer(); - Utilities.InBackground(() => CardsFarmer.Resume(true)); - return FormatBotResponse(Strings.BotAutomaticIdlingNowResumed); - } - - private static async Task ResponseResume(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.ResponseResume(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 string ResponseStart(ulong steamID) { - if (steamID == 0) { - ArchiLogger.LogNullError(nameof(steamID)); - return null; - } - - if (!IsMaster(steamID)) { - return null; - } - - if (KeepRunning) { - return FormatBotResponse(Strings.BotAlreadyRunning); - } - - SkipFirstShutdown = true; - Utilities.InBackground(Start); - return FormatBotResponse(Strings.Done); - } - - private static async Task ResponseStart(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.ResponseStart(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 string ResponseStats(ulong steamID) { - if (steamID == 0) { - ArchiLogger.LogNullError(nameof(steamID)); - return null; - } - - if (!IsOwner(steamID)) { - return null; - } - - ushort memoryInMegabytes = (ushort) (GC.GetTotalMemory(false) / 1024 / 1024); - 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)); - return null; - } - - if (!IsMaster(steamID)) { - return null; - } - - if (!IsConnectedAndLoggedOn) { - return FormatBotResponse(Strings.BotNotConnected); - } - - if (!LootingAllowed) { - return FormatBotResponse(Strings.BotLootingTemporarilyDisabled); - } - - if (!Bots.TryGetValue(botNameTo, out Bot targetBot)) { - return IsOwner(steamID) ? FormatBotResponse(string.Format(Strings.BotNotFound, botNameTo)) : null; - } - - if (!targetBot.IsConnectedAndLoggedOn) { - return FormatBotResponse(Strings.BotNotConnected); - } - - if (targetBot.CachedSteamID == CachedSteamID) { - return FormatBotResponse(Strings.BotSendingTradeToYourself); - } - - string[] modes = mode.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - - if (modes.Length == 0) { - return FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(modes))); - } - - HashSet transferTypes = new HashSet(); - - foreach (string singleMode in modes) { - switch (singleMode.ToUpper()) { - case "A": - case "ALL": - foreach (Steam.Asset.EType type in Enum.GetValues(typeof(Steam.Asset.EType))) { - transferTypes.Add(type); - } - - break; - case "BG": - case "BACKGROUND": - transferTypes.Add(Steam.Asset.EType.ProfileBackground); - break; - case "BO": - case "BOOSTER": - transferTypes.Add(Steam.Asset.EType.BoosterPack); - break; - case "C": - case "CARD": - transferTypes.Add(Steam.Asset.EType.TradingCard); - break; - case "E": - case "EMOTICON": - transferTypes.Add(Steam.Asset.EType.Emoticon); - break; - case "F": - case "FOIL": - transferTypes.Add(Steam.Asset.EType.FoilTradingCard); - break; - case "G": - case "GEMS": - transferTypes.Add(Steam.Asset.EType.SteamGems); - break; - case "U": - case "UNKNOWN": - transferTypes.Add(Steam.Asset.EType.Unknown); - break; - default: - return FormatBotResponse(string.Format(Strings.ErrorIsInvalid, mode)); - } - } - - lock (LootingSemaphore) { - if (LootingScheduled) { - return FormatBotResponse(Strings.Done); - } - - LootingScheduled = true; - } - - await LootingSemaphore.WaitAsync().ConfigureAwait(false); - - try { - lock (LootingSemaphore) { - LootingScheduled = false; - } - - HashSet inventory = await ArchiWebHandler.GetInventory(CachedSteamID, tradable: true, wantedTypes: transferTypes).ConfigureAwait(false); - if ((inventory == null) || (inventory.Count == 0)) { - return FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(inventory))); - } - - string tradeToken = null; - - if (SteamFriends.GetFriendRelationship(targetBot.CachedSteamID) != EFriendRelationship.Friend) { - tradeToken = await targetBot.ArchiWebHandler.GetTradeToken().ConfigureAwait(false); - if (string.IsNullOrEmpty(tradeToken)) { - return FormatBotResponse(Strings.BotLootingFailed); - } - } - - if (!await ArchiWebHandler.MarkSentTrades().ConfigureAwait(false)) { - return FormatBotResponse(Strings.BotLootingFailed); - } - - if (!await ArchiWebHandler.SendTradeOffer(targetBot.CachedSteamID, inventory, tradeToken).ConfigureAwait(false)) { - return FormatBotResponse(Strings.BotLootingFailed); - } - - if (HasMobileAuthenticator) { - // Give Steam network some time to generate confirmations - await Task.Delay(3000).ConfigureAwait(false); - if (!await AcceptConfirmations(true, Steam.ConfirmationDetails.EType.Trade, targetBot.CachedSteamID).ConfigureAwait(false)) { - return FormatBotResponse(Strings.BotLootingFailed); - } - } - } finally { - LootingSemaphore.Release(); - } - - return FormatBotResponse(Strings.BotLootingSuccess); - } - - private static async Task ResponseTransfer(ulong steamID, string botNames, string mode, string botNameTo) { - if ((steamID == 0) || string.IsNullOrEmpty(botNames) || string.IsNullOrEmpty(mode) || string.IsNullOrEmpty(botNameTo)) { - ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames) + " || " + nameof(mode) + " || " + nameof(botNameTo)); - 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.ResponseTransfer(steamID, mode, botNameTo)); - 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 string ResponseUnknown(ulong steamID) { - if (steamID == 0) { - ArchiLogger.LogNullError(nameof(steamID)); - return null; - } - - 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)); - } - - // This switch should cover ONLY bot properties - switch (inputType) { - case ASF.EUserInputType.DeviceID: - DeviceID = inputValue; - break; - case ASF.EUserInputType.Login: - if (BotConfig != null) { - BotConfig.SteamLogin = inputValue; - } - - break; - case ASF.EUserInputType.Password: - if (BotConfig != null) { - BotConfig.SteamPassword = inputValue; - } - - break; - case ASF.EUserInputType.SteamGuard: - AuthCode = inputValue; - break; - case ASF.EUserInputType.SteamParentalPIN: - if (BotConfig != null) { - BotConfig.SteamParentalPIN = inputValue; - } - - break; - case ASF.EUserInputType.TwoFactorAuthentication: - TwoFactorCode = inputValue; - break; - default: - ASF.ArchiLogger.LogGenericError(string.Format(Strings.WarningUnknownValuePleaseReport, nameof(inputType), inputType)); - break; - } - } - - private async Task Start() { - if (!KeepRunning) { - KeepRunning = true; - Utilities.InBackground(HandleCallbacks, true); - ArchiLogger.LogGenericInfo(Strings.Starting); - } - - // Support and convert 2FA files - if (!HasMobileAuthenticator && File.Exists(MobileAuthenticatorFilePath)) { - await ImportAuthenticator(MobileAuthenticatorFilePath).ConfigureAwait(false); - } - - if (File.Exists(KeysToRedeemFilePath)) { - await ImportKeysToRedeem(KeysToRedeemFilePath).ConfigureAwait(false); - } - - await Connect().ConfigureAwait(false); - } - - private void StartFamilySharingInactivityTimer() { - if (FamilySharingInactivityTimer != null) { - return; - } - - FamilySharingInactivityTimer = new Timer( - async e => await CheckFamilySharingInactivity().ConfigureAwait(false), - null, - TimeSpan.FromMinutes(FamilySharingInactivityMinutes), // Delay - Timeout.InfiniteTimeSpan // Period - ); - } - private void StopConnectionFailureTimer() { if (ConnectionFailureTimer == null) { return; @@ -5381,15 +2382,6 @@ namespace ArchiSteamFarm { ConnectionFailureTimer = null; } - private void StopFamilySharingInactivityTimer() { - if (FamilySharingInactivityTimer == null) { - return; - } - - FamilySharingInactivityTimer.Dispose(); - FamilySharingInactivityTimer = null; - } - private void StopPlayingWasBlockedTimer() { if (PlayingWasBlockedTimer == null) { return; @@ -5407,18 +2399,5 @@ namespace ArchiSteamFarm { return message.Replace("\\[", "[").Replace("\\\\", "\\"); } - - [Flags] - private enum ERedeemFlags : byte { - None = 0, - Validate = 1, - ForceForwarding = 2, - SkipForwarding = 4, - ForceDistributing = 8, - SkipDistributing = 16, - SkipInitial = 32, - ForceKeepMissingGames = 64, - SkipKeepMissingGames = 128 - } } } diff --git a/ArchiSteamFarm/CardsFarmer.cs b/ArchiSteamFarm/CardsFarmer.cs index b78c63548..f11e0dc22 100755 --- a/ArchiSteamFarm/CardsFarmer.cs +++ b/ArchiSteamFarm/CardsFarmer.cs @@ -168,7 +168,9 @@ namespace ArchiSteamFarm { // If we're not farming, and we got new items, it's likely to be a booster pack or likewise // In this case, perform a loot if user wants to do so - await Bot.LootIfNeeded().ConfigureAwait(false); + if (Bot.BotConfig.SendOnFarmingFinished) { + await Bot.Actions.Loot().ConfigureAwait(false); + } } internal async Task Pause(bool sticky) { diff --git a/ArchiSteamFarm/Commands.cs b/ArchiSteamFarm/Commands.cs new file mode 100644 index 000000000..6db612c28 --- /dev/null +++ b/ArchiSteamFarm/Commands.cs @@ -0,0 +1,2731 @@ +// _ _ _ ____ _ _____ +// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___ +// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \ +// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | +// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| +// +// Copyright 2015-2018 Łukasz "JustArchi" Domeradzki +// Contact: JustArchi@JustArchi.net +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using ArchiSteamFarm.Json; +using ArchiSteamFarm.Localization; +using SteamKit2; + +namespace ArchiSteamFarm { + internal sealed class Commands : IDisposable { + private readonly Bot Bot; + private readonly Dictionary CachedGamesOwned = new Dictionary(); + + private Timer CardsFarmerResumeTimer; + + internal Commands(Bot bot) => Bot = bot ?? throw new ArgumentNullException(nameof(bot)); + + public void Dispose() => CardsFarmerResumeTimer?.Dispose(); + + internal void OnNewLicenseList() { + lock (CachedGamesOwned) { + CachedGamesOwned.Clear(); + CachedGamesOwned.TrimExcess(); + } + } + + internal async Task Response(ulong steamID, string message) { + if ((steamID == 0) || string.IsNullOrEmpty(message)) { + Bot.ArchiLogger.LogNullError(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[] args = message.Split((char[]) null, StringSplitOptions.RemoveEmptyEntries); + + switch (args.Length) { + case 0: + Bot.ArchiLogger.LogNullError(nameof(args)); + 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": + return ResponseHelp(steamID); + case "IB": + return ResponseIdleBlacklist(steamID); + case "IQ": + return ResponseIdleQueue(steamID); + case "LOOT": + 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~": + return await ResponsePause(steamID, false).ConfigureAwait(false); + case "RESUME": + 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); + } + + return await ResponseAddLicense(steamID, args[1]).ConfigureAwait(false); + case "BL": + return await ResponseBlacklist(steamID, Utilities.GetArgsAsText(args, 1, ",")).ConfigureAwait(false); + case "BLADD": + if (args.Length > 2) { + return await ResponseBlacklistAdd(steamID, args[1], Utilities.GetArgsAsText(args, 2, ",")).ConfigureAwait(false); + } + + return await ResponseBlacklistAdd(steamID, args[1]).ConfigureAwait(false); + case "BLRM": + if (args.Length > 2) { + return await ResponseBlacklistRemove(steamID, args[1], Utilities.GetArgsAsText(args, 2, ",")).ConfigureAwait(false); + } + + return await ResponseBlacklistRemove(steamID, args[1]).ConfigureAwait(false); + case "FARM": + return await ResponseFarm(steamID, Utilities.GetArgsAsText(args, 1, ",")).ConfigureAwait(false); + case "INPUT": + if (args.Length > 3) { + return await ResponseInput(steamID, args[1], args[2], Utilities.GetArgsAsText(message, 3)).ConfigureAwait(false); + } + + if (args.Length > 2) { + return ResponseInput(steamID, args[1], args[2]); + } + + goto default; + case "IB": + return await ResponseIdleBlacklist(steamID, Utilities.GetArgsAsText(args, 1, ",")).ConfigureAwait(false); + case "IBADD": + if (args.Length > 2) { + return await ResponseIdleBlacklistAdd(steamID, args[1], Utilities.GetArgsAsText(args, 2, ",")).ConfigureAwait(false); + } + + return await ResponseIdleBlacklistAdd(steamID, args[1]).ConfigureAwait(false); + case "IBRM": + if (args.Length > 2) { + return await ResponseIdleBlacklistRemove(steamID, args[1], Utilities.GetArgsAsText(args, 2, ",")).ConfigureAwait(false); + } + + return await ResponseIdleBlacklistRemove(steamID, args[1]).ConfigureAwait(false); + case "IQ": + return await ResponseIdleQueue(steamID, Utilities.GetArgsAsText(args, 1, ",")).ConfigureAwait(false); + case "IQADD": + if (args.Length > 2) { + return await ResponseIdleQueueAdd(steamID, args[1], Utilities.GetArgsAsText(args, 2, ",")).ConfigureAwait(false); + } + + return await ResponseIdleQueueAdd(steamID, args[1]).ConfigureAwait(false); + case "IQRM": + if (args.Length > 2) { + return await ResponseIdleQueueRemove(steamID, args[1], Utilities.GetArgsAsText(args, 2, ",")).ConfigureAwait(false); + } + + return await ResponseIdleQueueRemove(steamID, args[1]).ConfigureAwait(false); + case "LOOT": + return await ResponseLoot(steamID, Utilities.GetArgsAsText(args, 1, ",")).ConfigureAwait(false); + case "LOOT^": + if (args.Length > 3) { + return await ResponseAdvancedLoot(steamID, args[1], args[2], Utilities.GetArgsAsText(args, 3, ",")).ConfigureAwait(false); + } + + if (args.Length > 2) { + return await ResponseAdvancedLoot(steamID, args[1], args[2]).ConfigureAwait(false); + } + + goto default; + case "LOOT@": + if (args.Length > 2) { + return await ResponseLootByRealAppIDs(steamID, args[1], Utilities.GetArgsAsText(args, 2, ",")).ConfigureAwait(false); + } + + return await ResponseLootByRealAppIDs(steamID, args[1]).ConfigureAwait(false); + case "LOOT&": + return await ResponseLootSwitch(steamID, Utilities.GetArgsAsText(args, 1, ",")).ConfigureAwait(false); + case "NICKNAME": + if (args.Length > 2) { + return await ResponseNickname(steamID, args[1], Utilities.GetArgsAsText(message, 2)).ConfigureAwait(false); + } + + return ResponseNickname(steamID, args[1]); + case "OA": + return await ResponseOwns(steamID, SharedInfo.ASF, Utilities.GetArgsAsText(message, 1)).ConfigureAwait(false); + case "OWNS": + if (args.Length > 2) { + return await ResponseOwns(steamID, args[1], Utilities.GetArgsAsText(message, 2)).ConfigureAwait(false); + } + + 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~": + return await ResponsePause(steamID, Utilities.GetArgsAsText(args, 1, ","), false).ConfigureAwait(false); + case "PAUSE&": + if (args.Length > 2) { + return await ResponsePause(steamID, args[1], true, Utilities.GetArgsAsText(args, 2, ",")).ConfigureAwait(false); + } + + return await ResponsePause(steamID, true, args[1]).ConfigureAwait(false); + case "PLAY": + if (args.Length > 2) { + return await ResponsePlay(steamID, args[1], Utilities.GetArgsAsText(args, 2, ",")).ConfigureAwait(false); + } + + return await ResponsePlay(steamID, args[1]).ConfigureAwait(false); + case "PRIVACY": + if (args.Length > 2) { + return await ResponsePrivacy(steamID, args[1], Utilities.GetArgsAsText(args, 2, ",")).ConfigureAwait(false); + } + + return await ResponsePrivacy(steamID, args[1]).ConfigureAwait(false); + case "R": + case "REDEEM": + if (args.Length > 2) { + return await ResponseRedeem(steamID, args[1], Utilities.GetArgsAsText(args, 2, ",")).ConfigureAwait(false); + } + + return await ResponseRedeem(steamID, args[1]).ConfigureAwait(false); + case "R^": + case "REDEEM^": + if (args.Length > 3) { + return await ResponseAdvancedRedeem(steamID, args[1], args[2], Utilities.GetArgsAsText(args, 3, ",")).ConfigureAwait(false); + } + + if (args.Length > 2) { + return await ResponseAdvancedRedeem(steamID, args[1], args[2]).ConfigureAwait(false); + } + + goto default; + case "RESUME": + 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); + } + + if (args.Length > 2) { + return await ResponseTransfer(steamID, args[1], args[2]).ConfigureAwait(false); + } + + goto default; + case "UNPACK": + return await ResponseUnpackBoosters(steamID, Utilities.GetArgsAsText(args, 1, ",")).ConfigureAwait(false); + default: + return ResponseUnknown(steamID); + } + } + } + + private static string FormatBotResponse(string response, string botName) { + if (string.IsNullOrEmpty(response) || string.IsNullOrEmpty(botName)) { + ASF.ArchiLogger.LogNullError(nameof(response) + " || " + nameof(botName)); + return null; + } + + return Environment.NewLine + "<" + botName + "> " + response; + } + + private string FormatBotResponse(string response) { + if (string.IsNullOrEmpty(response)) { + ASF.ArchiLogger.LogNullError(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; + } + + private bool IsOperator(ulong steamID) { + if (steamID == 0) { + Bot.ArchiLogger.LogNullError(nameof(steamID)); + return false; + } + + return ASF.IsOwner(steamID) || (Bot.GetSteamUserPermission(steamID) >= BotConfig.EPermission.Operator); + } + + private async Task Response2FA(ulong steamID) { + if (steamID == 0) { + Bot.ArchiLogger.LogNullError(nameof(steamID)); + return null; + } + + if (!Bot.IsMaster(steamID)) { + return null; + } + + if (!Bot.HasMobileAuthenticator) { + return FormatBotResponse(Strings.BotNoASFAuthenticator); + } + + string token = await Bot.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 = Bot.GetBots(botNames); + if ((bots == null) || (bots.Count == 0)) { + return ASF.IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null; + } + + IEnumerable> tasks = bots.Select(bot => bot.Commands.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) { + Bot.ArchiLogger.LogNullError(nameof(steamID)); + return null; + } + + if (!Bot.IsMaster(steamID)) { + return null; + } + + if (!Bot.IsConnectedAndLoggedOn) { + return FormatBotResponse(Strings.BotNotConnected); + } + + if (!Bot.HasMobileAuthenticator) { + return FormatBotResponse(Strings.BotNoASFAuthenticator); + } + + bool result = await Bot.Actions.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 = Bot.GetBots(botNames); + if ((bots == null) || (bots.Count == 0)) { + return ASF.IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null; + } + + IEnumerable> tasks = bots.Select(bot => bot.Commands.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)) { + Bot.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(gameIDs) + " || " + nameof(gameIDs.Count)); + return null; + } + + if (!IsOperator(steamID)) { + return null; + } + + if (!Bot.IsConnectedAndLoggedOn) { + return FormatBotResponse(Strings.BotNotConnected); + } + + StringBuilder response = new StringBuilder(); + + foreach (uint gameID in gameIDs) { + if (await Bot.ArchiWebHandler.AddFreeLicense(gameID).ConfigureAwait(false)) { + response.AppendLine(FormatBotResponse(string.Format(Strings.BotAddLicenseWithItems, gameID, EResult.OK, "sub/" + gameID))); + continue; + } + + SteamApps.FreeLicenseCallback callback; + + try { + callback = await Bot.SteamApps.RequestFreeLicense(gameID); + } catch (Exception e) { + Bot.ArchiLogger.LogGenericWarningException(e); + response.AppendLine(FormatBotResponse(string.Format(Strings.BotAddLicense, gameID, EResult.Timeout))); + break; + } + + if (callback == null) { + response.AppendLine(FormatBotResponse(string.Format(Strings.BotAddLicense, gameID, EResult.Timeout))); + break; + } + + response.AppendLine(FormatBotResponse((callback.GrantedApps.Count > 0) || (callback.GrantedPackages.Count > 0) ? string.Format(Strings.BotAddLicenseWithItems, gameID, callback.Result, string.Join(", ", callback.GrantedApps.Select(appID => "app/" + appID).Union(callback.GrantedPackages.Select(subID => "sub/" + subID)))) : string.Format(Strings.BotAddLicense, gameID, callback.Result))); + } + + return response.Length > 0 ? response.ToString() : null; + } + + private async Task ResponseAddLicense(ulong steamID, string targetGameIDs) { + if ((steamID == 0) || string.IsNullOrEmpty(targetGameIDs)) { + ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(targetGameIDs)); + return null; + } + + if (!IsOperator(steamID)) { + return null; + } + + if (!Bot.IsConnectedAndLoggedOn) { + return FormatBotResponse(Strings.BotNotConnected); + } + + string[] gameIDs = targetGameIDs.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + + if (gameIDs.Length == 0) { + return FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(gameIDs))); + } + + HashSet gamesToRedeem = new HashSet(); + + foreach (string game in gameIDs) { + if (!uint.TryParse(game, out uint gameID) || (gameID == 0)) { + return FormatBotResponse(string.Format(Strings.ErrorParsingObject, nameof(gameID))); + } + + gamesToRedeem.Add(gameID); + } + + return await ResponseAddLicense(steamID, gamesToRedeem).ConfigureAwait(false); + } + + private static async Task ResponseAddLicense(ulong steamID, string botNames, string targetGameIDs) { + if ((steamID == 0) || string.IsNullOrEmpty(botNames) || string.IsNullOrEmpty(targetGameIDs)) { + ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames) + " || " + nameof(targetGameIDs)); + return null; + } + + HashSet bots = Bot.GetBots(botNames); + if ((bots == null) || (bots.Count == 0)) { + return ASF.IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null; + } + + IEnumerable> tasks = bots.Select(bot => bot.Commands.ResponseAddLicense(steamID, targetGameIDs)); + 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 ResponseAdvancedLoot(ulong steamID, string targetAppID, string targetContextID) { + if ((steamID == 0) || string.IsNullOrEmpty(targetAppID) || string.IsNullOrEmpty(targetContextID)) { + Bot.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(targetAppID) + " || " + nameof(targetContextID)); + return null; + } + + if (!Bot.IsMaster(steamID)) { + return null; + } + + if (!Bot.IsConnectedAndLoggedOn) { + return FormatBotResponse(Strings.BotNotConnected); + } + + if (!uint.TryParse(targetAppID, out uint appID) || (appID == 0)) { + return FormatBotResponse(string.Format(Strings.ErrorIsInvalid, nameof(appID))); + } + + if (!byte.TryParse(targetContextID, out byte contextID) || (contextID == 0)) { + return FormatBotResponse(string.Format(Strings.ErrorIsInvalid, nameof(contextID))); + } + + (bool success, string output) = await Bot.Actions.Loot(appID, contextID).ConfigureAwait(false); + return FormatBotResponse(success ? output : string.Format(Strings.WarningFailedWithError, output)); + } + + private static async Task ResponseAdvancedLoot(ulong steamID, string botNames, string appID, string contextID) { + if ((steamID == 0) || string.IsNullOrEmpty(botNames) || string.IsNullOrEmpty(appID) || string.IsNullOrEmpty(contextID)) { + ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames) + " || " + nameof(appID) + " || " + nameof(contextID)); + return null; + } + + HashSet bots = Bot.GetBots(botNames); + if ((bots == null) || (bots.Count == 0)) { + return ASF.IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null; + } + + IEnumerable> tasks = bots.Select(bot => bot.Commands.ResponseAdvancedLoot(steamID, appID, contextID)); + 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 ResponseAdvancedRedeem(ulong steamID, string options, string keys) { + if ((steamID == 0) || string.IsNullOrEmpty(options) || string.IsNullOrEmpty(keys)) { + ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(options) + " || " + nameof(keys)); + return null; + } + + if (!IsOperator(steamID)) { + return null; + } + + string[] flags = options.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + + if (flags.Length == 0) { + return FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(flags))); + } + + ERedeemFlags redeemFlags = ERedeemFlags.None; + + foreach (string flag in flags) { + switch (flag.ToUpperInvariant()) { + case "FD": + redeemFlags |= ERedeemFlags.ForceDistributing; + break; + case "FF": + redeemFlags |= ERedeemFlags.ForceForwarding; + break; + case "FKMG": + redeemFlags |= ERedeemFlags.ForceKeepMissingGames; + break; + case "SD": + redeemFlags |= ERedeemFlags.SkipDistributing; + break; + case "SF": + redeemFlags |= ERedeemFlags.SkipForwarding; + break; + case "SI": + redeemFlags |= ERedeemFlags.SkipInitial; + break; + case "SKMG": + redeemFlags |= ERedeemFlags.SkipKeepMissingGames; + break; + case "V": + redeemFlags |= ERedeemFlags.Validate; + break; + default: + return FormatBotResponse(string.Format(Strings.ErrorIsInvalid, flag)); + } + } + + return await ResponseRedeem(steamID, keys, redeemFlags).ConfigureAwait(false); + } + + private static async Task ResponseAdvancedRedeem(ulong steamID, string botNames, string options, string keys) { + if ((steamID == 0) || string.IsNullOrEmpty(botNames) || string.IsNullOrEmpty(options) || string.IsNullOrEmpty(keys)) { + ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames) + " || " + nameof(options) + " || " + nameof(keys)); + return null; + } + + HashSet bots = Bot.GetBots(botNames); + if ((bots == null) || (bots.Count == 0)) { + return ASF.IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null; + } + + IEnumerable> tasks = bots.Select(bot => bot.Commands.ResponseAdvancedRedeem(steamID, options, keys)); + 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 string ResponseBlacklist(ulong steamID) { + if (steamID == 0) { + Bot.ArchiLogger.LogNullError(nameof(steamID)); + return null; + } + + if (!Bot.IsMaster(steamID)) { + return null; + } + + IReadOnlyCollection blacklist = Bot.BotDatabase.GetBlacklistedFromTradesSteamIDs(); + return FormatBotResponse(blacklist.Count > 0 ? string.Join(", ", blacklist) : string.Format(Strings.ErrorIsEmpty, nameof(blacklist))); + } + + private static async Task ResponseBlacklist(ulong steamID, string botNames) { + if ((steamID == 0) || string.IsNullOrEmpty(botNames)) { + ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames)); + return null; + } + + HashSet bots = Bot.GetBots(botNames); + if ((bots == null) || (bots.Count == 0)) { + return ASF.IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null; + } + + IEnumerable> tasks = bots.Select(bot => Task.Run(() => bot.Commands.ResponseBlacklist(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 ResponseBlacklistAdd(ulong steamID, string targetSteamIDs) { + if ((steamID == 0) || string.IsNullOrEmpty(targetSteamIDs)) { + Bot.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(targetSteamIDs)); + return null; + } + + if (!Bot.IsMaster(steamID)) { + return null; + } + + string[] targets = targetSteamIDs.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + + if (targets.Length == 0) { + return FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(targets))); + } + + HashSet targetIDs = new HashSet(); + + foreach (string target in targets) { + if (!ulong.TryParse(target, out ulong targetID) || (targetID == 0)) { + return FormatBotResponse(string.Format(Strings.ErrorParsingObject, nameof(targetID))); + } + + targetIDs.Add(targetID); + } + + await Bot.BotDatabase.AddBlacklistedFromTradesSteamIDs(targetIDs).ConfigureAwait(false); + return FormatBotResponse(Strings.Done); + } + + private static async Task ResponseBlacklistAdd(ulong steamID, string botNames, string targetSteamIDs) { + if ((steamID == 0) || string.IsNullOrEmpty(botNames) || string.IsNullOrEmpty(targetSteamIDs)) { + ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames) + " || " + nameof(targetSteamIDs)); + return null; + } + + HashSet bots = Bot.GetBots(botNames); + if ((bots == null) || (bots.Count == 0)) { + return ASF.IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null; + } + + IEnumerable> tasks = bots.Select(bot => bot.Commands.ResponseBlacklistAdd(steamID, targetSteamIDs)); + 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 ResponseBlacklistRemove(ulong steamID, string targetSteamIDs) { + if ((steamID == 0) || string.IsNullOrEmpty(targetSteamIDs)) { + Bot.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(targetSteamIDs)); + return null; + } + + if (!Bot.IsMaster(steamID)) { + return null; + } + + string[] targets = targetSteamIDs.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + + if (targets.Length == 0) { + return FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(targets))); + } + + HashSet targetIDs = new HashSet(); + + foreach (string target in targets) { + if (!ulong.TryParse(target, out ulong targetID) || (targetID == 0)) { + return FormatBotResponse(string.Format(Strings.ErrorParsingObject, nameof(targetID))); + } + + targetIDs.Add(targetID); + } + + await Bot.BotDatabase.RemoveBlacklistedFromTradesSteamIDs(targetIDs).ConfigureAwait(false); + return FormatBotResponse(Strings.Done); + } + + private static async Task ResponseBlacklistRemove(ulong steamID, string botNames, string targetSteamIDs) { + if ((steamID == 0) || string.IsNullOrEmpty(botNames) || string.IsNullOrEmpty(targetSteamIDs)) { + ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames) + " || " + nameof(targetSteamIDs)); + return null; + } + + HashSet bots = Bot.GetBots(botNames); + if ((bots == null) || (bots.Count == 0)) { + return ASF.IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null; + } + + IEnumerable> tasks = bots.Select(bot => bot.Commands.ResponseBlacklistRemove(steamID, targetSteamIDs)); + 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 string ResponseExit(ulong steamID) { + if (steamID == 0) { + ASF.ArchiLogger.LogNullError(nameof(steamID)); + return null; + } + + if (!ASF.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) { + Bot.ArchiLogger.LogNullError(nameof(steamID)); + return null; + } + + if (!Bot.IsMaster(steamID)) { + return null; + } + + if (!Bot.IsConnectedAndLoggedOn) { + return FormatBotResponse(Strings.BotNotConnected); + } + + if (Bot.CardsFarmer.NowFarming) { + await Bot.CardsFarmer.StopFarming().ConfigureAwait(false); + } + + Utilities.InBackground(Bot.CardsFarmer.StartFarming); + return FormatBotResponse(Strings.Done); + } + + private static async Task ResponseFarm(ulong steamID, string botNames) { + if ((steamID == 0) || string.IsNullOrEmpty(botNames)) { + ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames)); + return null; + } + + HashSet bots = Bot.GetBots(botNames); + if ((bots == null) || (bots.Count == 0)) { + return ASF.IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null; + } + + IEnumerable> tasks = bots.Select(bot => bot.Commands.ResponseFarm(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 string ResponseHelp(ulong steamID) { + if (steamID == 0) { + Bot.ArchiLogger.LogNullError(nameof(steamID)); + return null; + } + + return Bot.IsFamilySharing(steamID) ? FormatBotResponse("https://github.com/" + SharedInfo.GithubRepo + "/wiki/Commands") : null; + } + + private string ResponseIdleBlacklist(ulong steamID) { + if (steamID == 0) { + Bot.ArchiLogger.LogNullError(nameof(steamID)); + return null; + } + + if (!Bot.IsMaster(steamID)) { + return null; + } + + IReadOnlyCollection idleBlacklist = Bot.BotDatabase.GetIdlingBlacklistedAppIDs(); + return FormatBotResponse(idleBlacklist.Count > 0 ? string.Join(", ", idleBlacklist) : string.Format(Strings.ErrorIsEmpty, nameof(idleBlacklist))); + } + + private static async Task ResponseIdleBlacklist(ulong steamID, string botNames) { + if ((steamID == 0) || string.IsNullOrEmpty(botNames)) { + ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames)); + return null; + } + + HashSet bots = Bot.GetBots(botNames); + if ((bots == null) || (bots.Count == 0)) { + return ASF.IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null; + } + + IEnumerable> tasks = bots.Select(bot => Task.Run(() => bot.Commands.ResponseIdleBlacklist(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 ResponseIdleBlacklistAdd(ulong steamID, string targetAppIDs) { + if ((steamID == 0) || string.IsNullOrEmpty(targetAppIDs)) { + Bot.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(targetAppIDs)); + return null; + } + + if (!Bot.IsMaster(steamID)) { + return null; + } + + string[] targets = targetAppIDs.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + + if (targets.Length == 0) { + return FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(targets))); + } + + HashSet appIDs = new HashSet(); + + foreach (string target in targets) { + if (!uint.TryParse(target, out uint appID) || (appID == 0)) { + return FormatBotResponse(string.Format(Strings.ErrorParsingObject, nameof(appID))); + } + + appIDs.Add(appID); + } + + await Bot.BotDatabase.AddIdlingBlacklistedAppIDs(appIDs).ConfigureAwait(false); + return FormatBotResponse(Strings.Done); + } + + private static async Task ResponseIdleBlacklistAdd(ulong steamID, string botNames, string targetAppIDs) { + if ((steamID == 0) || string.IsNullOrEmpty(botNames) || string.IsNullOrEmpty(targetAppIDs)) { + ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames) + " || " + nameof(targetAppIDs)); + return null; + } + + HashSet bots = Bot.GetBots(botNames); + if ((bots == null) || (bots.Count == 0)) { + return ASF.IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null; + } + + IEnumerable> tasks = bots.Select(bot => bot.Commands.ResponseIdleBlacklistAdd(steamID, targetAppIDs)); + 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 ResponseIdleBlacklistRemove(ulong steamID, string targetAppIDs) { + if ((steamID == 0) || string.IsNullOrEmpty(targetAppIDs)) { + Bot.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(targetAppIDs)); + return null; + } + + if (!Bot.IsMaster(steamID)) { + return null; + } + + string[] targets = targetAppIDs.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + + if (targets.Length == 0) { + return FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(targets))); + } + + HashSet appIDs = new HashSet(); + + foreach (string target in targets) { + if (!uint.TryParse(target, out uint appID) || (appID == 0)) { + return FormatBotResponse(string.Format(Strings.ErrorParsingObject, nameof(appID))); + } + + appIDs.Add(appID); + } + + await Bot.BotDatabase.RemoveIdlingBlacklistedAppIDs(appIDs).ConfigureAwait(false); + return FormatBotResponse(Strings.Done); + } + + private static async Task ResponseIdleBlacklistRemove(ulong steamID, string botNames, string targetAppIDs) { + if ((steamID == 0) || string.IsNullOrEmpty(botNames) || string.IsNullOrEmpty(targetAppIDs)) { + ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames) + " || " + nameof(targetAppIDs)); + return null; + } + + HashSet bots = Bot.GetBots(botNames); + if ((bots == null) || (bots.Count == 0)) { + return ASF.IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null; + } + + IEnumerable> tasks = bots.Select(bot => bot.Commands.ResponseIdleBlacklistRemove(steamID, targetAppIDs)); + 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 string ResponseIdleQueue(ulong steamID) { + if (steamID == 0) { + Bot.ArchiLogger.LogNullError(nameof(steamID)); + return null; + } + + if (!Bot.IsMaster(steamID)) { + return null; + } + + IReadOnlyCollection idleQueue = Bot.BotDatabase.GetIdlingPriorityAppIDs(); + return FormatBotResponse(idleQueue.Count > 0 ? string.Join(", ", idleQueue) : string.Format(Strings.ErrorIsEmpty, nameof(idleQueue))); + } + + private static async Task ResponseIdleQueue(ulong steamID, string botNames) { + if ((steamID == 0) || string.IsNullOrEmpty(botNames)) { + ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames)); + return null; + } + + HashSet bots = Bot.GetBots(botNames); + if ((bots == null) || (bots.Count == 0)) { + return ASF.IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null; + } + + IEnumerable> tasks = bots.Select(bot => Task.Run(() => bot.Commands.ResponseIdleQueue(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 ResponseIdleQueueAdd(ulong steamID, string targetAppIDs) { + if ((steamID == 0) || string.IsNullOrEmpty(targetAppIDs)) { + Bot.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(targetAppIDs)); + return null; + } + + if (!Bot.IsMaster(steamID)) { + return null; + } + + string[] targets = targetAppIDs.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + + if (targets.Length == 0) { + return FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(targets))); + } + + HashSet appIDs = new HashSet(); + + foreach (string target in targets) { + if (!uint.TryParse(target, out uint appID) || (appID == 0)) { + return FormatBotResponse(string.Format(Strings.ErrorParsingObject, nameof(appID))); + } + + appIDs.Add(appID); + } + + await Bot.BotDatabase.AddIdlingPriorityAppIDs(appIDs).ConfigureAwait(false); + return FormatBotResponse(Strings.Done); + } + + private static async Task ResponseIdleQueueAdd(ulong steamID, string botNames, string targetAppIDs) { + if ((steamID == 0) || string.IsNullOrEmpty(botNames) || string.IsNullOrEmpty(targetAppIDs)) { + ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames) + " || " + nameof(targetAppIDs)); + return null; + } + + HashSet bots = Bot.GetBots(botNames); + if ((bots == null) || (bots.Count == 0)) { + return ASF.IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null; + } + + IEnumerable> tasks = bots.Select(bot => bot.Commands.ResponseIdleQueueAdd(steamID, targetAppIDs)); + 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 ResponseIdleQueueRemove(ulong steamID, string targetAppIDs) { + if ((steamID == 0) || string.IsNullOrEmpty(targetAppIDs)) { + Bot.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(targetAppIDs)); + return null; + } + + if (!Bot.IsMaster(steamID)) { + return null; + } + + string[] targets = targetAppIDs.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + + if (targets.Length == 0) { + return FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(targets))); + } + + HashSet appIDs = new HashSet(); + + foreach (string target in targets) { + if (!uint.TryParse(target, out uint appID) || (appID == 0)) { + return FormatBotResponse(string.Format(Strings.ErrorParsingObject, nameof(appID))); + } + + appIDs.Add(appID); + } + + await Bot.BotDatabase.RemoveIdlingPriorityAppIDs(appIDs).ConfigureAwait(false); + return FormatBotResponse(Strings.Done); + } + + private static async Task ResponseIdleQueueRemove(ulong steamID, string botNames, string targetAppIDs) { + if ((steamID == 0) || string.IsNullOrEmpty(botNames) || string.IsNullOrEmpty(targetAppIDs)) { + ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames) + " || " + nameof(targetAppIDs)); + return null; + } + + HashSet bots = Bot.GetBots(botNames); + if ((bots == null) || (bots.Count == 0)) { + return ASF.IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null; + } + + IEnumerable> tasks = bots.Select(bot => bot.Commands.ResponseIdleQueueRemove(steamID, targetAppIDs)); + 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 string ResponseInput(ulong steamID, string propertyName, string inputValue) { + if ((steamID == 0) || string.IsNullOrEmpty(propertyName) || string.IsNullOrEmpty(inputValue)) { + ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(propertyName) + " || " + nameof(inputValue)); + return null; + } + + if (!Bot.IsMaster(steamID)) { + return null; + } + + if (!Program.GlobalConfig.Headless) { + return FormatBotResponse(Strings.ErrorFunctionOnlyInHeadlessMode); + } + + if (!Enum.TryParse(propertyName, true, out ASF.EUserInputType inputType) || (inputType == ASF.EUserInputType.Unknown) || !Enum.IsDefined(typeof(ASF.EUserInputType), inputType)) { + return FormatBotResponse(string.Format(Strings.ErrorIsInvalid, nameof(inputType))); + } + + Bot.SetUserInput(inputType, inputValue); + return FormatBotResponse(Strings.Done); + } + + private static async Task ResponseInput(ulong steamID, string botNames, string propertyName, string inputValue) { + if ((steamID == 0) || string.IsNullOrEmpty(botNames) || string.IsNullOrEmpty(propertyName) || string.IsNullOrEmpty(inputValue)) { + ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames) + " || " + nameof(propertyName) + " || " + nameof(inputValue)); + return null; + } + + HashSet bots = Bot.GetBots(botNames); + if ((bots == null) || (bots.Count == 0)) { + return ASF.IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null; + } + + IEnumerable> tasks = bots.Select(bot => Task.Run(() => bot.Commands.ResponseInput(steamID, propertyName, inputValue))); + 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 ResponseLoot(ulong steamID) { + if (steamID == 0) { + Bot.ArchiLogger.LogNullError(nameof(steamID)); + return null; + } + + if (!Bot.IsMaster(steamID)) { + return null; + } + + if (!Bot.IsConnectedAndLoggedOn) { + return FormatBotResponse(Strings.BotNotConnected); + } + + (bool success, string output) = await Bot.Actions.Loot(wantedTypes: Bot.BotConfig.LootableTypes).ConfigureAwait(false); + return FormatBotResponse(success ? output : string.Format(Strings.WarningFailedWithError, output)); + } + + private static async Task ResponseLoot(ulong steamID, string botNames) { + if ((steamID == 0) || string.IsNullOrEmpty(botNames)) { + ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames)); + return null; + } + + HashSet bots = Bot.GetBots(botNames); + if ((bots == null) || (bots.Count == 0)) { + return ASF.IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null; + } + + IEnumerable> tasks = bots.Select(bot => bot.Commands.ResponseLoot(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 ResponseLootByRealAppIDs(ulong steamID, string realAppIDsText) { + if ((steamID == 0) || string.IsNullOrEmpty(realAppIDsText)) { + Bot.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(realAppIDsText)); + return null; + } + + if (!Bot.IsMaster(steamID)) { + return null; + } + + if (!Bot.IsConnectedAndLoggedOn) { + return FormatBotResponse(Strings.BotNotConnected); + } + + string[] appIDTexts = realAppIDsText.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + + if (appIDTexts.Length == 0) { + return FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(appIDTexts))); + } + + HashSet realAppIDs = new HashSet(); + + foreach (string appIDText in appIDTexts) { + if (!uint.TryParse(appIDText, out uint appID) || (appID == 0)) { + return FormatBotResponse(string.Format(Strings.ErrorIsInvalid, nameof(appID))); + } + + realAppIDs.Add(appID); + } + + (bool success, string output) = await Bot.Actions.Loot(wantedRealAppIDs: realAppIDs).ConfigureAwait(false); + return FormatBotResponse(success ? output : string.Format(Strings.WarningFailedWithError, output)); + } + + private static async Task ResponseLootByRealAppIDs(ulong steamID, string botNames, string realAppIDsText) { + if ((steamID == 0) || string.IsNullOrEmpty(botNames) || string.IsNullOrEmpty(realAppIDsText)) { + ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames) + " || " + nameof(realAppIDsText)); + return null; + } + + HashSet bots = Bot.GetBots(botNames); + if ((bots == null) || (bots.Count == 0)) { + return ASF.IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null; + } + + IEnumerable> tasks = bots.Select(bot => bot.Commands.ResponseLootByRealAppIDs(steamID, realAppIDsText)); + 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 string ResponseLootSwitch(ulong steamID) { + if (steamID == 0) { + Bot.ArchiLogger.LogNullError(nameof(steamID)); + return null; + } + + if (!Bot.IsMaster(steamID)) { + return null; + } + + bool newValue = Bot.Actions.SwitchLootingAllowed(); + return FormatBotResponse(newValue ? Strings.BotLootingNowEnabled : Strings.BotLootingNowDisabled); + } + + private static async Task ResponseLootSwitch(ulong steamID, string botNames) { + if ((steamID == 0) || string.IsNullOrEmpty(botNames)) { + ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames)); + return null; + } + + HashSet bots = Bot.GetBots(botNames); + if ((bots == null) || (bots.Count == 0)) { + return ASF.IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null; + } + + IEnumerable> tasks = bots.Select(bot => Task.Run(() => bot.Commands.ResponseLootSwitch(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 string ResponseNickname(ulong steamID, string nickname) { + if ((steamID == 0) || string.IsNullOrEmpty(nickname)) { + Bot.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(nickname)); + return null; + } + + if (!Bot.IsMaster(steamID)) { + return null; + } + + if (!Bot.IsConnectedAndLoggedOn) { + return FormatBotResponse(Strings.BotNotConnected); + } + + Bot.SteamFriends.SetPersonaName(nickname); + return FormatBotResponse(Strings.Done); + } + + private static async Task ResponseNickname(ulong steamID, string botNames, string nickname) { + if ((steamID == 0) || string.IsNullOrEmpty(botNames) || string.IsNullOrEmpty(nickname)) { + ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames) + " || " + nameof(nickname)); + return null; + } + + HashSet bots = Bot.GetBots(botNames); + if ((bots == null) || (bots.Count == 0)) { + return ASF.IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null; + } + + IEnumerable> tasks = bots.Select(bot => Task.Run(() => bot.Commands.ResponseNickname(steamID, nickname))); + 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<(string Response, HashSet OwnedGameIDs)> ResponseOwns(ulong steamID, string query) { + if ((steamID == 0) || string.IsNullOrEmpty(query)) { + Bot.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(query)); + return (null, null); + } + + if (!IsOperator(steamID)) { + return (null, null); + } + + if (!Bot.IsConnectedAndLoggedOn) { + return (FormatBotResponse(Strings.BotNotConnected), null); + } + + Dictionary ownedGames = null; + + lock (CachedGamesOwned) { + if (CachedGamesOwned.Count > 0) { + ownedGames = new Dictionary(CachedGamesOwned); + } + } + + if (ownedGames == null) { + ownedGames = await Bot.ArchiWebHandler.HasValidApiKey().ConfigureAwait(false) ? await Bot.ArchiWebHandler.GetOwnedGames(Bot.CachedSteamID).ConfigureAwait(false) : await Bot.ArchiWebHandler.GetMyOwnedGames().ConfigureAwait(false); + if ((ownedGames == null) || (ownedGames.Count == 0)) { + return (FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(ownedGames))), null); + } + + lock (CachedGamesOwned) { + if (CachedGamesOwned.Count == 0) { + foreach (KeyValuePair ownedGame in ownedGames) { + CachedGamesOwned[ownedGame.Key] = ownedGame.Value; + } + + CachedGamesOwned.TrimExcess(); + } + } + } + + StringBuilder response = new StringBuilder(); + HashSet ownedGameIDs = new HashSet(); + + if (query.Equals("*")) { + foreach (KeyValuePair ownedGame in ownedGames) { + ownedGameIDs.Add(ownedGame.Key); + response.AppendLine(FormatBotResponse(string.Format(Strings.BotOwnedAlreadyWithName, ownedGame.Key, ownedGame.Value))); + } + } else { + string[] games = query.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + + if (games.Length == 0) { + return (FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(games))), null); + } + + foreach (string game in games) { + // Check if this is gameID + if (uint.TryParse(game, out uint gameID) && (gameID != 0)) { + if (Bot.OwnedPackageIDs.ContainsKey(gameID)) { + ownedGameIDs.Add(gameID); + response.AppendLine(FormatBotResponse(string.Format(Strings.BotOwnedAlready, gameID))); + continue; + } + + if (ownedGames.TryGetValue(gameID, out string ownedName)) { + ownedGameIDs.Add(gameID); + response.AppendLine(FormatBotResponse(string.Format(Strings.BotOwnedAlreadyWithName, gameID, ownedName))); + } else { + response.AppendLine(FormatBotResponse(string.Format(Strings.BotNotOwnedYet, gameID))); + } + + continue; + } + + // This is a string, so check our entire library + foreach (KeyValuePair ownedGame in ownedGames.Where(ownedGame => ownedGame.Value.IndexOf(game, StringComparison.OrdinalIgnoreCase) >= 0)) { + ownedGameIDs.Add(ownedGame.Key); + response.AppendLine(FormatBotResponse(string.Format(Strings.BotOwnedAlreadyWithName, ownedGame.Key, ownedGame.Value))); + } + } + } + + return (response.Length > 0 ? response.ToString() : FormatBotResponse(string.Format(Strings.BotNotOwnedYet, query)), ownedGameIDs); + } + + private static async Task ResponseOwns(ulong steamID, string botNames, string query) { + if ((steamID == 0) || string.IsNullOrEmpty(botNames) || string.IsNullOrEmpty(query)) { + ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames) + " || " + nameof(query)); + return null; + } + + HashSet bots = Bot.GetBots(botNames); + if ((bots == null) || (bots.Count == 0)) { + return ASF.IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null; + } + + IEnumerable OwnedGameIDs)>> tasks = bots.Select(bot => bot.Commands.ResponseOwns(steamID, query)); + ICollection<(string Response, HashSet OwnedGameIDs)> results; + + switch (Program.GlobalConfig.OptimizationMode) { + case GlobalConfig.EOptimizationMode.MinMemoryUsage: + results = new List<(string Response, HashSet OwnedGameIDs)>(bots.Count); + foreach (Task<(string Response, HashSet OwnedGameIDs)> task in tasks) { + results.Add(await task.ConfigureAwait(false)); + } + + break; + default: + results = await Task.WhenAll(tasks).ConfigureAwait(false); + break; + } + + List<(string Response, HashSet OwnedGameIDs)> validResults = new List<(string Response, HashSet OwnedGameIDs)>(results.Where(result => !string.IsNullOrEmpty(result.Response))); + if (validResults.Count == 0) { + return null; + } + + Dictionary ownedGameCounts = new Dictionary(); + foreach (uint gameID in validResults.Where(validResult => (validResult.OwnedGameIDs != null) && (validResult.OwnedGameIDs.Count > 0)).SelectMany(validResult => validResult.OwnedGameIDs)) { + ownedGameCounts[gameID] = ownedGameCounts.TryGetValue(gameID, out ushort count) ? ++count : (ushort) 1; + } + + IEnumerable extraResponses = ownedGameCounts.Select(kv => FormatStaticResponse(string.Format(Strings.BotOwnsOverviewPerGame, kv.Value, validResults.Count, kv.Key))); + return string.Join(Environment.NewLine, validResults.Select(result => result.Response).Concat(extraResponses)); + } + + private string ResponsePassword(ulong steamID) { + if (steamID == 0) { + Bot.ArchiLogger.LogNullError(nameof(steamID)); + return null; + } + + if (!Bot.IsMaster(steamID)) { + return null; + } + + if (string.IsNullOrEmpty(Bot.BotConfig.SteamPassword)) { + return FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(BotConfig.SteamPassword))); + } + + string response = FormatBotResponse(string.Format(Strings.BotEncryptedPassword, ArchiCryptoHelper.ECryptoMethod.AES, ArchiCryptoHelper.Encrypt(ArchiCryptoHelper.ECryptoMethod.AES, Bot.BotConfig.SteamPassword))) + FormatBotResponse(string.Format(Strings.BotEncryptedPassword, ArchiCryptoHelper.ECryptoMethod.ProtectedDataForCurrentUser, ArchiCryptoHelper.Encrypt(ArchiCryptoHelper.ECryptoMethod.ProtectedDataForCurrentUser, Bot.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 = Bot.GetBots(botNames); + if ((bots == null) || (bots.Count == 0)) { + return ASF.IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null; + } + + IEnumerable> tasks = bots.Select(bot => Task.Run(() => bot.Commands.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) { + Bot.ArchiLogger.LogNullError(nameof(steamID)); + return null; + } + + if (!Bot.IsFamilySharing(steamID)) { + return null; + } + + if (sticky && !IsOperator(steamID)) { + return FormatBotResponse(Strings.ErrorAccessDenied); + } + + if (!Bot.IsConnectedAndLoggedOn) { + return FormatBotResponse(Strings.BotNotConnected); + } + + if (Bot.CardsFarmer.Paused) { + return FormatBotResponse(Strings.BotAutomaticIdlingPausedAlready); + } + + ushort resumeInSeconds = 0; + + if (sticky && !string.IsNullOrEmpty(timeout)) { + if (!ushort.TryParse(timeout, out resumeInSeconds) || (resumeInSeconds == 0)) { + return FormatBotResponse(string.Format(Strings.ErrorIsInvalid, nameof(timeout))); + } + } + + await Bot.CardsFarmer.Pause(sticky).ConfigureAwait(false); + + if (!sticky && (Bot.BotConfig.GamesPlayedWhileIdle.Count > 0)) { + // We want to let family sharing users access our library, and in this case we must also stop GamesPlayedWhileIdle + // We add extra delay because OnFarmingStopped() also executes PlayGames() + // Despite of proper order on our end, Steam network might not respect it + await Task.Delay(Bot.CallbackSleep).ConfigureAwait(false); + await Bot.ArchiHandler.PlayGames(Enumerable.Empty(), Bot.BotConfig.CustomGamePlayedWhileIdle).ConfigureAwait(false); + } + + if (resumeInSeconds > 0) { + if (CardsFarmerResumeTimer != null) { + CardsFarmerResumeTimer.Dispose(); + CardsFarmerResumeTimer = null; + } + + CardsFarmerResumeTimer = new Timer( + e => ResponseResume(steamID), + null, + TimeSpan.FromSeconds(resumeInSeconds), // Delay + Timeout.InfiniteTimeSpan // Period + ); + } + + if (IsOperator(steamID)) { + return FormatBotResponse(Strings.BotAutomaticIdlingNowPaused); + } + + Bot.StartFamilySharingInactivityTimer(); + return FormatBotResponse(string.Format(Strings.BotAutomaticIdlingPausedWithCountdown, TimeSpan.FromMinutes(Bot.FamilySharingInactivityMinutes).ToHumanReadable())); + } + + private static async Task ResponsePause(ulong steamID, string botNames, bool sticky, string timeout = null) { + if ((steamID == 0) || string.IsNullOrEmpty(botNames)) { + ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames)); + return null; + } + + HashSet bots = Bot.GetBots(botNames); + if ((bots == null) || (bots.Count == 0)) { + return ASF.IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null; + } + + IEnumerable> tasks = bots.Select(bot => bot.Commands.ResponsePause(steamID, sticky, timeout)); + 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 ResponsePlay(ulong steamID, IEnumerable gameIDs, string gameName = null) { + if ((steamID == 0) || (gameIDs == null)) { + Bot.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(gameIDs)); + return null; + } + + if (!Bot.IsMaster(steamID)) { + return null; + } + + if (!Bot.IsConnectedAndLoggedOn) { + return FormatBotResponse(Strings.BotNotConnected); + } + + if (!Bot.CardsFarmer.Paused) { + await Bot.CardsFarmer.Pause(false).ConfigureAwait(false); + } + + await Bot.ArchiHandler.PlayGames(gameIDs, gameName).ConfigureAwait(false); + return FormatBotResponse(Strings.Done); + } + + private async Task ResponsePlay(ulong steamID, string targetGameIDs) { + if ((steamID == 0) || string.IsNullOrEmpty(targetGameIDs)) { + ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(targetGameIDs)); + return null; + } + + if (!Bot.IsMaster(steamID)) { + return null; + } + + if (!Bot.IsConnectedAndLoggedOn) { + return FormatBotResponse(Strings.BotNotConnected); + } + + string[] games = targetGameIDs.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + + if (games.Length == 0) { + return FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(games))); + } + + HashSet gamesToPlay = new HashSet(); + StringBuilder gameName = new StringBuilder(); + + foreach (string game in games) { + if (!uint.TryParse(game, out uint gameID) || (gameID == 0)) { + gameName.AppendLine((gameName.Length > 0 ? " " : "") + game); + continue; + } + + if (gamesToPlay.Count >= ArchiHandler.MaxGamesPlayedConcurrently) { + continue; + } + + gamesToPlay.Add(gameID); + } + + return await ResponsePlay(steamID, gamesToPlay, gameName.ToString()).ConfigureAwait(false); + } + + private static async Task ResponsePlay(ulong steamID, string botNames, string targetGameIDs) { + if ((steamID == 0) || string.IsNullOrEmpty(botNames) || string.IsNullOrEmpty(targetGameIDs)) { + ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames) + " || " + nameof(targetGameIDs)); + return null; + } + + HashSet bots = Bot.GetBots(botNames); + if ((bots == null) || (bots.Count == 0)) { + return ASF.IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null; + } + + IEnumerable> tasks = bots.Select(bot => bot.Commands.ResponsePlay(steamID, targetGameIDs)); + 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 ResponsePrivacy(ulong steamID, string privacySettingsText) { + if ((steamID == 0) || string.IsNullOrEmpty(privacySettingsText)) { + Bot.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(privacySettingsText)); + return null; + } + + if (!Bot.IsMaster(steamID)) { + return null; + } + + if (!Bot.IsConnectedAndLoggedOn) { + return FormatBotResponse(Strings.BotNotConnected); + } + + string[] privacySettingsArgs = privacySettingsText.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + + if (privacySettingsArgs.Length == 0) { + return FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(privacySettingsArgs))); + } + + // There are only 6 privacy settings + if (privacySettingsArgs.Length > 6) { + return FormatBotResponse(string.Format(Strings.ErrorIsInvalid, nameof(privacySettingsArgs))); + } + + Steam.UserPrivacy.PrivacySettings.EPrivacySetting profile = Steam.UserPrivacy.PrivacySettings.EPrivacySetting.Private; + Steam.UserPrivacy.PrivacySettings.EPrivacySetting ownedGames = Steam.UserPrivacy.PrivacySettings.EPrivacySetting.Private; + Steam.UserPrivacy.PrivacySettings.EPrivacySetting playtime = Steam.UserPrivacy.PrivacySettings.EPrivacySetting.Private; + Steam.UserPrivacy.PrivacySettings.EPrivacySetting inventory = Steam.UserPrivacy.PrivacySettings.EPrivacySetting.Private; + Steam.UserPrivacy.PrivacySettings.EPrivacySetting inventoryGifts = Steam.UserPrivacy.PrivacySettings.EPrivacySetting.Private; + Steam.UserPrivacy.ECommentPermission comments = Steam.UserPrivacy.ECommentPermission.Private; + + // Converting digits to enum + for (byte index = 0; index < privacySettingsArgs.Length; index++) { + if (!Enum.TryParse(privacySettingsArgs[index], true, out Steam.UserPrivacy.PrivacySettings.EPrivacySetting privacySetting) || (privacySetting == Steam.UserPrivacy.PrivacySettings.EPrivacySetting.Unknown) || !Enum.IsDefined(typeof(Steam.UserPrivacy.PrivacySettings.EPrivacySetting), privacySetting)) { + return FormatBotResponse(string.Format(Strings.ErrorIsInvalid, nameof(privacySettingsArgs))); + } + + // Child setting can't be less restrictive than its parent + switch (index) { + case 0: // Profile + profile = privacySetting; + break; + case 1: // OwnedGames, child of Profile + if (profile < privacySetting) { + return FormatBotResponse(string.Format(Strings.ErrorIsInvalid, nameof(ownedGames))); + } + + ownedGames = privacySetting; + break; + case 2: // Playtime, child of OwnedGames + if (ownedGames < privacySetting) { + return FormatBotResponse(string.Format(Strings.ErrorIsInvalid, nameof(playtime))); + } + + playtime = privacySetting; + break; + case 3: // Inventory, child of Profile + if (profile < privacySetting) { + return FormatBotResponse(string.Format(Strings.ErrorIsInvalid, nameof(inventory))); + } + + inventory = privacySetting; + break; + case 4: // InventoryGifts, child of Inventory + if (inventory < privacySetting) { + return FormatBotResponse(string.Format(Strings.ErrorIsInvalid, nameof(inventoryGifts))); + } + + inventoryGifts = privacySetting; + break; + case 5: // Comments, child of Profile + if (profile < privacySetting) { + return FormatBotResponse(string.Format(Strings.ErrorIsInvalid, nameof(comments))); + } + + // Comments use different numbers than everything else, but we want to have this command consistent for end-user, so we'll map them + switch (privacySetting) { + case Steam.UserPrivacy.PrivacySettings.EPrivacySetting.FriendsOnly: + comments = Steam.UserPrivacy.ECommentPermission.FriendsOnly; + break; + case Steam.UserPrivacy.PrivacySettings.EPrivacySetting.Private: + comments = Steam.UserPrivacy.ECommentPermission.Private; + break; + case Steam.UserPrivacy.PrivacySettings.EPrivacySetting.Public: + comments = Steam.UserPrivacy.ECommentPermission.Public; + break; + default: + Bot.ArchiLogger.LogGenericError(string.Format(Strings.WarningUnknownValuePleaseReport, nameof(privacySetting), privacySetting)); + return FormatBotResponse(string.Format(Strings.ErrorIsInvalid, nameof(privacySetting))); + } + + break; + default: + Bot.ArchiLogger.LogGenericError(string.Format(Strings.WarningUnknownValuePleaseReport, nameof(index), index)); + return FormatBotResponse(string.Format(Strings.ErrorIsInvalid, nameof(index))); + } + } + + Steam.UserPrivacy userPrivacy = new Steam.UserPrivacy(new Steam.UserPrivacy.PrivacySettings(profile, ownedGames, playtime, inventory, inventoryGifts), comments); + return FormatBotResponse(await Bot.ArchiWebHandler.ChangePrivacySettings(userPrivacy).ConfigureAwait(false) ? Strings.Success : Strings.WarningFailed); + } + + private async Task ResponsePrivacy(ulong steamID, string botNames, string privacySettingsText) { + if ((steamID == 0) || string.IsNullOrEmpty(botNames) || string.IsNullOrEmpty(privacySettingsText)) { + ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames) + " || " + nameof(privacySettingsText)); + return null; + } + + HashSet bots = Bot.GetBots(botNames); + if ((bots == null) || (bots.Count == 0)) { + return ASF.IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null; + } + + IEnumerable> tasks = bots.Select(bot => bot.Commands.ResponsePrivacy(steamID, privacySettingsText)); + 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; + } + + [SuppressMessage("ReSharper", "FunctionComplexityOverflow")] + private async Task ResponseRedeem(ulong steamID, string keysText, ERedeemFlags redeemFlags = ERedeemFlags.None) { + if ((steamID == 0) || string.IsNullOrEmpty(keysText)) { + Bot.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(keysText)); + return null; + } + + if (!IsOperator(steamID)) { + return null; + } + + if (!Bot.IsConnectedAndLoggedOn) { + return FormatBotResponse(Strings.BotNotConnected); + } + + string[] keys = keysText.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + + if (keys.Length == 0) { + return FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(keys))); + } + + bool forward = !redeemFlags.HasFlag(ERedeemFlags.SkipForwarding) && (redeemFlags.HasFlag(ERedeemFlags.ForceForwarding) || Bot.BotConfig.RedeemingPreferences.HasFlag(BotConfig.ERedeemingPreferences.Forwarding)); + bool distribute = !redeemFlags.HasFlag(ERedeemFlags.SkipDistributing) && (redeemFlags.HasFlag(ERedeemFlags.ForceDistributing) || Bot.BotConfig.RedeemingPreferences.HasFlag(BotConfig.ERedeemingPreferences.Distributing)); + bool keepMissingGames = !redeemFlags.HasFlag(ERedeemFlags.SkipKeepMissingGames) && (redeemFlags.HasFlag(ERedeemFlags.ForceKeepMissingGames) || Bot.BotConfig.RedeemingPreferences.HasFlag(BotConfig.ERedeemingPreferences.KeepMissingGames)); + + HashSet pendingKeys = keys.ToHashSet(); + HashSet unusedKeys = pendingKeys.ToHashSet(); + + StringBuilder response = new StringBuilder(); + + using (HashSet.Enumerator keysEnumerator = pendingKeys.GetEnumerator()) { + HashSet rateLimitedBots = new HashSet(); + string key = keysEnumerator.MoveNext() ? keysEnumerator.Current : null; // Initial key + + while (!string.IsNullOrEmpty(key)) { + string startingKey = key; + + using (IEnumerator botsEnumerator = Bot.Bots.Where(bot => (bot.Value != Bot) && !rateLimitedBots.Contains(bot.Value) && bot.Value.IsConnectedAndLoggedOn && bot.Value.Commands.IsOperator(steamID)).OrderBy(bot => bot.Key).Select(bot => bot.Value).GetEnumerator()) { + Bot currentBot = Bot; + + while (!string.IsNullOrEmpty(key) && (currentBot != null)) { + if (redeemFlags.HasFlag(ERedeemFlags.Validate) && !Utilities.IsValidCdKey(key)) { + key = keysEnumerator.MoveNext() ? keysEnumerator.Current : null; // Next key + continue; // Keep current bot + } + + if ((currentBot == Bot) && (redeemFlags.HasFlag(ERedeemFlags.SkipInitial) || rateLimitedBots.Contains(currentBot))) { + currentBot = null; // Either bot will be changed, or loop aborted + } else { + ArchiHandler.PurchaseResponseCallback result = await currentBot.Actions.RedeemKey(key).ConfigureAwait(false); + if (result == null) { + response.AppendLine(FormatBotResponse(string.Format(Strings.BotRedeem, key, EPurchaseResultDetail.Timeout), currentBot.BotName)); + currentBot = null; // Either bot will be changed, or loop aborted + } else { + if (result.PurchaseResultDetail == EPurchaseResultDetail.CannotRedeemCodeFromClient) { + // If it's a wallet code, we try to redeem it first, then handle the inner result as our primary one + (EResult Result, EPurchaseResultDetail? PurchaseResult)? walletResult = await currentBot.ArchiWebHandler.RedeemWalletKey(key).ConfigureAwait(false); + + if (walletResult != null) { + result.Result = walletResult.Value.Result; + result.PurchaseResultDetail = walletResult.Value.PurchaseResult.GetValueOrDefault(walletResult.Value.Result == EResult.OK ? EPurchaseResultDetail.NoDetail : EPurchaseResultDetail.BadActivationCode); // BadActivationCode is our smart guess in this case + } else { + result.Result = EResult.Timeout; + result.PurchaseResultDetail = EPurchaseResultDetail.Timeout; + } + } + + switch (result.PurchaseResultDetail) { + case EPurchaseResultDetail.BadActivationCode: + case EPurchaseResultDetail.CannotRedeemCodeFromClient: + case EPurchaseResultDetail.DuplicateActivationCode: + case EPurchaseResultDetail.NoDetail: // OK + case EPurchaseResultDetail.Timeout: + if ((result.Items != null) && (result.Items.Count > 0)) { + response.AppendLine(FormatBotResponse(string.Format(Strings.BotRedeemWithItems, key, result.Result + "/" + result.PurchaseResultDetail, string.Join(", ", result.Items)), currentBot.BotName)); + } else { + response.AppendLine(FormatBotResponse(string.Format(Strings.BotRedeem, key, result.Result + "/" + result.PurchaseResultDetail), currentBot.BotName)); + } + + if ((result.Result != EResult.Timeout) && (result.PurchaseResultDetail != EPurchaseResultDetail.Timeout)) { + unusedKeys.Remove(key); + } + + key = keysEnumerator.MoveNext() ? keysEnumerator.Current : null; // Next key + + if (result.PurchaseResultDetail == EPurchaseResultDetail.NoDetail) { + break; // Next bot (if needed) + } + + continue; // Keep current bot + case EPurchaseResultDetail.AccountLocked: + case EPurchaseResultDetail.AlreadyPurchased: + case EPurchaseResultDetail.DoesNotOwnRequiredApp: + case EPurchaseResultDetail.RestrictedCountry: + if ((result.Items != null) && (result.Items.Count > 0)) { + response.AppendLine(FormatBotResponse(string.Format(Strings.BotRedeemWithItems, key, result.Result + "/" + result.PurchaseResultDetail, string.Join(", ", result.Items)), currentBot.BotName)); + } else { + response.AppendLine(FormatBotResponse(string.Format(Strings.BotRedeem, key, result.Result + "/" + result.PurchaseResultDetail), currentBot.BotName)); + } + + if (!forward || (keepMissingGames && (result.PurchaseResultDetail != EPurchaseResultDetail.AlreadyPurchased))) { + key = keysEnumerator.MoveNext() ? keysEnumerator.Current : null; // Next key + break; // Next bot (if needed) + } + + if (distribute) { + break; // Next bot, without changing key + } + + Dictionary items = result.Items ?? new Dictionary(); + + bool alreadyHandled = false; + foreach (Bot innerBot in Bot.Bots.Where(bot => (bot.Value != currentBot) && (!redeemFlags.HasFlag(ERedeemFlags.SkipInitial) || (bot.Value != Bot)) && !rateLimitedBots.Contains(bot.Value) && bot.Value.IsConnectedAndLoggedOn && bot.Value.Commands.IsOperator(steamID) && ((items.Count == 0) || items.Keys.Any(packageID => !bot.Value.OwnedPackageIDs.ContainsKey(packageID)))).OrderBy(bot => bot.Key).Select(bot => bot.Value)) { + ArchiHandler.PurchaseResponseCallback otherResult = await innerBot.Actions.RedeemKey(key).ConfigureAwait(false); + if (otherResult == null) { + response.AppendLine(FormatBotResponse(string.Format(Strings.BotRedeem, key, EResult.Timeout + "/" + EPurchaseResultDetail.Timeout), innerBot.BotName)); + continue; + } + + switch (otherResult.PurchaseResultDetail) { + case EPurchaseResultDetail.BadActivationCode: + case EPurchaseResultDetail.DuplicateActivationCode: + case EPurchaseResultDetail.NoDetail: // OK + alreadyHandled = true; // This key is already handled, as we either redeemed it or we're sure it's dupe/invalid + unusedKeys.Remove(key); + break; + case EPurchaseResultDetail.RateLimited: + rateLimitedBots.Add(innerBot); + break; + } + + if ((otherResult.Items != null) && (otherResult.Items.Count > 0)) { + response.AppendLine(FormatBotResponse(string.Format(Strings.BotRedeemWithItems, key, otherResult.Result + "/" + otherResult.PurchaseResultDetail, string.Join(", ", otherResult.Items)), innerBot.BotName)); + } else { + response.AppendLine(FormatBotResponse(string.Format(Strings.BotRedeem, key, otherResult.Result + "/" + otherResult.PurchaseResultDetail), innerBot.BotName)); + } + + if (alreadyHandled) { + break; + } + + if (otherResult.Items == null) { + continue; + } + + foreach (KeyValuePair item in otherResult.Items.Where(item => !items.ContainsKey(item.Key))) { + items[item.Key] = item.Value; + } + } + + key = keysEnumerator.MoveNext() ? keysEnumerator.Current : null; // Next key + break; // Next bot (if needed) + case EPurchaseResultDetail.RateLimited: + rateLimitedBots.Add(currentBot); + goto case EPurchaseResultDetail.AccountLocked; + default: + ASF.ArchiLogger.LogGenericError(string.Format(Strings.WarningUnknownValuePleaseReport, nameof(result.PurchaseResultDetail), result.PurchaseResultDetail)); + + if ((result.Items != null) && (result.Items.Count > 0)) { + response.AppendLine(FormatBotResponse(string.Format(Strings.BotRedeemWithItems, key, result.Result + "/" + result.PurchaseResultDetail, string.Join(", ", result.Items)), currentBot.BotName)); + } else { + response.AppendLine(FormatBotResponse(string.Format(Strings.BotRedeem, key, result.Result + "/" + result.PurchaseResultDetail), currentBot.BotName)); + } + + unusedKeys.Remove(key); + + key = keysEnumerator.MoveNext() ? keysEnumerator.Current : null; // Next key + break; // Next bot (if needed) + } + } + } + + // We want to change bot in two cases: + // a) When we have distribution enabled, obviously + // b) When we're skipping initial bot AND we have forwarding enabled, otherwise we won't get down to other accounts + if (distribute || (forward && redeemFlags.HasFlag(ERedeemFlags.SkipInitial))) { + currentBot = botsEnumerator.MoveNext() ? botsEnumerator.Current : null; + } + } + } + + if (key == startingKey) { + // We ran out of bots to try for this key, so change it to avoid infinite loop + key = keysEnumerator.MoveNext() ? keysEnumerator.Current : null; // Next key + } + } + } + + if (unusedKeys.Count > 0) { + response.AppendLine(FormatBotResponse(string.Format(Strings.UnusedKeys, string.Join(", ", unusedKeys)))); + } + + return response.Length > 0 ? response.ToString() : null; + } + + private static async Task ResponseRedeem(ulong steamID, string botNames, string keys, ERedeemFlags redeemFlags = ERedeemFlags.None) { + if ((steamID == 0) || string.IsNullOrEmpty(botNames) || string.IsNullOrEmpty(keys)) { + ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames) + " || " + nameof(keys)); + return null; + } + + HashSet bots = Bot.GetBots(botNames); + if ((bots == null) || (bots.Count == 0)) { + return ASF.IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null; + } + + IEnumerable> tasks = bots.Select(bot => bot.Commands.ResponseRedeem(steamID, keys, redeemFlags)); + 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 string ResponseRestart(ulong steamID) { + if (steamID == 0) { + ASF.ArchiLogger.LogNullError(nameof(steamID)); + return null; + } + + if (!ASF.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.Restart().ConfigureAwait(false); + } + ); + + return FormatStaticResponse(Strings.Done); + } + + private string ResponseResume(ulong steamID) { + if (steamID == 0) { + Bot.ArchiLogger.LogNullError(nameof(steamID)); + return null; + } + + if (!Bot.IsFamilySharing(steamID)) { + return null; + } + + if (!Bot.IsConnectedAndLoggedOn) { + return FormatBotResponse(Strings.BotNotConnected); + } + + if (!Bot.CardsFarmer.Paused) { + return FormatBotResponse(Strings.BotAutomaticIdlingResumedAlready); + } + + if (CardsFarmerResumeTimer != null) { + CardsFarmerResumeTimer.Dispose(); + CardsFarmerResumeTimer = null; + } + + Bot.StopFamilySharingInactivityTimer(); + Utilities.InBackground(() => Bot.CardsFarmer.Resume(true)); + return FormatBotResponse(Strings.BotAutomaticIdlingNowResumed); + } + + private static async Task ResponseResume(ulong steamID, string botNames) { + if ((steamID == 0) || string.IsNullOrEmpty(botNames)) { + ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames)); + return null; + } + + HashSet bots = Bot.GetBots(botNames); + if ((bots == null) || (bots.Count == 0)) { + return ASF.IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null; + } + + IEnumerable> tasks = bots.Select(bot => Task.Run(() => bot.Commands.ResponseResume(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 string ResponseStart(ulong steamID) { + if (steamID == 0) { + Bot.ArchiLogger.LogNullError(nameof(steamID)); + return null; + } + + if (!Bot.IsMaster(steamID)) { + return null; + } + + if (Bot.KeepRunning) { + return FormatBotResponse(Strings.BotAlreadyRunning); + } + + Bot.SkipFirstShutdown = true; + Utilities.InBackground(Bot.Start); + return FormatBotResponse(Strings.Done); + } + + private static async Task ResponseStart(ulong steamID, string botNames) { + if ((steamID == 0) || string.IsNullOrEmpty(botNames)) { + ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames)); + return null; + } + + HashSet bots = Bot.GetBots(botNames); + if ((bots == null) || (bots.Count == 0)) { + return ASF.IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null; + } + + IEnumerable> tasks = bots.Select(bot => Task.Run(() => bot.Commands.ResponseStart(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 string ResponseStats(ulong steamID) { + if (steamID == 0) { + Bot.ArchiLogger.LogNullError(nameof(steamID)); + return null; + } + + if (!ASF.IsOwner(steamID)) { + return null; + } + + ushort memoryInMegabytes = (ushort) (GC.GetTotalMemory(false) / 1024 / 1024); + return FormatBotResponse(string.Format(Strings.BotStats, memoryInMegabytes)); + } + + private (string Response, Bot Bot) ResponseStatus(ulong steamID) { + if (steamID == 0) { + Bot.ArchiLogger.LogNullError(nameof(steamID)); + return (null, Bot); + } + + if (!Bot.IsFamilySharing(steamID)) { + return (null, Bot); + } + + if (!Bot.IsConnectedAndLoggedOn) { + return (FormatBotResponse(Bot.KeepRunning ? Strings.BotStatusConnecting : Strings.BotStatusNotRunning), Bot); + } + + if (Bot.PlayingBlocked) { + return (FormatBotResponse(Strings.BotStatusPlayingNotAvailable), Bot); + } + + if (Bot.CardsFarmer.Paused) { + return (FormatBotResponse(Strings.BotStatusPaused), Bot); + } + + if (Bot.IsAccountLimited) { + return (FormatBotResponse(Strings.BotStatusLimited), Bot); + } + + if (Bot.IsAccountLocked) { + return (FormatBotResponse(Strings.BotStatusLocked), Bot); + } + + if (!Bot.CardsFarmer.NowFarming || (Bot.CardsFarmer.CurrentGamesFarming.Count == 0)) { + return (FormatBotResponse(Strings.BotStatusNotIdling), Bot); + } + + if (Bot.CardsFarmer.CurrentGamesFarming.Count > 1) { + return (FormatBotResponse(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())), Bot); + } + + CardsFarmer.Game soloGame = Bot.CardsFarmer.CurrentGamesFarming.First(); + return (FormatBotResponse(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())), Bot); + } + + 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 = Bot.GetBots(botNames); + if ((bots == null) || (bots.Count == 0)) { + return ASF.IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null; + } + + IEnumerable> tasks = bots.Select(bot => Task.Run(() => bot.Commands.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) { + Bot.ArchiLogger.LogNullError(nameof(steamID)); + return null; + } + + if (!Bot.IsMaster(steamID)) { + return null; + } + + if (!Bot.KeepRunning) { + return FormatBotResponse(Strings.BotAlreadyStopped); + } + + Bot.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 = Bot.GetBots(botNames); + if ((bots == null) || (bots.Count == 0)) { + return ASF.IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null; + } + + IEnumerable> tasks = bots.Select(bot => Task.Run(() => bot.Commands.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)); + return null; + } + + if (!Bot.IsMaster(steamID)) { + return null; + } + + if (!Bot.IsConnectedAndLoggedOn) { + return FormatBotResponse(Strings.BotNotConnected); + } + + if (!Bot.Bots.TryGetValue(botNameTo, out Bot targetBot)) { + return ASF.IsOwner(steamID) ? FormatBotResponse(string.Format(Strings.BotNotFound, botNameTo)) : null; + } + + if (!targetBot.IsConnectedAndLoggedOn) { + return FormatBotResponse(Strings.BotNotConnected); + } + + if (targetBot.CachedSteamID == Bot.CachedSteamID) { + return FormatBotResponse(Strings.BotSendingTradeToYourself); + } + + string[] modes = mode.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + + if (modes.Length == 0) { + return FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(modes))); + } + + HashSet transferTypes = new HashSet(); + + foreach (string singleMode in modes) { + switch (singleMode.ToUpper()) { + case "A": + case "ALL": + foreach (Steam.Asset.EType type in Enum.GetValues(typeof(Steam.Asset.EType))) { + transferTypes.Add(type); + } + + break; + case "BG": + case "BACKGROUND": + transferTypes.Add(Steam.Asset.EType.ProfileBackground); + break; + case "BO": + case "BOOSTER": + transferTypes.Add(Steam.Asset.EType.BoosterPack); + break; + case "C": + case "CARD": + transferTypes.Add(Steam.Asset.EType.TradingCard); + break; + case "E": + case "EMOTICON": + transferTypes.Add(Steam.Asset.EType.Emoticon); + break; + case "F": + case "FOIL": + transferTypes.Add(Steam.Asset.EType.FoilTradingCard); + break; + case "G": + case "GEMS": + transferTypes.Add(Steam.Asset.EType.SteamGems); + break; + case "U": + case "UNKNOWN": + transferTypes.Add(Steam.Asset.EType.Unknown); + break; + default: + return FormatBotResponse(string.Format(Strings.ErrorIsInvalid, mode)); + } + } + + (bool success, string output) = await Bot.Actions.Loot(targetSteamID: targetBot.CachedSteamID, wantedTypes: transferTypes).ConfigureAwait(false); + return FormatBotResponse(success ? output : string.Format(Strings.WarningFailedWithError, output)); + } + + private static async Task ResponseTransfer(ulong steamID, string botNames, string mode, string botNameTo) { + if ((steamID == 0) || string.IsNullOrEmpty(botNames) || string.IsNullOrEmpty(mode) || string.IsNullOrEmpty(botNameTo)) { + ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames) + " || " + nameof(mode) + " || " + nameof(botNameTo)); + return null; + } + + HashSet bots = Bot.GetBots(botNames); + if ((bots == null) || (bots.Count == 0)) { + return ASF.IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null; + } + + IEnumerable> tasks = bots.Select(bot => bot.Commands.ResponseTransfer(steamID, mode, botNameTo)); + 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 string ResponseUnknown(ulong steamID) { + if (steamID == 0) { + Bot.ArchiLogger.LogNullError(nameof(steamID)); + return null; + } + + return IsOperator(steamID) ? FormatBotResponse(Strings.UnknownCommand) : null; + } + + private async Task ResponseUnpackBoosters(ulong steamID) { + if (steamID == 0) { + Bot.ArchiLogger.LogNullError(nameof(steamID)); + return null; + } + + if (!Bot.IsMaster(steamID)) { + return null; + } + + if (!Bot.IsConnectedAndLoggedOn) { + return FormatBotResponse(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(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(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 = Bot.GetBots(botNames); + if ((bots == null) || (bots.Count == 0)) { + return ASF.IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null; + } + + IEnumerable> tasks = bots.Select(bot => bot.Commands.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 (!ASF.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) { + Bot.ArchiLogger.LogNullError(nameof(steamID)); + return null; + } + + return IsOperator(steamID) ? FormatBotResponse(string.Format(Strings.BotVersion, SharedInfo.ASF, SharedInfo.Version)) : null; + } + + [Flags] + private enum ERedeemFlags : byte { + None = 0, + Validate = 1, + ForceForwarding = 2, + SkipForwarding = 4, + ForceDistributing = 8, + SkipDistributing = 16, + SkipInitial = 32, + ForceKeepMissingGames = 64, + SkipKeepMissingGames = 128 + } + } +} diff --git a/ArchiSteamFarm/IPC/Controllers/Api/CommandController.cs b/ArchiSteamFarm/IPC/Controllers/Api/CommandController.cs index 18fbefd71..7b0245fd7 100644 --- a/ArchiSteamFarm/IPC/Controllers/Api/CommandController.cs +++ b/ArchiSteamFarm/IPC/Controllers/Api/CommandController.cs @@ -50,7 +50,7 @@ namespace ArchiSteamFarm.IPC.Controllers.Api { command = Program.GlobalConfig.CommandPrefix + command; } - string content = await targetBot.Response(Program.GlobalConfig.SteamOwnerID, command).ConfigureAwait(false); + string content = await targetBot.Commands.Response(Program.GlobalConfig.SteamOwnerID, command).ConfigureAwait(false); return Ok(new GenericResponse(content)); } } diff --git a/ArchiSteamFarm/Trading.cs b/ArchiSteamFarm/Trading.cs index 025c38b65..8d61bde64 100644 --- a/ArchiSteamFarm/Trading.cs +++ b/ArchiSteamFarm/Trading.cs @@ -216,13 +216,13 @@ namespace ArchiSteamFarm { if (acceptedWithItemLoseTradeIDs.Count > 0) { // Give Steam network some time to generate confirmations await Task.Delay(3000).ConfigureAwait(false); - await Bot.AcceptConfirmations(true, Steam.ConfirmationDetails.EType.Trade, 0, acceptedWithItemLoseTradeIDs).ConfigureAwait(false); + await Bot.Actions.AcceptConfirmations(true, Steam.ConfirmationDetails.EType.Trade, 0, acceptedWithItemLoseTradeIDs).ConfigureAwait(false); } } - if (results.Any(result => (result != null) && ((result.Result == ParseTradeResult.EResult.AcceptedWithItemLose) || (result.Result == ParseTradeResult.EResult.AcceptedWithoutItemLose)))) { + if (results.Any(result => (result != null) && ((result.Result == ParseTradeResult.EResult.AcceptedWithItemLose) || (result.Result == ParseTradeResult.EResult.AcceptedWithoutItemLose))) && Bot.BotConfig.SendOnFarmingFinished) { // If we finished a trade, perform a loot if user wants to do so - await Bot.LootIfNeeded().ConfigureAwait(false); + await Bot.Actions.Loot().ConfigureAwait(false); } } diff --git a/ArchiSteamFarm/Utilities.cs b/ArchiSteamFarm/Utilities.cs index 1bc2f879a..09dc8c9f9 100644 --- a/ArchiSteamFarm/Utilities.cs +++ b/ArchiSteamFarm/Utilities.cs @@ -25,6 +25,7 @@ using System.Globalization; using System.Linq; using System.Net; using System.Text; +using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using Humanizer; @@ -105,6 +106,15 @@ namespace ArchiSteamFarm { Task.Factory.StartNew(function, CancellationToken.None, options, TaskScheduler.Default); } + internal static bool IsValidCdKey(string key) { + if (string.IsNullOrEmpty(key)) { + ASF.ArchiLogger.LogNullError(nameof(key)); + return false; + } + + return Regex.IsMatch(key, @"^[0-9A-Z]{4,7}-[0-9A-Z]{4,7}-[0-9A-Z]{4,7}(?:(?:-[0-9A-Z]{4,7})?(?:-[0-9A-Z]{4,7}))?$", RegexOptions.CultureInvariant | RegexOptions.IgnoreCase); + } + internal static bool IsValidHexadecimalString(string text) { if (string.IsNullOrEmpty(text)) { ASF.ArchiLogger.LogNullError(nameof(text));