summaryrefslogtreecommitdiff
path: root/src/SMAPI.Toolkit/Framework/Clients/WebApi
diff options
context:
space:
mode:
authorJesse Plamondon-Willard <Pathoschild@users.noreply.github.com>2019-09-13 18:24:54 -0400
committerJesse Plamondon-Willard <Pathoschild@users.noreply.github.com>2019-09-13 18:24:54 -0400
commit6521df7b131924835eb797251c1e956fae0d6e13 (patch)
treeb704dc64b6b6fef72615bac8950d5eff3c80ea89 /src/SMAPI.Toolkit/Framework/Clients/WebApi
parente22a54212182d0adc443ac95bc791e83c90f7e10 (diff)
parentb7b8b001c5c2dc5d2c9fc1347532ca29368c2325 (diff)
downloadSMAPI-6521df7b131924835eb797251c1e956fae0d6e13.tar.gz
SMAPI-6521df7b131924835eb797251c1e956fae0d6e13.tar.bz2
SMAPI-6521df7b131924835eb797251c1e956fae0d6e13.zip
Merge branch 'develop' into stable
Diffstat (limited to 'src/SMAPI.Toolkit/Framework/Clients/WebApi')
-rw-r--r--src/SMAPI.Toolkit/Framework/Clients/WebApi/ModEntryModel.cs33
-rw-r--r--src/SMAPI.Toolkit/Framework/Clients/WebApi/ModEntryVersionModel.cs31
-rw-r--r--src/SMAPI.Toolkit/Framework/Clients/WebApi/ModExtendedMetadataModel.cs123
-rw-r--r--src/SMAPI.Toolkit/Framework/Clients/WebApi/ModSeachModel.cs36
-rw-r--r--src/SMAPI.Toolkit/Framework/Clients/WebApi/ModSearchEntryModel.cs34
-rw-r--r--src/SMAPI.Toolkit/Framework/Clients/WebApi/WebApiClient.cs73
6 files changed, 330 insertions, 0 deletions
diff --git a/src/SMAPI.Toolkit/Framework/Clients/WebApi/ModEntryModel.cs b/src/SMAPI.Toolkit/Framework/Clients/WebApi/ModEntryModel.cs
new file mode 100644
index 00000000..8a9c0a25
--- /dev/null
+++ b/src/SMAPI.Toolkit/Framework/Clients/WebApi/ModEntryModel.cs
@@ -0,0 +1,33 @@
+namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi
+{
+ /// <summary>Metadata about a mod.</summary>
+ public class ModEntryModel
+ {
+ /*********
+ ** Accessors
+ *********/
+ /// <summary>The mod's unique ID (if known).</summary>
+ public string ID { get; set; }
+
+ /// <summary>The main version.</summary>
+ public ModEntryVersionModel Main { get; set; }
+
+ /// <summary>The latest optional version, if newer than <see cref="Main"/>.</summary>
+ public ModEntryVersionModel Optional { get; set; }
+
+ /// <summary>The latest unofficial version, if newer than <see cref="Main"/> and <see cref="Optional"/>.</summary>
+ public ModEntryVersionModel Unofficial { get; set; }
+
+ /// <summary>The latest unofficial version for the current Stardew Valley or SMAPI beta, if any (see <see cref="HasBetaInfo"/>).</summary>
+ public ModEntryVersionModel UnofficialForBeta { get; set; }
+
+ /// <summary>Optional extended data which isn't needed for update checks.</summary>
+ public ModExtendedMetadataModel Metadata { get; set; }
+
+ /// <summary>Whether a Stardew Valley or SMAPI beta which affects mod compatibility is in progress. If this is true, <see cref="UnofficialForBeta"/> should be used for beta versions of SMAPI instead of <see cref="Unofficial"/>.</summary>
+ public bool HasBetaInfo { get; set; }
+
+ /// <summary>The errors that occurred while fetching update data.</summary>
+ public string[] Errors { get; set; } = new string[0];
+ }
+}
diff --git a/src/SMAPI.Toolkit/Framework/Clients/WebApi/ModEntryVersionModel.cs b/src/SMAPI.Toolkit/Framework/Clients/WebApi/ModEntryVersionModel.cs
new file mode 100644
index 00000000..dadb8c10
--- /dev/null
+++ b/src/SMAPI.Toolkit/Framework/Clients/WebApi/ModEntryVersionModel.cs
@@ -0,0 +1,31 @@
+namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi
+{
+ /// <summary>Metadata about a version.</summary>
+ public class ModEntryVersionModel
+ {
+ /*********
+ ** Accessors
+ *********/
+ /// <summary>The version number.</summary>
+ public ISemanticVersion Version { get; set; }
+
+ /// <summary>The mod page URL.</summary>
+ public string Url { get; set; }
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Construct an instance.</summary>
+ public ModEntryVersionModel() { }
+
+ /// <summary>Construct an instance.</summary>
+ /// <param name="version">The version number.</param>
+ /// <param name="url">The mod page URL.</param>
+ public ModEntryVersionModel(ISemanticVersion version, string url)
+ {
+ this.Version = version;
+ this.Url = url;
+ }
+ }
+}
diff --git a/src/SMAPI.Toolkit/Framework/Clients/WebApi/ModExtendedMetadataModel.cs b/src/SMAPI.Toolkit/Framework/Clients/WebApi/ModExtendedMetadataModel.cs
new file mode 100644
index 00000000..989c18b0
--- /dev/null
+++ b/src/SMAPI.Toolkit/Framework/Clients/WebApi/ModExtendedMetadataModel.cs
@@ -0,0 +1,123 @@
+using System.Collections.Generic;
+using System.Linq;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Converters;
+using StardewModdingAPI.Toolkit.Framework.Clients.Wiki;
+using StardewModdingAPI.Toolkit.Framework.ModData;
+
+namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi
+{
+ /// <summary>Extended metadata about a mod.</summary>
+ public class ModExtendedMetadataModel
+ {
+ /*********
+ ** Accessors
+ *********/
+ /****
+ ** Mod info
+ ****/
+ /// <summary>The mod's unique ID. A mod may have multiple current IDs in rare cases (e.g. due to parallel releases or unofficial updates).</summary>
+ public string[] ID { get; set; } = new string[0];
+
+ /// <summary>The mod's display name.</summary>
+ public string Name { get; set; }
+
+ /// <summary>The mod ID on Nexus.</summary>
+ public int? NexusID { get; set; }
+
+ /// <summary>The mod ID in the Chucklefish mod repo.</summary>
+ public int? ChucklefishID { get; set; }
+
+ /// <summary>The mod ID in the ModDrop mod repo.</summary>
+ public int? ModDropID { get; set; }
+
+ /// <summary>The GitHub repository in the form 'owner/repo'.</summary>
+ public string GitHubRepo { get; set; }
+
+ /// <summary>The URL to a non-GitHub source repo.</summary>
+ public string CustomSourceUrl { get; set; }
+
+ /// <summary>The custom mod page URL (if applicable).</summary>
+ public string CustomUrl { get; set; }
+
+
+ /****
+ ** Stable compatibility
+ ****/
+ /// <summary>The compatibility status.</summary>
+ [JsonConverter(typeof(StringEnumConverter))]
+ public WikiCompatibilityStatus? CompatibilityStatus { get; set; }
+
+ /// <summary>The human-readable summary of the compatibility status or workaround, without HTML formatitng.</summary>
+ public string CompatibilitySummary { get; set; }
+
+ /// <summary>The game or SMAPI version which broke this mod, if applicable.</summary>
+ public string BrokeIn { get; set; }
+
+
+ /****
+ ** Beta compatibility
+ ****/
+ /// <summary>The compatibility status for the Stardew Valley beta (if any).</summary>
+ [JsonConverter(typeof(StringEnumConverter))]
+ public WikiCompatibilityStatus? BetaCompatibilityStatus { get; set; }
+
+ /// <summary>The human-readable summary of the compatibility status or workaround for the Stardew Valley beta (if any), without HTML formatitng.</summary>
+ public string BetaCompatibilitySummary { get; set; }
+
+ /// <summary>The beta game or SMAPI version which broke this mod, if applicable.</summary>
+ public string BetaBrokeIn { get; set; }
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Construct an instance.</summary>
+ public ModExtendedMetadataModel() { }
+
+ /// <summary>Construct an instance.</summary>
+ /// <param name="wiki">The mod metadata from the wiki (if available).</param>
+ /// <param name="db">The mod metadata from SMAPI's internal DB (if available).</param>
+ public ModExtendedMetadataModel(WikiModEntry wiki, ModDataRecord db)
+ {
+ // wiki data
+ if (wiki != null)
+ {
+ this.ID = wiki.ID;
+ this.Name = wiki.Name.FirstOrDefault();
+ this.NexusID = wiki.NexusID;
+ this.ChucklefishID = wiki.ChucklefishID;
+ this.ModDropID = wiki.ModDropID;
+ this.GitHubRepo = wiki.GitHubRepo;
+ this.CustomSourceUrl = wiki.CustomSourceUrl;
+ this.CustomUrl = wiki.CustomUrl;
+
+ this.CompatibilityStatus = wiki.Compatibility.Status;
+ this.CompatibilitySummary = wiki.Compatibility.Summary;
+ this.BrokeIn = wiki.Compatibility.BrokeIn;
+
+ this.BetaCompatibilityStatus = wiki.BetaCompatibility?.Status;
+ this.BetaCompatibilitySummary = wiki.BetaCompatibility?.Summary;
+ this.BetaBrokeIn = wiki.BetaCompatibility?.BrokeIn;
+ }
+
+ // internal DB data
+ if (db != null)
+ {
+ this.ID = this.ID.Union(db.FormerIDs).ToArray();
+ this.Name = this.Name ?? db.DisplayName;
+ }
+ }
+
+ /// <summary>Get update keys based on the metadata.</summary>
+ public IEnumerable<string> GetUpdateKeys()
+ {
+ if (this.NexusID.HasValue)
+ yield return $"Nexus:{this.NexusID}";
+ if (this.ChucklefishID.HasValue)
+ yield return $"Chucklefish:{this.ChucklefishID}";
+ if (this.GitHubRepo != null)
+ yield return $"GitHub:{this.GitHubRepo}";
+ }
+ }
+}
diff --git a/src/SMAPI.Toolkit/Framework/Clients/WebApi/ModSeachModel.cs b/src/SMAPI.Toolkit/Framework/Clients/WebApi/ModSeachModel.cs
new file mode 100644
index 00000000..e352e1cc
--- /dev/null
+++ b/src/SMAPI.Toolkit/Framework/Clients/WebApi/ModSeachModel.cs
@@ -0,0 +1,36 @@
+using System.Linq;
+
+namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi
+{
+ /// <summary>Specifies mods whose update-check info to fetch.</summary>
+ public class ModSearchModel
+ {
+ /*********
+ ** Accessors
+ *********/
+ /// <summary>The mods for which to find data.</summary>
+ public ModSearchEntryModel[] Mods { get; set; }
+
+ /// <summary>Whether to include extended metadata for each mod.</summary>
+ public bool IncludeExtendedMetadata { get; set; }
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Construct an empty instance.</summary>
+ public ModSearchModel()
+ {
+ // needed for JSON deserialising
+ }
+
+ /// <summary>Construct an instance.</summary>
+ /// <param name="mods">The mods to search.</param>
+ /// <param name="includeExtendedMetadata">Whether to include extended metadata for each mod.</param>
+ public ModSearchModel(ModSearchEntryModel[] mods, bool includeExtendedMetadata)
+ {
+ this.Mods = mods.ToArray();
+ this.IncludeExtendedMetadata = includeExtendedMetadata;
+ }
+ }
+}
diff --git a/src/SMAPI.Toolkit/Framework/Clients/WebApi/ModSearchEntryModel.cs b/src/SMAPI.Toolkit/Framework/Clients/WebApi/ModSearchEntryModel.cs
new file mode 100644
index 00000000..bca47647
--- /dev/null
+++ b/src/SMAPI.Toolkit/Framework/Clients/WebApi/ModSearchEntryModel.cs
@@ -0,0 +1,34 @@
+namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi
+{
+ /// <summary>Specifies the identifiers for a mod to match.</summary>
+ public class ModSearchEntryModel
+ {
+ /*********
+ ** Accessors
+ *********/
+ /// <summary>The unique mod ID.</summary>
+ public string ID { get; set; }
+
+ /// <summary>The namespaced mod update keys (if available).</summary>
+ public string[] UpdateKeys { get; set; }
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Construct an empty instance.</summary>
+ public ModSearchEntryModel()
+ {
+ // needed for JSON deserialising
+ }
+
+ /// <summary>Construct an instance.</summary>
+ /// <param name="id">The unique mod ID.</param>
+ /// <param name="updateKeys">The namespaced mod update keys (if available).</param>
+ public ModSearchEntryModel(string id, string[] updateKeys)
+ {
+ this.ID = id;
+ this.UpdateKeys = updateKeys ?? new string[0];
+ }
+ }
+}
diff --git a/src/SMAPI.Toolkit/Framework/Clients/WebApi/WebApiClient.cs b/src/SMAPI.Toolkit/Framework/Clients/WebApi/WebApiClient.cs
new file mode 100644
index 00000000..7c3df384
--- /dev/null
+++ b/src/SMAPI.Toolkit/Framework/Clients/WebApi/WebApiClient.cs
@@ -0,0 +1,73 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using Newtonsoft.Json;
+using StardewModdingAPI.Toolkit.Serialisation;
+
+namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi
+{
+ /// <summary>Provides methods for interacting with the SMAPI web API.</summary>
+ public class WebApiClient
+ {
+ /*********
+ ** Fields
+ *********/
+ /// <summary>The base URL for the web API.</summary>
+ private readonly Uri BaseUrl;
+
+ /// <summary>The API version number.</summary>
+ private readonly ISemanticVersion Version;
+
+ /// <summary>The JSON serializer settings to use.</summary>
+ private readonly JsonSerializerSettings JsonSettings = new JsonHelper().JsonSettings;
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Construct an instance.</summary>
+ /// <param name="baseUrl">The base URL for the web API.</param>
+ /// <param name="version">The web API version.</param>
+ public WebApiClient(string baseUrl, ISemanticVersion version)
+ {
+ this.BaseUrl = new Uri(baseUrl);
+ this.Version = version;
+ }
+
+ /// <summary>Get metadata about a set of mods from the web API.</summary>
+ /// <param name="mods">The mod keys for which to fetch the latest version.</param>
+ /// <param name="includeExtendedMetadata">Whether to include extended metadata for each mod.</param>
+ public IDictionary<string, ModEntryModel> GetModInfo(ModSearchEntryModel[] mods, bool includeExtendedMetadata = false)
+ {
+ return this.Post<ModSearchModel, ModEntryModel[]>(
+ $"v{this.Version}/mods",
+ new ModSearchModel(mods, includeExtendedMetadata)
+ ).ToDictionary(p => p.ID);
+ }
+
+
+ /*********
+ ** Private methods
+ *********/
+ /// <summary>Fetch the response from the backend API.</summary>
+ /// <typeparam name="TBody">The body content type.</typeparam>
+ /// <typeparam name="TResult">The expected response type.</typeparam>
+ /// <param name="url">The request URL, optionally excluding the base URL.</param>
+ /// <param name="content">The body content to post.</param>
+ private TResult Post<TBody, TResult>(string url, TBody content)
+ {
+ // note: avoid HttpClient for Mac compatibility
+ using (WebClient client = new WebClient())
+ {
+ Uri fullUrl = new Uri(this.BaseUrl, url);
+ string data = JsonConvert.SerializeObject(content);
+
+ client.Headers["Content-Type"] = "application/json";
+ client.Headers["User-Agent"] = $"SMAPI/{this.Version}";
+ string response = client.UploadString(fullUrl, data);
+ return JsonConvert.DeserializeObject<TResult>(response, this.JsonSettings);
+ }
+ }
+ }
+}