add support for preview GitHub releases (#457)
This commit is contained in:
parent
9e052ae916
commit
436c071ba4
|
@ -9,9 +9,12 @@ namespace StardewModdingAPI.Common.Models
|
||||||
/// <summary>The mod name.</summary>
|
/// <summary>The mod name.</summary>
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
|
|
||||||
/// <summary>The mod's semantic version number.</summary>
|
/// <summary>The semantic version for the mod's latest release.</summary>
|
||||||
public string Version { get; set; }
|
public string Version { get; set; }
|
||||||
|
|
||||||
|
/// <summary>The semantic version for the mod's latest preview release, if available and different from <see cref="Version"/>.</summary>
|
||||||
|
public string PreviewVersion { get; set; }
|
||||||
|
|
||||||
/// <summary>The mod's web URL.</summary>
|
/// <summary>The mod's web URL.</summary>
|
||||||
public string Url { get; set; }
|
public string Url { get; set; }
|
||||||
|
|
||||||
|
@ -28,16 +31,17 @@ namespace StardewModdingAPI.Common.Models
|
||||||
// needed for JSON deserialising
|
// needed for JSON deserialising
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>Construct an instance.</summary>
|
/// <summary>Construct an instance.</summary>
|
||||||
/// <param name="name">The mod name.</param>
|
/// <param name="name">The mod name.</param>
|
||||||
/// <param name="version">The mod's semantic version number.</param>
|
/// <param name="version">The semantic version for the mod's latest release.</param>
|
||||||
|
/// <param name="previewVersion">The semantic version for the mod's latest preview release, if available and different from <see cref="Version"/>.</param>
|
||||||
/// <param name="url">The mod's web URL.</param>
|
/// <param name="url">The mod's web URL.</param>
|
||||||
/// <param name="error">The error message indicating why the mod is invalid (if applicable).</param>
|
/// <param name="error">The error message indicating why the mod is invalid (if applicable).</param>
|
||||||
public ModInfoModel(string name, string version, string url, string error = null)
|
public ModInfoModel(string name, string version, string url, string previewVersion = null, string error = null)
|
||||||
{
|
{
|
||||||
this.Name = name;
|
this.Name = name;
|
||||||
this.Version = version;
|
this.Version = version;
|
||||||
|
this.PreviewVersion = previewVersion;
|
||||||
this.Url = url;
|
this.Url = url;
|
||||||
this.Error = error; // mainly initialised here for the JSON deserialiser
|
this.Error = error; // mainly initialised here for the JSON deserialiser
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Pathoschild.Http.Client;
|
using Pathoschild.Http.Client;
|
||||||
|
@ -11,8 +12,11 @@ namespace StardewModdingAPI.Web.Framework.Clients.GitHub
|
||||||
/*********
|
/*********
|
||||||
** Properties
|
** Properties
|
||||||
*********/
|
*********/
|
||||||
/// <summary>The URL for a GitHub releases API query excluding the base URL, where {0} is the repository owner and name.</summary>
|
/// <summary>The URL for a GitHub API query for the latest stable release, excluding the base URL, where {0} is the organisation and project name.</summary>
|
||||||
private readonly string ReleaseUrlFormat;
|
private readonly string StableReleaseUrlFormat;
|
||||||
|
|
||||||
|
/// <summary>The URL for a GitHub API query for the latest release (including prerelease), excluding the base URL, where {0} is the organisation and project name.</summary>
|
||||||
|
private readonly string AnyReleaseUrlFormat;
|
||||||
|
|
||||||
/// <summary>The underlying HTTP client.</summary>
|
/// <summary>The underlying HTTP client.</summary>
|
||||||
private readonly IClient Client;
|
private readonly IClient Client;
|
||||||
|
@ -23,14 +27,16 @@ namespace StardewModdingAPI.Web.Framework.Clients.GitHub
|
||||||
*********/
|
*********/
|
||||||
/// <summary>Construct an instance.</summary>
|
/// <summary>Construct an instance.</summary>
|
||||||
/// <param name="baseUrl">The base URL for the GitHub API.</param>
|
/// <param name="baseUrl">The base URL for the GitHub API.</param>
|
||||||
/// <param name="releaseUrlFormat">The URL for a GitHub releases API query excluding the <paramref name="baseUrl"/>, where {0} is the repository owner and name.</param>
|
/// <param name="stableReleaseUrlFormat">The URL for a GitHub API query for the latest stable release, excluding the <paramref name="baseUrl"/>, where {0} is the organisation and project name.</param>
|
||||||
|
/// <param name="anyReleaseUrlFormat">The URL for a GitHub API query for the latest release (including prerelease), excluding the <paramref name="baseUrl"/>, where {0} is the organisation and project name.</param>
|
||||||
/// <param name="userAgent">The user agent for the 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="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="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>
|
/// <param name="password">The password with which to authenticate to the GitHub API.</param>
|
||||||
public GitHubClient(string baseUrl, string releaseUrlFormat, string userAgent, string acceptHeader, string username, string password)
|
public GitHubClient(string baseUrl, string stableReleaseUrlFormat, string anyReleaseUrlFormat, string userAgent, string acceptHeader, string username, string password)
|
||||||
{
|
{
|
||||||
this.ReleaseUrlFormat = releaseUrlFormat;
|
this.StableReleaseUrlFormat = stableReleaseUrlFormat;
|
||||||
|
this.AnyReleaseUrlFormat = anyReleaseUrlFormat;
|
||||||
|
|
||||||
this.Client = new FluentClient(baseUrl)
|
this.Client = new FluentClient(baseUrl)
|
||||||
.SetUserAgent(userAgent)
|
.SetUserAgent(userAgent)
|
||||||
|
@ -41,18 +47,23 @@ namespace StardewModdingAPI.Web.Framework.Clients.GitHub
|
||||||
|
|
||||||
/// <summary>Get the latest release for a GitHub repository.</summary>
|
/// <summary>Get the latest release for a GitHub repository.</summary>
|
||||||
/// <param name="repo">The repository key (like <c>Pathoschild/SMAPI</c>).</param>
|
/// <param name="repo">The repository key (like <c>Pathoschild/SMAPI</c>).</param>
|
||||||
/// <returns>Returns the latest release if found, else <c>null</c>.</returns>
|
/// <param name="includePrerelease">Whether to return a prerelease version if it's latest.</param>
|
||||||
public async Task<GitRelease> GetLatestReleaseAsync(string repo)
|
/// <returns>Returns the release if found, else <c>null</c>.</returns>
|
||||||
|
public async Task<GitRelease> GetLatestReleaseAsync(string repo, bool includePrerelease = false)
|
||||||
{
|
{
|
||||||
// validate key format
|
this.AssetKeyFormat(repo);
|
||||||
if (!repo.Contains("/") || repo.IndexOf("/", StringComparison.InvariantCultureIgnoreCase) != repo.LastIndexOf("/", StringComparison.InvariantCultureIgnoreCase))
|
|
||||||
throw new ArgumentException($"The value '{repo}' isn't a valid GitHub repository key, must be a username and project name like 'Pathoschild/SMAPI'.", nameof(repo));
|
|
||||||
|
|
||||||
// fetch info
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
if (includePrerelease)
|
||||||
|
{
|
||||||
|
GitRelease[] results = await this.Client
|
||||||
|
.GetAsync(string.Format(this.AnyReleaseUrlFormat, repo))
|
||||||
|
.AsArray<GitRelease>();
|
||||||
|
return results.FirstOrDefault();
|
||||||
|
}
|
||||||
|
|
||||||
return await this.Client
|
return await this.Client
|
||||||
.GetAsync(string.Format(this.ReleaseUrlFormat, repo))
|
.GetAsync(string.Format(this.StableReleaseUrlFormat, repo))
|
||||||
.As<GitRelease>();
|
.As<GitRelease>();
|
||||||
}
|
}
|
||||||
catch (ApiException ex) when (ex.Status == HttpStatusCode.NotFound)
|
catch (ApiException ex) when (ex.Status == HttpStatusCode.NotFound)
|
||||||
|
@ -66,5 +77,18 @@ namespace StardewModdingAPI.Web.Framework.Clients.GitHub
|
||||||
{
|
{
|
||||||
this.Client?.Dispose();
|
this.Client?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*********
|
||||||
|
** Private methods
|
||||||
|
*********/
|
||||||
|
/// <summary>Assert that a repository key is formatted correctly.</summary>
|
||||||
|
/// <param name="repo">The repository key (like <c>Pathoschild/SMAPI</c>).</param>
|
||||||
|
/// <exception cref="ArgumentException">The repository key is invalid.</exception>
|
||||||
|
private void AssetKeyFormat(string repo)
|
||||||
|
{
|
||||||
|
if (repo == null || !repo.Contains("/") || repo.IndexOf("/", StringComparison.InvariantCultureIgnoreCase) != repo.LastIndexOf("/", StringComparison.InvariantCultureIgnoreCase))
|
||||||
|
throw new ArgumentException($"The value '{repo}' isn't a valid GitHub repository key, must be a username and project name like 'Pathoschild/SMAPI'.", nameof(repo));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,10 @@ namespace StardewModdingAPI.Web.Framework.Clients.GitHub
|
||||||
/// <summary>The Markdown description for the release.</summary>
|
/// <summary>The Markdown description for the release.</summary>
|
||||||
public string Body { get; set; }
|
public string Body { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Whether this is a prerelease version.</summary>
|
||||||
|
[JsonProperty("prerelease")]
|
||||||
|
public bool IsPrerelease { get; set; }
|
||||||
|
|
||||||
/// <summary>The attached files.</summary>
|
/// <summary>The attached files.</summary>
|
||||||
public GitAsset[] Assets { get; set; }
|
public GitAsset[] Assets { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,8 @@ namespace StardewModdingAPI.Web.Framework.Clients.GitHub
|
||||||
*********/
|
*********/
|
||||||
/// <summary>Get the latest release for a GitHub repository.</summary>
|
/// <summary>Get the latest release for a GitHub repository.</summary>
|
||||||
/// <param name="repo">The repository key (like <c>Pathoschild/SMAPI</c>).</param>
|
/// <param name="repo">The repository key (like <c>Pathoschild/SMAPI</c>).</param>
|
||||||
/// <returns>Returns the latest release if found, else <c>null</c>.</returns>
|
/// <param name="includePrerelease">Whether to return a prerelease version if it's latest.</param>
|
||||||
Task<GitRelease> GetLatestReleaseAsync(string repo);
|
/// <returns>Returns the release if found, else <c>null</c>.</returns>
|
||||||
|
Task<GitRelease> GetLatestReleaseAsync(string repo, bool includePrerelease = false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,8 +29,11 @@ namespace StardewModdingAPI.Web.Framework.ConfigModels
|
||||||
/// <summary>The base URL for the GitHub API.</summary>
|
/// <summary>The base URL for the GitHub API.</summary>
|
||||||
public string GitHubBaseUrl { get; set; }
|
public string GitHubBaseUrl { get; set; }
|
||||||
|
|
||||||
/// <summary>The URL for a GitHub API latest-release query excluding the <see cref="GitHubBaseUrl"/>, where {0} is the organisation and project name.</summary>
|
/// <summary>The URL for a GitHub API query for the latest stable release, excluding the <see cref="GitHubBaseUrl"/>, where {0} is the organisation and project name.</summary>
|
||||||
public string GitHubReleaseUrlFormat { get; set; }
|
public string GitHubStableReleaseUrlFormat { get; set; }
|
||||||
|
|
||||||
|
/// <summary>The URL for a GitHub API query for the latest release (including prerelease), excluding the <see cref="GitHubBaseUrl"/>, where {0} is the organisation and project name.</summary>
|
||||||
|
public string GitHubAnyReleaseUrlFormat { get; set; }
|
||||||
|
|
||||||
/// <summary>The Accept header value expected by the GitHub API.</summary>
|
/// <summary>The Accept header value expected by the GitHub API.</summary>
|
||||||
public string GitHubAcceptHeader { get; set; }
|
public string GitHubAcceptHeader { get; set; }
|
||||||
|
|
|
@ -43,7 +43,7 @@ namespace StardewModdingAPI.Web.Framework.ModRepositories
|
||||||
return new ModInfoModel("Found no mod with this ID.");
|
return new ModInfoModel("Found no mod with this ID.");
|
||||||
|
|
||||||
// create model
|
// create model
|
||||||
return new ModInfoModel(mod.Name, this.NormaliseVersion(mod.Version), mod.Url);
|
return new ModInfoModel(name: mod.Name, version: this.NormaliseVersion(mod.Version), url: mod.Url);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
|
@ -38,10 +38,21 @@ namespace StardewModdingAPI.Web.Framework.ModRepositories
|
||||||
// fetch info
|
// fetch info
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
GitRelease release = await this.Client.GetLatestReleaseAsync(id);
|
// get latest release
|
||||||
return release != null
|
GitRelease latest = await this.Client.GetLatestReleaseAsync(id, includePrerelease: true);
|
||||||
? new ModInfoModel(id, this.NormaliseVersion(release.Tag), $"https://github.com/{id}/releases")
|
GitRelease preview = null;
|
||||||
: new ModInfoModel("Found no mod with this ID.");
|
if (latest == null)
|
||||||
|
return new ModInfoModel("Found no mod with this ID.");
|
||||||
|
|
||||||
|
// get latest stable release (if not latest)
|
||||||
|
if (latest.IsPrerelease)
|
||||||
|
{
|
||||||
|
preview = latest;
|
||||||
|
latest = await this.Client.GetLatestReleaseAsync(id, includePrerelease: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// return data
|
||||||
|
return new ModInfoModel(name: id, version: this.NormaliseVersion(latest?.Tag), previewVersion: this.NormaliseVersion(preview?.Tag), url: $"https://github.com/{id}/releases");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
|
@ -43,7 +43,7 @@ namespace StardewModdingAPI.Web.Framework.ModRepositories
|
||||||
return new ModInfoModel("Found no mod with this ID.");
|
return new ModInfoModel("Found no mod with this ID.");
|
||||||
if (mod.Error != null)
|
if (mod.Error != null)
|
||||||
return new ModInfoModel(mod.Error);
|
return new ModInfoModel(mod.Error);
|
||||||
return new ModInfoModel(mod.Name, this.NormaliseVersion(mod.Version), mod.Url);
|
return new ModInfoModel(name: mod.Name, version: this.NormaliseVersion(mod.Version), url: mod.Url);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
|
@ -74,7 +74,8 @@ namespace StardewModdingAPI.Web
|
||||||
|
|
||||||
services.AddSingleton<IGitHubClient>(new GitHubClient(
|
services.AddSingleton<IGitHubClient>(new GitHubClient(
|
||||||
baseUrl: api.GitHubBaseUrl,
|
baseUrl: api.GitHubBaseUrl,
|
||||||
releaseUrlFormat: api.GitHubReleaseUrlFormat,
|
stableReleaseUrlFormat: api.GitHubStableReleaseUrlFormat,
|
||||||
|
anyReleaseUrlFormat: api.GitHubAnyReleaseUrlFormat,
|
||||||
userAgent: userAgent,
|
userAgent: userAgent,
|
||||||
acceptHeader: api.GitHubAcceptHeader,
|
acceptHeader: api.GitHubAcceptHeader,
|
||||||
username: api.GitHubUsername,
|
username: api.GitHubUsername,
|
||||||
|
|
|
@ -24,7 +24,8 @@
|
||||||
"ChucklefishModPageUrlFormat": "resources/{0}",
|
"ChucklefishModPageUrlFormat": "resources/{0}",
|
||||||
|
|
||||||
"GitHubBaseUrl": "https://api.github.com",
|
"GitHubBaseUrl": "https://api.github.com",
|
||||||
"GitHubReleaseUrlFormat": "repos/{0}/releases/latest",
|
"GitHubStableReleaseUrlFormat": "repos/{0}/releases/latest",
|
||||||
|
"GitHubAnyReleaseUrlFormat": "repos/{0}/releases?per_page=1",
|
||||||
"GitHubAcceptHeader": "application/vnd.github.v3+json",
|
"GitHubAcceptHeader": "application/vnd.github.v3+json",
|
||||||
"GitHubUsername": null, // see top note
|
"GitHubUsername": null, // see top note
|
||||||
"GitHubPassword": null, // see top note
|
"GitHubPassword": null, // see top note
|
||||||
|
|
Loading…
Reference in New Issue