diff --git a/ArchiSteamFarm/Actions.cs b/ArchiSteamFarm/Actions.cs index 78aa02191..f5fa33aa6 100644 --- a/ArchiSteamFarm/Actions.cs +++ b/ArchiSteamFarm/Actions.cs @@ -157,74 +157,6 @@ namespace ArchiSteamFarm { return (true, Strings.Done); } - 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.SteamID) { - 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.SteamID, 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<(bool Success, string Output)> Pause(bool permanent, ushort resumeInSeconds = 0) { @@ -294,6 +226,73 @@ namespace ArchiSteamFarm { return (true, Strings.BotAutomaticIdlingNowResumed); } + internal async Task<(bool Success, string Output)> SendTradeOffer(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, string.Format(Strings.ErrorObjectIsNull, nameof(targetSteamID) + " || " + nameof(appID) + " || " + nameof(contextID))); + } + + 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.SteamID) { + 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.SteamID, 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 (bool Success, string Output) Start() { if (Bot.KeepRunning) { return (false, Strings.BotAlreadyRunning); diff --git a/ArchiSteamFarm/Bot.cs b/ArchiSteamFarm/Bot.cs index 0b8db9a60..e11d16099 100755 --- a/ArchiSteamFarm/Bot.cs +++ b/ArchiSteamFarm/Bot.cs @@ -788,7 +788,7 @@ namespace ArchiSteamFarm { FirstTradeSent = true; if (BotConfig.SendOnFarmingFinished) { - await Actions.Loot().ConfigureAwait(false); + await Actions.SendTradeOffer().ConfigureAwait(false); } } @@ -1426,7 +1426,7 @@ namespace ArchiSteamFarm { if ((BotConfig.SendTradePeriod > 0) && BotConfig.SteamUserPermissions.Values.Any(permission => permission >= BotConfig.EPermission.Master)) { SendItemsTimer = new Timer( - async e => await Actions.Loot().ConfigureAwait(false), + async e => await Actions.SendTradeOffer().ConfigureAwait(false), null, TimeSpan.FromHours(BotConfig.SendTradePeriod) + TimeSpan.FromSeconds(Program.LoadBalancingDelay * Bots.Count), // Delay TimeSpan.FromHours(BotConfig.SendTradePeriod) // Period diff --git a/ArchiSteamFarm/CardsFarmer.cs b/ArchiSteamFarm/CardsFarmer.cs index a264df11c..240bc0a09 100755 --- a/ArchiSteamFarm/CardsFarmer.cs +++ b/ArchiSteamFarm/CardsFarmer.cs @@ -169,7 +169,7 @@ 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 if (Bot.BotConfig.SendOnFarmingFinished) { - await Bot.Actions.Loot().ConfigureAwait(false); + await Bot.Actions.SendTradeOffer().ConfigureAwait(false); } } diff --git a/ArchiSteamFarm/Commands.cs b/ArchiSteamFarm/Commands.cs index 9780be06e..5a25b74f9 100644 --- a/ArchiSteamFarm/Commands.cs +++ b/ArchiSteamFarm/Commands.cs @@ -287,6 +287,16 @@ namespace ArchiSteamFarm { return await ResponseAdvancedTransfer(steamID, args[1], args[2], Utilities.GetArgsAsText(args, 3, ",")).ConfigureAwait(false); } + goto default; + case "TRANSFER@": + if (args.Length > 3) { + return await ResponseTransferByRealAppIDs(steamID, args[1], args[2], Utilities.GetArgsAsText(args, 3, ",")).ConfigureAwait(false); + } + + if (args.Length > 2) { + return await ResponseTransferByRealAppIDs(steamID, args[1], Utilities.GetArgsAsText(args, 2, ",")).ConfigureAwait(false); + } + goto default; case "UNPACK": return await ResponseUnpackBoosters(steamID, Utilities.GetArgsAsText(args, 1, ",")).ConfigureAwait(false); @@ -521,7 +531,7 @@ namespace ArchiSteamFarm { return FormatBotResponse(string.Format(Strings.ErrorIsInvalid, nameof(contextID))); } - (bool success, string output) = await Bot.Actions.Loot(appID, contextID).ConfigureAwait(false); + (bool success, string output) = await Bot.Actions.SendTradeOffer(appID, contextID).ConfigureAwait(false); return FormatBotResponse(success ? output : string.Format(Strings.WarningFailedWithError, output)); } @@ -611,9 +621,9 @@ namespace ArchiSteamFarm { return responses.Count > 0 ? string.Join(Environment.NewLine, responses) : null; } - private async Task ResponseAdvancedTransfer(ulong steamID, string targetAppID, string targetContextID, string botNameTo) { - if (steamID == 0 || string.IsNullOrEmpty(targetAppID) || string.IsNullOrEmpty(targetContextID) || string.IsNullOrEmpty(botNameTo)) { - Bot.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(targetAppID) + " || " + nameof(targetContextID) + " || " + nameof(botNameTo)); + private async Task ResponseAdvancedTransfer(ulong steamID, uint appID, byte contextID, Bot targetBot) { + if (steamID == 0 || appID == 0 || contextID == 0 || targetBot == null) { + Bot.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(appID) + " || " + nameof(contextID) + " || " + nameof(targetBot)); return null; } @@ -625,16 +635,22 @@ namespace ArchiSteamFarm { 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.SteamID == Bot.SteamID) { - return FormatBotResponse(Strings.BotSendingTradeToYourself); + (bool success, string output) = await Bot.Actions.SendTradeOffer(appID, contextID, targetBot.SteamID).ConfigureAwait(false); + return FormatBotResponse(success ? output : string.Format(Strings.WarningFailedWithError, output)); + } + + private async Task ResponseAdvancedTransfer(ulong steamID, string targetAppID, string targetContextID, string botNameTo) { + if (steamID == 0 || string.IsNullOrEmpty(targetAppID) || string.IsNullOrEmpty(targetContextID) || string.IsNullOrEmpty(botNameTo)) { + Bot.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(targetAppID) + " || " + nameof(targetContextID) + " || " + nameof(botNameTo)); + return null; + } + + if (!Bot.Bots.TryGetValue(botNameTo, out Bot targetBot)) { + return ASF.IsOwner(steamID) ? FormatBotResponse(string.Format(Strings.BotNotFound, botNameTo)) : null; } if (!uint.TryParse(targetAppID, out uint appID) || appID == 0) { @@ -645,8 +661,7 @@ namespace ArchiSteamFarm { return FormatBotResponse(string.Format(Strings.ErrorIsInvalid, nameof(contextID))); } - (bool success, string output) = await Bot.Actions.Loot(appID, contextID, targetBot.SteamID).ConfigureAwait(false); - return FormatBotResponse(success ? output : string.Format(Strings.WarningFailedWithError, output)); + return await ResponseAdvancedTransfer(steamID, appID, contextID, targetBot).ConfigureAwait(false); } private static async Task ResponseAdvancedTransfer(ulong steamID, string botNames, string targetAppID, string targetContextID, string botNameTo) { @@ -660,7 +675,19 @@ namespace ArchiSteamFarm { return ASF.IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null; } - IList results = await Utilities.InParallel(bots.Select(bot => bot.Commands.ResponseAdvancedTransfer(steamID, targetAppID, targetContextID, botNameTo))).ConfigureAwait(false); + if (!uint.TryParse(targetAppID, out uint appID) || appID == 0) { + return FormatStaticResponse(string.Format(Strings.ErrorIsInvalid, nameof(appID))); + } + + if (!byte.TryParse(targetContextID, out byte contextID) || contextID == 0) { + return FormatStaticResponse(string.Format(Strings.ErrorIsInvalid, nameof(contextID))); + } + + if (!Bot.Bots.TryGetValue(botNameTo, out Bot targetBot)) { + return ASF.IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNameTo)) : null; + } + + IList results = await Utilities.InParallel(bots.Select(bot => bot.Commands.ResponseAdvancedTransfer(steamID, appID, contextID, targetBot))).ConfigureAwait(false); List responses = new List(results.Where(result => !string.IsNullOrEmpty(result))); return responses.Count > 0 ? string.Join(Environment.NewLine, responses) : null; @@ -1156,7 +1183,7 @@ namespace ArchiSteamFarm { return FormatBotResponse(Strings.BotNotConnected); } - (bool success, string output) = await Bot.Actions.Loot(wantedTypes: Bot.BotConfig.LootableTypes).ConfigureAwait(false); + (bool success, string output) = await Bot.Actions.SendTradeOffer(wantedTypes: Bot.BotConfig.LootableTypes).ConfigureAwait(false); return FormatBotResponse(success ? output : string.Format(Strings.WarningFailedWithError, output)); } @@ -1207,7 +1234,7 @@ namespace ArchiSteamFarm { realAppIDs.Add(appID); } - (bool success, string output) = await Bot.Actions.Loot(wantedRealAppIDs: realAppIDs).ConfigureAwait(false); + (bool success, string output) = await Bot.Actions.SendTradeOffer(wantedRealAppIDs: realAppIDs).ConfigureAwait(false); return FormatBotResponse(success ? output : string.Format(Strings.WarningFailedWithError, output)); } @@ -2162,7 +2189,7 @@ namespace ArchiSteamFarm { } } - (bool success, string output) = await Bot.Actions.Loot(targetSteamID: targetBot.SteamID, wantedTypes: transferTypes).ConfigureAwait(false); + (bool success, string output) = await Bot.Actions.SendTradeOffer(targetSteamID: targetBot.SteamID, wantedTypes: transferTypes).ConfigureAwait(false); return FormatBotResponse(success ? output : string.Format(Strings.WarningFailedWithError, output)); } @@ -2183,6 +2210,111 @@ namespace ArchiSteamFarm { return responses.Count > 0 ? string.Join(Environment.NewLine, responses) : null; } + private async Task ResponseTransferByRealAppIDs(ulong steamID, HashSet realAppIDs, Bot targetBot) { + if (steamID == 0 || realAppIDs == null || realAppIDs.Count == 0 || targetBot == null) { + Bot.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(realAppIDs) + " || " + nameof(targetBot)); + return null; + } + + if (!Bot.IsMaster(steamID)) { + return null; + } + + if (!Bot.IsConnectedAndLoggedOn) { + return FormatBotResponse(Strings.BotNotConnected); + } + + if (!targetBot.IsConnectedAndLoggedOn) { + return FormatBotResponse(Strings.BotNotConnected); + } + + if (targetBot.SteamID == Bot.SteamID) { + return FormatBotResponse(Strings.BotSendingTradeToYourself); + } + + (bool success, string output) = await Bot.Actions.SendTradeOffer(targetSteamID: targetBot.SteamID, wantedRealAppIDs: realAppIDs).ConfigureAwait(false); + return FormatBotResponse(success ? output : string.Format(Strings.WarningFailedWithError, output)); + } + + private async Task ResponseTransferByRealAppIDs(ulong steamID, string realAppIDsText, string botNameTo) { + if (steamID == 0 || string.IsNullOrEmpty(realAppIDsText) || string.IsNullOrEmpty(botNameTo)) { + Bot.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(realAppIDsText) + " || " + 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.SteamID == Bot.SteamID) { + return FormatBotResponse(Strings.BotSendingTradeToYourself); + } + + 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.SendTradeOffer(targetSteamID: targetBot.SteamID, wantedRealAppIDs: realAppIDs).ConfigureAwait(false); + return FormatBotResponse(success ? output : string.Format(Strings.WarningFailedWithError, output)); + } + + private static async Task ResponseTransferByRealAppIDs(ulong steamID, string botNames, string realAppIDsText, string botNameTo) { + if ((steamID == 0) || string.IsNullOrEmpty(botNames) || string.IsNullOrEmpty(realAppIDsText) || string.IsNullOrEmpty(botNameTo)) { + ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames) + " || " + nameof(realAppIDsText) + " || " + 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; + } + + string[] appIDTexts = realAppIDsText.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + if (appIDTexts.Length == 0) { + return FormatStaticResponse(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 FormatStaticResponse(string.Format(Strings.ErrorIsInvalid, nameof(appID))); + } + + realAppIDs.Add(appID); + } + + if (!Bot.Bots.TryGetValue(botNameTo, out Bot targetBot)) { + return ASF.IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNameTo)) : null; + } + + IList results = await Utilities.InParallel(bots.Select(bot => bot.Commands.ResponseTransferByRealAppIDs(steamID, realAppIDs, targetBot))).ConfigureAwait(false); + + 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)); diff --git a/ArchiSteamFarm/Trading.cs b/ArchiSteamFarm/Trading.cs index 13b69d989..0ec287c5c 100644 --- a/ArchiSteamFarm/Trading.cs +++ b/ArchiSteamFarm/Trading.cs @@ -208,7 +208,7 @@ namespace ArchiSteamFarm { 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.Actions.Loot().ConfigureAwait(false); + await Bot.Actions.SendTradeOffer().ConfigureAwait(false); } }