mirror of
https://github.com/JustArchiNET/ArchiSteamFarm
synced 2024-11-10 07:04:27 +00:00
Inventory fetching through CM (#3155)
* New inventory fetching * use new method everywhere * Store description in the asset, add protobuf body as a backing field for InventoryDescription, add properties to description * parse trade offers as json, stub descriptions, fix build * formatting, misc fixes * fix pragma comments * fix passing tradable property * fix convesion of assets, add compatibility method * fix fetching tradeoffers * use 40k as default count per request * throw an exception instead of silencing the error
This commit is contained in:
parent
aedede3ba4
commit
184232995d
20 changed files with 835 additions and 511 deletions
|
@ -40,5 +40,5 @@ internal class AssetInInventory : AssetForMatching {
|
|||
AssetID = asset.AssetID;
|
||||
}
|
||||
|
||||
internal Asset ToAsset() => new(Asset.SteamAppID, Asset.SteamCommunityContextID, ClassID, Amount, tradable: Tradable, assetID: AssetID, realAppID: RealAppID, type: Type, rarity: Rarity);
|
||||
internal Asset ToAsset() => new(Asset.SteamAppID, Asset.SteamCommunityContextID, ClassID, Amount, new InventoryDescription { ProtobufBody = { tradable = Tradable } }, assetID: AssetID, realAppID: RealAppID, type: Type, rarity: Rarity);
|
||||
}
|
||||
|
|
|
@ -250,7 +250,7 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
|
|||
List<Asset> inventory;
|
||||
|
||||
try {
|
||||
inventory = await Bot.ArchiWebHandler.GetInventoryAsync().ToListAsync().ConfigureAwait(false);
|
||||
inventory = await Bot.ArchiHandler.GetMyInventoryAsync().ToListAsync().ConfigureAwait(false);
|
||||
} catch (HttpRequestException e) {
|
||||
// This is actually a network failure, so we'll stop sending heartbeats but not record it as valid check
|
||||
ShouldSendHeartBeats = false;
|
||||
|
@ -937,7 +937,7 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
|
|||
HashSet<Asset> assetsForMatching;
|
||||
|
||||
try {
|
||||
assetsForMatching = await Bot.ArchiWebHandler.GetInventoryAsync().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: > Asset.EType.Unknown, Rarity: > Asset.ERarity.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)));
|
||||
|
@ -1504,10 +1504,10 @@ 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.Tradable = false;
|
||||
item.Description.ProtobufBody.tradable = false;
|
||||
item.Amount += itemToReceive.Amount;
|
||||
} else {
|
||||
itemToReceive.Tradable = false;
|
||||
itemToReceive.Description.ProtobufBody.tradable = false;
|
||||
ourInventory[itemToReceive.AssetID] = itemToReceive;
|
||||
}
|
||||
|
||||
|
|
|
@ -489,7 +489,7 @@ 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, realAppID: realAppID, type: type, rarity: rarity);
|
||||
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 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);
|
||||
|
||||
|
|
|
@ -426,5 +426,5 @@ public sealed class Trading {
|
|||
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, realAppID: realAppID, type: type, rarity: rarity);
|
||||
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);
|
||||
}
|
||||
|
|
44
ArchiSteamFarm/Helpers/Json/BooleanNumberConverter.cs
Normal file
44
ArchiSteamFarm/Helpers/Json/BooleanNumberConverter.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.
|
||||
|
||||
using System;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace ArchiSteamFarm.Helpers.Json;
|
||||
|
||||
[PublicAPI]
|
||||
public sealed class BooleanNumberConverter : JsonConverter<bool> {
|
||||
public override bool Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) =>
|
||||
reader.TokenType switch {
|
||||
JsonTokenType.True => true,
|
||||
JsonTokenType.False => false,
|
||||
JsonTokenType.Number => reader.GetByte() == 1,
|
||||
_ => throw new JsonException()
|
||||
};
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, bool value, JsonSerializerOptions options) {
|
||||
ArgumentNullException.ThrowIfNull(writer);
|
||||
|
||||
writer.WriteNumberValue(value ? 1 : 0);
|
||||
}
|
||||
}
|
|
@ -3640,8 +3640,8 @@ public sealed class Bot : IAsyncDisposable, IDisposable {
|
|||
HashSet<Asset> inventory;
|
||||
|
||||
try {
|
||||
inventory = await ArchiWebHandler.GetInventoryAsync()
|
||||
.Where(item => item.Tradable && appIDs.Contains(item.RealAppID) && BotConfig.CompleteTypesToSend.Contains(item.Type))
|
||||
inventory = await ArchiHandler.GetMyInventoryAsync(tradableOnly: true)
|
||||
.Where(item => appIDs.Contains(item.RealAppID) && BotConfig.CompleteTypesToSend.Contains(item.Type))
|
||||
.ToHashSetAsync()
|
||||
.ConfigureAwait(false);
|
||||
} catch (HttpRequestException e) {
|
||||
|
|
31
ArchiSteamFarm/Steam/Data/APIWrappedResponse.cs
Normal file
31
ArchiSteamFarm/Steam/Data/APIWrappedResponse.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.
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace ArchiSteamFarm.Steam.Data;
|
||||
|
||||
public class APIWrappedResponse<T> where T : class {
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("response")]
|
||||
[JsonRequired]
|
||||
public T Response { get; private init; } = null!;
|
||||
}
|
|
@ -20,9 +20,7 @@
|
|||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
|
@ -41,11 +39,40 @@ public sealed class Asset {
|
|||
|
||||
[JsonIgnore]
|
||||
[PublicAPI]
|
||||
public IReadOnlyDictionary<string, JsonElement>? AdditionalPropertiesReadOnly => AdditionalProperties;
|
||||
public bool IsSteamPointsShopItem => !Tradable && (InstanceID == SteamPointsShopInstanceID);
|
||||
|
||||
[JsonIgnore]
|
||||
[PublicAPI]
|
||||
public bool IsSteamPointsShopItem => !Tradable && (InstanceID == SteamPointsShopInstanceID);
|
||||
public bool Marketable => Description.Marketable;
|
||||
|
||||
[JsonIgnore]
|
||||
[PublicAPI]
|
||||
public ERarity Rarity => OverriddenRarity ?? Description.Rarity;
|
||||
|
||||
[JsonIgnore]
|
||||
[PublicAPI]
|
||||
public uint RealAppID => OverriddenRealAppID ?? Description.RealAppID;
|
||||
|
||||
[JsonIgnore]
|
||||
[PublicAPI]
|
||||
public ImmutableHashSet<Tag> Tags => Description.Tags;
|
||||
|
||||
[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; }
|
||||
|
||||
[JsonInclude]
|
||||
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
|
||||
|
@ -76,40 +103,15 @@ public sealed class Asset {
|
|||
[PublicAPI]
|
||||
public ulong ContextID { get; private init; }
|
||||
|
||||
[PublicAPI]
|
||||
public InventoryDescription Description { get; internal set; } = null!;
|
||||
|
||||
[JsonInclude]
|
||||
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
|
||||
[JsonPropertyName("instanceid")]
|
||||
[PublicAPI]
|
||||
public ulong InstanceID { get; private init; }
|
||||
|
||||
[JsonIgnore]
|
||||
[PublicAPI]
|
||||
public bool Marketable { get; internal set; }
|
||||
|
||||
[JsonIgnore]
|
||||
[PublicAPI]
|
||||
public ERarity Rarity { get; internal set; }
|
||||
|
||||
[JsonIgnore]
|
||||
[PublicAPI]
|
||||
public uint RealAppID { get; internal set; }
|
||||
|
||||
[JsonIgnore]
|
||||
[PublicAPI]
|
||||
public ImmutableHashSet<Tag>? Tags { get; internal set; }
|
||||
|
||||
[JsonIgnore]
|
||||
[PublicAPI]
|
||||
public bool Tradable { get; internal set; }
|
||||
|
||||
[JsonIgnore]
|
||||
[PublicAPI]
|
||||
public EType Type { get; internal set; }
|
||||
|
||||
[JsonExtensionData]
|
||||
[JsonInclude]
|
||||
internal Dictionary<string, JsonElement>? AdditionalProperties { get; set; }
|
||||
|
||||
[JsonInclude]
|
||||
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
|
||||
[JsonPropertyName("id")]
|
||||
|
@ -118,8 +120,15 @@ public sealed class Asset {
|
|||
init => AssetID = value;
|
||||
}
|
||||
|
||||
// Constructed from trades being received or plugins
|
||||
public Asset(uint appID, ulong contextID, ulong classID, uint amount, ulong instanceID = 0, ulong assetID = 0, bool marketable = true, bool tradable = true, ImmutableHashSet<Tag>? tags = null, uint realAppID = 0, EType type = EType.Unknown, ERarity rarity = ERarity.Unknown) {
|
||||
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);
|
||||
|
@ -129,17 +138,9 @@ public sealed class Asset {
|
|||
ContextID = contextID;
|
||||
ClassID = classID;
|
||||
Amount = amount;
|
||||
Description = description;
|
||||
InstanceID = instanceID;
|
||||
AssetID = assetID;
|
||||
Marketable = marketable;
|
||||
Tradable = tradable;
|
||||
RealAppID = realAppID;
|
||||
Type = type;
|
||||
Rarity = rarity;
|
||||
|
||||
if (tags?.Count > 0) {
|
||||
Tags = tags;
|
||||
}
|
||||
}
|
||||
|
||||
[JsonConstructor]
|
||||
|
|
364
ArchiSteamFarm/Steam/Data/InventoryDescription.cs
Normal file
364
ArchiSteamFarm/Steam/Data/InventoryDescription.cs
Normal file
|
@ -0,0 +1,364 @@
|
|||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// 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.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text.Json.Serialization;
|
||||
using ArchiSteamFarm.Core;
|
||||
using ArchiSteamFarm.Helpers.Json;
|
||||
using ArchiSteamFarm.Localization;
|
||||
using JetBrains.Annotations;
|
||||
using SteamKit2.Internal;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("appid")]
|
||||
[JsonRequired]
|
||||
public uint AppID {
|
||||
get => (uint) ProtobufBody.appid;
|
||||
private init => ProtobufBody.appid = (int) value;
|
||||
}
|
||||
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("background_color")]
|
||||
public string BackgroundColor {
|
||||
get => ProtobufBody.background_color;
|
||||
private init => ProtobufBody.background_color = value;
|
||||
}
|
||||
|
||||
[JsonInclude]
|
||||
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
|
||||
[JsonPropertyName("classid")]
|
||||
[JsonRequired]
|
||||
public ulong ClassID {
|
||||
get => ProtobufBody.classid;
|
||||
private init => ProtobufBody.classid = value;
|
||||
}
|
||||
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("commodity")]
|
||||
[JsonConverter(typeof(BooleanNumberConverter))]
|
||||
public bool Commodity {
|
||||
get => ProtobufBody.commodity;
|
||||
private init => ProtobufBody.commodity = value;
|
||||
}
|
||||
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("currency")]
|
||||
[JsonConverter(typeof(BooleanNumberConverter))]
|
||||
public bool Currency {
|
||||
get => ProtobufBody.currency;
|
||||
private init => ProtobufBody.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();
|
||||
|
||||
foreach (ItemDescription description in value) {
|
||||
ProtobufBody.descriptions.Add(
|
||||
new CEconItem_DescriptionLine {
|
||||
color = description.Color,
|
||||
label = description.Label,
|
||||
type = description.Type,
|
||||
value = description.Value
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[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;
|
||||
}
|
||||
|
||||
[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;
|
||||
}
|
||||
|
||||
[JsonInclude]
|
||||
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
|
||||
[JsonPropertyName("instanceid")]
|
||||
public ulong InstanceID {
|
||||
get => ProtobufBody.instanceid;
|
||||
private init => ProtobufBody.instanceid = value;
|
||||
}
|
||||
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("marketable")]
|
||||
[JsonRequired]
|
||||
[JsonConverter(typeof(BooleanNumberConverter))]
|
||||
public bool Marketable {
|
||||
get => ProtobufBody.marketable;
|
||||
private init => ProtobufBody.marketable = value;
|
||||
}
|
||||
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("market_fee_app")]
|
||||
public uint MarketFeeApp {
|
||||
get => (uint) ProtobufBody.market_fee_app;
|
||||
private init => ProtobufBody.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;
|
||||
}
|
||||
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("market_name")]
|
||||
public string MarketName {
|
||||
get => ProtobufBody.market_name;
|
||||
private init => ProtobufBody.market_name = value;
|
||||
}
|
||||
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("name")]
|
||||
public string Name {
|
||||
get => ProtobufBody.name;
|
||||
private init => ProtobufBody.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();
|
||||
|
||||
foreach (ItemAction action in value) {
|
||||
ProtobufBody.owner_actions.Add(
|
||||
new CEconItem_Action {
|
||||
link = action.Link,
|
||||
name = action.Name
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("type")]
|
||||
public string TypeText {
|
||||
get => ProtobufBody.type;
|
||||
private init => ProtobufBody.type = 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();
|
||||
|
||||
foreach (Tag tag in value) {
|
||||
ProtobufBody.tags.Add(
|
||||
new CEconItem_Tag {
|
||||
appid = AppID,
|
||||
category = tag.Identifier,
|
||||
color = tag.Color,
|
||||
internal_name = tag.Value,
|
||||
localized_category_name = tag.LocalizedIdentifier,
|
||||
localized_tag_name = tag.LocalizedValue
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("tradable")]
|
||||
[JsonRequired]
|
||||
[JsonConverter(typeof(BooleanNumberConverter))]
|
||||
internal bool Tradable {
|
||||
get => ProtobufBody.tradable;
|
||||
private init => ProtobufBody.tradable = value;
|
||||
}
|
||||
|
||||
// For stubs and deserialization
|
||||
[JsonConstructor]
|
||||
internal InventoryDescription() { }
|
||||
|
||||
// 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);
|
||||
|
||||
AppID = appID;
|
||||
ClassID = classID;
|
||||
InstanceID = instanceID;
|
||||
Marketable = marketable;
|
||||
Tradable = tradable;
|
||||
|
||||
if (tags?.Count > 0) {
|
||||
Tags = tags.ToImmutableHashSet();
|
||||
}
|
||||
}
|
||||
|
||||
internal InventoryDescription(CEconItem_Description description) => ProtobufBody = description;
|
||||
|
||||
[UsedImplicitly]
|
||||
public static bool ShouldSerializeAdditionalProperties() => false;
|
||||
}
|
|
@ -20,17 +20,11 @@
|
|||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using ArchiSteamFarm.Core;
|
||||
using ArchiSteamFarm.Helpers.Json;
|
||||
using ArchiSteamFarm.Localization;
|
||||
using ArchiSteamFarm.Steam.Integration;
|
||||
using JetBrains.Annotations;
|
||||
using SteamKit2;
|
||||
|
||||
namespace ArchiSteamFarm.Steam.Data;
|
||||
|
@ -45,7 +39,7 @@ internal sealed class InventoryResponse : OptionalResultResponse {
|
|||
[JsonDisallowNull]
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("descriptions")]
|
||||
internal ImmutableHashSet<Description> Descriptions { get; private init; } = ImmutableHashSet<Description>.Empty;
|
||||
internal ImmutableHashSet<InventoryDescription> Descriptions { get; private init; } = ImmutableHashSet<InventoryDescription>.Empty;
|
||||
|
||||
internal EResult? ErrorCode { get; private init; }
|
||||
internal string? ErrorText { get; private init; }
|
||||
|
@ -55,6 +49,9 @@ internal sealed class InventoryResponse : OptionalResultResponse {
|
|||
[JsonPropertyName("last_assetid")]
|
||||
internal ulong LastAssetID { get; private init; }
|
||||
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("more_items")]
|
||||
[JsonConverter(typeof(BooleanNumberConverter))]
|
||||
internal bool MoreItems { get; private init; }
|
||||
|
||||
[JsonInclude]
|
||||
|
@ -75,201 +72,6 @@ internal sealed class InventoryResponse : OptionalResultResponse {
|
|||
}
|
||||
}
|
||||
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("more_items")]
|
||||
private byte MoreItemsNumber {
|
||||
get => MoreItems ? (byte) 1 : (byte) 0;
|
||||
init => MoreItems = value > 0;
|
||||
}
|
||||
|
||||
[JsonConstructor]
|
||||
private InventoryResponse() { }
|
||||
|
||||
internal sealed class Description {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
[JsonExtensionData]
|
||||
[JsonInclude]
|
||||
internal Dictionary<string, JsonElement>? AdditionalProperties { get; private init; }
|
||||
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("appid")]
|
||||
[JsonRequired]
|
||||
internal uint AppID { get; private init; }
|
||||
|
||||
[JsonInclude]
|
||||
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
|
||||
[JsonPropertyName("classid")]
|
||||
[JsonRequired]
|
||||
internal ulong ClassID { get; private init; }
|
||||
|
||||
[JsonInclude]
|
||||
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
|
||||
[JsonPropertyName("instanceid")]
|
||||
internal ulong InstanceID { get; private init; }
|
||||
|
||||
internal bool Marketable { get; private init; }
|
||||
|
||||
[JsonDisallowNull]
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("tags")]
|
||||
internal ImmutableHashSet<Tag> Tags { get; private init; } = ImmutableHashSet<Tag>.Empty;
|
||||
|
||||
internal bool Tradable { get; private init; }
|
||||
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("marketable")]
|
||||
[JsonRequired]
|
||||
private byte MarketableNumber {
|
||||
get => Marketable ? (byte) 1 : (byte) 0;
|
||||
init => Marketable = value > 0;
|
||||
}
|
||||
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("tradable")]
|
||||
[JsonRequired]
|
||||
private byte TradableNumber {
|
||||
get => Tradable ? (byte) 1 : (byte) 0;
|
||||
init => Tradable = value > 0;
|
||||
}
|
||||
|
||||
// Constructed from trades being received/sent
|
||||
internal Description(uint appID, ulong classID, ulong instanceID, bool marketable, IReadOnlyCollection<Tag>? tags = null) {
|
||||
ArgumentOutOfRangeException.ThrowIfZero(appID);
|
||||
ArgumentOutOfRangeException.ThrowIfZero(classID);
|
||||
|
||||
AppID = appID;
|
||||
ClassID = classID;
|
||||
InstanceID = instanceID;
|
||||
Marketable = marketable;
|
||||
Tradable = true;
|
||||
|
||||
if (tags?.Count > 0) {
|
||||
Tags = tags.ToImmutableHashSet();
|
||||
}
|
||||
}
|
||||
|
||||
[JsonConstructor]
|
||||
private Description() { }
|
||||
|
||||
[UsedImplicitly]
|
||||
public static bool ShouldSerializeAdditionalProperties() => false;
|
||||
}
|
||||
}
|
||||
|
|
45
ArchiSteamFarm/Steam/Data/ItemAction.cs
Normal file
45
ArchiSteamFarm/Steam/Data/ItemAction.cs
Normal file
|
@ -0,0 +1,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.Text.Json.Serialization;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace ArchiSteamFarm.Steam.Data;
|
||||
|
||||
public class ItemAction {
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("link")]
|
||||
[PublicAPI]
|
||||
public string Link { get; private init; } = null!;
|
||||
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("name")]
|
||||
[PublicAPI]
|
||||
public string Name { get; private init; } = null!;
|
||||
|
||||
internal ItemAction(string link, string name) {
|
||||
Link = link;
|
||||
Name = name;
|
||||
}
|
||||
|
||||
[JsonConstructor]
|
||||
private ItemAction() { }
|
||||
}
|
60
ArchiSteamFarm/Steam/Data/ItemDescription.cs
Normal file
60
ArchiSteamFarm/Steam/Data/ItemDescription.cs
Normal file
|
@ -0,0 +1,60 @@
|
|||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// 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.Text.Json.Serialization;
|
||||
using ArchiSteamFarm.Helpers.Json;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace ArchiSteamFarm.Steam.Data;
|
||||
|
||||
public class ItemDescription {
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("color")]
|
||||
[PublicAPI]
|
||||
public string? Color { get; private init; }
|
||||
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("label")]
|
||||
[PublicAPI]
|
||||
public string? Label { get; private init; }
|
||||
|
||||
[JsonDisallowNull]
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("type")]
|
||||
[PublicAPI]
|
||||
public string Type { get; private init; } = null!;
|
||||
|
||||
[JsonDisallowNull]
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("value")]
|
||||
[PublicAPI]
|
||||
public string Value { get; private init; } = null!;
|
||||
|
||||
internal ItemDescription(string type, string value, string color, string label) {
|
||||
Type = type;
|
||||
Value = value;
|
||||
Color = color;
|
||||
Label = label;
|
||||
}
|
||||
|
||||
[JsonConstructor]
|
||||
private ItemDescription() { }
|
||||
}
|
|
@ -32,18 +32,38 @@ public sealed class Tag {
|
|||
[PublicAPI]
|
||||
public string Identifier { get; private init; } = "";
|
||||
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("localized_category_name")]
|
||||
[JsonRequired]
|
||||
[PublicAPI]
|
||||
public string LocalizedIdentifier { get; private init; } = "";
|
||||
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("localized_tag_name")]
|
||||
[JsonRequired]
|
||||
[PublicAPI]
|
||||
public string LocalizedValue { get; private init; } = "";
|
||||
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("internal_name")]
|
||||
[JsonRequired]
|
||||
[PublicAPI]
|
||||
public string Value { get; private init; } = "";
|
||||
|
||||
internal Tag(string identifier, string value) {
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("color")]
|
||||
[PublicAPI]
|
||||
public string? Color { get; private init; }
|
||||
|
||||
internal Tag(string identifier, string value, string localizedIdentifier, string localizedValue, string? color = null) {
|
||||
ArgumentException.ThrowIfNullOrEmpty(identifier);
|
||||
ArgumentNullException.ThrowIfNull(value);
|
||||
|
||||
Identifier = identifier;
|
||||
Value = value;
|
||||
LocalizedIdentifier = localizedIdentifier;
|
||||
LocalizedValue = localizedValue;
|
||||
Color = color;
|
||||
}
|
||||
|
||||
[JsonConstructor]
|
||||
|
|
|
@ -21,14 +21,17 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Text.Json.Serialization;
|
||||
using ArchiSteamFarm.Helpers.Json;
|
||||
using JetBrains.Annotations;
|
||||
using SteamKit2;
|
||||
|
||||
namespace ArchiSteamFarm.Steam.Data;
|
||||
|
||||
// REF: https://developer.valvesoftware.com/wiki/Steam_Web_API/IEconService#CEcon_TradeOffer
|
||||
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
|
||||
public sealed class TradeOffer {
|
||||
[PublicAPI]
|
||||
public IReadOnlyCollection<Asset> ItemsToGiveReadOnly => ItemsToGive;
|
||||
|
@ -36,31 +39,37 @@ public sealed class TradeOffer {
|
|||
[PublicAPI]
|
||||
public IReadOnlyCollection<Asset> ItemsToReceiveReadOnly => ItemsToReceive;
|
||||
|
||||
internal readonly HashSet<Asset> ItemsToGive = [];
|
||||
internal readonly HashSet<Asset> ItemsToReceive = [];
|
||||
|
||||
[PublicAPI]
|
||||
public ulong OtherSteamID64 { get; private set; }
|
||||
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("trade_offer_state")]
|
||||
[PublicAPI]
|
||||
public ETradeOfferState State { get; private set; }
|
||||
|
||||
[JsonInclude]
|
||||
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
|
||||
[JsonPropertyName("tradeofferid")]
|
||||
[PublicAPI]
|
||||
public ulong TradeOfferID { get; private set; }
|
||||
|
||||
// Constructed from trades being received
|
||||
internal TradeOffer(ulong tradeOfferID, uint otherSteamID3, ETradeOfferState state) {
|
||||
ArgumentOutOfRangeException.ThrowIfZero(tradeOfferID);
|
||||
ArgumentOutOfRangeException.ThrowIfZero(otherSteamID3);
|
||||
[JsonDisallowNull]
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("items_to_give")]
|
||||
internal HashSet<Asset> ItemsToGive { get; private init; } = [];
|
||||
|
||||
if (!Enum.IsDefined(state)) {
|
||||
throw new InvalidEnumArgumentException(nameof(state), (int) state, typeof(ETradeOfferState));
|
||||
}
|
||||
[JsonDisallowNull]
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("items_to_receive")]
|
||||
internal HashSet<Asset> ItemsToReceive { get; private init; } = [];
|
||||
|
||||
TradeOfferID = tradeOfferID;
|
||||
OtherSteamID64 = new SteamID(otherSteamID3, EUniverse.Public, EAccountType.Individual);
|
||||
State = state;
|
||||
}
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("accountid_other")]
|
||||
[JsonRequired]
|
||||
private uint OtherSteamID3 { init => OtherSteamID64 = new SteamID(value, EUniverse.Public, EAccountType.Individual); }
|
||||
|
||||
[JsonConstructor]
|
||||
private TradeOffer() { }
|
||||
|
||||
[PublicAPI]
|
||||
public bool IsValidSteamItemsRequest(IReadOnlyCollection<Asset.EType> acceptedTypes) {
|
||||
|
|
43
ArchiSteamFarm/Steam/Data/TradeOffersResponse.cs
Normal file
43
ArchiSteamFarm/Steam/Data/TradeOffersResponse.cs
Normal file
|
@ -0,0 +1,43 @@
|
|||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// 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.Collections.Immutable;
|
||||
using System.Text.Json.Serialization;
|
||||
using ArchiSteamFarm.Helpers.Json;
|
||||
|
||||
namespace ArchiSteamFarm.Steam.Data;
|
||||
|
||||
public class TradeOffersResponse {
|
||||
[JsonDisallowNull]
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("descriptions")]
|
||||
public ImmutableHashSet<InventoryDescription> Descriptions { get; private init; } = [];
|
||||
|
||||
[JsonDisallowNull]
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("trade_offers_received")]
|
||||
public ImmutableHashSet<TradeOffer> TradeOffersReceived { get; private init; } = [];
|
||||
|
||||
[JsonDisallowNull]
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("trade_offers_sent")]
|
||||
public ImmutableHashSet<TradeOffer> TradeOffersSent { get; private init; } = [];
|
||||
}
|
|
@ -639,7 +639,7 @@ public sealed class Trading : IDisposable {
|
|||
HashSet<Asset> inventory;
|
||||
|
||||
try {
|
||||
inventory = await Bot.ArchiWebHandler.GetInventoryAsync().Where(item => !item.IsSteamPointsShopItem && wantedSets.Contains((item.RealAppID, item.Type, item.Rarity))).ToHashSetAsync().ConfigureAwait(false);
|
||||
inventory = await Bot.ArchiHandler.GetMyInventoryAsync().Where(item => !item.IsSteamPointsShopItem && wantedSets.Contains((item.RealAppID, item.Type, item.Rarity))).ToHashSetAsync().ConfigureAwait(false);
|
||||
} catch (HttpRequestException e) {
|
||||
// If we can't check our inventory when not using MatchEverything, this is a temporary failure, try again later
|
||||
Bot.ArchiLogger.LogGenericWarningException(e);
|
||||
|
|
|
@ -22,10 +22,13 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using ArchiSteamFarm.Core;
|
||||
using ArchiSteamFarm.Localization;
|
||||
using ArchiSteamFarm.NLog;
|
||||
using ArchiSteamFarm.Steam.Data;
|
||||
using ArchiSteamFarm.Steam.Integration.Callbacks;
|
||||
using ArchiSteamFarm.Steam.Integration.CMsgs;
|
||||
using JetBrains.Annotations;
|
||||
|
@ -151,6 +154,99 @@ public sealed class ArchiHandler : ClientMsgHandler {
|
|||
return response.Result == EResult.OK ? response.GetDeserializedResponse<CCredentials_LastCredentialChangeTime_Response>() : null;
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
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);
|
||||
|
||||
if (Client.SteamID == null) {
|
||||
throw new InvalidOperationException(nameof(Client.SteamID));
|
||||
}
|
||||
|
||||
SteamID steamID = Client.SteamID;
|
||||
|
||||
// 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;
|
||||
|
||||
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(),
|
||||
start_assetid = currentStartAssetID,
|
||||
count = itemsCountPerRequest
|
||||
};
|
||||
|
||||
SteamUnifiedMessages.ServiceMethodResponse genericResponse = await UnifiedEconService
|
||||
.SendMessage(x => x.GetInventoryItemsWithDescriptions(request))
|
||||
.ToLongRunningTask()
|
||||
.ConfigureAwait(false);
|
||||
|
||||
CEcon_GetInventoryItemsWithDescriptions_Response response = genericResponse.GetDeserializedResponse<CEcon_GetInventoryItemsWithDescriptions_Response>();
|
||||
|
||||
if ((response.total_inventory_count == 0) || (response.assets.Count == 0)) {
|
||||
// Empty inventory
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (response.descriptions.Count == 0) {
|
||||
throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsEmpty, nameof(response.descriptions)));
|
||||
}
|
||||
|
||||
if (response.total_inventory_count > Array.MaxLength) {
|
||||
throw new InvalidOperationException(nameof(response.total_inventory_count));
|
||||
}
|
||||
|
||||
assetIDs ??= new HashSet<ulong>((int) response.total_inventory_count);
|
||||
|
||||
if ((response.assets.Count == 0) || (response.descriptions.Count == 0)) {
|
||||
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)));
|
||||
}
|
||||
|
||||
(ulong ClassID, ulong InstanceID) key = (description.ClassID, description.InstanceID);
|
||||
|
||||
descriptions.TryAdd(key, description);
|
||||
}
|
||||
|
||||
foreach (CEcon_Asset? asset in response.assets) {
|
||||
if (!descriptions.TryGetValue((asset.classid, asset.instanceid), out InventoryDescription? description) || !assetIDs.Add(asset.assetid)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Asset convertedAsset = new(asset.appid, asset.contextid, asset.classid, (uint) asset.amount, description, asset.assetid, asset.instanceid);
|
||||
|
||||
yield return convertedAsset;
|
||||
}
|
||||
|
||||
if (!response.more_items) {
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (response.last_assetid == 0) {
|
||||
throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, Strings.ErrorObjectIsNull, nameof(response.last_assetid)));
|
||||
}
|
||||
|
||||
startAssetID = response.last_assetid;
|
||||
}
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public async Task<Dictionary<uint, string>?> GetOwnedGames(ulong steamID) {
|
||||
if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) {
|
||||
|
|
|
@ -33,6 +33,7 @@ using System.Text;
|
|||
using System.Text.Json.Nodes;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using AngleSharp.Dom;
|
||||
using ArchiSteamFarm.Core;
|
||||
using ArchiSteamFarm.Helpers;
|
||||
|
@ -230,30 +231,30 @@ public sealed class ArchiWebHandler : IDisposable {
|
|||
}
|
||||
|
||||
[PublicAPI]
|
||||
public async IAsyncEnumerable<Asset> GetInventoryAsync(ulong steamID = 0, uint appID = Asset.SteamAppID, ulong contextID = Asset.SteamCommunityContextID) {
|
||||
[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 IAsyncEnumerable<Asset> GetForeignInventoryAsync(ulong steamID, uint appID = Asset.SteamAppID, ulong contextID = Asset.SteamCommunityContextID) {
|
||||
ArgumentOutOfRangeException.ThrowIfZero(appID);
|
||||
ArgumentOutOfRangeException.ThrowIfZero(contextID);
|
||||
|
||||
if (ASF.InventorySemaphore == null) {
|
||||
throw new InvalidOperationException(nameof(ASF.InventorySemaphore));
|
||||
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) {
|
||||
throw new InvalidOperationException(nameof(ASF.InventorySemaphore));
|
||||
}
|
||||
|
||||
ulong startAssetID = 0;
|
||||
|
@ -343,9 +344,9 @@ public sealed class ArchiWebHandler : IDisposable {
|
|||
throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, Strings.ErrorObjectIsNull, $"{nameof(response.Content.Assets)} || {nameof(response.Content.Descriptions)}"));
|
||||
}
|
||||
|
||||
Dictionary<(ulong ClassID, ulong InstanceID), InventoryResponse.Description> descriptions = new();
|
||||
Dictionary<(ulong ClassID, ulong InstanceID), InventoryDescription> descriptions = new();
|
||||
|
||||
foreach (InventoryResponse.Description description in response.Content.Descriptions) {
|
||||
foreach (InventoryDescription description in response.Content.Descriptions) {
|
||||
if (description.ClassID == 0) {
|
||||
throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, Strings.ErrorObjectIsNull, nameof(description.ClassID)));
|
||||
}
|
||||
|
@ -356,20 +357,11 @@ public sealed class ArchiWebHandler : IDisposable {
|
|||
}
|
||||
|
||||
foreach (Asset asset in response.Content.Assets) {
|
||||
if (!descriptions.TryGetValue((asset.ClassID, asset.InstanceID), out InventoryResponse.Description? description) || !assetIDs.Add(asset.AssetID)) {
|
||||
if (!descriptions.TryGetValue((asset.ClassID, asset.InstanceID), out InventoryDescription? description) || !assetIDs.Add(asset.AssetID)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
asset.Marketable = description.Marketable;
|
||||
asset.Tradable = description.Tradable;
|
||||
asset.Tags = description.Tags;
|
||||
asset.RealAppID = description.RealAppID;
|
||||
asset.Type = description.Type;
|
||||
asset.Rarity = description.Rarity;
|
||||
|
||||
if (description.AdditionalProperties != null) {
|
||||
asset.AdditionalProperties = description.AdditionalProperties;
|
||||
}
|
||||
asset.Description = description;
|
||||
|
||||
yield return asset;
|
||||
}
|
||||
|
@ -457,7 +449,7 @@ public sealed class ArchiWebHandler : IDisposable {
|
|||
return null;
|
||||
}
|
||||
|
||||
Dictionary<string, object?> arguments = new(StringComparer.Ordinal) {
|
||||
Dictionary<string, object> arguments = new(StringComparer.Ordinal) {
|
||||
{ "access_token", accessToken }
|
||||
};
|
||||
|
||||
|
@ -484,30 +476,11 @@ public sealed class ArchiWebHandler : IDisposable {
|
|||
arguments["get_descriptions"] = withDescriptions.Value ? "true" : "false";
|
||||
}
|
||||
|
||||
KeyValue? response = null;
|
||||
string queryString = string.Join('&', arguments.Select(static argument => $"{argument.Key}={HttpUtility.UrlEncode(argument.Value.ToString())}"));
|
||||
|
||||
for (byte i = 0; (i < WebBrowser.MaxTries) && (response == null); i++) {
|
||||
if ((i > 0) && (WebLimiterDelay > 0)) {
|
||||
await Task.Delay(WebLimiterDelay).ConfigureAwait(false);
|
||||
}
|
||||
string request = $"/{EconService}/GetTradeOffers/v1/?" + queryString;
|
||||
|
||||
using WebAPI.AsyncInterface econService = Bot.SteamConfiguration.GetAsyncWebAPIInterface(EconService);
|
||||
|
||||
econService.Timeout = WebBrowser.Timeout;
|
||||
|
||||
try {
|
||||
response = await WebLimitRequest(
|
||||
WebAPI.DefaultBaseAddress,
|
||||
|
||||
// ReSharper disable once AccessToDisposedClosure
|
||||
async () => await econService.CallAsync(HttpMethod.Get, "GetTradeOffers", args: arguments).ConfigureAwait(false)
|
||||
).ConfigureAwait(false);
|
||||
} catch (TaskCanceledException e) {
|
||||
Bot.ArchiLogger.LogGenericDebuggingException(e);
|
||||
} catch (Exception e) {
|
||||
Bot.ArchiLogger.LogGenericWarningException(e);
|
||||
}
|
||||
}
|
||||
TradeOffersResponse? response = (await WebLimitRequest(WebAPI.DefaultBaseAddress, async () => await WebBrowser.UrlGetToJsonObject<APIWrappedResponse<TradeOffersResponse>>(new Uri(WebAPI.DefaultBaseAddress, request)).ConfigureAwait(false)).ConfigureAwait(false))?.Content?.Response;
|
||||
|
||||
if (response == null) {
|
||||
Bot.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.ErrorRequestFailedTooManyTimes, WebBrowser.MaxTries));
|
||||
|
@ -515,130 +488,27 @@ public sealed class ArchiWebHandler : IDisposable {
|
|||
return null;
|
||||
}
|
||||
|
||||
Dictionary<(uint AppID, ulong ClassID, ulong InstanceID), InventoryResponse.Description> descriptions = new();
|
||||
|
||||
foreach (KeyValue description in response["descriptions"].Children) {
|
||||
uint appID = description["appid"].AsUnsignedInteger();
|
||||
|
||||
if (appID == 0) {
|
||||
Bot.ArchiLogger.LogNullError(appID);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
ulong classID = description["classid"].AsUnsignedLong();
|
||||
|
||||
if (classID == 0) {
|
||||
Bot.ArchiLogger.LogNullError(classID);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
ulong instanceID = description["instanceid"].AsUnsignedLong();
|
||||
|
||||
(uint AppID, ulong ClassID, ulong InstanceID) key = (appID, classID, instanceID);
|
||||
|
||||
if (descriptions.ContainsKey(key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
bool marketable = description["marketable"].AsBoolean();
|
||||
|
||||
List<KeyValue> tags = description["tags"].Children;
|
||||
|
||||
HashSet<Tag>? parsedTags = null;
|
||||
|
||||
if (tags.Count > 0) {
|
||||
parsedTags = new HashSet<Tag>(tags.Count);
|
||||
|
||||
foreach (KeyValue tag in tags) {
|
||||
string? identifier = tag["category"].AsString();
|
||||
|
||||
if (string.IsNullOrEmpty(identifier)) {
|
||||
Bot.ArchiLogger.LogNullError(identifier);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
string? value = tag["internal_name"].AsString();
|
||||
|
||||
// Apparently, name can be empty, but not null
|
||||
if (value == null) {
|
||||
Bot.ArchiLogger.LogNullError(value);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
parsedTags.Add(new Tag(identifier, value));
|
||||
}
|
||||
}
|
||||
|
||||
InventoryResponse.Description parsedDescription = new(appID, classID, instanceID, marketable, parsedTags);
|
||||
|
||||
descriptions[key] = parsedDescription;
|
||||
}
|
||||
|
||||
IEnumerable<KeyValue> trades = Enumerable.Empty<KeyValue>();
|
||||
IEnumerable<TradeOffer> trades = Enumerable.Empty<TradeOffer>();
|
||||
|
||||
if (receivedOffers.GetValueOrDefault(true)) {
|
||||
trades = trades.Concat(response["trade_offers_received"].Children);
|
||||
trades = trades.Concat(response.TradeOffersReceived);
|
||||
}
|
||||
|
||||
if (sentOffers.GetValueOrDefault(true)) {
|
||||
trades = trades.Concat(response["trade_offers_sent"].Children);
|
||||
trades = trades.Concat(response.TradeOffersSent);
|
||||
}
|
||||
|
||||
Dictionary<(uint AppID, ulong ClassID, ulong InstanceID), InventoryDescription> descriptions = response.Descriptions.ToDictionary(static description => (description.AppID, description.ClassID, description.InstanceID), static description => description);
|
||||
|
||||
HashSet<TradeOffer> result = [];
|
||||
|
||||
foreach (KeyValue trade in trades) {
|
||||
ETradeOfferState state = trade["trade_offer_state"].AsEnum<ETradeOfferState>();
|
||||
|
||||
if (!Enum.IsDefined(state)) {
|
||||
Bot.ArchiLogger.LogNullError(state);
|
||||
|
||||
return null;
|
||||
foreach (TradeOffer tradeOffer in trades.Where(tradeOffer => !activeOnly.HasValue || ((!activeOnly.Value || (tradeOffer.State == ETradeOfferState.Active)) && (activeOnly.Value || (tradeOffer.State != ETradeOfferState.Active))))) {
|
||||
if (tradeOffer.ItemsToGive.Count > 0) {
|
||||
SetDescriptionsToAssets(tradeOffer.ItemsToGive, descriptions);
|
||||
}
|
||||
|
||||
if (activeOnly.HasValue && ((activeOnly.Value && (state != ETradeOfferState.Active)) || (!activeOnly.Value && (state == ETradeOfferState.Active)))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ulong tradeOfferID = trade["tradeofferid"].AsUnsignedLong();
|
||||
|
||||
if (tradeOfferID == 0) {
|
||||
Bot.ArchiLogger.LogNullError(tradeOfferID);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
uint otherSteamID3 = trade["accountid_other"].AsUnsignedInteger();
|
||||
|
||||
if (otherSteamID3 == 0) {
|
||||
Bot.ArchiLogger.LogNullError(otherSteamID3);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
TradeOffer tradeOffer = new(tradeOfferID, otherSteamID3, state);
|
||||
|
||||
List<KeyValue> itemsToGive = trade["items_to_give"].Children;
|
||||
|
||||
if (itemsToGive.Count > 0) {
|
||||
if (!ParseItems(descriptions, itemsToGive, tradeOffer.ItemsToGive)) {
|
||||
Bot.ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.ErrorParsingObject, nameof(itemsToGive)));
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
List<KeyValue> itemsToReceive = trade["items_to_receive"].Children;
|
||||
|
||||
if (itemsToReceive.Count > 0) {
|
||||
if (!ParseItems(descriptions, itemsToReceive, tradeOffer.ItemsToReceive)) {
|
||||
Bot.ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.ErrorParsingObject, nameof(itemsToReceive)));
|
||||
|
||||
return null;
|
||||
}
|
||||
if (tradeOffer.ItemsToReceive.Count > 0) {
|
||||
SetDescriptionsToAssets(tradeOffer.ItemsToReceive, descriptions);
|
||||
}
|
||||
|
||||
result.Add(tradeOffer);
|
||||
|
@ -2389,77 +2259,6 @@ public sealed class ArchiWebHandler : IDisposable {
|
|||
return uri.AbsolutePath.StartsWith("/login", StringComparison.OrdinalIgnoreCase) || uri.Host.Equals("lostauth", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private static bool ParseItems([SuppressMessage("ReSharper", "SuggestBaseTypeForParameter")] Dictionary<(uint AppID, ulong ClassID, ulong InstanceID), InventoryResponse.Description> descriptions, IReadOnlyCollection<KeyValue> input, ICollection<Asset> output) {
|
||||
ArgumentNullException.ThrowIfNull(descriptions);
|
||||
|
||||
if ((input == null) || (input.Count == 0)) {
|
||||
throw new ArgumentNullException(nameof(input));
|
||||
}
|
||||
|
||||
ArgumentNullException.ThrowIfNull(output);
|
||||
|
||||
foreach (KeyValue item in input) {
|
||||
uint appID = item["appid"].AsUnsignedInteger();
|
||||
|
||||
if (appID == 0) {
|
||||
ASF.ArchiLogger.LogNullError(appID);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
ulong contextID = item["contextid"].AsUnsignedLong();
|
||||
|
||||
if (contextID == 0) {
|
||||
ASF.ArchiLogger.LogNullError(contextID);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
ulong classID = item["classid"].AsUnsignedLong();
|
||||
|
||||
if (classID == 0) {
|
||||
ASF.ArchiLogger.LogNullError(classID);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
ulong instanceID = item["instanceid"].AsUnsignedLong();
|
||||
|
||||
(uint AppID, ulong ClassID, ulong InstanceID) key = (appID, classID, instanceID);
|
||||
|
||||
uint amount = item["amount"].AsUnsignedInteger();
|
||||
|
||||
if (amount == 0) {
|
||||
ASF.ArchiLogger.LogNullError(amount);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
ulong assetID = item["assetid"].AsUnsignedLong();
|
||||
|
||||
bool marketable = true;
|
||||
bool tradable = true;
|
||||
ImmutableHashSet<Tag>? tags = null;
|
||||
uint realAppID = 0;
|
||||
Asset.EType type = Asset.EType.Unknown;
|
||||
Asset.ERarity rarity = Asset.ERarity.Unknown;
|
||||
|
||||
if (descriptions.TryGetValue(key, out InventoryResponse.Description? description)) {
|
||||
marketable = description.Marketable;
|
||||
tradable = description.Tradable;
|
||||
tags = description.Tags;
|
||||
realAppID = description.RealAppID;
|
||||
type = description.Type;
|
||||
rarity = description.Rarity;
|
||||
}
|
||||
|
||||
Asset steamAsset = new(appID, contextID, classID, amount, instanceID, assetID, marketable, tradable, tags, realAppID, type, rarity);
|
||||
output.Add(steamAsset);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task<bool> RefreshSession() {
|
||||
if (!Bot.IsConnectedAndLoggedOn) {
|
||||
return false;
|
||||
|
@ -2569,6 +2368,16 @@ public sealed class ArchiWebHandler : IDisposable {
|
|||
return (true, result.ToFrozenSet());
|
||||
}
|
||||
|
||||
private static void SetDescriptionsToAssets(IEnumerable<Asset> assets, [SuppressMessage("ReSharper", "SuggestBaseTypeForParameter")] Dictionary<(uint AppID, ulong ClassID, ulong InstanceID), InventoryDescription> 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);
|
||||
}
|
||||
|
||||
asset.Description = description;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<bool> UnlockParentalAccount(string parentalCode) {
|
||||
ArgumentException.ThrowIfNullOrEmpty(parentalCode);
|
||||
|
||||
|
|
|
@ -429,7 +429,7 @@ public sealed class Actions : IAsyncDisposable, IDisposable {
|
|||
TradingScheduled = false;
|
||||
}
|
||||
|
||||
inventory = await Bot.ArchiWebHandler.GetInventoryAsync(appID: appID, contextID: contextID).Where(item => item.Tradable && filterFunction(item)).ToHashSetAsync().ConfigureAwait(false);
|
||||
inventory = await Bot.ArchiHandler.GetMyInventoryAsync(appID, contextID, true).Where(item => filterFunction(item)).ToHashSetAsync().ConfigureAwait(false);
|
||||
} catch (HttpRequestException e) {
|
||||
Bot.ArchiLogger.LogGenericWarningException(e);
|
||||
|
||||
|
|
|
@ -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.ArchiWebHandler.GetInventoryAsync().Where(static item => item.Type == Asset.EType.BoosterPack).ConfigureAwait(false)) {
|
||||
await foreach (Asset item in Bot.ArchiHandler.GetMyInventoryAsync().Where(static item => item.Type == Asset.EType.BoosterPack).ConfigureAwait(false)) {
|
||||
if (!await Bot.ArchiWebHandler.UnpackBooster(item.RealAppID, item.AssetID).ConfigureAwait(false)) {
|
||||
completeSuccess = false;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue