add support for update checks from the Chucklefish mod site (#336)
This commit is contained in:
parent
0863f9b7e5
commit
d3f0c8e4d2
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -12,13 +12,29 @@ namespace StardewModdingAPI.Web.Framework.ConfigModels
|
|||
/// <summary>The number of minutes update checks should be cached before refetching them.</summary>
|
||||
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>
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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; }
|
||||
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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))
|
||||
|
|
Loading…
Reference in New Issue