From 100e303b488a36e8410ff67e32c35bff80f21ba2 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 19 Aug 2018 20:27:28 -0400 Subject: add recursive mod search (#583) --- .../Framework/ModScanning/ModFolder.cs | 15 ++--- .../Framework/ModScanning/ModScanner.cs | 64 ++++++++++++++++++++-- 2 files changed, 63 insertions(+), 16 deletions(-) (limited to 'src/StardewModdingAPI.Toolkit/Framework') diff --git a/src/StardewModdingAPI.Toolkit/Framework/ModScanning/ModFolder.cs b/src/StardewModdingAPI.Toolkit/Framework/ModScanning/ModFolder.cs index 4aaa3f83..83c9c44d 100644 --- a/src/StardewModdingAPI.Toolkit/Framework/ModScanning/ModFolder.cs +++ b/src/StardewModdingAPI.Toolkit/Framework/ModScanning/ModFolder.cs @@ -11,11 +11,8 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning /********* ** Accessors *********/ - /// The Mods subfolder containing this mod. - public DirectoryInfo SearchDirectory { get; } - - /// The folder containing manifest.json. - public DirectoryInfo ActualDirectory { get; } + /// The folder containing the mod's manifest.json. + public DirectoryInfo Directory { get; } /// The mod manifest. public Manifest Manifest { get; } @@ -28,14 +25,12 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning ** Public methods *********/ /// Construct an instance. - /// The Mods subfolder containing this mod. - /// The folder containing manifest.json. + /// The folder containing the mod's manifest.json. /// The mod manifest. /// The error which occurred parsing the manifest, if any. - public ModFolder(DirectoryInfo searchDirectory, DirectoryInfo actualDirectory, Manifest manifest, string manifestParseError = null) + public ModFolder(DirectoryInfo directory, Manifest manifest, string manifestParseError = null) { - this.SearchDirectory = searchDirectory; - this.ActualDirectory = actualDirectory; + this.Directory = directory; this.Manifest = manifest; this.ManifestParseError = manifestParseError; } diff --git a/src/StardewModdingAPI.Toolkit/Framework/ModScanning/ModScanner.cs b/src/StardewModdingAPI.Toolkit/Framework/ModScanning/ModScanner.cs index f1cce4a4..063ec2f4 100644 --- a/src/StardewModdingAPI.Toolkit/Framework/ModScanning/ModScanner.cs +++ b/src/StardewModdingAPI.Toolkit/Framework/ModScanning/ModScanner.cs @@ -16,6 +16,14 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning /// The JSON helper with which to read manifests. private readonly JsonHelper JsonHelper; + /// A list of filesystem entry names to ignore when checking whether a folder should be treated as a mod. + private readonly HashSet IgnoreFilesystemEntries = new HashSet(StringComparer.InvariantCultureIgnoreCase) + { + ".DS_Store", + "mcs", + "Thumbs.db" + }; + /********* ** Public methods @@ -31,19 +39,23 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning /// The root folder containing mods. public IEnumerable GetModFolders(string rootPath) { - foreach (DirectoryInfo folder in new DirectoryInfo(rootPath).EnumerateDirectories()) - yield return this.ReadFolder(rootPath, folder); + DirectoryInfo root = new DirectoryInfo(rootPath); + return this.GetModFolders(root, root); } /// Extract information from a mod folder. - /// The root folder containing mods. /// The folder to search for a mod. - public ModFolder ReadFolder(string rootPath, DirectoryInfo searchFolder) + public ModFolder ReadFolder(DirectoryInfo searchFolder) { // find manifest.json FileInfo manifestFile = this.FindManifest(searchFolder); if (manifestFile == null) - return new ModFolder(searchFolder, null, null, "it doesn't have a manifest."); + { + bool isEmpty = !searchFolder.GetFileSystemInfos().Where(this.IsRelevant).Any(); + if (isEmpty) + return new ModFolder(searchFolder, null, "it's an empty folder."); + return new ModFolder(searchFolder, null, "it contains files, but none of them are manifest.json."); + } // read mod info Manifest manifest = null; @@ -64,13 +76,33 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning } } - return new ModFolder(searchFolder, manifestFile.Directory, manifest, manifestError); + return new ModFolder(manifestFile.Directory, manifest, manifestError); } /********* ** Private methods *********/ + /// Recursively extract information about all mods in the given folder. + /// The root mod folder. + /// The folder to search for mods. + public IEnumerable GetModFolders(DirectoryInfo root, DirectoryInfo folder) + { + // recurse into subfolders + if (this.IsModSearchFolder(root, folder)) + { + foreach (DirectoryInfo subfolder in folder.EnumerateDirectories()) + { + foreach (ModFolder match in this.GetModFolders(root, subfolder)) + yield return match; + } + } + + // treat as mod folder + else + yield return this.ReadFolder(folder); + } + /// Find the manifest for a mod folder. /// The folder to search. private FileInfo FindManifest(DirectoryInfo folder) @@ -94,5 +126,25 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning return null; } } + + /// Get whether a given folder should be treated as a search folder (i.e. look for subfolders containing mods). + /// The root mod folder. + /// The folder to search for mods. + private bool IsModSearchFolder(DirectoryInfo root, DirectoryInfo folder) + { + if (root.FullName == folder.FullName) + return true; + + DirectoryInfo[] subfolders = folder.GetDirectories().Where(this.IsRelevant).ToArray(); + FileInfo[] files = folder.GetFiles().Where(this.IsRelevant).ToArray(); + return subfolders.Any() && !files.Any(); + } + + /// Get whether a file or folder is relevant when deciding how to process a mod folder. + /// The file or folder. + private bool IsRelevant(FileSystemInfo entry) + { + return !this.IgnoreFilesystemEntries.Contains(entry.Name); + } } } -- cgit From ca8699c68f238f3092966a550643859bce357a86 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 19 Aug 2018 21:22:48 -0400 Subject: add display name field to ModFolder (#583) --- .../Framework/ModScanning/ModFolder.cs | 13 ++++++++++++- .../Framework/ModScanning/ModScanner.cs | 11 ++++++----- 2 files changed, 18 insertions(+), 6 deletions(-) (limited to 'src/StardewModdingAPI.Toolkit/Framework') diff --git a/src/StardewModdingAPI.Toolkit/Framework/ModScanning/ModFolder.cs b/src/StardewModdingAPI.Toolkit/Framework/ModScanning/ModFolder.cs index 83c9c44d..d2fea9e2 100644 --- a/src/StardewModdingAPI.Toolkit/Framework/ModScanning/ModFolder.cs +++ b/src/StardewModdingAPI.Toolkit/Framework/ModScanning/ModFolder.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using StardewModdingAPI.Toolkit.Serialisation.Models; +using StardewModdingAPI.Toolkit.Utilities; namespace StardewModdingAPI.Toolkit.Framework.ModScanning { @@ -11,6 +12,9 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning /********* ** Accessors *********/ + /// A suggested display name for the mod folder. + public string DisplayName { get; } + /// The folder containing the mod's manifest.json. public DirectoryInfo Directory { get; } @@ -25,14 +29,21 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning ** Public methods *********/ /// Construct an instance. + /// The root folder containing mods. /// The folder containing the mod's manifest.json. /// The mod manifest. /// The error which occurred parsing the manifest, if any. - public ModFolder(DirectoryInfo directory, Manifest manifest, string manifestParseError = null) + public ModFolder(DirectoryInfo root, DirectoryInfo directory, Manifest manifest, string manifestParseError = null) { + // save info this.Directory = directory; this.Manifest = manifest; this.ManifestParseError = manifestParseError; + + // set display name + this.DisplayName = manifest?.Name; + if (string.IsNullOrWhiteSpace(this.DisplayName)) + this.DisplayName = PathUtilities.GetRelativePath(root.FullName, directory.FullName); } /// Get the update keys for a mod. diff --git a/src/StardewModdingAPI.Toolkit/Framework/ModScanning/ModScanner.cs b/src/StardewModdingAPI.Toolkit/Framework/ModScanning/ModScanner.cs index 063ec2f4..71dc0cb3 100644 --- a/src/StardewModdingAPI.Toolkit/Framework/ModScanning/ModScanner.cs +++ b/src/StardewModdingAPI.Toolkit/Framework/ModScanning/ModScanner.cs @@ -44,8 +44,9 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning } /// Extract information from a mod folder. + /// The root folder containing mods. /// The folder to search for a mod. - public ModFolder ReadFolder(DirectoryInfo searchFolder) + public ModFolder ReadFolder(DirectoryInfo root, DirectoryInfo searchFolder) { // find manifest.json FileInfo manifestFile = this.FindManifest(searchFolder); @@ -53,8 +54,8 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning { bool isEmpty = !searchFolder.GetFileSystemInfos().Where(this.IsRelevant).Any(); if (isEmpty) - return new ModFolder(searchFolder, null, "it's an empty folder."); - return new ModFolder(searchFolder, null, "it contains files, but none of them are manifest.json."); + return new ModFolder(root, searchFolder, null, "it's an empty folder."); + return new ModFolder(root, searchFolder, null, "it contains files, but none of them are manifest.json."); } // read mod info @@ -76,7 +77,7 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning } } - return new ModFolder(manifestFile.Directory, manifest, manifestError); + return new ModFolder(root, manifestFile.Directory, manifest, manifestError); } @@ -100,7 +101,7 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning // treat as mod folder else - yield return this.ReadFolder(folder); + yield return this.ReadFolder(root, folder); } /// Find the manifest for a mod folder. -- cgit From d2b6a71aa4cc3383d55350e346e27c1ab2ff134b Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 22 Aug 2018 01:36:11 -0400 Subject: fix crash when a mod manifest is corrupted --- src/StardewModdingAPI.Toolkit/Framework/ModScanning/ModScanner.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/StardewModdingAPI.Toolkit/Framework') diff --git a/src/StardewModdingAPI.Toolkit/Framework/ModScanning/ModScanner.cs b/src/StardewModdingAPI.Toolkit/Framework/ModScanning/ModScanner.cs index 71dc0cb3..7512d5cb 100644 --- a/src/StardewModdingAPI.Toolkit/Framework/ModScanning/ModScanner.cs +++ b/src/StardewModdingAPI.Toolkit/Framework/ModScanning/ModScanner.cs @@ -64,7 +64,7 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning { try { - if (!this.JsonHelper.ReadJsonFileIfExists(manifestFile.FullName, out manifest)) + if (!this.JsonHelper.ReadJsonFileIfExists(manifestFile.FullName, out manifest) || manifest == null) manifestError = "its manifest is invalid."; } catch (SParseException ex) -- cgit From cd83782ef982b9791b233e384170e0064564c041 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 24 Aug 2018 20:35:13 -0400 Subject: fetch mod update keys from wiki when available --- src/StardewModdingAPI.Toolkit/Framework/ModData/ModDataRecord.cs | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'src/StardewModdingAPI.Toolkit/Framework') diff --git a/src/StardewModdingAPI.Toolkit/Framework/ModData/ModDataRecord.cs b/src/StardewModdingAPI.Toolkit/Framework/ModData/ModDataRecord.cs index 82ac8837..3949f7dc 100644 --- a/src/StardewModdingAPI.Toolkit/Framework/ModData/ModDataRecord.cs +++ b/src/StardewModdingAPI.Toolkit/Framework/ModData/ModDataRecord.cs @@ -96,6 +96,15 @@ namespace StardewModdingAPI.Toolkit.Framework.ModData .Distinct(); } + /// Get the default update key for this mod, if any. + public string GetDefaultUpdateKey() + { + string updateKey = this.Fields.FirstOrDefault(p => p.Key == ModDataFieldKey.UpdateKey && p.IsDefault)?.Value; + return !string.IsNullOrWhiteSpace(updateKey) + ? updateKey + : null; + } + /// Get a parsed representation of the which match a given manifest. /// The manifest to match. public ModDataRecordVersionedFields GetVersionedFields(IManifest manifest) -- cgit From 093117d777a84e3f1e3aaa8a1337059fb805a7dd Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 2 Sep 2018 19:06:37 -0400 Subject: add update key parsing to toolkit (#592) --- .../Framework/UpdateData/ModRepositoryKey.cs | 18 ++++++ .../Framework/UpdateData/UpdateKey.cs | 65 ++++++++++++++++++++++ 2 files changed, 83 insertions(+) create mode 100644 src/StardewModdingAPI.Toolkit/Framework/UpdateData/ModRepositoryKey.cs create mode 100644 src/StardewModdingAPI.Toolkit/Framework/UpdateData/UpdateKey.cs (limited to 'src/StardewModdingAPI.Toolkit/Framework') diff --git a/src/StardewModdingAPI.Toolkit/Framework/UpdateData/ModRepositoryKey.cs b/src/StardewModdingAPI.Toolkit/Framework/UpdateData/ModRepositoryKey.cs new file mode 100644 index 00000000..7ca32f04 --- /dev/null +++ b/src/StardewModdingAPI.Toolkit/Framework/UpdateData/ModRepositoryKey.cs @@ -0,0 +1,18 @@ +namespace StardewModdingAPI.Toolkit.Framework.UpdateData +{ + /// A mod repository which SMAPI can check for updates. + public enum ModRepositoryKey + { + /// An unknown or invalid mod repository. + Unknown, + + /// The Chucklefish mod repository. + Chucklefish, + + /// A GitHub project containing releases. + GitHub, + + /// The Nexus Mods mod repository. + Nexus + } +} diff --git a/src/StardewModdingAPI.Toolkit/Framework/UpdateData/UpdateKey.cs b/src/StardewModdingAPI.Toolkit/Framework/UpdateData/UpdateKey.cs new file mode 100644 index 00000000..49b7a272 --- /dev/null +++ b/src/StardewModdingAPI.Toolkit/Framework/UpdateData/UpdateKey.cs @@ -0,0 +1,65 @@ +using System; + +namespace StardewModdingAPI.Toolkit.Framework.UpdateData +{ + /// A namespaced mod ID which uniquely identifies a mod within a mod repository. + public class UpdateKey + { + /********* + ** Accessors + *********/ + /// The raw update key text. + public string RawText { get; } + + /// The mod repository containing the mod. + public ModRepositoryKey Repository { get; } + + /// The mod ID within the repository. + public string ID { get; } + + /// Whether the update key seems to be valid. + public bool LooksValid { get; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The raw update key text. + /// The mod repository containing the mod. + /// The mod ID within the repository. + public UpdateKey(string rawText, ModRepositoryKey repository, string id) + { + this.RawText = rawText; + this.Repository = repository; + this.ID = id; + this.LooksValid = + repository != ModRepositoryKey.Unknown + && !string.IsNullOrWhiteSpace(id); + } + + /// Parse a raw update key. + /// The raw update key to parse. + public static UpdateKey Parse(string raw) + { + // split parts + string[] parts = raw?.Split(':'); + if (parts == null || parts.Length != 2) + return new UpdateKey(raw, ModRepositoryKey.Unknown, null); + + // extract parts + string repositoryKey = parts[0].Trim(); + string id = parts[1].Trim(); + if (string.IsNullOrWhiteSpace(id)) + id = null; + + // parse + if (!Enum.TryParse(repositoryKey, true, out ModRepositoryKey repository)) + return new UpdateKey(raw, ModRepositoryKey.Unknown, id); + if (id == null) + return new UpdateKey(raw, repository, null); + + return new UpdateKey(raw, repository, id); + } + } +} -- cgit From c94f3e7c63a2f1aec89c68417db348d4e684fb79 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 2 Sep 2018 19:19:13 -0400 Subject: only use valid update keys in update-check logic (#592) --- src/StardewModdingAPI.Toolkit/Framework/UpdateData/UpdateKey.cs | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'src/StardewModdingAPI.Toolkit/Framework') diff --git a/src/StardewModdingAPI.Toolkit/Framework/UpdateData/UpdateKey.cs b/src/StardewModdingAPI.Toolkit/Framework/UpdateData/UpdateKey.cs index 49b7a272..865ebcf7 100644 --- a/src/StardewModdingAPI.Toolkit/Framework/UpdateData/UpdateKey.cs +++ b/src/StardewModdingAPI.Toolkit/Framework/UpdateData/UpdateKey.cs @@ -61,5 +61,13 @@ namespace StardewModdingAPI.Toolkit.Framework.UpdateData return new UpdateKey(raw, repository, id); } + + /// Get a string that represents the current object. + public override string ToString() + { + return this.LooksValid + ? $"{this.Repository}:{this.ID}" + : this.RawText; + } } } -- cgit From f2cb952dd1b3752bd172161afadf956b195ec73f Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 6 Sep 2018 21:41:02 -0400 Subject: add support for parallel stable/beta unofficial versions (#594) --- .../Framework/Clients/WebApi/ModEntryModel.cs | 6 ++ .../Clients/WebApi/ModExtendedMetadataModel.cs | 21 +++++++ .../Clients/Wiki/WikiCompatibilityClient.cs | 66 +++++++++++++++------- .../Clients/Wiki/WikiCompatibilityEntry.cs | 32 +++++++++-- 4 files changed, 100 insertions(+), 25 deletions(-) (limited to 'src/StardewModdingAPI.Toolkit/Framework') diff --git a/src/StardewModdingAPI.Toolkit/Framework/Clients/WebApi/ModEntryModel.cs b/src/StardewModdingAPI.Toolkit/Framework/Clients/WebApi/ModEntryModel.cs index 2aafe199..8a9c0a25 100644 --- a/src/StardewModdingAPI.Toolkit/Framework/Clients/WebApi/ModEntryModel.cs +++ b/src/StardewModdingAPI.Toolkit/Framework/Clients/WebApi/ModEntryModel.cs @@ -18,9 +18,15 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi /// The latest unofficial version, if newer than and . public ModEntryVersionModel Unofficial { get; set; } + /// The latest unofficial version for the current Stardew Valley or SMAPI beta, if any (see ). + public ModEntryVersionModel UnofficialForBeta { get; set; } + /// Optional extended data which isn't needed for update checks. public ModExtendedMetadataModel Metadata { get; set; } + /// Whether a Stardew Valley or SMAPI beta which affects mod compatibility is in progress. If this is true, should be used for beta versions of SMAPI instead of . + public bool HasBetaInfo { get; set; } + /// The errors that occurred while fetching update data. public string[] Errors { get; set; } = new string[0]; } diff --git a/src/StardewModdingAPI.Toolkit/Framework/Clients/WebApi/ModExtendedMetadataModel.cs b/src/StardewModdingAPI.Toolkit/Framework/Clients/WebApi/ModExtendedMetadataModel.cs index 21376b36..0f3cb26f 100644 --- a/src/StardewModdingAPI.Toolkit/Framework/Clients/WebApi/ModExtendedMetadataModel.cs +++ b/src/StardewModdingAPI.Toolkit/Framework/Clients/WebApi/ModExtendedMetadataModel.cs @@ -13,6 +13,9 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi /********* ** Accessors *********/ + /**** + ** Mod info + ****/ /// The mod's unique ID. A mod may have multiple current IDs in rare cases (e.g. due to parallel releases or unofficial updates). public string[] ID { get; set; } = new string[0]; @@ -34,6 +37,9 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi /// The custom mod page URL (if applicable). public string CustomUrl { get; set; } + /**** + ** Stable compatibility + ****/ /// The compatibility status. [JsonConverter(typeof(StringEnumConverter))] public WikiCompatibilityStatus? CompatibilityStatus { get; set; } @@ -42,6 +48,17 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi public string CompatibilitySummary { get; set; } + /**** + ** Beta compatibility + ****/ + /// The compatibility status for the Stardew Valley beta (if any). + [JsonConverter(typeof(StringEnumConverter))] + public WikiCompatibilityStatus? BetaCompatibilityStatus { get; set; } + + /// The human-readable summary of the compatibility status or workaround for the Stardew Valley beta (if any), without HTML formatitng. + public string BetaCompatibilitySummary { get; set; } + + /********* ** Public methods *********/ @@ -63,8 +80,12 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi this.GitHubRepo = wiki.GitHubRepo; this.CustomSourceUrl = wiki.CustomSourceUrl; this.CustomUrl = wiki.CustomUrl; + this.CompatibilityStatus = wiki.Status; this.CompatibilitySummary = wiki.Summary; + + this.BetaCompatibilityStatus = wiki.BetaStatus; + this.BetaCompatibilitySummary = wiki.BetaSummary; } // internal DB data diff --git a/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiCompatibilityClient.cs b/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiCompatibilityClient.cs index d0da42df..929284c3 100644 --- a/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiCompatibilityClient.cs +++ b/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiCompatibilityClient.cs @@ -73,26 +73,8 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki { foreach (HtmlNode node in nodes) { - // parse status - WikiCompatibilityStatus status; - { - string rawStatus = node.GetAttributeValue("data-status", null); - if (rawStatus == null) - continue; // not a mod node? - if (!Enum.TryParse(rawStatus, true, out status)) - throw new InvalidOperationException($"Unknown status '{rawStatus}' when parsing compatibility list."); - } - - // parse unofficial version - ISemanticVersion unofficialVersion = null; - { - string rawUnofficialVersion = node.GetAttributeValue("data-unofficial-version", null); - SemanticVersion.TryParse(rawUnofficialVersion, out unofficialVersion); - } - - // parse other fields + // parse mod info string name = node.Descendants("td").FirstOrDefault()?.InnerText?.Trim(); - string summary = node.Descendants("td").FirstOrDefault(p => p.GetAttributeValue("class", null) == "summary")?.InnerText.Trim(); string[] ids = this.GetAttribute(node, "data-id")?.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(p => p.Trim()).ToArray() ?? new string[0]; int? nexusID = this.GetNullableIntAttribute(node, "data-nexus-id"); int? chucklefishID = this.GetNullableIntAttribute(node, "data-chucklefish-id"); @@ -100,23 +82,65 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki string customSourceUrl = this.GetAttribute(node, "data-custom-source"); string customUrl = this.GetAttribute(node, "data-custom-url"); + // parse stable compatibility + WikiCompatibilityStatus status = this.GetStatusAttribute(node, "data-status") ?? WikiCompatibilityStatus.Ok; + ISemanticVersion unofficialVersion = this.GetSemanticVersionAttribute(node, "data-unofficial-version"); + string summary = node.Descendants().FirstOrDefault(p => p.HasClass("data-summary"))?.InnerText.Trim(); + + // parse beta compatibility + WikiCompatibilityStatus? betaStatus = this.GetStatusAttribute(node, "data-beta-status"); + ISemanticVersion betaUnofficialVersion = betaStatus.HasValue ? this.GetSemanticVersionAttribute(node, "data-beta-unofficial-version") : null; + string betaSummary = betaStatus.HasValue ? node.Descendants().FirstOrDefault(p => p.HasClass("data-beta-summary"))?.InnerText.Trim() : null; + // yield model yield return new WikiCompatibilityEntry { + // mod info ID = ids, Name = name, - Status = status, NexusID = nexusID, ChucklefishID = chucklefishID, GitHubRepo = githubRepo, CustomSourceUrl = customSourceUrl, CustomUrl = customUrl, + + // stable compatibility + Status = status, + Summary = summary, UnofficialVersion = unofficialVersion, - Summary = summary + + // beta compatibility + BetaStatus = betaStatus, + BetaSummary = betaSummary, + BetaUnofficialVersion = betaUnofficialVersion }; } } + /// Get a compatibility status attribute value. + /// The HTML node. + /// The attribute name. + private WikiCompatibilityStatus? GetStatusAttribute(HtmlNode node, string attributeName) + { + string raw = node.GetAttributeValue(attributeName, null); + if (raw == null) + return null; // not a mod node? + if (!Enum.TryParse(raw, true, out WikiCompatibilityStatus status)) + throw new InvalidOperationException($"Unknown status '{raw}' when parsing compatibility list."); + return status; + } + + /// Get a semantic version attribute value. + /// The HTML node. + /// The attribute name. + private ISemanticVersion GetSemanticVersionAttribute(HtmlNode node, string attributeName) + { + string raw = node.GetAttributeValue(attributeName, null); + return SemanticVersion.TryParse(raw, out ISemanticVersion version) + ? version + : null; + } + /// Get a nullable integer attribute value. /// The HTML node. /// The attribute name. diff --git a/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiCompatibilityEntry.cs b/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiCompatibilityEntry.cs index 8bc66e20..3cb9c97c 100644 --- a/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiCompatibilityEntry.cs +++ b/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiCompatibilityEntry.cs @@ -3,6 +3,12 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki /// An entry in the mod compatibility list. public class WikiCompatibilityEntry { + /********* + ** Accessors + *********/ + /**** + ** Mod info + ****/ /// The mod's unique ID. A mod may have multiple current IDs in rare cases (e.g. due to parallel releases or unofficial updates). public string[] ID { get; set; } @@ -24,13 +30,31 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki /// The custom mod page URL (if applicable). public string CustomUrl { get; set; } - /// The version of the latest unofficial update, if applicable. - public ISemanticVersion UnofficialVersion { get; set; } - + /**** + ** Stable compatibility + ****/ /// The compatibility status. public WikiCompatibilityStatus Status { get; set; } - /// The human-readable summary of the compatibility status or workaround, without HTML formatitng. + /// The human-readable summary of the compatibility status or workaround, without HTML formatting. public string Summary { get; set; } + + /// The version of the latest unofficial update, if applicable. + public ISemanticVersion UnofficialVersion { get; set; } + + /**** + ** Beta compatibility + ****/ + /// Whether a Stardew Valley or SMAPI beta which affects mod compatibility is in progress. If this is true, should be used for beta versions of SMAPI instead of . + public bool HasBetaInfo => this.BetaStatus != null; + + /// The compatibility status for the Stardew Valley beta, if a beta is in progress. + public WikiCompatibilityStatus? BetaStatus { get; set; } + + /// The human-readable summary of the compatibility status or workaround for the Stardew Valley beta (if any), without HTML formatting. + public string BetaSummary { get; set; } + + /// The version of the latest unofficial update for the Stardew Valley beta (if any), if applicable. + public ISemanticVersion BetaUnofficialVersion { get; set; } } } -- cgit From 074f730329659d0db4be3a99fa8e5c09383ca3e6 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 27 Sep 2018 00:36:31 -0400 Subject: add separate error when player puts an XNB mod in Mods --- .../Framework/ModScanning/ModScanner.cs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) (limited to 'src/StardewModdingAPI.Toolkit/Framework') diff --git a/src/StardewModdingAPI.Toolkit/Framework/ModScanning/ModScanner.cs b/src/StardewModdingAPI.Toolkit/Framework/ModScanning/ModScanner.cs index 7512d5cb..2c23a3ce 100644 --- a/src/StardewModdingAPI.Toolkit/Framework/ModScanning/ModScanner.cs +++ b/src/StardewModdingAPI.Toolkit/Framework/ModScanning/ModScanner.cs @@ -24,6 +24,15 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning "Thumbs.db" }; + /// The extensions for files which an XNB mod may contain. If a mod contains *only* these file extensions, it should be considered an XNB mod. + private readonly HashSet PotentialXnbModExtensions = new HashSet(StringComparer.InvariantCultureIgnoreCase) + { + ".md", + ".png", + ".txt", + ".xnb" + }; + /********* ** Public methods @@ -50,11 +59,15 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning { // find manifest.json FileInfo manifestFile = this.FindManifest(searchFolder); + + // set appropriate invalid-mod error if (manifestFile == null) { - bool isEmpty = !searchFolder.GetFileSystemInfos().Where(this.IsRelevant).Any(); - if (isEmpty) + FileInfo[] files = searchFolder.GetFiles("*", SearchOption.AllDirectories).Where(this.IsRelevant).ToArray(); + if (!files.Any()) return new ModFolder(root, searchFolder, null, "it's an empty folder."); + if (files.All(file => this.PotentialXnbModExtensions.Contains(file.Extension))) + return new ModFolder(root, searchFolder, null, "it's an older XNB mod which replaces game files (not run through SMAPI)."); return new ModFolder(root, searchFolder, null, "it contains files, but none of them are manifest.json."); } -- cgit From 91b3344feafc5c2da6f4560783575c27eb43a42e Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 29 Sep 2018 18:18:01 -0400 Subject: fix mod web API returning a concatenated name for mods with alternate names --- .../Framework/Clients/Wiki/WikiCompatibilityClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/StardewModdingAPI.Toolkit/Framework') diff --git a/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiCompatibilityClient.cs b/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiCompatibilityClient.cs index 929284c3..4060ed36 100644 --- a/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiCompatibilityClient.cs +++ b/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiCompatibilityClient.cs @@ -74,7 +74,7 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki foreach (HtmlNode node in nodes) { // parse mod info - string name = node.Descendants("td").FirstOrDefault()?.InnerText?.Trim(); + string name = node.Descendants("td").FirstOrDefault()?.Descendants("a")?.FirstOrDefault()?.InnerText?.Trim(); string[] ids = this.GetAttribute(node, "data-id")?.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(p => p.Trim()).ToArray() ?? new string[0]; int? nexusID = this.GetNullableIntAttribute(node, "data-nexus-id"); int? chucklefishID = this.GetNullableIntAttribute(node, "data-chucklefish-id"); -- cgit From f09befe24047de8187276c722557b6f0fddd6e35 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 20 Oct 2018 14:55:13 -0400 Subject: expand metadata fetched from the wiki (#597) --- .../Clients/WebApi/ModExtendedMetadataModel.cs | 10 +- .../Clients/Wiki/WikiCompatibilityClient.cs | 123 ++++++++++++--------- .../Clients/Wiki/WikiCompatibilityEntry.cs | 60 ---------- .../Clients/Wiki/WikiCompatibilityInfo.cs | 21 ++++ .../Framework/Clients/Wiki/WikiModEntry.cs | 54 +++++++++ 5 files changed, 148 insertions(+), 120 deletions(-) delete mode 100644 src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiCompatibilityEntry.cs create mode 100644 src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiCompatibilityInfo.cs create mode 100644 src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiModEntry.cs (limited to 'src/StardewModdingAPI.Toolkit/Framework') diff --git a/src/StardewModdingAPI.Toolkit/Framework/Clients/WebApi/ModExtendedMetadataModel.cs b/src/StardewModdingAPI.Toolkit/Framework/Clients/WebApi/ModExtendedMetadataModel.cs index 0f3cb26f..e6f2e4b4 100644 --- a/src/StardewModdingAPI.Toolkit/Framework/Clients/WebApi/ModExtendedMetadataModel.cs +++ b/src/StardewModdingAPI.Toolkit/Framework/Clients/WebApi/ModExtendedMetadataModel.cs @@ -68,7 +68,7 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi /// Construct an instance. /// The mod metadata from the wiki (if available). /// The mod metadata from SMAPI's internal DB (if available). - public ModExtendedMetadataModel(WikiCompatibilityEntry wiki, ModDataRecord db) + public ModExtendedMetadataModel(WikiModEntry wiki, ModDataRecord db) { // wiki data if (wiki != null) @@ -81,11 +81,11 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi this.CustomSourceUrl = wiki.CustomSourceUrl; this.CustomUrl = wiki.CustomUrl; - this.CompatibilityStatus = wiki.Status; - this.CompatibilitySummary = wiki.Summary; + this.CompatibilityStatus = wiki.Compatibility.Status; + this.CompatibilitySummary = wiki.Compatibility.Summary; - this.BetaCompatibilityStatus = wiki.BetaStatus; - this.BetaCompatibilitySummary = wiki.BetaSummary; + this.BetaCompatibilityStatus = wiki.BetaCompatibility?.Status; + this.BetaCompatibilitySummary = wiki.BetaCompatibility?.Summary; } // internal DB data diff --git a/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiCompatibilityClient.cs b/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiCompatibilityClient.cs index 4060ed36..569e820b 100644 --- a/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiCompatibilityClient.cs +++ b/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiCompatibilityClient.cs @@ -30,7 +30,7 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki } /// Fetch mod compatibility entries. - public async Task FetchAsync() + public async Task FetchAsync() { // fetch HTML ResponseModel response = await this.Client @@ -69,100 +69,113 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki *********/ /// Parse valid mod compatibility entries. /// The HTML compatibility entries. - private IEnumerable ParseEntries(IEnumerable nodes) + private IEnumerable ParseEntries(IEnumerable nodes) { foreach (HtmlNode node in nodes) { - // parse mod info - string name = node.Descendants("td").FirstOrDefault()?.Descendants("a")?.FirstOrDefault()?.InnerText?.Trim(); - string[] ids = this.GetAttribute(node, "data-id")?.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(p => p.Trim()).ToArray() ?? new string[0]; - int? nexusID = this.GetNullableIntAttribute(node, "data-nexus-id"); - int? chucklefishID = this.GetNullableIntAttribute(node, "data-chucklefish-id"); - string githubRepo = this.GetAttribute(node, "data-github"); - string customSourceUrl = this.GetAttribute(node, "data-custom-source"); - string customUrl = this.GetAttribute(node, "data-custom-url"); + // 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-chucklefish-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"); // parse stable compatibility - WikiCompatibilityStatus status = this.GetStatusAttribute(node, "data-status") ?? WikiCompatibilityStatus.Ok; - ISemanticVersion unofficialVersion = this.GetSemanticVersionAttribute(node, "data-unofficial-version"); - string summary = node.Descendants().FirstOrDefault(p => p.HasClass("data-summary"))?.InnerText.Trim(); + 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() + }; // parse beta compatibility - WikiCompatibilityStatus? betaStatus = this.GetStatusAttribute(node, "data-beta-status"); - ISemanticVersion betaUnofficialVersion = betaStatus.HasValue ? this.GetSemanticVersionAttribute(node, "data-beta-unofficial-version") : null; - string betaSummary = betaStatus.HasValue ? node.Descendants().FirstOrDefault(p => p.HasClass("data-beta-summary"))?.InnerText.Trim() : null; + WikiCompatibilityInfo betaCompatibility = null; + { + WikiCompatibilityStatus? betaStatus = this.GetStatusField(node, "mod-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") + }; + } + } // yield model - yield return new WikiCompatibilityEntry + yield return new WikiModEntry { - // mod info ID = ids, Name = name, + AlternateNames = alternateNames, + Author = author, + AlternateAuthors = alternateAuthors, NexusID = nexusID, ChucklefishID = chucklefishID, GitHubRepo = githubRepo, CustomSourceUrl = customSourceUrl, CustomUrl = customUrl, - - // stable compatibility - Status = status, - Summary = summary, - UnofficialVersion = unofficialVersion, - - // beta compatibility - BetaStatus = betaStatus, - BetaSummary = betaSummary, - BetaUnofficialVersion = betaUnofficialVersion + BrokeIn = brokeIn, + Compatibility = compatibility, + BetaCompatibility = betaCompatibility, + Anchor = anchor }; } } - /// Get a compatibility status attribute value. - /// The HTML node. - /// The attribute name. - private WikiCompatibilityStatus? GetStatusAttribute(HtmlNode node, string attributeName) + /// Get the value of a metadata field. + /// The metadata container. + /// The field name. + private string GetMetadataField(HtmlNode container, string name) { - string raw = node.GetAttributeValue(attributeName, null); + return container.Descendants().FirstOrDefault(p => p.HasClass(name))?.InnerHtml; + } + + /// Get the value of a metadata field as a compatibility status. + /// The metadata container. + /// The field name. + private WikiCompatibilityStatus? GetStatusField(HtmlNode container, string name) + { + string raw = this.GetMetadataField(container, name); if (raw == null) - return null; // not a mod node? + return null; if (!Enum.TryParse(raw, true, out WikiCompatibilityStatus status)) throw new InvalidOperationException($"Unknown status '{raw}' when parsing compatibility list."); return status; } - /// Get a semantic version attribute value. - /// The HTML node. - /// The attribute name. - private ISemanticVersion GetSemanticVersionAttribute(HtmlNode node, string attributeName) + /// Get the value of a metadata field as a semantic version. + /// The metadata container. + /// The field name. + private ISemanticVersion GetSemanticVersionField(HtmlNode container, string name) { - string raw = node.GetAttributeValue(attributeName, null); + string raw = this.GetMetadataField(container, name); return SemanticVersion.TryParse(raw, out ISemanticVersion version) ? version : null; } - /// Get a nullable integer attribute value. - /// The HTML node. - /// The attribute name. - private int? GetNullableIntAttribute(HtmlNode node, string attributeName) + /// Get the value of a metadata field as a nullable integer. + /// The metadata container. + /// The field name. + private int? GetNullableIntField(HtmlNode container, string name) { - string raw = this.GetAttribute(node, attributeName); + string raw = this.GetMetadataField(container, name); if (raw != null && int.TryParse(raw, out int value)) return value; return null; } - /// Get a strings attribute value. - /// The HTML node. - /// The attribute name. - private string GetAttribute(HtmlNode node, string attributeName) - { - string raw = node.GetAttributeValue(attributeName, null); - if (raw != null) - raw = HtmlEntity.DeEntitize(raw); - return raw; - } - /// The response model for the MediaWiki parse API. [SuppressMessage("ReSharper", "ClassNeverInstantiated.Local")] [SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local")] diff --git a/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiCompatibilityEntry.cs b/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiCompatibilityEntry.cs deleted file mode 100644 index 3cb9c97c..00000000 --- a/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiCompatibilityEntry.cs +++ /dev/null @@ -1,60 +0,0 @@ -namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki -{ - /// An entry in the mod compatibility list. - public class WikiCompatibilityEntry - { - /********* - ** Accessors - *********/ - /**** - ** Mod info - ****/ - /// The mod's unique ID. A mod may have multiple current IDs in rare cases (e.g. due to parallel releases or unofficial updates). - public string[] ID { get; set; } - - /// The mod's display name. - public string Name { get; set; } - - /// The mod ID on Nexus. - public int? NexusID { get; set; } - - /// The mod ID in the Chucklefish mod repo. - public int? ChucklefishID { get; set; } - - /// The GitHub repository in the form 'owner/repo'. - public string GitHubRepo { get; set; } - - /// The URL to a non-GitHub source repo. - public string CustomSourceUrl { get; set; } - - /// The custom mod page URL (if applicable). - public string CustomUrl { get; set; } - - /**** - ** Stable compatibility - ****/ - /// The compatibility status. - public WikiCompatibilityStatus Status { get; set; } - - /// The human-readable summary of the compatibility status or workaround, without HTML formatting. - public string Summary { get; set; } - - /// The version of the latest unofficial update, if applicable. - public ISemanticVersion UnofficialVersion { get; set; } - - /**** - ** Beta compatibility - ****/ - /// Whether a Stardew Valley or SMAPI beta which affects mod compatibility is in progress. If this is true, should be used for beta versions of SMAPI instead of . - public bool HasBetaInfo => this.BetaStatus != null; - - /// The compatibility status for the Stardew Valley beta, if a beta is in progress. - public WikiCompatibilityStatus? BetaStatus { get; set; } - - /// The human-readable summary of the compatibility status or workaround for the Stardew Valley beta (if any), without HTML formatting. - public string BetaSummary { get; set; } - - /// The version of the latest unofficial update for the Stardew Valley beta (if any), if applicable. - public ISemanticVersion BetaUnofficialVersion { get; set; } - } -} diff --git a/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiCompatibilityInfo.cs b/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiCompatibilityInfo.cs new file mode 100644 index 00000000..2725df1a --- /dev/null +++ b/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiCompatibilityInfo.cs @@ -0,0 +1,21 @@ +namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki +{ + /// Compatibility info for a mod. + public class WikiCompatibilityInfo + { + /********* + ** Accessors + *********/ + /// The compatibility status. + public WikiCompatibilityStatus Status { get; set; } + + /// The human-readable summary of the compatibility status or workaround, without HTML formatting. + public string Summary { get; set; } + + /// The version of the latest unofficial update, if applicable. + public ISemanticVersion UnofficialVersion { get; set; } + + /// The URL to the latest unofficial update, if applicable. + public string UnofficialUrl { get; set; } + } +} diff --git a/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiModEntry.cs b/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiModEntry.cs new file mode 100644 index 00000000..752b526c --- /dev/null +++ b/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiModEntry.cs @@ -0,0 +1,54 @@ +namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki +{ + /// A mod entry in the wiki list. + public class WikiModEntry + { + /********* + ** Accessors + *********/ + /// The mod's unique ID. A mod may have multiple current IDs in rare cases (e.g. due to parallel releases or unofficial updates). + public string[] ID { get; set; } + + /// The mod's display name. + public string Name { get; set; } + + /// The mod's alternative names, if any. + public string AlternateNames { get; set; } + + /// The mod's author name. + public string Author { get; set; } + + /// The mod's alternative author names, if any. + public string AlternateAuthors { get; set; } + + /// The mod ID on Nexus. + public int? NexusID { get; set; } + + /// The mod ID in the Chucklefish mod repo. + public int? ChucklefishID { get; set; } + + /// The GitHub repository in the form 'owner/repo'. + public string GitHubRepo { get; set; } + + /// The URL to a non-GitHub source repo. + public string CustomSourceUrl { get; set; } + + /// The custom mod page URL (if applicable). + public string CustomUrl { get; set; } + + /// The game or SMAPI version which broke this mod (if applicable). + public string BrokeIn { get; set; } + + /// The mod's compatibility with the latest stable version of the game. + public WikiCompatibilityInfo Compatibility { get; set; } + + /// The mod's compatibility with the latest beta version of the game (if any). + public WikiCompatibilityInfo BetaCompatibility { get; set; } + + /// Whether a Stardew Valley or SMAPI beta which affects mod compatibility is in progress. If this is true, should be used for beta versions of SMAPI instead of . + public bool HasBetaInfo => this.BetaCompatibility != null; + + /// The link anchor for the mod entry in the wiki compatibility list. + public string Anchor { get; set; } + } +} -- cgit From 4272669d89860846ba2dd5ef2896c1923057606c Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 20 Oct 2018 17:43:42 -0400 Subject: fix Chucklefish pages not being linked (#597) --- .../Framework/Clients/Wiki/WikiCompatibilityClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/StardewModdingAPI.Toolkit/Framework') diff --git a/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiCompatibilityClient.cs b/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiCompatibilityClient.cs index 569e820b..20436e66 100644 --- a/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiCompatibilityClient.cs +++ b/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiCompatibilityClient.cs @@ -80,7 +80,7 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki 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-chucklefish-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"); -- cgit From de561e52d7785597f1af2c6fd0d712d19ac5f928 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 20 Oct 2018 20:19:12 -0400 Subject: fetch game versions from the wiki (#597) --- .../Framework/Clients/Wiki/WikiClient.cs | 208 +++++++++++++++++++++ .../Clients/Wiki/WikiCompatibilityClient.cs | 198 -------------------- .../Framework/Clients/Wiki/WikiModList.cs | 18 ++ 3 files changed, 226 insertions(+), 198 deletions(-) create mode 100644 src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiClient.cs delete mode 100644 src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiCompatibilityClient.cs create mode 100644 src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiModList.cs (limited to 'src/StardewModdingAPI.Toolkit/Framework') diff --git a/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiClient.cs b/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiClient.cs new file mode 100644 index 00000000..9be760f8 --- /dev/null +++ b/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiClient.cs @@ -0,0 +1,208 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Threading.Tasks; +using HtmlAgilityPack; +using Pathoschild.Http.Client; + +namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki +{ + /// An HTTP client for fetching mod metadata from the wiki. + public class WikiClient : IDisposable + { + /********* + ** Properties + *********/ + /// The underlying HTTP client. + private readonly IClient Client; + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The user agent for the wiki API. + /// The base URL for the wiki API. + public WikiClient(string userAgent, string baseUrl = "https://stardewvalleywiki.com/mediawiki/api.php") + { + this.Client = new FluentClient(baseUrl).SetUserAgent(userAgent); + } + + /// Fetch mods from the compatibility list. + public async Task FetchModsAsync() + { + // fetch HTML + ResponseModel response = await this.Client + .GetAsync("") + .WithArguments(new + { + action = "parse", + page = "Modding:SMAPI_compatibility", + format = "json" + }) + .As(); + 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; + + // 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 + }; + } + + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + public void Dispose() + { + this.Client?.Dispose(); + } + + + /********* + ** Private methods + *********/ + /// Parse valid mod compatibility entries. + /// The HTML compatibility entries. + private IEnumerable ParseEntries(IEnumerable nodes) + { + 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"); + + // parse stable compatibility + WikiCompatibilityInfo compatibility = new WikiCompatibilityInfo + { + Status = this.GetStatusField(node, "mod-status") ?? WikiCompatibilityStatus.Ok, + UnofficialVersion = this.GetSemanticVersionField(node, "mod-unofficial-version"