diff --git a/ArchiSteamFarm.Tests/Bot.cs b/ArchiSteamFarm.Tests/Bot.cs index 125b85f41..92057ec9e 100644 --- a/ArchiSteamFarm.Tests/Bot.cs +++ b/ArchiSteamFarm.Tests/Bot.cs @@ -28,28 +28,6 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; namespace ArchiSteamFarm.Tests { [TestClass] public sealed class Bot { - [TestMethod] - public void MoreCardsThanNeeded() { - const uint appID = 42; - - HashSet items = new() { - CreateCard(1, appID), - CreateCard(1, appID), - CreateCard(2, appID), - CreateCard(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 } - }; - - AssertResultMatchesExpectation(expectedResult, itemsToSend); - } - [TestMethod] public void MaxItemsBarelyEnoughForOneSet() { const uint relevantAppID = 42; @@ -92,43 +70,25 @@ namespace ArchiSteamFarm.Tests { } [TestMethod] - public void TooManyCardsForSingleTrade() { + public void MoreCardsThanNeeded() { const uint appID = 42; - 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); - - Assert.IsTrue(itemsToSend.Count <= ArchiSteamFarm.Trading.MaxItemsPerTrade); - } - - [TestMethod] - public void TooManyCardsForSingleTradeMultipleAppIDs() { - const uint appID0 = 42; - const uint appID1 = 43; - - HashSet items = new(); - - for (byte i = 0; i < 100; i++) { - items.Add(CreateCard(1, appID0)); - items.Add(CreateCard(2, appID0)); - items.Add(CreateCard(1, appID1)); - items.Add(CreateCard(2, appID1)); - } - - Dictionary itemsPerSet = new() { - { appID0, 2 }, - { appID1, 2 } + HashSet items = new() { + CreateCard(1, appID), + CreateCard(1, appID), + CreateCard(2, appID), + CreateCard(3, appID) }; - HashSet itemsToSend = GetItemsForFullBadge(items, itemsPerSet); + HashSet itemsToSend = GetItemsForFullBadge(items, 3, appID); - Assert.IsTrue(itemsToSend.Count <= ArchiSteamFarm.Trading.MaxItemsPerTrade); + 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 } + }; + + AssertResultMatchesExpectation(expectedResult, itemsToSend); } [TestMethod] @@ -459,6 +419,46 @@ namespace ArchiSteamFarm.Tests { AssertResultMatchesExpectation(expectedResult, itemsToSend); } + [TestMethod] + public void TooManyCardsForSingleTrade() { + const uint appID = 42; + + 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); + + Assert.IsTrue(itemsToSend.Count <= ArchiSteamFarm.Trading.MaxItemsPerTrade); + } + + [TestMethod] + public void TooManyCardsForSingleTradeMultipleAppIDs() { + const uint appID0 = 42; + const uint appID1 = 43; + + HashSet items = new(); + + for (byte i = 0; i < 100; i++) { + items.Add(CreateCard(1, appID0)); + items.Add(CreateCard(2, appID0)); + items.Add(CreateCard(1, appID1)); + items.Add(CreateCard(2, appID1)); + } + + Dictionary itemsPerSet = new() { + { appID0, 2 }, + { appID1, 2 } + }; + + HashSet itemsToSend = GetItemsForFullBadge(items, itemsPerSet); + + Assert.IsTrue(itemsToSend.Count <= ArchiSteamFarm.Trading.MaxItemsPerTrade); + } + [TestMethod] [ExpectedException(typeof(InvalidOperationException))] public void TooManyCardsPerSet() { diff --git a/ArchiSteamFarm.sln.DotSettings b/ArchiSteamFarm.sln.DotSettings index d986c1dc4..701f61466 100644 --- a/ArchiSteamFarm.sln.DotSettings +++ b/ArchiSteamFarm.sln.DotSettings @@ -195,6 +195,7 @@ WARNING SUGGESTION + SUGGESTION WARNING SUGGESTION @@ -208,6 +209,9 @@ SUGGESTION SUGGESTION WARNING + SUGGESTION + SUGGESTION + SUGGESTION SUGGESTION SUGGESTION @@ -244,12 +248,15 @@ SUGGESTION SUGGESTION WARNING + SUGGESTION SUGGESTION WARNING SUGGESTION WARNING SUGGESTION WARNING + SUGGESTION + SUGGESTION SUGGESTION SUGGESTION SUGGESTION @@ -276,8 +283,11 @@ SUGGESTION SUGGESTION SUGGESTION + SUGGESTION SUGGESTION + SUGGESTION SUGGESTION + SUGGESTION SUGGESTION WARNING WARNING @@ -287,7 +297,9 @@ Default Default - <?xml version="1.0" encoding="utf-16"?><Profile name="Archi"><CSReorderTypeMembers>True</CSReorderTypeMembers><AspOptimizeRegisterDirectives>True</AspOptimizeRegisterDirectives><HtmlReformatCode>True</HtmlReformatCode><JsInsertSemicolon>True</JsInsertSemicolon><FormatAttributeQuoteDescriptor>True</FormatAttributeQuoteDescriptor><CorrectVariableKindsDescriptor>True</CorrectVariableKindsDescriptor><VariablesToInnerScopesDescriptor>True</VariablesToInnerScopesDescriptor><StringToTemplatesDescriptor>True</StringToTemplatesDescriptor><JsReformatCode>True</JsReformatCode><JsFormatDocComments>True</JsFormatDocComments><RemoveRedundantQualifiersTs>True</RemoveRedundantQualifiersTs><OptimizeImportsTs>True</OptimizeImportsTs><OptimizeReferenceCommentsTs>True</OptimizeReferenceCommentsTs><PublicModifierStyleTs>True</PublicModifierStyleTs><ExplicitAnyTs>True</ExplicitAnyTs><TypeAnnotationStyleTs>True</TypeAnnotationStyleTs><RelativePathStyleTs>True</RelativePathStyleTs><AsInsteadOfCastTs>True</AsInsteadOfCastTs><XMLReformatCode>True</XMLReformatCode><CSCodeStyleAttributes ArrangeTypeAccessModifier="True" ArrangeTypeMemberAccessModifier="True" SortModifiers="True" RemoveRedundantParentheses="True" AddMissingParentheses="True" ArrangeBraces="True" ArrangeAttributes="True" ArrangeArgumentsStyle="True" ArrangeCodeBodyStyle="True" ArrangeVarStyle="True" /><RemoveCodeRedundanciesVB>True</RemoveCodeRedundanciesVB><CssAlphabetizeProperties>True</CssAlphabetizeProperties><VBOptimizeImports>True</VBOptimizeImports><VBShortenReferences>True</VBShortenReferences><RemoveCodeRedundancies>True</RemoveCodeRedundancies><CSUseAutoProperty>True</CSUseAutoProperty><CSMakeFieldReadonly>True</CSMakeFieldReadonly><CSMakeAutoPropertyGetOnly>True</CSMakeAutoPropertyGetOnly><CSArrangeQualifiers>True</CSArrangeQualifiers><CSFixBuiltinTypeReferences>True</CSFixBuiltinTypeReferences><CssReformatCode>True</CssReformatCode><VBReformatCode>True</VBReformatCode><VBFormatDocComments>True</VBFormatDocComments><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings><EmbraceInRegion>False</EmbraceInRegion><RegionName></RegionName></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><CSReformatCode>True</CSReformatCode><CSharpFormatDocComments>True</CSharpFormatDocComments><CSUpdateFileHeader>True</CSUpdateFileHeader></Profile> + <?xml version="1.0" encoding="utf-16"?><Profile name="Archi"><CSReorderTypeMembers>True</CSReorderTypeMembers><AspOptimizeRegisterDirectives>True</AspOptimizeRegisterDirectives><HtmlReformatCode>True</HtmlReformatCode><JsInsertSemicolon>True</JsInsertSemicolon><FormatAttributeQuoteDescriptor>True</FormatAttributeQuoteDescriptor><CorrectVariableKindsDescriptor>True</CorrectVariableKindsDescriptor><VariablesToInnerScopesDescriptor>True</VariablesToInnerScopesDescriptor><StringToTemplatesDescriptor>True</StringToTemplatesDescriptor><JsReformatCode>True</JsReformatCode><JsFormatDocComments>True</JsFormatDocComments><RemoveRedundantQualifiersTs>True</RemoveRedundantQualifiersTs><OptimizeImportsTs>True</OptimizeImportsTs><OptimizeReferenceCommentsTs>True</OptimizeReferenceCommentsTs><PublicModifierStyleTs>True</PublicModifierStyleTs><ExplicitAnyTs>True</ExplicitAnyTs><TypeAnnotationStyleTs>True</TypeAnnotationStyleTs><RelativePathStyleTs>True</RelativePathStyleTs><AsInsteadOfCastTs>True</AsInsteadOfCastTs><XMLReformatCode>True</XMLReformatCode><CSCodeStyleAttributes ArrangeTypeAccessModifier="True" ArrangeTypeMemberAccessModifier="True" SortModifiers="True" RemoveRedundantParentheses="True" AddMissingParentheses="True" ArrangeBraces="True" ArrangeAttributes="True" ArrangeArgumentsStyle="True" ArrangeCodeBodyStyle="True" ArrangeVarStyle="True" ArrangeTrailingCommas="True" ArrangeObjectCreation="True" ArrangeDefaultValue="True" /><RemoveCodeRedundanciesVB>True</RemoveCodeRedundanciesVB><CssAlphabetizeProperties>True</CssAlphabetizeProperties><VBOptimizeImports>True</VBOptimizeImports><VBShortenReferences>True</VBShortenReferences><RemoveCodeRedundancies>True</RemoveCodeRedundancies><CSUseAutoProperty>True</CSUseAutoProperty><CSMakeFieldReadonly>True</CSMakeFieldReadonly><CSMakeAutoPropertyGetOnly>True</CSMakeAutoPropertyGetOnly><CSArrangeQualifiers>True</CSArrangeQualifiers><CSFixBuiltinTypeReferences>True</CSFixBuiltinTypeReferences><CssReformatCode>True</CssReformatCode><VBReformatCode>True</VBReformatCode><VBFormatDocComments>True</VBFormatDocComments><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings><EmbraceInRegion>False</EmbraceInRegion><RegionName></RegionName></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><CSReformatCode>True</CSReformatCode><CSharpFormatDocComments>True</CSharpFormatDocComments><CSUpdateFileHeader>True</CSUpdateFileHeader><Xaml.RedundantFreezeAttribute>True</Xaml.RedundantFreezeAttribute><Xaml.RemoveRedundantModifiersAttribute>True</Xaml.RemoveRedundantModifiersAttribute><Xaml.RemoveRedundantNameAttribute>True</Xaml.RemoveRedundantNameAttribute><Xaml.RemoveRedundantResource>True</Xaml.RemoveRedundantResource><Xaml.RemoveRedundantCollectionProperty>True</Xaml.RemoveRedundantCollectionProperty><Xaml.RemoveRedundantAttachedPropertySetter>True</Xaml.RemoveRedundantAttachedPropertySetter><Xaml.RemoveRedundantStyledValue>True</Xaml.RemoveRedundantStyledValue><Xaml.RemoveRedundantNamespaceAlias>True</Xaml.RemoveRedundantNamespaceAlias><Xaml.RemoveForbiddenResourceName>True</Xaml.RemoveForbiddenResourceName><Xaml.RemoveRedundantGridDefinitionsAttribute>True</Xaml.RemoveRedundantGridDefinitionsAttribute><Xaml.RemoveRedundantGridSpanAttribut>True</Xaml.RemoveRedundantGridSpanAttribut><Xaml.RemoveRedundantUpdateSourceTriggerAttribute>True</Xaml.RemoveRedundantUpdateSourceTriggerAttribute><Xaml.RemoveRedundantBindingModeAttribute>True</Xaml.RemoveRedundantBindingModeAttribute><CppAddTypenameTemplateKeywords>True</CppAddTypenameTemplateKeywords><CppJoinDeclarationAndAssignmentDescriptor>True</CppJoinDeclarationAndAssignmentDescriptor><CppMakeLocalVarConstDescriptor>True</CppMakeLocalVarConstDescriptor><CppMakeMethodConst>True</CppMakeMethodConst><CppMakeMethodStatic>True</CppMakeMethodStatic><CppRemoveElseKeyword>True</CppRemoveElseKeyword><CppRemoveRedundantMemberInitializerDescriptor>True</CppRemoveRedundantMemberInitializerDescriptor><CppRemoveRedundantParentheses>True</CppRemoveRedundantParentheses><CppShortenQualifiedName>True</CppShortenQualifiedName><CppDeleteRedundantSpecifier>True</CppDeleteRedundantSpecifier><CppRemoveStatement>True</CppRemoveStatement><CppRemoveTemplateArgumentsDescriptor>True</CppRemoveTemplateArgumentsDescriptor><CppDeleteRedundantTypenameTemplateKeywords>True</CppDeleteRedundantTypenameTemplateKeywords><CppRemoveUnreachableCode>True</CppRemoveUnreachableCode><CppRemoveUnusedIncludes>True</CppRemoveUnusedIncludes><CppRemoveUnusedLambdaCaptures>True</CppRemoveUnusedLambdaCaptures><CppCStyleToStaticCastDescriptor>True</CppCStyleToStaticCastDescriptor><CppReplaceExpressionWithBooleanConst>True</CppReplaceExpressionWithBooleanConst><CppMakeIfConstexpr>True</CppMakeIfConstexpr><CppMakePostfixOperatorPrefix>True</CppMakePostfixOperatorPrefix><CppChangeSmartPointerToMakeFunction>True</CppChangeSmartPointerToMakeFunction><CppReplaceThrowWithRethrowFix>True</CppReplaceThrowWithRethrowFix><CppReplaceExpressionWithNullptr>True</CppReplaceExpressionWithNullptr><CppCodeStyleCleanupDescriptor ArrangeAuto="True" ArrangeBraces="True" ArrangeCVQualifiers="True" ArrangeFunctionDeclarations="True" ArrangeNestedNamespaces="True" ArrangeOverridingFunctions="True" ArrangeSlashesInIncludeDirectives="True" ArrangeTypeAliases="True" SortIncludeDirectives="True" SortMemberInitializers="True" /><CppReformatCode>True</CppReformatCode><CppUpdateFileHeader>True</CppUpdateFileHeader><IDEA_SETTINGS>&lt;profile version="1.0"&gt; + &lt;option name="myName" value="Archi" /&gt; +&lt;/profile&gt;</IDEA_SETTINGS></Profile> Archi USE_TABS_ONLY True diff --git a/ArchiSteamFarm/ASF.cs b/ArchiSteamFarm/ASF.cs index 37b49440e..eb2e9d5d4 100644 --- a/ArchiSteamFarm/ASF.cs +++ b/ArchiSteamFarm/ASF.cs @@ -835,7 +835,7 @@ namespace ArchiSteamFarm { // Ensure that we ask for a list of servers if we don't have any saved servers available IEnumerable servers = await GlobalDatabase.ServerListProvider.FetchServerListAsync().ConfigureAwait(false); - if (servers?.Any() != true) { + if (!servers.Any()) { ArchiLogger.LogGenericInfo(string.Format(CultureInfo.CurrentCulture, Strings.Initializing, nameof(SteamDirectory))); SteamConfiguration steamConfiguration = SteamConfiguration.Create(builder => builder.WithProtocolTypes(GlobalConfig.SteamProtocols).WithCellID(GlobalDatabase.CellID).WithServerListProvider(GlobalDatabase.ServerListProvider).WithHttpClientFactory(() => WebBrowser.GenerateDisposableHttpClient())); diff --git a/ArchiSteamFarm/ArchiWebHandler.cs b/ArchiSteamFarm/ArchiWebHandler.cs index 04ba72d50..5c1ba55cd 100644 --- a/ArchiSteamFarm/ArchiWebHandler.cs +++ b/ArchiSteamFarm/ArchiWebHandler.cs @@ -2193,10 +2193,6 @@ namespace ArchiSteamFarm { } } - if (response == null) { - return false; - } - string? steamLogin = response["token"].AsString(); if (string.IsNullOrEmpty(steamLogin)) { @@ -2475,11 +2471,9 @@ namespace ArchiSteamFarm { // The page should have as little internal dependencies as possible, since every extra chunk increases likelihood of broken functionality. We can only make a guess here based on the amount of content that the page returns to us // It should also be URL with fairly fixed address that isn't going to disappear anytime soon, preferably something staple that is a dependency of other requests, so it's very unlikely to change in a way that would add overhead in the future // 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 host = SteamStoreURL; const string request = "/account"; - WebBrowser.BasicResponse? response = await WebLimitRequest(host, async () => await WebBrowser.UrlHead(host + request).ConfigureAwait(false)).ConfigureAwait(false); + WebBrowser.BasicResponse? response = await WebLimitRequest(SteamStoreURL, async () => await WebBrowser.UrlHead(SteamStoreURL + request).ConfigureAwait(false)).ConfigureAwait(false); if (response == null) { return null; diff --git a/ArchiSteamFarm/Bot.cs b/ArchiSteamFarm/Bot.cs index fe14ab21d..eece0aaa0 100755 --- a/ArchiSteamFarm/Bot.cs +++ b/ArchiSteamFarm/Bot.cs @@ -625,7 +625,7 @@ namespace ArchiSteamFarm { HashSet?>> tasks = new(maxPages - 1); for (byte page = 2; page <= maxPages; page++) { - // We need a copy of variable being passed when in for loops, as loop will proceed before our task is launched + // ReSharper disable once InlineTemporaryVariable - we need a copy of variable being passed when in for loops, as loop will proceed before our task is launched byte currentPage = page; tasks.Add(GetPossiblyCompletedBadgeAppIDs(currentPage)); } @@ -1900,6 +1900,61 @@ namespace ArchiSteamFarm { return keys; } + private async Task?> GetPossiblyCompletedBadgeAppIDs(byte page) { + if (page == 0) { + throw new ArgumentOutOfRangeException(nameof(page)); + } + + using IDocument? badgePage = await ArchiWebHandler.GetBadgePage(page).ConfigureAwait(false); + + if (badgePage == null) { + ArchiLogger.LogGenericWarning(Strings.WarningCouldNotCheckBadges); + + return null; + } + + return GetPossiblyCompletedBadgeAppIDs(badgePage); + } + + private HashSet? GetPossiblyCompletedBadgeAppIDs(IDocument badgePage) { + if (badgePage == null) { + throw new ArgumentNullException(nameof(badgePage)); + } + + List linkElements = badgePage.SelectNodes("//a[@class='badge_craft_button']"); + + // We need to also select all badges that we have max level, as those will not display with a craft button + // Level 5 is maximum level for card badges according to https://steamcommunity.com/tradingcards/faq + linkElements.AddRange(badgePage.SelectNodes("//div[@class='badges_sheet']/div[contains(@class, 'badge_row') and .//div[@class='badge_info_description']/div[contains(text(), 'Level 5')]]/a[@class='badge_row_overlay']")); + + if (linkElements.Count == 0) { + return new HashSet(0); + } + + HashSet result = new(linkElements.Count); + + foreach (string? badgeUri in linkElements.Select(htmlNode => htmlNode.GetAttribute("href"))) { + if (string.IsNullOrEmpty(badgeUri)) { + ArchiLogger.LogNullError(nameof(badgeUri)); + + return null; + } + + // URIs to foil badges are the same as for normal badges except they end with "?border=1" + string appIDText = badgeUri.Split('?', StringSplitOptions.RemoveEmptyEntries)[0].Split('/', StringSplitOptions.RemoveEmptyEntries)[^1]; + + if (!uint.TryParse(appIDText, out uint appID) || (appID == 0)) { + ArchiLogger.LogNullError(nameof(appID)); + + return null; + } + + result.Add(appID); + } + + return result; + } + private void HandleCallbacks() { TimeSpan timeSpan = TimeSpan.FromMilliseconds(CallbackSleep); @@ -2595,6 +2650,18 @@ namespace ArchiSteamFarm { await Commands.HandleMessage(notification.steamid_friend, message).ConfigureAwait(false); } + private void OnInventoryChanged() { + Utilities.InBackground(CardsFarmer.OnNewItemsNotification); + + if (BotConfig.BotBehaviour.HasFlag(BotConfig.EBotBehaviour.DismissInventoryNotifications)) { + Utilities.InBackground(ArchiWebHandler.MarkInventory); + } + + if (BotConfig.CompleteTypesToSend.Count > 0) { + Utilities.InBackground(SendCompletedSets); + } + } + private async void OnLicenseList(SteamApps.LicenseListCallback callback) { if (callback == null) { throw new ArgumentNullException(nameof(callback)); @@ -3130,149 +3197,6 @@ namespace ArchiSteamFarm { } } - private void OnInventoryChanged() { - Utilities.InBackground(CardsFarmer.OnNewItemsNotification); - - if (BotConfig.BotBehaviour.HasFlag(BotConfig.EBotBehaviour.DismissInventoryNotifications)) { - Utilities.InBackground(ArchiWebHandler.MarkInventory); - } - - if (BotConfig.CompleteTypesToSend.Count > 0) { - Utilities.InBackground(SendCompletedSets); - } - } - - private async Task?> GetPossiblyCompletedBadgeAppIDs(byte page) { - if (page == 0) { - throw new ArgumentOutOfRangeException(nameof(page)); - } - - using IDocument? badgePage = await ArchiWebHandler.GetBadgePage(page).ConfigureAwait(false); - - if (badgePage == null) { - ArchiLogger.LogGenericWarning(Strings.WarningCouldNotCheckBadges); - - return null; - } - - return GetPossiblyCompletedBadgeAppIDs(badgePage); - } - - private HashSet? GetPossiblyCompletedBadgeAppIDs(IDocument badgePage) { - if (badgePage == null) { - throw new ArgumentNullException(nameof(badgePage)); - } - - List linkElements = badgePage.SelectNodes("//a[@class='badge_craft_button']"); - - // We need to also select all badges that we have max level, as those will not display with a craft button - // Level 5 is maximum level for card badges according to https://steamcommunity.com/tradingcards/faq - linkElements.AddRange(badgePage.SelectNodes("//div[@class='badges_sheet']/div[contains(@class, 'badge_row') and .//div[@class='badge_info_description']/div[contains(text(), 'Level 5')]]/a[@class='badge_row_overlay']")); - - if (linkElements.Count == 0) { - return new HashSet(0); - } - - HashSet result = new(linkElements.Count); - - foreach (string? badgeUri in linkElements.Select(htmlNode => htmlNode.GetAttribute("href"))) { - if (string.IsNullOrEmpty(badgeUri)) { - ArchiLogger.LogNullError(nameof(badgeUri)); - - return null; - } - - // URIs to foil badges are the same as for normal badges except they end with "?border=1" - string appIDText = badgeUri.Split('?', StringSplitOptions.RemoveEmptyEntries)[0].Split('/', StringSplitOptions.RemoveEmptyEntries)[^1]; - - if (!uint.TryParse(appIDText, out uint appID) || (appID == 0)) { - ArchiLogger.LogNullError(nameof(appID)); - - return null; - } - - result.Add(appID); - } - - return result; - } - - private async Task SendCompletedSets() { - lock (SendCompleteTypesSemaphore) { - if (SendCompleteTypesScheduled) { - return; - } - - SendCompleteTypesScheduled = true; - } - - await SendCompleteTypesSemaphore.WaitAsync().ConfigureAwait(false); - - try { - using (await Actions.GetTradingLock().ConfigureAwait(false)) { - lock (SendCompleteTypesSemaphore) { - SendCompleteTypesScheduled = false; - } - - HashSet? appIDs = await GetPossiblyCompletedBadgeAppIDs().ConfigureAwait(false); - - if ((appIDs == null) || (appIDs.Count == 0)) { - return; - } - - HashSet inventory; - - try { - inventory = await ArchiWebHandler.GetInventoryAsync() - .Where(item => item.Tradable && appIDs.Contains(item.RealAppID) && BotConfig.CompleteTypesToSend.Contains(item.Type)) - .ToHashSetAsync() - .ConfigureAwait(false); - } catch (HttpRequestException e) { - ArchiLogger.LogGenericWarningException(e); - - return; - } catch (Exception e) { - ArchiLogger.LogGenericException(e); - - return; - } - - if (inventory.Count == 0) { - ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsEmpty, nameof(inventory))); - - return; - } - - Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.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) { - return; - } - - Dictionary? cardCountPerAppID = await LoadCardsPerSet(appIDs).ConfigureAwait(false); - - if ((cardCountPerAppID == null) || (cardCountPerAppID.Count == 0)) { - 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])); - - if (itemsToTakePerInventorySet.Values.All(value => value.Sets == 0)) { - return; - } - - HashSet result = GetItemsForFullSets(inventory, itemsToTakePerInventorySet); - - if (result.Count > 0) { - await Actions.SendInventory(result).ConfigureAwait(false); - } - } - } finally { - SendCompleteTypesSemaphore.Release(); - } - } - private void OnVanityURLChangedCallback(ArchiHandler.VanityURLChangedCallback callback) { if (callback == null) { throw new ArgumentNullException(nameof(callback)); @@ -3439,6 +3363,82 @@ namespace ArchiSteamFarm { StopPlayingWasBlockedTimer(); } + private async Task SendCompletedSets() { + lock (SendCompleteTypesSemaphore) { + if (SendCompleteTypesScheduled) { + return; + } + + SendCompleteTypesScheduled = true; + } + + await SendCompleteTypesSemaphore.WaitAsync().ConfigureAwait(false); + + try { + using (await Actions.GetTradingLock().ConfigureAwait(false)) { + lock (SendCompleteTypesSemaphore) { + SendCompleteTypesScheduled = false; + } + + HashSet? appIDs = await GetPossiblyCompletedBadgeAppIDs().ConfigureAwait(false); + + if ((appIDs == null) || (appIDs.Count == 0)) { + return; + } + + HashSet inventory; + + try { + inventory = await ArchiWebHandler.GetInventoryAsync() + .Where(item => item.Tradable && appIDs.Contains(item.RealAppID) && BotConfig.CompleteTypesToSend.Contains(item.Type)) + .ToHashSetAsync() + .ConfigureAwait(false); + } catch (HttpRequestException e) { + ArchiLogger.LogGenericWarningException(e); + + return; + } catch (Exception e) { + ArchiLogger.LogGenericException(e); + + return; + } + + if (inventory.Count == 0) { + ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsEmpty, nameof(inventory))); + + return; + } + + Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.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) { + return; + } + + Dictionary? cardCountPerAppID = await LoadCardsPerSet(appIDs).ConfigureAwait(false); + + if ((cardCountPerAppID == null) || (cardCountPerAppID.Count == 0)) { + 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])); + + if (itemsToTakePerInventorySet.Values.All(value => value.Sets == 0)) { + return; + } + + HashSet result = GetItemsForFullSets(inventory, itemsToTakePerInventorySet); + + if (result.Count > 0) { + await Actions.SendInventory(result).ConfigureAwait(false); + } + } + } finally { + SendCompleteTypesSemaphore.Release(); + } + } + private bool ShouldAckChatMessage(ulong steamID) { if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) { throw new ArgumentOutOfRangeException(nameof(steamID)); @@ -3511,7 +3511,7 @@ namespace ArchiSteamFarm { byte i = 0; byte[] password = new byte[steamParentalCode.Length]; - foreach (char character in steamParentalCode.TakeWhile(character => (character >= '0') && (character <= '9'))) { + foreach (char character in steamParentalCode.TakeWhile(character => character is >= '0' and <= '9')) { password[i++] = (byte) character; } diff --git a/ArchiSteamFarm/BotConfig.cs b/ArchiSteamFarm/BotConfig.cs index 2b142cc5b..12a53b48b 100644 --- a/ArchiSteamFarm/BotConfig.cs +++ b/ArchiSteamFarm/BotConfig.cs @@ -48,10 +48,10 @@ namespace ArchiSteamFarm { public const EBotBehaviour DefaultBotBehaviour = EBotBehaviour.None; [PublicAPI] - public const string DefaultCustomGamePlayedWhileFarming = null; + public const string? DefaultCustomGamePlayedWhileFarming = null; [PublicAPI] - public const string DefaultCustomGamePlayedWhileIdle = null; + public const string? DefaultCustomGamePlayedWhileIdle = null; [PublicAPI] public const bool DefaultEnabled = false; @@ -87,19 +87,19 @@ namespace ArchiSteamFarm { public const bool DefaultShutdownOnFarmingFinished = false; [PublicAPI] - public const string DefaultSteamLogin = null; + public const string? DefaultSteamLogin = null; [PublicAPI] public const ulong DefaultSteamMasterClanID = 0; [PublicAPI] - public const string DefaultSteamParentalCode = null; + public const string? DefaultSteamParentalCode = null; [PublicAPI] - public const string DefaultSteamPassword = null; + public const string? DefaultSteamPassword = null; [PublicAPI] - public const string DefaultSteamTradeToken = null; + public const string? DefaultSteamTradeToken = null; [PublicAPI] public const ETradingPreferences DefaultTradingPreferences = ETradingPreferences.None; diff --git a/ArchiSteamFarm/CardsFarmer.cs b/ArchiSteamFarm/CardsFarmer.cs index 07a77fc5a..b7301a4d9 100755 --- a/ArchiSteamFarm/CardsFarmer.cs +++ b/ArchiSteamFarm/CardsFarmer.cs @@ -669,7 +669,7 @@ namespace ArchiSteamFarm { levelText = levelText[levelStartIndex..levelEndIndex]; - if (!byte.TryParse(levelText, out badgeLevel) || (badgeLevel == 0) || (badgeLevel > 5)) { + if (!byte.TryParse(levelText, out badgeLevel) || badgeLevel is 0 or > 5) { Bot.ArchiLogger.LogNullError(nameof(badgeLevel)); continue; @@ -1041,7 +1041,7 @@ namespace ArchiSteamFarm { Bot.ArchiLogger.LogGenericInfo(Strings.CheckingOtherBadgePages); for (byte page = 2; page <= maxPages; page++) { - // We need a copy of variable being passed when in for loops, as loop will proceed before our task is launched + // ReSharper disable once InlineTemporaryVariable - we need a copy of variable being passed when in for loops, as loop will proceed before our task is launched byte currentPage = page; tasks.Add(CheckPage(currentPage, parsedAppIDs)); } diff --git a/ArchiSteamFarm/GitHub.cs b/ArchiSteamFarm/GitHub.cs index 0fae1f4b0..7868ba9e8 100644 --- a/ArchiSteamFarm/GitHub.cs +++ b/ArchiSteamFarm/GitHub.cs @@ -160,7 +160,7 @@ namespace ArchiSteamFarm { MarkdownDocument markdownDocument = Markdown.Parse(markdownText); MarkdownDocument result = new(); - foreach (Block block in markdownDocument.SkipWhile(block => block is not HeadingBlock headingBlock || headingBlock.Inline?.FirstChild is not LiteralInline literalInline || !literalInline.Content.ToString().Equals("Changelog", StringComparison.OrdinalIgnoreCase)).Skip(1).TakeWhile(block => block is not ThematicBreakBlock).ToList()) { + foreach (Block block in markdownDocument.SkipWhile(block => block is not HeadingBlock { Inline: { FirstChild: LiteralInline literalInline } } || !literalInline.Content.ToString().Equals("Changelog", StringComparison.OrdinalIgnoreCase)).Skip(1).TakeWhile(block => block is not ThematicBreakBlock).ToList()) { // All blocks that we're interested in must be removed from original markdownDocument firstly markdownDocument.Remove(block); result.Add(block); diff --git a/ArchiSteamFarm/GlobalConfig.cs b/ArchiSteamFarm/GlobalConfig.cs index af200c969..80c8ac6e6 100644 --- a/ArchiSteamFarm/GlobalConfig.cs +++ b/ArchiSteamFarm/GlobalConfig.cs @@ -41,7 +41,7 @@ namespace ArchiSteamFarm { public const bool DefaultAutoRestart = true; [PublicAPI] - public const string DefaultCommandPrefix = "!"; + public const string? DefaultCommandPrefix = "!"; [PublicAPI] public const byte DefaultConfirmationsLimiterDelay = 10; @@ -50,7 +50,7 @@ namespace ArchiSteamFarm { public const byte DefaultConnectionTimeout = 90; [PublicAPI] - public const string DefaultCurrentCulture = null; + public const string? DefaultCurrentCulture = null; [PublicAPI] public const bool DefaultDebug = false; @@ -74,7 +74,7 @@ namespace ArchiSteamFarm { public const bool DefaultIPC = false; [PublicAPI] - public const string DefaultIPCPassword = null; + public const string? DefaultIPCPassword = null; [PublicAPI] public const ArchiCryptoHelper.EHashingMethod DefaultIPCPasswordFormat = ArchiCryptoHelper.EHashingMethod.PlainText; @@ -95,7 +95,7 @@ namespace ArchiSteamFarm { public const bool DefaultStatistics = true; [PublicAPI] - public const string DefaultSteamMessagePrefix = "/me "; + public const string? DefaultSteamMessagePrefix = "/me "; [PublicAPI] public const ulong DefaultSteamOwnerID = 0; @@ -113,13 +113,13 @@ namespace ArchiSteamFarm { public const ushort DefaultWebLimiterDelay = 300; [PublicAPI] - public const string DefaultWebProxyPassword = null; + public const string? DefaultWebProxyPassword = null; [PublicAPI] - public const string DefaultWebProxyText = null; + public const string? DefaultWebProxyText = null; [PublicAPI] - public const string DefaultWebProxyUsername = null; + public const string? DefaultWebProxyUsername = null; [PublicAPI] public static readonly ImmutableHashSet DefaultBlacklist = ImmutableHashSet.Empty; @@ -329,7 +329,7 @@ namespace ArchiSteamFarm { return (false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorConfigPropertyInvalid, nameof(SteamOwnerID), SteamOwnerID)); } - if ((SteamProtocols <= 0) || (SteamProtocols > ProtocolTypes.All)) { + if (SteamProtocols is <= 0 or > ProtocolTypes.All) { return (false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorConfigPropertyInvalid, nameof(SteamProtocols), SteamProtocols)); } diff --git a/ArchiSteamFarm/IPC/Controllers/Api/ASFController.cs b/ArchiSteamFarm/IPC/Controllers/Api/ASFController.cs index 537a22ef7..390c1c938 100644 --- a/ArchiSteamFarm/IPC/Controllers/Api/ASFController.cs +++ b/ArchiSteamFarm/IPC/Controllers/Api/ASFController.cs @@ -123,7 +123,7 @@ namespace ArchiSteamFarm.IPC.Controllers.Api { request.GlobalConfig.WebProxyPassword = ASF.GlobalConfig.WebProxyPassword; } - if ((ASF.GlobalConfig.AdditionalProperties != null) && (ASF.GlobalConfig.AdditionalProperties.Count > 0)) { + if (ASF.GlobalConfig.AdditionalProperties is { Count: > 0 }) { request.GlobalConfig.AdditionalProperties ??= new Dictionary(ASF.GlobalConfig.AdditionalProperties.Count, ASF.GlobalConfig.AdditionalProperties.Comparer); foreach ((string key, JToken value) in ASF.GlobalConfig.AdditionalProperties.Where(property => !request.GlobalConfig.AdditionalProperties.ContainsKey(property.Key))) { diff --git a/ArchiSteamFarm/IPC/Controllers/Api/WWWController.cs b/ArchiSteamFarm/IPC/Controllers/Api/GitHubController.cs similarity index 96% rename from ArchiSteamFarm/IPC/Controllers/Api/WWWController.cs rename to ArchiSteamFarm/IPC/Controllers/Api/GitHubController.cs index e34f81933..3e7ceb1ed 100644 --- a/ArchiSteamFarm/IPC/Controllers/Api/WWWController.cs +++ b/ArchiSteamFarm/IPC/Controllers/Api/GitHubController.cs @@ -30,60 +30,15 @@ using ArchiSteamFarm.Localization; using Microsoft.AspNetCore.Mvc; namespace ArchiSteamFarm.IPC.Controllers.Api { - [Route("Api/WWW")] - public sealed class WWWController : ArchiController { - /// - /// Fetches history of specific GitHub page from ASF project. - /// - /// - /// This is internal API being utilizied by our ASF-ui IPC frontend. You should not depend on existence of any /Api/WWW endpoints as they can disappear and change anytime. - /// - [HttpGet("GitHub/Wiki/History/{page:required}")] - [ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.OK)] - [ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.BadRequest)] - [ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.ServiceUnavailable)] - public async Task> GitHubWikiHistoryGet(string page) { - if (string.IsNullOrEmpty(page)) { - throw new ArgumentNullException(nameof(page)); - } - - Dictionary? revisions = await GitHub.GetWikiHistory(page).ConfigureAwait(false); - - return revisions != null ? revisions.Count > 0 ? Ok(new GenericResponse>(revisions.ToImmutableDictionary())) : BadRequest(new GenericResponse(false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsInvalid, nameof(page)))) : StatusCode((int) HttpStatusCode.ServiceUnavailable, new GenericResponse(false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorRequestFailedTooManyTimes, WebBrowser.MaxTries))); - } - - /// - /// Fetches specific GitHub page of ASF project. - /// - /// - /// This is internal API being utilizied by our ASF-ui IPC frontend. You should not depend on existence of any /Api/WWW endpoints as they can disappear and change anytime. - /// Specifying revision is optional - when not specified, will fetch latest available. If specified revision is invalid, GitHub will automatically fetch the latest revision as well. - /// - [HttpGet("GitHub/Wiki/Page/{page:required}")] - [ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.OK)] - [ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.BadRequest)] - [ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.ServiceUnavailable)] - public async Task> GitHubWikiPageGet(string page, [FromQuery] string? revision = null) { - if (string.IsNullOrEmpty(page)) { - throw new ArgumentNullException(nameof(page)); - } - - string? html = await GitHub.GetWikiPage(page, revision).ConfigureAwait(false); - - return html switch { - null => StatusCode((int) HttpStatusCode.ServiceUnavailable, new GenericResponse(false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorRequestFailedTooManyTimes, WebBrowser.MaxTries))), - "" => BadRequest(new GenericResponse(false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsInvalid, nameof(page)))), - _ => Ok(new GenericResponse(html)) - }; - } - + [Route("Api/WWW/GitHub")] + public sealed class GitHubController : ArchiController { /// /// Fetches the most recent GitHub release of ASF project. /// /// /// This is internal API being utilizied by our ASF-ui IPC frontend. You should not depend on existence of any /Api/WWW endpoints as they can disappear and change anytime. /// - [HttpGet("GitHub/Release")] + [HttpGet("Release")] [ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.OK)] [ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.ServiceUnavailable)] public async Task> GitHubReleaseGet() { @@ -98,7 +53,7 @@ namespace ArchiSteamFarm.IPC.Controllers.Api { /// /// This is internal API being utilizied by our ASF-ui IPC frontend. You should not depend on existence of any /Api/WWW endpoints as they can disappear and change anytime. /// - [HttpGet("GitHub/Release/{version:required}")] + [HttpGet("Release/{version:required}")] [ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.OK)] [ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.BadRequest)] [ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.ServiceUnavailable)] @@ -126,5 +81,50 @@ namespace ArchiSteamFarm.IPC.Controllers.Api { return releaseResponse != null ? Ok(new GenericResponse(new GitHubReleaseResponse(releaseResponse))) : StatusCode((int) HttpStatusCode.ServiceUnavailable, new GenericResponse(false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorRequestFailedTooManyTimes, WebBrowser.MaxTries))); } + + /// + /// Fetches history of specific GitHub page from ASF project. + /// + /// + /// This is internal API being utilizied by our ASF-ui IPC frontend. You should not depend on existence of any /Api/WWW endpoints as they can disappear and change anytime. + /// + [HttpGet("Wiki/History/{page:required}")] + [ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.OK)] + [ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.BadRequest)] + [ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.ServiceUnavailable)] + public async Task> GitHubWikiHistoryGet(string page) { + if (string.IsNullOrEmpty(page)) { + throw new ArgumentNullException(nameof(page)); + } + + Dictionary? revisions = await GitHub.GetWikiHistory(page).ConfigureAwait(false); + + return revisions != null ? revisions.Count > 0 ? Ok(new GenericResponse>(revisions.ToImmutableDictionary())) : BadRequest(new GenericResponse(false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsInvalid, nameof(page)))) : StatusCode((int) HttpStatusCode.ServiceUnavailable, new GenericResponse(false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorRequestFailedTooManyTimes, WebBrowser.MaxTries))); + } + + /// + /// Fetches specific GitHub page of ASF project. + /// + /// + /// This is internal API being utilizied by our ASF-ui IPC frontend. You should not depend on existence of any /Api/WWW endpoints as they can disappear and change anytime. + /// Specifying revision is optional - when not specified, will fetch latest available. If specified revision is invalid, GitHub will automatically fetch the latest revision as well. + /// + [HttpGet("Wiki/Page/{page:required}")] + [ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.OK)] + [ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.BadRequest)] + [ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.ServiceUnavailable)] + public async Task> GitHubWikiPageGet(string page, [FromQuery] string? revision = null) { + if (string.IsNullOrEmpty(page)) { + throw new ArgumentNullException(nameof(page)); + } + + string? html = await GitHub.GetWikiPage(page, revision).ConfigureAwait(false); + + return html switch { + null => StatusCode((int) HttpStatusCode.ServiceUnavailable, new GenericResponse(false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorRequestFailedTooManyTimes, WebBrowser.MaxTries))), + "" => BadRequest(new GenericResponse(false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsInvalid, nameof(page)))), + _ => Ok(new GenericResponse(html)) + }; + } } } diff --git a/ArchiSteamFarm/IPC/Integration/EnumSchemaFilter.cs b/ArchiSteamFarm/IPC/Integration/EnumSchemaFilter.cs index 29e5881ae..8abde3080 100644 --- a/ArchiSteamFarm/IPC/Integration/EnumSchemaFilter.cs +++ b/ArchiSteamFarm/IPC/Integration/EnumSchemaFilter.cs @@ -39,7 +39,7 @@ namespace ArchiSteamFarm.IPC.Integration { throw new ArgumentNullException(nameof(context)); } - if ((context.Type == null) || !context.Type.IsEnum) { + if (context.Type is not { IsEnum: true }) { return; } diff --git a/ArchiSteamFarm/NLog/Logging.cs b/ArchiSteamFarm/NLog/Logging.cs index 1fb99ebb7..1beb6f205 100644 --- a/ArchiSteamFarm/NLog/Logging.cs +++ b/ArchiSteamFarm/NLog/Logging.cs @@ -371,7 +371,7 @@ namespace ArchiSteamFarm.NLog { private static void InitConsoleLoggers() { ConsoleLoggingRules.Clear(); - foreach (LoggingRule loggingRule in LogManager.Configuration.LoggingRules.Where(loggingRule => loggingRule.Targets.Any(target => target is ColoredConsoleTarget || target is ConsoleTarget))) { + foreach (LoggingRule loggingRule in LogManager.Configuration.LoggingRules.Where(loggingRule => loggingRule.Targets.Any(target => target is ColoredConsoleTarget or ConsoleTarget))) { ConsoleLoggingRules.Add(loggingRule); } } diff --git a/ArchiSteamFarm/Plugins/PluginsCore.cs b/ArchiSteamFarm/Plugins/PluginsCore.cs index 4e4c46955..db39a7b9b 100644 --- a/ArchiSteamFarm/Plugins/PluginsCore.cs +++ b/ArchiSteamFarm/Plugins/PluginsCore.cs @@ -38,7 +38,7 @@ using SteamKit2; namespace ArchiSteamFarm.Plugins { internal static class PluginsCore { - internal static bool HasCustomPluginsLoaded => ActivePlugins?.Any(plugin => !(plugin is OfficialPlugin officialPlugin) || !officialPlugin.HasSameVersion()) == true; + internal static bool HasCustomPluginsLoaded => ActivePlugins?.Any(plugin => plugin is not OfficialPlugin officialPlugin || !officialPlugin.HasSameVersion()) == true; [ImportMany] internal static ImmutableHashSet? ActivePlugins { get; private set; } diff --git a/ArchiSteamFarm/Statistics.cs b/ArchiSteamFarm/Statistics.cs index be5ec75ec..dcbb585f1 100644 --- a/ArchiSteamFarm/Statistics.cs +++ b/ArchiSteamFarm/Statistics.cs @@ -434,7 +434,7 @@ namespace ArchiSteamFarm { 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 == Steam.Asset.EType.FoilTradingCard) || (item.Type == 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 Steam.Asset.EType.FoilTradingCard or Steam.Asset.EType.TradingCard && CardsFarmer.SalesBlacklist.Contains(item.RealAppID)))).ToHashSetAsync().ConfigureAwait(false); } catch (HttpRequestException e) { Bot.ArchiLogger.LogGenericWarningException(e); diff --git a/ArchiSteamFarm/Trading.cs b/ArchiSteamFarm/Trading.cs index 9ef3e12cb..70c750372 100644 --- a/ArchiSteamFarm/Trading.cs +++ b/ArchiSteamFarm/Trading.cs @@ -620,7 +620,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 == Steam.Asset.EType.FoilTradingCard) || (item.Type == Steam.Asset.EType.TradingCard)) && CardsFarmer.SalesBlacklist.Contains(item.RealAppID)): + 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)): Bot.ArchiLogger.LogGenericDebug(string.Format(CultureInfo.CurrentCulture, Strings.BotTradeOfferResult, tradeOffer.TradeOfferID, ParseTradeResult.EResult.Rejected, nameof(holdDuration) + " > 0: " + holdDuration.Value)); return ParseTradeResult.EResult.Rejected; diff --git a/ArchiSteamFarm/Utilities.cs b/ArchiSteamFarm/Utilities.cs index b33205f95..33fc21f32 100644 --- a/ArchiSteamFarm/Utilities.cs +++ b/ArchiSteamFarm/Utilities.cs @@ -172,10 +172,10 @@ namespace ArchiSteamFarm { } [PublicAPI] - public static bool IsClientErrorCode(this HttpStatusCode statusCode) => (statusCode >= HttpStatusCode.BadRequest) && (statusCode < HttpStatusCode.InternalServerError); + public static bool IsClientErrorCode(this HttpStatusCode statusCode) => statusCode is >= HttpStatusCode.BadRequest and < HttpStatusCode.InternalServerError; [PublicAPI] - public static bool IsServerErrorCode(this HttpStatusCode statusCode) => (statusCode >= HttpStatusCode.InternalServerError) && (statusCode < (HttpStatusCode) 600); + public static bool IsServerErrorCode(this HttpStatusCode statusCode) => statusCode is >= HttpStatusCode.InternalServerError and < (HttpStatusCode) 600; [PublicAPI] public static bool IsValidCdKey(string key) { diff --git a/ArchiSteamFarm/WebBrowser.cs b/ArchiSteamFarm/WebBrowser.cs index 3fdbb61e2..abc66cbd7 100644 --- a/ArchiSteamFarm/WebBrowser.cs +++ b/ArchiSteamFarm/WebBrowser.cs @@ -805,14 +805,6 @@ namespace ArchiSteamFarm { } } - if (response == null) { - if (Debugging.IsUserDebugging) { - ArchiLogger.LogGenericDebug("null <- " + httpMethod + " " + requestUri); - } - - return null; - } - if (Debugging.IsUserDebugging) { ArchiLogger.LogGenericDebug(response.StatusCode + " <- " + httpMethod + " " + requestUri); } @@ -822,7 +814,7 @@ namespace ArchiSteamFarm { } // WARNING: We still have not disposed response by now, make sure to dispose it ASAP if we're not returning it! - if ((response.StatusCode >= HttpStatusCode.Ambiguous) && (response.StatusCode < HttpStatusCode.BadRequest) && (maxRedirections > 0)) { + if (response.StatusCode is >= HttpStatusCode.Ambiguous and < HttpStatusCode.BadRequest && (maxRedirections > 0)) { Uri? redirectUri = response.Headers.Location; if (redirectUri == null) {