From 55d49f87ff79a3d58cc097bd93e7ce7485cb35a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Domeradzki?= Date: Mon, 30 Sep 2024 22:02:09 +0200 Subject: [PATCH] Closes #3299 --- .../IPC/Controllers/Api/BotController.cs | 28 ++++++ .../Steam/Integration/ArchiHandler.cs | 31 +++++++ ArchiSteamFarm/Steam/Interaction/Actions.cs | 7 ++ ArchiSteamFarm/Steam/Interaction/Commands.cs | 87 +++++++++++++++++++ 4 files changed, 153 insertions(+) diff --git a/ArchiSteamFarm/IPC/Controllers/Api/BotController.cs b/ArchiSteamFarm/IPC/Controllers/Api/BotController.cs index ab78e4eda..8973a6b5d 100644 --- a/ArchiSteamFarm/IPC/Controllers/Api/BotController.cs +++ b/ArchiSteamFarm/IPC/Controllers/Api/BotController.cs @@ -74,6 +74,34 @@ public sealed class BotController : ArchiController { return Ok(new GenericResponse>(result)); } + /// + /// Redeems points on given bots. + /// + [Consumes("application/json")] + [HttpPost("{botNames:required}/RedeemPoints/{definitionID:required}")] + [ProducesResponseType>>((int) HttpStatusCode.OK)] + [ProducesResponseType((int) HttpStatusCode.BadRequest)] + public async Task> AddLicensePost(string botNames, uint definitionID) { + ArgumentException.ThrowIfNullOrEmpty(botNames); + ArgumentOutOfRangeException.ThrowIfZero(definitionID); + + HashSet? bots = Bot.GetBots(botNames); + + if ((bots == null) || (bots.Count == 0)) { + return BadRequest(new GenericResponse(false, Strings.FormatBotNotFound(botNames))); + } + + IList results = await Utilities.InParallel(bots.Select(bot => bot.Actions.RedeemPoints(definitionID))).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. /// diff --git a/ArchiSteamFarm/Steam/Integration/ArchiHandler.cs b/ArchiSteamFarm/Steam/Integration/ArchiHandler.cs index 90160b51c..23e0c5ca5 100644 --- a/ArchiSteamFarm/Steam/Integration/ArchiHandler.cs +++ b/ArchiSteamFarm/Steam/Integration/ArchiHandler.cs @@ -61,6 +61,7 @@ public sealed class ArchiHandler : ClientMsgHandler { private readonly SteamUnifiedMessages.UnifiedService UnifiedEconService; private readonly SteamUnifiedMessages.UnifiedService UnifiedFamilyGroups; private readonly SteamUnifiedMessages.UnifiedService UnifiedFriendMessagesService; + private readonly SteamUnifiedMessages.UnifiedService UnifiedLoyaltyRewards; private readonly SteamUnifiedMessages.UnifiedService UnifiedPlayerService; private readonly SteamUnifiedMessages.UnifiedService UnifiedStoreService; private readonly SteamUnifiedMessages.UnifiedService UnifiedTwoFactorService; @@ -80,6 +81,7 @@ public sealed class ArchiHandler : ClientMsgHandler { UnifiedEconService = steamUnifiedMessages.CreateService(); UnifiedFamilyGroups = steamUnifiedMessages.CreateService(); UnifiedFriendMessagesService = steamUnifiedMessages.CreateService(); + UnifiedLoyaltyRewards = steamUnifiedMessages.CreateService(); UnifiedPlayerService = steamUnifiedMessages.CreateService(); UnifiedStoreService = steamUnifiedMessages.CreateService(); UnifiedTwoFactorService = steamUnifiedMessages.CreateService(); @@ -500,6 +502,35 @@ public sealed class ArchiHandler : ClientMsgHandler { return response.Result == EResult.OK; } + [PublicAPI] + public async Task RedeemPoints(uint definitionID) { + ArgumentOutOfRangeException.ThrowIfZero(definitionID); + + if (Client == null) { + throw new InvalidOperationException(nameof(Client)); + } + + if (!Client.IsConnected) { + return EResult.NoConnection; + } + + CLoyaltyRewards_RedeemPoints_Request request = new() { + defid = definitionID + }; + + SteamUnifiedMessages.ServiceMethodResponse response; + + try { + response = await UnifiedLoyaltyRewards.SendMessage(x => x.RedeemPoints(request)).ToLongRunningTask().ConfigureAwait(false); + } catch (Exception e) { + ArchiLogger.LogGenericWarningException(e); + + return EResult.Timeout; + } + + return response.Result; + } + [PublicAPI] public async Task RemoveFriend(ulong steamID) { if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) { diff --git a/ArchiSteamFarm/Steam/Interaction/Actions.cs b/ArchiSteamFarm/Steam/Interaction/Actions.cs index 12526ee48..31e0fcf3b 100644 --- a/ArchiSteamFarm/Steam/Interaction/Actions.cs +++ b/ArchiSteamFarm/Steam/Interaction/Actions.cs @@ -313,6 +313,13 @@ public sealed class Actions : IAsyncDisposable, IDisposable { return await Bot.ArchiHandler.RedeemKey(key).ConfigureAwait(false); } + [PublicAPI] + public async Task RedeemPoints(uint definitionID) { + ArgumentOutOfRangeException.ThrowIfZero(definitionID); + + return await Bot.ArchiHandler.RedeemPoints(definitionID).ConfigureAwait(false); + } + [PublicAPI] public static (bool Success, string Message) Restart() { if (!Program.RestartAllowed) { diff --git a/ArchiSteamFarm/Steam/Interaction/Commands.cs b/ArchiSteamFarm/Steam/Interaction/Commands.cs index 02d16ee0e..fd6c137ed 100644 --- a/ArchiSteamFarm/Steam/Interaction/Commands.cs +++ b/ArchiSteamFarm/Steam/Interaction/Commands.cs @@ -298,6 +298,10 @@ public sealed class Commands { return await ResponseAdvancedRedeem(access, args[1], args[2], Utilities.GetArgsAsText(args, 3, ","), steamID).ConfigureAwait(false); case "R^" or "REDEEM^" when args.Length > 2: return await ResponseAdvancedRedeem(access, args[1], args[2], steamID).ConfigureAwait(false); + case "RP" or "REDEEMPOINTS" when args.Length > 2: + return await ResponseRedeemPoints(access, args[1], Utilities.GetArgsAsText(args, 2, ","), steamID).ConfigureAwait(false); + case "RP" or "REDEEMPOINTS": + return await ResponseRedeemPoints(access, args[1]).ConfigureAwait(false); case "RESET": return await ResponseReset(access, Utilities.GetArgsAsText(args, 1, ","), steamID).ConfigureAwait(false); case "RESUME": @@ -2763,6 +2767,89 @@ public sealed class Commands { return responses.Count > 0 ? string.Join(Environment.NewLine, responses) : null; } + private async Task ResponseRedeemPoints(EAccess access, HashSet definitionIDs) { + if (!Enum.IsDefined(access)) { + throw new InvalidEnumArgumentException(nameof(access), (int) access, typeof(EAccess)); + } + + if ((definitionIDs == null) || (definitionIDs.Count == 0)) { + throw new ArgumentNullException(nameof(definitionIDs)); + } + + if (access < EAccess.Operator) { + return null; + } + + if (!Bot.IsConnectedAndLoggedOn) { + return FormatBotResponse(Strings.BotNotConnected); + } + + StringBuilder response = new(); + + foreach (uint definitionID in definitionIDs) { + EResult result = await Bot.Actions.RedeemPoints(definitionID).ConfigureAwait(false); + + response.AppendLine(FormatBotResponse(Strings.FormatBotAddLicense(definitionID, result))); + } + + return response.Length > 0 ? response.ToString() : null; + } + + private async Task ResponseRedeemPoints(EAccess access, string targetDefinitionIDs) { + if (!Enum.IsDefined(access)) { + throw new InvalidEnumArgumentException(nameof(access), (int) access, typeof(EAccess)); + } + + ArgumentException.ThrowIfNullOrEmpty(targetDefinitionIDs); + + if (access < EAccess.Operator) { + return null; + } + + if (!Bot.IsConnectedAndLoggedOn) { + return FormatBotResponse(Strings.BotNotConnected); + } + + string[] definitions = targetDefinitionIDs.Split(SharedInfo.ListElementSeparators, StringSplitOptions.RemoveEmptyEntries); + + if (definitions.Length == 0) { + return FormatBotResponse(Strings.FormatErrorIsEmpty(nameof(definitions))); + } + + HashSet definitionIDs = new(definitions.Length); + + foreach (string definition in definitions) { + if (!uint.TryParse(definition, out uint definitionID) || (definitionID == 0)) { + return FormatBotResponse(Strings.FormatErrorIsInvalid(nameof(definition))); + } + + definitionIDs.Add(definitionID); + } + + return await ResponseRedeemPoints(access, definitionIDs).ConfigureAwait(false); + } + + private static async Task ResponseRedeemPoints(EAccess access, string botNames, string targetDefinitionIDs, ulong steamID = 0) { + if (!Enum.IsDefined(access)) { + throw new InvalidEnumArgumentException(nameof(access), (int) access, typeof(EAccess)); + } + + ArgumentException.ThrowIfNullOrEmpty(botNames); + ArgumentException.ThrowIfNullOrEmpty(targetDefinitionIDs); + + HashSet? bots = Bot.GetBots(botNames); + + if ((bots == null) || (bots.Count == 0)) { + return access >= EAccess.Owner ? FormatStaticResponse(Strings.FormatBotNotFound(botNames)) : null; + } + + IList results = await Utilities.InParallel(bots.Select(bot => bot.Commands.ResponseRedeemPoints(GetProxyAccess(bot, access, steamID), targetDefinitionIDs))).ConfigureAwait(false); + + List responses = [..results.Where(static result => !string.IsNullOrEmpty(result))!]; + + return responses.Count > 0 ? string.Join(Environment.NewLine, responses) : null; + } + private async Task ResponseReset(EAccess access) { if (!Enum.IsDefined(access)) { throw new InvalidEnumArgumentException(nameof(access), (int) access, typeof(EAccess));