public async Task <IEnumerable <ModEntryModel> > PostAsync([FromBody] ModSearchModel model) { if (model?.Mods == null) { return(new ModEntryModel[0]); } // fetch wiki data WikiCompatibilityEntry[] wikiData = await this.GetWikiDataAsync(); IDictionary <string, ModEntryModel> mods = new Dictionary <string, ModEntryModel>(StringComparer.CurrentCultureIgnoreCase); foreach (ModSearchEntryModel mod in model.Mods) { if (string.IsNullOrWhiteSpace(mod.ID)) { continue; } ModEntryModel result = await this.GetModData(mod, wikiData, model.IncludeExtendedMetadata); mods[mod.ID] = result; } // return data return(mods.Values); }
public async Task <IEnumerable <ModEntryModel> > PostAsync([FromBody] ModSearchModel model, [FromRoute] string version) { if (model?.Mods == null) { return(new ModEntryModel[0]); } // fetch wiki data WikiModEntry[] wikiData = this.WikiCache.GetWikiMods().Select(p => p.Data).ToArray(); IDictionary <string, ModEntryModel> mods = new Dictionary <string, ModEntryModel>(StringComparer.CurrentCultureIgnoreCase); foreach (ModSearchEntryModel mod in model.Mods) { if (string.IsNullOrWhiteSpace(mod.ID)) { continue; } 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(); } mods[mod.ID] = result; } // return data return(mods.Values); }
public async Task <object> PostAsync([FromBody] ModSearchModel model) { // parse request data ISemanticVersion apiVersion = this.GetApiVersion(); ModSearchEntryModel[] searchMods = this.GetSearchMods(model, apiVersion).ToArray(); // fetch wiki data WikiCompatibilityEntry[] wikiData = await this.GetWikiDataAsync(); // fetch data IDictionary <string, ModEntryModel> mods = new Dictionary <string, ModEntryModel>(StringComparer.CurrentCultureIgnoreCase); foreach (ModSearchEntryModel mod in searchMods) { if (string.IsNullOrWhiteSpace(mod.ID)) { continue; } ModEntryModel result = await this.GetModData(mod, wikiData, model.IncludeExtendedMetadata); result.SetBackwardsCompatibility(apiVersion); mods[mod.ID] = result; } // return in expected structure return(apiVersion.IsNewerThan("2.6-beta.18") ? mods.Values : (object)mods); }
/// <summary>Gets the update status of all mods.</summary> /// <param name="statuses">All mod statuses.</param> /// <returns>Whether any non skipped statuses were added.</returns> public bool GetUpdateStatuses(out IList <ModStatus> statuses) { statuses = new List <ModStatus>(); bool addedNonSkippedStatus = false; foreach (object modMetaData in GetInstanceField <IEnumerable <object> >(GetInstanceField <object>(this.helper.ModRegistry, "Registry"), "Mods")) { ModEntryModel result = GetInstanceProperty <ModEntryModel>(modMetaData, "UpdateCheckData"); IManifest manifest = GetInstanceProperty <IManifest>(modMetaData, "Manifest"); string fallbackURL = manifest.UpdateKeys?.Select(this.toolkit.GetUpdateUrl).FirstOrDefault(p => p != null) ?? ""; if (result == null) { statuses.Add(new ModStatus(UpdateStatus.Skipped, manifest, fallbackURL, null, "SMAPI didn't check for an update")); continue; } if (result.SuggestedUpdate == null) { if (result.Errors.Length != 0) { string error = result.Errors[0]; if (result.Errors.Any(err => err.Contains("???"))) { statuses.Add(new ModStatus(UpdateStatus.Skipped, manifest, fallbackURL, null, "This mod intentionally doesn't have an update key")); } else { // Return the first error. That's not perfect, but generally users don't care why each different update failed, they just want to know there was an error. statuses.Add(new ModStatus(UpdateStatus.UpToDate, manifest, fallbackURL, null, result.Errors[0])); } } else { statuses.Add(new ModStatus(UpdateStatus.UpToDate, manifest, fallbackURL)); } } else { statuses.Add(new ModStatus(UpdateStatus.OutOfDate, manifest, result.SuggestedUpdate.Url, result.SuggestedUpdate.Version.ToString())); } addedNonSkippedStatus = true; } return(addedNonSkippedStatus); }
public async Task <IEnumerable <ModEntryModel> > PostAsync([FromBody] ModSearchModel model, [FromRoute] string version) { if (model?.Mods == null) { return(new ModEntryModel[0]); } bool legacyMode = SemanticVersion.TryParse(version, out ISemanticVersion parsedVersion) && parsedVersion.IsOlderThan("3.0.0-beta.20191109"); // fetch wiki data WikiModEntry[] wikiData = this.WikiCache.GetWikiMods().Select(p => p.GetModel()).ToArray(); IDictionary <string, ModEntryModel> mods = new Dictionary <string, ModEntryModel>(StringComparer.CurrentCultureIgnoreCase); foreach (ModSearchEntryModel mod in model.Mods) { if (string.IsNullOrWhiteSpace(mod.ID)) { continue; } ModEntryModel result = await this.GetModData(mod, wikiData, model.IncludeExtendedMetadata || legacyMode, model.ApiVersion); if (legacyMode) { result.Main = result.Metadata.Main; result.Optional = result.Metadata.Optional; result.Unofficial = result.Metadata.Unofficial; result.UnofficialForBeta = result.Metadata.UnofficialForBeta; result.HasBetaInfo = result.Metadata.BetaCompatibilityStatus != null; result.SuggestedUpdate = null; if (!model.IncludeExtendedMetadata) { result.Metadata = null; } } else 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(); } mods[mod.ID] = result; } // return data return(mods.Values); }
public async Task <IEnumerable <ModEntryModel> > PostAsync([FromBody] ModSearchModel model, [FromRoute] string version) { if (model?.Mods == null) { return(Array.Empty <ModEntryModel>()); } ModUpdateCheckConfig config = this.Config.Value; // fetch wiki data WikiModEntry[] wikiData = this.WikiCache.GetWikiMods().Select(p => p.Data).ToArray(); IDictionary <string, ModEntryModel> mods = new Dictionary <string, ModEntryModel>(StringComparer.CurrentCultureIgnoreCase); foreach (ModSearchEntryModel mod in model.Mods) { if (string.IsNullOrWhiteSpace(mod.ID)) { 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(); } // 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(); } mods[mod.ID] = result; } // return data return(mods.Values); }
public bool GetUpdateStatuses(out IList <ModStatus> statuses) { statuses = new List <ModStatus>(); object registry = this.helper.ModRegistry.GetType() .GetField("Registry", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(this.helper.ModRegistry); bool addedNonSkippedStatus = false; foreach (object modMetaData in (IEnumerable <object>)registry.GetType().GetField("Mods", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(registry)) { ModEntryModel result = GetInstanceProperty <ModEntryModel>(modMetaData, "UpdateCheckData"); IManifest manifest = GetInstanceProperty <IManifest>(modMetaData, "Manifest"); if (result == null) { statuses.Add(new ModStatus(UpdateStatus.Skipped, manifest, "", null, "SMAPI didn't check for an update")); continue; } if (!(bool)modMetaData.GetType().GetMethod("HasValidUpdateKeys").Invoke(modMetaData, null)) { statuses.Add(new ModStatus(UpdateStatus.Skipped, manifest, "", null, "Mod has no update keys")); continue; } ModDataRecordVersionedFields dataRecord = GetInstanceProperty <ModDataRecordVersionedFields>(modMetaData, "DataRecord"); //This section largely taken from https://github.com/Pathoschild/SMAPI/blob/924c3a5d3fe6bfad483834112883156bdf202b57/src/SMAPI/Framework/SCore.cs#L618-L630 bool useBetaInfo = result.HasBetaInfo && Constants.ApiVersion.IsPrerelease(); ISemanticVersion localVersion = dataRecord?.GetLocalVersionForUpdateChecks(manifest.Version) ?? manifest.Version; ISemanticVersion latestVersion = dataRecord?.GetRemoteVersionForUpdateChecks(result.Main?.Version) ?? result.Main?.Version; ISemanticVersion optionalVersion = dataRecord?.GetRemoteVersionForUpdateChecks(result.Optional?.Version) ?? result.Optional?.Version; ISemanticVersion unofficialVersion = useBetaInfo ? result.UnofficialForBeta?.Version : result.Unofficial?.Version; if (this.IsValidUpdate(localVersion, latestVersion, useBetaChannel: true)) { statuses.Add(new ModStatus(UpdateStatus.OutOfDate, manifest, result.Main?.Url, latestVersion.ToString())); } else if (this.IsValidUpdate(localVersion, optionalVersion, useBetaChannel: localVersion.IsPrerelease())) { statuses.Add(new ModStatus(UpdateStatus.OutOfDate, manifest, result.Optional?.Url, optionalVersion.ToString())); } else if (this.IsValidUpdate(localVersion, unofficialVersion, useBetaChannel: GetEnumName(modMetaData, "Status") == "Failed")) { statuses.Add(new ModStatus(UpdateStatus.OutOfDate, manifest, useBetaInfo ? result.UnofficialForBeta?.Url : result.Unofficial?.Url, unofficialVersion.ToString())); } else { string updateURL = null; UpdateStatus updateStatus = UpdateStatus.UpToDate; if (localVersion.Equals(latestVersion)) { updateURL = result.Main?.Url; } else if (localVersion.Equals(optionalVersion)) { updateURL = result.Optional?.Url; } else if (localVersion.Equals(unofficialVersion)) { updateURL = useBetaInfo ? result.UnofficialForBeta?.Url : result.Unofficial?.Url; } else if (latestVersion != null && this.IsValidUpdate(latestVersion, localVersion, useBetaChannel: true)) { updateURL = result.Main?.Url; updateStatus = UpdateStatus.VeryNew; } else if (optionalVersion != null && this.IsValidUpdate(optionalVersion, localVersion, useBetaChannel: localVersion.IsPrerelease())) { updateURL = result.Optional?.Url; updateStatus = UpdateStatus.VeryNew; } else if (unofficialVersion != null && this.IsValidUpdate(unofficialVersion, localVersion, useBetaChannel: GetEnumName(modMetaData, "Status") == "Failed")) { updateURL = useBetaInfo ? result.UnofficialForBeta?.Url : result.Unofficial?.Url; updateStatus = UpdateStatus.VeryNew; } if (updateURL != null) { statuses.Add(new ModStatus(updateStatus, manifest, updateURL)); } else if (result.Errors != null && result.Errors.Any()) { statuses.Add(new ModStatus(UpdateStatus.Error, manifest, "", "", result.Errors[0])); } else { statuses.Add(new ModStatus(UpdateStatus.Error, manifest, "", "", "Unknown Error")); } } addedNonSkippedStatus = true; } return(addedNonSkippedStatus); }
/// <inheritdoc /> public IModMetadata SetUpdateData(ModEntryModel data) { this.UpdateCheckData = data; return(this); }
/********* ** Private methods *********/ /// <summary>Get the metadata for a mod.</summary> /// <param name="search">The mod data to match.</param> /// <param name="wikiData">The wiki data.</param> /// <param name="includeExtendedMetadata">Whether to include extended metadata for each mod.</param> /// <returns>Returns the mod data if found, else <c>null</c>.</returns> private async Task <ModEntryModel> GetModData(ModSearchEntryModel search, WikiCompatibilityEntry[] wikiData, bool includeExtendedMetadata) { // resolve update keys var updateKeys = new HashSet <string>(search.UpdateKeys ?? new string[0], StringComparer.InvariantCultureIgnoreCase); ModDataRecord record = this.ModDatabase.Get(search.ID); if (record?.Fields != null) { string defaultUpdateKey = record.Fields.FirstOrDefault(p => p.Key == ModDataFieldKey.UpdateKey && p.IsDefault)?.Value; if (!string.IsNullOrWhiteSpace(defaultUpdateKey)) { updateKeys.Add(defaultUpdateKey); } } // get latest versions ModEntryModel result = new ModEntryModel { ID = search.ID }; IList <string> errors = new List <string>(); foreach (string updateKey in updateKeys) { // fetch data ModInfoModel data = await this.GetInfoForUpdateKeyAsync(updateKey); if (data.Error != null) { errors.Add(data.Error); continue; } // handle main version if (data.Version != null) { if (!SemanticVersion.TryParse(data.Version, out ISemanticVersion version)) { errors.Add($"The update key '{updateKey}' matches a mod with invalid semantic version '{data.Version}'."); continue; } if (this.IsNewer(version, result.Main?.Version)) { result.Main = new ModEntryVersionModel(version, data.Url); } } // handle optional version if (data.PreviewVersion != null) { if (!SemanticVersion.TryParse(data.PreviewVersion, out ISemanticVersion version)) { errors.Add($"The update key '{updateKey}' matches a mod with invalid optional semantic version '{data.PreviewVersion}'."); continue; } if (this.IsNewer(version, result.Optional?.Version)) { result.Optional = new ModEntryVersionModel(version, data.Url); } } } // get unofficial version WikiCompatibilityEntry wikiEntry = wikiData.FirstOrDefault(entry => entry.ID.Contains(result.ID.Trim(), StringComparer.InvariantCultureIgnoreCase)); if (wikiEntry?.UnofficialVersion != null && this.IsNewer(wikiEntry.UnofficialVersion, result.Main?.Version) && this.IsNewer(wikiEntry.UnofficialVersion, result.Optional?.Version)) { result.Unofficial = new ModEntryVersionModel(wikiEntry.UnofficialVersion, this.WikiCompatibilityPageUrl); } // fallback to preview if latest is invalid if (result.Main == null && result.Optional != null) { result.Main = result.Optional; result.Optional = null; } // special cases if (result.ID == "Pathoschild.SMAPI") { if (result.Main != null) { result.Main.Url = "https://smapi.io/"; } if (result.Optional != null) { result.Optional.Url = "https://smapi.io/"; } } // add extended metadata if (includeExtendedMetadata && (wikiEntry != null || record != null)) { result.Metadata = new ModExtendedMetadataModel(wikiEntry, record); } // add result result.Errors = errors.ToArray(); return(result); }
/********* ** Private methods *********/ /// <summary>Get the metadata for a mod.</summary> /// <param name="search">The mod data to match.</param> /// <param name="wikiData">The wiki data.</param> /// <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) { // cross-reference data 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)); bool allowNonStandardVersions = overrides?.AllowNonStandardVersions ?? false; // get latest versions ModEntryModel result = new ModEntryModel { ID = search.ID }; IList <string> errors = new List <string>(); ModEntryVersionModel main = null; ModEntryVersionModel optional = null; ModEntryVersionModel unofficial = null; ModEntryVersionModel unofficialForBeta = null; foreach (UpdateKey updateKey in updateKeys) { // validate update key if (!updateKey.LooksValid) { errors.Add($"The update key '{updateKey}' isn't in a valid format. It should contain the site key and mod ID like 'Nexus:541', with an optional subkey like 'Nexus:541@subkey'."); continue; } // fetch data ModInfoModel data = await this.GetInfoForUpdateKeyAsync(updateKey, allowNonStandardVersions, wikiEntry?.MapRemoteVersions); if (data.Status != RemoteModStatus.Ok) { errors.Add(data.Error ?? data.Status.ToString()); continue; } // handle versions if (this.IsNewer(data.Version, main?.Version)) { main = new ModEntryVersionModel(data.Version, data.Url); } if (this.IsNewer(data.PreviewVersion, optional?.Version)) { optional = new ModEntryVersionModel(data.PreviewVersion, data.Url); } } // get unofficial version if (wikiEntry?.Compatibility.UnofficialVersion != null && this.IsNewer(wikiEntry.Compatibility.UnofficialVersion, main?.Version) && this.IsNewer(wikiEntry.Compatibility.UnofficialVersion, optional?.Version)) { 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.BetaCompatibility.Status == WikiCompatibilityStatus.Unofficial) { if (wikiEntry.BetaCompatibility.UnofficialVersion != null) { unofficialForBeta = (wikiEntry.BetaCompatibility.UnofficialVersion != null && this.IsNewer(wikiEntry.BetaCompatibility.UnofficialVersion, main?.Version) && this.IsNewer(wikiEntry.BetaCompatibility.UnofficialVersion, optional?.Version)) ? new ModEntryVersionModel(wikiEntry.BetaCompatibility.UnofficialVersion, $"{this.Url.PlainAction("Index", "Mods", absoluteUrl: true)}#{wikiEntry.Anchor}") : null; } else { unofficialForBeta = unofficial; } } } // fallback to preview if latest is invalid if (main == null && optional != null) { main = optional; optional = null; } // special cases if (overrides?.SetUrl != null) { if (main != null) { main.Url = overrides.SetUrl; } if (optional != null) { optional.Url = overrides.SetUrl; } } // get recommended update (if any) ISemanticVersion installedVersion = this.ModSites.GetMappedVersion(search.InstalledVersion?.ToString(), wikiEntry?.MapLocalVersions, allowNonStandard: allowNonStandardVersions); if (apiVersion != null && installedVersion != null) { // get newer versions List <ModEntryVersionModel> updates = new List <ModEntryVersionModel>(); if (this.IsRecommendedUpdate(installedVersion, main?.Version, useBetaChannel: true)) { updates.Add(main); } if (this.IsRecommendedUpdate(installedVersion, optional?.Version, useBetaChannel: installedVersion.IsPrerelease() || search.IsBroken)) { updates.Add(optional); } if (this.IsRecommendedUpdate(installedVersion, unofficial?.Version, useBetaChannel: true)) { updates.Add(unofficial); } if (this.IsRecommendedUpdate(installedVersion, unofficialForBeta?.Version, useBetaChannel: apiVersion.IsPrerelease())) { updates.Add(unofficialForBeta); } // get newest version ModEntryVersionModel newest = null; foreach (ModEntryVersionModel update in updates) { if (newest == null || update.Version.IsNewerThan(newest.Version)) { newest = update; } } // set field result.SuggestedUpdate = newest != null ? new ModEntryVersionModel(newest.Version, newest.Url) : null; } // add extended metadata if (includeExtendedMetadata) { result.Metadata = new ModExtendedMetadataModel(wikiEntry, record, main: main, optional: optional, unofficial: unofficial, unofficialForBeta: unofficialForBeta); } // add result result.Errors = errors.ToArray(); return(result); }
/********* ** Private methods *********/ /// <summary>Get the metadata for a mod.</summary> /// <param name="search">The mod data to match.</param> /// <param name="wikiData">The wiki data.</param> /// <param name="includeExtendedMetadata">Whether to include extended metadata for each mod.</param> /// <returns>Returns the mod data if found, else <c>null</c>.</returns> private async Task <ModEntryModel> GetModData(ModSearchEntryModel search, WikiModEntry[] wikiData, bool includeExtendedMetadata) { // cross-reference data ModDataRecord record = this.ModDatabase.Get(search.ID); WikiModEntry wikiEntry = wikiData.FirstOrDefault(entry => entry.ID.Contains(search.ID.Trim(), StringComparer.InvariantCultureIgnoreCase)); UpdateKey[] updateKeys = this.GetUpdateKeys(search.UpdateKeys, record, wikiEntry).ToArray(); // get latest versions ModEntryModel result = new ModEntryModel { ID = search.ID }; IList <string> errors = new List <string>(); foreach (UpdateKey updateKey in updateKeys) { // validate update key if (!updateKey.LooksValid) { errors.Add($"The update key '{updateKey}' isn't in a valid format. It should contain the site key and mod ID like 'Nexus:541'."); continue; } // fetch data ModInfoModel data = await this.GetInfoForUpdateKeyAsync(updateKey); if (data.Error != null) { errors.Add(data.Error); continue; } // handle main version if (data.Version != null) { if (!SemanticVersion.TryParse(data.Version, out ISemanticVersion version)) { errors.Add($"The update key '{updateKey}' matches a mod with invalid semantic version '{data.Version}'."); continue; } if (this.IsNewer(version, result.Main?.Version)) { result.Main = new ModEntryVersionModel(version, data.Url); } } // handle optional version if (data.PreviewVersion != null) { if (!SemanticVersion.TryParse(data.PreviewVersion, out ISemanticVersion version)) { errors.Add($"The update key '{updateKey}' matches a mod with invalid optional semantic version '{data.PreviewVersion}'."); continue; } if (this.IsNewer(version, result.Optional?.Version)) { result.Optional = new ModEntryVersionModel(version, data.Url); } } } // get unofficial version if (wikiEntry?.Compatibility.UnofficialVersion != null && this.IsNewer(wikiEntry.Compatibility.UnofficialVersion, result.Main?.Version) && this.IsNewer(wikiEntry.Compatibility.UnofficialVersion, result.Optional?.Version)) { result.Unofficial = new ModEntryVersionModel(wikiEntry.Compatibility.UnofficialVersion, $"{this.CompatibilityPageUrl}/#{wikiEntry.Anchor}"); } // get unofficial version for beta if (wikiEntry?.HasBetaInfo == true) { result.HasBetaInfo = true; if (wikiEntry.BetaCompatibility.Status == WikiCompatibilityStatus.Unofficial) { if (wikiEntry.BetaCompatibility.UnofficialVersion != null) { result.UnofficialForBeta = (wikiEntry.BetaCompatibility.UnofficialVersion != null && this.IsNewer(wikiEntry.BetaCompatibility.UnofficialVersion, result.Main?.Version) && this.IsNewer(wikiEntry.BetaCompatibility.UnofficialVersion, result.Optional?.Version)) ? new ModEntryVersionModel(wikiEntry.BetaCompatibility.UnofficialVersion, $"{this.CompatibilityPageUrl}/#{wikiEntry.Anchor}") : null; } else { result.UnofficialForBeta = result.Unofficial; } } } // fallback to preview if latest is invalid if (result.Main == null && result.Optional != null) { result.Main = result.Optional; result.Optional = null; } // special cases if (result.ID == "Pathoschild.SMAPI") { if (result.Main != null) { result.Main.Url = "https://smapi.io/"; } if (result.Optional != null) { result.Optional.Url = "https://smapi.io/"; } } // add extended metadata if (includeExtendedMetadata && (wikiEntry != null || record != null)) { result.Metadata = new ModExtendedMetadataModel(wikiEntry, record); } // add result result.Errors = errors.ToArray(); return(result); }
public bool GetUpdateStatuses(out IList <ModStatus> statuses) { statuses = new List <ModStatus>(); object registry = this.helper.ModRegistry.GetType() .GetField("Registry", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(this.helper.ModRegistry); foreach (object modMetaData in (IEnumerable <object>)registry.GetType() .GetMethod("GetAll", BindingFlags.Public | BindingFlags.Instance) .Invoke(registry, new object[] { true, true })) { ModEntryModel updateCheckModel = GetInstanceProperty <ModEntryModel>(modMetaData, "UpdateCheckData"); if (updateCheckModel == null) { return(false); } IManifest modManifest = GetInstanceProperty <IManifest>(modMetaData, "Manifest"); ModEntryVersionModel latestModEntryVersionModel = updateCheckModel.Main; ModEntryVersionModel optionalModEntryVersionModel = updateCheckModel.Optional; ModEntryVersionModel unofficialModEntryVersionModel = updateCheckModel.Unofficial; // get versions ISemanticVersion localVersion = modManifest.Version; ISemanticVersion latestVersion = latestModEntryVersionModel?.Version; ISemanticVersion optionalVersion = optionalModEntryVersionModel?.Version; ISemanticVersion unofficialVersion = unofficialModEntryVersionModel?.Version; UpdateStatus status = UpdateStatus.OutOfDate; ModEntryVersionModel whichModel = null; ISemanticVersion updateVersion; string error = null; // get update alerts if (IsValidUpdate(localVersion, latestVersion, useBetaChannel: true)) { whichModel = latestModEntryVersionModel; updateVersion = latestVersion; } else if (IsValidUpdate(localVersion, optionalVersion, useBetaChannel: localVersion.IsPrerelease())) { whichModel = optionalModEntryVersionModel; updateVersion = optionalVersion; } else if (IsValidUpdate(localVersion, unofficialVersion, useBetaChannel: true)) //Different from SMAPI: useBetaChannel is always true { whichModel = unofficialModEntryVersionModel; unofficialModEntryVersionModel.Url = $"https://stardewvalleywiki.com/Modding:SMAPI_compatibility#{GenerateAnchor(modManifest.Name)}"; updateVersion = unofficialVersion; } else { if (updateCheckModel.Errors.Length > 0) { status = UpdateStatus.Error; error = updateCheckModel.Errors[0]; updateVersion = modManifest.Version; } else { updateVersion = modManifest.Version; status = UpdateStatus.UpToDate; if (latestVersion != null && (latestVersion.Equals(localVersion) || IsValidUpdate(latestVersion, localVersion, true))) { whichModel = latestModEntryVersionModel; } else if (optionalVersion != null && (optionalVersion.Equals(localVersion) || IsValidUpdate(optionalVersion, localVersion, true))) { whichModel = optionalModEntryVersionModel; } else if (unofficialVersion != null && (unofficialVersion.Equals(localVersion) || IsValidUpdate(unofficialVersion, localVersion, true))) { whichModel = unofficialModEntryVersionModel; } else { status = UpdateStatus.Skipped; } } } statuses.Add(new ModStatus(status, modManifest.UniqueID, modManifest.Name, modManifest.Author, whichModel?.Url ?? "", modManifest.Version.ToString(), updateVersion?.ToString() ?? "", error)); } return(true); }