diff --git a/ArchiSteamFarm/ArchiWebHandler.cs b/ArchiSteamFarm/ArchiWebHandler.cs index 2fa4a4c13..300f45d75 100644 --- a/ArchiSteamFarm/ArchiWebHandler.cs +++ b/ArchiSteamFarm/ArchiWebHandler.cs @@ -589,6 +589,113 @@ namespace ArchiSteamFarm { } } + [SuppressMessage("ReSharper", "FunctionComplexityOverflow")] + internal async Task> GetMyCommunityInventory(bool tradableOnly = false, string appId = null, string contextId = 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 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 (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 5d837ce74..ee6c2d4e1 100755 --- a/ArchiSteamFarm/Bot.cs +++ b/ArchiSteamFarm/Bot.cs @@ -1026,6 +1026,22 @@ namespace ArchiSteamFarm { return await ResponseAdvancedRedeem(steamID, args[1], args[2]).ConfigureAwait(false); } + goto default; + case "!TAKE": + if (args.Length > 2) { + return await ResponseTake(steamID, Utilities.GetArgsString(args, 1, ","), args[2]).ConfigureAwait(false); + } + + return await ResponseTake(steamID, args[1]).ConfigureAwait(false); + case "!TAKE^": + if (args.Length > 3) { + return await ResponseAdvancedTake(steamID, Utilities.GetArgsString(args, 1, ","), args[2], args[3]).ConfigureAwait(false); + } + + if (args.Length > 2) { + return await ResponseAdvancedTake(steamID, args[1], args[2]).ConfigureAwait(false); + } + goto default; case "!REJOINCHAT": return await ResponseRejoinChat(steamID, Utilities.GetArgsString(args, 1, ",")).ConfigureAwait(false); @@ -3150,6 +3166,212 @@ namespace ArchiSteamFarm { return responses.Count > 0 ? string.Join("", responses) : null; } + private async Task ResponseAdvancedTake(ulong steamID, string appId, string contextId) { + if (steamID == 0 || string.IsNullOrEmpty(appId) || string.IsNullOrEmpty(contextId)) { + ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(appId) + " || " + nameof(contextId)); + 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.GetMyCommunityInventory(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 ResponseAdvancedTake(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.ResponseAdvancedTake(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 ResponseTake(ulong steamID, string appId) { + if (steamID == 0 || string.IsNullOrEmpty(appId)) { + ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(appId)); + 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.GetMyCommunityInventory(true, appId, "2").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 ResponseTake(ulong steamID, string botNames, string appId) { + if ((steamID == 0) || string.IsNullOrEmpty(botNames) || string.IsNullOrEmpty(appId)) { + ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames) + " || " + nameof(appId)); + 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.ResponseTake(steamID, appId)); + 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));