diff --git a/ArchiSteamFarm/ArchiWebHandler.cs b/ArchiSteamFarm/ArchiWebHandler.cs index 5dbd87829..099239679 100644 --- a/ArchiSteamFarm/ArchiWebHandler.cs +++ b/ArchiSteamFarm/ArchiWebHandler.cs @@ -313,12 +313,12 @@ namespace ArchiSteamFarm { uint appID = 0; Steam.Item.EType type = Steam.Item.EType.Unknown; - string hashName = description["market_hash_name"].ToString(); + string hashName = description["market_hash_name"].Value; if (!string.IsNullOrEmpty(hashName)) { appID = GetAppIDFromMarketHashName(hashName); } - string descriptionType = description["type"].ToString(); + string descriptionType = description["type"].Value; if (!string.IsNullOrEmpty(descriptionType)) { type = GetItemType(descriptionType); } @@ -460,94 +460,107 @@ namespace ArchiSteamFarm { return null; } - JObject jObject = null; - for (byte i = 0; i < WebBrowser.MaxRetries && jObject == null; i++) { - jObject = await WebBrowser.UrlGetToJObject(SteamCommunityURL + "/my/inventory/json/" + Steam.Item.SteamAppID + "/" + Steam.Item.SteamContextID + "?trading=1").ConfigureAwait(false); - } - - if (jObject == null) { - Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName); - return null; - } - - IEnumerable descriptions = jObject.SelectTokens("$.rgDescriptions.*"); - if (descriptions == null) { - return null; - } - - Dictionary, Tuple> descriptionMap = new Dictionary, Tuple>(); - foreach (JToken description in descriptions) { - string classIDString = description["classid"].ToString(); - if (string.IsNullOrEmpty(classIDString)) { - continue; - } - - ulong classID; - if (!ulong.TryParse(classIDString, out classID) || classID == 0) { - continue; - } - - string instanceIDString = description["instanceid"].ToString(); - if (string.IsNullOrEmpty(instanceIDString)) { - continue; - } - - ulong instanceID; - if (!ulong.TryParse(instanceIDString, out instanceID)) { - continue; - } - - Tuple key = new Tuple(classID, instanceID); - if (descriptionMap.ContainsKey(key)) { - continue; - } - - uint appID = 0; - Steam.Item.EType type = Steam.Item.EType.Unknown; - - string hashName = description["market_hash_name"].ToString(); - if (!string.IsNullOrEmpty(hashName)) { - appID = GetAppIDFromMarketHashName(hashName); - } - - string descriptionType = description["type"].ToString(); - if (!string.IsNullOrEmpty(descriptionType)) { - type = GetItemType(descriptionType); - } - - descriptionMap[key] = new Tuple(appID, type); - } - - IEnumerable items = jObject.SelectTokens("$.rgInventory.*"); - if (descriptions == null) { - return null; - } - HashSet result = new HashSet(); - foreach (JToken item in items) { - Steam.Item steamItem; - - try { - steamItem = JsonConvert.DeserializeObject(item.ToString()); - } catch (JsonException e) { - Logging.LogGenericException(e, Bot.BotName); - continue; + ushort nextPage = 0; + while (true) { + JObject jObject = null; + for (byte i = 0; i < WebBrowser.MaxRetries && jObject == null; i++) { + jObject = await WebBrowser.UrlGetToJObject(SteamCommunityURL + "/my/inventory/json/" + Steam.Item.SteamAppID + "/" + Steam.Item.SteamContextID + "?trading=1&start=" + nextPage).ConfigureAwait(false); } - if (steamItem == null) { - continue; + if (jObject == null) { + Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName); + return null; } - Tuple key = new Tuple(steamItem.ClassID, steamItem.InstanceID); - - Tuple description; - if (descriptionMap.TryGetValue(key, out description)) { - steamItem.RealAppID = description.Item1; - steamItem.Type = description.Item2; + IEnumerable descriptions = jObject.SelectTokens("$.rgDescriptions.*"); + if (descriptions == null) { + return null; } - result.Add(steamItem); + Dictionary, Tuple> descriptionMap = new Dictionary, Tuple>(); + foreach (JToken description in descriptions) { + string classIDString = description["classid"].ToString(); + if (string.IsNullOrEmpty(classIDString)) { + continue; + } + + ulong classID; + if (!ulong.TryParse(classIDString, out classID) || classID == 0) { + continue; + } + + string instanceIDString = description["instanceid"].ToString(); + if (string.IsNullOrEmpty(instanceIDString)) { + continue; + } + + ulong instanceID; + if (!ulong.TryParse(instanceIDString, out instanceID)) { + continue; + } + + Tuple key = new Tuple(classID, instanceID); + if (descriptionMap.ContainsKey(key)) { + continue; + } + + uint appID = 0; + Steam.Item.EType type = Steam.Item.EType.Unknown; + + string hashName = description["market_hash_name"].ToString(); + if (!string.IsNullOrEmpty(hashName)) { + appID = GetAppIDFromMarketHashName(hashName); + } + + string descriptionType = description["type"].ToString(); + if (!string.IsNullOrEmpty(descriptionType)) { + type = GetItemType(descriptionType); + } + + descriptionMap[key] = new Tuple(appID, type); + } + + IEnumerable items = jObject.SelectTokens("$.rgInventory.*"); + if (descriptions == null) { + return null; + } + + foreach (JToken item in items) { + + Steam.Item steamItem; + + try { + steamItem = JsonConvert.DeserializeObject(item.ToString()); + } catch (JsonException e) { + Logging.LogGenericException(e, Bot.BotName); + continue; + } + + if (steamItem == null) { + continue; + } + + Tuple key = new Tuple(steamItem.ClassID, steamItem.InstanceID); + + Tuple description; + if (descriptionMap.TryGetValue(key, out description)) { + steamItem.RealAppID = description.Item1; + steamItem.Type = description.Item2; + } + + result.Add(steamItem); + } + + bool more; + if (!bool.TryParse(jObject["more"].ToString(), out more) || !more) { + break; + } + + if (!ushort.TryParse(jObject["more_start"].ToString(), out nextPage)) { + break; + } } return result; diff --git a/ArchiSteamFarm/Trading.cs b/ArchiSteamFarm/Trading.cs index 3df71faf5..44361ca94 100644 --- a/ArchiSteamFarm/Trading.cs +++ b/ArchiSteamFarm/Trading.cs @@ -119,7 +119,7 @@ namespace ArchiSteamFarm { return; } - if (ShouldAcceptTrade(tradeOffer)) { + if (await ShouldAcceptTrade(tradeOffer).ConfigureAwait(false)) { Logging.LogGenericInfo("Accepting trade: " + tradeOffer.TradeOfferID, Bot.BotName); await Bot.ArchiWebHandler.AcceptTradeOffer(tradeOffer.TradeOfferID).ConfigureAwait(false); } else { @@ -127,7 +127,7 @@ namespace ArchiSteamFarm { } } - private bool ShouldAcceptTrade(Steam.TradeOffer tradeOffer) { + private async Task ShouldAcceptTrade(Steam.TradeOffer tradeOffer) { if (tradeOffer == null) { return false; } @@ -158,10 +158,64 @@ namespace ArchiSteamFarm { return false; } - // This STM trade SHOULD be fine - // Potential TODO: Ensure that our inventory in fact has proper amount of both received and given cards - // This way we could calculate amounts before and after trade, ensuring that we're in fact trading dupes and not 1 + 2 -> 0 + 3 - return true; + // At this point we're sure that STM trade is valid + // Now check if it's worth for us to do the trade + HashSet inventory = await Bot.ArchiWebHandler.GetMyTradableInventory().ConfigureAwait(false); + if (inventory == null || inventory.Count == 0) { + return true; // OK, assume that this trade is valid, we can't check our EQ + } + + // Get appIDs we're interested in + HashSet appIDs = new HashSet(); + foreach (Steam.Item item in tradeOffer.ItemsToGive) { + appIDs.Add(item.RealAppID); + } + + // Now remove from our inventory all items we're NOT interested in + inventory.RemoveWhere(item => !appIDs.Contains(item.RealAppID)); + + // Now let's create a map which maps items to their amount in our EQ + Dictionary, uint> amountMap = new Dictionary, uint>(); + foreach (Steam.Item item in inventory) { + Tuple key = new Tuple(item.ClassID, item.InstanceID); + + uint amount; + if (amountMap.TryGetValue(key, out amount)) { + amountMap[key] = amount + item.Amount; + } else { + amountMap[key] = item.Amount; + } + } + + // Calculate our value of items to give + uint itemsToGiveDupesValue = 0; + foreach (Steam.Item item in tradeOffer.ItemsToGive) { + Tuple key = new Tuple(item.ClassID, item.InstanceID); + + uint amount; + if (!amountMap.TryGetValue(key, out amount)) { + continue; + } + + itemsToGiveDupesValue += amount; + } + + // Calculate our value of items to receive + uint itemsToReceiveDupesValue = 0; + foreach (Steam.Item item in tradeOffer.ItemsToReceive) { + Tuple key = new Tuple(item.ClassID, item.InstanceID); + + uint amount; + if (!amountMap.TryGetValue(key, out amount)) { + continue; + } + + itemsToReceiveDupesValue += amount; + } + + // Trade is worth for us if we're in total trading more of our dupes for less of our dupes (or at least same amount) + // Which means that itemsToGiveDupesValue should be at least itemsToReceiveDupesValue + return itemsToGiveDupesValue >= itemsToReceiveDupesValue; } } }