Skip to content

Commit

Permalink
Closes #3299
Browse files Browse the repository at this point in the history
  • Loading branch information
JustArchi committed Sep 30, 2024
1 parent c2abbf0 commit 55d49f8
Show file tree
Hide file tree
Showing 4 changed files with 153 additions and 0 deletions.
28 changes: 28 additions & 0 deletions ArchiSteamFarm/IPC/Controllers/Api/BotController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,34 @@ public async Task<ActionResult<GenericResponse>> AddLicensePost(string botNames,
return Ok(new GenericResponse<IReadOnlyDictionary<string, BotAddLicenseResponse>>(result));
}

/// <summary>
/// Redeems points on given bots.
/// </summary>
[Consumes("application/json")]
[HttpPost("{botNames:required}/RedeemPoints/{definitionID:required}")]
[ProducesResponseType<GenericResponse<IReadOnlyDictionary<string, EResult>>>((int) HttpStatusCode.OK)]
[ProducesResponseType<GenericResponse>((int) HttpStatusCode.BadRequest)]
public async Task<ActionResult<GenericResponse>> AddLicensePost(string botNames, uint definitionID) {
ArgumentException.ThrowIfNullOrEmpty(botNames);
ArgumentOutOfRangeException.ThrowIfZero(definitionID);

HashSet<Bot>? bots = Bot.GetBots(botNames);

if ((bots == null) || (bots.Count == 0)) {
return BadRequest(new GenericResponse(false, Strings.FormatBotNotFound(botNames)));
}

IList<EResult> results = await Utilities.InParallel(bots.Select(bot => bot.Actions.RedeemPoints(definitionID))).ConfigureAwait(false);

Dictionary<string, EResult> result = new(bots.Count, Bot.BotsComparer);

foreach (Bot bot in bots) {
result[bot.BotName] = results[result.Count];
}

return Ok(new GenericResponse<IReadOnlyDictionary<string, EResult>>(result));
}

/// <summary>
/// Deletes all files related to given bots.
/// </summary>
Expand Down
31 changes: 31 additions & 0 deletions ArchiSteamFarm/Steam/Integration/ArchiHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ public sealed class ArchiHandler : ClientMsgHandler {
private readonly SteamUnifiedMessages.UnifiedService<IEcon> UnifiedEconService;
private readonly SteamUnifiedMessages.UnifiedService<IFamilyGroups> UnifiedFamilyGroups;
private readonly SteamUnifiedMessages.UnifiedService<IFriendMessages> UnifiedFriendMessagesService;
private readonly SteamUnifiedMessages.UnifiedService<ILoyaltyRewards> UnifiedLoyaltyRewards;
private readonly SteamUnifiedMessages.UnifiedService<IPlayer> UnifiedPlayerService;
private readonly SteamUnifiedMessages.UnifiedService<IStore> UnifiedStoreService;
private readonly SteamUnifiedMessages.UnifiedService<ITwoFactor> UnifiedTwoFactorService;
Expand All @@ -80,6 +81,7 @@ internal ArchiHandler(ArchiLogger archiLogger, SteamUnifiedMessages steamUnified
UnifiedEconService = steamUnifiedMessages.CreateService<IEcon>();
UnifiedFamilyGroups = steamUnifiedMessages.CreateService<IFamilyGroups>();
UnifiedFriendMessagesService = steamUnifiedMessages.CreateService<IFriendMessages>();
UnifiedLoyaltyRewards = steamUnifiedMessages.CreateService<ILoyaltyRewards>();
UnifiedPlayerService = steamUnifiedMessages.CreateService<IPlayer>();
UnifiedStoreService = steamUnifiedMessages.CreateService<IStore>();
UnifiedTwoFactorService = steamUnifiedMessages.CreateService<ITwoFactor>();
Expand Down Expand Up @@ -500,6 +502,35 @@ public async Task<bool> LeaveChatRoomGroup(ulong chatGroupID) {
return response.Result == EResult.OK;
}

[PublicAPI]
public async Task<EResult> 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<bool> RemoveFriend(ulong steamID) {
if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) {
Expand Down
7 changes: 7 additions & 0 deletions ArchiSteamFarm/Steam/Interaction/Actions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,13 @@ public static string Hash(ArchiCryptoHelper.EHashingMethod hashingMethod, string
return await Bot.ArchiHandler.RedeemKey(key).ConfigureAwait(false);
}

[PublicAPI]
public async Task<EResult> 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) {
Expand Down
87 changes: 87 additions & 0 deletions ArchiSteamFarm/Steam/Interaction/Commands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,10 @@ public static EAccess GetProxyAccess(Bot bot, EAccess access, ulong steamID = 0)
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":
Expand Down Expand Up @@ -2763,6 +2767,89 @@ internal void OnNewLicenseList() {
return responses.Count > 0 ? string.Join(Environment.NewLine, responses) : null;
}

private async Task<string?> ResponseRedeemPoints(EAccess access, HashSet<uint> 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<string?> 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<uint> 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<string?> 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<Bot>? bots = Bot.GetBots(botNames);

if ((bots == null) || (bots.Count == 0)) {
return access >= EAccess.Owner ? FormatStaticResponse(Strings.FormatBotNotFound(botNames)) : null;
}

IList<string?> results = await Utilities.InParallel(bots.Select(bot => bot.Commands.ResponseRedeemPoints(GetProxyAccess(bot, access, steamID), targetDefinitionIDs))).ConfigureAwait(false);

List<string> responses = [..results.Where(static result => !string.IsNullOrEmpty(result))!];

return responses.Count > 0 ? string.Join(Environment.NewLine, responses) : null;
}

private async Task<string?> ResponseReset(EAccess access) {
if (!Enum.IsDefined(access)) {
throw new InvalidEnumArgumentException(nameof(access), (int) access, typeof(EAccess));
Expand Down

0 comments on commit 55d49f8

Please sign in to comment.