Implement command TRANSFER@ for the sake of completeness (#911)

* Implement command TRANSFER@ for the sake of completeness

* Fix formatting

* Make targetSteamID in SendTradeOffer mandatory and fix logic in new commands


in the previous build the new commands would act like a loot and send trades to the first master instead of the targetted bot

* Improve performance


Strings will only be parsed into uint/byte once if needed for several bots and the targetBot will only be searched for once in Bot.Bots

* Improve performance

* Make SendTradeOffer a generic action responsible for sending a trade and call it with different arguments from different loot/transfer commands

* Move check to appropriate place

* Shuffle arguments around

* Fix compilation errors

* I'm lazy and dumb... but mostly dumb
This commit is contained in:
Abrynos
2018-09-29 22:20:35 +02:00
committed by Łukasz Domeradzki
parent a0ec70d12a
commit e188a1d751
5 changed files with 219 additions and 88 deletions

View File

@@ -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<Steam.Asset.EType> wantedTypes = null, IReadOnlyCollection<uint> 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<Steam.Asset> 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<Steam.Asset.EType> wantedTypes = null, IReadOnlyCollection<uint> 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<Steam.Asset> 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);

View File

@@ -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

View File

@@ -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);
}
}

View File

@@ -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<string> 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<string> 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<string> 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<string> 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<string> 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<string> results = await Utilities.InParallel(bots.Select(bot => bot.Commands.ResponseAdvancedTransfer(steamID, appID, contextID, targetBot))).ConfigureAwait(false);
List<string> responses = new List<string>(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<string> ResponseTransferByRealAppIDs(ulong steamID, HashSet<uint> 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<string> 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<uint> realAppIDs = new HashSet<uint>();
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<string> 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<Bot> 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<uint> realAppIDs = new HashSet<uint>();
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<string> results = await Utilities.InParallel(bots.Select(bot => bot.Commands.ResponseTransferByRealAppIDs(steamID, realAppIDs, targetBot))).ConfigureAwait(false);
List<string> responses = new List<string>(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));

View File

@@ -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);
}
}