From 0e52112016fe9942f7448cd83914f6266904c311 Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 3 Jun 2022 19:04:49 -0300 Subject: feat: add some api calls to modrinth Calls added: - Get version from hash - Get versions from hashes - Latest version of a project from a hash, loader(s), and game version(s) - Latest versions of multiple project from hashes, loader(s), and game version(s) Some of those are not used yet, but may be of use later on, so we have it if we need it :) Signed-off-by: flow --- launcher/modplatform/ModIndex.h | 1 + 1 file changed, 1 insertion(+) (limited to 'launcher/modplatform/ModIndex.h') diff --git a/launcher/modplatform/ModIndex.h b/launcher/modplatform/ModIndex.h index c27643af..459eb261 100644 --- a/launcher/modplatform/ModIndex.h +++ b/launcher/modplatform/ModIndex.h @@ -61,6 +61,7 @@ struct IndexedVersion { QVector loaders = {}; QString hash_type; QString hash; + bool is_preferred = true; }; struct ExtraPackData { -- cgit From c3f6c3dd8228b23b403af6b091a888ea3cc436ca Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 3 Jun 2022 20:45:44 -0300 Subject: feat: add changelog to mod providers The Modrinth changelog is fairly straight-forward, as it's given to us directly with the API call we already did. Flame, on the other hand, requires us to do another call to get the changelog, so it can introduce quite a heavy performance impact. This way, we make it optional to get such changelog. Signed-off-by: flow --- launcher/modplatform/ModIndex.h | 1 + launcher/modplatform/flame/FlameModIndex.cpp | 7 ++++++- launcher/modplatform/flame/FlameModIndex.h | 2 +- launcher/modplatform/modrinth/ModrinthPackIndex.cpp | 1 + 4 files changed, 9 insertions(+), 2 deletions(-) (limited to 'launcher/modplatform/ModIndex.h') diff --git a/launcher/modplatform/ModIndex.h b/launcher/modplatform/ModIndex.h index 459eb261..966082ab 100644 --- a/launcher/modplatform/ModIndex.h +++ b/launcher/modplatform/ModIndex.h @@ -62,6 +62,7 @@ struct IndexedVersion { QString hash_type; QString hash; bool is_preferred = true; + QString changelog; }; struct ExtraPackData { diff --git a/launcher/modplatform/flame/FlameModIndex.cpp b/launcher/modplatform/flame/FlameModIndex.cpp index 0ad1d4ba..a3222f44 100644 --- a/launcher/modplatform/flame/FlameModIndex.cpp +++ b/launcher/modplatform/flame/FlameModIndex.cpp @@ -7,6 +7,7 @@ #include "net/NetJob.h" static ModPlatform::ProviderCapabilities ProviderCaps; +static FlameAPI api; void FlameMod::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj) { @@ -91,7 +92,7 @@ void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, pack.versionsLoaded = true; } -auto FlameMod::loadIndexedPackVersion(QJsonObject& obj) -> ModPlatform::IndexedVersion +auto FlameMod::loadIndexedPackVersion(QJsonObject& obj, bool load_changelog) -> ModPlatform::IndexedVersion { auto versionArray = Json::requireArray(obj, "gameVersions"); if (versionArray.isEmpty()) { @@ -124,5 +125,9 @@ auto FlameMod::loadIndexedPackVersion(QJsonObject& obj) -> ModPlatform::IndexedV break; } } + + if(load_changelog) + file.changelog = api.getModFileChangelog(file.addonId.toInt(), file.fileId.toInt()); + return file; } diff --git a/launcher/modplatform/flame/FlameModIndex.h b/launcher/modplatform/flame/FlameModIndex.h index 9c6c1c6c..a839dd83 100644 --- a/launcher/modplatform/flame/FlameModIndex.h +++ b/launcher/modplatform/flame/FlameModIndex.h @@ -17,6 +17,6 @@ void loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr, const shared_qobject_ptr& network, BaseInstance* inst); -auto loadIndexedPackVersion(QJsonObject& obj) -> ModPlatform::IndexedVersion; +auto loadIndexedPackVersion(QJsonObject& obj, bool load_changelog = false) -> ModPlatform::IndexedVersion; } // namespace FlameMod diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp index 4e738819..9736e861 100644 --- a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp @@ -130,6 +130,7 @@ auto Modrinth::loadIndexedPackVersion(QJsonObject &obj, QString preferred_hash_t file.loaders.append(loader.toString()); } file.version = Json::requireString(obj, "name"); + file.changelog = Json::requireString(obj, "changelog"); auto files = Json::requireArray(obj, "files"); int i = 0; -- cgit From b8b71c7dd29fbdc6c98d60ec54c57cff74f4cbfd Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 3 Jun 2022 21:26:26 -0300 Subject: feat: add mod update check tasks Those tasks take a list of mods and check on the mod providers for updates. They assume that the mods have metadata already. Signed-off-by: flow --- launcher/CMakeLists.txt | 6 + launcher/modplatform/CheckUpdateTask.h | 51 +++++ launcher/modplatform/ModIndex.h | 1 + launcher/modplatform/flame/FlameCheckUpdate.cpp | 228 +++++++++++++++++++++ launcher/modplatform/flame/FlameCheckUpdate.h | 25 +++ .../modplatform/modrinth/ModrinthCheckUpdate.cpp | 163 +++++++++++++++ .../modplatform/modrinth/ModrinthCheckUpdate.h | 23 +++ .../modplatform/modrinth/ModrinthPackIndex.cpp | 3 +- 8 files changed, 499 insertions(+), 1 deletion(-) create mode 100644 launcher/modplatform/CheckUpdateTask.h create mode 100644 launcher/modplatform/flame/FlameCheckUpdate.cpp create mode 100644 launcher/modplatform/flame/FlameCheckUpdate.h create mode 100644 launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp create mode 100644 launcher/modplatform/modrinth/ModrinthCheckUpdate.h (limited to 'launcher/modplatform/ModIndex.h') diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 25546c38..2313f0e4 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -485,6 +485,8 @@ set(API_SOURCES modplatform/EnsureMetadataTask.h modplatform/EnsureMetadataTask.cpp + modplatform/CheckUpdateTask.h + modplatform/flame/FlameAPI.h modplatform/flame/FlameAPI.cpp modplatform/modrinth/ModrinthAPI.h @@ -514,6 +516,8 @@ set(FLAME_SOURCES modplatform/flame/PackManifest.cpp modplatform/flame/FileResolvingTask.h modplatform/flame/FileResolvingTask.cpp + modplatform/flame/FlameCheckUpdate.cpp + modplatform/flame/FlameCheckUpdate.h ) set(MODRINTH_SOURCES @@ -521,6 +525,8 @@ set(MODRINTH_SOURCES modplatform/modrinth/ModrinthPackIndex.h modplatform/modrinth/ModrinthPackManifest.cpp modplatform/modrinth/ModrinthPackManifest.h + modplatform/modrinth/ModrinthCheckUpdate.cpp + modplatform/modrinth/ModrinthCheckUpdate.h ) set(MODPACKSCH_SOURCES diff --git a/launcher/modplatform/CheckUpdateTask.h b/launcher/modplatform/CheckUpdateTask.h new file mode 100644 index 00000000..8c701e46 --- /dev/null +++ b/launcher/modplatform/CheckUpdateTask.h @@ -0,0 +1,51 @@ +#pragma once + +#include "minecraft/mod/Mod.h" +#include "modplatform/ModAPI.h" +#include "modplatform/ModIndex.h" +#include "tasks/Task.h" + +class ModDownloadTask; +class ModFolderModel; + +class CheckUpdateTask : public Task { + Q_OBJECT + + public: + CheckUpdateTask(std::list& mods, std::list& mcVersions, ModAPI::ModLoaderTypes loaders, std::shared_ptr mods_folder) + : m_mods(mods), m_game_versions(mcVersions), m_loaders(loaders), m_mods_folder(mods_folder) {}; + + struct UpdatableMod { + QString name; + QString old_hash; + QString old_version; + QString new_version; + QString changelog; + ModPlatform::Provider provider; + ModDownloadTask* download; + + public: + UpdatableMod(QString name, QString old_h, QString old_v, QString new_v, QString changelog, ModPlatform::Provider p, ModDownloadTask* t) + : name(name), old_hash(old_h), old_version(old_v), new_version(new_v), changelog(changelog), provider(p), download(t) + {} + }; + + auto getUpdatable() -> std::vector&& { return std::move(m_updatable); } + + public slots: + bool abort() override = 0; + + protected slots: + void executeTask() override = 0; + + signals: + void checkFailed(Mod failed, QString reason, QUrl recover_url = {}); + + protected: + std::list& m_mods; + std::list& m_game_versions; + ModAPI::ModLoaderTypes m_loaders; + std::shared_ptr m_mods_folder; + + std::vector m_updatable; +}; diff --git a/launcher/modplatform/ModIndex.h b/launcher/modplatform/ModIndex.h index 966082ab..f8ef211e 100644 --- a/launcher/modplatform/ModIndex.h +++ b/launcher/modplatform/ModIndex.h @@ -54,6 +54,7 @@ struct IndexedVersion { QVariant addonId; QVariant fileId; QString version; + QString version_number = {}; QVector mcVersion; QString downloadUrl; QString date; diff --git a/launcher/modplatform/flame/FlameCheckUpdate.cpp b/launcher/modplatform/flame/FlameCheckUpdate.cpp new file mode 100644 index 00000000..f1983fa4 --- /dev/null +++ b/launcher/modplatform/flame/FlameCheckUpdate.cpp @@ -0,0 +1,228 @@ +#include "FlameCheckUpdate.h" +#include "FlameAPI.h" +#include "FlameModIndex.h" + +#include + +#include "FileSystem.h" +#include "Json.h" + +#include "ModDownloadTask.h" + +static FlameAPI api; +static ModPlatform::ProviderCapabilities ProviderCaps; + +bool FlameCheckUpdate::abort() +{ + m_was_aborted = true; + if (m_net_job) + return m_net_job->abort(); + return true; +} + +ModPlatform::IndexedPack getProjectInfo(ModPlatform::IndexedVersion& ver_info) +{ + ModPlatform::IndexedPack pack; + + QEventLoop loop; + + auto get_project_job = new NetJob("Flame::GetProjectJob", APPLICATION->network()); + + auto response = new QByteArray(); + auto url = QString("https://api.curseforge.com/v1/mods/%1").arg(ver_info.addonId.toString()); + auto dl = Net::Download::makeByteArray(url, response); + get_project_job->addNetAction(dl); + + QObject::connect(get_project_job, &NetJob::succeeded, [response, &pack]() { + QJsonParseError parse_error{}; + QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); + if (parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response from FlameCheckUpdate at " << parse_error.offset + << " reason: " << parse_error.errorString(); + qWarning() << *response; + return; + } + + try { + auto doc_obj = Json::requireObject(doc); + auto data_obj = Json::requireObject(doc_obj, "data"); + FlameMod::loadIndexedPack(pack, data_obj); + } catch (Json::JsonException& e) { + qWarning() << e.cause(); + qDebug() << doc; + } + }); + + QObject::connect(get_project_job, &NetJob::finished, [&loop, get_project_job] { + get_project_job->deleteLater(); + loop.quit(); + }); + + get_project_job->start(); + loop.exec(); + + return pack; +} + +/* Check for update: + * - Get latest version available + * - Compare hash of the latest version with the current hash + * - If equal, no updates, else, there's updates, so add to the list + * */ +void FlameCheckUpdate::executeTask() +{ + setStatus(tr("Preparing mods for CurseForge...")); + setProgress(0, 5); + + QHash mappings; + + // Create all hashes + QStringList hashes; + std::list murmur_hashes; + + auto best_hash_type = ProviderCaps.hashType(ModPlatform::Provider::FLAME).first(); + for (auto mod : m_mods) { + auto hash = mod.metadata()->hash; + + QByteArray jar_data; + + try { + jar_data = FS::read(mod.fileinfo().absoluteFilePath()); + } catch (FS::FileSystemException& e) { + qCritical() << QString("Failed to open / read JAR file of %1").arg(mod.name()); + qCritical() << QString("Reason: ") << e.cause(); + + failed(e.what()); + return; + } + + QByteArray jar_data_treated; + for (char c : jar_data) { + // CF-specific + if (!(c == 9 || c == 10 || c == 13 || c == 32)) + jar_data_treated.push_back(c); + } + + auto murmur_hash = MurmurHash2(jar_data_treated, jar_data_treated.length()); + murmur_hashes.emplace_back(murmur_hash); + + // Sadly the API can only handle one hash type per call, se we + // need to generate a new hash if the current one is innadequate + // (though it will rarely happen, if at all) + if (mod.metadata()->hash_format != best_hash_type) + hash = QString(ProviderCaps.hash(ModPlatform::Provider::FLAME, jar_data, best_hash_type).toHex()); + + hashes.append(hash); + mappings.insert(hash, mod); + } + + auto* response = new QByteArray(); + auto job = api.matchFingerprints(murmur_hashes, response); + + QEventLoop lock; + + connect(job.get(), &Task::succeeded, this, [this, response, &mappings] { + QJsonParseError parse_error{}; + QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); + if (parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response from FlameCheckUpdate at " << parse_error.offset + << " reason: " << parse_error.errorString(); + qWarning() << *response; + + failed(parse_error.errorString()); + return; + } + + setStatus(tr("Parsing the first API response from CurseForge...")); + setProgress(2, 5); + + try { + auto doc_obj = Json::requireObject(doc); + auto data_obj = Json::ensureObject(doc_obj, "data"); + auto match_arr = Json::ensureArray(data_obj, "exactMatches"); + for (auto match : match_arr) { + auto match_obj = Json::ensureObject(match); + + ModPlatform::IndexedVersion current_ver; + try { + auto file_obj = Json::requireObject(match_obj, "file"); + current_ver = FlameMod::loadIndexedPackVersion(file_obj); + } catch (Json::JsonException& e) { + qCritical() << "Error while parsing Flame indexed version"; + qCritical() << e.what(); + failed(tr("An error occured while parsing a CurseForge indexed version!")); + return; + } + + auto mod_iter = mappings.find(current_ver.hash); + if (mod_iter == mappings.end()) { + qCritical() << "Failed to remap mod from Flame!"; + continue; + } + + auto mod = mod_iter.value(); + + setStatus(tr("Waiting for the API response from CurseForge for '%1'...").arg(mod.name())); + setProgress(3, 5); + + auto latest_ver = api.getLatestVersion({ current_ver.addonId.toString(), m_game_versions, m_loaders }); + + // Check if we were aborted while getting the latest version + if (m_was_aborted) { + aborted(); + return; + } + + setStatus(tr("Parsing the API response from CurseForge for '%1'...").arg(mod.name())); + setProgress(4, 5); + + if (!latest_ver.addonId.isValid()) { + emit checkFailed( + mod, + tr("No valid version found for this mod. It's probably unavailable for the current game version / mod loader.")); + continue; + } + + if (latest_ver.downloadUrl.isEmpty() && latest_ver.fileId != current_ver.fileId) { + auto pack = getProjectInfo(latest_ver); + auto recover_url = QString("%1/download/%2").arg(pack.websiteUrl, latest_ver.fileId.toString()); + emit checkFailed(mod, tr("Mod has a new update available, but is opted-out on CurseForge"), recover_url); + + continue; + } + + if (!latest_ver.hash.isEmpty() && current_ver.hash != latest_ver.hash) { + // Fake pack with the necessary info to pass to the download task :) + ModPlatform::IndexedPack pack; + pack.name = mod.name(); + pack.addonId = mod.metadata()->project_id; + pack.websiteUrl = mod.homeurl(); + for (auto& author : mod.authors()) + pack.authors.append({ author }); + pack.description = mod.description(); + pack.provider = ModPlatform::Provider::FLAME; + + auto download_task = new ModDownloadTask(pack, latest_ver, m_mods_folder); + m_updatable.emplace_back(mod.name(), current_ver.hash, current_ver.version, latest_ver.version, + api.getModFileChangelog(latest_ver.addonId.toInt(), latest_ver.fileId.toInt()), + ModPlatform::Provider::FLAME, download_task); + } + } + + } catch (Json::JsonException& e) { + failed(e.cause() + " : " + e.what()); + } + }); + + connect(job.get(), &Task::finished, &lock, &QEventLoop::quit); + + setStatus(tr("Waiting for the first API response from CurseForge...")); + setProgress(1, 5); + + m_net_job = job.get(); + job->start(); + + lock.exec(); + + emitSucceeded(); +} diff --git a/launcher/modplatform/flame/FlameCheckUpdate.h b/launcher/modplatform/flame/FlameCheckUpdate.h new file mode 100644 index 00000000..f068f08f --- /dev/null +++ b/launcher/modplatform/flame/FlameCheckUpdate.h @@ -0,0 +1,25 @@ +#pragma once + +#include "Application.h" +#include "modplatform/CheckUpdateTask.h" +#include "net/NetJob.h" + +class FlameCheckUpdate : public CheckUpdateTask { + Q_OBJECT + + public: + FlameCheckUpdate(std::list& mods, std::list& mcVersions, ModAPI::ModLoaderTypes loaders, std::shared_ptr mods_folder) + : CheckUpdateTask(mods, mcVersions, loaders, mods_folder) + {} + + public slots: + bool abort() override; + + protected slots: + void executeTask() override; + + private: + NetJob* m_net_job = nullptr; + + bool m_was_aborted = false; +}; diff --git a/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp b/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp new file mode 100644 index 00000000..81a2652a --- /dev/null +++ b/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp @@ -0,0 +1,163 @@ +#include "ModrinthCheckUpdate.h" +#include "ModrinthAPI.h" +#include "ModrinthPackIndex.h" + +#include "FileSystem.h" +#include "Json.h" + +#include "ModDownloadTask.h" + +static ModrinthAPI api; +static ModPlatform::ProviderCapabilities ProviderCaps; + +bool ModrinthCheckUpdate::abort() +{ + if (m_net_job) + return m_net_job->abort(); + return true; +} + +/* Check for update: + * - Get latest version available + * - Compare hash of the latest version with the current hash + * - If equal, no updates, else, there's updates, so add to the list + * */ +void ModrinthCheckUpdate::executeTask() +{ + setStatus(tr("Preparing mods for Modrinth...")); + setProgress(0, 3); + + QHash mappings; + + // Create all hashes + QStringList hashes; + auto best_hash_type = ProviderCaps.hashType(ModPlatform::Provider::MODRINTH).first(); + for (auto mod : m_mods) { + auto hash = mod.metadata()->hash; + + // Sadly the API can only handle one hash type per call, se we + // need to generate a new hash if the current one is innadequate + // (though it will rarely happen, if at all) + if (mod.metadata()->hash_format != best_hash_type) { + QByteArray jar_data; + + try { + jar_data = FS::read(mod.fileinfo().absoluteFilePath()); + } catch (FS::FileSystemException& e) { + qCritical() << QString("Failed to open / read JAR file of %1").arg(mod.name()); + qCritical() << QString("Reason: ") << e.cause(); + + failed(e.what()); + return; + } + + hash = QString(ProviderCaps.hash(ModPlatform::Provider::MODRINTH, jar_data, best_hash_type).toHex()); + } + + hashes.append(hash); + mappings.insert(hash, mod); + } + + auto* response = new QByteArray(); + auto job = api.latestVersions(hashes, best_hash_type, m_game_versions, m_loaders, response); + + QEventLoop lock; + + connect(job.get(), &Task::succeeded, this, [this, response, &mappings, best_hash_type, job] { + QJsonParseError parse_error{}; + QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); + if (parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response from ModrinthCheckUpdate at " << parse_error.offset + << " reason: " << parse_error.errorString(); + qWarning() << *response; + + failed(parse_error.errorString()); + return; + } + + setStatus(tr("Parsing the API response from Modrinth...")); + setProgress(2, 3); + + try { + for (auto hash : mappings.keys()) { + auto project_obj = doc[hash].toObject(); + if (project_obj.isEmpty()) { + qDebug() << "Mod " << mappings.find(hash).value().name() << " got an empty response."; + qDebug() << "Hash: " << hash; + + emit checkFailed(mappings.find(hash).value(), tr("Couldn't find mod in Modrinth")); + + continue; + } + + // Sometimes a version may have multiple files, one with "forge" and one with "fabric", + // so we may want to filter it + QString loader_filter; + static auto flags = { ModAPI::ModLoaderType::Forge, ModAPI::ModLoaderType::Fabric, ModAPI::ModLoaderType::Quilt }; + for (auto flag : flags) { + if (m_loaders.testFlag(flag)) { + loader_filter = api.getModLoaderString(flag); + break; + } + } + + // Currently, we rely on a couple heuristics to determine whether an update is actually available or not: + // - The file needs to be preferred: It is either the primary file, or the one found via (explicit) usage of the loader_filter + // - The version reported by the JAR is different from the version reported by the indexed version (it's usually the case) + // Such is the pain of having arbitrary files for a given version .-. + + auto project_ver = Modrinth::loadIndexedPackVersion(project_obj, best_hash_type, loader_filter); + if (project_ver.downloadUrl.isEmpty()) { + qCritical() << "Modrinth mod without download url!"; + qCritical() << project_ver.fileName; + + emit checkFailed(mappings.find(hash).value(), tr("Mod has an empty download URL")); + + continue; + } + + auto mod_iter = mappings.find(hash); + if (mod_iter == mappings.end()) { + qCritical() << "Failed to remap mod from Modrinth!"; + continue; + } + auto mod = *mod_iter; + + auto key = project_ver.hash; + if ((key != hash && project_ver.is_preferred) || (mod.status() == ModStatus::NotInstalled)) { + if (mod.version() == project_ver.version_number) + continue; + + // Fake pack with the necessary info to pass to the download task :) + ModPlatform::IndexedPack pack; + pack.name = mod.name(); + pack.addonId = mod.metadata()->project_id; + pack.websiteUrl = mod.homeurl(); + for (auto& author : mod.authors()) + pack.authors.append({ author }); + pack.description = mod.description(); + pack.provider = ModPlatform::Provider::MODRINTH; + + auto download_task = new ModDownloadTask(pack, project_ver, m_mods_folder); + + m_updatable.emplace_back(mod.name(), hash, mod.version(), project_ver.version_number, project_ver.changelog, + ModPlatform::Provider::MODRINTH, download_task); + } + } + } catch (Json::JsonException& e) { + failed(e.cause() + " : " + e.what()); + } + }); + + connect(job.get(), &Task::finished, &lock, &QEventLoop::quit); + + setStatus(tr("Waiting for the API response from Modrinth...")); + setProgress(1, 3); + + m_net_job = job.get(); + job->start(); + + lock.exec(); + + emitSucceeded(); +} diff --git a/launcher/modplatform/modrinth/ModrinthCheckUpdate.h b/launcher/modplatform/modrinth/ModrinthCheckUpdate.h new file mode 100644 index 00000000..7e685a6d --- /dev/null +++ b/launcher/modplatform/modrinth/ModrinthCheckUpdate.h @@ -0,0 +1,23 @@ +#pragma once + +#include "Application.h" +#include "modplatform/CheckUpdateTask.h" +#include "net/NetJob.h" + +class ModrinthCheckUpdate : public CheckUpdateTask { + Q_OBJECT + + public: + ModrinthCheckUpdate(std::list& mods, std::list& mcVersions, ModAPI::ModLoaderTypes loaders, std::shared_ptr mods_folder) + : CheckUpdateTask(mods, mcVersions, loaders, mods_folder) + {} + + public slots: + bool abort() override; + + protected slots: + void executeTask() override; + + private: + NetJob* m_net_job = nullptr; +}; diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp index 9736e861..1910c9be 100644 --- a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp @@ -130,6 +130,7 @@ auto Modrinth::loadIndexedPackVersion(QJsonObject &obj, QString preferred_hash_t file.loaders.append(loader.toString()); } file.version = Json::requireString(obj, "name"); + file.version_number = Json::requireString(obj, "version_number"); file.changelog = Json::requireString(obj, "changelog"); auto files = Json::requireArray(obj, "files"); @@ -159,7 +160,7 @@ auto Modrinth::loadIndexedPackVersion(QJsonObject &obj, QString preferred_hash_t if (parent.contains("url")) { file.downloadUrl = Json::requireString(parent, "url"); file.fileName = Json::requireString(parent, "filename"); - file.is_preferred = Json::requireBoolean(parent, "primary"); + file.is_preferred = Json::requireBoolean(parent, "primary") || (files.count() == 1); auto hash_list = Json::requireObject(parent, "hashes"); if (hash_list.contains(preferred_hash_type)) { -- cgit From a7648d60ce1d1567cd1c878aaa55dae3696a0210 Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 19 Jun 2022 14:31:44 -0300 Subject: fix: don't require non-essential items in mod index Also adds slug field. Signed-off-by: flow --- launcher/modplatform/ModIndex.h | 5 +++-- launcher/modplatform/flame/FlameModIndex.cpp | 9 +++++---- launcher/modplatform/modrinth/ModrinthPackIndex.cpp | 13 ++++++++----- 3 files changed, 16 insertions(+), 11 deletions(-) (limited to 'launcher/modplatform/ModIndex.h') diff --git a/launcher/modplatform/ModIndex.h b/launcher/modplatform/ModIndex.h index f8ef211e..dc297d03 100644 --- a/launcher/modplatform/ModIndex.h +++ b/launcher/modplatform/ModIndex.h @@ -55,11 +55,11 @@ struct IndexedVersion { QVariant fileId; QString version; QString version_number = {}; - QVector mcVersion; + QStringList mcVersion; QString downloadUrl; QString date; QString fileName; - QVector loaders = {}; + QStringList loaders = {}; QString hash_type; QString hash; bool is_preferred = true; @@ -79,6 +79,7 @@ struct IndexedPack { QVariant addonId; Provider provider; QString name; + QString slug; QString description; QList authors; QString logoName; diff --git a/launcher/modplatform/flame/FlameModIndex.cpp b/launcher/modplatform/flame/FlameModIndex.cpp index a3222f44..746018e2 100644 --- a/launcher/modplatform/flame/FlameModIndex.cpp +++ b/launcher/modplatform/flame/FlameModIndex.cpp @@ -14,14 +14,15 @@ void FlameMod::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj) pack.addonId = Json::requireInteger(obj, "id"); pack.provider = ModPlatform::Provider::FLAME; pack.name = Json::requireString(obj, "name"); + pack.slug = Json::requireString(obj, "slug"); pack.websiteUrl = Json::ensureString(Json::ensureObject(obj, "links"), "websiteUrl", ""); pack.description = Json::ensureString(obj, "summary", ""); - QJsonObject logo = Json::requireObject(obj, "logo"); - pack.logoName = Json::requireString(logo, "title"); - pack.logoUrl = Json::requireString(logo, "thumbnailUrl"); + QJsonObject logo = Json::ensureObject(obj, "logo"); + pack.logoName = Json::ensureString(logo, "title"); + pack.logoUrl = Json::ensureString(logo, "thumbnailUrl"); - auto authors = Json::requireArray(obj, "authors"); + auto authors = Json::ensureArray(obj, "authors"); for (auto authorIter : authors) { auto author = Json::requireObject(authorIter); ModPlatform::ModpackAuthor packAuthor; diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp index 1910c9be..e50dd96d 100644 --- a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp @@ -29,13 +29,16 @@ static ModPlatform::ProviderCapabilities ProviderCaps; void Modrinth::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj) { - pack.addonId = Json::requireString(obj, "project_id"); + pack.addonId = Json::ensureString(obj, "project_id"); + if (pack.addonId.toString().isEmpty()) + pack.addonId = Json::requireString(obj, "id"); + pack.provider = ModPlatform::Provider::MODRINTH; pack.name = Json::requireString(obj, "title"); - QString slug = Json::ensureString(obj, "slug", ""); - if (!slug.isEmpty()) - pack.websiteUrl = "https://modrinth.com/mod/" + Json::ensureString(obj, "slug", ""); + pack.slug = Json::ensureString(obj, "slug", ""); + if (!pack.slug.isEmpty()) + pack.websiteUrl = "https://modrinth.com/mod/" + pack.slug; else pack.websiteUrl = ""; @@ -45,7 +48,7 @@ void Modrinth::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj) pack.logoName = pack.addonId.toString(); ModPlatform::ModpackAuthor modAuthor; - modAuthor.name = Json::requireString(obj, "author"); + modAuthor.name = Json::ensureString(obj, "author", QObject::tr("No author(s)")); modAuthor.url = api.getAuthorURL(modAuthor.name); pack.authors.append(modAuthor); -- cgit