enable nullable annotations in the web project & related code (#837)
This commit is contained in:
parent
9fbed0fa1f
commit
0b48c1748b
|
@ -7,8 +7,8 @@
|
|||
<AssemblySearchPaths>$(AssemblySearchPaths);{GAC}</AssemblySearchPaths>
|
||||
|
||||
<!--enable nullable annotations, except in .NET Standard 2.0 where they aren't supported-->
|
||||
<Nullable Condition="'$(TargetFramework)' == 'net5.0'">enable</Nullable>
|
||||
<NoWarn Condition="'$(TargetFramework)' != 'net5.0'">$(NoWarn);CS8632</NoWarn>
|
||||
<Nullable Condition="'$(TargetFramework)' != 'netstandard2.0'">enable</Nullable>
|
||||
<NoWarn Condition="'$(TargetFramework)' == 'netstandard2.0'">$(NoWarn);CS8632</NoWarn>
|
||||
|
||||
<!--set platform-->
|
||||
<DefineConstants Condition="$(OS) == 'Windows_NT'">$(DefineConstants);SMAPI_FOR_WINDOWS</DefineConstants>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace StardewModdingAPI
|
||||
{
|
||||
|
@ -28,6 +29,9 @@ namespace StardewModdingAPI
|
|||
** Accessors
|
||||
*********/
|
||||
/// <summary>Whether this is a prerelease version.</summary>
|
||||
#if NET5_0_OR_GREATER
|
||||
[MemberNotNullWhen(true, nameof(ISemanticVersion.PrereleaseTag))]
|
||||
#endif
|
||||
bool IsPrerelease();
|
||||
|
||||
/// <summary>Get whether this version is older than the specified version.</summary>
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
|
||||
namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi
|
||||
|
@ -11,15 +9,26 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi
|
|||
** Accessors
|
||||
*********/
|
||||
/// <summary>The mod's unique ID (if known).</summary>
|
||||
public string ID { get; set; }
|
||||
public string ID { get; }
|
||||
|
||||
/// <summary>The update version recommended by the web API based on its version update and mapping rules.</summary>
|
||||
public ModEntryVersionModel SuggestedUpdate { get; set; }
|
||||
public ModEntryVersionModel? SuggestedUpdate { get; set; }
|
||||
|
||||
/// <summary>Optional extended data which isn't needed for update checks.</summary>
|
||||
public ModExtendedMetadataModel Metadata { get; set; }
|
||||
public ModExtendedMetadataModel? Metadata { get; set; }
|
||||
|
||||
/// <summary>The errors that occurred while fetching update data.</summary>
|
||||
public string[] Errors { get; set; } = Array.Empty<string>();
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="id">The mod's unique ID (if known).</param>
|
||||
public ModEntryModel(string id)
|
||||
{
|
||||
this.ID = id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using StardewModdingAPI.Toolkit.Serialization.Converters;
|
||||
|
||||
|
@ -13,18 +11,15 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi
|
|||
*********/
|
||||
/// <summary>The version number.</summary>
|
||||
[JsonConverter(typeof(NonStandardSemanticVersionConverter))]
|
||||
public ISemanticVersion Version { get; set; }
|
||||
public ISemanticVersion Version { get; }
|
||||
|
||||
/// <summary>The mod page URL.</summary>
|
||||
public string Url { get; set; }
|
||||
public string Url { get; }
|
||||
|
||||
|
||||
/*********
|
||||
** 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>
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
@ -23,7 +21,7 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi
|
|||
public string[] ID { get; set; } = Array.Empty<string>();
|
||||
|
||||
/// <summary>The mod's display name.</summary>
|
||||
public string Name { get; set; }
|
||||
public string? Name { get; set; }
|
||||
|
||||
/// <summary>The mod ID on Nexus.</summary>
|
||||
public int? NexusID { get; set; }
|
||||
|
@ -35,31 +33,31 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi
|
|||
public int? CurseForgeID { get; set; }
|
||||
|
||||
/// <summary>The mod key in the CurseForge mod repo (used in mod page URLs).</summary>
|
||||
public string CurseForgeKey { get; set; }
|
||||
public string? CurseForgeKey { 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; }
|
||||
public string? GitHubRepo { get; set; }
|
||||
|
||||
/// <summary>The URL to a non-GitHub source repo.</summary>
|
||||
public string CustomSourceUrl { get; set; }
|
||||
public string? CustomSourceUrl { get; set; }
|
||||
|
||||
/// <summary>The custom mod page URL (if applicable).</summary>
|
||||
public string CustomUrl { get; set; }
|
||||
public string? CustomUrl { get; set; }
|
||||
|
||||
/// <summary>The main version.</summary>
|
||||
public ModEntryVersionModel Main { get; set; }
|
||||
public ModEntryVersionModel? Main { get; set; }
|
||||
|
||||
/// <summary>The latest optional version, if newer than <see cref="Main"/>.</summary>
|
||||
public ModEntryVersionModel Optional { get; set; }
|
||||
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; }
|
||||
public ModEntryVersionModel? Unofficial { get; set; }
|
||||
|
||||
/// <summary>The latest unofficial version for the current Stardew Valley or SMAPI beta, if any.</summary>
|
||||
public ModEntryVersionModel UnofficialForBeta { get; set; }
|
||||
public ModEntryVersionModel? UnofficialForBeta { get; set; }
|
||||
|
||||
/****
|
||||
** Stable compatibility
|
||||
|
@ -69,10 +67,10 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi
|
|||
public WikiCompatibilityStatus? CompatibilityStatus { get; set; }
|
||||
|
||||
/// <summary>The human-readable summary of the compatibility status or workaround, without HTML formatting.</summary>
|
||||
public string CompatibilitySummary { get; set; }
|
||||
public string? CompatibilitySummary { get; set; }
|
||||
|
||||
/// <summary>The game or SMAPI version which broke this mod, if applicable.</summary>
|
||||
public string BrokeIn { get; set; }
|
||||
public string? BrokeIn { get; set; }
|
||||
|
||||
/****
|
||||
** Beta compatibility
|
||||
|
@ -82,22 +80,22 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi
|
|||
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 formatting.</summary>
|
||||
public string BetaCompatibilitySummary { get; set; }
|
||||
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 string? BetaBrokeIn { get; set; }
|
||||
|
||||
/****
|
||||
** Version mappings
|
||||
****/
|
||||
/// <summary>A serialized change descriptor to apply to the local version during update checks (see <see cref="ChangeDescriptor"/>).</summary>
|
||||
public string ChangeLocalVersions { get; set; }
|
||||
public string? ChangeLocalVersions { get; set; }
|
||||
|
||||
/// <summary>A serialized change descriptor to apply to the remote version during update checks (see <see cref="ChangeDescriptor"/>).</summary>
|
||||
public string ChangeRemoteVersions { get; set; }
|
||||
public string? ChangeRemoteVersions { get; set; }
|
||||
|
||||
/// <summary>A serialized change descriptor to apply to the update keys during update checks (see <see cref="ChangeDescriptor"/>).</summary>
|
||||
public string ChangeUpdateKeys { get; set; }
|
||||
public string? ChangeUpdateKeys { get; set; }
|
||||
|
||||
|
||||
/*********
|
||||
|
@ -113,7 +111,7 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi
|
|||
/// <param name="optional">The latest optional version, if newer than <paramref name="main"/>.</param>
|
||||
/// <param name="unofficial">The latest unofficial version, if newer than <paramref name="main"/> and <paramref name="optional"/>.</param>
|
||||
/// <param name="unofficialForBeta">The latest unofficial version for the current Stardew Valley or SMAPI beta, if any.</param>
|
||||
public ModExtendedMetadataModel(WikiModEntry wiki, ModDataRecord db, ModEntryVersionModel main, ModEntryVersionModel optional, ModEntryVersionModel unofficial, ModEntryVersionModel unofficialForBeta)
|
||||
public ModExtendedMetadataModel(WikiModEntry? wiki, ModDataRecord? db, ModEntryVersionModel? main, ModEntryVersionModel? optional, ModEntryVersionModel? unofficial, ModEntryVersionModel? unofficialForBeta)
|
||||
{
|
||||
// versions
|
||||
this.Main = main;
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi
|
||||
{
|
||||
|
@ -11,37 +10,39 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi
|
|||
** Accessors
|
||||
*********/
|
||||
/// <summary>The unique mod ID.</summary>
|
||||
public string ID { get; set; }
|
||||
public string ID { get; }
|
||||
|
||||
/// <summary>The namespaced mod update keys (if available).</summary>
|
||||
public string[] UpdateKeys { get; set; }
|
||||
public string[] UpdateKeys { get; private set; }
|
||||
|
||||
/// <summary>The mod version installed by the local player. This is used for version mapping in some cases.</summary>
|
||||
public ISemanticVersion InstalledVersion { get; set; }
|
||||
public ISemanticVersion? InstalledVersion { get; }
|
||||
|
||||
/// <summary>Whether the installed version is broken or could not be loaded.</summary>
|
||||
public bool IsBroken { get; set; }
|
||||
public bool IsBroken { get; }
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an empty instance.</summary>
|
||||
public ModSearchEntryModel()
|
||||
{
|
||||
// needed for JSON deserializing
|
||||
}
|
||||
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="id">The unique mod ID.</param>
|
||||
/// <param name="installedVersion">The version installed by the local player. This is used for version mapping in some cases.</param>
|
||||
/// <param name="updateKeys">The namespaced mod update keys (if available).</param>
|
||||
/// <param name="isBroken">Whether the installed version is broken or could not be loaded.</param>
|
||||
public ModSearchEntryModel(string id, ISemanticVersion installedVersion, string[] updateKeys, bool isBroken = false)
|
||||
public ModSearchEntryModel(string id, ISemanticVersion? installedVersion, string[]? updateKeys, bool isBroken = false)
|
||||
{
|
||||
this.ID = id;
|
||||
this.InstalledVersion = installedVersion;
|
||||
this.UpdateKeys = updateKeys ?? Array.Empty<string>();
|
||||
this.IsBroken = isBroken;
|
||||
}
|
||||
|
||||
/// <summary>Add update keys for the mod.</summary>
|
||||
/// <param name="updateKeys">The update keys to add.</param>
|
||||
public void AddUpdateKeys(params string[] updateKeys)
|
||||
{
|
||||
this.UpdateKeys = this.UpdateKeys.Concat(updateKeys).ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System.Linq;
|
||||
using StardewModdingAPI.Toolkit.Utilities;
|
||||
|
||||
|
@ -24,18 +22,12 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi
|
|||
public ISemanticVersion GameVersion { get; set; }
|
||||
|
||||
/// <summary>The OS on which the player plays.</summary>
|
||||
public Platform? Platform { get; set; }
|
||||
public Platform Platform { get; set; }
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an empty instance.</summary>
|
||||
public ModSearchModel()
|
||||
{
|
||||
// needed for JSON deserializing
|
||||
}
|
||||
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="mods">The mods to search.</param>
|
||||
/// <param name="apiVersion">The SMAPI version installed by the player. If this is null, the API won't provide a recommended update.</param>
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
@ -72,7 +70,8 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi
|
|||
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);
|
||||
return JsonConvert.DeserializeObject<TResult>(response, this.JsonSettings)
|
||||
?? throw new InvalidOperationException($"Could not parse the response from POST {url}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
|
@ -49,7 +47,7 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki
|
|||
/// <summary>Apply the change descriptors to a comma-delimited field.</summary>
|
||||
/// <param name="rawField">The raw field text.</param>
|
||||
/// <returns>Returns the modified field.</returns>
|
||||
public string ApplyToCopy(string rawField)
|
||||
public string? ApplyToCopy(string? rawField)
|
||||
{
|
||||
// get list
|
||||
List<string> values = !string.IsNullOrWhiteSpace(rawField)
|
||||
|
@ -75,12 +73,12 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki
|
|||
{
|
||||
for (int i = values.Count - 1; i >= 0; i--)
|
||||
{
|
||||
string value = this.FormatValue(values[i]?.Trim() ?? string.Empty);
|
||||
string value = this.FormatValue(values[i].Trim());
|
||||
|
||||
if (this.Remove.Contains(value))
|
||||
values.RemoveAt(i);
|
||||
|
||||
else if (this.Replace.TryGetValue(value, out string newValue))
|
||||
else if (this.Replace.TryGetValue(value, out string? newValue))
|
||||
values[i] = newValue;
|
||||
}
|
||||
}
|
||||
|
@ -88,7 +86,7 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki
|
|||
// add values
|
||||
if (this.Add.Any())
|
||||
{
|
||||
HashSet<string> curValues = new HashSet<string>(values.Select(p => p?.Trim() ?? string.Empty), StringComparer.OrdinalIgnoreCase);
|
||||
HashSet<string> curValues = new HashSet<string>(values.Select(p => p.Trim()), StringComparer.OrdinalIgnoreCase);
|
||||
foreach (string add in this.Add)
|
||||
{
|
||||
if (!curValues.Contains(add))
|
||||
|
@ -121,7 +119,7 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki
|
|||
/// <param name="descriptor">The raw change descriptor.</param>
|
||||
/// <param name="errors">The human-readable error message describing any invalid values that were ignored.</param>
|
||||
/// <param name="formatValue">Format a raw value into a normalized form if needed.</param>
|
||||
public static ChangeDescriptor Parse(string descriptor, out string[] errors, Func<string, string> formatValue = null)
|
||||
public static ChangeDescriptor Parse(string descriptor, out string[] errors, Func<string, string>? formatValue = null)
|
||||
{
|
||||
// init
|
||||
formatValue ??= p => p;
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
@ -53,8 +51,8 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki
|
|||
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;
|
||||
string? stableVersion = doc.DocumentNode.SelectSingleNode("//div[@class='game-stable-version']")?.InnerText;
|
||||
string? betaVersion = doc.DocumentNode.SelectSingleNode("//div[@class='game-beta-version']")?.InnerText;
|
||||
if (betaVersion == stableVersion)
|
||||
betaVersion = null;
|
||||
|
||||
|
@ -65,7 +63,7 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki
|
|||
if (modNodes == null)
|
||||
throw new InvalidOperationException("Can't parse wiki compatibility list, no mod data overrides section found.");
|
||||
|
||||
foreach (var entry in this.ParseOverrideEntries(modNodes))
|
||||
foreach (WikiDataOverrideEntry entry in this.ParseOverrideEntries(modNodes))
|
||||
{
|
||||
if (entry.Ids.Any() != true || !entry.HasChanges)
|
||||
continue;
|
||||
|
@ -85,18 +83,17 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki
|
|||
}
|
||||
|
||||
// build model
|
||||
return new WikiModList
|
||||
{
|
||||
StableVersion = stableVersion,
|
||||
BetaVersion = betaVersion,
|
||||
Mods = mods
|
||||
};
|
||||
return new WikiModList(
|
||||
stableVersion: stableVersion,
|
||||
betaVersion: betaVersion,
|
||||
mods: mods
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
|
||||
public void Dispose()
|
||||
{
|
||||
this.Client?.Dispose();
|
||||
this.Client.Dispose();
|
||||
}
|
||||
|
||||
|
||||
|
@ -118,71 +115,68 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki
|
|||
int? nexusID = this.GetAttributeAsNullableInt(node, "data-nexus-id");
|
||||
int? chucklefishID = this.GetAttributeAsNullableInt(node, "data-cf-id");
|
||||
int? curseForgeID = this.GetAttributeAsNullableInt(node, "data-curseforge-id");
|
||||
string curseForgeKey = this.GetAttribute(node, "data-curseforge-key");
|
||||
string? curseForgeKey = this.GetAttribute(node, "data-curseforge-key");
|
||||
int? modDropID = this.GetAttributeAsNullableInt(node, "data-moddrop-id");
|
||||
string githubRepo = this.GetAttribute(node, "data-github");
|
||||
string customSourceUrl = this.GetAttribute(node, "data-custom-source");
|
||||
string customUrl = this.GetAttribute(node, "data-url");
|
||||
string anchor = this.GetAttribute(node, "id");
|
||||
string contentPackFor = this.GetAttribute(node, "data-content-pack-for");
|
||||
string devNote = this.GetAttribute(node, "data-dev-note");
|
||||
string pullRequestUrl = this.GetAttribute(node, "data-pr");
|
||||
string? githubRepo = this.GetAttribute(node, "data-github");
|
||||
string? customSourceUrl = this.GetAttribute(node, "data-custom-source");
|
||||
string? customUrl = this.GetAttribute(node, "data-url");
|
||||
string? anchor = this.GetAttribute(node, "id");
|
||||
string? contentPackFor = this.GetAttribute(node, "data-content-pack-for");
|
||||
string? devNote = this.GetAttribute(node, "data-dev-note");
|
||||
string? pullRequestUrl = this.GetAttribute(node, "data-pr");
|
||||
|
||||
// parse stable compatibility
|
||||
WikiCompatibilityInfo compatibility = new()
|
||||
{
|
||||
Status = this.GetAttributeAsEnum<WikiCompatibilityStatus>(node, "data-status") ?? WikiCompatibilityStatus.Ok,
|
||||
BrokeIn = this.GetAttribute(node, "data-broke-in"),
|
||||
UnofficialVersion = this.GetAttributeAsSemanticVersion(node, "data-unofficial-version"),
|
||||
UnofficialUrl = this.GetAttribute(node, "data-unofficial-url"),
|
||||
Summary = this.GetInnerHtml(node, "mod-summary")?.Trim()
|
||||
};
|
||||
WikiCompatibilityInfo compatibility = new(
|
||||
status: this.GetAttributeAsEnum<WikiCompatibilityStatus>(node, "data-status") ?? WikiCompatibilityStatus.Ok,
|
||||
brokeIn: this.GetAttribute(node, "data-broke-in"),
|
||||
unofficialVersion: this.GetAttributeAsSemanticVersion(node, "data-unofficial-version"),
|
||||
unofficialUrl: this.GetAttribute(node, "data-unofficial-url"),
|
||||
summary: this.GetInnerHtml(node, "mod-summary")?.Trim()
|
||||
);
|
||||
|
||||
// parse beta compatibility
|
||||
WikiCompatibilityInfo betaCompatibility = null;
|
||||
WikiCompatibilityInfo? betaCompatibility = null;
|
||||
{
|
||||
WikiCompatibilityStatus? betaStatus = this.GetAttributeAsEnum<WikiCompatibilityStatus>(node, "data-beta-status");
|
||||
if (betaStatus.HasValue)
|
||||
{
|
||||
betaCompatibility = new WikiCompatibilityInfo
|
||||
{
|
||||
Status = betaStatus.Value,
|
||||
BrokeIn = this.GetAttribute(node, "data-beta-broke-in"),
|
||||
UnofficialVersion = this.GetAttributeAsSemanticVersion(node, "data-beta-unofficial-version"),
|
||||
UnofficialUrl = this.GetAttribute(node, "data-beta-unofficial-url"),
|
||||
Summary = this.GetInnerHtml(node, "mod-beta-summary")
|
||||
};
|
||||
betaCompatibility = new WikiCompatibilityInfo(
|
||||
status: betaStatus.Value,
|
||||
brokeIn: this.GetAttribute(node, "data-beta-broke-in"),
|
||||
unofficialVersion: this.GetAttributeAsSemanticVersion(node, "data-beta-unofficial-version"),
|
||||
unofficialUrl: this.GetAttribute(node, "data-beta-unofficial-url"),
|
||||
summary: this.GetInnerHtml(node, "mod-beta-summary")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// find data overrides
|
||||
WikiDataOverrideEntry overrides = ids
|
||||
WikiDataOverrideEntry? overrides = ids
|
||||
.Select(id => overridesById.TryGetValue(id, out overrides) ? overrides : null)
|
||||
.FirstOrDefault(p => p != null);
|
||||
|
||||
// yield model
|
||||
yield return new WikiModEntry
|
||||
{
|
||||
ID = ids,
|
||||
Name = names,
|
||||
Author = authors,
|
||||
NexusID = nexusID,
|
||||
ChucklefishID = chucklefishID,
|
||||
CurseForgeID = curseForgeID,
|
||||
CurseForgeKey = curseForgeKey,
|
||||
ModDropID = modDropID,
|
||||
GitHubRepo = githubRepo,
|
||||
CustomSourceUrl = customSourceUrl,
|
||||
CustomUrl = customUrl,
|
||||
ContentPackFor = contentPackFor,
|
||||
Compatibility = compatibility,
|
||||
BetaCompatibility = betaCompatibility,
|
||||
Warnings = warnings,
|
||||
PullRequestUrl = pullRequestUrl,
|
||||
DevNote = devNote,
|
||||
Overrides = overrides,
|
||||
Anchor = anchor
|
||||
};
|
||||
yield return new WikiModEntry(
|
||||
id: ids,
|
||||
name: names,
|
||||
author: authors,
|
||||
nexusId: nexusID,
|
||||
chucklefishId: chucklefishID,
|
||||
curseForgeId: curseForgeID,
|
||||
curseForgeKey: curseForgeKey,
|
||||
modDropId: modDropID,
|
||||
githubRepo: githubRepo,
|
||||
customSourceUrl: customSourceUrl,
|
||||
customUrl: customUrl,
|
||||
contentPackFor: contentPackFor,
|
||||
compatibility: compatibility,
|
||||
betaCompatibility: betaCompatibility,
|
||||
warnings: warnings,
|
||||
pullRequestUrl: pullRequestUrl,
|
||||
devNote: devNote,
|
||||
overrides: overrides,
|
||||
anchor: anchor
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -196,10 +190,10 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki
|
|||
{
|
||||
Ids = this.GetAttributeAsCsv(node, "data-id"),
|
||||
ChangeLocalVersions = this.GetAttributeAsChangeDescriptor(node, "data-local-version",
|
||||
raw => SemanticVersion.TryParse(raw, out ISemanticVersion version) ? version.ToString() : raw
|
||||
raw => SemanticVersion.TryParse(raw, out ISemanticVersion? version) ? version.ToString() : raw
|
||||
),
|
||||
ChangeRemoteVersions = this.GetAttributeAsChangeDescriptor(node, "data-remote-version",
|
||||
raw => SemanticVersion.TryParse(raw, out ISemanticVersion version) ? version.ToString() : raw
|
||||
raw => SemanticVersion.TryParse(raw, out ISemanticVersion? version) ? version.ToString() : raw
|
||||
),
|
||||
|
||||
ChangeUpdateKeys = this.GetAttributeAsChangeDescriptor(node, "data-update-keys",
|
||||
|
@ -212,7 +206,7 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki
|
|||
/// <summary>Get an attribute value.</summary>
|
||||
/// <param name="element">The element whose attributes to read.</param>
|
||||
/// <param name="name">The attribute name.</param>
|
||||
private string GetAttribute(HtmlNode element, string name)
|
||||
private string? GetAttribute(HtmlNode element, string name)
|
||||
{
|
||||
string value = element.GetAttributeValue(name, null);
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
|
@ -225,9 +219,9 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki
|
|||
/// <param name="element">The element whose attributes to read.</param>
|
||||
/// <param name="name">The attribute name.</param>
|
||||
/// <param name="formatValue">Format an raw entry value when applying changes.</param>
|
||||
private ChangeDescriptor GetAttributeAsChangeDescriptor(HtmlNode element, string name, Func<string, string> formatValue)
|
||||
private ChangeDescriptor? GetAttributeAsChangeDescriptor(HtmlNode element, string name, Func<string, string> formatValue)
|
||||
{
|
||||
string raw = this.GetAttribute(element, name);
|
||||
string? raw = this.GetAttribute(element, name);
|
||||
return raw != null
|
||||
? ChangeDescriptor.Parse(raw, out _, formatValue)
|
||||
: null;
|
||||
|
@ -238,7 +232,7 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki
|
|||
/// <param name="name">The attribute name.</param>
|
||||
private string[] GetAttributeAsCsv(HtmlNode element, string name)
|
||||
{
|
||||
string raw = this.GetAttribute(element, name);
|
||||
string? raw = this.GetAttribute(element, name);
|
||||
return !string.IsNullOrWhiteSpace(raw)
|
||||
? raw.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(p => p.Trim()).ToArray()
|
||||
: Array.Empty<string>();
|
||||
|
@ -250,7 +244,7 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki
|
|||
/// <param name="name">The attribute name.</param>
|
||||
private TEnum? GetAttributeAsEnum<TEnum>(HtmlNode element, string name) where TEnum : struct
|
||||
{
|
||||
string raw = this.GetAttribute(element, name);
|
||||
string? raw = this.GetAttribute(element, name);
|
||||
if (raw == null)
|
||||
return null;
|
||||
if (!Enum.TryParse(raw, true, out TEnum value) && Enum.IsDefined(typeof(TEnum), value))
|
||||
|
@ -261,10 +255,10 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki
|
|||
/// <summary>Get an attribute value and parse it as a semantic version.</summary>
|
||||
/// <param name="element">The element whose attributes to read.</param>
|
||||
/// <param name="name">The attribute name.</param>
|
||||
private ISemanticVersion GetAttributeAsSemanticVersion(HtmlNode element, string name)
|
||||
private ISemanticVersion? GetAttributeAsSemanticVersion(HtmlNode element, string name)
|
||||
{
|
||||
string raw = this.GetAttribute(element, name);
|
||||
return SemanticVersion.TryParse(raw, out ISemanticVersion version)
|
||||
string? raw = this.GetAttribute(element, name);
|
||||
return SemanticVersion.TryParse(raw, out ISemanticVersion? version)
|
||||
? version
|
||||
: null;
|
||||
}
|
||||
|
@ -274,7 +268,7 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki
|
|||
/// <param name="name">The attribute name.</param>
|
||||
private int? GetAttributeAsNullableInt(HtmlNode element, string name)
|
||||
{
|
||||
string raw = this.GetAttribute(element, name);
|
||||
string? raw = this.GetAttribute(element, name);
|
||||
if (raw != null && int.TryParse(raw, out int value))
|
||||
return value;
|
||||
return null;
|
||||
|
@ -283,7 +277,7 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki
|
|||
/// <summary>Get the text of an element with the given class name.</summary>
|
||||
/// <param name="container">The metadata container.</param>
|
||||
/// <param name="className">The field name.</param>
|
||||
private string GetInnerHtml(HtmlNode container, string className)
|
||||
private string? GetInnerHtml(HtmlNode container, string className)
|
||||
{
|
||||
return container.Descendants().FirstOrDefault(p => p.HasClass(className))?.InnerHtml;
|
||||
}
|
||||
|
@ -293,8 +287,22 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki
|
|||
[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local")]
|
||||
private class ResponseModel
|
||||
{
|
||||
/*********
|
||||
** Accessors
|
||||
*********/
|
||||
/// <summary>The parse API results.</summary>
|
||||
public ResponseParseModel Parse { get; set; }
|
||||
public ResponseParseModel Parse { get; }
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="parse">The parse API results.</param>
|
||||
public ResponseModel(ResponseParseModel parse)
|
||||
{
|
||||
this.Parse = parse;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>The inner response model for the MediaWiki parse API.</summary>
|
||||
|
@ -303,8 +311,11 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki
|
|||
[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local")]
|
||||
private class ResponseParseModel
|
||||
{
|
||||
/*********
|
||||
** Accessors
|
||||
*********/
|
||||
/// <summary>The parsed text.</summary>
|
||||
public IDictionary<string, string> Text { get; set; }
|
||||
public IDictionary<string, string> Text { get; } = new Dictionary<string, string>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki
|
||||
{
|
||||
/// <summary>Compatibility info for a mod.</summary>
|
||||
|
@ -9,18 +7,37 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki
|
|||
** Accessors
|
||||
*********/
|
||||
/// <summary>The compatibility status.</summary>
|
||||
public WikiCompatibilityStatus Status { get; set; }
|
||||
public WikiCompatibilityStatus Status { get; }
|
||||
|
||||
/// <summary>The human-readable summary of the compatibility status or workaround, without HTML formatting.</summary>
|
||||
public string Summary { get; set; }
|
||||
public string? Summary { get; }
|
||||
|
||||
/// <summary>The game or SMAPI version which broke this mod (if applicable).</summary>
|
||||
public string BrokeIn { get; set; }
|
||||
/// <summary>The game or SMAPI version which broke this mod, if applicable.</summary>
|
||||
public string? BrokeIn { get; }
|
||||
|
||||
/// <summary>The version of the latest unofficial update, if applicable.</summary>
|
||||
public ISemanticVersion UnofficialVersion { get; set; }
|
||||
public ISemanticVersion? UnofficialVersion { get; }
|
||||
|
||||
/// <summary>The URL to the latest unofficial update, if applicable.</summary>
|
||||
public string UnofficialUrl { get; set; }
|
||||
public string? UnofficialUrl { get; }
|
||||
|
||||
|
||||
/*********
|
||||
** Accessors
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="status">The compatibility status.</param>
|
||||
/// <param name="summary">The human-readable summary of the compatibility status or workaround, without HTML formatting.</param>
|
||||
/// <param name="brokeIn">The game or SMAPI version which broke this mod, if applicable.</param>
|
||||
/// <param name="unofficialVersion">The version of the latest unofficial update, if applicable.</param>
|
||||
/// <param name="unofficialUrl">The URL to the latest unofficial update, if applicable.</param>
|
||||
public WikiCompatibilityInfo(WikiCompatibilityStatus status, string? summary, string? brokeIn, ISemanticVersion? unofficialVersion, string? unofficialUrl)
|
||||
{
|
||||
this.Status = status;
|
||||
this.Summary = summary;
|
||||
this.BrokeIn = brokeIn;
|
||||
this.UnofficialVersion = unofficialVersion;
|
||||
this.UnofficialUrl = unofficialUrl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki
|
||||
{
|
||||
/// <summary>The compatibility status for a mod.</summary>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#nullable disable
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki
|
||||
{
|
||||
|
@ -8,64 +8,114 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki
|
|||
/*********
|
||||
** Accessors
|
||||
*********/
|
||||
/// <summary>The mod's unique ID. If the mod has alternate/old IDs, they're listed in latest to newest order.</summary>
|
||||
public string[] ID { get; set; }
|
||||
/// <summary>The mod's unique ID. If the mod has alternate/old IDs, they're listed in latest to oldest order.</summary>
|
||||
public string[] ID { get; }
|
||||
|
||||
/// <summary>The mod's display name. If the mod has multiple names, the first one is the most canonical name.</summary>
|
||||
public string[] Name { get; set; }
|
||||
public string[] Name { get; }
|
||||
|
||||
/// <summary>The mod's author name. If the author has multiple names, the first one is the most canonical name.</summary>
|
||||
public string[] Author { get; set; }
|
||||
public string[] Author { get; }
|
||||
|
||||
/// <summary>The mod ID on Nexus.</summary>
|
||||
public int? NexusID { get; set; }
|
||||
public int? NexusID { get; }
|
||||
|
||||
/// <summary>The mod ID in the Chucklefish mod repo.</summary>
|
||||
public int? ChucklefishID { get; set; }
|
||||
public int? ChucklefishID { get; }
|
||||
|
||||
/// <summary>The mod ID in the CurseForge mod repo.</summary>
|
||||
public int? CurseForgeID { get; set; }
|
||||
public int? CurseForgeID { get; }
|
||||
|
||||
/// <summary>The mod key in the CurseForge mod repo (used in mod page URLs).</summary>
|
||||
public string CurseForgeKey { get; set; }
|
||||
public string? CurseForgeKey { get; }
|
||||
|
||||
/// <summary>The mod ID in the ModDrop mod repo.</summary>
|
||||
public int? ModDropID { get; set; }
|
||||
public int? ModDropID { get; }
|
||||
|
||||
/// <summary>The GitHub repository in the form 'owner/repo'.</summary>
|
||||
public string GitHubRepo { get; set; }
|
||||
public string? GitHubRepo { get; }
|
||||
|
||||
/// <summary>The URL to a non-GitHub source repo.</summary>
|
||||
public string CustomSourceUrl { get; set; }
|
||||
public string? CustomSourceUrl { get; }
|
||||
|
||||
/// <summary>The custom mod page URL (if applicable).</summary>
|
||||
public string CustomUrl { get; set; }
|
||||
public string? CustomUrl { get; }
|
||||
|
||||
/// <summary>The name of the mod which loads this content pack, if applicable.</summary>
|
||||
public string ContentPackFor { get; set; }
|
||||
public string? ContentPackFor { get; }
|
||||
|
||||
/// <summary>The mod's compatibility with the latest stable version of the game.</summary>
|
||||
public WikiCompatibilityInfo Compatibility { get; set; }
|
||||
public WikiCompatibilityInfo Compatibility { get; }
|
||||
|
||||
/// <summary>The mod's compatibility with the latest beta version of the game (if any).</summary>
|
||||
public WikiCompatibilityInfo BetaCompatibility { get; set; }
|
||||
public WikiCompatibilityInfo? BetaCompatibility { get; }
|
||||
|
||||
/// <summary>Whether a Stardew Valley or SMAPI beta which affects mod compatibility is in progress. If this is true, <see cref="BetaCompatibility"/> should be used for beta versions of SMAPI instead of <see cref="Compatibility"/>.</summary>
|
||||
#if NET5_0_OR_GREATER
|
||||
[MemberNotNullWhen(true, nameof(WikiModEntry.BetaCompatibility))]
|
||||
#endif
|
||||
public bool HasBetaInfo => this.BetaCompatibility != null;
|
||||
|
||||
/// <summary>The human-readable warnings for players about this mod.</summary>
|
||||
public string[] Warnings { get; set; }
|
||||
public string[] Warnings { get; }
|
||||
|
||||
/// <summary>The URL of the pull request which submits changes for an unofficial update to the author, if any.</summary>
|
||||
public string PullRequestUrl { get; set; }
|
||||
public string? PullRequestUrl { get; }
|
||||
|
||||
/// <summary>Special notes intended for developers who maintain unofficial updates or submit pull requests. </summary>
|
||||
public string DevNote { get; set; }
|
||||
/// <summary>Special notes intended for developers who maintain unofficial updates or submit pull requests.</summary>
|
||||
public string? DevNote { get; }
|
||||
|
||||
/// <summary>The data overrides to apply to the mod's manifest or remote mod page data, if any.</summary>
|
||||
public WikiDataOverrideEntry Overrides { get; set; }
|
||||
public WikiDataOverrideEntry? Overrides { get; }
|
||||
|
||||
/// <summary>The link anchor for the mod entry in the wiki compatibility list.</summary>
|
||||
public string Anchor { get; set; }
|
||||
public string? Anchor { get; }
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="id">The mod's unique ID. If the mod has alternate/old IDs, they're listed in latest to oldest order.</param>
|
||||
/// <param name="name">The mod's display name. If the mod has multiple names, the first one is the most canonical name.</param>
|
||||
/// <param name="author">The mod's author name. If the author has multiple names, the first one is the most canonical name.</param>
|
||||
/// <param name="nexusId">The mod ID on Nexus.</param>
|
||||
/// <param name="chucklefishId">The mod ID in the Chucklefish mod repo.</param>
|
||||
/// <param name="curseForgeId">The mod ID in the CurseForge mod repo.</param>
|
||||
/// <param name="curseForgeKey">The mod ID in the CurseForge mod repo.</param>
|
||||
/// <param name="modDropId">The mod ID in the ModDrop mod repo.</param>
|
||||
/// <param name="githubRepo">The GitHub repository in the form 'owner/repo'.</param>
|
||||
/// <param name="customSourceUrl">The URL to a non-GitHub source repo.</param>
|
||||
/// <param name="customUrl">The custom mod page URL (if applicable).</param>
|
||||
/// <param name="contentPackFor">The name of the mod which loads this content pack, if applicable.</param>
|
||||
/// <param name="compatibility">The mod's compatibility with the latest stable version of the game.</param>
|
||||
/// <param name="betaCompatibility">The mod's compatibility with the latest beta version of the game (if any).</param>
|
||||
/// <param name="warnings">The human-readable warnings for players about this mod.</param>
|
||||
/// <param name="pullRequestUrl">The URL of the pull request which submits changes for an unofficial update to the author, if any.</param>
|
||||
/// <param name="devNote">Special notes intended for developers who maintain unofficial updates or submit pull requests.</param>
|
||||
/// <param name="overrides">The data overrides to apply to the mod's manifest or remote mod page data, if any.</param>
|
||||
/// <param name="anchor">The link anchor for the mod entry in the wiki compatibility list.</param>
|
||||
public WikiModEntry(string[] id, string[] name, string[] author, int? nexusId, int? chucklefishId, int? curseForgeId, string? curseForgeKey, int? modDropId, string? githubRepo, string? customSourceUrl, string? customUrl, string? contentPackFor, WikiCompatibilityInfo compatibility, WikiCompatibilityInfo? betaCompatibility, string[] warnings, string? pullRequestUrl, string? devNote, WikiDataOverrideEntry? overrides, string? anchor)
|
||||
{
|
||||
this.ID = id;
|
||||
this.Name = name;
|
||||
this.Author = author;
|
||||
this.NexusID = nexusId;
|
||||
this.ChucklefishID = chucklefishId;
|
||||
this.CurseForgeID = curseForgeId;
|
||||
this.CurseForgeKey = curseForgeKey;
|
||||
this.ModDropID = modDropId;
|
||||
this.GitHubRepo = githubRepo;
|
||||
this.CustomSourceUrl = customSourceUrl;
|
||||
this.CustomUrl = customUrl;
|
||||
this.ContentPackFor = contentPackFor;
|
||||
this.Compatibility = compatibility;
|
||||
this.BetaCompatibility = betaCompatibility;
|
||||
this.Warnings = warnings;
|
||||
this.PullRequestUrl = pullRequestUrl;
|
||||
this.DevNote = devNote;
|
||||
this.Overrides = overrides;
|
||||
this.Anchor = anchor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki
|
||||
{
|
||||
/// <summary>Metadata from the wiki's mod compatibility list.</summary>
|
||||
|
@ -9,12 +7,27 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki
|
|||
** Accessors
|
||||
*********/
|
||||
/// <summary>The stable game version.</summary>
|
||||
public string StableVersion { get; set; }
|
||||
public string? StableVersion { get; }
|
||||
|
||||
/// <summary>The beta game version (if any).</summary>
|
||||
public string BetaVersion { get; set; }
|
||||
public string? BetaVersion { get; }
|
||||
|
||||
/// <summary>The mods on the wiki.</summary>
|
||||
public WikiModEntry[] Mods { get; set; }
|
||||
public WikiModEntry[] Mods { get; }
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="stableVersion">The stable game version.</param>
|
||||
/// <param name="betaVersion">The beta game version (if any).</param>
|
||||
/// <param name="mods">The mods on the wiki.</param>
|
||||
public WikiModList(string? stableVersion, string? betaVersion, WikiModEntry[] mods)
|
||||
{
|
||||
this.StableVersion = stableVersion;
|
||||
this.BetaVersion = betaVersion;
|
||||
this.Mods = mods;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -119,6 +119,9 @@ namespace StardewModdingAPI.Toolkit
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
#if NET5_0_OR_GREATER
|
||||
[MemberNotNullWhen(true, nameof(SemanticVersion.PrereleaseTag))]
|
||||
#endif
|
||||
public bool IsPrerelease()
|
||||
{
|
||||
return !string.IsNullOrWhiteSpace(this.PrereleaseTag);
|
||||
|
|
|
@ -3,7 +3,7 @@ using System.Collections.Generic;
|
|||
namespace StardewModdingAPI.Toolkit
|
||||
{
|
||||
/// <summary>A comparer for semantic versions based on the <see cref="SemanticVersion.CompareTo(ISemanticVersion)"/> field.</summary>
|
||||
public class SemanticVersionComparer : IComparer<ISemanticVersion>
|
||||
public class SemanticVersionComparer : IComparer<ISemanticVersion?>
|
||||
{
|
||||
/*********
|
||||
** Accessors
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Threading;
|
||||
|
@ -21,13 +19,17 @@ namespace StardewModdingAPI.Web
|
|||
** Fields
|
||||
*********/
|
||||
/// <summary>The background task server.</summary>
|
||||
private static BackgroundJobServer JobServer;
|
||||
private static BackgroundJobServer? JobServer;
|
||||
|
||||
/// <summary>The cache in which to store wiki metadata.</summary>
|
||||
private static IWikiCacheRepository WikiCache;
|
||||
private static IWikiCacheRepository? WikiCache;
|
||||
|
||||
/// <summary>The cache in which to store mod data.</summary>
|
||||
private static IModCacheRepository ModCache;
|
||||
private static IModCacheRepository? ModCache;
|
||||
|
||||
/// <summary>Whether the service has been started.</summary>
|
||||
[MemberNotNullWhen(true, nameof(BackgroundService.JobServer), nameof(BackgroundService.WikiCache), nameof(BackgroundService.ModCache))]
|
||||
private static bool IsStarted { get; set; }
|
||||
|
||||
|
||||
/*********
|
||||
|
@ -61,6 +63,8 @@ namespace StardewModdingAPI.Web
|
|||
RecurringJob.AddOrUpdate(() => BackgroundService.UpdateWikiAsync(), "*/10 * * * *"); // every 10 minutes
|
||||
RecurringJob.AddOrUpdate(() => BackgroundService.RemoveStaleModsAsync(), "0 * * * *"); // hourly
|
||||
|
||||
BackgroundService.IsStarted = true;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
|
@ -68,6 +72,8 @@ namespace StardewModdingAPI.Web
|
|||
/// <param name="cancellationToken">Tracks whether the shutdown process should no longer be graceful.</param>
|
||||
public async Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
BackgroundService.IsStarted = false;
|
||||
|
||||
if (BackgroundService.JobServer != null)
|
||||
await BackgroundService.JobServer.WaitForShutdownAsync(cancellationToken);
|
||||
}
|
||||
|
@ -75,6 +81,8 @@ namespace StardewModdingAPI.Web
|
|||
/// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
|
||||
public void Dispose()
|
||||
{
|
||||
BackgroundService.IsStarted = false;
|
||||
|
||||
BackgroundService.JobServer?.Dispose();
|
||||
}
|
||||
|
||||
|
@ -85,6 +93,9 @@ namespace StardewModdingAPI.Web
|
|||
[AutomaticRetry(Attempts = 3, DelaysInSeconds = new[] { 30, 60, 120 })]
|
||||
public static async Task UpdateWikiAsync()
|
||||
{
|
||||
if (!BackgroundService.IsStarted)
|
||||
throw new InvalidOperationException($"Must call {nameof(BackgroundService.StartAsync)} before scheduling tasks.");
|
||||
|
||||
WikiModList wikiCompatList = await new ModToolkit().GetWikiCompatibilityListAsync();
|
||||
BackgroundService.WikiCache.SaveWikiData(wikiCompatList.StableVersion, wikiCompatList.BetaVersion, wikiCompatList.Mods);
|
||||
}
|
||||
|
@ -92,6 +103,9 @@ namespace StardewModdingAPI.Web
|
|||
/// <summary>Remove mods which haven't been requested in over 48 hours.</summary>
|
||||
public static Task RemoveStaleModsAsync()
|
||||
{
|
||||
if (!BackgroundService.IsStarted)
|
||||
throw new InvalidOperationException($"Must call {nameof(BackgroundService.StartAsync)} before scheduling tasks.");
|
||||
|
||||
BackgroundService.ModCache.RemoveStaleMods(TimeSpan.FromHours(48));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
@ -59,8 +57,8 @@ namespace StardewModdingAPI.Web.Controllers
|
|||
{
|
||||
// choose versions
|
||||
ReleaseVersion[] versions = await this.GetReleaseVersionsAsync();
|
||||
ReleaseVersion stableVersion = versions.LastOrDefault(version => !version.IsForDevs);
|
||||
ReleaseVersion stableVersionForDevs = versions.LastOrDefault(version => version.IsForDevs);
|
||||
ReleaseVersion? stableVersion = versions.LastOrDefault(version => !version.IsForDevs);
|
||||
ReleaseVersion? stableVersionForDevs = versions.LastOrDefault(version => version.IsForDevs);
|
||||
|
||||
// render view
|
||||
IndexVersionModel stableVersionModel = stableVersion != null
|
||||
|
@ -91,7 +89,7 @@ namespace StardewModdingAPI.Web.Controllers
|
|||
entry.AbsoluteExpiration = DateTimeOffset.UtcNow.Add(this.CacheTime);
|
||||
|
||||
// get latest stable release
|
||||
GitRelease release = await this.GitHub.GetLatestReleaseAsync(this.RepositoryName, includePrerelease: false);
|
||||
GitRelease? release = await this.GitHub.GetLatestReleaseAsync(this.RepositoryName, includePrerelease: false);
|
||||
|
||||
// strip 'noinclude' blocks from release description
|
||||
if (release != null)
|
||||
|
@ -113,7 +111,7 @@ namespace StardewModdingAPI.Web.Controllers
|
|||
|
||||
/// <summary>Get a parsed list of SMAPI downloads for a release.</summary>
|
||||
/// <param name="release">The GitHub release.</param>
|
||||
private IEnumerable<ReleaseVersion> ParseReleaseVersions(GitRelease release)
|
||||
private IEnumerable<ReleaseVersion> ParseReleaseVersions(GitRelease? release)
|
||||
{
|
||||
if (release?.Assets == null)
|
||||
yield break;
|
||||
|
@ -124,7 +122,7 @@ namespace StardewModdingAPI.Web.Controllers
|
|||
continue;
|
||||
|
||||
Match match = Regex.Match(asset.FileName, @"SMAPI-(?<version>[\d\.]+(?:-.+)?)-installer(?<forDevs>-for-developers)?.zip");
|
||||
if (!match.Success || !SemanticVersion.TryParse(match.Groups["version"].Value, out ISemanticVersion version))
|
||||
if (!match.Success || !SemanticVersion.TryParse(match.Groups["version"].Value, out ISemanticVersion? version))
|
||||
continue;
|
||||
bool isForDevs = match.Groups["forDevs"].Success;
|
||||
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
@ -66,7 +64,7 @@ namespace StardewModdingAPI.Web.Controllers
|
|||
[Route("json/{schemaName}")]
|
||||
[Route("json/{schemaName}/{id}")]
|
||||
[Route("json/{schemaName}/{id}/{operation}")]
|
||||
public async Task<ViewResult> Index(string schemaName = null, string id = null, string operation = null)
|
||||
public async Task<ViewResult> Index(string? schemaName = null, string? id = null, string? operation = null)
|
||||
{
|
||||
// parse arguments
|
||||
schemaName = this.NormalizeSchemaName(schemaName);
|
||||
|
@ -81,7 +79,7 @@ namespace StardewModdingAPI.Web.Controllers
|
|||
return this.View("Index", result);
|
||||
|
||||
// fetch raw JSON
|
||||
StoredFileInfo file = await this.Storage.GetAsync(id, renew);
|
||||
StoredFileInfo file = await this.Storage.GetAsync(id!, renew);
|
||||
if (string.IsNullOrWhiteSpace(file.Content))
|
||||
return this.View("Index", result.SetUploadError("The JSON file seems to be empty."));
|
||||
result.SetContent(file.Content, expiry: file.Expiry, uploadWarning: file.Warning);
|
||||
|
@ -105,7 +103,7 @@ namespace StardewModdingAPI.Web.Controllers
|
|||
}
|
||||
catch (JsonReaderException ex)
|
||||
{
|
||||
return this.View("Index", result.AddErrors(new JsonValidatorErrorModel(ex.LineNumber, ex.Path, ex.Message, ErrorType.None)));
|
||||
return this.View("Index", result.AddErrors(new JsonValidatorErrorModel(ex.LineNumber, ex.Path!, ex.Message, ErrorType.None)));
|
||||
}
|
||||
|
||||
// format JSON
|
||||
|
@ -121,7 +119,7 @@ namespace StardewModdingAPI.Web.Controllers
|
|||
// load schema
|
||||
JSchema schema;
|
||||
{
|
||||
FileInfo schemaFile = this.FindSchemaFile(schemaName);
|
||||
FileInfo? schemaFile = this.FindSchemaFile(schemaName);
|
||||
if (schemaFile == null)
|
||||
return this.View("Index", result.SetParseError($"Invalid schema '{schemaName}'."));
|
||||
schema = JSchema.Parse(System.IO.File.ReadAllText(schemaFile.FullName));
|
||||
|
@ -144,7 +142,7 @@ namespace StardewModdingAPI.Web.Controllers
|
|||
/// <summary>Save raw JSON data.</summary>
|
||||
[HttpPost, AllowLargePosts]
|
||||
[Route("json")]
|
||||
public async Task<ActionResult> PostAsync(JsonValidatorRequestModel request)
|
||||
public async Task<ActionResult> PostAsync(JsonValidatorRequestModel? request)
|
||||
{
|
||||
if (request == null)
|
||||
return this.View("Index", this.GetModel(null, null, isEditView: true).SetUploadError("The request seems to be invalid."));
|
||||
|
@ -163,7 +161,7 @@ namespace StardewModdingAPI.Web.Controllers
|
|||
return this.View("Index", this.GetModel(result.ID, schemaName, isEditView: true).SetContent(input, null).SetUploadError(result.UploadError));
|
||||
|
||||
// redirect to view
|
||||
return this.Redirect(this.Url.PlainAction("Index", "JsonValidator", new { schemaName, id = result.ID }));
|
||||
return this.Redirect(this.Url.PlainAction("Index", "JsonValidator", new { schemaName, id = result.ID })!);
|
||||
}
|
||||
|
||||
|
||||
|
@ -174,14 +172,14 @@ namespace StardewModdingAPI.Web.Controllers
|
|||
/// <param name="pasteID">The stored file ID.</param>
|
||||
/// <param name="schemaName">The schema name with which the JSON was validated.</param>
|
||||
/// <param name="isEditView">Whether to show the edit view.</param>
|
||||
private JsonValidatorModel GetModel(string pasteID, string schemaName, bool isEditView)
|
||||
private JsonValidatorModel GetModel(string? pasteID, string? schemaName, bool isEditView)
|
||||
{
|
||||
return new JsonValidatorModel(pasteID, schemaName, this.SchemaFormats, isEditView);
|
||||
}
|
||||
|
||||
/// <summary>Get a normalized schema name, or the <see cref="DefaultSchemaID"/> if blank.</summary>
|
||||
/// <param name="schemaName">The raw schema name to normalize.</param>
|
||||
private string NormalizeSchemaName(string schemaName)
|
||||
private string NormalizeSchemaName(string? schemaName)
|
||||
{
|
||||
schemaName = schemaName?.Trim().ToLower();
|
||||
return !string.IsNullOrWhiteSpace(schemaName)
|
||||
|
@ -191,7 +189,7 @@ namespace StardewModdingAPI.Web.Controllers
|
|||
|
||||
/// <summary>Get the schema file given its unique ID.</summary>
|
||||
/// <param name="id">The schema ID.</param>
|
||||
private FileInfo FindSchemaFile(string id)
|
||||
private FileInfo? FindSchemaFile(string? id)
|
||||
{
|
||||
// normalize ID
|
||||
id = id?.Trim().ToLower();
|
||||
|
@ -216,13 +214,13 @@ namespace StardewModdingAPI.Web.Controllers
|
|||
// skip through transparent errors
|
||||
if (this.IsTransparentError(error))
|
||||
{
|
||||
foreach (var model in error.ChildErrors.SelectMany(this.GetErrorModels))
|
||||
foreach (JsonValidatorErrorModel model in error.ChildErrors.SelectMany(this.GetErrorModels))
|
||||
yield return model;
|
||||
yield break;
|
||||
}
|
||||
|
||||
// get message
|
||||
string message = this.GetOverrideError(error);
|
||||
string? message = this.GetOverrideError(error);
|
||||
if (message == null || message == this.TransparentToken)
|
||||
message = this.FlattenErrorMessage(error);
|
||||
|
||||
|
@ -236,7 +234,7 @@ namespace StardewModdingAPI.Web.Controllers
|
|||
private string FlattenErrorMessage(ValidationError error, int indent = 0)
|
||||
{
|
||||
// get override
|
||||
string message = this.GetOverrideError(error);
|
||||
string? message = this.GetOverrideError(error);
|
||||
if (message != null && message != this.TransparentToken)
|
||||
return message;
|
||||
|
||||
|
@ -257,7 +255,7 @@ namespace StardewModdingAPI.Web.Controllers
|
|||
break;
|
||||
|
||||
case ErrorType.Required:
|
||||
message = $"Missing required fields: {string.Join(", ", (List<string>)error.Value)}.";
|
||||
message = $"Missing required fields: {string.Join(", ", (List<string>)error.Value!)}.";
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -274,7 +272,7 @@ namespace StardewModdingAPI.Web.Controllers
|
|||
if (!error.ChildErrors.Any())
|
||||
return false;
|
||||
|
||||
string @override = this.GetOverrideError(error);
|
||||
string? @override = this.GetOverrideError(error);
|
||||
return
|
||||
@override == this.TransparentToken
|
||||
|| (error.ErrorType == ErrorType.Then && @override == null);
|
||||
|
@ -282,18 +280,18 @@ namespace StardewModdingAPI.Web.Controllers
|
|||
|
||||
/// <summary>Get an override error from the JSON schema, if any.</summary>
|
||||
/// <param name="error">The schema validation error.</param>
|
||||
private string GetOverrideError(ValidationError error)
|
||||
private string? GetOverrideError(ValidationError error)
|
||||
{
|
||||
string GetRawOverrideError()
|
||||
string? GetRawOverrideError()
|
||||
{
|
||||
// get override errors
|
||||
IDictionary<string, string> errors = this.GetExtensionField<Dictionary<string, string>>(error.Schema, "@errorMessages");
|
||||
IDictionary<string, string?>? errors = this.GetExtensionField<Dictionary<string, string?>>(error.Schema, "@errorMessages");
|
||||
if (errors == null)
|
||||
return null;
|
||||
errors = new Dictionary<string, string>(errors, StringComparer.OrdinalIgnoreCase);
|
||||
errors = new Dictionary<string, string?>(errors, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
// match error by type and message
|
||||
foreach ((string target, string errorMessage) in errors)
|
||||
foreach ((string target, string? errorMessage) in errors)
|
||||
{
|
||||
if (!target.Contains(":"))
|
||||
continue;
|
||||
|
@ -304,7 +302,7 @@ namespace StardewModdingAPI.Web.Controllers
|
|||
}
|
||||
|
||||
// match by type
|
||||
return errors.TryGetValue(error.ErrorType.ToString(), out string message)
|
||||
return errors.TryGetValue(error.ErrorType.ToString(), out string? message)
|
||||
? message?.Trim()
|
||||
: null;
|
||||
}
|
||||
|
@ -317,7 +315,7 @@ namespace StardewModdingAPI.Web.Controllers
|
|||
/// <typeparam name="T">The field type.</typeparam>
|
||||
/// <param name="schema">The schema whose extension fields to search.</param>
|
||||
/// <param name="key">The case-insensitive field key.</param>
|
||||
private T GetExtensionField<T>(JSchema schema, string key)
|
||||
private T? GetExtensionField<T>(JSchema schema, string key)
|
||||
{
|
||||
foreach ((string curKey, JToken value) in schema.ExtensionData)
|
||||
{
|
||||
|
@ -330,7 +328,7 @@ namespace StardewModdingAPI.Web.Controllers
|
|||
|
||||
/// <summary>Format a schema value for display.</summary>
|
||||
/// <param name="value">The value to format.</param>
|
||||
private string FormatValue(object value)
|
||||
private string FormatValue(object? value)
|
||||
{
|
||||
return value switch
|
||||
{
|
||||
|
|
|
@ -69,7 +69,7 @@ namespace StardewModdingAPI.Web.Controllers
|
|||
|
||||
case LogViewFormat.RawDownload:
|
||||
{
|
||||
string content = file.Error ?? file.Content;
|
||||
string content = file.Error ?? file.Content ?? string.Empty;
|
||||
return this.File(Encoding.UTF8.GetBytes(content), "plain/text", $"SMAPI log ({id}).txt");
|
||||
}
|
||||
|
||||
|
@ -97,7 +97,7 @@ namespace StardewModdingAPI.Web.Controllers
|
|||
return this.View("Index", this.GetModel(null, uploadError: uploadResult.UploadError));
|
||||
|
||||
// redirect to view
|
||||
return this.Redirect(this.Url.PlainAction("Index", "LogParser", new { id = uploadResult.ID }));
|
||||
return this.Redirect(this.Url.PlainAction("Index", "LogParser", new { id = uploadResult.ID })!);
|
||||
}
|
||||
|
||||
|
||||
|
@ -109,7 +109,7 @@ namespace StardewModdingAPI.Web.Controllers
|
|||
/// <param name="expiry">When the uploaded file will no longer be available.</param>
|
||||
/// <param name="uploadWarning">A non-blocking warning while uploading the log.</param>
|
||||
/// <param name="uploadError">An error which occurred while uploading the log.</param>
|
||||
private LogParserModel GetModel(string? pasteID, DateTime? expiry = null, string? uploadWarning = null, string? uploadError = null)
|
||||
private LogParserModel GetModel(string? pasteID, DateTimeOffset? expiry = null, string? uploadWarning = null, string? uploadError = null)
|
||||
{
|
||||
Platform? platform = this.DetectClientPlatform();
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -78,7 +77,7 @@ namespace StardewModdingAPI.Web.Controllers
|
|||
/// <param name="model">The mod search criteria.</param>
|
||||
/// <param name="version">The requested API version.</param>
|
||||
[HttpPost]
|
||||
public async Task<IEnumerable<ModEntryModel>> PostAsync([FromBody] ModSearchModel model, [FromRoute] string version)
|
||||
public async Task<IEnumerable<ModEntryModel>> PostAsync([FromBody] ModSearchModel? model, [FromRoute] string version)
|
||||
{
|
||||
if (model?.Mods == null)
|
||||
return Array.Empty<ModEntryModel>();
|
||||
|
@ -94,16 +93,16 @@ namespace StardewModdingAPI.Web.Controllers
|
|||
continue;
|
||||
|
||||
// special case: if this is an update check for the official SMAPI repo, check the Nexus mod page for beta versions
|
||||
if (mod.ID == config.SmapiInfo.ID && mod.UpdateKeys?.Any(key => key == config.SmapiInfo.DefaultUpdateKey) == true && mod.InstalledVersion?.IsPrerelease() == true)
|
||||
mod.UpdateKeys = mod.UpdateKeys.Concat(config.SmapiInfo.AddBetaUpdateKeys).ToArray();
|
||||
if (mod.ID == config.SmapiInfo.ID && mod.UpdateKeys.Any(key => key == config.SmapiInfo.DefaultUpdateKey) && mod.InstalledVersion?.IsPrerelease() == true)
|
||||
mod.AddUpdateKeys(config.SmapiInfo.AddBetaUpdateKeys);
|
||||
|
||||
// fetch result
|
||||
ModEntryModel result = await this.GetModData(mod, wikiData, model.IncludeExtendedMetadata, model.ApiVersion);
|
||||
if (!model.IncludeExtendedMetadata && (model.ApiVersion == null || mod.InstalledVersion == null))
|
||||
{
|
||||
var errors = new List<string>(result.Errors);
|
||||
errors.Add($"This API can't suggest an update because {nameof(model.ApiVersion)} or {nameof(mod.InstalledVersion)} are null, and you didn't specify {nameof(model.IncludeExtendedMetadata)} to get other info. See the SMAPI technical docs for usage.");
|
||||
result.Errors = errors.ToArray();
|
||||
result.Errors = result.Errors
|
||||
.Concat(new[] { $"This API can't suggest an update because {nameof(model.ApiVersion)} or {nameof(mod.InstalledVersion)} are null, and you didn't specify {nameof(model.IncludeExtendedMetadata)} to get other info. See the SMAPI technical docs for usage." })
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
mods[mod.ID] = result;
|
||||
|
@ -123,26 +122,26 @@ namespace StardewModdingAPI.Web.Controllers
|
|||
/// <param name="includeExtendedMetadata">Whether to include extended metadata for each mod.</param>
|
||||
/// <param name="apiVersion">The SMAPI version installed by the player.</param>
|
||||
/// <returns>Returns the mod data if found, else <c>null</c>.</returns>
|
||||
private async Task<ModEntryModel> GetModData(ModSearchEntryModel search, WikiModEntry[] wikiData, bool includeExtendedMetadata, ISemanticVersion apiVersion)
|
||||
private async Task<ModEntryModel> GetModData(ModSearchEntryModel search, WikiModEntry[] wikiData, bool includeExtendedMetadata, ISemanticVersion? apiVersion)
|
||||
{
|
||||
// cross-reference data
|
||||
ModDataRecord record = this.ModDatabase.Get(search.ID);
|
||||
WikiModEntry wikiEntry = wikiData.FirstOrDefault(entry => entry.ID.Contains(search.ID.Trim(), StringComparer.OrdinalIgnoreCase));
|
||||
ModDataRecord? record = this.ModDatabase.Get(search.ID);
|
||||
WikiModEntry? wikiEntry = wikiData.FirstOrDefault(entry => entry.ID.Contains(search.ID.Trim(), StringComparer.OrdinalIgnoreCase));
|
||||
UpdateKey[] updateKeys = this.GetUpdateKeys(search.UpdateKeys, record, wikiEntry).ToArray();
|
||||
ModOverrideConfig overrides = this.Config.Value.ModOverrides.FirstOrDefault(p => p.ID.Equals(search.ID?.Trim(), StringComparison.OrdinalIgnoreCase));
|
||||
ModOverrideConfig? overrides = this.Config.Value.ModOverrides.FirstOrDefault(p => p.ID.Equals(search.ID.Trim(), StringComparison.OrdinalIgnoreCase));
|
||||
bool allowNonStandardVersions = overrides?.AllowNonStandardVersions ?? false;
|
||||
|
||||
// SMAPI versions with a '-beta' tag indicate major changes that may need beta mod versions.
|
||||
// This doesn't apply to normal prerelease versions which have an '-alpha' tag.
|
||||
bool isSmapiBeta = apiVersion.IsPrerelease() && apiVersion.PrereleaseTag.StartsWith("beta");
|
||||
bool isSmapiBeta = apiVersion != null && apiVersion.IsPrerelease() && apiVersion.PrereleaseTag.StartsWith("beta");
|
||||
|
||||
// get latest versions
|
||||
ModEntryModel result = new() { ID = search.ID };
|
||||
ModEntryModel result = new(search.ID);
|
||||
IList<string> errors = new List<string>();
|
||||
ModEntryVersionModel main = null;
|
||||
ModEntryVersionModel optional = null;
|
||||
ModEntryVersionModel unofficial = null;
|
||||
ModEntryVersionModel unofficialForBeta = null;
|
||||
ModEntryVersionModel? main = null;
|
||||
ModEntryVersionModel? optional = null;
|
||||
ModEntryVersionModel? unofficial = null;
|
||||
ModEntryVersionModel? unofficialForBeta = null;
|
||||
foreach (UpdateKey updateKey in updateKeys)
|
||||
{
|
||||
// validate update key
|
||||
|
@ -162,9 +161,9 @@ namespace StardewModdingAPI.Web.Controllers
|
|||
|
||||
// handle versions
|
||||
if (this.IsNewer(data.Version, main?.Version))
|
||||
main = new ModEntryVersionModel(data.Version, data.Url);
|
||||
main = new ModEntryVersionModel(data.Version, data.Url!);
|
||||
if (this.IsNewer(data.PreviewVersion, optional?.Version))
|
||||
optional = new ModEntryVersionModel(data.PreviewVersion, data.Url);
|
||||
optional = new ModEntryVersionModel(data.PreviewVersion, data.Url!);
|
||||
}
|
||||
|
||||
// get unofficial version
|
||||
|
@ -172,7 +171,7 @@ namespace StardewModdingAPI.Web.Controllers
|
|||
unofficial = new ModEntryVersionModel(wikiEntry.Compatibility.UnofficialVersion, $"{this.Url.PlainAction("Index", "Mods", absoluteUrl: true)}#{wikiEntry.Anchor}");
|
||||
|
||||
// get unofficial version for beta
|
||||
if (wikiEntry?.HasBetaInfo == true)
|
||||
if (wikiEntry is { HasBetaInfo: true })
|
||||
{
|
||||
if (wikiEntry.BetaCompatibility.Status == WikiCompatibilityStatus.Unofficial)
|
||||
{
|
||||
|
@ -198,13 +197,13 @@ namespace StardewModdingAPI.Web.Controllers
|
|||
if (overrides?.SetUrl != null)
|
||||
{
|
||||
if (main != null)
|
||||
main.Url = overrides.SetUrl;
|
||||
main = new(main.Version, overrides.SetUrl);
|
||||
if (optional != null)
|
||||
optional.Url = overrides.SetUrl;
|
||||
optional = new(optional.Version, overrides.SetUrl);
|
||||
}
|
||||
|
||||
// get recommended update (if any)
|
||||
ISemanticVersion installedVersion = this.ModSites.GetMappedVersion(search.InstalledVersion?.ToString(), wikiEntry?.Overrides?.ChangeLocalVersions, allowNonStandard: allowNonStandardVersions);
|
||||
ISemanticVersion? installedVersion = this.ModSites.GetMappedVersion(search.InstalledVersion?.ToString(), wikiEntry?.Overrides?.ChangeLocalVersions, allowNonStandard: allowNonStandardVersions);
|
||||
if (apiVersion != null && installedVersion != null)
|
||||
{
|
||||
// get newer versions
|
||||
|
@ -219,7 +218,7 @@ namespace StardewModdingAPI.Web.Controllers
|
|||
updates.Add(unofficialForBeta);
|
||||
|
||||
// get newest version
|
||||
ModEntryVersionModel newest = null;
|
||||
ModEntryVersionModel? newest = null;
|
||||
foreach (ModEntryVersionModel update in updates)
|
||||
{
|
||||
if (newest == null || update.Version.IsNewerThan(newest.Version))
|
||||
|
@ -245,7 +244,7 @@ namespace StardewModdingAPI.Web.Controllers
|
|||
/// <param name="currentVersion">The current semantic version.</param>
|
||||
/// <param name="newVersion">The target semantic version.</param>
|
||||
/// <param name="useBetaChannel">Whether the user enabled the beta channel and should be offered prerelease updates.</param>
|
||||
private bool IsRecommendedUpdate(ISemanticVersion currentVersion, ISemanticVersion newVersion, bool useBetaChannel)
|
||||
private bool IsRecommendedUpdate(ISemanticVersion currentVersion, [NotNullWhen(true)] ISemanticVersion? newVersion, bool useBetaChannel)
|
||||
{
|
||||
return
|
||||
newVersion != null
|
||||
|
@ -256,7 +255,7 @@ namespace StardewModdingAPI.Web.Controllers
|
|||
/// <summary>Get whether a <paramref name="current"/> version is newer than an <paramref name="other"/> version.</summary>
|
||||
/// <param name="current">The current version.</param>
|
||||
/// <param name="other">The other version.</param>
|
||||
private bool IsNewer(ISemanticVersion current, ISemanticVersion other)
|
||||
private bool IsNewer([NotNullWhen(true)] ISemanticVersion? current, ISemanticVersion? other)
|
||||
{
|
||||
return current != null && (other == null || other.IsOlderThan(current));
|
||||
}
|
||||
|
@ -265,17 +264,20 @@ namespace StardewModdingAPI.Web.Controllers
|
|||
/// <param name="updateKey">The namespaced update key.</param>
|
||||
/// <param name="allowNonStandardVersions">Whether to allow non-standard versions.</param>
|
||||
/// <param name="mapRemoteVersions">The changes to apply to remote versions for update checks.</param>
|
||||
private async Task<ModInfoModel> GetInfoForUpdateKeyAsync(UpdateKey updateKey, bool allowNonStandardVersions, ChangeDescriptor mapRemoteVersions)
|
||||
private async Task<ModInfoModel> GetInfoForUpdateKeyAsync(UpdateKey updateKey, bool allowNonStandardVersions, ChangeDescriptor? mapRemoteVersions)
|
||||
{
|
||||
if (!updateKey.LooksValid)
|
||||
return new ModInfoModel().SetError(RemoteModStatus.DoesNotExist, $"Invalid update key '{updateKey}'.");
|
||||
|
||||
// get mod page
|
||||
IModPage page;
|
||||
{
|
||||
bool isCached =
|
||||
this.ModCache.TryGetMod(updateKey.Site, updateKey.ID, out Cached<IModPage> cachedMod)
|
||||
this.ModCache.TryGetMod(updateKey.Site, updateKey.ID, out Cached<IModPage>? cachedMod)
|
||||
&& !this.ModCache.IsStale(cachedMod.LastUpdated, cachedMod.Data.Status == RemoteModStatus.TemporaryError ? this.Config.Value.ErrorCacheMinutes : this.Config.Value.SuccessCacheMinutes);
|
||||
|
||||
if (isCached)
|
||||
page = cachedMod.Data;
|
||||
page = cachedMod!.Data;
|
||||
else
|
||||
{
|
||||
page = await this.ModSites.GetModPageAsync(updateKey);
|
||||
|
@ -291,7 +293,7 @@ namespace StardewModdingAPI.Web.Controllers
|
|||
/// <param name="specifiedKeys">The specified update keys.</param>
|
||||
/// <param name="record">The mod's entry in SMAPI's internal database.</param>
|
||||
/// <param name="entry">The mod's entry in the wiki list.</param>
|
||||
private IEnumerable<UpdateKey> GetUpdateKeys(string[] specifiedKeys, ModDataRecord record, WikiModEntry entry)
|
||||
private IEnumerable<UpdateKey> GetUpdateKeys(string[]? specifiedKeys, ModDataRecord? record, WikiModEntry? entry)
|
||||
{
|
||||
// get unique update keys
|
||||
List<UpdateKey> updateKeys = this.GetUnfilteredUpdateKeys(specifiedKeys, record, entry)
|
||||
|
@ -310,7 +312,7 @@ namespace StardewModdingAPI.Web.Controllers
|
|||
// if the list has both an update key (like "Nexus:2400") and subkey (like "Nexus:2400@subkey") for the same page, the subkey takes priority
|
||||
{
|
||||
var removeKeys = new HashSet<UpdateKey>();
|
||||
foreach (var key in updateKeys)
|
||||
foreach (UpdateKey key in updateKeys)
|
||||
{
|
||||
if (key.Subkey != null)
|
||||
removeKeys.Add(new UpdateKey(key.Site, key.ID, null));
|
||||
|
@ -326,7 +328,7 @@ namespace StardewModdingAPI.Web.Controllers
|
|||
/// <param name="specifiedKeys">The specified update keys.</param>
|
||||
/// <param name="record">The mod's entry in SMAPI's internal database.</param>
|
||||
/// <param name="entry">The mod's entry in the wiki list.</param>
|
||||
private IEnumerable<string> GetUnfilteredUpdateKeys(string[] specifiedKeys, ModDataRecord record, WikiModEntry entry)
|
||||
private IEnumerable<string> GetUnfilteredUpdateKeys(string[]? specifiedKeys, ModDataRecord? record, WikiModEntry? entry)
|
||||
{
|
||||
// specified update keys
|
||||
foreach (string key in specifiedKeys ?? Array.Empty<string>())
|
||||
|
@ -337,7 +339,7 @@ namespace StardewModdingAPI.Web.Controllers
|
|||
|
||||
// default update key
|
||||
{
|
||||
string defaultKey = record?.GetDefaultUpdateKey();
|
||||
string? defaultKey = record?.GetDefaultUpdateKey();
|
||||
if (!string.IsNullOrWhiteSpace(defaultKey))
|
||||
yield return defaultKey;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
@ -54,8 +53,8 @@ namespace StardewModdingAPI.Web.Controllers
|
|||
public ModListModel FetchData()
|
||||
{
|
||||
// fetch cached data
|
||||
if (!this.Cache.TryGetWikiMetadata(out Cached<WikiMetadata> metadata))
|
||||
return new ModListModel();
|
||||
if (!this.Cache.TryGetWikiMetadata(out Cached<WikiMetadata>? metadata))
|
||||
return new ModListModel(null, null, Array.Empty<ModModel>(), lastUpdated: DateTimeOffset.UtcNow, isStale: true);
|
||||
|
||||
// build model
|
||||
return new ModListModel(
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
|
@ -42,7 +40,7 @@ namespace StardewModdingAPI.Web.Framework
|
|||
public void OnAuthorization(AuthorizationFilterContext context)
|
||||
{
|
||||
IFeatureCollection features = context.HttpContext.Features;
|
||||
IFormFeature formFeature = features.Get<IFormFeature>();
|
||||
IFormFeature? formFeature = features.Get<IFormFeature>();
|
||||
|
||||
if (formFeature?.Form == null)
|
||||
{
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
|
||||
namespace StardewModdingAPI.Web.Framework.Caching
|
||||
|
@ -12,21 +10,18 @@ namespace StardewModdingAPI.Web.Framework.Caching
|
|||
** Accessors
|
||||
*********/
|
||||
/// <summary>The cached data.</summary>
|
||||
public T Data { get; set; }
|
||||
public T Data { get; }
|
||||
|
||||
/// <summary>When the data was last updated.</summary>
|
||||
public DateTimeOffset LastUpdated { get; set; }
|
||||
public DateTimeOffset LastUpdated { get; }
|
||||
|
||||
/// <summary>When the data was last requested through the mod API.</summary>
|
||||
public DateTimeOffset LastRequested { get; set; }
|
||||
public DateTimeOffset LastRequested { get; internal set; }
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an empty instance.</summary>
|
||||
public Cached() { }
|
||||
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="data">The cached data.</param>
|
||||
public Cached(T data)
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using StardewModdingAPI.Toolkit.Framework.UpdateData;
|
||||
|
||||
namespace StardewModdingAPI.Web.Framework.Caching.Mods
|
||||
|
@ -16,7 +15,7 @@ namespace StardewModdingAPI.Web.Framework.Caching.Mods
|
|||
/// <param name="id">The mod's unique ID within the <paramref name="site"/>.</param>
|
||||
/// <param name="mod">The fetched mod.</param>
|
||||
/// <param name="markRequested">Whether to update the mod's 'last requested' date.</param>
|
||||
bool TryGetMod(ModSiteKey site, string id, out Cached<IModPage> mod, bool markRequested = true);
|
||||
bool TryGetMod(ModSiteKey site, string id, [NotNullWhen(true)] out Cached<IModPage>? mod, bool markRequested = true);
|
||||
|
||||
/// <summary>Save data fetched for a mod.</summary>
|
||||
/// <param name="site">The mod site on which the mod is found.</param>
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using StardewModdingAPI.Toolkit.Framework.UpdateData;
|
||||
|
||||
|
@ -25,7 +24,7 @@ namespace StardewModdingAPI.Web.Framework.Caching.Mods
|
|||
/// <param name="id">The mod's unique ID within the <paramref name="site"/>.</param>
|
||||
/// <param name="mod">The fetched mod.</param>
|
||||
/// <param name="markRequested">Whether to update the mod's 'last requested' date.</param>
|
||||
public bool TryGetMod(ModSiteKey site, string id, out Cached<IModPage> mod, bool markRequested = true)
|
||||
public bool TryGetMod(ModSiteKey site, string id, [NotNullWhen(true)] out Cached<IModPage>? mod, bool markRequested = true)
|
||||
{
|
||||
// get mod
|
||||
if (!this.Mods.TryGetValue(this.GetKey(site, id), out var cachedMod))
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using StardewModdingAPI.Toolkit.Framework.Clients.Wiki;
|
||||
|
||||
namespace StardewModdingAPI.Web.Framework.Caching.Wiki
|
||||
|
@ -14,16 +13,16 @@ namespace StardewModdingAPI.Web.Framework.Caching.Wiki
|
|||
*********/
|
||||
/// <summary>Get the cached wiki metadata.</summary>
|
||||
/// <param name="metadata">The fetched metadata.</param>
|
||||
bool TryGetWikiMetadata(out Cached<WikiMetadata> metadata);
|
||||
bool TryGetWikiMetadata([NotNullWhen(true)] out Cached<WikiMetadata>? metadata);
|
||||
|
||||
/// <summary>Get the cached wiki mods.</summary>
|
||||
/// <param name="filter">A filter to apply, if any.</param>
|
||||
IEnumerable<Cached<WikiModEntry>> GetWikiMods(Func<WikiModEntry, bool> filter = null);
|
||||
IEnumerable<Cached<WikiModEntry>> GetWikiMods(Func<WikiModEntry, bool>? filter = null);
|
||||
|
||||
/// <summary>Save data fetched from the wiki compatibility list.</summary>
|
||||
/// <param name="stableVersion">The current stable Stardew Valley version.</param>
|
||||
/// <param name="betaVersion">The current beta Stardew Valley version.</param>
|
||||
/// <param name="mods">The mod data.</param>
|
||||
void SaveWikiData(string stableVersion, string betaVersion, IEnumerable<WikiModEntry> mods);
|
||||
void SaveWikiData(string? stableVersion, string? betaVersion, IEnumerable<WikiModEntry> mods);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using StardewModdingAPI.Toolkit.Framework.Clients.Wiki;
|
||||
|
||||
|
@ -14,7 +13,7 @@ namespace StardewModdingAPI.Web.Framework.Caching.Wiki
|
|||
** Fields
|
||||
*********/
|
||||
/// <summary>The saved wiki metadata.</summary>
|
||||
private Cached<WikiMetadata> Metadata;
|
||||
private Cached<WikiMetadata>? Metadata;
|
||||
|
||||
/// <summary>The cached wiki data.</summary>
|
||||
private Cached<WikiModEntry>[] Mods = Array.Empty<Cached<WikiModEntry>>();
|
||||
|
@ -25,7 +24,7 @@ namespace StardewModdingAPI.Web.Framework.Caching.Wiki
|
|||
*********/
|
||||
/// <summary>Get the cached wiki metadata.</summary>
|
||||
/// <param name="metadata">The fetched metadata.</param>
|
||||
public bool TryGetWikiMetadata(out Cached<WikiMetadata> metadata)
|
||||
public bool TryGetWikiMetadata([NotNullWhen(true)] out Cached<WikiMetadata>? metadata)
|
||||
{
|
||||
metadata = this.Metadata;
|
||||
return metadata != null;
|
||||
|
@ -33,7 +32,7 @@ namespace StardewModdingAPI.Web.Framework.Caching.Wiki
|
|||
|
||||
/// <summary>Get the cached wiki mods.</summary>
|
||||
/// <param name="filter">A filter to apply, if any.</param>
|
||||
public IEnumerable<Cached<WikiModEntry>> GetWikiMods(Func<WikiModEntry, bool> filter = null)
|
||||
public IEnumerable<Cached<WikiModEntry>> GetWikiMods(Func<WikiModEntry, bool>? filter = null)
|
||||
{
|
||||
foreach (var mod in this.Mods)
|
||||
{
|
||||
|
@ -46,7 +45,7 @@ namespace StardewModdingAPI.Web.Framework.Caching.Wiki
|
|||
/// <param name="stableVersion">The current stable Stardew Valley version.</param>
|
||||
/// <param name="betaVersion">The current beta Stardew Valley version.</param>
|
||||
/// <param name="mods">The mod data.</param>
|
||||
public void SaveWikiData(string stableVersion, string betaVersion, IEnumerable<WikiModEntry> mods)
|
||||
public void SaveWikiData(string? stableVersion, string? betaVersion, IEnumerable<WikiModEntry> mods)
|
||||
{
|
||||
this.Metadata = new Cached<WikiMetadata>(new WikiMetadata(stableVersion, betaVersion));
|
||||
this.Mods = mods.Select(mod => new Cached<WikiModEntry>(mod)).ToArray();
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
namespace StardewModdingAPI.Web.Framework.Caching.Wiki
|
||||
{
|
||||
/// <summary>The model for cached wiki metadata.</summary>
|
||||
|
@ -9,22 +7,19 @@ namespace StardewModdingAPI.Web.Framework.Caching.Wiki
|
|||
** Accessors
|
||||
*********/
|
||||
/// <summary>The current stable Stardew Valley version.</summary>
|
||||
public string StableVersion { get; set; }
|
||||
public string? StableVersion { get; }
|
||||
|
||||
/// <summary>The current beta Stardew Valley version.</summary>
|
||||
public string BetaVersion { get; set; }
|
||||
public string? BetaVersion { get; }
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
public WikiMetadata() { }
|
||||
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="stableVersion">The current stable Stardew Valley version.</param>
|
||||
/// <param name="betaVersion">The current beta Stardew Valley version.</param>
|
||||
public WikiMetadata(string stableVersion, string betaVersion)
|
||||
public WikiMetadata(string? stableVersion, string? betaVersion)
|
||||
{
|
||||
this.StableVersion = stableVersion;
|
||||
this.BetaVersion = betaVersion;
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -44,7 +42,7 @@ namespace StardewModdingAPI.Web.Framework.Clients.Chucklefish
|
|||
|
||||
/// <summary>Get update check info about a mod.</summary>
|
||||
/// <param name="id">The mod ID.</param>
|
||||
public async Task<IModPage> GetModData(string id)
|
||||
public async Task<IModPage?> GetModData(string id)
|
||||
{
|
||||
IModPage page = new GenericModPage(this.SiteKey, id);
|
||||
|
||||
|
@ -53,7 +51,7 @@ namespace StardewModdingAPI.Web.Framework.Clients.Chucklefish
|
|||
return page.SetError(RemoteModStatus.DoesNotExist, $"The value '{id}' isn't a valid Chucklefish mod ID, must be an integer ID.");
|
||||
|
||||
// fetch HTML
|
||||
string html;
|
||||
string? html;
|
||||
try
|
||||
{
|
||||
html = await this.Client
|
||||
|
@ -69,7 +67,7 @@ namespace StardewModdingAPI.Web.Framework.Clients.Chucklefish
|
|||
|
||||
// extract mod info
|
||||
string url = this.GetModUrl(parsedId);
|
||||
string version = doc.DocumentNode.SelectSingleNode("//h1/span")?.InnerText;
|
||||
string? version = doc.DocumentNode.SelectSingleNode("//h1/span")?.InnerText;
|
||||
string name = doc.DocumentNode.SelectSingleNode("//h1").ChildNodes[0].InnerText.Trim();
|
||||
if (name.StartsWith("[SMAPI]"))
|
||||
name = name.Substring("[SMAPI]".Length).TrimStart();
|
||||
|
@ -81,7 +79,7 @@ namespace StardewModdingAPI.Web.Framework.Clients.Chucklefish
|
|||
/// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
|
||||
public void Dispose()
|
||||
{
|
||||
this.Client?.Dispose();
|
||||
this.Client.Dispose();
|
||||
}
|
||||
|
||||
|
||||
|
@ -92,7 +90,7 @@ namespace StardewModdingAPI.Web.Framework.Clients.Chucklefish
|
|||
/// <param name="id">The mod ID.</param>
|
||||
private string GetModUrl(uint id)
|
||||
{
|
||||
UriBuilder builder = new(this.Client.BaseClient.BaseAddress);
|
||||
UriBuilder builder = new(this.Client.BaseClient.BaseAddress!);
|
||||
builder.Path += string.Format(this.ModPageUrlFormat, id);
|
||||
return builder.Uri.ToString();
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -42,7 +40,7 @@ namespace StardewModdingAPI.Web.Framework.Clients.CurseForge
|
|||
|
||||
/// <summary>Get update check info about a mod.</summary>
|
||||
/// <param name="id">The mod ID.</param>
|
||||
public async Task<IModPage> GetModData(string id)
|
||||
public async Task<IModPage?> GetModData(string id)
|
||||
{
|
||||
IModPage page = new GenericModPage(this.SiteKey, id);
|
||||
|
||||
|
@ -51,9 +49,9 @@ namespace StardewModdingAPI.Web.Framework.Clients.CurseForge
|
|||
return page.SetError(RemoteModStatus.DoesNotExist, $"The value '{id}' isn't a valid CurseForge mod ID, must be an integer ID.");
|
||||
|
||||
// get raw data
|
||||
ModModel mod = await this.Client
|
||||
ModModel? mod = await this.Client
|
||||
.GetAsync($"addon/{parsedId}")
|
||||
.As<ModModel>();
|
||||
.As<ModModel?>();
|
||||
if (mod == null)
|
||||
return page.SetError(RemoteModStatus.DoesNotExist, "Found no CurseForge mod with this ID.");
|
||||
|
||||
|
@ -73,7 +71,7 @@ namespace StardewModdingAPI.Web.Framework.Clients.CurseForge
|
|||
/// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
|
||||
public void Dispose()
|
||||
{
|
||||
this.Client?.Dispose();
|
||||
this.Client.Dispose();
|
||||
}
|
||||
|
||||
|
||||
|
@ -82,9 +80,9 @@ namespace StardewModdingAPI.Web.Framework.Clients.CurseForge
|
|||
*********/
|
||||
/// <summary>Get a raw version string for a mod file, if available.</summary>
|
||||
/// <param name="file">The file whose version to get.</param>
|
||||
private string GetRawVersion(ModFileModel file)
|
||||
private string? GetRawVersion(ModFileModel file)
|
||||
{
|
||||
Match match = this.VersionInNamePattern.Match(file.DisplayName);
|
||||
Match match = this.VersionInNamePattern.Match(file.DisplayName ?? "");
|
||||
if (!match.Success)
|
||||
match = this.VersionInNamePattern.Match(file.FileName);
|
||||
|
||||
|
|
|
@ -1,14 +1,28 @@
|
|||
#nullable disable
|
||||
|
||||
namespace StardewModdingAPI.Web.Framework.Clients.CurseForge.ResponseModels
|
||||
{
|
||||
/// <summary>Metadata from the CurseForge API about a mod file.</summary>
|
||||
public class ModFileModel
|
||||
{
|
||||
/*********
|
||||
** Accessors
|
||||
*********/
|
||||
/// <summary>The file name as downloaded.</summary>
|
||||
public string FileName { get; set; }
|
||||
public string FileName { get; }
|
||||
|
||||
/// <summary>The file display name.</summary>
|
||||
public string DisplayName { get; set; }
|
||||
public string? DisplayName { get; }
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="fileName">The file name as downloaded.</param>
|
||||
/// <param name="displayName">The file display name.</param>
|
||||
public ModFileModel(string fileName, string? displayName)
|
||||
{
|
||||
this.FileName = fileName;
|
||||
this.DisplayName = displayName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,20 +1,38 @@
|
|||
#nullable disable
|
||||
|
||||
namespace StardewModdingAPI.Web.Framework.Clients.CurseForge.ResponseModels
|
||||
{
|
||||
/// <summary>An mod from the CurseForge API.</summary>
|
||||
public class ModModel
|
||||
{
|
||||
/*********
|
||||
** Accessors
|
||||
*********/
|
||||
/// <summary>The mod's unique ID on CurseForge.</summary>
|
||||
public int ID { get; set; }
|
||||
public int ID { get; }
|
||||
|
||||
/// <summary>The mod name.</summary>
|
||||
public string Name { get; set; }
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>The web URL for the mod page.</summary>
|
||||
public string WebsiteUrl { get; set; }
|
||||
public string WebsiteUrl { get; }
|
||||
|
||||
/// <summary>The available file downloads.</summary>
|
||||
public ModFileModel[] LatestFiles { get; set; }
|
||||
public ModFileModel[] LatestFiles { get; }
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="id">The mod's unique ID on CurseForge.</param>
|
||||
/// <param name="name">The mod name.</param>
|
||||
/// <param name="websiteUrl">The web URL for the mod page.</param>
|
||||
/// <param name="latestFiles">The available file downloads.</param>
|
||||
public ModModel(int id, string name, string websiteUrl, ModFileModel[] latestFiles)
|
||||
{
|
||||
this.ID = id;
|
||||
this.Name = name;
|
||||
this.WebsiteUrl = websiteUrl;
|
||||
this.LatestFiles = latestFiles;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
namespace StardewModdingAPI.Web.Framework.Clients
|
||||
{
|
||||
/// <summary>Generic metadata about a file download on a mod page.</summary>
|
||||
|
@ -9,26 +7,23 @@ namespace StardewModdingAPI.Web.Framework.Clients
|
|||
** Accessors
|
||||
*********/
|
||||
/// <summary>The download's display name.</summary>
|
||||
public string Name { get; set; }
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>The download's description.</summary>
|
||||
public string Description { get; set; }
|
||||
public string? Description { get; }
|
||||
|
||||
/// <summary>The download's file version.</summary>
|
||||
public string Version { get; set; }
|
||||
public string? Version { get; }
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an empty instance.</summary>
|
||||
public GenericModDownload() { }
|
||||
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="name">The download's display name.</param>
|
||||
/// <param name="description">The download's description.</param>
|
||||
/// <param name="version">The download's file version.</param>
|
||||
public GenericModDownload(string name, string description, string version)
|
||||
public GenericModDownload(string name, string? description, string? version)
|
||||
{
|
||||
this.Name = name;
|
||||
this.Description = description;
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using StardewModdingAPI.Toolkit.Framework.UpdateData;
|
||||
|
||||
|
@ -20,30 +19,31 @@ namespace StardewModdingAPI.Web.Framework.Clients
|
|||
public string Id { get; set; }
|
||||
|
||||
/// <summary>The mod name.</summary>
|
||||
public string Name { get; set; }
|
||||
public string? Name { get; set; }
|
||||
|
||||
/// <summary>The mod's semantic version number.</summary>
|
||||
public string Version { get; set; }
|
||||
public string? Version { get; set; }
|
||||
|
||||
/// <summary>The mod's web URL.</summary>
|
||||
public string Url { get; set; }
|
||||
public string? Url { get; set; }
|
||||
|
||||
/// <summary>The mod downloads.</summary>
|
||||
public IModDownload[] Downloads { get; set; } = Array.Empty<IModDownload>();
|
||||
|
||||
/// <summary>The mod availability status on the remote site.</summary>
|
||||
public RemoteModStatus Status { get; set; } = RemoteModStatus.Ok;
|
||||
public RemoteModStatus Status { get; set; } = RemoteModStatus.InvalidData;
|
||||
|
||||
/// <summary>A user-friendly error which indicates why fetching the mod info failed (if applicable).</summary>
|
||||
public string Error { get; set; }
|
||||
public string? Error { get; set; }
|
||||
|
||||
/// <summary>Whether the mod data is valid.</summary>
|
||||
[MemberNotNullWhen(true, nameof(IModPage.Name), nameof(IModPage.Url))]
|
||||
public bool IsValid => this.Status == RemoteModStatus.Ok;
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an empty instance.</summary>
|
||||
public GenericModPage() { }
|
||||
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="site">The mod site containing the mod.</param>
|
||||
/// <param name="id">The mod's unique ID within the site.</param>
|
||||
|
@ -58,12 +58,13 @@ namespace StardewModdingAPI.Web.Framework.Clients
|
|||
/// <param name="version">The mod's semantic version number.</param>
|
||||
/// <param name="url">The mod's web URL.</param>
|
||||
/// <param name="downloads">The mod downloads.</param>
|
||||
public IModPage SetInfo(string name, string version, string url, IEnumerable<IModDownload> downloads)
|
||||
public IModPage SetInfo(string name, string? version, string url, IEnumerable<IModDownload> downloads)
|
||||
{
|
||||
this.Name = name;
|
||||
this.Version = version;
|
||||
this.Url = url;
|
||||
this.Downloads = downloads.ToArray();
|
||||
this.Status = RemoteModStatus.Ok;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace StardewModdingAPI.Web.Framework.Clients.GitHub
|
||||
|
@ -7,16 +5,34 @@ namespace StardewModdingAPI.Web.Framework.Clients.GitHub
|
|||
/// <summary>A GitHub download attached to a release.</summary>
|
||||
internal class GitAsset
|
||||
{
|
||||
/*********
|
||||
** Accessors
|
||||
*********/
|
||||
/// <summary>The file name.</summary>
|
||||
[JsonProperty("name")]
|
||||
public string FileName { get; set; }
|
||||
public string FileName { get; }
|
||||
|
||||
/// <summary>The file content type.</summary>
|
||||
[JsonProperty("content_type")]
|
||||
public string ContentType { get; set; }
|
||||
public string ContentType { get; }
|
||||
|
||||
/// <summary>The download URL.</summary>
|
||||
[JsonProperty("browser_download_url")]
|
||||
public string DownloadUrl { get; set; }
|
||||
public string DownloadUrl { get; }
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="fileName">The file name.</param>
|
||||
/// <param name="contentType">The file content type.</param>
|
||||
/// <param name="downloadUrl">The download URL.</param>
|
||||
public GitAsset(string fileName, string contentType, string downloadUrl)
|
||||
{
|
||||
this.FileName = fileName;
|
||||
this.ContentType = contentType;
|
||||
this.DownloadUrl = downloadUrl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
|
@ -35,26 +33,26 @@ namespace StardewModdingAPI.Web.Framework.Clients.GitHub
|
|||
/// <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>
|
||||
public GitHubClient(string baseUrl, string userAgent, string acceptHeader, string username, string password)
|
||||
public GitHubClient(string baseUrl, string userAgent, string acceptHeader, string? username, string? password)
|
||||
{
|
||||
this.Client = new FluentClient(baseUrl)
|
||||
.SetUserAgent(userAgent)
|
||||
.AddDefault(req => req.WithHeader("Accept", acceptHeader));
|
||||
if (!string.IsNullOrWhiteSpace(username))
|
||||
this.Client = this.Client.SetBasicAuthentication(username, password);
|
||||
this.Client = this.Client.SetBasicAuthentication(username, password!);
|
||||
}
|
||||
|
||||
/// <summary>Get basic metadata for a GitHub repository, if available.</summary>
|
||||
/// <param name="repo">The repository key (like <c>Pathoschild/SMAPI</c>).</param>
|
||||
/// <returns>Returns the repository info if it exists, else <c>null</c>.</returns>
|
||||
public async Task<GitRepo> GetRepositoryAsync(string repo)
|
||||
public async Task<GitRepo?> GetRepositoryAsync(string repo)
|
||||
{
|
||||
this.AssertKeyFormat(repo);
|
||||
try
|
||||
{
|
||||
return await this.Client
|
||||
.GetAsync($"repos/{repo}")
|
||||
.As<GitRepo>();
|
||||
.As<GitRepo?>();
|
||||
}
|
||||
catch (ApiException ex) when (ex.Status == HttpStatusCode.NotFound)
|
||||
{
|
||||
|
@ -66,7 +64,7 @@ namespace StardewModdingAPI.Web.Framework.Clients.GitHub
|
|||
/// <param name="repo">The repository key (like <c>Pathoschild/SMAPI</c>).</param>
|
||||
/// <param name="includePrerelease">Whether to return a prerelease version if it's latest.</param>
|
||||
/// <returns>Returns the release if found, else <c>null</c>.</returns>
|
||||
public async Task<GitRelease> GetLatestReleaseAsync(string repo, bool includePrerelease = false)
|
||||
public async Task<GitRelease?> GetLatestReleaseAsync(string repo, bool includePrerelease = false)
|
||||
{
|
||||
this.AssertKeyFormat(repo);
|
||||
try
|
||||
|
@ -81,7 +79,7 @@ namespace StardewModdingAPI.Web.Framework.Clients.GitHub
|
|||
|
||||
return await this.Client
|
||||
.GetAsync($"repos/{repo}/releases/latest")
|
||||
.As<GitRelease>();
|
||||
.As<GitRelease?>();
|
||||
}
|
||||
catch (ApiException ex) when (ex.Status == HttpStatusCode.NotFound)
|
||||
{
|
||||
|
@ -91,7 +89,7 @@ namespace StardewModdingAPI.Web.Framework.Clients.GitHub
|
|||
|
||||
/// <summary>Get update check info about a mod.</summary>
|
||||
/// <param name="id">The mod ID.</param>
|
||||
public async Task<IModPage> GetModData(string id)
|
||||
public async Task<IModPage?> GetModData(string id)
|
||||
{
|
||||
IModPage page = new GenericModPage(this.SiteKey, id);
|
||||
|
||||
|
@ -99,15 +97,15 @@ namespace StardewModdingAPI.Web.Framework.Clients.GitHub
|
|||
return page.SetError(RemoteModStatus.DoesNotExist, $"The value '{id}' isn't a valid GitHub mod ID, must be a username and project name like 'Pathoschild/SMAPI'.");
|
||||
|
||||
// fetch repo info
|
||||
GitRepo repository = await this.GetRepositoryAsync(id);
|
||||
GitRepo? repository = await this.GetRepositoryAsync(id);
|
||||
if (repository == null)
|
||||
return page.SetError(RemoteModStatus.DoesNotExist, "Found no GitHub repository for this ID.");
|
||||
string name = repository.FullName;
|
||||
string url = $"{repository.WebUrl}/releases";
|
||||
|
||||
// get releases
|
||||
GitRelease latest;
|
||||
GitRelease preview;
|
||||
GitRelease? latest;
|
||||
GitRelease? preview;
|
||||
{
|
||||
// get latest release (whether preview or stable)
|
||||
latest = await this.GetLatestReleaseAsync(id, includePrerelease: true);
|
||||
|
@ -118,7 +116,7 @@ namespace StardewModdingAPI.Web.Framework.Clients.GitHub
|
|||
preview = null;
|
||||
if (latest.IsPrerelease)
|
||||
{
|
||||
GitRelease release = await this.GetLatestReleaseAsync(id, includePrerelease: false);
|
||||
GitRelease? release = await this.GetLatestReleaseAsync(id, includePrerelease: false);
|
||||
if (release != null)
|
||||
{
|
||||
preview = latest;
|
||||
|
@ -129,8 +127,8 @@ namespace StardewModdingAPI.Web.Framework.Clients.GitHub
|
|||
|
||||
// get downloads
|
||||
IModDownload[] downloads = new[] { latest, preview }
|
||||
.Where(release => release != null)
|
||||
.Select(release => (IModDownload)new GenericModDownload(release.Name, release.Body, release.Tag))
|
||||
.Where(release => release is not null)
|
||||
.Select(release => (IModDownload)new GenericModDownload(release!.Name, release.Body, release.Tag))
|
||||
.ToArray();
|
||||
|
||||
// return info
|
||||
|
@ -140,7 +138,7 @@ namespace StardewModdingAPI.Web.Framework.Clients.GitHub
|
|||
/// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
|
||||
public void Dispose()
|
||||
{
|
||||
this.Client?.Dispose();
|
||||
this.Client.Dispose();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace StardewModdingAPI.Web.Framework.Clients.GitHub
|
||||
|
@ -7,16 +5,34 @@ namespace StardewModdingAPI.Web.Framework.Clients.GitHub
|
|||
/// <summary>The license info for a GitHub project.</summary>
|
||||
internal class GitLicense
|
||||
{
|
||||
/*********
|
||||
** Accessors
|
||||
*********/
|
||||
/// <summary>The license display name.</summary>
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; set; }
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>The SPDX ID for the license.</summary>
|
||||
[JsonProperty("spdx_id")]
|
||||
public string SpdxId { get; set; }
|
||||
public string SpdxId { get; }
|
||||
|
||||
/// <summary>The URL for the license info.</summary>
|
||||
[JsonProperty("url")]
|
||||
public string Url { get; set; }
|
||||
public string Url { get; }
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="name">The license display name.</param>
|
||||
/// <param name="spdxId">The SPDX ID for the license.</param>
|
||||
/// <param name="url">The URL for the license info.</param>
|
||||
public GitLicense(string name, string spdxId, string url)
|
||||
{
|
||||
this.Name = name;
|
||||
this.SpdxId = spdxId;
|
||||
this.Url = url;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace StardewModdingAPI.Web.Framework.Clients.GitHub
|
||||
|
@ -12,24 +11,45 @@ namespace StardewModdingAPI.Web.Framework.Clients.GitHub
|
|||
*********/
|
||||
/// <summary>The display name.</summary>
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; set; }
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>The semantic version string.</summary>
|
||||
[JsonProperty("tag_name")]
|
||||
public string Tag { get; set; }
|
||||
public string Tag { get; }
|
||||
|
||||
/// <summary>The Markdown description for the release.</summary>
|
||||
public string Body { get; set; }
|
||||
public string Body { get; internal set; }
|
||||
|
||||
/// <summary>Whether this is a draft version.</summary>
|
||||
[JsonProperty("draft")]
|
||||
public bool IsDraft { get; set; }
|
||||
public bool IsDraft { get; }
|
||||
|
||||
/// <summary>Whether this is a prerelease version.</summary>
|
||||
[JsonProperty("prerelease")]
|
||||
public bool IsPrerelease { get; set; }
|
||||
public bool IsPrerelease { get; }
|
||||
|
||||
/// <summary>The attached files.</summary>
|
||||
public GitAsset[] Assets { get; set; }
|
||||
public GitAsset[] Assets { get; }
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="name">The display name.</param>
|
||||
/// <param name="tag">The semantic version string.</param>
|
||||
/// <param name="body">The Markdown description for the release.</param>
|
||||
/// <param name="isDraft">Whether this is a draft version.</param>
|
||||
/// <param name="isPrerelease">Whether this is a prerelease version.</param>
|
||||
/// <param name="assets">The attached files.</param>
|
||||
public GitRelease(string name, string tag, string? body, bool isDraft, bool isPrerelease, GitAsset[]? assets)
|
||||
{
|
||||
this.Name = name;
|
||||
this.Tag = tag;
|
||||
this.Body = body ?? string.Empty;
|
||||
this.IsDraft = isDraft;
|
||||
this.IsPrerelease = isPrerelease;
|
||||
this.Assets = assets ?? Array.Empty<GitAsset>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace StardewModdingAPI.Web.Framework.Clients.GitHub
|
||||
|
@ -7,16 +5,34 @@ namespace StardewModdingAPI.Web.Framework.Clients.GitHub
|
|||
/// <summary>Basic metadata about a GitHub project.</summary>
|
||||
internal class GitRepo
|
||||
{
|
||||
/*********
|
||||
** Accessors
|
||||
*********/
|
||||
/// <summary>The full repository name, including the owner.</summary>
|
||||
[JsonProperty("full_name")]
|
||||
public string FullName { get; set; }
|
||||
public string FullName { get; }
|
||||
|
||||
/// <summary>The URL to the repository web page, if any.</summary>
|
||||
[JsonProperty("html_url")]
|
||||
public string WebUrl { get; set; }
|
||||
public string? WebUrl { get; }
|
||||
|
||||
/// <summary>The code license, if any.</summary>
|
||||
[JsonProperty("license")]
|
||||
public GitLicense License { get; set; }
|
||||
public GitLicense? License { get; }
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="fullName">The full repository name, including the owner.</param>
|
||||
/// <param name="webUrl">The URL to the repository web page, if any.</param>
|
||||
/// <param name="license">The code license, if any.</param>
|
||||
public GitRepo(string fullName, string? webUrl, GitLicense? license)
|
||||
{
|
||||
this.FullName = fullName;
|
||||
this.WebUrl = webUrl;
|
||||
this.License = license;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
@ -14,12 +12,12 @@ namespace StardewModdingAPI.Web.Framework.Clients.GitHub
|
|||
/// <summary>Get basic metadata for a GitHub repository, if available.</summary>
|
||||
/// <param name="repo">The repository key (like <c>Pathoschild/SMAPI</c>).</param>
|
||||
/// <returns>Returns the repository info if it exists, else <c>null</c>.</returns>
|
||||
Task<GitRepo> GetRepositoryAsync(string repo);
|
||||
Task<GitRepo?> GetRepositoryAsync(string repo);
|
||||
|
||||
/// <summary>Get the latest release for a GitHub repository.</summary>
|
||||
/// <param name="repo">The repository key (like <c>Pathoschild/SMAPI</c>).</param>
|
||||
/// <param name="includePrerelease">Whether to return a prerelease version if it's latest.</param>
|
||||
/// <returns>Returns the release if found, else <c>null</c>.</returns>
|
||||
Task<GitRelease> GetLatestReleaseAsync(string repo, bool includePrerelease = false);
|
||||
Task<GitRelease?> GetLatestReleaseAsync(string repo, bool includePrerelease = false);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System.Threading.Tasks;
|
||||
using StardewModdingAPI.Toolkit.Framework.UpdateData;
|
||||
|
||||
|
@ -20,6 +18,6 @@ namespace StardewModdingAPI.Web.Framework.Clients
|
|||
*********/
|
||||
/// <summary>Get update check info about a mod.</summary>
|
||||
/// <param name="id">The mod ID.</param>
|
||||
Task<IModPage> GetModData(string id);
|
||||
Task<IModPage?> GetModData(string id);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
#nullable disable
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Threading.Tasks;
|
||||
using Pathoschild.Http.Client;
|
||||
using StardewModdingAPI.Toolkit.Framework.UpdateData;
|
||||
|
@ -43,9 +42,10 @@ namespace StardewModdingAPI.Web.Framework.Clients.ModDrop
|
|||
|
||||
/// <summary>Get update check info about a mod.</summary>
|
||||
/// <param name="id">The mod ID.</param>
|
||||
public async Task<IModPage> GetModData(string id)
|
||||
[SuppressMessage("ReSharper", "ConstantConditionalAccessQualifier", Justification = "The nullability is validated in this method.")]
|
||||
public async Task<IModPage?> GetModData(string id)
|
||||
{
|
||||
var page = new GenericModPage(this.SiteKey, id);
|
||||
IModPage page = new GenericModPage(this.SiteKey, id);
|
||||
|
||||
if (!long.TryParse(id, out long parsedId))
|
||||
return page.SetError(RemoteModStatus.DoesNotExist, $"The value '{id}' isn't a valid ModDrop mod ID, must be an integer ID.");
|
||||
|
@ -60,9 +60,11 @@ namespace StardewModdingAPI.Web.Framework.Clients.ModDrop
|
|||
Mods = true
|
||||
})
|
||||
.As<ModListModel>();
|
||||
ModModel mod = response.Mods[parsedId];
|
||||
if (mod.Mod?.Title == null || mod.Mod.ErrorCode.HasValue)
|
||||
return null;
|
||||
|
||||
if (!response.Mods.TryGetValue(parsedId, out ModModel? mod) || mod?.Mod is null)
|
||||
return page.SetError(RemoteModStatus.DoesNotExist, "Found no ModDrop page with this ID.");
|
||||
if (mod.Mod.ErrorCode is not null)
|
||||
return page.SetError(RemoteModStatus.InvalidData, $"ModDrop returned error code {mod.Mod.ErrorCode} for mod ID '{id}'.");
|
||||
|
||||
// get files
|
||||
var downloads = new List<IModDownload>();
|
||||
|
@ -77,7 +79,7 @@ namespace StardewModdingAPI.Web.Framework.Clients.ModDrop
|
|||
}
|
||||
|
||||
// return info
|
||||
string name = mod.Mod?.Title;
|
||||
string name = mod.Mod.Title;
|
||||
string url = string.Format(this.ModUrlFormat, id);
|
||||
return page.SetInfo(name: name, version: null, url: url, downloads: downloads);
|
||||
}
|
||||
|
@ -85,7 +87,7 @@ namespace StardewModdingAPI.Web.Framework.Clients.ModDrop
|
|||
/// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
|
||||
public void Dispose()
|
||||
{
|
||||
this.Client?.Dispose();
|
||||
this.Client.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace StardewModdingAPI.Web.Framework.Clients.ModDrop.ResponseModels
|
||||
|
@ -7,27 +5,53 @@ namespace StardewModdingAPI.Web.Framework.Clients.ModDrop.ResponseModels
|
|||
/// <summary>Metadata from the ModDrop API about a mod file.</summary>
|
||||
public class FileDataModel
|
||||
{
|
||||
/*********
|
||||
** Accessors
|
||||
*********/
|
||||
/// <summary>The file title.</summary>
|
||||
[JsonProperty("title")]
|
||||
public string Name { get; set; }
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>The file description.</summary>
|
||||
[JsonProperty("desc")]
|
||||
public string Description { get; set; }
|
||||
public string Description { get; }
|
||||
|
||||
/// <summary>The file version.</summary>
|
||||
public string Version { get; set; }
|
||||
public string Version { get; }
|
||||
|
||||
/// <summary>Whether the file is deleted.</summary>
|
||||
public bool IsDeleted { get; set; }
|
||||
public bool IsDeleted { get; }
|
||||
|
||||
/// <summary>Whether the file is hidden from users.</summary>
|
||||
public bool IsHidden { get; set; }
|
||||
public bool IsHidden { get; }
|
||||
|
||||
/// <summary>Whether this is the default file for the mod.</summary>
|
||||
public bool IsDefault { get; set; }
|
||||
public bool IsDefault { get; }
|
||||
|
||||
/// <summary>Whether this is an archived file.</summary>
|
||||
public bool IsOld { get; set; }
|
||||
public bool IsOld { get; }
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="name">The file title.</param>
|
||||
/// <param name="description">The file description.</param>
|
||||
/// <param name="version">The file version.</param>
|
||||
/// <param name="isDeleted">Whether the file is deleted.</param>
|
||||
/// <param name="isHidden">Whether the file is hidden from users.</param>
|
||||
/// <param name="isDefault">Whether this is the default file for the mod.</param>
|
||||
/// <param name="isOld">Whether this is an archived file.</param>
|
||||
public FileDataModel(string name, string description, string version, bool isDeleted, bool isHidden, bool isDefault, bool isOld)
|
||||
{
|
||||
this.Name = name;
|
||||
this.Description = description;
|
||||
this.Version = version;
|
||||
this.IsDeleted = isDeleted;
|
||||
this.IsHidden = isHidden;
|
||||
this.IsDefault = isDefault;
|
||||
this.IsOld = isOld;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,17 +1,33 @@
|
|||
#nullable disable
|
||||
|
||||
namespace StardewModdingAPI.Web.Framework.Clients.ModDrop.ResponseModels
|
||||
{
|
||||
/// <summary>Metadata about a mod from the ModDrop API.</summary>
|
||||
public class ModDataModel
|
||||
{
|
||||
/*********
|
||||
** Accessors
|
||||
*********/
|
||||
/// <summary>The mod's unique ID on ModDrop.</summary>
|
||||
public int ID { get; set; }
|
||||
|
||||
/// <summary>The mod name.</summary>
|
||||
public string Title { get; set; }
|
||||
|
||||
/// <summary>The error code, if any.</summary>
|
||||
public int? ErrorCode { get; set; }
|
||||
|
||||
/// <summary>The mod name.</summary>
|
||||
public string Title { get; set; }
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="id">The mod's unique ID on ModDrop.</param>
|
||||
/// <param name="title">The mod name.</param>
|
||||
/// <param name="errorCode">The error code, if any.</param>
|
||||
public ModDataModel(int id, string title, int? errorCode)
|
||||
{
|
||||
this.ID = id;
|
||||
this.Title = title;
|
||||
this.ErrorCode = errorCode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace StardewModdingAPI.Web.Framework.Clients.ModDrop.ResponseModels
|
||||
|
@ -7,7 +5,10 @@ namespace StardewModdingAPI.Web.Framework.Clients.ModDrop.ResponseModels
|
|||
/// <summary>A list of mods from the ModDrop API.</summary>
|
||||
public class ModListModel
|
||||
{
|
||||
/*********
|
||||
** Accessors
|
||||
*********/
|
||||
/// <summary>The mod data.</summary>
|
||||
public IDictionary<long, ModModel> Mods { get; set; }
|
||||
public IDictionary<long, ModModel> Mods { get; } = new Dictionary<long, ModModel>();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,28 @@
|
|||
#nullable disable
|
||||
|
||||
namespace StardewModdingAPI.Web.Framework.Clients.ModDrop.ResponseModels
|
||||
{
|
||||
/// <summary>An entry in a mod list from the ModDrop API.</summary>
|
||||
public class ModModel
|
||||
{
|
||||
/*********
|
||||
** Accessors
|
||||
*********/
|
||||
/// <summary>The available file downloads.</summary>
|
||||
public FileDataModel[] Files { get; set; }
|
||||
public FileDataModel[] Files { get; }
|
||||
|
||||
/// <summary>The mod metadata.</summary>
|
||||
public ModDataModel Mod { get; set; }
|
||||
public ModDataModel Mod { get; }
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="files">The available file downloads.</param>
|
||||
/// <param name="mod">The mod metadata.</param>
|
||||
public ModModel(FileDataModel[] files, ModDataModel mod)
|
||||
{
|
||||
this.Files = files;
|
||||
this.Mod = mod;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
@ -61,7 +59,7 @@ namespace StardewModdingAPI.Web.Framework.Clients.Nexus
|
|||
|
||||
/// <summary>Get update check info about a mod.</summary>
|
||||
/// <param name="id">The mod ID.</param>
|
||||
public async Task<IModPage> GetModData(string id)
|
||||
public async Task<IModPage?> GetModData(string id)
|
||||
{
|
||||
IModPage page = new GenericModPage(this.SiteKey, id);
|
||||
|
||||
|
@ -72,7 +70,7 @@ namespace StardewModdingAPI.Web.Framework.Clients.Nexus
|
|||
// adult content are hidden for anonymous users, so fall back to the API in that case.
|
||||
// Note that the API has very restrictive rate limits which means we can't just use it
|
||||
// for all cases.
|
||||
NexusMod mod = await this.GetModFromWebsiteAsync(parsedId);
|
||||
NexusMod? mod = await this.GetModFromWebsiteAsync(parsedId);
|
||||
if (mod?.Status == NexusModStatus.AdultContentForbidden)
|
||||
mod = await this.GetModFromApiAsync(parsedId);
|
||||
|
||||
|
@ -81,16 +79,16 @@ namespace StardewModdingAPI.Web.Framework.Clients.Nexus
|
|||
return page.SetError(RemoteModStatus.DoesNotExist, "Found no Nexus mod with this ID.");
|
||||
|
||||
// return info
|
||||
page.SetInfo(name: mod.Name, url: mod.Url, version: mod.Version, downloads: mod.Downloads);
|
||||
page.SetInfo(name: mod.Name ?? parsedId.ToString(), url: mod.Url ?? this.GetModUrl(parsedId), version: mod.Version, downloads: mod.Downloads);
|
||||
if (mod.Status != NexusModStatus.Ok)
|
||||
page.SetError(RemoteModStatus.TemporaryError, mod.Error);
|
||||
page.SetError(RemoteModStatus.TemporaryError, mod.Error!);
|
||||
return page;
|
||||
}
|
||||
|
||||
/// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
|
||||
public void Dispose()
|
||||
{
|
||||
this.WebClient?.Dispose();
|
||||
this.WebClient.Dispose();
|
||||
}
|
||||
|
||||
|
||||
|
@ -100,7 +98,7 @@ namespace StardewModdingAPI.Web.Framework.Clients.Nexus
|
|||
/// <summary>Get metadata about a mod by scraping the Nexus website.</summary>
|
||||
/// <param name="id">The Nexus mod ID.</param>
|
||||
/// <returns>Returns the mod info if found, else <c>null</c>.</returns>
|
||||
private async Task<NexusMod> GetModFromWebsiteAsync(uint id)
|
||||
private async Task<NexusMod?> GetModFromWebsiteAsync(uint id)
|
||||
{
|
||||
// fetch HTML
|
||||
string html;
|
||||
|
@ -116,35 +114,38 @@ namespace StardewModdingAPI.Web.Framework.Clients.Nexus
|
|||
}
|
||||
|
||||
// parse HTML
|
||||
var doc = new HtmlDocument();
|
||||
HtmlDocument doc = new();
|
||||
doc.LoadHtml(html);
|
||||
|
||||
// handle Nexus error message
|
||||
HtmlNode node = doc.DocumentNode.SelectSingleNode("//div[contains(@class, 'site-notice')][contains(@class, 'warning')]");
|
||||
HtmlNode? node = doc.DocumentNode.SelectSingleNode("//div[contains(@class, 'site-notice')][contains(@class, 'warning')]");
|
||||
if (node != null)
|
||||
{
|
||||
string[] errorParts = node.InnerText.Trim().Split(new[] { '\n' }, 2, System.StringSplitOptions.RemoveEmptyEntries);
|
||||
string errorCode = errorParts[0];
|
||||
string errorText = errorParts.Length > 1 ? errorParts[1] : null;
|
||||
string? errorText = errorParts.Length > 1 ? errorParts[1] : null;
|
||||
switch (errorCode.Trim().ToLower())
|
||||
{
|
||||
case "not found":
|
||||
return null;
|
||||
|
||||
default:
|
||||
return new NexusMod { Error = $"Nexus error: {errorCode} ({errorText}).", Status = this.GetWebStatus(errorCode) };
|
||||
return new NexusMod(
|
||||
status: this.GetWebStatus(errorCode),
|
||||
error: $"Nexus error: {errorCode} ({errorText})."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// extract mod info
|
||||
string url = this.GetModUrl(id);
|
||||
string name = doc.DocumentNode.SelectSingleNode("//div[@id='pagetitle']//h1")?.InnerText.Trim();
|
||||
string version = doc.DocumentNode.SelectSingleNode("//ul[contains(@class, 'stats')]//li[@class='stat-version']//div[@class='stat']")?.InnerText.Trim();
|
||||
SemanticVersion.TryParse(version, out ISemanticVersion parsedVersion);
|
||||
string? name = doc.DocumentNode.SelectSingleNode("//div[@id='pagetitle']//h1")?.InnerText.Trim();
|
||||
string? version = doc.DocumentNode.SelectSingleNode("//ul[contains(@class, 'stats')]//li[@class='stat-version']//div[@class='stat']")?.InnerText.Trim();
|
||||
SemanticVersion.TryParse(version, out ISemanticVersion? parsedVersion);
|
||||
|
||||
// extract files
|
||||
var downloads = new List<IModDownload>();
|
||||
foreach (var fileSection in doc.DocumentNode.SelectNodes("//div[contains(@class, 'files-tabs')]"))
|
||||
foreach (HtmlNode fileSection in doc.DocumentNode.SelectNodes("//div[contains(@class, 'files-tabs')]"))
|
||||
{
|
||||
string sectionName = fileSection.Descendants("h2").First().InnerText;
|
||||
if (sectionName != "Main files" && sectionName != "Optional files")
|
||||
|
@ -154,7 +155,7 @@ namespace StardewModdingAPI.Web.Framework.Clients.Nexus
|
|||
{
|
||||
string fileName = container.GetDataAttribute("name").Value;
|
||||
string fileVersion = container.GetDataAttribute("version").Value;
|
||||
string description = container.SelectSingleNode("following-sibling::*[1][self::dd]//div").InnerText?.Trim(); // get text of next <dd> tag; derived from https://stackoverflow.com/a/25535623/262123
|
||||
string? description = container.SelectSingleNode("following-sibling::*[1][self::dd]//div").InnerText?.Trim(); // get text of next <dd> tag; derived from https://stackoverflow.com/a/25535623/262123
|
||||
|
||||
downloads.Add(
|
||||
new GenericModDownload(fileName, description, fileVersion)
|
||||
|
@ -163,13 +164,12 @@ namespace StardewModdingAPI.Web.Framework.Clients.Nexus
|
|||
}
|
||||
|
||||
// yield info
|
||||
return new NexusMod
|
||||
{
|
||||
Name = name,
|
||||
Version = parsedVersion?.ToString() ?? version,
|
||||
Url = url,
|
||||
Downloads = downloads.ToArray()
|
||||
};
|
||||
return new NexusMod(
|
||||
name: name ?? id.ToString(),
|
||||
version: parsedVersion?.ToString() ?? version,
|
||||
url: url,
|
||||
downloads: downloads.ToArray()
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Get metadata about a mod from the Nexus API.</summary>
|
||||
|
@ -182,22 +182,21 @@ namespace StardewModdingAPI.Web.Framework.Clients.Nexus
|
|||
ModFileList files = await this.ApiClient.ModFiles.GetModFiles("stardewvalley", (int)id, FileCategory.Main, FileCategory.Optional);
|
||||
|
||||
// yield info
|
||||
return new NexusMod
|
||||
{
|
||||
Name = mod.Name,
|
||||
Version = SemanticVersion.TryParse(mod.Version, out ISemanticVersion version) ? version?.ToString() : mod.Version,
|
||||
Url = this.GetModUrl(id),
|
||||
Downloads = files.Files
|
||||
return new NexusMod(
|
||||
name: mod.Name,
|
||||
version: SemanticVersion.TryParse(mod.Version, out ISemanticVersion? version) ? version.ToString() : mod.Version,
|
||||
url: this.GetModUrl(id),
|
||||
downloads: files.Files
|
||||
.Select(file => (IModDownload)new GenericModDownload(file.Name, file.Description, file.FileVersion))
|
||||
.ToArray()
|
||||
};
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Get the full mod page URL for a given ID.</summary>
|
||||
/// <param name="id">The mod ID.</param>
|
||||
private string GetModUrl(uint id)
|
||||
{
|
||||
UriBuilder builder = new(this.WebClient.BaseClient.BaseAddress);
|
||||
UriBuilder builder = new(this.WebClient.BaseClient.BaseAddress!);
|
||||
builder.Path += string.Format(this.WebModUrlFormat, id);
|
||||
return builder.Uri.ToString();
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace StardewModdingAPI.Web.Framework.Clients.Nexus.ResponseModels
|
||||
|
@ -11,25 +10,53 @@ namespace StardewModdingAPI.Web.Framework.Clients.Nexus.ResponseModels
|
|||
** Accessors
|
||||
*********/
|
||||
/// <summary>The mod name.</summary>
|
||||
public string Name { get; set; }
|
||||
public string? Name { get; }
|
||||
|
||||
/// <summary>The mod's semantic version number.</summary>
|
||||
public string Version { get; set; }
|
||||
public string? Version { get; }
|
||||
|
||||
/// <summary>The mod's web URL.</summary>
|
||||
[JsonProperty("mod_page_uri")]
|
||||
public string Url { get; set; }
|
||||
public string? Url { get; }
|
||||
|
||||
/// <summary>The mod's publication status.</summary>
|
||||
[JsonIgnore]
|
||||
public NexusModStatus Status { get; set; } = NexusModStatus.Ok;
|
||||
public NexusModStatus Status { get; }
|
||||
|
||||
/// <summary>The files available to download.</summary>
|
||||
[JsonIgnore]
|
||||
public IModDownload[] Downloads { get; set; }
|
||||
public IModDownload[] Downloads { get; }
|
||||
|
||||
/// <summary>A custom user-friendly error which indicates why fetching the mod info failed (if applicable).</summary>
|
||||
[JsonIgnore]
|
||||
public string Error { get; set; }
|
||||
public string? Error { get; }
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="name">The mod name</param>
|
||||
/// <param name="version">The mod's semantic version number.</param>
|
||||
/// <param name="url">The mod's web URL.</param>
|
||||
/// <param name="downloads">The files available to download.</param>
|
||||
public NexusMod(string name, string? version, string url, IModDownload[] downloads)
|
||||
{
|
||||
this.Name = name;
|
||||
this.Version = version;
|
||||
this.Url = url;
|
||||
this.Status = NexusModStatus.Ok;
|
||||
this.Downloads = downloads;
|
||||
}
|
||||
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="status">The mod's publication status.</param>
|
||||
/// <param name="error">A custom user-friendly error which indicates why fetching the mod info failed (if applicable).</param>
|
||||
public NexusMod(NexusModStatus status, string error)
|
||||
{
|
||||
this.Status = status;
|
||||
this.Error = error;
|
||||
this.Downloads = Array.Empty<IModDownload>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
|
|
@ -1,17 +1,35 @@
|
|||
#nullable disable
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace StardewModdingAPI.Web.Framework.Clients.Pastebin
|
||||
{
|
||||
/// <summary>The response for a get-paste request.</summary>
|
||||
internal class PasteInfo
|
||||
{
|
||||
/*********
|
||||
** Accessors
|
||||
*********/
|
||||
/// <summary>Whether the log was successfully fetched.</summary>
|
||||
public bool Success { get; set; }
|
||||
[MemberNotNullWhen(true, nameof(PasteInfo.Content))]
|
||||
[MemberNotNullWhen(false, nameof(PasteInfo.Error))]
|
||||
public bool Success => this.Error == null || this.Content != null;
|
||||
|
||||
/// <summary>The fetched paste content (if <see cref="Success"/> is <c>true</c>).</summary>
|
||||
public string Content { get; set; }
|
||||
public string? Content { get; internal set; }
|
||||
|
||||
/// <summary>The error message if saving failed.</summary>
|
||||
public string Error { get; set; }
|
||||
/// <summary>The error message (if <see cref="Success"/> is <c>false</c>).</summary>
|
||||
public string? Error { get; }
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="content">The fetched paste content.</param>
|
||||
/// <param name="error">The error message, if it failed.</param>
|
||||
public PasteInfo(string? content, string? error)
|
||||
{
|
||||
this.Content = content;
|
||||
this.Error = error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -35,24 +33,24 @@ namespace StardewModdingAPI.Web.Framework.Clients.Pastebin
|
|||
try
|
||||
{
|
||||
// get from API
|
||||
string content = await this.Client
|
||||
string? content = await this.Client
|
||||
.GetAsync($"raw/{id}")
|
||||
.AsString();
|
||||
|
||||
// handle Pastebin errors
|
||||
if (string.IsNullOrWhiteSpace(content))
|
||||
return new PasteInfo { Error = "Received an empty response from Pastebin." };
|
||||
return new PasteInfo(null, "Received an empty response from Pastebin.");
|
||||
if (content.StartsWith("<!DOCTYPE"))
|
||||
return new PasteInfo { Error = $"Received a captcha challenge from Pastebin. Please visit https://pastebin.com/{id} in a new window to solve it." };
|
||||
return new PasteInfo { Success = true, Content = content };
|
||||
return new PasteInfo(null, $"Received a captcha challenge from Pastebin. Please visit https://pastebin.com/{id} in a new window to solve it.");
|
||||
return new PasteInfo(content, null);
|
||||
}
|
||||
catch (ApiException ex) when (ex.Status == HttpStatusCode.NotFound)
|
||||
{
|
||||
return new PasteInfo { Error = "There's no log with that ID." };
|
||||
return new PasteInfo(null, "There's no log with that ID.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new PasteInfo { Error = $"Pastebin error: {ex}" };
|
||||
return new PasteInfo(null, $"Pastebin error: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Text;
|
||||
|
@ -53,8 +52,12 @@ namespace StardewModdingAPI.Web.Framework.Compression
|
|||
/// <summary>Decompress a string.</summary>
|
||||
/// <param name="rawText">The compressed text.</param>
|
||||
/// <remarks>Derived from <a href="https://stackoverflow.com/a/17993002/262123"/>.</remarks>
|
||||
public string DecompressString(string rawText)
|
||||
[return: NotNullIfNotNull("rawText")]
|
||||
public string? DecompressString(string? rawText)
|
||||
{
|
||||
if (rawText is null)
|
||||
return rawText;
|
||||
|
||||
// get raw bytes
|
||||
byte[] zipBuffer;
|
||||
try
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#nullable disable
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace StardewModdingAPI.Web.Framework.Compression
|
||||
{
|
||||
|
@ -14,6 +14,7 @@ namespace StardewModdingAPI.Web.Framework.Compression
|
|||
|
||||
/// <summary>Decompress a string.</summary>
|
||||
/// <param name="rawText">The compressed text.</param>
|
||||
string DecompressString(string rawText);
|
||||
[return: NotNullIfNotNull("rawText")]
|
||||
string? DecompressString(string? rawText);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
namespace StardewModdingAPI.Web.Framework.ConfigModels
|
||||
{
|
||||
/// <summary>The config settings for the API clients.</summary>
|
||||
|
@ -12,17 +10,17 @@ namespace StardewModdingAPI.Web.Framework.ConfigModels
|
|||
** Generic
|
||||
****/
|
||||
/// <summary>The user agent for API clients, where {0} is the SMAPI version.</summary>
|
||||
public string UserAgent { get; set; }
|
||||
public string UserAgent { get; set; } = null!;
|
||||
|
||||
|
||||
/****
|
||||
** Azure
|
||||
****/
|
||||
/// <summary>The connection string for the Azure Blob storage account.</summary>
|
||||
public string AzureBlobConnectionString { get; set; }
|
||||
public string? AzureBlobConnectionString { get; set; }
|
||||
|
||||
/// <summary>The Azure Blob container in which to store temporary uploaded logs.</summary>
|
||||
public string AzureBlobTempContainer { get; set; }
|
||||
public string AzureBlobTempContainer { get; set; } = null!;
|
||||
|
||||
/// <summary>The number of days since the blob's last-modified date when it will be deleted.</summary>
|
||||
public int AzureBlobTempExpiryDays { get; set; }
|
||||
|
@ -32,65 +30,65 @@ namespace StardewModdingAPI.Web.Framework.ConfigModels
|
|||
** Chucklefish
|
||||
****/
|
||||
/// <summary>The base URL for the Chucklefish mod site.</summary>
|
||||
public string ChucklefishBaseUrl { get; set; }
|
||||
public string ChucklefishBaseUrl { get; set; } = null!;
|
||||
|
||||
/// <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; }
|
||||
public string ChucklefishModPageUrlFormat { get; set; } = null!;
|
||||
|
||||
|
||||
/****
|
||||
** CurseForge
|
||||
****/
|
||||
/// <summary>The base URL for the CurseForge API.</summary>
|
||||
public string CurseForgeBaseUrl { get; set; }
|
||||
public string CurseForgeBaseUrl { get; set; } = null!;
|
||||
|
||||
|
||||
/****
|
||||
** GitHub
|
||||
****/
|
||||
/// <summary>The base URL for the GitHub API.</summary>
|
||||
public string GitHubBaseUrl { get; set; }
|
||||
public string GitHubBaseUrl { get; set; } = null!;
|
||||
|
||||
/// <summary>The Accept header value expected by the GitHub API.</summary>
|
||||
public string GitHubAcceptHeader { get; set; }
|
||||
public string GitHubAcceptHeader { get; set; } = null!;
|
||||
|
||||
/// <summary>The username with which to authenticate to the GitHub API (if any).</summary>
|
||||
public string GitHubUsername { get; set; }
|
||||
public string? GitHubUsername { get; set; }
|
||||
|
||||
/// <summary>The password with which to authenticate to the GitHub API (if any).</summary>
|
||||
public string GitHubPassword { get; set; }
|
||||
public string? GitHubPassword { get; set; }
|
||||
|
||||
|
||||
/****
|
||||
** ModDrop
|
||||
****/
|
||||
/// <summary>The base URL for the ModDrop API.</summary>
|
||||
public string ModDropApiUrl { get; set; }
|
||||
public string ModDropApiUrl { get; set; } = null!;
|
||||
|
||||
/// <summary>The URL for a ModDrop mod page for the user, where {0} is the mod ID.</summary>
|
||||
public string ModDropModPageUrl { get; set; }
|
||||
public string ModDropModPageUrl { get; set; } = null!;
|
||||
|
||||
|
||||
/****
|
||||
** Nexus Mods
|
||||
****/
|
||||
/// <summary>The base URL for the Nexus Mods API.</summary>
|
||||
public string NexusBaseUrl { get; set; }
|
||||
public string NexusBaseUrl { get; set; } = null!;
|
||||
|
||||
/// <summary>The URL for a Nexus mod page for the user, excluding the <see cref="NexusBaseUrl"/>, where {0} is the mod ID.</summary>
|
||||
public string NexusModUrlFormat { get; set; }
|
||||
public string NexusModUrlFormat { get; set; } = null!;
|
||||
|
||||
/// <summary>The URL for a Nexus mod page to scrape for versions, excluding the <see cref="NexusBaseUrl"/>, where {0} is the mod ID.</summary>
|
||||
public string NexusModScrapeUrlFormat { get; set; }
|
||||
public string NexusModScrapeUrlFormat { get; set; } = null!;
|
||||
|
||||
/// <summary>The Nexus API authentication key.</summary>
|
||||
public string NexusApiKey { get; set; }
|
||||
public string? NexusApiKey { get; set; }
|
||||
|
||||
|
||||
/****
|
||||
** Pastebin
|
||||
****/
|
||||
/// <summary>The base URL for the Pastebin API.</summary>
|
||||
public string PastebinBaseUrl { get; set; }
|
||||
public string PastebinBaseUrl { get; set; } = null!;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,17 +1,15 @@
|
|||
#nullable disable
|
||||
|
||||
namespace StardewModdingAPI.Web.Framework.ConfigModels
|
||||
{
|
||||
/// <summary>Override update-check metadata for a mod.</summary>
|
||||
internal class ModOverrideConfig
|
||||
{
|
||||
/// <summary>The unique ID from the mod's manifest.</summary>
|
||||
public string ID { get; set; }
|
||||
public string ID { get; set; } = null!;
|
||||
|
||||
/// <summary>Whether to allow non-standard versions.</summary>
|
||||
public bool AllowNonStandardVersions { get; set; }
|
||||
|
||||
/// <summary>The mod page URL to use regardless of which site has the update, or <c>null</c> to use the site URL.</summary>
|
||||
public string SetUrl { get; set; }
|
||||
public string? SetUrl { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#nullable disable
|
||||
using System;
|
||||
|
||||
namespace StardewModdingAPI.Web.Framework.ConfigModels
|
||||
{
|
||||
|
@ -8,16 +8,16 @@ namespace StardewModdingAPI.Web.Framework.ConfigModels
|
|||
/*********
|
||||
** Accessors
|
||||
*********/
|
||||
/// <summary>The number of minutes successful update checks should be cached before refetching them.</summary>
|
||||
/// <summary>The number of minutes successful update checks should be cached before re-fetching them.</summary>
|
||||
public int SuccessCacheMinutes { get; set; }
|
||||
|
||||
/// <summary>The number of minutes failed update checks should be cached before refetching them.</summary>
|
||||
/// <summary>The number of minutes failed update checks should be cached before re-fetching them.</summary>
|
||||
public int ErrorCacheMinutes { get; set; }
|
||||
|
||||
/// <summary>Update-check metadata to override.</summary>
|
||||
public ModOverrideConfig[] ModOverrides { get; set; }
|
||||
public ModOverrideConfig[] ModOverrides { get; set; } = Array.Empty<ModOverrideConfig>();
|
||||
|
||||
/// <summary>The update-check config for SMAPI's own update checks.</summary>
|
||||
public SmapiInfoConfig SmapiInfo { get; set; }
|
||||
public SmapiInfoConfig SmapiInfo { get; set; } = null!;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
namespace StardewModdingAPI.Web.Framework.ConfigModels
|
||||
{
|
||||
/// <summary>The site config settings.</summary>
|
||||
|
@ -9,9 +7,9 @@ namespace StardewModdingAPI.Web.Framework.ConfigModels
|
|||
** Accessors
|
||||
*********/
|
||||
/// <summary>A message to show below the download button (e.g. for details on downloading a beta version), in Markdown format.</summary>
|
||||
public string OtherBlurb { get; set; }
|
||||
public string? OtherBlurb { get; set; }
|
||||
|
||||
/// <summary>A list of supports to credit on the main page, in Markdown format.</summary>
|
||||
public string SupporterList { get; set; }
|
||||
public string? SupporterList { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#nullable disable
|
||||
using System;
|
||||
|
||||
namespace StardewModdingAPI.Web.Framework.ConfigModels
|
||||
{
|
||||
|
@ -6,12 +6,12 @@ namespace StardewModdingAPI.Web.Framework.ConfigModels
|
|||
internal class SmapiInfoConfig
|
||||
{
|
||||
/// <summary>The mod ID used for SMAPI update checks.</summary>
|
||||
public string ID { get; set; }
|
||||
public string ID { get; set; } = null!;
|
||||
|
||||
/// <summary>The default update key used for SMAPI update checks.</summary>
|
||||
public string DefaultUpdateKey { get; set; }
|
||||
public string DefaultUpdateKey { get; set; } = null!;
|
||||
|
||||
/// <summary>The update keys to add for SMAPI update checks when the player has a beta version installed.</summary>
|
||||
public string[] AddBetaUpdateKeys { get; set; }
|
||||
public string[] AddBetaUpdateKeys { get; set; } = Array.Empty<string>();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.AspNetCore.Html;
|
||||
|
@ -28,7 +26,7 @@ namespace StardewModdingAPI.Web.Framework
|
|||
/// <param name="values">An object that contains route values.</param>
|
||||
/// <param name="absoluteUrl">Get an absolute URL instead of a server-relative path/</param>
|
||||
/// <returns>The generated URL.</returns>
|
||||
public static string PlainAction(this IUrlHelper helper, [AspMvcAction] string action, [AspMvcController] string controller, object values = null, bool absoluteUrl = false)
|
||||
public static string? PlainAction(this IUrlHelper helper, [AspMvcAction] string action, [AspMvcController] string controller, object? values = null, bool absoluteUrl = false)
|
||||
{
|
||||
// get route values
|
||||
RouteValueDictionary valuesDict = new(values);
|
||||
|
@ -39,7 +37,7 @@ namespace StardewModdingAPI.Web.Framework
|
|||
}
|
||||
|
||||
// get relative URL
|
||||
string url = helper.Action(action, controller, valuesDict);
|
||||
string? url = helper.Action(action, controller, valuesDict);
|
||||
if (url == null && action.EndsWith("Async"))
|
||||
url = helper.Action(action[..^"Async".Length], controller, valuesDict);
|
||||
|
||||
|
@ -59,7 +57,7 @@ namespace StardewModdingAPI.Web.Framework
|
|||
/// <param name="value">The value to serialize.</param>
|
||||
/// <returns>The serialized JSON.</returns>
|
||||
/// <remarks>This bypasses unnecessary validation (e.g. not allowing null values) in <see cref="IJsonHelper.Serialize"/>.</remarks>
|
||||
public static IHtmlContent ForJson(this RazorPageBase page, object value)
|
||||
public static IHtmlContent ForJson(this RazorPageBase page, object? value)
|
||||
{
|
||||
string json = JsonConvert.SerializeObject(value);
|
||||
return new HtmlString(json);
|
||||
|
|
|
@ -1,17 +1,18 @@
|
|||
#nullable disable
|
||||
|
||||
namespace StardewModdingAPI.Web.Framework
|
||||
{
|
||||
/// <summary>Generic metadata about a file download on a mod page.</summary>
|
||||
internal interface IModDownload
|
||||
{
|
||||
/*********
|
||||
** Accessors
|
||||
*********/
|
||||
/// <summary>The download's display name.</summary>
|
||||
string Name { get; }
|
||||
|
||||
/// <summary>The download's description.</summary>
|
||||
string Description { get; }
|
||||
string? Description { get; }
|
||||
|
||||
/// <summary>The download's file version.</summary>
|
||||
string Version { get; }
|
||||
string? Version { get; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
#nullable disable
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using StardewModdingAPI.Toolkit.Framework.UpdateData;
|
||||
|
||||
namespace StardewModdingAPI.Web.Framework
|
||||
|
@ -18,13 +17,13 @@ namespace StardewModdingAPI.Web.Framework
|
|||
string Id { get; }
|
||||
|
||||
/// <summary>The mod name.</summary>
|
||||
string Name { get; }
|
||||
string? Name { get; }
|
||||
|
||||
/// <summary>The mod's semantic version number.</summary>
|
||||
string Version { get; }
|
||||
string? Version { get; }
|
||||
|
||||
/// <summary>The mod's web URL.</summary>
|
||||
string Url { get; }
|
||||
string? Url { get; }
|
||||
|
||||
/// <summary>The mod downloads.</summary>
|
||||
IModDownload[] Downloads { get; }
|
||||
|
@ -33,7 +32,12 @@ namespace StardewModdingAPI.Web.Framework
|
|||
RemoteModStatus Status { get; }
|
||||
|
||||
/// <summary>A user-friendly error which indicates why fetching the mod info failed (if applicable).</summary>
|
||||
string Error { get; }
|
||||
string? Error { get; }
|
||||
|
||||
/// <summary>Whether the mod data is valid.</summary>
|
||||
[MemberNotNullWhen(true, nameof(IModPage.Name), nameof(IModPage.Url))]
|
||||
[MemberNotNullWhen(false, nameof(IModPage.Error))]
|
||||
bool IsValid { get; }
|
||||
|
||||
|
||||
/*********
|
||||
|
@ -44,7 +48,7 @@ namespace StardewModdingAPI.Web.Framework
|
|||
/// <param name="version">The mod's semantic version number.</param>
|
||||
/// <param name="url">The mod's web URL.</param>
|
||||
/// <param name="downloads">The mod downloads.</param>
|
||||
IModPage SetInfo(string name, string version, string url, IEnumerable<IModDownload> downloads);
|
||||
IModPage SetInfo(string name, string? version, string url, IEnumerable<IModDownload> downloads);
|
||||
|
||||
/// <summary>Set a mod fetch error.</summary>
|
||||
/// <param name="status">The mod availability status on the remote site.</param>
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using Hangfire.Dashboard;
|
||||
|
||||
namespace StardewModdingAPI.Web.Framework
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#nullable disable
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace StardewModdingAPI.Web.Framework
|
||||
{
|
||||
|
@ -9,22 +10,22 @@ namespace StardewModdingAPI.Web.Framework
|
|||
** Accessors
|
||||
*********/
|
||||
/// <summary>The mod name.</summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>The mod's latest version.</summary>
|
||||
public ISemanticVersion Version { get; set; }
|
||||
|
||||
/// <summary>The mod's latest optional or prerelease version, if newer than <see cref="Version"/>.</summary>
|
||||
public ISemanticVersion PreviewVersion { get; set; }
|
||||
public string? Name { get; private set; }
|
||||
|
||||
/// <summary>The mod's web URL.</summary>
|
||||
public string Url { get; set; }
|
||||
public string? Url { get; private set; }
|
||||
|
||||
/// <summary>The mod's latest version.</summary>
|
||||
public ISemanticVersion? Version { get; private set; }
|
||||
|
||||
/// <summary>The mod's latest optional or prerelease version, if newer than <see cref="Version"/>.</summary>
|
||||
public ISemanticVersion? PreviewVersion { get; private set; }
|
||||
|
||||
/// <summary>The mod availability status on the remote site.</summary>
|
||||
public RemoteModStatus Status { get; set; } = RemoteModStatus.Ok;
|
||||
public RemoteModStatus Status { get; private set; }
|
||||
|
||||
/// <summary>The error message indicating why the mod is invalid (if applicable).</summary>
|
||||
public string Error { get; set; }
|
||||
public string? Error { get; private set; }
|
||||
|
||||
|
||||
/*********
|
||||
|
@ -35,19 +36,24 @@ namespace StardewModdingAPI.Web.Framework
|
|||
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="name">The mod name.</param>
|
||||
/// <param name="url">The mod's web URL.</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>
|
||||
public ModInfoModel(string name, ISemanticVersion version, string url, ISemanticVersion previewVersion = null)
|
||||
/// <param name="status">The mod availability status on the remote site.</param>
|
||||
/// <param name="error">The error message indicating why the mod is invalid (if applicable).</param>
|
||||
[JsonConstructor]
|
||||
public ModInfoModel(string name, string url, ISemanticVersion? version, ISemanticVersion? previewVersion = null, RemoteModStatus status = RemoteModStatus.Ok, string? error = null)
|
||||
{
|
||||
this
|
||||
.SetBasicInfo(name, url)
|
||||
.SetVersions(version, previewVersion);
|
||||
.SetVersions(version!, previewVersion)
|
||||
.SetError(status, error!);
|
||||
}
|
||||
|
||||
/// <summary>Set the basic mod info.</summary>
|
||||
/// <param name="name">The mod name.</param>
|
||||
/// <param name="url">The mod's web URL.</param>
|
||||
[MemberNotNull(nameof(ModInfoModel.Name), nameof(ModInfoModel.Url))]
|
||||
public ModInfoModel SetBasicInfo(string name, string url)
|
||||
{
|
||||
this.Name = name;
|
||||
|
@ -59,7 +65,8 @@ namespace StardewModdingAPI.Web.Framework
|
|||
/// <summary>Set the mod version info.</summary>
|
||||
/// <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>
|
||||
public ModInfoModel SetVersions(ISemanticVersion version, ISemanticVersion previewVersion = null)
|
||||
[MemberNotNull(nameof(ModInfoModel.Version))]
|
||||
public ModInfoModel SetVersions(ISemanticVersion version, ISemanticVersion? previewVersion = null)
|
||||
{
|
||||
this.Version = version;
|
||||
this.PreviewVersion = previewVersion;
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -36,12 +35,15 @@ namespace StardewModdingAPI.Web.Framework
|
|||
/// <param name="updateKey">The namespaced update key.</param>
|
||||
public async Task<IModPage> GetModPageAsync(UpdateKey updateKey)
|
||||
{
|
||||
if (!updateKey.LooksValid)
|
||||
return new GenericModPage(updateKey.Site, updateKey.ID!).SetError(RemoteModStatus.DoesNotExist, $"Invalid update key '{updateKey}'.");
|
||||
|
||||
// get site
|
||||
if (!this.ModSites.TryGetValue(updateKey.Site, out IModSiteClient client))
|
||||
if (!this.ModSites.TryGetValue(updateKey.Site, out IModSiteClient? client))
|
||||
return new GenericModPage(updateKey.Site, updateKey.ID).SetError(RemoteModStatus.DoesNotExist, $"There's no mod site with key '{updateKey.Site}'. Expected one of [{string.Join(", ", this.ModSites.Keys)}].");
|
||||
|
||||
// fetch mod
|
||||
IModPage mod;
|
||||
IModPage? mod;
|
||||
try
|
||||
{
|
||||
mod = await client.GetModData(updateKey.ID);
|
||||
|
@ -60,39 +62,42 @@ namespace StardewModdingAPI.Web.Framework
|
|||
/// <param name="subkey">The optional update subkey to match in available files. (If no file names or descriptions contain the subkey, it'll be ignored.)</param>
|
||||
/// <param name="mapRemoteVersions">The changes to apply to remote versions for update checks.</param>
|
||||
/// <param name="allowNonStandardVersions">Whether to allow non-standard versions.</param>
|
||||
public ModInfoModel GetPageVersions(IModPage page, string subkey, bool allowNonStandardVersions, ChangeDescriptor mapRemoteVersions)
|
||||
public ModInfoModel GetPageVersions(IModPage page, string? subkey, bool allowNonStandardVersions, ChangeDescriptor? mapRemoteVersions)
|
||||
{
|
||||
// get base model
|
||||
ModInfoModel model = new ModInfoModel()
|
||||
.SetBasicInfo(page.Name, page.Url)
|
||||
.SetError(page.Status, page.Error);
|
||||
if (page.Status != RemoteModStatus.Ok)
|
||||
ModInfoModel model = new();
|
||||
if (page.IsValid)
|
||||
model.SetBasicInfo(page.Name, page.Url);
|
||||
else
|
||||
{
|
||||
model.SetError(page.Status, page.Error);
|
||||
return model;
|
||||
}
|
||||
|
||||
// fetch versions
|
||||
bool hasVersions = this.TryGetLatestVersions(page, subkey, allowNonStandardVersions, mapRemoteVersions, out ISemanticVersion mainVersion, out ISemanticVersion previewVersion);
|
||||
bool hasVersions = this.TryGetLatestVersions(page, subkey, allowNonStandardVersions, mapRemoteVersions, out ISemanticVersion? mainVersion, out ISemanticVersion? previewVersion);
|
||||
if (!hasVersions && subkey != null)
|
||||
hasVersions = this.TryGetLatestVersions(page, null, allowNonStandardVersions, mapRemoteVersions, out mainVersion, out previewVersion);
|
||||
if (!hasVersions)
|
||||
return model.SetError(RemoteModStatus.InvalidData, $"The {page.Site} mod with ID '{page.Id}' has no valid versions.");
|
||||
|
||||
// return info
|
||||
return model.SetVersions(mainVersion, previewVersion);
|
||||
return model.SetVersions(mainVersion!, previewVersion);
|
||||
}
|
||||
|
||||
/// <summary>Get a semantic local version for update checks.</summary>
|
||||
/// <param name="version">The version to parse.</param>
|
||||
/// <param name="map">Changes to apply to the raw version, if any.</param>
|
||||
/// <param name="allowNonStandard">Whether to allow non-standard versions.</param>
|
||||
public ISemanticVersion GetMappedVersion(string version, ChangeDescriptor map, bool allowNonStandard)
|
||||
public ISemanticVersion? GetMappedVersion(string? version, ChangeDescriptor? map, bool allowNonStandard)
|
||||
{
|
||||
// try mapped version
|
||||
string rawNewVersion = this.GetRawMappedVersion(version, map, allowNonStandard);
|
||||
if (SemanticVersion.TryParse(rawNewVersion, allowNonStandard, out ISemanticVersion parsedNew))
|
||||
string? rawNewVersion = this.GetRawMappedVersion(version, map);
|
||||
if (SemanticVersion.TryParse(rawNewVersion, allowNonStandard, out ISemanticVersion? parsedNew))
|
||||
return parsedNew;
|
||||
|
||||
// return original version
|
||||
return SemanticVersion.TryParse(version, allowNonStandard, out ISemanticVersion parsedOld)
|
||||
return SemanticVersion.TryParse(version, allowNonStandard, out ISemanticVersion? parsedOld)
|
||||
? parsedOld
|
||||
: null;
|
||||
}
|
||||
|
@ -108,31 +113,31 @@ namespace StardewModdingAPI.Web.Framework
|
|||
/// <param name="mapRemoteVersions">The changes to apply to remote versions for update checks.</param>
|
||||
/// <param name="main">The main mod version.</param>
|
||||
/// <param name="preview">The latest prerelease version, if newer than <paramref name="main"/>.</param>
|
||||
private bool TryGetLatestVersions(IModPage mod, string subkey, bool allowNonStandardVersions, ChangeDescriptor mapRemoteVersions, out ISemanticVersion main, out ISemanticVersion preview)
|
||||
private bool TryGetLatestVersions(IModPage? mod, string? subkey, bool allowNonStandardVersions, ChangeDescriptor? mapRemoteVersions, [NotNullWhen(true)] out ISemanticVersion? main, out ISemanticVersion? preview)
|
||||
{
|
||||
main = null;
|
||||
preview = null;
|
||||
|
||||
// parse all versions from the mod page
|
||||
IEnumerable<(string name, string description, ISemanticVersion version)> GetAllVersions()
|
||||
IEnumerable<(string? name, string? description, ISemanticVersion? version)> GetAllVersions()
|
||||
{
|
||||
if (mod != null)
|
||||
{
|
||||
ISemanticVersion ParseAndMapVersion(string raw)
|
||||
ISemanticVersion? ParseAndMapVersion(string? raw)
|
||||
{
|
||||
raw = this.NormalizeVersion(raw);
|
||||
return this.GetMappedVersion(raw, mapRemoteVersions, allowNonStandardVersions);
|
||||
}
|
||||
|
||||
// get mod version
|
||||
ISemanticVersion modVersion = ParseAndMapVersion(mod.Version);
|
||||
ISemanticVersion? modVersion = ParseAndMapVersion(mod.Version);
|
||||
if (modVersion != null)
|
||||
yield return (name: null, description: null, version: ParseAndMapVersion(mod.Version));
|
||||
|
||||
// get file versions
|
||||
foreach (IModDownload download in mod.Downloads)
|
||||
{
|
||||
ISemanticVersion cur = ParseAndMapVersion(download.Version);
|
||||
ISemanticVersion? cur = ParseAndMapVersion(download.Version);
|
||||
if (cur != null)
|
||||
yield return (download.Name, download.Description, cur);
|
||||
}
|
||||
|
@ -143,15 +148,15 @@ namespace StardewModdingAPI.Web.Framework
|
|||
.ToArray();
|
||||
|
||||
// get main + preview versions
|
||||
void TryGetVersions(out ISemanticVersion mainVersion, out ISemanticVersion previewVersion, Func<(string name, string description, ISemanticVersion version), bool> filter = null)
|
||||
void TryGetVersions([NotNullWhen(true)] out ISemanticVersion? mainVersion, out ISemanticVersion? previewVersion, Func<(string? name, string? description, ISemanticVersion? version), bool>? filter = null)
|
||||
{
|
||||
mainVersion = null;
|
||||
previewVersion = null;
|
||||
|
||||
// get latest main + preview version
|
||||
foreach (var entry in versions)
|
||||
foreach ((string? name, string? description, ISemanticVersion? version) entry in versions)
|
||||
{
|
||||
if (filter?.Invoke(entry) == false)
|
||||
if (entry.version is null || filter?.Invoke(entry) == false)
|
||||
continue;
|
||||
|
||||
if (entry.version.IsPrerelease())
|
||||
|
@ -160,7 +165,7 @@ namespace StardewModdingAPI.Web.Framework
|
|||
mainVersion ??= entry.version;
|
||||
|
||||
if (mainVersion != null)
|
||||
break; // any other values will be older
|
||||
break; // any others will be older since entries are sorted by version
|
||||
}
|
||||
|
||||
// normalize values
|
||||
|
@ -183,8 +188,7 @@ namespace StardewModdingAPI.Web.Framework
|
|||
/// <summary>Get a semantic local version for update checks.</summary>
|
||||
/// <param name="version">The version to map.</param>
|
||||
/// <param name="map">Changes to apply to the raw version, if any.</param>
|
||||
/// <param name="allowNonStandard">Whether to allow non-standard versions.</param>
|
||||
private string GetRawMappedVersion(string version, ChangeDescriptor map, bool allowNonStandard)
|
||||
private string? GetRawMappedVersion(string? version, ChangeDescriptor? map)
|
||||
{
|
||||
if (version == null || map?.HasChanges != true)
|
||||
return version;
|
||||
|
@ -197,7 +201,7 @@ namespace StardewModdingAPI.Web.Framework
|
|||
|
||||
/// <summary>Normalize a version string.</summary>
|
||||
/// <param name="version">The version to normalize.</param>
|
||||
private string NormalizeVersion(string version)
|
||||
private string? NormalizeVersion(string? version)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(version))
|
||||
return null;
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Net;
|
||||
using Microsoft.AspNetCore.Rewrite;
|
||||
|
@ -13,7 +11,7 @@ namespace StardewModdingAPI.Web.Framework.RedirectRules
|
|||
** Fields
|
||||
*********/
|
||||
/// <summary>Maps a lowercase hostname to the resulting redirect URL.</summary>
|
||||
private readonly Func<string, string> Map;
|
||||
private readonly Func<string, string?> Map;
|
||||
|
||||
|
||||
/*********
|
||||
|
@ -22,7 +20,7 @@ namespace StardewModdingAPI.Web.Framework.RedirectRules
|
|||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="statusCode">The status code to use for redirects.</param>
|
||||
/// <param name="map">Hostnames mapped to the resulting redirect URL.</param>
|
||||
public RedirectHostsToUrlsRule(HttpStatusCode statusCode, Func<string, string> map)
|
||||
public RedirectHostsToUrlsRule(HttpStatusCode statusCode, Func<string, string?> map)
|
||||
{
|
||||
this.StatusCode = statusCode;
|
||||
this.Map = map ?? throw new ArgumentNullException(nameof(map));
|
||||
|
@ -35,10 +33,10 @@ namespace StardewModdingAPI.Web.Framework.RedirectRules
|
|||
/// <summary>Get the new redirect URL.</summary>
|
||||
/// <param name="context">The rewrite context.</param>
|
||||
/// <returns>Returns the redirect URL, or <c>null</c> if the redirect doesn't apply.</returns>
|
||||
protected override string GetNewUrl(RewriteContext context)
|
||||
protected override string? GetNewUrl(RewriteContext context)
|
||||
{
|
||||
// get requested host
|
||||
string host = context.HttpContext.Request.Host.Host;
|
||||
string? host = context.HttpContext.Request.Host.Host;
|
||||
|
||||
// get new host
|
||||
host = this.Map(host);
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Net;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
@ -24,7 +22,7 @@ namespace StardewModdingAPI.Web.Framework.RedirectRules
|
|||
/// <param name="context">The rewrite context.</param>
|
||||
public void ApplyRule(RewriteContext context)
|
||||
{
|
||||
string newUrl = this.GetNewUrl(context);
|
||||
string? newUrl = this.GetNewUrl(context);
|
||||
if (newUrl == null)
|
||||
return;
|
||||
|
||||
|
@ -41,7 +39,7 @@ namespace StardewModdingAPI.Web.Framework.RedirectRules
|
|||
/// <summary>Get the new redirect URL.</summary>
|
||||
/// <param name="context">The rewrite context.</param>
|
||||
/// <returns>Returns the redirect URL, or <c>null</c> if the redirect doesn't apply.</returns>
|
||||
protected abstract string GetNewUrl(RewriteContext context);
|
||||
protected abstract string? GetNewUrl(RewriteContext context);
|
||||
|
||||
/// <summary>Get the full request URL.</summary>
|
||||
/// <param name="request">The request.</param>
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
|
@ -39,9 +37,9 @@ namespace StardewModdingAPI.Web.Framework.RedirectRules
|
|||
/// <summary>Get the new redirect URL.</summary>
|
||||
/// <param name="context">The rewrite context.</param>
|
||||
/// <returns>Returns the redirect URL, or <c>null</c> if the redirect doesn't apply.</returns>
|
||||
protected override string GetNewUrl(RewriteContext context)
|
||||
protected override string? GetNewUrl(RewriteContext context)
|
||||
{
|
||||
string path = context.HttpContext.Request.Path.Value;
|
||||
string? path = context.HttpContext.Request.Path.Value;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(path))
|
||||
{
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Net;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
@ -22,7 +20,7 @@ namespace StardewModdingAPI.Web.Framework.RedirectRules
|
|||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="except">Matches requests which should be ignored.</param>
|
||||
public RedirectToHttpsRule(Func<HttpRequest, bool> except = null)
|
||||
public RedirectToHttpsRule(Func<HttpRequest, bool>? except = null)
|
||||
{
|
||||
this.Except = except ?? (_ => false);
|
||||
this.StatusCode = HttpStatusCode.RedirectKeepVerb;
|
||||
|
@ -35,7 +33,7 @@ namespace StardewModdingAPI.Web.Framework.RedirectRules
|
|||
/// <summary>Get the new redirect URL.</summary>
|
||||
/// <param name="context">The rewrite context.</param>
|
||||
/// <returns>Returns the redirect URL, or <c>null</c> if the redirect doesn't apply.</returns>
|
||||
protected override string GetNewUrl(RewriteContext context)
|
||||
protected override string? GetNewUrl(RewriteContext context)
|
||||
{
|
||||
HttpRequest request = context.HttpContext.Request;
|
||||
if (request.IsHttps || this.Except(request))
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace StardewModdingAPI.Web.Framework.Storage
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
@ -65,11 +63,11 @@ namespace StardewModdingAPI.Web.Framework.Storage
|
|||
BlobClient blob = this.GetAzureBlobClient(id);
|
||||
await blob.UploadAsync(stream);
|
||||
|
||||
return new UploadResult(true, id, null);
|
||||
return new UploadResult(id, null);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new UploadResult(false, null, ex.Message);
|
||||
return new UploadResult(null, ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -77,10 +75,10 @@ namespace StardewModdingAPI.Web.Framework.Storage
|
|||
else
|
||||
{
|
||||
string path = this.GetDevFilePath(id);
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(path)!);
|
||||
|
||||
File.WriteAllText(path, content);
|
||||
return new UploadResult(true, id, null);
|
||||
return new UploadResult(id, null);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -110,21 +108,15 @@ namespace StardewModdingAPI.Web.Framework.Storage
|
|||
string content = this.GzipHelper.DecompressString(reader.ReadToEnd());
|
||||
|
||||
// build model
|
||||
return new StoredFileInfo
|
||||
{
|
||||
Success = true,
|
||||
Content = content,
|
||||
Expiry = expiry.UtcDateTime
|
||||
};
|
||||
return new StoredFileInfo(content, expiry);
|
||||
}
|
||||
catch (RequestFailedException ex)
|
||||
{
|
||||
return new StoredFileInfo
|
||||
{
|
||||
Error = ex.ErrorCode == "BlobNotFound"
|
||||
return new StoredFileInfo(
|
||||
error: ex.ErrorCode == "BlobNotFound"
|
||||
? "There's no file with that ID."
|
||||
: $"Could not fetch that file from storage ({ex.ErrorCode}: {ex.Message})."
|
||||
};
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -137,10 +129,7 @@ namespace StardewModdingAPI.Web.Framework.Storage
|
|||
file.Delete();
|
||||
if (!file.Exists)
|
||||
{
|
||||
return new StoredFileInfo
|
||||
{
|
||||
Error = "There's no file with that ID."
|
||||
};
|
||||
return new StoredFileInfo(error: "There's no file with that ID.");
|
||||
}
|
||||
|
||||
// renew
|
||||
|
@ -151,13 +140,11 @@ namespace StardewModdingAPI.Web.Framework.Storage
|
|||
}
|
||||
|
||||
// build model
|
||||
return new StoredFileInfo
|
||||
{
|
||||
Success = true,
|
||||
Content = File.ReadAllText(file.FullName),
|
||||
Expiry = DateTime.UtcNow.AddDays(this.ExpiryDays),
|
||||
Warning = "This file was saved temporarily to the local computer. This should only happen in a local development environment."
|
||||
};
|
||||
return new StoredFileInfo(
|
||||
content: File.ReadAllText(file.FullName),
|
||||
expiry: DateTime.UtcNow.AddDays(this.ExpiryDays),
|
||||
warning: "This file was saved temporarily to the local computer. This should only happen in a local development environment."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -166,12 +153,7 @@ namespace StardewModdingAPI.Web.Framework.Storage
|
|||
{
|
||||
PasteInfo response = await this.Pastebin.GetAsync(id);
|
||||
response.Content = this.GzipHelper.DecompressString(response.Content);
|
||||
return new StoredFileInfo
|
||||
{
|
||||
Success = response.Success,
|
||||
Content = response.Content,
|
||||
Error = response.Error
|
||||
};
|
||||
return new StoredFileInfo(response.Content, null, error: response.Error);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -179,8 +161,8 @@ namespace StardewModdingAPI.Web.Framework.Storage
|
|||
/// <param name="id">The file ID.</param>
|
||||
private BlobClient GetAzureBlobClient(string id)
|
||||
{
|
||||
var azure = new BlobServiceClient(this.ClientsConfig.AzureBlobConnectionString);
|
||||
var container = azure.GetBlobContainerClient(this.ClientsConfig.AzureBlobTempContainer);
|
||||
BlobServiceClient azure = new(this.ClientsConfig.AzureBlobConnectionString);
|
||||
BlobContainerClient container = azure.GetBlobContainerClient(this.ClientsConfig.AzureBlobTempContainer);
|
||||
return container.GetBlobClient($"uploads/{id}");
|
||||
}
|
||||
|
||||
|
|
|
@ -1,25 +1,52 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace StardewModdingAPI.Web.Framework.Storage
|
||||
{
|
||||
/// <summary>The response for a get-file request.</summary>
|
||||
internal class StoredFileInfo
|
||||
{
|
||||
/*********
|
||||
** Accessors
|
||||
*********/
|
||||
/// <summary>Whether the file was successfully fetched.</summary>
|
||||
public bool Success { get; set; }
|
||||
[MemberNotNullWhen(true, nameof(StoredFileInfo.Content))]
|
||||
public bool Success => this.Content != null && this.Error == null;
|
||||
|
||||
/// <summary>The fetched file content (if <see cref="Success"/> is <c>true</c>).</summary>
|
||||
public string Content { get; set; }
|
||||
public string? Content { get; }
|
||||
|
||||
/// <summary>When the file will no longer be available.</summary>
|
||||
public DateTime? Expiry { get; set; }
|
||||
public DateTimeOffset? Expiry { get; }
|
||||
|
||||
/// <summary>The error message if saving succeeded, but a non-blocking issue was encountered.</summary>
|
||||
public string Warning { get; set; }
|
||||
public string? Warning { get; }
|
||||
|
||||
/// <summary>The error message if saving failed.</summary>
|
||||
public string Error { get; set; }
|
||||
public string? Error { get; }
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="content">The fetched file content (if <see cref="Success"/> is <c>true</c>).</param>
|
||||
/// <param name="expiry">When the file will no longer be available.</param>
|
||||
/// <param name="warning">The error message if saving succeeded, but a non-blocking issue was encountered.</param>
|
||||
/// <param name="error">The error message if saving failed.</param>
|
||||
public StoredFileInfo(string? content, DateTimeOffset? expiry, string? warning = null, string? error = null)
|
||||
{
|
||||
this.Content = content;
|
||||
this.Expiry = expiry;
|
||||
this.Warning = warning;
|
||||
this.Error = error;
|
||||
}
|
||||
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="error">The error message if saving failed.</param>
|
||||
public StoredFileInfo(string error)
|
||||
{
|
||||
this.Error = error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#nullable disable
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace StardewModdingAPI.Web.Framework.Storage
|
||||
{
|
||||
|
@ -9,25 +9,25 @@ namespace StardewModdingAPI.Web.Framework.Storage
|
|||
** Accessors
|
||||
*********/
|
||||
/// <summary>Whether the file upload succeeded.</summary>
|
||||
public bool Succeeded { get; }
|
||||
[MemberNotNullWhen(true, nameof(UploadResult.ID))]
|
||||
[MemberNotNullWhen(false, nameof(UploadResult.UploadError))]
|
||||
public bool Succeeded => this.ID != null && this.UploadError == null;
|
||||
|
||||
/// <summary>The file ID, if applicable.</summary>
|
||||
public string ID { get; }
|
||||
public string? ID { get; }
|
||||
|
||||
/// <summary>The upload error, if any.</summary>
|
||||
public string UploadError { get; }
|
||||
public string? UploadError { get; }
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="succeeded">Whether the file upload succeeded.</param>
|
||||
/// <param name="id">The file ID, if applicable.</param>
|
||||
/// <param name="uploadError">The upload error, if any.</param>
|
||||
public UploadResult(bool succeeded, string id, string uploadError)
|
||||
public UploadResult(string? id, string? uploadError)
|
||||
{
|
||||
this.Succeeded = succeeded;
|
||||
this.ID = id;
|
||||
this.UploadError = uploadError;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
|
@ -20,7 +18,7 @@ namespace StardewModdingAPI.Web.Framework
|
|||
/// <param name="values">A dictionary that contains the parameters for the URL.</param>
|
||||
/// <param name="routeDirection">An object that indicates whether the constraint check is being performed when an incoming request is being handled or when a URL is being generated.</param>
|
||||
/// <returns><c>true</c> if the URL parameter contains a valid value; otherwise, <c>false</c>.</returns>
|
||||
public bool Match(HttpContext httpContext, IRouter route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection)
|
||||
public bool Match(HttpContext? httpContext, IRouter? route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection)
|
||||
{
|
||||
if (routeKey == null)
|
||||
throw new ArgumentNullException(nameof(routeKey));
|
||||
|
@ -28,7 +26,7 @@ namespace StardewModdingAPI.Web.Framework
|
|||
throw new ArgumentNullException(nameof(values));
|
||||
|
||||
return
|
||||
values.TryGetValue(routeKey, out object routeValue)
|
||||
values.TryGetValue(routeKey, out object? routeValue)
|
||||
&& routeValue is string routeStr
|
||||
&& SemanticVersion.TryParse(routeStr, allowNonStandard: true, out _);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using Hangfire;
|
||||
|
@ -102,7 +100,7 @@ namespace StardewModdingAPI.Web
|
|||
// init API clients
|
||||
{
|
||||
ApiClientsConfig api = this.Configuration.GetSection("ApiClients").Get<ApiClientsConfig>();
|
||||
string version = this.GetType().Assembly.GetName().Version.ToString(3);
|
||||
string version = this.GetType().Assembly.GetName().Version!.ToString(3);
|
||||
string userAgent = string.Format(api.UserAgent, version);
|
||||
|
||||
services.AddSingleton<IChucklefishClient>(new ChucklefishClient(
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
namespace StardewModdingAPI.Web.ViewModels
|
||||
{
|
||||
/// <summary>The view model for the index page.</summary>
|
||||
|
@ -9,26 +7,23 @@ namespace StardewModdingAPI.Web.ViewModels
|
|||
** Accessors
|
||||
*********/
|
||||
/// <summary>The latest stable SMAPI version.</summary>
|
||||
public IndexVersionModel StableVersion { get; set; }
|
||||
public IndexVersionModel StableVersion { get; }
|
||||
|
||||
/// <summary>A message to show below the download button (e.g. for details on downloading a beta version), in Markdown format.</summary>
|
||||
public string OtherBlurb { get; set; }
|
||||
public string? OtherBlurb { get; }
|
||||
|
||||
/// <summary>A list of supports to credit on the main page, in Markdown format.</summary>
|
||||
public string SupporterList { get; set; }
|
||||
public string? SupporterList { get; }
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
public IndexModel() { }
|
||||
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="stableVersion">The latest stable SMAPI version.</param>
|
||||
/// <param name="otherBlurb">A message to show below the download button (e.g. for details on downloading a beta version), in Markdown format.</param>
|
||||
/// <param name="supporterList">A list of supports to credit on the main page, in Markdown format.</param>
|
||||
internal IndexModel(IndexVersionModel stableVersion, string otherBlurb, string supporterList)
|
||||
internal IndexModel(IndexVersionModel stableVersion, string? otherBlurb, string? supporterList)
|
||||
{
|
||||
this.StableVersion = stableVersion;
|
||||
this.OtherBlurb = otherBlurb;
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
namespace StardewModdingAPI.Web.ViewModels
|
||||
{
|
||||
/// <summary>The fields for a SMAPI version.</summary>
|
||||
|
@ -9,30 +7,27 @@ namespace StardewModdingAPI.Web.ViewModels
|
|||
** Accessors
|
||||
*********/
|
||||
/// <summary>The release version.</summary>
|
||||
public string Version { get; set; }
|
||||
public string Version { get; }
|
||||
|
||||
/// <summary>The Markdown description for the release.</summary>
|
||||
public string Description { get; set; }
|
||||
public string Description { get; }
|
||||
|
||||
/// <summary>The main download URL.</summary>
|
||||
public string DownloadUrl { get; set; }
|
||||
public string DownloadUrl { get; }
|
||||
|
||||
/// <summary>The for-developers download URL (not applicable for prerelease versions).</summary>
|
||||
public string DevDownloadUrl { get; set; }
|
||||
public string? DevDownloadUrl { get; }
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
public IndexVersionModel() { }
|
||||
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="version">The release number.</param>
|
||||
/// <param name="description">The Markdown description for the release.</param>
|
||||
/// <param name="downloadUrl">The main download URL.</param>
|
||||
/// <param name="devDownloadUrl">The for-developers download URL (not applicable for prerelease versions).</param>
|
||||
internal IndexVersionModel(string version, string description, string downloadUrl, string devDownloadUrl)
|
||||
internal IndexVersionModel(string version, string description, string downloadUrl, string? devDownloadUrl)
|
||||
{
|
||||
this.Version = version;
|
||||
this.Description = description;
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using Newtonsoft.Json.Schema;
|
||||
|
||||
namespace StardewModdingAPI.Web.ViewModels.JsonValidator
|
||||
|
@ -11,30 +9,27 @@ namespace StardewModdingAPI.Web.ViewModels.JsonValidator
|
|||
** Accessors
|
||||
*********/
|
||||
/// <summary>The line number on which the error occurred.</summary>
|
||||
public int Line { get; set; }
|
||||
public int Line { get; }
|
||||
|
||||
/// <summary>The field path in the JSON file where the error occurred.</summary>
|
||||
public string Path { get; set; }
|
||||
public string? Path { get; }
|
||||
|
||||
/// <summary>A human-readable description of the error.</summary>
|
||||
public string Message { get; set; }
|
||||
public string Message { get; }
|
||||
|
||||
/// <summary>The schema error type.</summary>
|
||||
public ErrorType SchemaErrorType { get; set; }
|
||||
public ErrorType SchemaErrorType { get; }
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
public JsonValidatorErrorModel() { }
|
||||
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="line">The line number on which the error occurred.</param>
|
||||
/// <param name="path">The field path in the JSON file where the error occurred.</param>
|
||||
/// <param name="message">A human-readable description of the error.</param>
|
||||
/// <param name="schemaErrorType">The schema error type.</param>
|
||||
public JsonValidatorErrorModel(int line, string path, string message, ErrorType schemaErrorType)
|
||||
public JsonValidatorErrorModel(int line, string? path, string message, ErrorType schemaErrorType)
|
||||
{
|
||||
this.Line = line;
|
||||
this.Path = path;
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
@ -13,51 +11,48 @@ namespace StardewModdingAPI.Web.ViewModels.JsonValidator
|
|||
** Accessors
|
||||
*********/
|
||||
/// <summary>Whether to show the edit view.</summary>
|
||||
public bool IsEditView { get; set; }
|
||||
public bool IsEditView { get; }
|
||||
|
||||
/// <summary>The paste ID.</summary>
|
||||
public string PasteID { get; set; }
|
||||
public string? PasteID { get; }
|
||||
|
||||
/// <summary>The schema name with which the JSON was validated.</summary>
|
||||
public string SchemaName { get; set; }
|
||||
public string? SchemaName { get; }
|
||||
|
||||
/// <summary>The supported JSON schemas (names indexed by ID).</summary>
|
||||
public readonly IDictionary<string, string> SchemaFormats;
|
||||
public IDictionary<string, string> SchemaFormats { get; }
|
||||
|
||||
/// <summary>The validated content.</summary>
|
||||
public string Content { get; set; }
|
||||
public string? Content { get; set; }
|
||||
|
||||
/// <summary>The schema validation errors, if any.</summary>
|
||||
public JsonValidatorErrorModel[] Errors { get; set; } = Array.Empty<JsonValidatorErrorModel>();
|
||||
|
||||
/// <summary>A non-blocking warning while uploading the file.</summary>
|
||||
public string UploadWarning { get; set; }
|
||||
public string? UploadWarning { get; set; }
|
||||
|
||||
/// <summary>When the uploaded file will no longer be available.</summary>
|
||||
public DateTime? Expiry { get; set; }
|
||||
public DateTimeOffset? Expiry { get; set; }
|
||||
|
||||
/// <summary>An error which occurred while uploading the JSON.</summary>
|
||||
public string UploadError { get; set; }
|
||||
public string? UploadError { get; set; }
|
||||
|
||||
/// <summary>An error which occurred while parsing the JSON.</summary>
|
||||
public string ParseError { get; set; }
|
||||
public string? ParseError { get; set; }
|
||||
|
||||
/// <summary>A web URL to the user-facing format documentation.</summary>
|
||||
public string FormatUrl { get; set; }
|
||||
public string? FormatUrl { get; set; }
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
public JsonValidatorModel() { }
|
||||
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="pasteID">The stored file ID.</param>
|
||||
/// <param name="schemaName">The schema name with which the JSON was validated.</param>
|
||||
/// <param name="schemaFormats">The supported JSON schemas (names indexed by ID).</param>
|
||||
/// <param name="isEditView">Whether to show the edit view.</param>
|
||||
public JsonValidatorModel(string pasteID, string schemaName, IDictionary<string, string> schemaFormats, bool isEditView)
|
||||
public JsonValidatorModel(string? pasteID, string? schemaName, IDictionary<string, string> schemaFormats, bool isEditView)
|
||||
{
|
||||
this.PasteID = pasteID;
|
||||
this.SchemaName = schemaName;
|
||||
|
@ -69,7 +64,7 @@ namespace StardewModdingAPI.Web.ViewModels.JsonValidator
|
|||
/// <param name="content">The validated content.</param>
|
||||
/// <param name="expiry">When the uploaded file will no longer be available.</param>
|
||||
/// <param name="uploadWarning">A non-blocking warning while uploading the log.</param>
|
||||
public JsonValidatorModel SetContent(string content, DateTime? expiry, string uploadWarning = null)
|
||||
public JsonValidatorModel SetContent(string content, DateTimeOffset? expiry, string? uploadWarning = null)
|
||||
{
|
||||
this.Content = content;
|
||||
this.Expiry = expiry;
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
namespace StardewModdingAPI.Web.ViewModels.JsonValidator
|
||||
{
|
||||
/// <summary>The view model for a JSON validation request.</summary>
|
||||
|
@ -9,9 +7,22 @@ namespace StardewModdingAPI.Web.ViewModels.JsonValidator
|
|||
** Accessors
|
||||
*********/
|
||||
/// <summary>The schema name with which to validate the JSON.</summary>
|
||||
public string SchemaName { get; set; }
|
||||
public string SchemaName { get; }
|
||||
|
||||
/// <summary>The raw content to validate.</summary>
|
||||
public string Content { get; set; }
|
||||
public string Content { get; }
|
||||
|
||||
|
||||
/*********
|
||||
** Accessors
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="schemaName">The schema name with which to validate the JSON.</param>
|
||||
/// <param name="content">The raw content to validate.</param>
|
||||
public JsonValidatorRequestModel(string schemaName, string content)
|
||||
{
|
||||
this.SchemaName = schemaName;
|
||||
this.Content = content;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,7 +44,7 @@ namespace StardewModdingAPI.Web.ViewModels
|
|||
public string? ParseError => this.ParsedLog?.Error;
|
||||
|
||||
/// <summary>When the uploaded file will no longer be available.</summary>
|
||||
public DateTime? Expiry { get; set; }
|
||||
public DateTimeOffset? Expiry { get; set; }
|
||||
|
||||
/// <summary>Whether parsed log data is available.</summary>
|
||||
[MemberNotNullWhen(true, nameof(LogParserModel.PasteID), nameof(LogParserModel.ParsedLog))]
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#nullable disable
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using StardewModdingAPI.Toolkit.Framework.Clients.Wiki;
|
||||
|
||||
namespace StardewModdingAPI.Web.ViewModels
|
||||
|
@ -11,21 +10,35 @@ namespace StardewModdingAPI.Web.ViewModels
|
|||
** Accessors
|
||||
*********/
|
||||
/// <summary>The compatibility status, as a string like <c>"Broken"</c>.</summary>
|
||||
public string Status { get; set; }
|
||||
public string Status { get; }
|
||||
|
||||
/// <summary>The human-readable summary, as an HTML block.</summary>
|
||||
public string Summary { get; set; }
|
||||
public string? Summary { get; }
|
||||
|
||||
/// <summary>The game or SMAPI version which broke this mod (if applicable).</summary>
|
||||
public string BrokeIn { get; set; }
|
||||
public string? BrokeIn { get; }
|
||||
|
||||
/// <summary>A link to the unofficial version which fixes compatibility, if any.</summary>
|
||||
public ModLinkModel UnofficialVersion { get; set; }
|
||||
public ModLinkModel? UnofficialVersion { get; }
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="status">The compatibility status, as a string like <c>"Broken"</c>.</param>
|
||||
/// <param name="summary">The human-readable summary, as an HTML block.</param>
|
||||
/// <param name="brokeIn">The game or SMAPI version which broke this mod (if applicable).</param>
|
||||
/// <param name="unofficialVersion">A link to the unofficial version which fixes compatibility, if any.</param>
|
||||
[JsonConstructor]
|
||||
public ModCompatibilityModel(string status, string? summary, string? brokeIn, ModLinkModel? unofficialVersion)
|
||||
{
|
||||
this.Status = status;
|
||||
this.Summary = summary;
|
||||
this.BrokeIn = brokeIn;
|
||||
this.UnofficialVersion = unofficialVersion;
|
||||
}
|
||||
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="info">The mod metadata.</param>
|
||||
public ModCompatibilityModel(WikiCompatibilityInfo info)
|
||||
|
@ -36,7 +49,7 @@ namespace StardewModdingAPI.Web.ViewModels
|
|||
this.Summary = info.Summary;
|
||||
this.BrokeIn = info.BrokeIn;
|
||||
if (info.UnofficialVersion != null)
|
||||
this.UnofficialVersion = new ModLinkModel(info.UnofficialUrl, info.UnofficialVersion.ToString());
|
||||
this.UnofficialVersion = new ModLinkModel(info.UnofficialUrl!, info.UnofficialVersion.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
namespace StardewModdingAPI.Web.ViewModels
|
||||
{
|
||||
/// <summary>Metadata about a link.</summary>
|
||||
|
@ -9,10 +7,10 @@ namespace StardewModdingAPI.Web.ViewModels
|
|||
** Accessors
|
||||
*********/
|
||||
/// <summary>The URL of the linked page.</summary>
|
||||
public string Url { get; set; }
|
||||
public string Url { get; }
|
||||
|
||||
/// <summary>The suggested link text.</summary>
|
||||
public string Text { get; set; }
|
||||
public string Text { get; }
|
||||
|
||||
|
||||
/*********
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
@ -13,37 +11,34 @@ namespace StardewModdingAPI.Web.ViewModels
|
|||
** Accessors
|
||||
*********/
|
||||
/// <summary>The current stable version of the game.</summary>
|
||||
public string StableVersion { get; set; }
|
||||
public string? StableVersion { get; }
|
||||
|
||||
/// <summary>The current beta version of the game (if any).</summary>
|
||||
public string BetaVersion { get; set; }
|
||||
public string? BetaVersion { get; }
|
||||
|
||||
/// <summary>The mods to display.</summary>
|
||||
public ModModel[] Mods { get; set; }
|
||||
public ModModel[] Mods { get; }
|
||||
|
||||
/// <summary>When the data was last updated.</summary>
|
||||
public DateTimeOffset LastUpdated { get; set; }
|
||||
public DateTimeOffset LastUpdated { get; }
|
||||
|
||||
/// <summary>Whether the data hasn't been updated in a while.</summary>
|
||||
public bool IsStale { get; set; }
|
||||
public bool IsStale { get; }
|
||||
|
||||
/// <summary>Whether the mod metadata is available.</summary>
|
||||
public bool HasData => this.Mods?.Any() == true;
|
||||
public bool HasData => this.Mods.Any();
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an empty instance.</summary>
|
||||
public ModListModel() { }
|
||||
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="stableVersion">The current stable version of the game.</param>
|
||||
/// <param name="betaVersion">The current beta version of the game (if any).</param>
|
||||
/// <param name="mods">The mods to display.</param>
|
||||
/// <param name="lastUpdated">When the data was last updated.</param>
|
||||
/// <param name="isStale">Whether the data hasn't been updated in a while.</param>
|
||||
public ModListModel(string stableVersion, string betaVersion, IEnumerable<ModModel> mods, DateTimeOffset lastUpdated, bool isStale)
|
||||
public ModListModel(string? stableVersion, string? betaVersion, IEnumerable<ModModel> mods, DateTimeOffset lastUpdated, bool isStale)
|
||||
{
|
||||
this.StableVersion = stableVersion;
|
||||
this.BetaVersion = betaVersion;
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
#nullable disable
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using StardewModdingAPI.Toolkit.Framework.Clients.Wiki;
|
||||
|
||||
namespace StardewModdingAPI.Web.ViewModels
|
||||
|
@ -13,43 +12,43 @@ namespace StardewModdingAPI.Web.ViewModels
|
|||
** Accessors
|
||||
*********/
|
||||
/// <summary>The mod name.</summary>
|
||||
public string Name { get; set; }
|
||||
public string? Name { get; }
|
||||
|
||||
/// <summary>The mod's alternative names, if any.</summary>
|
||||
public string AlternateNames { get; set; }
|
||||
public string AlternateNames { get; }
|
||||
|
||||
/// <summary>The mod author's name.</summary>
|
||||
public string Author { get; set; }
|
||||
public string? Author { get; }
|
||||
|
||||
/// <summary>The mod author's alternative names, if any.</summary>
|
||||
public string AlternateAuthors { get; set; }
|
||||
public string AlternateAuthors { get; }
|
||||
|
||||
/// <summary>The GitHub repo, if any.</summary>
|
||||
public string GitHubRepo { get; set; }
|
||||
public string? GitHubRepo { get; }
|
||||
|
||||
/// <summary>The URL to the mod's source code, if any.</summary>
|
||||
public string SourceUrl { get; set; }
|
||||
public string? SourceUrl { get; }
|
||||
|
||||
/// <summary>The compatibility status for the stable version of the game.</summary>
|
||||
public ModCompatibilityModel Compatibility { get; set; }
|
||||
public ModCompatibilityModel Compatibility { get; }
|
||||
|
||||
/// <summary>The compatibility status for the beta version of the game.</summary>
|
||||
public ModCompatibilityModel BetaCompatibility { get; set; }
|
||||
public ModCompatibilityModel? BetaCompatibility { get; }
|
||||
|
||||
/// <summary>Links to the available mod pages.</summary>
|
||||
public ModLinkModel[] ModPages { get; set; }
|
||||
public ModLinkModel[] ModPages { get; }
|
||||
|
||||
/// <summary>The human-readable warnings for players about this mod.</summary>
|
||||
public string[] Warnings { get; set; }
|
||||
public string[] Warnings { get; }
|
||||
|
||||
/// <summary>The URL of the pull request which submits changes for an unofficial update to the author, if any.</summary>
|
||||
public string PullRequestUrl { get; set; }
|
||||
public string? PullRequestUrl { get; }
|
||||
|
||||
/// <summary>Special notes intended for developers who maintain unofficial updates or submit pull requests. </summary>
|
||||
public string DevNote { get; set; }
|
||||
/// <summary>Special notes intended for developers who maintain unofficial updates or submit pull requests.</summary>
|
||||
public string? DevNote { get; }
|
||||
|
||||
/// <summary>A unique identifier for the mod that can be used in an anchor URL.</summary>
|
||||
public string Slug { get; set; }
|
||||
public string? Slug { get; }
|
||||
|
||||
/// <summary>The sites where the mod can be downloaded.</summary>
|
||||
public string[] ModPageSites => this.ModPages.Select(p => p.Text).ToArray();
|
||||
|
@ -58,6 +57,38 @@ namespace StardewModdingAPI.Web.ViewModels
|
|||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="name">The mod name.</param>
|
||||
/// <param name="alternateNames">The mod's alternative names, if any.</param>
|
||||
/// <param name="author">The mod author's name.</param>
|
||||
/// <param name="alternateAuthors">The mod author's alternative names, if any.</param>
|
||||
/// <param name="gitHubRepo">The GitHub repo, if any.</param>
|
||||
/// <param name="sourceUrl">The URL to the mod's source code, if any.</param>
|
||||
/// <param name="compatibility">The compatibility status for the stable version of the game.</param>
|
||||
/// <param name="betaCompatibility">The compatibility status for the beta version of the game.</param>
|
||||
/// <param name="modPages">Links to the available mod pages.</param>
|
||||
/// <param name="warnings">The human-readable warnings for players about this mod.</param>
|
||||
/// <param name="pullRequestUrl">The URL of the pull request which submits changes for an unofficial update to the author, if any.</param>
|
||||
/// <param name="devNote">Special notes intended for developers who maintain unofficial updates or submit pull requests.</param>
|
||||
/// <param name="slug">A unique identifier for the mod that can be used in an anchor URL.</param>
|
||||
[JsonConstructor]
|
||||
public ModModel(string? name, string alternateNames, string author, string alternateAuthors, string gitHubRepo, string sourceUrl, ModCompatibilityModel compatibility, ModCompatibilityModel betaCompatibility, ModLinkModel[] modPages, string[] warnings, string pullRequestUrl, string devNote, string slug)
|
||||
{
|
||||
this.Name = name;
|
||||
this.AlternateNames = alternateNames;
|
||||
this.Author = author;
|
||||
this.AlternateAuthors = alternateAuthors;
|
||||
this.GitHubRepo = gitHubRepo;
|
||||
this.SourceUrl = sourceUrl;
|
||||
this.Compatibility = compatibility;
|
||||
this.BetaCompatibility = betaCompatibility;
|
||||
this.ModPages = modPages;
|
||||
this.Warnings = warnings;
|
||||
this.PullRequestUrl = pullRequestUrl;
|
||||
this.DevNote = devNote;
|
||||
this.Slug = slug;
|
||||
}
|
||||
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="entry">The mod metadata.</param>
|
||||
public ModModel(WikiModEntry entry)
|
||||
|
@ -84,7 +115,7 @@ namespace StardewModdingAPI.Web.ViewModels
|
|||
*********/
|
||||
/// <summary>Get the web URL for the mod's source code repository, if any.</summary>
|
||||
/// <param name="entry">The mod metadata.</param>
|
||||
private string GetSourceUrl(WikiModEntry entry)
|
||||
private string? GetSourceUrl(WikiModEntry entry)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(entry.GitHubRepo))
|
||||
return $"https://github.com/{entry.GitHubRepo}";
|
||||
|
|
|
@ -1,7 +1,3 @@
|
|||
@{
|
||||
#nullable disable
|
||||
}
|
||||
|
||||
@using Microsoft.Extensions.Options
|
||||
@using StardewModdingAPI.Web.Framework
|
||||
@using StardewModdingAPI.Web.Framework.ConfigModels
|
||||
|
@ -28,7 +24,7 @@
|
|||
|
||||
<div id="call-to-action">
|
||||
<div class="cta-dropdown">
|
||||
<a href="@Model.StableVersion.DownloadUrl" class="main-cta download">Download SMAPI @Model.StableVersion.Version</a><br />
|
||||
<a href="@Model!.StableVersion.DownloadUrl" class="main-cta download">Download SMAPI @Model.StableVersion.Version</a><br />
|
||||
<div class="dropdown-content">
|
||||
<a href="https://www.nexusmods.com/stardewvalley/mods/2400"><img src="Content/images/nexus-icon.png" /> Download from Nexus</a>
|
||||
<a href="@Model.StableVersion.DownloadUrl"><img src="Content/images/direct-download-icon.png" /> Direct download</a>
|
||||
|
|
|
@ -1,7 +1,3 @@
|
|||
@{
|
||||
#nullable disable
|
||||
}
|
||||
|
||||
@using Microsoft.Extensions.Options
|
||||
@using StardewModdingAPI.Web.Framework
|
||||
@using StardewModdingAPI.Web.Framework.ConfigModels
|
||||
|
|
|
@ -1,7 +1,3 @@
|
|||
@{
|
||||
#nullable disable
|
||||
}
|
||||
|
||||
@using Humanizer
|
||||
@using StardewModdingAPI.Web.Framework
|
||||
@using StardewModdingAPI.Web.ViewModels.JsonValidator
|
||||
|
@ -9,10 +5,10 @@
|
|||
|
||||
@{
|
||||
// get view data
|
||||
string curPageUrl = this.Url.PlainAction("Index", "JsonValidator", new { schemaName = Model.SchemaName, id = Model.PasteID }, absoluteUrl: true);
|
||||
string newUploadUrl = this.Url.PlainAction("Index", "JsonValidator", new { schemaName = Model.SchemaName });
|
||||
string schemaDisplayName = null;
|
||||
bool isValidSchema = Model.SchemaName != null && Model.SchemaFormats.TryGetValue(Model.SchemaName, out schemaDisplayName) && schemaDisplayName?.ToLower() != "none";
|
||||
string curPageUrl = this.Url.PlainAction("Index", "JsonValidator", new { schemaName = Model!.SchemaName, id = Model.PasteID }, absoluteUrl: true)!;
|
||||
string newUploadUrl = this.Url.PlainAction("Index", "JsonValidator", new { schemaName = Model.SchemaName })!;
|
||||
string? schemaDisplayName = null;
|
||||
bool isValidSchema = Model.SchemaName != null && Model.SchemaFormats.TryGetValue(Model.SchemaName, out schemaDisplayName) && schemaDisplayName.ToLower() != "none";
|
||||
|
||||
// build title
|
||||
ViewData["Title"] = "JSON validator";
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
.GetValues<LogSection>()
|
||||
.ToDictionary(section => (int)section, section => section.ToString());
|
||||
|
||||
string curPageUrl = this.Url.PlainAction("Index", "LogParser", new { id = Model.PasteID }, absoluteUrl: true);
|
||||
string curPageUrl = this.Url.PlainAction("Index", "LogParser", new { id = Model.PasteID }, absoluteUrl: true)!;
|
||||
|
||||
ISet<int> screenIds = new HashSet<int>(log?.Messages.Select(p => p.ScreenId) ?? Array.Empty<int>());
|
||||
}
|
||||
|
|
|
@ -1,7 +1,3 @@
|
|||
@{
|
||||
#nullable disable
|
||||
}
|
||||
|
||||
@using Humanizer
|
||||
@using Humanizer.Localisation
|
||||
@using StardewModdingAPI.Web.Framework
|
||||
|
@ -10,7 +6,7 @@
|
|||
@{
|
||||
ViewData["Title"] = "Mod compatibility";
|
||||
|
||||
TimeSpan staleAge = DateTimeOffset.UtcNow - Model.LastUpdated;
|
||||
TimeSpan staleAge = DateTimeOffset.UtcNow - Model!.LastUpdated;
|
||||
|
||||
bool hasBeta = Model.BetaVersion != null;
|
||||
string betaLabel = $"SDV {Model.BetaVersion} only";
|
||||
|
|
|
@ -1,7 +1,3 @@
|
|||
@{
|
||||
#nullable disable
|
||||
}
|
||||
|
||||
@using Microsoft.Extensions.Options
|
||||
@using StardewModdingAPI.Web.Framework
|
||||
@using StardewModdingAPI.Web.Framework.ConfigModels
|
||||
|
|
|
@ -1,7 +1,3 @@
|
|||
@{
|
||||
#nullable disable
|
||||
}
|
||||
|
||||
@{
|
||||
Layout = "_Layout";
|
||||
}
|
||||
|
|
|
@ -17,7 +17,8 @@
|
|||
|
||||
"Site": {
|
||||
"BetaEnabled": false,
|
||||
"OtherBlurb": null
|
||||
"OtherBlurb": null,
|
||||
"SupporterList": null
|
||||
},
|
||||
|
||||
"ApiClients": {
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
<s:Boolean x:Key="/Default/UserDictionary/Words/=debounced/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=decoratable/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=devs/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=disambiguator/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=fallbacks/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=filenames/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=gamepad/@EntryIndexedValue">True</s:Boolean>
|
||||
|
|
|
@ -85,6 +85,9 @@ namespace StardewModdingAPI
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
#if NET5_0_OR_GREATER
|
||||
[MemberNotNullWhen(true, nameof(SemanticVersion.PrereleaseTag))]
|
||||
#endif
|
||||
public bool IsPrerelease()
|
||||
{
|
||||
return this.Version.IsPrerelease();
|
||||
|
|
Loading…
Reference in New Issue