Implement AngleSharp.XPath breaking changes

Bump of B warranted, more in the release notes
This commit is contained in:
JustArchi
2022-08-06 18:51:32 +02:00
parent 5c6ca3fee2
commit 7ceaab5ae3
10 changed files with 64 additions and 48 deletions

View File

@@ -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" ]
}
]
}

View File

@@ -189,24 +189,45 @@ public static class Utilities {
}
[PublicAPI]
public static IEnumerable<IElement> SelectNodes(this IDocument document, string xpath) {
public static IList<INode> SelectNodes(this IDocument document, string xpath) {
ArgumentNullException.ThrowIfNull(document);
return document.Body.SelectNodes(xpath).OfType<IElement>();
return document.Body.SelectNodes(xpath);
}
[PublicAPI]
public static IElement? SelectSingleElementNode(this IElement element, string xpath) {
public static IEnumerable<T> SelectNodes<T>(this IDocument document, string xpath) where T : class, INode {
ArgumentNullException.ThrowIfNull(document);
return document.SelectNodes(xpath).OfType<T>();
}
[PublicAPI]
public static IEnumerable<T> SelectNodes<T>(this IElement element, string xpath) where T : class, INode {
ArgumentNullException.ThrowIfNull(element);
return (IElement?) element.SelectSingleNode(xpath);
return element.SelectNodes(xpath).OfType<T>();
}
[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<T>(this IDocument document, string xpath) where T : class, INode {
ArgumentNullException.ThrowIfNull(document);
return document.Body.SelectSingleNode(xpath) as T;
}
[PublicAPI]
public static T? SelectSingleNode<T>(this IElement element, string xpath) where T : class, INode {
ArgumentNullException.ThrowIfNull(element);
return element.SelectSingleNode(xpath) as T;
}
[PublicAPI]

View File

@@ -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<IElement> 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<IAttr> linkElements = badgePage.SelectNodes<IAttr>("//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<uint> 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);

View File

@@ -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<IElement> htmlNodes = htmlDocument.SelectNodes("//div[@class='badge_row_inner']");
IEnumerable<IElement> htmlNodes = htmlDocument.SelectNodes<IElement>("//div[@class='badge_row_inner']");
HashSet<Task>? 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<IElement>(".//div[@class='badge_title_stats_content']");
IAttr? appIDNode = statsNode?.SelectSingleNode<IAttr>(".//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;

View File

@@ -1695,16 +1695,16 @@ public sealed class ArchiWebHandler : IDisposable {
return 0;
}
IEnumerable<IElement> htmlNodes = htmlDocument.SelectNodes("//div[@class='badge_card_set_cards']/div[starts-with(@class, 'badge_card_set_card')]");
IList<INode> 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<IElement> htmlNodes = response.Content.SelectNodes("//div[@class='pending_gift']/div[starts-with(@id, 'pending_gift_')][count(div[@class='pending_giftcard_leftcol']) > 0]/@id");
IEnumerable<IAttr> htmlNodes = response.Content.SelectNodes<IAttr>("//div[@class='pending_gift']/div[starts-with(@id, 'pending_gift_')][count(div[@class='pending_giftcard_leftcol']) > 0]/@id");
HashSet<ulong> 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<IElement> htmlNodes = response.Content.SelectNodes("(//table[@class='accountTable'])[2]//a/@data-miniprofile");
IEnumerable<IAttr> htmlNodes = response.Content.SelectNodes<IAttr>("(//table[@class='accountTable'])[2]//a/@data-miniprofile");
HashSet<ulong> 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);

View File

@@ -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

View File

@@ -121,7 +121,7 @@ public sealed class MobileAuthenticator : IDisposable {
return null;
}
IEnumerable<IElement> confirmationNodes = htmlDocument.SelectNodes("//div[@class='mobileconf_list_entry']");
IEnumerable<IElement> confirmationNodes = htmlDocument.SelectNodes<IElement>("//div[@class='mobileconf_list_entry']");
HashSet<Confirmation> result = new();

View File

@@ -86,12 +86,12 @@ internal static class GitHub {
return null;
}
IEnumerable<IElement> revisionNodes = response.Content.SelectNodes("//li[contains(@class, 'wiki-history-revision')]");
IEnumerable<IElement> revisionNodes = response.Content.SelectNodes<IElement>("//li[contains(@class, 'wiki-history-revision')]");
Dictionary<string, DateTime> result = new();
foreach (IElement revisionNode in revisionNodes) {
IElement? versionNode = revisionNode.SelectSingleElementNode(".//input/@value");
IAttr? versionNode = revisionNode.SelectSingleNode<IAttr>(".//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<IAttr>(".//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<IElement>("//div[@class='markdown-body']");
return markdownBodyNode?.InnerHtml.Trim() ?? "";
}

View File

@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<Version>5.2.9.0</Version>
<Version>5.3.0.0</Version>
</PropertyGroup>
<PropertyGroup>

View File

@@ -1,6 +1,6 @@
<Project>
<ItemGroup>
<PackageVersion Include="AngleSharp.XPath" Version="1.1.7" />
<PackageVersion Include="AngleSharp.XPath" Version="2.0.1" />
<PackageVersion Include="ConfigureAwaitChecker.Analyzer" Version="5.0.0.1" />
<PackageVersion Include="CryptSharpStandard" Version="1.0.0" />
<PackageVersion Include="Humanizer" Version="2.14.1" />