From 51b2d366ab64d5c5b0d51a20684c61c6e6794947 Mon Sep 17 00:00:00 2001 From: Citrinate Date: Sat, 24 Feb 2024 17:51:23 -0500 Subject: [PATCH 01/13] Update ArchiSteamFarm to 6.0.0.2 --- ArchiSteamFarm | 2 +- BoosterManager/BoosterManager.cs | 47 +-- BoosterManager/Boosters/BoosterDatabase.cs | 12 +- BoosterManager/Boosters/BoosterLastCraft.cs | 12 +- .../Boosters/BoosterPageResponse.cs | 4 +- BoosterManager/Data/ItemIdentifier.cs | 8 +- BoosterManager/Data/ItemListing.cs | 13 +- BoosterManager/Data/SteamData.cs | 27 +- BoosterManager/Data/SteamDataResponse.cs | 33 +- BoosterManager/Handlers/MarketHandler.cs | 36 +-- .../IPC/Api/BoosterManagerController.cs | 14 +- BoosterManager/Json.cs | 305 +++++++++++------- BoosterManager/WebRequest.cs | 15 +- 13 files changed, 309 insertions(+), 219 deletions(-) diff --git a/ArchiSteamFarm b/ArchiSteamFarm index 716b253..7a13895 160000 --- a/ArchiSteamFarm +++ b/ArchiSteamFarm @@ -1 +1 @@ -Subproject commit 716b253a044c9560bea1e9a77cb34afede93c6a3 +Subproject commit 7a13895429f3161c0e32736cd3086c515e42464c diff --git a/BoosterManager/BoosterManager.cs b/BoosterManager/BoosterManager.cs index 66ac1be..4eb51e6 100644 --- a/BoosterManager/BoosterManager.cs +++ b/BoosterManager/BoosterManager.cs @@ -6,8 +6,9 @@ using ArchiSteamFarm.Core; using ArchiSteamFarm.Steam; using ArchiSteamFarm.Plugins.Interfaces; -using Newtonsoft.Json.Linq; using ArchiSteamFarm.Steam.Exchange; +using System.Text.Json; +using ArchiSteamFarm.Helpers.Json; namespace BoosterManager { [Export(typeof(IPlugin))] @@ -22,51 +23,51 @@ public Task OnLoaded() { public async Task OnBotCommand(Bot bot, EAccess access, string message, string[] args, ulong steamID = 0) => await Commands.Response(bot, access, steamID, message, args).ConfigureAwait(false); - public Task OnASFInit(IReadOnlyDictionary? additionalConfigProperties = null) { + public Task OnASFInit(IReadOnlyDictionary? additionalConfigProperties = null) { if (additionalConfigProperties == null) { return Task.FromResult(0); } - foreach (KeyValuePair configProperty in additionalConfigProperties) { + foreach (KeyValuePair configProperty in additionalConfigProperties) { switch (configProperty.Key) { - case "AllowCraftUntradableBoosters" when configProperty.Value.Type == JTokenType.Boolean: { + case "AllowCraftUntradableBoosters" when (configProperty.Value.ValueKind == JsonValueKind.True || configProperty.Value.ValueKind == JsonValueKind.False): { ASF.ArchiLogger.LogGenericInfo("Allow Craft Untradable Boosters : " + configProperty.Value); - BoosterHandler.AllowCraftUntradableBoosters = configProperty.Value.ToObject(); + BoosterHandler.AllowCraftUntradableBoosters = configProperty.Value.GetBoolean(); break; } - case "BoosterDelayBetweenBots" when configProperty.Value.Type == JTokenType.Integer: { + case "BoosterDelayBetweenBots" when configProperty.Value.ValueKind == JsonValueKind.Number: { ASF.ArchiLogger.LogGenericInfo("Booster Delay Between Bots : " + configProperty.Value); - BoosterHandler.UpdateBotDelays((int)configProperty.Value.ToObject()); + BoosterHandler.UpdateBotDelays(configProperty.Value.GetInt32()); break; } - case "BoosterDataAPI" when configProperty.Value.Type == JTokenType.String: { + case "BoosterDataAPI" when configProperty.Value.ValueKind == JsonValueKind.String: { ASF.ArchiLogger.LogGenericInfo("Booster Data API : " + configProperty.Value); - DataHandler.BoosterDataAPI = new Uri(configProperty.Value.ToObject()!); + DataHandler.BoosterDataAPI = new Uri(configProperty.Value.GetString()!); break; } - case "InventoryHistoryAPI" when configProperty.Value.Type == JTokenType.String: { + case "InventoryHistoryAPI" when configProperty.Value.ValueKind == JsonValueKind.String: { ASF.ArchiLogger.LogGenericInfo("Inventory History API : " + configProperty.Value); - DataHandler.InventoryHistoryAPI = new Uri(configProperty.Value.ToObject()!); + DataHandler.InventoryHistoryAPI = new Uri(configProperty.Value.GetString()!); break; } - case "MarketListingsAPI" when configProperty.Value.Type == JTokenType.String: { + case "MarketListingsAPI" when configProperty.Value.ValueKind == JsonValueKind.String: { ASF.ArchiLogger.LogGenericInfo("Market Listings API : " + configProperty.Value); - DataHandler.MarketListingsAPI = new Uri(configProperty.Value.ToObject()!); + DataHandler.MarketListingsAPI = new Uri(configProperty.Value.GetString()!); break; } - case "MarketHistoryAPI" when configProperty.Value.Type == JTokenType.String: { + case "MarketHistoryAPI" when configProperty.Value.ValueKind == JsonValueKind.String: { ASF.ArchiLogger.LogGenericInfo("Market History API : " + configProperty.Value); - DataHandler.MarketHistoryAPI = new Uri(configProperty.Value.ToObject()!); + DataHandler.MarketHistoryAPI = new Uri(configProperty.Value.GetString()!); break; } - case "LogDataPageDelay" or "MarketHistoryDelay" when configProperty.Value.Type == JTokenType.Integer: { + case "LogDataPageDelay" or "MarketHistoryDelay" when configProperty.Value.ValueKind == JsonValueKind.Number: { ASF.ArchiLogger.LogGenericInfo("Log Data Page Delay : " + configProperty.Value); - DataHandler.LogDataPageDelay = configProperty.Value.ToObject(); + DataHandler.LogDataPageDelay = configProperty.Value.GetUInt32(); break; } - case "InventoryHistoryAppFilter" when configProperty.Value.Type == JTokenType.Array && configProperty.Value.Any(): { + case "InventoryHistoryAppFilter" when configProperty.Value.ValueKind == JsonValueKind.Array && configProperty.Value.GetArrayLength() > 0: { ASF.ArchiLogger.LogGenericInfo("Inventory History App Filter : " + string.Join(",", configProperty.Value)); - List? appIDs = configProperty.Value.ToObject>(); + List? appIDs = configProperty.Value.ToJsonObject>(); if (appIDs == null) { ASF.ArchiLogger.LogNullError(appIDs); } else { @@ -80,18 +81,18 @@ public Task OnASFInit(IReadOnlyDictionary? additionalConfigPrope return Task.FromResult(0); } - public Task OnBotInitModules(Bot bot, IReadOnlyDictionary? additionalConfigProperties = null) { + public Task OnBotInitModules(Bot bot, IReadOnlyDictionary? additionalConfigProperties = null) { BoosterHandler.AddHandler(bot); if (additionalConfigProperties == null) { return Task.FromResult(0); } - foreach (KeyValuePair configProperty in additionalConfigProperties) { + foreach (KeyValuePair configProperty in additionalConfigProperties) { switch (configProperty.Key) { - case "GamesToBooster" when configProperty.Value.Type == JTokenType.Array && configProperty.Value.Any(): { + case "GamesToBooster" when configProperty.Value.ValueKind == JsonValueKind.Array && configProperty.Value.GetArrayLength() > 0: { bot.ArchiLogger.LogGenericInfo("Games To Booster : " + string.Join(",", configProperty.Value)); - HashSet? gameIDs = configProperty.Value.ToObject>(); + HashSet? gameIDs = configProperty.Value.ToJsonObject>(); if (gameIDs == null) { bot.ArchiLogger.LogNullError(gameIDs); } else { diff --git a/BoosterManager/Boosters/BoosterDatabase.cs b/BoosterManager/Boosters/BoosterDatabase.cs index 969eedf..7b3df05 100644 --- a/BoosterManager/Boosters/BoosterDatabase.cs +++ b/BoosterManager/Boosters/BoosterDatabase.cs @@ -1,15 +1,17 @@ using System; using System.Collections.Concurrent; using System.IO; +using System.Text.Json.Serialization; +using System.Threading.Tasks; using ArchiSteamFarm.Core; using ArchiSteamFarm.Helpers; +using ArchiSteamFarm.Helpers.Json; using ArchiSteamFarm.Localization; -using Newtonsoft.Json; namespace BoosterManager { internal sealed class BoosterDatabase : SerializableFile { - [JsonProperty(Required = Required.DisallowNull)] - private readonly ConcurrentDictionary BoosterLastCrafts = new(); + [JsonInclude] + private ConcurrentDictionary BoosterLastCrafts { get; init; } = new(); [JsonConstructor] private BoosterDatabase() { } @@ -22,6 +24,8 @@ private BoosterDatabase(string filePath) : this() { FilePath = filePath; } + protected override Task Save() => Save(this); + internal static BoosterDatabase? CreateOrLoad(string filePath) { if (string.IsNullOrEmpty(filePath)) { throw new ArgumentNullException(nameof(filePath)); @@ -42,7 +46,7 @@ private BoosterDatabase(string filePath) : this() { return null; } - boosterDatabase = JsonConvert.DeserializeObject(json); + boosterDatabase = json.ToJsonObject(); } catch (Exception e) { ASF.ArchiLogger.LogGenericException(e); diff --git a/BoosterManager/Boosters/BoosterLastCraft.cs b/BoosterManager/Boosters/BoosterLastCraft.cs index 0dbb145..0104e4f 100644 --- a/BoosterManager/Boosters/BoosterLastCraft.cs +++ b/BoosterManager/Boosters/BoosterLastCraft.cs @@ -1,13 +1,15 @@ using System; -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace BoosterManager { internal sealed class BoosterLastCraft { - [JsonProperty(Required = Required.Always)] - internal DateTime CraftTime; + [JsonInclude] + [JsonRequired] + internal DateTime CraftTime { get; set; } - [JsonProperty(Required = Required.Always)] - internal int BoosterDelay; + [JsonInclude] + [JsonRequired] + internal int BoosterDelay { get; set; } [JsonConstructor] private BoosterLastCraft() { } diff --git a/BoosterManager/Boosters/BoosterPageResponse.cs b/BoosterManager/Boosters/BoosterPageResponse.cs index 4605cef..edcf62b 100644 --- a/BoosterManager/Boosters/BoosterPageResponse.cs +++ b/BoosterManager/Boosters/BoosterPageResponse.cs @@ -1,10 +1,10 @@ using System; using System.Collections.Generic; +using System.Text.Json; using System.Text.RegularExpressions; using AngleSharp.Dom; using ArchiSteamFarm.Localization; using ArchiSteamFarm.Steam; -using Newtonsoft.Json; namespace BoosterManager { internal sealed class BoosterPageResponse { @@ -37,7 +37,7 @@ internal BoosterPageResponse(Bot bot, IDocument? boosterPage) { IEnumerable? enumerableBoosters; try { - enumerableBoosters = JsonConvert.DeserializeObject>(info.Value, new Steam.BoosterInfoDateConverter()); + enumerableBoosters = JsonSerializer.Deserialize>(info.Value, new JsonSerializerOptions { NumberHandling = System.Text.Json.Serialization.JsonNumberHandling.AllowReadingFromString }); } catch (JsonException ex) { Bot.ArchiLogger.LogGenericError(ex.Message); diff --git a/BoosterManager/Data/ItemIdentifier.cs b/BoosterManager/Data/ItemIdentifier.cs index e9b884e..3508c9b 100644 --- a/BoosterManager/Data/ItemIdentifier.cs +++ b/BoosterManager/Data/ItemIdentifier.cs @@ -92,10 +92,10 @@ internal bool IsItemMatch(Asset item) { } if (TextID != null) { - string? name = item.AdditionalPropertiesReadOnly?["name"].ToObject(); - string? marketName = item.AdditionalPropertiesReadOnly?["market_name"].ToObject(); - string? marketHashName = item.AdditionalPropertiesReadOnly?["market_hash_name"].ToObject(); - string? type = item.AdditionalPropertiesReadOnly?["type"].ToObject(); + string? name = item.AdditionalPropertiesReadOnly?["name"].GetString(); + string? marketName = item.AdditionalPropertiesReadOnly?["market_name"].GetString(); + string? marketHashName = item.AdditionalPropertiesReadOnly?["market_hash_name"].GetString(); + string? type = item.AdditionalPropertiesReadOnly?["type"].GetString(); if ((name == null || !name.Contains(TextID)) && (marketName == null || !marketName.Contains(TextID)) diff --git a/BoosterManager/Data/ItemListing.cs b/BoosterManager/Data/ItemListing.cs index f9ca089..2f4ed64 100644 --- a/BoosterManager/Data/ItemListing.cs +++ b/BoosterManager/Data/ItemListing.cs @@ -1,6 +1,7 @@ using System; +using System.Text.Json; +using System.Text.Json.Nodes; using ArchiSteamFarm.Core; -using Newtonsoft.Json.Linq; namespace BoosterManager { internal sealed class ItemListing { @@ -12,10 +13,10 @@ internal sealed class ItemListing { internal ulong ContextID; internal ulong ClassID; - internal ItemListing(JObject listing) { + internal ItemListing(JsonObject listing) { string? name = listing["asset"]?["name"]?.ToString(); if (name == null) { - ASF.ArchiLogger.LogNullError(name); + ASF.ArchiLogger.LogNullError(name); throw new InvalidOperationException(); } @@ -37,19 +38,19 @@ internal ItemListing(JObject listing) { throw new InvalidOperationException(); } - uint? appID = listing["asset"]?["appid"]?.ToObject(); + uint? appID = listing["asset"]?["appid"]?.GetValue(); if (appID == null) { ASF.ArchiLogger.LogNullError(appID); throw new InvalidOperationException(); } - ulong? contextID = listing["asset"]?["contextid"]?.ToObject(); + ulong? contextID = listing["asset"]?["contextid"]?.GetValue(); if (contextID == null) { ASF.ArchiLogger.LogNullError(contextID); throw new InvalidOperationException(); } - ulong? classID = listing["asset"]?["classid"]?.ToObject(); + ulong? classID = listing["asset"]?["classid"]?.GetValue(); if (classID == null) { ASF.ArchiLogger.LogNullError(classID); throw new InvalidOperationException(); diff --git a/BoosterManager/Data/SteamData.cs b/BoosterManager/Data/SteamData.cs index 71dc0b7..b497091 100644 --- a/BoosterManager/Data/SteamData.cs +++ b/BoosterManager/Data/SteamData.cs @@ -1,23 +1,28 @@ using System; +using System.Text.Json.Serialization; using ArchiSteamFarm.Steam; -using Newtonsoft.Json; namespace BoosterManager { internal sealed class SteamData { - [JsonProperty(PropertyName = "steamid")] - public ulong SteamID; + [JsonInclude] + [JsonPropertyName("steamid")] + public ulong SteamID { get; private init; } - [JsonProperty(PropertyName = "source")] - public string Source; + [JsonInclude] + [JsonPropertyName("source")] + public string Source { get; private init; } - [JsonProperty(PropertyName = "page")] - public uint? Page; + [JsonInclude] + [JsonPropertyName("page")] + public uint? Page { get; private init; } - [JsonProperty(PropertyName = "cursor")] - public Steam.InventoryHistoryCursor? Cursor; + [JsonInclude] + [JsonPropertyName("cursor")] + public Steam.InventoryHistoryCursor? Cursor { get; private init; } - [JsonProperty(PropertyName = "data")] - public T Data; + [JsonInclude] + [JsonPropertyName("data")] + public T Data { get; private init; } internal SteamData(Bot bot, T steamData, Uri source, uint? page, Steam.InventoryHistoryCursor? cursor) { SteamID = bot.SteamID; diff --git a/BoosterManager/Data/SteamDataResponse.cs b/BoosterManager/Data/SteamDataResponse.cs index 919f9d8..e08b6be 100644 --- a/BoosterManager/Data/SteamDataResponse.cs +++ b/BoosterManager/Data/SteamDataResponse.cs @@ -1,24 +1,31 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace BoosterManager { internal sealed class SteamDataResponse { - [JsonProperty(PropertyName = "success", Required = Required.Always)] - internal readonly bool Success = false; + [JsonInclude] + [JsonPropertyName("success")] + [JsonRequired] + internal bool Success { get; private init; } = false; - [JsonProperty(PropertyName = "message", Required = Required.Default)] - internal readonly string? Message = null; + [JsonInclude] + [JsonPropertyName("message")] + internal string? Message { get; private init; } = null; - [JsonProperty(PropertyName = "show_message", Required = Required.DisallowNull)] - internal readonly bool ShowMessage = true; + [JsonInclude] + [JsonPropertyName("show_message")] + internal bool ShowMessage { get; private init; } = true; - [JsonProperty(PropertyName = "get_next_page", Required = Required.DisallowNull)] - internal readonly bool GetNextPage = false; + [JsonInclude] + [JsonPropertyName("get_next_page")] + internal bool GetNextPage { get; private init; } = false; - [JsonProperty(PropertyName = "next_page", Required = Required.Default)] - internal readonly uint? NextPage = null; + [JsonInclude] + [JsonPropertyName("next_page")] + internal uint? NextPage { get; private init; } = null; - [JsonProperty(PropertyName = "next_cursor", Required = Required.Default)] - internal readonly Steam.InventoryHistoryCursor? NextCursor = null; + [JsonInclude] + [JsonPropertyName("next_cursor")] + internal Steam.InventoryHistoryCursor? NextCursor { get; private init; } = null; [JsonConstructor] internal SteamDataResponse() { } diff --git a/BoosterManager/Handlers/MarketHandler.cs b/BoosterManager/Handlers/MarketHandler.cs index 616bc86..c2839a4 100644 --- a/BoosterManager/Handlers/MarketHandler.cs +++ b/BoosterManager/Handlers/MarketHandler.cs @@ -1,11 +1,11 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text.Json.Nodes; using System.Threading.Tasks; using ArchiSteamFarm.Localization; using ArchiSteamFarm.Steam; using ArchiSteamFarm.Steam.Data; -using Newtonsoft.Json.Linq; namespace BoosterManager { internal static class MarketHandler { @@ -34,15 +34,15 @@ internal static async Task GetValue(Bot bot, uint subtractFrom = 0) { } private static async Task GetMarketListingsValue(Bot bot) { - Dictionary? listings = await GetFullMarketListings(bot).ConfigureAwait(false); + Dictionary? listings = await GetFullMarketListings(bot).ConfigureAwait(false); if (listings == null) { return null; } uint listingsValue = 0; - foreach (JObject listing in listings.Values) { - uint? price = listing["price"]?.ToObject(); + foreach (JsonObject listing in listings.Values) { + uint? price = listing["price"]?.GetValue(); if (price == null) { bot.ArchiLogger.LogNullError(price); @@ -106,8 +106,8 @@ internal static async Task FindAndRemoveListings(Bot bot, List?> GetFullMarketListings(Bot bot, uint delayInMilliseconds = 5000) { - Dictionary? listings = null; + private static async Task?> GetFullMarketListings(Bot bot, uint delayInMilliseconds = 5000) { + Dictionary? listings = null; uint totalListings = 0; uint listingsCollected = 0; do { @@ -128,27 +128,27 @@ internal static async Task FindAndRemoveListings(Bot bot, List((int)totalListings); + listings = new Dictionary((int)totalListings); } - foreach (JObject listing in marketListings.Listings) { - ulong? listingid = listing["listingid"]?.ToObject(); + foreach (JsonNode? listing in marketListings.Listings) { + if (listing == null) { + bot.ArchiLogger.LogNullError(listing); + + return null; + } + + ulong? listingid = listing["listingid"]?.GetValue(); if (listingid == null) { bot.ArchiLogger.LogNullError(listingid); return null; } - if (listings.TryAdd(listingid.Value, listing)) { + if (listings.TryAdd(listingid.Value, listing.AsObject())) { listingsCollected++; } } @@ -158,14 +158,14 @@ internal static async Task FindAndRemoveListings(Bot bot, List>?> GetListingIDsFromIdentifiers(Bot bot, List itemIdentifiers) { - Dictionary? listings = await GetFullMarketListings(bot).ConfigureAwait(false); + Dictionary? listings = await GetFullMarketListings(bot).ConfigureAwait(false); if (listings == null) { return null; } Dictionary> filteredListings = new Dictionary>(); - foreach ((ulong listingID, JObject listing) in listings) { + foreach ((ulong listingID, JsonObject listing) in listings) { ItemListing item; try { item = new ItemListing(listing); diff --git a/BoosterManager/IPC/Api/BoosterManagerController.cs b/BoosterManager/IPC/Api/BoosterManagerController.cs index dacdb1c..83a27ef 100644 --- a/BoosterManager/IPC/Api/BoosterManagerController.cs +++ b/BoosterManager/IPC/Api/BoosterManagerController.cs @@ -2,13 +2,13 @@ using System.Collections.Generic; using System.Linq; using System.Net; +using System.Text.Json; using System.Threading.Tasks; using ArchiSteamFarm.IPC.Controllers.Api; using ArchiSteamFarm.IPC.Responses; using ArchiSteamFarm.Localization; using ArchiSteamFarm.Steam; using Microsoft.AspNetCore.Mvc; -using Newtonsoft.Json.Linq; using Swashbuckle.AspNetCore.Annotations; namespace BoosterManager { @@ -150,7 +150,7 @@ public async Task> InventoryHistory(string botName /// [HttpGet("{botName:required}/GetBadgeInfo/{appID:required}")] [SwaggerOperation (Summary = "Retrieves badge info for given bot.")] - [ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.OK)] + [ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.OK)] [ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.BadRequest)] public async Task> GetBadgeInfo(string botName, uint appID, uint border = 0) { if (string.IsNullOrEmpty(botName)) { @@ -166,12 +166,12 @@ public async Task> GetBadgeInfo(string botName, ui return BadRequest(new GenericResponse(false, Strings.BotNotConnected)); } - JToken? badgeInfo = await WebRequest.GetBadgeInfo(bot, appID, border).ConfigureAwait(false); + JsonDocument? badgeInfo = await WebRequest.GetBadgeInfo(bot, appID, border).ConfigureAwait(false); if (badgeInfo == null) { return BadRequest(new GenericResponse(false, "Failed to fetch badge info")); } - return Ok(new GenericResponse(true, badgeInfo)); + return Ok(new GenericResponse(true, badgeInfo)); } /// @@ -179,7 +179,7 @@ public async Task> GetBadgeInfo(string botName, ui /// [HttpGet("{botNames:required}/GetPriceHistory/{appID:required}/{hashName:required}")] [SwaggerOperation (Summary = "Retrieves price history for market items.")] - [ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.OK)] + [ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.OK)] [ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.BadRequest)] public async Task> GetPriceHistory(string botNames, uint appID, string hashName) { if (string.IsNullOrEmpty(botNames)) { @@ -200,12 +200,12 @@ public async Task> GetPriceHistory(string botNames return BadRequest(new GenericResponse(false, Strings.BotNotConnected)); } - JToken? priceHistory = await WebRequest.GetPriceHistory(bot, appID, hashName).ConfigureAwait(false); + JsonDocument? priceHistory = await WebRequest.GetPriceHistory(bot, appID, hashName).ConfigureAwait(false); if (priceHistory == null) { return BadRequest(new GenericResponse(false, "Failed to fetch price history")); } - return Ok(new GenericResponse(true, priceHistory)); + return Ok(new GenericResponse(true, priceHistory)); } } } \ No newline at end of file diff --git a/BoosterManager/Json.cs b/BoosterManager/Json.cs index e4eedd8..3a754f6 100644 --- a/BoosterManager/Json.cs +++ b/BoosterManager/Json.cs @@ -1,15 +1,18 @@ using System; using System.Collections.Generic; using System.Globalization; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; +using System.Text.Json; +using System.Text.Json.Nodes; +using System.Text.Json.Serialization; using SteamKit2; #pragma warning disable 649 namespace BoosterManager { internal static class Steam { internal class EResultResponse { - [JsonProperty(PropertyName = "success", Required = Required.Always)] + [JsonInclude] + [JsonPropertyName("success")] + [JsonRequired] public readonly EResult Result; [JsonConstructor] @@ -17,121 +20,180 @@ public EResultResponse() { } } internal sealed class BoosterInfo { - [JsonProperty(PropertyName = "appid", Required = Required.Always)] - internal readonly uint AppID; - - [JsonProperty(PropertyName = "name", Required = Required.Always)] - internal readonly string Name = ""; - - [JsonProperty(PropertyName = "series", Required = Required.Always)] - internal readonly uint Series; - - [JsonProperty(PropertyName = "price", Required = Required.Always)] - internal readonly uint Price; - - [JsonProperty(PropertyName = "unavailable", Required = Required.DisallowNull)] - internal readonly bool Unavailable; - - [JsonProperty(PropertyName = "available_at_time", Required = Required.Default)] - internal readonly DateTime? AvailableAtTime; + [JsonInclude] + [JsonPropertyName("appid")] + [JsonRequired] + internal uint AppID { get; private init; } + + [JsonInclude] + [JsonPropertyName("name")] + [JsonRequired] + internal string Name { get; private init; } = ""; + + [JsonInclude] + [JsonPropertyName("series")] + [JsonRequired] + internal uint Series { get; private init; } + + [JsonInclude] + [JsonPropertyName("price")] + [JsonRequired] + internal uint Price { get; private init; } + + [JsonInclude] + [JsonPropertyName("unavailable")] + internal bool Unavailable { get; private init; } + + [JsonInclude] + [JsonPropertyName("available_at_time")] + [JsonConverter(typeof(BoosterInfoDateConverter))] + internal DateTime? AvailableAtTime { get; private init; } [JsonConstructor] public BoosterInfo() { } } internal sealed class BoostersResponse { - [JsonProperty(PropertyName = "goo_amount", Required = Required.Always)] - internal readonly uint GooAmount; + [JsonInclude] + [JsonPropertyName("goo_amount")] + [JsonRequired] + internal uint GooAmount { get; private init; } - [JsonProperty(PropertyName = "tradable_goo_amount", Required = Required.Always)] - internal readonly uint TradableGooAmount; + [JsonInclude] + [JsonPropertyName("tradable_goo_amount")] + [JsonRequired] + internal uint TradableGooAmount { get; private init; } - [JsonProperty(PropertyName = "untradable_goo_amount", Required = Required.Always)] - internal readonly uint UntradableGooAmount; + [JsonInclude] + [JsonPropertyName("untradable_goo_amount")] + [JsonRequired] + internal uint UntradableGooAmount { get; private init; } - [JsonProperty(PropertyName = "purchase_result", Required = Required.DisallowNull)] - internal readonly EResultResponse Result = new(); + [JsonInclude] + [JsonPropertyName("purchase_result")] + internal EResultResponse Result { get; private init; } = new(); - [JsonProperty(PropertyName = "purchase_eresult", Required = Required.DisallowNull)] - internal readonly EResult PurchaseEResult; + [JsonInclude] + [JsonPropertyName("purchase_eresult")] + internal EResult PurchaseEResult { get; private init; } [JsonConstructor] private BoostersResponse() { } } internal sealed class MarketListingsResponse { - [JsonProperty(PropertyName = "success", Required = Required.Always)] - internal readonly bool Success; - - [JsonProperty(PropertyName = "pagesize", Required = Required.Always)] - internal readonly int PageSize; - - [JsonProperty(PropertyName = "total_count", Required = Required.Always)] - internal readonly uint TotalCount; - - [JsonProperty(PropertyName = "assets", Required = Required.Always)] - internal readonly JToken? Assets; - - [JsonProperty(PropertyName = "start", Required = Required.Always)] - internal readonly uint Start; - - [JsonProperty(PropertyName = "num_active_listings", Required = Required.Always)] - internal readonly uint NumActiveListings; - - [JsonProperty(PropertyName = "listings", Required = Required.AllowNull)] - internal readonly JArray? Listings; - - [JsonProperty(PropertyName = "listings_on_hold", Required = Required.Always)] - internal readonly JArray? ListingsOnHold = new(); - - [JsonProperty(PropertyName = "listings_to_confirm", Required = Required.Always)] - internal readonly JArray ListingsToConfirm = new(); - - [JsonProperty(PropertyName = "buy_orders", Required = Required.Always)] - internal readonly JArray BuyOrders = new(); + [JsonInclude] + [JsonPropertyName("success")] + [JsonRequired] + internal bool Success { get; private init; } + + [JsonInclude] + [JsonPropertyName("pagesize")] + [JsonRequired] + internal int PageSize { get; private init; } + + [JsonInclude] + [JsonPropertyName("total_count")] + [JsonRequired] + internal uint TotalCount { get; private init; } + + [JsonInclude] + [JsonPropertyName("assets")] + [JsonRequired] + internal JsonElement? Assets { get; private init; } + + [JsonInclude] + [JsonPropertyName("start")] + [JsonRequired] + internal uint Start { get; private init; } + + [JsonInclude] + [JsonPropertyName("num_active_listings")] + [JsonRequired] + internal uint NumActiveListings { get; private init; } + + [JsonInclude] + [JsonPropertyName("listings")] + [JsonRequired] + internal JsonArray? Listings { get; private init; } + + [JsonInclude] + [JsonPropertyName("listings_on_hold")] + [JsonRequired] + internal JsonArray? ListingsOnHold { get; private init; } = new(); + + [JsonInclude] + [JsonPropertyName("listings_to_confirm")] + [JsonRequired] + internal JsonArray ListingsToConfirm { get; private init; } = new(); + + [JsonInclude] + [JsonPropertyName("buy_orders")] + [JsonRequired] + internal JsonArray BuyOrders { get; private init; } = new(); [JsonConstructor] private MarketListingsResponse() { } } internal sealed class MarketHistoryResponse { - [JsonProperty(PropertyName = "success", Required = Required.Always)] - internal readonly bool Success; - - [JsonProperty(PropertyName = "pagesize", Required = Required.Always)] - internal readonly uint PageSize; - - [JsonProperty(PropertyName = "total_count", Required = Required.Always)] - internal readonly uint? TotalCount; - - [JsonProperty(PropertyName = "start", Required = Required.Always)] - internal readonly uint Start; - - [JsonProperty(PropertyName = "assets", Required = Required.Always)] - internal readonly JToken? Assets; - - [JsonProperty(PropertyName = "events", Required = Required.Default)] - internal readonly JArray? Events = new(); - - [JsonProperty(PropertyName = "purchases", Required = Required.Default)] - internal readonly JToken? Purchases; - - [JsonProperty(PropertyName = "listings", Required = Required.Always)] - internal readonly JToken? Listings; + [JsonInclude] + [JsonPropertyName("success")] + [JsonRequired] + internal bool Success { get; private init; } + + [JsonInclude] + [JsonPropertyName("pagesize")] + [JsonRequired] + internal uint PageSize { get; private init; } + + [JsonInclude] + [JsonPropertyName("total_count")] + [JsonRequired] + internal uint? TotalCount { get; private init; } + + [JsonInclude] + [JsonPropertyName("start")] + [JsonRequired] + internal uint Start { get; private init; } + + [JsonInclude] + [JsonPropertyName("assets")] + [JsonRequired] + internal JsonElement? Assets { get; private init; } + + [JsonInclude] + [JsonPropertyName("events")] + internal JsonArray? Events { get; private init; } + + [JsonInclude] + [JsonPropertyName("purchases")] + internal JsonElement? Purchases { get; private init; } + + [JsonInclude] + [JsonPropertyName("listings")] + [JsonRequired] + internal JsonElement? Listings { get; private init; } [JsonConstructor] private MarketHistoryResponse() { } } internal sealed class InventoryHistoryCursor { - [JsonProperty(PropertyName = "time", Required = Required.Always)] - internal readonly uint Time; + [JsonInclude] + [JsonPropertyName("time")] + [JsonRequired] + internal uint Time { get; private init; } - [JsonProperty(PropertyName = "time_frac", Required = Required.Always)] - internal readonly uint TimeFrac; + [JsonInclude] + [JsonPropertyName("time_frac")] + [JsonRequired] + internal uint TimeFrac { get; private init; } - [JsonProperty(PropertyName = "s", Required = Required.Always)] - internal readonly string S = ""; + [JsonInclude] + [JsonPropertyName("s")] + [JsonRequired] + internal string S { get; private init; } = ""; [JsonConstructor] internal InventoryHistoryCursor() { } @@ -144,41 +206,52 @@ internal InventoryHistoryCursor(uint time, uint timeFrac, string s) { } internal sealed class InventoryHistoryResponse { - [JsonProperty(PropertyName = "success", Required = Required.Always)] - internal readonly bool Success; + [JsonInclude] + [JsonPropertyName("success")] + [JsonRequired] + internal bool Success { get; private init; } - [JsonProperty(PropertyName = "error", Required = Required.Default)] - internal readonly string? Error = ""; + [JsonInclude] + [JsonPropertyName("error")] + internal string? Error { get; private init; } = ""; - [JsonProperty(PropertyName = "html", Required = Required.Default)] - internal readonly string Html = ""; + [JsonInclude] + [JsonPropertyName("html")] + internal string Html { get; private init; } = ""; - [JsonProperty(PropertyName = "num", Required = Required.Default)] - internal readonly uint Num = 0; + [JsonInclude] + [JsonPropertyName("num")] + internal uint Num { get; private init; } = 0; - [JsonProperty(PropertyName = "descriptions", Required = Required.Default)] - internal readonly JToken? Descriptions; + [JsonInclude] + [JsonPropertyName("descriptions")] + internal JsonElement? Descriptions { get; private init; } - [JsonProperty(PropertyName = "apps", Required = Required.Default)] - internal readonly JArray Apps = new(); + [JsonInclude] + [JsonPropertyName("apps")] + internal JsonArray Apps { get; private init; } = new(); - [JsonProperty(PropertyName = "cursor", Required = Required.Default)] - internal readonly InventoryHistoryCursor? Cursor; + [JsonInclude] + [JsonPropertyName("cursor")] + internal InventoryHistoryCursor? Cursor { get; private init; } [JsonConstructor] private InventoryHistoryResponse() { } } internal sealed class ExchangeGooResponse { - [JsonProperty(PropertyName = "success", Required = Required.Always)] - internal readonly bool Success; + [JsonInclude] + [JsonPropertyName("success")] + [JsonRequired] + internal bool Success { get; private init; } [JsonConstructor] private ExchangeGooResponse() { } } // https://stackoverflow.com/a/51319347 - internal sealed class BoosterInfoDateConverter : JsonConverter { + // internal sealed class BoosterInfoDateConverter : JsonConverter { + internal sealed class BoosterInfoDateConverter : JsonConverter { private List DateTimeFormats = new List() { "MMM d @ h:mmtt", "MMM d, yyyy @ h:mmtt", @@ -186,30 +259,24 @@ internal sealed class BoosterInfoDateConverter : JsonConverter { "d MMM, yyyy @ h:mmtt" }; - public override bool CanConvert(Type objectType) { - return objectType == typeof(DateTime?); - } - - public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) { - if (reader.Value == null) { + public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { + string? dateString = reader.GetString(); + if (dateString == null) { throw new JsonException("Unable to parse null as a date."); } - string dateString = (string)reader.Value; + DateTime date; foreach (string format in DateTimeFormats) { if (DateTime.TryParseExact(dateString, format, new CultureInfo("en-US"), DateTimeStyles.None, out date)) { return date; } } + throw new JsonException("Unable to parse \"" + dateString + "\" as a date."); } - - public override bool CanWrite { - get { return false; } - } - - public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) { - throw new NotImplementedException(); + + public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) { + writer.WriteStringValue(value.ToString("s", System.Globalization.CultureInfo.InvariantCulture)); } } } diff --git a/BoosterManager/WebRequest.cs b/BoosterManager/WebRequest.cs index b39e305..55a0733 100644 --- a/BoosterManager/WebRequest.cs +++ b/BoosterManager/WebRequest.cs @@ -1,13 +1,14 @@ using System; using System.Collections.Generic; using System.Net; +using System.Text.Json; using System.Threading.Tasks; +using ArchiSteamFarm.Core; using ArchiSteamFarm.Steam; using ArchiSteamFarm.Steam.Data; using ArchiSteamFarm.Steam.Integration; using ArchiSteamFarm.Web; using ArchiSteamFarm.Web.Responses; -using Newtonsoft.Json.Linq; namespace BoosterManager { internal static class WebRequest { @@ -19,7 +20,9 @@ internal static class WebRequest { BoosterPageResponse boosterPageResponse = new BoosterPageResponse(bot, boosterPage?.Content); return (boosterPageResponse, request); - } catch (Exception) { + } catch (Exception e) { + ASF.ArchiLogger.LogGenericException(e); + return (null, request); } } @@ -120,15 +123,15 @@ internal static async Task SendSteamData(Uri request, Bot return response.Content; } - internal static async Task GetBadgeInfo(Bot bot, uint appID, uint border = 0) { + internal static async Task GetBadgeInfo(Bot bot, uint appID, uint border = 0) { Uri request = new(ArchiWebHandler.SteamCommunityURL, String.Format("/profiles/{0}/ajaxgetbadgeinfo/{1}?border={2}", bot.SteamID, appID, border)); - ObjectResponse? badgeInfoResponse = await bot.ArchiWebHandler.UrlGetToJsonObjectWithSession(request).ConfigureAwait(false); + ObjectResponse? badgeInfoResponse = await bot.ArchiWebHandler.UrlGetToJsonObjectWithSession(request).ConfigureAwait(false); return badgeInfoResponse?.Content; } - internal static async Task GetPriceHistory(Bot bot, uint appID, string hashName) { + internal static async Task GetPriceHistory(Bot bot, uint appID, string hashName) { Uri request = new(ArchiWebHandler.SteamCommunityURL, String.Format("/market/pricehistory/?appid={0}&market_hash_name={1}", appID, Uri.EscapeDataString(hashName))); - ObjectResponse? priceHistoryResponse = await bot.ArchiWebHandler.UrlGetToJsonObjectWithSession(request).ConfigureAwait(false); + ObjectResponse? priceHistoryResponse = await bot.ArchiWebHandler.UrlGetToJsonObjectWithSession(request).ConfigureAwait(false); return priceHistoryResponse?.Content; } } From 561123c69d18060b76fc61876514d6dd9bad8446 Mon Sep 17 00:00:00 2001 From: Citrinate Date: Mon, 26 Feb 2024 13:40:14 -0500 Subject: [PATCH 02/13] Update README --- BoosterManager/Docs/ItemIDs.md | 67 ++++++++++++++++++++++++++++++++++ README.md | 3 ++ 2 files changed, 70 insertions(+) create mode 100644 BoosterManager/Docs/ItemIDs.md diff --git a/BoosterManager/Docs/ItemIDs.md b/BoosterManager/Docs/ItemIDs.md new file mode 100644 index 0000000..d51d569 --- /dev/null +++ b/BoosterManager/Docs/ItemIDs.md @@ -0,0 +1,67 @@ +# How to find Steam Item IDs + +## AppID + +An AppID refers to a specific Steam App. This can be found in the url of an App's Steam store page. + +--- + +#### Example + +`https://store.steampowered.com/app/730/CounterStrike_2/` + +AppID of `730` + +--- + +## ContextID + +Apps can have multiple inventories, and the ContextID refers to the inventory an item exists in. The ContextID can be found in the url you'd get by right clicking any item in your inventory, and selecting "Copy link address". + +--- + +#### Example + +`https://steamcommunity.com/id/████/inventory/#753_6_22000101010` + +AppID of `753` + +ContextID of `6` + +AssetID of `22000101010` + +--- + +## ClassID + +The ClassID refers to all copies of an item. This can be found in the source code of an item's market listing page. Alternatively, or if an item doesn't have a market listing page, this can be found at `https://steamcommunity.com/my/inventory/json/AppID/ContextID`. There also exists another location which can be used similarly at `https://steamcommunity.com/inventory/SteamID/AppID/ContextID`, which has the query parameters `count` and `start_assetid`. + +--- + +#### Example using a Market Listing Page + +The source code for [:TheMessenger:](https://steamcommunity.com/market/listings/753/764790-%3ATheMessenger%3A) contains the text: + +```javascript +var g_rgAssets = {"753":{"6":{"28191259516":{"currency":0,"appid":753,"contextid":"6","id":"28191259516","classid":"2994832731","instanceid":"0" +``` + +AppID of `753` + +ContextID of `6` + +ClassID of `2994832731` + +--- + +#### Example using JSON inventory + +It will be necessary to first get the AppID, ContextID, and AssetID of the item. The AssetID refers to a specific copy of an item, and all three can be found using the above method to find the ContextID. + +When navigating to https://steamcommunity.com/my/inventory/json/753/6 and searching for the AssetID of `22000101010`, we may find something like this: + +```json +{"22000101010":{"id":"22000101010","classid":"2994832731","instanceid":"0","amount":"1","hide_in_china":0,"pos":42}, +``` + +ClassID of `2994832731` diff --git a/README.md b/README.md index 3204382..4287a02 100644 --- a/README.md +++ b/README.md @@ -131,6 +131,9 @@ Format | Example | `AppID::ContextID`|The identifier `753::6` will match with all Steam Community items `AppID::ContextID::ClassID`|The identifier `753::6::667933237` will match all "Sack of Gems" items +> **Note** +> Information on how to determine an item's `AppID`, `ContextID`, and `ClassID` may be found (https://github.com/Citrinate/BoosterManager/blob/master/BoosterManager/Docs/ItemIDs.md). + --- ### Command Aliases From 301b667ccbf5d3fb7cd5f01f4d0907ed6d0496ee Mon Sep 17 00:00:00 2001 From: Citrinate Date: Mon, 26 Feb 2024 13:41:08 -0500 Subject: [PATCH 03/13] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4287a02..250391e 100644 --- a/README.md +++ b/README.md @@ -132,7 +132,7 @@ Format | Example | `AppID::ContextID::ClassID`|The identifier `753::6::667933237` will match all "Sack of Gems" items > **Note** -> Information on how to determine an item's `AppID`, `ContextID`, and `ClassID` may be found (https://github.com/Citrinate/BoosterManager/blob/master/BoosterManager/Docs/ItemIDs.md). +> Information on how to determine an item's `AppID`, `ContextID`, and `ClassID` may be found [here](https://github.com/Citrinate/BoosterManager/blob/master/BoosterManager/Docs/ItemIDs.md). --- From 035b7a9c89bfe51d2a2de4db5393218aabac0463 Mon Sep 17 00:00:00 2001 From: Citrinate Date: Mon, 26 Feb 2024 17:33:08 -0500 Subject: [PATCH 04/13] Crash fix --- BoosterManager/Data/ItemListing.cs | 8 ++++---- BoosterManager/Handlers/MarketHandler.cs | 5 +++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/BoosterManager/Data/ItemListing.cs b/BoosterManager/Data/ItemListing.cs index 2f4ed64..ffe8afc 100644 --- a/BoosterManager/Data/ItemListing.cs +++ b/BoosterManager/Data/ItemListing.cs @@ -1,7 +1,7 @@ using System; -using System.Text.Json; using System.Text.Json.Nodes; using ArchiSteamFarm.Core; +using ArchiSteamFarm.Helpers.Json; namespace BoosterManager { internal sealed class ItemListing { @@ -38,19 +38,19 @@ internal ItemListing(JsonObject listing) { throw new InvalidOperationException(); } - uint? appID = listing["asset"]?["appid"]?.GetValue(); + uint? appID = listing["asset"]?["appid"]?.ToString().ToJsonObject(); if (appID == null) { ASF.ArchiLogger.LogNullError(appID); throw new InvalidOperationException(); } - ulong? contextID = listing["asset"]?["contextid"]?.GetValue(); + ulong? contextID = listing["asset"]?["contextid"]?.ToString().ToJsonObject(); if (contextID == null) { ASF.ArchiLogger.LogNullError(contextID); throw new InvalidOperationException(); } - ulong? classID = listing["asset"]?["classid"]?.GetValue(); + ulong? classID = listing["asset"]?["classid"]?.ToString().ToJsonObject(); if (classID == null) { ASF.ArchiLogger.LogNullError(classID); throw new InvalidOperationException(); diff --git a/BoosterManager/Handlers/MarketHandler.cs b/BoosterManager/Handlers/MarketHandler.cs index c2839a4..997d77b 100644 --- a/BoosterManager/Handlers/MarketHandler.cs +++ b/BoosterManager/Handlers/MarketHandler.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Text.Json.Nodes; using System.Threading.Tasks; +using ArchiSteamFarm.Helpers.Json; using ArchiSteamFarm.Localization; using ArchiSteamFarm.Steam; using ArchiSteamFarm.Steam.Data; @@ -42,7 +43,7 @@ internal static async Task GetValue(Bot bot, uint subtractFrom = 0) { uint listingsValue = 0; foreach (JsonObject listing in listings.Values) { - uint? price = listing["price"]?.GetValue(); + uint? price = listing["price"]?.ToString().ToJsonObject(); if (price == null) { bot.ArchiLogger.LogNullError(price); @@ -141,7 +142,7 @@ internal static async Task FindAndRemoveListings(Bot bot, List(); + ulong? listingid = listing["listingid"]?.ToString().ToJsonObject(); if (listingid == null) { bot.ArchiLogger.LogNullError(listingid); From b0e638b3d39873a00812d1afd0cc5fed6de86c55 Mon Sep 17 00:00:00 2001 From: Citrinate Date: Mon, 26 Feb 2024 17:59:16 -0500 Subject: [PATCH 05/13] Update ItemIDs.md --- BoosterManager/Docs/ItemIDs.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/BoosterManager/Docs/ItemIDs.md b/BoosterManager/Docs/ItemIDs.md index d51d569..5da3419 100644 --- a/BoosterManager/Docs/ItemIDs.md +++ b/BoosterManager/Docs/ItemIDs.md @@ -2,15 +2,15 @@ ## AppID -An AppID refers to a specific Steam App. This can be found in the url of an App's Steam store page. +An AppID refers to a specific Steam App. This can be found in the url of an App's inventory page. --- #### Example -`https://store.steampowered.com/app/730/CounterStrike_2/` +`https://steamcommunity.com/id/████/inventory/#753` -AppID of `730` +AppID of `753` --- From 00b820f4f0df0a55fb6da1d65636a9f60571621d Mon Sep 17 00:00:00 2001 From: Citrinate Date: Mon, 26 Feb 2024 18:03:40 -0500 Subject: [PATCH 06/13] Update InventoryHistory.md --- BoosterManager/Docs/InventoryHistory.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BoosterManager/Docs/InventoryHistory.md b/BoosterManager/Docs/InventoryHistory.md index 096e801..44c2f2d 100644 --- a/BoosterManager/Docs/InventoryHistory.md +++ b/BoosterManager/Docs/InventoryHistory.md @@ -109,7 +109,7 @@ Be aware that each of these descriptions describes a unique type of event. For Event descriptions for Valve games can be found in the following files as strings starting with `ItemHistory_Action` - Artifact Classic: [game/dcg/resource/dcg_common_english.txt](https://github.com/SteamDatabase/GameTracking-Artifact/blob/master/game/dcg/resource/dcg_common_english.txt) -- Counter-Strike: Global Offensive: [csgo/resource/csgo_english.txt](https://github.com/SteamDatabase/GameTracking-CS2/blob/master/game/csgo/resource/csgo_english.txt) +- Counter-Strike 2: [csgo/resource/csgo_english.txt](https://github.com/SteamDatabase/GameTracking-CS2/blob/master/game/csgo/pak01_dir/resource/csgo_english.txt) - Dota 2: [game/dota/pak01_dir/resource/localization/dota_english.txt](https://github.com/SteamDatabase/GameTracking-Dota2/blob/6abdd9d13de2f4330ca748082467b9ff6e6cd928/game/dota/pak01_dir/resource/localization/dota_english.txt) - Portal 2: [portal2/portal2/resource/portal2_english.txt](https://github.com/SteamDatabase/GameTracking/blob/master/portal2/portal2/resource/portal2_english.txt) - Team Fortress 2: [tf/resource/tf_english.txt](https://github.com/SteamDatabase/GameTracking-TF2/blob/master/tf/resource/tf_english.txt) From eb644eed1de6dc7d4a5204e349e136db11b82644 Mon Sep 17 00:00:00 2001 From: Citrinate Date: Mon, 26 Feb 2024 18:20:01 -0500 Subject: [PATCH 07/13] Update InventoryHistory.md --- BoosterManager/Docs/InventoryHistory.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/BoosterManager/Docs/InventoryHistory.md b/BoosterManager/Docs/InventoryHistory.md index 44c2f2d..af319d2 100644 --- a/BoosterManager/Docs/InventoryHistory.md +++ b/BoosterManager/Docs/InventoryHistory.md @@ -121,18 +121,18 @@ The Inventory History API provides no way to fetch specific pages, instead we sp As an example, assuming you know there should be history on your account between `4/30/21` and `1/5/21`, your history might look like this: ``` -… → 5/2/21 → 5/1/21 → 4/30/21 → 1/5/21 → 1/4/21 → … +… → 5/2/21 → 5/1/21 → 4/30/21 → … Missing … → 1/5/21 → 1/4/21 → … ``` This bug can be addressed by searching for history within the gap. You can use the in-browser "Jump to date" feature, or try setting the `start_time` parameter yourself. It may take several attempts to find a value for `start_time` that causes the missing history to re-appear. -If you search at date `x` and find missing history there, then history older than `x` should also re-appear, but history newer than `x` might not. Looking at the previous example and assuming there's history between `4/30/21` and `3/14/21`; if `x = 3/14/21` then the gap may shrink, but not disappear, and some of the missing history may also show up right before `3/14/21`: +If you search at date `x` and find missing history there, then history older than `x` should also re-appear, but history newer than `x` might not. Looking at the previous example and assuming there's history between `4/30/21` and `3/14/21`; if `x = 3/14/21` then the gap may shrink, but not disappear: ``` -… → 5/2/21 → 5/1/21 → 4/30/21 → 3/15/21 → 3/14/21 → 3/13/21 → … → 1/5/21 → 1/4/21 → … +… → 5/2/21 → 5/1/21 → 4/30/21 → … Still Missing … → 3/15/21 → 3/14/21 → 3/13/21 → … Not Missing Anymore … → 1/6/21 → 1/5/21 → … ``` -For this reason it's better to start your search right where the gap begins and proceed gradually. Setting the `start_time` parameter yourself allows you to move in increments of 1 second. The "Jump to date" feature moves in increments of 24 hours. You can also use the `cursor[time]` and `cursor[time_frac]` parameters to move in increments of 1 millisecond. +For this reason it's better to start your search right where the gap begins and proceed gradually. The "Jump to date" feature moves in increments of 24 hours. Setting the `start_time` parameter yourself allows you to move in increments of 1 second. You can also use the `cursor[time]` and `cursor[time_frac]` parameters to move in increments of 1 millisecond. Not all gaps are as large as in the examples above. It's very common to have lots of small gaps when numerous events share the same time (ex: confirming multiple market listings at once). Here the gaps length can be shorter than a second, and may skip as few as 1 event. These gaps can be addressed in the same way as large gaps, but because of how small they are, they're very hard to identify and correct for. From c9720d059d7d7c27c1e948e355a09951f8ff48a7 Mon Sep 17 00:00:00 2001 From: Citrinate Date: Tue, 27 Feb 2024 00:30:49 -0500 Subject: [PATCH 08/13] Fix unpackgems command --- BoosterManager/Handlers/GemHandler.cs | 2 +- BoosterManager/Json.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/BoosterManager/Handlers/GemHandler.cs b/BoosterManager/Handlers/GemHandler.cs index 976f01a..e6a3034 100644 --- a/BoosterManager/Handlers/GemHandler.cs +++ b/BoosterManager/Handlers/GemHandler.cs @@ -54,7 +54,7 @@ internal static async Task UnpackGems(Bot bot) { foreach (Asset sack in sacks) { Steam.ExchangeGooResponse? response = await WebRequest.UnpackGems(bot, sack.AssetID, sack.Amount); - if (response == null || !response.Success) { + if (response == null || response.Success != 1) { return Commands.FormatBotResponse(bot, Strings.WarningFailed); } } diff --git a/BoosterManager/Json.cs b/BoosterManager/Json.cs index 3a754f6..7ba8fa2 100644 --- a/BoosterManager/Json.cs +++ b/BoosterManager/Json.cs @@ -243,7 +243,7 @@ internal sealed class ExchangeGooResponse { [JsonInclude] [JsonPropertyName("success")] [JsonRequired] - internal bool Success { get; private init; } + internal int Success { get; private init; } [JsonConstructor] private ExchangeGooResponse() { } From b590f21de42d655a90d52eba88e2226f82f6fbd5 Mon Sep 17 00:00:00 2001 From: Citrinate Date: Tue, 27 Feb 2024 01:23:55 -0500 Subject: [PATCH 09/13] Update ArchiSteamFarm to 6.0.0.3 --- ArchiSteamFarm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ArchiSteamFarm b/ArchiSteamFarm index 7a13895..e4c20df 160000 --- a/ArchiSteamFarm +++ b/ArchiSteamFarm @@ -1 +1 @@ -Subproject commit 7a13895429f3161c0e32736cd3086c515e42464c +Subproject commit e4c20df4a896209636078c7acf33fbcaa8ad35bc From 7d9f2bc460c43e423cfdd4e2a53866d547564985 Mon Sep 17 00:00:00 2001 From: Citrinate Date: Fri, 1 Mar 2024 09:08:43 -0500 Subject: [PATCH 10/13] Version bump --- BoosterManager/BoosterManager.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BoosterManager/BoosterManager.csproj b/BoosterManager/BoosterManager.csproj index 3543e8f..4b1f4a0 100644 --- a/BoosterManager/BoosterManager.csproj +++ b/BoosterManager/BoosterManager.csproj @@ -2,7 +2,7 @@ Citrinate - 2.8.2 + 2.8.3 enable latest net8.0 From d60bcbf9aa30d02d1f95b2820f06eeee8b08bc55 Mon Sep 17 00:00:00 2001 From: Citrinate Date: Sun, 3 Mar 2024 09:44:15 -0500 Subject: [PATCH 11/13] Fix BoostersResponse deserialization --- BoosterManager/Json.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/BoosterManager/Json.cs b/BoosterManager/Json.cs index 7ba8fa2..27c3e0c 100644 --- a/BoosterManager/Json.cs +++ b/BoosterManager/Json.cs @@ -13,7 +13,7 @@ internal class EResultResponse { [JsonInclude] [JsonPropertyName("success")] [JsonRequired] - public readonly EResult Result; + public EResult Result { get; private init; } [JsonConstructor] public EResultResponse() { } @@ -56,16 +56,19 @@ public BoosterInfo() { } internal sealed class BoostersResponse { [JsonInclude] [JsonPropertyName("goo_amount")] + [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)] [JsonRequired] internal uint GooAmount { get; private init; } [JsonInclude] [JsonPropertyName("tradable_goo_amount")] + [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)] [JsonRequired] internal uint TradableGooAmount { get; private init; } [JsonInclude] [JsonPropertyName("untradable_goo_amount")] + [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)] [JsonRequired] internal uint UntradableGooAmount { get; private init; } From 3efdd7beb3026ea22c5951005d553fc1cccc2a48 Mon Sep 17 00:00:00 2001 From: Citrinate Date: Sun, 3 Mar 2024 10:52:12 -0500 Subject: [PATCH 12/13] Added support for ASFEnhance features --- BoosterManager/AdapterBridge.cs | 57 ++++++++++++++++++++++++++++++++ BoosterManager/BoosterManager.cs | 20 ++++++++++- BoosterManager/Commands.cs | 4 ++- 3 files changed, 79 insertions(+), 2 deletions(-) create mode 100644 BoosterManager/AdapterBridge.cs diff --git a/BoosterManager/AdapterBridge.cs b/BoosterManager/AdapterBridge.cs new file mode 100644 index 0000000..49702cb --- /dev/null +++ b/BoosterManager/AdapterBridge.cs @@ -0,0 +1,57 @@ +using System; +using System.Reflection; +using ArchiSteamFarm.Core; + +// ASFEnhanced Adapter https://github.com/chr233/ASFEnhanceAdapterDemoPlugin + +namespace BoosterManager; +internal static class AdapterBridge +{ + /// + /// 注册子模块 + /// + /// 插件名称 + /// 插件唯一标识符 + /// 命令前缀 + /// 自动更新仓库 + /// 命令处理函数 + /// + public static bool InitAdapter(string pluginName, string pluginId, string? cmdPrefix, string? repoName, MethodInfo? cmdHandler) + { + try + { + var adapterEndpoint = Assembly.Load("ASFEnhance").GetType("ASFEnhance._Adapter_.Endpoint"); + var registerModule = adapterEndpoint?.GetMethod("RegisterModule", BindingFlags.Static | BindingFlags.Public); + var pluinVersion = Assembly.GetExecutingAssembly().GetName().Version; + + if (registerModule != null && adapterEndpoint != null) + { + var result = registerModule?.Invoke(null, new object?[] { pluginName, pluginId, cmdPrefix, repoName, pluinVersion, cmdHandler }); + + if (result is string str) + { + if (str == pluginName) + { + return true; + } + else + { + ASF.ArchiLogger.LogGenericWarning(str); + } + } + } + } +#if DEBUG + catch (Exception ex) + { + ASF.ArchiLogger.LogGenericException(ex, "Community with ASFEnhance failed"); + } +#else + catch (Exception) + { + ASF.ArchiLogger.LogGenericDebug("Community with ASFEnhance failed"); + } +#endif + return false; + } +} diff --git a/BoosterManager/BoosterManager.cs b/BoosterManager/BoosterManager.cs index 4eb51e6..be42569 100644 --- a/BoosterManager/BoosterManager.cs +++ b/BoosterManager/BoosterManager.cs @@ -9,19 +9,37 @@ using ArchiSteamFarm.Steam.Exchange; using System.Text.Json; using ArchiSteamFarm.Helpers.Json; +using System.Reflection; namespace BoosterManager { [Export(typeof(IPlugin))] public sealed class BoosterManager : IASF, IBotModules, IBotCommand2, IBotTradeOfferResults { public string Name => nameof(BoosterManager); public Version Version => typeof(BoosterManager).Assembly.GetName().Version ?? new Version("0"); + private bool ASFEnhanceEnabled = false; public Task OnLoaded() { ASF.ArchiLogger.LogGenericInfo("BoosterManager ASF Plugin by Citrinate"); + + // ASFEnhanced Adapter https://github.com/chr233/ASFEnhanceAdapterDemoPlugin + var flag = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; + var handler = typeof(Commands).GetMethod(nameof(Commands.Response), flag); + const string pluginId = nameof(BoosterManager); + const string cmdPrefix = "BOOSTERMANAGER"; + const string repoName = "Citrinate/BoosterManager"; + var registered = AdapterBridge.InitAdapter(Name, pluginId, cmdPrefix, repoName, handler); + ASFEnhanceEnabled = registered; + return Task.CompletedTask; } - public async Task OnBotCommand(Bot bot, EAccess access, string message, string[] args, ulong steamID = 0) => await Commands.Response(bot, access, steamID, message, args).ConfigureAwait(false); + public async Task OnBotCommand(Bot bot, EAccess access, string message, string[] args, ulong steamID = 0) { + if (ASFEnhanceEnabled) { + return null; + } + + return await Commands.Response(bot, access, steamID, message, args).ConfigureAwait(false); + } public Task OnASFInit(IReadOnlyDictionary? additionalConfigProperties = null) { if (additionalConfigProperties == null) { diff --git a/BoosterManager/Commands.cs b/BoosterManager/Commands.cs index edefb57..b419f7b 100644 --- a/BoosterManager/Commands.cs +++ b/BoosterManager/Commands.cs @@ -7,7 +7,6 @@ using ArchiSteamFarm.Localization; using ArchiSteamFarm.Steam.Data; using System.ComponentModel; -using System.Collections.Immutable; using System.Reflection; namespace BoosterManager { @@ -24,6 +23,9 @@ internal static class Commands { switch (args.Length) { case 1: switch (args[0].ToUpperInvariant()) { + case "BOOSTERMANAGER" when access >= EAccess.FamilySharing: + return String.Format("{0} {1}", nameof(BoosterManager), (typeof(BoosterManager).Assembly.GetName().Version ?? new Version("0")).ToString()); + case "BDROP" or "BDROPS": return await ResponseBoosterDrops(bot, access).ConfigureAwait(false); From 0d177b1ec4e6833e7d8bf84846f2371a40f1bb5b Mon Sep 17 00:00:00 2001 From: Citrinate Date: Sun, 3 Mar 2024 12:03:27 -0500 Subject: [PATCH 13/13] Formatting --- BoosterManager/AdapterBridge.cs | 69 +++++++++++---------------------- 1 file changed, 22 insertions(+), 47 deletions(-) diff --git a/BoosterManager/AdapterBridge.cs b/BoosterManager/AdapterBridge.cs index 49702cb..857a75c 100644 --- a/BoosterManager/AdapterBridge.cs +++ b/BoosterManager/AdapterBridge.cs @@ -5,53 +5,28 @@ // ASFEnhanced Adapter https://github.com/chr233/ASFEnhanceAdapterDemoPlugin namespace BoosterManager; -internal static class AdapterBridge -{ - /// - /// 注册子模块 - /// - /// 插件名称 - /// 插件唯一标识符 - /// 命令前缀 - /// 自动更新仓库 - /// 命令处理函数 - /// - public static bool InitAdapter(string pluginName, string pluginId, string? cmdPrefix, string? repoName, MethodInfo? cmdHandler) - { - try - { - var adapterEndpoint = Assembly.Load("ASFEnhance").GetType("ASFEnhance._Adapter_.Endpoint"); - var registerModule = adapterEndpoint?.GetMethod("RegisterModule", BindingFlags.Static | BindingFlags.Public); - var pluinVersion = Assembly.GetExecutingAssembly().GetName().Version; +internal static class AdapterBridge { + public static bool InitAdapter(string pluginName, string pluginId, string? cmdPrefix, string? repoName, MethodInfo? cmdHandler) { + try { + var adapterEndpoint = Assembly.Load("ASFEnhance").GetType("ASFEnhance._Adapter_.Endpoint"); + var registerModule = adapterEndpoint?.GetMethod("RegisterModule", BindingFlags.Static | BindingFlags.Public); + var pluinVersion = Assembly.GetExecutingAssembly().GetName().Version; - if (registerModule != null && adapterEndpoint != null) - { - var result = registerModule?.Invoke(null, new object?[] { pluginName, pluginId, cmdPrefix, repoName, pluinVersion, cmdHandler }); + if (registerModule != null && adapterEndpoint != null) { + var result = registerModule?.Invoke(null, new object?[] { pluginName, pluginId, cmdPrefix, repoName, pluinVersion, cmdHandler }); - if (result is string str) - { - if (str == pluginName) - { - return true; - } - else - { - ASF.ArchiLogger.LogGenericWarning(str); - } - } - } - } -#if DEBUG - catch (Exception ex) - { - ASF.ArchiLogger.LogGenericException(ex, "Community with ASFEnhance failed"); - } -#else - catch (Exception) - { - ASF.ArchiLogger.LogGenericDebug("Community with ASFEnhance failed"); - } -#endif - return false; - } + if (result is string str) { + if (str == pluginName) { + return true; + } else { + ASF.ArchiLogger.LogGenericWarning(str); + } + } + } + } catch (Exception ex) { + ASF.ArchiLogger.LogGenericException(ex, "Community with ASFEnhance failed"); + } + + return false; + } }