summaryrefslogtreecommitdiff
path: root/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiClient.cs
diff options
context:
space:
mode:
authorJesse Plamondon-Willard <Pathoschild@users.noreply.github.com>2019-09-13 18:24:54 -0400
committerJesse Plamondon-Willard <Pathoschild@users.noreply.github.com>2019-09-13 18:24:54 -0400
commit6521df7b131924835eb797251c1e956fae0d6e13 (patch)
treeb704dc64b6b6fef72615bac8950d5eff3c80ea89 /src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiClient.cs
parente22a54212182d0adc443ac95bc791e83c90f7e10 (diff)
parentb7b8b001c5c2dc5d2c9fc1347532ca29368c2325 (diff)
downloadSMAPI-6521df7b131924835eb797251c1e956fae0d6e13.tar.gz
SMAPI-6521df7b131924835eb797251c1e956fae0d6e13.tar.bz2
SMAPI-6521df7b131924835eb797251c1e956fae0d6e13.zip
Merge branch 'develop' into stable
Diffstat (limited to 'src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiClient.cs')
-rw-r--r--src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiClient.cs243
1 files changed, 0 insertions, 243 deletions
diff --git a/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiClient.cs b/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiClient.cs
deleted file mode 100644
index ac279d88..00000000
--- a/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiClient.cs
+++ /dev/null
@@ -1,243 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics.CodeAnalysis;
-using System.Linq;
-using System.Net;
-using System.Threading.Tasks;
-using HtmlAgilityPack;
-using Pathoschild.Http.Client;
-
-namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki
-{
- /// <summary>An HTTP client for fetching mod metadata from the wiki.</summary>
- public class WikiClient : IDisposable
- {
- /*********
- ** Fields
- *********/
- /// <summary>The underlying HTTP client.</summary>
- private readonly IClient Client;
-
-
- /*********
- ** Public methods
- *********/
- /// <summary>Construct an instance.</summary>
- /// <param name="userAgent">The user agent for the wiki API.</param>
- /// <param name="baseUrl">The base URL for the wiki API.</param>
- public WikiClient(string userAgent, string baseUrl = "https://stardewvalleywiki.com/mediawiki/api.php")
- {
- this.Client = new FluentClient(baseUrl).SetUserAgent(userAgent);
- }
-
- /// <summary>Fetch mods from the compatibility list.</summary>
- public async Task<WikiModList> FetchModsAsync()
- {
- // fetch HTML
- ResponseModel response = await this.Client
- .GetAsync("")
- .WithArguments(new
- {
- action = "parse",
- page = "Modding:Mod_compatibility",
- format = "json"
- })
- .As<ResponseModel>();
- string html = response.Parse.Text["*"];
-
- // parse HTML
- var doc = new HtmlDocument();
- doc.LoadHtml(html);
-
- // fetch game versions
- string stableVersion = doc.DocumentNode.SelectSingleNode("div[@class='game-stable-version']")?.InnerText;
- string betaVersion = doc.DocumentNode.SelectSingleNode("div[@class='game-beta-version']")?.InnerText;
- if (betaVersion == stableVersion)
- betaVersion = null;
-
- // find mod entries
- HtmlNodeCollection modNodes = doc.DocumentNode.SelectNodes("table[@id='mod-list']//tr[@class='mod']");
- if (modNodes == null)
- throw new InvalidOperationException("Can't parse wiki compatibility list, no mods found.");
-
- // parse
- WikiModEntry[] mods = this.ParseEntries(modNodes).ToArray();
- return new WikiModList
- {
- StableVersion = stableVersion,
- BetaVersion = betaVersion,
- Mods = mods
- };
- }
-
- /// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
- public void Dispose()
- {
- this.Client?.Dispose();
- }
-
-
- /*********
- ** Private methods
- *********/
- /// <summary>Parse valid mod compatibility entries.</summary>
- /// <param name="nodes">The HTML compatibility entries.</param>
- private IEnumerable<WikiModEntry> ParseEntries(IEnumerable<HtmlNode> nodes)
- {
- foreach (HtmlNode node in nodes)
- {
- // extract fields
- string[] names = this.GetAttributeAsCsv(node, "data-name");
- string[] authors = this.GetAttributeAsCsv(node, "data-author");
- string[] ids = this.GetAttributeAsCsv(node, "data-id");
- string[] warnings = this.GetAttributeAsCsv(node, "data-warnings");
- int? nexusID = this.GetAttributeAsNullableInt(node, "data-nexus-id");
- int? chucklefishID = this.GetAttributeAsNullableInt(node, "data-cf-id");
- int? modDropID = this.GetAttributeAsNullableInt(node, "data-moddrop-id");
- string githubRepo = this.GetAttribute(node, "data-github");
- string customSourceUrl = this.GetAttribute(node, "data-custom-source");
- string customUrl = this.GetAttribute(node, "data-url");
- string anchor = this.GetAttribute(node, "id");
- string contentPackFor = this.GetAttribute(node, "data-content-pack-for");
-
- // parse stable compatibility
- WikiCompatibilityInfo compatibility = new WikiCompatibilityInfo
- {
- Status = this.GetAttributeAsEnum<WikiCompatibilityStatus>(node, "data-status") ?? WikiCompatibilityStatus.Ok,
- BrokeIn = this.GetAttribute(node, "data-broke-in"),
- UnofficialVersion = this.GetAttributeAsSemanticVersion(node, "data-unofficial-version"),
- UnofficialUrl = this.GetAttribute(node, "data-unofficial-url"),
- Summary = this.GetInnerHtml(node, "mod-summary")?.Trim()
- };
-
- // parse beta compatibility
- WikiCompatibilityInfo betaCompatibility = null;
- {
- WikiCompatibilityStatus? betaStatus = this.GetAttributeAsEnum<WikiCompatibilityStatus>(node, "data-beta-status");
- if (betaStatus.HasValue)
- {
- betaCompatibility = new WikiCompatibilityInfo
- {
- Status = betaStatus.Value,
- BrokeIn = this.GetAttribute(node, "data-beta-broke-in"),
- UnofficialVersion = this.GetAttributeAsSemanticVersion(node, "data-beta-unofficial-version"),
- UnofficialUrl = this.GetAttribute(node, "data-beta-unofficial-url"),
- Summary = this.GetInnerHtml(node, "mod-beta-summary")
- };
- }
- }
-
- // parse SMAPI 3.0 readiness status
- WikiSmapi3Status smapi3Status = this.GetAttributeAsEnum<WikiSmapi3Status>(node, "data-smapi-3-status") ?? WikiSmapi3Status.Unknown;
- string smapi3Url = this.GetAttribute(node, "data-smapi-3-url");
-
- // yield model
- yield return new WikiModEntry
- {
- ID = ids,
- Name = names,
- Author = authors,
- NexusID = nexusID,
- ChucklefishID = chucklefishID,
- ModDropID = modDropID,
- GitHubRepo = githubRepo,
- CustomSourceUrl = customSourceUrl,
- CustomUrl = customUrl,
- ContentPackFor = contentPackFor,
- Compatibility = compatibility,
- BetaCompatibility = betaCompatibility,
- Smapi3Status = smapi3Status,
- Smapi3Url = smapi3Url,
- Warnings = warnings,
- Anchor = anchor
- };
- }
- }
-
- /// <summary>Get an attribute value.</summary>
- /// <param name="element">The element whose attributes to read.</param>
- /// <param name="name">The attribute name.</param>
- private string GetAttribute(HtmlNode element, string name)
- {
- string value = element.GetAttributeValue(name, null);
- if (string.IsNullOrWhiteSpace(value))
- return null;
-
- return WebUtility.HtmlDecode(value);
- }
-
- /// <summary>Get an attribute value and parse it as a comma-delimited list of strings.</summary>
- /// <param name="element">The element whose attributes to read.</param>
- /// <param name="name">The attribute name.</param>
- private string[] GetAttributeAsCsv(HtmlNode element, string name)
- {
- string raw = this.GetAttribute(element, name);
- return !string.IsNullOrWhiteSpace(raw)
- ? raw.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(p => p.Trim()).ToArray()
- : new string[0];
- }
-
- /// <summary>Get an attribute value and parse it as an enum value.</summary>
- /// <typeparam name="TEnum">The enum type.</typeparam>
- /// <param name="element">The element whose attributes to read.</param>
- /// <param name="name">The attribute name.</param>
- private TEnum? GetAttributeAsEnum<TEnum>(HtmlNode element, string name) where TEnum : struct
- {
- string raw = this.GetAttribute(element, name);
- if (raw == null)
- return null;
- if (!Enum.TryParse(raw, true, out TEnum value) && Enum.IsDefined(typeof(TEnum), value))
- throw new InvalidOperationException($"Unknown {typeof(TEnum).Name} value '{raw}' when parsing compatibility list.");
- return value;
- }
-
- /// <summary>Get an attribute value and parse it as a semantic version.</summary>
- /// <param name="element">The element whose attributes to read.</param>
- /// <param name="name">The attribute name.</param>
- private ISemanticVersion GetAttributeAsSemanticVersion(HtmlNode element, string name)
- {
- string raw = this.GetAttribute(element, name);
- return SemanticVersion.TryParse(raw, out ISemanticVersion version)
- ? version
- : null;
- }
-
- /// <summary>Get an attribute value and parse it as a nullable int.</summary>
- /// <param name="element">The element whose attributes to read.</param>
- /// <param name="name">The attribute name.</param>
- private int? GetAttributeAsNullableInt(HtmlNode element, string name)
- {
- string raw = this.GetAttribute(element, name);
- if (raw != null && int.TryParse(raw, out int value))
- return value;
- return null;
- }
-
- /// <summary>Get the text of an element with the given class name.</summary>
- /// <param name="container">The metadata container.</param>
- /// <param name="className">The field name.</param>
- private string GetInnerHtml(HtmlNode container, string className)
- {
- return container.Descendants().FirstOrDefault(p => p.HasClass(className))?.InnerHtml;
- }
-
- /// <summary>The response model for the MediaWiki parse API.</summary>
- [SuppressMessage("ReSharper", "ClassNeverInstantiated.Local")]
- [SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local")]
- private class ResponseModel
- {
- /// <summary>The parse API results.</summary>
- public ResponseParseModel Parse { get; set; }
- }
-
- /// <summary>The inner response model for the MediaWiki parse API.</summary>
- [SuppressMessage("ReSharper", "ClassNeverInstantiated.Local")]
- [SuppressMessage("ReSharper", "CollectionNeverUpdated.Local")]
- [SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local")]
- private class ResponseParseModel
- {
- /// <summary>The parsed text.</summary>
- public IDictionary<string, string> Text { get; set; }
- }
- }
-}