From da2fd37aa178568987bb243620ab6e134ad4b157 Mon Sep 17 00:00:00 2001 From: Archi Date: Tue, 5 Dec 2023 01:43:55 +0100 Subject: [PATCH] Make STD work also in on-demand mode --- .../GlobalCache.cs | 6 +- .../SteamTokenDumperPlugin.cs | 184 ++++++++++-------- 2 files changed, 109 insertions(+), 81 deletions(-) diff --git a/ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/GlobalCache.cs b/ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/GlobalCache.cs index 57359d34f..f038f2dc4 100644 --- a/ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/GlobalCache.cs +++ b/ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/GlobalCache.cs @@ -89,15 +89,15 @@ internal sealed class GlobalCache : SerializableFile { internal ulong GetAppToken(uint appID) => AppTokens[appID]; - internal Dictionary GetAppTokensForSubmission() => AppTokens.Where(appToken => (SteamTokenDumperPlugin.Config?.SecretAppIDs.Contains(appToken.Key) == false) && (appToken.Value > 0) && (!SubmittedApps.TryGetValue(appToken.Key, out ulong token) || (appToken.Value != token))).ToDictionary(static appToken => appToken.Key, static appToken => appToken.Value); - internal Dictionary GetDepotKeysForSubmission() => DepotKeys.Where(depotKey => (SteamTokenDumperPlugin.Config?.SecretDepotIDs.Contains(depotKey.Key) == false) && !string.IsNullOrEmpty(depotKey.Value) && (!SubmittedDepots.TryGetValue(depotKey.Key, out string? key) || (depotKey.Value != key))).ToDictionary(static depotKey => depotKey.Key, static depotKey => depotKey.Value); + internal Dictionary GetAppTokensForSubmission() => AppTokens.Where(appToken => (SteamTokenDumperPlugin.Config?.SecretAppIDs.Contains(appToken.Key) != true) && (appToken.Value > 0) && (!SubmittedApps.TryGetValue(appToken.Key, out ulong token) || (appToken.Value != token))).ToDictionary(static appToken => appToken.Key, static appToken => appToken.Value); + internal Dictionary GetDepotKeysForSubmission() => DepotKeys.Where(depotKey => (SteamTokenDumperPlugin.Config?.SecretDepotIDs.Contains(depotKey.Key) != true) && !string.IsNullOrEmpty(depotKey.Value) && (!SubmittedDepots.TryGetValue(depotKey.Key, out string? key) || (depotKey.Value != key))).ToDictionary(static depotKey => depotKey.Key, static depotKey => depotKey.Value); internal Dictionary GetPackageTokensForSubmission() { if (ASF.GlobalDatabase == null) { throw new InvalidOperationException(nameof(ASF.GlobalDatabase)); } - return ASF.GlobalDatabase.PackageAccessTokensReadOnly.Where(packageToken => (SteamTokenDumperPlugin.Config?.SecretPackageIDs.Contains(packageToken.Key) == false) && (packageToken.Value > 0) && (!SubmittedPackages.TryGetValue(packageToken.Key, out ulong token) || (packageToken.Value != token))).ToDictionary(static packageToken => packageToken.Key, static packageToken => packageToken.Value); + return ASF.GlobalDatabase.PackageAccessTokensReadOnly.Where(packageToken => (SteamTokenDumperPlugin.Config?.SecretPackageIDs.Contains(packageToken.Key) != true) && (packageToken.Value > 0) && (!SubmittedPackages.TryGetValue(packageToken.Key, out ulong token) || (packageToken.Value != token))).ToDictionary(static packageToken => packageToken.Key, static packageToken => packageToken.Value); } internal static async Task Load() { diff --git a/ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/SteamTokenDumperPlugin.cs b/ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/SteamTokenDumperPlugin.cs index 878dc4f2c..49085287a 100644 --- a/ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/SteamTokenDumperPlugin.cs +++ b/ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/SteamTokenDumperPlugin.cs @@ -37,6 +37,7 @@ using ArchiSteamFarm.OfficialPlugins.SteamTokenDumper.Localization; using ArchiSteamFarm.Plugins; using ArchiSteamFarm.Plugins.Interfaces; using ArchiSteamFarm.Steam; +using ArchiSteamFarm.Steam.Interaction; using ArchiSteamFarm.Storage; using ArchiSteamFarm.Web; using ArchiSteamFarm.Web.Responses; @@ -67,7 +68,7 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotC [JsonProperty] public override Version Version => typeof(SteamTokenDumperPlugin).Assembly.GetName().Version ?? throw new InvalidOperationException(nameof(Version)); - public Task GetPreferredChangeNumberToStartFrom() => Task.FromResult(Config?.Enabled == true ? GlobalCache?.LastChangeNumber ?? 0 : 0); + public Task GetPreferredChangeNumberToStartFrom() => Task.FromResult(GlobalCache?.LastChangeNumber ?? 0); public async Task OnASFInit(IReadOnlyDictionary? additionalConfigProperties = null) { if (!SharedInfo.HasValidToken) { @@ -101,6 +102,24 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotC } } + if (GlobalCache == null) { + GlobalCache? globalCache = await GlobalCache.Load().ConfigureAwait(false); + + if (globalCache == null) { + ASF.ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.FileCouldNotBeLoadedFreshInit, nameof(GlobalCache))); + + GlobalCache = new GlobalCache(); + } else { + GlobalCache = globalCache; + } + } + + if (!isEnabled && (config == null)) { + ASF.ArchiLogger.LogGenericInfo(string.Format(CultureInfo.CurrentCulture, Strings.PluginDisabledInConfig, nameof(SteamTokenDumperPlugin))); + + return; + } + config ??= new SteamTokenDumperConfig(); if (isEnabled) { @@ -109,8 +128,6 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotC if (!config.Enabled) { ASF.ArchiLogger.LogGenericInfo(string.Format(CultureInfo.CurrentCulture, Strings.PluginDisabledInConfig, nameof(SteamTokenDumperPlugin))); - - return; } if (!config.SecretAppIDs.IsEmpty) { @@ -125,20 +142,12 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotC ASF.ArchiLogger.LogGenericInfo(string.Format(CultureInfo.CurrentCulture, Strings.PluginSecretListInitialized, nameof(config.SecretDepotIDs), string.Join(", ", config.SecretDepotIDs))); } - if (GlobalCache == null) { - GlobalCache? globalCache = await GlobalCache.Load().ConfigureAwait(false); - - if (globalCache == null) { - ASF.ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.FileCouldNotBeLoadedFreshInit, nameof(GlobalCache))); - - GlobalCache = new GlobalCache(); - } else { - GlobalCache = globalCache; - } - } - Config = config; + if (!config.Enabled) { + return; + } + #pragma warning disable CA5394 // This call isn't used in a security-sensitive manner TimeSpan startIn = TimeSpan.FromMinutes(Random.Shared.Next(SharedInfo.MinimumMinutesBeforeFirstUpload, SharedInfo.MaximumMinutesBeforeFirstUpload)); #pragma warning restore CA5394 // This call isn't used in a security-sensitive manner @@ -151,7 +160,7 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotC ASF.ArchiLogger.LogGenericInfo(string.Format(CultureInfo.CurrentCulture, Strings.PluginInitializedAndEnabled, nameof(SteamTokenDumperPlugin), startIn.ToHumanReadable())); } - public Task OnBotCommand(Bot bot, EAccess access, string message, string[] args, ulong steamID = 0) { + public async Task OnBotCommand(Bot bot, EAccess access, string message, string[] args, ulong steamID = 0) { ArgumentNullException.ThrowIfNull(bot); if (!Enum.IsDefined(access)) { @@ -162,29 +171,24 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotC throw new ArgumentNullException(nameof(args)); } - switch (args[0].ToUpperInvariant()) { - case "STD" when access >= EAccess.Owner: - if (Config is not { Enabled: true }) { - return Task.FromResult((string?) string.Format(CultureInfo.CurrentCulture, ArchiSteamFarm.Localization.Strings.WarningFailedWithError, nameof(Config))); + switch (args.Length) { + case 1: + switch (args[0].ToUpperInvariant()) { + case "STD": + return await ResponseRefreshManually(access, bot).ConfigureAwait(false); } - TimeSpan minimumTimeBetweenUpload = TimeSpan.FromMinutes(SharedInfo.MinimumMinutesBetweenUploads); - - if (LastUploadAt + minimumTimeBetweenUpload > DateTimeOffset.UtcNow) { - return Task.FromResult((string?) string.Format(CultureInfo.CurrentCulture, Strings.SubmissionFailedTooManyRequests, minimumTimeBetweenUpload.ToHumanReadable())); - } - - // ReSharper disable once SuspiciousLockOverSynchronizationPrimitive - this is not a mistake, we need extra synchronization, and we can re-use the semaphore object for that - lock (SubmissionSemaphore) { - SubmissionTimer.Change(TimeSpan.Zero, TimeSpan.FromHours(SharedInfo.HoursBetweenUploads)); - } - - return Task.FromResult((string?) ArchiSteamFarm.Localization.Strings.Done); - case "STD" when access > EAccess.None: - return Task.FromResult((string?) ArchiSteamFarm.Localization.Strings.ErrorAccessDenied); + break; default: - return Task.FromResult((string?) null); + switch (args[0].ToUpperInvariant()) { + case "STD": + return await ResponseRefreshManually(access, Utilities.GetArgsAsText(args, 1, ","), steamID).ConfigureAwait(false); + } + + break; } + + return null; } public async Task OnBotDestroy(Bot bot) { @@ -207,10 +211,6 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotC public async Task OnBotInit(Bot bot) { ArgumentNullException.ThrowIfNull(bot); - if (Config is not { Enabled: true }) { - return; - } - SemaphoreSlim refreshSemaphore = new(1, 1); Timer refreshTimer = new(OnBotRefreshTimer, bot, Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan); @@ -255,15 +255,7 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotC ArgumentNullException.ThrowIfNull(appChanges); ArgumentNullException.ThrowIfNull(packageChanges); - if (Config is not { Enabled: true }) { - return Task.CompletedTask; - } - - if (GlobalCache == null) { - throw new InvalidOperationException(nameof(GlobalCache)); - } - - GlobalCache.OnPICSChanges(currentChangeNumber, appChanges); + GlobalCache?.OnPICSChanges(currentChangeNumber, appChanges); return Task.CompletedTask; } @@ -271,15 +263,7 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotC public Task OnPICSChangesRestart(uint currentChangeNumber) { ArgumentOutOfRangeException.ThrowIfZero(currentChangeNumber); - if (Config is not { Enabled: true }) { - return Task.CompletedTask; - } - - if (GlobalCache == null) { - throw new InvalidOperationException(nameof(GlobalCache)); - } - - GlobalCache.OnPICSChangesRestart(currentChangeNumber); + GlobalCache?.OnPICSChangesRestart(currentChangeNumber); return Task.CompletedTask; } @@ -300,10 +284,6 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotC return; } - if (GlobalCache == null) { - throw new InvalidOperationException(nameof(GlobalCache)); - } - HashSet packageIDs = callback.LicenseList.Where(static license => !Config.SecretPackageIDs.Contains(license.PackageID) && ((license.PaymentMethod != EPaymentMethod.AutoGrant) || !Config.SkipAutoGrantPackages)).Select(static license => license.PackageID).ToHashSet(); await Refresh(bot, packageIDs).ConfigureAwait(false); @@ -314,16 +294,12 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotC private static async Task Refresh(Bot bot, IReadOnlyCollection? packageIDs = null) { ArgumentNullException.ThrowIfNull(bot); - if (Config is not { Enabled: true }) { - return; - } - if (GlobalCache == null) { throw new InvalidOperationException(nameof(GlobalCache)); } if (ASF.GlobalDatabase == null) { - throw new InvalidOperationException(nameof(GlobalCache)); + throw new InvalidOperationException(nameof(ASF.GlobalDatabase)); } if (!BotSynchronizations.TryGetValue(bot, out (SemaphoreSlim RefreshSemaphore, Timer RefreshTimer) synchronization)) { @@ -341,17 +317,17 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotC return; } - packageIDs ??= bot.OwnedPackageIDs.Where(static package => !Config.SecretPackageIDs.Contains(package.Key) && ((package.Value.PaymentMethod != EPaymentMethod.AutoGrant) || !Config.SkipAutoGrantPackages)).Select(static package => package.Key).ToHashSet(); + packageIDs ??= bot.OwnedPackageIDs.Where(static package => (Config?.SecretPackageIDs.Contains(package.Key) != true) && ((package.Value.PaymentMethod != EPaymentMethod.AutoGrant) || (Config?.SkipAutoGrantPackages == false))).Select(static package => package.Key).ToHashSet(); HashSet appIDsToRefresh = new(); - foreach (uint packageID in packageIDs.Where(static packageID => !Config.SecretPackageIDs.Contains(packageID))) { + foreach (uint packageID in packageIDs.Where(static packageID => Config?.SecretPackageIDs.Contains(packageID) != true)) { if (!ASF.GlobalDatabase.PackagesDataReadOnly.TryGetValue(packageID, out PackageData? packageData) || (packageData.AppIDs == null)) { // ASF might not have the package info for us at the moment, we'll retry later continue; } - appIDsToRefresh.UnionWith(packageData.AppIDs.Where(static appID => !Config.SecretAppIDs.Contains(appID) && GlobalCache.ShouldRefreshAppInfo(appID))); + appIDsToRefresh.UnionWith(packageData.AppIDs.Where(static appID => (Config?.SecretAppIDs.Contains(appID) != true) && GlobalCache.ShouldRefreshAppInfo(appID))); } if (appIDsToRefresh.Count == 0) { @@ -456,7 +432,7 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotC bool shouldFetchMainKey = false; foreach (KeyValue depot in app.KeyValues["depots"].Children) { - if (!uint.TryParse(depot.Name, out uint depotID) || (knownDepotIDs?.Contains(depotID) == true) || Config.SecretDepotIDs.Contains(depotID) || !GlobalCache.ShouldRefreshDepotKey(depotID)) { + if (!uint.TryParse(depot.Name, out uint depotID) || (knownDepotIDs?.Contains(depotID) == true) || (Config?.SecretDepotIDs.Contains(depotID) == true) || !GlobalCache.ShouldRefreshDepotKey(depotID)) { continue; } @@ -532,9 +508,11 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotC bot.ArchiLogger.LogGenericInfo(string.Format(CultureInfo.CurrentCulture, Strings.BotFinishedRetrievingTotalDepots, appIDsToRefresh.Count)); } finally { - TimeSpan timeSpan = TimeSpan.FromHours(SharedInfo.MaximumHoursBetweenRefresh); + if (Config?.Enabled == true) { + TimeSpan timeSpan = TimeSpan.FromHours(SharedInfo.MaximumHoursBetweenRefresh); - synchronization.RefreshTimer.Change(timeSpan, timeSpan); + synchronization.RefreshTimer.Change(timeSpan, timeSpan); + } await depotsRateLimitingSemaphore.WaitAsync().ConfigureAwait(false); @@ -544,15 +522,65 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotC } } + private static async Task ResponseRefreshManually(EAccess access, Bot bot, bool submit = true) { + if (!Enum.IsDefined(access)) { + throw new InvalidEnumArgumentException(nameof(access), (int) access, typeof(EAccess)); + } + + ArgumentNullException.ThrowIfNull(bot); + + if (access < EAccess.Master) { + return access > EAccess.None ? bot.Commands.FormatBotResponse(ArchiSteamFarm.Localization.Strings.ErrorAccessDenied) : null; + } + + if (GlobalCache == null) { + return bot.Commands.FormatBotResponse(string.Format(CultureInfo.CurrentCulture, ArchiSteamFarm.Localization.Strings.WarningFailedWithError, nameof(GlobalCache))); + } + + await Refresh(bot).ConfigureAwait(false); + + if (submit) { + Utilities.InBackground(static () => SubmitData()); + } + + return bot.Commands.FormatBotResponse(ArchiSteamFarm.Localization.Strings.Done); + } + + private static async Task ResponseRefreshManually(EAccess access, string botNames, ulong steamID = 0) { + if (!Enum.IsDefined(access)) { + throw new InvalidEnumArgumentException(nameof(access), (int) access, typeof(EAccess)); + } + + ArgumentException.ThrowIfNullOrEmpty(botNames); + + if ((steamID != 0) && !new SteamID(steamID).IsIndividualAccount) { + throw new ArgumentOutOfRangeException(nameof(steamID)); + } + + HashSet? bots = Bot.GetBots(botNames); + + if ((bots == null) || (bots.Count == 0)) { + return access >= EAccess.Owner ? Commands.FormatStaticResponse(string.Format(CultureInfo.CurrentCulture, ArchiSteamFarm.Localization.Strings.BotNotFound, botNames)) : null; + } + + if (GlobalCache == null) { + return Commands.FormatStaticResponse(string.Format(CultureInfo.CurrentCulture, ArchiSteamFarm.Localization.Strings.WarningFailedWithError, nameof(GlobalCache))); + } + + IList results = await Utilities.InParallel(bots.Select(bot => ResponseRefreshManually(Commands.GetProxyAccess(bot, access, steamID), bot, false))).ConfigureAwait(false); + + Utilities.InBackground(static () => SubmitData()); + + List responses = new(results.Where(static result => !string.IsNullOrEmpty(result))!); + + return responses.Count > 0 ? string.Join(Environment.NewLine, responses) : null; + } + private static async Task SubmitData(CancellationToken cancellationToken = default) { if (Bot.Bots == null) { throw new InvalidOperationException(nameof(Bot.Bots)); } - if (Config is not { Enabled: true }) { - return; - } - if (GlobalCache == null) { throw new InvalidOperationException(nameof(GlobalCache)); } @@ -608,7 +636,7 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotC ASF.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, ArchiSteamFarm.Localization.Strings.WarningFailedWithError, response.StatusCode)); switch (response.StatusCode) { - case HttpStatusCode.Forbidden: + case HttpStatusCode.Forbidden when Config?.Enabled == true: // SteamDB told us to stop submitting data for now // ReSharper disable once SuspiciousLockOverSynchronizationPrimitive - this is not a mistake, we need extra synchronization, and we can re-use the semaphore object for that lock (SubmissionSemaphore) { @@ -621,7 +649,7 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotC GlobalCache.Reset(true); break; - case HttpStatusCode.TooManyRequests: + case HttpStatusCode.TooManyRequests when Config?.Enabled == true: // SteamDB told us to try again later #pragma warning disable CA5394 // This call isn't used in a security-sensitive manner TimeSpan startIn = TimeSpan.FromMinutes(Random.Shared.Next(SharedInfo.MinimumMinutesBeforeFirstUpload, SharedInfo.MaximumMinutesBeforeFirstUpload));