// SPDX-FileCopyrightText: 2023 flowln // // SPDX-License-Identifier: GPL-3.0-only #include "ModrinthAPI.h" #include "Application.h" #include "Json.h" #include "net/ApiDownload.h" #include "net/ApiUpload.h" #include "net/NetJob.h" std::pair ModrinthAPI::currentVersion(const QString& hash, const QString& hash_format) const { auto netJob = makeShared(QString("Modrinth::GetCurrentVersion"), APPLICATION->network()); auto [action, response] = Net::ApiDownload::makeByteArray(QString(BuildConfig.MODRINTH_PROD_URL + "/version_file/%1?algorithm=%2").arg(hash, hash_format)); netJob->addNetAction(action); return { netJob, response }; } std::pair ModrinthAPI::currentVersions(const QStringList& hashes, QString hash_format) const { auto netJob = makeShared(QString("Modrinth::GetCurrentVersions"), APPLICATION->network()); QJsonObject body_obj; Json::writeStringList(body_obj, "hashes", hashes); Json::writeString(body_obj, "algorithm", hash_format); QJsonDocument body(body_obj); auto body_raw = body.toJson(); auto [action, response] = Net::ApiUpload::makeByteArray(QString(BuildConfig.MODRINTH_PROD_URL + "/version_files"), body_raw); netJob->addNetAction(action); netJob->setAskRetry(false); return { netJob, response }; } std::pair ModrinthAPI::latestVersion(const QString& hash, const QString& hash_format, std::optional> mcVersions, std::optional loaders) const { auto netJob = makeShared(QString("Modrinth::GetLatestVersion"), APPLICATION->network()); QJsonObject body_obj; if (loaders.has_value()) { Json::writeStringList(body_obj, "loaders", getModLoaderStrings(loaders.value())); } if (mcVersions.has_value()) { QStringList game_versions; for (auto& ver : mcVersions.value()) { game_versions.append(mapMCVersionToModrinth(ver)); } Json::writeStringList(body_obj, "game_versions", game_versions); } QJsonDocument body(body_obj); auto body_raw = body.toJson(); auto [action, response] = Net::ApiUpload::makeByteArray( QString(BuildConfig.MODRINTH_PROD_URL + "/version_file/%1/update?algorithm=%2").arg(hash, hash_format), body_raw); netJob->addNetAction(action); return { netJob, response }; } std::pair ModrinthAPI::latestVersions(const QStringList& hashes, const QString& hash_format, std::optional> mcVersions, std::optional loaders) const { auto netJob = makeShared(QString("Modrinth::GetLatestVersions"), APPLICATION->network()); QJsonObject body_obj; Json::writeStringList(body_obj, "hashes", hashes); Json::writeString(body_obj, "algorithm", hash_format); if (loaders.has_value()) { Json::writeStringList(body_obj, "loaders", getModLoaderStrings(loaders.value())); } if (mcVersions.has_value()) { QStringList game_versions; for (auto& ver : mcVersions.value()) { game_versions.append(mapMCVersionToModrinth(ver)); } Json::writeStringList(body_obj, "game_versions", game_versions); } QJsonDocument body(body_obj); auto body_raw = body.toJson(); auto [action, response] = Net::ApiUpload::makeByteArray(QString(BuildConfig.MODRINTH_PROD_URL + "/version_files/update"), body_raw); netJob->addNetAction(action); return { netJob, response }; } std::pair ModrinthAPI::getProjects(QStringList addonIds) const { auto netJob = makeShared(QString("Modrinth::GetProjects"), APPLICATION->network()); auto searchUrl = getMultipleModInfoURL(addonIds); auto [action, response] = Net::ApiDownload::makeByteArray(QUrl(searchUrl)); netJob->addNetAction(action); return { netJob, response }; } QList ModrinthAPI::getSortingMethods() const { // https://docs.modrinth.com/api-spec/#tag/projects/operation/searchProjects return { { .index = 1, .name = "relevance", .readable_name = QObject::tr("Sort by Relevance") }, { .index = 2, .name = "downloads", .readable_name = QObject::tr("Sort by Downloads") }, { .index = 3, .name = "follows", .readable_name = QObject::tr("Sort by Follows") }, { .index = 4, .name = "newest", .readable_name = QObject::tr("Sort by Newest") }, { .index = 5, .name = "updated", .readable_name = QObject::tr("Sort by Last Updated") } }; } std::pair ModrinthAPI::getModCategories() { auto netJob = makeShared(QString("Modrinth::GetCategories"), APPLICATION->network()); auto [action, response] = Net::ApiDownload::makeByteArray(QUrl(BuildConfig.MODRINTH_PROD_URL + "/tag/category")); netJob->addNetAction(action); QObject::connect(netJob.get(), &Task::failed, [](const QString& msg) { qDebug() << "Modrinth failed to get categories:" << msg; }); return { netJob, response }; } QList ModrinthAPI::loadCategories(const QByteArray& response, const QString& projectType) { QList categories; QJsonParseError parse_error{}; QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error); if (parse_error.error != QJsonParseError::NoError) { qWarning() << "Error while parsing JSON response from categories at" << parse_error.offset << "reason:" << parse_error.errorString(); qWarning() << *response; return categories; } try { auto arr = Json::requireArray(doc); for (auto val : arr) { auto cat = Json::requireObject(val); auto name = Json::requireString(cat, "name"); if (cat["project_type"].toString() == projectType) { categories.push_back({ .name = name, .id = name }); } } } catch (Json::JsonException& e) { qCritical() << "Failed to parse response from a version request."; qCritical() << e.what(); qDebug() << doc; } return categories; } QList ModrinthAPI::loadModCategories(const QByteArray& response) { return loadCategories(response, "mod"); };