From b6772b9b1e6af61d3f82412ff602563402c0de82 Mon Sep 17 00:00:00 2001 From: JustArchi Date: Thu, 6 May 2021 20:16:06 +0200 Subject: [PATCH] Resolve CA1034 --- .editorconfig | 4 +- .../CatAPI.cs | 3 +- .../ExamplePlugin.cs | 15 +- .../GlobalCache.cs | 9 +- .../SteamTokenDumperPlugin.cs | 7 +- ArchiSteamFarm.Tests/Bot.cs | 218 +++--- ArchiSteamFarm.Tests/Trading.cs | 152 ++-- ArchiSteamFarm.sln.DotSettings | 2 +- ArchiSteamFarm/ASF.cs | 59 +- ArchiSteamFarm/Actions.cs | 27 +- ArchiSteamFarm/ArchiHandler.cs | 183 +---- ArchiSteamFarm/ArchiWebHandler.cs | 141 ++-- ArchiSteamFarm/Bot.cs | 55 +- ArchiSteamFarm/BotConfig.cs | 33 +- ArchiSteamFarm/BotDatabase.cs | 6 +- .../Callbacks/PurchaseResponseCallback.cs | 120 +++ .../Callbacks/UserNotificationsCallback.cs | 120 +++ ArchiSteamFarm/CardsFarmer.cs | 36 +- ArchiSteamFarm/Commands.cs | 23 +- ArchiSteamFarm/Confirmation.cs | 61 ++ ArchiSteamFarm/Game.cs | 58 ++ ArchiSteamFarm/GitHub.cs | 9 +- ArchiSteamFarm/GlobalDatabase.cs | 10 +- ArchiSteamFarm/IPC/ArchiKestrel.cs | 6 +- .../IPC/Controllers/Api/ASFController.cs | 3 +- .../IPC/Controllers/Api/BotController.cs | 31 +- .../IPC/Controllers/Api/GitHubController.cs | 1 + .../IPC/Controllers/Api/NLogController.cs | 4 + .../IPC/Controllers/Api/TypeController.cs | 2 +- ...actorAuthenticationConfirmationsRequest.cs | 2 +- .../IPC/Responses/TypeProperties.cs | 61 ++ ArchiSteamFarm/IPC/Responses/TypeResponse.cs | 35 - ArchiSteamFarm/IPC/Startup.cs | 6 +- ArchiSteamFarm/Json/Asset.cs | 260 ++++++ ArchiSteamFarm/Json/BooleanResponse.cs | 36 + ArchiSteamFarm/Json/EResultResponse.cs | 37 + ArchiSteamFarm/Json/InventoryResponse.cs | 262 +++++++ .../Json/NewDiscoveryQueueResponse.cs | 35 + ArchiSteamFarm/Json/RedeemWalletResponse.cs | 35 + ArchiSteamFarm/Json/Steam.cs | 741 ------------------ ArchiSteamFarm/Json/Tag.cs | 44 ++ ArchiSteamFarm/Json/TradeOffer.cs | 78 ++ .../Json/TradeOfferAcceptResponse.cs | 37 + ArchiSteamFarm/Json/TradeOfferSendRequest.cs | 38 + ArchiSteamFarm/Json/TradeOfferSendResponse.cs | 58 ++ ArchiSteamFarm/Json/UserPrivacy.cs | 108 +++ ArchiSteamFarm/MobileAuthenticator.cs | 36 - ArchiSteamFarm/OS.cs | 8 +- ArchiSteamFarm/ParseTradeResult.cs | 66 ++ ArchiSteamFarm/Plugins/IBotTradeOffer.cs | 2 +- .../Plugins/IBotTradeOfferResults.cs | 2 +- .../Plugins/IBotUserNotifications.cs | 3 +- ArchiSteamFarm/Plugins/PluginsCore.cs | 7 +- ArchiSteamFarm/Program.cs | 1 + ArchiSteamFarm/RuntimeCompatibility/File.cs | 70 ++ .../RuntimeCompatibility/HashCode.cs | 34 + ArchiSteamFarm/RuntimeCompatibility/Path.cs | 45 ++ .../StaticHelpers.cs} | 80 +- ArchiSteamFarm/SharedInfo.cs | 3 +- ArchiSteamFarm/Statistics.cs | 83 +- .../SteamKit2/ServerRecordEndPoint.cs | 3 +- ArchiSteamFarm/SteamPICSChanges.cs | 1 + ArchiSteamFarm/SteamSaleEvent.cs | 4 + ArchiSteamFarm/Trading.cs | 130 ++- ArchiSteamFarm/Web/BasicResponse.cs | 52 ++ ArchiSteamFarm/Web/BinaryResponse.cs | 38 + ArchiSteamFarm/Web/HtmlDocumentResponse.cs | 62 ++ ArchiSteamFarm/Web/ObjectResponse.cs | 38 + ArchiSteamFarm/Web/StreamResponse.cs | 55 ++ ArchiSteamFarm/Web/StringResponse.cs | 39 + ArchiSteamFarm/{ => Web}/WebBrowser.cs | 143 +--- ArchiSteamFarm/Web/XmlDocumentResponse.cs | 39 + 72 files changed, 2564 insertions(+), 1751 deletions(-) create mode 100644 ArchiSteamFarm/Callbacks/PurchaseResponseCallback.cs create mode 100644 ArchiSteamFarm/Callbacks/UserNotificationsCallback.cs create mode 100644 ArchiSteamFarm/Confirmation.cs create mode 100644 ArchiSteamFarm/Game.cs create mode 100644 ArchiSteamFarm/IPC/Responses/TypeProperties.cs create mode 100644 ArchiSteamFarm/Json/Asset.cs create mode 100644 ArchiSteamFarm/Json/BooleanResponse.cs create mode 100644 ArchiSteamFarm/Json/EResultResponse.cs create mode 100644 ArchiSteamFarm/Json/InventoryResponse.cs create mode 100644 ArchiSteamFarm/Json/NewDiscoveryQueueResponse.cs create mode 100644 ArchiSteamFarm/Json/RedeemWalletResponse.cs delete mode 100644 ArchiSteamFarm/Json/Steam.cs create mode 100644 ArchiSteamFarm/Json/Tag.cs create mode 100644 ArchiSteamFarm/Json/TradeOffer.cs create mode 100644 ArchiSteamFarm/Json/TradeOfferAcceptResponse.cs create mode 100644 ArchiSteamFarm/Json/TradeOfferSendRequest.cs create mode 100644 ArchiSteamFarm/Json/TradeOfferSendResponse.cs create mode 100644 ArchiSteamFarm/Json/UserPrivacy.cs create mode 100644 ArchiSteamFarm/ParseTradeResult.cs create mode 100644 ArchiSteamFarm/RuntimeCompatibility/File.cs create mode 100644 ArchiSteamFarm/RuntimeCompatibility/HashCode.cs create mode 100644 ArchiSteamFarm/RuntimeCompatibility/Path.cs rename ArchiSteamFarm/{RuntimeCompatibility.cs => RuntimeCompatibility/StaticHelpers.cs} (62%) create mode 100644 ArchiSteamFarm/Web/BasicResponse.cs create mode 100644 ArchiSteamFarm/Web/BinaryResponse.cs create mode 100644 ArchiSteamFarm/Web/HtmlDocumentResponse.cs create mode 100644 ArchiSteamFarm/Web/ObjectResponse.cs create mode 100644 ArchiSteamFarm/Web/StreamResponse.cs create mode 100644 ArchiSteamFarm/Web/StringResponse.cs rename ArchiSteamFarm/{ => Web}/WebBrowser.cs (88%) create mode 100644 ArchiSteamFarm/Web/XmlDocumentResponse.cs diff --git a/.editorconfig b/.editorconfig index 63776ffbe..f331152e0 100644 --- a/.editorconfig +++ b/.editorconfig @@ -107,10 +107,10 @@ csharp_using_directive_placement = outside_namespace dotnet_analyzer_diagnostic.severity = warning dotnet_code_quality_unused_parameters = all:warning -# TODO - one at a time dotnet_diagnostic.ca1028.severity = silent dotnet_diagnostic.ca1031.severity = silent -dotnet_diagnostic.ca1034.severity = silent + +# TODO - one at a time dotnet_diagnostic.ca1044.severity = silent dotnet_diagnostic.ca1054.severity = silent dotnet_diagnostic.ca1062.severity = silent diff --git a/ArchiSteamFarm.CustomPlugins.ExamplePlugin/CatAPI.cs b/ArchiSteamFarm.CustomPlugins.ExamplePlugin/CatAPI.cs index 23c9d9eb8..c7aba2c35 100644 --- a/ArchiSteamFarm.CustomPlugins.ExamplePlugin/CatAPI.cs +++ b/ArchiSteamFarm.CustomPlugins.ExamplePlugin/CatAPI.cs @@ -22,6 +22,7 @@ using System; using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; +using ArchiSteamFarm.Web; using Newtonsoft.Json; namespace ArchiSteamFarm.CustomPlugins.ExamplePlugin { @@ -38,7 +39,7 @@ namespace ArchiSteamFarm.CustomPlugins.ExamplePlugin { const string request = URL + "/meow"; - WebBrowser.ObjectResponse? response = await webBrowser.UrlGetToJsonObject(request).ConfigureAwait(false); + ObjectResponse? response = await webBrowser.UrlGetToJsonObject(request).ConfigureAwait(false); if (response == null) { return null; diff --git a/ArchiSteamFarm.CustomPlugins.ExamplePlugin/ExamplePlugin.cs b/ArchiSteamFarm.CustomPlugins.ExamplePlugin/ExamplePlugin.cs index aec184fd4..7e8f4fca9 100644 --- a/ArchiSteamFarm.CustomPlugins.ExamplePlugin/ExamplePlugin.cs +++ b/ArchiSteamFarm.CustomPlugins.ExamplePlugin/ExamplePlugin.cs @@ -30,6 +30,10 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; using SteamKit2; +#if NETFRAMEWORK +using ArchiSteamFarm.RuntimeCompatibility; +#endif + namespace ArchiSteamFarm.CustomPlugins.ExamplePlugin { // In order for your plugin to work, it must export generic ASF's IPlugin interface [Export(typeof(IPlugin))] @@ -59,12 +63,11 @@ namespace ArchiSteamFarm.CustomPlugins.ExamplePlugin { return; } - // ReSharper disable once UseDeconstruction - deconstruction is not available in .NET Framework - foreach (KeyValuePair configProperty in additionalConfigProperties) { + foreach ((string configProperty, JToken configValue) in additionalConfigProperties) { // It's a good idea to prefix your custom properties with the name of your plugin, so there will be no possible conflict of ASF or other plugins using the same name, neither now or in the future - switch (configProperty.Key) { - case nameof(ExamplePlugin) + "TestProperty" when configProperty.Value.Type == JTokenType.Boolean: - bool exampleBooleanValue = configProperty.Value.Value(); + switch (configProperty) { + case nameof(ExamplePlugin) + "TestProperty" when configValue.Type == JTokenType.Boolean: + bool exampleBooleanValue = configValue.Value(); ASF.ArchiLogger.LogGenericInfo(nameof(ExamplePlugin) + "TestProperty boolean property has been found with a value of: " + exampleBooleanValue); break; @@ -164,7 +167,7 @@ namespace ArchiSteamFarm.CustomPlugins.ExamplePlugin { // It allows you not only to analyze such trades, but generate a response whether ASF should accept it (true), or proceed like usual (false) // Thanks to that, you can implement custom rules for all trades that aren't handled by ASF, for example cross-set trading on your own custom rules // You'd implement your own logic here, as an example we'll allow all trades to be accepted if the bot's name starts from "TrashBot" - public Task OnBotTradeOffer(Bot bot, Steam.TradeOffer tradeOffer) => Task.FromResult(bot.BotName.StartsWith("TrashBot", StringComparison.OrdinalIgnoreCase)); + public Task OnBotTradeOffer(Bot bot, TradeOffer tradeOffer) => Task.FromResult(bot.BotName.StartsWith("TrashBot", StringComparison.OrdinalIgnoreCase)); // This is the earliest method that will be called, right after loading the plugin, long before any bot initialization takes place // It's a good place to initialize all potential (non-bot-specific) structures that you will need across lifetime of your plugin, such as global timers, concurrent dictionaries and alike diff --git a/ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/GlobalCache.cs b/ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/GlobalCache.cs index 38b471253..5c0f2ea91 100644 --- a/ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/GlobalCache.cs +++ b/ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/GlobalCache.cs @@ -23,7 +23,6 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; -using System.IO; using System.Linq; using System.Threading.Tasks; using ArchiSteamFarm.Helpers; @@ -31,6 +30,14 @@ using ArchiSteamFarm.Localization; using Newtonsoft.Json; using SteamKit2; +#if NETFRAMEWORK +using ArchiSteamFarm.RuntimeCompatibility; +using File = System.IO.File; +using Path = System.IO.Path; +#else +using System.IO; +#endif + namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper { internal sealed class GlobalCache : SerializableFile { private static string SharedFilePath => Path.Combine(ArchiSteamFarm.SharedInfo.ConfigDirectory, nameof(SteamTokenDumper) + ".cache"); diff --git a/ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/SteamTokenDumperPlugin.cs b/ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/SteamTokenDumperPlugin.cs index 8d5815881..3dddd7acd 100644 --- a/ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/SteamTokenDumperPlugin.cs +++ b/ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/SteamTokenDumperPlugin.cs @@ -31,10 +31,15 @@ using System.Threading; using System.Threading.Tasks; using ArchiSteamFarm.OfficialPlugins.SteamTokenDumper.Localization; using ArchiSteamFarm.Plugins; +using ArchiSteamFarm.Web; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using SteamKit2; +#if NETFRAMEWORK +using ArchiSteamFarm.RuntimeCompatibility; +#endif + namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper { [Export(typeof(IPlugin))] internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotSteamClient, ISteamPICSChanges { @@ -512,7 +517,7 @@ namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper { ASF.ArchiLogger.LogGenericInfo(string.Format(CultureInfo.CurrentCulture, Strings.SubmissionInProgress, appTokens.Count, packageTokens.Count, depotKeys.Count)); - WebBrowser.ObjectResponse? response = await ASF.WebBrowser.UrlPostToJsonObject(request, data: requestData, requestOptions: WebBrowser.ERequestOptions.ReturnClientErrors).ConfigureAwait(false); + ObjectResponse? response = await ASF.WebBrowser.UrlPostToJsonObject(request, data: requestData, requestOptions: WebBrowser.ERequestOptions.ReturnClientErrors).ConfigureAwait(false); if (response == null) { ASF.ArchiLogger.LogGenericWarning(ArchiSteamFarm.Localization.Strings.WarningFailed); diff --git a/ArchiSteamFarm.Tests/Bot.cs b/ArchiSteamFarm.Tests/Bot.cs index 92057ec9e..0f98b5f41 100644 --- a/ArchiSteamFarm.Tests/Bot.cs +++ b/ArchiSteamFarm.Tests/Bot.cs @@ -25,6 +25,10 @@ using System.Linq; using ArchiSteamFarm.Json; using Microsoft.VisualStudio.TestTools.UnitTesting; +#if NETFRAMEWORK +using ArchiSteamFarm.RuntimeCompatibility; +#endif + namespace ArchiSteamFarm.Tests { [TestClass] public sealed class Bot { @@ -37,7 +41,7 @@ namespace ArchiSteamFarm.Tests { { 43, ArchiSteamFarm.Bot.MinCardsPerBadge + 1 } }; - HashSet items = new(); + HashSet items = new(); foreach ((uint appID, byte cards) in itemsPerSet) { for (byte i = 1; i <= cards; i++) { @@ -45,7 +49,7 @@ namespace ArchiSteamFarm.Tests { } } - HashSet itemsToSend = GetItemsForFullBadge(items, itemsPerSet, ArchiSteamFarm.Bot.MinCardsPerBadge); + HashSet itemsToSend = GetItemsForFullBadge(items, itemsPerSet, ArchiSteamFarm.Bot.MinCardsPerBadge); Dictionary<(uint RealAppID, ulong ContextID, ulong ClassID), uint> expectedResult = items.Where(item => item.RealAppID == relevantAppID) .GroupBy(item => (item.RealAppID, item.ContextID, item.ClassID)) @@ -59,7 +63,7 @@ namespace ArchiSteamFarm.Tests { public void MaxItemsTooSmall() { const uint appID = 42; - HashSet items = new() { + HashSet items = new() { CreateCard(1, appID), CreateCard(2, appID) }; @@ -73,19 +77,19 @@ namespace ArchiSteamFarm.Tests { public void MoreCardsThanNeeded() { const uint appID = 42; - HashSet items = new() { + HashSet items = new() { CreateCard(1, appID), CreateCard(1, appID), CreateCard(2, appID), CreateCard(3, appID) }; - HashSet itemsToSend = GetItemsForFullBadge(items, 3, appID); + HashSet itemsToSend = GetItemsForFullBadge(items, 3, appID); Dictionary<(uint RealAppID, ulong ContextID, ulong ClassID), uint> expectedResult = new() { - { (appID, Steam.Asset.SteamCommunityContextID, 1), 1 }, - { (appID, Steam.Asset.SteamCommunityContextID, 2), 1 }, - { (appID, Steam.Asset.SteamCommunityContextID, 3), 1 } + { (appID, Asset.SteamCommunityContextID, 1), 1 }, + { (appID, Asset.SteamCommunityContextID, 2), 1 }, + { (appID, Asset.SteamCommunityContextID, 3), 1 } }; AssertResultMatchesExpectation(expectedResult, itemsToSend); @@ -95,18 +99,18 @@ namespace ArchiSteamFarm.Tests { public void MultipleSets() { const uint appID = 42; - HashSet items = new() { + HashSet items = new() { CreateCard(1, appID), CreateCard(1, appID), CreateCard(2, appID), CreateCard(2, appID) }; - HashSet itemsToSend = GetItemsForFullBadge(items, 2, appID); + HashSet itemsToSend = GetItemsForFullBadge(items, 2, appID); Dictionary<(uint RealAppID, ulong ContextID, ulong ClassID), uint> expectedResult = new() { - { (appID, Steam.Asset.SteamCommunityContextID, 1), 2 }, - { (appID, Steam.Asset.SteamCommunityContextID, 2), 2 } + { (appID, Asset.SteamCommunityContextID, 1), 2 }, + { (appID, Asset.SteamCommunityContextID, 2), 2 } }; AssertResultMatchesExpectation(expectedResult, itemsToSend); @@ -116,17 +120,17 @@ namespace ArchiSteamFarm.Tests { public void MultipleSetsDifferentAmount() { const uint appID = 42; - HashSet items = new() { + HashSet items = new() { CreateCard(1, appID, 2), CreateCard(2, appID), CreateCard(2, appID) }; - HashSet itemsToSend = GetItemsForFullBadge(items, 2, appID); + HashSet itemsToSend = GetItemsForFullBadge(items, 2, appID); Dictionary<(uint RealAppID, ulong ContextID, ulong ClassID), uint> expectedResult = new() { - { (appID, Steam.Asset.SteamCommunityContextID, 1), 2 }, - { (appID, Steam.Asset.SteamCommunityContextID, 2), 2 } + { (appID, Asset.SteamCommunityContextID, 1), 2 }, + { (appID, Asset.SteamCommunityContextID, 2), 2 } }; AssertResultMatchesExpectation(expectedResult, itemsToSend); @@ -136,38 +140,38 @@ namespace ArchiSteamFarm.Tests { public void MutliRarityAndType() { const uint appID = 42; - HashSet items = new() { - CreateCard(1, appID, type: Steam.Asset.EType.TradingCard, rarity: Steam.Asset.ERarity.Common), - CreateCard(2, appID, type: Steam.Asset.EType.TradingCard, rarity: Steam.Asset.ERarity.Common), + HashSet items = new() { + CreateCard(1, appID, type: Asset.EType.TradingCard, rarity: Asset.ERarity.Common), + CreateCard(2, appID, type: Asset.EType.TradingCard, rarity: Asset.ERarity.Common), - CreateCard(1, appID, type: Steam.Asset.EType.FoilTradingCard, rarity: Steam.Asset.ERarity.Uncommon), - CreateCard(2, appID, type: Steam.Asset.EType.FoilTradingCard, rarity: Steam.Asset.ERarity.Uncommon), + CreateCard(1, appID, type: Asset.EType.FoilTradingCard, rarity: Asset.ERarity.Uncommon), + CreateCard(2, appID, type: Asset.EType.FoilTradingCard, rarity: Asset.ERarity.Uncommon), - CreateCard(1, appID, type: Steam.Asset.EType.FoilTradingCard, rarity: Steam.Asset.ERarity.Rare), - CreateCard(2, appID, type: Steam.Asset.EType.FoilTradingCard, rarity: Steam.Asset.ERarity.Rare), + CreateCard(1, appID, type: Asset.EType.FoilTradingCard, rarity: Asset.ERarity.Rare), + CreateCard(2, appID, type: Asset.EType.FoilTradingCard, rarity: Asset.ERarity.Rare), // for better readability and easier verification when thinking about this test the items that shall be selected for sending are the ones below this comment - CreateCard(1, appID, type: Steam.Asset.EType.TradingCard, rarity: Steam.Asset.ERarity.Uncommon), - CreateCard(2, appID, type: Steam.Asset.EType.TradingCard, rarity: Steam.Asset.ERarity.Uncommon), - CreateCard(3, appID, type: Steam.Asset.EType.TradingCard, rarity: Steam.Asset.ERarity.Uncommon), + CreateCard(1, appID, type: Asset.EType.TradingCard, rarity: Asset.ERarity.Uncommon), + CreateCard(2, appID, type: Asset.EType.TradingCard, rarity: Asset.ERarity.Uncommon), + CreateCard(3, appID, type: Asset.EType.TradingCard, rarity: Asset.ERarity.Uncommon), - CreateCard(1, appID, type: Steam.Asset.EType.FoilTradingCard, rarity: Steam.Asset.ERarity.Common), - CreateCard(3, appID, type: Steam.Asset.EType.FoilTradingCard, rarity: Steam.Asset.ERarity.Common), - CreateCard(7, appID, type: Steam.Asset.EType.FoilTradingCard, rarity: Steam.Asset.ERarity.Common), + CreateCard(1, appID, type: Asset.EType.FoilTradingCard, rarity: Asset.ERarity.Common), + CreateCard(3, appID, type: Asset.EType.FoilTradingCard, rarity: Asset.ERarity.Common), + CreateCard(7, appID, type: Asset.EType.FoilTradingCard, rarity: Asset.ERarity.Common), - CreateCard(2, appID, type: Steam.Asset.EType.Unknown, rarity: Steam.Asset.ERarity.Rare), - CreateCard(3, appID, type: Steam.Asset.EType.Unknown, rarity: Steam.Asset.ERarity.Rare), - CreateCard(4, appID, type: Steam.Asset.EType.Unknown, rarity: Steam.Asset.ERarity.Rare) + CreateCard(2, appID, type: Asset.EType.Unknown, rarity: Asset.ERarity.Rare), + CreateCard(3, appID, type: Asset.EType.Unknown, rarity: Asset.ERarity.Rare), + CreateCard(4, appID, type: Asset.EType.Unknown, rarity: Asset.ERarity.Rare) }; - HashSet itemsToSend = GetItemsForFullBadge(items, 3, appID); + HashSet itemsToSend = GetItemsForFullBadge(items, 3, appID); Dictionary<(uint RealAppID, ulong ContextID, ulong ClassID), uint> expectedResult = new() { - { (appID, Steam.Asset.SteamCommunityContextID, 1), 2 }, - { (appID, Steam.Asset.SteamCommunityContextID, 2), 2 }, - { (appID, Steam.Asset.SteamCommunityContextID, 3), 3 }, - { (appID, Steam.Asset.SteamCommunityContextID, 4), 1 }, - { (appID, Steam.Asset.SteamCommunityContextID, 7), 1 } + { (appID, Asset.SteamCommunityContextID, 1), 2 }, + { (appID, Asset.SteamCommunityContextID, 2), 2 }, + { (appID, Asset.SteamCommunityContextID, 3), 3 }, + { (appID, Asset.SteamCommunityContextID, 4), 1 }, + { (appID, Asset.SteamCommunityContextID, 7), 1 } }; AssertResultMatchesExpectation(expectedResult, itemsToSend); @@ -177,12 +181,12 @@ namespace ArchiSteamFarm.Tests { public void NotAllCardsPresent() { const uint appID = 42; - HashSet items = new() { + HashSet items = new() { CreateCard(1, appID), CreateCard(2, appID) }; - HashSet itemsToSend = GetItemsForFullBadge(items, 3, appID); + HashSet itemsToSend = GetItemsForFullBadge(items, 3, appID); Dictionary<(uint RealAppID, ulong ContextID, ulong ClassID), uint> expectedResult = new(0); AssertResultMatchesExpectation(expectedResult, itemsToSend); @@ -192,16 +196,16 @@ namespace ArchiSteamFarm.Tests { public void OneSet() { const uint appID = 42; - HashSet items = new() { + HashSet items = new() { CreateCard(1, appID), CreateCard(2, appID) }; - HashSet itemsToSend = GetItemsForFullBadge(items, 2, appID); + HashSet itemsToSend = GetItemsForFullBadge(items, 2, appID); Dictionary<(uint RealAppID, ulong ContextID, ulong ClassID), uint> expectedResult = new() { - { (appID, Steam.Asset.SteamCommunityContextID, 1), 1 }, - { (appID, Steam.Asset.SteamCommunityContextID, 2), 1 } + { (appID, Asset.SteamCommunityContextID, 1), 1 }, + { (appID, Asset.SteamCommunityContextID, 2), 1 } }; AssertResultMatchesExpectation(expectedResult, itemsToSend); @@ -212,12 +216,12 @@ namespace ArchiSteamFarm.Tests { const uint appID0 = 42; const uint appID1 = 43; - HashSet items = new() { + HashSet items = new() { CreateCard(1, appID0), CreateCard(1, appID1) }; - HashSet itemsToSend = GetItemsForFullBadge( + HashSet itemsToSend = GetItemsForFullBadge( items, new Dictionary { { appID0, 1 }, { appID1, 1 } @@ -225,8 +229,8 @@ namespace ArchiSteamFarm.Tests { ); Dictionary<(uint RealAppID, ulong ContextID, ulong ClassID), uint> expectedResult = new() { - { (appID0, Steam.Asset.SteamCommunityContextID, 1), 1 }, - { (appID1, Steam.Asset.SteamCommunityContextID, 1), 1 } + { (appID0, Asset.SteamCommunityContextID, 1), 1 }, + { (appID1, Asset.SteamCommunityContextID, 1), 1 } }; AssertResultMatchesExpectation(expectedResult, itemsToSend); @@ -237,12 +241,12 @@ namespace ArchiSteamFarm.Tests { const uint appID0 = 42; const uint appID1 = 43; - HashSet items = new() { + HashSet items = new() { CreateCard(1, appID0), CreateCard(1, appID1) }; - HashSet itemsToSend = GetItemsForFullBadge( + HashSet itemsToSend = GetItemsForFullBadge( items, new Dictionary { { appID0, 2 }, { appID1, 2 } @@ -260,7 +264,7 @@ namespace ArchiSteamFarm.Tests { const uint appID1 = 43; const uint appID2 = 44; - HashSet items = new() { + HashSet items = new() { CreateCard(1, appID0), CreateCard(2, appID0), @@ -269,7 +273,7 @@ namespace ArchiSteamFarm.Tests { CreateCard(3, appID1) }; - HashSet itemsToSend = GetItemsForFullBadge( + HashSet itemsToSend = GetItemsForFullBadge( items, new Dictionary { { appID0, 3 }, { appID1, 3 }, @@ -278,9 +282,9 @@ namespace ArchiSteamFarm.Tests { ); Dictionary<(uint RealAppID, ulong ContextID, ulong ClassID), uint> expectedResult = new() { - { (appID1, Steam.Asset.SteamCommunityContextID, 1), 1 }, - { (appID1, Steam.Asset.SteamCommunityContextID, 2), 1 }, - { (appID1, Steam.Asset.SteamCommunityContextID, 3), 1 } + { (appID1, Asset.SteamCommunityContextID, 1), 1 }, + { (appID1, Asset.SteamCommunityContextID, 2), 1 }, + { (appID1, Asset.SteamCommunityContextID, 3), 1 } }; AssertResultMatchesExpectation(expectedResult, itemsToSend); @@ -290,15 +294,15 @@ namespace ArchiSteamFarm.Tests { public void OtherRarityFullSets() { const uint appID = 42; - HashSet items = new() { - CreateCard(1, appID, rarity: Steam.Asset.ERarity.Common), - CreateCard(1, appID, rarity: Steam.Asset.ERarity.Rare) + HashSet items = new() { + CreateCard(1, appID, rarity: Asset.ERarity.Common), + CreateCard(1, appID, rarity: Asset.ERarity.Rare) }; - HashSet itemsToSend = GetItemsForFullBadge(items, 1, appID); + HashSet itemsToSend = GetItemsForFullBadge(items, 1, appID); Dictionary<(uint RealAppID, ulong ContextID, ulong ClassID), uint> expectedResult = new() { - { (appID, Steam.Asset.SteamCommunityContextID, 1), 2 } + { (appID, Asset.SteamCommunityContextID, 1), 2 } }; AssertResultMatchesExpectation(expectedResult, itemsToSend); @@ -308,12 +312,12 @@ namespace ArchiSteamFarm.Tests { public void OtherRarityNoSets() { const uint appID = 42; - HashSet items = new() { - CreateCard(1, appID, rarity: Steam.Asset.ERarity.Common), - CreateCard(1, appID, rarity: Steam.Asset.ERarity.Rare) + HashSet items = new() { + CreateCard(1, appID, rarity: Asset.ERarity.Common), + CreateCard(1, appID, rarity: Asset.ERarity.Rare) }; - HashSet itemsToSend = GetItemsForFullBadge(items, 2, appID); + HashSet itemsToSend = GetItemsForFullBadge(items, 2, appID); Dictionary<(uint RealAppID, ulong ContextID, ulong ClassID), uint> expectedResult = new(0); @@ -324,20 +328,20 @@ namespace ArchiSteamFarm.Tests { public void OtherRarityOneSet() { const uint appID = 42; - HashSet items = new() { - CreateCard(1, appID, rarity: Steam.Asset.ERarity.Common), - CreateCard(2, appID, rarity: Steam.Asset.ERarity.Common), - CreateCard(1, appID, rarity: Steam.Asset.ERarity.Uncommon), - CreateCard(2, appID, rarity: Steam.Asset.ERarity.Uncommon), - CreateCard(3, appID, rarity: Steam.Asset.ERarity.Uncommon) + HashSet items = new() { + CreateCard(1, appID, rarity: Asset.ERarity.Common), + CreateCard(2, appID, rarity: Asset.ERarity.Common), + CreateCard(1, appID, rarity: Asset.ERarity.Uncommon), + CreateCard(2, appID, rarity: Asset.ERarity.Uncommon), + CreateCard(3, appID, rarity: Asset.ERarity.Uncommon) }; - HashSet itemsToSend = GetItemsForFullBadge(items, 3, appID); + HashSet itemsToSend = GetItemsForFullBadge(items, 3, appID); Dictionary<(uint RealAppID, ulong ContextID, ulong ClassID), uint> expectedResult = new() { - { (appID, Steam.Asset.SteamCommunityContextID, 1), 1 }, - { (appID, Steam.Asset.SteamCommunityContextID, 2), 1 }, - { (appID, Steam.Asset.SteamCommunityContextID, 3), 1 } + { (appID, Asset.SteamCommunityContextID, 1), 1 }, + { (appID, Asset.SteamCommunityContextID, 2), 1 }, + { (appID, Asset.SteamCommunityContextID, 3), 1 } }; AssertResultMatchesExpectation(expectedResult, itemsToSend); @@ -347,15 +351,15 @@ namespace ArchiSteamFarm.Tests { public void OtherTypeFullSets() { const uint appID = 42; - HashSet items = new() { - CreateCard(1, appID, type: Steam.Asset.EType.TradingCard), - CreateCard(1, appID, type: Steam.Asset.EType.FoilTradingCard) + HashSet items = new() { + CreateCard(1, appID, type: Asset.EType.TradingCard), + CreateCard(1, appID, type: Asset.EType.FoilTradingCard) }; - HashSet itemsToSend = GetItemsForFullBadge(items, 1, appID); + HashSet itemsToSend = GetItemsForFullBadge(items, 1, appID); Dictionary<(uint RealAppID, ulong ContextID, ulong ClassID), uint> expectedResult = new() { - { (appID, Steam.Asset.SteamCommunityContextID, 1), 2 } + { (appID, Asset.SteamCommunityContextID, 1), 2 } }; AssertResultMatchesExpectation(expectedResult, itemsToSend); @@ -365,12 +369,12 @@ namespace ArchiSteamFarm.Tests { public void OtherTypeNoSets() { const uint appID = 42; - HashSet items = new() { - CreateCard(1, appID, type: Steam.Asset.EType.TradingCard), - CreateCard(1, appID, type: Steam.Asset.EType.FoilTradingCard) + HashSet items = new() { + CreateCard(1, appID, type: Asset.EType.TradingCard), + CreateCard(1, appID, type: Asset.EType.FoilTradingCard) }; - HashSet itemsToSend = GetItemsForFullBadge(items, 2, appID); + HashSet itemsToSend = GetItemsForFullBadge(items, 2, appID); Dictionary<(uint RealAppID, ulong ContextID, ulong ClassID), uint> expectedResult = new(0); @@ -381,20 +385,20 @@ namespace ArchiSteamFarm.Tests { public void OtherTypeOneSet() { const uint appID = 42; - HashSet items = new() { - CreateCard(1, appID, type: Steam.Asset.EType.TradingCard), - CreateCard(2, appID, type: Steam.Asset.EType.TradingCard), - CreateCard(1, appID, type: Steam.Asset.EType.FoilTradingCard), - CreateCard(2, appID, type: Steam.Asset.EType.FoilTradingCard), - CreateCard(3, appID, type: Steam.Asset.EType.FoilTradingCard) + HashSet items = new() { + CreateCard(1, appID, type: Asset.EType.TradingCard), + CreateCard(2, appID, type: Asset.EType.TradingCard), + CreateCard(1, appID, type: Asset.EType.FoilTradingCard), + CreateCard(2, appID, type: Asset.EType.FoilTradingCard), + CreateCard(3, appID, type: Asset.EType.FoilTradingCard) }; - HashSet itemsToSend = GetItemsForFullBadge(items, 3, appID); + HashSet itemsToSend = GetItemsForFullBadge(items, 3, appID); Dictionary<(uint RealAppID, ulong ContextID, ulong ClassID), uint> expectedResult = new() { - { (appID, Steam.Asset.SteamCommunityContextID, 1), 1 }, - { (appID, Steam.Asset.SteamCommunityContextID, 2), 1 }, - { (appID, Steam.Asset.SteamCommunityContextID, 3), 1 } + { (appID, Asset.SteamCommunityContextID, 1), 1 }, + { (appID, Asset.SteamCommunityContextID, 2), 1 }, + { (appID, Asset.SteamCommunityContextID, 3), 1 } }; AssertResultMatchesExpectation(expectedResult, itemsToSend); @@ -404,16 +408,16 @@ namespace ArchiSteamFarm.Tests { public void TooHighAmount() { const uint appID0 = 42; - HashSet items = new() { + HashSet items = new() { CreateCard(1, appID0, 2), CreateCard(2, appID0) }; - HashSet itemsToSend = GetItemsForFullBadge(items, 2, appID0); + HashSet itemsToSend = GetItemsForFullBadge(items, 2, appID0); Dictionary<(uint RealAppID, ulong ContextID, ulong ClassID), uint> expectedResult = new() { - { (appID0, Steam.Asset.SteamCommunityContextID, 1), 1 }, - { (appID0, Steam.Asset.SteamCommunityContextID, 2), 1 } + { (appID0, Asset.SteamCommunityContextID, 1), 1 }, + { (appID0, Asset.SteamCommunityContextID, 2), 1 } }; AssertResultMatchesExpectation(expectedResult, itemsToSend); @@ -423,14 +427,14 @@ namespace ArchiSteamFarm.Tests { public void TooManyCardsForSingleTrade() { const uint appID = 42; - HashSet items = new(); + HashSet items = new(); for (byte i = 0; i < ArchiSteamFarm.Trading.MaxItemsPerTrade; i++) { items.Add(CreateCard(1, appID)); items.Add(CreateCard(2, appID)); } - HashSet itemsToSend = GetItemsForFullBadge(items, 2, appID); + HashSet itemsToSend = GetItemsForFullBadge(items, 2, appID); Assert.IsTrue(itemsToSend.Count <= ArchiSteamFarm.Trading.MaxItemsPerTrade); } @@ -440,7 +444,7 @@ namespace ArchiSteamFarm.Tests { const uint appID0 = 42; const uint appID1 = 43; - HashSet items = new(); + HashSet items = new(); for (byte i = 0; i < 100; i++) { items.Add(CreateCard(1, appID0)); @@ -454,7 +458,7 @@ namespace ArchiSteamFarm.Tests { { appID1, 2 } }; - HashSet itemsToSend = GetItemsForFullBadge(items, itemsPerSet); + HashSet itemsToSend = GetItemsForFullBadge(items, itemsPerSet); Assert.IsTrue(itemsToSend.Count <= ArchiSteamFarm.Trading.MaxItemsPerTrade); } @@ -466,7 +470,7 @@ namespace ArchiSteamFarm.Tests { const uint appID1 = 43; const uint appID2 = 44; - HashSet items = new() { + HashSet items = new() { CreateCard(1, appID0), CreateCard(2, appID0), CreateCard(3, appID0), @@ -484,7 +488,7 @@ namespace ArchiSteamFarm.Tests { Assert.Fail(); } - private static void AssertResultMatchesExpectation(IReadOnlyDictionary<(uint RealAppID, ulong ContextID, ulong ClassID), uint> expectedResult, IReadOnlyCollection itemsToSend) { + private static void AssertResultMatchesExpectation(IReadOnlyDictionary<(uint RealAppID, ulong ContextID, ulong ClassID), uint> expectedResult, IReadOnlyCollection itemsToSend) { if (expectedResult == null) { throw new ArgumentNullException(nameof(expectedResult)); } @@ -498,12 +502,12 @@ namespace ArchiSteamFarm.Tests { Assert.IsTrue(expectedResult.All(expectation => realResult.TryGetValue(expectation.Key, out long reality) && (expectation.Value == reality))); } - private static Steam.Asset CreateCard(ulong classID, uint realAppID, uint amount = 1, Steam.Asset.EType type = Steam.Asset.EType.TradingCard, Steam.Asset.ERarity rarity = Steam.Asset.ERarity.Common) => new(Steam.Asset.SteamAppID, Steam.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, realAppID: realAppID, type: type, rarity: rarity); - private static HashSet GetItemsForFullBadge(IReadOnlyCollection inventory, byte cardsPerSet, uint appID, ushort maxItems = ArchiSteamFarm.Trading.MaxItemsPerTrade) => GetItemsForFullBadge(inventory, new Dictionary { { appID, cardsPerSet } }, maxItems); + private static HashSet GetItemsForFullBadge(IReadOnlyCollection inventory, byte cardsPerSet, uint appID, ushort maxItems = ArchiSteamFarm.Trading.MaxItemsPerTrade) => GetItemsForFullBadge(inventory, new Dictionary { { appID, cardsPerSet } }, maxItems); - private static HashSet GetItemsForFullBadge(IReadOnlyCollection inventory, IDictionary cardsPerSet, ushort maxItems = ArchiSteamFarm.Trading.MaxItemsPerTrade) { - Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), List> inventorySets = ArchiSteamFarm.Trading.GetInventorySets(inventory); + private static HashSet GetItemsForFullBadge(IReadOnlyCollection inventory, IDictionary cardsPerSet, ushort maxItems = ArchiSteamFarm.Trading.MaxItemsPerTrade) { + Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), List> inventorySets = ArchiSteamFarm.Trading.GetInventorySets(inventory); return ArchiSteamFarm.Bot.GetItemsForFullSets(inventory, inventorySets.ToDictionary(kv => kv.Key, kv => (SetsToExtract: inventorySets[kv.Key][0], cardsPerSet[kv.Key.RealAppID])), maxItems).ToHashSet(); } diff --git a/ArchiSteamFarm.Tests/Trading.cs b/ArchiSteamFarm.Tests/Trading.cs index 0c2f33868..afeb01fa9 100644 --- a/ArchiSteamFarm.Tests/Trading.cs +++ b/ArchiSteamFarm.Tests/Trading.cs @@ -29,44 +29,44 @@ namespace ArchiSteamFarm.Tests { public sealed class Trading { [TestMethod] public void MismatchRarityIsNotFair() { - HashSet itemsToGive = new() { CreateItem(1, rarity: Steam.Asset.ERarity.Rare) }; - HashSet itemsToReceive = new() { CreateItem(2) }; + HashSet itemsToGive = new() { CreateItem(1, rarity: Asset.ERarity.Rare) }; + HashSet itemsToReceive = new() { CreateItem(2) }; Assert.IsFalse(IsFairExchange(itemsToGive, itemsToReceive)); } [TestMethod] public void MismatchRealAppIDsIsNotFair() { - HashSet itemsToGive = new() { CreateItem(1, realAppID: 570) }; - HashSet itemsToReceive = new() { CreateItem(2) }; + HashSet itemsToGive = new() { CreateItem(1, realAppID: 570) }; + HashSet itemsToReceive = new() { CreateItem(2) }; Assert.IsFalse(IsFairExchange(itemsToGive, itemsToReceive)); } [TestMethod] public void MismatchTypesIsNotFair() { - HashSet itemsToGive = new() { CreateItem(1, type: Steam.Asset.EType.Emoticon) }; - HashSet itemsToReceive = new() { CreateItem(2) }; + HashSet itemsToGive = new() { CreateItem(1, type: Asset.EType.Emoticon) }; + HashSet itemsToReceive = new() { CreateItem(2) }; Assert.IsFalse(IsFairExchange(itemsToGive, itemsToReceive)); } [TestMethod] public void MultiGameMultiTypeBadReject() { - HashSet inventory = new() { + HashSet inventory = new() { CreateItem(1, 9), - CreateItem(3, 9, 730, Steam.Asset.EType.Emoticon), - CreateItem(4, realAppID: 730, type: Steam.Asset.EType.Emoticon) + CreateItem(3, 9, 730, Asset.EType.Emoticon), + CreateItem(4, realAppID: 730, type: Asset.EType.Emoticon) }; - HashSet itemsToGive = new() { + HashSet itemsToGive = new() { CreateItem(1), - CreateItem(4, realAppID: 730, type: Steam.Asset.EType.Emoticon) + CreateItem(4, realAppID: 730, type: Asset.EType.Emoticon) }; - HashSet itemsToReceive = new() { + HashSet itemsToReceive = new() { CreateItem(2), - CreateItem(3, realAppID: 730, type: Steam.Asset.EType.Emoticon) + CreateItem(3, realAppID: 730, type: Asset.EType.Emoticon) }; Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive)); @@ -75,19 +75,19 @@ namespace ArchiSteamFarm.Tests { [TestMethod] public void MultiGameMultiTypeNeutralAccept() { - HashSet inventory = new() { + HashSet inventory = new() { CreateItem(1, 9), - CreateItem(3, realAppID: 730, type: Steam.Asset.EType.Emoticon) + CreateItem(3, realAppID: 730, type: Asset.EType.Emoticon) }; - HashSet itemsToGive = new() { + HashSet itemsToGive = new() { CreateItem(1), - CreateItem(3, realAppID: 730, type: Steam.Asset.EType.Emoticon) + CreateItem(3, realAppID: 730, type: Asset.EType.Emoticon) }; - HashSet itemsToReceive = new() { + HashSet itemsToReceive = new() { CreateItem(2), - CreateItem(4, realAppID: 730, type: Steam.Asset.EType.Emoticon) + CreateItem(4, realAppID: 730, type: Asset.EType.Emoticon) }; Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive)); @@ -96,18 +96,18 @@ namespace ArchiSteamFarm.Tests { [TestMethod] public void MultiGameSingleTypeBadReject() { - HashSet inventory = new() { + HashSet inventory = new() { CreateItem(1, 9), CreateItem(3, realAppID: 730), CreateItem(4, realAppID: 730) }; - HashSet itemsToGive = new() { + HashSet itemsToGive = new() { CreateItem(1), CreateItem(3, realAppID: 730) }; - HashSet itemsToReceive = new() { + HashSet itemsToReceive = new() { CreateItem(2), CreateItem(4, realAppID: 730) }; @@ -118,17 +118,17 @@ namespace ArchiSteamFarm.Tests { [TestMethod] public void MultiGameSingleTypeNeutralAccept() { - HashSet inventory = new() { + HashSet inventory = new() { CreateItem(1, 2), CreateItem(3, realAppID: 730) }; - HashSet itemsToGive = new() { + HashSet itemsToGive = new() { CreateItem(1), CreateItem(3, realAppID: 730) }; - HashSet itemsToReceive = new() { + HashSet itemsToReceive = new() { CreateItem(2), CreateItem(4, realAppID: 730) }; @@ -139,7 +139,7 @@ namespace ArchiSteamFarm.Tests { [TestMethod] public void SingleGameAbrynosWasWrongNeutralAccept() { - HashSet inventory = new() { + HashSet inventory = new() { CreateItem(1), CreateItem(2, 2), CreateItem(3), @@ -147,11 +147,11 @@ namespace ArchiSteamFarm.Tests { CreateItem(5) }; - HashSet itemsToGive = new() { + HashSet itemsToGive = new() { CreateItem(2) }; - HashSet itemsToReceive = new() { + HashSet itemsToReceive = new() { CreateItem(3) }; @@ -161,17 +161,17 @@ namespace ArchiSteamFarm.Tests { [TestMethod] public void SingleGameDonationAccept() { - HashSet inventory = new() { + HashSet inventory = new() { CreateItem(1) }; - HashSet itemsToGive = new() { + HashSet itemsToGive = new() { CreateItem(1) }; - HashSet itemsToReceive = new() { + HashSet itemsToReceive = new() { CreateItem(2), - CreateItem(3, type: Steam.Asset.EType.SteamGems) + CreateItem(3, type: Asset.EType.SteamGems) }; Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive)); @@ -180,20 +180,20 @@ namespace ArchiSteamFarm.Tests { [TestMethod] public void SingleGameMultiTypeBadReject() { - HashSet inventory = new() { + HashSet inventory = new() { CreateItem(1, 9), - CreateItem(3, 9, type: Steam.Asset.EType.Emoticon), - CreateItem(4, type: Steam.Asset.EType.Emoticon) + CreateItem(3, 9, type: Asset.EType.Emoticon), + CreateItem(4, type: Asset.EType.Emoticon) }; - HashSet itemsToGive = new() { + HashSet itemsToGive = new() { CreateItem(1), - CreateItem(4, type: Steam.Asset.EType.Emoticon) + CreateItem(4, type: Asset.EType.Emoticon) }; - HashSet itemsToReceive = new() { + HashSet itemsToReceive = new() { CreateItem(2), - CreateItem(3, type: Steam.Asset.EType.Emoticon) + CreateItem(3, type: Asset.EType.Emoticon) }; Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive)); @@ -202,19 +202,19 @@ namespace ArchiSteamFarm.Tests { [TestMethod] public void SingleGameMultiTypeNeutralAccept() { - HashSet inventory = new() { + HashSet inventory = new() { CreateItem(1, 9), - CreateItem(3, type: Steam.Asset.EType.Emoticon) + CreateItem(3, type: Asset.EType.Emoticon) }; - HashSet itemsToGive = new() { + HashSet itemsToGive = new() { CreateItem(1), - CreateItem(3, type: Steam.Asset.EType.Emoticon) + CreateItem(3, type: Asset.EType.Emoticon) }; - HashSet itemsToReceive = new() { + HashSet itemsToReceive = new() { CreateItem(2), - CreateItem(4, type: Steam.Asset.EType.Emoticon) + CreateItem(4, type: Asset.EType.Emoticon) }; Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive)); @@ -223,19 +223,19 @@ namespace ArchiSteamFarm.Tests { [TestMethod] public void SingleGameQuantityBadReject() { - HashSet inventory = new() { + HashSet inventory = new() { CreateItem(1), CreateItem(2), CreateItem(3) }; - HashSet itemsToGive = new() { + HashSet itemsToGive = new() { CreateItem(1), CreateItem(2), CreateItem(3) }; - HashSet itemsToReceive = new() { CreateItem(4, 3) }; + HashSet itemsToReceive = new() { CreateItem(4, 3) }; Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive)); Assert.IsFalse(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive)); @@ -243,17 +243,17 @@ namespace ArchiSteamFarm.Tests { [TestMethod] public void SingleGameQuantityBadReject2() { - HashSet inventory = new() { + HashSet inventory = new() { CreateItem(1), CreateItem(2, 2) }; - HashSet itemsToGive = new() { + HashSet itemsToGive = new() { CreateItem(1), CreateItem(2, 2) }; - HashSet itemsToReceive = new() { CreateItem(3, 3) }; + HashSet itemsToReceive = new() { CreateItem(3, 3) }; Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive)); Assert.IsFalse(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive)); @@ -261,17 +261,17 @@ namespace ArchiSteamFarm.Tests { [TestMethod] public void SingleGameQuantityNeutralAccept() { - HashSet inventory = new() { + HashSet inventory = new() { CreateItem(1, 2), CreateItem(2) }; - HashSet itemsToGive = new() { + HashSet itemsToGive = new() { CreateItem(1), CreateItem(2) }; - HashSet itemsToReceive = new() { CreateItem(3, 2) }; + HashSet itemsToReceive = new() { CreateItem(3, 2) }; Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive)); Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive)); @@ -279,13 +279,13 @@ namespace ArchiSteamFarm.Tests { [TestMethod] public void SingleGameSingleTypeBadReject() { - HashSet inventory = new() { + HashSet inventory = new() { CreateItem(1), CreateItem(2) }; - HashSet itemsToGive = new() { CreateItem(1) }; - HashSet itemsToReceive = new() { CreateItem(2) }; + HashSet itemsToGive = new() { CreateItem(1) }; + HashSet itemsToReceive = new() { CreateItem(2) }; Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive)); Assert.IsFalse(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive)); @@ -293,15 +293,15 @@ namespace ArchiSteamFarm.Tests { [TestMethod] public void SingleGameSingleTypeBadWithOverpayingReject() { - HashSet inventory = new() { + HashSet inventory = new() { CreateItem(1, 2), CreateItem(2, 2), CreateItem(3, 2) }; - HashSet itemsToGive = new() { CreateItem(2) }; + HashSet itemsToGive = new() { CreateItem(2) }; - HashSet itemsToReceive = new() { + HashSet itemsToReceive = new() { CreateItem(1), CreateItem(3) }; @@ -312,14 +312,14 @@ namespace ArchiSteamFarm.Tests { [TestMethod] public void SingleGameSingleTypeBigDifferenceAccept() { - HashSet inventory = new() { + HashSet inventory = new() { CreateItem(1), CreateItem(2, 5), CreateItem(3) }; - HashSet itemsToGive = new() { CreateItem(2) }; - HashSet itemsToReceive = new() { CreateItem(3) }; + HashSet itemsToGive = new() { CreateItem(2) }; + HashSet itemsToReceive = new() { CreateItem(3) }; Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive)); Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive)); @@ -327,7 +327,7 @@ namespace ArchiSteamFarm.Tests { [TestMethod] public void SingleGameSingleTypeBigDifferenceReject() { - HashSet inventory = new() { + HashSet inventory = new() { CreateItem(1), CreateItem(2, 2), CreateItem(3, 2), @@ -335,12 +335,12 @@ namespace ArchiSteamFarm.Tests { CreateItem(5, 10) }; - HashSet itemsToGive = new() { + HashSet itemsToGive = new() { CreateItem(2), CreateItem(5) }; - HashSet itemsToReceive = new() { + HashSet itemsToReceive = new() { CreateItem(3), CreateItem(4) }; @@ -351,9 +351,9 @@ namespace ArchiSteamFarm.Tests { [TestMethod] public void SingleGameSingleTypeGoodAccept() { - HashSet inventory = new() { CreateItem(1, 2) }; - HashSet itemsToGive = new() { CreateItem(1) }; - HashSet itemsToReceive = new() { CreateItem(2) }; + HashSet inventory = new() { CreateItem(1, 2) }; + HashSet itemsToGive = new() { CreateItem(1) }; + HashSet itemsToReceive = new() { CreateItem(2) }; Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive)); Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive)); @@ -361,9 +361,9 @@ namespace ArchiSteamFarm.Tests { [TestMethod] public void SingleGameSingleTypeNeutralAccept() { - HashSet inventory = new() { CreateItem(1) }; - HashSet itemsToGive = new() { CreateItem(1) }; - HashSet itemsToReceive = new() { CreateItem(2) }; + HashSet inventory = new() { CreateItem(1) }; + HashSet itemsToGive = new() { CreateItem(1) }; + HashSet itemsToReceive = new() { CreateItem(2) }; Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive)); Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive)); @@ -371,14 +371,14 @@ namespace ArchiSteamFarm.Tests { [TestMethod] public void SingleGameSingleTypeNeutralWithOverpayingAccept() { - HashSet inventory = new() { + HashSet inventory = new() { CreateItem(1, 2), CreateItem(2, 2) }; - HashSet itemsToGive = new() { CreateItem(2) }; + HashSet itemsToGive = new() { CreateItem(2) }; - HashSet itemsToReceive = new() { + HashSet itemsToReceive = new() { CreateItem(1), CreateItem(3) }; @@ -387,6 +387,6 @@ namespace ArchiSteamFarm.Tests { Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive)); } - private static Steam.Asset CreateItem(ulong classID, uint amount = 1, uint realAppID = Steam.Asset.SteamAppID, Steam.Asset.EType type = Steam.Asset.EType.TradingCard, Steam.Asset.ERarity rarity = Steam.Asset.ERarity.Common) => new(Steam.Asset.SteamAppID, Steam.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, realAppID: realAppID, type: type, rarity: rarity); } } diff --git a/ArchiSteamFarm.sln.DotSettings b/ArchiSteamFarm.sln.DotSettings index 0b40dbd04..55d60192b 100644 --- a/ArchiSteamFarm.sln.DotSettings +++ b/ArchiSteamFarm.sln.DotSettings @@ -224,7 +224,7 @@ WARNING WARNING WARNING - WARNING + HINT WARNING SUGGESTION SUGGESTION diff --git a/ArchiSteamFarm/ASF.cs b/ArchiSteamFarm/ASF.cs index eb2e9d5d4..c353da338 100644 --- a/ArchiSteamFarm/ASF.cs +++ b/ArchiSteamFarm/ASF.cs @@ -39,9 +39,12 @@ using ArchiSteamFarm.IPC; using ArchiSteamFarm.Localization; using ArchiSteamFarm.NLog; using ArchiSteamFarm.Plugins; +using ArchiSteamFarm.Web; using JetBrains.Annotations; using SteamKit2; using SteamKit2.Discovery; +using File = ArchiSteamFarm.RuntimeCompatibility.File; +using Path = ArchiSteamFarm.RuntimeCompatibility.Path; namespace ArchiSteamFarm { public static class ASF { @@ -91,8 +94,8 @@ namespace ArchiSteamFarm { } return fileType switch { - EFileType.Config => Path.Combine(SharedInfo.ConfigDirectory, SharedInfo.GlobalConfigFileName), - EFileType.Database => Path.Combine(SharedInfo.ConfigDirectory, SharedInfo.GlobalDatabaseFileName), + EFileType.Config => System.IO.Path.Combine(SharedInfo.ConfigDirectory, SharedInfo.GlobalConfigFileName), + EFileType.Database => System.IO.Path.Combine(SharedInfo.ConfigDirectory, SharedInfo.GlobalDatabaseFileName), _ => throw new ArgumentOutOfRangeException(nameof(fileType)) }; } @@ -233,7 +236,7 @@ namespace ArchiSteamFarm { try { // If backup directory from previous update exists, it's a good idea to purge it now - string backupDirectory = Path.Combine(SharedInfo.HomeDirectory, SharedInfo.UpdateDirectory); + string backupDirectory = System.IO.Path.Combine(SharedInfo.HomeDirectory, SharedInfo.UpdateDirectory); if (Directory.Exists(backupDirectory)) { ArchiLogger.LogGenericInfo(Strings.UpdateCleanup); @@ -320,7 +323,7 @@ namespace ArchiSteamFarm { progressReporter.ProgressChanged += OnProgressChanged; - WebBrowser.BinaryResponse? response; + BinaryResponse? response; try { response = await WebBrowser.UrlGetToBinary(binaryAsset.DownloadURL!, progressReporter: progressReporter).ConfigureAwait(false); @@ -361,9 +364,9 @@ namespace ArchiSteamFarm { } if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { - string executable = Path.Combine(SharedInfo.HomeDirectory, SharedInfo.AssemblyName); + string executable = System.IO.Path.Combine(SharedInfo.HomeDirectory, SharedInfo.AssemblyName); - if (File.Exists(executable)) { + if (System.IO.File.Exists(executable)) { OS.UnixSetFileAccess(executable, OS.EUnixPermission.Combined755); } } @@ -505,7 +508,7 @@ namespace ArchiSteamFarm { throw new ArgumentNullException(nameof(fullPath)); } - string extension = Path.GetExtension(name); + string extension = System.IO.Path.GetExtension(name); switch (extension) { case SharedInfo.JsonConfigExtension: @@ -561,7 +564,7 @@ namespace ArchiSteamFarm { throw new ArgumentNullException(nameof(fullPath)); } - string extension = Path.GetExtension(name); + string extension = System.IO.Path.GetExtension(name); switch (extension) { case SharedInfo.IPCConfigExtension: @@ -584,7 +587,7 @@ namespace ArchiSteamFarm { throw new ArgumentNullException(nameof(fullPath)); } - string extension = Path.GetExtension(name); + string extension = System.IO.Path.GetExtension(name); switch (extension) { case SharedInfo.JsonConfigExtension: @@ -612,7 +615,7 @@ namespace ArchiSteamFarm { throw new InvalidOperationException(nameof(Bot.Bots)); } - string botName = Path.GetFileNameWithoutExtension(name); + string botName = System.IO.Path.GetFileNameWithoutExtension(name); if (string.IsNullOrEmpty(botName) || (botName[0] == '.')) { return; @@ -657,7 +660,7 @@ namespace ArchiSteamFarm { throw new InvalidOperationException(nameof(Bot.Bots)); } - string botName = Path.GetFileNameWithoutExtension(name); + string botName = System.IO.Path.GetFileNameWithoutExtension(name); if (string.IsNullOrEmpty(botName) || (botName[0] == '.')) { return; @@ -703,7 +706,7 @@ namespace ArchiSteamFarm { throw new ArgumentNullException(nameof(fullPath)); } - string extension = Path.GetExtension(name); + string extension = System.IO.Path.GetExtension(name); switch (extension) { case SharedInfo.IPCConfigExtension: @@ -726,7 +729,7 @@ namespace ArchiSteamFarm { throw new ArgumentNullException(nameof(fullPath)); } - string extension = Path.GetExtension(name); + string extension = System.IO.Path.GetExtension(name); switch (extension) { case SharedInfo.JsonConfigExtension: @@ -750,7 +753,7 @@ namespace ArchiSteamFarm { throw new InvalidOperationException(nameof(Bot.Bots)); } - string botName = Path.GetFileNameWithoutExtension(name); + string botName = System.IO.Path.GetFileNameWithoutExtension(name); if (string.IsNullOrEmpty(botName)) { return; @@ -761,7 +764,7 @@ namespace ArchiSteamFarm { } if (botName.Equals(SharedInfo.ASF, StringComparison.OrdinalIgnoreCase)) { - if (File.Exists(fullPath)) { + if (System.IO.File.Exists(fullPath)) { return; } @@ -769,7 +772,7 @@ namespace ArchiSteamFarm { // If that's the case, we wait for maximum of 5 seconds before shutting down await Task.Delay(5000).ConfigureAwait(false); - if (File.Exists(fullPath)) { + if (System.IO.File.Exists(fullPath)) { return; } @@ -854,7 +857,7 @@ namespace ArchiSteamFarm { HashSet botNames; try { - botNames = Directory.EnumerateFiles(SharedInfo.ConfigDirectory, "*" + SharedInfo.JsonConfigExtension).Select(Path.GetFileNameWithoutExtension).Where(botName => !string.IsNullOrEmpty(botName) && IsValidBotName(botName)).ToHashSet(Bot.BotsComparer)!; + botNames = Directory.EnumerateFiles(SharedInfo.ConfigDirectory, "*" + SharedInfo.JsonConfigExtension).Select(System.IO.Path.GetFileNameWithoutExtension).Where(botName => !string.IsNullOrEmpty(botName) && IsValidBotName(botName)).ToHashSet(Bot.BotsComparer)!; } catch (Exception e) { ArchiLogger.LogGenericException(e); @@ -923,10 +926,10 @@ namespace ArchiSteamFarm { } // Firstly we'll move all our existing files to a backup directory - string backupDirectory = Path.Combine(targetDirectory, SharedInfo.UpdateDirectory); + string backupDirectory = System.IO.Path.Combine(targetDirectory, SharedInfo.UpdateDirectory); foreach (string file in Directory.EnumerateFiles(targetDirectory, "*", SearchOption.AllDirectories)) { - string fileName = Path.GetFileName(file); + string fileName = System.IO.Path.GetFileName(file); if (string.IsNullOrEmpty(fileName)) { ArchiLogger.LogNullError(nameof(fileName)); @@ -934,7 +937,7 @@ namespace ArchiSteamFarm { return false; } - string relativeFilePath = RuntimeCompatibility.Path.GetRelativePath(targetDirectory, file); + string relativeFilePath = Path.GetRelativePath(targetDirectory, file); if (string.IsNullOrEmpty(relativeFilePath)) { ArchiLogger.LogNullError(nameof(relativeFilePath)); @@ -942,7 +945,7 @@ namespace ArchiSteamFarm { return false; } - string? relativeDirectoryName = Path.GetDirectoryName(relativeFilePath); + string? relativeDirectoryName = System.IO.Path.GetDirectoryName(relativeFilePath); switch (relativeDirectoryName) { case null: @@ -975,11 +978,11 @@ namespace ArchiSteamFarm { break; } - string targetBackupDirectory = relativeDirectoryName.Length > 0 ? Path.Combine(backupDirectory, relativeDirectoryName) : backupDirectory; + string targetBackupDirectory = relativeDirectoryName.Length > 0 ? System.IO.Path.Combine(backupDirectory, relativeDirectoryName) : backupDirectory; Directory.CreateDirectory(targetBackupDirectory); - string targetBackupFile = Path.Combine(targetBackupDirectory, fileName); - RuntimeCompatibility.File.Move(file, targetBackupFile, true); + string targetBackupFile = System.IO.Path.Combine(targetBackupDirectory, fileName); + File.Move(file, targetBackupFile, true); } // We can now get rid of directories that are empty @@ -991,17 +994,17 @@ namespace ArchiSteamFarm { // Now enumerate over files in the zip archive, skip directory entries that we're not interested in (we can create them ourselves if needed) foreach (ZipArchiveEntry zipFile in archive.Entries.Where(zipFile => !string.IsNullOrEmpty(zipFile.Name))) { - string file = Path.Combine(targetDirectory, zipFile.FullName); + string file = System.IO.Path.Combine(targetDirectory, zipFile.FullName); - if (File.Exists(file)) { + if (System.IO.File.Exists(file)) { // This is possible only with files that we decided to leave in place during our backup function string targetBackupFile = file + ".bak"; - RuntimeCompatibility.File.Move(file, targetBackupFile, true); + File.Move(file, targetBackupFile, true); } // Check if this file requires its own folder if (zipFile.Name != zipFile.FullName) { - string? directory = Path.GetDirectoryName(file); + string? directory = System.IO.Path.GetDirectoryName(file); if (string.IsNullOrEmpty(directory)) { ArchiLogger.LogNullError(nameof(directory)); diff --git a/ArchiSteamFarm/Actions.cs b/ArchiSteamFarm/Actions.cs index 5762d4881..af5b076d5 100644 --- a/ArchiSteamFarm/Actions.cs +++ b/ArchiSteamFarm/Actions.cs @@ -27,13 +27,20 @@ using System.Linq; using System.Net.Http; using System.Threading; using System.Threading.Tasks; +using ArchiSteamFarm.Callbacks; using ArchiSteamFarm.Collections; using ArchiSteamFarm.Helpers; using ArchiSteamFarm.Json; using ArchiSteamFarm.Localization; +using ArchiSteamFarm.Web; using JetBrains.Annotations; using SteamKit2; +#if NETFRAMEWORK +using ArchiSteamFarm.RuntimeCompatibility; + +#endif + namespace ArchiSteamFarm { public sealed class Actions : IAsyncDisposable { private static readonly SemaphoreSlim GiftCardsSemaphore = new(1, 1); @@ -103,7 +110,7 @@ namespace ArchiSteamFarm { } [PublicAPI] - public async Task<(bool Success, IReadOnlyCollection? HandledConfirmations, string Message)> HandleTwoFactorAuthenticationConfirmations(bool accept, MobileAuthenticator.Confirmation.EType? acceptedType = null, IReadOnlyCollection? acceptedCreatorIDs = null, bool waitIfNeeded = false) { + public async Task<(bool Success, IReadOnlyCollection? HandledConfirmations, string Message)> HandleTwoFactorAuthenticationConfirmations(bool accept, Confirmation.EType? acceptedType = null, IReadOnlyCollection? acceptedCreatorIDs = null, bool waitIfNeeded = false) { if (Bot.BotDatabase.MobileAuthenticator == null) { return (false, null, Strings.BotNoASFAuthenticator); } @@ -112,14 +119,14 @@ namespace ArchiSteamFarm { return (false, null, Strings.BotNotConnected); } - Dictionary? handledConfirmations = null; + Dictionary? handledConfirmations = null; for (byte i = 0; (i == 0) || ((i < WebBrowser.MaxTries) && waitIfNeeded); i++) { if (i > 0) { await Task.Delay(1000).ConfigureAwait(false); } - HashSet? confirmations = await Bot.BotDatabase.MobileAuthenticator.GetConfirmations().ConfigureAwait(false); + HashSet? confirmations = await Bot.BotDatabase.MobileAuthenticator.GetConfirmations().ConfigureAwait(false); if ((confirmations == null) || (confirmations.Count == 0)) { continue; @@ -145,9 +152,9 @@ namespace ArchiSteamFarm { return (false, handledConfirmations?.Values, Strings.WarningFailed); } - handledConfirmations ??= new Dictionary(); + handledConfirmations ??= new Dictionary(); - foreach (MobileAuthenticator.Confirmation? confirmation in confirmations) { + foreach (Confirmation? confirmation in confirmations) { handledConfirmations[confirmation.Creator] = confirmation; } @@ -229,7 +236,7 @@ namespace ArchiSteamFarm { } [PublicAPI] - public async Task RedeemKey(string key) { + public async Task RedeemKey(string key) { await LimitGiftsRequestsAsync().ConfigureAwait(false); return await Bot.ArchiHandler.RedeemKey(key).ConfigureAwait(false); @@ -260,7 +267,7 @@ namespace ArchiSteamFarm { } [PublicAPI] - public async Task<(bool Success, string Message)> SendInventory(IReadOnlyCollection items, ulong targetSteamID = 0, string? tradeToken = null, ushort itemsPerTrade = Trading.MaxItemsPerTrade) { + public async Task<(bool Success, string Message)> SendInventory(IReadOnlyCollection items, ulong targetSteamID = 0, string? tradeToken = null, ushort itemsPerTrade = Trading.MaxItemsPerTrade) { if ((items == null) || (items.Count == 0)) { throw new ArgumentNullException(nameof(items)); } @@ -306,7 +313,7 @@ namespace ArchiSteamFarm { (bool success, HashSet? mobileTradeOfferIDs) = await Bot.ArchiWebHandler.SendTradeOffer(targetSteamID, items, token: tradeToken, itemsPerTrade: itemsPerTrade).ConfigureAwait(false); if ((mobileTradeOfferIDs?.Count > 0) && Bot.HasMobileAuthenticator) { - (bool twoFactorSuccess, _, _) = await HandleTwoFactorAuthenticationConfirmations(true, MobileAuthenticator.Confirmation.EType.Trade, mobileTradeOfferIDs, true).ConfigureAwait(false); + (bool twoFactorSuccess, _, _) = await HandleTwoFactorAuthenticationConfirmations(true, Confirmation.EType.Trade, mobileTradeOfferIDs, true).ConfigureAwait(false); if (!twoFactorSuccess) { return (false, Strings.BotLootingFailed); @@ -317,7 +324,7 @@ namespace ArchiSteamFarm { } [PublicAPI] - public async Task<(bool Success, string Message)> SendInventory(uint appID = Steam.Asset.SteamAppID, ulong contextID = Steam.Asset.SteamCommunityContextID, ulong targetSteamID = 0, string? tradeToken = null, Func? filterFunction = null, ushort itemsPerTrade = Trading.MaxItemsPerTrade) { + public async Task<(bool Success, string Message)> SendInventory(uint appID = Asset.SteamAppID, ulong contextID = Asset.SteamCommunityContextID, ulong targetSteamID = 0, string? tradeToken = null, Func? filterFunction = null, ushort itemsPerTrade = Trading.MaxItemsPerTrade) { if (appID == 0) { throw new ArgumentOutOfRangeException(nameof(appID)); } @@ -332,7 +339,7 @@ namespace ArchiSteamFarm { filterFunction ??= _ => true; - HashSet inventory; + HashSet inventory; lock (TradingSemaphore) { if (TradingScheduled) { diff --git a/ArchiSteamFarm/ArchiHandler.cs b/ArchiSteamFarm/ArchiHandler.cs index 19a73955b..8a52dc50c 100644 --- a/ArchiSteamFarm/ArchiHandler.cs +++ b/ArchiSteamFarm/ArchiHandler.cs @@ -22,14 +22,10 @@ using System; using System.Collections.Generic; using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Globalization; -using System.IO; using System.Linq; -using System.Net; using System.Threading.Tasks; +using ArchiSteamFarm.Callbacks; using ArchiSteamFarm.CMsgs; -using ArchiSteamFarm.Localization; using ArchiSteamFarm.NLog; using JetBrains.Annotations; using SteamKit2; @@ -732,183 +728,6 @@ namespace ArchiSteamFarm { Client.Send(request); } - [SuppressMessage("ReSharper", "MemberCanBeInternal")] - public sealed class PurchaseResponseCallback : CallbackMsg { - public Dictionary? Items { get; } - - public EPurchaseResultDetail PurchaseResultDetail { get; internal set; } - public EResult Result { get; internal set; } - - internal PurchaseResponseCallback(EResult result, EPurchaseResultDetail purchaseResult) { - if (!Enum.IsDefined(typeof(EResult), result)) { - throw new InvalidEnumArgumentException(nameof(result), (int) result, typeof(EResult)); - } - - if (!Enum.IsDefined(typeof(EPurchaseResultDetail), purchaseResult)) { - throw new InvalidEnumArgumentException(nameof(purchaseResult), (int) purchaseResult, typeof(EPurchaseResultDetail)); - } - - Result = result; - PurchaseResultDetail = purchaseResult; - } - - internal PurchaseResponseCallback(JobID jobID, CMsgClientPurchaseResponse msg) { - if (jobID == null) { - throw new ArgumentNullException(nameof(jobID)); - } - - if (msg == null) { - throw new ArgumentNullException(nameof(msg)); - } - - JobID = jobID; - PurchaseResultDetail = (EPurchaseResultDetail) msg.purchase_result_details; - Result = (EResult) msg.eresult; - - if (msg.purchase_receipt_info == null) { - ASF.ArchiLogger.LogNullError(nameof(msg.purchase_receipt_info)); - - return; - } - - KeyValue receiptInfo = new(); - - using (MemoryStream ms = new(msg.purchase_receipt_info)) { - if (!receiptInfo.TryReadAsBinary(ms)) { - ASF.ArchiLogger.LogNullError(nameof(ms)); - - return; - } - } - - List lineItems = receiptInfo["lineitems"].Children; - - if (lineItems.Count == 0) { - return; - } - - Items = new Dictionary(lineItems.Count); - - foreach (KeyValue lineItem in lineItems) { - uint packageID = lineItem["PackageID"].AsUnsignedInteger(); - - if (packageID == 0) { - // Coupons have PackageID of -1 (don't ask me why) - // We'll use ItemAppID in this case - packageID = lineItem["ItemAppID"].AsUnsignedInteger(); - - if (packageID == 0) { - ASF.ArchiLogger.LogNullError(nameof(packageID)); - - return; - } - } - - string? gameName = lineItem["ItemDescription"].AsString(); - - if (string.IsNullOrEmpty(gameName)) { - ASF.ArchiLogger.LogNullError(nameof(gameName)); - - return; - } - - // Apparently steam expects client to decode sent HTML - gameName = WebUtility.HtmlDecode(gameName); - Items[packageID] = gameName; - } - } - } - - public sealed class UserNotificationsCallback : CallbackMsg { - internal readonly Dictionary Notifications; - - internal UserNotificationsCallback(JobID jobID, CMsgClientUserNotifications msg) { - if (jobID == null) { - throw new ArgumentNullException(nameof(jobID)); - } - - if (msg == null) { - throw new ArgumentNullException(nameof(msg)); - } - - JobID = jobID; - - // We might get null body here, and that means there are no notifications related to trading - // TODO: Check if this workaround is still needed - Notifications = new Dictionary { { EUserNotification.Trading, 0 } }; - - if (msg.notifications == null) { - return; - } - - foreach (CMsgClientUserNotifications.Notification notification in msg.notifications) { - EUserNotification type = (EUserNotification) notification.user_notification_type; - - switch (type) { - case EUserNotification.AccountAlerts: - case EUserNotification.Chat: - case EUserNotification.Comments: - case EUserNotification.GameTurns: - case EUserNotification.Gifts: - case EUserNotification.HelpRequestReplies: - case EUserNotification.Invites: - case EUserNotification.Items: - case EUserNotification.ModeratorMessages: - case EUserNotification.Trading: - break; - default: - ASF.ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.WarningUnknownValuePleaseReport, nameof(type), type)); - - break; - } - - Notifications[type] = notification.count; - } - } - - internal UserNotificationsCallback(JobID jobID, CMsgClientItemAnnouncements msg) { - if (jobID == null) { - throw new ArgumentNullException(nameof(jobID)); - } - - if (msg == null) { - throw new ArgumentNullException(nameof(msg)); - } - - JobID = jobID; - Notifications = new Dictionary(1) { { EUserNotification.Items, msg.count_new_items } }; - } - - internal UserNotificationsCallback(JobID jobID, CMsgClientCommentNotifications msg) { - if (jobID == null) { - throw new ArgumentNullException(nameof(jobID)); - } - - if (msg == null) { - throw new ArgumentNullException(nameof(msg)); - } - - JobID = jobID; - Notifications = new Dictionary(1) { { EUserNotification.Comments, msg.count_new_comments + msg.count_new_comments_owner + msg.count_new_comments_subscriptions } }; - } - - [PublicAPI] - public enum EUserNotification : byte { - Unknown, - Trading, - GameTurns, - ModeratorMessages, - Comments, - Items, - Invites, - Unknown7, // Unknown type of notification, never seen in the wild - Gifts, - Chat, - HelpRequestReplies, - AccountAlerts - } - } - [PublicAPI] public enum EUserInterfaceMode : byte { Default = 0, diff --git a/ArchiSteamFarm/ArchiWebHandler.cs b/ArchiSteamFarm/ArchiWebHandler.cs index 7795f440a..1c0ea9e46 100644 --- a/ArchiSteamFarm/ArchiWebHandler.cs +++ b/ArchiSteamFarm/ArchiWebHandler.cs @@ -36,6 +36,7 @@ using AngleSharp.Dom; using ArchiSteamFarm.Helpers; using ArchiSteamFarm.Json; using ArchiSteamFarm.Localization; +using ArchiSteamFarm.Web; using JetBrains.Annotations; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -114,7 +115,7 @@ namespace ArchiSteamFarm { } [PublicAPI] - public async IAsyncEnumerable GetInventoryAsync(ulong steamID = 0, uint appID = Steam.Asset.SteamAppID, ulong contextID = Steam.Asset.SteamCommunityContextID) { + public async IAsyncEnumerable GetInventoryAsync(ulong steamID = 0, uint appID = Asset.SteamAppID, ulong contextID = Asset.SteamCommunityContextID) { if (appID == 0) { throw new ArgumentOutOfRangeException(nameof(appID)); } @@ -155,7 +156,7 @@ namespace ArchiSteamFarm { await ASF.InventorySemaphore.WaitAsync().ConfigureAwait(false); try { - WebBrowser.ObjectResponse? response = await UrlGetToJsonObjectWithSession(SteamCommunityURL, request + (startAssetID > 0 ? "&start_assetid=" + startAssetID : "")).ConfigureAwait(false); + ObjectResponse? response = await UrlGetToJsonObjectWithSession(SteamCommunityURL, request + (startAssetID > 0 ? "&start_assetid=" + startAssetID : "")).ConfigureAwait(false); if (response == null) { throw new HttpRequestException(string.Format(CultureInfo.CurrentCulture, Strings.ErrorObjectIsNull, nameof(response))); @@ -176,9 +177,9 @@ namespace ArchiSteamFarm { throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, Strings.ErrorObjectIsNull, nameof(response.Content.Assets) + " || " + nameof(response.Content.Descriptions))); } - Dictionary<(ulong ClassID, ulong InstanceID), Steam.InventoryResponse.Description> descriptions = new(); + Dictionary<(ulong ClassID, ulong InstanceID), InventoryResponse.Description> descriptions = new(); - foreach (Steam.InventoryResponse.Description description in response.Content.Descriptions) { + foreach (InventoryResponse.Description description in response.Content.Descriptions) { if (description.ClassID == 0) { throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, Strings.ErrorObjectIsNull, nameof(description.ClassID))); } @@ -192,8 +193,8 @@ namespace ArchiSteamFarm { descriptions[key] = description; } - foreach (Steam.Asset asset in response.Content.Assets) { - if (!descriptions.TryGetValue((asset.ClassID, asset.InstanceID), out Steam.InventoryResponse.Description? description) || assetIDs.Contains(asset.AssetID)) { + foreach (Asset asset in response.Content.Assets) { + if (!descriptions.TryGetValue((asset.ClassID, asset.InstanceID), out InventoryResponse.Description? description) || assetIDs.Contains(asset.AssetID)) { continue; } @@ -243,7 +244,7 @@ namespace ArchiSteamFarm { public async Task?> GetMyOwnedGames() { const string request = "/my/games?l=english&xml=1"; - WebBrowser.XmlDocumentResponse? response = await UrlGetToXmlDocumentWithSession(SteamCommunityURL, request, checkSessionPreemptively: false).ConfigureAwait(false); + XmlDocumentResponse? response = await UrlGetToXmlDocumentWithSession(SteamCommunityURL, request, checkSessionPreemptively: false).ConfigureAwait(false); using XmlNodeList? xmlNodeList = response?.Content.SelectNodes("gamesList/games/game"); @@ -383,7 +384,7 @@ namespace ArchiSteamFarm { } [PublicAPI] - public async Task<(bool Success, HashSet? MobileTradeOfferIDs)> SendTradeOffer(ulong steamID, IReadOnlyCollection? itemsToGive = null, IReadOnlyCollection? itemsToReceive = null, string? token = null, bool forcedSingleOffer = false, ushort itemsPerTrade = Trading.MaxItemsPerTrade) { + public async Task<(bool Success, HashSet? MobileTradeOfferIDs)> SendTradeOffer(ulong steamID, IReadOnlyCollection? itemsToGive = null, IReadOnlyCollection? itemsToReceive = null, string? token = null, bool forcedSingleOffer = false, ushort itemsPerTrade = Trading.MaxItemsPerTrade) { if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) { throw new ArgumentOutOfRangeException(nameof(steamID)); } @@ -396,17 +397,17 @@ namespace ArchiSteamFarm { throw new ArgumentOutOfRangeException(nameof(itemsPerTrade)); } - Steam.TradeOfferSendRequest singleTrade = new(); - HashSet trades = new() { singleTrade }; + TradeOfferSendRequest singleTrade = new(); + HashSet trades = new() { singleTrade }; if (itemsToGive != null) { - foreach (Steam.Asset itemToGive in itemsToGive) { + foreach (Asset itemToGive in itemsToGive) { if (!forcedSingleOffer && (singleTrade.ItemsToGive.Assets.Count + singleTrade.ItemsToReceive.Assets.Count >= itemsPerTrade)) { if (trades.Count >= Trading.MaxTradesPerAccount) { break; } - singleTrade = new Steam.TradeOfferSendRequest(); + singleTrade = new TradeOfferSendRequest(); trades.Add(singleTrade); } @@ -415,13 +416,13 @@ namespace ArchiSteamFarm { } if (itemsToReceive != null) { - foreach (Steam.Asset itemToReceive in itemsToReceive) { + foreach (Asset itemToReceive in itemsToReceive) { if (!forcedSingleOffer && (singleTrade.ItemsToGive.Assets.Count + singleTrade.ItemsToReceive.Assets.Count >= itemsPerTrade)) { if (trades.Count >= Trading.MaxTradesPerAccount) { break; } - singleTrade = new Steam.TradeOfferSendRequest(); + singleTrade = new TradeOfferSendRequest(); trades.Add(singleTrade); } @@ -442,13 +443,13 @@ namespace ArchiSteamFarm { HashSet mobileTradeOfferIDs = new(); - foreach (Steam.TradeOfferSendRequest trade in trades) { + foreach (TradeOfferSendRequest trade in trades) { data["json_tradeoffer"] = JsonConvert.SerializeObject(trade); - WebBrowser.ObjectResponse? response = null; + ObjectResponse? response = null; for (byte i = 0; (i < WebBrowser.MaxTries) && (response == null); i++) { - response = await UrlPostToJsonObjectWithSession(SteamCommunityURL, request, data: data, referer: referer, requestOptions: WebBrowser.ERequestOptions.ReturnServerErrors).ConfigureAwait(false); + response = await UrlPostToJsonObjectWithSession(SteamCommunityURL, request, data: data, referer: referer, requestOptions: WebBrowser.ERequestOptions.ReturnServerErrors).ConfigureAwait(false); if (response == null) { return (false, mobileTradeOfferIDs); @@ -488,7 +489,7 @@ namespace ArchiSteamFarm { } [PublicAPI] - public async Task UrlGetToHtmlDocumentWithSession(string host, string request, IReadOnlyCollection>? headers = null, string? referer = null, WebBrowser.ERequestOptions requestOptions = WebBrowser.ERequestOptions.None, bool checkSessionPreemptively = true, byte maxTries = WebBrowser.MaxTries) { + public async Task UrlGetToHtmlDocumentWithSession(string host, string request, IReadOnlyCollection>? headers = null, string? referer = null, WebBrowser.ERequestOptions requestOptions = WebBrowser.ERequestOptions.None, bool checkSessionPreemptively = true, byte maxTries = WebBrowser.MaxTries) { if (string.IsNullOrEmpty(host)) { throw new ArgumentNullException(nameof(host)); } @@ -539,7 +540,7 @@ namespace ArchiSteamFarm { } } - WebBrowser.HtmlDocumentResponse? response = await WebLimitRequest(host, async () => await WebBrowser.UrlGetToHtmlDocument(host + request, headers, referer, requestOptions).ConfigureAwait(false)).ConfigureAwait(false); + HtmlDocumentResponse? response = await WebLimitRequest(host, async () => await WebBrowser.UrlGetToHtmlDocument(host + request, headers, referer, requestOptions).ConfigureAwait(false)).ConfigureAwait(false); if (response == null) { return null; @@ -567,7 +568,7 @@ namespace ArchiSteamFarm { } [PublicAPI] - public async Task?> UrlGetToJsonObjectWithSession(string host, string request, IReadOnlyCollection>? headers = null, string? referer = null, WebBrowser.ERequestOptions requestOptions = WebBrowser.ERequestOptions.None, bool checkSessionPreemptively = true, byte maxTries = WebBrowser.MaxTries) { + public async Task?> UrlGetToJsonObjectWithSession(string host, string request, IReadOnlyCollection>? headers = null, string? referer = null, WebBrowser.ERequestOptions requestOptions = WebBrowser.ERequestOptions.None, bool checkSessionPreemptively = true, byte maxTries = WebBrowser.MaxTries) { if (string.IsNullOrEmpty(host)) { throw new ArgumentNullException(nameof(host)); } @@ -580,7 +581,7 @@ namespace ArchiSteamFarm { Bot.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.ErrorRequestFailedTooManyTimes, WebBrowser.MaxTries)); Bot.ArchiLogger.LogGenericDebug(string.Format(CultureInfo.CurrentCulture, Strings.ErrorFailingRequest, host + request)); - return default(WebBrowser.ObjectResponse?); + return default(ObjectResponse?); } if (checkSessionPreemptively) { @@ -614,14 +615,14 @@ namespace ArchiSteamFarm { Bot.ArchiLogger.LogGenericWarning(Strings.WarningFailed); Bot.ArchiLogger.LogGenericDebug(string.Format(CultureInfo.CurrentCulture, Strings.ErrorFailingRequest, host + request)); - return default(WebBrowser.ObjectResponse?); + return default(ObjectResponse?); } } - WebBrowser.ObjectResponse? response = await WebLimitRequest(host, async () => await WebBrowser.UrlGetToJsonObject(host + request, headers, referer, requestOptions).ConfigureAwait(false)).ConfigureAwait(false); + ObjectResponse? response = await WebLimitRequest(host, async () => await WebBrowser.UrlGetToJsonObject(host + request, headers, referer, requestOptions).ConfigureAwait(false)).ConfigureAwait(false); if (response == null) { - return default(WebBrowser.ObjectResponse?); + return default(ObjectResponse?); } if (IsSessionExpiredUri(response.FinalUri)) { @@ -646,7 +647,7 @@ namespace ArchiSteamFarm { } [PublicAPI] - public async Task UrlGetToXmlDocumentWithSession(string host, string request, IReadOnlyCollection>? headers = null, string? referer = null, WebBrowser.ERequestOptions requestOptions = WebBrowser.ERequestOptions.None, bool checkSessionPreemptively = true, byte maxTries = WebBrowser.MaxTries) { + public async Task UrlGetToXmlDocumentWithSession(string host, string request, IReadOnlyCollection>? headers = null, string? referer = null, WebBrowser.ERequestOptions requestOptions = WebBrowser.ERequestOptions.None, bool checkSessionPreemptively = true, byte maxTries = WebBrowser.MaxTries) { if (string.IsNullOrEmpty(host)) { throw new ArgumentNullException(nameof(host)); } @@ -697,7 +698,7 @@ namespace ArchiSteamFarm { } } - WebBrowser.XmlDocumentResponse? response = await WebLimitRequest(host, async () => await WebBrowser.UrlGetToXmlDocument(host + request, headers, referer, requestOptions).ConfigureAwait(false)).ConfigureAwait(false); + XmlDocumentResponse? response = await WebLimitRequest(host, async () => await WebBrowser.UrlGetToXmlDocument(host + request, headers, referer, requestOptions).ConfigureAwait(false)).ConfigureAwait(false); if (response == null) { return null; @@ -776,7 +777,7 @@ namespace ArchiSteamFarm { } } - WebBrowser.BasicResponse? response = await WebLimitRequest(host, async () => await WebBrowser.UrlHead(host + request, headers, referer, requestOptions).ConfigureAwait(false)).ConfigureAwait(false); + BasicResponse? response = await WebLimitRequest(host, async () => await WebBrowser.UrlHead(host + request, headers, referer, requestOptions).ConfigureAwait(false)).ConfigureAwait(false); if (response == null) { return false; @@ -804,7 +805,7 @@ namespace ArchiSteamFarm { } [PublicAPI] - public async Task UrlPostToHtmlDocumentWithSession(string host, string request, IReadOnlyCollection>? headers = null, IDictionary? data = null, string? referer = null, WebBrowser.ERequestOptions requestOptions = WebBrowser.ERequestOptions.None, ESession session = ESession.Lowercase, bool checkSessionPreemptively = true, byte maxTries = WebBrowser.MaxTries) { + public async Task UrlPostToHtmlDocumentWithSession(string host, string request, IReadOnlyCollection>? headers = null, IDictionary? data = null, string? referer = null, WebBrowser.ERequestOptions requestOptions = WebBrowser.ERequestOptions.None, ESession session = ESession.Lowercase, bool checkSessionPreemptively = true, byte maxTries = WebBrowser.MaxTries) { if (string.IsNullOrEmpty(host)) { throw new ArgumentNullException(nameof(host)); } @@ -882,7 +883,7 @@ namespace ArchiSteamFarm { } } - WebBrowser.HtmlDocumentResponse? response = await WebLimitRequest(host, async () => await WebBrowser.UrlPostToHtmlDocument(host + request, headers, data, referer, requestOptions).ConfigureAwait(false)).ConfigureAwait(false); + HtmlDocumentResponse? response = await WebLimitRequest(host, async () => await WebBrowser.UrlPostToHtmlDocument(host + request, headers, data, referer, requestOptions).ConfigureAwait(false)).ConfigureAwait(false); if (response == null) { return null; @@ -910,7 +911,7 @@ namespace ArchiSteamFarm { } [PublicAPI] - public async Task?> UrlPostToJsonObjectWithSession(string host, string request, IReadOnlyCollection>? headers = null, IDictionary? data = null, string? referer = null, WebBrowser.ERequestOptions requestOptions = WebBrowser.ERequestOptions.None, ESession session = ESession.Lowercase, bool checkSessionPreemptively = true, byte maxTries = WebBrowser.MaxTries) { + public async Task?> UrlPostToJsonObjectWithSession(string host, string request, IReadOnlyCollection>? headers = null, IDictionary? data = null, string? referer = null, WebBrowser.ERequestOptions requestOptions = WebBrowser.ERequestOptions.None, ESession session = ESession.Lowercase, bool checkSessionPreemptively = true, byte maxTries = WebBrowser.MaxTries) { if (string.IsNullOrEmpty(host)) { throw new ArgumentNullException(nameof(host)); } @@ -988,7 +989,7 @@ namespace ArchiSteamFarm { } } - WebBrowser.ObjectResponse? response = await WebLimitRequest(host, async () => await WebBrowser.UrlPostToJsonObject>(host + request, headers, data, referer, requestOptions).ConfigureAwait(false)).ConfigureAwait(false); + ObjectResponse? response = await WebLimitRequest(host, async () => await WebBrowser.UrlPostToJsonObject>(host + request, headers, data, referer, requestOptions).ConfigureAwait(false)).ConfigureAwait(false); if (response == null) { return null; @@ -1016,7 +1017,7 @@ namespace ArchiSteamFarm { } [PublicAPI] - public async Task?> UrlPostToJsonObjectWithSession(string host, string request, IReadOnlyCollection>? headers = null, ICollection>? data = null, string? referer = null, WebBrowser.ERequestOptions requestOptions = WebBrowser.ERequestOptions.None, ESession session = ESession.Lowercase, bool checkSessionPreemptively = true, byte maxTries = WebBrowser.MaxTries) { + public async Task?> UrlPostToJsonObjectWithSession(string host, string request, IReadOnlyCollection>? headers = null, ICollection>? data = null, string? referer = null, WebBrowser.ERequestOptions requestOptions = WebBrowser.ERequestOptions.None, ESession session = ESession.Lowercase, bool checkSessionPreemptively = true, byte maxTries = WebBrowser.MaxTries) { if (string.IsNullOrEmpty(host)) { throw new ArgumentNullException(nameof(host)); } @@ -1097,7 +1098,7 @@ namespace ArchiSteamFarm { } } - WebBrowser.ObjectResponse? response = await WebLimitRequest(host, async () => await WebBrowser.UrlPostToJsonObject>>(host + request, headers, data, referer, requestOptions).ConfigureAwait(false)).ConfigureAwait(false); + ObjectResponse? response = await WebLimitRequest(host, async () => await WebBrowser.UrlPostToJsonObject>>(host + request, headers, data, referer, requestOptions).ConfigureAwait(false)).ConfigureAwait(false); if (response == null) { return null; @@ -1203,7 +1204,7 @@ namespace ArchiSteamFarm { } } - WebBrowser.BasicResponse? response = await WebLimitRequest(host, async () => await WebBrowser.UrlPost(host + request, headers, data, referer, requestOptions).ConfigureAwait(false)).ConfigureAwait(false); + BasicResponse? response = await WebLimitRequest(host, async () => await WebBrowser.UrlPost(host + request, headers, data, referer, requestOptions).ConfigureAwait(false)).ConfigureAwait(false); if (response == null) { return false; @@ -1295,7 +1296,7 @@ namespace ArchiSteamFarm { { "giftcardid", giftCardID.ToString(CultureInfo.InvariantCulture) } }; - WebBrowser.ObjectResponse? response = await UrlPostToJsonObjectWithSession(SteamStoreURL, request, data: data).ConfigureAwait(false); + ObjectResponse? response = await UrlPostToJsonObjectWithSession(SteamStoreURL, request, data: data).ConfigureAwait(false); if (response == null) { return false; @@ -1324,10 +1325,10 @@ namespace ArchiSteamFarm { { "tradeofferid", tradeID.ToString(CultureInfo.InvariantCulture) } }; - WebBrowser.ObjectResponse? response = null; + ObjectResponse? response = null; for (byte i = 0; (i < WebBrowser.MaxTries) && (response == null); i++) { - response = await UrlPostToJsonObjectWithSession(SteamCommunityURL, request, data: data, referer: referer, requestOptions: WebBrowser.ERequestOptions.ReturnServerErrors).ConfigureAwait(false); + response = await UrlPostToJsonObjectWithSession(SteamCommunityURL, request, data: data, referer: referer, requestOptions: WebBrowser.ERequestOptions.ReturnServerErrors).ConfigureAwait(false); if (response == null) { return (false, false); @@ -1364,12 +1365,12 @@ namespace ArchiSteamFarm { { "subid", subID.ToString(CultureInfo.InvariantCulture) } }; - using WebBrowser.HtmlDocumentResponse? response = await UrlPostToHtmlDocumentWithSession(SteamStoreURL, request, data: data).ConfigureAwait(false); + using HtmlDocumentResponse? response = await UrlPostToHtmlDocumentWithSession(SteamStoreURL, request, data: data).ConfigureAwait(false); return response?.Content.SelectSingleNode("//div[@class='add_free_content_success_area']") != null; } - internal async Task ChangePrivacySettings(Steam.UserPrivacy userPrivacy) { + internal async Task ChangePrivacySettings(UserPrivacy userPrivacy) { if (userPrivacy == null) { throw new ArgumentNullException(nameof(userPrivacy)); } @@ -1390,7 +1391,7 @@ namespace ArchiSteamFarm { { "Privacy", JsonConvert.SerializeObject(userPrivacy.Settings) } }; - WebBrowser.ObjectResponse? response = await UrlPostToJsonObjectWithSession(SteamCommunityURL, request, data: data).ConfigureAwait(false); + ObjectResponse? response = await UrlPostToJsonObjectWithSession(SteamCommunityURL, request, data: data).ConfigureAwait(false); if (response == null) { return false; @@ -1472,12 +1473,12 @@ namespace ArchiSteamFarm { // Extra entry for sessionID Dictionary data = new(2, StringComparer.Ordinal) { { "queuetype", "0" } }; - WebBrowser.ObjectResponse? response = await UrlPostToJsonObjectWithSession(SteamStoreURL, request, data: data).ConfigureAwait(false); + ObjectResponse? response = await UrlPostToJsonObjectWithSession(SteamStoreURL, request, data: data).ConfigureAwait(false); return response?.Content.Queue; } - internal async Task?> GetActiveTradeOffers() { + internal async Task?> GetActiveTradeOffers() { (bool success, string? steamApiKey) = await CachedApiKey.GetValue().ConfigureAwait(false); if (!success || string.IsNullOrEmpty(steamApiKey)) { @@ -1519,7 +1520,7 @@ namespace ArchiSteamFarm { return null; } - Dictionary<(uint AppID, ulong ClassID, ulong InstanceID), Steam.InventoryResponse.Description> descriptions = new(); + Dictionary<(uint AppID, ulong ClassID, ulong InstanceID), InventoryResponse.Description> descriptions = new(); foreach (KeyValue description in response["descriptions"].Children) { uint appID = description["appid"].AsUnsignedInteger(); @@ -1546,7 +1547,7 @@ namespace ArchiSteamFarm { continue; } - Steam.InventoryResponse.Description parsedDescription = new() { + InventoryResponse.Description parsedDescription = new() { AppID = appID, ClassID = classID, InstanceID = instanceID, @@ -1557,7 +1558,7 @@ namespace ArchiSteamFarm { List tags = description["tags"].Children; if (tags.Count > 0) { - HashSet parsedTags = new(tags.Count); + HashSet parsedTags = new(tags.Count); foreach (KeyValue tag in tags) { string? identifier = tag["category"].AsString(); @@ -1577,7 +1578,7 @@ namespace ArchiSteamFarm { return null; } - parsedTags.Add(new Steam.Asset.Tag(identifier!, value!)); + parsedTags.Add(new Tag(identifier!, value!)); } parsedDescription.Tags = parsedTags.ToImmutableHashSet(); @@ -1586,7 +1587,7 @@ namespace ArchiSteamFarm { descriptions[key] = parsedDescription; } - HashSet result = new(); + HashSet result = new(); foreach (KeyValue trade in response["trade_offers_received"].Children) { ETradeOfferState state = trade["trade_offer_state"].AsEnum(); @@ -1617,7 +1618,7 @@ namespace ArchiSteamFarm { return null; } - Steam.TradeOffer tradeOffer = new(tradeOfferID, otherSteamID3, state); + TradeOffer tradeOffer = new(tradeOfferID, otherSteamID3, state); List itemsToGive = trade["items_to_give"].Children; @@ -1703,7 +1704,7 @@ namespace ArchiSteamFarm { string request = "/my/badges?l=english&p=" + page; - WebBrowser.HtmlDocumentResponse? response = await UrlGetToHtmlDocumentWithSession(SteamCommunityURL, request, checkSessionPreemptively: false).ConfigureAwait(false); + HtmlDocumentResponse? response = await UrlGetToHtmlDocumentWithSession(SteamCommunityURL, request, checkSessionPreemptively: false).ConfigureAwait(false); return response?.Content; } @@ -1769,7 +1770,7 @@ namespace ArchiSteamFarm { string request = "/mobileconf/conf?a=" + Bot.SteamID + "&k=" + WebUtility.UrlEncode(confirmationHash) + "&l=english&m=android&p=" + WebUtility.UrlEncode(deviceID) + "&t=" + time + "&tag=conf"; - WebBrowser.HtmlDocumentResponse? response = await UrlGetToHtmlDocumentWithSession(SteamCommunityURL, request).ConfigureAwait(false); + HtmlDocumentResponse? response = await UrlGetToHtmlDocumentWithSession(SteamCommunityURL, request).ConfigureAwait(false); return response?.Content; } @@ -1777,7 +1778,7 @@ namespace ArchiSteamFarm { internal async Task?> GetDigitalGiftCards() { const string request = "/gifts"; - using WebBrowser.HtmlDocumentResponse? response = await UrlGetToHtmlDocumentWithSession(SteamStoreURL, request).ConfigureAwait(false); + using HtmlDocumentResponse? response = await UrlGetToHtmlDocumentWithSession(SteamStoreURL, request).ConfigureAwait(false); if (response == null) { return null; @@ -1815,7 +1816,7 @@ namespace ArchiSteamFarm { internal async Task GetDiscoveryQueuePage() { const string request = "/explore?l=english"; - WebBrowser.HtmlDocumentResponse? response = await UrlGetToHtmlDocumentWithSession(SteamStoreURL, request).ConfigureAwait(false); + HtmlDocumentResponse? response = await UrlGetToHtmlDocumentWithSession(SteamStoreURL, request).ConfigureAwait(false); return response?.Content; } @@ -1823,7 +1824,7 @@ namespace ArchiSteamFarm { internal async Task?> GetFamilySharingSteamIDs() { const string request = "/account/managedevices?l=english"; - using WebBrowser.HtmlDocumentResponse? response = await UrlGetToHtmlDocumentWithSession(SteamStoreURL, request).ConfigureAwait(false); + using HtmlDocumentResponse? response = await UrlGetToHtmlDocumentWithSession(SteamStoreURL, request).ConfigureAwait(false); if (response == null) { return null; @@ -1860,7 +1861,7 @@ namespace ArchiSteamFarm { string request = "/my/gamecards/" + appID + "?l=english"; - WebBrowser.HtmlDocumentResponse? response = await UrlGetToHtmlDocumentWithSession(SteamCommunityURL, request, checkSessionPreemptively: false).ConfigureAwait(false); + HtmlDocumentResponse? response = await UrlGetToHtmlDocumentWithSession(SteamCommunityURL, request, checkSessionPreemptively: false).ConfigureAwait(false); return response?.Content; } @@ -1911,7 +1912,7 @@ namespace ArchiSteamFarm { string request = "/tradeoffer/" + tradeID + "?l=english"; - using WebBrowser.HtmlDocumentResponse? response = await UrlGetToHtmlDocumentWithSession(SteamCommunityURL, request).ConfigureAwait(false); + using HtmlDocumentResponse? response = await UrlGetToHtmlDocumentWithSession(SteamCommunityURL, request).ConfigureAwait(false); IElement? htmlNode = response?.Content.SelectSingleNode("//div[@class='pagecontent']/script"); @@ -2054,12 +2055,12 @@ namespace ArchiSteamFarm { string request = "/mobileconf/ajaxop?a=" + Bot.SteamID + "&cid=" + confirmationID + "&ck=" + confirmationKey + "&k=" + WebUtility.UrlEncode(confirmationHash) + "&l=english&m=android&op=" + (accept ? "allow" : "cancel") + "&p=" + WebUtility.UrlEncode(deviceID) + "&t=" + time + "&tag=conf"; - WebBrowser.ObjectResponse? response = await UrlGetToJsonObjectWithSession(SteamCommunityURL, request).ConfigureAwait(false); + ObjectResponse? response = await UrlGetToJsonObjectWithSession(SteamCommunityURL, request).ConfigureAwait(false); return response?.Content.Success; } - internal async Task HandleConfirmations(string deviceID, string confirmationHash, uint time, IReadOnlyCollection confirmations, bool accept) { + internal async Task HandleConfirmations(string deviceID, string confirmationHash, uint time, IReadOnlyCollection confirmations, bool accept) { if (string.IsNullOrEmpty(deviceID)) { throw new ArgumentNullException(nameof(deviceID)); } @@ -2103,12 +2104,12 @@ namespace ArchiSteamFarm { new KeyValuePair("tag", "conf") }; - foreach (MobileAuthenticator.Confirmation confirmation in confirmations) { + foreach (Confirmation confirmation in confirmations) { data.Add(new KeyValuePair("cid[]", confirmation.ID.ToString(CultureInfo.InvariantCulture))); data.Add(new KeyValuePair("ck[]", confirmation.Key.ToString(CultureInfo.InvariantCulture))); } - WebBrowser.ObjectResponse? response = await UrlPostToJsonObjectWithSession(SteamCommunityURL, request, data: data).ConfigureAwait(false); + ObjectResponse? response = await UrlPostToJsonObjectWithSession(SteamCommunityURL, request, data: data).ConfigureAwait(false); return response?.Content.Success; } @@ -2307,7 +2308,7 @@ namespace ArchiSteamFarm { // Extra entry for sessionID Dictionary data = new(2, StringComparer.Ordinal) { { "wallet_code", key } }; - WebBrowser.ObjectResponse? response = await UrlPostToJsonObjectWithSession(SteamStoreURL, requestValidateCode, data: data).ConfigureAwait(false); + ObjectResponse? response = await UrlPostToJsonObjectWithSession(SteamStoreURL, requestValidateCode, data: data).ConfigureAwait(false); if (response == null) { return null; @@ -2346,7 +2347,7 @@ namespace ArchiSteamFarm { { "communityitemid", itemID.ToString(CultureInfo.InvariantCulture) } }; - WebBrowser.ObjectResponse? response = await UrlPostToJsonObjectWithSession(SteamCommunityURL, request, data: data).ConfigureAwait(false); + ObjectResponse? response = await UrlPostToJsonObjectWithSession(SteamCommunityURL, request, data: data).ConfigureAwait(false); return response?.Content.Result == EResult.OK; } @@ -2354,7 +2355,7 @@ namespace ArchiSteamFarm { private async Task<(ESteamApiKeyState State, string? Key)> GetApiKeyState() { const string request = "/dev/apikey?l=english"; - using WebBrowser.HtmlDocumentResponse? response = await UrlGetToHtmlDocumentWithSession(SteamCommunityURL, request).ConfigureAwait(false); + using HtmlDocumentResponse? response = await UrlGetToHtmlDocumentWithSession(SteamCommunityURL, request).ConfigureAwait(false); if (response == null) { return (ESteamApiKeyState.Timeout, null); @@ -2465,7 +2466,7 @@ namespace ArchiSteamFarm { // Lastly, it should be a request that is preferably generic enough as a routine check, not something specialized and targetted, to make it very clear that we're just checking if session is up, and to further aid internal dependencies specified above by rendering as general Steam info as possible const string request = "/account"; - WebBrowser.BasicResponse? response = await WebLimitRequest(SteamStoreURL, async () => await WebBrowser.UrlHead(SteamStoreURL + request).ConfigureAwait(false)).ConfigureAwait(false); + BasicResponse? response = await WebLimitRequest(SteamStoreURL, async () => await WebBrowser.UrlHead(SteamStoreURL + request).ConfigureAwait(false)).ConfigureAwait(false); if (response == null) { return null; @@ -2497,7 +2498,7 @@ namespace ArchiSteamFarm { return uri.AbsolutePath.StartsWith("/login", StringComparison.OrdinalIgnoreCase) || uri.Host.Equals("lostauth", StringComparison.OrdinalIgnoreCase); } - private static bool ParseItems(IReadOnlyDictionary<(uint AppID, ulong ClassID, ulong InstanceID), Steam.InventoryResponse.Description> descriptions, IReadOnlyCollection input, ICollection output) { + private static bool ParseItems(IReadOnlyDictionary<(uint AppID, ulong ClassID, ulong InstanceID), InventoryResponse.Description> descriptions, IReadOnlyCollection input, ICollection output) { if (descriptions == null) { throw new ArgumentNullException(nameof(descriptions)); } @@ -2551,12 +2552,12 @@ namespace ArchiSteamFarm { bool marketable = true; bool tradable = true; - ImmutableHashSet? tags = null; + ImmutableHashSet? tags = null; uint realAppID = 0; - Steam.Asset.EType type = Steam.Asset.EType.Unknown; - Steam.Asset.ERarity rarity = Steam.Asset.ERarity.Unknown; + Asset.EType type = Asset.EType.Unknown; + Asset.ERarity rarity = Asset.ERarity.Unknown; - if (descriptions.TryGetValue(key, out Steam.InventoryResponse.Description? description)) { + if (descriptions.TryGetValue(key, out InventoryResponse.Description? description)) { marketable = description.Marketable; tradable = description.Tradable; tags = description.Tags; @@ -2565,7 +2566,7 @@ namespace ArchiSteamFarm { rarity = description.Rarity; } - Steam.Asset steamAsset = new(appID, contextID, classID, amount, instanceID, assetID, marketable, tradable, tags, realAppID, type, rarity); + Asset steamAsset = new(appID, contextID, classID, amount, instanceID, assetID, marketable, tradable, tags, realAppID, type, rarity); output.Add(steamAsset); } @@ -2728,7 +2729,7 @@ namespace ArchiSteamFarm { }; // This request doesn't go through UrlPostRetryWithSession as we have no access to session refresh capability (this is in fact session initialization) - WebBrowser.BasicResponse? response = await WebLimitRequest(serviceURL, async () => await WebBrowser.UrlPost(serviceURL + request, data: data, referer: serviceURL).ConfigureAwait(false)).ConfigureAwait(false); + BasicResponse? response = await WebLimitRequest(serviceURL, async () => await WebBrowser.UrlPost(serviceURL + request, data: data, referer: serviceURL).ConfigureAwait(false)).ConfigureAwait(false); if ((response == null) || IsSessionExpiredUri(response.FinalUri)) { // There is no session refresh capability at this stage diff --git a/ArchiSteamFarm/Bot.cs b/ArchiSteamFarm/Bot.cs index 308e40a90..4e9a4264c 100755 --- a/ArchiSteamFarm/Bot.cs +++ b/ArchiSteamFarm/Bot.cs @@ -35,16 +35,25 @@ using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using AngleSharp.Dom; +using ArchiSteamFarm.Callbacks; using ArchiSteamFarm.Collections; using ArchiSteamFarm.Json; using ArchiSteamFarm.Localization; using ArchiSteamFarm.NLog; using ArchiSteamFarm.Plugins; +using ArchiSteamFarm.Web; using JetBrains.Annotations; using Newtonsoft.Json; using SteamKit2; using SteamKit2.Internal; +#if NETFRAMEWORK +using ArchiSteamFarm.RuntimeCompatibility; + +using File = System.IO.File; +using Path = System.IO.Path; +#endif + namespace ArchiSteamFarm { public sealed class Bot : IAsyncDisposable { internal const ushort CallbackSleep = 500; // In milliseconds @@ -142,7 +151,7 @@ namespace ArchiSteamFarm { private readonly Timer HeartBeatTimer; private readonly SemaphoreSlim InitializationSemaphore = new(1, 1); private readonly SemaphoreSlim MessagingSemaphore = new(1, 1); - private readonly ConcurrentDictionary PastNotifications = new(); + private readonly ConcurrentDictionary PastNotifications = new(); private readonly SemaphoreSlim SendCompleteTypesSemaphore = new(1, 1); private readonly Statistics? Statistics; private readonly SteamClient SteamClient; @@ -295,7 +304,7 @@ namespace ArchiSteamFarm { CallbackManager.Subscribe(OnPlayingSessionState); CallbackManager.Subscribe(OnSharedLibraryLockStatus); - CallbackManager.Subscribe(OnUserNotifications); + CallbackManager.Subscribe(OnUserNotifications); CallbackManager.Subscribe(OnVanityURLChangedCallback); Actions = new Actions(this); @@ -513,7 +522,7 @@ namespace ArchiSteamFarm { } [PublicAPI] - public static HashSet GetItemsForFullSets(IReadOnlyCollection inventory, IReadOnlyDictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), (uint SetsToExtract, byte ItemsPerSet)> amountsToExtract, ushort maxItems = Trading.MaxItemsPerTrade) { + public static HashSet GetItemsForFullSets(IReadOnlyCollection inventory, IReadOnlyDictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), (uint SetsToExtract, byte ItemsPerSet)> amountsToExtract, ushort maxItems = Trading.MaxItemsPerTrade) { if ((inventory == null) || (inventory.Count == 0)) { throw new ArgumentNullException(nameof(inventory)); } @@ -526,11 +535,11 @@ namespace ArchiSteamFarm { throw new ArgumentOutOfRangeException(nameof(maxItems)); } - HashSet result = new(); - Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), Dictionary>> itemsPerClassIDPerSet = inventory.GroupBy(item => (item.RealAppID, item.Type, item.Rarity)).ToDictionary(grouping => grouping.Key, grouping => grouping.GroupBy(item => item.ClassID).ToDictionary(group => group.Key, group => group.ToHashSet())); + HashSet result = new(); + Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), Dictionary>> itemsPerClassIDPerSet = inventory.GroupBy(item => (item.RealAppID, item.Type, item.Rarity)).ToDictionary(grouping => grouping.Key, grouping => grouping.GroupBy(item => item.ClassID).ToDictionary(group => group.Key, group => group.ToHashSet())); - foreach (((uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity) set, (uint setsToExtract, byte itemsPerSet)) in amountsToExtract.OrderBy(kv => kv.Value.ItemsPerSet)) { - if (!itemsPerClassIDPerSet.TryGetValue(set, out Dictionary>? itemsPerClassID)) { + foreach (((uint RealAppID, Asset.EType Type, Asset.ERarity Rarity) set, (uint setsToExtract, byte itemsPerSet)) in amountsToExtract.OrderBy(kv => kv.Value.ItemsPerSet)) { + if (!itemsPerClassIDPerSet.TryGetValue(set, out Dictionary>? itemsPerClassID)) { continue; } @@ -551,12 +560,12 @@ namespace ArchiSteamFarm { break; } - foreach (HashSet itemsOfClass in itemsPerClassID.Values) { + foreach (HashSet itemsOfClass in itemsPerClassID.Values) { ushort classRemaining = realSetsToExtract; - foreach (Steam.Asset item in itemsOfClass.TakeWhile(_ => classRemaining > 0)) { + foreach (Asset item in itemsOfClass.TakeWhile(_ => classRemaining > 0)) { if (item.Amount > classRemaining) { - Steam.Asset itemToSend = item.CreateShallowCopy(); + Asset itemToSend = item.CreateShallowCopy(); itemToSend.Amount = classRemaining; result.Add(itemToSend); @@ -1331,7 +1340,7 @@ namespace ArchiSteamFarm { return ((ArchiHandler.EPrivacySetting) privacySettings.privacy_state == ArchiHandler.EPrivacySetting.Public) && ((ArchiHandler.EPrivacySetting) privacySettings.privacy_state_inventory == ArchiHandler.EPrivacySetting.Public); } - internal async Task IdleGame(CardsFarmer.Game game) { + internal async Task IdleGame(Game game) { if (game == null) { throw new ArgumentNullException(nameof(game)); } @@ -1345,7 +1354,7 @@ namespace ArchiSteamFarm { await ArchiHandler.PlayGames(game.PlayableAppID.ToEnumerable(), gameName).ConfigureAwait(false); } - internal async Task IdleGames(IReadOnlyCollection games) { + internal async Task IdleGames(IReadOnlyCollection games) { if ((games == null) || (games.Count == 0)) { throw new ArgumentNullException(nameof(games)); } @@ -3148,7 +3157,7 @@ namespace ArchiSteamFarm { await CheckOccupationStatus().ConfigureAwait(false); } - private void OnUserNotifications(ArchiHandler.UserNotificationsCallback callback) { + private void OnUserNotifications(UserNotificationsCallback callback) { if (callback == null) { throw new ArgumentNullException(nameof(callback)); } @@ -3161,9 +3170,9 @@ namespace ArchiSteamFarm { return; } - HashSet newPluginNotifications = new(); + HashSet newPluginNotifications = new(); - foreach ((ArchiHandler.UserNotificationsCallback.EUserNotification notification, uint count) in callback.Notifications) { + foreach ((UserNotificationsCallback.EUserNotification notification, uint count) in callback.Notifications) { bool newNotification; if (count > 0) { @@ -3181,15 +3190,15 @@ namespace ArchiSteamFarm { ArchiLogger.LogGenericTrace(notification + " = " + count); switch (notification) { - case ArchiHandler.UserNotificationsCallback.EUserNotification.Gifts when newNotification && BotConfig.AcceptGifts: + case UserNotificationsCallback.EUserNotification.Gifts when newNotification && BotConfig.AcceptGifts: Utilities.InBackground(Actions.AcceptDigitalGiftCards); break; - case ArchiHandler.UserNotificationsCallback.EUserNotification.Items when newNotification: + case UserNotificationsCallback.EUserNotification.Items when newNotification: OnInventoryChanged(); break; - case ArchiHandler.UserNotificationsCallback.EUserNotification.Trading when newNotification: + case UserNotificationsCallback.EUserNotification.Trading when newNotification: Utilities.InBackground(Trading.OnNewTrade); break; @@ -3243,7 +3252,7 @@ namespace ArchiSteamFarm { break; } - ArchiHandler.PurchaseResponseCallback? result = await Actions.RedeemKey(key!).ConfigureAwait(false); + PurchaseResponseCallback? result = await Actions.RedeemKey(key!).ConfigureAwait(false); if (result == null) { continue; @@ -3390,7 +3399,7 @@ namespace ArchiSteamFarm { return; } - HashSet inventory; + HashSet inventory; try { inventory = await ArchiWebHandler.GetInventoryAsync() @@ -3413,7 +3422,7 @@ namespace ArchiSteamFarm { return; } - Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), List> inventorySets = Trading.GetInventorySets(inventory); + Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), List> inventorySets = Trading.GetInventorySets(inventory); appIDs.IntersectWith(inventorySets.Where(kv => kv.Value.Count >= MinCardsPerBadge).Select(kv => kv.Key.RealAppID)); if (appIDs.Count == 0) { @@ -3426,13 +3435,13 @@ namespace ArchiSteamFarm { return; } - Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), (uint Sets, byte CardsPerSet)> itemsToTakePerInventorySet = inventorySets.Where(kv => appIDs.Contains(kv.Key.RealAppID)).ToDictionary(kv => kv.Key, kv => (kv.Value[0], cardCountPerAppID[kv.Key.RealAppID])); + Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), (uint Sets, byte CardsPerSet)> itemsToTakePerInventorySet = inventorySets.Where(kv => appIDs.Contains(kv.Key.RealAppID)).ToDictionary(kv => kv.Key, kv => (kv.Value[0], cardCountPerAppID[kv.Key.RealAppID])); if (itemsToTakePerInventorySet.Values.All(value => value.Sets == 0)) { return; } - HashSet result = GetItemsForFullSets(inventory, itemsToTakePerInventorySet); + HashSet result = GetItemsForFullSets(inventory, itemsToTakePerInventorySet); if (result.Count > 0) { await Actions.SendInventory(result).ConfigureAwait(false); diff --git a/ArchiSteamFarm/BotConfig.cs b/ArchiSteamFarm/BotConfig.cs index 12a53b48b..526536b5f 100644 --- a/ArchiSteamFarm/BotConfig.cs +++ b/ArchiSteamFarm/BotConfig.cs @@ -24,7 +24,6 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using System.Globalization; -using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -35,6 +34,14 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; using SteamKit2; +#if NETFRAMEWORK +using ArchiSteamFarm.RuntimeCompatibility; + +using File = System.IO.File; +#else +using System.IO; +#endif + namespace ArchiSteamFarm { [SuppressMessage("ReSharper", "ClassCannotBeInstantiated")] public sealed class BotConfig { @@ -115,10 +122,10 @@ namespace ArchiSteamFarm { private const byte SteamTradeTokenLength = 8; [PublicAPI] - public static readonly ImmutableHashSet AllowedCompleteTypesToSend = ImmutableHashSet.Create(Steam.Asset.EType.TradingCard, Steam.Asset.EType.FoilTradingCard); + public static readonly ImmutableHashSet AllowedCompleteTypesToSend = ImmutableHashSet.Create(Asset.EType.TradingCard, Asset.EType.FoilTradingCard); [PublicAPI] - public static readonly ImmutableHashSet DefaultCompleteTypesToSend = ImmutableHashSet.Empty; + public static readonly ImmutableHashSet DefaultCompleteTypesToSend = ImmutableHashSet.Empty; [PublicAPI] public static readonly ImmutableList DefaultFarmingOrders = ImmutableList.Empty; @@ -127,16 +134,16 @@ namespace ArchiSteamFarm { public static readonly ImmutableHashSet DefaultGamesPlayedWhileIdle = ImmutableHashSet.Empty; [PublicAPI] - public static readonly ImmutableHashSet DefaultLootableTypes = ImmutableHashSet.Create(Steam.Asset.EType.BoosterPack, Steam.Asset.EType.FoilTradingCard, Steam.Asset.EType.TradingCard); + public static readonly ImmutableHashSet DefaultLootableTypes = ImmutableHashSet.Create(Asset.EType.BoosterPack, Asset.EType.FoilTradingCard, Asset.EType.TradingCard); [PublicAPI] - public static readonly ImmutableHashSet DefaultMatchableTypes = ImmutableHashSet.Create(Steam.Asset.EType.TradingCard); + public static readonly ImmutableHashSet DefaultMatchableTypes = ImmutableHashSet.Create(Asset.EType.TradingCard); [PublicAPI] public static readonly ImmutableDictionary DefaultSteamUserPermissions = ImmutableDictionary.Empty; [PublicAPI] - public static readonly ImmutableHashSet DefaultTransferableTypes = ImmutableHashSet.Create(Steam.Asset.EType.BoosterPack, Steam.Asset.EType.FoilTradingCard, Steam.Asset.EType.TradingCard); + public static readonly ImmutableHashSet DefaultTransferableTypes = ImmutableHashSet.Create(Asset.EType.BoosterPack, Asset.EType.FoilTradingCard, Asset.EType.TradingCard); private static readonly SemaphoreSlim WriteSemaphore = new(1, 1); @@ -150,7 +157,7 @@ namespace ArchiSteamFarm { public EBotBehaviour BotBehaviour { get; private set; } = DefaultBotBehaviour; [JsonProperty(Required = Required.DisallowNull)] - public ImmutableHashSet CompleteTypesToSend { get; private set; } = DefaultCompleteTypesToSend; + public ImmutableHashSet CompleteTypesToSend { get; private set; } = DefaultCompleteTypesToSend; [JsonProperty] public string? CustomGamePlayedWhileFarming { get; private set; } = DefaultCustomGamePlayedWhileFarming; @@ -177,10 +184,10 @@ namespace ArchiSteamFarm { public bool IdleRefundableGames { get; private set; } = DefaultIdleRefundableGames; [JsonProperty(Required = Required.DisallowNull)] - public ImmutableHashSet LootableTypes { get; private set; } = DefaultLootableTypes; + public ImmutableHashSet LootableTypes { get; private set; } = DefaultLootableTypes; [JsonProperty(Required = Required.DisallowNull)] - public ImmutableHashSet MatchableTypes { get; private set; } = DefaultMatchableTypes; + public ImmutableHashSet MatchableTypes { get; private set; } = DefaultMatchableTypes; [JsonProperty(Required = Required.DisallowNull)] public EPersonaState OnlineStatus { get; private set; } = DefaultOnlineStatus; @@ -246,7 +253,7 @@ namespace ArchiSteamFarm { public ETradingPreferences TradingPreferences { get; private set; } = DefaultTradingPreferences; [JsonProperty(Required = Required.DisallowNull)] - public ImmutableHashSet TransferableTypes { get; private set; } = DefaultTransferableTypes; + public ImmutableHashSet TransferableTypes { get; private set; } = DefaultTransferableTypes; [JsonProperty(Required = Required.DisallowNull)] public bool UseLoginKeys { get; private set; } = DefaultUseLoginKeys; @@ -367,15 +374,15 @@ namespace ArchiSteamFarm { return (false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorConfigPropertyInvalid, nameof(GamesPlayedWhileIdle), GamesPlayedWhileIdle.Count + " > " + ArchiHandler.MaxGamesPlayedConcurrently)); } - foreach (Steam.Asset.EType lootableType in LootableTypes.Where(lootableType => !Enum.IsDefined(typeof(Steam.Asset.EType), lootableType))) { + foreach (Asset.EType lootableType in LootableTypes.Where(lootableType => !Enum.IsDefined(typeof(Asset.EType), lootableType))) { return (false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorConfigPropertyInvalid, nameof(LootableTypes), lootableType)); } - foreach (Steam.Asset.EType completableType in CompleteTypesToSend.Where(completableType => !Enum.IsDefined(typeof(Steam.Asset.EType), completableType) || !AllowedCompleteTypesToSend.Contains(completableType))) { + foreach (Asset.EType completableType in CompleteTypesToSend.Where(completableType => !Enum.IsDefined(typeof(Asset.EType), completableType) || !AllowedCompleteTypesToSend.Contains(completableType))) { return (false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorConfigPropertyInvalid, nameof(CompleteTypesToSend), completableType)); } - foreach (Steam.Asset.EType matchableType in MatchableTypes.Where(matchableType => !Enum.IsDefined(typeof(Steam.Asset.EType), matchableType))) { + foreach (Asset.EType matchableType in MatchableTypes.Where(matchableType => !Enum.IsDefined(typeof(Asset.EType), matchableType))) { return (false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorConfigPropertyInvalid, nameof(MatchableTypes), matchableType)); } diff --git a/ArchiSteamFarm/BotDatabase.cs b/ArchiSteamFarm/BotDatabase.cs index 6623f981d..7f4516057 100644 --- a/ArchiSteamFarm/BotDatabase.cs +++ b/ArchiSteamFarm/BotDatabase.cs @@ -23,13 +23,13 @@ using System; using System.Collections; using System.Collections.Specialized; using System.Globalization; -using System.IO; using System.Linq; using System.Threading.Tasks; using ArchiSteamFarm.Collections; using ArchiSteamFarm.Helpers; using ArchiSteamFarm.Localization; using Newtonsoft.Json; +using File = ArchiSteamFarm.RuntimeCompatibility.File; namespace ArchiSteamFarm { internal sealed class BotDatabase : SerializableFile { @@ -141,14 +141,14 @@ namespace ArchiSteamFarm { throw new ArgumentNullException(nameof(filePath)); } - if (!File.Exists(filePath)) { + if (!System.IO.File.Exists(filePath)) { return new BotDatabase(filePath); } BotDatabase? botDatabase; try { - string json = await RuntimeCompatibility.File.ReadAllTextAsync(filePath).ConfigureAwait(false); + string json = await File.ReadAllTextAsync(filePath).ConfigureAwait(false); if (string.IsNullOrEmpty(json)) { ASF.ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsEmpty, nameof(json))); diff --git a/ArchiSteamFarm/Callbacks/PurchaseResponseCallback.cs b/ArchiSteamFarm/Callbacks/PurchaseResponseCallback.cs new file mode 100644 index 000000000..99bc7bb49 --- /dev/null +++ b/ArchiSteamFarm/Callbacks/PurchaseResponseCallback.cs @@ -0,0 +1,120 @@ +// _ _ _ ____ _ _____ +// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___ +// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \ +// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | +// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| +// | +// Copyright 2015-2021 Ł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.ComponentModel; +using System.IO; +using System.Net; +using JetBrains.Annotations; +using SteamKit2; +using SteamKit2.Internal; + +namespace ArchiSteamFarm.Callbacks { + public sealed class PurchaseResponseCallback : CallbackMsg { + [PublicAPI] + public Dictionary? Items { get; } + + public EPurchaseResultDetail PurchaseResultDetail { get; internal set; } + + [PublicAPI] + public EResult Result { get; internal set; } + + internal PurchaseResponseCallback(EResult result, EPurchaseResultDetail purchaseResult) { + if (!Enum.IsDefined(typeof(EResult), result)) { + throw new InvalidEnumArgumentException(nameof(result), (int) result, typeof(EResult)); + } + + if (!Enum.IsDefined(typeof(EPurchaseResultDetail), purchaseResult)) { + throw new InvalidEnumArgumentException(nameof(purchaseResult), (int) purchaseResult, typeof(EPurchaseResultDetail)); + } + + Result = result; + PurchaseResultDetail = purchaseResult; + } + + internal PurchaseResponseCallback(JobID jobID, CMsgClientPurchaseResponse msg) { + if (jobID == null) { + throw new ArgumentNullException(nameof(jobID)); + } + + if (msg == null) { + throw new ArgumentNullException(nameof(msg)); + } + + JobID = jobID; + PurchaseResultDetail = (EPurchaseResultDetail) msg.purchase_result_details; + Result = (EResult) msg.eresult; + + if (msg.purchase_receipt_info == null) { + ASF.ArchiLogger.LogNullError(nameof(msg.purchase_receipt_info)); + + return; + } + + KeyValue receiptInfo = new(); + + using (MemoryStream ms = new(msg.purchase_receipt_info)) { + if (!receiptInfo.TryReadAsBinary(ms)) { + ASF.ArchiLogger.LogNullError(nameof(ms)); + + return; + } + } + + List lineItems = receiptInfo["lineitems"].Children; + + if (lineItems.Count == 0) { + return; + } + + Items = new Dictionary(lineItems.Count); + + foreach (KeyValue lineItem in lineItems) { + uint packageID = lineItem["PackageID"].AsUnsignedInteger(); + + if (packageID == 0) { + // Coupons have PackageID of -1 (don't ask me why) + // We'll use ItemAppID in this case + packageID = lineItem["ItemAppID"].AsUnsignedInteger(); + + if (packageID == 0) { + ASF.ArchiLogger.LogNullError(nameof(packageID)); + + return; + } + } + + string? gameName = lineItem["ItemDescription"].AsString(); + + if (string.IsNullOrEmpty(gameName)) { + ASF.ArchiLogger.LogNullError(nameof(gameName)); + + return; + } + + // Apparently steam expects client to decode sent HTML + gameName = WebUtility.HtmlDecode(gameName); + Items[packageID] = gameName; + } + } + } +} diff --git a/ArchiSteamFarm/Callbacks/UserNotificationsCallback.cs b/ArchiSteamFarm/Callbacks/UserNotificationsCallback.cs new file mode 100644 index 000000000..083359a8f --- /dev/null +++ b/ArchiSteamFarm/Callbacks/UserNotificationsCallback.cs @@ -0,0 +1,120 @@ +// _ _ _ ____ _ _____ +// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___ +// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \ +// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | +// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| +// | +// Copyright 2015-2021 Ł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.Globalization; +using ArchiSteamFarm.Localization; +using JetBrains.Annotations; +using SteamKit2; +using SteamKit2.Internal; + +namespace ArchiSteamFarm.Callbacks { + public sealed class UserNotificationsCallback : CallbackMsg { + internal readonly Dictionary Notifications; + + internal UserNotificationsCallback(JobID jobID, CMsgClientUserNotifications msg) { + if (jobID == null) { + throw new ArgumentNullException(nameof(jobID)); + } + + if (msg == null) { + throw new ArgumentNullException(nameof(msg)); + } + + JobID = jobID; + + // We might get null body here, and that means there are no notifications related to trading + // TODO: Check if this workaround is still needed + Notifications = new Dictionary { { EUserNotification.Trading, 0 } }; + + if (msg.notifications == null) { + return; + } + + foreach (CMsgClientUserNotifications.Notification notification in msg.notifications) { + EUserNotification type = (EUserNotification) notification.user_notification_type; + + switch (type) { + case EUserNotification.AccountAlerts: + case EUserNotification.Chat: + case EUserNotification.Comments: + case EUserNotification.GameTurns: + case EUserNotification.Gifts: + case EUserNotification.HelpRequestReplies: + case EUserNotification.Invites: + case EUserNotification.Items: + case EUserNotification.ModeratorMessages: + case EUserNotification.Trading: + break; + default: + ASF.ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.WarningUnknownValuePleaseReport, nameof(type), type)); + + break; + } + + Notifications[type] = notification.count; + } + } + + internal UserNotificationsCallback(JobID jobID, CMsgClientItemAnnouncements msg) { + if (jobID == null) { + throw new ArgumentNullException(nameof(jobID)); + } + + if (msg == null) { + throw new ArgumentNullException(nameof(msg)); + } + + JobID = jobID; + Notifications = new Dictionary(1) { { EUserNotification.Items, msg.count_new_items } }; + } + + internal UserNotificationsCallback(JobID jobID, CMsgClientCommentNotifications msg) { + if (jobID == null) { + throw new ArgumentNullException(nameof(jobID)); + } + + if (msg == null) { + throw new ArgumentNullException(nameof(msg)); + } + + JobID = jobID; + Notifications = new Dictionary(1) { { EUserNotification.Comments, msg.count_new_comments + msg.count_new_comments_owner + msg.count_new_comments_subscriptions } }; + } + + [PublicAPI] + public enum EUserNotification : byte { + Unknown, + Trading, + GameTurns, + ModeratorMessages, + Comments, + Items, + Invites, + Unknown7, // Unknown type of notification, never seen in the wild + Gifts, + Chat, + HelpRequestReplies, + AccountAlerts + } + } +} diff --git a/ArchiSteamFarm/CardsFarmer.cs b/ArchiSteamFarm/CardsFarmer.cs index dec2582a1..21f8d3570 100755 --- a/ArchiSteamFarm/CardsFarmer.cs +++ b/ArchiSteamFarm/CardsFarmer.cs @@ -37,6 +37,10 @@ using JetBrains.Annotations; using Newtonsoft.Json; using SteamKit2; +#if NETFRAMEWORK +using ArchiSteamFarm.RuntimeCompatibility; +#endif + namespace ArchiSteamFarm { public sealed class CardsFarmer : IAsyncDisposable { internal const byte DaysForRefund = 14; // In how many days since payment we're allowed to refund @@ -1237,37 +1241,5 @@ namespace ArchiSteamFarm { List gamesToFarm = orderedGamesToFarm.ToList(); GamesToFarm.ReplaceWith(gamesToFarm); } - - public sealed class Game : IEquatable { - [JsonProperty] - public uint AppID { get; } - - [JsonProperty] - public string GameName { get; } - - internal readonly byte BadgeLevel; - - [JsonProperty] - public ushort CardsRemaining { get; internal set; } - - [JsonProperty] - public float HoursPlayed { get; internal set; } - - internal uint PlayableAppID { get; set; } - - internal Game(uint appID, string gameName, float hoursPlayed, ushort cardsRemaining, byte badgeLevel) { - AppID = appID > 0 ? appID : throw new ArgumentOutOfRangeException(nameof(appID)); - GameName = !string.IsNullOrEmpty(gameName) ? gameName : throw new ArgumentNullException(nameof(gameName)); - HoursPlayed = hoursPlayed >= 0 ? hoursPlayed : throw new ArgumentOutOfRangeException(nameof(hoursPlayed)); - CardsRemaining = cardsRemaining > 0 ? cardsRemaining : throw new ArgumentOutOfRangeException(nameof(cardsRemaining)); - BadgeLevel = badgeLevel; - - PlayableAppID = appID; - } - - public bool Equals(Game? other) => (other != null) && (ReferenceEquals(other, this) || ((AppID == other.AppID) && (BadgeLevel == other.BadgeLevel) && (GameName == other.GameName))); - public override bool Equals(object? obj) => (obj != null) && ((obj == this) || (obj is Game game && Equals(game))); - public override int GetHashCode() => RuntimeCompatibility.HashCode.Combine(AppID, BadgeLevel, GameName); - } } } diff --git a/ArchiSteamFarm/Commands.cs b/ArchiSteamFarm/Commands.cs index fc10c3822..2110d825e 100644 --- a/ArchiSteamFarm/Commands.cs +++ b/ArchiSteamFarm/Commands.cs @@ -27,12 +27,15 @@ using System.Net.Http; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; +using ArchiSteamFarm.Callbacks; using ArchiSteamFarm.Json; using ArchiSteamFarm.Localization; using ArchiSteamFarm.Plugins; using JetBrains.Annotations; using SteamKit2; +using ArchiSteamFarm.RuntimeCompatibility; + namespace ArchiSteamFarm { public sealed class Commands { private const ushort SteamTypingStatusDelay = 10 * 1000; // Steam client broadcasts typing status each 10 seconds @@ -2438,7 +2441,7 @@ namespace ArchiSteamFarm { ArchiHandler.EPrivacySetting friendsList = ArchiHandler.EPrivacySetting.Private; ArchiHandler.EPrivacySetting inventory = ArchiHandler.EPrivacySetting.Private; ArchiHandler.EPrivacySetting inventoryGifts = ArchiHandler.EPrivacySetting.Private; - Steam.UserPrivacy.ECommentPermission comments = Steam.UserPrivacy.ECommentPermission.Private; + UserPrivacy.ECommentPermission comments = UserPrivacy.ECommentPermission.Private; // Converting digits to enum for (byte index = 0; index < privacySettingsArgs.Length; index++) { @@ -2507,15 +2510,15 @@ namespace ArchiSteamFarm { // Comments use different numbers than everything else, but we want to have this command consistent for end-user, so we'll map them switch (privacySetting) { case ArchiHandler.EPrivacySetting.FriendsOnly: - comments = Steam.UserPrivacy.ECommentPermission.FriendsOnly; + comments = UserPrivacy.ECommentPermission.FriendsOnly; break; case ArchiHandler.EPrivacySetting.Private: - comments = Steam.UserPrivacy.ECommentPermission.Private; + comments = UserPrivacy.ECommentPermission.Private; break; case ArchiHandler.EPrivacySetting.Public: - comments = Steam.UserPrivacy.ECommentPermission.Public; + comments = UserPrivacy.ECommentPermission.Public; break; default: @@ -2532,7 +2535,7 @@ namespace ArchiSteamFarm { } } - Steam.UserPrivacy userPrivacy = new(new Steam.UserPrivacy.PrivacySettings(profile, ownedGames, playtime, friendsList, inventory, inventoryGifts), comments); + UserPrivacy userPrivacy = new(new UserPrivacy.PrivacySettings(profile, ownedGames, playtime, friendsList, inventory, inventoryGifts), comments); return FormatBotResponse(await Bot.ArchiWebHandler.ChangePrivacySettings(userPrivacy).ConfigureAwait(false) ? Strings.Success : Strings.WarningFailed); } @@ -2634,7 +2637,7 @@ namespace ArchiSteamFarm { } else { bool skipRequest = triedBots.Contains(currentBot) || rateLimitedBots.Contains(currentBot); - ArchiHandler.PurchaseResponseCallback? result = skipRequest ? new ArchiHandler.PurchaseResponseCallback(EResult.Fail, EPurchaseResultDetail.CancelledByUser) : await currentBot.Actions.RedeemKey(key!).ConfigureAwait(false); + PurchaseResponseCallback? result = skipRequest ? new PurchaseResponseCallback(EResult.Fail, EPurchaseResultDetail.CancelledByUser) : await currentBot.Actions.RedeemKey(key!).ConfigureAwait(false); if (result == null) { response.AppendLine(FormatBotResponse(string.Format(CultureInfo.CurrentCulture, Strings.BotRedeem, key, EPurchaseResultDetail.Timeout), currentBot.BotName)); @@ -2711,7 +2714,7 @@ namespace ArchiSteamFarm { bool alreadyHandled = false; foreach (Bot innerBot in Bot.Bots.Where(bot => (bot.Value != currentBot) && (!redeemFlags.HasFlag(ERedeemFlags.SkipInitial) || (bot.Value != Bot)) && !triedBots.Contains(bot.Value) && !rateLimitedBots.Contains(bot.Value) && bot.Value.IsConnectedAndLoggedOn && bot.Value.Commands.Bot.HasAccess(steamID, BotConfig.EAccess.Operator) && ((items.Count == 0) || items.Keys.Any(packageID => !bot.Value.OwnedPackageIDs.ContainsKey(packageID)))).OrderBy(bot => bot.Key, Bot.BotsComparer).Select(bot => bot.Value)) { - ArchiHandler.PurchaseResponseCallback? otherResult = await innerBot.Actions.RedeemKey(key!).ConfigureAwait(false); + PurchaseResponseCallback? otherResult = await innerBot.Actions.RedeemKey(key!).ConfigureAwait(false); if (otherResult == null) { response.AppendLine(FormatBotResponse(string.Format(CultureInfo.CurrentCulture, Strings.BotRedeem, key, EResult.Timeout + "/" + EPurchaseResultDetail.Timeout), innerBot.BotName)); @@ -2959,7 +2962,7 @@ namespace ArchiSteamFarm { } ushort memoryInMegabytes = (ushort) (GC.GetTotalMemory(false) / 1024 / 1024); - TimeSpan uptime = DateTime.UtcNow.Subtract(RuntimeCompatibility.ProcessStartTime.ToUniversalTime()); + TimeSpan uptime = DateTime.UtcNow.Subtract(StaticHelpers.ProcessStartTime.ToUniversalTime()); return FormatBotResponse(string.Format(CultureInfo.CurrentCulture, Strings.BotStats, memoryInMegabytes, uptime.ToHumanReadable())); } @@ -3001,7 +3004,7 @@ namespace ArchiSteamFarm { return (FormatBotResponse(string.Format(CultureInfo.CurrentCulture, Strings.BotStatusIdlingList, string.Join(", ", Bot.CardsFarmer.CurrentGamesFarmingReadOnly.Select(game => game.AppID + " (" + game.GameName + ")")), Bot.CardsFarmer.GamesToFarmReadOnly.Count, Bot.CardsFarmer.GamesToFarmReadOnly.Sum(game => game.CardsRemaining), Bot.CardsFarmer.TimeRemaining.ToHumanReadable())), Bot); } - CardsFarmer.Game soloGame = Bot.CardsFarmer.CurrentGamesFarmingReadOnly.First(); + Game soloGame = Bot.CardsFarmer.CurrentGamesFarmingReadOnly.First(); return (FormatBotResponse(string.Format(CultureInfo.CurrentCulture, Strings.BotStatusIdling, soloGame.AppID, soloGame.GameName, soloGame.CardsRemaining, Bot.CardsFarmer.GamesToFarmReadOnly.Count, Bot.CardsFarmer.GamesToFarmReadOnly.Sum(game => game.CardsRemaining), Bot.CardsFarmer.TimeRemaining.ToHumanReadable())), Bot); } @@ -3296,7 +3299,7 @@ namespace ArchiSteamFarm { // 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 (Steam.Asset item in Bot.ArchiWebHandler.GetInventoryAsync().Where(item => item.Type == Steam.Asset.EType.BoosterPack).ConfigureAwait(false)) { + await foreach (Asset item in Bot.ArchiWebHandler.GetInventoryAsync().Where(item => item.Type == Asset.EType.BoosterPack).ConfigureAwait(false)) { if (!await Bot.ArchiWebHandler.UnpackBooster(item.RealAppID, item.AssetID).ConfigureAwait(false)) { completeSuccess = false; } diff --git a/ArchiSteamFarm/Confirmation.cs b/ArchiSteamFarm/Confirmation.cs new file mode 100644 index 000000000..6125965d3 --- /dev/null +++ b/ArchiSteamFarm/Confirmation.cs @@ -0,0 +1,61 @@ +// _ _ _ ____ _ _____ +// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___ +// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \ +// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | +// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| +// | +// Copyright 2015-2021 Ł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.ComponentModel; +using JetBrains.Annotations; +using Newtonsoft.Json; + +namespace ArchiSteamFarm { + public sealed class Confirmation { + [JsonProperty(Required = Required.Always)] + public ulong Creator { get; } + + [JsonProperty(Required = Required.Always)] + public ulong ID { get; } + + [JsonProperty(Required = Required.Always)] + public ulong Key { get; } + + [JsonProperty(Required = Required.Always)] + public EType Type { get; } + + internal Confirmation(ulong id, ulong key, ulong creator, EType type) { + ID = id > 0 ? id : throw new ArgumentOutOfRangeException(nameof(id)); + Key = key > 0 ? key : throw new ArgumentOutOfRangeException(nameof(key)); + Creator = creator > 0 ? creator : throw new ArgumentOutOfRangeException(nameof(creator)); + Type = Enum.IsDefined(typeof(EType), type) ? type : throw new InvalidEnumArgumentException(nameof(type), (int) type, typeof(EType)); + } + + // REF: Internal documentation + [PublicAPI] + public enum EType : byte { + Unknown, + Generic, + Trade, + Market, + + // We're missing information about definition of number 4 type + PhoneNumberChange = 5, + AccountRecovery = 6 + } + } +} diff --git a/ArchiSteamFarm/Game.cs b/ArchiSteamFarm/Game.cs new file mode 100644 index 000000000..9022998c9 --- /dev/null +++ b/ArchiSteamFarm/Game.cs @@ -0,0 +1,58 @@ +// _ _ _ ____ _ _____ +// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___ +// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \ +// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | +// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| +// | +// Copyright 2015-2021 Ł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 Newtonsoft.Json; +using HashCode = ArchiSteamFarm.RuntimeCompatibility.HashCode; + +namespace ArchiSteamFarm { + public sealed class Game : IEquatable { + [JsonProperty] + public uint AppID { get; } + + [JsonProperty] + public string GameName { get; } + + internal readonly byte BadgeLevel; + + [JsonProperty] + public ushort CardsRemaining { get; internal set; } + + [JsonProperty] + public float HoursPlayed { get; internal set; } + + internal uint PlayableAppID { get; set; } + + internal Game(uint appID, string gameName, float hoursPlayed, ushort cardsRemaining, byte badgeLevel) { + AppID = appID > 0 ? appID : throw new ArgumentOutOfRangeException(nameof(appID)); + GameName = !string.IsNullOrEmpty(gameName) ? gameName : throw new ArgumentNullException(nameof(gameName)); + HoursPlayed = hoursPlayed >= 0 ? hoursPlayed : throw new ArgumentOutOfRangeException(nameof(hoursPlayed)); + CardsRemaining = cardsRemaining > 0 ? cardsRemaining : throw new ArgumentOutOfRangeException(nameof(cardsRemaining)); + BadgeLevel = badgeLevel; + + PlayableAppID = appID; + } + + public bool Equals(Game? other) => (other != null) && (ReferenceEquals(other, this) || ((AppID == other.AppID) && (BadgeLevel == other.BadgeLevel) && (GameName == other.GameName))); + public override bool Equals(object? obj) => (obj != null) && ((obj == this) || (obj is Game game && Equals(game))); + public override int GetHashCode() => HashCode.Combine(AppID, BadgeLevel, GameName); + } +} diff --git a/ArchiSteamFarm/GitHub.cs b/ArchiSteamFarm/GitHub.cs index 119739a13..22f31634b 100644 --- a/ArchiSteamFarm/GitHub.cs +++ b/ArchiSteamFarm/GitHub.cs @@ -29,6 +29,7 @@ using System.Linq; using System.Net; using System.Threading.Tasks; using AngleSharp.Dom; +using ArchiSteamFarm.Web; using Markdig; using Markdig.Renderers; using Markdig.Syntax; @@ -68,7 +69,7 @@ namespace ArchiSteamFarm { string url = SharedInfo.ProjectURL + "/wiki/" + page + "/_history"; - using WebBrowser.HtmlDocumentResponse? response = await ASF.WebBrowser.UrlGetToHtmlDocument(url, requestOptions: WebBrowser.ERequestOptions.ReturnClientErrors).ConfigureAwait(false); + using HtmlDocumentResponse? response = await ASF.WebBrowser.UrlGetToHtmlDocument(url, requestOptions: WebBrowser.ERequestOptions.ReturnClientErrors).ConfigureAwait(false); if (response == null) { return null; @@ -141,7 +142,7 @@ namespace ArchiSteamFarm { string url = SharedInfo.ProjectURL + "/wiki/" + page + (!string.IsNullOrEmpty(revision) ? "/" + revision : ""); - using WebBrowser.HtmlDocumentResponse? response = await ASF.WebBrowser.UrlGetToHtmlDocument(url).ConfigureAwait(false); + using HtmlDocumentResponse? response = await ASF.WebBrowser.UrlGetToHtmlDocument(url).ConfigureAwait(false); if (response == null) { return null; @@ -178,7 +179,7 @@ namespace ArchiSteamFarm { throw new InvalidOperationException(nameof(ASF.WebBrowser)); } - WebBrowser.ObjectResponse? response = await ASF.WebBrowser.UrlGetToJsonObject(releaseURL).ConfigureAwait(false); + ObjectResponse? response = await ASF.WebBrowser.UrlGetToJsonObject(releaseURL).ConfigureAwait(false); return response?.Content; } @@ -192,7 +193,7 @@ namespace ArchiSteamFarm { throw new InvalidOperationException(nameof(ASF.WebBrowser)); } - WebBrowser.ObjectResponse>? response = await ASF.WebBrowser.UrlGetToJsonObject>(releaseURL).ConfigureAwait(false); + ObjectResponse>? response = await ASF.WebBrowser.UrlGetToJsonObject>(releaseURL).ConfigureAwait(false); return response?.Content; } diff --git a/ArchiSteamFarm/GlobalDatabase.cs b/ArchiSteamFarm/GlobalDatabase.cs index 5b7bf8654..61132b9b2 100644 --- a/ArchiSteamFarm/GlobalDatabase.cs +++ b/ArchiSteamFarm/GlobalDatabase.cs @@ -24,7 +24,6 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; using System.Globalization; -using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -33,6 +32,11 @@ using ArchiSteamFarm.Localization; using ArchiSteamFarm.SteamKit2; using JetBrains.Annotations; using Newtonsoft.Json; +using File = ArchiSteamFarm.RuntimeCompatibility.File; + +#if NETFRAMEWORK +using ArchiSteamFarm.RuntimeCompatibility; +#endif namespace ArchiSteamFarm { public sealed class GlobalDatabase : SerializableFile { @@ -118,7 +122,7 @@ namespace ArchiSteamFarm { throw new ArgumentNullException(nameof(filePath)); } - if (!File.Exists(filePath)) { + if (!System.IO.File.Exists(filePath)) { GlobalDatabase result = new(filePath); Utilities.InBackground(result.Save); @@ -129,7 +133,7 @@ namespace ArchiSteamFarm { GlobalDatabase? globalDatabase; try { - string json = await RuntimeCompatibility.File.ReadAllTextAsync(filePath).ConfigureAwait(false); + string json = await File.ReadAllTextAsync(filePath).ConfigureAwait(false); if (string.IsNullOrEmpty(json)) { ASF.ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsEmpty, nameof(json))); diff --git a/ArchiSteamFarm/IPC/ArchiKestrel.cs b/ArchiSteamFarm/IPC/ArchiKestrel.cs index d24c859af..eb9b08bf0 100644 --- a/ArchiSteamFarm/IPC/ArchiKestrel.cs +++ b/ArchiSteamFarm/IPC/ArchiKestrel.cs @@ -32,7 +32,11 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; using NLog.Web; -#if !NETFRAMEWORK +#if NETFRAMEWORK +using ArchiSteamFarm.RuntimeCompatibility; +using File = System.IO.File; +using Path = System.IO.Path; +#else using Microsoft.Extensions.Hosting; #endif diff --git a/ArchiSteamFarm/IPC/Controllers/Api/ASFController.cs b/ArchiSteamFarm/IPC/Controllers/Api/ASFController.cs index 390c1c938..757bff2dd 100644 --- a/ArchiSteamFarm/IPC/Controllers/Api/ASFController.cs +++ b/ArchiSteamFarm/IPC/Controllers/Api/ASFController.cs @@ -28,6 +28,7 @@ using System.Threading.Tasks; using ArchiSteamFarm.IPC.Requests; using ArchiSteamFarm.IPC.Responses; using ArchiSteamFarm.Localization; +using ArchiSteamFarm.RuntimeCompatibility; using Microsoft.AspNetCore.Mvc; using Newtonsoft.Json.Linq; @@ -67,7 +68,7 @@ namespace ArchiSteamFarm.IPC.Controllers.Api { uint memoryUsage = (uint) GC.GetTotalMemory(false) / 1024; - ASFResponse result = new(SharedInfo.BuildInfo.Variant, SharedInfo.BuildInfo.CanUpdate, ASF.GlobalConfig, memoryUsage, RuntimeCompatibility.ProcessStartTime, SharedInfo.Version); + ASFResponse result = new(SharedInfo.BuildInfo.Variant, SharedInfo.BuildInfo.CanUpdate, ASF.GlobalConfig, memoryUsage, StaticHelpers.ProcessStartTime, SharedInfo.Version); return Ok(new GenericResponse(result)); } diff --git a/ArchiSteamFarm/IPC/Controllers/Api/BotController.cs b/ArchiSteamFarm/IPC/Controllers/Api/BotController.cs index 82324d2f5..22455ca10 100644 --- a/ArchiSteamFarm/IPC/Controllers/Api/BotController.cs +++ b/ArchiSteamFarm/IPC/Controllers/Api/BotController.cs @@ -26,12 +26,17 @@ using System.Globalization; using System.Linq; using System.Net; using System.Threading.Tasks; +using ArchiSteamFarm.Callbacks; using ArchiSteamFarm.IPC.Requests; using ArchiSteamFarm.IPC.Responses; using ArchiSteamFarm.Localization; using Microsoft.AspNetCore.Mvc; using Newtonsoft.Json.Linq; +#if NETFRAMEWORK +using ArchiSteamFarm.RuntimeCompatibility; +#endif + namespace ArchiSteamFarm.IPC.Controllers.Api { [Route("Api/Bot")] public sealed class BotController : ArchiController { @@ -315,7 +320,7 @@ namespace ArchiSteamFarm.IPC.Controllers.Api { /// [Consumes("application/json")] [HttpPost("{botNames:required}/Redeem")] - [ProducesResponseType(typeof(GenericResponse>>), (int) HttpStatusCode.OK)] + [ProducesResponseType(typeof(GenericResponse>>), (int) HttpStatusCode.OK)] [ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.BadRequest)] public async Task> RedeemPost(string botNames, [FromBody] BotRedeemRequest request) { if (string.IsNullOrEmpty(botNames)) { @@ -336,14 +341,14 @@ namespace ArchiSteamFarm.IPC.Controllers.Api { return BadRequest(new GenericResponse(false, string.Format(CultureInfo.CurrentCulture, Strings.BotNotFound, botNames))); } - IList results = await Utilities.InParallel(bots.Select(bot => request.KeysToRedeem.Select(key => bot.Actions.RedeemKey(key))).SelectMany(task => task)).ConfigureAwait(false); + IList results = await Utilities.InParallel(bots.Select(bot => request.KeysToRedeem.Select(key => bot.Actions.RedeemKey(key))).SelectMany(task => task)).ConfigureAwait(false); - Dictionary> result = new(bots.Count, Bot.BotsComparer); + Dictionary> result = new(bots.Count, Bot.BotsComparer); int count = 0; foreach (Bot bot in bots) { - Dictionary responses = new(request.KeysToRedeem.Count, StringComparer.Ordinal); + Dictionary responses = new(request.KeysToRedeem.Count, StringComparer.Ordinal); result[bot.BotName] = responses; foreach (string key in request.KeysToRedeem) { @@ -351,7 +356,7 @@ namespace ArchiSteamFarm.IPC.Controllers.Api { } } - return Ok(new GenericResponse>>(result.Values.SelectMany(responses => responses.Values).All(value => value != null), result)); + return Ok(new GenericResponse>>(result.Values.SelectMany(responses => responses.Values).All(value => value != null), result)); } /// @@ -457,7 +462,7 @@ namespace ArchiSteamFarm.IPC.Controllers.Api { /// Handles 2FA confirmations of given bots, requires ASF 2FA module to be active on them. /// [HttpPost("{botNames:required}/TwoFactorAuthentication/Confirmations")] - [ProducesResponseType(typeof(GenericResponse>>>), (int) HttpStatusCode.OK)] + [ProducesResponseType(typeof(GenericResponse>>>), (int) HttpStatusCode.OK)] [ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.BadRequest)] public async Task> TwoFactorAuthenticationConfirmationsPost(string botNames, [FromBody] TwoFactorAuthenticationConfirmationsRequest request) { if (string.IsNullOrEmpty(botNames)) { @@ -468,26 +473,26 @@ namespace ArchiSteamFarm.IPC.Controllers.Api { throw new ArgumentNullException(nameof(request)); } - if (request.AcceptedType.HasValue && ((request.AcceptedType.Value == MobileAuthenticator.Confirmation.EType.Unknown) || !Enum.IsDefined(typeof(MobileAuthenticator.Confirmation.EType), request.AcceptedType.Value))) { + if (request.AcceptedType.HasValue && ((request.AcceptedType.Value == Confirmation.EType.Unknown) || !Enum.IsDefined(typeof(Confirmation.EType), request.AcceptedType.Value))) { return BadRequest(new GenericResponse(false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsInvalid, nameof(request.AcceptedType)))); } HashSet? bots = Bot.GetBots(botNames); if ((bots == null) || (bots.Count == 0)) { - return BadRequest(new GenericResponse>>>(false, string.Format(CultureInfo.CurrentCulture, Strings.BotNotFound, botNames))); + return BadRequest(new GenericResponse>>>(false, string.Format(CultureInfo.CurrentCulture, Strings.BotNotFound, botNames))); } - IList<(bool Success, IReadOnlyCollection? HandledConfirmations, string Message)> results = await Utilities.InParallel(bots.Select(bot => bot.Actions.HandleTwoFactorAuthenticationConfirmations(request.Accept, request.AcceptedType, request.AcceptedCreatorIDs.Count > 0 ? request.AcceptedCreatorIDs : null, request.WaitIfNeeded))).ConfigureAwait(false); + IList<(bool Success, IReadOnlyCollection? HandledConfirmations, string Message)> results = await Utilities.InParallel(bots.Select(bot => bot.Actions.HandleTwoFactorAuthenticationConfirmations(request.Accept, request.AcceptedType, request.AcceptedCreatorIDs.Count > 0 ? request.AcceptedCreatorIDs : null, request.WaitIfNeeded))).ConfigureAwait(false); - Dictionary>> result = new(bots.Count, Bot.BotsComparer); + Dictionary>> result = new(bots.Count, Bot.BotsComparer); foreach (Bot bot in bots) { - (bool success, IReadOnlyCollection? handledConfirmations, string message) = results[result.Count]; - result[bot.BotName] = new GenericResponse>(success, message, handledConfirmations); + (bool success, IReadOnlyCollection? handledConfirmations, string message) = results[result.Count]; + result[bot.BotName] = new GenericResponse>(success, message, handledConfirmations); } - return Ok(new GenericResponse>>>(result)); + return Ok(new GenericResponse>>>(result)); } /// diff --git a/ArchiSteamFarm/IPC/Controllers/Api/GitHubController.cs b/ArchiSteamFarm/IPC/Controllers/Api/GitHubController.cs index 3e7ceb1ed..a1f88f6f3 100644 --- a/ArchiSteamFarm/IPC/Controllers/Api/GitHubController.cs +++ b/ArchiSteamFarm/IPC/Controllers/Api/GitHubController.cs @@ -27,6 +27,7 @@ using System.Net; using System.Threading.Tasks; using ArchiSteamFarm.IPC.Responses; using ArchiSteamFarm.Localization; +using ArchiSteamFarm.Web; using Microsoft.AspNetCore.Mvc; namespace ArchiSteamFarm.IPC.Controllers.Api { diff --git a/ArchiSteamFarm/IPC/Controllers/Api/NLogController.cs b/ArchiSteamFarm/IPC/Controllers/Api/NLogController.cs index 7d2b75bba..0033f1116 100644 --- a/ArchiSteamFarm/IPC/Controllers/Api/NLogController.cs +++ b/ArchiSteamFarm/IPC/Controllers/Api/NLogController.cs @@ -36,6 +36,10 @@ using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Mvc; using Newtonsoft.Json; +#if NETFRAMEWORK +using ArchiSteamFarm.RuntimeCompatibility; +#endif + namespace ArchiSteamFarm.IPC.Controllers.Api { [Route("Api/NLog")] public sealed class NLogController : ArchiController { diff --git a/ArchiSteamFarm/IPC/Controllers/Api/TypeController.cs b/ArchiSteamFarm/IPC/Controllers/Api/TypeController.cs index 22a591f50..ec32e8d4f 100644 --- a/ArchiSteamFarm/IPC/Controllers/Api/TypeController.cs +++ b/ArchiSteamFarm/IPC/Controllers/Api/TypeController.cs @@ -106,7 +106,7 @@ namespace ArchiSteamFarm.IPC.Controllers.Api { } } - TypeResponse.TypeProperties properties = new(baseType, customAttributes.Count > 0 ? customAttributes : null, underlyingType); + TypeProperties properties = new(baseType, customAttributes.Count > 0 ? customAttributes : null, underlyingType); TypeResponse response = new(body, properties); diff --git a/ArchiSteamFarm/IPC/Requests/TwoFactorAuthenticationConfirmationsRequest.cs b/ArchiSteamFarm/IPC/Requests/TwoFactorAuthenticationConfirmationsRequest.cs index 872f42458..30852ce9a 100644 --- a/ArchiSteamFarm/IPC/Requests/TwoFactorAuthenticationConfirmationsRequest.cs +++ b/ArchiSteamFarm/IPC/Requests/TwoFactorAuthenticationConfirmationsRequest.cs @@ -46,7 +46,7 @@ namespace ArchiSteamFarm.IPC.Requests { /// Specifies the type of confirmations to handle. If not provided, all confirmation types are considered for an action. /// [JsonProperty] - public MobileAuthenticator.Confirmation.EType? AcceptedType { get; private set; } + public Confirmation.EType? AcceptedType { get; private set; } /// /// A helper property which works the same as but with values written as strings - for javascript compatibility purposes. Use either this one, or , not both. diff --git a/ArchiSteamFarm/IPC/Responses/TypeProperties.cs b/ArchiSteamFarm/IPC/Responses/TypeProperties.cs new file mode 100644 index 000000000..31cf009eb --- /dev/null +++ b/ArchiSteamFarm/IPC/Responses/TypeProperties.cs @@ -0,0 +1,61 @@ +// _ _ _ ____ _ _____ +// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___ +// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \ +// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | +// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| +// | +// Copyright 2015-2021 Ł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 Newtonsoft.Json; + +namespace ArchiSteamFarm.IPC.Responses { + public sealed class TypeProperties { + /// + /// Base type of given type, if available. + /// + /// + /// This can be used for determining how the body of the response should be interpreted. + /// + [JsonProperty] + public string? BaseType { get; private set; } + + /// + /// Custom attributes of given type, if available. + /// + /// + /// This can be used for determining main enum type if is . + /// + [JsonProperty] + public HashSet? CustomAttributes { get; private set; } + + /// + /// Underlying type of given type, if available. + /// + /// + /// This can be used for determining underlying enum type if is . + /// + [JsonProperty] + public string? UnderlyingType { get; private set; } + + internal TypeProperties(string? baseType = null, HashSet? customAttributes = null, string? underlyingType = null) { + BaseType = baseType; + CustomAttributes = customAttributes; + UnderlyingType = underlyingType; + } + } +} diff --git a/ArchiSteamFarm/IPC/Responses/TypeResponse.cs b/ArchiSteamFarm/IPC/Responses/TypeResponse.cs index 91c1ba742..6cdf12396 100644 --- a/ArchiSteamFarm/IPC/Responses/TypeResponse.cs +++ b/ArchiSteamFarm/IPC/Responses/TypeResponse.cs @@ -50,40 +50,5 @@ namespace ArchiSteamFarm.IPC.Responses { Body = body ?? throw new ArgumentNullException(nameof(body)); Properties = properties ?? throw new ArgumentNullException(nameof(properties)); } - - public sealed class TypeProperties { - /// - /// Base type of given type, if available. - /// - /// - /// This can be used for determining how should be interpreted. - /// - [JsonProperty] - public string? BaseType { get; private set; } - - /// - /// Custom attributes of given type, if available. - /// - /// - /// This can be used for determining main enum type if is . - /// - [JsonProperty] - public HashSet? CustomAttributes { get; private set; } - - /// - /// Underlying type of given type, if available. - /// - /// - /// This can be used for determining underlying enum type if is . - /// - [JsonProperty] - public string? UnderlyingType { get; private set; } - - internal TypeProperties(string? baseType = null, HashSet? customAttributes = null, string? underlyingType = null) { - BaseType = baseType; - CustomAttributes = customAttributes; - UnderlyingType = underlyingType; - } - } } } diff --git a/ArchiSteamFarm/IPC/Startup.cs b/ArchiSteamFarm/IPC/Startup.cs index 8d84a8f0e..37228b295 100644 --- a/ArchiSteamFarm/IPC/Startup.cs +++ b/ArchiSteamFarm/IPC/Startup.cs @@ -22,7 +22,6 @@ using System; using System.Collections.Generic; using System.Globalization; -using System.IO; using System.Net; using System.Reflection; using ArchiSteamFarm.IPC.Integration; @@ -41,6 +40,7 @@ using Newtonsoft.Json; using Newtonsoft.Json.Serialization; #if NETFRAMEWORK +using ArchiSteamFarm.RuntimeCompatibility; using Newtonsoft.Json.Converters; #endif @@ -229,9 +229,9 @@ namespace ArchiSteamFarm.IPC { } ); - string xmlDocumentationFile = Path.Combine(AppContext.BaseDirectory, SharedInfo.AssemblyDocumentation); + string xmlDocumentationFile = System.IO.Path.Combine(AppContext.BaseDirectory, SharedInfo.AssemblyDocumentation); - if (File.Exists(xmlDocumentationFile)) { + if (System.IO.File.Exists(xmlDocumentationFile)) { options.IncludeXmlComments(xmlDocumentationFile); } } diff --git a/ArchiSteamFarm/Json/Asset.cs b/ArchiSteamFarm/Json/Asset.cs new file mode 100644 index 000000000..05d35bd6d --- /dev/null +++ b/ArchiSteamFarm/Json/Asset.cs @@ -0,0 +1,260 @@ +// _ _ _ ____ _ _____ +// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___ +// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \ +// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | +// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| +// | +// Copyright 2015-2021 Ł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 JetBrains.Annotations; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace ArchiSteamFarm.Json { + // REF: https://developer.valvesoftware.com/wiki/Steam_Web_API/IEconService#CEcon_Asset + public sealed class Asset { + [PublicAPI] + public const uint SteamAppID = 753; + + [PublicAPI] + public const ulong SteamCommunityContextID = 6; + + [JsonIgnore] + [PublicAPI] + public IReadOnlyDictionary? AdditionalPropertiesReadOnly => AdditionalProperties; + + [JsonIgnore] + [PublicAPI] + public uint Amount { get; internal set; } + + [JsonProperty(PropertyName = "appid", Required = Required.DisallowNull)] + public uint AppID { get; private set; } + + [JsonIgnore] + [PublicAPI] + public ulong AssetID { get; private set; } + + [JsonIgnore] + [PublicAPI] + public ulong ClassID { get; private set; } + + [JsonIgnore] + [PublicAPI] + public ulong ContextID { get; private set; } + + [JsonIgnore] + [PublicAPI] + public ulong InstanceID { get; private set; } + + [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? Tags { get; internal set; } + + [JsonIgnore] + [PublicAPI] + public bool Tradable { get; internal set; } + + [JsonIgnore] + [PublicAPI] + public EType Type { get; internal set; } + + [JsonExtensionData] + internal Dictionary? AdditionalProperties { private get; set; } + + [JsonProperty(PropertyName = "amount", Required = Required.Always)] + private string AmountText { + get => Amount.ToString(CultureInfo.InvariantCulture); + + set { + if (string.IsNullOrEmpty(value)) { + ASF.ArchiLogger.LogNullError(nameof(value)); + + return; + } + + if (!uint.TryParse(value, out uint amount) || (amount == 0)) { + ASF.ArchiLogger.LogNullError(nameof(amount)); + + return; + } + + Amount = amount; + } + } + + [JsonProperty(PropertyName = "assetid", Required = Required.DisallowNull)] + private string AssetIDText { + get => AssetID.ToString(CultureInfo.InvariantCulture); + + set { + if (string.IsNullOrEmpty(value)) { + ASF.ArchiLogger.LogNullError(nameof(value)); + + return; + } + + if (!ulong.TryParse(value, out ulong assetID) || (assetID == 0)) { + ASF.ArchiLogger.LogNullError(nameof(assetID)); + + return; + } + + AssetID = assetID; + } + } + + [JsonProperty(PropertyName = "classid", Required = Required.DisallowNull)] + private string ClassIDText { + set { + if (string.IsNullOrEmpty(value)) { + ASF.ArchiLogger.LogNullError(nameof(value)); + + return; + } + + if (!ulong.TryParse(value, out ulong classID) || (classID == 0)) { + return; + } + + ClassID = classID; + } + } + + [JsonProperty(PropertyName = "contextid", Required = Required.DisallowNull)] + private string ContextIDText { + get => ContextID.ToString(CultureInfo.InvariantCulture); + + set { + if (string.IsNullOrEmpty(value)) { + ASF.ArchiLogger.LogNullError(nameof(value)); + + return; + } + + if (!ulong.TryParse(value, out ulong contextID) || (contextID == 0)) { + ASF.ArchiLogger.LogNullError(nameof(contextID)); + + return; + } + + ContextID = contextID; + } + } + + [JsonProperty(PropertyName = "id", Required = Required.DisallowNull)] + private string IDText { + set => AssetIDText = value; + } + + [JsonProperty(PropertyName = "instanceid", Required = Required.DisallowNull)] + private string InstanceIDText { + set { + if (string.IsNullOrEmpty(value)) { + return; + } + + if (!ulong.TryParse(value, out ulong instanceID)) { + ASF.ArchiLogger.LogNullError(nameof(instanceID)); + + return; + } + + InstanceID = instanceID; + } + } + + // 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? tags = null, uint realAppID = 0, EType type = EType.Unknown, ERarity rarity = ERarity.Unknown) { + if (appID == 0) { + throw new ArgumentOutOfRangeException(nameof(appID)); + } + + if (contextID == 0) { + throw new ArgumentOutOfRangeException(nameof(contextID)); + } + + if (classID == 0) { + throw new ArgumentOutOfRangeException(nameof(classID)); + } + + if (amount == 0) { + throw new ArgumentOutOfRangeException(nameof(amount)); + } + + AppID = appID; + ContextID = contextID; + ClassID = classID; + Amount = amount; + InstanceID = instanceID; + AssetID = assetID; + Marketable = marketable; + Tradable = tradable; + RealAppID = realAppID; + Type = type; + Rarity = rarity; + + if (tags?.Count > 0) { + Tags = tags; + } + } + + [JsonConstructor] + private Asset() { } + + internal Asset CreateShallowCopy() => (Asset) MemberwiseClone(); + + public enum ERarity : byte { + Unknown, + Common, + Uncommon, + Rare + } + + public enum EType : byte { + Unknown, + BoosterPack, + Emoticon, + FoilTradingCard, + ProfileBackground, + TradingCard, + SteamGems, + SaleItem, + Consumable, + ProfileModifier, + Sticker, + ChatEffect, + MiniProfileBackground, + AvatarProfileFrame, + AnimatedAvatar + } + } +} diff --git a/ArchiSteamFarm/Json/BooleanResponse.cs b/ArchiSteamFarm/Json/BooleanResponse.cs new file mode 100644 index 000000000..2f89c5f5d --- /dev/null +++ b/ArchiSteamFarm/Json/BooleanResponse.cs @@ -0,0 +1,36 @@ +// _ _ _ ____ _ _____ +// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___ +// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \ +// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | +// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| +// | +// Copyright 2015-2021 Łukasz "JustArchi" Domeradzki +// Contact: JustArchi@JustArchi.net +// | +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// | +// http://www.apache.org/licenses/LICENSE-2.0 +// | +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System.Diagnostics.CodeAnalysis; +using JetBrains.Annotations; +using Newtonsoft.Json; + +namespace ArchiSteamFarm.Json { + [PublicAPI] + [SuppressMessage("ReSharper", "ClassCannotBeInstantiated")] + public class BooleanResponse { + [JsonProperty(PropertyName = "success", Required = Required.Always)] + public bool Success { get; private set; } + + [JsonConstructor] + protected BooleanResponse() { } + } +} diff --git a/ArchiSteamFarm/Json/EResultResponse.cs b/ArchiSteamFarm/Json/EResultResponse.cs new file mode 100644 index 000000000..20a0b11ae --- /dev/null +++ b/ArchiSteamFarm/Json/EResultResponse.cs @@ -0,0 +1,37 @@ +// _ _ _ ____ _ _____ +// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___ +// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \ +// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | +// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| +// | +// Copyright 2015-2021 Łukasz "JustArchi" Domeradzki +// Contact: JustArchi@JustArchi.net +// | +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// | +// http://www.apache.org/licenses/LICENSE-2.0 +// | +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System.Diagnostics.CodeAnalysis; +using JetBrains.Annotations; +using Newtonsoft.Json; +using SteamKit2; + +namespace ArchiSteamFarm.Json { + [PublicAPI] + [SuppressMessage("ReSharper", "ClassCannotBeInstantiated")] + public class EResultResponse { + [JsonProperty(PropertyName = "success", Required = Required.Always)] + public EResult Result { get; private set; } + + [JsonConstructor] + protected EResultResponse() { } + } +} diff --git a/ArchiSteamFarm/Json/InventoryResponse.cs b/ArchiSteamFarm/Json/InventoryResponse.cs new file mode 100644 index 000000000..898a9e4f6 --- /dev/null +++ b/ArchiSteamFarm/Json/InventoryResponse.cs @@ -0,0 +1,262 @@ +// _ _ _ ____ _ _____ +// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___ +// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \ +// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | +// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| +// | +// Copyright 2015-2021 Ł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.Diagnostics.CodeAnalysis; +using System.Globalization; +using ArchiSteamFarm.Localization; +using JetBrains.Annotations; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace ArchiSteamFarm.Json { + [SuppressMessage("ReSharper", "ClassCannotBeInstantiated")] + internal sealed class InventoryResponse : EResultResponse { + [JsonProperty(PropertyName = "assets", Required = Required.DisallowNull)] + internal readonly ImmutableHashSet Assets = ImmutableHashSet.Empty; + + [JsonProperty(PropertyName = "descriptions", Required = Required.DisallowNull)] + internal readonly ImmutableHashSet Descriptions = ImmutableHashSet.Empty; + + [JsonProperty(PropertyName = "error", Required = Required.DisallowNull)] + internal readonly string Error = ""; + + [JsonProperty(PropertyName = "total_inventory_count", Required = Required.DisallowNull)] + internal readonly uint TotalInventoryCount; + + internal ulong LastAssetID { get; private set; } + internal bool MoreItems { get; private set; } + + [JsonProperty(PropertyName = "last_assetid", Required = Required.DisallowNull)] + private string LastAssetIDText { + set { + if (string.IsNullOrEmpty(value)) { + ASF.ArchiLogger.LogNullError(nameof(value)); + + return; + } + + if (!ulong.TryParse(value, out ulong lastAssetID) || (lastAssetID == 0)) { + ASF.ArchiLogger.LogNullError(nameof(lastAssetID)); + + return; + } + + LastAssetID = lastAssetID; + } + } + + [JsonProperty(PropertyName = "more_items", Required = Required.DisallowNull)] + private byte MoreItemsNumber { + set => 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(nameof(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; + default: + ASF.ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.WarningUnknownValuePleaseReport, nameof(tag.Value), tag.Value)); + + return Asset.EType.Unknown; + } + } + } + + return type; + } + } + + [JsonExtensionData] + internal Dictionary? AdditionalProperties { + get; + [UsedImplicitly] + set; + } + + [JsonProperty(PropertyName = "appid", Required = Required.Always)] + internal uint AppID { get; set; } + + internal ulong ClassID { get; set; } + internal ulong InstanceID { get; set; } + internal bool Marketable { get; set; } + + [JsonProperty(PropertyName = "tags", Required = Required.DisallowNull)] + internal ImmutableHashSet Tags { get; set; } = ImmutableHashSet.Empty; + + internal bool Tradable { get; set; } + + [JsonProperty(PropertyName = "classid", Required = Required.Always)] + private string ClassIDText { + set { + if (string.IsNullOrEmpty(value)) { + ASF.ArchiLogger.LogNullError(nameof(value)); + + return; + } + + if (!ulong.TryParse(value, out ulong classID) || (classID == 0)) { + ASF.ArchiLogger.LogNullError(nameof(classID)); + + return; + } + + ClassID = classID; + } + } + + [JsonProperty(PropertyName = "instanceid", Required = Required.DisallowNull)] + private string InstanceIDText { + set { + if (string.IsNullOrEmpty(value)) { + return; + } + + if (!ulong.TryParse(value, out ulong instanceID)) { + ASF.ArchiLogger.LogNullError(nameof(instanceID)); + + return; + } + + InstanceID = instanceID; + } + } + + [JsonProperty(PropertyName = "marketable", Required = Required.Always)] + private byte MarketableNumber { + set => Marketable = value > 0; + } + + [JsonProperty(PropertyName = "tradable", Required = Required.Always)] + private byte TradableNumber { + set => Tradable = value > 0; + } + + [JsonConstructor] + internal Description() { } + } + } +} diff --git a/ArchiSteamFarm/Json/NewDiscoveryQueueResponse.cs b/ArchiSteamFarm/Json/NewDiscoveryQueueResponse.cs new file mode 100644 index 000000000..c3684e968 --- /dev/null +++ b/ArchiSteamFarm/Json/NewDiscoveryQueueResponse.cs @@ -0,0 +1,35 @@ +// _ _ _ ____ _ _____ +// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___ +// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \ +// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | +// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| +// | +// Copyright 2015-2021 Ł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.Diagnostics.CodeAnalysis; +using Newtonsoft.Json; + +namespace ArchiSteamFarm.Json { + [SuppressMessage("ReSharper", "ClassCannotBeInstantiated")] + internal sealed class NewDiscoveryQueueResponse { + [JsonProperty(PropertyName = "queue", Required = Required.Always)] + internal readonly ImmutableHashSet Queue = ImmutableHashSet.Empty; + + [JsonConstructor] + private NewDiscoveryQueueResponse() { } + } +} diff --git a/ArchiSteamFarm/Json/RedeemWalletResponse.cs b/ArchiSteamFarm/Json/RedeemWalletResponse.cs new file mode 100644 index 000000000..9f354b4db --- /dev/null +++ b/ArchiSteamFarm/Json/RedeemWalletResponse.cs @@ -0,0 +1,35 @@ +// _ _ _ ____ _ _____ +// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___ +// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \ +// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | +// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| +// | +// Copyright 2015-2021 Łukasz "JustArchi" Domeradzki +// Contact: JustArchi@JustArchi.net +// | +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// | +// http://www.apache.org/licenses/LICENSE-2.0 +// | +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System.Diagnostics.CodeAnalysis; +using Newtonsoft.Json; +using SteamKit2; + +namespace ArchiSteamFarm.Json { + [SuppressMessage("ReSharper", "ClassCannotBeInstantiated")] + internal sealed class RedeemWalletResponse : EResultResponse { + [JsonProperty(PropertyName = "detail", Required = Required.DisallowNull)] + internal readonly EPurchaseResultDetail PurchaseResultDetail; + + [JsonConstructor] + private RedeemWalletResponse() { } + } +} diff --git a/ArchiSteamFarm/Json/Steam.cs b/ArchiSteamFarm/Json/Steam.cs deleted file mode 100644 index de3fcdf13..000000000 --- a/ArchiSteamFarm/Json/Steam.cs +++ /dev/null @@ -1,741 +0,0 @@ -// _ _ _ ____ _ _____ -// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___ -// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \ -// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | -// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| -// | -// Copyright 2015-2021 Ł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.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Globalization; -using System.Linq; -using ArchiSteamFarm.Localization; -using JetBrains.Annotations; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using SteamKit2; - -namespace ArchiSteamFarm.Json { - public static class Steam { - // REF: https://developer.valvesoftware.com/wiki/Steam_Web_API/IEconService#CEcon_Asset - public sealed class Asset { - [PublicAPI] - public const uint SteamAppID = 753; - - [PublicAPI] - public const ulong SteamCommunityContextID = 6; - - [JsonIgnore] - [PublicAPI] - public IReadOnlyDictionary? AdditionalPropertiesReadOnly => AdditionalProperties; - - [JsonIgnore] - [PublicAPI] - public uint Amount { get; internal set; } - - [JsonProperty(PropertyName = "appid", Required = Required.DisallowNull)] - public uint AppID { get; private set; } - - [JsonIgnore] - [PublicAPI] - public ulong AssetID { get; private set; } - - [JsonIgnore] - [PublicAPI] - public ulong ClassID { get; private set; } - - [JsonIgnore] - [PublicAPI] - public ulong ContextID { get; private set; } - - [JsonIgnore] - [PublicAPI] - public ulong InstanceID { get; private set; } - - [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? Tags { get; internal set; } - - [JsonIgnore] - [PublicAPI] - public bool Tradable { get; internal set; } - - [JsonIgnore] - [PublicAPI] - public EType Type { get; internal set; } - - [JsonExtensionData] - internal Dictionary? AdditionalProperties { private get; set; } - - [JsonProperty(PropertyName = "amount", Required = Required.Always)] - private string AmountText { - get => Amount.ToString(CultureInfo.InvariantCulture); - - set { - if (string.IsNullOrEmpty(value)) { - ASF.ArchiLogger.LogNullError(nameof(value)); - - return; - } - - if (!uint.TryParse(value, out uint amount) || (amount == 0)) { - ASF.ArchiLogger.LogNullError(nameof(amount)); - - return; - } - - Amount = amount; - } - } - - [JsonProperty(PropertyName = "assetid", Required = Required.DisallowNull)] - private string AssetIDText { - get => AssetID.ToString(CultureInfo.InvariantCulture); - - set { - if (string.IsNullOrEmpty(value)) { - ASF.ArchiLogger.LogNullError(nameof(value)); - - return; - } - - if (!ulong.TryParse(value, out ulong assetID) || (assetID == 0)) { - ASF.ArchiLogger.LogNullError(nameof(assetID)); - - return; - } - - AssetID = assetID; - } - } - - [JsonProperty(PropertyName = "classid", Required = Required.DisallowNull)] - private string ClassIDText { - set { - if (string.IsNullOrEmpty(value)) { - ASF.ArchiLogger.LogNullError(nameof(value)); - - return; - } - - if (!ulong.TryParse(value, out ulong classID) || (classID == 0)) { - return; - } - - ClassID = classID; - } - } - - [JsonProperty(PropertyName = "contextid", Required = Required.DisallowNull)] - private string ContextIDText { - get => ContextID.ToString(CultureInfo.InvariantCulture); - - set { - if (string.IsNullOrEmpty(value)) { - ASF.ArchiLogger.LogNullError(nameof(value)); - - return; - } - - if (!ulong.TryParse(value, out ulong contextID) || (contextID == 0)) { - ASF.ArchiLogger.LogNullError(nameof(contextID)); - - return; - } - - ContextID = contextID; - } - } - - [JsonProperty(PropertyName = "id", Required = Required.DisallowNull)] - private string IDText { - set => AssetIDText = value; - } - - [JsonProperty(PropertyName = "instanceid", Required = Required.DisallowNull)] - private string InstanceIDText { - set { - if (string.IsNullOrEmpty(value)) { - return; - } - - if (!ulong.TryParse(value, out ulong instanceID)) { - ASF.ArchiLogger.LogNullError(nameof(instanceID)); - - return; - } - - InstanceID = instanceID; - } - } - - // 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? tags = null, uint realAppID = 0, EType type = EType.Unknown, ERarity rarity = ERarity.Unknown) { - if (appID == 0) { - throw new ArgumentOutOfRangeException(nameof(appID)); - } - - if (contextID == 0) { - throw new ArgumentOutOfRangeException(nameof(contextID)); - } - - if (classID == 0) { - throw new ArgumentOutOfRangeException(nameof(classID)); - } - - if (amount == 0) { - throw new ArgumentOutOfRangeException(nameof(amount)); - } - - AppID = appID; - ContextID = contextID; - ClassID = classID; - Amount = amount; - InstanceID = instanceID; - AssetID = assetID; - Marketable = marketable; - Tradable = tradable; - RealAppID = realAppID; - Type = type; - Rarity = rarity; - - if (tags?.Count > 0) { - Tags = tags; - } - } - - [JsonConstructor] - private Asset() { } - - internal Asset CreateShallowCopy() => (Asset) MemberwiseClone(); - - public sealed class Tag { - [JsonProperty(PropertyName = "category", Required = Required.Always)] - [PublicAPI] - public string Identifier { get; private set; } = ""; - - [JsonProperty(PropertyName = "internal_name", Required = Required.Always)] - [PublicAPI] - public string Value { get; private set; } = ""; - - internal Tag(string identifier, string value) { - Identifier = !string.IsNullOrEmpty(identifier) ? identifier : throw new ArgumentNullException(nameof(identifier)); - Value = value ?? throw new ArgumentNullException(nameof(value)); - } - - [JsonConstructor] - private Tag() { } - } - - public enum ERarity : byte { - Unknown, - Common, - Uncommon, - Rare - } - - public enum EType : byte { - Unknown, - BoosterPack, - Emoticon, - FoilTradingCard, - ProfileBackground, - TradingCard, - SteamGems, - SaleItem, - Consumable, - ProfileModifier, - Sticker, - ChatEffect, - MiniProfileBackground, - AvatarProfileFrame, - AnimatedAvatar - } - } - - [PublicAPI] - [SuppressMessage("ReSharper", "ClassCannotBeInstantiated")] - public class BooleanResponse { - [JsonProperty(PropertyName = "success", Required = Required.Always)] - public bool Success { get; private set; } - - [JsonConstructor] - protected BooleanResponse() { } - } - - [PublicAPI] - [SuppressMessage("ReSharper", "ClassCannotBeInstantiated")] - public class EResultResponse { - [JsonProperty(PropertyName = "success", Required = Required.Always)] - public EResult Result { get; private set; } - - [JsonConstructor] - protected EResultResponse() { } - } - - // REF: https://developer.valvesoftware.com/wiki/Steam_Web_API/IEconService#CEcon_TradeOffer - public sealed class TradeOffer { - [PublicAPI] - public IReadOnlyCollection ItemsToGiveReadOnly => ItemsToGive; - - [PublicAPI] - public IReadOnlyCollection ItemsToReceiveReadOnly => ItemsToReceive; - - internal readonly HashSet ItemsToGive = new(); - internal readonly HashSet ItemsToReceive = new(); - - [PublicAPI] - public ulong OtherSteamID64 { get; private set; } - - [PublicAPI] - public ETradeOfferState State { get; private set; } - - [PublicAPI] - public ulong TradeOfferID { get; private set; } - - // Constructed from trades being received - internal TradeOffer(ulong tradeOfferID, uint otherSteamID3, ETradeOfferState state) { - if (tradeOfferID == 0) { - throw new ArgumentOutOfRangeException(nameof(tradeOfferID)); - } - - if (otherSteamID3 == 0) { - throw new ArgumentOutOfRangeException(nameof(otherSteamID3)); - } - - if (!Enum.IsDefined(typeof(ETradeOfferState), state)) { - throw new InvalidEnumArgumentException(nameof(state), (int) state, typeof(ETradeOfferState)); - } - - TradeOfferID = tradeOfferID; - OtherSteamID64 = new SteamID(otherSteamID3, EUniverse.Public, EAccountType.Individual); - State = state; - } - - [PublicAPI] - public bool IsValidSteamItemsRequest(IReadOnlyCollection acceptedTypes) { - if ((acceptedTypes == null) || (acceptedTypes.Count == 0)) { - throw new ArgumentNullException(nameof(acceptedTypes)); - } - - return ItemsToGive.All(item => (item.AppID == Asset.SteamAppID) && (item.ContextID == Asset.SteamCommunityContextID) && acceptedTypes.Contains(item.Type)); - } - } - - [SuppressMessage("ReSharper", "ClassCannotBeInstantiated")] - internal sealed class InventoryResponse : EResultResponse { - [JsonProperty(PropertyName = "assets", Required = Required.DisallowNull)] - internal readonly ImmutableHashSet Assets = ImmutableHashSet.Empty; - - [JsonProperty(PropertyName = "descriptions", Required = Required.DisallowNull)] - internal readonly ImmutableHashSet Descriptions = ImmutableHashSet.Empty; - - [JsonProperty(PropertyName = "error", Required = Required.DisallowNull)] - internal readonly string Error = ""; - - [JsonProperty(PropertyName = "total_inventory_count", Required = Required.DisallowNull)] - internal readonly uint TotalInventoryCount; - - internal ulong LastAssetID { get; private set; } - internal bool MoreItems { get; private set; } - - [JsonProperty(PropertyName = "last_assetid", Required = Required.DisallowNull)] - private string LastAssetIDText { - set { - if (string.IsNullOrEmpty(value)) { - ASF.ArchiLogger.LogNullError(nameof(value)); - - return; - } - - if (!ulong.TryParse(value, out ulong lastAssetID) || (lastAssetID == 0)) { - ASF.ArchiLogger.LogNullError(nameof(lastAssetID)); - - return; - } - - LastAssetID = lastAssetID; - } - } - - [JsonProperty(PropertyName = "more_items", Required = Required.DisallowNull)] - private byte MoreItemsNumber { - set => MoreItems = value > 0; - } - - [JsonConstructor] - private InventoryResponse() { } - - internal sealed class Description { - internal Asset.ERarity Rarity { - get { - foreach (Asset.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 (Asset.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(nameof(appID)); - - break; - } - - return appID; - } - } - - return 0; - } - } - - internal Asset.EType Type { - get { - Asset.EType type = Asset.EType.Unknown; - - foreach (Asset.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; - default: - ASF.ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.WarningUnknownValuePleaseReport, nameof(tag.Value), tag.Value)); - - return Asset.EType.Unknown; - } - } - } - - return type; - } - } - - [JsonExtensionData] - internal Dictionary? AdditionalProperties { - get; - [UsedImplicitly] - set; - } - - [JsonProperty(PropertyName = "appid", Required = Required.Always)] - internal uint AppID { get; set; } - - internal ulong ClassID { get; set; } - internal ulong InstanceID { get; set; } - internal bool Marketable { get; set; } - - [JsonProperty(PropertyName = "tags", Required = Required.DisallowNull)] - internal ImmutableHashSet Tags { get; set; } = ImmutableHashSet.Empty; - - internal bool Tradable { get; set; } - - [JsonProperty(PropertyName = "classid", Required = Required.Always)] - private string ClassIDText { - set { - if (string.IsNullOrEmpty(value)) { - ASF.ArchiLogger.LogNullError(nameof(value)); - - return; - } - - if (!ulong.TryParse(value, out ulong classID) || (classID == 0)) { - ASF.ArchiLogger.LogNullError(nameof(classID)); - - return; - } - - ClassID = classID; - } - } - - [JsonProperty(PropertyName = "instanceid", Required = Required.DisallowNull)] - private string InstanceIDText { - set { - if (string.IsNullOrEmpty(value)) { - return; - } - - if (!ulong.TryParse(value, out ulong instanceID)) { - ASF.ArchiLogger.LogNullError(nameof(instanceID)); - - return; - } - - InstanceID = instanceID; - } - } - - [JsonProperty(PropertyName = "marketable", Required = Required.Always)] - private byte MarketableNumber { - set => Marketable = value > 0; - } - - [JsonProperty(PropertyName = "tradable", Required = Required.Always)] - private byte TradableNumber { - set => Tradable = value > 0; - } - - [JsonConstructor] - internal Description() { } - } - } - - [SuppressMessage("ReSharper", "ClassCannotBeInstantiated")] - internal sealed class NewDiscoveryQueueResponse { - [JsonProperty(PropertyName = "queue", Required = Required.Always)] - internal readonly ImmutableHashSet Queue = ImmutableHashSet.Empty; - - [JsonConstructor] - private NewDiscoveryQueueResponse() { } - } - - [SuppressMessage("ReSharper", "ClassCannotBeInstantiated")] - internal sealed class RedeemWalletResponse : EResultResponse { - [JsonProperty(PropertyName = "detail", Required = Required.DisallowNull)] - internal readonly EPurchaseResultDetail PurchaseResultDetail; - - [JsonConstructor] - private RedeemWalletResponse() { } - } - - [SuppressMessage("ReSharper", "ClassCannotBeInstantiated")] - internal sealed class TradeOfferAcceptResponse { - [JsonProperty(PropertyName = "strError", Required = Required.DisallowNull)] - internal readonly string ErrorText = ""; - - [JsonProperty(PropertyName = "needs_mobile_confirmation", Required = Required.DisallowNull)] - internal readonly bool RequiresMobileConfirmation; - - [JsonConstructor] - private TradeOfferAcceptResponse() { } - } - - internal sealed class TradeOfferSendRequest { - [JsonProperty(PropertyName = "me", Required = Required.Always)] - internal readonly ItemList ItemsToGive = new(); - - [JsonProperty(PropertyName = "them", Required = Required.Always)] - internal readonly ItemList ItemsToReceive = new(); - - internal sealed class ItemList { - [JsonProperty(PropertyName = "assets", Required = Required.Always)] - internal readonly HashSet Assets = new(); - } - } - - [SuppressMessage("ReSharper", "ClassCannotBeInstantiated")] - internal sealed class TradeOfferSendResponse { - [JsonProperty(PropertyName = "strError", Required = Required.DisallowNull)] - internal readonly string ErrorText = ""; - - [JsonProperty(PropertyName = "needs_mobile_confirmation", Required = Required.DisallowNull)] - internal readonly bool RequiresMobileConfirmation; - - internal ulong TradeOfferID { get; private set; } - - [JsonProperty(PropertyName = "tradeofferid", Required = Required.DisallowNull)] - private string TradeOfferIDText { - set { - if (string.IsNullOrEmpty(value)) { - ASF.ArchiLogger.LogNullError(nameof(value)); - - return; - } - - if (!ulong.TryParse(value, out ulong tradeOfferID) || (tradeOfferID == 0)) { - ASF.ArchiLogger.LogNullError(nameof(tradeOfferID)); - - return; - } - - TradeOfferID = tradeOfferID; - } - } - - [JsonConstructor] - private TradeOfferSendResponse() { } - } - - [SuppressMessage("ReSharper", "ClassCannotBeInstantiated")] - internal sealed class UserPrivacy { - [JsonProperty(PropertyName = "eCommentPermission", Required = Required.Always)] - internal readonly ECommentPermission CommentPermission; - - [JsonProperty(PropertyName = "PrivacySettings", Required = Required.Always)] - internal readonly PrivacySettings Settings = new(); - - // Constructed from privacy change request - internal UserPrivacy(PrivacySettings settings, ECommentPermission commentPermission) { - Settings = settings ?? throw new ArgumentNullException(nameof(settings)); - CommentPermission = commentPermission; - } - - [JsonConstructor] - private UserPrivacy() { } - - internal sealed class PrivacySettings { - [JsonProperty(PropertyName = "PrivacyFriendsList", Required = Required.Always)] - internal readonly ArchiHandler.EPrivacySetting FriendsList; - - [JsonProperty(PropertyName = "PrivacyInventory", Required = Required.Always)] - internal readonly ArchiHandler.EPrivacySetting Inventory; - - [JsonProperty(PropertyName = "PrivacyInventoryGifts", Required = Required.Always)] - internal readonly ArchiHandler.EPrivacySetting InventoryGifts; - - [JsonProperty(PropertyName = "PrivacyOwnedGames", Required = Required.Always)] - internal readonly ArchiHandler.EPrivacySetting OwnedGames; - - [JsonProperty(PropertyName = "PrivacyPlaytime", Required = Required.Always)] - internal readonly ArchiHandler.EPrivacySetting Playtime; - - [JsonProperty(PropertyName = "PrivacyProfile", Required = Required.Always)] - internal readonly ArchiHandler.EPrivacySetting Profile; - - // Constructed from privacy change request - internal PrivacySettings(ArchiHandler.EPrivacySetting profile, ArchiHandler.EPrivacySetting ownedGames, ArchiHandler.EPrivacySetting playtime, ArchiHandler.EPrivacySetting friendsList, ArchiHandler.EPrivacySetting inventory, ArchiHandler.EPrivacySetting inventoryGifts) { - if ((profile == ArchiHandler.EPrivacySetting.Unknown) || !Enum.IsDefined(typeof(ArchiHandler.EPrivacySetting), profile)) { - throw new InvalidEnumArgumentException(nameof(profile), (int) profile, typeof(ArchiHandler.EPrivacySetting)); - } - - if ((ownedGames == ArchiHandler.EPrivacySetting.Unknown) || !Enum.IsDefined(typeof(ArchiHandler.EPrivacySetting), ownedGames)) { - throw new InvalidEnumArgumentException(nameof(ownedGames), (int) ownedGames, typeof(ArchiHandler.EPrivacySetting)); - } - - if ((playtime == ArchiHandler.EPrivacySetting.Unknown) || !Enum.IsDefined(typeof(ArchiHandler.EPrivacySetting), playtime)) { - throw new InvalidEnumArgumentException(nameof(playtime), (int) playtime, typeof(ArchiHandler.EPrivacySetting)); - } - - if ((friendsList == ArchiHandler.EPrivacySetting.Unknown) || !Enum.IsDefined(typeof(ArchiHandler.EPrivacySetting), friendsList)) { - throw new InvalidEnumArgumentException(nameof(friendsList), (int) friendsList, typeof(ArchiHandler.EPrivacySetting)); - } - - if ((inventory == ArchiHandler.EPrivacySetting.Unknown) || !Enum.IsDefined(typeof(ArchiHandler.EPrivacySetting), inventory)) { - throw new InvalidEnumArgumentException(nameof(inventory), (int) inventory, typeof(ArchiHandler.EPrivacySetting)); - } - - if ((inventoryGifts == ArchiHandler.EPrivacySetting.Unknown) || !Enum.IsDefined(typeof(ArchiHandler.EPrivacySetting), inventoryGifts)) { - throw new InvalidEnumArgumentException(nameof(inventoryGifts), (int) inventoryGifts, typeof(ArchiHandler.EPrivacySetting)); - } - - Profile = profile; - OwnedGames = ownedGames; - Playtime = playtime; - FriendsList = friendsList; - Inventory = inventory; - InventoryGifts = inventoryGifts; - } - - [JsonConstructor] - internal PrivacySettings() { } - } - - internal enum ECommentPermission : byte { - FriendsOnly, - Public, - Private - } - } - } -} diff --git a/ArchiSteamFarm/Json/Tag.cs b/ArchiSteamFarm/Json/Tag.cs new file mode 100644 index 000000000..ef50edc29 --- /dev/null +++ b/ArchiSteamFarm/Json/Tag.cs @@ -0,0 +1,44 @@ +// _ _ _ ____ _ _____ +// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___ +// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \ +// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | +// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| +// | +// Copyright 2015-2021 Ł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 JetBrains.Annotations; +using Newtonsoft.Json; + +namespace ArchiSteamFarm.Json { + public sealed class Tag { + [JsonProperty(PropertyName = "category", Required = Required.Always)] + [PublicAPI] + public string Identifier { get; private set; } = ""; + + [JsonProperty(PropertyName = "internal_name", Required = Required.Always)] + [PublicAPI] + public string Value { get; private set; } = ""; + + internal Tag(string identifier, string value) { + Identifier = !string.IsNullOrEmpty(identifier) ? identifier : throw new ArgumentNullException(nameof(identifier)); + Value = value ?? throw new ArgumentNullException(nameof(value)); + } + + [JsonConstructor] + private Tag() { } + } +} diff --git a/ArchiSteamFarm/Json/TradeOffer.cs b/ArchiSteamFarm/Json/TradeOffer.cs new file mode 100644 index 000000000..8ecdf01e5 --- /dev/null +++ b/ArchiSteamFarm/Json/TradeOffer.cs @@ -0,0 +1,78 @@ +// _ _ _ ____ _ _____ +// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___ +// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \ +// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | +// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| +// | +// Copyright 2015-2021 Ł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.ComponentModel; +using System.Linq; +using JetBrains.Annotations; +using SteamKit2; + +namespace ArchiSteamFarm.Json { + // REF: https://developer.valvesoftware.com/wiki/Steam_Web_API/IEconService#CEcon_TradeOffer + public sealed class TradeOffer { + [PublicAPI] + public IReadOnlyCollection ItemsToGiveReadOnly => ItemsToGive; + + [PublicAPI] + public IReadOnlyCollection ItemsToReceiveReadOnly => ItemsToReceive; + + internal readonly HashSet ItemsToGive = new(); + internal readonly HashSet ItemsToReceive = new(); + + [PublicAPI] + public ulong OtherSteamID64 { get; private set; } + + [PublicAPI] + public ETradeOfferState State { get; private set; } + + [PublicAPI] + public ulong TradeOfferID { get; private set; } + + // Constructed from trades being received + internal TradeOffer(ulong tradeOfferID, uint otherSteamID3, ETradeOfferState state) { + if (tradeOfferID == 0) { + throw new ArgumentOutOfRangeException(nameof(tradeOfferID)); + } + + if (otherSteamID3 == 0) { + throw new ArgumentOutOfRangeException(nameof(otherSteamID3)); + } + + if (!Enum.IsDefined(typeof(ETradeOfferState), state)) { + throw new InvalidEnumArgumentException(nameof(state), (int) state, typeof(ETradeOfferState)); + } + + TradeOfferID = tradeOfferID; + OtherSteamID64 = new SteamID(otherSteamID3, EUniverse.Public, EAccountType.Individual); + State = state; + } + + [PublicAPI] + public bool IsValidSteamItemsRequest(IReadOnlyCollection acceptedTypes) { + if ((acceptedTypes == null) || (acceptedTypes.Count == 0)) { + throw new ArgumentNullException(nameof(acceptedTypes)); + } + + return ItemsToGive.All(item => (item.AppID == Asset.SteamAppID) && (item.ContextID == Asset.SteamCommunityContextID) && acceptedTypes.Contains(item.Type)); + } + } +} diff --git a/ArchiSteamFarm/Json/TradeOfferAcceptResponse.cs b/ArchiSteamFarm/Json/TradeOfferAcceptResponse.cs new file mode 100644 index 000000000..aa3a5623d --- /dev/null +++ b/ArchiSteamFarm/Json/TradeOfferAcceptResponse.cs @@ -0,0 +1,37 @@ +// _ _ _ ____ _ _____ +// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___ +// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \ +// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | +// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| +// | +// Copyright 2015-2021 Łukasz "JustArchi" Domeradzki +// Contact: JustArchi@JustArchi.net +// | +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// | +// http://www.apache.org/licenses/LICENSE-2.0 +// | +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System.Diagnostics.CodeAnalysis; +using Newtonsoft.Json; + +namespace ArchiSteamFarm.Json { + [SuppressMessage("ReSharper", "ClassCannotBeInstantiated")] + internal sealed class TradeOfferAcceptResponse { + [JsonProperty(PropertyName = "strError", Required = Required.DisallowNull)] + internal readonly string ErrorText = ""; + + [JsonProperty(PropertyName = "needs_mobile_confirmation", Required = Required.DisallowNull)] + internal readonly bool RequiresMobileConfirmation; + + [JsonConstructor] + private TradeOfferAcceptResponse() { } + } +} diff --git a/ArchiSteamFarm/Json/TradeOfferSendRequest.cs b/ArchiSteamFarm/Json/TradeOfferSendRequest.cs new file mode 100644 index 000000000..c29700bbf --- /dev/null +++ b/ArchiSteamFarm/Json/TradeOfferSendRequest.cs @@ -0,0 +1,38 @@ +// _ _ _ ____ _ _____ +// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___ +// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \ +// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | +// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| +// | +// Copyright 2015-2021 Ł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.Generic; +using Newtonsoft.Json; + +namespace ArchiSteamFarm.Json { + internal sealed class TradeOfferSendRequest { + [JsonProperty(PropertyName = "me", Required = Required.Always)] + internal readonly ItemList ItemsToGive = new(); + + [JsonProperty(PropertyName = "them", Required = Required.Always)] + internal readonly ItemList ItemsToReceive = new(); + + internal sealed class ItemList { + [JsonProperty(PropertyName = "assets", Required = Required.Always)] + internal readonly HashSet Assets = new(); + } + } +} diff --git a/ArchiSteamFarm/Json/TradeOfferSendResponse.cs b/ArchiSteamFarm/Json/TradeOfferSendResponse.cs new file mode 100644 index 000000000..f5c881cd7 --- /dev/null +++ b/ArchiSteamFarm/Json/TradeOfferSendResponse.cs @@ -0,0 +1,58 @@ +// _ _ _ ____ _ _____ +// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___ +// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \ +// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | +// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| +// | +// Copyright 2015-2021 Łukasz "JustArchi" Domeradzki +// Contact: JustArchi@JustArchi.net +// | +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// | +// http://www.apache.org/licenses/LICENSE-2.0 +// | +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System.Diagnostics.CodeAnalysis; +using Newtonsoft.Json; + +namespace ArchiSteamFarm.Json { + [SuppressMessage("ReSharper", "ClassCannotBeInstantiated")] + internal sealed class TradeOfferSendResponse { + [JsonProperty(PropertyName = "strError", Required = Required.DisallowNull)] + internal readonly string ErrorText = ""; + + [JsonProperty(PropertyName = "needs_mobile_confirmation", Required = Required.DisallowNull)] + internal readonly bool RequiresMobileConfirmation; + + internal ulong TradeOfferID { get; private set; } + + [JsonProperty(PropertyName = "tradeofferid", Required = Required.DisallowNull)] + private string TradeOfferIDText { + set { + if (string.IsNullOrEmpty(value)) { + ASF.ArchiLogger.LogNullError(nameof(value)); + + return; + } + + if (!ulong.TryParse(value, out ulong tradeOfferID) || (tradeOfferID == 0)) { + ASF.ArchiLogger.LogNullError(nameof(tradeOfferID)); + + return; + } + + TradeOfferID = tradeOfferID; + } + } + + [JsonConstructor] + private TradeOfferSendResponse() { } + } +} diff --git a/ArchiSteamFarm/Json/UserPrivacy.cs b/ArchiSteamFarm/Json/UserPrivacy.cs new file mode 100644 index 000000000..c37006e8a --- /dev/null +++ b/ArchiSteamFarm/Json/UserPrivacy.cs @@ -0,0 +1,108 @@ +// _ _ _ ____ _ _____ +// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___ +// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \ +// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | +// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| +// | +// Copyright 2015-2021 Ł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.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using Newtonsoft.Json; + +namespace ArchiSteamFarm.Json { + [SuppressMessage("ReSharper", "ClassCannotBeInstantiated")] + internal sealed class UserPrivacy { + [JsonProperty(PropertyName = "eCommentPermission", Required = Required.Always)] + internal readonly ECommentPermission CommentPermission; + + [JsonProperty(PropertyName = "PrivacySettings", Required = Required.Always)] + internal readonly PrivacySettings Settings = new(); + + // Constructed from privacy change request + internal UserPrivacy(PrivacySettings settings, ECommentPermission commentPermission) { + Settings = settings ?? throw new ArgumentNullException(nameof(settings)); + CommentPermission = commentPermission; + } + + [JsonConstructor] + private UserPrivacy() { } + + internal sealed class PrivacySettings { + [JsonProperty(PropertyName = "PrivacyFriendsList", Required = Required.Always)] + internal readonly ArchiHandler.EPrivacySetting FriendsList; + + [JsonProperty(PropertyName = "PrivacyInventory", Required = Required.Always)] + internal readonly ArchiHandler.EPrivacySetting Inventory; + + [JsonProperty(PropertyName = "PrivacyInventoryGifts", Required = Required.Always)] + internal readonly ArchiHandler.EPrivacySetting InventoryGifts; + + [JsonProperty(PropertyName = "PrivacyOwnedGames", Required = Required.Always)] + internal readonly ArchiHandler.EPrivacySetting OwnedGames; + + [JsonProperty(PropertyName = "PrivacyPlaytime", Required = Required.Always)] + internal readonly ArchiHandler.EPrivacySetting Playtime; + + [JsonProperty(PropertyName = "PrivacyProfile", Required = Required.Always)] + internal readonly ArchiHandler.EPrivacySetting Profile; + + // Constructed from privacy change request + internal PrivacySettings(ArchiHandler.EPrivacySetting profile, ArchiHandler.EPrivacySetting ownedGames, ArchiHandler.EPrivacySetting playtime, ArchiHandler.EPrivacySetting friendsList, ArchiHandler.EPrivacySetting inventory, ArchiHandler.EPrivacySetting inventoryGifts) { + if ((profile == ArchiHandler.EPrivacySetting.Unknown) || !Enum.IsDefined(typeof(ArchiHandler.EPrivacySetting), profile)) { + throw new InvalidEnumArgumentException(nameof(profile), (int) profile, typeof(ArchiHandler.EPrivacySetting)); + } + + if ((ownedGames == ArchiHandler.EPrivacySetting.Unknown) || !Enum.IsDefined(typeof(ArchiHandler.EPrivacySetting), ownedGames)) { + throw new InvalidEnumArgumentException(nameof(ownedGames), (int) ownedGames, typeof(ArchiHandler.EPrivacySetting)); + } + + if ((playtime == ArchiHandler.EPrivacySetting.Unknown) || !Enum.IsDefined(typeof(ArchiHandler.EPrivacySetting), playtime)) { + throw new InvalidEnumArgumentException(nameof(playtime), (int) playtime, typeof(ArchiHandler.EPrivacySetting)); + } + + if ((friendsList == ArchiHandler.EPrivacySetting.Unknown) || !Enum.IsDefined(typeof(ArchiHandler.EPrivacySetting), friendsList)) { + throw new InvalidEnumArgumentException(nameof(friendsList), (int) friendsList, typeof(ArchiHandler.EPrivacySetting)); + } + + if ((inventory == ArchiHandler.EPrivacySetting.Unknown) || !Enum.IsDefined(typeof(ArchiHandler.EPrivacySetting), inventory)) { + throw new InvalidEnumArgumentException(nameof(inventory), (int) inventory, typeof(ArchiHandler.EPrivacySetting)); + } + + if ((inventoryGifts == ArchiHandler.EPrivacySetting.Unknown) || !Enum.IsDefined(typeof(ArchiHandler.EPrivacySetting), inventoryGifts)) { + throw new InvalidEnumArgumentException(nameof(inventoryGifts), (int) inventoryGifts, typeof(ArchiHandler.EPrivacySetting)); + } + + Profile = profile; + OwnedGames = ownedGames; + Playtime = playtime; + FriendsList = friendsList; + Inventory = inventory; + InventoryGifts = inventoryGifts; + } + + [JsonConstructor] + internal PrivacySettings() { } + } + + internal enum ECommentPermission : byte { + FriendsOnly, + Public, + Private + } + } +} diff --git a/ArchiSteamFarm/MobileAuthenticator.cs b/ArchiSteamFarm/MobileAuthenticator.cs index 3754aa2b9..c98fce17f 100644 --- a/ArchiSteamFarm/MobileAuthenticator.cs +++ b/ArchiSteamFarm/MobileAuthenticator.cs @@ -22,7 +22,6 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; -using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Security.Cryptography; @@ -32,7 +31,6 @@ using System.Threading.Tasks; using AngleSharp.Dom; using ArchiSteamFarm.Helpers; using ArchiSteamFarm.Localization; -using JetBrains.Annotations; using Newtonsoft.Json; namespace ArchiSteamFarm { @@ -460,39 +458,5 @@ namespace ArchiSteamFarm { return (true, deviceID); } - - public sealed class Confirmation { - [JsonProperty(Required = Required.Always)] - public ulong Creator { get; } - - [JsonProperty(Required = Required.Always)] - public ulong ID { get; } - - [JsonProperty(Required = Required.Always)] - public ulong Key { get; } - - [JsonProperty(Required = Required.Always)] - public EType Type { get; } - - internal Confirmation(ulong id, ulong key, ulong creator, EType type) { - ID = id > 0 ? id : throw new ArgumentOutOfRangeException(nameof(id)); - Key = key > 0 ? key : throw new ArgumentOutOfRangeException(nameof(key)); - Creator = creator > 0 ? creator : throw new ArgumentOutOfRangeException(nameof(creator)); - Type = Enum.IsDefined(typeof(EType), type) ? type : throw new InvalidEnumArgumentException(nameof(type), (int) type, typeof(EType)); - } - - // REF: Internal documentation - [PublicAPI] - public enum EType : byte { - Unknown, - Generic, - Trade, - Market, - - // We're missing information about definition of number 4 type - PhoneNumberChange = 5, - AccountRecovery = 6 - } - } } } diff --git a/ArchiSteamFarm/OS.cs b/ArchiSteamFarm/OS.cs index 3d6706d5d..0f690bd36 100644 --- a/ArchiSteamFarm/OS.cs +++ b/ArchiSteamFarm/OS.cs @@ -30,6 +30,12 @@ using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using ArchiSteamFarm.Localization; +using ArchiSteamFarm.Web; + +#if NETFRAMEWORK +using ArchiSteamFarm.RuntimeCompatibility; +using File = System.IO.File; +#endif namespace ArchiSteamFarm { internal static class OS { @@ -172,7 +178,7 @@ namespace ArchiSteamFarm { } // All windows variants have valid .NET Core build, and generic-netf is supported only on mono - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || !RuntimeCompatibility.IsRunningOnMono) { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || !StaticHelpers.IsRunningOnMono) { return false; } diff --git a/ArchiSteamFarm/ParseTradeResult.cs b/ArchiSteamFarm/ParseTradeResult.cs new file mode 100644 index 000000000..d214b4cff --- /dev/null +++ b/ArchiSteamFarm/ParseTradeResult.cs @@ -0,0 +1,66 @@ +// _ _ _ ____ _ _____ +// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___ +// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \ +// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | +// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| +// | +// Copyright 2015-2021 Ł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.ComponentModel; +using System.Linq; +using ArchiSteamFarm.Json; +using JetBrains.Annotations; + +namespace ArchiSteamFarm { + public sealed class ParseTradeResult { + [PublicAPI] + public EResult Result { get; } + + [PublicAPI] + public ulong TradeOfferID { get; } + + internal readonly ImmutableHashSet? ReceivedItemTypes; + + internal ParseTradeResult(ulong tradeOfferID, EResult result, IReadOnlyCollection? itemsToReceive = null) { + if (tradeOfferID == 0) { + throw new ArgumentOutOfRangeException(nameof(tradeOfferID)); + } + + if ((result == EResult.Unknown) || !Enum.IsDefined(typeof(EResult), result)) { + throw new InvalidEnumArgumentException(nameof(result), (int) result, typeof(EResult)); + } + + TradeOfferID = tradeOfferID; + Result = result; + + if (itemsToReceive?.Count > 0) { + ReceivedItemTypes = itemsToReceive.Select(item => item.Type).ToImmutableHashSet(); + } + } + + public enum EResult : byte { + Unknown, + Accepted, + Blacklisted, + Ignored, + Rejected, + TryAgain + } + } +} diff --git a/ArchiSteamFarm/Plugins/IBotTradeOffer.cs b/ArchiSteamFarm/Plugins/IBotTradeOffer.cs index 623fb6ff9..b08c70464 100644 --- a/ArchiSteamFarm/Plugins/IBotTradeOffer.cs +++ b/ArchiSteamFarm/Plugins/IBotTradeOffer.cs @@ -32,6 +32,6 @@ namespace ArchiSteamFarm.Plugins { /// Bot object related to this callback. /// Trade offer related to this callback. /// True if the trade offer should be accepted as part of this plugin, false otherwise. - Task OnBotTradeOffer(Bot bot, Steam.TradeOffer tradeOffer); + Task OnBotTradeOffer(Bot bot, TradeOffer tradeOffer); } } diff --git a/ArchiSteamFarm/Plugins/IBotTradeOfferResults.cs b/ArchiSteamFarm/Plugins/IBotTradeOfferResults.cs index 2fbe89103..7175075f0 100644 --- a/ArchiSteamFarm/Plugins/IBotTradeOfferResults.cs +++ b/ArchiSteamFarm/Plugins/IBotTradeOfferResults.cs @@ -30,6 +30,6 @@ namespace ArchiSteamFarm.Plugins { /// /// Bot object related to this callback. /// Trade results related to this callback. - void OnBotTradeOfferResults(Bot bot, IReadOnlyCollection tradeResults); + void OnBotTradeOfferResults(Bot bot, IReadOnlyCollection tradeResults); } } diff --git a/ArchiSteamFarm/Plugins/IBotUserNotifications.cs b/ArchiSteamFarm/Plugins/IBotUserNotifications.cs index ae92c4957..0c8033b49 100644 --- a/ArchiSteamFarm/Plugins/IBotUserNotifications.cs +++ b/ArchiSteamFarm/Plugins/IBotUserNotifications.cs @@ -20,6 +20,7 @@ // limitations under the License. using System.Collections.Generic; +using ArchiSteamFarm.Callbacks; using JetBrains.Annotations; namespace ArchiSteamFarm.Plugins { @@ -30,6 +31,6 @@ namespace ArchiSteamFarm.Plugins { /// /// Bot object related to this callback. /// Collection containing those notification types that are new (that is, when new count > previous count of that notification type). - void OnBotUserNotifications(Bot bot, IReadOnlyCollection newNotifications); + void OnBotUserNotifications(Bot bot, IReadOnlyCollection newNotifications); } } diff --git a/ArchiSteamFarm/Plugins/PluginsCore.cs b/ArchiSteamFarm/Plugins/PluginsCore.cs index db39a7b9b..3dce0b146 100644 --- a/ArchiSteamFarm/Plugins/PluginsCore.cs +++ b/ArchiSteamFarm/Plugins/PluginsCore.cs @@ -30,6 +30,7 @@ using System.IO; using System.Linq; using System.Reflection; using System.Threading.Tasks; +using ArchiSteamFarm.Callbacks; using ArchiSteamFarm.Helpers; using ArchiSteamFarm.Json; using ArchiSteamFarm.Localization; @@ -498,7 +499,7 @@ namespace ArchiSteamFarm.Plugins { return responses.Where(response => response != null).SelectMany(handlers => handlers ?? Enumerable.Empty()).ToHashSet(); } - internal static async Task OnBotTradeOffer(Bot bot, Steam.TradeOffer tradeOffer) { + internal static async Task OnBotTradeOffer(Bot bot, TradeOffer tradeOffer) { if (bot == null) { throw new ArgumentNullException(nameof(bot)); } @@ -524,7 +525,7 @@ namespace ArchiSteamFarm.Plugins { return responses.Any(response => response); } - internal static async Task OnBotTradeOfferResults(Bot bot, IReadOnlyCollection tradeResults) { + internal static async Task OnBotTradeOfferResults(Bot bot, IReadOnlyCollection tradeResults) { if (bot == null) { throw new ArgumentNullException(nameof(bot)); } @@ -544,7 +545,7 @@ namespace ArchiSteamFarm.Plugins { } } - internal static async Task OnBotUserNotifications(Bot bot, IReadOnlyCollection newNotifications) { + internal static async Task OnBotUserNotifications(Bot bot, IReadOnlyCollection newNotifications) { if (bot == null) { throw new ArgumentNullException(nameof(bot)); } diff --git a/ArchiSteamFarm/Program.cs b/ArchiSteamFarm/Program.cs index 4571e2878..2136aae6a 100644 --- a/ArchiSteamFarm/Program.cs +++ b/ArchiSteamFarm/Program.cs @@ -31,6 +31,7 @@ using System.Threading.Tasks; using ArchiSteamFarm.IPC; using ArchiSteamFarm.Localization; using ArchiSteamFarm.NLog; +using ArchiSteamFarm.Web; using Newtonsoft.Json; using NLog; using NLog.Targets; diff --git a/ArchiSteamFarm/RuntimeCompatibility/File.cs b/ArchiSteamFarm/RuntimeCompatibility/File.cs new file mode 100644 index 000000000..10f7fcd5f --- /dev/null +++ b/ArchiSteamFarm/RuntimeCompatibility/File.cs @@ -0,0 +1,70 @@ +// _ _ _ ____ _ _____ +// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___ +// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \ +// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | +// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| +// | +// Copyright 2015-2021 Ł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.Threading.Tasks; +using JetBrains.Annotations; + +#pragma warning disable CS1998 +namespace ArchiSteamFarm.RuntimeCompatibility { + [PublicAPI] + public static class File { + public static async Task AppendAllTextAsync(string path, string contents) => +#if NETFRAMEWORK + System.IO.File.AppendAllText(path, contents); +#else + await System.IO.File.AppendAllTextAsync(path, contents).ConfigureAwait(false); +#endif + + public static void Move(string sourceFileName, string destFileName, bool overwrite) { +#if NETFRAMEWORK + if (overwrite && System.IO.File.Exists(destFileName)) { + System.IO.File.Delete(destFileName); + } + + System.IO.File.Move(sourceFileName, destFileName); +#else + System.IO.File.Move(sourceFileName, destFileName, overwrite); +#endif + } + + public static async Task ReadAllBytesAsync(string path) => +#if NETFRAMEWORK + System.IO.File.ReadAllBytes(path); +#else + await System.IO.File.ReadAllBytesAsync(path).ConfigureAwait(false); +#endif + + public static async Task ReadAllTextAsync(string path) => +#if NETFRAMEWORK + System.IO.File.ReadAllText(path); +#else + await System.IO.File.ReadAllTextAsync(path).ConfigureAwait(false); +#endif + + public static async Task WriteAllTextAsync(string path, string contents) => +#if NETFRAMEWORK + System.IO.File.WriteAllText(path, contents); +#else + await System.IO.File.WriteAllTextAsync(path, contents).ConfigureAwait(false); +#endif + } +} +#pragma warning restore CS1998 diff --git a/ArchiSteamFarm/RuntimeCompatibility/HashCode.cs b/ArchiSteamFarm/RuntimeCompatibility/HashCode.cs new file mode 100644 index 000000000..61e8d7779 --- /dev/null +++ b/ArchiSteamFarm/RuntimeCompatibility/HashCode.cs @@ -0,0 +1,34 @@ +// _ _ _ ____ _ _____ +// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___ +// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \ +// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | +// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| +// | +// Copyright 2015-2021 Ł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 JetBrains.Annotations; + +namespace ArchiSteamFarm.RuntimeCompatibility { + [PublicAPI] + public static class HashCode { + public static int Combine(T1 value1, T2 value2, T3 value3) => +#if NETFRAMEWORK + (value1, value2, value3).GetHashCode(); +#else + System.HashCode.Combine(value1, value2, value3); +#endif + } +} diff --git a/ArchiSteamFarm/RuntimeCompatibility/Path.cs b/ArchiSteamFarm/RuntimeCompatibility/Path.cs new file mode 100644 index 000000000..60b53e49c --- /dev/null +++ b/ArchiSteamFarm/RuntimeCompatibility/Path.cs @@ -0,0 +1,45 @@ +// _ _ _ ____ _ _____ +// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___ +// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \ +// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | +// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| +// | +// Copyright 2015-2021 Ł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 JetBrains.Annotations; + +#if NETFRAMEWORK +using System; +#endif + +namespace ArchiSteamFarm.RuntimeCompatibility { + [PublicAPI] + public static class Path { + public static string GetRelativePath(string relativeTo, string path) { +#if NETFRAMEWORK + if (!path.StartsWith(relativeTo, StringComparison.Ordinal)) { + throw new NotImplementedException(); + } + + string result = path[relativeTo.Length..]; + + return (result[0] == System.IO.Path.DirectorySeparatorChar) || (result[0] == System.IO.Path.AltDirectorySeparatorChar) ? result[1..] : result; +#else + return System.IO.Path.GetRelativePath(relativeTo, path); +#endif + } + } +} diff --git a/ArchiSteamFarm/RuntimeCompatibility.cs b/ArchiSteamFarm/RuntimeCompatibility/StaticHelpers.cs similarity index 62% rename from ArchiSteamFarm/RuntimeCompatibility.cs rename to ArchiSteamFarm/RuntimeCompatibility/StaticHelpers.cs index dd20db314..4fc058084 100644 --- a/ArchiSteamFarm/RuntimeCompatibility.cs +++ b/ArchiSteamFarm/RuntimeCompatibility/StaticHelpers.cs @@ -21,21 +21,21 @@ using System; using System.Diagnostics; -using System.Threading.Tasks; using JetBrains.Annotations; #if NETFRAMEWORK -using Microsoft.AspNetCore.Hosting; using System.Collections.Generic; using System.IO; using System.Net.WebSockets; using System.Security.Cryptography; using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting; #endif -namespace ArchiSteamFarm { +namespace ArchiSteamFarm.RuntimeCompatibility { [PublicAPI] - public static class RuntimeCompatibility { + public static class StaticHelpers { #if NETFRAMEWORK private static readonly DateTime SavedProcessStartTime = DateTime.UtcNow; #endif @@ -60,78 +60,6 @@ namespace ArchiSteamFarm { } } -#pragma warning disable CS1998 - [PublicAPI] - public static class File { - public static async Task AppendAllTextAsync(string path, string contents) => -#if NETFRAMEWORK - System.IO.File.AppendAllText(path, contents); -#else - await System.IO.File.AppendAllTextAsync(path, contents).ConfigureAwait(false); -#endif - - public static void Move(string sourceFileName, string destFileName, bool overwrite) { -#if NETFRAMEWORK - if (overwrite && System.IO.File.Exists(destFileName)) { - System.IO.File.Delete(destFileName); - } - - System.IO.File.Move(sourceFileName, destFileName); -#else - System.IO.File.Move(sourceFileName, destFileName, overwrite); -#endif - } - - public static async Task ReadAllBytesAsync(string path) => -#if NETFRAMEWORK - System.IO.File.ReadAllBytes(path); -#else - await System.IO.File.ReadAllBytesAsync(path).ConfigureAwait(false); -#endif - - public static async Task ReadAllTextAsync(string path) => -#if NETFRAMEWORK - System.IO.File.ReadAllText(path); -#else - await System.IO.File.ReadAllTextAsync(path).ConfigureAwait(false); -#endif - - public static async Task WriteAllTextAsync(string path, string contents) => -#if NETFRAMEWORK - System.IO.File.WriteAllText(path, contents); -#else - await System.IO.File.WriteAllTextAsync(path, contents).ConfigureAwait(false); -#endif - } -#pragma warning restore CS1998 - - [PublicAPI] - public static class HashCode { - public static int Combine(T1 value1, T2 value2, T3 value3) => -#if NETFRAMEWORK - (value1, value2, value3).GetHashCode(); -#else - System.HashCode.Combine(value1, value2, value3); -#endif - } - - [PublicAPI] - public static class Path { - public static string GetRelativePath(string relativeTo, string path) { -#if NETFRAMEWORK - if (!path.StartsWith(relativeTo, StringComparison.Ordinal)) { - throw new NotImplementedException(); - } - - string result = path[relativeTo.Length..]; - - return (result[0] == System.IO.Path.DirectorySeparatorChar) || (result[0] == System.IO.Path.AltDirectorySeparatorChar) ? result[1..] : result; -#else - return System.IO.Path.GetRelativePath(relativeTo, path); -#endif - } - } - #if NETFRAMEWORK public static Task ComputeHashAsync(this HashAlgorithm hashAlgorithm, Stream inputStream) => Task.FromResult(hashAlgorithm.ComputeHash(inputStream)); diff --git a/ArchiSteamFarm/SharedInfo.cs b/ArchiSteamFarm/SharedInfo.cs index 83b7c42d4..765d02e77 100644 --- a/ArchiSteamFarm/SharedInfo.cs +++ b/ArchiSteamFarm/SharedInfo.cs @@ -20,7 +20,6 @@ // limitations under the License. using System; -using System.IO; using System.Reflection; using ArchiSteamFarm.Plugins; using JetBrains.Annotations; @@ -75,7 +74,7 @@ namespace ArchiSteamFarm { // We can't just return our base directory since it could lead to the (wrong) temporary directory of extracted files in a single-publish scenario // If the path goes to our own binary, the user is using OS-specific build, single-file or not, we'll use path to location of that binary then // Otherwise, this path goes to some third-party binary, likely dotnet/mono, the user is using our generic build or other custom binary, we need to trust our base directory then - CachedHomeDirectory = Path.GetFileNameWithoutExtension(OS.ProcessFileName) == AssemblyName ? Path.GetDirectoryName(OS.ProcessFileName) ?? AppContext.BaseDirectory : AppContext.BaseDirectory; + CachedHomeDirectory = System.IO.Path.GetFileNameWithoutExtension(OS.ProcessFileName) == AssemblyName ? System.IO.Path.GetDirectoryName(OS.ProcessFileName) ?? AppContext.BaseDirectory : AppContext.BaseDirectory; return CachedHomeDirectory; } diff --git a/ArchiSteamFarm/Statistics.cs b/ArchiSteamFarm/Statistics.cs index 282df766e..2cd8f4afe 100644 --- a/ArchiSteamFarm/Statistics.cs +++ b/ArchiSteamFarm/Statistics.cs @@ -30,8 +30,13 @@ using System.Threading; using System.Threading.Tasks; using ArchiSteamFarm.Json; using ArchiSteamFarm.Localization; +using ArchiSteamFarm.Web; using Newtonsoft.Json; +#if NETFRAMEWORK +using ArchiSteamFarm.RuntimeCompatibility; +#endif + namespace ArchiSteamFarm { internal sealed class Statistics : IAsyncDisposable { private const ushort MaxItemsForFairBots = ArchiWebHandler.MaxItemsInSingleInventoryRequest * WebBrowser.MaxTries; // Determines which fair bots we'll deprioritize when matching due to excessive number of inventory requests they need to make, which are likely to fail in the process or cause excessive delays @@ -43,11 +48,11 @@ namespace ArchiSteamFarm { private const byte MinPersonaStateTTL = 8; // Minimum amount of hours we must wait before requesting persona state update private const string URL = "https://" + SharedInfo.StatisticsServer; - private static readonly ImmutableHashSet AcceptedMatchableTypes = ImmutableHashSet.Create( - Steam.Asset.EType.Emoticon, - Steam.Asset.EType.FoilTradingCard, - Steam.Asset.EType.ProfileBackground, - Steam.Asset.EType.TradingCard + private static readonly ImmutableHashSet AcceptedMatchableTypes = ImmutableHashSet.Create( + Asset.EType.Emoticon, + Asset.EType.FoilTradingCard, + Asset.EType.ProfileBackground, + Asset.EType.TradingCard ); private readonly Bot Bot; @@ -103,7 +108,7 @@ namespace ArchiSteamFarm { { "SteamID", Bot.SteamID.ToString(CultureInfo.InvariantCulture) } }; - WebBrowser.BasicResponse? response = await Bot.ArchiWebHandler.WebBrowser.UrlPost(request, data: data, requestOptions: WebBrowser.ERequestOptions.ReturnClientErrors).ConfigureAwait(false); + BasicResponse? response = await Bot.ArchiWebHandler.WebBrowser.UrlPost(request, data: data, requestOptions: WebBrowser.ERequestOptions.ReturnClientErrors).ConfigureAwait(false); if (response == null) { return; @@ -166,7 +171,7 @@ namespace ArchiSteamFarm { return; } - HashSet acceptedMatchableTypes = Bot.BotConfig.MatchableTypes.Where(type => AcceptedMatchableTypes.Contains(type)).ToHashSet(); + HashSet acceptedMatchableTypes = Bot.BotConfig.MatchableTypes.Where(type => AcceptedMatchableTypes.Contains(type)).ToHashSet(); if (acceptedMatchableTypes.Count == 0) { Bot.ArchiLogger.LogNullError(nameof(acceptedMatchableTypes)); @@ -176,7 +181,7 @@ namespace ArchiSteamFarm { return; } - HashSet inventory; + HashSet inventory; try { inventory = await Bot.ArchiWebHandler.GetInventoryAsync().Where(item => item.Tradable && acceptedMatchableTypes.Contains(item.Type)).ToHashSetAsync().ConfigureAwait(false); @@ -219,7 +224,7 @@ namespace ArchiSteamFarm { { "TradeToken", tradeToken! } }; - WebBrowser.BasicResponse? response = await Bot.ArchiWebHandler.WebBrowser.UrlPost(request, data: data, requestOptions: WebBrowser.ERequestOptions.ReturnClientErrors).ConfigureAwait(false); + BasicResponse? response = await Bot.ArchiWebHandler.WebBrowser.UrlPost(request, data: data, requestOptions: WebBrowser.ERequestOptions.ReturnClientErrors).ConfigureAwait(false); if (response == null) { return; @@ -242,7 +247,7 @@ namespace ArchiSteamFarm { private async Task?> GetListedUsers() { const string request = URL + "/Api/Bots"; - WebBrowser.ObjectResponse>? response = await Bot.ArchiWebHandler.WebBrowser.UrlGetToJsonObject>(request).ConfigureAwait(false); + ObjectResponse>? response = await Bot.ArchiWebHandler.WebBrowser.UrlGetToJsonObject>(request).ConfigureAwait(false); return response?.Content; } @@ -307,7 +312,7 @@ namespace ArchiSteamFarm { return; } - HashSet acceptedMatchableTypes = Bot.BotConfig.MatchableTypes.Where(AcceptedMatchableTypes.Contains).ToHashSet(); + HashSet acceptedMatchableTypes = Bot.BotConfig.MatchableTypes.Where(AcceptedMatchableTypes.Contains).ToHashSet(); if (acceptedMatchableTypes.Count == 0) { Bot.ArchiLogger.LogGenericTrace(Strings.ErrorAborted); @@ -354,7 +359,7 @@ namespace ArchiSteamFarm { } } - private async Task<(bool ShouldContinueMatching, bool TradedSomething)> MatchActivelyRound(IReadOnlyCollection acceptedMatchableTypes, IDictionary? GivenAssetIDs, ISet? ReceivedAssetIDs)> triedSteamIDs) { + private async Task<(bool ShouldContinueMatching, bool TradedSomething)> MatchActivelyRound(IReadOnlyCollection acceptedMatchableTypes, IDictionary? GivenAssetIDs, ISet? ReceivedAssetIDs)> triedSteamIDs) { if ((acceptedMatchableTypes == null) || (acceptedMatchableTypes.Count == 0)) { throw new ArgumentNullException(nameof(acceptedMatchableTypes)); } @@ -363,7 +368,7 @@ namespace ArchiSteamFarm { throw new ArgumentNullException(nameof(triedSteamIDs)); } - HashSet ourInventory; + HashSet ourInventory; try { ourInventory = await Bot.ArchiWebHandler.GetInventoryAsync().Where(item => acceptedMatchableTypes.Contains(item.Type) && !Bot.BotDatabase.MatchActivelyBlacklistedAppIDs.Contains(item.RealAppID)).ToHashSetAsync().ConfigureAwait(false); @@ -383,7 +388,7 @@ namespace ArchiSteamFarm { return (false, false); } - (Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), Dictionary> ourFullState, Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), Dictionary> ourTradableState) = Trading.GetDividedInventoryState(ourInventory); + (Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), Dictionary> ourFullState, Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), Dictionary> ourTradableState) = Trading.GetDividedInventoryState(ourInventory); if (Trading.IsEmptyForMatching(ourFullState, ourTradableState)) { // User doesn't have any more dupes in the inventory @@ -403,10 +408,10 @@ namespace ArchiSteamFarm { byte maxTradeHoldDuration = ASF.GlobalConfig?.MaxTradeHoldDuration ?? GlobalConfig.DefaultMaxTradeHoldDuration; byte totalMatches = 0; - HashSet<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity)> skippedSetsThisRound = new(); + HashSet<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity)> skippedSetsThisRound = new(); foreach (ListedUser listedUser in listedUsers.Where(listedUser => (listedUser.SteamID != Bot.SteamID) && acceptedMatchableTypes.Any(listedUser.MatchableTypes.Contains) && (!triedSteamIDs.TryGetValue(listedUser.SteamID, out (byte Tries, ISet? GivenAssetIDs, ISet? ReceivedAssetIDs) attempt) || (attempt.Tries < byte.MaxValue)) && !Bot.IsBlacklistedFromTrades(listedUser.SteamID)).OrderBy(listedUser => triedSteamIDs.TryGetValue(listedUser.SteamID, out (byte Tries, ISet? GivenAssetIDs, ISet? ReceivedAssetIDs) attempt) ? attempt.Tries : 0).ThenByDescending(listedUser => listedUser.MatchEverything).ThenByDescending(listedUser => listedUser.MatchEverything || (listedUser.ItemsCount < MaxItemsForFairBots)).ThenByDescending(listedUser => listedUser.Score)) { - HashSet<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity)> wantedSets = ourTradableState.Keys.Where(set => !skippedSetsThisRound.Contains(set) && listedUser.MatchableTypes.Contains(set.Type)).ToHashSet(); + HashSet<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity)> wantedSets = ourTradableState.Keys.Where(set => !skippedSetsThisRound.Contains(set) && listedUser.MatchableTypes.Contains(set.Type)).ToHashSet(); if (wantedSets.Count == 0) { continue; @@ -431,10 +436,10 @@ namespace ArchiSteamFarm { continue; } - HashSet theirInventory; + HashSet theirInventory; try { - theirInventory = await Bot.ArchiWebHandler.GetInventoryAsync(listedUser.SteamID).Where(item => (!listedUser.MatchEverything || item.Tradable) && wantedSets.Contains((item.RealAppID, item.Type, item.Rarity)) && ((holdDuration.Value == 0) || !(item.Type is Steam.Asset.EType.FoilTradingCard or Steam.Asset.EType.TradingCard && CardsFarmer.SalesBlacklist.Contains(item.RealAppID)))).ToHashSetAsync().ConfigureAwait(false); + theirInventory = await Bot.ArchiWebHandler.GetInventoryAsync(listedUser.SteamID).Where(item => (!listedUser.MatchEverything || item.Tradable) && wantedSets.Contains((item.RealAppID, item.Type, item.Rarity)) && ((holdDuration.Value == 0) || !(item.Type is Asset.EType.FoilTradingCard or Asset.EType.TradingCard && CardsFarmer.SalesBlacklist.Contains(item.RealAppID)))).ToHashSetAsync().ConfigureAwait(false); } catch (HttpRequestException e) { Bot.ArchiLogger.LogGenericWarningException(e); @@ -451,21 +456,21 @@ namespace ArchiSteamFarm { continue; } - HashSet<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity)> skippedSetsThisUser = new(); + HashSet<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity)> skippedSetsThisUser = new(); - Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), Dictionary> theirTradableState = Trading.GetTradableInventoryState(theirInventory); - Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), Dictionary> inventoryStateChanges = new(); + Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), Dictionary> theirTradableState = Trading.GetTradableInventoryState(theirInventory); + Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), Dictionary> inventoryStateChanges = new(); for (byte i = 0; i < Trading.MaxTradesPerAccount; i++) { byte itemsInTrade = 0; - HashSet<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity)> skippedSetsThisTrade = new(); + HashSet<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity)> skippedSetsThisTrade = new(); Dictionary classIDsToGive = new(); Dictionary classIDsToReceive = new(); Dictionary fairClassIDsToGive = new(); Dictionary fairClassIDsToReceive = new(); - foreach (((uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity) set, Dictionary ourFullItems) in ourFullState.Where(set => !skippedSetsThisUser.Contains(set.Key) && listedUser.MatchableTypes.Contains(set.Key.Type) && set.Value.Values.Any(count => count > 1))) { + foreach (((uint RealAppID, Asset.EType Type, Asset.ERarity Rarity) set, Dictionary ourFullItems) in ourFullState.Where(set => !skippedSetsThisUser.Contains(set.Key) && listedUser.MatchableTypes.Contains(set.Key.Type) && set.Value.Values.Any(count => count > 1))) { if (!ourTradableState.TryGetValue(set, out Dictionary? ourTradableItems) || (ourTradableItems.Count == 0)) { continue; } @@ -534,11 +539,11 @@ namespace ArchiSteamFarm { fairClassIDsToReceive[theirItem] = ++fairReceivedAmount; // Filter their inventory for the sets we're trading or have traded with this user - HashSet fairFiltered = theirInventory.Where(item => ((item.RealAppID == set.RealAppID) && (item.Type == set.Type) && (item.Rarity == set.Rarity)) || skippedSetsThisTrade.Contains((item.RealAppID, item.Type, item.Rarity))).Select(item => item.CreateShallowCopy()).ToHashSet(); + HashSet fairFiltered = theirInventory.Where(item => ((item.RealAppID == set.RealAppID) && (item.Type == set.Type) && (item.Rarity == set.Rarity)) || skippedSetsThisTrade.Contains((item.RealAppID, item.Type, item.Rarity))).Select(item => item.CreateShallowCopy()).ToHashSet(); // Copy list to HashSet - HashSet fairItemsToGive = Trading.GetTradableItemsFromInventory(ourInventory.Where(item => ((item.RealAppID == set.RealAppID) && (item.Type == set.Type) && (item.Rarity == set.Rarity)) || skippedSetsThisTrade.Contains((item.RealAppID, item.Type, item.Rarity))).Select(item => item.CreateShallowCopy()).ToHashSet(), fairClassIDsToGive.ToDictionary(classID => classID.Key, classID => classID.Value)); - HashSet fairItemsToReceive = Trading.GetTradableItemsFromInventory(fairFiltered.Select(item => item.CreateShallowCopy()).ToHashSet(), fairClassIDsToReceive.ToDictionary(classID => classID.Key, classID => classID.Value)); + HashSet fairItemsToGive = Trading.GetTradableItemsFromInventory(ourInventory.Where(item => ((item.RealAppID == set.RealAppID) && (item.Type == set.Type) && (item.Rarity == set.Rarity)) || skippedSetsThisTrade.Contains((item.RealAppID, item.Type, item.Rarity))).Select(item => item.CreateShallowCopy()).ToHashSet(), fairClassIDsToGive.ToDictionary(classID => classID.Key, classID => classID.Value)); + HashSet fairItemsToReceive = Trading.GetTradableItemsFromInventory(fairFiltered.Select(item => item.CreateShallowCopy()).ToHashSet(), fairClassIDsToReceive.ToDictionary(classID => classID.Key, classID => classID.Value)); // Actual check: if (!Trading.IsTradeNeutralOrBetter(fairFiltered, fairItemsToReceive, fairItemsToGive)) { @@ -615,8 +620,8 @@ namespace ArchiSteamFarm { } // Remove the items from inventories - HashSet itemsToGive = Trading.GetTradableItemsFromInventory(ourInventory, classIDsToGive); - HashSet itemsToReceive = Trading.GetTradableItemsFromInventory(theirInventory, classIDsToReceive); + HashSet itemsToGive = Trading.GetTradableItemsFromInventory(ourInventory, classIDsToGive); + HashSet itemsToReceive = Trading.GetTradableItemsFromInventory(theirInventory, classIDsToReceive); if ((itemsToGive.Count != itemsToReceive.Count) || !Trading.IsFairExchange(itemsToGive, itemsToReceive)) { // Failsafe @@ -647,7 +652,7 @@ namespace ArchiSteamFarm { (bool success, HashSet? mobileTradeOfferIDs) = await Bot.ArchiWebHandler.SendTradeOffer(listedUser.SteamID, itemsToGive, itemsToReceive, listedUser.TradeToken, true).ConfigureAwait(false); if ((mobileTradeOfferIDs?.Count > 0) && Bot.HasMobileAuthenticator) { - (bool twoFactorSuccess, _, _) = await Bot.Actions.HandleTwoFactorAuthenticationConfirmations(true, MobileAuthenticator.Confirmation.EType.Trade, mobileTradeOfferIDs, true).ConfigureAwait(false); + (bool twoFactorSuccess, _, _) = await Bot.Actions.HandleTwoFactorAuthenticationConfirmations(true, Confirmation.EType.Trade, mobileTradeOfferIDs, true).ConfigureAwait(false); if (!twoFactorSuccess) { Bot.ArchiLogger.LogGenericTrace(Strings.WarningFailed); @@ -680,7 +685,7 @@ namespace ArchiSteamFarm { skippedSetsThisRound.UnionWith(skippedSetsThisUser); - foreach ((uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity) skippedSet in skippedSetsThisUser) { + foreach ((uint RealAppID, Asset.EType Type, Asset.ERarity Rarity) skippedSet in skippedSetsThisUser) { ourFullState.Remove(skippedSet); ourTradableState.Remove(skippedSet); } @@ -707,7 +712,7 @@ namespace ArchiSteamFarm { internal readonly ushort ItemsCount; #pragma warning restore CS0649 - internal readonly HashSet MatchableTypes = new(); + internal readonly HashSet MatchableTypes = new(); #pragma warning disable CS0649 [JsonProperty(PropertyName = "steam_id", Required = Required.Always)] @@ -731,11 +736,11 @@ namespace ArchiSteamFarm { set { switch (value) { case 0: - MatchableTypes.Remove(Steam.Asset.EType.ProfileBackground); + MatchableTypes.Remove(Asset.EType.ProfileBackground); break; case 1: - MatchableTypes.Add(Steam.Asset.EType.ProfileBackground); + MatchableTypes.Add(Asset.EType.ProfileBackground); break; default: @@ -751,11 +756,11 @@ namespace ArchiSteamFarm { set { switch (value) { case 0: - MatchableTypes.Remove(Steam.Asset.EType.TradingCard); + MatchableTypes.Remove(Asset.EType.TradingCard); break; case 1: - MatchableTypes.Add(Steam.Asset.EType.TradingCard); + MatchableTypes.Add(Asset.EType.TradingCard); break; default: @@ -771,11 +776,11 @@ namespace ArchiSteamFarm { set { switch (value) { case 0: - MatchableTypes.Remove(Steam.Asset.EType.Emoticon); + MatchableTypes.Remove(Asset.EType.Emoticon); break; case 1: - MatchableTypes.Add(Steam.Asset.EType.Emoticon); + MatchableTypes.Add(Asset.EType.Emoticon); break; default: @@ -791,11 +796,11 @@ namespace ArchiSteamFarm { set { switch (value) { case 0: - MatchableTypes.Remove(Steam.Asset.EType.FoilTradingCard); + MatchableTypes.Remove(Asset.EType.FoilTradingCard); break; case 1: - MatchableTypes.Add(Steam.Asset.EType.FoilTradingCard); + MatchableTypes.Add(Asset.EType.FoilTradingCard); break; default: diff --git a/ArchiSteamFarm/SteamKit2/ServerRecordEndPoint.cs b/ArchiSteamFarm/SteamKit2/ServerRecordEndPoint.cs index afb87297a..73427f6a6 100644 --- a/ArchiSteamFarm/SteamKit2/ServerRecordEndPoint.cs +++ b/ArchiSteamFarm/SteamKit2/ServerRecordEndPoint.cs @@ -23,6 +23,7 @@ using System; using System.ComponentModel; using Newtonsoft.Json; using SteamKit2; +using HashCode = ArchiSteamFarm.RuntimeCompatibility.HashCode; namespace ArchiSteamFarm.SteamKit2 { internal sealed class ServerRecordEndPoint : IEquatable { @@ -58,6 +59,6 @@ namespace ArchiSteamFarm.SteamKit2 { public bool Equals(ServerRecordEndPoint? other) => (other != null) && (ReferenceEquals(other, this) || ((Host == other.Host) && (Port == other.Port) && (ProtocolTypes == other.ProtocolTypes))); public override bool Equals(object? obj) => (obj != null) && ((obj == this) || (obj is ServerRecordEndPoint serverRecord && Equals(serverRecord))); - public override int GetHashCode() => RuntimeCompatibility.HashCode.Combine(Host, Port, ProtocolTypes); + public override int GetHashCode() => HashCode.Combine(Host, Port, ProtocolTypes); } } diff --git a/ArchiSteamFarm/SteamPICSChanges.cs b/ArchiSteamFarm/SteamPICSChanges.cs index 98b96812c..e50e8cc28 100644 --- a/ArchiSteamFarm/SteamPICSChanges.cs +++ b/ArchiSteamFarm/SteamPICSChanges.cs @@ -24,6 +24,7 @@ using System.Linq; using System.Threading; using ArchiSteamFarm.Localization; using ArchiSteamFarm.Plugins; +using ArchiSteamFarm.Web; using SteamKit2; namespace ArchiSteamFarm { diff --git a/ArchiSteamFarm/SteamSaleEvent.cs b/ArchiSteamFarm/SteamSaleEvent.cs index 6b40cb785..713668dbe 100644 --- a/ArchiSteamFarm/SteamSaleEvent.cs +++ b/ArchiSteamFarm/SteamSaleEvent.cs @@ -27,6 +27,10 @@ using System.Threading.Tasks; using AngleSharp.Dom; using ArchiSteamFarm.Localization; +#if NETFRAMEWORK +using ArchiSteamFarm.RuntimeCompatibility; +#endif + namespace ArchiSteamFarm { internal sealed class SteamSaleEvent : IAsyncDisposable { private const byte MaxSingleQueuesDaily = 3; // This is only a failsafe for infinite queue clearing (in case IsDiscoveryQueueAvailable() would fail us) diff --git a/ArchiSteamFarm/Trading.cs b/ArchiSteamFarm/Trading.cs index 1523869b9..84bf4a71f 100644 --- a/ArchiSteamFarm/Trading.cs +++ b/ArchiSteamFarm/Trading.cs @@ -21,8 +21,6 @@ using System; using System.Collections.Generic; -using System.Collections.Immutable; -using System.ComponentModel; using System.Globalization; using System.Linq; using System.Net.Http; @@ -35,6 +33,10 @@ using ArchiSteamFarm.Plugins; using JetBrains.Annotations; using SteamKit2; +#if NETFRAMEWORK +using ArchiSteamFarm.RuntimeCompatibility; +#endif + namespace ArchiSteamFarm { public sealed class Trading : IDisposable { internal const byte MaxItemsPerTrade = byte.MaxValue; // This is decided upon various factors, mainly stability of Steam servers when dealing with huge trade offers @@ -51,18 +53,18 @@ namespace ArchiSteamFarm { public void Dispose() => TradesSemaphore.Dispose(); [PublicAPI] - public static Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), List> GetInventorySets(IReadOnlyCollection inventory) { + public static Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), List> GetInventorySets(IReadOnlyCollection inventory) { if ((inventory == null) || (inventory.Count == 0)) { throw new ArgumentNullException(nameof(inventory)); } - Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), Dictionary> sets = GetInventoryState(inventory); + Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), Dictionary> sets = GetInventoryState(inventory); return sets.ToDictionary(set => set.Key, set => set.Value.Values.OrderBy(amount => amount).ToList()); } [PublicAPI] - public static bool IsFairExchange(IReadOnlyCollection itemsToGive, IReadOnlyCollection itemsToReceive) { + public static bool IsFairExchange(IReadOnlyCollection itemsToGive, IReadOnlyCollection itemsToReceive) { if ((itemsToGive == null) || (itemsToGive.Count == 0)) { throw new ArgumentNullException(nameof(itemsToGive)); } @@ -71,22 +73,22 @@ namespace ArchiSteamFarm { throw new ArgumentNullException(nameof(itemsToReceive)); } - Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), uint> itemsToGiveAmounts = new(); + Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), uint> itemsToGiveAmounts = new(); - foreach (Steam.Asset item in itemsToGive) { - (uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity) key = (item.RealAppID, item.Type, item.Rarity); + foreach (Asset item in itemsToGive) { + (uint RealAppID, Asset.EType Type, Asset.ERarity Rarity) key = (item.RealAppID, item.Type, item.Rarity); itemsToGiveAmounts[key] = itemsToGiveAmounts.TryGetValue(key, out uint amount) ? amount + item.Amount : item.Amount; } - Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), uint> itemsToReceiveAmounts = new(); + Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), uint> itemsToReceiveAmounts = new(); - foreach (Steam.Asset item in itemsToReceive) { - (uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity) key = (item.RealAppID, item.Type, item.Rarity); + foreach (Asset item in itemsToReceive) { + (uint RealAppID, Asset.EType Type, Asset.ERarity Rarity) key = (item.RealAppID, item.Type, item.Rarity); itemsToReceiveAmounts[key] = itemsToReceiveAmounts.TryGetValue(key, out uint amount) ? amount + item.Amount : item.Amount; } // Ensure that amount of items to give is at least amount of items to receive (per all fairness factors) - foreach (((uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity) key, uint amountToGive) in itemsToGiveAmounts) { + foreach (((uint RealAppID, Asset.EType Type, Asset.ERarity Rarity) key, uint amountToGive) in itemsToGiveAmounts) { if (!itemsToReceiveAmounts.TryGetValue(key, out uint amountToReceive) || (amountToGive > amountToReceive)) { return false; } @@ -96,7 +98,7 @@ namespace ArchiSteamFarm { } [PublicAPI] - public static bool IsTradeNeutralOrBetter(HashSet inventory, IReadOnlyCollection itemsToGive, IReadOnlyCollection itemsToReceive) { + public static bool IsTradeNeutralOrBetter(HashSet inventory, IReadOnlyCollection itemsToGive, IReadOnlyCollection itemsToReceive) { if ((inventory == null) || (inventory.Count == 0)) { throw new ArgumentNullException(nameof(inventory)); } @@ -115,16 +117,16 @@ namespace ArchiSteamFarm { // All of those cases should be verified by our unit tests to ensure that the logic here matches all possible cases, especially those that were incorrectly handled previously // Firstly we get initial sets state of our inventory - Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), List> initialSets = GetInventorySets(inventory); + Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), List> initialSets = GetInventorySets(inventory); // Once we have initial state, we remove items that we're supposed to give from our inventory // This loop is a bit more complex due to the fact that we might have a mix of the same item splitted into different amounts - foreach (Steam.Asset itemToGive in itemsToGive) { + foreach (Asset itemToGive in itemsToGive) { uint amountToGive = itemToGive.Amount; - HashSet itemsToRemove = new(); + HashSet itemsToRemove = new(); // Keep in mind that ClassID is unique only within appID scope - we can do it like this because we're not dealing with non-Steam items here (otherwise we'd need to check appID too) - foreach (Steam.Asset item in inventory.Where(item => item.ClassID == itemToGive.ClassID)) { + foreach (Asset item in inventory.Where(item => item.ClassID == itemToGive.ClassID)) { if (amountToGive >= item.Amount) { itemsToRemove.Add(item); amountToGive -= item.Amount; @@ -148,15 +150,15 @@ namespace ArchiSteamFarm { } // Now we can add items that we're supposed to receive, this one doesn't require advanced amounts logic since we can just add items regardless - foreach (Steam.Asset itemToReceive in itemsToReceive) { + foreach (Asset itemToReceive in itemsToReceive) { inventory.Add(itemToReceive); } // Now we can get final sets state of our inventory after the exchange - Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), List> finalSets = GetInventorySets(inventory); + Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), List> finalSets = GetInventorySets(inventory); // Once we have both states, we can check overall fairness - foreach (((uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity) set, List beforeAmounts) in initialSets) { + foreach (((uint RealAppID, Asset.EType Type, Asset.ERarity Rarity) set, List beforeAmounts) in initialSets) { if (!finalSets.TryGetValue(set, out List? afterAmounts)) { // If we have no info about this set, then it has to be a bad one return false; @@ -205,16 +207,16 @@ namespace ArchiSteamFarm { return true; } - internal static (Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), Dictionary> FullState, Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), Dictionary> TradableState) GetDividedInventoryState(IReadOnlyCollection inventory) { + internal static (Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), Dictionary> FullState, Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), Dictionary> TradableState) GetDividedInventoryState(IReadOnlyCollection inventory) { if ((inventory == null) || (inventory.Count == 0)) { throw new ArgumentNullException(nameof(inventory)); } - Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), Dictionary> fullState = new(); - Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), Dictionary> tradableState = new(); + Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), Dictionary> fullState = new(); + Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), Dictionary> tradableState = new(); - foreach (Steam.Asset item in inventory) { - (uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity) key = (item.RealAppID, item.Type, item.Rarity); + foreach (Asset item in inventory) { + (uint RealAppID, Asset.EType Type, Asset.ERarity Rarity) key = (item.RealAppID, item.Type, item.Rarity); if (fullState.TryGetValue(key, out Dictionary? fullSet)) { fullSet[item.ClassID] = fullSet.TryGetValue(item.ClassID, out uint amount) ? amount + item.Amount : item.Amount; @@ -236,15 +238,15 @@ namespace ArchiSteamFarm { return (fullState, tradableState); } - internal static Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), Dictionary> GetTradableInventoryState(IReadOnlyCollection inventory) { + internal static Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), Dictionary> GetTradableInventoryState(IReadOnlyCollection inventory) { if ((inventory == null) || (inventory.Count == 0)) { throw new ArgumentNullException(nameof(inventory)); } - Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), Dictionary> tradableState = new(); + Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), Dictionary> tradableState = new(); - foreach (Steam.Asset item in inventory.Where(item => item.Tradable)) { - (uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity) key = (item.RealAppID, item.Type, item.Rarity); + foreach (Asset item in inventory.Where(item => item.Tradable)) { + (uint RealAppID, Asset.EType Type, Asset.ERarity Rarity) key = (item.RealAppID, item.Type, item.Rarity); if (tradableState.TryGetValue(key, out Dictionary? tradableSet)) { tradableSet[item.ClassID] = tradableSet.TryGetValue(item.ClassID, out uint amount) ? amount + item.Amount : item.Amount; @@ -256,7 +258,7 @@ namespace ArchiSteamFarm { return tradableState; } - internal static HashSet GetTradableItemsFromInventory(IReadOnlyCollection inventory, IDictionary classIDs) { + internal static HashSet GetTradableItemsFromInventory(IReadOnlyCollection inventory, IDictionary classIDs) { if ((inventory == null) || (inventory.Count == 0)) { throw new ArgumentNullException(nameof(inventory)); } @@ -265,9 +267,9 @@ namespace ArchiSteamFarm { throw new ArgumentNullException(nameof(classIDs)); } - HashSet result = new(); + HashSet result = new(); - foreach (Steam.Asset item in inventory.Where(item => item.Tradable)) { + foreach (Asset item in inventory.Where(item => item.Tradable)) { if (!classIDs.TryGetValue(item.ClassID, out uint amount)) { continue; } @@ -288,7 +290,7 @@ namespace ArchiSteamFarm { return result; } - internal static bool IsEmptyForMatching(IReadOnlyDictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), Dictionary> fullState, IReadOnlyDictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), Dictionary> tradableState) { + internal static bool IsEmptyForMatching(IReadOnlyDictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), Dictionary> fullState, IReadOnlyDictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), Dictionary> tradableState) { if (fullState == null) { throw new ArgumentNullException(nameof(fullState)); } @@ -297,7 +299,7 @@ namespace ArchiSteamFarm { throw new ArgumentNullException(nameof(tradableState)); } - foreach (((uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity) set, IReadOnlyDictionary state) in tradableState) { + foreach (((uint RealAppID, Asset.EType Type, Asset.ERarity Rarity) set, IReadOnlyDictionary state) in tradableState) { if (!fullState.TryGetValue(set, out Dictionary? fullSet) || (fullSet.Count == 0)) { throw new InvalidOperationException(nameof(fullSet)); } @@ -382,15 +384,15 @@ namespace ArchiSteamFarm { } } - private static Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), Dictionary> GetInventoryState(IReadOnlyCollection inventory) { + private static Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), Dictionary> GetInventoryState(IReadOnlyCollection inventory) { if ((inventory == null) || (inventory.Count == 0)) { throw new ArgumentNullException(nameof(inventory)); } - Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), Dictionary> state = new(); + Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), Dictionary> state = new(); - foreach (Steam.Asset item in inventory) { - (uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity) key = (item.RealAppID, item.Type, item.Rarity); + foreach (Asset item in inventory) { + (uint RealAppID, Asset.EType Type, Asset.ERarity Rarity) key = (item.RealAppID, item.Type, item.Rarity); if (state.TryGetValue(key, out Dictionary? set)) { set[item.ClassID] = set.TryGetValue(item.ClassID, out uint amount) ? amount + item.Amount : item.Amount; @@ -403,7 +405,7 @@ namespace ArchiSteamFarm { } private async Task ParseActiveTrades() { - HashSet? tradeOffers = await Bot.ArchiWebHandler.GetActiveTradeOffers().ConfigureAwait(false); + HashSet? tradeOffers = await Bot.ArchiWebHandler.GetActiveTradeOffers().ConfigureAwait(false); if ((tradeOffers == null) || (tradeOffers.Count == 0)) { return false; @@ -420,7 +422,7 @@ namespace ArchiSteamFarm { HashSet mobileTradeOfferIDs = results.Where(result => (result.TradeResult?.Result == ParseTradeResult.EResult.Accepted) && result.RequiresMobileConfirmation).Select(result => result.TradeResult!.TradeOfferID).ToHashSet(); if (mobileTradeOfferIDs.Count > 0) { - (bool twoFactorSuccess, _, _) = await Bot.Actions.HandleTwoFactorAuthenticationConfirmations(true, MobileAuthenticator.Confirmation.EType.Trade, mobileTradeOfferIDs, true).ConfigureAwait(false); + (bool twoFactorSuccess, _, _) = await Bot.Actions.HandleTwoFactorAuthenticationConfirmations(true, Confirmation.EType.Trade, mobileTradeOfferIDs, true).ConfigureAwait(false); if (!twoFactorSuccess) { HandledTradeOfferIDs.ExceptWith(mobileTradeOfferIDs); @@ -439,7 +441,7 @@ namespace ArchiSteamFarm { return results.Any(result => (result.TradeResult?.Result == ParseTradeResult.EResult.Accepted) && (!result.RequiresMobileConfirmation || Bot.HasMobileAuthenticator) && (result.TradeResult.ReceivedItemTypes?.Any(receivedItemType => Bot.BotConfig.LootableTypes.Contains(receivedItemType)) == true)); } - private async Task<(ParseTradeResult? TradeResult, bool RequiresMobileConfirmation)> ParseTrade(Steam.TradeOffer tradeOffer) { + private async Task<(ParseTradeResult? TradeResult, bool RequiresMobileConfirmation)> ParseTrade(TradeOffer tradeOffer) { if (tradeOffer == null) { throw new ArgumentNullException(nameof(tradeOffer)); } @@ -520,7 +522,7 @@ namespace ArchiSteamFarm { return (new ParseTradeResult(tradeOffer.TradeOfferID, result, tradeOffer.ItemsToReceive), tradeRequiresMobileConfirmation); } - private async Task ShouldAcceptTrade(Steam.TradeOffer tradeOffer) { + private async Task ShouldAcceptTrade(TradeOffer tradeOffer) { if (tradeOffer == null) { throw new ArgumentNullException(nameof(tradeOffer)); } @@ -620,7 +622,7 @@ namespace ArchiSteamFarm { // If user has a trade hold, we add extra logic // If trade hold duration exceeds our max, or user asks for cards with short lifespan, reject the trade - case > 0 when (holdDuration.Value > ASF.GlobalConfig.MaxTradeHoldDuration) || tradeOffer.ItemsToGive.Any(item => item.Type is Steam.Asset.EType.FoilTradingCard or Steam.Asset.EType.TradingCard && CardsFarmer.SalesBlacklist.Contains(item.RealAppID)): + case > 0 when (holdDuration.Value > ASF.GlobalConfig.MaxTradeHoldDuration) || tradeOffer.ItemsToGive.Any(item => item.Type is Asset.EType.FoilTradingCard or Asset.EType.TradingCard && CardsFarmer.SalesBlacklist.Contains(item.RealAppID)): Bot.ArchiLogger.LogGenericDebug(string.Format(CultureInfo.CurrentCulture, Strings.BotTradeOfferResult, tradeOffer.TradeOfferID, ParseTradeResult.EResult.Rejected, nameof(holdDuration) + " > 0: " + holdDuration.Value)); return ParseTradeResult.EResult.Rejected; @@ -634,14 +636,14 @@ namespace ArchiSteamFarm { } // Get sets we're interested in - HashSet<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity)> wantedSets = new(); + HashSet<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity)> wantedSets = new(); - foreach (Steam.Asset item in tradeOffer.ItemsToGive) { + foreach (Asset item in tradeOffer.ItemsToGive) { wantedSets.Add((item.RealAppID, item.Type, item.Rarity)); } // Now check if it's worth for us to do the trade - HashSet inventory; + HashSet inventory; try { inventory = await Bot.ArchiWebHandler.GetInventoryAsync().Where(item => wantedSets.Contains((item.RealAppID, item.Type, item.Rarity))).ToHashSetAsync().ConfigureAwait(false); @@ -676,41 +678,5 @@ namespace ArchiSteamFarm { return acceptResult; } - - public sealed class ParseTradeResult { - [PublicAPI] - public EResult Result { get; } - - [PublicAPI] - public ulong TradeOfferID { get; } - - internal readonly ImmutableHashSet? ReceivedItemTypes; - - internal ParseTradeResult(ulong tradeOfferID, EResult result, IReadOnlyCollection? itemsToReceive = null) { - if (tradeOfferID == 0) { - throw new ArgumentOutOfRangeException(nameof(tradeOfferID)); - } - - if ((result == EResult.Unknown) || !Enum.IsDefined(typeof(EResult), result)) { - throw new InvalidEnumArgumentException(nameof(result), (int) result, typeof(EResult)); - } - - TradeOfferID = tradeOfferID; - Result = result; - - if (itemsToReceive?.Count > 0) { - ReceivedItemTypes = itemsToReceive.Select(item => item.Type).ToImmutableHashSet(); - } - } - - public enum EResult : byte { - Unknown, - Accepted, - Blacklisted, - Ignored, - Rejected, - TryAgain - } - } } } diff --git a/ArchiSteamFarm/Web/BasicResponse.cs b/ArchiSteamFarm/Web/BasicResponse.cs new file mode 100644 index 000000000..828aa7410 --- /dev/null +++ b/ArchiSteamFarm/Web/BasicResponse.cs @@ -0,0 +1,52 @@ +// _ _ _ ____ _ _____ +// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___ +// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \ +// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | +// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| +// | +// Copyright 2015-2021 Ł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.Net; +using System.Net.Http; +using JetBrains.Annotations; + +namespace ArchiSteamFarm.Web { + public class BasicResponse { + [PublicAPI] + public HttpStatusCode StatusCode { get; } + + internal readonly Uri FinalUri; + + internal BasicResponse(HttpResponseMessage httpResponseMessage) { + if (httpResponseMessage == null) { + throw new ArgumentNullException(nameof(httpResponseMessage)); + } + + FinalUri = httpResponseMessage.Headers.Location ?? httpResponseMessage.RequestMessage?.RequestUri ?? throw new InvalidOperationException(); + StatusCode = httpResponseMessage.StatusCode; + } + + internal BasicResponse(BasicResponse basicResponse) { + if (basicResponse == null) { + throw new ArgumentNullException(nameof(basicResponse)); + } + + FinalUri = basicResponse.FinalUri; + StatusCode = basicResponse.StatusCode; + } + } +} diff --git a/ArchiSteamFarm/Web/BinaryResponse.cs b/ArchiSteamFarm/Web/BinaryResponse.cs new file mode 100644 index 000000000..f2ce636ad --- /dev/null +++ b/ArchiSteamFarm/Web/BinaryResponse.cs @@ -0,0 +1,38 @@ +// _ _ _ ____ _ _____ +// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___ +// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \ +// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | +// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| +// | +// Copyright 2015-2021 Ł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 JetBrains.Annotations; + +namespace ArchiSteamFarm.Web { + public sealed class BinaryResponse : BasicResponse { + [PublicAPI] + public byte[] Content { get; } + + public BinaryResponse(BasicResponse basicResponse, byte[] content) : base(basicResponse) { + if (basicResponse == null) { + throw new ArgumentNullException(nameof(basicResponse)); + } + + Content = content ?? throw new ArgumentNullException(nameof(content)); + } + } +} diff --git a/ArchiSteamFarm/Web/HtmlDocumentResponse.cs b/ArchiSteamFarm/Web/HtmlDocumentResponse.cs new file mode 100644 index 000000000..9dece4d16 --- /dev/null +++ b/ArchiSteamFarm/Web/HtmlDocumentResponse.cs @@ -0,0 +1,62 @@ +// _ _ _ ____ _ _____ +// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___ +// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \ +// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | +// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| +// | +// Copyright 2015-2021 Ł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.Threading.Tasks; +using AngleSharp; +using AngleSharp.Dom; +using JetBrains.Annotations; + +namespace ArchiSteamFarm.Web { + public sealed class HtmlDocumentResponse : BasicResponse, IDisposable { + [PublicAPI] + public IDocument Content { get; } + + private HtmlDocumentResponse(BasicResponse basicResponse, IDocument content) : base(basicResponse) { + if (basicResponse == null) { + throw new ArgumentNullException(nameof(basicResponse)); + } + + Content = content ?? throw new ArgumentNullException(nameof(content)); + } + + public void Dispose() => Content.Dispose(); + + [PublicAPI] + public static async Task Create(StreamResponse streamResponse) { + if (streamResponse == null) { + throw new ArgumentNullException(nameof(streamResponse)); + } + + IBrowsingContext context = BrowsingContext.New(); + + try { + IDocument document = await context.OpenAsync(req => req.Content(streamResponse.Content, true)).ConfigureAwait(false); + + return new HtmlDocumentResponse(streamResponse, document); + } catch (Exception e) { + ASF.ArchiLogger.LogGenericWarningException(e); + + return null; + } + } + } +} diff --git a/ArchiSteamFarm/Web/ObjectResponse.cs b/ArchiSteamFarm/Web/ObjectResponse.cs new file mode 100644 index 000000000..ad0442c61 --- /dev/null +++ b/ArchiSteamFarm/Web/ObjectResponse.cs @@ -0,0 +1,38 @@ +// _ _ _ ____ _ _____ +// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___ +// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \ +// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | +// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| +// | +// Copyright 2015-2021 Ł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 JetBrains.Annotations; + +namespace ArchiSteamFarm.Web { + public sealed class ObjectResponse : BasicResponse { + [PublicAPI] + public T Content { get; } + + public ObjectResponse(BasicResponse basicResponse, T content) : base(basicResponse) { + if (basicResponse == null) { + throw new ArgumentNullException(nameof(basicResponse)); + } + + Content = content ?? throw new ArgumentNullException(nameof(content)); + } + } +} diff --git a/ArchiSteamFarm/Web/StreamResponse.cs b/ArchiSteamFarm/Web/StreamResponse.cs new file mode 100644 index 000000000..15bd78f19 --- /dev/null +++ b/ArchiSteamFarm/Web/StreamResponse.cs @@ -0,0 +1,55 @@ +// _ _ _ ____ _ _____ +// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___ +// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \ +// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | +// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| +// | +// Copyright 2015-2021 Ł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.IO; +using System.Net.Http; +using System.Threading.Tasks; +using JetBrains.Annotations; + +#if NETFRAMEWORK +using ArchiSteamFarm.RuntimeCompatibility; +#endif + +namespace ArchiSteamFarm.Web { + public sealed class StreamResponse : BasicResponse, IAsyncDisposable { + [PublicAPI] + public Stream Content { get; } + + [PublicAPI] + public long Length { get; } + + private readonly HttpResponseMessage ResponseMessage; + + internal StreamResponse(HttpResponseMessage httpResponseMessage, Stream content) : base(httpResponseMessage) { + ResponseMessage = httpResponseMessage ?? throw new ArgumentNullException(nameof(httpResponseMessage)); + Content = content ?? throw new ArgumentNullException(nameof(content)); + + Length = httpResponseMessage.Content.Headers.ContentLength.GetValueOrDefault(); + } + + public async ValueTask DisposeAsync() { + await Content.DisposeAsync().ConfigureAwait(false); + + ResponseMessage.Dispose(); + } + } +} diff --git a/ArchiSteamFarm/Web/StringResponse.cs b/ArchiSteamFarm/Web/StringResponse.cs new file mode 100644 index 000000000..d33685898 --- /dev/null +++ b/ArchiSteamFarm/Web/StringResponse.cs @@ -0,0 +1,39 @@ +// _ _ _ ____ _ _____ +// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___ +// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \ +// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | +// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| +// | +// Copyright 2015-2021 Ł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.Net.Http; +using JetBrains.Annotations; + +namespace ArchiSteamFarm.Web { + public sealed class StringResponse : BasicResponse { + [PublicAPI] + public string Content { get; } + + internal StringResponse(HttpResponseMessage httpResponseMessage, string content) : base(httpResponseMessage) { + if (httpResponseMessage == null) { + throw new ArgumentNullException(nameof(httpResponseMessage)); + } + + Content = content ?? throw new ArgumentNullException(nameof(content)); + } + } +} diff --git a/ArchiSteamFarm/WebBrowser.cs b/ArchiSteamFarm/Web/WebBrowser.cs similarity index 88% rename from ArchiSteamFarm/WebBrowser.cs rename to ArchiSteamFarm/Web/WebBrowser.cs index abc66cbd7..a5fc57ed6 100644 --- a/ArchiSteamFarm/WebBrowser.cs +++ b/ArchiSteamFarm/Web/WebBrowser.cs @@ -29,14 +29,13 @@ using System.Net.Http; using System.Text; using System.Threading.Tasks; using System.Xml; -using AngleSharp; -using AngleSharp.Dom; using ArchiSteamFarm.Localization; using ArchiSteamFarm.NLog; using JetBrains.Annotations; using Newtonsoft.Json; +using ArchiSteamFarm.RuntimeCompatibility; -namespace ArchiSteamFarm { +namespace ArchiSteamFarm.Web { public sealed class WebBrowser : IDisposable { [PublicAPI] public const byte MaxTries = 5; // Defines maximum number of recommended tries for a single request @@ -76,7 +75,7 @@ namespace ArchiSteamFarm { HttpClientHandler.UseProxy = true; } - if (!RuntimeCompatibility.IsRunningOnMono) { + if (!StaticHelpers.IsRunningOnMono) { HttpClientHandler.MaxConnectionsPerServer = MaxConnections; } @@ -707,7 +706,7 @@ namespace ArchiSteamFarm { ServicePointManager.Expect100Continue = false; // Reuse ports if possible - if (!RuntimeCompatibility.IsRunningOnMono) { + if (!StaticHelpers.IsRunningOnMono) { ServicePointManager.ReusePort = true; } } @@ -896,140 +895,6 @@ namespace ArchiSteamFarm { } } - public class BasicResponse { - [PublicAPI] - public HttpStatusCode StatusCode { get; } - - internal readonly Uri FinalUri; - - internal BasicResponse(HttpResponseMessage httpResponseMessage) { - if (httpResponseMessage == null) { - throw new ArgumentNullException(nameof(httpResponseMessage)); - } - - FinalUri = httpResponseMessage.Headers.Location ?? httpResponseMessage.RequestMessage?.RequestUri ?? throw new InvalidOperationException(); - StatusCode = httpResponseMessage.StatusCode; - } - - internal BasicResponse(BasicResponse basicResponse) { - if (basicResponse == null) { - throw new ArgumentNullException(nameof(basicResponse)); - } - - FinalUri = basicResponse.FinalUri; - StatusCode = basicResponse.StatusCode; - } - } - - public sealed class BinaryResponse : BasicResponse { - [PublicAPI] - public byte[] Content { get; } - - public BinaryResponse(BasicResponse basicResponse, byte[] content) : base(basicResponse) { - if (basicResponse == null) { - throw new ArgumentNullException(nameof(basicResponse)); - } - - Content = content ?? throw new ArgumentNullException(nameof(content)); - } - } - - public sealed class HtmlDocumentResponse : BasicResponse, IDisposable { - [PublicAPI] - public IDocument Content { get; } - - private HtmlDocumentResponse(BasicResponse basicResponse, IDocument content) : base(basicResponse) { - if (basicResponse == null) { - throw new ArgumentNullException(nameof(basicResponse)); - } - - Content = content ?? throw new ArgumentNullException(nameof(content)); - } - - public void Dispose() => Content.Dispose(); - - [PublicAPI] - public static async Task Create(StreamResponse streamResponse) { - if (streamResponse == null) { - throw new ArgumentNullException(nameof(streamResponse)); - } - - IBrowsingContext context = BrowsingContext.New(); - - try { - IDocument document = await context.OpenAsync(req => req.Content(streamResponse.Content, true)).ConfigureAwait(false); - - return new HtmlDocumentResponse(streamResponse, document); - } catch (Exception e) { - ASF.ArchiLogger.LogGenericWarningException(e); - - return null; - } - } - } - - public sealed class ObjectResponse : BasicResponse { - [PublicAPI] - public T Content { get; } - - public ObjectResponse(BasicResponse basicResponse, T content) : base(basicResponse) { - if (basicResponse == null) { - throw new ArgumentNullException(nameof(basicResponse)); - } - - Content = content ?? throw new ArgumentNullException(nameof(content)); - } - } - - public sealed class StreamResponse : BasicResponse, IAsyncDisposable { - [PublicAPI] - public Stream Content { get; } - - [PublicAPI] - public long Length { get; } - - private readonly HttpResponseMessage ResponseMessage; - - internal StreamResponse(HttpResponseMessage httpResponseMessage, Stream content) : base(httpResponseMessage) { - ResponseMessage = httpResponseMessage ?? throw new ArgumentNullException(nameof(httpResponseMessage)); - Content = content ?? throw new ArgumentNullException(nameof(content)); - - Length = httpResponseMessage.Content.Headers.ContentLength.GetValueOrDefault(); - } - - public async ValueTask DisposeAsync() { - await Content.DisposeAsync().ConfigureAwait(false); - - ResponseMessage.Dispose(); - } - } - - public sealed class StringResponse : BasicResponse { - [PublicAPI] - public string Content { get; } - - internal StringResponse(HttpResponseMessage httpResponseMessage, string content) : base(httpResponseMessage) { - if (httpResponseMessage == null) { - throw new ArgumentNullException(nameof(httpResponseMessage)); - } - - Content = content ?? throw new ArgumentNullException(nameof(content)); - } - } - - public sealed class XmlDocumentResponse : BasicResponse { - [PublicAPI] - public XmlDocument Content { get; } - - public XmlDocumentResponse(BasicResponse basicResponse, XmlDocument content) : base(basicResponse) { - if (basicResponse == null) { - throw new ArgumentNullException(nameof(basicResponse)); - } - - Content = content ?? throw new ArgumentNullException(nameof(content)); - } - } - [Flags] public enum ERequestOptions : byte { None = 0, diff --git a/ArchiSteamFarm/Web/XmlDocumentResponse.cs b/ArchiSteamFarm/Web/XmlDocumentResponse.cs new file mode 100644 index 000000000..b747d062e --- /dev/null +++ b/ArchiSteamFarm/Web/XmlDocumentResponse.cs @@ -0,0 +1,39 @@ +// _ _ _ ____ _ _____ +// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___ +// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \ +// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | +// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| +// | +// Copyright 2015-2021 Ł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.Xml; +using JetBrains.Annotations; + +namespace ArchiSteamFarm.Web { + public sealed class XmlDocumentResponse : BasicResponse { + [PublicAPI] + public XmlDocument Content { get; } + + public XmlDocumentResponse(BasicResponse basicResponse, XmlDocument content) : base(basicResponse) { + if (basicResponse == null) { + throw new ArgumentNullException(nameof(basicResponse)); + } + + Content = content ?? throw new ArgumentNullException(nameof(content)); + } + } +}