Make STD work also in on-demand mode

This commit is contained in:
Archi 2023-12-05 01:43:55 +01:00
parent 8d1d508fe5
commit da2fd37aa1
No known key found for this signature in database
GPG key ID: 6B138B4C64555AEA
2 changed files with 109 additions and 81 deletions

View file

@ -89,15 +89,15 @@ internal sealed class GlobalCache : SerializableFile {
internal ulong GetAppToken(uint appID) => AppTokens[appID];
internal Dictionary<uint, ulong> 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<uint, string> 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<uint, ulong> 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<uint, string> 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<uint, ulong> 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<GlobalCache?> Load() {

View file

@ -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<uint> GetPreferredChangeNumberToStartFrom() => Task.FromResult(Config?.Enabled == true ? GlobalCache?.LastChangeNumber ?? 0 : 0);
public Task<uint> GetPreferredChangeNumberToStartFrom() => Task.FromResult(GlobalCache?.LastChangeNumber ?? 0);
public async Task OnASFInit(IReadOnlyDictionary<string, JToken>? 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<string?> OnBotCommand(Bot bot, EAccess access, string message, string[] args, ulong steamID = 0) {
public async Task<string?> 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<uint> 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<uint>? 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<uint> 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<string?> 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<string?> 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<Bot>? 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<string?> results = await Utilities.InParallel(bots.Select(bot => ResponseRefreshManually(Commands.GetProxyAccess(bot, access, steamID), bot, false))).ConfigureAwait(false);
Utilities.InBackground(static () => SubmitData());
List<string> 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));