From e7f517aecfa094d9d127f30b4cd647245e9977e8 Mon Sep 17 00:00:00 2001 From: JustArchi Date: Mon, 8 Jan 2018 01:48:57 +0100 Subject: [PATCH] Follow-up code cleanup after #727 --- ArchiSteamFarm/ArchiWebHandler.cs | 214 ++++++++++++------------- ArchiSteamFarm/Bot.cs | 251 ++++++++++++++++-------------- ArchiSteamFarm/Statistics.cs | 2 +- ArchiSteamFarm/Trading.cs | 2 +- ArchiSteamFarm/Utilities.cs | 4 +- 5 files changed, 243 insertions(+), 230 deletions(-) diff --git a/ArchiSteamFarm/ArchiWebHandler.cs b/ArchiSteamFarm/ArchiWebHandler.cs index 66885934b..9e7034c28 100644 --- a/ArchiSteamFarm/ArchiWebHandler.cs +++ b/ArchiSteamFarm/ArchiWebHandler.cs @@ -443,6 +443,113 @@ namespace ArchiSteamFarm { return await WebBrowser.UrlGetToHtmlDocumentRetry(request).ConfigureAwait(false); } + [SuppressMessage("ReSharper", "FunctionComplexityOverflow")] + internal async Task> GetMyInventory(bool tradableOnly = false, uint appID = Steam.Asset.SteamAppID, byte contextID = Steam.Asset.SteamCommunityContextID, IReadOnlyCollection wantedTypes = null, IReadOnlyCollection wantedRealAppIDs = null) { + if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) { + return null; + } + + HashSet result = new HashSet(); + + // 5000 is maximum allowed count per single request + string request = SteamCommunityURL + "/inventory/" + SteamID + "/" + appID + "/" + contextID + "?l=english&count=5000"; + ulong startAssetID = 0; + + await InventorySemaphore.WaitAsync().ConfigureAwait(false); + + try { + while (true) { + Steam.InventoryResponse response = await WebBrowser.UrlGetToJsonResultRetry(request + (startAssetID > 0 ? "&start_assetid=" + startAssetID : "")).ConfigureAwait(false); + + if (response == null) { + return null; + } + + if (!response.Success) { + Bot.ArchiLogger.LogGenericWarning(!string.IsNullOrEmpty(response.Error) ? string.Format(Strings.WarningFailedWithError, response.Error) : Strings.WarningFailed); + return null; + } + + if (response.TotalInventoryCount == 0) { + // Empty inventory + return result; + } + + if ((response.Assets == null) || (response.Assets.Count == 0) || (response.Descriptions == null) || (response.Descriptions.Count == 0)) { + Bot.ArchiLogger.LogNullError(nameof(response.Assets) + " || " + nameof(response.Descriptions)); + return null; + } + + Dictionary descriptionMap = new Dictionary(); + foreach (Steam.InventoryResponse.Description description in response.Descriptions.Where(description => description != null)) { + if (description.ClassID == 0) { + Bot.ArchiLogger.LogNullError(nameof(description.ClassID)); + return null; + } + + if (descriptionMap.ContainsKey(description.ClassID)) { + continue; + } + + uint realAppID = 0; + + if (!string.IsNullOrEmpty(description.MarketHashName)) { + realAppID = GetAppIDFromMarketHashName(description.MarketHashName); + } + + if (realAppID == 0) { + realAppID = description.AppID; + } + + Steam.Asset.EType type = Steam.Asset.EType.Unknown; + + if (!string.IsNullOrEmpty(description.Type)) { + type = GetItemType(description.Type); + } + + descriptionMap[description.ClassID] = (realAppID, type, description.Tradable); + } + + foreach (Steam.Asset asset in response.Assets.Where(asset => asset != null)) { + if (descriptionMap.TryGetValue(asset.ClassID, out (uint RealAppID, Steam.Asset.EType Type, bool Tradable) description)) { + if (tradableOnly && !description.Tradable) { + continue; + } + + asset.RealAppID = description.RealAppID; + asset.Type = description.Type; + } + + if ((wantedTypes?.Contains(asset.Type) == false) || (wantedRealAppIDs?.Contains(asset.RealAppID) == false)) { + continue; + } + + result.Add(asset); + } + + if (!response.MoreItems) { + return result; + } + + if (response.LastAssetID == 0) { + Bot.ArchiLogger.LogNullError(nameof(response.LastAssetID)); + return null; + } + + startAssetID = response.LastAssetID; + } + } finally { + if (Program.GlobalConfig.InventoryLimiterDelay == 0) { + InventorySemaphore.Release(); + } else { + Task.Run(async () => { + await Task.Delay(Program.GlobalConfig.InventoryLimiterDelay * 1000).ConfigureAwait(false); + InventorySemaphore.Release(); + }).Forget(); + } + } + } + internal async Task> GetMyOwnedGames() { if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) { return null; @@ -482,113 +589,6 @@ namespace ArchiSteamFarm { return result; } - [SuppressMessage("ReSharper", "FunctionComplexityOverflow")] - internal async Task> GetMySteamInventory(bool tradableOnly = false, IReadOnlyCollection wantedTypes = null, IReadOnlyCollection wantedRealAppIDs = null, uint inventoryAppID = Steam.Asset.SteamAppID, byte contextID = Steam.Asset.SteamCommunityContextID) { - if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) { - return null; - } - - HashSet result = new HashSet(); - - // 5000 is maximum allowed count per single request - string request = SteamCommunityURL + "/inventory/" + SteamID + "/" + inventoryAppID + "/" + contextID + "?l=english&count=5000"; - ulong startAssetID = 0; - - await InventorySemaphore.WaitAsync().ConfigureAwait(false); - - try { - while (true) { - Steam.InventoryResponse response = await WebBrowser.UrlGetToJsonResultRetry(request + (startAssetID > 0 ? "&start_assetid=" + startAssetID : "")).ConfigureAwait(false); - - if (response == null) { - return null; - } - - if (!response.Success) { - Bot.ArchiLogger.LogGenericWarning(!string.IsNullOrEmpty(response.Error) ? string.Format(Strings.WarningFailedWithError, response.Error) : Strings.WarningFailed); - return null; - } - - if (response.TotalInventoryCount == 0) { - // Empty inventory - return result; - } - - if ((response.Assets == null) || (response.Assets.Count == 0) || (response.Descriptions == null) || (response.Descriptions.Count == 0)) { - Bot.ArchiLogger.LogNullError(nameof(response.Assets) + " || " + nameof(response.Descriptions)); - return null; - } - - Dictionary descriptionMap = new Dictionary(); - foreach (Steam.InventoryResponse.Description description in response.Descriptions.Where(description => description != null)) { - if (description.ClassID == 0) { - Bot.ArchiLogger.LogNullError(nameof(description.ClassID)); - return null; - } - - if (descriptionMap.ContainsKey(description.ClassID)) { - continue; - } - - uint appID = 0; - - if (!string.IsNullOrEmpty(description.MarketHashName)) { - appID = GetAppIDFromMarketHashName(description.MarketHashName); - } - - if (appID == 0) { - appID = description.AppID; - } - - Steam.Asset.EType type = Steam.Asset.EType.Unknown; - - if (!string.IsNullOrEmpty(description.Type)) { - type = GetItemType(description.Type); - } - - descriptionMap[description.ClassID] = (appID, type, description.Tradable); - } - - foreach (Steam.Asset asset in response.Assets.Where(asset => asset != null)) { - if (descriptionMap.TryGetValue(asset.ClassID, out (uint AppID, Steam.Asset.EType Type, bool Tradable) description)) { - if (tradableOnly && !description.Tradable) { - continue; - } - - asset.RealAppID = description.AppID; - asset.Type = description.Type; - } - - if ((wantedTypes?.Contains(asset.Type) == false) || (wantedRealAppIDs?.Contains(asset.RealAppID) == false)) { - continue; - } - - result.Add(asset); - } - - if (!response.MoreItems) { - return result; - } - - if (response.LastAssetID == 0) { - Bot.ArchiLogger.LogNullError(nameof(response.LastAssetID)); - return null; - } - - startAssetID = response.LastAssetID; - } - } finally { - if (Program.GlobalConfig.InventoryLimiterDelay == 0) { - InventorySemaphore.Release(); - } else { - Task.Run(async () => { - await Task.Delay(Program.GlobalConfig.InventoryLimiterDelay * 1000).ConfigureAwait(false); - InventorySemaphore.Release(); - }).Forget(); - } - } - } - internal async Task> GetOwnedGames(ulong steamID) { if (steamID == 0) { Bot.ArchiLogger.LogNullError(nameof(steamID)); diff --git a/ArchiSteamFarm/Bot.cs b/ArchiSteamFarm/Bot.cs index 592d05976..7a9ee6ec9 100755 --- a/ArchiSteamFarm/Bot.cs +++ b/ArchiSteamFarm/Bot.cs @@ -868,7 +868,7 @@ namespace ArchiSteamFarm { goto default; case "!LOOT": return await ResponseLoot(steamID).ConfigureAwait(false); - case "!LOOT^": + case "!LOOT&": return ResponseLootSwitch(steamID); case "!PASSWORD": return ResponsePassword(steamID); @@ -977,13 +977,15 @@ namespace ArchiSteamFarm { return await ResponseLoot(steamID, Utilities.GetArgsString(args, 1, ",")).ConfigureAwait(false); case "!LOOT^": if (args.Length > 3) { - return await ResponseAdvancedLoot(steamID, args[1], args[2], args[3]).ConfigureAwait(false); + return await ResponseAdvancedLoot(steamID, args[1], args[2], Utilities.GetArgsString(args, 3, ",")).ConfigureAwait(false); } if (args.Length > 2) { return await ResponseAdvancedLoot(steamID, args[1], args[2]).ConfigureAwait(false); } + goto default; + case "!LOOT&": return await ResponseLootSwitch(steamID, Utilities.GetArgsString(args, 1, ",")).ConfigureAwait(false); case "!NICKNAME": if (args.Length > 2) { @@ -992,7 +994,7 @@ namespace ArchiSteamFarm { return await ResponseNickname(steamID, args[1]).ConfigureAwait(false); case "!OA": - return await ResponseOwns(steamID, SharedInfo.ASF, Utilities.GetArgsString(args)).ConfigureAwait(false); + return await ResponseOwns(steamID, SharedInfo.ASF, Utilities.GetArgsString(args, 1)).ConfigureAwait(false); case "!OWNS": if (args.Length > 2) { return await ResponseOwns(steamID, args[1], Utilities.GetArgsString(args, 2)).ConfigureAwait(false); @@ -2396,6 +2398,118 @@ namespace ArchiSteamFarm { return responses.Count > 0 ? string.Join("", 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); + } + + lock (LootingSemaphore) { + if (LootingScheduled) { + return FormatBotResponse(Strings.Done); + } + + LootingScheduled = true; + } + + await LootingSemaphore.WaitAsync().ConfigureAwait(false); + + try { + lock (LootingSemaphore) { + LootingScheduled = false; + } + + HashSet inventory = await ArchiWebHandler.GetMyInventory(true, appID, contextID).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(inventory, targetSteamMasterID, 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("", 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)); @@ -3158,117 +3272,6 @@ namespace ArchiSteamFarm { return responses.Count > 0 ? string.Join("", 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 (!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; - } - - 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))); - } - - HashSet inventory = await ArchiWebHandler.GetMySteamInventory(true, null, null, appID, contextID).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(inventory, targetSteamMasterID, 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("", responses) : null; - } - private async Task ResponseLoot(ulong steamID) { if (steamID == 0) { ArchiLogger.LogNullError(nameof(steamID)); @@ -3315,7 +3318,7 @@ namespace ArchiSteamFarm { LootingScheduled = false; } - HashSet inventory = await ArchiWebHandler.GetMySteamInventory(true, BotConfig.LootableTypes).ConfigureAwait(false); + HashSet inventory = await ArchiWebHandler.GetMyInventory(true, wantedTypes: BotConfig.LootableTypes).ConfigureAwait(false); if ((inventory == null) || (inventory.Count == 0)) { return FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(inventory))); } @@ -4428,12 +4431,22 @@ namespace ArchiSteamFarm { } } - if (!LootingSemaphore.Wait(0)) { - return FormatBotResponse(Strings.BotLootingFailed); + lock (LootingSemaphore) { + if (LootingScheduled) { + return FormatBotResponse(Strings.Done); + } + + LootingScheduled = true; } + await LootingSemaphore.WaitAsync().ConfigureAwait(false); + try { - HashSet inventory = await ArchiWebHandler.GetMySteamInventory(true, transferTypes).ConfigureAwait(false); + lock (LootingSemaphore) { + LootingScheduled = false; + } + + HashSet inventory = await ArchiWebHandler.GetMyInventory(true, wantedTypes: transferTypes).ConfigureAwait(false); if ((inventory == null) || (inventory.Count == 0)) { return FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(inventory))); } @@ -4523,7 +4536,7 @@ namespace ArchiSteamFarm { return FormatBotResponse(Strings.BotNotConnected); } - HashSet inventory = await ArchiWebHandler.GetMySteamInventory(false, new HashSet { Steam.Asset.EType.BoosterPack }).ConfigureAwait(false); + HashSet inventory = await ArchiWebHandler.GetMyInventory(false, wantedTypes: new HashSet { Steam.Asset.EType.BoosterPack }).ConfigureAwait(false); if ((inventory == null) || (inventory.Count == 0)) { return FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(inventory))); } diff --git a/ArchiSteamFarm/Statistics.cs b/ArchiSteamFarm/Statistics.cs index f293d4773..8f673157e 100644 --- a/ArchiSteamFarm/Statistics.cs +++ b/ArchiSteamFarm/Statistics.cs @@ -118,7 +118,7 @@ namespace ArchiSteamFarm { return; } - HashSet inventory = await Bot.ArchiWebHandler.GetMySteamInventory(true, Bot.BotConfig.MatchableTypes).ConfigureAwait(false); + HashSet inventory = await Bot.ArchiWebHandler.GetMyInventory(true, wantedTypes: Bot.BotConfig.MatchableTypes).ConfigureAwait(false); // This is actually inventory failure, so we'll stop sending heartbeats but not record it as valid check if (inventory == null) { diff --git a/ArchiSteamFarm/Trading.cs b/ArchiSteamFarm/Trading.cs index 2103097fa..3e72c580c 100644 --- a/ArchiSteamFarm/Trading.cs +++ b/ArchiSteamFarm/Trading.cs @@ -306,7 +306,7 @@ namespace ArchiSteamFarm { } // Now check if it's worth for us to do the trade - HashSet inventory = await Bot.ArchiWebHandler.GetMySteamInventory(false, types, appIDs).ConfigureAwait(false); + HashSet inventory = await Bot.ArchiWebHandler.GetMyInventory(false, wantedTypes: types, wantedRealAppIDs: appIDs).ConfigureAwait(false); if ((inventory == null) || (inventory.Count == 0)) { // If we can't check our inventory when not using MatchEverything, this is a temporary failure Bot.ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorIsEmpty, nameof(inventory))); diff --git a/ArchiSteamFarm/Utilities.cs b/ArchiSteamFarm/Utilities.cs index fbffd0f7a..3365be183 100644 --- a/ArchiSteamFarm/Utilities.cs +++ b/ArchiSteamFarm/Utilities.cs @@ -39,7 +39,7 @@ namespace ArchiSteamFarm { [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static void Forget(this object obj) { } - internal static string GetArgsString(string[] args, byte argsToSkip = 1, string delimiter = " ") { + internal static string GetArgsString(string[] args, byte argsToSkip, string delimiter = " ") { if ((args == null) || (args.Length < argsToSkip) || string.IsNullOrEmpty(delimiter)) { ASF.ArchiLogger.LogNullError(nameof(args) + " || " + nameof(delimiter)); return null; @@ -181,7 +181,7 @@ namespace ArchiSteamFarm { internal static string ToHumanReadable(this TimeSpan timeSpan) => timeSpan.Humanize(3, maxUnit: TimeUnit.Year); - private static string[] GetArgs(string[] args, byte argsToSkip = 1) { + private static string[] GetArgs(string[] args, byte argsToSkip) { if ((args == null) || (args.Length < argsToSkip)) { ASF.ArchiLogger.LogNullError(nameof(args)); return null;