mirror of
https://github.com/JustArchiNET/ArchiSteamFarm
synced 2024-11-10 15:14:41 +00:00
Confirmations logic improvements
This commit is contained in:
parent
a1e2d4f8c1
commit
353e7e7b88
4 changed files with 104 additions and 54 deletions
|
@ -284,15 +284,21 @@ namespace ArchiSteamFarm {
|
|||
return (false, string.Format(Strings.ErrorIsEmpty, nameof(inventory)));
|
||||
}
|
||||
|
||||
if (!await Bot.ArchiWebHandler.MarkSentTrades().ConfigureAwait(false) || !await Bot.ArchiWebHandler.SendTradeOffer(targetSteamID, inventory, Bot.BotConfig.SteamTradeToken).ConfigureAwait(false)) {
|
||||
if (!await Bot.ArchiWebHandler.MarkSentTrades().ConfigureAwait(false)) {
|
||||
return (false, Strings.BotLootingFailed);
|
||||
}
|
||||
|
||||
if (Bot.HasMobileAuthenticator) {
|
||||
if (!await AcceptConfirmations(true, Steam.ConfirmationDetails.EType.Trade, targetSteamID, waitIfNeeded: true).ConfigureAwait(false)) {
|
||||
(bool success, HashSet<ulong> mobileTradeOfferIDs) = await Bot.ArchiWebHandler.SendTradeOffer(targetSteamID, inventory, Bot.BotConfig.SteamTradeToken).ConfigureAwait(false);
|
||||
|
||||
if ((mobileTradeOfferIDs != null) && (mobileTradeOfferIDs.Count > 0) && Bot.HasMobileAuthenticator) {
|
||||
if (!await AcceptConfirmations(true, Steam.ConfirmationDetails.EType.Trade, targetSteamID, mobileTradeOfferIDs, true).ConfigureAwait(false)) {
|
||||
return (false, Strings.BotLootingFailed);
|
||||
}
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
return (false, Strings.BotLootingFailed);
|
||||
}
|
||||
} finally {
|
||||
LootingSemaphore.Release();
|
||||
}
|
||||
|
|
|
@ -102,10 +102,10 @@ namespace ArchiSteamFarm {
|
|||
return result?.Success == true;
|
||||
}
|
||||
|
||||
internal async Task<bool> AcceptTradeOffer(ulong tradeID) {
|
||||
internal async Task<(bool Success, bool RequiresMobileConfirmation)> AcceptTradeOffer(ulong tradeID) {
|
||||
if (tradeID == 0) {
|
||||
Bot.ArchiLogger.LogNullError(nameof(tradeID));
|
||||
return false;
|
||||
return (false, false);
|
||||
}
|
||||
|
||||
string request = "/tradeoffer/" + tradeID + "/accept";
|
||||
|
@ -117,7 +117,8 @@ namespace ArchiSteamFarm {
|
|||
{ "tradeofferid", tradeID.ToString() }
|
||||
};
|
||||
|
||||
return await UrlPostWithSession(SteamCommunityURL, request, data, referer).ConfigureAwait(false);
|
||||
Steam.TradeOfferAcceptResponse response = await UrlPostToJsonObjectWithSession<Steam.TradeOfferAcceptResponse>(SteamCommunityURL, request, data, referer).ConfigureAwait(false);
|
||||
return response != null ? (true, response.RequiresMobileConfirmation) : (false, false);
|
||||
}
|
||||
|
||||
internal async Task<bool> AddFreeLicense(uint subID) {
|
||||
|
@ -1196,14 +1197,14 @@ namespace ArchiSteamFarm {
|
|||
return response == null ? ((EResult Result, EPurchaseResultDetail? PurchaseResult)?) null : (response.Result, response.PurchaseResultDetail);
|
||||
}
|
||||
|
||||
internal async Task<bool> SendTradeOffer(ulong partnerID, IReadOnlyCollection<Steam.Asset> itemsToGive, string token = null) {
|
||||
internal async Task<(bool Success, HashSet<ulong> MobileTradeOfferIDs)> SendTradeOffer(ulong partnerID, IReadOnlyCollection<Steam.Asset> itemsToGive, string token = null) {
|
||||
if ((partnerID == 0) || (itemsToGive == null) || (itemsToGive.Count == 0)) {
|
||||
Bot.ArchiLogger.LogNullError(nameof(partnerID) + " || " + nameof(itemsToGive));
|
||||
return false;
|
||||
return (false, null);
|
||||
}
|
||||
|
||||
Steam.TradeOfferRequest singleTrade = new Steam.TradeOfferRequest();
|
||||
HashSet<Steam.TradeOfferRequest> trades = new HashSet<Steam.TradeOfferRequest> { singleTrade };
|
||||
Steam.TradeOfferSendRequest singleTrade = new Steam.TradeOfferSendRequest();
|
||||
HashSet<Steam.TradeOfferSendRequest> trades = new HashSet<Steam.TradeOfferSendRequest> { singleTrade };
|
||||
|
||||
foreach (Steam.Asset itemToGive in itemsToGive) {
|
||||
if (singleTrade.ItemsToGive.Assets.Count >= Trading.MaxItemsPerTrade) {
|
||||
|
@ -1211,7 +1212,7 @@ namespace ArchiSteamFarm {
|
|||
break;
|
||||
}
|
||||
|
||||
singleTrade = new Steam.TradeOfferRequest();
|
||||
singleTrade = new Steam.TradeOfferSendRequest();
|
||||
trades.Add(singleTrade);
|
||||
}
|
||||
|
||||
|
@ -1222,21 +1223,30 @@ namespace ArchiSteamFarm {
|
|||
const string referer = SteamCommunityURL + "/tradeoffer/new";
|
||||
|
||||
// Extra entry for sessionID
|
||||
foreach (Dictionary<string, string> data in trades.Select(
|
||||
trade => new Dictionary<string, string>(6) {
|
||||
{ "json_tradeoffer", JsonConvert.SerializeObject(trade) },
|
||||
{ "partner", partnerID.ToString() },
|
||||
{ "serverid", "1" },
|
||||
{ "trade_offer_create_params", string.IsNullOrEmpty(token) ? "" : new JObject { { "trade_offer_access_token", token } }.ToString(Formatting.None) },
|
||||
{ "tradeoffermessage", "Sent by " + SharedInfo.PublicIdentifier + "/" + SharedInfo.Version }
|
||||
Dictionary<string, string> data = new Dictionary<string, string>(6) {
|
||||
{ "partner", partnerID.ToString() },
|
||||
{ "serverid", "1" },
|
||||
{ "trade_offer_create_params", string.IsNullOrEmpty(token) ? "" : new JObject { { "trade_offer_access_token", token } }.ToString(Formatting.None) },
|
||||
{ "tradeoffermessage", "Sent by " + SharedInfo.PublicIdentifier + "/" + SharedInfo.Version }
|
||||
};
|
||||
|
||||
HashSet<ulong> mobileTradeOfferIDs = new HashSet<ulong>();
|
||||
|
||||
foreach (Steam.TradeOfferSendRequest trade in trades) {
|
||||
data["json_tradeoffer"] = JsonConvert.SerializeObject(trade);
|
||||
|
||||
Steam.TradeOfferSendResponse response = await UrlPostToJsonObjectWithSession<Steam.TradeOfferSendResponse>(SteamCommunityURL, request, data, referer).ConfigureAwait(false);
|
||||
|
||||
if (response == null) {
|
||||
return (false, mobileTradeOfferIDs);
|
||||
}
|
||||
)) {
|
||||
if (!await UrlPostWithSession(SteamCommunityURL, request, data, referer).ConfigureAwait(false)) {
|
||||
return false;
|
||||
|
||||
if (response.RequiresMobileConfirmation) {
|
||||
mobileTradeOfferIDs.Add(response.TradeOfferID);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return (true, mobileTradeOfferIDs);
|
||||
}
|
||||
|
||||
internal async Task<bool> SteamAwardsVote(byte voteID, uint appID) {
|
||||
|
|
|
@ -483,7 +483,16 @@ namespace ArchiSteamFarm.Json {
|
|||
}
|
||||
}
|
||||
|
||||
internal sealed class TradeOfferRequest {
|
||||
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
|
||||
internal sealed class TradeOfferAcceptResponse {
|
||||
[JsonProperty(PropertyName = "needs_mobile_confirmation", Required = Required.Always)]
|
||||
internal readonly bool RequiresMobileConfirmation;
|
||||
|
||||
// Deserialized from JSON
|
||||
private TradeOfferAcceptResponse() { }
|
||||
}
|
||||
|
||||
internal sealed class TradeOfferSendRequest {
|
||||
[JsonProperty(PropertyName = "me", Required = Required.Always)]
|
||||
internal readonly ItemList ItemsToGive = new ItemList();
|
||||
|
||||
|
@ -496,6 +505,34 @@ namespace ArchiSteamFarm.Json {
|
|||
}
|
||||
}
|
||||
|
||||
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
|
||||
internal sealed class TradeOfferSendResponse {
|
||||
[JsonProperty(PropertyName = "needs_mobile_confirmation", Required = Required.Always)]
|
||||
internal readonly bool RequiresMobileConfirmation;
|
||||
|
||||
internal ulong TradeOfferID { get; private set; }
|
||||
|
||||
[JsonProperty(PropertyName = "tradeofferid", Required = Required.Always)]
|
||||
private string TradeOfferIDText {
|
||||
set {
|
||||
if (string.IsNullOrEmpty(value)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(value));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ulong.TryParse(value, out ulong tradeOfferID) || (tradeOfferID == 0)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(tradeOfferID));
|
||||
return;
|
||||
}
|
||||
|
||||
TradeOfferID = tradeOfferID;
|
||||
}
|
||||
}
|
||||
|
||||
// Deserialized from JSON
|
||||
private TradeOfferSendResponse() { }
|
||||
}
|
||||
|
||||
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
|
||||
internal sealed class UserPrivacy {
|
||||
[JsonProperty(PropertyName = "eCommentPermission", Required = Required.Always)]
|
||||
|
|
|
@ -199,50 +199,51 @@ namespace ArchiSteamFarm {
|
|||
return;
|
||||
}
|
||||
|
||||
IList<ParseTradeResult> results = await Utilities.InParallel(tradeOffers.Select(ParseTrade)).ConfigureAwait(false);
|
||||
IList<(ParseTradeResult TradeResult, bool RequiresMobileConfirmation)> results = await Utilities.InParallel(tradeOffers.Select(ParseTrade)).ConfigureAwait(false);
|
||||
|
||||
if (Bot.HasMobileAuthenticator) {
|
||||
HashSet<ulong> acceptedWithItemLoseTradeIDs = results.Where(result => (result != null) && (result.Result == ParseTradeResult.EResult.AcceptedWithItemLose)).Select(result => result.TradeID).ToHashSet();
|
||||
if (acceptedWithItemLoseTradeIDs.Count > 0) {
|
||||
await Bot.Actions.AcceptConfirmations(true, Steam.ConfirmationDetails.EType.Trade, 0, acceptedWithItemLoseTradeIDs, true).ConfigureAwait(false);
|
||||
HashSet<ulong> mobileTradeOfferIDs = results.Where(result => (result.TradeResult != null) && result.RequiresMobileConfirmation).Select(result => result.TradeResult.TradeOfferID).ToHashSet();
|
||||
if (mobileTradeOfferIDs.Count > 0) {
|
||||
await Bot.Actions.AcceptConfirmations(true, Steam.ConfirmationDetails.EType.Trade, 0, mobileTradeOfferIDs, true).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (results.Any(result => (result != null) && ((result.Result == ParseTradeResult.EResult.AcceptedWithItemLose) || (result.Result == ParseTradeResult.EResult.AcceptedWithoutItemLose))) && Bot.BotConfig.SendOnFarmingFinished) {
|
||||
if (results.Any(result => (result.TradeResult != null) && (result.TradeResult.Result == ParseTradeResult.EResult.Accepted)) && Bot.BotConfig.SendOnFarmingFinished) {
|
||||
// If we finished a trade, perform a loot if user wants to do so
|
||||
await Bot.Actions.SendTradeOffer(wantedTypes: Bot.BotConfig.LootableTypes).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<ParseTradeResult> ParseTrade(Steam.TradeOffer tradeOffer) {
|
||||
private async Task<(ParseTradeResult TradeResult, bool RequiresMobileConfirmation)> ParseTrade(Steam.TradeOffer tradeOffer) {
|
||||
if (tradeOffer == null) {
|
||||
Bot.ArchiLogger.LogNullError(nameof(tradeOffer));
|
||||
return null;
|
||||
return (null, false);
|
||||
}
|
||||
|
||||
if (tradeOffer.State != Steam.TradeOffer.ETradeOfferState.Active) {
|
||||
Bot.ArchiLogger.LogGenericError(string.Format(Strings.ErrorIsInvalid, tradeOffer.State));
|
||||
return null;
|
||||
return (null, false);
|
||||
}
|
||||
|
||||
ParseTradeResult result = await ShouldAcceptTrade(tradeOffer).ConfigureAwait(false);
|
||||
if (result == null) {
|
||||
Bot.ArchiLogger.LogNullError(nameof(result));
|
||||
return null;
|
||||
return (null, false);
|
||||
}
|
||||
|
||||
switch (result.Result) {
|
||||
case ParseTradeResult.EResult.AcceptedWithItemLose:
|
||||
case ParseTradeResult.EResult.AcceptedWithoutItemLose:
|
||||
case ParseTradeResult.EResult.Accepted:
|
||||
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.AcceptingTrade, tradeOffer.TradeOfferID));
|
||||
|
||||
if (await Bot.ArchiWebHandler.AcceptTradeOffer(tradeOffer.TradeOfferID).ConfigureAwait(false)) {
|
||||
(bool success, bool requiresMobileConfirmation) = await Bot.ArchiWebHandler.AcceptTradeOffer(tradeOffer.TradeOfferID).ConfigureAwait(false);
|
||||
|
||||
if (success) {
|
||||
if (tradeOffer.ItemsToReceive.Sum(item => item.Amount) > tradeOffer.ItemsToGive.Sum(item => item.Amount)) {
|
||||
Bot.ArchiLogger.LogGenericTrace(string.Format(Strings.BotAcceptedDonationTrade, tradeOffer.TradeOfferID));
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
return (result, requiresMobileConfirmation);
|
||||
case ParseTradeResult.EResult.RejectedPermanently:
|
||||
case ParseTradeResult.EResult.RejectedTemporarily:
|
||||
if (result.Result == ParseTradeResult.EResult.RejectedPermanently) {
|
||||
|
@ -254,17 +255,15 @@ namespace ArchiSteamFarm {
|
|||
}
|
||||
|
||||
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.IgnoringTrade, tradeOffer.TradeOfferID));
|
||||
break;
|
||||
return (result, false);
|
||||
case ParseTradeResult.EResult.RejectedAndBlacklisted:
|
||||
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.RejectingTrade, tradeOffer.TradeOfferID));
|
||||
await Bot.ArchiWebHandler.DeclineTradeOffer(tradeOffer.TradeOfferID).ConfigureAwait(false);
|
||||
break;
|
||||
return (result, false);
|
||||
default:
|
||||
Bot.ArchiLogger.LogGenericError(string.Format(Strings.WarningUnknownValuePleaseReport, nameof(result.Result), result.Result));
|
||||
return null;
|
||||
return (null, false);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private async Task<ParseTradeResult> ShouldAcceptTrade(Steam.TradeOffer tradeOffer) {
|
||||
|
@ -276,7 +275,7 @@ namespace ArchiSteamFarm {
|
|||
if (tradeOffer.OtherSteamID64 != 0) {
|
||||
// Always accept trades from SteamMasterID
|
||||
if (Bot.IsMaster(tradeOffer.OtherSteamID64)) {
|
||||
return new ParseTradeResult(tradeOffer.TradeOfferID, tradeOffer.ItemsToGive.Count > 0 ? ParseTradeResult.EResult.AcceptedWithItemLose : ParseTradeResult.EResult.AcceptedWithoutItemLose);
|
||||
return new ParseTradeResult(tradeOffer.TradeOfferID, ParseTradeResult.EResult.Accepted);
|
||||
}
|
||||
|
||||
// Always deny trades from blacklisted steamIDs
|
||||
|
@ -297,7 +296,7 @@ namespace ArchiSteamFarm {
|
|||
|
||||
// If we accept donations and bot trades, accept it right away
|
||||
if (acceptDonations && acceptBotTrades) {
|
||||
return new ParseTradeResult(tradeOffer.TradeOfferID, ParseTradeResult.EResult.AcceptedWithoutItemLose);
|
||||
return new ParseTradeResult(tradeOffer.TradeOfferID, ParseTradeResult.EResult.Accepted);
|
||||
}
|
||||
|
||||
// If we don't accept donations, neither bot trades, deny it right away
|
||||
|
@ -307,7 +306,7 @@ namespace ArchiSteamFarm {
|
|||
|
||||
// Otherwise we either accept donations but not bot trades, or we accept bot trades but not donations
|
||||
bool isBotTrade = (tradeOffer.OtherSteamID64 != 0) && Bot.Bots.Values.Any(bot => bot.SteamID == tradeOffer.OtherSteamID64);
|
||||
return new ParseTradeResult(tradeOffer.TradeOfferID, (acceptDonations && !isBotTrade) || (acceptBotTrades && isBotTrade) ? ParseTradeResult.EResult.AcceptedWithoutItemLose : ParseTradeResult.EResult.RejectedPermanently);
|
||||
return new ParseTradeResult(tradeOffer.TradeOfferID, (acceptDonations && !isBotTrade) || (acceptBotTrades && isBotTrade) ? ParseTradeResult.EResult.Accepted : ParseTradeResult.EResult.RejectedPermanently);
|
||||
}
|
||||
|
||||
// If we don't have SteamTradeMatcher enabled, this is the end for us
|
||||
|
@ -344,7 +343,7 @@ namespace ArchiSteamFarm {
|
|||
|
||||
// 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);
|
||||
return new ParseTradeResult(tradeOffer.TradeOfferID, ParseTradeResult.EResult.Accepted);
|
||||
}
|
||||
|
||||
// Get appIDs/types we're interested in
|
||||
|
@ -367,27 +366,25 @@ namespace ArchiSteamFarm {
|
|||
bool accept = IsTradeNeutralOrBetter(inventory, tradeOffer.ItemsToGive, tradeOffer.ItemsToReceive);
|
||||
|
||||
// Even if trade is not neutral+ for us right now, it might be in the future, unless we're bot account where we assume that inventory doesn't change
|
||||
return new ParseTradeResult(tradeOffer.TradeOfferID, accept ? ParseTradeResult.EResult.AcceptedWithItemLose : (Bot.BotConfig.BotBehaviour.HasFlag(BotConfig.EBotBehaviour.RejectInvalidTrades) ? ParseTradeResult.EResult.RejectedPermanently : ParseTradeResult.EResult.RejectedTemporarily));
|
||||
return new ParseTradeResult(tradeOffer.TradeOfferID, accept ? ParseTradeResult.EResult.Accepted : (Bot.BotConfig.BotBehaviour.HasFlag(BotConfig.EBotBehaviour.RejectInvalidTrades) ? ParseTradeResult.EResult.RejectedPermanently : ParseTradeResult.EResult.RejectedTemporarily));
|
||||
}
|
||||
|
||||
private sealed class ParseTradeResult {
|
||||
internal readonly EResult Result;
|
||||
internal readonly ulong TradeOfferID;
|
||||
|
||||
internal readonly ulong TradeID;
|
||||
|
||||
internal ParseTradeResult(ulong tradeID, EResult result) {
|
||||
if ((tradeID == 0) || (result == EResult.Unknown)) {
|
||||
throw new ArgumentNullException(nameof(tradeID) + " || " + nameof(result));
|
||||
internal ParseTradeResult(ulong tradeOfferID, EResult result) {
|
||||
if ((tradeOfferID == 0) || (result == EResult.Unknown)) {
|
||||
throw new ArgumentNullException(nameof(tradeOfferID) + " || " + nameof(result));
|
||||
}
|
||||
|
||||
TradeID = tradeID;
|
||||
TradeOfferID = tradeOfferID;
|
||||
Result = result;
|
||||
}
|
||||
|
||||
internal enum EResult : byte {
|
||||
Unknown,
|
||||
AcceptedWithItemLose,
|
||||
AcceptedWithoutItemLose,
|
||||
Accepted,
|
||||
RejectedTemporarily,
|
||||
RejectedPermanently,
|
||||
RejectedAndBlacklisted
|
||||
|
|
Loading…
Reference in a new issue