diff --git a/docs/release-notes.md b/docs/release-notes.md
index 93d07385..903eaf6f 100644
--- a/docs/release-notes.md
+++ b/docs/release-notes.md
@@ -6,6 +6,7 @@
* Fixed some incompatible-mod errors not showing the mod URL.
* Fixed rare crash with a specific combination of `manifest.json` fields and internal SMAPI mod data.
* Fixed update checks not working for Nexus Mods due to a change in their API.
+ * Fixed update checks failing for some older mods with non-standard versions.
* For the [log parser][]:
* Fixed mod list not including all mods if at least one has no author name.
diff --git a/src/SMAPI.Common/Models/ModSeachModel.cs b/src/SMAPI.Common/Models/ModSeachModel.cs
index 13b05d2d..3c33d0b6 100644
--- a/src/SMAPI.Common/Models/ModSeachModel.cs
+++ b/src/SMAPI.Common/Models/ModSeachModel.cs
@@ -12,6 +12,9 @@ namespace StardewModdingAPI.Common.Models
/// The namespaced mod keys to search.
public string[] ModKeys { get; set; }
+ /// Whether to allow non-semantic versions, instead of returning an error for those.
+ public bool AllowInvalidVersions { get; set; }
+
/*********
** Public methods
@@ -24,9 +27,11 @@ namespace StardewModdingAPI.Common.Models
/// Construct an instance.
/// The namespaced mod keys to search.
- public ModSearchModel(IEnumerable modKeys)
+ /// Whether to allow non-semantic versions, instead of returning an error for those.
+ public ModSearchModel(IEnumerable modKeys, bool allowInvalidVersions)
{
this.ModKeys = modKeys.ToArray();
+ this.AllowInvalidVersions = allowInvalidVersions;
}
}
}
diff --git a/src/SMAPI.Web/Controllers/ModsApiController.cs b/src/SMAPI.Web/Controllers/ModsApiController.cs
index dcb4ec52..abae7db7 100644
--- a/src/SMAPI.Web/Controllers/ModsApiController.cs
+++ b/src/SMAPI.Web/Controllers/ModsApiController.cs
@@ -64,14 +64,15 @@ namespace StardewModdingAPI.Web.Controllers
/// Fetch version metadata for the given mods.
/// The namespaced mod keys to search as a comma-delimited array.
+ /// Whether to allow non-semantic versions, instead of returning an error for those.
[HttpGet]
- public async Task> GetAsync(string modKeys)
+ public async Task> GetAsync(string modKeys, bool allowInvalidVersions = false)
{
string[] modKeysArray = modKeys?.Split(',').ToArray();
if (modKeysArray == null || !modKeysArray.Any())
return new Dictionary();
- return await this.PostAsync(new ModSearchModel(modKeysArray));
+ return await this.PostAsync(new ModSearchModel(modKeysArray, allowInvalidVersions));
}
/// Fetch version metadata for the given mods.
@@ -79,7 +80,8 @@ namespace StardewModdingAPI.Web.Controllers
[HttpPost]
public async Task> PostAsync([FromBody] ModSearchModel search)
{
- // sort & filter keys
+ // parse model
+ bool allowInvalidVersions = search?.AllowInvalidVersions ?? false;
string[] modKeys = (search?.ModKeys?.ToArray() ?? new string[0])
.Distinct(StringComparer.CurrentCultureIgnoreCase)
.OrderBy(p => p, StringComparer.CurrentCultureIgnoreCase)
@@ -106,12 +108,20 @@ namespace StardewModdingAPI.Web.Controllers
// fetch mod info
result[modKey] = await this.Cache.GetOrCreateAsync($"{repository.VendorKey}:{modID}".ToLower(), async entry =>
{
- entry.AbsoluteExpiration = DateTimeOffset.UtcNow.AddMinutes(this.CacheMinutes);
-
+ // fetch info
ModInfoModel info = await repository.GetModInfoAsync(modID);
- if (info.Error == null && (info.Version == null || !Regex.IsMatch(info.Version, this.VersionRegex, RegexOptions.CultureInvariant | RegexOptions.IgnoreCase)))
- info = new ModInfoModel(info.Name, info.Version, info.Url, info.Version == null ? "Mod has no version number." : $"Mod has invalid semantic version '{info.Version}'.");
+ // validate
+ if (info.Error == null)
+ {
+ if (info.Version == null)
+ info = new ModInfoModel(info.Name, info.Version, info.Url, "Mod has no version number.");
+ if (!allowInvalidVersions && !Regex.IsMatch(info.Version, this.VersionRegex, RegexOptions.CultureInvariant | RegexOptions.IgnoreCase))
+ info = new ModInfoModel(info.Name, info.Version, info.Url, $"Mod has invalid semantic version '{info.Version}'.");
+ }
+
+ // cache & return
+ entry.AbsoluteExpiration = DateTimeOffset.UtcNow.AddMinutes(this.CacheMinutes);
return info;
});
}
diff --git a/src/SMAPI/Framework/ModData/ModDataRecord.cs b/src/SMAPI/Framework/ModData/ModDataRecord.cs
index 79a954f7..56275f53 100644
--- a/src/SMAPI/Framework/ModData/ModDataRecord.cs
+++ b/src/SMAPI/Framework/ModData/ModDataRecord.cs
@@ -106,10 +106,10 @@ namespace StardewModdingAPI.Framework.ModData
/// Get a semantic local version for update checks.
/// The remote version to normalise.
- public string GetLocalVersionForUpdateChecks(string version)
+ public ISemanticVersion GetLocalVersionForUpdateChecks(ISemanticVersion version)
{
- return this.MapLocalVersions != null && this.MapLocalVersions.TryGetValue(version, out string newVersion)
- ? newVersion
+ return this.MapLocalVersions != null && this.MapLocalVersions.TryGetValue(version.ToString(), out string newVersion)
+ ? new SemanticVersion(newVersion)
: version;
}
@@ -117,6 +117,11 @@ namespace StardewModdingAPI.Framework.ModData
/// The remote version to normalise.
public string GetRemoteVersionForUpdateChecks(string version)
{
+ // normalise version if possible
+ if (SemanticVersion.TryParse(version, out ISemanticVersion parsed))
+ version = parsed.ToString();
+
+ // fetch remote version
return this.MapRemoteVersions != null && this.MapRemoteVersions.TryGetValue(version, out string newVersion)
? newVersion
: version;
diff --git a/src/SMAPI/Framework/ModData/ParsedModDataRecord.cs b/src/SMAPI/Framework/ModData/ParsedModDataRecord.cs
index 7f49790d..deb12bdc 100644
--- a/src/SMAPI/Framework/ModData/ParsedModDataRecord.cs
+++ b/src/SMAPI/Framework/ModData/ParsedModDataRecord.cs
@@ -33,7 +33,7 @@ namespace StardewModdingAPI.Framework.ModData
*********/
/// Get a semantic local version for update checks.
/// The remote version to normalise.
- public string GetLocalVersionForUpdateChecks(string version)
+ public ISemanticVersion GetLocalVersionForUpdateChecks(ISemanticVersion version)
{
return this.DataRecord.GetLocalVersionForUpdateChecks(version);
}
diff --git a/src/SMAPI/Framework/WebApiClient.cs b/src/SMAPI/Framework/WebApiClient.cs
index e78ac14b..7f0122cf 100644
--- a/src/SMAPI/Framework/WebApiClient.cs
+++ b/src/SMAPI/Framework/WebApiClient.cs
@@ -40,7 +40,7 @@ namespace StardewModdingAPI.Framework
{
return this.Post>(
$"v{this.Version}/mods",
- new ModSearchModel(modKeys)
+ new ModSearchModel(modKeys, allowInvalidVersions: true)
);
}
diff --git a/src/SMAPI/Program.cs b/src/SMAPI/Program.cs
index 979f6328..1a1b9645 100644
--- a/src/SMAPI/Program.cs
+++ b/src/SMAPI/Program.cs
@@ -604,29 +604,30 @@ namespace StardewModdingAPI
foreach (var result in results)
{
IModMetadata mod = result.Mod;
- ModInfoModel info = result.Info;
+ ModInfoModel remoteInfo = result.Info;
// handle error
- if (info.Error != null)
+ if (remoteInfo.Error != null)
{
- this.Monitor.Log($" {mod.DisplayName} ({result.Key}): update error: {info.Error}", LogLevel.Trace);
+ this.Monitor.Log($" {mod.DisplayName} ({result.Key}): update error: {remoteInfo.Error}", LogLevel.Trace);
continue;
}
- // track update
- ISemanticVersion localVersion = mod.DataRecord != null
- ? new SemanticVersion(mod.DataRecord.GetLocalVersionForUpdateChecks(mod.Manifest.Version.ToString()))
- : mod.Manifest.Version;
- ISemanticVersion latestVersion = new SemanticVersion(mod.DataRecord != null
- ? mod.DataRecord.GetRemoteVersionForUpdateChecks(new SemanticVersion(info.Version).ToString())
- : info.Version
- );
- bool isUpdate = latestVersion.IsNewerThan(localVersion);
- this.VerboseLog($" {mod.DisplayName} ({result.Key}): {(isUpdate ? $"{mod.Manifest.Version}{(!localVersion.Equals(mod.Manifest.Version) ? $" [{localVersion}]" : "")} => {info.Version}{(!latestVersion.Equals(new SemanticVersion(info.Version)) ? $" [{latestVersion}]" : "")}" : "okay")}.");
+ // normalise versions
+ ISemanticVersion localVersion = mod.DataRecord?.GetLocalVersionForUpdateChecks(mod.Manifest.Version) ?? mod.Manifest.Version;
+ if (!SemanticVersion.TryParse(mod.DataRecord?.GetRemoteVersionForUpdateChecks(remoteInfo.Version) ?? remoteInfo.Version, out ISemanticVersion remoteVersion))
+ {
+ this.Monitor.Log($" {mod.DisplayName} ({result.Key}): update error: Mod has invalid version {remoteInfo.Version}", LogLevel.Trace);
+ continue;
+ }
+
+ // compare versions
+ bool isUpdate = remoteVersion.IsNewerThan(localVersion);
+ this.VerboseLog($" {mod.DisplayName} ({result.Key}): {(isUpdate ? $"{mod.Manifest.Version}{(!localVersion.Equals(mod.Manifest.Version) ? $" [{localVersion}]" : "")} => {remoteInfo.Version}{(!remoteVersion.Equals(new SemanticVersion(remoteInfo.Version)) ? $" [{remoteVersion}]" : "")}" : "okay")}.");
if (isUpdate)
{
- if (!updatesByMod.TryGetValue(mod, out ModInfoModel other) || latestVersion.IsNewerThan(other.Version))
- updatesByMod[mod] = info;
+ if (!updatesByMod.TryGetValue(mod, out ModInfoModel other) || remoteVersion.IsNewerThan(other.Version))
+ updatesByMod[mod] = remoteInfo;
}
}