From c9acbb7bf27c2100144b03ea8b634c130ea5bfbb Mon Sep 17 00:00:00 2001 From: Archi Date: Sun, 17 Mar 2024 02:29:04 +0100 Subject: [PATCH] Big post-PR cleanup --- .../Backend.cs | 16 +- .../Data/AnnouncementDiffRequest.cs | 10 +- .../Data/AnnouncementRequest.cs | 12 +- .../Data/AssetForMatching.cs | 12 +- .../Data/AssetInInventory.cs | 10 +- .../Data/InventoriesRequest.cs | 12 +- .../Data/ListedUser.cs | 10 +- .../Data/SetPart.cs | 12 +- .../Data/SetPartsRequest.cs | 12 +- .../RemoteCommunication.cs | 76 +-- ArchiSteamFarm.Tests/Bot.cs | 156 +++--- ArchiSteamFarm.Tests/Trading.cs | 223 +++++--- ArchiSteamFarm.sln.DotSettings | 8 +- ArchiSteamFarm/Steam/Bot.cs | 20 +- .../Steam/Data/APIWrappedResponse.cs | 15 +- ArchiSteamFarm/Steam/Data/Asset.cs | 77 +-- ArchiSteamFarm/Steam/Data/EAssetRarity.cs | 31 ++ ArchiSteamFarm/Steam/Data/EAssetType.cs | 44 ++ .../Steam/Data/InventoryDescription.cs | 499 +++++++++++------- ArchiSteamFarm/Steam/Data/ItemAction.cs | 20 +- ArchiSteamFarm/Steam/Data/ItemDescription.cs | 19 +- ArchiSteamFarm/Steam/Data/Tag.cs | 16 +- ArchiSteamFarm/Steam/Data/TradeOffer.cs | 12 +- .../Steam/Data/TradeOffersResponse.cs | 15 +- ArchiSteamFarm/Steam/Exchange/Trading.cs | 56 +- .../Steam/Integration/ArchiHandler.cs | 39 +- .../Steam/Integration/ArchiWebHandler.cs | 54 +- ArchiSteamFarm/Steam/Interaction/Commands.cs | 10 +- ArchiSteamFarm/Steam/Storage/BotConfig.cs | 36 +- 29 files changed, 872 insertions(+), 660 deletions(-) create mode 100644 ArchiSteamFarm/Steam/Data/EAssetRarity.cs create mode 100644 ArchiSteamFarm/Steam/Data/EAssetType.cs diff --git a/ArchiSteamFarm.OfficialPlugins.ItemsMatcher/Backend.cs b/ArchiSteamFarm.OfficialPlugins.ItemsMatcher/Backend.cs index 421d2cada..e1414eaed 100644 --- a/ArchiSteamFarm.OfficialPlugins.ItemsMatcher/Backend.cs +++ b/ArchiSteamFarm.OfficialPlugins.ItemsMatcher/Backend.cs @@ -5,16 +5,16 @@ // / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | // /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| // ---------------------------------------------------------------------------------------------- -// +// // Copyright 2015-2024 Ł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. @@ -43,7 +43,7 @@ using SteamKit2; namespace ArchiSteamFarm.OfficialPlugins.ItemsMatcher; internal static class Backend { - internal static async Task>?> AnnounceDiffForListing(WebBrowser webBrowser, ulong steamID, IReadOnlyCollection inventory, string inventoryChecksum, IReadOnlyCollection acceptedMatchableTypes, uint totalInventoryCount, bool matchEverything, string tradeToken, IReadOnlyCollection inventoryRemoved, string? previousInventoryChecksum, string? nickname = null, string? avatarHash = null) { + internal static async Task>?> AnnounceDiffForListing(WebBrowser webBrowser, ulong steamID, IReadOnlyCollection inventory, string inventoryChecksum, IReadOnlyCollection acceptedMatchableTypes, uint totalInventoryCount, bool matchEverything, string tradeToken, IReadOnlyCollection inventoryRemoved, string? previousInventoryChecksum, string? nickname = null, string? avatarHash = null) { ArgumentNullException.ThrowIfNull(webBrowser); if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) { @@ -74,7 +74,7 @@ internal static class Backend { return await webBrowser.UrlPostToJsonObject, AnnouncementDiffRequest>(request, data: data, requestOptions: WebBrowser.ERequestOptions.ReturnRedirections | WebBrowser.ERequestOptions.ReturnClientErrors | WebBrowser.ERequestOptions.AllowInvalidBodyOnErrors | WebBrowser.ERequestOptions.CompressRequest).ConfigureAwait(false); } - internal static async Task>?> AnnounceForListing(WebBrowser webBrowser, ulong steamID, IReadOnlyCollection inventory, string inventoryChecksum, IReadOnlyCollection acceptedMatchableTypes, uint totalInventoryCount, bool matchEverything, string tradeToken, string? nickname = null, string? avatarHash = null) { + internal static async Task>?> AnnounceForListing(WebBrowser webBrowser, ulong steamID, IReadOnlyCollection inventory, string inventoryChecksum, IReadOnlyCollection acceptedMatchableTypes, uint totalInventoryCount, bool matchEverything, string tradeToken, string? nickname = null, string? avatarHash = null) { ArgumentNullException.ThrowIfNull(webBrowser); if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) { @@ -131,7 +131,7 @@ internal static class Backend { return response?.StatusCode; } - internal static async Task<(HttpStatusCode StatusCode, ImmutableHashSet Users)?> GetListedUsersForMatching(Guid licenseID, Bot bot, WebBrowser webBrowser, IReadOnlyCollection inventory, IReadOnlyCollection acceptedMatchableTypes) { + internal static async Task<(HttpStatusCode StatusCode, ImmutableHashSet Users)?> GetListedUsersForMatching(Guid licenseID, Bot bot, WebBrowser webBrowser, IReadOnlyCollection inventory, IReadOnlyCollection acceptedMatchableTypes) { ArgumentOutOfRangeException.ThrowIfEqual(licenseID, Guid.Empty); ArgumentNullException.ThrowIfNull(bot); ArgumentNullException.ThrowIfNull(webBrowser); @@ -161,7 +161,7 @@ internal static class Backend { return (response.StatusCode, response.Content?.Result ?? ImmutableHashSet.Empty); } - internal static async Task>>?> GetSetParts(WebBrowser webBrowser, ulong steamID, IReadOnlyCollection matchableTypes, IReadOnlyCollection realAppIDs, CancellationToken cancellationToken = default) { + internal static async Task>>?> GetSetParts(WebBrowser webBrowser, ulong steamID, IReadOnlyCollection matchableTypes, IReadOnlyCollection realAppIDs, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(webBrowser); if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) { diff --git a/ArchiSteamFarm.OfficialPlugins.ItemsMatcher/Data/AnnouncementDiffRequest.cs b/ArchiSteamFarm.OfficialPlugins.ItemsMatcher/Data/AnnouncementDiffRequest.cs index c05d383f1..b3db60576 100644 --- a/ArchiSteamFarm.OfficialPlugins.ItemsMatcher/Data/AnnouncementDiffRequest.cs +++ b/ArchiSteamFarm.OfficialPlugins.ItemsMatcher/Data/AnnouncementDiffRequest.cs @@ -5,16 +5,16 @@ // / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | // /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| // ---------------------------------------------------------------------------------------------- -// +// // Copyright 2015-2024 Ł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. @@ -40,7 +40,7 @@ internal sealed class AnnouncementDiffRequest : AnnouncementRequest { [JsonRequired] private string PreviousInventoryChecksum { get; init; } - internal AnnouncementDiffRequest(Guid guid, ulong steamID, IReadOnlyCollection inventory, string inventoryChecksum, IReadOnlyCollection matchableTypes, uint totalInventoryCount, bool matchEverything, byte maxTradeHoldDuration, string tradeToken, IReadOnlyCollection inventoryRemoved, string previousInventoryChecksum, string? nickname = null, string? avatarHash = null) : base(guid, steamID, inventory, inventoryChecksum, matchableTypes, totalInventoryCount, matchEverything, maxTradeHoldDuration, tradeToken, nickname, avatarHash) { + internal AnnouncementDiffRequest(Guid guid, ulong steamID, IReadOnlyCollection inventory, string inventoryChecksum, IReadOnlyCollection matchableTypes, uint totalInventoryCount, bool matchEverything, byte maxTradeHoldDuration, string tradeToken, IReadOnlyCollection inventoryRemoved, string previousInventoryChecksum, string? nickname = null, string? avatarHash = null) : base(guid, steamID, inventory, inventoryChecksum, matchableTypes, totalInventoryCount, matchEverything, maxTradeHoldDuration, tradeToken, nickname, avatarHash) { ArgumentOutOfRangeException.ThrowIfEqual(guid, Guid.Empty); if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) { diff --git a/ArchiSteamFarm.OfficialPlugins.ItemsMatcher/Data/AnnouncementRequest.cs b/ArchiSteamFarm.OfficialPlugins.ItemsMatcher/Data/AnnouncementRequest.cs index d954aec17..7f39f836e 100644 --- a/ArchiSteamFarm.OfficialPlugins.ItemsMatcher/Data/AnnouncementRequest.cs +++ b/ArchiSteamFarm.OfficialPlugins.ItemsMatcher/Data/AnnouncementRequest.cs @@ -5,16 +5,16 @@ // / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | // /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| // ---------------------------------------------------------------------------------------------- -// +// // Copyright 2015-2024 Ł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. @@ -50,7 +50,7 @@ internal class AnnouncementRequest { [JsonInclude] [JsonRequired] - private ImmutableHashSet MatchableTypes { get; init; } + private ImmutableHashSet MatchableTypes { get; init; } [JsonInclude] [JsonRequired] @@ -75,7 +75,7 @@ internal class AnnouncementRequest { [JsonRequired] private string TradeToken { get; init; } - internal AnnouncementRequest(Guid guid, ulong steamID, IReadOnlyCollection inventory, string inventoryChecksum, IReadOnlyCollection matchableTypes, uint totalInventoryCount, bool matchEverything, byte maxTradeHoldDuration, string tradeToken, string? nickname = null, string? avatarHash = null) { + internal AnnouncementRequest(Guid guid, ulong steamID, IReadOnlyCollection inventory, string inventoryChecksum, IReadOnlyCollection matchableTypes, uint totalInventoryCount, bool matchEverything, byte maxTradeHoldDuration, string tradeToken, string? nickname = null, string? avatarHash = null) { ArgumentOutOfRangeException.ThrowIfEqual(guid, Guid.Empty); if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) { diff --git a/ArchiSteamFarm.OfficialPlugins.ItemsMatcher/Data/AssetForMatching.cs b/ArchiSteamFarm.OfficialPlugins.ItemsMatcher/Data/AssetForMatching.cs index 8c2df873c..ff2ab6a7c 100644 --- a/ArchiSteamFarm.OfficialPlugins.ItemsMatcher/Data/AssetForMatching.cs +++ b/ArchiSteamFarm.OfficialPlugins.ItemsMatcher/Data/AssetForMatching.cs @@ -5,16 +5,16 @@ // / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | // /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| // ---------------------------------------------------------------------------------------------- -// +// // Copyright 2015-2024 Ł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. @@ -41,7 +41,7 @@ internal class AssetForMatching { [JsonInclude] [JsonPropertyName("r")] [JsonRequired] - internal Asset.ERarity Rarity { get; private init; } + internal EAssetRarity Rarity { get; private init; } [JsonInclude] [JsonPropertyName("e")] @@ -56,7 +56,7 @@ internal class AssetForMatching { [JsonInclude] [JsonPropertyName("p")] [JsonRequired] - internal Asset.EType Type { get; private init; } + internal EAssetType Type { get; private init; } [JsonConstructor] protected AssetForMatching() { } diff --git a/ArchiSteamFarm.OfficialPlugins.ItemsMatcher/Data/AssetInInventory.cs b/ArchiSteamFarm.OfficialPlugins.ItemsMatcher/Data/AssetInInventory.cs index 9488310be..ab6a25270 100644 --- a/ArchiSteamFarm.OfficialPlugins.ItemsMatcher/Data/AssetInInventory.cs +++ b/ArchiSteamFarm.OfficialPlugins.ItemsMatcher/Data/AssetInInventory.cs @@ -5,16 +5,16 @@ // / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | // /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| // ---------------------------------------------------------------------------------------------- -// +// // Copyright 2015-2024 Ł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. @@ -42,5 +42,5 @@ internal class AssetInInventory : AssetForMatching { AssetID = asset.AssetID; } - internal Asset ToAsset() => new(Asset.SteamAppID, Asset.SteamCommunityContextID, ClassID, Amount, new InventoryDescription { ProtobufBody = { tradable = Tradable } }, assetID: AssetID, realAppID: RealAppID, type: Type, rarity: Rarity); + internal Asset ToAsset() => new(Asset.SteamAppID, Asset.SteamCommunityContextID, ClassID, Amount, new InventoryDescription(Asset.SteamAppID, ClassID, tradable: false, realAppID: RealAppID, type: Type, rarity: Rarity), AssetID); } diff --git a/ArchiSteamFarm.OfficialPlugins.ItemsMatcher/Data/InventoriesRequest.cs b/ArchiSteamFarm.OfficialPlugins.ItemsMatcher/Data/InventoriesRequest.cs index 4231dc611..f3e4db241 100644 --- a/ArchiSteamFarm.OfficialPlugins.ItemsMatcher/Data/InventoriesRequest.cs +++ b/ArchiSteamFarm.OfficialPlugins.ItemsMatcher/Data/InventoriesRequest.cs @@ -5,16 +5,16 @@ // / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | // /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| // ---------------------------------------------------------------------------------------------- -// +// // Copyright 2015-2024 Ł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. @@ -42,13 +42,13 @@ internal sealed class InventoriesRequest { [JsonInclude] [JsonRequired] - internal ImmutableHashSet MatchableTypes { get; private init; } + internal ImmutableHashSet MatchableTypes { get; private init; } [JsonInclude] [JsonRequired] internal ulong SteamID { get; private init; } - internal InventoriesRequest(Guid guid, ulong steamID, IReadOnlyCollection inventory, IReadOnlyCollection matchableTypes) { + internal InventoriesRequest(Guid guid, ulong steamID, IReadOnlyCollection inventory, IReadOnlyCollection matchableTypes) { ArgumentOutOfRangeException.ThrowIfEqual(guid, Guid.Empty); if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) { diff --git a/ArchiSteamFarm.OfficialPlugins.ItemsMatcher/Data/ListedUser.cs b/ArchiSteamFarm.OfficialPlugins.ItemsMatcher/Data/ListedUser.cs index aa0ec03d6..e7b858338 100644 --- a/ArchiSteamFarm.OfficialPlugins.ItemsMatcher/Data/ListedUser.cs +++ b/ArchiSteamFarm.OfficialPlugins.ItemsMatcher/Data/ListedUser.cs @@ -5,16 +5,16 @@ // / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | // /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| // ---------------------------------------------------------------------------------------------- -// +// // Copyright 2015-2024 Ł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. @@ -37,7 +37,7 @@ internal sealed class ListedUser { [JsonInclude] [JsonRequired] - internal ImmutableHashSet MatchableTypes { get; private init; } = ImmutableHashSet.Empty; + internal ImmutableHashSet MatchableTypes { get; private init; } = ImmutableHashSet.Empty; [JsonInclude] [JsonRequired] diff --git a/ArchiSteamFarm.OfficialPlugins.ItemsMatcher/Data/SetPart.cs b/ArchiSteamFarm.OfficialPlugins.ItemsMatcher/Data/SetPart.cs index f0ba6a705..070cc151d 100644 --- a/ArchiSteamFarm.OfficialPlugins.ItemsMatcher/Data/SetPart.cs +++ b/ArchiSteamFarm.OfficialPlugins.ItemsMatcher/Data/SetPart.cs @@ -5,16 +5,16 @@ // / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | // /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| // ---------------------------------------------------------------------------------------------- -// +// // Copyright 2015-2024 Ł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. @@ -38,7 +38,7 @@ internal sealed class SetPart { [JsonInclude] [JsonPropertyName("r")] [JsonRequired] - internal Asset.ERarity Rarity { get; private init; } + internal EAssetRarity Rarity { get; private init; } [JsonInclude] [JsonPropertyName("e")] @@ -48,7 +48,7 @@ internal sealed class SetPart { [JsonInclude] [JsonPropertyName("p")] [JsonRequired] - internal Asset.EType Type { get; private init; } + internal EAssetType Type { get; private init; } [JsonConstructor] private SetPart() { } diff --git a/ArchiSteamFarm.OfficialPlugins.ItemsMatcher/Data/SetPartsRequest.cs b/ArchiSteamFarm.OfficialPlugins.ItemsMatcher/Data/SetPartsRequest.cs index c4854ac2b..136bcdd72 100644 --- a/ArchiSteamFarm.OfficialPlugins.ItemsMatcher/Data/SetPartsRequest.cs +++ b/ArchiSteamFarm.OfficialPlugins.ItemsMatcher/Data/SetPartsRequest.cs @@ -5,16 +5,16 @@ // / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | // /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| // ---------------------------------------------------------------------------------------------- -// +// // Copyright 2015-2024 Ł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. @@ -37,7 +37,7 @@ internal sealed class SetPartsRequest { [JsonInclude] [JsonRequired] - internal ImmutableHashSet MatchableTypes { get; private init; } + internal ImmutableHashSet MatchableTypes { get; private init; } [JsonInclude] [JsonRequired] @@ -47,7 +47,7 @@ internal sealed class SetPartsRequest { [JsonRequired] internal ulong SteamID { get; private init; } - internal SetPartsRequest(Guid guid, ulong steamID, IReadOnlyCollection matchableTypes, IReadOnlyCollection realAppIDs) { + internal SetPartsRequest(Guid guid, ulong steamID, IReadOnlyCollection matchableTypes, IReadOnlyCollection realAppIDs) { ArgumentOutOfRangeException.ThrowIfEqual(guid, Guid.Empty); if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) { diff --git a/ArchiSteamFarm.OfficialPlugins.ItemsMatcher/RemoteCommunication.cs b/ArchiSteamFarm.OfficialPlugins.ItemsMatcher/RemoteCommunication.cs index 37155dfe5..796d40319 100644 --- a/ArchiSteamFarm.OfficialPlugins.ItemsMatcher/RemoteCommunication.cs +++ b/ArchiSteamFarm.OfficialPlugins.ItemsMatcher/RemoteCommunication.cs @@ -5,16 +5,16 @@ // / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | // /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| // ---------------------------------------------------------------------------------------------- -// +// // Copyright 2015-2024 Ł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. @@ -63,11 +63,11 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable { private const byte MinimumSteamGuardEnabledDays = 15; // As imposed by Steam limits private const byte MinPersonaStateTTL = 5; // Minimum amount of minutes we must wait before requesting persona state update - private static readonly FrozenSet AcceptedMatchableTypes = new HashSet(4) { - Asset.EType.Emoticon, - Asset.EType.FoilTradingCard, - Asset.EType.ProfileBackground, - Asset.EType.TradingCard + private static readonly FrozenSet AcceptedMatchableTypes = new HashSet(4) { + EAssetType.Emoticon, + EAssetType.FoilTradingCard, + EAssetType.ProfileBackground, + EAssetType.TradingCard }.ToFrozenSet(); private readonly Bot Bot; @@ -231,7 +231,7 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable { return; } - HashSet acceptedMatchableTypes = Bot.BotConfig.MatchableTypes.Where(AcceptedMatchableTypes.Contains).ToHashSet(); + HashSet acceptedMatchableTypes = Bot.BotConfig.MatchableTypes.Where(AcceptedMatchableTypes.Contains).ToHashSet(); if (acceptedMatchableTypes.Count == 0) { throw new InvalidOperationException(nameof(acceptedMatchableTypes)); @@ -284,10 +284,10 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable { List assetsForListing = []; - Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), bool> tradableSets = new(); + Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), bool> tradableSets = new(); foreach (Asset item in inventory) { - if (item is { AssetID: > 0, Amount: > 0, ClassID: > 0, RealAppID: > 0, Type: > Asset.EType.Unknown, Rarity: > Asset.ERarity.Unknown, IsSteamPointsShopItem: false } && acceptedMatchableTypes.Contains(item.Type)) { + if (item is { AssetID: > 0, Amount: > 0, ClassID: > 0, RealAppID: > 0, Type: > EAssetType.Unknown, Rarity: > EAssetRarity.Unknown, IsSteamPointsShopItem: false } && acceptedMatchableTypes.Contains(item.Type)) { // Only tradable assets matter for MatchEverything bots if (!matchEverything || item.Tradable) { assetsForListing.Add(new AssetForListing(item, index, previousAssetID)); @@ -295,7 +295,7 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable { // But even for Fair bots, we should track and skip sets where we don't have any item to trade with if (!matchEverything) { - (uint RealAppID, Asset.EType Type, Asset.ERarity Rarity) key = (item.RealAppID, item.Type, item.Rarity); + (uint RealAppID, EAssetType Type, EAssetRarity Rarity) key = (item.RealAppID, item.Type, item.Rarity); if (tradableSets.TryGetValue(key, out bool tradable)) { if (!tradable && item.Tradable) { @@ -377,12 +377,12 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable { if (!matchEverything) { // We should deduplicate our sets before sending them to the server, for doing that we'll use ASFB set parts data HashSet realAppIDs = []; - Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), Dictionary> state = new(); + Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), Dictionary> state = new(); foreach (AssetForListing asset in assetsForListing) { realAppIDs.Add(asset.RealAppID); - (uint RealAppID, Asset.EType Type, Asset.ERarity Rarity) key = (asset.RealAppID, asset.Type, asset.Rarity); + (uint RealAppID, EAssetType Type, EAssetRarity Rarity) key = (asset.RealAppID, asset.Type, asset.Rarity); if (state.TryGetValue(key, out Dictionary? set)) { set[asset.ClassID] = set.GetValueOrDefault(asset.ClassID) + asset.Amount; @@ -448,11 +448,11 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable { return; } - Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), HashSet> databaseSets = setPartsResponse.Content.Result.GroupBy(static setPart => (setPart.RealAppID, setPart.Type, setPart.Rarity)).ToDictionary(static group => group.Key, static group => group.Select(static setPart => setPart.ClassID).ToHashSet()); + Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), HashSet> databaseSets = setPartsResponse.Content.Result.GroupBy(static setPart => (setPart.RealAppID, setPart.Type, setPart.Rarity)).ToDictionary(static group => group.Key, static group => group.Select(static setPart => setPart.ClassID).ToHashSet()); Dictionary setCopy = []; - foreach (((uint RealAppID, Asset.EType Type, Asset.ERarity Rarity) key, Dictionary set) in state) { + foreach (((uint RealAppID, EAssetType Type, EAssetRarity Rarity) key, Dictionary set) in state) { if (!databaseSets.TryGetValue(key, out HashSet? databaseSet)) { // We have no clue about this set, we can't do any optimization continue; @@ -490,7 +490,7 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable { HashSet assetsForListingFiltered = []; foreach (AssetForListing asset in assetsForListing.Where(asset => state.TryGetValue((asset.RealAppID, asset.Type, asset.Rarity), out Dictionary? setState) && setState.TryGetValue(asset.ClassID, out uint targetAmount) && (targetAmount > 0)).OrderByDescending(static asset => asset.Tradable).ThenByDescending(static asset => asset.Index)) { - (uint RealAppID, Asset.EType Type, Asset.ERarity Rarity) key = (asset.RealAppID, asset.Type, asset.Rarity); + (uint RealAppID, EAssetType Type, EAssetRarity Rarity) key = (asset.RealAppID, asset.Type, asset.Rarity); if (!state.TryGetValue(key, out Dictionary? setState) || !setState.TryGetValue(asset.ClassID, out uint targetAmount) || (targetAmount == 0)) { // We're not interested in this combination @@ -903,7 +903,7 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable { return; } - HashSet acceptedMatchableTypes = Bot.BotConfig.MatchableTypes.Where(AcceptedMatchableTypes.Contains).ToHashSet(); + HashSet acceptedMatchableTypes = Bot.BotConfig.MatchableTypes.Where(AcceptedMatchableTypes.Contains).ToHashSet(); if (acceptedMatchableTypes.Count == 0) { Bot.ArchiLogger.LogNullError(acceptedMatchableTypes); @@ -939,7 +939,7 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable { HashSet assetsForMatching; try { - assetsForMatching = await Bot.ArchiHandler.GetMyInventoryAsync().Where(item => item is { AssetID: > 0, Amount: > 0, ClassID: > 0, RealAppID: > 0, Type: > Asset.EType.Unknown, Rarity: > Asset.ERarity.Unknown, IsSteamPointsShopItem: false } && acceptedMatchableTypes.Contains(item.Type) && !Bot.BotDatabase.MatchActivelyBlacklistAppIDs.Contains(item.RealAppID)).ToHashSetAsync().ConfigureAwait(false); + assetsForMatching = await Bot.ArchiHandler.GetMyInventoryAsync().Where(item => item is { AssetID: > 0, Amount: > 0, ClassID: > 0, RealAppID: > 0, Type: > EAssetType.Unknown, Rarity: > EAssetRarity.Unknown, IsSteamPointsShopItem: false } && acceptedMatchableTypes.Contains(item.Type) && !Bot.BotDatabase.MatchActivelyBlacklistAppIDs.Contains(item.RealAppID)).ToHashSetAsync().ConfigureAwait(false); } catch (HttpRequestException e) { Bot.ArchiLogger.LogGenericWarningException(e); Bot.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, nameof(assetsForMatching))); @@ -959,7 +959,7 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable { } // Remove from our inventory items that can't be possibly matched due to no dupes to offer available - HashSet<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity)> setsToKeep = Trading.GetInventorySets(assetsForMatching).Where(static set => set.Value.Any(static amount => amount > 1)).Select(static set => set.Key).ToHashSet(); + HashSet<(uint RealAppID, EAssetType Type, EAssetRarity Rarity)> setsToKeep = Trading.GetInventorySets(assetsForMatching).Where(static set => set.Value.Any(static amount => amount > 1)).Select(static set => set.Key).ToHashSet(); if (assetsForMatching.RemoveWhere(item => !setsToKeep.Contains((item.RealAppID, item.Type, item.Rarity))) > 0) { if (assetsForMatching.Count == 0) { @@ -971,12 +971,12 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable { // We should deduplicate our sets before sending them to the server, for doing that we'll use ASFB set parts data HashSet realAppIDs = []; - Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), Dictionary> setsState = new(); + Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), Dictionary> setsState = new(); foreach (Asset asset in assetsForMatching) { realAppIDs.Add(asset.RealAppID); - (uint RealAppID, Asset.EType Type, Asset.ERarity Rarity) key = (asset.RealAppID, asset.Type, asset.Rarity); + (uint RealAppID, EAssetType Type, EAssetRarity Rarity) key = (asset.RealAppID, asset.Type, asset.Rarity); if (setsState.TryGetValue(key, out Dictionary? set)) { set[asset.ClassID] = set.GetValueOrDefault(asset.ClassID) + asset.Amount; @@ -1033,11 +1033,11 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable { return; } - Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), HashSet> databaseSets = setPartsResponse.Content.Result.GroupBy(static setPart => (setPart.RealAppID, setPart.Type, setPart.Rarity)).ToDictionary(static group => group.Key, static group => group.Select(static setPart => setPart.ClassID).ToHashSet()); + Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), HashSet> databaseSets = setPartsResponse.Content.Result.GroupBy(static setPart => (setPart.RealAppID, setPart.Type, setPart.Rarity)).ToDictionary(static group => group.Key, static group => group.Select(static setPart => setPart.ClassID).ToHashSet()); Dictionary setCopy = []; - foreach (((uint RealAppID, Asset.EType Type, Asset.ERarity Rarity) key, Dictionary set) in setsState) { + foreach (((uint RealAppID, EAssetType Type, EAssetRarity Rarity) key, Dictionary set) in setsState) { uint minimumAmount = uint.MaxValue; uint maximumAmount = uint.MinValue; @@ -1096,7 +1096,7 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable { HashSet assetsForMatchingFiltered = []; foreach (Asset asset in assetsForMatching.Where(asset => setsState.TryGetValue((asset.RealAppID, asset.Type, asset.Rarity), out Dictionary? setState) && setState.TryGetValue(asset.ClassID, out uint targetAmount) && (targetAmount > 0)).OrderByDescending(static asset => asset.Tradable)) { - (uint RealAppID, Asset.EType Type, Asset.ERarity Rarity) key = (asset.RealAppID, asset.Type, asset.Rarity); + (uint RealAppID, EAssetType Type, EAssetRarity Rarity) key = (asset.RealAppID, asset.Type, asset.Rarity); if (!setsState.TryGetValue(key, out Dictionary? setState) || !setState.TryGetValue(asset.ClassID, out uint targetAmount) || (targetAmount == 0)) { // We're not interested in this combination @@ -1167,7 +1167,7 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable { } } - private async Task MatchActively(IReadOnlyCollection listedUsers, IReadOnlyCollection ourAssets, IReadOnlyCollection acceptedMatchableTypes) { + private async Task MatchActively(IReadOnlyCollection listedUsers, IReadOnlyCollection ourAssets, IReadOnlyCollection acceptedMatchableTypes) { if ((listedUsers == null) || (listedUsers.Count == 0)) { throw new ArgumentNullException(nameof(listedUsers)); } @@ -1180,7 +1180,7 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable { throw new ArgumentNullException(nameof(acceptedMatchableTypes)); } - (Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), Dictionary> ourFullState, Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), Dictionary> ourTradableState) = Trading.GetDividedInventoryState(ourAssets); + (Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), Dictionary> ourFullState, Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), Dictionary> ourTradableState) = Trading.GetDividedInventoryState(ourAssets); if (Trading.IsEmptyForMatching(ourFullState, ourTradableState)) { // User doesn't have any more dupes in the inventory @@ -1263,7 +1263,7 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable { break; } - HashSet<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity)> wantedSets = ourTradableState.Keys.Where(set => listedUser.MatchableTypes.Contains(set.Type)).ToHashSet(); + HashSet<(uint RealAppID, EAssetType Type, EAssetRarity Rarity)> wantedSets = ourTradableState.Keys.Where(set => listedUser.MatchableTypes.Contains(set.Type)).ToHashSet(); if (wantedSets.Count == 0) { continue; @@ -1284,26 +1284,26 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable { continue; } - HashSet theirInventory = listedUser.Assets.Where(item => (!listedUser.MatchEverything || item.Tradable) && wantedSets.Contains((item.RealAppID, item.Type, item.Rarity)) && ((tradeHoldDuration.Value == 0) || !(item.Type is Asset.EType.FoilTradingCard or Asset.EType.TradingCard && CardsFarmer.SalesBlacklist.Contains(item.RealAppID)))).Select(static asset => asset.ToAsset()).ToHashSet(); + HashSet theirInventory = listedUser.Assets.Where(item => (!listedUser.MatchEverything || item.Tradable) && wantedSets.Contains((item.RealAppID, item.Type, item.Rarity)) && ((tradeHoldDuration.Value == 0) || !(item.Type is EAssetType.FoilTradingCard or EAssetType.TradingCard && CardsFarmer.SalesBlacklist.Contains(item.RealAppID)))).Select(static asset => asset.ToAsset()).ToHashSet(); if (theirInventory.Count == 0) { continue; } - HashSet<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity)> skippedSetsThisUser = []; + HashSet<(uint RealAppID, EAssetType Type, EAssetRarity Rarity)> skippedSetsThisUser = []; - Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), Dictionary> theirTradableState = Trading.GetTradableInventoryState(theirInventory); + Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), Dictionary> theirTradableState = Trading.GetTradableInventoryState(theirInventory); for (byte i = 0; i < Trading.MaxTradesPerAccount; i++) { byte itemsInTrade = 0; - HashSet<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity)> skippedSetsThisTrade = []; + HashSet<(uint RealAppID, EAssetType Type, EAssetRarity Rarity)> skippedSetsThisTrade = []; Dictionary classIDsToGive = new(); Dictionary classIDsToReceive = new(); Dictionary fairClassIDsToGive = new(); Dictionary fairClassIDsToReceive = new(); - foreach (((uint RealAppID, Asset.EType Type, Asset.ERarity Rarity) set, Dictionary ourFullItems) in ourFullState.Where(set => !skippedSetsThisUser.Contains(set.Key) && listedUser.MatchableTypes.Contains(set.Key.Type) && set.Value.Values.Any(static count => count > 1))) { + foreach (((uint RealAppID, EAssetType Type, EAssetRarity Rarity) set, Dictionary ourFullItems) in ourFullState.Where(set => !skippedSetsThisUser.Contains(set.Key) && listedUser.MatchableTypes.Contains(set.Key.Type) && set.Value.Values.Any(static count => count > 1))) { if (!ourTradableState.TryGetValue(set, out Dictionary? ourTradableItems) || (ourTradableItems.Count == 0)) { // We may have no more tradable items from this set continue; @@ -1506,10 +1506,14 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable { // However, since this is only an assumption, we must mark newly acquired items as untradable so we're sure that they're not considered for trading, only for matching foreach (Asset itemToReceive in itemsToReceive) { if (ourInventory.TryGetValue(itemToReceive.AssetID, out Asset? item)) { - item.Description.ProtobufBody.tradable = false; + item.Description ??= new InventoryDescription(itemToReceive.AppID, itemToReceive.ClassID, itemToReceive.InstanceID, realAppID: itemToReceive.RealAppID, type: itemToReceive.Type, rarity: itemToReceive.Rarity); + + item.Description.Body.tradable = false; item.Amount += itemToReceive.Amount; } else { - itemToReceive.Description.ProtobufBody.tradable = false; + itemToReceive.Description ??= new InventoryDescription(itemToReceive.AppID, itemToReceive.ClassID, itemToReceive.InstanceID, realAppID: itemToReceive.RealAppID, type: itemToReceive.Type, rarity: itemToReceive.Rarity); + + itemToReceive.Description.Body.tradable = false; ourInventory[itemToReceive.AssetID] = itemToReceive; } diff --git a/ArchiSteamFarm.Tests/Bot.cs b/ArchiSteamFarm.Tests/Bot.cs index 24610e8c3..adff22ada 100644 --- a/ArchiSteamFarm.Tests/Bot.cs +++ b/ArchiSteamFarm.Tests/Bot.cs @@ -5,16 +5,16 @@ // / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | // /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| // ---------------------------------------------------------------------------------------------- -// +// // Copyright 2015-2024 Ł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. @@ -45,7 +45,7 @@ public sealed class Bot { foreach ((uint appID, byte cards) in itemsPerSet) { for (byte i = 1; i <= cards; i++) { - items.Add(CreateCard(i, appID)); + items.Add(CreateCard(i, realAppID: appID)); } } @@ -61,8 +61,8 @@ public sealed class Bot { const uint appID = 42; HashSet items = [ - CreateCard(1, appID), - CreateCard(2, appID) + CreateCard(1, realAppID: appID), + CreateCard(2, realAppID: appID) ]; Assert.ThrowsException(() => GetItemsForFullBadge(items, 2, appID, MinCardsPerBadge - 1)); @@ -73,10 +73,10 @@ public sealed class Bot { const uint appID = 42; HashSet items = [ - CreateCard(1, appID), - CreateCard(1, appID), - CreateCard(2, appID), - CreateCard(3, appID) + CreateCard(1, realAppID: appID), + CreateCard(1, realAppID: appID), + CreateCard(2, realAppID: appID), + CreateCard(3, realAppID: appID) ]; HashSet itemsToSend = GetItemsForFullBadge(items, 3, appID); @@ -95,10 +95,10 @@ public sealed class Bot { const uint appID = 42; HashSet items = [ - CreateCard(1, appID), - CreateCard(1, appID), - CreateCard(2, appID), - CreateCard(2, appID) + CreateCard(1, realAppID: appID), + CreateCard(1, realAppID: appID), + CreateCard(2, realAppID: appID), + CreateCard(2, realAppID: appID) ]; HashSet itemsToSend = GetItemsForFullBadge(items, 2, appID); @@ -116,9 +116,9 @@ public sealed class Bot { const uint appID = 42; HashSet items = [ - CreateCard(1, appID, 2), - CreateCard(2, appID), - CreateCard(2, appID) + CreateCard(1, amount: 2, realAppID: appID), + CreateCard(2, realAppID: appID), + CreateCard(2, realAppID: appID) ]; HashSet itemsToSend = GetItemsForFullBadge(items, 2, appID); @@ -136,27 +136,27 @@ public sealed class Bot { const uint appID = 42; HashSet items = [ - CreateCard(1, appID, type: Asset.EType.TradingCard, rarity: Asset.ERarity.Common), - CreateCard(2, appID, type: Asset.EType.TradingCard, rarity: Asset.ERarity.Common), + CreateCard(1, realAppID: appID, type: EAssetType.TradingCard, rarity: EAssetRarity.Common), + CreateCard(2, realAppID: appID, type: EAssetType.TradingCard, rarity: EAssetRarity.Common), - CreateCard(1, appID, type: Asset.EType.FoilTradingCard, rarity: Asset.ERarity.Uncommon), - CreateCard(2, appID, type: Asset.EType.FoilTradingCard, rarity: Asset.ERarity.Uncommon), + CreateCard(1, realAppID: appID, type: EAssetType.FoilTradingCard, rarity: EAssetRarity.Uncommon), + CreateCard(2, realAppID: appID, type: EAssetType.FoilTradingCard, rarity: EAssetRarity.Uncommon), - CreateCard(1, appID, type: Asset.EType.FoilTradingCard, rarity: Asset.ERarity.Rare), - CreateCard(2, appID, type: Asset.EType.FoilTradingCard, rarity: Asset.ERarity.Rare), + CreateCard(1, realAppID: appID, type: EAssetType.FoilTradingCard, rarity: EAssetRarity.Rare), + CreateCard(2, realAppID: appID, type: EAssetType.FoilTradingCard, rarity: EAssetRarity.Rare), // for better readability and easier verification when thinking about this test the items that shall be selected for sending are the ones below this comment - CreateCard(1, appID, type: Asset.EType.TradingCard, rarity: Asset.ERarity.Uncommon), - CreateCard(2, appID, type: Asset.EType.TradingCard, rarity: Asset.ERarity.Uncommon), - CreateCard(3, appID, type: Asset.EType.TradingCard, rarity: Asset.ERarity.Uncommon), + CreateCard(1, realAppID: appID, type: EAssetType.TradingCard, rarity: EAssetRarity.Uncommon), + CreateCard(2, realAppID: appID, type: EAssetType.TradingCard, rarity: EAssetRarity.Uncommon), + CreateCard(3, realAppID: appID, type: EAssetType.TradingCard, rarity: EAssetRarity.Uncommon), - CreateCard(1, appID, type: Asset.EType.FoilTradingCard, rarity: Asset.ERarity.Common), - CreateCard(3, appID, type: Asset.EType.FoilTradingCard, rarity: Asset.ERarity.Common), - CreateCard(7, appID, type: Asset.EType.FoilTradingCard, rarity: Asset.ERarity.Common), + CreateCard(1, realAppID: appID, type: EAssetType.FoilTradingCard, rarity: EAssetRarity.Common), + CreateCard(3, realAppID: appID, type: EAssetType.FoilTradingCard, rarity: EAssetRarity.Common), + CreateCard(7, realAppID: appID, type: EAssetType.FoilTradingCard, rarity: EAssetRarity.Common), - CreateCard(2, appID, type: Asset.EType.Unknown, rarity: Asset.ERarity.Rare), - CreateCard(3, appID, type: Asset.EType.Unknown, rarity: Asset.ERarity.Rare), - CreateCard(4, appID, type: Asset.EType.Unknown, rarity: Asset.ERarity.Rare) + CreateCard(2, realAppID: appID, type: EAssetType.Unknown, rarity: EAssetRarity.Rare), + CreateCard(3, realAppID: appID, type: EAssetType.Unknown, rarity: EAssetRarity.Rare), + CreateCard(4, realAppID: appID, type: EAssetType.Unknown, rarity: EAssetRarity.Rare) ]; HashSet itemsToSend = GetItemsForFullBadge(items, 3, appID); @@ -177,8 +177,8 @@ public sealed class Bot { const uint appID = 42; HashSet items = [ - CreateCard(1, appID), - CreateCard(2, appID) + CreateCard(1, realAppID: appID), + CreateCard(2, realAppID: appID) ]; HashSet itemsToSend = GetItemsForFullBadge(items, 3, appID); @@ -192,8 +192,8 @@ public sealed class Bot { const uint appID = 42; HashSet items = [ - CreateCard(1, appID), - CreateCard(2, appID) + CreateCard(1, realAppID: appID), + CreateCard(2, realAppID: appID) ]; HashSet itemsToSend = GetItemsForFullBadge(items, 2, appID); @@ -212,8 +212,8 @@ public sealed class Bot { const uint appID1 = 43; HashSet items = [ - CreateCard(1, appID0), - CreateCard(1, appID1) + CreateCard(1, realAppID: appID0), + CreateCard(1, realAppID: appID1) ]; HashSet itemsToSend = GetItemsForFullBadge( @@ -237,8 +237,8 @@ public sealed class Bot { const uint appID1 = 43; HashSet items = [ - CreateCard(1, appID0), - CreateCard(1, appID1) + CreateCard(1, realAppID: appID0), + CreateCard(1, realAppID: appID1) ]; HashSet itemsToSend = GetItemsForFullBadge( @@ -260,12 +260,12 @@ public sealed class Bot { const uint appID2 = 44; HashSet items = [ - CreateCard(1, appID0), - CreateCard(2, appID0), + CreateCard(1, realAppID: appID0), + CreateCard(2, realAppID: appID0), - CreateCard(1, appID1), - CreateCard(2, appID1), - CreateCard(3, appID1) + CreateCard(1, realAppID: appID1), + CreateCard(2, realAppID: appID1), + CreateCard(3, realAppID: appID1) ]; HashSet itemsToSend = GetItemsForFullBadge( @@ -290,8 +290,8 @@ public sealed class Bot { const uint appID = 42; HashSet items = [ - CreateCard(1, appID, rarity: Asset.ERarity.Common), - CreateCard(1, appID, rarity: Asset.ERarity.Rare) + CreateCard(1, realAppID: appID, rarity: EAssetRarity.Common), + CreateCard(1, realAppID: appID, rarity: EAssetRarity.Rare) ]; HashSet itemsToSend = GetItemsForFullBadge(items, 1, appID); @@ -308,8 +308,8 @@ public sealed class Bot { const uint appID = 42; HashSet items = [ - CreateCard(1, appID, rarity: Asset.ERarity.Common), - CreateCard(1, appID, rarity: Asset.ERarity.Rare) + CreateCard(1, realAppID: appID, rarity: EAssetRarity.Common), + CreateCard(1, realAppID: appID, rarity: EAssetRarity.Rare) ]; HashSet itemsToSend = GetItemsForFullBadge(items, 2, appID); @@ -324,11 +324,11 @@ public sealed class Bot { const uint appID = 42; HashSet items = [ - CreateCard(1, appID, rarity: Asset.ERarity.Common), - CreateCard(2, appID, rarity: Asset.ERarity.Common), - CreateCard(1, appID, rarity: Asset.ERarity.Uncommon), - CreateCard(2, appID, rarity: Asset.ERarity.Uncommon), - CreateCard(3, appID, rarity: Asset.ERarity.Uncommon) + CreateCard(1, realAppID: appID, rarity: EAssetRarity.Common), + CreateCard(2, realAppID: appID, rarity: EAssetRarity.Common), + CreateCard(1, realAppID: appID, rarity: EAssetRarity.Uncommon), + CreateCard(2, realAppID: appID, rarity: EAssetRarity.Uncommon), + CreateCard(3, realAppID: appID, rarity: EAssetRarity.Uncommon) ]; HashSet itemsToSend = GetItemsForFullBadge(items, 3, appID); @@ -347,8 +347,8 @@ public sealed class Bot { const uint appID = 42; HashSet items = [ - CreateCard(1, appID, type: Asset.EType.TradingCard), - CreateCard(1, appID, type: Asset.EType.FoilTradingCard) + CreateCard(1, realAppID: appID, type: EAssetType.TradingCard), + CreateCard(1, realAppID: appID, type: EAssetType.FoilTradingCard) ]; HashSet itemsToSend = GetItemsForFullBadge(items, 1, appID); @@ -365,8 +365,8 @@ public sealed class Bot { const uint appID = 42; HashSet items = [ - CreateCard(1, appID, type: Asset.EType.TradingCard), - CreateCard(1, appID, type: Asset.EType.FoilTradingCard) + CreateCard(1, realAppID: appID, type: EAssetType.TradingCard), + CreateCard(1, realAppID: appID, type: EAssetType.FoilTradingCard) ]; HashSet itemsToSend = GetItemsForFullBadge(items, 2, appID); @@ -381,11 +381,11 @@ public sealed class Bot { const uint appID = 42; HashSet items = [ - CreateCard(1, appID, type: Asset.EType.TradingCard), - CreateCard(2, appID, type: Asset.EType.TradingCard), - CreateCard(1, appID, type: Asset.EType.FoilTradingCard), - CreateCard(2, appID, type: Asset.EType.FoilTradingCard), - CreateCard(3, appID, type: Asset.EType.FoilTradingCard) + CreateCard(1, realAppID: appID, type: EAssetType.TradingCard), + CreateCard(2, realAppID: appID, type: EAssetType.TradingCard), + CreateCard(1, realAppID: appID, type: EAssetType.FoilTradingCard), + CreateCard(2, realAppID: appID, type: EAssetType.FoilTradingCard), + CreateCard(3, realAppID: appID, type: EAssetType.FoilTradingCard) ]; HashSet itemsToSend = GetItemsForFullBadge(items, 3, appID); @@ -404,8 +404,8 @@ public sealed class Bot { const uint appID0 = 42; HashSet items = [ - CreateCard(1, appID0, 2), - CreateCard(2, appID0) + CreateCard(1, amount: 2, realAppID: appID0), + CreateCard(2, realAppID: appID0) ]; HashSet itemsToSend = GetItemsForFullBadge(items, 2, appID0); @@ -425,8 +425,8 @@ public sealed class Bot { HashSet items = []; for (byte i = 0; i < Steam.Exchange.Trading.MaxItemsPerTrade; i++) { - items.Add(CreateCard(1, appID)); - items.Add(CreateCard(2, appID)); + items.Add(CreateCard(1, realAppID: appID)); + items.Add(CreateCard(2, realAppID: appID)); } HashSet itemsToSend = GetItemsForFullBadge(items, 2, appID); @@ -442,10 +442,10 @@ public sealed class Bot { HashSet items = []; for (byte i = 0; i < 100; i++) { - items.Add(CreateCard(1, appID0)); - items.Add(CreateCard(2, appID0)); - items.Add(CreateCard(1, appID1)); - items.Add(CreateCard(2, appID1)); + items.Add(CreateCard(1, realAppID: appID0)); + items.Add(CreateCard(2, realAppID: appID0)); + items.Add(CreateCard(1, realAppID: appID1)); + items.Add(CreateCard(2, realAppID: appID1)); } Dictionary itemsPerSet = new() { @@ -465,10 +465,10 @@ public sealed class Bot { const uint appID2 = 44; HashSet items = [ - CreateCard(1, appID0), - CreateCard(2, appID0), - CreateCard(3, appID0), - CreateCard(4, appID0) + CreateCard(1, realAppID: appID0), + CreateCard(2, realAppID: appID0), + CreateCard(3, realAppID: appID0), + CreateCard(4, realAppID: appID0) ]; Assert.ThrowsException( @@ -491,12 +491,12 @@ public sealed class Bot { Assert.IsTrue(expectedResult.All(expectation => realResult.TryGetValue(expectation.Key, out long reality) && (expectation.Value == reality))); } - private static Asset CreateCard(ulong classID, uint realAppID, uint amount = 1, Asset.EType type = Asset.EType.TradingCard, Asset.ERarity rarity = Asset.ERarity.Common) => new(Asset.SteamAppID, Asset.SteamCommunityContextID, classID, amount, new InventoryDescription(), realAppID, type, rarity); + private static Asset CreateCard(ulong classID, ulong instanceID = 0, uint amount = 1, bool marketable = false, bool tradable = false, uint realAppID = Asset.SteamAppID, EAssetType type = EAssetType.TradingCard, EAssetRarity rarity = EAssetRarity.Common) => new(Asset.SteamAppID, Asset.SteamCommunityContextID, classID, amount, new InventoryDescription(Asset.SteamAppID, classID, instanceID, marketable, tradable, realAppID, type, rarity)); private static HashSet GetItemsForFullBadge(IReadOnlyCollection inventory, byte cardsPerSet, uint appID, ushort maxItems = Steam.Exchange.Trading.MaxItemsPerTrade) => GetItemsForFullBadge(inventory, new Dictionary { { appID, cardsPerSet } }, maxItems); private static HashSet GetItemsForFullBadge(IReadOnlyCollection inventory, IDictionary cardsPerSet, ushort maxItems = Steam.Exchange.Trading.MaxItemsPerTrade) { - Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), List> inventorySets = Steam.Exchange.Trading.GetInventorySets(inventory); + Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), List> inventorySets = Steam.Exchange.Trading.GetInventorySets(inventory); return GetItemsForFullSets(inventory, inventorySets.ToDictionary(static kv => kv.Key, kv => (SetsToExtract: inventorySets[kv.Key][0], cardsPerSet[kv.Key.RealAppID])), maxItems).ToHashSet(); } diff --git a/ArchiSteamFarm.Tests/Trading.cs b/ArchiSteamFarm.Tests/Trading.cs index 654e2eca2..46c24dff8 100644 --- a/ArchiSteamFarm.Tests/Trading.cs +++ b/ArchiSteamFarm.Tests/Trading.cs @@ -5,16 +5,16 @@ // / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | // /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| // ---------------------------------------------------------------------------------------------- -// +// // Copyright 2015-2024 Ł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. @@ -33,18 +33,18 @@ public sealed class Trading { [TestMethod] public void ExploitingNewSetsIsFairButNotNeutral() { HashSet inventory = [ - CreateItem(1, 40), - CreateItem(2, 10), - CreateItem(3, 10) + CreateItem(1, amount: 40), + CreateItem(2, amount: 10), + CreateItem(3, amount: 10) ]; HashSet itemsToGive = [ - CreateItem(2, 5), - CreateItem(3, 5) + CreateItem(2, amount: 5), + CreateItem(3, amount: 5) ]; HashSet itemsToReceive = [ - CreateItem(1, 9), + CreateItem(1, amount: 9), CreateItem(4) ]; @@ -54,24 +54,39 @@ public sealed class Trading { [TestMethod] public void MismatchRarityIsNotFair() { - HashSet itemsToGive = [CreateItem(1, rarity: Asset.ERarity.Rare)]; - HashSet itemsToReceive = [CreateItem(2)]; + HashSet itemsToGive = [ + CreateItem(1, rarity: EAssetRarity.Rare) + ]; + + HashSet itemsToReceive = [ + CreateItem(2) + ]; Assert.IsFalse(IsFairExchange(itemsToGive, itemsToReceive)); } [TestMethod] public void MismatchRealAppIDsIsNotFair() { - HashSet itemsToGive = [CreateItem(1, realAppID: 570)]; - HashSet itemsToReceive = [CreateItem(2)]; + HashSet itemsToGive = [ + CreateItem(1, realAppID: 570) + ]; + + HashSet itemsToReceive = [ + CreateItem(2) + ]; Assert.IsFalse(IsFairExchange(itemsToGive, itemsToReceive)); } [TestMethod] public void MismatchTypesIsNotFair() { - HashSet itemsToGive = [CreateItem(1, type: Asset.EType.Emoticon)]; - HashSet itemsToReceive = [CreateItem(2)]; + HashSet itemsToGive = [ + CreateItem(1, type: EAssetType.Emoticon) + ]; + + HashSet itemsToReceive = [ + CreateItem(2) + ]; Assert.IsFalse(IsFairExchange(itemsToGive, itemsToReceive)); } @@ -79,19 +94,19 @@ public sealed class Trading { [TestMethod] public void MultiGameMultiTypeBadReject() { HashSet inventory = [ - CreateItem(1, 9), - CreateItem(3, 9, 730, Asset.EType.Emoticon), - CreateItem(4, realAppID: 730, type: Asset.EType.Emoticon) + CreateItem(1, amount: 9), + CreateItem(3, amount: 9, realAppID: 730, type: EAssetType.Emoticon), + CreateItem(4, realAppID: 730, type: EAssetType.Emoticon) ]; HashSet itemsToGive = [ CreateItem(1), - CreateItem(4, realAppID: 730, type: Asset.EType.Emoticon) + CreateItem(4, realAppID: 730, type: EAssetType.Emoticon) ]; HashSet itemsToReceive = [ CreateItem(2), - CreateItem(3, realAppID: 730, type: Asset.EType.Emoticon) + CreateItem(3, realAppID: 730, type: EAssetType.Emoticon) ]; Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive)); @@ -101,18 +116,18 @@ public sealed class Trading { [TestMethod] public void MultiGameMultiTypeNeutralAccept() { HashSet inventory = [ - CreateItem(1, 9), - CreateItem(3, realAppID: 730, type: Asset.EType.Emoticon) + CreateItem(1, amount: 9), + CreateItem(3, realAppID: 730, type: EAssetType.Emoticon) ]; HashSet itemsToGive = [ CreateItem(1), - CreateItem(3, realAppID: 730, type: Asset.EType.Emoticon) + CreateItem(3, realAppID: 730, type: EAssetType.Emoticon) ]; HashSet itemsToReceive = [ CreateItem(2), - CreateItem(4, realAppID: 730, type: Asset.EType.Emoticon) + CreateItem(4, realAppID: 730, type: EAssetType.Emoticon) ]; Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive)); @@ -122,7 +137,7 @@ public sealed class Trading { [TestMethod] public void MultiGameSingleTypeBadReject() { HashSet inventory = [ - CreateItem(1, 9), + CreateItem(1, amount: 9), CreateItem(3, realAppID: 730), CreateItem(4, realAppID: 730) ]; @@ -144,7 +159,7 @@ public sealed class Trading { [TestMethod] public void MultiGameSingleTypeNeutralAccept() { HashSet inventory = [ - CreateItem(1, 2), + CreateItem(1, amount: 2), CreateItem(3, realAppID: 730) ]; @@ -166,15 +181,19 @@ public sealed class Trading { public void SingleGameAbrynosWasWrongNeutralAccept() { HashSet inventory = [ CreateItem(1), - CreateItem(2, 2), + CreateItem(2, amount: 2), CreateItem(3), CreateItem(4), CreateItem(5) ]; - HashSet itemsToGive = [CreateItem(2)]; + HashSet itemsToGive = [ + CreateItem(2) + ]; - HashSet itemsToReceive = [CreateItem(3)]; + HashSet itemsToReceive = [ + CreateItem(3) + ]; Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive)); Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive)); @@ -182,13 +201,17 @@ public sealed class Trading { [TestMethod] public void SingleGameDonationAccept() { - HashSet inventory = [CreateItem(1)]; + HashSet inventory = [ + CreateItem(1) + ]; - HashSet itemsToGive = [CreateItem(1)]; + HashSet itemsToGive = [ + CreateItem(1) + ]; HashSet itemsToReceive = [ CreateItem(2), - CreateItem(3, type: Asset.EType.SteamGems) + CreateItem(3, type: EAssetType.SteamGems) ]; Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive)); @@ -198,19 +221,19 @@ public sealed class Trading { [TestMethod] public void SingleGameMultiTypeBadReject() { HashSet inventory = [ - CreateItem(1, 9), - CreateItem(3, 9, type: Asset.EType.Emoticon), - CreateItem(4, type: Asset.EType.Emoticon) + CreateItem(1, amount: 9), + CreateItem(3, amount: 9, type: EAssetType.Emoticon), + CreateItem(4, type: EAssetType.Emoticon) ]; HashSet itemsToGive = [ CreateItem(1), - CreateItem(4, type: Asset.EType.Emoticon) + CreateItem(4, type: EAssetType.Emoticon) ]; HashSet itemsToReceive = [ CreateItem(2), - CreateItem(3, type: Asset.EType.Emoticon) + CreateItem(3, type: EAssetType.Emoticon) ]; Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive)); @@ -220,18 +243,18 @@ public sealed class Trading { [TestMethod] public void SingleGameMultiTypeNeutralAccept() { HashSet inventory = [ - CreateItem(1, 9), - CreateItem(3, type: Asset.EType.Emoticon) + CreateItem(1, amount: 9), + CreateItem(3, type: EAssetType.Emoticon) ]; HashSet itemsToGive = [ CreateItem(1), - CreateItem(3, type: Asset.EType.Emoticon) + CreateItem(3, type: EAssetType.Emoticon) ]; HashSet itemsToReceive = [ CreateItem(2), - CreateItem(4, type: Asset.EType.Emoticon) + CreateItem(4, type: EAssetType.Emoticon) ]; Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive)); @@ -252,7 +275,9 @@ public sealed class Trading { CreateItem(3) ]; - HashSet itemsToReceive = [CreateItem(4, 3)]; + HashSet itemsToReceive = [ + CreateItem(4, amount: 3) + ]; Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive)); Assert.IsFalse(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive)); @@ -262,15 +287,17 @@ public sealed class Trading { public void SingleGameQuantityBadReject2() { HashSet inventory = [ CreateItem(1), - CreateItem(2, 2) + CreateItem(2, amount: 2) ]; HashSet itemsToGive = [ CreateItem(1), - CreateItem(2, 2) + CreateItem(2, amount: 2) ]; - HashSet itemsToReceive = [CreateItem(3, 3)]; + HashSet itemsToReceive = [ + CreateItem(3, amount: 3) + ]; Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive)); Assert.IsFalse(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive)); @@ -279,7 +306,7 @@ public sealed class Trading { [TestMethod] public void SingleGameQuantityNeutralAccept() { HashSet inventory = [ - CreateItem(1, 2), + CreateItem(1, amount: 2), CreateItem(2) ]; @@ -288,7 +315,9 @@ public sealed class Trading { CreateItem(2) ]; - HashSet itemsToReceive = [CreateItem(3, 2)]; + HashSet itemsToReceive = [ + CreateItem(3, amount: 2) + ]; Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive)); Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive)); @@ -301,8 +330,13 @@ public sealed class Trading { CreateItem(2) ]; - HashSet itemsToGive = [CreateItem(1)]; - HashSet itemsToReceive = [CreateItem(2)]; + HashSet itemsToGive = [ + CreateItem(1) + ]; + + HashSet itemsToReceive = [ + CreateItem(2) + ]; Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive)); Assert.IsFalse(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive)); @@ -311,12 +345,14 @@ public sealed class Trading { [TestMethod] public void SingleGameSingleTypeBadWithOverpayingReject() { HashSet inventory = [ - CreateItem(1, 2), - CreateItem(2, 2), - CreateItem(3, 2) + CreateItem(1, amount: 2), + CreateItem(2, amount: 2), + CreateItem(3, amount: 2) ]; - HashSet itemsToGive = [CreateItem(2)]; + HashSet itemsToGive = [ + CreateItem(2) + ]; HashSet itemsToReceive = [ CreateItem(1), @@ -331,12 +367,17 @@ public sealed class Trading { public void SingleGameSingleTypeBigDifferenceAccept() { HashSet inventory = [ CreateItem(1), - CreateItem(2, 5), + CreateItem(2, amount: 5), CreateItem(3) ]; - HashSet itemsToGive = [CreateItem(2)]; - HashSet itemsToReceive = [CreateItem(3)]; + HashSet itemsToGive = [ + CreateItem(2) + ]; + + HashSet itemsToReceive = [ + CreateItem(3) + ]; Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive)); Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive)); @@ -346,10 +387,10 @@ public sealed class Trading { public void SingleGameSingleTypeBigDifferenceReject() { HashSet inventory = [ CreateItem(1), - CreateItem(2, 2), - CreateItem(3, 2), - CreateItem(4, 3), - CreateItem(5, 10) + CreateItem(2, amount: 2), + CreateItem(3, amount: 2), + CreateItem(4, amount: 3), + CreateItem(5, amount: 10) ]; HashSet itemsToGive = [ @@ -368,9 +409,17 @@ public sealed class Trading { [TestMethod] public void SingleGameSingleTypeGoodAccept() { - HashSet inventory = [CreateItem(1, 2)]; - HashSet itemsToGive = [CreateItem(1)]; - HashSet itemsToReceive = [CreateItem(2)]; + HashSet inventory = [ + CreateItem(1, amount: 2) + ]; + + HashSet itemsToGive = [ + CreateItem(1) + ]; + + HashSet itemsToReceive = [ + CreateItem(2) + ]; Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive)); Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive)); @@ -378,9 +427,17 @@ public sealed class Trading { [TestMethod] public void SingleGameSingleTypeNeutralAccept() { - HashSet inventory = [CreateItem(1)]; - HashSet itemsToGive = [CreateItem(1)]; - HashSet itemsToReceive = [CreateItem(2)]; + HashSet inventory = [ + CreateItem(1) + ]; + + HashSet itemsToGive = [ + CreateItem(1) + ]; + + HashSet itemsToReceive = [ + CreateItem(2) + ]; Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive)); Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive)); @@ -389,11 +446,13 @@ public sealed class Trading { [TestMethod] public void SingleGameSingleTypeNeutralWithOverpayingAccept() { HashSet inventory = [ - CreateItem(1, 2), - CreateItem(2, 2) + CreateItem(1, amount: 2), + CreateItem(2, amount: 2) ]; - HashSet itemsToGive = [CreateItem(2)]; + HashSet itemsToGive = [ + CreateItem(2) + ]; HashSet itemsToReceive = [ CreateItem(1), @@ -407,26 +466,28 @@ public sealed class Trading { [TestMethod] public void TakingExcessiveAmountOfSingleCardCanStillBeFairAndNeutral() { HashSet inventory = [ - CreateItem(1, 52), - CreateItem(2, 73), - CreateItem(3, 52), - CreateItem(4, 47), + CreateItem(1, amount: 52), + CreateItem(2, amount: 73), + CreateItem(3, amount: 52), + CreateItem(4, amount: 47), CreateItem(5) ]; - HashSet itemsToGive = [CreateItem(2, 73)]; + HashSet itemsToGive = [ + CreateItem(2, amount: 73) + ]; HashSet itemsToReceive = [ - CreateItem(1, 9), - CreateItem(3, 9), - CreateItem(4, 8), - CreateItem(5, 24), - CreateItem(6, 23) + CreateItem(1, amount: 9), + CreateItem(3, amount: 9), + CreateItem(4, amount: 8), + CreateItem(5, amount: 24), + CreateItem(6, amount: 23) ]; Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive)); Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive)); } - private static Asset CreateItem(ulong classID, uint amount = 1, uint realAppID = Asset.SteamAppID, Asset.EType type = Asset.EType.TradingCard, Asset.ERarity rarity = Asset.ERarity.Common) => new(Asset.SteamAppID, Asset.SteamCommunityContextID, classID, amount, new InventoryDescription(), realAppID, type, rarity); + private static Asset CreateItem(ulong classID, ulong instanceID = 0, uint amount = 1, bool marketable = false, bool tradable = false, uint realAppID = Asset.SteamAppID, EAssetType type = EAssetType.TradingCard, EAssetRarity rarity = EAssetRarity.Common) => new(Asset.SteamAppID, Asset.SteamCommunityContextID, classID, amount, new InventoryDescription(Asset.SteamAppID, classID, instanceID, marketable, tradable, realAppID, type, rarity)); } diff --git a/ArchiSteamFarm.sln.DotSettings b/ArchiSteamFarm.sln.DotSettings index bc35f16b5..a95d2582a 100644 --- a/ArchiSteamFarm.sln.DotSettings +++ b/ArchiSteamFarm.sln.DotSettings @@ -719,16 +719,16 @@ / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| ---------------------------------------------------------------------------------------------- - +| Copyright 2015-${CurrentDate.Year} Ł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. diff --git a/ArchiSteamFarm/Steam/Bot.cs b/ArchiSteamFarm/Steam/Bot.cs index b713edf31..858005b2f 100644 --- a/ArchiSteamFarm/Steam/Bot.cs +++ b/ArchiSteamFarm/Steam/Bot.cs @@ -5,16 +5,16 @@ // / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | // /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| // ---------------------------------------------------------------------------------------------- -// +// // Copyright 2015-2024 Ł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. @@ -684,7 +684,7 @@ public sealed class Bot : IAsyncDisposable, IDisposable { public T? GetHandler() where T : ClientMsgHandler => SteamClient.GetHandler(); [PublicAPI] - public static HashSet GetItemsForFullSets(IReadOnlyCollection inventory, IReadOnlyDictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), (uint SetsToExtract, byte ItemsPerSet)> amountsToExtract, ushort maxItems = Trading.MaxItemsPerTrade) { + public static HashSet GetItemsForFullSets(IReadOnlyCollection inventory, IReadOnlyDictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), (uint SetsToExtract, byte ItemsPerSet)> amountsToExtract, ushort maxItems = Trading.MaxItemsPerTrade) { if ((inventory == null) || (inventory.Count == 0)) { throw new ArgumentNullException(nameof(inventory)); } @@ -696,9 +696,9 @@ public sealed class Bot : IAsyncDisposable, IDisposable { ArgumentOutOfRangeException.ThrowIfLessThan(maxItems, MinCardsPerBadge); HashSet result = []; - Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), Dictionary>> itemsPerClassIDPerSet = inventory.GroupBy(static item => (item.RealAppID, item.Type, item.Rarity)).ToDictionary(static grouping => grouping.Key, static grouping => grouping.GroupBy(static item => item.ClassID).ToDictionary(static group => group.Key, static group => group.ToHashSet())); + Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), Dictionary>> itemsPerClassIDPerSet = inventory.GroupBy(static item => (item.RealAppID, item.Type, item.Rarity)).ToDictionary(static grouping => grouping.Key, static grouping => grouping.GroupBy(static item => item.ClassID).ToDictionary(static group => group.Key, static group => group.ToHashSet())); - foreach (((uint RealAppID, Asset.EType Type, Asset.ERarity Rarity) set, (uint setsToExtract, byte itemsPerSet)) in amountsToExtract.OrderBy(static kv => kv.Value.ItemsPerSet)) { + foreach (((uint RealAppID, EAssetType Type, EAssetRarity Rarity) set, (uint setsToExtract, byte itemsPerSet)) in amountsToExtract.OrderBy(static kv => kv.Value.ItemsPerSet)) { if (!itemsPerClassIDPerSet.TryGetValue(set, out Dictionary>? itemsPerClassID)) { continue; } @@ -3662,7 +3662,7 @@ public sealed class Bot : IAsyncDisposable, IDisposable { return; } - Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), List> inventorySets = Trading.GetInventorySets(inventory); + Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), List> inventorySets = Trading.GetInventorySets(inventory); // Filter appIDs that can't possibly be completed due to having less cards than smallest badges possible appIDs.IntersectWith(inventorySets.Where(static kv => kv.Value.Count >= MinCardsPerBadge).Select(static kv => kv.Key.RealAppID)); @@ -3677,9 +3677,9 @@ public sealed class Bot : IAsyncDisposable, IDisposable { return; } - Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), (uint Sets, byte CardsPerSet)> itemsToTakePerInventorySet = new(); + Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), (uint Sets, byte CardsPerSet)> itemsToTakePerInventorySet = new(); - foreach (((uint RealAppID, Asset.EType Type, Asset.ERarity Rarity) key, List amounts) in inventorySets.Where(set => appIDs.Contains(set.Key.RealAppID))) { + foreach (((uint RealAppID, EAssetType Type, EAssetRarity Rarity) key, List amounts) in inventorySets.Where(set => appIDs.Contains(set.Key.RealAppID))) { if (!cardsCountPerAppID.TryGetValue(key.RealAppID, out byte cardsCount) || (cardsCount == 0)) { throw new InvalidOperationException(nameof(cardsCount)); } diff --git a/ArchiSteamFarm/Steam/Data/APIWrappedResponse.cs b/ArchiSteamFarm/Steam/Data/APIWrappedResponse.cs index 961969f66..b3891246a 100644 --- a/ArchiSteamFarm/Steam/Data/APIWrappedResponse.cs +++ b/ArchiSteamFarm/Steam/Data/APIWrappedResponse.cs @@ -5,29 +5,34 @@ // / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | // /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| // ---------------------------------------------------------------------------------------------- -// +// // Copyright 2015-2024 Ł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.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; namespace ArchiSteamFarm.Steam.Data; -public class APIWrappedResponse where T : class { +[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")] +public sealed class APIWrappedResponse where T : class { [JsonInclude] [JsonPropertyName("response")] [JsonRequired] public T Response { get; private init; } = null!; + + [JsonConstructor] + private APIWrappedResponse() { } } diff --git a/ArchiSteamFarm/Steam/Data/Asset.cs b/ArchiSteamFarm/Steam/Data/Asset.cs index f9262c178..21d8d76f8 100644 --- a/ArchiSteamFarm/Steam/Data/Asset.cs +++ b/ArchiSteamFarm/Steam/Data/Asset.cs @@ -5,16 +5,16 @@ // / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | // /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| // ---------------------------------------------------------------------------------------------- -// +// | // Copyright 2015-2024 Ł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. @@ -22,7 +22,6 @@ // limitations under the License. using System; -using System.Collections.Immutable; using System.Text.Json.Serialization; using JetBrains.Annotations; @@ -45,36 +44,23 @@ public sealed class Asset { [JsonIgnore] [PublicAPI] - public bool Marketable => Description.Marketable; + public bool Marketable => Description?.Marketable ?? false; [JsonIgnore] [PublicAPI] - public ERarity Rarity => OverriddenRarity ?? Description.Rarity; + public EAssetRarity Rarity => Description?.Rarity ?? EAssetRarity.Unknown; [JsonIgnore] [PublicAPI] - public uint RealAppID => OverriddenRealAppID ?? Description.RealAppID; + public uint RealAppID => Description?.RealAppID ?? 0; [JsonIgnore] [PublicAPI] - public ImmutableHashSet Tags => Description.Tags; + public bool Tradable => Description?.Tradable ?? false; [JsonIgnore] [PublicAPI] - public bool Tradable => Description.Tradable; - - [JsonIgnore] - [PublicAPI] - public EType Type => OverriddenType ?? Description.Type; - - [JsonIgnore] - private ERarity? OverriddenRarity { get; } - - [JsonIgnore] - private uint? OverriddenRealAppID { get; } - - [JsonIgnore] - private EType? OverriddenType { get; } + public EAssetType Type => Description?.Type ?? EAssetType.Unknown; [JsonInclude] [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)] @@ -105,8 +91,9 @@ public sealed class Asset { [PublicAPI] public ulong ContextID { get; private init; } + [JsonIgnore] [PublicAPI] - public InventoryDescription Description { get; internal set; } = null!; + public InventoryDescription? Description { get; internal set; } [JsonInclude] [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)] @@ -122,61 +109,25 @@ public sealed class Asset { init => AssetID = value; } - internal Asset(uint appID, ulong contextID, ulong classID, uint amount, InventoryDescription description, uint realAppID, EType? type, ERarity? rarity, ulong assetID = 0, ulong instanceID = 0) : this(appID, contextID, classID, amount, description, assetID, instanceID) { - ArgumentOutOfRangeException.ThrowIfZero(realAppID); - - OverriddenRealAppID = realAppID; - OverriddenType = type; - OverriddenRarity = rarity; - } - internal Asset(uint appID, ulong contextID, ulong classID, uint amount, InventoryDescription description, ulong assetID = 0, ulong instanceID = 0) { ArgumentOutOfRangeException.ThrowIfZero(appID); ArgumentOutOfRangeException.ThrowIfZero(contextID); ArgumentOutOfRangeException.ThrowIfZero(classID); ArgumentOutOfRangeException.ThrowIfZero(amount); + ArgumentNullException.ThrowIfNull(description); AppID = appID; ContextID = contextID; ClassID = classID; Amount = amount; Description = description; - InstanceID = instanceID; + AssetID = assetID; + InstanceID = instanceID; } [JsonConstructor] private Asset() { } - [UsedImplicitly] - public static bool ShouldSerializeAdditionalProperties() => false; - internal Asset CreateShallowCopy() => (Asset) MemberwiseClone(); - - public enum ERarity : byte { - Unknown, - Common, - Uncommon, - Rare - } - - public enum EType : byte { - Unknown, - BoosterPack, - Emoticon, - FoilTradingCard, - ProfileBackground, - TradingCard, - SteamGems, - SaleItem, - Consumable, - ProfileModifier, - Sticker, - ChatEffect, - MiniProfileBackground, - AvatarProfileFrame, - AnimatedAvatar, - KeyboardSkin, - StartupVideo - } } diff --git a/ArchiSteamFarm/Steam/Data/EAssetRarity.cs b/ArchiSteamFarm/Steam/Data/EAssetRarity.cs new file mode 100644 index 000000000..3b97ebf43 --- /dev/null +++ b/ArchiSteamFarm/Steam/Data/EAssetRarity.cs @@ -0,0 +1,31 @@ +// ---------------------------------------------------------------------------------------------- +// _ _ _ ____ _ _____ +// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___ +// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \ +// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | +// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| +// ---------------------------------------------------------------------------------------------- +// | +// Copyright 2015-2024 Ł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. + +namespace ArchiSteamFarm.Steam.Data; + +public enum EAssetRarity : byte { + Unknown, + Common, + Uncommon, + Rare +} diff --git a/ArchiSteamFarm/Steam/Data/EAssetType.cs b/ArchiSteamFarm/Steam/Data/EAssetType.cs new file mode 100644 index 000000000..f0eba3bbc --- /dev/null +++ b/ArchiSteamFarm/Steam/Data/EAssetType.cs @@ -0,0 +1,44 @@ +// ---------------------------------------------------------------------------------------------- +// _ _ _ ____ _ _____ +// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___ +// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \ +// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | +// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| +// ---------------------------------------------------------------------------------------------- +// | +// Copyright 2015-2024 Ł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. + +namespace ArchiSteamFarm.Steam.Data; + +public enum EAssetType : byte { + Unknown, + BoosterPack, + Emoticon, + FoilTradingCard, + ProfileBackground, + TradingCard, + SteamGems, + SaleItem, + Consumable, + ProfileModifier, + Sticker, + ChatEffect, + MiniProfileBackground, + AvatarProfileFrame, + AnimatedAvatar, + KeyboardSkin, + StartupVideo +} diff --git a/ArchiSteamFarm/Steam/Data/InventoryDescription.cs b/ArchiSteamFarm/Steam/Data/InventoryDescription.cs index e4700b6e4..4760d924a 100644 --- a/ArchiSteamFarm/Steam/Data/InventoryDescription.cs +++ b/ArchiSteamFarm/Steam/Data/InventoryDescription.cs @@ -5,16 +5,16 @@ // / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | // /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| // ---------------------------------------------------------------------------------------------- -// +// | // Copyright 2015-2024 Ł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. @@ -22,8 +22,8 @@ // limitations under the License. using System; -using System.Collections.Generic; using System.Collections.Immutable; +using System.ComponentModel; using System.Globalization; using System.Linq; using System.Text.Json.Serialization; @@ -38,140 +38,21 @@ namespace ArchiSteamFarm.Steam.Data; [PublicAPI] public sealed class InventoryDescription { [JsonIgnore] - public CEconItem_Description ProtobufBody { get; } = new(); - - internal Asset.ERarity Rarity { - get { - foreach (Tag tag in Tags) { - switch (tag.Identifier) { - case "droprate": - switch (tag.Value) { - case "droprate_0": - return Asset.ERarity.Common; - case "droprate_1": - return Asset.ERarity.Uncommon; - case "droprate_2": - return Asset.ERarity.Rare; - default: - ASF.ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.WarningUnknownValuePleaseReport, nameof(tag.Value), tag.Value)); - - break; - } - - break; - } - } - - return Asset.ERarity.Unknown; - } - } - - internal uint RealAppID { - get { - foreach (Tag tag in Tags) { - switch (tag.Identifier) { - case "Game": - if (string.IsNullOrEmpty(tag.Value) || (tag.Value.Length <= 4) || !tag.Value.StartsWith("app_", StringComparison.Ordinal)) { - ASF.ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.WarningUnknownValuePleaseReport, nameof(tag.Value), tag.Value)); - - break; - } - - string appIDText = tag.Value[4..]; - - if (!uint.TryParse(appIDText, out uint appID) || (appID == 0)) { - ASF.ArchiLogger.LogNullError(appID); - - break; - } - - return appID; - } - } - - return 0; - } - } - - internal Asset.EType Type { - get { - Asset.EType type = Asset.EType.Unknown; - - foreach (Tag tag in Tags) { - switch (tag.Identifier) { - case "cardborder": - switch (tag.Value) { - case "cardborder_0": - return Asset.EType.TradingCard; - case "cardborder_1": - return Asset.EType.FoilTradingCard; - default: - ASF.ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.WarningUnknownValuePleaseReport, nameof(tag.Value), tag.Value)); - - return Asset.EType.Unknown; - } - case "item_class": - switch (tag.Value) { - case "item_class_2": - if (type == Asset.EType.Unknown) { - // This is a fallback in case we'd have no cardborder available to interpret - type = Asset.EType.TradingCard; - } - - continue; - case "item_class_3": - return Asset.EType.ProfileBackground; - case "item_class_4": - return Asset.EType.Emoticon; - case "item_class_5": - return Asset.EType.BoosterPack; - case "item_class_6": - return Asset.EType.Consumable; - case "item_class_7": - return Asset.EType.SteamGems; - case "item_class_8": - return Asset.EType.ProfileModifier; - case "item_class_10": - return Asset.EType.SaleItem; - case "item_class_11": - return Asset.EType.Sticker; - case "item_class_12": - return Asset.EType.ChatEffect; - case "item_class_13": - return Asset.EType.MiniProfileBackground; - case "item_class_14": - return Asset.EType.AvatarProfileFrame; - case "item_class_15": - return Asset.EType.AnimatedAvatar; - case "item_class_16": - return Asset.EType.KeyboardSkin; - case "item_class_17": - return Asset.EType.StartupVideo; - default: - ASF.ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.WarningUnknownValuePleaseReport, nameof(tag.Value), tag.Value)); - - return Asset.EType.Unknown; - } - } - } - - return type; - } - } + public CEconItem_Description Body { get; } = new(); [JsonInclude] [JsonPropertyName("appid")] [JsonRequired] public uint AppID { - get => (uint) ProtobufBody.appid; - private init => ProtobufBody.appid = (int) value; + get => (uint) Body.appid; + private init => Body.appid = (int) value; } [JsonInclude] [JsonPropertyName("background_color")] public string BackgroundColor { - get => ProtobufBody.background_color; - private init => ProtobufBody.background_color = value; + get => Body.background_color; + private init => Body.background_color = value; } [JsonInclude] @@ -179,70 +60,71 @@ public sealed class InventoryDescription { [JsonPropertyName("classid")] [JsonRequired] public ulong ClassID { - get => ProtobufBody.classid; - private init => ProtobufBody.classid = value; + get => Body.classid; + private init => Body.classid = value; } [JsonInclude] [JsonPropertyName("commodity")] [JsonConverter(typeof(BooleanNumberConverter))] public bool Commodity { - get => ProtobufBody.commodity; - private init => ProtobufBody.commodity = value; + get => Body.commodity; + private init => Body.commodity = value; } [JsonInclude] [JsonPropertyName("currency")] [JsonConverter(typeof(BooleanNumberConverter))] public bool Currency { - get => ProtobufBody.currency; - private init => ProtobufBody.currency = value; + get => Body.currency; + private init => Body.currency = value; } [JsonInclude] [JsonPropertyName("descriptions")] public ImmutableHashSet Descriptions { - get => ProtobufBody.descriptions.Select(static description => new ItemDescription(description.type, description.value, description.color, description.label)).ToImmutableHashSet(); - private init { - ProtobufBody.descriptions.Clear(); + get => Body.descriptions.Select(static description => new ItemDescription(description.type, description.value, description.color, description.label)).ToImmutableHashSet(); - foreach (ItemDescription description in value) { - ProtobufBody.descriptions.Add( - new CEconItem_DescriptionLine { + private init { + Body.descriptions.Clear(); + + Body.descriptions.AddRange( + value.Select( + static description => new CEconItem_DescriptionLine { color = description.Color, label = description.Label, type = description.Type, value = description.Value } - ); - } + ) + ); } } +#pragma warning disable CA1056 // This property is not guaranteed to have parsable Uri [JsonInclude] [JsonPropertyName("icon_url")] -#pragma warning disable CA1056 // this is a JSON/Protobuf field, and even then it doesn't contain full URL public string IconURL { -#pragma warning restore CA1056 // this is a JSON/Protobuf field, and even then it doesn't contain full URL - get => ProtobufBody.icon_url; - private init => ProtobufBody.icon_url = value; + get => Body.icon_url; + private init => Body.icon_url = value; } +#pragma warning restore CA1056 // This property is not guaranteed to have parsable Uri +#pragma warning disable CA1056 // This property is not guaranteed to have parsable Uri [JsonInclude] [JsonPropertyName("icon_url_large")] -#pragma warning disable CA1056 // this is a JSON/Protobuf field, and even then it doesn't contain full URL public string IconURLLarge { -#pragma warning restore CA1056 // this is a JSON/Protobuf field, and even then it doesn't contain full URL - get => ProtobufBody.icon_url_large; - private init => ProtobufBody.icon_url_large = value; + get => Body.icon_url_large; + private init => Body.icon_url_large = value; } +#pragma warning restore CA1056 // This property is not guaranteed to have parsable Uri [JsonInclude] [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)] [JsonPropertyName("instanceid")] public ulong InstanceID { - get => ProtobufBody.instanceid; - private init => ProtobufBody.instanceid = value; + get => Body.instanceid; + private init => Body.instanceid = value; } [JsonInclude] @@ -250,74 +132,163 @@ public sealed class InventoryDescription { [JsonRequired] [JsonConverter(typeof(BooleanNumberConverter))] public bool Marketable { - get => ProtobufBody.marketable; - private init => ProtobufBody.marketable = value; + get => Body.marketable; + private init => Body.marketable = value; } [JsonInclude] [JsonPropertyName("market_fee_app")] public uint MarketFeeApp { - get => (uint) ProtobufBody.market_fee_app; - private init => ProtobufBody.market_fee_app = (int) value; + get => (uint) Body.market_fee_app; + private init => Body.market_fee_app = (int) value; } [JsonInclude] [JsonPropertyName("market_hash_name")] public string MarketHashName { - get => ProtobufBody.market_hash_name; - private init => ProtobufBody.market_hash_name = value; + get => Body.market_hash_name; + private init => Body.market_hash_name = value; } [JsonInclude] [JsonPropertyName("market_name")] public string MarketName { - get => ProtobufBody.market_name; - private init => ProtobufBody.market_name = value; + get => Body.market_name; + private init => Body.market_name = value; } [JsonInclude] [JsonPropertyName("name")] public string Name { - get => ProtobufBody.name; - private init => ProtobufBody.name = value; + get => Body.name; + private init => Body.name = value; } [JsonInclude] [JsonPropertyName("owner_actions")] public ImmutableHashSet OwnerActions { - get => ProtobufBody.owner_actions.Select(static action => new ItemAction(action.link, action.name)).ToImmutableHashSet(); - private init { - ProtobufBody.owner_actions.Clear(); + get => Body.owner_actions.Select(static action => new ItemAction(action.link, action.name)).ToImmutableHashSet(); - foreach (ItemAction action in value) { - ProtobufBody.owner_actions.Add( - new CEconItem_Action { + private init { + Body.owner_actions.Clear(); + + Body.owner_actions.AddRange( + value.Select( + static action => new CEconItem_Action { link = action.Link, name = action.Name } - ); - } + ) + ); } } [JsonInclude] [JsonPropertyName("type")] public string TypeText { - get => ProtobufBody.type; - private init => ProtobufBody.type = value; + get => Body.type; + private init => Body.type = value; + } + + internal EAssetRarity Rarity { + get { + if (CachedRarity.HasValue) { + return CachedRarity.Value; + } + + foreach (CEconItem_Tag? tag in Body.tags) { + switch (tag.category) { + case "droprate": + switch (tag.internal_name) { + case "droprate_0": + CachedRarity = EAssetRarity.Common; + + return CachedRarity.Value; + case "droprate_1": + CachedRarity = EAssetRarity.Uncommon; + + return CachedRarity.Value; + case "droprate_2": + CachedRarity = EAssetRarity.Rare; + + return CachedRarity.Value; + default: + ASF.ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.WarningUnknownValuePleaseReport, nameof(tag.internal_name), tag.internal_name)); + + CachedRarity = EAssetRarity.Unknown; + + return CachedRarity.Value; + } + } + } + + CachedRarity = EAssetRarity.Unknown; + + return CachedRarity.Value; + } + + private init { + if (!Enum.IsDefined(value)) { + throw new InvalidEnumArgumentException(nameof(value), (int) value, typeof(EAssetRarity)); + } + + CachedRarity = value; + } + } + + internal uint RealAppID { + get { + if (CachedRealAppID.HasValue) { + return CachedRealAppID.Value; + } + + foreach (CEconItem_Tag? tag in Body.tags) { + switch (tag.category) { + case "Game": + if (string.IsNullOrEmpty(tag.internal_name) || (tag.internal_name.Length <= 4) || !tag.internal_name.StartsWith("app_", StringComparison.Ordinal)) { + ASF.ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.WarningUnknownValuePleaseReport, nameof(tag.internal_name), tag.internal_name)); + + break; + } + + string appIDText = tag.internal_name[4..]; + + if (!uint.TryParse(appIDText, out uint appID) || (appID == 0)) { + ASF.ArchiLogger.LogNullError(appID); + + break; + } + + CachedRealAppID = appID; + + return CachedRealAppID.Value; + } + } + + CachedRealAppID = 0; + + return CachedRealAppID.Value; + } + + private init { + ArgumentOutOfRangeException.ThrowIfZero(value); + + CachedRealAppID = value; + } } [JsonDisallowNull] [JsonInclude] [JsonPropertyName("tags")] internal ImmutableHashSet Tags { - get => ProtobufBody.tags.Select(static x => new Tag(x.category, x.internal_name, x.localized_category_name, x.localized_tag_name)).ToImmutableHashSet(); - private init { - ProtobufBody.tags.Clear(); + get => Body.tags.Select(static tag => new Tag(tag.category, tag.internal_name, tag.localized_category_name, tag.localized_tag_name, tag.color)).ToImmutableHashSet(); - foreach (Tag tag in value) { - ProtobufBody.tags.Add( - new CEconItem_Tag { + private init { + Body.tags.Clear(); + + Body.tags.AddRange( + value.Select( + tag => new CEconItem_Tag { appid = AppID, category = tag.Identifier, color = tag.Color, @@ -325,8 +296,8 @@ public sealed class InventoryDescription { localized_category_name = tag.LocalizedIdentifier, localized_tag_name = tag.LocalizedValue } - ); - } + ) + ); } } @@ -335,32 +306,160 @@ public sealed class InventoryDescription { [JsonRequired] [JsonConverter(typeof(BooleanNumberConverter))] internal bool Tradable { - get => ProtobufBody.tradable; - private init => ProtobufBody.tradable = value; + get => Body.tradable; + private init => Body.tradable = value; } - // For stubs and deserialization - [JsonConstructor] - internal InventoryDescription() { } + internal EAssetType Type { + get { + if (CachedType.HasValue) { + return CachedType.Value; + } - // Constructed from trades being received/sent - internal InventoryDescription(uint appID, ulong classID, ulong instanceID, bool marketable, bool tradable, IReadOnlyCollection? tags = null) { - ArgumentOutOfRangeException.ThrowIfZero(appID); - ArgumentOutOfRangeException.ThrowIfZero(classID); + EAssetType type = EAssetType.Unknown; - AppID = appID; - ClassID = classID; - InstanceID = instanceID; - Marketable = marketable; - Tradable = tradable; + foreach (CEconItem_Tag? tag in Body.tags) { + switch (tag.category) { + case "cardborder": + switch (tag.internal_name) { + case "cardborder_0": + CachedType = EAssetType.TradingCard; - if (tags?.Count > 0) { - Tags = tags.ToImmutableHashSet(); + return CachedType.Value; + case "cardborder_1": + CachedType = EAssetType.FoilTradingCard; + + return CachedType.Value; + default: + ASF.ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.WarningUnknownValuePleaseReport, nameof(tag.internal_name), tag.internal_name)); + + CachedType = EAssetType.Unknown; + + return CachedType.Value; + } + case "item_class": + switch (tag.internal_name) { + case "item_class_2": + if (type == EAssetType.Unknown) { + // This is a fallback in case we'd have no cardborder available to interpret + type = EAssetType.TradingCard; + } + + continue; + case "item_class_3": + CachedType = EAssetType.ProfileBackground; + + return CachedType.Value; + case "item_class_4": + CachedType = EAssetType.Emoticon; + + return CachedType.Value; + case "item_class_5": + CachedType = EAssetType.BoosterPack; + + return CachedType.Value; + case "item_class_6": + CachedType = EAssetType.Consumable; + + return CachedType.Value; + case "item_class_7": + CachedType = EAssetType.SteamGems; + + return CachedType.Value; + case "item_class_8": + CachedType = EAssetType.ProfileModifier; + + return CachedType.Value; + case "item_class_10": + CachedType = EAssetType.SaleItem; + + return CachedType.Value; + case "item_class_11": + CachedType = EAssetType.Sticker; + + return CachedType.Value; + case "item_class_12": + CachedType = EAssetType.ChatEffect; + + return CachedType.Value; + case "item_class_13": + CachedType = EAssetType.MiniProfileBackground; + + return CachedType.Value; + case "item_class_14": + CachedType = EAssetType.AvatarProfileFrame; + + return CachedType.Value; + case "item_class_15": + CachedType = EAssetType.AnimatedAvatar; + + return CachedType.Value; + case "item_class_16": + CachedType = EAssetType.KeyboardSkin; + + return CachedType.Value; + case "item_class_17": + CachedType = EAssetType.StartupVideo; + + return CachedType.Value; + default: + ASF.ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.WarningUnknownValuePleaseReport, nameof(tag.internal_name), tag.internal_name)); + + CachedType = EAssetType.Unknown; + + return CachedType.Value; + } + } + } + + CachedType = type; + + return CachedType.Value; + } + + private init { + if (!Enum.IsDefined(value)) { + throw new InvalidEnumArgumentException(nameof(value), (int) value, typeof(EAssetType)); + } + + CachedType = value; } } - internal InventoryDescription(CEconItem_Description description) => ProtobufBody = description; + private EAssetRarity? CachedRarity; + private uint? CachedRealAppID; + private EAssetType? CachedType; - [UsedImplicitly] - public static bool ShouldSerializeAdditionalProperties() => false; + internal InventoryDescription(CEconItem_Description description) { + ArgumentNullException.ThrowIfNull(description); + + Body = description; + } + + // For self-created stubs + internal InventoryDescription(uint appID, ulong classID, ulong instanceID = 0, bool marketable = false, bool tradable = false, uint realAppID = 0, EAssetType type = EAssetType.Unknown, EAssetRarity rarity = EAssetRarity.Unknown) { + ArgumentOutOfRangeException.ThrowIfZero(appID); + ArgumentOutOfRangeException.ThrowIfZero(classID); + + if (!Enum.IsDefined(type)) { + throw new InvalidEnumArgumentException(nameof(type), (int) type, typeof(EAssetType)); + } + + if (!Enum.IsDefined(rarity)) { + throw new InvalidEnumArgumentException(nameof(rarity), (int) rarity, typeof(EAssetRarity)); + } + + AppID = appID; + ClassID = classID; + + InstanceID = instanceID; + Marketable = marketable; + Tradable = tradable; + RealAppID = realAppID; + Type = type; + Rarity = rarity; + } + + [JsonConstructor] + private InventoryDescription() { } } diff --git a/ArchiSteamFarm/Steam/Data/ItemAction.cs b/ArchiSteamFarm/Steam/Data/ItemAction.cs index 7c7916185..391318d85 100644 --- a/ArchiSteamFarm/Steam/Data/ItemAction.cs +++ b/ArchiSteamFarm/Steam/Data/ItemAction.cs @@ -5,39 +5,45 @@ // / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | // /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| // ---------------------------------------------------------------------------------------------- -// +// // Copyright 2015-2024 Ł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; using System.Text.Json.Serialization; using JetBrains.Annotations; namespace ArchiSteamFarm.Steam.Data; -public class ItemAction { +public sealed class ItemAction { [JsonInclude] [JsonPropertyName("link")] + [JsonRequired] [PublicAPI] - public string Link { get; private init; } = null!; + public string Link { get; private init; } = ""; [JsonInclude] [JsonPropertyName("name")] + [JsonRequired] [PublicAPI] - public string Name { get; private init; } = null!; + public string Name { get; private init; } = ""; internal ItemAction(string link, string name) { + ArgumentException.ThrowIfNullOrEmpty(link); + ArgumentException.ThrowIfNullOrEmpty(name); + Link = link; Name = name; } diff --git a/ArchiSteamFarm/Steam/Data/ItemDescription.cs b/ArchiSteamFarm/Steam/Data/ItemDescription.cs index 5b43b00a7..309e2894f 100644 --- a/ArchiSteamFarm/Steam/Data/ItemDescription.cs +++ b/ArchiSteamFarm/Steam/Data/ItemDescription.cs @@ -5,16 +5,16 @@ // / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | // /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| // ---------------------------------------------------------------------------------------------- -// +// | // Copyright 2015-2024 Ł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. @@ -22,12 +22,11 @@ // limitations under the License. using System.Text.Json.Serialization; -using ArchiSteamFarm.Helpers.Json; using JetBrains.Annotations; namespace ArchiSteamFarm.Steam.Data; -public class ItemDescription { +public sealed class ItemDescription { [JsonInclude] [JsonPropertyName("color")] [PublicAPI] @@ -38,19 +37,17 @@ public class ItemDescription { [PublicAPI] public string? Label { get; private init; } - [JsonDisallowNull] [JsonInclude] [JsonPropertyName("type")] [PublicAPI] - public string Type { get; private init; } = null!; + public string? Type { get; private init; } - [JsonDisallowNull] [JsonInclude] [JsonPropertyName("value")] [PublicAPI] - public string Value { get; private init; } = null!; + public string? Value { get; private init; } - internal ItemDescription(string type, string value, string color, string label) { + internal ItemDescription(string? type = null, string? value = null, string? color = null, string? label = null) { Type = type; Value = value; Color = color; diff --git a/ArchiSteamFarm/Steam/Data/Tag.cs b/ArchiSteamFarm/Steam/Data/Tag.cs index 889afc245..13c497783 100644 --- a/ArchiSteamFarm/Steam/Data/Tag.cs +++ b/ArchiSteamFarm/Steam/Data/Tag.cs @@ -5,16 +5,16 @@ // / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | // /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| // ---------------------------------------------------------------------------------------------- -// +// // Copyright 2015-2024 Ł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. @@ -41,15 +41,13 @@ public sealed class Tag { [JsonInclude] [JsonPropertyName("localized_category_name")] - [JsonRequired] [PublicAPI] - public string LocalizedIdentifier { get; private init; } = ""; + public string? LocalizedIdentifier { get; private init; } [JsonInclude] [JsonPropertyName("localized_tag_name")] - [JsonRequired] [PublicAPI] - public string LocalizedValue { get; private init; } = ""; + public string? LocalizedValue { get; private init; } [JsonInclude] [JsonPropertyName("internal_name")] @@ -57,7 +55,7 @@ public sealed class Tag { [PublicAPI] public string Value { get; private init; } = ""; - internal Tag(string identifier, string value, string localizedIdentifier, string localizedValue, string? color = null) { + internal Tag(string identifier, string value, string? localizedIdentifier = null, string? localizedValue = null, string? color = null) { ArgumentException.ThrowIfNullOrEmpty(identifier); ArgumentNullException.ThrowIfNull(value); diff --git a/ArchiSteamFarm/Steam/Data/TradeOffer.cs b/ArchiSteamFarm/Steam/Data/TradeOffer.cs index 9a57b89c2..539bdb101 100644 --- a/ArchiSteamFarm/Steam/Data/TradeOffer.cs +++ b/ArchiSteamFarm/Steam/Data/TradeOffer.cs @@ -5,16 +5,16 @@ // / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | // /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| // ---------------------------------------------------------------------------------------------- -// +// // Copyright 2015-2024 Ł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. @@ -74,11 +74,11 @@ public sealed class TradeOffer { private TradeOffer() { } [PublicAPI] - public bool IsValidSteamItemsRequest(IReadOnlyCollection acceptedTypes) { + public bool IsValidSteamItemsRequest(IReadOnlyCollection acceptedTypes) { if ((acceptedTypes == null) || (acceptedTypes.Count == 0)) { throw new ArgumentNullException(nameof(acceptedTypes)); } - return ItemsToGive.All(item => item is { AppID: Asset.SteamAppID, ContextID: Asset.SteamCommunityContextID, AssetID: > 0, Amount: > 0, ClassID: > 0, RealAppID: > 0 and not Asset.SteamAppID, Type: > Asset.EType.Unknown, Rarity: > Asset.ERarity.Unknown } && acceptedTypes.Contains(item.Type)); + return ItemsToGive.All(item => item is { AppID: Asset.SteamAppID, ContextID: Asset.SteamCommunityContextID, AssetID: > 0, Amount: > 0, ClassID: > 0, RealAppID: > 0 and not Asset.SteamAppID, Type: > EAssetType.Unknown, Rarity: > EAssetRarity.Unknown } && acceptedTypes.Contains(item.Type)); } } diff --git a/ArchiSteamFarm/Steam/Data/TradeOffersResponse.cs b/ArchiSteamFarm/Steam/Data/TradeOffersResponse.cs index 89e48bca6..7378d7724 100644 --- a/ArchiSteamFarm/Steam/Data/TradeOffersResponse.cs +++ b/ArchiSteamFarm/Steam/Data/TradeOffersResponse.cs @@ -5,16 +5,16 @@ // / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | // /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| // ---------------------------------------------------------------------------------------------- -// +// // Copyright 2015-2024 Ł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. @@ -22,12 +22,14 @@ // limitations under the License. using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; using ArchiSteamFarm.Helpers.Json; namespace ArchiSteamFarm.Steam.Data; -public class TradeOffersResponse { +[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")] +public sealed class TradeOffersResponse { [JsonDisallowNull] [JsonInclude] [JsonPropertyName("descriptions")] @@ -42,4 +44,7 @@ public class TradeOffersResponse { [JsonInclude] [JsonPropertyName("trade_offers_sent")] public ImmutableHashSet TradeOffersSent { get; private init; } = []; + + [JsonConstructor] + private TradeOffersResponse() { } } diff --git a/ArchiSteamFarm/Steam/Exchange/Trading.cs b/ArchiSteamFarm/Steam/Exchange/Trading.cs index ec70df4ea..fb7c8087f 100644 --- a/ArchiSteamFarm/Steam/Exchange/Trading.cs +++ b/ArchiSteamFarm/Steam/Exchange/Trading.cs @@ -5,16 +5,16 @@ // / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | // /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| // ---------------------------------------------------------------------------------------------- -// +// // Copyright 2015-2024 Ł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. @@ -60,12 +60,12 @@ public sealed class Trading : IDisposable { public void Dispose() => TradesSemaphore.Dispose(); [PublicAPI] - public static Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), List> GetInventorySets(IReadOnlyCollection inventory) { + public static Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), List> GetInventorySets(IReadOnlyCollection inventory) { if ((inventory == null) || (inventory.Count == 0)) { throw new ArgumentNullException(nameof(inventory)); } - Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), Dictionary> sets = GetInventoryState(inventory); + Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), Dictionary> sets = GetInventoryState(inventory); return sets.ToDictionary(static set => set.Key, static set => set.Value.Values.OrderBy(static amount => amount).ToList()); } @@ -80,22 +80,22 @@ public sealed class Trading : IDisposable { throw new ArgumentNullException(nameof(itemsToReceive)); } - Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), uint> itemsToGiveAmounts = new(); + Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), uint> itemsToGiveAmounts = new(); foreach (Asset item in itemsToGive) { - (uint RealAppID, Asset.EType Type, Asset.ERarity Rarity) key = (item.RealAppID, item.Type, item.Rarity); + (uint RealAppID, EAssetType Type, EAssetRarity Rarity) key = (item.RealAppID, item.Type, item.Rarity); itemsToGiveAmounts[key] = itemsToGiveAmounts.GetValueOrDefault(key) + item.Amount; } - Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), uint> itemsToReceiveAmounts = new(); + Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), uint> itemsToReceiveAmounts = new(); foreach (Asset item in itemsToReceive) { - (uint RealAppID, Asset.EType Type, Asset.ERarity Rarity) key = (item.RealAppID, item.Type, item.Rarity); + (uint RealAppID, EAssetType Type, EAssetRarity Rarity) key = (item.RealAppID, item.Type, item.Rarity); itemsToReceiveAmounts[key] = itemsToReceiveAmounts.GetValueOrDefault(key) + item.Amount; } // Ensure that amount of items to give is at least amount of items to receive (per all fairness factors) - foreach (((uint RealAppID, Asset.EType Type, Asset.ERarity Rarity) key, uint amountToGive) in itemsToGiveAmounts) { + foreach (((uint RealAppID, EAssetType Type, EAssetRarity Rarity) key, uint amountToGive) in itemsToGiveAmounts) { if (!itemsToReceiveAmounts.TryGetValue(key, out uint amountToReceive) || (amountToGive > amountToReceive)) { return false; } @@ -124,7 +124,7 @@ public sealed class Trading : IDisposable { // All of those cases should be verified by our unit tests to ensure that the logic here matches all possible cases, especially those that were incorrectly handled previously // Firstly we get initial sets state of our inventory - Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), List> initialSets = GetInventorySets(inventory); + Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), List> initialSets = GetInventorySets(inventory); // Once we have initial state, we remove items that we're supposed to give from our inventory // This loop is a bit more complex due to the fact that we might have a mix of the same item splitted into different amounts @@ -162,10 +162,10 @@ public sealed class Trading : IDisposable { } // Now we can get final sets state of our inventory after the exchange - Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), List> finalSets = GetInventorySets(inventory); + Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), List> finalSets = GetInventorySets(inventory); // Once we have both states, we can check overall fairness - foreach (((uint RealAppID, Asset.EType Type, Asset.ERarity Rarity) set, List beforeAmounts) in initialSets) { + foreach (((uint RealAppID, EAssetType Type, EAssetRarity Rarity) set, List beforeAmounts) in initialSets) { if (!finalSets.TryGetValue(set, out List? afterAmounts)) { // If we have no info about this set, then it has to be a bad one return false; @@ -200,16 +200,16 @@ public sealed class Trading : IDisposable { return true; } - internal static (Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), Dictionary> FullState, Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), Dictionary> TradableState) GetDividedInventoryState(IReadOnlyCollection inventory) { + internal static (Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), Dictionary> FullState, Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), Dictionary> TradableState) GetDividedInventoryState(IReadOnlyCollection inventory) { if ((inventory == null) || (inventory.Count == 0)) { throw new ArgumentNullException(nameof(inventory)); } - Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), Dictionary> fullState = new(); - Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), Dictionary> tradableState = new(); + Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), Dictionary> fullState = new(); + Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), Dictionary> tradableState = new(); foreach (Asset item in inventory) { - (uint RealAppID, Asset.EType Type, Asset.ERarity Rarity) key = (item.RealAppID, item.Type, item.Rarity); + (uint RealAppID, EAssetType Type, EAssetRarity Rarity) key = (item.RealAppID, item.Type, item.Rarity); if (fullState.TryGetValue(key, out Dictionary? fullSet)) { fullSet[item.ClassID] = fullSet.GetValueOrDefault(item.ClassID) + item.Amount; @@ -231,15 +231,15 @@ public sealed class Trading : IDisposable { return (fullState, tradableState); } - internal static Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), Dictionary> GetTradableInventoryState(IReadOnlyCollection inventory) { + internal static Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), Dictionary> GetTradableInventoryState(IReadOnlyCollection inventory) { if ((inventory == null) || (inventory.Count == 0)) { throw new ArgumentNullException(nameof(inventory)); } - Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), Dictionary> tradableState = new(); + Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), Dictionary> tradableState = new(); foreach (Asset item in inventory.Where(static item => item.Tradable)) { - (uint RealAppID, Asset.EType Type, Asset.ERarity Rarity) key = (item.RealAppID, item.Type, item.Rarity); + (uint RealAppID, EAssetType Type, EAssetRarity Rarity) key = (item.RealAppID, item.Type, item.Rarity); if (tradableState.TryGetValue(key, out Dictionary? tradableSet)) { tradableSet[item.ClassID] = tradableSet.GetValueOrDefault(item.ClassID) + item.Amount; @@ -295,11 +295,11 @@ public sealed class Trading : IDisposable { return result; } - internal static bool IsEmptyForMatching(IReadOnlyDictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), Dictionary> fullState, IReadOnlyDictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), Dictionary> tradableState) { + internal static bool IsEmptyForMatching(IReadOnlyDictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), Dictionary> fullState, IReadOnlyDictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), Dictionary> tradableState) { ArgumentNullException.ThrowIfNull(fullState); ArgumentNullException.ThrowIfNull(tradableState); - foreach (((uint RealAppID, Asset.EType Type, Asset.ERarity Rarity) set, IReadOnlyDictionary state) in tradableState) { + foreach (((uint RealAppID, EAssetType Type, EAssetRarity Rarity) set, IReadOnlyDictionary state) in tradableState) { if (!fullState.TryGetValue(set, out Dictionary? fullSet) || (fullSet.Count == 0)) { throw new InvalidOperationException(nameof(fullSet)); } @@ -382,15 +382,15 @@ public sealed class Trading : IDisposable { } } - private static Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), Dictionary> GetInventoryState(IReadOnlyCollection inventory) { + private static Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), Dictionary> GetInventoryState(IReadOnlyCollection inventory) { if ((inventory == null) || (inventory.Count == 0)) { throw new ArgumentNullException(nameof(inventory)); } - Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), Dictionary> state = new(); + Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), Dictionary> state = new(); foreach (Asset item in inventory) { - (uint RealAppID, Asset.EType Type, Asset.ERarity Rarity) key = (item.RealAppID, item.Type, item.Rarity); + (uint RealAppID, EAssetType Type, EAssetRarity Rarity) key = (item.RealAppID, item.Type, item.Rarity); if (state.TryGetValue(key, out Dictionary? set)) { set[item.ClassID] = set.GetValueOrDefault(item.ClassID) + item.Amount; @@ -621,7 +621,7 @@ public sealed class Trading : IDisposable { // If user has a trade hold, we add extra logic // If trade hold duration exceeds our max, or user asks for cards with short lifespan, reject the trade - case > 0 when (holdDuration.Value > (ASF.GlobalConfig?.MaxTradeHoldDuration ?? GlobalConfig.DefaultMaxTradeHoldDuration)) || tradeOffer.ItemsToGive.Any(static item => item.Type is Asset.EType.FoilTradingCard or Asset.EType.TradingCard && CardsFarmer.SalesBlacklist.Contains(item.RealAppID)): + case > 0 when (holdDuration.Value > (ASF.GlobalConfig?.MaxTradeHoldDuration ?? GlobalConfig.DefaultMaxTradeHoldDuration)) || tradeOffer.ItemsToGive.Any(static item => item.Type is EAssetType.FoilTradingCard or EAssetType.TradingCard && CardsFarmer.SalesBlacklist.Contains(item.RealAppID)): Bot.ArchiLogger.LogGenericDebug(string.Format(CultureInfo.CurrentCulture, Strings.BotTradeOfferResult, tradeOffer.TradeOfferID, ParseTradeResult.EResult.Rejected, $"{nameof(holdDuration)} > 0: {holdDuration.Value}")); return ParseTradeResult.EResult.Rejected; @@ -635,7 +635,7 @@ public sealed class Trading : IDisposable { } // Get sets we're interested in - HashSet<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity)> wantedSets = tradeOffer.ItemsToGive.Select(static item => (item.RealAppID, item.Type, item.Rarity)).ToHashSet(); + HashSet<(uint RealAppID, EAssetType Type, EAssetRarity Rarity)> wantedSets = tradeOffer.ItemsToGive.Select(static item => (item.RealAppID, item.Type, item.Rarity)).ToHashSet(); // Now check if it's worth for us to do the trade HashSet inventory; diff --git a/ArchiSteamFarm/Steam/Integration/ArchiHandler.cs b/ArchiSteamFarm/Steam/Integration/ArchiHandler.cs index 42dd5c5d9..b38c4b248 100644 --- a/ArchiSteamFarm/Steam/Integration/ArchiHandler.cs +++ b/ArchiSteamFarm/Steam/Integration/ArchiHandler.cs @@ -5,16 +5,16 @@ // / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | // /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| // ---------------------------------------------------------------------------------------------- -// +// // Copyright 2015-2024 Ł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. @@ -160,16 +160,18 @@ public sealed class ArchiHandler : ClientMsgHandler { public async IAsyncEnumerable GetMyInventoryAsync(uint appID = Asset.SteamAppID, ulong contextID = Asset.SteamCommunityContextID, bool tradableOnly = false, bool marketableOnly = false, ushort itemsCountPerRequest = 40000) { ArgumentOutOfRangeException.ThrowIfZero(appID); ArgumentOutOfRangeException.ThrowIfZero(contextID); + ArgumentOutOfRangeException.ThrowIfZero(itemsCountPerRequest); if (Client.SteamID == null) { throw new InvalidOperationException(nameof(Client.SteamID)); } - SteamID steamID = Client.SteamID; + ulong steamID = Client.SteamID; + + ulong startAssetID = 0; // We need to store asset IDs to make sure we won't get duplicate items HashSet? assetIDs = null; - ulong startAssetID = 0; while (true) { ulong currentStartAssetID = startAssetID; @@ -177,20 +179,19 @@ public sealed class ArchiHandler : ClientMsgHandler { CEcon_GetInventoryItemsWithDescriptions_Request request = new() { appid = appID, contextid = contextID, + filters = new CEcon_GetInventoryItemsWithDescriptions_Request.FilterOptions { tradable_only = tradableOnly, marketable_only = marketableOnly }, + get_descriptions = true, - steamid = steamID.ConvertToUInt64(), + steamid = steamID, start_assetid = currentStartAssetID, count = itemsCountPerRequest }; - SteamUnifiedMessages.ServiceMethodResponse genericResponse = await UnifiedEconService - .SendMessage(x => x.GetInventoryItemsWithDescriptions(request)) - .ToLongRunningTask() - .ConfigureAwait(false); + SteamUnifiedMessages.ServiceMethodResponse genericResponse = await UnifiedEconService.SendMessage(x => x.GetInventoryItemsWithDescriptions(request)).ToLongRunningTask().ConfigureAwait(false); CEcon_GetInventoryItemsWithDescriptions_Response response = genericResponse.GetDeserializedResponse(); @@ -213,18 +214,20 @@ public sealed class ArchiHandler : ClientMsgHandler { throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, Strings.ErrorObjectIsNull, $"{nameof(response.assets)} || {nameof(response.descriptions)}")); } - List convertedDescriptions = response.descriptions.Select(static description => new InventoryDescription(description)).ToList(); - Dictionary<(ulong ClassID, ulong InstanceID), InventoryDescription> descriptions = new(); - foreach (InventoryDescription description in convertedDescriptions) { - if (description.ClassID == 0) { - throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, Strings.ErrorObjectIsNull, nameof(description.ClassID))); + foreach (CEconItem_Description? description in response.descriptions) { + if (description.classid == 0) { + throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, Strings.ErrorObjectIsNull, nameof(description.classid))); } - (ulong ClassID, ulong InstanceID) key = (description.ClassID, description.InstanceID); + (ulong ClassID, ulong InstanceID) key = (description.classid, description.instanceid); - descriptions.TryAdd(key, description); + if (descriptions.ContainsKey(key)) { + continue; + } + + descriptions.TryAdd(key, new InventoryDescription(description)); } foreach (CEcon_Asset? asset in response.assets) { diff --git a/ArchiSteamFarm/Steam/Integration/ArchiWebHandler.cs b/ArchiSteamFarm/Steam/Integration/ArchiWebHandler.cs index 16b634293..098e2aaff 100644 --- a/ArchiSteamFarm/Steam/Integration/ArchiWebHandler.cs +++ b/ArchiSteamFarm/Steam/Integration/ArchiWebHandler.cs @@ -5,16 +5,16 @@ // / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | // /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| // ---------------------------------------------------------------------------------------------- -// +// | // Copyright 2015-2024 Ł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. @@ -232,17 +232,31 @@ public sealed class ArchiWebHandler : IDisposable { return result; } + /// + /// If targetting inventory of this instance, consider using much more efficient instead. + /// This method should be used exclusively for foreign inventories (other users), but in special circumstances it can be used for fetching bot's own inventory as well. + /// [PublicAPI] - public async IAsyncEnumerable GetForeignInventoryAsync(ulong steamID, uint appID = Asset.SteamAppID, ulong contextID = Asset.SteamCommunityContextID) { + public async IAsyncEnumerable GetInventoryAsync(ulong steamID = 0, uint appID = Asset.SteamAppID, ulong contextID = Asset.SteamCommunityContextID) { ArgumentOutOfRangeException.ThrowIfZero(appID); ArgumentOutOfRangeException.ThrowIfZero(contextID); - if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) { - throw new ArgumentOutOfRangeException(nameof(steamID)); - } + if (steamID == 0) { + if (!Initialized) { + byte connectionTimeout = ASF.GlobalConfig?.ConnectionTimeout ?? GlobalConfig.DefaultConnectionTimeout; - if (steamID == Bot.SteamID) { - throw new NotSupportedException(); + for (byte i = 0; (i < connectionTimeout) && !Initialized && Bot.IsConnectedAndLoggedOn; i++) { + await Task.Delay(1000).ConfigureAwait(false); + } + + if (!Initialized) { + throw new HttpRequestException(Strings.WarningFailed); + } + } + + steamID = Bot.SteamID; + } else if (!new SteamID(steamID).IsIndividualAccount) { + throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, Strings.ErrorObjectIsNull, nameof(steamID))); } if (ASF.InventorySemaphore == null) { @@ -370,16 +384,6 @@ public sealed class ArchiWebHandler : IDisposable { } } - [PublicAPI] - [Obsolete($"Use ArchiHandler.{nameof(ArchiHandler.GetMyInventoryAsync)} for getting bot's own inventory or ArchiWebHandler.${nameof(GetForeignInventoryAsync)} in other cases instead.")] - public IAsyncEnumerable GetInventoryAsync(ulong steamID = 0, uint appID = Asset.SteamAppID, ulong contextID = Asset.SteamCommunityContextID) { - if ((steamID == 0) || (steamID == Bot.SteamID)) { - return Bot.ArchiHandler.GetMyInventoryAsync(appID, contextID); - } - - return GetForeignInventoryAsync(steamID, appID, contextID); - } - [PublicAPI] public async Task GetPointsBalance() { string? accessToken = Bot.AccessToken; @@ -480,9 +484,9 @@ public sealed class ArchiWebHandler : IDisposable { string queryString = string.Join('&', arguments.Select(static argument => $"{argument.Key}={HttpUtility.UrlEncode(argument.Value.ToString())}")); - string request = $"/{EconService}/GetTradeOffers/v1/?" + queryString; + Uri request = new(WebAPI.DefaultBaseAddress, $"/{EconService}/GetTradeOffers/v1?{queryString}"); - TradeOffersResponse? response = (await WebLimitRequest(WebAPI.DefaultBaseAddress, async () => await WebBrowser.UrlGetToJsonObject>(new Uri(WebAPI.DefaultBaseAddress, request)).ConfigureAwait(false)).ConfigureAwait(false))?.Content?.Response; + TradeOffersResponse? response = (await WebLimitRequest(WebAPI.DefaultBaseAddress, async () => await WebBrowser.UrlGetToJsonObject>(request).ConfigureAwait(false)).ConfigureAwait(false))?.Content?.Response; if (response == null) { Bot.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.ErrorRequestFailedTooManyTimes, WebBrowser.MaxTries)); @@ -2371,9 +2375,13 @@ public sealed class ArchiWebHandler : IDisposable { } private static void SetDescriptionsToAssets(IEnumerable assets, [SuppressMessage("ReSharper", "SuggestBaseTypeForParameter")] Dictionary<(uint AppID, ulong ClassID, ulong InstanceID), InventoryDescription> descriptions) { + ArgumentNullException.ThrowIfNull(assets); + ArgumentNullException.ThrowIfNull(descriptions); + foreach (Asset asset in assets) { if (!descriptions.TryGetValue((asset.AppID, asset.ClassID, asset.InstanceID), out InventoryDescription? description)) { - description = new InventoryDescription(asset.AppID, asset.ClassID, asset.InstanceID, true, true); + // No description, deal with it + continue; } asset.Description = description; diff --git a/ArchiSteamFarm/Steam/Interaction/Commands.cs b/ArchiSteamFarm/Steam/Interaction/Commands.cs index 5b8c95e4b..4e6d4f89e 100644 --- a/ArchiSteamFarm/Steam/Interaction/Commands.cs +++ b/ArchiSteamFarm/Steam/Interaction/Commands.cs @@ -5,16 +5,16 @@ // / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | // /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| // ---------------------------------------------------------------------------------------------- -// +// // Copyright 2015-2024 Ł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. @@ -3107,7 +3107,7 @@ public sealed class Commands { // It'd also make sense to run all of this in parallel, but it seems that Steam has a lot of problems with inventory-related parallel requests | https://steamcommunity.com/groups/archiasf/discussions/1/3559414588264550284/ try { - await foreach (Asset item in Bot.ArchiHandler.GetMyInventoryAsync().Where(static item => item.Type == Asset.EType.BoosterPack).ConfigureAwait(false)) { + await foreach (Asset item in Bot.ArchiHandler.GetMyInventoryAsync().Where(static item => item.Type == EAssetType.BoosterPack).ConfigureAwait(false)) { if (!await Bot.ArchiWebHandler.UnpackBooster(item.RealAppID, item.AssetID).ConfigureAwait(false)) { completeSuccess = false; } diff --git a/ArchiSteamFarm/Steam/Storage/BotConfig.cs b/ArchiSteamFarm/Steam/Storage/BotConfig.cs index 308de913d..4bca863d5 100644 --- a/ArchiSteamFarm/Steam/Storage/BotConfig.cs +++ b/ArchiSteamFarm/Steam/Storage/BotConfig.cs @@ -5,16 +5,16 @@ // / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | // /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| // ---------------------------------------------------------------------------------------------- -// +// // Copyright 2015-2024 Ł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. @@ -117,7 +117,7 @@ public sealed class BotConfig { internal const byte SteamTradeTokenLength = 8; [PublicAPI] - public static readonly ImmutableHashSet DefaultCompleteTypesToSend = ImmutableHashSet.Empty; + public static readonly ImmutableHashSet DefaultCompleteTypesToSend = ImmutableHashSet.Empty; [PublicAPI] public static readonly ImmutableList DefaultFarmingOrders = ImmutableList.Empty; @@ -126,16 +126,16 @@ public sealed class BotConfig { public static readonly ImmutableList DefaultGamesPlayedWhileIdle = ImmutableList.Empty; [PublicAPI] - public static readonly ImmutableHashSet DefaultLootableTypes = ImmutableHashSet.Create(Asset.EType.BoosterPack, Asset.EType.FoilTradingCard, Asset.EType.TradingCard); + public static readonly ImmutableHashSet DefaultLootableTypes = ImmutableHashSet.Create(EAssetType.BoosterPack, EAssetType.FoilTradingCard, EAssetType.TradingCard); [PublicAPI] - public static readonly ImmutableHashSet DefaultMatchableTypes = ImmutableHashSet.Create(Asset.EType.TradingCard); + public static readonly ImmutableHashSet DefaultMatchableTypes = ImmutableHashSet.Create(EAssetType.TradingCard); [PublicAPI] public static readonly ImmutableDictionary DefaultSteamUserPermissions = ImmutableDictionary.Empty; [PublicAPI] - public static readonly ImmutableHashSet DefaultTransferableTypes = ImmutableHashSet.Create(Asset.EType.BoosterPack, Asset.EType.FoilTradingCard, Asset.EType.TradingCard); + public static readonly ImmutableHashSet DefaultTransferableTypes = ImmutableHashSet.Create(EAssetType.BoosterPack, EAssetType.FoilTradingCard, EAssetType.TradingCard); [JsonInclude] public bool AcceptGifts { get; private init; } = DefaultAcceptGifts; @@ -145,8 +145,8 @@ public sealed class BotConfig { [JsonDisallowNull] [JsonInclude] - [SwaggerValidValues(ValidIntValues = [(int) Asset.EType.FoilTradingCard, (int) Asset.EType.TradingCard])] - public ImmutableHashSet CompleteTypesToSend { get; private init; } = DefaultCompleteTypesToSend; + [SwaggerValidValues(ValidIntValues = [(int) EAssetType.FoilTradingCard, (int) EAssetType.TradingCard])] + public ImmutableHashSet CompleteTypesToSend { get; private init; } = DefaultCompleteTypesToSend; [JsonInclude] public string? CustomGamePlayedWhileFarming { get; private init; } = DefaultCustomGamePlayedWhileFarming; @@ -177,11 +177,11 @@ public sealed class BotConfig { [JsonDisallowNull] [JsonInclude] - public ImmutableHashSet LootableTypes { get; private init; } = DefaultLootableTypes; + public ImmutableHashSet LootableTypes { get; private init; } = DefaultLootableTypes; [JsonDisallowNull] [JsonInclude] - public ImmutableHashSet MatchableTypes { get; private init; } = DefaultMatchableTypes; + public ImmutableHashSet MatchableTypes { get; private init; } = DefaultMatchableTypes; [JsonInclude] public EPersonaStateFlag OnlineFlags { get; private init; } = DefaultOnlineFlags; @@ -260,7 +260,7 @@ public sealed class BotConfig { [JsonDisallowNull] [JsonInclude] - public ImmutableHashSet TransferableTypes { get; private init; } = DefaultTransferableTypes; + public ImmutableHashSet TransferableTypes { get; private init; } = DefaultTransferableTypes; [JsonInclude] public bool UseLoginKeys { get; private init; } = DefaultUseLoginKeys; @@ -424,13 +424,13 @@ public sealed class BotConfig { return (false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorConfigPropertyInvalid, nameof(GamesPlayedWhileIdle), $"{nameof(GamesPlayedWhileIdle.Count)} {GamesPlayedWhileIdle.Count} > {ArchiHandler.MaxGamesPlayedConcurrently}")); } - foreach (Asset.EType lootableType in LootableTypes.Where(static lootableType => !Enum.IsDefined(lootableType))) { + foreach (EAssetType lootableType in LootableTypes.Where(static lootableType => !Enum.IsDefined(lootableType))) { return (false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorConfigPropertyInvalid, nameof(LootableTypes), lootableType)); } - HashSet? completeTypesToSendValidTypes = null; + HashSet? completeTypesToSendValidTypes = null; - foreach (Asset.EType completableType in CompleteTypesToSend) { + foreach (EAssetType completableType in CompleteTypesToSend) { if (!Enum.IsDefined(completableType)) { return (false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorConfigPropertyInvalid, nameof(CompleteTypesToSend), completableType)); } @@ -442,7 +442,7 @@ public sealed class BotConfig { throw new InvalidOperationException(nameof(completeTypesToSendValidValues)); } - completeTypesToSendValidTypes = completeTypesToSendValidValues.ValidIntValues.Select(static value => (Asset.EType) value).ToHashSet(); + completeTypesToSendValidTypes = completeTypesToSendValidValues.ValidIntValues.Select(static value => (EAssetType) value).ToHashSet(); } if (!completeTypesToSendValidTypes.Contains(completableType)) { @@ -450,7 +450,7 @@ public sealed class BotConfig { } } - foreach (Asset.EType matchableType in MatchableTypes.Where(static matchableType => !Enum.IsDefined(matchableType))) { + foreach (EAssetType matchableType in MatchableTypes.Where(static matchableType => !Enum.IsDefined(matchableType))) { return (false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorConfigPropertyInvalid, nameof(MatchableTypes), matchableType)); }