summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJesse Plamondon-Willard <github@jplamondonw.com>2017-09-24 01:10:17 -0400
committerJesse Plamondon-Willard <github@jplamondonw.com>2017-09-24 01:10:17 -0400
commitd3f0c8e4d2d9ada099cba67c359c5df1d69a1257 (patch)
tree992e6bbf83bb19357f7486f902998fec53c9f214
parent0863f9b7e5f165f2b1db8750b20ed35bc0c3701a (diff)
downloadSMAPI-d3f0c8e4d2d9ada099cba67c359c5df1d69a1257.tar.gz
SMAPI-d3f0c8e4d2d9ada099cba67c359c5df1d69a1257.tar.bz2
SMAPI-d3f0c8e4d2d9ada099cba67c359c5df1d69a1257.zip
add support for update checks from the Chucklefish mod site (#336)
-rw-r--r--release-notes.md2
-rw-r--r--src/StardewModdingAPI.Web/Controllers/ModsController.cs11
-rw-r--r--src/StardewModdingAPI.Web/Framework/ConfigModels/ModUpdateCheckConfig.cs18
-rw-r--r--src/StardewModdingAPI.Web/Framework/ModRepositories/ChucklefishRepository.cs94
-rw-r--r--src/StardewModdingAPI.Web/Framework/ModRepositories/GitHubRepository.cs4
-rw-r--r--src/StardewModdingAPI.Web/StardewModdingAPI.Web.csproj1
-rw-r--r--src/StardewModdingAPI.Web/appsettings.json7
-rw-r--r--src/StardewModdingAPI/Framework/Models/Manifest.cs3
-rw-r--r--src/StardewModdingAPI/IManifest.cs7
-rw-r--r--src/StardewModdingAPI/Program.cs2
10 files changed, 140 insertions, 9 deletions
diff --git a/release-notes.md b/release-notes.md
index a07de71f..41d946c7 100644
--- a/release-notes.md
+++ b/release-notes.md
@@ -14,7 +14,7 @@ For players:
For mod developers:
* Added new APIs to edit, inject, and reload XNB assets loaded by the game at any time.
<small>_This let mods do anything previously only possible with XNB mods, plus enables new mod scenarios (e.g. seasonal textures, NPC clothing that depend on the weather or location, etc)._</small>
-* Added new manifest fields to enable automatic update checks.
+* Added support for automatic update checks from Chucklefish, GitHub, or Nexus Mods.
* Added new input events.
<small>_The new `InputEvents` combine keyboard + mouse + controller input into one event for easy handling, add metadata like the cursor position and grab tile to support click handling, and add an option to suppress input from the game to enable new scenarios like action highjacking and UI overlays._</small>
* Added support for optional dependencies.
diff --git a/src/StardewModdingAPI.Web/Controllers/ModsController.cs b/src/StardewModdingAPI.Web/Controllers/ModsController.cs
index 8fc2cb51..c5c79600 100644
--- a/src/StardewModdingAPI.Web/Controllers/ModsController.cs
+++ b/src/StardewModdingAPI.Web/Controllers/ModsController.cs
@@ -5,9 +5,9 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
+using StardewModdingAPI.Models;
using StardewModdingAPI.Web.Framework.ConfigModels;
using StardewModdingAPI.Web.Framework.ModRepositories;
-using StardewModdingAPI.Models;
namespace StardewModdingAPI.Web.Controllers
{
@@ -41,14 +41,21 @@ namespace StardewModdingAPI.Web.Controllers
this.Cache = cache;
this.CacheMinutes = config.CacheMinutes;
+ string version = this.GetType().Assembly.GetName().Version.ToString(3);
this.Repositories =
new IModRepository[]
{
+ new ChucklefishRepository(
+ vendorKey: config.ChucklefishKey,
+ userAgent: string.Format(config.ChucklefishUserAgent, version),
+ baseUrl: config.ChucklefishBaseUrl,
+ modPageUrlFormat: config.ChucklefishModPageUrlFormat
+ ),
new GitHubRepository(
vendorKey: config.GitHubKey,
baseUrl: config.GitHubBaseUrl,
releaseUrlFormat: config.GitHubReleaseUrlFormat,
- userAgent: config.GitHubUserAgent,
+ userAgent: string.Format(config.GitHubUserAgent, version),
acceptHeader: config.GitHubAcceptHeader,
username: config.GitHubUsername,
password: config.GitHubPassword
diff --git a/src/StardewModdingAPI.Web/Framework/ConfigModels/ModUpdateCheckConfig.cs b/src/StardewModdingAPI.Web/Framework/ConfigModels/ModUpdateCheckConfig.cs
index 5d55ba18..8ca555a3 100644
--- a/src/StardewModdingAPI.Web/Framework/ConfigModels/ModUpdateCheckConfig.cs
+++ b/src/StardewModdingAPI.Web/Framework/ConfigModels/ModUpdateCheckConfig.cs
@@ -13,12 +13,28 @@ namespace StardewModdingAPI.Web.Framework.ConfigModels
public int CacheMinutes { get; set; }
/****
+ ** Chucklefish mod site
+ ****/
+ /// <summary>The repository key for the Chucklefish mod site.</summary>
+ public string ChucklefishKey { get; set; }
+
+ /// <summary>The user agent for the Chucklefish API client, where {0} is the SMAPI version.</summary>
+ public string ChucklefishUserAgent { get; set; }
+
+ /// <summary>The base URL for the Chucklefish mod site.</summary>
+ public string ChucklefishBaseUrl { get; set; }
+
+ /// <summary>The URL for a mod page on the Chucklefish mod site excluding the <see cref="GitHubBaseUrl"/>, where {0} is the mod ID.</summary>
+ public string ChucklefishModPageUrlFormat { get; set; }
+
+
+ /****
** GitHub
****/
/// <summary>The repository key for Nexus Mods.</summary>
public string GitHubKey { get; set; }
- /// <summary>The user agent for the GitHub API client.</summary>
+ /// <summary>The user agent for the GitHub API client, where {0} is the SMAPI version.</summary>
public string GitHubUserAgent { get; set; }
/// <summary>The base URL for the GitHub API.</summary>
diff --git a/src/StardewModdingAPI.Web/Framework/ModRepositories/ChucklefishRepository.cs b/src/StardewModdingAPI.Web/Framework/ModRepositories/ChucklefishRepository.cs
new file mode 100644
index 00000000..59d7f3ba
--- /dev/null
+++ b/src/StardewModdingAPI.Web/Framework/ModRepositories/ChucklefishRepository.cs
@@ -0,0 +1,94 @@
+using System;
+using System.Net;
+using System.Threading.Tasks;
+using HtmlAgilityPack;
+using Pathoschild.Http.Client;
+using StardewModdingAPI.Models;
+
+namespace StardewModdingAPI.Web.Framework.ModRepositories
+{
+ /// <summary>An HTTP client for fetching mod metadata from the Chucklefish mod site.</summary>
+ internal class ChucklefishRepository : IModRepository
+ {
+ /*********
+ ** Properties
+ *********/
+ /// <summary>The underlying HTTP client.</summary>
+ private readonly IClient Client;
+
+
+ /*********
+ ** Accessors
+ *********/
+ /// <summary>The unique key for this vendor.</summary>
+ public string VendorKey { get; }
+
+ /// <summary>The base URL for the Chucklefish mod site.</summary>
+ public string BaseUrl { get; }
+
+ /// <summary>The URL for a mod page excluding the base URL, where {0} is the mod ID.</summary>
+ public string ModPageUrlFormat { get; }
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Construct an instance.</summary>
+ /// <param name="vendorKey">The unique key for this vendor.</param>
+ /// <param name="userAgent">The user agent for the API client.</param>
+ /// <param name="baseUrl">The base URL for the Chucklefish mod site.</param>
+ /// <param name="modPageUrlFormat">The URL for a mod page excluding the <paramref name="baseUrl"/>, where {0} is the mod ID.</param>
+ public ChucklefishRepository(string vendorKey, string userAgent, string baseUrl, string modPageUrlFormat)
+ {
+ this.VendorKey = vendorKey;
+ this.BaseUrl = baseUrl;
+ this.ModPageUrlFormat = modPageUrlFormat;
+ this.Client = new FluentClient(baseUrl).SetUserAgent(userAgent);
+ }
+
+ /// <summary>Get metadata about a mod in the repository.</summary>
+ /// <param name="id">The mod ID in this repository.</param>
+ public async Task<ModInfoModel> GetModInfoAsync(string id)
+ {
+ try
+ {
+ // fetch HTML
+ string html;
+ try
+ {
+ html = await this.Client
+ .GetAsync(string.Format(this.ModPageUrlFormat, id))
+ .AsString();
+ }
+ catch (ApiException ex) when (ex.Status == HttpStatusCode.NotFound)
+ {
+ return new ModInfoModel("Found no mod with this ID.");
+ }
+
+ // parse HTML
+ var doc = new HtmlDocument();
+ doc.LoadHtml(html);
+
+ // extract mod info
+ string url = new UriBuilder(new Uri(this.BaseUrl)) { Path = string.Format(this.ModPageUrlFormat, id) }.Uri.ToString();
+ string name = doc.DocumentNode.SelectSingleNode("//meta[@name='twitter:title']").Attributes["content"].Value;
+ if (name.StartsWith("[SMAPI] "))
+ name = name.Substring("[SMAPI] ".Length);
+ string version = doc.DocumentNode.SelectSingleNode("//h1/span").InnerText;
+
+ // create model
+ return new ModInfoModel(name, version, url);
+ }
+ catch (Exception ex)
+ {
+ return new ModInfoModel(ex.ToString());
+ }
+ }
+
+ /// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
+ public void Dispose()
+ {
+ this.Client.Dispose();
+ }
+ }
+}
diff --git a/src/StardewModdingAPI.Web/Framework/ModRepositories/GitHubRepository.cs b/src/StardewModdingAPI.Web/Framework/ModRepositories/GitHubRepository.cs
index 421220de..b08e8b4d 100644
--- a/src/StardewModdingAPI.Web/Framework/ModRepositories/GitHubRepository.cs
+++ b/src/StardewModdingAPI.Web/Framework/ModRepositories/GitHubRepository.cs
@@ -33,7 +33,7 @@ namespace StardewModdingAPI.Web.Framework.ModRepositories
/// <param name="vendorKey">The unique key for this vendor.</param>
/// <param name="baseUrl">The base URL for the Nexus Mods API.</param>
/// <param name="releaseUrlFormat">The URL for a Nexus Mods API query excluding the <paramref name="baseUrl"/>, where {0} is the mod ID.</param>
- /// <param name="userAgent">The user agent for the GitHub API client.</param>
+ /// <param name="userAgent">The user agent for the API client.</param>
/// <param name="acceptHeader">The Accept header value expected by the GitHub API.</param>
/// <param name="username">The username with which to authenticate to the GitHub API.</param>
/// <param name="password">The password with which to authenticate to the GitHub API.</param>
@@ -43,7 +43,7 @@ namespace StardewModdingAPI.Web.Framework.ModRepositories
this.ReleaseUrlFormat = releaseUrlFormat;
this.Client = new FluentClient(baseUrl)
- .SetUserAgent(string.Format(userAgent, this.GetType().Assembly.GetName().Version))
+ .SetUserAgent(userAgent)
.AddDefault(req => req.WithHeader("Accept", acceptHeader));
if (!string.IsNullOrWhiteSpace(username))
this.Client = this.Client.SetBasicAuthentication(username, password);
diff --git a/src/StardewModdingAPI.Web/StardewModdingAPI.Web.csproj b/src/StardewModdingAPI.Web/StardewModdingAPI.Web.csproj
index c30abc55..bf67449b 100644
--- a/src/StardewModdingAPI.Web/StardewModdingAPI.Web.csproj
+++ b/src/StardewModdingAPI.Web/StardewModdingAPI.Web.csproj
@@ -5,6 +5,7 @@
</PropertyGroup>
<ItemGroup>
+ <PackageReference Include="HtmlAgilityPack" Version="1.5.5" />
<PackageReference Include="Microsoft.AspNetCore" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Rewrite" Version="2.0.0" />
diff --git a/src/StardewModdingAPI.Web/appsettings.json b/src/StardewModdingAPI.Web/appsettings.json
index 29fb195e..a0d4d078 100644
--- a/src/StardewModdingAPI.Web/appsettings.json
+++ b/src/StardewModdingAPI.Web/appsettings.json
@@ -8,6 +8,11 @@
"ModUpdateCheck": {
"CacheMinutes": 60,
+ "ChucklefishKey": "Chucklefish",
+ "ChucklefishUserAgent": "SMAPI/{0} (+https://github.com/Pathoschild/SMAPI)",
+ "ChucklefishBaseUrl": "https://community.playstarbound.com",
+ "ChucklefishModPageUrlFormat": "resources/{0}",
+
"GitHubKey": "GitHub",
"GitHubUserAgent": "SMAPI/{0} (+https://github.com/Pathoschild/SMAPI)",
"GitHubBaseUrl": "https://api.github.com",
@@ -20,5 +25,5 @@
"NexusUserAgent": "Nexus Client v0.63.15",
"NexusBaseUrl": "http://www.nexusmods.com/stardewvalley",
"NexusModUrlFormat": "mods/{0}"
- }
+ }
}
diff --git a/src/StardewModdingAPI/Framework/Models/Manifest.cs b/src/StardewModdingAPI/Framework/Models/Manifest.cs
index 2e9566bf..c891644f 100644
--- a/src/StardewModdingAPI/Framework/Models/Manifest.cs
+++ b/src/StardewModdingAPI/Framework/Models/Manifest.cs
@@ -34,6 +34,9 @@ namespace StardewModdingAPI.Framework.Models
[JsonConverter(typeof(SFieldConverter))]
public IManifestDependency[] Dependencies { get; set; }
+ /// <summary>The mod's unique ID in the Chucklefish mod site (if any), used for update checks.</summary>
+ public string ChucklefishID { get; set; }
+
/// <summary>The mod's unique ID in Nexus Mods (if any), used for update checks.</summary>
public string NexusID { get; set; }
diff --git a/src/StardewModdingAPI/IManifest.cs b/src/StardewModdingAPI/IManifest.cs
index 28f6570c..9513834a 100644
--- a/src/StardewModdingAPI/IManifest.cs
+++ b/src/StardewModdingAPI/IManifest.cs
@@ -32,11 +32,14 @@ namespace StardewModdingAPI
/// <summary>The other mods that must be loaded before this mod.</summary>
IManifestDependency[] Dependencies { get; }
+ /// <summary>The mod's unique ID in the Chucklefish mod site (if any), used for update checks.</summary>
+ string ChucklefishID { get; }
+
/// <summary>The mod's unique ID in Nexus Mods (if any), used for update checks.</summary>
- string NexusID { get; set; }
+ string NexusID { get; }
/// <summary>The mod's organisation and project name on GitHub (if any), used for update checks.</summary>
- string GitHubProject { get; set; }
+ string GitHubProject { get; }
/// <summary>Any manifest fields which didn't match a valid field.</summary>
IDictionary<string, object> ExtraFields { get; }
diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs
index 4860968c..f821b559 100644
--- a/src/StardewModdingAPI/Program.cs
+++ b/src/StardewModdingAPI/Program.cs
@@ -522,6 +522,8 @@ namespace StardewModdingAPI
IDictionary<string, IModMetadata> modsByKey = new Dictionary<string, IModMetadata>(StringComparer.InvariantCultureIgnoreCase);
foreach (IModMetadata mod in mods)
{
+ if (!string.IsNullOrWhiteSpace(mod.Manifest.ChucklefishID))
+ modsByKey[$"Chucklefish:{mod.Manifest.ChucklefishID}"] = mod;
if (!string.IsNullOrWhiteSpace(mod.Manifest.NexusID))
modsByKey[$"Nexus:{mod.Manifest.NexusID}"] = mod;
if (!string.IsNullOrWhiteSpace(mod.Manifest.GitHubProject))