private async Task ParseTrade(Steam.TradeOffer tradeOffer) { if (tradeOffer == null) { Logging.LogNullError(nameof(tradeOffer), Bot.BotName); return; } if (tradeOffer.State != Steam.TradeOffer.ETradeOfferState.Active) { return; } if (await ShouldAcceptTrade(tradeOffer).ConfigureAwait(false)) { Logging.LogGenericInfo("Accepting trade: " + tradeOffer.TradeOfferID, Bot.BotName); await Bot.ArchiWebHandler.AcceptTradeOffer(tradeOffer.TradeOfferID).ConfigureAwait(false); } else if (Bot.BotConfig.IsBotAccount) { Logging.LogGenericInfo("Rejecting trade: " + tradeOffer.TradeOfferID, Bot.BotName); Bot.ArchiWebHandler.DeclineTradeOffer(tradeOffer.TradeOfferID); } else { Logging.LogGenericInfo("Ignoring trade: " + tradeOffer.TradeOfferID, Bot.BotName); } }
private async Task <ParseTradeResult> ParseTrade(Steam.TradeOffer tradeOffer) { if (tradeOffer == null) { Bot.ArchiLogger.LogNullError(nameof(tradeOffer)); return(null); } if (tradeOffer.State != Steam.TradeOffer.ETradeOfferState.Active) { Bot.ArchiLogger.LogGenericError(string.Format(Strings.ErrorIsInvalid, tradeOffer.State)); return(null); } ParseTradeResult result = await ShouldAcceptTrade(tradeOffer).ConfigureAwait(false); if (result == null) { Bot.ArchiLogger.LogNullError(nameof(result)); return(null); } switch (result.Result) { case ParseTradeResult.EResult.AcceptedWithItemLose: case ParseTradeResult.EResult.AcceptedWithoutItemLose: Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.AcceptingTrade, tradeOffer.TradeOfferID)); await Bot.ArchiWebHandler.AcceptTradeOffer(tradeOffer.TradeOfferID).ConfigureAwait(false); break; case ParseTradeResult.EResult.RejectedPermanently: case ParseTradeResult.EResult.RejectedTemporarily: if (result.Result == ParseTradeResult.EResult.RejectedPermanently) { if (Bot.BotConfig.IsBotAccount) { Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.RejectingTrade, tradeOffer.TradeOfferID)); await Bot.ArchiWebHandler.DeclineTradeOffer(tradeOffer.TradeOfferID).ConfigureAwait(false); break; } IgnoredTrades.Add(tradeOffer.TradeOfferID); } Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.IgnoringTrade, tradeOffer.TradeOfferID)); break; } return(result); }
private async Task <ParseTradeResult> ParseTrade(Steam.TradeOffer tradeOffer) { if (tradeOffer == null) { Logging.LogNullError(nameof(tradeOffer), Bot.BotName); return(null); } if (tradeOffer.State != Steam.TradeOffer.ETradeOfferState.Active) { Logging.LogGenericError("Ignoring trade in non-active state!", Bot.BotName); return(null); } ParseTradeResult result = await ShouldAcceptTrade(tradeOffer).ConfigureAwait(false); if (result == null) { Logging.LogNullError(nameof(result), Bot.BotName); return(null); } switch (result.Result) { case ParseTradeResult.EResult.AcceptedWithItemLose: case ParseTradeResult.EResult.AcceptedWithoutItemLose: Logging.LogGenericInfo("Accepting trade: " + tradeOffer.TradeOfferID, Bot.BotName); await Bot.ArchiWebHandler.AcceptTradeOffer(tradeOffer.TradeOfferID).ConfigureAwait(false); break; case ParseTradeResult.EResult.RejectedPermanently: case ParseTradeResult.EResult.RejectedTemporarily: if (result.Result == ParseTradeResult.EResult.RejectedPermanently) { if (Bot.BotConfig.IsBotAccount) { Logging.LogGenericInfo("Rejecting trade: " + tradeOffer.TradeOfferID, Bot.BotName); Bot.ArchiWebHandler.DeclineTradeOffer(tradeOffer.TradeOfferID); break; } IgnoredTrades.Add(tradeOffer.TradeOfferID); } Logging.LogGenericInfo("Ignoring trade: " + tradeOffer.TradeOfferID, Bot.BotName); break; } return(result); }
private async Task ParseTrade(Steam.TradeOffer tradeOffer) { if ((tradeOffer == null) || (tradeOffer.State != Steam.TradeOffer.ETradeOfferState.Active)) { return; } if (await ShouldAcceptTrade(tradeOffer).ConfigureAwait(false)) { Logging.LogGenericInfo("Accepting trade: " + tradeOffer.TradeOfferID, Bot.BotName); await Bot.ArchiWebHandler.AcceptTradeOffer(tradeOffer.TradeOfferID).ConfigureAwait(false); } else { Logging.LogGenericInfo("Ignoring trade: " + tradeOffer.TradeOfferID, Bot.BotName); } }
private bool ShouldAcceptTrade(Steam.TradeOffer tradeOffer) { if (tradeOffer == null) { return(false); } // Always accept trades when we're not losing anything if (tradeOffer.items_to_give.Count == 0) { return(true); } // Always accept trades from SteamMasterID if (tradeOffer.OtherSteamID64 != 0 && tradeOffer.OtherSteamID64 == Bot.BotConfig.SteamMasterID) { return(true); } // TODO: Add optional SteamTradeMatcher integration here // If no rule above matched this trade, reject it return(false); }
private async Task ParseTrade(Steam.TradeOffer tradeOffer) { if (tradeOffer == null || tradeOffer.trade_offer_state != Steam.TradeOffer.ETradeOfferState.Active) { return; } ulong tradeID; if (!ulong.TryParse(tradeOffer.tradeofferid, out tradeID)) { return; } if (ShouldAcceptTrade(tradeOffer)) { Logging.LogGenericInfo("Accepting trade: " + tradeID, Bot.BotName); await Bot.ArchiWebHandler.AcceptTradeOffer(tradeID).ConfigureAwait(false); } else { Logging.LogGenericInfo("Ignoring trade: " + tradeID, Bot.BotName); } }
private async Task ParseTrade(Steam.TradeOffer tradeOffer) { if (tradeOffer == null) { return; } ulong tradeID; if (!ulong.TryParse(tradeOffer.tradeofferid, out tradeID)) { return; } if (tradeOffer.items_to_give.Count == 0 || tradeOffer.OtherSteamID64 == Bot.BotConfig.SteamMasterID) { Logging.LogGenericInfo("Accepting trade: " + tradeID, Bot.BotName); await Bot.ArchiWebHandler.AcceptTradeOffer(tradeID).ConfigureAwait(false); } else { Logging.LogGenericInfo("Ignoring trade: " + tradeID, Bot.BotName); } }
private async Task <bool> MatchActivelyRound() { // TODO: This function has a lot of debug leftovers for logic testing, once that period is over, get rid of them if (!Bot.IsConnectedAndLoggedOn || Bot.BotConfig.TradingPreferences.HasFlag(BotConfig.ETradingPreferences.MatchEverything) || !Bot.BotConfig.TradingPreferences.HasFlag(BotConfig.ETradingPreferences.MatchActively) || !await IsEligibleForMatching().ConfigureAwait(false)) { Bot.ArchiLogger.LogGenericDebug("User not eligible for this function, returning"); return(false); } HashSet <Steam.Asset.EType> acceptedMatchableTypes = Bot.BotConfig.MatchableTypes.Where(type => AcceptedMatchableTypes.Contains(type)).ToHashSet(); if (acceptedMatchableTypes.Count == 0) { Bot.ArchiLogger.LogGenericDebug("No acceptable matchable types, returning"); return(false); } HashSet <Steam.Asset> ourInventory = await Bot.ArchiWebHandler.GetInventory(Bot.SteamID, tradable : true, wantedTypes : acceptedMatchableTypes).ConfigureAwait(false); if ((ourInventory == null) || (ourInventory.Count == 0)) { Bot.ArchiLogger.LogGenericDebug("Empty inventory, returning"); return(false); } Dictionary <(uint AppID, Steam.Asset.EType Type), Dictionary <ulong, uint> > ourInventoryState = Trading.GetInventoryState(ourInventory); if (ourInventoryState.Values.All(set => set.Values.All(amount => amount <= 1))) { // User doesn't have any more dupes in the inventory Bot.ArchiLogger.LogGenericDebug("No dupes in inventory, returning"); return(false); } HashSet <ListedUser> listedUsers = await GetListedUsers().ConfigureAwait(false); if ((listedUsers == null) || (listedUsers.Count == 0)) { Bot.ArchiLogger.LogGenericDebug("No listed users, returning"); return(false); } byte emptyMatches = 0; HashSet <(uint AppID, Steam.Asset.EType Type)> skippedSets = new HashSet <(uint AppID, Steam.Asset.EType Type)>(); foreach (ListedUser listedUser in listedUsers.Where(listedUser => listedUser.MatchEverything && !Bot.IsBlacklistedFromTrades(listedUser.SteamID)).OrderByDescending(listedUser => listedUser.Score).Take(MaxMatchedBotsHard)) { Bot.ArchiLogger.LogGenericDebug("Now matching " + listedUser.SteamID + "..."); HashSet <Steam.Asset> theirInventory = await Bot.ArchiWebHandler.GetInventory(listedUser.SteamID, tradable : true, wantedTypes : acceptedMatchableTypes, skippedSets : skippedSets).ConfigureAwait(false); if ((theirInventory == null) || (theirInventory.Count == 0)) { Bot.ArchiLogger.LogGenericDebug("Inventory of " + listedUser.SteamID + " is empty, continuing..."); continue; } Dictionary <(uint AppID, Steam.Asset.EType Type), Dictionary <ulong, uint> > theirInventoryState = Trading.GetInventoryState(theirInventory); Dictionary <ulong, uint> classIDsToGive = new Dictionary <ulong, uint>(); Dictionary <ulong, uint> classIDsToReceive = new Dictionary <ulong, uint>(); HashSet <(uint AppID, Steam.Asset.EType Type)> skippedSetsThisTrade = new HashSet <(uint AppID, Steam.Asset.EType Type)>(); foreach (KeyValuePair <(uint AppID, Steam.Asset.EType Type), Dictionary <ulong, uint> > ourInventoryStateSet in ourInventoryState.Where(set => listedUser.MatchableTypes.Contains(set.Key.Type) && set.Value.Values.Any(count => count > 1))) { if (!theirInventoryState.TryGetValue(ourInventoryStateSet.Key, out Dictionary <ulong, uint> theirItems)) { continue; } bool match; do { match = false; foreach (KeyValuePair <ulong, uint> ourItem in ourInventoryStateSet.Value.Where(item => item.Value > 1).OrderByDescending(item => item.Value)) { foreach (KeyValuePair <ulong, uint> theirItem in theirItems.OrderBy(item => ourInventoryStateSet.Value.TryGetValue(item.Key, out uint ourAmount) ? ourAmount : 0)) { if (ourInventoryStateSet.Value.TryGetValue(theirItem.Key, out uint ourAmountOfTheirItem) && (ourItem.Value <= ourAmountOfTheirItem + 1)) { continue; } Bot.ArchiLogger.LogGenericDebug("Found a match: our " + ourItem.Key + " for theirs " + theirItem.Key); // Skip this set from the remaining of this round skippedSetsThisTrade.Add(ourInventoryStateSet.Key); // Update our state based on given items classIDsToGive[ourItem.Key] = classIDsToGive.TryGetValue(ourItem.Key, out uint givenAmount) ? givenAmount + 1 : 1; ourInventoryStateSet.Value[ourItem.Key] = ourItem.Value - 1; // Update our state based on received items classIDsToReceive[theirItem.Key] = classIDsToReceive.TryGetValue(theirItem.Key, out uint receivedAmount) ? receivedAmount + 1 : 1; ourInventoryStateSet.Value[theirItem.Key] = ourAmountOfTheirItem + 1; // Update their state based on taken items if (theirItems.TryGetValue(theirItem.Key, out uint theirAmount) && (theirAmount > 1)) { theirItems[theirItem.Key] = theirAmount - 1; } else { theirItems.Remove(theirItem.Key); } match = true; break; } if (match) { break; } } } while (match); } if ((classIDsToGive.Count == 0) && (classIDsToReceive.Count == 0)) { Bot.ArchiLogger.LogGenericDebug("No matches found, continuing..."); if (++emptyMatches >= MaxMatchesBotsSoft) { break; } continue; } emptyMatches = 0; HashSet <Steam.Asset> itemsToGive = Trading.GetItemsFromInventory(ourInventory, classIDsToGive); HashSet <Steam.Asset> itemsToReceive = Trading.GetItemsFromInventory(theirInventory, classIDsToReceive); // TODO: Debug only offer, should be removed after tests Steam.TradeOffer debugOffer = new Steam.TradeOffer(1, 46697991, Steam.TradeOffer.ETradeOfferState.Active); foreach (Steam.Asset itemToGive in itemsToGive) { debugOffer.ItemsToGive.Add(itemToGive); } foreach (Steam.Asset itemToReceive in itemsToReceive) { debugOffer.ItemsToReceive.Add(itemToReceive); } if (!debugOffer.IsFairTypesExchange()) { Bot.ArchiLogger.LogGenericDebug("CRITICAL: This offer is NOT fair!!!"); return(false); } Bot.ArchiLogger.LogGenericDebug("Sending trade: our " + string.Join(", ", itemsToGive.Select(item => item.RealAppID + "/" + item.Type + " " + item.ClassID + " of " + item.Amount))); (bool success, HashSet <ulong> mobileTradeOfferIDs) = await Bot.ArchiWebHandler.SendTradeOffer(listedUser.SteamID, itemsToGive, itemsToReceive, listedUser.TradeToken, true).ConfigureAwait(false); if ((mobileTradeOfferIDs != null) && (mobileTradeOfferIDs.Count > 0) && Bot.HasMobileAuthenticator) { if (!await Bot.Actions.AcceptConfirmations(true, Steam.ConfirmationDetails.EType.Trade, listedUser.SteamID, mobileTradeOfferIDs, true).ConfigureAwait(false)) { return(false); } } if (!success) { Bot.ArchiLogger.LogGenericDebug("Trade failed (?), continuing..."); continue; } Bot.ArchiLogger.LogGenericDebug("Trade succeeded!"); foreach ((uint AppID, Steam.Asset.EType Type)skippedSetThisTrade in skippedSetsThisTrade) { ourInventoryState.Remove(skippedSetThisTrade); } if (ourInventoryState.Values.All(set => set.Values.All(amount => amount <= 1))) { // User doesn't have any more dupes in the inventory Bot.ArchiLogger.LogGenericDebug("No dupes in inventory, returning"); break; } skippedSets.UnionWith(skippedSetsThisTrade); } Bot.ArchiLogger.LogGenericDebug("This round is over, we traded " + skippedSets.Count + " sets!"); return(skippedSets.Count > 0); }
internal HashSet <Steam.TradeOffer> GetActiveTradeOffers() { if (string.IsNullOrEmpty(Bot.BotConfig.SteamApiKey)) { Logging.LogNullError(nameof(Bot.BotConfig.SteamApiKey), Bot.BotName); return(null); } KeyValue response = null; for (byte i = 0; (i < WebBrowser.MaxRetries) && (response == null); i++) { using (dynamic iEconService = WebAPI.GetInterface("IEconService", Bot.BotConfig.SteamApiKey)) { iEconService.Timeout = Timeout; try { response = iEconService.GetTradeOffers( get_received_offers: 1, active_only: 1, get_descriptions: 1, secure: !Program.GlobalConfig.ForceHttp ); } catch (Exception e) { Logging.LogGenericException(e, Bot.BotName); } } } if (response == null) { Logging.LogGenericWarning("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName); return(null); } Dictionary <ulong, Tuple <uint, Steam.Item.EType> > descriptions = new Dictionary <ulong, Tuple <uint, Steam.Item.EType> >(); foreach (KeyValue description in response["descriptions"].Children) { ulong classID = description["classid"].AsUnsignedLong(); if (classID == 0) { Logging.LogNullError(nameof(classID), Bot.BotName); return(null); } if (descriptions.ContainsKey(classID)) { continue; } uint appID = 0; string hashName = description["market_hash_name"].Value; if (!string.IsNullOrEmpty(hashName)) { appID = GetAppIDFromMarketHashName(hashName); } if (appID == 0) { appID = description["appid"].AsUnsignedInteger(); } Steam.Item.EType type = Steam.Item.EType.Unknown; string descriptionType = description["type"].Value; if (!string.IsNullOrEmpty(descriptionType)) { type = GetItemType(descriptionType); } descriptions[classID] = new Tuple <uint, Steam.Item.EType>(appID, type); } HashSet <Steam.TradeOffer> result = new HashSet <Steam.TradeOffer>(); foreach (KeyValue trade in response["trade_offers_received"].Children) { Steam.TradeOffer.ETradeOfferState state = trade["trade_offer_state"].AsEnum <Steam.TradeOffer.ETradeOfferState>(); if (state == Steam.TradeOffer.ETradeOfferState.Unknown) { Logging.LogNullError(nameof(state)); return(null); } if (state != Steam.TradeOffer.ETradeOfferState.Active) { continue; } ulong tradeOfferID = trade["tradeofferid"].AsUnsignedLong(); if (tradeOfferID == 0) { Logging.LogNullError(nameof(tradeOfferID)); return(null); } uint otherSteamID3 = trade["accountid_other"].AsUnsignedInteger(); if (otherSteamID3 == 0) { Logging.LogNullError(nameof(otherSteamID3)); return(null); } Steam.TradeOffer tradeOffer = new Steam.TradeOffer(tradeOfferID, otherSteamID3, state); List <KeyValue> itemsToGive = trade["items_to_give"].Children; if (itemsToGive.Count > 0) { if (!ParseItems(descriptions, itemsToGive, tradeOffer.ItemsToGive)) { Logging.LogGenericError("Parsing " + nameof(itemsToGive) + " failed!", Bot.BotName); return(null); } } List <KeyValue> itemsToReceive = trade["items_to_receive"].Children; if (itemsToReceive.Count > 0) { if (!ParseItems(descriptions, itemsToReceive, tradeOffer.ItemsToReceive)) { Logging.LogGenericError("Parsing " + nameof(itemsToReceive) + " failed!", Bot.BotName); return(null); } } result.Add(tradeOffer); } return(result); }
private async Task <ParseTradeResult> ShouldAcceptTrade(Steam.TradeOffer tradeOffer) { if (tradeOffer == null) { Logging.LogNullError(nameof(tradeOffer), Bot.BotName); return(ParseTradeResult.Error); } // Always accept trades when we're not losing anything if (tradeOffer.ItemsToGive.Count == 0) { // Unless it's steam fuckup and we're dealing with broken trade return(tradeOffer.ItemsToReceive.Count > 0 ? ParseTradeResult.AcceptedWithoutItemLose : ParseTradeResult.RejectedTemporarily); } // Always accept trades from SteamMasterID if ((tradeOffer.OtherSteamID64 != 0) && (tradeOffer.OtherSteamID64 == Bot.BotConfig.SteamMasterID)) { return(ParseTradeResult.AcceptedWithItemLose); } // If we don't have SteamTradeMatcher enabled, this is the end for us if (!Bot.BotConfig.SteamTradeMatcher) { return(ParseTradeResult.RejectedPermanently); } // Decline trade if we're giving more count-wise if (tradeOffer.ItemsToGive.Count > tradeOffer.ItemsToReceive.Count) { return(ParseTradeResult.RejectedPermanently); } // Decline trade if we're losing anything but steam cards, or if it's non-dupes trade if (!tradeOffer.IsSteamCardsOnlyTradeForUs() || !tradeOffer.IsPotentiallyDupesTradeForUs()) { return(ParseTradeResult.RejectedPermanently); } // At this point we're sure that STM trade is valid // Fetch trade hold duration byte?holdDuration = await Bot.ArchiWebHandler.GetTradeHoldDuration(tradeOffer.TradeOfferID).ConfigureAwait(false); if (!holdDuration.HasValue) { // If we can't get trade hold duration, reject trade temporarily return(ParseTradeResult.RejectedTemporarily); } // If user has a trade hold, we add extra logic if (holdDuration.Value > 0) { // If trade hold duration exceeds our max, or user asks for cards with short lifespan, reject the trade if ((holdDuration.Value > Program.GlobalConfig.MaxTradeHoldDuration) || tradeOffer.ItemsToGive.Any(item => GlobalConfig.GlobalBlacklist.Contains(item.RealAppID))) { return(ParseTradeResult.RejectedPermanently); } } // Now check if it's worth for us to do the trade await LimitInventoryRequestsAsync().ConfigureAwait(false); HashSet <Steam.Item> inventory = await Bot.ArchiWebHandler.GetMyInventory(false).ConfigureAwait(false); if ((inventory == null) || (inventory.Count == 0)) { return(ParseTradeResult.AcceptedWithItemLose); // OK, assume that this trade is valid, we can't check our EQ } // Get appIDs we're interested in HashSet <uint> appIDs = new HashSet <uint>(tradeOffer.ItemsToGive.Select(item => item.RealAppID)); // Now remove from our inventory all items we're NOT interested in inventory.RemoveWhere(item => !appIDs.Contains(item.RealAppID)); inventory.TrimExcess(); // If for some reason Valve is talking crap and we can't find mentioned items, assume OK if (inventory.Count == 0) { return(ParseTradeResult.AcceptedWithItemLose); } // Now let's create a map which maps items to their amount in our EQ Dictionary <ulong, uint> amountMap = new Dictionary <ulong, uint>(); foreach (Steam.Item item in inventory) { uint amount; if (amountMap.TryGetValue(item.ClassID, out amount)) { amountMap[item.ClassID] = amount + item.Amount; } else { amountMap[item.ClassID] = item.Amount; } } // Calculate our value of items to give List <uint> amountsToGive = new List <uint>(tradeOffer.ItemsToGive.Count); Dictionary <ulong, uint> amountMapToGive = new Dictionary <ulong, uint>(amountMap); foreach (ulong key in tradeOffer.ItemsToGive.Select(item => item.ClassID)) { uint amount; if (!amountMapToGive.TryGetValue(key, out amount)) { amountsToGive.Add(0); continue; } amountsToGive.Add(amount); amountMapToGive[key] = amount - 1; // We're giving one, so we have one less } // Sort it ascending amountsToGive.Sort(); // Calculate our value of items to receive List <uint> amountsToReceive = new List <uint>(tradeOffer.ItemsToReceive.Count); Dictionary <ulong, uint> amountMapToReceive = new Dictionary <ulong, uint>(amountMap); foreach (ulong key in tradeOffer.ItemsToReceive.Select(item => item.ClassID)) { uint amount; if (!amountMapToReceive.TryGetValue(key, out amount)) { amountsToReceive.Add(0); continue; } amountsToReceive.Add(amount); amountMapToReceive[key] = amount + 1; // We're getting one, so we have one more } // Sort it ascending amountsToReceive.Sort(); // Check actual difference // We sum only values at proper indexes of giving, because user might be overpaying int difference = amountsToGive.Select((t, i) => (int)(t - amountsToReceive[i])).Sum(); // Trade is worth for us if the difference is greater than 0 return(difference > 0 ? ParseTradeResult.AcceptedWithItemLose : ParseTradeResult.RejectedTemporarily); }
private async Task <ParseTradeResult> ShouldAcceptTrade(Steam.TradeOffer tradeOffer) { if (tradeOffer == null) { Bot.ArchiLogger.LogNullError(nameof(tradeOffer)); return(null); } // Always accept trades from SteamMasterID if ((tradeOffer.OtherSteamID64 != 0) && ((tradeOffer.OtherSteamID64 == Bot.BotConfig.SteamMasterID) || (tradeOffer.OtherSteamID64 == Program.GlobalConfig.SteamOwnerID))) { return(new ParseTradeResult(tradeOffer.TradeOfferID, tradeOffer.ItemsToGive.Count > 0 ? ParseTradeResult.EResult.AcceptedWithItemLose : ParseTradeResult.EResult.AcceptedWithoutItemLose)); } // Check if it's donation trade if (tradeOffer.ItemsToGive.Count == 0) { ParseTradeResult.EResult donationResult; // If it's steam fuckup, temporarily ignore it, otherwise react accordingly, depending on our preference if (tradeOffer.ItemsToReceive.Count == 0) { donationResult = ParseTradeResult.EResult.RejectedTemporarily; } else if (Bot.BotConfig.TradingPreferences.HasFlag(BotConfig.ETradingPreferences.AcceptDonations) || ((tradeOffer.OtherSteamID64 != 0) && Bot.Bots.Values.Any(bot => bot.SteamID == tradeOffer.OtherSteamID64))) { donationResult = ParseTradeResult.EResult.AcceptedWithoutItemLose; } else { donationResult = ParseTradeResult.EResult.RejectedPermanently; } return(new ParseTradeResult(tradeOffer.TradeOfferID, donationResult)); } // If we don't have SteamTradeMatcher enabled, this is the end for us if (!Bot.BotConfig.TradingPreferences.HasFlag(BotConfig.ETradingPreferences.SteamTradeMatcher)) { return(new ParseTradeResult(tradeOffer.TradeOfferID, ParseTradeResult.EResult.RejectedPermanently)); } // Decline trade if we're giving more count-wise if (tradeOffer.ItemsToGive.Count > tradeOffer.ItemsToReceive.Count) { return(new ParseTradeResult(tradeOffer.TradeOfferID, ParseTradeResult.EResult.RejectedPermanently)); } // Decline trade if we're losing anything but steam cards, or if it's non-dupes trade if (!tradeOffer.IsSteamCardsRequest() || !tradeOffer.IsFairTypesExchange()) { return(new ParseTradeResult(tradeOffer.TradeOfferID, ParseTradeResult.EResult.RejectedPermanently)); } // At this point we're sure that STM trade is valid // Fetch trade hold duration byte?holdDuration = await Bot.ArchiWebHandler.GetTradeHoldDuration(tradeOffer.TradeOfferID).ConfigureAwait(false); if (!holdDuration.HasValue) { // If we can't get trade hold duration, reject trade temporarily return(new ParseTradeResult(tradeOffer.TradeOfferID, ParseTradeResult.EResult.RejectedTemporarily)); } // If user has a trade hold, we add extra logic if (holdDuration.Value > 0) { // If trade hold duration exceeds our max, or user asks for cards with short lifespan, reject the trade if ((holdDuration.Value > Program.GlobalConfig.MaxTradeHoldDuration) || tradeOffer.ItemsToGive.Any(item => GlobalConfig.GlobalBlacklist.Contains(item.RealAppID))) { return(new ParseTradeResult(tradeOffer.TradeOfferID, ParseTradeResult.EResult.RejectedPermanently)); } } // If we're matching everything, this is enough for us if (Bot.BotConfig.TradingPreferences.HasFlag(BotConfig.ETradingPreferences.MatchEverything)) { return(new ParseTradeResult(tradeOffer.TradeOfferID, ParseTradeResult.EResult.AcceptedWithItemLose)); } // Now check if it's worth for us to do the trade await LimitInventoryRequestsAsync().ConfigureAwait(false); HashSet <Steam.Item> inventory = await Bot.ArchiWebHandler.GetMySteamInventory(false, new HashSet <Steam.Item.EType> { Steam.Item.EType.TradingCard }).ConfigureAwait(false); if ((inventory == null) || (inventory.Count == 0)) { return(new ParseTradeResult(tradeOffer.TradeOfferID, ParseTradeResult.EResult.AcceptedWithItemLose)); // OK, assume that this trade is valid, we can't check our EQ } // Get appIDs we're interested in HashSet <uint> appIDs = new HashSet <uint>(tradeOffer.ItemsToGive.Select(item => item.RealAppID)); // Now remove from our inventory all items we're NOT interested in inventory.RemoveWhere(item => !appIDs.Contains(item.RealAppID)); // If for some reason Valve is talking crap and we can't find mentioned items, assume OK if (inventory.Count == 0) { return(new ParseTradeResult(tradeOffer.TradeOfferID, ParseTradeResult.EResult.AcceptedWithItemLose)); } // Now let's create a map which maps items to their amount in our EQ Dictionary <ulong, uint> amountMap = new Dictionary <ulong, uint>(); foreach (Steam.Item item in inventory) { uint amount; if (amountMap.TryGetValue(item.ClassID, out amount)) { amountMap[item.ClassID] = amount + item.Amount; } else { amountMap[item.ClassID] = item.Amount; } } // Calculate our value of items to give List <uint> amountsToGive = new List <uint>(tradeOffer.ItemsToGive.Count); Dictionary <ulong, uint> amountMapToGive = new Dictionary <ulong, uint>(amountMap); foreach (ulong key in tradeOffer.ItemsToGive.Select(item => item.ClassID)) { uint amount; if (!amountMapToGive.TryGetValue(key, out amount)) { amountsToGive.Add(0); continue; } amountsToGive.Add(amount); amountMapToGive[key] = amount - 1; // We're giving one, so we have one less } // Sort it ascending amountsToGive.Sort(); // Calculate our value of items to receive List <uint> amountsToReceive = new List <uint>(tradeOffer.ItemsToReceive.Count); Dictionary <ulong, uint> amountMapToReceive = new Dictionary <ulong, uint>(amountMap); foreach (ulong key in tradeOffer.ItemsToReceive.Select(item => item.ClassID)) { uint amount; if (!amountMapToReceive.TryGetValue(key, out amount)) { amountsToReceive.Add(0); continue; } amountsToReceive.Add(amount); amountMapToReceive[key] = amount + 1; // We're getting one, so we have one more } // Sort it ascending amountsToReceive.Sort(); // Check actual difference // We sum only values at proper indexes of giving, because user might be overpaying int difference = amountsToGive.Select((t, i) => (int)(t - amountsToReceive[i])).Sum(); // Trade is worth for us if the difference is greater than 0 // If not, we assume that the trade might be good for us in the future, unless we're bot account where we assume that inventory doesn't change return(new ParseTradeResult(tradeOffer.TradeOfferID, difference > 0 ? ParseTradeResult.EResult.AcceptedWithItemLose : (Bot.BotConfig.IsBotAccount ? ParseTradeResult.EResult.RejectedPermanently : ParseTradeResult.EResult.RejectedTemporarily))); }
internal List <Steam.TradeOffer> GetTradeOffers() { if (string.IsNullOrEmpty(Bot.BotConfig.SteamApiKey)) { return(null); } KeyValue response = null; using (dynamic iEconService = WebAPI.GetInterface("IEconService", Bot.BotConfig.SteamApiKey)) { iEconService.Timeout = Timeout; for (byte i = 0; i < WebBrowser.MaxRetries && response == null; i++) { try { response = iEconService.GetTradeOffers( get_received_offers: 1, active_only: 1, secure: !Program.GlobalConfig.ForceHttp ); } catch (Exception e) { Logging.LogGenericException(e, Bot.BotName); } } } if (response == null) { Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName); return(null); } List <Steam.TradeOffer> result = new List <Steam.TradeOffer>(); foreach (KeyValue trade in response["trade_offers_received"].Children) { Steam.TradeOffer tradeOffer = new Steam.TradeOffer { tradeofferid = trade["tradeofferid"].AsString(), accountid_other = (uint)trade["accountid_other"].AsUnsignedLong(), // TODO: Correct this when SK2 with https://github.com/SteamRE/SteamKit/pull/255 gets released trade_offer_state = trade["trade_offer_state"].AsEnum <Steam.TradeOffer.ETradeOfferState>() }; foreach (KeyValue item in trade["items_to_give"].Children) { tradeOffer.items_to_give.Add(new Steam.Item { appid = item["appid"].AsString(), contextid = item["contextid"].AsString(), assetid = item["assetid"].AsString(), classid = item["classid"].AsString(), instanceid = item["instanceid"].AsString(), amount = item["amount"].AsString(), }); } foreach (KeyValue item in trade["items_to_receive"].Children) { tradeOffer.items_to_receive.Add(new Steam.Item { appid = item["appid"].AsString(), contextid = item["contextid"].AsString(), assetid = item["assetid"].AsString(), classid = item["classid"].AsString(), instanceid = item["instanceid"].AsString(), amount = item["amount"].AsString(), }); } result.Add(tradeOffer); } return(result); }
internal HashSet <Steam.TradeOffer> GetTradeOffers() { if (string.IsNullOrEmpty(Bot.BotConfig.SteamApiKey)) { // TODO: Correct this when Mono 4.4+ will be a latest stable one | https://bugzilla.xamarin.com/show_bug.cgi?id=39455 Logging.LogNullError("SteamApiKey", Bot.BotName); //Logging.LogNullError(nameof(Bot.BotConfig.SteamApiKey), Bot.BotName); return(null); } KeyValue response = null; using (dynamic iEconService = WebAPI.GetInterface("IEconService", Bot.BotConfig.SteamApiKey)) { iEconService.Timeout = Timeout; for (byte i = 0; (i < WebBrowser.MaxRetries) && (response == null); i++) { try { response = iEconService.GetTradeOffers( get_received_offers: 1, active_only: 1, get_descriptions: 1, secure: !Program.GlobalConfig.ForceHttp ); } catch (Exception e) { Logging.LogGenericException(e, Bot.BotName); } } } if (response == null) { Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName); return(null); } Dictionary <ulong, Tuple <uint, Steam.Item.EType> > descriptions = new Dictionary <ulong, Tuple <uint, Steam.Item.EType> >(); foreach (KeyValue description in response["descriptions"].Children) { ulong classID = description["classid"].AsUnsignedLong(); if (classID == 0) { Logging.LogNullError(nameof(classID), Bot.BotName); continue; } if (descriptions.ContainsKey(classID)) { continue; } uint appID = 0; string hashName = description["market_hash_name"].Value; if (!string.IsNullOrEmpty(hashName)) { appID = GetAppIDFromMarketHashName(hashName); } if (appID == 0) { appID = (uint)description["appid"].AsUnsignedLong(); } Steam.Item.EType type = Steam.Item.EType.Unknown; string descriptionType = description["type"].Value; if (!string.IsNullOrEmpty(descriptionType)) { type = GetItemType(descriptionType); } descriptions[classID] = new Tuple <uint, Steam.Item.EType>(appID, type); } HashSet <Steam.TradeOffer> result = new HashSet <Steam.TradeOffer>(); foreach (KeyValue trade in response["trade_offers_received"].Children) { Steam.TradeOffer tradeOffer = new Steam.TradeOffer { TradeOfferID = trade["tradeofferid"].AsUnsignedLong(), OtherSteamID3 = (uint)trade["accountid_other"].AsUnsignedLong(), State = trade["trade_offer_state"].AsEnum <Steam.TradeOffer.ETradeOfferState>() }; foreach (Steam.Item steamItem in trade["items_to_give"].Children.Select(item => new Steam.Item { AppID = (uint)item["appid"].AsUnsignedLong(), ContextID = item["contextid"].AsUnsignedLong(), AssetID = item["assetid"].AsUnsignedLong(), ClassID = item["classid"].AsUnsignedLong(), InstanceID = item["instanceid"].AsUnsignedLong(), Amount = (uint)item["amount"].AsUnsignedLong() })) { Tuple <uint, Steam.Item.EType> description; if (descriptions.TryGetValue(steamItem.ClassID, out description)) { steamItem.RealAppID = description.Item1; steamItem.Type = description.Item2; } tradeOffer.ItemsToGive.Add(steamItem); } foreach (Steam.Item steamItem in trade["items_to_receive"].Children.Select(item => new Steam.Item { AppID = (uint)item["appid"].AsUnsignedLong(), ContextID = item["contextid"].AsUnsignedLong(), AssetID = item["assetid"].AsUnsignedLong(), ClassID = item["classid"].AsUnsignedLong(), InstanceID = item["instanceid"].AsUnsignedLong(), Amount = (uint)item["amount"].AsUnsignedLong() })) { Tuple <uint, Steam.Item.EType> description; if (descriptions.TryGetValue(steamItem.ClassID, out description)) { steamItem.RealAppID = description.Item1; steamItem.Type = description.Item2; } tradeOffer.ItemsToReceive.Add(steamItem); } result.Add(tradeOffer); } return(result); }
private async Task <bool> ShouldAcceptTrade(Steam.TradeOffer tradeOffer) { if (tradeOffer == null) { Logging.LogNullError(nameof(tradeOffer), Bot.BotName); return(false); } // Always accept trades when we're not losing anything if (tradeOffer.ItemsToGive.Count == 0) { // Unless it's steam fuckup and we're dealing with broken trade return(tradeOffer.ItemsToReceive.Count > 0); } // Always accept trades from SteamMasterID if ((tradeOffer.OtherSteamID64 != 0) && (tradeOffer.OtherSteamID64 == Bot.BotConfig.SteamMasterID)) { return(true); } // If we don't have SteamTradeMatcher enabled, this is the end for us if (!Bot.BotConfig.SteamTradeMatcher) { return(false); } // Decline trade if we're giving more count-wise if (tradeOffer.ItemsToGive.Count > tradeOffer.ItemsToReceive.Count) { return(false); } // Decline trade if we're losing anything but steam cards, or if it's non-dupes trade if (!tradeOffer.IsSteamCardsOnlyTradeForUs() || !tradeOffer.IsPotentiallyDupesTradeForUs()) { return(false); } // At this point we're sure that STM trade is valid // Now check if it's worth for us to do the trade HashSet <Steam.Item> inventory = await Bot.ArchiWebHandler.GetMyInventory(false).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 <uint> appIDs = new HashSet <uint>(tradeOffer.ItemsToGive.Select(item => item.RealAppID)); // Now remove from our inventory all items we're NOT interested in inventory.RemoveWhere(item => !appIDs.Contains(item.RealAppID)); inventory.TrimExcess(); // If for some reason Valve is talking crap and we can't find mentioned items, assume OK if (inventory.Count == 0) { return(true); } // Now let's create a map which maps items to their amount in our EQ Dictionary <ulong, uint> amountMap = new Dictionary <ulong, uint>(); foreach (Steam.Item item in inventory) { uint amount; if (amountMap.TryGetValue(item.ClassID, out amount)) { amountMap[item.ClassID] = amount + item.Amount; } else { amountMap[item.ClassID] = item.Amount; } } // Calculate our value of items to give List <uint> amountsToGive = new List <uint>(tradeOffer.ItemsToGive.Count); foreach (ulong key in tradeOffer.ItemsToGive.Select(item => item.ClassID)) { uint amount; if (!amountMap.TryGetValue(key, out amount)) { amountsToGive.Add(0); continue; } amountsToGive.Add(amount); } // Sort it ascending amountsToGive.Sort(); // Calculate our value of items to receive List <uint> amountsToReceive = new List <uint>(tradeOffer.ItemsToReceive.Count); foreach (ulong key in tradeOffer.ItemsToReceive.Select(item => item.ClassID)) { uint amount; if (!amountMap.TryGetValue(key, out amount)) { amountsToReceive.Add(0); continue; } amountsToReceive.Add(amount); } // Sort it ascending amountsToReceive.Sort(); // Check actual difference int difference = amountsToGive.Select((t, i) => (int)(t - amountsToReceive[i])).Sum(); // Trade is worth for us if the difference is greater than 0 return(difference > 0); }
internal List<Steam.TradeOffer> GetTradeOffers() { if (string.IsNullOrEmpty(Bot.BotConfig.SteamApiKey)) { return null; } KeyValue response = null; using (dynamic iEconService = WebAPI.GetInterface("IEconService", Bot.BotConfig.SteamApiKey)) { iEconService.Timeout = Timeout; for (byte i = 0; i < WebBrowser.MaxRetries && response == null; i++) { try { response = iEconService.GetTradeOffers( get_received_offers: 1, active_only: 1, secure: !Program.GlobalConfig.ForceHttp ); } catch (Exception e) { Logging.LogGenericException(e, Bot.BotName); } } } if (response == null) { Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName); return null; } List<Steam.TradeOffer> result = new List<Steam.TradeOffer>(); foreach (KeyValue trade in response["trade_offers_received"].Children) { Steam.TradeOffer tradeOffer = new Steam.TradeOffer { tradeofferid = trade["tradeofferid"].AsString(), accountid_other = (uint) trade["accountid_other"].AsUnsignedLong(), // TODO: Correct this when SK2 with https://github.com/SteamRE/SteamKit/pull/255 gets released trade_offer_state = trade["trade_offer_state"].AsEnum<Steam.TradeOffer.ETradeOfferState>() }; foreach (KeyValue item in trade["items_to_give"].Children) { tradeOffer.items_to_give.Add(new Steam.Item { appid = item["appid"].AsString(), contextid = item["contextid"].AsString(), assetid = item["assetid"].AsString(), classid = item["classid"].AsString(), instanceid = item["instanceid"].AsString(), amount = item["amount"].AsString(), }); } foreach (KeyValue item in trade["items_to_receive"].Children) { tradeOffer.items_to_receive.Add(new Steam.Item { appid = item["appid"].AsString(), contextid = item["contextid"].AsString(), assetid = item["assetid"].AsString(), classid = item["classid"].AsString(), instanceid = item["instanceid"].AsString(), amount = item["amount"].AsString(), }); } result.Add(tradeOffer); } return result; }