diff --git a/ArchiSteamFarm.CustomPlugins.ExamplePlugin/CatAPI.cs b/ArchiSteamFarm.CustomPlugins.ExamplePlugin/CatAPI.cs index 7120434d3..4fdc631e0 100644 --- a/ArchiSteamFarm.CustomPlugins.ExamplePlugin/CatAPI.cs +++ b/ArchiSteamFarm.CustomPlugins.ExamplePlugin/CatAPI.cs @@ -41,7 +41,7 @@ internal static class CatAPI { ObjectResponse? response = await webBrowser.UrlGetToJsonObject(request).ConfigureAwait(false); - if (response == null) { + if (response?.Content == null) { return null; } diff --git a/ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/SteamTokenDumperPlugin.cs b/ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/SteamTokenDumperPlugin.cs index 7ee227e50..6ec7f28c9 100644 --- a/ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/SteamTokenDumperPlugin.cs +++ b/ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/SteamTokenDumperPlugin.cs @@ -530,7 +530,7 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotC ASF.ArchiLogger.LogGenericInfo(string.Format(CultureInfo.CurrentCulture, Strings.SubmissionInProgress, appTokens.Count, packageTokens.Count, depotKeys.Count)); - OptionalObjectResponse? response = await ASF.WebBrowser.UrlPostToOptionalJsonObject(request, data: requestData, requestOptions: WebBrowser.ERequestOptions.ReturnClientErrors | WebBrowser.ERequestOptions.AllowInvalidBodyOnErrors).ConfigureAwait(false); + ObjectResponse? response = await ASF.WebBrowser.UrlPostToJsonObject(request, data: requestData, requestOptions: WebBrowser.ERequestOptions.ReturnClientErrors | WebBrowser.ERequestOptions.AllowInvalidBodyOnErrors).ConfigureAwait(false); if (response == null) { ASF.ArchiLogger.LogGenericWarning(ArchiSteamFarm.Localization.Strings.WarningFailed); diff --git a/ArchiSteamFarm/Core/ArchiNet.cs b/ArchiSteamFarm/Core/ArchiNet.cs index 3d24b7aa8..ce2b20b21 100644 --- a/ArchiSteamFarm/Core/ArchiNet.cs +++ b/ArchiSteamFarm/Core/ArchiNet.cs @@ -93,7 +93,7 @@ internal static class ArchiNet { ObjectResponse? response = await ASF.WebBrowser.UrlGetToJsonObject(request).ConfigureAwait(false); - if (response == null) { + if (response?.Content == null) { return null; } diff --git a/ArchiSteamFarm/Steam/Integration/ArchiWebHandler.cs b/ArchiSteamFarm/Steam/Integration/ArchiWebHandler.cs index 5e6f3913f..616350e17 100644 --- a/ArchiSteamFarm/Steam/Integration/ArchiWebHandler.cs +++ b/ArchiSteamFarm/Steam/Integration/ArchiWebHandler.cs @@ -181,7 +181,7 @@ public sealed class ArchiWebHandler : IDisposable { response = await UrlGetToJsonObjectWithSession(request, requestOptions: WebBrowser.ERequestOptions.ReturnServerErrors, rateLimitingDelay: rateLimitingDelay).ConfigureAwait(false); - if (response == null) { + if (response?.Content == null) { throw new HttpRequestException(string.Format(CultureInfo.CurrentCulture, Strings.ErrorObjectIsNull, nameof(response))); } @@ -210,7 +210,7 @@ public sealed class ArchiWebHandler : IDisposable { } } - if (response == null) { + if (response?.Content == null) { throw new HttpRequestException(string.Format(CultureInfo.CurrentCulture, Strings.ErrorObjectIsNull, nameof(response))); } @@ -434,7 +434,7 @@ public sealed class ArchiWebHandler : IDisposable { for (byte i = 0; (i < WebBrowser.MaxTries) && (response == null); i++) { response = await UrlPostToJsonObjectWithSession(request, data: data, referer: referer, requestOptions: WebBrowser.ERequestOptions.ReturnServerErrors).ConfigureAwait(false); - if (response == null) { + if (response?.Content == null) { return (false, mobileTradeOfferIDs); } @@ -453,7 +453,7 @@ public sealed class ArchiWebHandler : IDisposable { } } - if (response == null) { + if (response?.Content == null) { return (false, mobileTradeOfferIDs); } @@ -1231,7 +1231,7 @@ public sealed class ArchiWebHandler : IDisposable { ObjectResponse? response = await UrlPostToJsonObjectWithSession(request, data: data).ConfigureAwait(false); - if (response == null) { + if (response?.Content == null) { return false; } @@ -1263,7 +1263,7 @@ public sealed class ArchiWebHandler : IDisposable { for (byte i = 0; (i < WebBrowser.MaxTries) && (response == null); i++) { response = await UrlPostToJsonObjectWithSession(request, data: data, referer: referer, requestOptions: WebBrowser.ERequestOptions.ReturnServerErrors).ConfigureAwait(false); - if (response == null) { + if (response?.Content == null) { return (false, false); } @@ -1282,7 +1282,7 @@ public sealed class ArchiWebHandler : IDisposable { } } - return response != null ? (true, response.Content.RequiresMobileConfirmation) : (false, false); + return response?.Content != null ? (true, response.Content.RequiresMobileConfirmation) : (false, false); } internal async Task<(EResult Result, EPurchaseResultDetail PurchaseResult)> AddFreeLicense(uint subID) { @@ -1354,7 +1354,7 @@ public sealed class ArchiWebHandler : IDisposable { ObjectResponse? response = await UrlPostToJsonObjectWithSession(request, data: data).ConfigureAwait(false); - if (response == null) { + if (response?.Content == null) { return false; } @@ -1400,7 +1400,7 @@ public sealed class ArchiWebHandler : IDisposable { ObjectResponse? response = await UrlPostToJsonObjectWithSession(request, data: data).ConfigureAwait(false); - return response?.Content.Queue; + return response?.Content?.Queue; } internal async Task?> GetActiveTradeOffers() { @@ -1717,7 +1717,7 @@ public sealed class ArchiWebHandler : IDisposable { using HtmlDocumentResponse? response = await UrlGetToHtmlDocumentWithSession(request, checkSessionPreemptively: false).ConfigureAwait(false); - if (response == null) { + if (response?.Content == null) { return null; } @@ -1763,7 +1763,7 @@ public sealed class ArchiWebHandler : IDisposable { using HtmlDocumentResponse? response = await UrlGetToHtmlDocumentWithSession(request, checkSessionPreemptively: false).ConfigureAwait(false); - if (response == null) { + if (response?.Content == null) { return null; } @@ -1855,7 +1855,7 @@ public sealed class ArchiWebHandler : IDisposable { using HtmlDocumentResponse? response = await UrlGetToHtmlDocumentWithSession(request, checkSessionPreemptively: false).ConfigureAwait(false); - IElement? htmlNode = response?.Content.SelectSingleNode("//div[@class='pagecontent']/script"); + IElement? htmlNode = response?.Content?.SelectSingleNode("//div[@class='pagecontent']/script"); if (htmlNode == null) { // Trade can be no longer valid @@ -2005,7 +2005,7 @@ public sealed class ArchiWebHandler : IDisposable { ObjectResponse? response = await UrlGetToJsonObjectWithSession(request).ConfigureAwait(false); - return response?.Content.Success; + return response?.Content?.Success; } internal async Task HandleConfirmations(string deviceID, string confirmationHash, uint time, IReadOnlyCollection confirmations, bool accept) { @@ -2059,7 +2059,7 @@ public sealed class ArchiWebHandler : IDisposable { ObjectResponse? response = await UrlPostToJsonObjectWithSession(request, data: data).ConfigureAwait(false); - return response?.Content.Success; + return response?.Content?.Success; } internal async Task Init(ulong steamID, EUniverse universe, string webAPIUserNonce, string? parentalCode = null) { @@ -2253,7 +2253,7 @@ public sealed class ArchiWebHandler : IDisposable { ObjectResponse? response = await UrlPostToJsonObjectWithSession(request, data: data).ConfigureAwait(false); - if (response == null) { + if (response?.Content == null) { return null; } @@ -2292,7 +2292,7 @@ public sealed class ArchiWebHandler : IDisposable { ObjectResponse? response = await UrlPostToJsonObjectWithSession(request, data: data).ConfigureAwait(false); - return response?.Content.Result == EResult.OK; + return response?.Content?.Result == EResult.OK; } private async Task<(ESteamApiKeyState State, string? Key)> GetApiKeyState() { @@ -2300,7 +2300,7 @@ public sealed class ArchiWebHandler : IDisposable { using HtmlDocumentResponse? response = await UrlGetToHtmlDocumentWithSession(request, checkSessionPreemptively: false).ConfigureAwait(false); - if (response == null) { + if (response?.Content == null) { return (ESteamApiKeyState.Timeout, null); } @@ -2570,7 +2570,7 @@ public sealed class ArchiWebHandler : IDisposable { ObjectResponse? response = await UrlGetToJsonObjectWithSession(request).ConfigureAwait(false); // ReSharper disable once RedundantSuppressNullableWarningExpression - required for .NET Framework - return !string.IsNullOrEmpty(response?.Content.Data.WebAPIToken) ? (true, response!.Content.Data.WebAPIToken) : (false, null); + return !string.IsNullOrEmpty(response?.Content?.Data.WebAPIToken) ? (true, response!.Content!.Data.WebAPIToken) : (false, null); } private async Task<(bool Success, string? Result)> ResolveApiKey() { diff --git a/ArchiSteamFarm/Web/GitHub.cs b/ArchiSteamFarm/Web/GitHub.cs index 9f9d51b83..94678a545 100644 --- a/ArchiSteamFarm/Web/GitHub.cs +++ b/ArchiSteamFarm/Web/GitHub.cs @@ -73,19 +73,19 @@ internal static class GitHub { Uri request = new($"{SharedInfo.ProjectURL}/wiki/{page}/_history"); - using HtmlDocumentResponse? response = await ASF.WebBrowser.UrlGetToHtmlDocument(request, requestOptions: WebBrowser.ERequestOptions.ReturnClientErrors).ConfigureAwait(false); + using HtmlDocumentResponse? response = await ASF.WebBrowser.UrlGetToHtmlDocument(request, requestOptions: WebBrowser.ERequestOptions.ReturnClientErrors | WebBrowser.ERequestOptions.AllowInvalidBodyOnErrors).ConfigureAwait(false); - if (response == null) { - return null; - } - - if (response.StatusCode.IsClientErrorCode()) { + if (response?.StatusCode.IsClientErrorCode() == true) { return response.StatusCode switch { HttpStatusCode.NotFound => new Dictionary(0), _ => null }; } + if (response?.Content == null) { + return null; + } + IEnumerable revisionNodes = response.Content.SelectNodes("//li[contains(@class, 'wiki-history-revision')]"); Dictionary result = new(); @@ -148,7 +148,7 @@ internal static class GitHub { using HtmlDocumentResponse? response = await ASF.WebBrowser.UrlGetToHtmlDocument(request).ConfigureAwait(false); - if (response == null) { + if (response?.Content == null) { return null; } diff --git a/ArchiSteamFarm/Web/Responses/HtmlDocumentResponse.cs b/ArchiSteamFarm/Web/Responses/HtmlDocumentResponse.cs index fa2365fb2..7027b5692 100644 --- a/ArchiSteamFarm/Web/Responses/HtmlDocumentResponse.cs +++ b/ArchiSteamFarm/Web/Responses/HtmlDocumentResponse.cs @@ -23,22 +23,19 @@ using System; using System.Threading.Tasks; using AngleSharp; using AngleSharp.Dom; -using ArchiSteamFarm.Core; using JetBrains.Annotations; namespace ArchiSteamFarm.Web.Responses; public sealed class HtmlDocumentResponse : BasicResponse, IDisposable { [PublicAPI] - public IDocument Content { get; } + public IDocument? Content { get; } - private HtmlDocumentResponse(BasicResponse basicResponse, IDocument content) : base(basicResponse) { - ArgumentNullException.ThrowIfNull(basicResponse); + public HtmlDocumentResponse(BasicResponse basicResponse) : base(basicResponse) => ArgumentNullException.ThrowIfNull(basicResponse); - Content = content ?? throw new ArgumentNullException(nameof(content)); - } + private HtmlDocumentResponse(BasicResponse basicResponse, IDocument content) : this(basicResponse) => Content = content ?? throw new ArgumentNullException(nameof(content)); - public void Dispose() => Content.Dispose(); + public void Dispose() => Content?.Dispose(); [PublicAPI] public static async Task Create(StreamResponse streamResponse) { @@ -46,14 +43,8 @@ public sealed class HtmlDocumentResponse : BasicResponse, IDisposable { IBrowsingContext context = BrowsingContext.New(); - try { - IDocument document = await context.OpenAsync(req => req.Content(streamResponse.Content, true)).ConfigureAwait(false); + IDocument document = await context.OpenAsync(request => request.Content(streamResponse.Content, true)).ConfigureAwait(false); - return new HtmlDocumentResponse(streamResponse, document); - } catch (Exception e) { - ASF.ArchiLogger.LogGenericWarningException(e); - - return null; - } + return new HtmlDocumentResponse(streamResponse, document); } } diff --git a/ArchiSteamFarm/Web/Responses/ObjectResponse.cs b/ArchiSteamFarm/Web/Responses/ObjectResponse.cs index 60b2ad649..0f0d2edca 100644 --- a/ArchiSteamFarm/Web/Responses/ObjectResponse.cs +++ b/ArchiSteamFarm/Web/Responses/ObjectResponse.cs @@ -26,11 +26,9 @@ namespace ArchiSteamFarm.Web.Responses; public sealed class ObjectResponse : BasicResponse { [PublicAPI] - public T Content { get; } + public T? Content { get; } - public ObjectResponse(BasicResponse basicResponse, T content) : base(basicResponse) { - ArgumentNullException.ThrowIfNull(basicResponse); + public ObjectResponse(BasicResponse basicResponse, T? content) : this(basicResponse) => Content = content; - Content = content ?? throw new ArgumentNullException(nameof(content)); - } + public ObjectResponse(BasicResponse basicResponse) : base(basicResponse) => ArgumentNullException.ThrowIfNull(basicResponse); } diff --git a/ArchiSteamFarm/Web/Responses/OptionalObjectResponse.cs b/ArchiSteamFarm/Web/Responses/OptionalObjectResponse.cs deleted file mode 100644 index 8fa5e2bdf..000000000 --- a/ArchiSteamFarm/Web/Responses/OptionalObjectResponse.cs +++ /dev/null @@ -1,38 +0,0 @@ -// _ _ _ ____ _ _____ -// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___ -// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \ -// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | -// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| -// | -// Copyright 2015-2022 Ɓukasz "JustArchi" Domeradzki -// Contact: JustArchi@JustArchi.net -// | -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// | -// http://www.apache.org/licenses/LICENSE-2.0 -// | -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using System; -using JetBrains.Annotations; - -namespace ArchiSteamFarm.Web.Responses; - -public sealed class OptionalObjectResponse : BasicResponse { - [PublicAPI] - public T? Content { get; } - - public OptionalObjectResponse(BasicResponse basicResponse, T? content) : base(basicResponse) { - ArgumentNullException.ThrowIfNull(basicResponse); - - Content = content; - } - - public OptionalObjectResponse(BasicResponse basicResponse) : base(basicResponse) => ArgumentNullException.ThrowIfNull(basicResponse); -} diff --git a/ArchiSteamFarm/Web/WebBrowser.cs b/ArchiSteamFarm/Web/WebBrowser.cs index a331a6fd3..c88c7a6b9 100644 --- a/ArchiSteamFarm/Web/WebBrowser.cs +++ b/ArchiSteamFarm/Web/WebBrowser.cs @@ -266,6 +266,10 @@ public sealed class WebBrowser : IDisposable { try { return await HtmlDocumentResponse.Create(response).ConfigureAwait(false); } catch (Exception e) { + if ((requestOptions.HasFlag(ERequestOptions.AllowInvalidBodyOnSuccess) && response.StatusCode.IsSuccessCode()) || (requestOptions.HasFlag(ERequestOptions.AllowInvalidBodyOnErrors) && !response.StatusCode.IsSuccessCode())) { + return new HtmlDocumentResponse(response); + } + ArchiLogger.LogGenericWarningException(e); ArchiLogger.LogGenericDebug(string.Format(CultureInfo.CurrentCulture, Strings.ErrorFailingRequest, request)); } @@ -332,6 +336,10 @@ public sealed class WebBrowser : IDisposable { obj = serializer.Deserialize(jsonReader); } catch (Exception e) { + if ((requestOptions.HasFlag(ERequestOptions.AllowInvalidBodyOnSuccess) && response.StatusCode.IsSuccessCode()) || (requestOptions.HasFlag(ERequestOptions.AllowInvalidBodyOnErrors) && !response.StatusCode.IsSuccessCode())) { + return new ObjectResponse(response); + } + ArchiLogger.LogGenericWarningException(e); ArchiLogger.LogGenericDebug(string.Format(CultureInfo.CurrentCulture, Strings.ErrorFailingRequest, request)); @@ -339,6 +347,10 @@ public sealed class WebBrowser : IDisposable { } if (obj is null) { + if ((requestOptions.HasFlag(ERequestOptions.AllowInvalidBodyOnSuccess) && response.StatusCode.IsSuccessCode()) || (requestOptions.HasFlag(ERequestOptions.AllowInvalidBodyOnErrors) && !response.StatusCode.IsSuccessCode())) { + return new ObjectResponse(response); + } + ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsEmpty, nameof(obj))); continue; @@ -573,6 +585,10 @@ public sealed class WebBrowser : IDisposable { try { return await HtmlDocumentResponse.Create(response).ConfigureAwait(false); } catch (Exception e) { + if ((requestOptions.HasFlag(ERequestOptions.AllowInvalidBodyOnSuccess) && response.StatusCode.IsSuccessCode()) || (requestOptions.HasFlag(ERequestOptions.AllowInvalidBodyOnErrors) && !response.StatusCode.IsSuccessCode())) { + return new HtmlDocumentResponse(response); + } + ArchiLogger.LogGenericWarningException(e); ArchiLogger.LogGenericDebug(string.Format(CultureInfo.CurrentCulture, Strings.ErrorFailingRequest, request)); } @@ -599,82 +615,6 @@ public sealed class WebBrowser : IDisposable { throw new ArgumentOutOfRangeException(nameof(rateLimitingDelay)); } - for (byte i = 0; i < maxTries; i++) { - if ((i > 0) && (rateLimitingDelay > 0)) { - await Task.Delay(rateLimitingDelay).ConfigureAwait(false); - } - - StreamResponse? response = await UrlPostToStream(request, headers, data, referer, requestOptions | ERequestOptions.ReturnClientErrors, 1, rateLimitingDelay).ConfigureAwait(false); - - if (response == null) { - // Request timed out, try again - continue; - } - - await using (response.ConfigureAwait(false)) { - if (response.StatusCode.IsRedirectionCode()) { - if (!requestOptions.HasFlag(ERequestOptions.ReturnRedirections)) { - // We're not handling this error, do not try again - break; - } - } else if (response.StatusCode.IsClientErrorCode()) { - if (!requestOptions.HasFlag(ERequestOptions.ReturnClientErrors)) { - // We're not handling this error, do not try again - break; - } - } else if (response.StatusCode.IsServerErrorCode()) { - if (!requestOptions.HasFlag(ERequestOptions.ReturnServerErrors)) { - // We're not handling this error, try again - continue; - } - } - - TResult? obj; - - try { - using StreamReader steamReader = new(response.Content); - using JsonReader jsonReader = new JsonTextReader(steamReader); - - JsonSerializer serializer = new(); - - obj = serializer.Deserialize(jsonReader); - } catch (Exception e) { - ArchiLogger.LogGenericWarningException(e); - ArchiLogger.LogGenericDebug(string.Format(CultureInfo.CurrentCulture, Strings.ErrorFailingRequest, request)); - - continue; - } - - if (obj is null) { - ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsEmpty, nameof(obj))); - - continue; - } - - return new ObjectResponse(response, obj); - } - } - - if (maxTries > 1) { - ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.ErrorRequestFailedTooManyTimes, maxTries)); - ArchiLogger.LogGenericDebug(string.Format(CultureInfo.CurrentCulture, Strings.ErrorFailingRequest, request)); - } - - return null; - } - - [PublicAPI] - public async Task?> UrlPostToOptionalJsonObject(Uri request, IReadOnlyCollection>? headers = null, TData? data = null, Uri? referer = null, ERequestOptions requestOptions = ERequestOptions.None, byte maxTries = MaxTries, int rateLimitingDelay = 0) where TData : class { - ArgumentNullException.ThrowIfNull(request); - - if (maxTries == 0) { - throw new ArgumentOutOfRangeException(nameof(maxTries)); - } - - if (rateLimitingDelay < 0) { - throw new ArgumentOutOfRangeException(nameof(rateLimitingDelay)); - } - for (byte i = 0; i < maxTries; i++) { if ((i > 0) && (rateLimitingDelay > 0)) { await Task.Delay(rateLimitingDelay).ConfigureAwait(false); @@ -716,7 +656,7 @@ public sealed class WebBrowser : IDisposable { obj = serializer.Deserialize(jsonReader); } catch (Exception e) { if ((requestOptions.HasFlag(ERequestOptions.AllowInvalidBodyOnSuccess) && response.StatusCode.IsSuccessCode()) || (requestOptions.HasFlag(ERequestOptions.AllowInvalidBodyOnErrors) && !response.StatusCode.IsSuccessCode())) { - return new OptionalObjectResponse(response); + return new ObjectResponse(response); } ArchiLogger.LogGenericWarningException(e); @@ -727,7 +667,7 @@ public sealed class WebBrowser : IDisposable { if (obj is null) { if ((requestOptions.HasFlag(ERequestOptions.AllowInvalidBodyOnSuccess) && response.StatusCode.IsSuccessCode()) || (requestOptions.HasFlag(ERequestOptions.AllowInvalidBodyOnErrors) && !response.StatusCode.IsSuccessCode())) { - return new OptionalObjectResponse(response); + return new ObjectResponse(response); } ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsEmpty, nameof(obj))); @@ -735,7 +675,7 @@ public sealed class WebBrowser : IDisposable { continue; } - return new OptionalObjectResponse(response, obj); + return new ObjectResponse(response, obj); } }