Announce to the listing sooner if inventory has changed

We should announce to the listing at least each 60 minutes, but we should do it faster if we know that our inventory has changed. With this logic we can report in up to 1 minute since the change, which should provide very up-to-date state, but at the same time we still limit amount of announcements to not more than one per 5 minutes.
This commit is contained in:
Archi
2022-12-16 19:57:32 +01:00
parent 047a3ca1d9
commit 643b8a60fc
2 changed files with 196 additions and 169 deletions

View File

@@ -23,12 +23,14 @@ using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Composition;
using System.Linq;
using System.Threading.Tasks;
using ArchiSteamFarm.Core;
using ArchiSteamFarm.OfficialPlugins.ItemsMatcher.Localization;
using ArchiSteamFarm.Plugins;
using ArchiSteamFarm.Plugins.Interfaces;
using ArchiSteamFarm.Steam;
using ArchiSteamFarm.Steam.Integration.Callbacks;
using ArchiSteamFarm.Steam.Storage;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
@@ -37,7 +39,7 @@ using SteamKit2;
namespace ArchiSteamFarm.OfficialPlugins.ItemsMatcher;
[Export(typeof(IPlugin))]
internal sealed class ItemsMatcherPlugin : OfficialPlugin, IBot, IBotIdentity, IBotModules {
internal sealed class ItemsMatcherPlugin : OfficialPlugin, IBot, IBotIdentity, IBotModules, IBotUserNotifications {
private static readonly ConcurrentDictionary<Bot, RemoteCommunication> RemoteCommunications = new();
[JsonProperty]
@@ -49,8 +51,8 @@ internal sealed class ItemsMatcherPlugin : OfficialPlugin, IBot, IBotIdentity, I
public async Task OnBotDestroy(Bot bot) {
ArgumentNullException.ThrowIfNull(bot);
if (RemoteCommunications.TryRemove(bot, out RemoteCommunication? remoteCommunications)) {
await remoteCommunications.DisposeAsync().ConfigureAwait(false);
if (RemoteCommunications.TryRemove(bot, out RemoteCommunication? remoteCommunication)) {
await remoteCommunication.DisposeAsync().ConfigureAwait(false);
}
}
@@ -63,21 +65,38 @@ internal sealed class ItemsMatcherPlugin : OfficialPlugin, IBot, IBotIdentity, I
public async Task OnBotInitModules(Bot bot, IReadOnlyDictionary<string, JToken>? additionalConfigProperties = null) {
ArgumentNullException.ThrowIfNull(bot);
if (RemoteCommunications.TryRemove(bot, out RemoteCommunication? remoteCommunications)) {
await remoteCommunications.DisposeAsync().ConfigureAwait(false);
if (RemoteCommunications.TryRemove(bot, out RemoteCommunication? remoteCommunication)) {
await remoteCommunication.DisposeAsync().ConfigureAwait(false);
}
if (bot.BotConfig.RemoteCommunication == BotConfig.ERemoteCommunication.None) {
return;
}
RemoteCommunication remoteCommunication = new(bot);
remoteCommunication = new RemoteCommunication(bot);
if (!RemoteCommunications.TryAdd(bot, remoteCommunication)) {
await remoteCommunication.DisposeAsync().ConfigureAwait(false);
}
}
public Task OnBotUserNotifications(Bot bot, IReadOnlyCollection<UserNotificationsCallback.EUserNotification> newNotifications) {
ArgumentNullException.ThrowIfNull(bot);
if ((newNotifications == null) || (newNotifications.Count == 0)) {
throw new ArgumentNullException(nameof(newNotifications));
}
// We're interested only in Items notification for Bot that has RemoteCommunication enabled
if (!newNotifications.Contains(UserNotificationsCallback.EUserNotification.Items) || !RemoteCommunications.TryGetValue(bot, out RemoteCommunication? remoteCommunication)) {
return Task.CompletedTask;
}
remoteCommunication.OnNewItemsNotification();
return Task.CompletedTask;
}
public override Task OnLoaded() {
Utilities.WarnAboutIncompleteTranslation(Strings.ResourceManager);
@@ -88,6 +107,7 @@ internal sealed class ItemsMatcherPlugin : OfficialPlugin, IBot, IBotIdentity, I
ArgumentNullException.ThrowIfNull(bot);
if (!RemoteCommunications.TryGetValue(bot, out RemoteCommunication? remoteCommunication)) {
// This bot doesn't have RemoteCommunication enabled
return;
}

View File

@@ -29,8 +29,8 @@ using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using ArchiSteamFarm.Core;
using ArchiSteamFarm.Localization;
using ArchiSteamFarm.OfficialPlugins.ItemsMatcher.Data;
using ArchiSteamFarm.OfficialPlugins.ItemsMatcher.Localization;
using ArchiSteamFarm.Steam;
using ArchiSteamFarm.Steam.Cards;
using ArchiSteamFarm.Steam.Data;
@@ -42,10 +42,11 @@ using ArchiSteamFarm.Storage;
namespace ArchiSteamFarm.OfficialPlugins.ItemsMatcher;
internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
private const byte MinAnnouncementCheckTTL = 45; // Minimum amount of minutes we must wait before checking eligibility for Announcement, should be lower than MinPersonaStateTTL
private const byte MaxAnnouncementTTL = 60; // 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
private const byte MinPersonaStateTTL = 60; // Minimum amount of minutes we must wait before requesting persona state update
private const byte MinPersonaStateTTL = 5; // Minimum amount of minutes we must wait before requesting persona state update
private static readonly ImmutableHashSet<Asset.EType> AcceptedMatchableTypes = ImmutableHashSet.Create(
Asset.EType.Emoticon,
@@ -55,14 +56,15 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
);
private readonly Bot Bot;
private readonly SemaphoreSlim MatchActivelySemaphore = new(1, 1);
private readonly Timer? HeartBeatTimer;
private readonly SemaphoreSlim MatchActivelySemaphore = new(1, 1);
private readonly Timer? MatchActivelyTimer;
private readonly SemaphoreSlim RequestsSemaphore = new(1, 1);
private DateTime LastAnnouncementCheck;
private DateTime LastAnnouncement;
private DateTime LastHeartBeat;
private DateTime LastPersonaStateRequest;
private bool ShouldSendAnnouncementEarlier;
private bool ShouldSendHeartBeats;
internal RemoteCommunication(Bot bot) {
@@ -74,7 +76,7 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
HeartBeatTimer = new Timer(
HeartBeat,
null,
TimeSpan.FromMinutes(MinHeartBeatTTL) + TimeSpan.FromSeconds(ASF.LoadBalancingDelay * Bot.Bots?.Count ?? 0),
TimeSpan.FromMinutes(1) + TimeSpan.FromSeconds(ASF.LoadBalancingDelay * Bot.Bots?.Count ?? 0),
TimeSpan.FromMinutes(1)
);
}
@@ -88,7 +90,7 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
TimeSpan.FromHours(6)
);
} else {
bot.ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, ArchiSteamFarm.Localization.Strings.WarningNoLicense, nameof(BotConfig.ETradingPreferences.MatchActively)));
bot.ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.WarningNoLicense, nameof(BotConfig.ETradingPreferences.MatchActively)));
}
}
}
@@ -118,13 +120,147 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
}
}
internal void OnNewItemsNotification() => ShouldSendAnnouncementEarlier = true;
internal async Task OnPersonaState(string? nickname = null, string? avatarHash = null) {
if (!Bot.BotConfig.RemoteCommunication.HasFlag(BotConfig.ERemoteCommunication.PublicListing)) {
return;
}
if ((DateTime.UtcNow < LastAnnouncement.AddMinutes(ShouldSendAnnouncementEarlier ? MinAnnouncementTTL : MaxAnnouncementTTL)) && ShouldSendHeartBeats) {
return;
}
await RequestsSemaphore.WaitAsync().ConfigureAwait(false);
try {
if ((DateTime.UtcNow < LastAnnouncement.AddMinutes(ShouldSendAnnouncementEarlier ? MinAnnouncementTTL : MaxAnnouncementTTL)) && ShouldSendHeartBeats) {
return;
}
// Don't announce if we don't meet conditions
bool? eligible = await IsEligibleForListing().ConfigureAwait(false);
if (!eligible.HasValue) {
// This is actually network failure, so we'll stop sending heartbeats but not record it as valid check
ShouldSendHeartBeats = false;
return;
}
if (!eligible.Value) {
// We're not eligible, record this as a valid check
LastAnnouncement = DateTime.UtcNow;
ShouldSendAnnouncementEarlier = ShouldSendHeartBeats = false;
return;
}
HashSet<Asset.EType> acceptedMatchableTypes = Bot.BotConfig.MatchableTypes.Where(AcceptedMatchableTypes.Contains).ToHashSet();
if (acceptedMatchableTypes.Count == 0) {
throw new InvalidOperationException(nameof(acceptedMatchableTypes));
}
string? tradeToken = await Bot.ArchiHandler.GetTradeToken().ConfigureAwait(false);
if (string.IsNullOrEmpty(tradeToken)) {
// This is actually a network failure, so we'll stop sending heartbeats but not record it as valid check
ShouldSendHeartBeats = false;
Bot.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, nameof(tradeToken)));
return;
}
List<Asset> inventory;
try {
inventory = await Bot.ArchiWebHandler.GetInventoryAsync().ToListAsync().ConfigureAwait(false);
} catch (HttpRequestException e) {
// This is actually a network failure, so we'll stop sending heartbeats but not record it as valid check
ShouldSendHeartBeats = false;
Bot.ArchiLogger.LogGenericWarningException(e);
return;
} catch (Exception e) {
// This is actually a network failure, so we'll stop sending heartbeats but not record it as valid check
ShouldSendHeartBeats = false;
Bot.ArchiLogger.LogGenericException(e);
return;
}
// This is actual inventory
if (inventory.Count(item => item.Tradable && acceptedMatchableTypes.Contains(item.Type)) < MinItemsCount) {
// We're not eligible, record this as a valid check
LastAnnouncement = DateTime.UtcNow;
ShouldSendAnnouncementEarlier = ShouldSendHeartBeats = false;
return;
}
Bot.ArchiLogger.LogGenericInfo(string.Format(CultureInfo.CurrentCulture, Localization.Strings.ListingAnnouncing, Bot.SteamID, nickname, inventory.Count));
// ReSharper disable once RedundantSuppressNullableWarningExpression - required for .NET Framework
HttpStatusCode? response = await Backend.AnnounceForListing(Bot, inventory, acceptedMatchableTypes, tradeToken!, nickname, avatarHash).ConfigureAwait(false);
if (!response.HasValue) {
// This is actually a network failure, so we'll stop sending heartbeats but not record it as valid check
ShouldSendHeartBeats = false;
return;
}
// We've got the response, regardless what happened, we've succeeded in a valid check
LastAnnouncement = DateTime.UtcNow;
ShouldSendAnnouncementEarlier = false;
if (response.Value.IsClientErrorCode()) {
// ArchiNet told us that we've sent a bad request, so the process should restart from the beginning at later time
ShouldSendHeartBeats = false;
Bot.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, response));
switch (response) {
case HttpStatusCode.Forbidden:
// ArchiNet told us to stop submitting data for now
LastAnnouncement = DateTime.UtcNow.AddYears(1);
break;
#if NETFRAMEWORK
case (HttpStatusCode) 429:
#else
case HttpStatusCode.TooManyRequests:
#endif
// ArchiNet told us to try again later
LastAnnouncement = DateTime.UtcNow.AddDays(1);
break;
}
return;
}
LastHeartBeat = DateTime.UtcNow;
ShouldSendHeartBeats = true;
Bot.ArchiLogger.LogGenericInfo(Strings.Success);
} finally {
RequestsSemaphore.Release();
}
}
private async void HeartBeat(object? state = null) {
if (!Bot.IsConnectedAndLoggedOn || (Bot.HeartBeatFailures > 0)) {
return;
}
// Request persona update if needed
if ((DateTime.UtcNow > LastPersonaStateRequest.AddMinutes(MinPersonaStateTTL)) && (DateTime.UtcNow > LastAnnouncementCheck.AddMinutes(MinAnnouncementCheckTTL))) {
if ((DateTime.UtcNow > LastPersonaStateRequest.AddMinutes(MinPersonaStateTTL)) && (DateTime.UtcNow > LastAnnouncement.AddMinutes(ShouldSendAnnouncementEarlier ? MinAnnouncementTTL : MaxAnnouncementTTL))) {
LastPersonaStateRequest = DateTime.UtcNow;
Bot.RequestPersonaStateUpdate();
}
@@ -145,7 +281,6 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
}
if (response.Value.IsClientErrorCode()) {
LastHeartBeat = DateTime.MinValue;
ShouldSendHeartBeats = false;
return;
@@ -157,134 +292,6 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
}
}
internal async Task OnPersonaState(string? nickname = null, string? avatarHash = null) {
if (!Bot.BotConfig.RemoteCommunication.HasFlag(BotConfig.ERemoteCommunication.PublicListing)) {
return;
}
if ((DateTime.UtcNow < LastAnnouncementCheck.AddMinutes(MinAnnouncementCheckTTL)) && (ShouldSendHeartBeats || (LastHeartBeat == DateTime.MinValue))) {
return;
}
await RequestsSemaphore.WaitAsync().ConfigureAwait(false);
try {
if ((DateTime.UtcNow < LastAnnouncementCheck.AddMinutes(MinAnnouncementCheckTTL)) && (ShouldSendHeartBeats || (LastHeartBeat == DateTime.MinValue))) {
return;
}
// Don't announce if we don't meet conditions
bool? eligible = await IsEligibleForListing().ConfigureAwait(false);
if (!eligible.HasValue) {
// This is actually network failure, so we'll stop sending heartbeats but not record it as valid check
ShouldSendHeartBeats = false;
return;
}
if (!eligible.Value) {
LastAnnouncementCheck = DateTime.UtcNow;
ShouldSendHeartBeats = false;
return;
}
string? tradeToken = await Bot.ArchiHandler.GetTradeToken().ConfigureAwait(false);
if (string.IsNullOrEmpty(tradeToken)) {
// This is actually network failure, so we'll stop sending heartbeats but not record it as valid check
ShouldSendHeartBeats = false;
Bot.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, ArchiSteamFarm.Localization.Strings.WarningFailedWithError, nameof(tradeToken)));
return;
}
HashSet<Asset.EType> acceptedMatchableTypes = Bot.BotConfig.MatchableTypes.Where(AcceptedMatchableTypes.Contains).ToHashSet();
if (acceptedMatchableTypes.Count == 0) {
Bot.ArchiLogger.LogNullError(acceptedMatchableTypes);
LastAnnouncementCheck = DateTime.UtcNow;
ShouldSendHeartBeats = false;
return;
}
List<Asset> inventory;
try {
inventory = await Bot.ArchiWebHandler.GetInventoryAsync().ToListAsync().ConfigureAwait(false);
} catch (HttpRequestException e) {
Bot.ArchiLogger.LogGenericWarningException(e);
// This is actually inventory failure, so we'll stop sending heartbeats but not record it as valid check
ShouldSendHeartBeats = false;
return;
} catch (Exception e) {
Bot.ArchiLogger.LogGenericException(e);
// This is actually inventory failure, so we'll stop sending heartbeats but not record it as valid check
ShouldSendHeartBeats = false;
return;
}
LastAnnouncementCheck = DateTime.UtcNow;
// This is actual inventory
if (inventory.Count(item => item.Tradable && acceptedMatchableTypes.Contains(item.Type)) < MinItemsCount) {
ShouldSendHeartBeats = false;
return;
}
Bot.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.ListingAnnouncing, Bot.SteamID, nickname, inventory.Count));
// ReSharper disable once RedundantSuppressNullableWarningExpression - required for .NET Framework
HttpStatusCode? response = await Backend.AnnounceForListing(Bot, inventory, acceptedMatchableTypes, tradeToken!, nickname, avatarHash).ConfigureAwait(false);
if (!response.HasValue) {
return;
}
if (response.Value.IsClientErrorCode()) {
ASF.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, ArchiSteamFarm.Localization.Strings.WarningFailedWithError, response));
LastHeartBeat = DateTime.MinValue;
ShouldSendHeartBeats = false;
switch (response) {
case HttpStatusCode.Forbidden:
// ArchiNet told us to stop submitting data for now
LastAnnouncementCheck = DateTime.UtcNow.AddYears(1);
break;
#if NETFRAMEWORK
case (HttpStatusCode) 429:
#else
case HttpStatusCode.TooManyRequests:
#endif
// ArchiNet told us to try again later
LastAnnouncementCheck = DateTime.UtcNow.AddDays(1);
break;
}
return;
}
LastHeartBeat = DateTime.UtcNow;
ShouldSendHeartBeats = true;
Bot.ArchiLogger.LogGenericInfo(ArchiSteamFarm.Localization.Strings.Success);
} finally {
RequestsSemaphore.Release();
}
}
private async Task<bool?> IsEligibleForListing() {
// Bot must be eligible for matching first
bool? isEligibleForMatching = await IsEligibleForMatching().ConfigureAwait(false);
@@ -295,7 +302,7 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
// Bot must have STM enabled in TradingPreferences
if (!Bot.BotConfig.TradingPreferences.HasFlag(BotConfig.ETradingPreferences.SteamTradeMatcher)) {
Bot.ArchiLogger.LogGenericTrace(string.Format(CultureInfo.CurrentCulture, ArchiSteamFarm.Localization.Strings.WarningFailedWithError, $"{nameof(Bot.BotConfig.TradingPreferences)}: {Bot.BotConfig.TradingPreferences}"));
Bot.ArchiLogger.LogGenericTrace(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, $"{nameof(Bot.BotConfig.TradingPreferences)}: {Bot.BotConfig.TradingPreferences}"));
return false;
}
@@ -304,7 +311,7 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
bool? hasPublicInventory = await Bot.HasPublicInventory().ConfigureAwait(false);
if (hasPublicInventory != true) {
Bot.ArchiLogger.LogGenericTrace(string.Format(CultureInfo.CurrentCulture, ArchiSteamFarm.Localization.Strings.WarningFailedWithError, $"{nameof(Bot.HasPublicInventory)}: {hasPublicInventory?.ToString() ?? "null"}"));
Bot.ArchiLogger.LogGenericTrace(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, $"{nameof(Bot.HasPublicInventory)}: {hasPublicInventory?.ToString() ?? "null"}"));
return hasPublicInventory;
}
@@ -315,14 +322,14 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
private async Task<bool?> IsEligibleForMatching() {
// Bot must have ASF 2FA
if (!Bot.HasMobileAuthenticator) {
Bot.ArchiLogger.LogGenericTrace(string.Format(CultureInfo.CurrentCulture, ArchiSteamFarm.Localization.Strings.WarningFailedWithError, $"{nameof(Bot.HasMobileAuthenticator)}: {Bot.HasMobileAuthenticator}"));
Bot.ArchiLogger.LogGenericTrace(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, $"{nameof(Bot.HasMobileAuthenticator)}: {Bot.HasMobileAuthenticator}"));
return false;
}
// Bot must have at least one accepted matchable type set
if ((Bot.BotConfig.MatchableTypes.Count == 0) || Bot.BotConfig.MatchableTypes.All(static type => !AcceptedMatchableTypes.Contains(type))) {
Bot.ArchiLogger.LogGenericTrace(string.Format(CultureInfo.CurrentCulture, ArchiSteamFarm.Localization.Strings.WarningFailedWithError, $"{nameof(Bot.BotConfig.MatchableTypes)}: {string.Join(", ", Bot.BotConfig.MatchableTypes)}"));
Bot.ArchiLogger.LogGenericTrace(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, $"{nameof(Bot.BotConfig.MatchableTypes)}: {string.Join(", ", Bot.BotConfig.MatchableTypes)}"));
return false;
}
@@ -331,7 +338,7 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
bool? hasValidApiKey = await Bot.ArchiWebHandler.HasValidApiKey().ConfigureAwait(false);
if (hasValidApiKey != true) {
Bot.ArchiLogger.LogGenericTrace(string.Format(CultureInfo.CurrentCulture, ArchiSteamFarm.Localization.Strings.WarningFailedWithError, $"{nameof(Bot.ArchiWebHandler.HasValidApiKey)}: {hasValidApiKey?.ToString() ?? "null"}"));
Bot.ArchiLogger.LogGenericTrace(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, $"{nameof(Bot.ArchiWebHandler.HasValidApiKey)}: {hasValidApiKey?.ToString() ?? "null"}"));
return hasValidApiKey;
}
@@ -349,7 +356,7 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
}
if (!Bot.IsConnectedAndLoggedOn || Bot.BotConfig.TradingPreferences.HasFlag(BotConfig.ETradingPreferences.MatchEverything) || !Bot.BotConfig.TradingPreferences.HasFlag(BotConfig.ETradingPreferences.MatchActively) || (await IsEligibleForMatching().ConfigureAwait(false) != true)) {
Bot.ArchiLogger.LogGenericTrace(ArchiSteamFarm.Localization.Strings.ErrorAborted);
Bot.ArchiLogger.LogGenericTrace(Strings.ErrorAborted);
return;
}
@@ -363,18 +370,18 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
}
if (!await MatchActivelySemaphore.WaitAsync(0).ConfigureAwait(false)) {
Bot.ArchiLogger.LogGenericTrace(ArchiSteamFarm.Localization.Strings.ErrorAborted);
Bot.ArchiLogger.LogGenericTrace(Strings.ErrorAborted);
return;
}
try {
Bot.ArchiLogger.LogGenericInfo(ArchiSteamFarm.Localization.Strings.Starting);
Bot.ArchiLogger.LogGenericInfo(Strings.Starting);
string? tradeToken = await Bot.ArchiHandler.GetTradeToken().ConfigureAwait(false);
if (string.IsNullOrEmpty(tradeToken)) {
Bot.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, ArchiSteamFarm.Localization.Strings.WarningFailedWithError, nameof(tradeToken)));
Bot.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, nameof(tradeToken)));
return;
}
@@ -385,18 +392,18 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
ourInventory = await Bot.ArchiWebHandler.GetInventoryAsync().Where(item => acceptedMatchableTypes.Contains(item.Type) && !Bot.BotDatabase.MatchActivelyBlacklistAppIDs.Contains(item.RealAppID)).ToHashSetAsync().ConfigureAwait(false);
} catch (HttpRequestException e) {
Bot.ArchiLogger.LogGenericWarningException(e);
Bot.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, ArchiSteamFarm.Localization.Strings.WarningFailedWithError, nameof(ourInventory)));
Bot.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, nameof(ourInventory)));
return;
} catch (Exception e) {
Bot.ArchiLogger.LogGenericException(e);
Bot.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, ArchiSteamFarm.Localization.Strings.WarningFailedWithError, nameof(ourInventory)));
Bot.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, nameof(ourInventory)));
return;
}
if (ourInventory.Count == 0) {
Bot.ArchiLogger.LogGenericInfo(string.Format(CultureInfo.CurrentCulture, ArchiSteamFarm.Localization.Strings.ErrorIsEmpty, nameof(ourInventory)));
Bot.ArchiLogger.LogGenericInfo(string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsEmpty, nameof(ourInventory)));
return;
}
@@ -405,19 +412,19 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
(HttpStatusCode StatusCode, ImmutableHashSet<ListedUser> Users)? response = await Backend.GetListedUsersForMatching(ASF.GlobalConfig.LicenseID.Value, Bot, ourInventory, acceptedMatchableTypes, tradeToken!).ConfigureAwait(false);
if (response == null) {
Bot.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, ArchiSteamFarm.Localization.Strings.WarningFailedWithError, nameof(response)));
Bot.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, nameof(response)));
return;
}
if (!response.Value.StatusCode.IsSuccessCode()) {
Bot.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, ArchiSteamFarm.Localization.Strings.WarningFailedWithError, response.Value.StatusCode));
Bot.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, response.Value.StatusCode));
return;
}
if (response.Value.Users.IsEmpty) {
Bot.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, ArchiSteamFarm.Localization.Strings.WarningFailedWithError, nameof(response.Value.Users)));
Bot.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, nameof(response.Value.Users)));
return;
}
@@ -425,11 +432,11 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
#pragma warning disable CA2000 // False positive, we're actually wrapping it in the using clause below exactly for that purpose
using (await Bot.Actions.GetTradingLock().ConfigureAwait(false)) {
#pragma warning restore CA2000 // False positive, we're actually wrapping it in the using clause below exactly for that purpose
Bot.ArchiLogger.LogGenericInfo(ArchiSteamFarm.Localization.Strings.Starting);
Bot.ArchiLogger.LogGenericInfo(Strings.Starting);
await MatchActivelyRound(response.Value.Users, ourInventory, acceptedMatchableTypes).ConfigureAwait(false);
}
Bot.ArchiLogger.LogGenericInfo(ArchiSteamFarm.Localization.Strings.Done);
Bot.ArchiLogger.LogGenericInfo(Strings.Done);
} finally {
MatchActivelySemaphore.Release();
}
@@ -452,7 +459,7 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
if (Trading.IsEmptyForMatching(ourFullState, ourTradableState)) {
// User doesn't have any more dupes in the inventory
Bot.ArchiLogger.LogGenericTrace(string.Format(CultureInfo.CurrentCulture, ArchiSteamFarm.Localization.Strings.ErrorIsEmpty, $"{nameof(ourFullState)} || {nameof(ourTradableState)}"));
Bot.ArchiLogger.LogGenericTrace(string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsEmpty, $"{nameof(ourFullState)} || {nameof(ourTradableState)}"));
return;
}
@@ -475,7 +482,7 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
switch (tradeHoldDuration) {
case null:
Bot.ArchiLogger.LogGenericTrace(string.Format(CultureInfo.CurrentCulture, ArchiSteamFarm.Localization.Strings.ErrorIsEmpty, nameof(tradeHoldDuration)));
Bot.ArchiLogger.LogGenericTrace(string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsEmpty, nameof(tradeHoldDuration)));
continue;
case > 0 when (tradeHoldDuration.Value > maxTradeHoldDuration) || (tradeHoldDuration.Value > listedUser.MaxTradeHoldDuration):
@@ -644,7 +651,7 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
}
if (skippedSetsThisTrade.Count == 0) {
Bot.ArchiLogger.LogGenericTrace(string.Format(CultureInfo.CurrentCulture, ArchiSteamFarm.Localization.Strings.ErrorIsEmpty, nameof(skippedSetsThisTrade)));
Bot.ArchiLogger.LogGenericTrace(string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsEmpty, nameof(skippedSetsThisTrade)));
break;
}
@@ -655,14 +662,14 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
if ((itemsToGive.Count != itemsToReceive.Count) || !Trading.IsFairExchange(itemsToGive, itemsToReceive)) {
// Failsafe
Bot.ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, ArchiSteamFarm.Localization.Strings.WarningFailedWithError, ArchiSteamFarm.Localization.Strings.ErrorAborted));
Bot.ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, Strings.ErrorAborted));
return;
}
triedSteamIDs.Add(listedUser.SteamID);
Bot.ArchiLogger.LogGenericInfo(string.Format(CultureInfo.CurrentCulture, Strings.MatchingFound, itemsToReceive.Count, listedUser.SteamID, listedUser.Nickname));
Bot.ArchiLogger.LogGenericInfo(string.Format(CultureInfo.CurrentCulture, Localization.Strings.MatchingFound, itemsToReceive.Count, listedUser.SteamID, listedUser.Nickname));
Bot.ArchiLogger.LogGenericTrace($"{Bot.SteamID} <- {string.Join(", ", itemsToReceive.Select(static item => $"{item.RealAppID}/{item.Type}/{item.Rarity}/{item.ClassID} #{item.Amount}"))} | {string.Join(", ", itemsToGive.Select(static item => $"{item.RealAppID}/{item.Type}/{item.Rarity}/{item.ClassID} #{item.Amount}"))} -> {listedUser.SteamID}");
@@ -672,14 +679,14 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
(bool twoFactorSuccess, _, _) = await Bot.Actions.HandleTwoFactorAuthenticationConfirmations(true, Confirmation.EType.Trade, mobileTradeOfferIDs, true).ConfigureAwait(false);
if (!twoFactorSuccess) {
Bot.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, ArchiSteamFarm.Localization.Strings.WarningFailedWithError, nameof(twoFactorSuccess)));
Bot.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, nameof(twoFactorSuccess)));
return;
}
}
if (!success) {
Bot.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.TradeOfferFailed, listedUser.SteamID, listedUser.Nickname));
Bot.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Localization.Strings.TradeOfferFailed, listedUser.SteamID, listedUser.Nickname));
break;
}
@@ -688,7 +695,7 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
theirInventory.UnionWith(itemsToGive);
skippedSetsThisUser.UnionWith(skippedSetsThisTrade);
Bot.ArchiLogger.LogGenericInfo(ArchiSteamFarm.Localization.Strings.Success);
Bot.ArchiLogger.LogGenericInfo(Strings.Success);
}
if (skippedSetsThisUser.Count == 0) {
@@ -711,6 +718,6 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
ourTradableState.TrimExcess();
}
Bot.ArchiLogger.LogGenericInfo(string.Format(CultureInfo.CurrentCulture, Strings.ActivelyMatchingItemsRound, skippedSetsThisRound.Count));
Bot.ArchiLogger.LogGenericInfo(string.Format(CultureInfo.CurrentCulture, Localization.Strings.ActivelyMatchingItemsRound, skippedSetsThisRound.Count));
}
}