From 416ec920cbe0943c6f6b2d7b1867b61796e851ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Domeradzki?= Date: Sun, 19 May 2024 20:07:41 +0200 Subject: [PATCH] Add AddLicense to ASF API --- .../IPC/Controllers/Api/BotController.cs | 75 +++++++++++++++++++ .../IPC/Requests/BotAddLicenseRequest.cs | 46 ++++++++++++ .../IPC/Responses/AddLicenseResult.cs | 55 ++++++++++++++ .../IPC/Responses/BotAddLicenseResponse.cs | 47 ++++++++++++ 4 files changed, 223 insertions(+) create mode 100644 ArchiSteamFarm/IPC/Requests/BotAddLicenseRequest.cs create mode 100644 ArchiSteamFarm/IPC/Responses/AddLicenseResult.cs create mode 100644 ArchiSteamFarm/IPC/Responses/BotAddLicenseResponse.cs diff --git a/ArchiSteamFarm/IPC/Controllers/Api/BotController.cs b/ArchiSteamFarm/IPC/Controllers/Api/BotController.cs index 041d8bf1a..ea71147fa 100644 --- a/ArchiSteamFarm/IPC/Controllers/Api/BotController.cs +++ b/ArchiSteamFarm/IPC/Controllers/Api/BotController.cs @@ -36,12 +36,45 @@ using ArchiSteamFarm.Localization; using ArchiSteamFarm.Steam; using ArchiSteamFarm.Steam.Storage; using Microsoft.AspNetCore.Mvc; +using SteamKit2; using SteamKit2.Internal; namespace ArchiSteamFarm.IPC.Controllers.Api; [Route("Api/Bot")] public sealed class BotController : ArchiController { + /// + /// Adds (free) licenses on given bots. + /// + [Consumes("application/json")] + [HttpPost("{botNames:required}/AddLicense")] + [ProducesResponseType>>((int) HttpStatusCode.OK)] + [ProducesResponseType((int) HttpStatusCode.BadRequest)] + public async Task> AddLicensePost(string botNames, [FromBody] BotAddLicenseRequest request) { + ArgumentException.ThrowIfNullOrEmpty(botNames); + ArgumentNullException.ThrowIfNull(request); + + if ((request.Apps?.IsEmpty != false) && (request.Packages?.IsEmpty != false)) { + return BadRequest(new GenericResponse(false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsEmpty, $"{nameof(request.Apps)} && {nameof(request.Packages)}"))); + } + + HashSet? bots = Bot.GetBots(botNames); + + if ((bots == null) || (bots.Count == 0)) { + return BadRequest(new GenericResponse(false, string.Format(CultureInfo.CurrentCulture, Strings.BotNotFound, botNames))); + } + + IList results = await Utilities.InParallel(bots.Select(bot => AddLicense(bot, request))).ConfigureAwait(false); + + Dictionary result = new(bots.Count, Bot.BotsComparer); + + foreach (Bot bot in bots) { + result[bot.BotName] = results[result.Count]; + } + + return Ok(new GenericResponse>(result)); + } + /// /// Deletes all files related to given bots. /// @@ -416,4 +449,46 @@ public sealed class BotController : ArchiController { return Ok(new GenericResponse(results.All(static result => result.Success), string.Join(Environment.NewLine, results.Select(static result => result.Message)))); } + + private static async Task AddLicense(Bot bot, BotAddLicenseRequest request) { + ArgumentNullException.ThrowIfNull(bot); + ArgumentNullException.ThrowIfNull(request); + + Dictionary? apps = null; + Dictionary? packages = null; + + if (request.Apps != null) { + apps = new Dictionary(request.Apps.Count); + + foreach (uint appID in request.Apps) { + if (!bot.IsConnectedAndLoggedOn) { + apps[appID] = new AddLicenseResult(EResult.Timeout, EPurchaseResultDetail.Timeout); + + continue; + } + + (EResult result, IReadOnlyCollection? grantedApps, IReadOnlyCollection? grantedPackages) = await bot.Actions.AddFreeLicenseApp(appID).ConfigureAwait(false); + + apps[appID] = new AddLicenseResult(result, (grantedApps?.Count > 0) || (grantedPackages?.Count > 0) ? EPurchaseResultDetail.NoDetail : EPurchaseResultDetail.InvalidData); + } + } + + if (request.Packages != null) { + packages = new Dictionary(request.Packages.Count); + + foreach (uint subID in request.Packages) { + if (!bot.IsConnectedAndLoggedOn) { + packages[subID] = new AddLicenseResult(EResult.Timeout, EPurchaseResultDetail.Timeout); + + continue; + } + + (EResult result, EPurchaseResultDetail purchaseResultDetail) = await bot.Actions.AddFreeLicensePackage(subID).ConfigureAwait(false); + + packages[subID] = new AddLicenseResult(result, purchaseResultDetail); + } + } + + return new BotAddLicenseResponse(apps, packages); + } } diff --git a/ArchiSteamFarm/IPC/Requests/BotAddLicenseRequest.cs b/ArchiSteamFarm/IPC/Requests/BotAddLicenseRequest.cs new file mode 100644 index 000000000..fade3b3f1 --- /dev/null +++ b/ArchiSteamFarm/IPC/Requests/BotAddLicenseRequest.cs @@ -0,0 +1,46 @@ +// ---------------------------------------------------------------------------------------------- +// _ _ _ ____ _ _____ +// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___ +// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \ +// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | +// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| +// ---------------------------------------------------------------------------------------------- +// | +// Copyright 2015-2024 Ł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.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using System.Text.Json.Serialization; + +namespace ArchiSteamFarm.IPC.Requests; + +[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")] +public sealed class BotAddLicenseRequest { + /// + /// A collection (set) of apps (appIDs) to ask license for. + /// + [JsonInclude] + public ImmutableList? Apps { get; private init; } + + /// + /// A collection (set) of packages (subIDs) to ask license for. + /// + [JsonInclude] + public ImmutableList? Packages { get; private init; } + + [JsonConstructor] + private BotAddLicenseRequest() { } +} diff --git a/ArchiSteamFarm/IPC/Responses/AddLicenseResult.cs b/ArchiSteamFarm/IPC/Responses/AddLicenseResult.cs new file mode 100644 index 000000000..2a1993247 --- /dev/null +++ b/ArchiSteamFarm/IPC/Responses/AddLicenseResult.cs @@ -0,0 +1,55 @@ +// ---------------------------------------------------------------------------------------------- +// _ _ _ ____ _ _____ +// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___ +// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \ +// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | +// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| +// ---------------------------------------------------------------------------------------------- +// | +// Copyright 2015-2024 Ł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 System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; +using SteamKit2; + +namespace ArchiSteamFarm.IPC.Responses; + +public sealed class AddLicenseResult { + [JsonInclude] + [JsonRequired] + [Required] + public EPurchaseResultDetail PurchaseResultDetail { get; private init; } + + [JsonInclude] + [JsonRequired] + [Required] + public EResult Result { get; private init; } + + internal AddLicenseResult(EResult result, EPurchaseResultDetail purchaseResultDetail) { + if (!Enum.IsDefined(result)) { + throw new InvalidEnumArgumentException(nameof(result), (int) result, typeof(EResult)); + } + + if (!Enum.IsDefined(purchaseResultDetail)) { + throw new InvalidEnumArgumentException(nameof(purchaseResultDetail), (int) purchaseResultDetail, typeof(EPurchaseResultDetail)); + } + + Result = result; + PurchaseResultDetail = purchaseResultDetail; + } +} diff --git a/ArchiSteamFarm/IPC/Responses/BotAddLicenseResponse.cs b/ArchiSteamFarm/IPC/Responses/BotAddLicenseResponse.cs new file mode 100644 index 000000000..76a69053e --- /dev/null +++ b/ArchiSteamFarm/IPC/Responses/BotAddLicenseResponse.cs @@ -0,0 +1,47 @@ +// ---------------------------------------------------------------------------------------------- +// _ _ _ ____ _ _____ +// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___ +// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \ +// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | +// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| +// ---------------------------------------------------------------------------------------------- +// | +// Copyright 2015-2024 Ł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.Collections.Generic; +using System.Collections.Immutable; +using System.Text.Json.Serialization; + +namespace ArchiSteamFarm.IPC.Responses; + +public sealed class BotAddLicenseResponse { + /// + /// A collection (set) of apps (appIDs) to ask license for. + /// + [JsonInclude] + public ImmutableDictionary? Apps { get; private init; } + + /// + /// A collection (set) of packages (subIDs) to ask license for. + /// + [JsonInclude] + public ImmutableDictionary? Packages { get; private init; } + + internal BotAddLicenseResponse(IReadOnlyDictionary? apps, IReadOnlyDictionary? packages) { + Apps = apps?.ToImmutableDictionary(); + Packages = packages?.ToImmutableDictionary(); + } +}