From 362f4db10d88cbe6a9f2942647acdee0b152e5c8 Mon Sep 17 00:00:00 2001 From: JustArchi Date: Mon, 29 Oct 2018 21:43:44 +0100 Subject: [PATCH] Add /Bot/Redeem API endpoint @ASF-ui --- ArchiSteamFarm/ArchiHandler.cs | 37 ++++++++--------- .../IPC/Controllers/Api/BotController.cs | 39 +++++++++++++++++- ...=> BotGamesToRedeemInBackgroundRequest.cs} | 4 +- .../IPC/Requests/BotRedeemRequest.cs | 40 +++++++++++++++++++ ArchiSteamFarm/IPC/Startup.cs | 10 +---- 5 files changed, 99 insertions(+), 31 deletions(-) rename ArchiSteamFarm/IPC/Requests/{GamesToRedeemInBackgroundRequest.cs => BotGamesToRedeemInBackgroundRequest.cs} (94%) create mode 100644 ArchiSteamFarm/IPC/Requests/BotRedeemRequest.cs diff --git a/ArchiSteamFarm/ArchiHandler.cs b/ArchiSteamFarm/ArchiHandler.cs index ad43cde41..373b1f906 100644 --- a/ArchiSteamFarm/ArchiHandler.cs +++ b/ArchiSteamFarm/ArchiHandler.cs @@ -34,7 +34,7 @@ using SteamKit2.Internal; using SteamKit2.Unified.Internal; namespace ArchiSteamFarm { - internal sealed class ArchiHandler : ClientMsgHandler { + public sealed class ArchiHandler : ClientMsgHandler { internal const byte MaxGamesPlayedConcurrently = 32; // This is limit introduced by Steam Network private readonly ArchiLogger ArchiLogger; @@ -555,24 +555,12 @@ namespace ArchiSteamFarm { Client.PostCallback(new VanityURLChangedCallback(packetMsg.TargetJobID, response.Body)); } - internal sealed class PlayingSessionStateCallback : CallbackMsg { - internal readonly bool PlayingBlocked; + [SuppressMessage("ReSharper", "MemberCanBeInternal")] + public sealed class PurchaseResponseCallback : CallbackMsg { + public readonly Dictionary Items; - internal PlayingSessionStateCallback(JobID jobID, CMsgClientPlayingSessionState msg) { - if ((jobID == null) || (msg == null)) { - throw new ArgumentNullException(nameof(jobID) + " || " + nameof(msg)); - } - - JobID = jobID; - PlayingBlocked = msg.playing_blocked; - } - } - - internal sealed class PurchaseResponseCallback : CallbackMsg { - internal readonly Dictionary Items; - - internal EPurchaseResultDetail PurchaseResultDetail { get; set; } - internal EResult Result { get; set; } + public EPurchaseResultDetail PurchaseResultDetail { get; internal set; } + public EResult Result { get; internal set; } internal PurchaseResponseCallback(JobID jobID, CMsgClientPurchaseResponse msg) { if ((jobID == null) || (msg == null)) { @@ -627,6 +615,19 @@ namespace ArchiSteamFarm { } } + internal sealed class PlayingSessionStateCallback : CallbackMsg { + internal readonly bool PlayingBlocked; + + internal PlayingSessionStateCallback(JobID jobID, CMsgClientPlayingSessionState msg) { + if ((jobID == null) || (msg == null)) { + throw new ArgumentNullException(nameof(jobID) + " || " + nameof(msg)); + } + + JobID = jobID; + PlayingBlocked = msg.playing_blocked; + } + } + internal sealed class RedeemGuestPassResponseCallback : CallbackMsg { internal readonly EResult Result; diff --git a/ArchiSteamFarm/IPC/Controllers/Api/BotController.cs b/ArchiSteamFarm/IPC/Controllers/Api/BotController.cs index 7e6b4e9c3..b75e0e0d6 100644 --- a/ArchiSteamFarm/IPC/Controllers/Api/BotController.cs +++ b/ArchiSteamFarm/IPC/Controllers/Api/BotController.cs @@ -150,7 +150,7 @@ namespace ArchiSteamFarm.IPC.Controllers.Api { IList<(Dictionary UnusedKeys, Dictionary UsedKeys)> results = await Utilities.InParallel(bots.Select(bot => bot.GetUsedAndUnusedKeys())).ConfigureAwait(false); - Dictionary result = new Dictionary(); + Dictionary result = new Dictionary(bots.Count); foreach (Bot bot in bots) { (Dictionary unusedKeys, Dictionary usedKeys) = results[result.Count]; @@ -166,7 +166,7 @@ namespace ArchiSteamFarm.IPC.Controllers.Api { [Consumes("application/json")] [HttpPost("{botName:required}/GamesToRedeemInBackground")] [ProducesResponseType(typeof(GenericResponse), 200)] - public async Task>> GamesToRedeemInBackgroundPost(string botName, [FromBody] GamesToRedeemInBackgroundRequest request) { + public async Task>> GamesToRedeemInBackgroundPost(string botName, [FromBody] BotGamesToRedeemInBackgroundRequest request) { if (string.IsNullOrEmpty(botName) || (request == null)) { ASF.ArchiLogger.LogNullError(nameof(botName) + " || " + nameof(request)); return BadRequest(new GenericResponse(false, string.Format(Strings.ErrorIsEmpty, nameof(botName) + " || " + nameof(request)))); @@ -205,6 +205,41 @@ namespace ArchiSteamFarm.IPC.Controllers.Api { return Ok(new GenericResponse(results.All(result => result.Success), string.Join(Environment.NewLine, results.Select(result => result.Output)))); } + /// + /// Redeems cd-keys on given bot. + /// + /// + /// Response contains a map that maps each provided cd-key to its redeem result. + /// Redeem result can be a null value, this means that ASF didn't even attempt to send a request (e.g. because of bot not being connected to Steam network). + /// + [Consumes("application/json")] + [HttpPost("{botName:required}/Redeem")] + [ProducesResponseType(typeof(GenericResponse>), 200)] + public async Task>>> RedeemPost(string botName, [FromBody] BotRedeemRequest request) { + if (string.IsNullOrEmpty(botName) || (request == null)) { + ASF.ArchiLogger.LogNullError(nameof(botName) + " || " + nameof(request)); + return BadRequest(new GenericResponse>(false, string.Format(Strings.ErrorIsEmpty, nameof(botName) + " || " + nameof(request)))); + } + + if (request.KeysToRedeem.Count == 0) { + return BadRequest(new GenericResponse>(false, string.Format(Strings.ErrorIsEmpty, nameof(request.KeysToRedeem)))); + } + + if (!Bot.Bots.TryGetValue(botName, out Bot bot)) { + return BadRequest(new GenericResponse>(false, string.Format(Strings.BotNotFound, botName))); + } + + IList results = await Utilities.InParallel(request.KeysToRedeem.Select(key => bot.Actions.RedeemKey(key))).ConfigureAwait(false); + + Dictionary result = new Dictionary(request.KeysToRedeem.Count); + + foreach (string key in request.KeysToRedeem) { + result[key] = results[result.Count]; + } + + return Ok(new GenericResponse>(result.Values.All(value => value != null), result)); + } + /// /// Resumes given bots. /// diff --git a/ArchiSteamFarm/IPC/Requests/GamesToRedeemInBackgroundRequest.cs b/ArchiSteamFarm/IPC/Requests/BotGamesToRedeemInBackgroundRequest.cs similarity index 94% rename from ArchiSteamFarm/IPC/Requests/GamesToRedeemInBackgroundRequest.cs rename to ArchiSteamFarm/IPC/Requests/BotGamesToRedeemInBackgroundRequest.cs index 02e4923c6..ea1f211d7 100644 --- a/ArchiSteamFarm/IPC/Requests/GamesToRedeemInBackgroundRequest.cs +++ b/ArchiSteamFarm/IPC/Requests/BotGamesToRedeemInBackgroundRequest.cs @@ -26,7 +26,7 @@ using Newtonsoft.Json; namespace ArchiSteamFarm.IPC.Requests { [SuppressMessage("ReSharper", "ClassCannotBeInstantiated")] - public sealed class GamesToRedeemInBackgroundRequest { + public sealed class BotGamesToRedeemInBackgroundRequest { /// /// A string-string map that maps cd-key to redeem (key) to its name (value). /// @@ -39,6 +39,6 @@ namespace ArchiSteamFarm.IPC.Requests { public readonly OrderedDictionary GamesToRedeemInBackground; // Deserialized from JSON - private GamesToRedeemInBackgroundRequest() { } + private BotGamesToRedeemInBackgroundRequest() { } } } diff --git a/ArchiSteamFarm/IPC/Requests/BotRedeemRequest.cs b/ArchiSteamFarm/IPC/Requests/BotRedeemRequest.cs new file mode 100644 index 000000000..a93ef42d0 --- /dev/null +++ b/ArchiSteamFarm/IPC/Requests/BotRedeemRequest.cs @@ -0,0 +1,40 @@ +// _ _ _ ____ _ _____ +// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___ +// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \ +// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | +// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| +// +// Copyright 2015-2018 Ł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.ComponentModel.DataAnnotations; +using System.Diagnostics.CodeAnalysis; +using Newtonsoft.Json; + +namespace ArchiSteamFarm.IPC.Requests { + [SuppressMessage("ReSharper", "ClassCannotBeInstantiated")] + public sealed class BotRedeemRequest { + /// + /// A collection (set) of keys to redeem. + /// + [JsonProperty(Required = Required.Always)] + [Required] + public readonly ImmutableHashSet KeysToRedeem; + + // Deserialized from JSON + private BotRedeemRequest() { } + } +} diff --git a/ArchiSteamFarm/IPC/Startup.cs b/ArchiSteamFarm/IPC/Startup.cs index 8c29813a7..1a60af5da 100644 --- a/ArchiSteamFarm/IPC/Startup.cs +++ b/ArchiSteamFarm/IPC/Startup.cs @@ -96,15 +96,7 @@ namespace ArchiSteamFarm.IPC { services.AddResponseCompression(); // Add CORS to allow userscripts and third-party apps - services.AddCors( - builder => { - builder.AddDefaultPolicy( - policyBuilder => { - policyBuilder.AllowAnyOrigin(); - } - ); - } - ); + services.AddCors(builder => builder.AddDefaultPolicy(policyBuilder => policyBuilder.AllowAnyOrigin())); // Add swagger documentation generation services.AddSwaggerGen(