diff --git a/ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/SteamTokenDumperPlugin.cs b/ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/SteamTokenDumperPlugin.cs index fda28480b..7ee227e50 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)); - ObjectResponse? response = await ASF.WebBrowser.UrlPostToJsonObject(request, data: requestData, requestOptions: WebBrowser.ERequestOptions.ReturnClientErrors).ConfigureAwait(false); + OptionalObjectResponse? response = await ASF.WebBrowser.UrlPostToOptionalJsonObject(request, data: requestData, requestOptions: WebBrowser.ERequestOptions.ReturnClientErrors | WebBrowser.ERequestOptions.AllowInvalidBodyOnErrors).ConfigureAwait(false); if (response == null) { ASF.ArchiLogger.LogGenericWarning(ArchiSteamFarm.Localization.Strings.WarningFailed); @@ -564,7 +564,7 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotC return; } - if (!response.Content.Success) { + if (response.Content is not { Success: true }) { ASF.ArchiLogger.LogGenericError(ArchiSteamFarm.Localization.Strings.WarningFailed); return; diff --git a/ArchiSteamFarm/Web/Responses/OptionalObjectResponse.cs b/ArchiSteamFarm/Web/Responses/OptionalObjectResponse.cs new file mode 100644 index 000000000..8fa5e2bdf --- /dev/null +++ b/ArchiSteamFarm/Web/Responses/OptionalObjectResponse.cs @@ -0,0 +1,38 @@ +// _ _ _ ____ _ _____ +// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___ +// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \ +// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | +// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| +// | +// 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 f26386ba2..a331a6fd3 100644 --- a/ArchiSteamFarm/Web/WebBrowser.cs +++ b/ArchiSteamFarm/Web/WebBrowser.cs @@ -331,12 +331,6 @@ public sealed class WebBrowser : IDisposable { JsonSerializer serializer = new(); obj = serializer.Deserialize(jsonReader); - - if (obj is null) { - ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsEmpty, nameof(obj))); - - continue; - } } catch (Exception e) { ArchiLogger.LogGenericWarningException(e); ArchiLogger.LogGenericDebug(string.Format(CultureInfo.CurrentCulture, Strings.ErrorFailingRequest, request)); @@ -344,6 +338,12 @@ public sealed class WebBrowser : IDisposable { continue; } + if (obj is null) { + ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsEmpty, nameof(obj))); + + continue; + } + return new ObjectResponse(response, obj); } } @@ -638,12 +638,6 @@ public sealed class WebBrowser : IDisposable { JsonSerializer serializer = new(); obj = serializer.Deserialize(jsonReader); - - if (obj is null) { - ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsEmpty, nameof(obj))); - - continue; - } } catch (Exception e) { ArchiLogger.LogGenericWarningException(e); ArchiLogger.LogGenericDebug(string.Format(CultureInfo.CurrentCulture, Strings.ErrorFailingRequest, request)); @@ -651,6 +645,12 @@ public sealed class WebBrowser : IDisposable { continue; } + if (obj is null) { + ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsEmpty, nameof(obj))); + + continue; + } + return new ObjectResponse(response, obj); } } @@ -663,6 +663,90 @@ public sealed class WebBrowser : IDisposable { 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); + } + + 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) { + if ((requestOptions.HasFlag(ERequestOptions.AllowInvalidBodyOnSuccess) && response.StatusCode.IsSuccessCode()) || (requestOptions.HasFlag(ERequestOptions.AllowInvalidBodyOnErrors) && !response.StatusCode.IsSuccessCode())) { + return new OptionalObjectResponse(response); + } + + ArchiLogger.LogGenericWarningException(e); + ArchiLogger.LogGenericDebug(string.Format(CultureInfo.CurrentCulture, Strings.ErrorFailingRequest, request)); + + continue; + } + + if (obj is null) { + if ((requestOptions.HasFlag(ERequestOptions.AllowInvalidBodyOnSuccess) && response.StatusCode.IsSuccessCode()) || (requestOptions.HasFlag(ERequestOptions.AllowInvalidBodyOnErrors) && !response.StatusCode.IsSuccessCode())) { + return new OptionalObjectResponse(response); + } + + ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsEmpty, nameof(obj))); + + continue; + } + + return new OptionalObjectResponse(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 UrlPostToStream(Uri request, IReadOnlyCollection>? headers = null, T? data = null, Uri? referer = null, ERequestOptions requestOptions = ERequestOptions.None, byte maxTries = MaxTries, int rateLimitingDelay = 0) where T : class { ArgumentNullException.ThrowIfNull(request); @@ -925,6 +1009,8 @@ public sealed class WebBrowser : IDisposable { None = 0, ReturnClientErrors = 1, ReturnServerErrors = 2, - ReturnRedirections = 4 + ReturnRedirections = 4, + AllowInvalidBodyOnSuccess = 8, + AllowInvalidBodyOnErrors = 16 } }