This commit is contained in:
Łukasz Domeradzki
2024-11-06 17:40:05 +01:00
parent b66d883c1c
commit 27651c8669
5 changed files with 122 additions and 10 deletions

View File

@@ -325,7 +325,7 @@ public sealed class BotController : ArchiController {
[HttpPost("{botNames:required}/RedeemPoints/{definitionID:required}")]
[ProducesResponseType<GenericResponse<IReadOnlyDictionary<string, EResult>>>((int) HttpStatusCode.OK)]
[ProducesResponseType<GenericResponse>((int) HttpStatusCode.BadRequest)]
public async Task<ActionResult<GenericResponse>> RedeemPointsPost(string botNames, uint definitionID) {
public async Task<ActionResult<GenericResponse>> RedeemPointsPost(string botNames, uint definitionID, [FromQuery] bool forced = false) {
ArgumentException.ThrowIfNullOrEmpty(botNames);
ArgumentOutOfRangeException.ThrowIfZero(definitionID);
@@ -335,7 +335,7 @@ public sealed class BotController : ArchiController {
return BadRequest(new GenericResponse(false, Strings.FormatBotNotFound(botNames)));
}
IList<EResult> results = await Utilities.InParallel(bots.Select(bot => bot.Actions.RedeemPoints(definitionID))).ConfigureAwait(false);
IList<EResult> results = await Utilities.InParallel(bots.Select(bot => bot.Actions.RedeemPoints(definitionID, forced))).ConfigureAwait(false);
Dictionary<string, EResult> result = new(bots.Count, Bot.BotsComparer);

View File

@@ -386,6 +386,58 @@ public sealed class ArchiHandler : ClientMsgHandler {
return response.Result == EResult.OK ? response.Body.summary?.points : null;
}
[PublicAPI]
public async Task<Dictionary<uint, LoyaltyRewardDefinition>?> GetRewardItems(IReadOnlyCollection<uint> definitionIDs) {
if ((definitionIDs == null) || (definitionIDs.Count == 0)) {
throw new ArgumentNullException(nameof(definitionIDs));
}
if (Client == null) {
throw new InvalidOperationException(nameof(Client));
}
if (!Client.IsConnected) {
return null;
}
CLoyaltyRewards_QueryRewardItems_Request request = new();
request.definitionids.AddRange(definitionIDs is IReadOnlySet<uint> or ISet<uint> ? definitionIDs : definitionIDs.Distinct());
Dictionary<uint, LoyaltyRewardDefinition>? result = null;
while (true) {
SteamUnifiedMessages.ServiceMethodResponse<CLoyaltyRewards_QueryRewardItems_Response> response;
try {
response = await UnifiedLoyaltyRewards.QueryRewardItems(request).ToLongRunningTask().ConfigureAwait(false);
} catch (Exception e) {
ArchiLogger.LogGenericWarningException(e);
return null;
}
if (response.Result != EResult.OK) {
return null;
}
result ??= new Dictionary<uint, LoyaltyRewardDefinition>(response.Body.total_count);
bool added = false;
foreach (LoyaltyRewardDefinition _ in response.Body.definitions.Where(entry => result.TryAdd(entry.defid, entry))) {
added = true;
}
// Normally it should be enough to compare counts exclusively, but we're going to use additional bulletproofing against infinite loops just in case
if (!added || (result.Count >= response.Body.total_count) || string.IsNullOrEmpty(response.Body.next_cursor) || (request.cursor == response.Body.next_cursor)) {
return result;
}
request.cursor = response.Body.next_cursor;
}
}
[PublicAPI]
public async Task<CCredentials_GetSteamGuardDetails_Response?> GetSteamGuardStatus() {
if (Client == null) {

View File

@@ -42,6 +42,7 @@ using ArchiSteamFarm.Web;
using JetBrains.Annotations;
using SteamKit2;
using SteamKit2.Internal;
using SteamKit2.WebUI.Internal;
namespace ArchiSteamFarm.Steam.Interaction;
@@ -171,6 +172,15 @@ public sealed class Actions : IAsyncDisposable, IDisposable {
return (steamOwnerID > 0) && new SteamID(steamOwnerID).IsIndividualAccount ? steamOwnerID : 0;
}
[PublicAPI]
public async Task<Dictionary<uint, LoyaltyRewardDefinition>?> GetRewardItems(IReadOnlyCollection<uint> definitionIDs) {
if ((definitionIDs == null) || (definitionIDs.Count == 0)) {
throw new ArgumentNullException(nameof(definitionIDs));
}
return await Bot.ArchiHandler.GetRewardItems(definitionIDs).ConfigureAwait(false);
}
[MustDisposeResource]
[PublicAPI]
public async Task<IDisposable> GetTradingLock() {
@@ -314,9 +324,25 @@ public sealed class Actions : IAsyncDisposable, IDisposable {
}
[PublicAPI]
public async Task<EResult> RedeemPoints(uint definitionID) {
public async Task<EResult> RedeemPoints(uint definitionID, bool forced = false) {
ArgumentOutOfRangeException.ThrowIfZero(definitionID);
if (!forced) {
Dictionary<uint, LoyaltyRewardDefinition>? definitions = await Bot.Actions.GetRewardItems(new HashSet<uint>(1) { definitionID }).ConfigureAwait(false);
if (definitions == null) {
return EResult.Timeout;
}
if (!definitions.TryGetValue(definitionID, out LoyaltyRewardDefinition? definition)) {
return EResult.InvalidParam;
}
if (definition.point_cost > 0) {
return EResult.InvalidState;
}
}
return await Bot.ArchiHandler.RedeemPoints(definitionID).ConfigureAwait(false);
}

View File

@@ -41,6 +41,7 @@ using ArchiSteamFarm.Storage;
using JetBrains.Annotations;
using SteamKit2;
using SteamKit2.Internal;
using SteamKit2.WebUI.Internal;
namespace ArchiSteamFarm.Steam.Interaction;
@@ -1028,6 +1029,10 @@ public sealed class Commands {
ArgumentOutOfRangeException.ThrowIfZero(contextID);
ArgumentNullException.ThrowIfNull(targetBot);
if ((assetRarities == null) || (assetRarities.Count == 0)) {
throw new ArgumentNullException(nameof(assetRarities));
}
if (access < EAccess.Master) {
return null;
}
@@ -2767,7 +2772,7 @@ public sealed class Commands {
return responses.Count > 0 ? string.Join(Environment.NewLine, responses) : null;
}
private async Task<string?> ResponseRedeemPoints(EAccess access, HashSet<uint> definitionIDs) {
private async Task<string?> ResponseRedeemPoints(EAccess access, Dictionary<uint, bool> definitionIDs) {
if (!Enum.IsDefined(access)) {
throw new InvalidEnumArgumentException(nameof(access), (int) access, typeof(EAccess));
}
@@ -2784,13 +2789,34 @@ public sealed class Commands {
return FormatBotResponse(Strings.BotNotConnected);
}
IList<EResult> results = await Utilities.InParallel(definitionIDs.Select(Bot.Actions.RedeemPoints)).ConfigureAwait(false);
HashSet<uint> definitionIDsToCheck = definitionIDs.Where(static entry => !entry.Value).Select(static entry => entry.Key).ToHashSet();
if (definitionIDsToCheck.Count > 0) {
Dictionary<uint, LoyaltyRewardDefinition>? definitions = await Bot.Actions.GetRewardItems(definitionIDsToCheck).ConfigureAwait(false);
if (definitions == null) {
return FormatBotResponse(Strings.FormatWarningFailedWithError(nameof(Bot.Actions.GetRewardItems)));
}
foreach (uint definitionID in definitionIDsToCheck) {
if (!definitions.TryGetValue(definitionID, out LoyaltyRewardDefinition? definition)) {
return FormatBotResponse(Strings.FormatWarningFailedWithError(definitionID));
}
if (definition.point_cost > 0) {
return FormatBotResponse(Strings.FormatWarningFailedWithError($"{definitionID} {nameof(definition.point_cost)} ({definition.point_cost}) > 0"));
}
}
}
// We already did more optimized check, therefore we can skip the one in actions
IList<EResult> results = await Utilities.InParallel(definitionIDs.Keys.Select(definitionID => Bot.Actions.RedeemPoints(definitionID, true))).ConfigureAwait(false);
int i = 0;
StringBuilder response = new();
foreach (uint definitionID in definitionIDs) {
foreach (uint definitionID in definitionIDs.Keys) {
response.AppendLine(FormatBotResponse(Strings.FormatBotAddLicense(definitionID, results[i++])));
}
@@ -2818,14 +2844,22 @@ public sealed class Commands {
return FormatBotResponse(Strings.FormatErrorIsEmpty(nameof(definitions)));
}
HashSet<uint> definitionIDs = new(definitions.Length);
Dictionary<uint, bool> definitionIDs = new(definitions.Length);
foreach (string definition in definitions) {
if (!uint.TryParse(definition, out uint definitionID) || (definitionID == 0)) {
bool forced = false;
string definitionToParse = definition;
if (definitionToParse.EndsWith('!')) {
forced = true;
definitionToParse = definitionToParse[..^1];
}
if (!uint.TryParse(definitionToParse, out uint definitionID) || (definitionID == 0)) {
return FormatBotResponse(Strings.FormatErrorIsInvalid(nameof(definition)));
}
definitionIDs.Add(definitionID);
definitionIDs[definitionID] = forced;
}
return await ResponseRedeemPoints(access, definitionIDs).ConfigureAwait(false);

View File

@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<Version>6.0.8.8</Version>
<Version>6.0.9.0</Version>
</PropertyGroup>
<PropertyGroup>