From 9a02c79e0aa50cf184dbab5af4f0b8525241adb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Domeradzki?= Date: Wed, 27 Mar 2024 19:55:07 +0100 Subject: [PATCH] Closes #3174 --- .../RemoteCommunication.cs | 5 +- ArchiSteamFarm/Steam/Bot.cs | 3 +- ArchiSteamFarm/Steam/Exchange/Trading.cs | 3 +- .../Steam/Integration/ArchiHandler.cs | 64 +++++++++++++++---- .../Steam/Integration/ArchiWebHandler.cs | 26 ++++---- ArchiSteamFarm/Steam/Interaction/Actions.cs | 3 +- ArchiSteamFarm/Steam/Interaction/Commands.cs | 3 +- 7 files changed, 70 insertions(+), 37 deletions(-) diff --git a/ArchiSteamFarm.OfficialPlugins.ItemsMatcher/RemoteCommunication.cs b/ArchiSteamFarm.OfficialPlugins.ItemsMatcher/RemoteCommunication.cs index b25397c589b4a..2a2be7377513c 100644 --- a/ArchiSteamFarm.OfficialPlugins.ItemsMatcher/RemoteCommunication.cs +++ b/ArchiSteamFarm.OfficialPlugins.ItemsMatcher/RemoteCommunication.cs @@ -29,7 +29,6 @@ using System.IO; using System.Linq; using System.Net; -using System.Net.Http; using System.Text.Json; using System.Threading; using System.Threading.Tasks; @@ -253,7 +252,7 @@ internal async Task OnPersonaState(string? nickname = null, string? avatarHash = try { inventory = await Bot.ArchiHandler.GetMyInventoryAsync().ToListAsync().ConfigureAwait(false); - } catch (HttpRequestException e) { + } catch (TimeoutException e) { // This is actually a network failure, so we'll stop sending heartbeats but not record it as valid check ShouldSendHeartBeats = false; @@ -940,7 +939,7 @@ private async void MatchActively(object? state = null) { try { assetsForMatching = await Bot.ArchiHandler.GetMyInventoryAsync().Where(item => item is { AssetID: > 0, Amount: > 0, ClassID: > 0, RealAppID: > 0, Type: > EAssetType.Unknown, Rarity: > EAssetRarity.Unknown, IsSteamPointsShopItem: false } && acceptedMatchableTypes.Contains(item.Type) && !Bot.BotDatabase.MatchActivelyBlacklistAppIDs.Contains(item.RealAppID)).ToHashSetAsync().ConfigureAwait(false); - } catch (HttpRequestException e) { + } catch (TimeoutException e) { Bot.ArchiLogger.LogGenericWarningException(e); Bot.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, nameof(assetsForMatching))); diff --git a/ArchiSteamFarm/Steam/Bot.cs b/ArchiSteamFarm/Steam/Bot.cs index 43f2e48520ca6..3925383c5fa5d 100644 --- a/ArchiSteamFarm/Steam/Bot.cs +++ b/ArchiSteamFarm/Steam/Bot.cs @@ -33,7 +33,6 @@ using System.Globalization; using System.IO; using System.Linq; -using System.Net.Http; using System.Text.Json.Serialization; using System.Text.RegularExpressions; using System.Threading; @@ -3669,7 +3668,7 @@ private async Task SendCompletedSets() { .Where(item => appIDs.Contains(item.RealAppID) && BotConfig.CompleteTypesToSend.Contains(item.Type)) .ToHashSetAsync() .ConfigureAwait(false); - } catch (HttpRequestException e) { + } catch (TimeoutException e) { ArchiLogger.LogGenericWarningException(e); return; diff --git a/ArchiSteamFarm/Steam/Exchange/Trading.cs b/ArchiSteamFarm/Steam/Exchange/Trading.cs index a6d7a73745f82..3836e14197cb3 100644 --- a/ArchiSteamFarm/Steam/Exchange/Trading.cs +++ b/ArchiSteamFarm/Steam/Exchange/Trading.cs @@ -25,7 +25,6 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; -using System.Net.Http; using System.Threading; using System.Threading.Tasks; using ArchiSteamFarm.Collections; @@ -499,7 +498,7 @@ private async Task ParseTrade(TradeOffer tradeOffer) { try { inventory = await Bot.ArchiHandler.GetMyInventoryAsync().Where(item => !item.IsSteamPointsShopItem && wantedSets.Contains((item.RealAppID, item.Type, item.Rarity))).ToHashSetAsync().ConfigureAwait(false); - } catch (HttpRequestException e) { + } catch (TimeoutException e) { // If we can't check our inventory when not using MatchEverything, this is a temporary failure, try again later Bot.ArchiLogger.LogGenericWarningException(e); Bot.ArchiLogger.LogGenericDebug(string.Format(CultureInfo.CurrentCulture, Strings.BotTradeOfferResult, tradeOffer.TradeOfferID, ParseTradeResult.EResult.TryAgain, nameof(inventory))); diff --git a/ArchiSteamFarm/Steam/Integration/ArchiHandler.cs b/ArchiSteamFarm/Steam/Integration/ArchiHandler.cs index 72ee55f48f914..18c605b993ab9 100644 --- a/ArchiSteamFarm/Steam/Integration/ArchiHandler.cs +++ b/ArchiSteamFarm/Steam/Integration/ArchiHandler.cs @@ -26,7 +26,6 @@ using System.ComponentModel; using System.Globalization; using System.Linq; -using System.Net.Http; using System.Threading.Tasks; using ArchiSteamFarm.Core; using ArchiSteamFarm.Localization; @@ -34,6 +33,7 @@ using ArchiSteamFarm.Steam.Data; using ArchiSteamFarm.Steam.Integration.Callbacks; using ArchiSteamFarm.Steam.Integration.CMsgs; +using ArchiSteamFarm.Web; using JetBrains.Annotations; using SteamKit2; using SteamKit2.Internal; @@ -179,14 +179,15 @@ public async IAsyncEnumerable GetMyInventoryAsync(uint appID = Asset.Stea ArgumentOutOfRangeException.ThrowIfZero(contextID); ArgumentOutOfRangeException.ThrowIfZero(itemsCountPerRequest); - if (Client.SteamID == null) { - throw new InvalidOperationException(nameof(Client.SteamID)); + if (!Client.IsConnected || (Client.SteamID == null)) { + throw new TimeoutException(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, nameof(Client.IsConnected))); } - // We need to store asset IDs to make sure we won't get duplicate items - HashSet? assetIDs = null; + ulong steamID = Client.SteamID; - Dictionary<(ulong ClassID, ulong InstanceID), InventoryDescription>? descriptions = null; + if (steamID == 0) { + throw new InvalidOperationException(nameof(Client.SteamID)); + } CEcon_GetInventoryItemsWithDescriptions_Request request = new() { appid = appID, @@ -198,23 +199,58 @@ public async IAsyncEnumerable GetMyInventoryAsync(uint appID = Asset.Stea }, get_descriptions = true, - steamid = Client.SteamID, + steamid = steamID, count = itemsCountPerRequest }; + // We need to store asset IDs to make sure we won't get duplicate items + HashSet? assetIDs = null; + + Dictionary<(ulong ClassID, ulong InstanceID), InventoryDescription>? descriptions = null; + while (true) { - SteamUnifiedMessages.ServiceMethodResponse serviceMethodResponse; + SteamUnifiedMessages.ServiceMethodResponse? serviceMethodResponse = null; + + for (byte i = 0; (i < WebBrowser.MaxTries) && (serviceMethodResponse?.Result != EResult.OK) && Client.IsConnected && (Client.SteamID != null); i++) { + if (i > 0) { + // It seems 2 seconds is enough to win over DuplicateRequest, so we'll use that for this and also other network-related failures + await Task.Delay(2000).ConfigureAwait(false); + } + + try { + serviceMethodResponse = await UnifiedEconService.SendMessage(x => x.GetInventoryItemsWithDescriptions(request)).ToLongRunningTask().ConfigureAwait(false); + } catch (Exception e) { + ArchiLogger.LogGenericWarningException(e); - try { - serviceMethodResponse = await UnifiedEconService.SendMessage(x => x.GetInventoryItemsWithDescriptions(request)).ToLongRunningTask().ConfigureAwait(false); - } catch (Exception e) { - ArchiLogger.LogGenericWarningException(e); + continue; + } + + // Interpret the result and see what we should do about it + switch (serviceMethodResponse.Result) { + case EResult.Busy: + case EResult.DuplicateRequest: + case EResult.ServiceUnavailable: + // Those are generic failures that we should be able to retry + ArchiLogger.LogGenericDebug(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, serviceMethodResponse.Result)); + + continue; + case EResult.OK: + // Success, we can continue + break; + default: + // Unknown failures, report them and do not retry since we're unsure if we should + ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.WarningUnknownValuePleaseReport, nameof(serviceMethodResponse.Result), serviceMethodResponse.Result)); + + throw new TimeoutException(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, serviceMethodResponse.Result)); + } + } - throw new HttpRequestException(string.Format(CultureInfo.CurrentCulture, Strings.ErrorObjectIsNull, nameof(serviceMethodResponse))); + if (serviceMethodResponse == null) { + throw new TimeoutException(string.Format(CultureInfo.CurrentCulture, Strings.ErrorObjectIsNull, nameof(serviceMethodResponse))); } if (serviceMethodResponse.Result != EResult.OK) { - throw new HttpRequestException(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, serviceMethodResponse.Result)); + throw new TimeoutException(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, serviceMethodResponse.Result)); } CEcon_GetInventoryItemsWithDescriptions_Response response = serviceMethodResponse.GetDeserializedResponse(); diff --git a/ArchiSteamFarm/Steam/Integration/ArchiWebHandler.cs b/ArchiSteamFarm/Steam/Integration/ArchiWebHandler.cs index c9f382e2073e4..08fd93dfde30d 100644 --- a/ArchiSteamFarm/Steam/Integration/ArchiWebHandler.cs +++ b/ArchiSteamFarm/Steam/Integration/ArchiWebHandler.cs @@ -275,7 +275,7 @@ public async IAsyncEnumerable GetInventoryAsync(ulong steamID = 0, uint a ObjectResponse? response = null; try { - for (byte i = 0; (i < WebBrowser.MaxTries) && (response == null); i++) { + for (byte i = 0; (i < WebBrowser.MaxTries) && (response?.StatusCode.IsSuccessCode() != true); i++) { if ((i > 0) && (rateLimitingDelay > 0)) { await Task.Delay(rateLimitingDelay).ConfigureAwait(false); } @@ -293,22 +293,24 @@ public async IAsyncEnumerable GetInventoryAsync(ulong steamID = 0, uint a if (response.StatusCode.IsServerErrorCode()) { if (string.IsNullOrEmpty(response.Content?.ErrorText)) { // This is a generic server error without a reason, try again - response = null; - continue; } - // Interpret the reason and see if we should try again + Bot.ArchiLogger.LogGenericDebug(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, response.Content.ErrorText)); + + // Try to interpret the failure reason and see if we should try again switch (response.Content.ErrorCode) { + case EResult.Busy: case EResult.DuplicateRequest: case EResult.ServiceUnavailable: - response = null; - + // Those are generic failures that we should be able to retry continue; - } + default: + // Unknown failures, report them and do not retry since we're unsure if we should + Bot.ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.WarningUnknownValuePleaseReport, nameof(response.Content.ErrorText), response.Content.ErrorText)); - // This is actually client error with a reason, so it doesn't make sense to retry - throw new HttpRequestException(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, response.Content.ErrorText), null, response.StatusCode); + throw new HttpRequestException(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, response.Content.ErrorText), null, response.StatusCode); + } } } } finally { @@ -324,12 +326,12 @@ public async IAsyncEnumerable GetInventoryAsync(ulong steamID = 0, uint a } } - if (response?.Content == null) { + if (response == null) { throw new HttpRequestException(string.Format(CultureInfo.CurrentCulture, Strings.ErrorObjectIsNull, nameof(response))); } - if (response.Content.Result is not EResult.OK) { - throw new HttpRequestException(!string.IsNullOrEmpty(response.Content.ErrorText) ? string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, response.Content.ErrorText) : response.Content.Result.HasValue ? string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, response.Content.Result) : Strings.WarningFailed); + if ((response.Content == null) || (response.StatusCode.IsSuccessCode() != true)) { + throw new HttpRequestException(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, !string.IsNullOrEmpty(response.Content?.ErrorText) ? response.Content.ErrorText : response.Content?.Result.HasValue == true ? response.Content.Result : response.StatusCode)); } if ((response.Content.TotalInventoryCount == 0) || (response.Content.Assets.Count == 0)) { diff --git a/ArchiSteamFarm/Steam/Interaction/Actions.cs b/ArchiSteamFarm/Steam/Interaction/Actions.cs index 6977f2812aead..37dfbbb8e7bed 100644 --- a/ArchiSteamFarm/Steam/Interaction/Actions.cs +++ b/ArchiSteamFarm/Steam/Interaction/Actions.cs @@ -27,7 +27,6 @@ using System.ComponentModel; using System.Globalization; using System.Linq; -using System.Net.Http; using System.Threading; using System.Threading.Tasks; using ArchiSteamFarm.Collections; @@ -430,7 +429,7 @@ static async () => { } inventory = await Bot.ArchiHandler.GetMyInventoryAsync(appID, contextID, true).Where(item => filterFunction(item)).ToHashSetAsync().ConfigureAwait(false); - } catch (HttpRequestException e) { + } catch (TimeoutException e) { Bot.ArchiLogger.LogGenericWarningException(e); return (false, string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, e.Message)); diff --git a/ArchiSteamFarm/Steam/Interaction/Commands.cs b/ArchiSteamFarm/Steam/Interaction/Commands.cs index 02afcb1267e89..4eb5855de817b 100644 --- a/ArchiSteamFarm/Steam/Interaction/Commands.cs +++ b/ArchiSteamFarm/Steam/Interaction/Commands.cs @@ -26,7 +26,6 @@ using System.ComponentModel; using System.Globalization; using System.Linq; -using System.Net.Http; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; @@ -3114,7 +3113,7 @@ internal void OnNewLicenseList() { completeSuccess = false; } } - } catch (HttpRequestException e) { + } catch (TimeoutException e) { Bot.ArchiLogger.LogGenericWarningException(e); completeSuccess = false;