This commit is contained in:
wentao DAI 2026-06-26 11:59:26 -04:00 committed by GitHub
commit 3f08b621bb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 235 additions and 4 deletions

View file

@ -85,6 +85,14 @@
<string>curseforge</string>
</array>
</dict>
<dict>
<key>CFBundleURLName</key>
<string>Modrinth</string>
<key>CFBundleURLSchemes</key>
<array>
<string>modrinth</string>
</array>
</dict>
<dict>
<key>CFBundleURLName</key>
<string>${Launcher_Name}</string>

View file

@ -561,6 +561,8 @@ set(MODRINTH_SOURCES
modplatform/modrinth/ModrinthInstanceCreationTask.h
modplatform/modrinth/ModrinthPackExportTask.cpp
modplatform/modrinth/ModrinthPackExportTask.h
modplatform/modrinth/ModrinthUrl.cpp
modplatform/modrinth/ModrinthUrl.h
)
set(PACKWIZ_SOURCES

View file

@ -0,0 +1,45 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "ModrinthUrl.h"
namespace Modrinth {
auto parseModpackLink(const QUrl& url) -> std::optional<ParsedModpackLink>
{
if (url.scheme().compare("modrinth", Qt::CaseInsensitive) != 0) {
return std::nullopt;
}
if (url.host().compare("modpack", Qt::CaseInsensitive) != 0) {
return std::nullopt;
}
const auto segments = QUrl::fromPercentEncoding(url.path().toUtf8()).split('/', Qt::SkipEmptyParts);
if (segments.size() != 1) {
return std::nullopt;
}
const auto slug = segments.constFirst().trimmed();
if (slug.isEmpty()) {
return std::nullopt;
}
return ParsedModpackLink{ slug };
}
} // namespace Modrinth

View file

@ -0,0 +1,32 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <QUrl>
#include <optional>
namespace Modrinth {
struct ParsedModpackLink {
QString slug;
};
auto parseModpackLink(const QUrl& url) -> std::optional<ParsedModpackLink>;
} // namespace Modrinth

View file

@ -125,6 +125,7 @@
#include "modplatform/ModIndex.h"
#include "modplatform/flame/FlameAPI.h"
#include "modplatform/flame/FlameModIndex.h"
#include "modplatform/modrinth/ModrinthUrl.h"
#include "KonamiCode.h"
@ -896,7 +897,7 @@ void MainWindow::on_actionCopyInstance_triggered()
runModalTask(task.get());
}
void MainWindow::addInstance(const QString& url, const QMap<QString, QString>& extra_info)
void MainWindow::addInstance(const QString& url, const QMap<QString, QString>& extra_info, const QString& modrinthSlug)
{
QString groupName;
do {
@ -917,6 +918,9 @@ void MainWindow::addInstance(const QString& url, const QMap<QString, QString>& e
}
NewInstanceDialog newInstDlg(groupName, url, extra_info, this);
if (!modrinthSlug.isEmpty()) {
newInstDlg.openModrinthModpack(modrinthSlug);
}
if (!newInstDlg.exec())
return;
@ -950,6 +954,19 @@ void MainWindow::processURLs(QList<QUrl> urls)
QMap<QString, QString> extra_info;
QUrl local_url;
if (!url.isLocalFile()) { // download the remote resource and identify
if (url.scheme().compare("modrinth", Qt::CaseInsensitive) == 0) {
if (auto modpackLink = Modrinth::parseModpackLink(url)) {
addInstance(QString(), {}, modpackLink->slug);
} else {
CustomMessageBox::selectable(
this, tr("Error"),
tr("Unsupported Modrinth link.\n\nPrism Launcher currently only supports modpack links such as "
"modrinth://modpack/fabulously-optimized."),
QMessageBox::Critical)
->show();
}
continue;
}
const bool isExternalURLImport = (url.host().toLower() == "import") || (url.path().startsWith("/import", Qt::CaseInsensitive));

View file

@ -221,7 +221,8 @@ class MainWindow : public QMainWindow {
private:
void retranslateUi();
void addInstance(const QString& url = QString(), const QMap<QString, QString>& extra_info = {});
void addInstance(const QString& url = QString(), const QMap<QString, QString>& extra_info = {},
const QString& modrinthSlug = QString());
void activateInstance(BaseInstance* instance);
void setCatBackground(bool enabled);
void updateInstanceToolIcon(QString new_icon);

View file

@ -250,6 +250,23 @@ void NewInstanceDialog::setSuggestedIcon(const QString& key)
ui->iconButton->setIcon(icon);
}
void NewInstanceDialog::openModrinthModpack(const QString& slug)
{
auto trimmedSlug = slug.trimmed();
if (trimmedSlug.isEmpty()) {
return;
}
auto* page = dynamic_cast<ModrinthPage*>(m_container->getPage("modrinth"));
if (!page) {
return;
}
m_searchTerm = trimmedSlug;
page->openProjectBySlug(trimmedSlug);
m_container->selectPage(page->id());
}
InstanceTask* NewInstanceDialog::extractTask()
{
InstanceTask* extracted = creationTask.release();

View file

@ -65,6 +65,7 @@ class NewInstanceDialog : public QDialog, public BasePageProvider {
void setSuggestedPack(const QString& name, QString version, InstanceTask* task = nullptr);
void setSuggestedIconFromFile(const QString& path, const QString& name);
void setSuggestedIcon(const QString& key);
void openModrinthModpack(const QString& slug);
InstanceTask* extractTask();

View file

@ -56,6 +56,7 @@
#include <QComboBox>
#include <QKeyEvent>
#include <QPushButton>
#include <utility>
ModrinthPage::ModrinthPage(NewInstanceDialog* dialog, QWidget* parent)
: QWidget(parent), m_ui(new Ui::ModrinthPage), m_dialog(dialog), m_fetch_progress(this, false)
@ -328,6 +329,44 @@ void ModrinthPage::suggestCurrent()
}
}
void ModrinthPage::openProjectBySlug(QString slug)
{
m_requestedProjectSlug = std::move(slug).trimmed();
if (m_requestedProjectSlug.isEmpty()) {
return;
}
setSearchTerm(m_requestedProjectSlug);
}
void ModrinthPage::selectRequestedProject()
{
if (m_requestedProjectSlug.isEmpty()) {
return;
}
const auto requestedSlug = m_requestedProjectSlug;
m_requestedProjectSlug.clear();
for (int row = 0; row < m_model->rowCount({}); row++) {
const auto index = m_model->index(row, 0);
const auto pack = m_model->data(index, Qt::UserRole).value<ModPlatform::IndexedPack::Ptr>();
if (pack && pack->slug.compare(requestedSlug, Qt::CaseInsensitive) == 0) {
m_ui->packView->setCurrentIndex(index);
m_ui->packView->scrollTo(index);
return;
}
}
if (m_model->rowCount({}) == 1) {
const auto index = m_model->index(0, 0);
m_ui->packView->setCurrentIndex(index);
m_ui->packView->scrollTo(index);
}
}
void ModrinthPage::triggerSearch()
{
m_ui->packView->selectionModel()->setCurrentIndex({}, QItemSelectionModel::SelectionFlag::ClearAndSelect);
@ -335,8 +374,17 @@ void ModrinthPage::triggerSearch()
m_ui->packDescription->clear();
m_ui->versionSelectionBox->clear();
bool filterChanged = m_filterWidget->changed();
m_model->searchWithTerm(m_ui->searchEdit->text(), m_ui->sortByBox->currentIndex(), m_filterWidget->getFilter(), filterChanged);
const auto searchTerm = m_requestedProjectSlug.isEmpty() ? m_ui->searchEdit->text() : "#" + m_requestedProjectSlug;
m_model->searchWithTerm(searchTerm, m_ui->sortByBox->currentIndex(), m_filterWidget->getFilter(), filterChanged);
m_fetch_progress.watch(m_model->activeSearchJob().get());
if (!m_requestedProjectSlug.isEmpty()) {
if (m_model->hasActiveSearchJob()) {
connect(m_model->activeSearchJob().get(), &Task::finished, this, [this] { selectRequestedProject(); });
} else {
selectRequestedProject();
}
}
}
void ModrinthPage::onVersionSelectionChanged(int index)

View file

@ -72,6 +72,7 @@ class ModrinthPage : public QWidget, public ModpackProviderBasePage {
ModPlatform::IndexedPack::Ptr getCurrent() { return m_current; }
void suggestCurrent();
void openProjectBySlug(QString slug);
void updateUI();
@ -91,12 +92,15 @@ class ModrinthPage : public QWidget, public ModpackProviderBasePage {
void createFilterWidget();
private:
void selectRequestedProject();
Ui::ModrinthPage* m_ui;
NewInstanceDialog* m_dialog;
Modrinth::ModpackListModel* m_model;
ModPlatform::IndexedPack::Ptr m_current;
QString m_selectedVersion;
QString m_requestedProjectSlug;
ProgressWidget m_fetch_progress;

View file

@ -10,4 +10,4 @@ Icon=@Launcher_AppID@
Categories=Game;ActionGame;AdventureGame;Simulation;PackageManager;
Keywords=game;minecraft;mc;
StartupWMClass=@Launcher_CommonName@
MimeType=application/zip;application/x-modrinth-modpack+zip;x-scheme-handler/curseforge;x-scheme-handler/prismlauncher;x-scheme-handler/@Launcher_APP_BINARY_NAME@;
MimeType=application/zip;application/x-modrinth-modpack+zip;x-scheme-handler/curseforge;x-scheme-handler/modrinth;x-scheme-handler/prismlauncher;x-scheme-handler/@Launcher_APP_BINARY_NAME@;

View file

@ -394,6 +394,10 @@ Section "@Launcher_DisplayName@"
WriteRegStr HKCU Software\Classes\curseforge "URL Protocol" ""
WriteRegStr HKCU Software\Classes\curseforge\shell\open\command "" '"$INSTDIR\@Launcher_APP_BINARY_NAME@.exe" "%1"'
; Write the URL Handler into registry for modrinth
WriteRegStr HKCU Software\Classes\modrinth "URL Protocol" ""
WriteRegStr HKCU Software\Classes\modrinth\shell\open\command "" '"$INSTDIR\@Launcher_APP_BINARY_NAME@.exe" "%1"'
; Write the URL Handler into registry for prismlauncher
WriteRegStr HKCU Software\Classes\@Launcher_APP_BINARY_NAME@ "URL Protocol" ""
WriteRegStr HKCU Software\Classes\@Launcher_APP_BINARY_NAME@\shell\open\command "" '"$INSTDIR\@Launcher_APP_BINARY_NAME@.exe" "%1"'

View file

@ -54,6 +54,9 @@ ecm_add_test(Index_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}:
ecm_add_test(Version_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test
TEST_NAME Version)
ecm_add_test(ModrinthUrl_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test
TEST_NAME ModrinthUrl)
ecm_add_test(MetaComponentParse_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test
TEST_NAME MetaComponentParse)

View file

@ -0,0 +1,49 @@
#include <QTest>
#include <modplatform/modrinth/ModrinthUrl.h>
class ModrinthUrlTest : public QObject {
Q_OBJECT
private slots:
void parseModpackLink_data()
{
QTest::addColumn<QString>("link");
QTest::addColumn<QString>("slug");
QTest::newRow("official") << "modrinth://modpack/fabulously-optimized" << "fabulously-optimized";
QTest::newRow("trailing slash") << "modrinth://modpack/fabulously-optimized/" << "fabulously-optimized";
QTest::newRow("case insensitive host") << "modrinth://MODPACK/fo" << "fo";
}
void parseModpackLink()
{
QFETCH(QString, link);
QFETCH(QString, slug);
auto parsed = Modrinth::parseModpackLink(QUrl(link));
QVERIFY(parsed.has_value());
QCOMPARE(parsed->slug, slug);
}
void rejectInvalidLinks_data()
{
QTest::addColumn<QString>("link");
QTest::newRow("wrong scheme") << "https://modrinth.com/modpack/fabulously-optimized";
QTest::newRow("missing slug") << "modrinth://modpack/";
QTest::newRow("unsupported resource") << "modrinth://mod/sodium";
QTest::newRow("extra path") << "modrinth://modpack/fabulously-optimized/version/latest";
}
void rejectInvalidLinks()
{
QFETCH(QString, link);
QVERIFY(!Modrinth::parseModpackLink(QUrl(link)).has_value());
}
};
QTEST_GUILESS_MAIN(ModrinthUrlTest)
#include "ModrinthUrl_test.moc"