From 7dc3d16666c307954055377f1d3ba08ec0857a15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Domeradzki?= Date: Fri, 20 Dec 2024 14:17:53 +0100 Subject: [PATCH] Properly resolve #3358 --- .../SteamTokenDumperPlugin.cs | 4 +-- ArchiSteamFarm/Steam/Bot.cs | 26 ++++++++++++------- ArchiSteamFarm/Steam/Cards/CardsFarmer.cs | 4 +-- ArchiSteamFarm/Steam/Interaction/Commands.cs | 10 +++---- 4 files changed, 25 insertions(+), 19 deletions(-) diff --git a/ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/SteamTokenDumperPlugin.cs b/ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/SteamTokenDumperPlugin.cs index 04cde147d877c..b0b1ab868c2c7 100644 --- a/ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/SteamTokenDumperPlugin.cs +++ b/ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/SteamTokenDumperPlugin.cs @@ -342,7 +342,7 @@ private static async Task Refresh(Bot bot) { return; } - HashSet 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 packageIDs = bot.OwnedPackages.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 = []; @@ -644,7 +644,7 @@ private static async Task SubmitData(CancellationToken cancellationToken = defau return; } - ulong contributorSteamID = ASF.GlobalConfig is { SteamOwnerID: > 0 } && new SteamID(ASF.GlobalConfig.SteamOwnerID).IsIndividualAccount ? ASF.GlobalConfig.SteamOwnerID : Bot.Bots.Values.Where(static bot => bot.SteamID > 0).MaxBy(static bot => bot.OwnedPackageIDs.Count)?.SteamID ?? 0; + ulong contributorSteamID = ASF.GlobalConfig is { SteamOwnerID: > 0 } && new SteamID(ASF.GlobalConfig.SteamOwnerID).IsIndividualAccount ? ASF.GlobalConfig.SteamOwnerID : Bot.Bots.Values.Where(static bot => bot.SteamID > 0).MaxBy(static bot => bot.OwnedPackages.Count)?.SteamID ?? 0; if (contributorSteamID == 0) { ASF.ArchiLogger.LogGenericError(Strings.FormatSubmissionNoContributorSet(nameof(ASF.GlobalConfig.SteamOwnerID))); diff --git a/ArchiSteamFarm/Steam/Bot.cs b/ArchiSteamFarm/Steam/Bot.cs index 934cbaab1906b..0bcfc01d5d01d 100644 --- a/ArchiSteamFarm/Steam/Bot.cs +++ b/ArchiSteamFarm/Steam/Bot.cs @@ -257,8 +257,13 @@ private set { public string? Nickname { get; private set; } [JsonIgnore] + [Obsolete($"Use {nameof(OwnedPackages)} instead, this property will be removed in the future version")] [PublicAPI] - public FrozenDictionary OwnedPackageIDs { get; private set; } = FrozenDictionary.Empty; + public FrozenDictionary OwnedPackageIDs => OwnedPackages.Values.ToFrozenDictionary(static entry => entry.PackageID, static entry => (entry.PaymentMethod, entry.TimeCreated)); + + [JsonIgnore] + [PublicAPI] + public FrozenDictionary OwnedPackages { get; private set; } = FrozenDictionary.Empty; [JsonInclude] [JsonRequired] @@ -1112,7 +1117,7 @@ internal static string FormatBotResponse(string response, string botName) { ArgumentOutOfRangeException.ThrowIfZero(appID); ArgumentOutOfRangeException.ThrowIfNegative(hoursPlayed); - HashSet? packageIDs = ASF.GlobalDatabase?.GetPackageIDs(appID, OwnedPackageIDs.Keys); + HashSet? packageIDs = ASF.GlobalDatabase?.GetPackageIDs(appID, OwnedPackages.Keys); if ((packageIDs == null) || (packageIDs.Count == 0)) { return (0, DateTime.MaxValue, true); @@ -1122,7 +1127,7 @@ internal static string FormatBotResponse(string response, string botName) { DateTime mostRecent = DateTime.MinValue; foreach (uint packageID in packageIDs) { - if (!OwnedPackageIDs.TryGetValue(packageID, out (EPaymentMethod PaymentMethod, DateTime TimeCreated) packageData)) { + if (!OwnedPackages.TryGetValue(packageID, out SteamApps.LicenseListCallback.License? packageData)) { continue; } @@ -1147,7 +1152,7 @@ internal static string FormatBotResponse(string response, string botName) { DateTime safePlayableBefore = DateTime.UtcNow.AddMonths(-RegionRestrictionPlayableBlockMonths); foreach (uint packageID in packageIDs) { - if (!OwnedPackageIDs.TryGetValue(packageID, out (EPaymentMethod PaymentMethod, DateTime TimeCreated) ownedPackageData)) { + if (!OwnedPackages.TryGetValue(packageID, out SteamApps.LicenseListCallback.License? ownedPackageData)) { // We don't own that packageID, keep checking continue; } @@ -2849,7 +2854,7 @@ private async void OnDisconnected(SteamClient.DisconnectedCallback callback) { Trading.OnDisconnected(); FirstTradeSent = false; - OwnedPackageIDs = FrozenDictionary.Empty; + OwnedPackages = FrozenDictionary.Empty; EResult lastLogOnResult = LastLogOnResult; @@ -3173,17 +3178,18 @@ private async void OnLicenseList(SteamApps.LicenseListCallback callback) { Commands.OnNewLicenseList(); - Dictionary ownedPackageIDs = new(); + Dictionary ownedPackages = new(); Dictionary packageAccessTokens = new(); Dictionary packagesToRefresh = new(); bool hasNewEntries = false; - foreach (SteamApps.LicenseListCallback.License license in callback.LicenseList.GroupBy(static license => license.PackageID, static (_, licenses) => licenses.OrderByDescending(static license => license.TimeCreated).First())) { - ownedPackageIDs[license.PackageID] = (license.PaymentMethod, license.TimeCreated); + // We want to record only the most relevant entry, therefore we apply ordering here so we end up preferably with the most recent non-borrowed entry + foreach (SteamApps.LicenseListCallback.License license in callback.LicenseList.OrderByDescending(static license => license.LicenseFlags.HasFlag(ELicenseFlags.Borrowed)).ThenBy(static license => license.TimeCreated)) { + ownedPackages[license.PackageID] = license; - if (!OwnedPackageIDs.ContainsKey(license.PackageID)) { + if (!OwnedPackages.ContainsKey(license.PackageID)) { hasNewEntries = true; } @@ -3197,7 +3203,7 @@ private async void OnLicenseList(SteamApps.LicenseListCallback callback) { } } - OwnedPackageIDs = ownedPackageIDs.ToFrozenDictionary(); + OwnedPackages = ownedPackages.ToFrozenDictionary(); if (packageAccessTokens.Count > 0) { ASF.GlobalDatabase.RefreshPackageAccessTokens(packageAccessTokens); diff --git a/ArchiSteamFarm/Steam/Cards/CardsFarmer.cs b/ArchiSteamFarm/Steam/Cards/CardsFarmer.cs index 0884091911e33..a57099a5ac980 100644 --- a/ArchiSteamFarm/Steam/Cards/CardsFarmer.cs +++ b/ArchiSteamFarm/Steam/Cards/CardsFarmer.cs @@ -1496,11 +1496,11 @@ private async Task SortGamesToFarm() { foreach (Game game in GamesToFarm) { DateTime redeemDate = DateTime.MinValue; - HashSet? packageIDs = ASF.GlobalDatabase?.GetPackageIDs(game.AppID, Bot.OwnedPackageIDs.Keys); + HashSet? packageIDs = ASF.GlobalDatabase?.GetPackageIDs(game.AppID, Bot.OwnedPackages.Values.Where(static package => !package.LicenseFlags.HasFlag(ELicenseFlags.Borrowed)).Select(static package => package.PackageID)); if (packageIDs != null) { foreach (uint packageID in packageIDs) { - if (!Bot.OwnedPackageIDs.TryGetValue(packageID, out (EPaymentMethod PaymentMethod, DateTime TimeCreated) packageData)) { + if (!Bot.OwnedPackages.TryGetValue(packageID, out SteamApps.LicenseListCallback.License? packageData)) { Bot.ArchiLogger.LogNullError(packageData); return; diff --git a/ArchiSteamFarm/Steam/Interaction/Commands.cs b/ArchiSteamFarm/Steam/Interaction/Commands.cs index f293106f82231..47d2aa4d7fc67 100644 --- a/ArchiSteamFarm/Steam/Interaction/Commands.cs +++ b/ArchiSteamFarm/Steam/Interaction/Commands.cs @@ -666,7 +666,7 @@ internal void OnNewLicenseList() { switch (type.ToUpperInvariant()) { case "A" or "APP": { - HashSet? packageIDs = ASF.GlobalDatabase?.GetPackageIDs(gameID, Bot.OwnedPackageIDs.Keys, 1); + HashSet? packageIDs = ASF.GlobalDatabase?.GetPackageIDs(gameID, Bot.OwnedPackages.Values.Where(static package => !package.LicenseFlags.HasFlag(ELicenseFlags.Borrowed)).Select(static package => package.PackageID), 1); if (packageIDs is { Count: > 0 }) { response.AppendLine(FormatBotResponse(Strings.FormatBotAddLicense($"app/{gameID}", $"{EResult.Fail}/{EPurchaseResultDetail.AlreadyPurchased}"))); @@ -690,7 +690,7 @@ internal void OnNewLicenseList() { break; } default: { - if (Bot.OwnedPackageIDs.ContainsKey(gameID)) { + if (Bot.OwnedPackages.TryGetValue(gameID, out SteamApps.LicenseListCallback.License? package) && !package.LicenseFlags.HasFlag(ELicenseFlags.Borrowed)) { response.AppendLine(FormatBotResponse(Strings.FormatBotAddLicense($"sub/{gameID}", $"{EResult.Fail}/{EPurchaseResultDetail.AlreadyPurchased}"))); break; @@ -2020,7 +2020,7 @@ internal void OnNewLicenseList() { switch (type.ToUpperInvariant()) { case "A" or "APP" when uint.TryParse(game, out uint appID) && (appID > 0): - HashSet? packageIDs = ASF.GlobalDatabase?.GetPackageIDs(appID, Bot.OwnedPackageIDs.Keys, 1); + HashSet? packageIDs = ASF.GlobalDatabase?.GetPackageIDs(appID, Bot.OwnedPackages.Values.Where(static package => !package.LicenseFlags.HasFlag(ELicenseFlags.Borrowed)).Select(static package => package.PackageID), 1); if (packageIDs?.Count > 0) { if ((gamesOwned != null) && gamesOwned.TryGetValue(appID, out string? cachedGameName)) { @@ -2087,7 +2087,7 @@ internal void OnNewLicenseList() { continue; case "S" or "SUB" when uint.TryParse(game, out uint packageID) && (packageID > 0): - if (Bot.OwnedPackageIDs.ContainsKey(packageID)) { + if (Bot.OwnedPackages.TryGetValue(packageID, out SteamApps.LicenseListCallback.License? package) && !package.LicenseFlags.HasFlag(ELicenseFlags.Borrowed)) { result[$"sub/{packageID}"] = packageID.ToString(CultureInfo.InvariantCulture); response.AppendLine(FormatBotResponse(Strings.FormatBotOwnedAlready($"sub/{packageID}"))); } else { @@ -2658,7 +2658,7 @@ internal void OnNewLicenseList() { bool alreadyHandled = false; - foreach (Bot innerBot in Bot.Bots.Where(bot => (bot.Value != currentBot) && (!redeemFlags.HasFlag(ERedeemFlags.SkipInitial) || (bot.Value != Bot)) && !triedBots.Contains(bot.Value) && !rateLimitedBots.Contains(bot.Value) && bot.Value.IsConnectedAndLoggedOn && ((access >= EAccess.Owner) || ((steamID != 0) && (bot.Value.GetAccess(steamID) >= EAccess.Operator))) && ((items.Count == 0) || items.Keys.Any(packageID => !bot.Value.OwnedPackageIDs.ContainsKey(packageID)))).OrderBy(static bot => bot.Key, Bot.BotsComparer).Select(static bot => bot.Value)) { + foreach (Bot innerBot in Bot.Bots.Where(bot => (bot.Value != currentBot) && (!redeemFlags.HasFlag(ERedeemFlags.SkipInitial) || (bot.Value != Bot)) && !triedBots.Contains(bot.Value) && !rateLimitedBots.Contains(bot.Value) && bot.Value.IsConnectedAndLoggedOn && ((access >= EAccess.Owner) || ((steamID != 0) && (bot.Value.GetAccess(steamID) >= EAccess.Operator))) && ((items.Count == 0) || items.Keys.Any(packageID => !bot.Value.OwnedPackages.ContainsKey(packageID)))).OrderBy(static bot => bot.Key, Bot.BotsComparer).Select(static bot => bot.Value)) { CStore_RegisterCDKey_Response? redeemResponse = await innerBot.Actions.RedeemKey(key).ConfigureAwait(false); if (redeemResponse == null) {