mirror of
https://github.com/JustArchiNET/ArchiSteamFarm.git
synced 2025-12-16 22:40:30 +00:00
* Closes #3415 * Misc * Refresh tokens always for non-listed packages
This commit is contained in:
committed by
GitHub
parent
a19611c3ae
commit
10abfb847f
@@ -25,7 +25,6 @@ namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper;
|
||||
|
||||
internal static class SharedInfo {
|
||||
internal const byte ApiVersion = 2;
|
||||
internal const byte AppInfosPerSingleRequest = byte.MaxValue;
|
||||
internal const byte HoursBetweenUploads = 24;
|
||||
internal const byte MaximumHoursBetweenRefresh = 8; // Per single bot account, makes sense to be 2 or 3 times less than MinimumHoursBetweenUploads
|
||||
internal const byte MaximumMinutesBeforeFirstUpload = 60; // Must be greater or equal to MinimumMinutesBeforeFirstUpload
|
||||
|
||||
@@ -360,7 +360,7 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotC
|
||||
|
||||
bot.ArchiLogger.LogGenericInfo(Strings.FormatBotRetrievingTotalAppAccessTokens(appIDsToRefresh.Count));
|
||||
|
||||
HashSet<uint> appIDsThisRound = new(Math.Min(appIDsToRefresh.Count, SharedInfo.AppInfosPerSingleRequest));
|
||||
HashSet<uint> appIDsThisRound = new(Math.Min(appIDsToRefresh.Count, Bot.EntriesPerSinglePICSRequest));
|
||||
|
||||
using (HashSet<uint>.Enumerator enumerator = appIDsToRefresh.GetEnumerator()) {
|
||||
while (true) {
|
||||
@@ -368,7 +368,7 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotC
|
||||
return;
|
||||
}
|
||||
|
||||
while ((appIDsThisRound.Count < SharedInfo.AppInfosPerSingleRequest) && enumerator.MoveNext()) {
|
||||
while ((appIDsThisRound.Count < Bot.EntriesPerSinglePICSRequest) && enumerator.MoveNext()) {
|
||||
appIDsThisRound.Add(enumerator.Current);
|
||||
}
|
||||
|
||||
@@ -409,7 +409,7 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotC
|
||||
return;
|
||||
}
|
||||
|
||||
while ((appIDsThisRound.Count < SharedInfo.AppInfosPerSingleRequest) && enumerator.MoveNext()) {
|
||||
while ((appIDsThisRound.Count < Bot.EntriesPerSinglePICSRequest) && enumerator.MoveNext()) {
|
||||
appIDsThisRound.Add(enumerator.Current);
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ using System.Collections.Immutable;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@@ -65,9 +66,11 @@ namespace ArchiSteamFarm.Steam;
|
||||
|
||||
public sealed class Bot : IAsyncDisposable, IDisposable {
|
||||
internal const ushort CallbackSleep = 500; // In milliseconds
|
||||
internal const byte EntriesPerSinglePICSRequest = byte.MaxValue;
|
||||
internal const byte MinCardsPerBadge = 5;
|
||||
|
||||
private const char DefaultBackgroundKeysRedeemerSeparator = '\t';
|
||||
private const byte ExtraStorePackagesValidForDays = 7;
|
||||
private const byte LoginCooldownInMinutes = 25; // Captcha disappears after around 20 minutes, so we make it 25
|
||||
private const uint LoginID = 1242; // This must be the same for all ASF bots and all ASF processes
|
||||
private const byte MaxLoginFailures = WebBrowser.MaxTries; // Max login failures in a row before we determine that our credentials are invalid (because Steam wrongly returns those, of course)course)
|
||||
@@ -2100,6 +2103,24 @@ public sealed class Bot : IAsyncDisposable, IDisposable {
|
||||
UnpackBoosterPacksSemaphore.Dispose();
|
||||
}
|
||||
|
||||
private async Task ExtendWithStoreData([SuppressMessage("ReSharper", "SuggestBaseTypeForParameter")] Dictionary<uint, LicenseData> ownedPackages, HashSet<uint> allPackages, Dictionary<uint, uint> packagesToRefresh) {
|
||||
ArgumentNullException.ThrowIfNull(ownedPackages);
|
||||
ArgumentNullException.ThrowIfNull(allPackages);
|
||||
ArgumentNullException.ThrowIfNull(packagesToRefresh);
|
||||
|
||||
if (BotDatabase.ExtraStorePackagesRefreshedAt.AddDays(ExtraStorePackagesValidForDays) < DateTime.UtcNow) {
|
||||
await RefreshStoreData(allPackages, packagesToRefresh).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
foreach (uint packageID in BotDatabase.ExtraStorePackages) {
|
||||
ownedPackages[packageID] = new LicenseData {
|
||||
PackageID = packageID,
|
||||
PaymentMethod = EPaymentMethod.None,
|
||||
TimeCreated = DateTime.UnixEpoch
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<Dictionary<string, string>?> GetKeysFromFile(string filePath) {
|
||||
ArgumentException.ThrowIfNullOrEmpty(filePath);
|
||||
|
||||
@@ -3171,15 +3192,22 @@ public sealed class Bot : IAsyncDisposable, IDisposable {
|
||||
|
||||
Commands.OnNewLicenseList();
|
||||
|
||||
Dictionary<uint, LicenseData> ownedPackages = new();
|
||||
Dictionary<uint, LicenseData> ownedPackages = [];
|
||||
HashSet<uint> allPackages = [];
|
||||
|
||||
Dictionary<uint, ulong> packageAccessTokens = new();
|
||||
Dictionary<uint, uint> packagesToRefresh = new();
|
||||
Dictionary<uint, ulong> packageAccessTokens = [];
|
||||
Dictionary<uint, uint> packagesToRefresh = [];
|
||||
|
||||
bool hasNewEntries = false;
|
||||
|
||||
// We want to record only the most relevant entry from non-borrowed games, therefore we also apply ordering here
|
||||
foreach (SteamApps.LicenseListCallback.License license in callback.LicenseList.Where(static license => !license.LicenseFlags.HasFlag(ELicenseFlags.Borrowed)).OrderByDescending(static license => license.TimeCreated).Where(license => !ownedPackages.ContainsKey(license.PackageID))) {
|
||||
foreach (SteamApps.LicenseListCallback.License license in callback.LicenseList.OrderByDescending(static license => license.TimeCreated)) {
|
||||
allPackages.Add(license.PackageID);
|
||||
|
||||
if (license.LicenseFlags.HasFlag(ELicenseFlags.Borrowed) || ownedPackages.ContainsKey(license.PackageID)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ownedPackages[license.PackageID] = new LicenseData {
|
||||
PackageID = license.PackageID,
|
||||
PaymentMethod = license.PaymentMethod,
|
||||
@@ -3200,6 +3228,8 @@ public sealed class Bot : IAsyncDisposable, IDisposable {
|
||||
}
|
||||
}
|
||||
|
||||
await ExtendWithStoreData(ownedPackages, allPackages, packagesToRefresh).ConfigureAwait(false);
|
||||
|
||||
OwnedPackages = ownedPackages.ToFrozenDictionary();
|
||||
|
||||
if (packageAccessTokens.Count > 0) {
|
||||
@@ -3687,6 +3717,45 @@ public sealed class Bot : IAsyncDisposable, IDisposable {
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RefreshStoreData([SuppressMessage("ReSharper", "SuggestBaseTypeForParameter")] HashSet<uint> allPackages, [SuppressMessage("ReSharper", "SuggestBaseTypeForParameter")] Dictionary<uint, uint> packagesToRefresh) {
|
||||
ArgumentNullException.ThrowIfNull(allPackages);
|
||||
ArgumentNullException.ThrowIfNull(packagesToRefresh);
|
||||
|
||||
if (ASF.GlobalDatabase == null) {
|
||||
throw new InvalidOperationException(nameof(ASF.GlobalDatabase));
|
||||
}
|
||||
|
||||
StoreUserData? storeData = await ArchiWebHandler.GetStoreUserData().ConfigureAwait(false);
|
||||
|
||||
if (storeData == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
BotDatabase.ExtraStorePackages.ReplaceWith(storeData.OwnedPackages.Where(packageID => !allPackages.Contains(packageID)));
|
||||
BotDatabase.ExtraStorePackagesRefreshedAt = DateTime.UtcNow;
|
||||
|
||||
foreach (uint[] packageIDs in BotDatabase.ExtraStorePackages.Chunk(EntriesPerSinglePICSRequest)) {
|
||||
try {
|
||||
SteamApps.PICSTokensCallback accessTokens = await SteamApps.PICSGetAccessTokens([], packageIDs);
|
||||
|
||||
if (accessTokens.PackageTokens.Count > 0) {
|
||||
ASF.GlobalDatabase.RefreshPackageAccessTokens(accessTokens.PackageTokens);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
ArchiLogger.LogGenericWarningException(e);
|
||||
}
|
||||
}
|
||||
|
||||
// Wait up to 5 seconds for initialization, we can work with any change number, although non-zero is preferred
|
||||
for (byte i = 0; (i < WebBrowser.MaxTries) && (SteamPICSChanges.LastChangeNumber == 0); i++) {
|
||||
await Task.Delay(1000).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
foreach (uint packageID in BotDatabase.ExtraStorePackages) {
|
||||
packagesToRefresh.Add(packageID, SteamPICSChanges.LastChangeNumber);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ResetGamesPlayed() {
|
||||
if (!IsConnectedAndLoggedOn || CardsFarmer.NowFarming) {
|
||||
return;
|
||||
|
||||
39
ArchiSteamFarm/Steam/Data/StoreUserData.cs
Normal file
39
ArchiSteamFarm/Steam/Data/StoreUserData.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2025 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// |
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
// |
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace ArchiSteamFarm.Steam.Data;
|
||||
|
||||
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
|
||||
internal sealed class StoreUserData {
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("rgOwnedPackages")]
|
||||
[JsonRequired]
|
||||
internal ImmutableHashSet<uint> OwnedPackages { get; private init; } = ImmutableHashSet<uint>.Empty;
|
||||
|
||||
[JsonConstructor]
|
||||
private StoreUserData() { }
|
||||
}
|
||||
@@ -1875,6 +1875,14 @@ public sealed class ArchiWebHandler : IDisposable {
|
||||
return response?.Content;
|
||||
}
|
||||
|
||||
internal async Task<StoreUserData?> GetStoreUserData() {
|
||||
Uri request = new(SteamStoreURL, "/dynamicstore/userdata?l=english");
|
||||
|
||||
ObjectResponse<StoreUserData>? response = await UrlGetToJsonObjectWithSession<StoreUserData>(request).ConfigureAwait(false);
|
||||
|
||||
return response?.Content;
|
||||
}
|
||||
|
||||
internal async Task<byte?> GetTradeHoldDurationForTrade(ulong tradeID) {
|
||||
ArgumentOutOfRangeException.ThrowIfZero(tradeID);
|
||||
|
||||
|
||||
@@ -35,12 +35,12 @@ namespace ArchiSteamFarm.Steam.Integration;
|
||||
internal static class SteamPICSChanges {
|
||||
private const byte RefreshTimerInMinutes = 5;
|
||||
|
||||
internal static uint LastChangeNumber { get; private set; }
|
||||
internal static bool LiveUpdate { get; private set; }
|
||||
|
||||
private static readonly SemaphoreSlim RefreshSemaphore = new(1, 1);
|
||||
private static readonly Timer RefreshTimer = new(RefreshChanges);
|
||||
|
||||
private static uint LastChangeNumber;
|
||||
private static bool TimerAlreadySet;
|
||||
|
||||
internal static void Init(uint changeNumberToStartFrom) => LastChangeNumber = changeNumberToStartFrom;
|
||||
|
||||
@@ -63,6 +63,23 @@ public sealed class BotDatabase : GenericDatabase {
|
||||
}
|
||||
}
|
||||
|
||||
[JsonDisallowNull]
|
||||
[JsonInclude]
|
||||
internal ConcurrentHashSet<uint> ExtraStorePackages { get; private init; } = [];
|
||||
|
||||
internal DateTime ExtraStorePackagesRefreshedAt {
|
||||
get => BackingExtraStorePackagesRefreshedAt;
|
||||
|
||||
set {
|
||||
if (BackingExtraStorePackagesRefreshedAt == value) {
|
||||
return;
|
||||
}
|
||||
|
||||
BackingExtraStorePackagesRefreshedAt = value;
|
||||
Utilities.InBackground(Save);
|
||||
}
|
||||
}
|
||||
|
||||
[JsonDisallowNull]
|
||||
[JsonInclude]
|
||||
internal ConcurrentHashSet<uint> FarmingBlacklistAppIDs { get; private init; } = [];
|
||||
@@ -129,6 +146,9 @@ public sealed class BotDatabase : GenericDatabase {
|
||||
[JsonInclude]
|
||||
private string? BackingAccessToken { get; set; }
|
||||
|
||||
[JsonInclude]
|
||||
private DateTime BackingExtraStorePackagesRefreshedAt { get; set; }
|
||||
|
||||
[JsonInclude]
|
||||
[JsonPropertyName($"_{nameof(MobileAuthenticator)}")]
|
||||
private MobileAuthenticator? BackingMobileAuthenticator { get; set; }
|
||||
@@ -151,6 +171,7 @@ public sealed class BotDatabase : GenericDatabase {
|
||||
|
||||
[JsonConstructor]
|
||||
private BotDatabase() {
|
||||
ExtraStorePackages.OnModified += OnObjectModified;
|
||||
FarmingBlacklistAppIDs.OnModified += OnObjectModified;
|
||||
FarmingPriorityQueueAppIDs.OnModified += OnObjectModified;
|
||||
FarmingRiskyIgnoredAppIDs.OnModified += OnObjectModified;
|
||||
@@ -188,6 +209,9 @@ public sealed class BotDatabase : GenericDatabase {
|
||||
[UsedImplicitly]
|
||||
public bool ShouldSerializeBackingAccessToken() => !string.IsNullOrEmpty(BackingAccessToken);
|
||||
|
||||
[UsedImplicitly]
|
||||
public bool ShouldSerializeBackingExtraStorePackagesRefreshedAt() => BackingExtraStorePackagesRefreshedAt > DateTime.MinValue;
|
||||
|
||||
[UsedImplicitly]
|
||||
public bool ShouldSerializeBackingMobileAuthenticator() => BackingMobileAuthenticator != null;
|
||||
|
||||
@@ -197,6 +221,9 @@ public sealed class BotDatabase : GenericDatabase {
|
||||
[UsedImplicitly]
|
||||
public bool ShouldSerializeBackingSteamGuardData() => !string.IsNullOrEmpty(BackingSteamGuardData);
|
||||
|
||||
[UsedImplicitly]
|
||||
public bool ShouldSerializeExtraStorePackages() => ExtraStorePackages.Count > 0;
|
||||
|
||||
[UsedImplicitly]
|
||||
public bool ShouldSerializeFarmingBlacklistAppIDs() => FarmingBlacklistAppIDs.Count > 0;
|
||||
|
||||
@@ -221,6 +248,7 @@ public sealed class BotDatabase : GenericDatabase {
|
||||
protected override void Dispose(bool disposing) {
|
||||
if (disposing) {
|
||||
// Events we registered
|
||||
ExtraStorePackages.OnModified -= OnObjectModified;
|
||||
FarmingBlacklistAppIDs.OnModified -= OnObjectModified;
|
||||
FarmingPriorityQueueAppIDs.OnModified -= OnObjectModified;
|
||||
FarmingRiskyIgnoredAppIDs.OnModified -= OnObjectModified;
|
||||
|
||||
Reference in New Issue
Block a user