diff --git a/ArchiSteamFarm/Bot.cs b/ArchiSteamFarm/Bot.cs index 4cc65561b..ac19d7135 100755 --- a/ArchiSteamFarm/Bot.cs +++ b/ArchiSteamFarm/Bot.cs @@ -241,16 +241,53 @@ namespace ArchiSteamFarm { Start().Forget(); } - internal async Task AcceptConfirmations(bool accept) { + internal async Task AcceptConfirmations(bool accept, Steam.ConfirmationDetails.EType acceptedType = Steam.ConfirmationDetails.EType.Unknown, ulong acceptedSteamID = 0, HashSet acceptedTradeIDs = null) { if (BotDatabase.MobileAuthenticator == null) { return; } HashSet confirmations = await BotDatabase.MobileAuthenticator.GetConfirmations().ConfigureAwait(false); - if (confirmations == null) { + if ((confirmations == null) || (confirmations.Count == 0)) { return; } + if (acceptedType != Steam.ConfirmationDetails.EType.Unknown) { + if (confirmations.RemoveWhere(confirmation => confirmation.Type != acceptedType) > 0) { + confirmations.TrimExcess(); + if (confirmations.Count == 0) { + return; + } + } + } + + if ((acceptedSteamID != 0) || ((acceptedTradeIDs != null) && (acceptedTradeIDs.Count > 0))) { + HashSet ignoredConfirmations = new HashSet(); + // TODO: This could be potentially multi-threaded like below + foreach (MobileAuthenticator.Confirmation confirmation in confirmations) { + Steam.ConfirmationDetails details = await BotDatabase.MobileAuthenticator.GetConfirmationDetails(confirmation).ConfigureAwait(false); + if (details == null) { + ignoredConfirmations.Add(confirmation); + continue; + } + + if ((acceptedSteamID != 0) && (acceptedSteamID != details.OtherSteamID64)) { + ignoredConfirmations.Add(confirmation); + continue; + } + + if ((acceptedTradeIDs != null) && !acceptedTradeIDs.Contains(details.TradeOfferID)) { + ignoredConfirmations.Add(confirmation); + } + } + + confirmations.ExceptWith(ignoredConfirmations); + confirmations.TrimExcess(); + + if (confirmations.Count == 0) { + return; + } + } + await confirmations.ForEachAsync(async confirmation => await BotDatabase.MobileAuthenticator.HandleConfirmation(confirmation, accept).ConfigureAwait(false)).ConfigureAwait(false); } @@ -613,7 +650,7 @@ namespace ArchiSteamFarm { return "Trade offer failed due to error!"; } - await AcceptConfirmations(true).ConfigureAwait(false); + await AcceptConfirmations(true, Steam.ConfirmationDetails.EType.Trade, BotConfig.SteamMasterID).ConfigureAwait(false); return "Trade offer sent successfully!"; } diff --git a/ArchiSteamFarm/JSON/Steam.cs b/ArchiSteamFarm/JSON/Steam.cs index a8b6fd4cf..27fc30c64 100644 --- a/ArchiSteamFarm/JSON/Steam.cs +++ b/ArchiSteamFarm/JSON/Steam.cs @@ -341,9 +341,95 @@ namespace ArchiSteamFarm.JSON { [SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")] [SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local")] internal sealed class ConfirmationDetails { + internal enum EType : byte { + Unknown, + Trade, + Market, + Other + } + [JsonProperty(PropertyName = "success", Required = Required.Always)] internal bool Success { get; private set; } + private EType _Type; + private EType Type { + get { + if (_Type != EType.Unknown) { + return _Type; + } + + if (HtmlDocument == null) { + Logging.LogNullError(nameof(HtmlDocument)); + return EType.Unknown; + } + + HtmlNode testNode = HtmlDocument.DocumentNode.SelectSingleNode("//div[@class='mobileconf_listing_prices']"); + if (testNode != null) { + _Type = EType.Market; + return _Type; + } + + testNode = HtmlDocument.DocumentNode.SelectSingleNode("//div[@class='mobileconf_trade_area']"); + if (testNode != null) { + _Type = EType.Trade; + return _Type; + } + + _Type = EType.Other; + return _Type; + } + } + + private ulong _TradeOfferID; + internal ulong TradeOfferID { + get { + if (_TradeOfferID != 0) { + return _TradeOfferID; + } + + if (Type != EType.Trade) { + return 0; + } + + if (HtmlDocument == null) { + Logging.LogNullError(nameof(HtmlDocument)); + return 0; + } + + HtmlNode htmlNode = HtmlDocument.DocumentNode.SelectSingleNode("//div[@class='tradeoffer']"); + if (htmlNode == null) { + Logging.LogNullError(nameof(htmlNode)); + return 0; + } + + string id = htmlNode.GetAttributeValue("id", null); + if (string.IsNullOrEmpty(id)) { + Logging.LogNullError(nameof(id)); + return 0; + } + + int index = id.IndexOf('_'); + if (index < 0) { + Logging.LogNullError(nameof(index)); + return 0; + } + + index++; + if (id.Length <= index) { + Logging.LogNullError(nameof(id.Length)); + return 0; + } + + id = id.Substring(index); + if (ulong.TryParse(id, out _TradeOfferID) && (_TradeOfferID != 0)) { + return _TradeOfferID; + } + + Logging.LogNullError(nameof(_TradeOfferID)); + return 0; + } + } + private ulong _OtherSteamID64; internal ulong OtherSteamID64 { get { @@ -351,6 +437,10 @@ namespace ArchiSteamFarm.JSON { return _OtherSteamID64; } + if (Type != EType.Trade) { + return 0; + } + if (OtherSteamID3 == 0) { Logging.LogNullError(nameof(OtherSteamID3)); return 0; @@ -371,6 +461,10 @@ namespace ArchiSteamFarm.JSON { return _OtherSteamID3; } + if (Type != EType.Trade) { + return 0; + } + if (HtmlDocument == null) { Logging.LogNullError(nameof(HtmlDocument)); return 0; diff --git a/ArchiSteamFarm/MobileAuthenticator.cs b/ArchiSteamFarm/MobileAuthenticator.cs index 796930198..e85915e59 100644 --- a/ArchiSteamFarm/MobileAuthenticator.cs +++ b/ArchiSteamFarm/MobileAuthenticator.cs @@ -13,14 +13,16 @@ namespace ArchiSteamFarm { internal sealed class Confirmation { internal readonly uint ID; internal readonly ulong Key; + internal readonly Steam.ConfirmationDetails.EType Type; - internal Confirmation(uint id, ulong key) { - if ((id == 0) || (key == 0)) { - throw new ArgumentNullException(nameof(id) + " || " + nameof(key)); + internal Confirmation(uint id, ulong key, Steam.ConfirmationDetails.EType type) { + if ((id == 0) || (key == 0) || (type == Steam.ConfirmationDetails.EType.Unknown)) { + throw new ArgumentNullException(nameof(id) + " || " + nameof(key) + " || " + nameof(type)); } ID = id; Key = key; + Type = type; } } @@ -146,14 +148,14 @@ namespace ArchiSteamFarm { return null; } - HtmlNodeCollection confirmations = htmlDocument.DocumentNode.SelectNodes("//div[@class='mobileconf_list_entry']"); - if (confirmations == null) { + HtmlNodeCollection confirmationNodes = htmlDocument.DocumentNode.SelectNodes("//div[@class='mobileconf_list_entry']"); + if (confirmationNodes == null) { return null; } HashSet result = new HashSet(); - foreach (HtmlNode confirmation in confirmations) { - string idString = confirmation.GetAttributeValue("data-confid", null); + foreach (HtmlNode confirmationNode in confirmationNodes) { + string idString = confirmationNode.GetAttributeValue("data-confid", null); if (string.IsNullOrEmpty(idString)) { Logging.LogNullError(nameof(idString), Bot.BotName); continue; @@ -165,7 +167,7 @@ namespace ArchiSteamFarm { continue; } - string keyString = confirmation.GetAttributeValue("data-key", null); + string keyString = confirmationNode.GetAttributeValue("data-key", null); if (string.IsNullOrEmpty(keyString)) { Logging.LogNullError(nameof(keyString), Bot.BotName); continue; @@ -177,7 +179,24 @@ namespace ArchiSteamFarm { continue; } - result.Add(new Confirmation(id, key)); + HtmlNode descriptionNode = confirmationNode.SelectSingleNode(".//div[@class='mobileconf_list_entry_description']/div"); + if (descriptionNode == null) { + Logging.LogNullError(nameof(descriptionNode), Bot.BotName); + continue; + } + + Steam.ConfirmationDetails.EType type; + + string description = descriptionNode.InnerText; + if (description.Equals("Sell - Market Listing")) { + type = Steam.ConfirmationDetails.EType.Market; + } else if (description.StartsWith("Trade with ", StringComparison.Ordinal)) { + type = Steam.ConfirmationDetails.EType.Trade; + } else { + type = Steam.ConfirmationDetails.EType.Other; + } + + result.Add(new Confirmation(id, key, type)); } return result; diff --git a/ArchiSteamFarm/Trading.cs b/ArchiSteamFarm/Trading.cs index e9da06782..82fdd32c3 100644 --- a/ArchiSteamFarm/Trading.cs +++ b/ArchiSteamFarm/Trading.cs @@ -94,7 +94,9 @@ namespace ArchiSteamFarm { } await tradeOffers.ForEachAsync(ParseTrade).ConfigureAwait(false); - await Bot.AcceptConfirmations(true).ConfigureAwait(false); + + HashSet tradeIDs = new HashSet(tradeOffers.Select(tradeOffer => tradeOffer.TradeOfferID)); + await Bot.AcceptConfirmations(true, Steam.ConfirmationDetails.EType.Trade, 0, tradeIDs).ConfigureAwait(false); } private async Task ParseTrade(Steam.TradeOffer tradeOffer) {