Decrease announcement time, set listener for finished trade offers

This commit is contained in:
Archi
2023-01-05 14:30:08 +01:00
parent 8a497fe4d3
commit dea715ff1e
4 changed files with 53 additions and 27 deletions

View File

@@ -4,7 +4,7 @@
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// |
// Copyright 2015-2022 Łukasz "JustArchi" Domeradzki
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
// Contact: JustArchi@JustArchi.net
// |
// Licensed under the Apache License, Version 2.0 (the "License");
@@ -31,6 +31,7 @@ using ArchiSteamFarm.OfficialPlugins.ItemsMatcher.Localization;
using ArchiSteamFarm.Plugins;
using ArchiSteamFarm.Plugins.Interfaces;
using ArchiSteamFarm.Steam;
using ArchiSteamFarm.Steam.Exchange;
using ArchiSteamFarm.Steam.Integration.Callbacks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
@@ -39,7 +40,7 @@ using SteamKit2;
namespace ArchiSteamFarm.OfficialPlugins.ItemsMatcher;
[Export(typeof(IPlugin))]
internal sealed class ItemsMatcherPlugin : OfficialPlugin, IBot, IBotCommand2, IBotIdentity, IBotModules, IBotUserNotifications {
internal sealed class ItemsMatcherPlugin : OfficialPlugin, IBot, IBotCommand2, IBotIdentity, IBotModules, IBotTradeOfferResults, IBotUserNotifications {
internal static readonly ConcurrentDictionary<Bot, RemoteCommunication> RemoteCommunications = new();
[JsonProperty]
@@ -98,6 +99,23 @@ internal sealed class ItemsMatcherPlugin : OfficialPlugin, IBot, IBotCommand2, I
}
}
public Task OnBotTradeOfferResults(Bot bot, IReadOnlyCollection<ParseTradeResult> tradeResults) {
ArgumentNullException.ThrowIfNull(bot);
if ((tradeResults == null) || (tradeResults.Count == 0)) {
throw new ArgumentNullException(nameof(tradeResults));
}
// We're interested only in Items notification for Bot that has RemoteCommunication enabled
if (!RemoteCommunications.TryGetValue(bot, out RemoteCommunication? remoteCommunication) || !tradeResults.Any(tradeResult => tradeResult is { Result: ParseTradeResult.EResult.Accepted, Confirmed: true } && ((tradeResult.ItemsToGive?.Any(item => bot.BotConfig.MatchableTypes.Contains(item.Type)) == true) || (tradeResult.ItemsToReceive?.Any(item => bot.BotConfig.MatchableTypes.Contains(item.Type)) == true)))) {
return Task.CompletedTask;
}
remoteCommunication.OnNewItemsNotification();
return Task.CompletedTask;
}
public Task OnBotUserNotifications(Bot bot, IReadOnlyCollection<UserNotificationsCallback.EUserNotification> newNotifications) {
ArgumentNullException.ThrowIfNull(bot);

View File

@@ -45,7 +45,7 @@ using ArchiSteamFarm.Web.Responses;
namespace ArchiSteamFarm.OfficialPlugins.ItemsMatcher;
internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
private const byte MaxAnnouncementTTL = 60; // Maximum amount of minutes we can wait before the next Announcement
private const byte MaxAnnouncementTTL = 180; // Maximum amount of minutes we can wait before the next Announcement
private const byte MinAnnouncementTTL = 5; // Minimum amount of minutes we must wait before the next Announcement
private const byte MinHeartBeatTTL = 10; // Minimum amount of minutes we must wait before sending next HeartBeat
private const byte MinItemsCount = 100; // Minimum amount of items to be eligible for public listing

View File

@@ -4,7 +4,7 @@
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// |
// Copyright 2015-2022 Łukasz "JustArchi" Domeradzki
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
// Contact: JustArchi@JustArchi.net
// |
// Licensed under the Apache License, Version 2.0 (the "License");
@@ -21,24 +21,29 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel;
using System.Linq;
using ArchiSteamFarm.Steam.Data;
using JetBrains.Annotations;
namespace ArchiSteamFarm.Steam.Exchange;
public sealed class ParseTradeResult {
[PublicAPI]
public IReadOnlyCollection<Asset>? ItemsToGive { get; }
[PublicAPI]
public IReadOnlyCollection<Asset>? ItemsToReceive { get; }
[PublicAPI]
public EResult Result { get; }
[PublicAPI]
public ulong TradeOfferID { get; }
internal readonly ImmutableHashSet<Asset.EType>? ReceivedItemTypes;
[PublicAPI]
public bool Confirmed { get; internal set; }
internal ParseTradeResult(ulong tradeOfferID, EResult result, IReadOnlyCollection<Asset>? itemsToReceive = null) {
internal ParseTradeResult(ulong tradeOfferID, EResult result, bool requiresMobileConfirmation, IReadOnlyCollection<Asset>? itemsToGive = null, IReadOnlyCollection<Asset>? itemsToReceive = null) {
if (tradeOfferID == 0) {
throw new ArgumentOutOfRangeException(nameof(tradeOfferID));
}
@@ -49,10 +54,9 @@ public sealed class ParseTradeResult {
TradeOfferID = tradeOfferID;
Result = result;
if (itemsToReceive?.Count > 0) {
ReceivedItemTypes = itemsToReceive.Select(static item => item.Type).ToImmutableHashSet();
}
Confirmed = !requiresMobileConfirmation;
ItemsToGive = itemsToGive;
ItemsToReceive = itemsToReceive;
}
public enum EResult : byte {

View File

@@ -424,46 +424,50 @@ public sealed class Trading : IDisposable {
HandledTradeOfferIDs.IntersectWith(tradeOffers.Select(static tradeOffer => tradeOffer.TradeOfferID));
}
IEnumerable<Task<(ParseTradeResult? TradeResult, bool RequiresMobileConfirmation)>> tasks = tradeOffers.Where(tradeOffer => !HandledTradeOfferIDs.Contains(tradeOffer.TradeOfferID)).Select(ParseTrade);
IList<(ParseTradeResult? TradeResult, bool RequiresMobileConfirmation)> results = await Utilities.InParallel(tasks).ConfigureAwait(false);
IEnumerable<Task<ParseTradeResult?>> tasks = tradeOffers.Where(tradeOffer => !HandledTradeOfferIDs.Contains(tradeOffer.TradeOfferID)).Select(ParseTrade);
IList<ParseTradeResult?> results = await Utilities.InParallel(tasks).ConfigureAwait(false);
HashSet<ParseTradeResult> validTradeResults = results.Where(static result => result != null).Select(static result => result!).ToHashSet();
if (Bot.HasMobileAuthenticator) {
HashSet<ulong> mobileTradeOfferIDs = results.Where(static result => (result.TradeResult?.Result == ParseTradeResult.EResult.Accepted) && result.RequiresMobileConfirmation).Select(static result => result.TradeResult!.TradeOfferID).ToHashSet();
HashSet<ParseTradeResult> mobileTradeResults = validTradeResults.Where(static result => result is { Result: ParseTradeResult.EResult.Accepted, Confirmed: false }).ToHashSet();
if (mobileTradeResults.Count > 0) {
HashSet<ulong> mobileTradeOfferIDs = mobileTradeResults.Select(static tradeOffer => tradeOffer.TradeOfferID).ToHashSet();
if (mobileTradeOfferIDs.Count > 0) {
(bool twoFactorSuccess, _, _) = await Bot.Actions.HandleTwoFactorAuthenticationConfirmations(true, Confirmation.EType.Trade, mobileTradeOfferIDs, true).ConfigureAwait(false);
if (!twoFactorSuccess) {
if (twoFactorSuccess) {
foreach (ParseTradeResult mobileTradeResult in mobileTradeResults) {
mobileTradeResult.Confirmed = true;
}
} else {
HandledTradeOfferIDs.ExceptWith(mobileTradeOfferIDs);
return false;
}
}
}
HashSet<ParseTradeResult> validTradeResults = results.Where(static result => result.TradeResult != null).Select(static result => result.TradeResult!).ToHashSet();
if (validTradeResults.Count > 0) {
await PluginsCore.OnBotTradeOfferResults(Bot, validTradeResults).ConfigureAwait(false);
}
return results.Any(result => (result.TradeResult?.Result == ParseTradeResult.EResult.Accepted) && (!result.RequiresMobileConfirmation || Bot.HasMobileAuthenticator) && (result.TradeResult.ReceivedItemTypes?.Any(receivedItemType => Bot.BotConfig.LootableTypes.Contains(receivedItemType)) == true));
return validTradeResults.Any(result => result is { Result: ParseTradeResult.EResult.Accepted, Confirmed: true } && (result.ItemsToReceive?.Any(receivedItem => Bot.BotConfig.LootableTypes.Contains(receivedItem.Type)) == true));
}
private async Task<(ParseTradeResult? TradeResult, bool RequiresMobileConfirmation)> ParseTrade(TradeOffer tradeOffer) {
private async Task<ParseTradeResult?> ParseTrade(TradeOffer tradeOffer) {
ArgumentNullException.ThrowIfNull(tradeOffer);
if (tradeOffer.State != ETradeOfferState.Active) {
Bot.ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsInvalid, tradeOffer.State));
return (null, false);
return null;
}
if (!HandledTradeOfferIDs.Add(tradeOffer.TradeOfferID)) {
// We've already seen this trade, this should not happen
Bot.ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.IgnoringTrade, tradeOffer.TradeOfferID));
return (new ParseTradeResult(tradeOffer.TradeOfferID, ParseTradeResult.EResult.Ignored, tradeOffer.ItemsToReceive), false);
return new ParseTradeResult(tradeOffer.TradeOfferID, ParseTradeResult.EResult.Ignored, false, tradeOffer.ItemsToGive, tradeOffer.ItemsToReceive);
}
ParseTradeResult.EResult result = await ShouldAcceptTrade(tradeOffer).ConfigureAwait(false);
@@ -523,10 +527,10 @@ public sealed class Trading : IDisposable {
default:
Bot.ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.WarningUnknownValuePleaseReport, nameof(result), result));
return (null, false);
return null;
}
return (new ParseTradeResult(tradeOffer.TradeOfferID, result, tradeOffer.ItemsToReceive), tradeRequiresMobileConfirmation);
return new ParseTradeResult(tradeOffer.TradeOfferID, result, tradeRequiresMobileConfirmation, tradeOffer.ItemsToGive, tradeOffer.ItemsToReceive);
}
private async Task<ParseTradeResult.EResult> ShouldAcceptTrade(TradeOffer tradeOffer) {