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