예제 #1
0
        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);
        }
예제 #2
0
        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).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)));
        }