From f3229fa45f3b5a17aafaa2d13124b4cdc0a272ab Mon Sep 17 00:00:00 2001 From: JustArchi Date: Sat, 6 Aug 2022 18:51:32 +0200 Subject: [PATCH] Implement AngleSharp.XPath breaking changes Bump of B warranted, more in the release notes --- .github/renovate.json5 | 6 ---- ArchiSteamFarm/Core/Utilities.cs | 33 +++++++++++++++---- ArchiSteamFarm/Steam/Bot.cs | 6 ++-- ArchiSteamFarm/Steam/Cards/CardsFarmer.cs | 23 ++++++------- .../Steam/Integration/ArchiWebHandler.cs | 24 +++++++------- .../Steam/Integration/SteamSaleEvent.cs | 2 +- .../Steam/Security/MobileAuthenticator.cs | 2 +- ArchiSteamFarm/Web/GitHub.cs | 12 +++---- Directory.Build.props | 2 +- Directory.Packages.props | 2 +- 10 files changed, 64 insertions(+), 48 deletions(-) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 0d27064ff..200c54878 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -19,12 +19,6 @@ "allowedVersions": "<= 3.1", "matchManagers": [ "nuget" ], "matchPackageNames": [ "Microsoft.Extensions.Configuration.Json", "Microsoft.Extensions.Logging.Configuration" ] - }, - { - // TODO: https://github.com/AngleSharp/AngleSharp.XPath/issues/40 - "allowedVersions": "< 2.0", - "matchManagers": [ "nuget" ], - "matchPackageNames": [ "AngleSharp.XPath" ] } ] } diff --git a/ArchiSteamFarm/Core/Utilities.cs b/ArchiSteamFarm/Core/Utilities.cs index 5df9065bc..0859355b7 100644 --- a/ArchiSteamFarm/Core/Utilities.cs +++ b/ArchiSteamFarm/Core/Utilities.cs @@ -189,24 +189,45 @@ public static class Utilities { } [PublicAPI] - public static IEnumerable SelectNodes(this IDocument document, string xpath) { + public static IList SelectNodes(this IDocument document, string xpath) { ArgumentNullException.ThrowIfNull(document); - return document.Body.SelectNodes(xpath).OfType(); + return document.Body.SelectNodes(xpath); } [PublicAPI] - public static IElement? SelectSingleElementNode(this IElement element, string xpath) { + public static IEnumerable SelectNodes(this IDocument document, string xpath) where T : class, INode { + ArgumentNullException.ThrowIfNull(document); + + return document.SelectNodes(xpath).OfType(); + } + + [PublicAPI] + public static IEnumerable SelectNodes(this IElement element, string xpath) where T : class, INode { ArgumentNullException.ThrowIfNull(element); - return (IElement?) element.SelectSingleNode(xpath); + return element.SelectNodes(xpath).OfType(); } [PublicAPI] - public static IElement? SelectSingleNode(this IDocument document, string xpath) { + public static INode? SelectSingleNode(this IDocument document, string xpath) { ArgumentNullException.ThrowIfNull(document); - return (IElement?) document.Body.SelectSingleNode(xpath); + return document.Body.SelectSingleNode(xpath); + } + + [PublicAPI] + public static T? SelectSingleNode(this IDocument document, string xpath) where T : class, INode { + ArgumentNullException.ThrowIfNull(document); + + return document.Body.SelectSingleNode(xpath) as T; + } + + [PublicAPI] + public static T? SelectSingleNode(this IElement element, string xpath) where T : class, INode { + ArgumentNullException.ThrowIfNull(element); + + return element.SelectSingleNode(xpath) as T; } [PublicAPI] diff --git a/ArchiSteamFarm/Steam/Bot.cs b/ArchiSteamFarm/Steam/Bot.cs index 977cc0ad2..284aa7afa 100644 --- a/ArchiSteamFarm/Steam/Bot.cs +++ b/ArchiSteamFarm/Steam/Bot.cs @@ -651,7 +651,7 @@ public sealed class Bot : IAsyncDisposable, IDisposable { } byte maxPages = 1; - IElement? htmlNode = badgePage.SelectSingleNode("(//a[@class='pagelink'])[last()]"); + INode? htmlNode = badgePage.SelectSingleNode("(//a[@class='pagelink'])[last()]"); if (htmlNode != null) { string lastPage = htmlNode.TextContent; @@ -1882,11 +1882,11 @@ public sealed class Bot : IAsyncDisposable, IDisposable { // We select badges that are ready to craft, as well as those that are already crafted to a maximum 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 - IEnumerable linkElements = badgePage.SelectNodes("//a[@class='badge_craft_button'] | //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']"); + IEnumerable linkElements = badgePage.SelectNodes("//a[@class='badge_craft_button'] | //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']/@href"); HashSet result = new(); - foreach (string? badgeUri in linkElements.Select(static htmlNode => htmlNode.GetAttribute("href"))) { + foreach (string? badgeUri in linkElements.Select(static htmlNode => htmlNode.Value)) { if (string.IsNullOrEmpty(badgeUri)) { ArchiLogger.LogNullError(badgeUri); diff --git a/ArchiSteamFarm/Steam/Cards/CardsFarmer.cs b/ArchiSteamFarm/Steam/Cards/CardsFarmer.cs index caeeb19dc..b3c820f79 100644 --- a/ArchiSteamFarm/Steam/Cards/CardsFarmer.cs +++ b/ArchiSteamFarm/Steam/Cards/CardsFarmer.cs @@ -29,6 +29,7 @@ using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using AngleSharp.Dom; +using AngleSharp.XPath; using ArchiSteamFarm.Collections; using ArchiSteamFarm.Core; using ArchiSteamFarm.Localization; @@ -451,20 +452,20 @@ public sealed class CardsFarmer : IAsyncDisposable, IDisposable { ArgumentNullException.ThrowIfNull(htmlDocument); ArgumentNullException.ThrowIfNull(parsedAppIDs); - IEnumerable htmlNodes = htmlDocument.SelectNodes("//div[@class='badge_row_inner']"); + IEnumerable htmlNodes = htmlDocument.SelectNodes("//div[@class='badge_row_inner']"); HashSet? backgroundTasks = null; foreach (IElement htmlNode in htmlNodes) { - IElement? statsNode = htmlNode.SelectSingleElementNode(".//div[@class='badge_title_stats_content']"); - IElement? appIDNode = statsNode?.SelectSingleElementNode(".//div[@class='card_drop_info_dialog']"); + IElement? statsNode = htmlNode.SelectSingleNode(".//div[@class='badge_title_stats_content']"); + IAttr? appIDNode = statsNode?.SelectSingleNode(".//div[@class='card_drop_info_dialog']/@id"); if (appIDNode == null) { // It's just a badge, nothing more continue; } - string? appIDText = appIDNode.GetAttribute("id"); + string appIDText = appIDNode.Value; if (string.IsNullOrEmpty(appIDText)) { Bot.ArchiLogger.LogNullError(appIDText); @@ -522,7 +523,7 @@ public sealed class CardsFarmer : IAsyncDisposable, IDisposable { } // Cards - IElement? progressNode = statsNode?.SelectSingleElementNode(".//span[@class='progress_info_bold']"); + INode? progressNode = statsNode?.SelectSingleNode(".//span[@class='progress_info_bold']"); if (progressNode == null) { Bot.ArchiLogger.LogNullError(progressNode); @@ -561,7 +562,7 @@ public sealed class CardsFarmer : IAsyncDisposable, IDisposable { } // To save us on extra work, check cards earned so far first - IElement? cardsEarnedNode = statsNode?.SelectSingleElementNode(".//div[@class='card_drop_info_header']"); + INode? cardsEarnedNode = statsNode?.SelectSingleNode(".//div[@class='card_drop_info_header']"); if (cardsEarnedNode == null) { Bot.ArchiLogger.LogNullError(cardsEarnedNode); @@ -606,7 +607,7 @@ public sealed class CardsFarmer : IAsyncDisposable, IDisposable { } // Hours - IElement? timeNode = statsNode?.SelectSingleElementNode(".//div[@class='badge_title_stats_playtime']"); + INode? timeNode = statsNode?.SelectSingleNode(".//div[@class='badge_title_stats_playtime']"); if (timeNode == null) { Bot.ArchiLogger.LogNullError(timeNode); @@ -635,7 +636,7 @@ public sealed class CardsFarmer : IAsyncDisposable, IDisposable { } // Names - IElement? nameNode = statsNode?.SelectSingleElementNode("(.//div[@class='card_drop_info_body'])[last()]"); + INode? nameNode = statsNode?.SelectSingleNode("(.//div[@class='card_drop_info_body'])[last()]"); if (nameNode == null) { Bot.ArchiLogger.LogNullError(nameNode); @@ -687,7 +688,7 @@ public sealed class CardsFarmer : IAsyncDisposable, IDisposable { // Levels byte badgeLevel = 0; - IElement? levelNode = htmlNode.SelectSingleElementNode(".//div[@class='badge_info_description']/div[2]"); + INode? levelNode = htmlNode.SelectSingleNode(".//div[@class='badge_info_description']/div[2]"); if (levelNode != null) { // There is no levelNode if we didn't craft that badge yet (level 0) @@ -1009,7 +1010,7 @@ public sealed class CardsFarmer : IAsyncDisposable, IDisposable { using IDocument? htmlDocument = await Bot.ArchiWebHandler.GetGameCardsPage(appID).ConfigureAwait(false); - IElement? progressNode = htmlDocument?.SelectSingleNode("//span[@class='progress_info_bold']"); + INode? progressNode = htmlDocument?.SelectSingleNode("//span[@class='progress_info_bold']"); if (progressNode == null) { return null; @@ -1052,7 +1053,7 @@ public sealed class CardsFarmer : IAsyncDisposable, IDisposable { byte maxPages = 1; - IElement? htmlNode = htmlDocument.SelectSingleNode("(//a[@class='pagelink'])[last()]"); + INode? htmlNode = htmlDocument.SelectSingleNode("(//a[@class='pagelink'])[last()]"); if (htmlNode != null) { string lastPage = htmlNode.TextContent; diff --git a/ArchiSteamFarm/Steam/Integration/ArchiWebHandler.cs b/ArchiSteamFarm/Steam/Integration/ArchiWebHandler.cs index 246ebb423..dc7b195ee 100644 --- a/ArchiSteamFarm/Steam/Integration/ArchiWebHandler.cs +++ b/ArchiSteamFarm/Steam/Integration/ArchiWebHandler.cs @@ -1695,16 +1695,16 @@ public sealed class ArchiWebHandler : IDisposable { return 0; } - IEnumerable htmlNodes = htmlDocument.SelectNodes("//div[@class='badge_card_set_cards']/div[starts-with(@class, 'badge_card_set_card')]"); + IList htmlNodes = htmlDocument.SelectNodes("//div[@class='badge_card_set_cards']/div[starts-with(@class, 'badge_card_set_card')]"); - result = (byte) htmlNodes.Count(); - - if (result == 0) { - Bot.ArchiLogger.LogNullError(result); + if (htmlNodes.Count == 0) { + Bot.ArchiLogger.LogNullError(htmlNodes); return 0; } + result = (byte) htmlNodes.Count; + ASF.GlobalDatabase?.CardCountsPerGame.TryAdd(appID, result); return result; @@ -1818,11 +1818,11 @@ public sealed class ArchiWebHandler : IDisposable { return null; } - IEnumerable htmlNodes = response.Content.SelectNodes("//div[@class='pending_gift']/div[starts-with(@id, 'pending_gift_')][count(div[@class='pending_giftcard_leftcol']) > 0]/@id"); + IEnumerable htmlNodes = response.Content.SelectNodes("//div[@class='pending_gift']/div[starts-with(@id, 'pending_gift_')][count(div[@class='pending_giftcard_leftcol']) > 0]/@id"); HashSet results = new(); - foreach (string? giftCardIDText in htmlNodes.Select(static node => node.GetAttribute("id"))) { + foreach (string? giftCardIDText in htmlNodes.Select(static node => node.Value)) { if (string.IsNullOrEmpty(giftCardIDText)) { Bot.ArchiLogger.LogNullError(giftCardIDText); @@ -1865,11 +1865,11 @@ public sealed class ArchiWebHandler : IDisposable { return null; } - IEnumerable htmlNodes = response.Content.SelectNodes("(//table[@class='accountTable'])[2]//a/@data-miniprofile"); + IEnumerable htmlNodes = response.Content.SelectNodes("(//table[@class='accountTable'])[2]//a/@data-miniprofile"); HashSet result = new(); - foreach (string? miniProfile in htmlNodes.Select(static htmlNode => htmlNode.GetAttribute("data-miniprofile"))) { + foreach (string? miniProfile in htmlNodes.Select(static htmlNode => htmlNode.Value)) { if (string.IsNullOrEmpty(miniProfile)) { Bot.ArchiLogger.LogNullError(miniProfile); @@ -1953,7 +1953,7 @@ public sealed class ArchiWebHandler : IDisposable { using HtmlDocumentResponse? response = await UrlGetToHtmlDocumentWithSession(request, checkSessionPreemptively: false).ConfigureAwait(false); - IElement? htmlNode = response?.Content?.SelectSingleNode("//div[@class='pagecontent']/script"); + INode? htmlNode = response?.Content?.SelectSingleNode("//div[@class='pagecontent']/script"); if (htmlNode == null) { // Trade can be no longer valid @@ -2345,7 +2345,7 @@ public sealed class ArchiWebHandler : IDisposable { return (ESteamApiKeyState.Timeout, null); } - IElement? titleNode = response.Content.SelectSingleNode("//div[@id='mainContents']/h2"); + INode? titleNode = response.Content.SelectSingleNode("//div[@id='mainContents']/h2"); if (titleNode == null) { Bot.ArchiLogger.LogNullError(titleNode); @@ -2365,7 +2365,7 @@ public sealed class ArchiWebHandler : IDisposable { return (ESteamApiKeyState.AccessDenied, null); } - IElement? htmlNode = response.Content.SelectSingleNode("//div[@id='bodyContents_ex']/p"); + INode? htmlNode = response.Content.SelectSingleNode("//div[@id='bodyContents_ex']/p"); if (htmlNode == null) { Bot.ArchiLogger.LogNullError(htmlNode); diff --git a/ArchiSteamFarm/Steam/Integration/SteamSaleEvent.cs b/ArchiSteamFarm/Steam/Integration/SteamSaleEvent.cs index 1e70a54a6..20b8a38e4 100644 --- a/ArchiSteamFarm/Steam/Integration/SteamSaleEvent.cs +++ b/ArchiSteamFarm/Steam/Integration/SteamSaleEvent.cs @@ -93,7 +93,7 @@ internal sealed class SteamSaleEvent : IAsyncDisposable, IDisposable { return null; } - IElement? htmlNode = htmlDocument.SelectSingleNode("//div[@class='subtext']"); + INode? htmlNode = htmlDocument.SelectSingleNode("//div[@class='subtext']"); if (htmlNode == null) { // Valid, no cards for exploring the queue available diff --git a/ArchiSteamFarm/Steam/Security/MobileAuthenticator.cs b/ArchiSteamFarm/Steam/Security/MobileAuthenticator.cs index 5f232017c..2ff22d3f2 100644 --- a/ArchiSteamFarm/Steam/Security/MobileAuthenticator.cs +++ b/ArchiSteamFarm/Steam/Security/MobileAuthenticator.cs @@ -121,7 +121,7 @@ public sealed class MobileAuthenticator : IDisposable { return null; } - IEnumerable confirmationNodes = htmlDocument.SelectNodes("//div[@class='mobileconf_list_entry']"); + IEnumerable confirmationNodes = htmlDocument.SelectNodes("//div[@class='mobileconf_list_entry']"); HashSet result = new(); diff --git a/ArchiSteamFarm/Web/GitHub.cs b/ArchiSteamFarm/Web/GitHub.cs index ec0cab9c6..d6aec7e2f 100644 --- a/ArchiSteamFarm/Web/GitHub.cs +++ b/ArchiSteamFarm/Web/GitHub.cs @@ -86,12 +86,12 @@ internal static class GitHub { return null; } - IEnumerable revisionNodes = response.Content.SelectNodes("//li[contains(@class, 'wiki-history-revision')]"); + IEnumerable revisionNodes = response.Content.SelectNodes("//li[contains(@class, 'wiki-history-revision')]"); Dictionary result = new(); foreach (IElement revisionNode in revisionNodes) { - IElement? versionNode = revisionNode.SelectSingleElementNode(".//input/@value"); + IAttr? versionNode = revisionNode.SelectSingleNode(".//input/@value"); if (versionNode == null) { ASF.ArchiLogger.LogNullError(versionNode); @@ -99,7 +99,7 @@ internal static class GitHub { return null; } - string? versionText = versionNode.GetAttribute("value"); + string versionText = versionNode.Value; if (string.IsNullOrEmpty(versionText)) { ASF.ArchiLogger.LogNullError(versionText); @@ -107,7 +107,7 @@ internal static class GitHub { return null; } - IElement? dateTimeNode = revisionNode.SelectSingleElementNode(".//relative-time/@datetime"); + IAttr? dateTimeNode = revisionNode.SelectSingleNode(".//relative-time/@datetime"); if (dateTimeNode == null) { ASF.ArchiLogger.LogNullError(dateTimeNode); @@ -115,7 +115,7 @@ internal static class GitHub { return null; } - string? dateTimeText = dateTimeNode.GetAttribute("datetime"); + string dateTimeText = dateTimeNode.Value; if (string.IsNullOrEmpty(dateTimeText)) { ASF.ArchiLogger.LogNullError(dateTimeText); @@ -153,7 +153,7 @@ internal static class GitHub { return null; } - IElement? markdownBodyNode = response.Content.SelectSingleNode("//div[@class='markdown-body']"); + IElement? markdownBodyNode = response.Content.SelectSingleNode("//div[@class='markdown-body']"); return markdownBodyNode?.InnerHtml.Trim() ?? ""; } diff --git a/Directory.Build.props b/Directory.Build.props index dafe5417d..09c562ca7 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,6 +1,6 @@ - 5.2.9.0 + 5.3.0.0 diff --git a/Directory.Packages.props b/Directory.Packages.props index c7eb630f3..d2625da21 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,6 +1,6 @@ - +