mirror of
https://github.com/JustArchiNET/ArchiSteamFarm
synced 2024-11-10 07:04:27 +00:00
Big post-PR cleanup
This commit is contained in:
parent
f98a159799
commit
c9acbb7bf2
29 changed files with 872 additions and 660 deletions
|
@ -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<ObjectResponse<GenericResponse<BackgroundTaskResponse>>?> AnnounceDiffForListing(WebBrowser webBrowser, ulong steamID, IReadOnlyCollection<AssetForListing> inventory, string inventoryChecksum, IReadOnlyCollection<Asset.EType> acceptedMatchableTypes, uint totalInventoryCount, bool matchEverything, string tradeToken, IReadOnlyCollection<AssetForListing> inventoryRemoved, string? previousInventoryChecksum, string? nickname = null, string? avatarHash = null) {
|
||||
internal static async Task<ObjectResponse<GenericResponse<BackgroundTaskResponse>>?> AnnounceDiffForListing(WebBrowser webBrowser, ulong steamID, IReadOnlyCollection<AssetForListing> inventory, string inventoryChecksum, IReadOnlyCollection<EAssetType> acceptedMatchableTypes, uint totalInventoryCount, bool matchEverything, string tradeToken, IReadOnlyCollection<AssetForListing> 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<GenericResponse<BackgroundTaskResponse>, AnnouncementDiffRequest>(request, data: data, requestOptions: WebBrowser.ERequestOptions.ReturnRedirections | WebBrowser.ERequestOptions.ReturnClientErrors | WebBrowser.ERequestOptions.AllowInvalidBodyOnErrors | WebBrowser.ERequestOptions.CompressRequest).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
internal static async Task<ObjectResponse<GenericResponse<BackgroundTaskResponse>>?> AnnounceForListing(WebBrowser webBrowser, ulong steamID, IReadOnlyCollection<AssetForListing> inventory, string inventoryChecksum, IReadOnlyCollection<Asset.EType> acceptedMatchableTypes, uint totalInventoryCount, bool matchEverything, string tradeToken, string? nickname = null, string? avatarHash = null) {
|
||||
internal static async Task<ObjectResponse<GenericResponse<BackgroundTaskResponse>>?> AnnounceForListing(WebBrowser webBrowser, ulong steamID, IReadOnlyCollection<AssetForListing> inventory, string inventoryChecksum, IReadOnlyCollection<EAssetType> 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<ListedUser> Users)?> GetListedUsersForMatching(Guid licenseID, Bot bot, WebBrowser webBrowser, IReadOnlyCollection<Asset> inventory, IReadOnlyCollection<Asset.EType> acceptedMatchableTypes) {
|
||||
internal static async Task<(HttpStatusCode StatusCode, ImmutableHashSet<ListedUser> Users)?> GetListedUsersForMatching(Guid licenseID, Bot bot, WebBrowser webBrowser, IReadOnlyCollection<Asset> inventory, IReadOnlyCollection<EAssetType> 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<ListedUser>.Empty);
|
||||
}
|
||||
|
||||
internal static async Task<ObjectResponse<GenericResponse<ImmutableHashSet<SetPart>>>?> GetSetParts(WebBrowser webBrowser, ulong steamID, IReadOnlyCollection<Asset.EType> matchableTypes, IReadOnlyCollection<uint> realAppIDs, CancellationToken cancellationToken = default) {
|
||||
internal static async Task<ObjectResponse<GenericResponse<ImmutableHashSet<SetPart>>>?> GetSetParts(WebBrowser webBrowser, ulong steamID, IReadOnlyCollection<EAssetType> matchableTypes, IReadOnlyCollection<uint> realAppIDs, CancellationToken cancellationToken = default) {
|
||||
ArgumentNullException.ThrowIfNull(webBrowser);
|
||||
|
||||
if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) {
|
||||
|
|
|
@ -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<AssetForListing> inventory, string inventoryChecksum, IReadOnlyCollection<Asset.EType> matchableTypes, uint totalInventoryCount, bool matchEverything, byte maxTradeHoldDuration, string tradeToken, IReadOnlyCollection<AssetForListing> 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<AssetForListing> inventory, string inventoryChecksum, IReadOnlyCollection<EAssetType> matchableTypes, uint totalInventoryCount, bool matchEverything, byte maxTradeHoldDuration, string tradeToken, IReadOnlyCollection<AssetForListing> 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) {
|
||||
|
|
|
@ -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<Asset.EType> MatchableTypes { get; init; }
|
||||
private ImmutableHashSet<EAssetType> 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<AssetForListing> inventory, string inventoryChecksum, IReadOnlyCollection<Asset.EType> matchableTypes, uint totalInventoryCount, bool matchEverything, byte maxTradeHoldDuration, string tradeToken, string? nickname = null, string? avatarHash = null) {
|
||||
internal AnnouncementRequest(Guid guid, ulong steamID, IReadOnlyCollection<AssetForListing> inventory, string inventoryChecksum, IReadOnlyCollection<EAssetType> 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) {
|
||||
|
|
|
@ -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() { }
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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<Asset.EType> MatchableTypes { get; private init; }
|
||||
internal ImmutableHashSet<EAssetType> MatchableTypes { get; private init; }
|
||||
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
internal ulong SteamID { get; private init; }
|
||||
|
||||
internal InventoriesRequest(Guid guid, ulong steamID, IReadOnlyCollection<Asset> inventory, IReadOnlyCollection<Asset.EType> matchableTypes) {
|
||||
internal InventoriesRequest(Guid guid, ulong steamID, IReadOnlyCollection<Asset> inventory, IReadOnlyCollection<EAssetType> matchableTypes) {
|
||||
ArgumentOutOfRangeException.ThrowIfEqual(guid, Guid.Empty);
|
||||
|
||||
if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) {
|
||||
|
|
|
@ -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<Asset.EType> MatchableTypes { get; private init; } = ImmutableHashSet<Asset.EType>.Empty;
|
||||
internal ImmutableHashSet<EAssetType> MatchableTypes { get; private init; } = ImmutableHashSet<EAssetType>.Empty;
|
||||
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
|
|
|
@ -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() { }
|
||||
|
|
|
@ -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<Asset.EType> MatchableTypes { get; private init; }
|
||||
internal ImmutableHashSet<EAssetType> 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<Asset.EType> matchableTypes, IReadOnlyCollection<uint> realAppIDs) {
|
||||
internal SetPartsRequest(Guid guid, ulong steamID, IReadOnlyCollection<EAssetType> matchableTypes, IReadOnlyCollection<uint> realAppIDs) {
|
||||
ArgumentOutOfRangeException.ThrowIfEqual(guid, Guid.Empty);
|
||||
|
||||
if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) {
|
||||
|
|
|
@ -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<Asset.EType> AcceptedMatchableTypes = new HashSet<Asset.EType>(4) {
|
||||
Asset.EType.Emoticon,
|
||||
Asset.EType.FoilTradingCard,
|
||||
Asset.EType.ProfileBackground,
|
||||
Asset.EType.TradingCard
|
||||
private static readonly FrozenSet<EAssetType> AcceptedMatchableTypes = new HashSet<EAssetType>(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<Asset.EType> acceptedMatchableTypes = Bot.BotConfig.MatchableTypes.Where(AcceptedMatchableTypes.Contains).ToHashSet();
|
||||
HashSet<EAssetType> 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<AssetForListing> 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<uint> realAppIDs = [];
|
||||
Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), Dictionary<ulong, uint>> state = new();
|
||||
Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), Dictionary<ulong, uint>> 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<ulong, uint>? 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<ulong>> 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<ulong>> 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<ulong, uint> setCopy = [];
|
||||
|
||||
foreach (((uint RealAppID, Asset.EType Type, Asset.ERarity Rarity) key, Dictionary<ulong, uint> set) in state) {
|
||||
foreach (((uint RealAppID, EAssetType Type, EAssetRarity Rarity) key, Dictionary<ulong, uint> set) in state) {
|
||||
if (!databaseSets.TryGetValue(key, out HashSet<ulong>? 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<AssetForListing> assetsForListingFiltered = [];
|
||||
|
||||
foreach (AssetForListing asset in assetsForListing.Where(asset => state.TryGetValue((asset.RealAppID, asset.Type, asset.Rarity), out Dictionary<ulong, uint>? 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<ulong, uint>? 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<Asset.EType> acceptedMatchableTypes = Bot.BotConfig.MatchableTypes.Where(AcceptedMatchableTypes.Contains).ToHashSet();
|
||||
HashSet<EAssetType> 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<Asset> 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<uint> realAppIDs = [];
|
||||
Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), Dictionary<ulong, uint>> setsState = new();
|
||||
Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), Dictionary<ulong, uint>> 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<ulong, uint>? 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<ulong>> 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<ulong>> 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<ulong, uint> setCopy = [];
|
||||
|
||||
foreach (((uint RealAppID, Asset.EType Type, Asset.ERarity Rarity) key, Dictionary<ulong, uint> set) in setsState) {
|
||||
foreach (((uint RealAppID, EAssetType Type, EAssetRarity Rarity) key, Dictionary<ulong, uint> set) in setsState) {
|
||||
uint minimumAmount = uint.MaxValue;
|
||||
uint maximumAmount = uint.MinValue;
|
||||
|
||||
|
@ -1096,7 +1096,7 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
|
|||
HashSet<Asset> assetsForMatchingFiltered = [];
|
||||
|
||||
foreach (Asset asset in assetsForMatching.Where(asset => setsState.TryGetValue((asset.RealAppID, asset.Type, asset.Rarity), out Dictionary<ulong, uint>? 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<ulong, uint>? 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<bool> MatchActively(IReadOnlyCollection<ListedUser> listedUsers, IReadOnlyCollection<Asset> ourAssets, IReadOnlyCollection<Asset.EType> acceptedMatchableTypes) {
|
||||
private async Task<bool> MatchActively(IReadOnlyCollection<ListedUser> listedUsers, IReadOnlyCollection<Asset> ourAssets, IReadOnlyCollection<EAssetType> 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<ulong, uint>> ourFullState, Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), Dictionary<ulong, uint>> ourTradableState) = Trading.GetDividedInventoryState(ourAssets);
|
||||
(Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), Dictionary<ulong, uint>> ourFullState, Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), Dictionary<ulong, uint>> 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<Asset> 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<Asset> 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<ulong, uint>> theirTradableState = Trading.GetTradableInventoryState(theirInventory);
|
||||
Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), Dictionary<ulong, uint>> 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<ulong, uint> classIDsToGive = new();
|
||||
Dictionary<ulong, uint> classIDsToReceive = new();
|
||||
Dictionary<ulong, uint> fairClassIDsToGive = new();
|
||||
Dictionary<ulong, uint> fairClassIDsToReceive = new();
|
||||
|
||||
foreach (((uint RealAppID, Asset.EType Type, Asset.ERarity Rarity) set, Dictionary<ulong, uint> 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<ulong, uint> 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<ulong, uint>? 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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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<Asset> items = [
|
||||
CreateCard(1, appID),
|
||||
CreateCard(2, appID)
|
||||
CreateCard(1, realAppID: appID),
|
||||
CreateCard(2, realAppID: appID)
|
||||
];
|
||||
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => GetItemsForFullBadge(items, 2, appID, MinCardsPerBadge - 1));
|
||||
|
@ -73,10 +73,10 @@ public sealed class Bot {
|
|||
const uint appID = 42;
|
||||
|
||||
HashSet<Asset> 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<Asset> itemsToSend = GetItemsForFullBadge(items, 3, appID);
|
||||
|
@ -95,10 +95,10 @@ public sealed class Bot {
|
|||
const uint appID = 42;
|
||||
|
||||
HashSet<Asset> 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<Asset> itemsToSend = GetItemsForFullBadge(items, 2, appID);
|
||||
|
@ -116,9 +116,9 @@ public sealed class Bot {
|
|||
const uint appID = 42;
|
||||
|
||||
HashSet<Asset> 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<Asset> itemsToSend = GetItemsForFullBadge(items, 2, appID);
|
||||
|
@ -136,27 +136,27 @@ public sealed class Bot {
|
|||
const uint appID = 42;
|
||||
|
||||
HashSet<Asset> 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<Asset> itemsToSend = GetItemsForFullBadge(items, 3, appID);
|
||||
|
@ -177,8 +177,8 @@ public sealed class Bot {
|
|||
const uint appID = 42;
|
||||
|
||||
HashSet<Asset> items = [
|
||||
CreateCard(1, appID),
|
||||
CreateCard(2, appID)
|
||||
CreateCard(1, realAppID: appID),
|
||||
CreateCard(2, realAppID: appID)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 3, appID);
|
||||
|
@ -192,8 +192,8 @@ public sealed class Bot {
|
|||
const uint appID = 42;
|
||||
|
||||
HashSet<Asset> items = [
|
||||
CreateCard(1, appID),
|
||||
CreateCard(2, appID)
|
||||
CreateCard(1, realAppID: appID),
|
||||
CreateCard(2, realAppID: appID)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 2, appID);
|
||||
|
@ -212,8 +212,8 @@ public sealed class Bot {
|
|||
const uint appID1 = 43;
|
||||
|
||||
HashSet<Asset> items = [
|
||||
CreateCard(1, appID0),
|
||||
CreateCard(1, appID1)
|
||||
CreateCard(1, realAppID: appID0),
|
||||
CreateCard(1, realAppID: appID1)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToSend = GetItemsForFullBadge(
|
||||
|
@ -237,8 +237,8 @@ public sealed class Bot {
|
|||
const uint appID1 = 43;
|
||||
|
||||
HashSet<Asset> items = [
|
||||
CreateCard(1, appID0),
|
||||
CreateCard(1, appID1)
|
||||
CreateCard(1, realAppID: appID0),
|
||||
CreateCard(1, realAppID: appID1)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToSend = GetItemsForFullBadge(
|
||||
|
@ -260,12 +260,12 @@ public sealed class Bot {
|
|||
const uint appID2 = 44;
|
||||
|
||||
HashSet<Asset> 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<Asset> itemsToSend = GetItemsForFullBadge(
|
||||
|
@ -290,8 +290,8 @@ public sealed class Bot {
|
|||
const uint appID = 42;
|
||||
|
||||
HashSet<Asset> 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<Asset> itemsToSend = GetItemsForFullBadge(items, 1, appID);
|
||||
|
@ -308,8 +308,8 @@ public sealed class Bot {
|
|||
const uint appID = 42;
|
||||
|
||||
HashSet<Asset> 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<Asset> itemsToSend = GetItemsForFullBadge(items, 2, appID);
|
||||
|
@ -324,11 +324,11 @@ public sealed class Bot {
|
|||
const uint appID = 42;
|
||||
|
||||
HashSet<Asset> 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<Asset> itemsToSend = GetItemsForFullBadge(items, 3, appID);
|
||||
|
@ -347,8 +347,8 @@ public sealed class Bot {
|
|||
const uint appID = 42;
|
||||
|
||||
HashSet<Asset> 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<Asset> itemsToSend = GetItemsForFullBadge(items, 1, appID);
|
||||
|
@ -365,8 +365,8 @@ public sealed class Bot {
|
|||
const uint appID = 42;
|
||||
|
||||
HashSet<Asset> 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<Asset> itemsToSend = GetItemsForFullBadge(items, 2, appID);
|
||||
|
@ -381,11 +381,11 @@ public sealed class Bot {
|
|||
const uint appID = 42;
|
||||
|
||||
HashSet<Asset> 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<Asset> itemsToSend = GetItemsForFullBadge(items, 3, appID);
|
||||
|
@ -404,8 +404,8 @@ public sealed class Bot {
|
|||
const uint appID0 = 42;
|
||||
|
||||
HashSet<Asset> items = [
|
||||
CreateCard(1, appID0, 2),
|
||||
CreateCard(2, appID0)
|
||||
CreateCard(1, amount: 2, realAppID: appID0),
|
||||
CreateCard(2, realAppID: appID0)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 2, appID0);
|
||||
|
@ -425,8 +425,8 @@ public sealed class Bot {
|
|||
HashSet<Asset> 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<Asset> itemsToSend = GetItemsForFullBadge(items, 2, appID);
|
||||
|
@ -442,10 +442,10 @@ public sealed class Bot {
|
|||
HashSet<Asset> 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<uint, byte> itemsPerSet = new() {
|
||||
|
@ -465,10 +465,10 @@ public sealed class Bot {
|
|||
const uint appID2 = 44;
|
||||
|
||||
HashSet<Asset> 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<InvalidOperationException>(
|
||||
|
@ -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<Asset> GetItemsForFullBadge(IReadOnlyCollection<Asset> inventory, byte cardsPerSet, uint appID, ushort maxItems = Steam.Exchange.Trading.MaxItemsPerTrade) => GetItemsForFullBadge(inventory, new Dictionary<uint, byte> { { appID, cardsPerSet } }, maxItems);
|
||||
|
||||
private static HashSet<Asset> GetItemsForFullBadge(IReadOnlyCollection<Asset> inventory, IDictionary<uint, byte> cardsPerSet, ushort maxItems = Steam.Exchange.Trading.MaxItemsPerTrade) {
|
||||
Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), List<uint>> inventorySets = Steam.Exchange.Trading.GetInventorySets(inventory);
|
||||
Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), List<uint>> 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();
|
||||
}
|
||||
|
|
|
@ -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<Asset> inventory = [
|
||||
CreateItem(1, 40),
|
||||
CreateItem(2, 10),
|
||||
CreateItem(3, 10)
|
||||
CreateItem(1, amount: 40),
|
||||
CreateItem(2, amount: 10),
|
||||
CreateItem(3, amount: 10)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToGive = [
|
||||
CreateItem(2, 5),
|
||||
CreateItem(3, 5)
|
||||
CreateItem(2, amount: 5),
|
||||
CreateItem(3, amount: 5)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToReceive = [
|
||||
CreateItem(1, 9),
|
||||
CreateItem(1, amount: 9),
|
||||
CreateItem(4)
|
||||
];
|
||||
|
||||
|
@ -54,24 +54,39 @@ public sealed class Trading {
|
|||
|
||||
[TestMethod]
|
||||
public void MismatchRarityIsNotFair() {
|
||||
HashSet<Asset> itemsToGive = [CreateItem(1, rarity: Asset.ERarity.Rare)];
|
||||
HashSet<Asset> itemsToReceive = [CreateItem(2)];
|
||||
HashSet<Asset> itemsToGive = [
|
||||
CreateItem(1, rarity: EAssetRarity.Rare)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToReceive = [
|
||||
CreateItem(2)
|
||||
];
|
||||
|
||||
Assert.IsFalse(IsFairExchange(itemsToGive, itemsToReceive));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MismatchRealAppIDsIsNotFair() {
|
||||
HashSet<Asset> itemsToGive = [CreateItem(1, realAppID: 570)];
|
||||
HashSet<Asset> itemsToReceive = [CreateItem(2)];
|
||||
HashSet<Asset> itemsToGive = [
|
||||
CreateItem(1, realAppID: 570)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToReceive = [
|
||||
CreateItem(2)
|
||||
];
|
||||
|
||||
Assert.IsFalse(IsFairExchange(itemsToGive, itemsToReceive));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MismatchTypesIsNotFair() {
|
||||
HashSet<Asset> itemsToGive = [CreateItem(1, type: Asset.EType.Emoticon)];
|
||||
HashSet<Asset> itemsToReceive = [CreateItem(2)];
|
||||
HashSet<Asset> itemsToGive = [
|
||||
CreateItem(1, type: EAssetType.Emoticon)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToReceive = [
|
||||
CreateItem(2)
|
||||
];
|
||||
|
||||
Assert.IsFalse(IsFairExchange(itemsToGive, itemsToReceive));
|
||||
}
|
||||
|
@ -79,19 +94,19 @@ public sealed class Trading {
|
|||
[TestMethod]
|
||||
public void MultiGameMultiTypeBadReject() {
|
||||
HashSet<Asset> 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<Asset> itemsToGive = [
|
||||
CreateItem(1),
|
||||
CreateItem(4, realAppID: 730, type: Asset.EType.Emoticon)
|
||||
CreateItem(4, realAppID: 730, type: EAssetType.Emoticon)
|
||||
];
|
||||
|
||||
HashSet<Asset> 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<Asset> inventory = [
|
||||
CreateItem(1, 9),
|
||||
CreateItem(3, realAppID: 730, type: Asset.EType.Emoticon)
|
||||
CreateItem(1, amount: 9),
|
||||
CreateItem(3, realAppID: 730, type: EAssetType.Emoticon)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToGive = [
|
||||
CreateItem(1),
|
||||
CreateItem(3, realAppID: 730, type: Asset.EType.Emoticon)
|
||||
CreateItem(3, realAppID: 730, type: EAssetType.Emoticon)
|
||||
];
|
||||
|
||||
HashSet<Asset> 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<Asset> 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<Asset> inventory = [
|
||||
CreateItem(1, 2),
|
||||
CreateItem(1, amount: 2),
|
||||
CreateItem(3, realAppID: 730)
|
||||
];
|
||||
|
||||
|
@ -166,15 +181,19 @@ public sealed class Trading {
|
|||
public void SingleGameAbrynosWasWrongNeutralAccept() {
|
||||
HashSet<Asset> inventory = [
|
||||
CreateItem(1),
|
||||
CreateItem(2, 2),
|
||||
CreateItem(2, amount: 2),
|
||||
CreateItem(3),
|
||||
CreateItem(4),
|
||||
CreateItem(5)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToGive = [CreateItem(2)];
|
||||
HashSet<Asset> itemsToGive = [
|
||||
CreateItem(2)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToReceive = [CreateItem(3)];
|
||||
HashSet<Asset> 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<Asset> inventory = [CreateItem(1)];
|
||||
HashSet<Asset> inventory = [
|
||||
CreateItem(1)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToGive = [CreateItem(1)];
|
||||
HashSet<Asset> itemsToGive = [
|
||||
CreateItem(1)
|
||||
];
|
||||
|
||||
HashSet<Asset> 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<Asset> 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<Asset> itemsToGive = [
|
||||
CreateItem(1),
|
||||
CreateItem(4, type: Asset.EType.Emoticon)
|
||||
CreateItem(4, type: EAssetType.Emoticon)
|
||||
];
|
||||
|
||||
HashSet<Asset> 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<Asset> inventory = [
|
||||
CreateItem(1, 9),
|
||||
CreateItem(3, type: Asset.EType.Emoticon)
|
||||
CreateItem(1, amount: 9),
|
||||
CreateItem(3, type: EAssetType.Emoticon)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToGive = [
|
||||
CreateItem(1),
|
||||
CreateItem(3, type: Asset.EType.Emoticon)
|
||||
CreateItem(3, type: EAssetType.Emoticon)
|
||||
];
|
||||
|
||||
HashSet<Asset> 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<Asset> itemsToReceive = [CreateItem(4, 3)];
|
||||
HashSet<Asset> 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<Asset> inventory = [
|
||||
CreateItem(1),
|
||||
CreateItem(2, 2)
|
||||
CreateItem(2, amount: 2)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToGive = [
|
||||
CreateItem(1),
|
||||
CreateItem(2, 2)
|
||||
CreateItem(2, amount: 2)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToReceive = [CreateItem(3, 3)];
|
||||
HashSet<Asset> 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<Asset> inventory = [
|
||||
CreateItem(1, 2),
|
||||
CreateItem(1, amount: 2),
|
||||
CreateItem(2)
|
||||
];
|
||||
|
||||
|
@ -288,7 +315,9 @@ public sealed class Trading {
|
|||
CreateItem(2)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToReceive = [CreateItem(3, 2)];
|
||||
HashSet<Asset> 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<Asset> itemsToGive = [CreateItem(1)];
|
||||
HashSet<Asset> itemsToReceive = [CreateItem(2)];
|
||||
HashSet<Asset> itemsToGive = [
|
||||
CreateItem(1)
|
||||
];
|
||||
|
||||
HashSet<Asset> 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<Asset> inventory = [
|
||||
CreateItem(1, 2),
|
||||
CreateItem(2, 2),
|
||||
CreateItem(3, 2)
|
||||
CreateItem(1, amount: 2),
|
||||
CreateItem(2, amount: 2),
|
||||
CreateItem(3, amount: 2)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToGive = [CreateItem(2)];
|
||||
HashSet<Asset> itemsToGive = [
|
||||
CreateItem(2)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToReceive = [
|
||||
CreateItem(1),
|
||||
|
@ -331,12 +367,17 @@ public sealed class Trading {
|
|||
public void SingleGameSingleTypeBigDifferenceAccept() {
|
||||
HashSet<Asset> inventory = [
|
||||
CreateItem(1),
|
||||
CreateItem(2, 5),
|
||||
CreateItem(2, amount: 5),
|
||||
CreateItem(3)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToGive = [CreateItem(2)];
|
||||
HashSet<Asset> itemsToReceive = [CreateItem(3)];
|
||||
HashSet<Asset> itemsToGive = [
|
||||
CreateItem(2)
|
||||
];
|
||||
|
||||
HashSet<Asset> 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<Asset> 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<Asset> itemsToGive = [
|
||||
|
@ -368,9 +409,17 @@ public sealed class Trading {
|
|||
|
||||
[TestMethod]
|
||||
public void SingleGameSingleTypeGoodAccept() {
|
||||
HashSet<Asset> inventory = [CreateItem(1, 2)];
|
||||
HashSet<Asset> itemsToGive = [CreateItem(1)];
|
||||
HashSet<Asset> itemsToReceive = [CreateItem(2)];
|
||||
HashSet<Asset> inventory = [
|
||||
CreateItem(1, amount: 2)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToGive = [
|
||||
CreateItem(1)
|
||||
];
|
||||
|
||||
HashSet<Asset> 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<Asset> inventory = [CreateItem(1)];
|
||||
HashSet<Asset> itemsToGive = [CreateItem(1)];
|
||||
HashSet<Asset> itemsToReceive = [CreateItem(2)];
|
||||
HashSet<Asset> inventory = [
|
||||
CreateItem(1)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToGive = [
|
||||
CreateItem(1)
|
||||
];
|
||||
|
||||
HashSet<Asset> 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<Asset> inventory = [
|
||||
CreateItem(1, 2),
|
||||
CreateItem(2, 2)
|
||||
CreateItem(1, amount: 2),
|
||||
CreateItem(2, amount: 2)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToGive = [CreateItem(2)];
|
||||
HashSet<Asset> itemsToGive = [
|
||||
CreateItem(2)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToReceive = [
|
||||
CreateItem(1),
|
||||
|
@ -407,26 +466,28 @@ public sealed class Trading {
|
|||
[TestMethod]
|
||||
public void TakingExcessiveAmountOfSingleCardCanStillBeFairAndNeutral() {
|
||||
HashSet<Asset> 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<Asset> itemsToGive = [CreateItem(2, 73)];
|
||||
HashSet<Asset> itemsToGive = [
|
||||
CreateItem(2, amount: 73)
|
||||
];
|
||||
|
||||
HashSet<Asset> 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));
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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<T>() where T : ClientMsgHandler => SteamClient.GetHandler<T>();
|
||||
|
||||
[PublicAPI]
|
||||
public static HashSet<Asset> GetItemsForFullSets(IReadOnlyCollection<Asset> inventory, IReadOnlyDictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), (uint SetsToExtract, byte ItemsPerSet)> amountsToExtract, ushort maxItems = Trading.MaxItemsPerTrade) {
|
||||
public static HashSet<Asset> GetItemsForFullSets(IReadOnlyCollection<Asset> 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<Asset> result = [];
|
||||
Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), Dictionary<ulong, HashSet<Asset>>> 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<ulong, HashSet<Asset>>> 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<ulong, HashSet<Asset>>? itemsPerClassID)) {
|
||||
continue;
|
||||
}
|
||||
|
@ -3662,7 +3662,7 @@ public sealed class Bot : IAsyncDisposable, IDisposable {
|
|||
return;
|
||||
}
|
||||
|
||||
Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), List<uint>> inventorySets = Trading.GetInventorySets(inventory);
|
||||
Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), List<uint>> 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<uint> amounts) in inventorySets.Where(set => appIDs.Contains(set.Key.RealAppID))) {
|
||||
foreach (((uint RealAppID, EAssetType Type, EAssetRarity Rarity) key, List<uint> 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));
|
||||
}
|
||||
|
|
|
@ -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<T> where T : class {
|
||||
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
|
||||
public sealed class APIWrappedResponse<T> where T : class {
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("response")]
|
||||
[JsonRequired]
|
||||
public T Response { get; private init; } = null!;
|
||||
|
||||
[JsonConstructor]
|
||||
private APIWrappedResponse() { }
|
||||
}
|
||||
|
|
|
@ -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<Tag> 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
|
||||
}
|
||||
}
|
||||
|
|
31
ArchiSteamFarm/Steam/Data/EAssetRarity.cs
Normal file
31
ArchiSteamFarm/Steam/Data/EAssetRarity.cs
Normal file
|
@ -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
|
||||
}
|
44
ArchiSteamFarm/Steam/Data/EAssetType.cs
Normal file
44
ArchiSteamFarm/Steam/Data/EAssetType.cs
Normal file
|
@ -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
|
||||
}
|
|
@ -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<ItemDescription> 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<ItemAction> 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<Tag> 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<Tag>? 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() { }
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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<Asset.EType> acceptedTypes) {
|
||||
public bool IsValidSteamItemsRequest(IReadOnlyCollection<EAssetType> 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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<TradeOffer> TradeOffersSent { get; private init; } = [];
|
||||
|
||||
[JsonConstructor]
|
||||
private TradeOffersResponse() { }
|
||||
}
|
||||
|
|
|
@ -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<uint>> GetInventorySets(IReadOnlyCollection<Asset> inventory) {
|
||||
public static Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), List<uint>> GetInventorySets(IReadOnlyCollection<Asset> inventory) {
|
||||
if ((inventory == null) || (inventory.Count == 0)) {
|
||||
throw new ArgumentNullException(nameof(inventory));
|
||||
}
|
||||
|
||||
Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), Dictionary<ulong, uint>> sets = GetInventoryState(inventory);
|
||||
Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), Dictionary<ulong, uint>> 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<uint>> initialSets = GetInventorySets(inventory);
|
||||
Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), List<uint>> 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<uint>> finalSets = GetInventorySets(inventory);
|
||||
Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), List<uint>> finalSets = GetInventorySets(inventory);
|
||||
|
||||
// Once we have both states, we can check overall fairness
|
||||
foreach (((uint RealAppID, Asset.EType Type, Asset.ERarity Rarity) set, List<uint> beforeAmounts) in initialSets) {
|
||||
foreach (((uint RealAppID, EAssetType Type, EAssetRarity Rarity) set, List<uint> beforeAmounts) in initialSets) {
|
||||
if (!finalSets.TryGetValue(set, out List<uint>? 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<ulong, uint>> FullState, Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), Dictionary<ulong, uint>> TradableState) GetDividedInventoryState(IReadOnlyCollection<Asset> inventory) {
|
||||
internal static (Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), Dictionary<ulong, uint>> FullState, Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), Dictionary<ulong, uint>> TradableState) GetDividedInventoryState(IReadOnlyCollection<Asset> inventory) {
|
||||
if ((inventory == null) || (inventory.Count == 0)) {
|
||||
throw new ArgumentNullException(nameof(inventory));
|
||||
}
|
||||
|
||||
Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), Dictionary<ulong, uint>> fullState = new();
|
||||
Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), Dictionary<ulong, uint>> tradableState = new();
|
||||
Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), Dictionary<ulong, uint>> fullState = new();
|
||||
Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), Dictionary<ulong, uint>> 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<ulong, uint>? 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<ulong, uint>> GetTradableInventoryState(IReadOnlyCollection<Asset> inventory) {
|
||||
internal static Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), Dictionary<ulong, uint>> GetTradableInventoryState(IReadOnlyCollection<Asset> inventory) {
|
||||
if ((inventory == null) || (inventory.Count == 0)) {
|
||||
throw new ArgumentNullException(nameof(inventory));
|
||||
}
|
||||
|
||||
Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), Dictionary<ulong, uint>> tradableState = new();
|
||||
Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), Dictionary<ulong, uint>> 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<ulong, uint>? 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<ulong, uint>> fullState, IReadOnlyDictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), Dictionary<ulong, uint>> tradableState) {
|
||||
internal static bool IsEmptyForMatching(IReadOnlyDictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), Dictionary<ulong, uint>> fullState, IReadOnlyDictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), Dictionary<ulong, uint>> tradableState) {
|
||||
ArgumentNullException.ThrowIfNull(fullState);
|
||||
ArgumentNullException.ThrowIfNull(tradableState);
|
||||
|
||||
foreach (((uint RealAppID, Asset.EType Type, Asset.ERarity Rarity) set, IReadOnlyDictionary<ulong, uint> state) in tradableState) {
|
||||
foreach (((uint RealAppID, EAssetType Type, EAssetRarity Rarity) set, IReadOnlyDictionary<ulong, uint> state) in tradableState) {
|
||||
if (!fullState.TryGetValue(set, out Dictionary<ulong, uint>? 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<ulong, uint>> GetInventoryState(IReadOnlyCollection<Asset> inventory) {
|
||||
private static Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), Dictionary<ulong, uint>> GetInventoryState(IReadOnlyCollection<Asset> inventory) {
|
||||
if ((inventory == null) || (inventory.Count == 0)) {
|
||||
throw new ArgumentNullException(nameof(inventory));
|
||||
}
|
||||
|
||||
Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), Dictionary<ulong, uint>> state = new();
|
||||
Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), Dictionary<ulong, uint>> 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<ulong, uint>? 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<Asset> inventory;
|
||||
|
|
|
@ -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<Asset> 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<ulong>? 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<CEcon_GetInventoryItemsWithDescriptions_Response>();
|
||||
|
||||
|
@ -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<InventoryDescription> 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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
/// If targetting inventory of this <see cref="Bot" /> instance, consider using much more efficient <see cref="ArchiHandler.GetMyInventoryAsync" /> 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.
|
||||
/// </remarks>
|
||||
[PublicAPI]
|
||||
public async IAsyncEnumerable<Asset> GetForeignInventoryAsync(ulong steamID, uint appID = Asset.SteamAppID, ulong contextID = Asset.SteamCommunityContextID) {
|
||||
public async IAsyncEnumerable<Asset> 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<Asset> 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<uint?> 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<APIWrappedResponse<TradeOffersResponse>>(new Uri(WebAPI.DefaultBaseAddress, request)).ConfigureAwait(false)).ConfigureAwait(false))?.Content?.Response;
|
||||
TradeOffersResponse? response = (await WebLimitRequest(WebAPI.DefaultBaseAddress, async () => await WebBrowser.UrlGetToJsonObject<APIWrappedResponse<TradeOffersResponse>>(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<Asset> 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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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<Asset.EType> DefaultCompleteTypesToSend = ImmutableHashSet<Asset.EType>.Empty;
|
||||
public static readonly ImmutableHashSet<EAssetType> DefaultCompleteTypesToSend = ImmutableHashSet<EAssetType>.Empty;
|
||||
|
||||
[PublicAPI]
|
||||
public static readonly ImmutableList<EFarmingOrder> DefaultFarmingOrders = ImmutableList<EFarmingOrder>.Empty;
|
||||
|
@ -126,16 +126,16 @@ public sealed class BotConfig {
|
|||
public static readonly ImmutableList<uint> DefaultGamesPlayedWhileIdle = ImmutableList<uint>.Empty;
|
||||
|
||||
[PublicAPI]
|
||||
public static readonly ImmutableHashSet<Asset.EType> DefaultLootableTypes = ImmutableHashSet.Create(Asset.EType.BoosterPack, Asset.EType.FoilTradingCard, Asset.EType.TradingCard);
|
||||
public static readonly ImmutableHashSet<EAssetType> DefaultLootableTypes = ImmutableHashSet.Create(EAssetType.BoosterPack, EAssetType.FoilTradingCard, EAssetType.TradingCard);
|
||||
|
||||
[PublicAPI]
|
||||
public static readonly ImmutableHashSet<Asset.EType> DefaultMatchableTypes = ImmutableHashSet.Create(Asset.EType.TradingCard);
|
||||
public static readonly ImmutableHashSet<EAssetType> DefaultMatchableTypes = ImmutableHashSet.Create(EAssetType.TradingCard);
|
||||
|
||||
[PublicAPI]
|
||||
public static readonly ImmutableDictionary<ulong, EAccess> DefaultSteamUserPermissions = ImmutableDictionary<ulong, EAccess>.Empty;
|
||||
|
||||
[PublicAPI]
|
||||
public static readonly ImmutableHashSet<Asset.EType> DefaultTransferableTypes = ImmutableHashSet.Create(Asset.EType.BoosterPack, Asset.EType.FoilTradingCard, Asset.EType.TradingCard);
|
||||
public static readonly ImmutableHashSet<EAssetType> 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<Asset.EType> CompleteTypesToSend { get; private init; } = DefaultCompleteTypesToSend;
|
||||
[SwaggerValidValues(ValidIntValues = [(int) EAssetType.FoilTradingCard, (int) EAssetType.TradingCard])]
|
||||
public ImmutableHashSet<EAssetType> 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<Asset.EType> LootableTypes { get; private init; } = DefaultLootableTypes;
|
||||
public ImmutableHashSet<EAssetType> LootableTypes { get; private init; } = DefaultLootableTypes;
|
||||
|
||||
[JsonDisallowNull]
|
||||
[JsonInclude]
|
||||
public ImmutableHashSet<Asset.EType> MatchableTypes { get; private init; } = DefaultMatchableTypes;
|
||||
public ImmutableHashSet<EAssetType> 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<Asset.EType> TransferableTypes { get; private init; } = DefaultTransferableTypes;
|
||||
public ImmutableHashSet<EAssetType> 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<Asset.EType>? completeTypesToSendValidTypes = null;
|
||||
HashSet<EAssetType>? 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));
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue