diff options
7 files changed, 92 insertions, 73 deletions
diff --git a/src/SMAPI.Web/ViewModels/ModCompatibilityModel.cs b/src/SMAPI.Web/ViewModels/ModCompatibilityModel.cs index d331c093..61756176 100644 --- a/src/SMAPI.Web/ViewModels/ModCompatibilityModel.cs +++ b/src/SMAPI.Web/ViewModels/ModCompatibilityModel.cs @@ -11,12 +11,15 @@ namespace StardewModdingAPI.Web.ViewModels /// <summary>The compatibility status, as a string like <c>"Broken"</c>.</summary> public string Status { get; set; } - /// <summary>A link to the unofficial version which fixes compatibility, if any.</summary> - public ModLinkModel UnofficialVersion { get; set; } - /// <summary>The human-readable summary, as an HTML block.</summary> public string Summary { get; set; } + /// <summary>The game or SMAPI version which broke this mod (if applicable).</summary> + public string BrokeIn { get; set; } + + /// <summary>A link to the unofficial version which fixes compatibility, if any.</summary> + public ModLinkModel UnofficialVersion { get; set; } + /********* ** Public methods @@ -26,9 +29,10 @@ namespace StardewModdingAPI.Web.ViewModels public ModCompatibilityModel(WikiCompatibilityInfo info) { this.Status = info.Status.ToString(); + this.Summary = info.Summary; + this.BrokeIn = info.BrokeIn; if (info.UnofficialVersion != null) this.UnofficialVersion = new ModLinkModel(info.UnofficialUrl, info.UnofficialVersion.ToString()); - this.Summary = info.Summary; } } } diff --git a/src/SMAPI.Web/ViewModels/ModModel.cs b/src/SMAPI.Web/ViewModels/ModModel.cs index 4fb9d5b5..1199fe20 100644 --- a/src/SMAPI.Web/ViewModels/ModModel.cs +++ b/src/SMAPI.Web/ViewModels/ModModel.cs @@ -34,9 +34,6 @@ namespace StardewModdingAPI.Web.ViewModels /// <summary>Links to the available mod pages.</summary> public ModLinkModel[] ModPages { get; set; } - /// <summary>The game or SMAPI version which broke this mod (if applicable).</summary> - public string BrokeIn { get; set; } - /// <summary>A unique identifier for the mod that can be used in an anchor URL.</summary> public string Slug { get; set; } @@ -49,15 +46,14 @@ namespace StardewModdingAPI.Web.ViewModels public ModModel(WikiModEntry entry) { // basic info - this.Name = entry.Name; - this.AlternateNames = entry.AlternateNames; - this.Author = entry.Author; - this.AlternateAuthors = entry.AlternateAuthors; + this.Name = entry.Name.FirstOrDefault(); + this.AlternateNames = string.Join(", ", entry.Name.Skip(1).ToArray()); + this.Author = entry.Author.FirstOrDefault(); + this.AlternateAuthors = string.Join(", ", entry.Author.Skip(1).ToArray()); this.SourceUrl = this.GetSourceUrl(entry); this.Compatibility = new ModCompatibilityModel(entry.Compatibility); this.BetaCompatibility = entry.BetaCompatibility != null ? new ModCompatibilityModel(entry.BetaCompatibility) : null; this.ModPages = this.GetModPageUrls(entry).ToArray(); - this.BrokeIn = entry.BrokeIn; this.Slug = entry.Anchor; } diff --git a/src/SMAPI.Web/Views/Mods/Index.cshtml b/src/SMAPI.Web/Views/Mods/Index.cshtml index b2ab61d7..b2e20c7a 100644 --- a/src/SMAPI.Web/Views/Mods/Index.cshtml +++ b/src/SMAPI.Web/Views/Mods/Index.cshtml @@ -71,7 +71,7 @@ <span v-html="mod.BetaCompatibility.Summary"></span> </div> </td> - <td class="mod-broke-in" v-html="mod.BrokeIn" v-show="showAllFields"></td> + <td class="mod-broke-in" v-html="mod.BetaCompatibility ? mod.BetaCompatibility.BrokeIn : mod.Compatibility.BrokeIn" v-show="showAllFields"></td> <td v-show="showAllFields"> <span v-if="mod.SourceUrl"><a v-bind:href="mod.SourceUrl">source</a></span> <span v-else class="mod-closed-source">no source</span> diff --git a/src/StardewModdingAPI.Toolkit/Framework/Clients/WebApi/ModExtendedMetadataModel.cs b/src/StardewModdingAPI.Toolkit/Framework/Clients/WebApi/ModExtendedMetadataModel.cs index e6f2e4b4..247730d7 100644 --- a/src/StardewModdingAPI.Toolkit/Framework/Clients/WebApi/ModExtendedMetadataModel.cs +++ b/src/StardewModdingAPI.Toolkit/Framework/Clients/WebApi/ModExtendedMetadataModel.cs @@ -74,7 +74,7 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi if (wiki != null) { this.ID = wiki.ID; - this.Name = wiki.Name; + this.Name = wiki.Name.FirstOrDefault(); this.NexusID = wiki.NexusID; this.ChucklefishID = wiki.ChucklefishID; this.GitHubRepo = wiki.GitHubRepo; diff --git a/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiClient.cs b/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiClient.cs index 9be760f8..7197bf2c 100644 --- a/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiClient.cs +++ b/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiClient.cs @@ -2,6 +2,7 @@ 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; @@ -84,40 +85,40 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki foreach (HtmlNode node in nodes) { // extract fields - string name = this.GetMetadataField(node, "mod-name"); - string alternateNames = this.GetMetadataField(node, "mod-name2"); - string author = this.GetMetadataField(node, "mod-author"); - string alternateAuthors = this.GetMetadataField(node, "mod-author2"); - string[] ids = this.GetMetadataField(node, "mod-id")?.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(p => p.Trim()).ToArray() ?? new string[0]; - int? nexusID = this.GetNullableIntField(node, "mod-nexus-id"); - int? chucklefishID = this.GetNullableIntField(node, "mod-cf-id"); - string githubRepo = this.GetMetadataField(node, "mod-github"); - string customSourceUrl = this.GetMetadataField(node, "mod-custom-source"); - string customUrl = this.GetMetadataField(node, "mod-url"); - string brokeIn = this.GetMetadataField(node, "mod-broke-in"); - string anchor = this.GetMetadataField(node, "mod-anchor"); + 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"); + 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"); // parse stable compatibility WikiCompatibilityInfo compatibility = new WikiCompatibilityInfo { - Status = this.GetStatusField(node, "mod-status") ?? WikiCompatibilityStatus.Ok, - UnofficialVersion = this.GetSemanticVersionField(node, "mod-unofficial-version"), - UnofficialUrl = this.GetMetadataField(node, "mod-unofficial-url"), - Summary = this.GetMetadataField(node, "mod-summary")?.Trim() + Status = this.GetAttributeAsStatus(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.GetStatusField(node, "mod-beta-status"); + WikiCompatibilityStatus? betaStatus = this.GetAttributeAsStatus(node, "data-beta-status"); if (betaStatus.HasValue) { betaCompatibility = new WikiCompatibilityInfo { Status = betaStatus.Value, - UnofficialVersion = this.GetSemanticVersionField(node, "mod-beta-unofficial-version"), - UnofficialUrl = this.GetMetadataField(node, "mod-beta-unofficial-url"), - Summary = this.GetMetadataField(node, "mod-beta-summary") + 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") }; } } @@ -126,37 +127,50 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki yield return new WikiModEntry { ID = ids, - Name = name, - AlternateNames = alternateNames, - Author = author, - AlternateAuthors = alternateAuthors, + Name = names, + Author = authors, NexusID = nexusID, ChucklefishID = chucklefishID, GitHubRepo = githubRepo, CustomSourceUrl = customSourceUrl, CustomUrl = customUrl, - BrokeIn = brokeIn, Compatibility = compatibility, BetaCompatibility = betaCompatibility, + Warnings = warnings, Anchor = anchor }; } } - /// <summary>Get the value of a metadata field.</summary> - /// <param name="container">The metadata container.</param> - /// <param name="name">The field name.</param> - private string GetMetadataField(HtmlNode container, string name) + /// <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) { - return container.Descendants().FirstOrDefault(p => p.HasClass(name))?.InnerHtml; + string value = element.GetAttributeValue(name, null); + if (string.IsNullOrWhiteSpace(value)) + return null; + + return WebUtility.HtmlDecode(value); } - /// <summary>Get the value of a metadata field as a compatibility status.</summary> - /// <param name="container">The metadata container.</param> - /// <param name="name">The field name.</param> - private WikiCompatibilityStatus? GetStatusField(HtmlNode container, string name) + /// <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 a compatibility status.</summary> + /// <param name="element">The element whose attributes to read.</param> + /// <param name="name">The attribute name.</param> + private WikiCompatibilityStatus? GetAttributeAsStatus(HtmlNode element, string name) { - string raw = this.GetMetadataField(container, name); + string raw = this.GetAttribute(element, name); if (raw == null) return null; if (!Enum.TryParse(raw, true, out WikiCompatibilityStatus status)) @@ -164,28 +178,36 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki return status; } - /// <summary>Get the value of a metadata field as a semantic version.</summary> - /// <param name="container">The metadata container.</param> - /// <param name="name">The field name.</param> - private ISemanticVersion GetSemanticVersionField(HtmlNode container, string name) + /// <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.GetMetadataField(container, name); + string raw = this.GetAttribute(element, name); return SemanticVersion.TryParse(raw, out ISemanticVersion version) ? version : null; } - /// <summary>Get the value of a metadata field as a nullable integer.</summary> - /// <param name="container">The metadata container.</param> - /// <param name="name">The field name.</param> - private int? GetNullableIntField(HtmlNode container, string name) + /// <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.GetMetadataField(container, 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")] diff --git a/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiCompatibilityInfo.cs b/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiCompatibilityInfo.cs index 2725df1a..204acd2b 100644 --- a/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiCompatibilityInfo.cs +++ b/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiCompatibilityInfo.cs @@ -12,6 +12,9 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki /// <summary>The human-readable summary of the compatibility status or workaround, without HTML formatting.</summary> public string Summary { get; set; } + /// <summary>The game or SMAPI version which broke this mod (if applicable).</summary> + public string BrokeIn { get; set; } + /// <summary>The version of the latest unofficial update, if applicable.</summary> public ISemanticVersion UnofficialVersion { get; set; } diff --git a/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiModEntry.cs b/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiModEntry.cs index 752b526c..ce8d6c5f 100644 --- a/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiModEntry.cs +++ b/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiModEntry.cs @@ -6,20 +6,14 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki /********* ** Accessors *********/ - /// <summary>The mod's unique ID. A mod may have multiple current IDs in rare cases (e.g. due to parallel releases or unofficial updates).</summary> + /// <summary>The mod's unique ID. If the mod has alternate/old IDs, they're listed in latest to newest order.</summary> public string[] ID { get; set; } - /// <summary>The mod's display name.</summary> - public string Name { get; set; } + /// <summary>The mod's display name. If the mod has multiple names, the first one is the most canonical name.</summary> + public string[] Name { get; set; } - /// <summary>The mod's alternative names, if any.</summary> - public string AlternateNames { get; set; } - - /// <summary>The mod's author name.</summary> - public string Author { get; set; } - - /// <summary>The mod's alternative author names, if any.</summary> - public string AlternateAuthors { get; set; } + /// <summary>The mod's author name. If the author has multiple names, the first one is the most canonical name.</summary> + public string[] Author { get; set; } /// <summary>The mod ID on Nexus.</summary> public int? NexusID { get; set; } @@ -36,9 +30,6 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki /// <summary>The custom mod page URL (if applicable).</summary> public string CustomUrl { get; set; } - /// <summary>The game or SMAPI version which broke this mod (if applicable).</summary> - public string BrokeIn { get; set; } - /// <summary>The mod's compatibility with the latest stable version of the game.</summary> public WikiCompatibilityInfo Compatibility { get; set; } @@ -48,6 +39,9 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki /// <summary>Whether a Stardew Valley or SMAPI beta which affects mod compatibility is in progress. If this is true, <see cref="BetaCompatibility"/> should be used for beta versions of SMAPI instead of <see cref="Compatibility"/>.</summary> public bool HasBetaInfo => this.BetaCompatibility != null; + /// <summary>The human-readable warnings for players about this mod.</summary> + public string[] Warnings { get; set; } + /// <summary>The link anchor for the mod entry in the wiki compatibility list.</summary> public string Anchor { get; set; } } |