From de1901082000b1430b54629d4f669f56e4c34c9a Mon Sep 17 00:00:00 2001 From: Archi Date: Mon, 27 Sep 2021 19:50:18 +0200 Subject: [PATCH] Enhance !addlicense command We can actually provide very limited details about license activation with the newly implemented endpoint. While this won't provide valuable feedback for vast majority of cases, it can at least pretty reliably provide Fail/RateLimited, for example. Huge thanks to @xPaw for the discovery and help --- ArchiSteamFarm/Core/Utilities.cs | 3 ++ .../Steam/Integration/ArchiWebHandler.cs | 41 +++++++++++++++---- ArchiSteamFarm/Steam/Interaction/Commands.cs | 8 +--- 3 files changed, 39 insertions(+), 13 deletions(-) diff --git a/ArchiSteamFarm/Core/Utilities.cs b/ArchiSteamFarm/Core/Utilities.cs index 270df7d72..369543f18 100644 --- a/ArchiSteamFarm/Core/Utilities.cs +++ b/ArchiSteamFarm/Core/Utilities.cs @@ -178,6 +178,9 @@ namespace ArchiSteamFarm.Core { [PublicAPI] public static bool IsServerErrorCode(this HttpStatusCode statusCode) => statusCode is >= HttpStatusCode.InternalServerError and < (HttpStatusCode) 600; + [PublicAPI] + public static bool IsSuccessCode(this HttpStatusCode statusCode) => statusCode is >= HttpStatusCode.OK and < HttpStatusCode.Ambiguous; + [PublicAPI] public static bool IsValidCdKey(string key) { if (string.IsNullOrEmpty(key)) { diff --git a/ArchiSteamFarm/Steam/Integration/ArchiWebHandler.cs b/ArchiSteamFarm/Steam/Integration/ArchiWebHandler.cs index 094114879..7011f95d6 100644 --- a/ArchiSteamFarm/Steam/Integration/ArchiWebHandler.cs +++ b/ArchiSteamFarm/Steam/Integration/ArchiWebHandler.cs @@ -1295,22 +1295,49 @@ namespace ArchiSteamFarm.Steam.Integration { return response != null ? (true, response.Content.RequiresMobileConfirmation) : (false, false); } - internal async Task AddFreeLicense(uint subID) { + internal async Task<(EResult Result, EPurchaseResultDetail PurchaseResult)> AddFreeLicense(uint subID) { if (subID == 0) { throw new ArgumentOutOfRangeException(nameof(subID)); } - Uri request = new(SteamStoreURL, "/checkout/addfreelicense"); + Uri request = new(SteamStoreURL, $"/checkout/addfreelicense/{subID}"); // Extra entry for sessionID - Dictionary data = new(3, StringComparer.Ordinal) { - { "action", "add_to_cart" }, - { "subid", subID.ToString(CultureInfo.InvariantCulture) } + Dictionary data = new(2, StringComparer.Ordinal) { + { "ajax", "true" } }; - using HtmlDocumentResponse? response = await UrlPostToHtmlDocumentWithSession(request, data: data).ConfigureAwait(false); + ObjectResponse? response = await UrlPostToJsonObjectWithSession(request, data: data, requestOptions: WebBrowser.ERequestOptions.ReturnClientErrors | WebBrowser.ERequestOptions.ReturnServerErrors).ConfigureAwait(false); - return response?.Content.SelectSingleNode("//div[@class='add_free_content_success_area']") != null; + if (response == null) { + return (EResult.Fail, EPurchaseResultDetail.Timeout); + } + + switch (response.StatusCode) { + case HttpStatusCode.Forbidden: + // Let's convert this into something reasonable + return (EResult.AccessDenied, EPurchaseResultDetail.InvalidPackage); + case HttpStatusCode.InternalServerError: + case HttpStatusCode.OK: + // This API is total nuts, it returns sometimes [ ], sometimes { "purchaseresultdetail": int } and sometimes null because f**k you, that's why, I wouldn't be surprised if it returned XML one day + // There is not much we can do apart from trying to extract the result and returning it along with the OK and non-OK response, it's also why it doesn't make any sense to strong-type it + EPurchaseResultDetail purchaseResult = EPurchaseResultDetail.NoDetail; + + if (response.Content is JObject jObject) { + byte? numberResult = jObject["purchaseresultdetail"]?.Value(); + + if (numberResult.HasValue) { + purchaseResult = (EPurchaseResultDetail) numberResult.Value; + } + } + + return (response.StatusCode.IsSuccessCode() ? EResult.OK : EResult.Fail, purchaseResult); + default: + // We should handle all expected status codes above, this is a generic fallback for those that we don't + Bot.ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.WarningUnknownValuePleaseReport, nameof(response.StatusCode), response.StatusCode)); + + return (response.StatusCode.IsSuccessCode() ? EResult.OK : EResult.Fail, EPurchaseResultDetail.ContactSupport); + } } internal async Task ChangePrivacySettings(UserPrivacy userPrivacy) { diff --git a/ArchiSteamFarm/Steam/Interaction/Commands.cs b/ArchiSteamFarm/Steam/Interaction/Commands.cs index 1761bd374..b4b2e31f4 100644 --- a/ArchiSteamFarm/Steam/Interaction/Commands.cs +++ b/ArchiSteamFarm/Steam/Interaction/Commands.cs @@ -647,13 +647,9 @@ namespace ArchiSteamFarm.Steam.Interaction { break; default: - if (!await Bot.ArchiWebHandler.AddFreeLicense(gameID).ConfigureAwait(false)) { - response.AppendLine(FormatBotResponse(string.Format(CultureInfo.CurrentCulture, Strings.BotAddLicense, "sub/" + gameID, EResult.Fail))); + (EResult result, EPurchaseResultDetail purchaseResult) = await Bot.ArchiWebHandler.AddFreeLicense(gameID).ConfigureAwait(false); - continue; - } - - response.AppendLine(FormatBotResponse(string.Format(CultureInfo.CurrentCulture, Strings.BotAddLicenseWithItems, gameID, EResult.OK, "sub/" + gameID))); + response.AppendLine(FormatBotResponse(string.Format(CultureInfo.CurrentCulture, Strings.BotAddLicense, "sub/" + gameID, result + "/" + purchaseResult))); break; }