summaryrefslogtreecommitdiff
path: root/meshmc/launcher/ui/dialogs/ProfileSetupDialog.cpp
diff options
context:
space:
mode:
authorMehmet Samet Duman <yongdohyun@projecttick.org>2026-04-02 18:45:07 +0300
committerMehmet Samet Duman <yongdohyun@projecttick.org>2026-04-02 18:45:07 +0300
commit31b9a8949ed0a288143e23bf739f2eb64fdc63be (patch)
tree8a984fa143c38fccad461a77792d6864f3e82cd3 /meshmc/launcher/ui/dialogs/ProfileSetupDialog.cpp
parent934382c8a1ce738589dee9ee0f14e1cec812770e (diff)
parentfad6a1066616b69d7f5fef01178efdf014c59537 (diff)
downloadProject-Tick-31b9a8949ed0a288143e23bf739f2eb64fdc63be.tar.gz
Project-Tick-31b9a8949ed0a288143e23bf739f2eb64fdc63be.zip
Add 'meshmc/' from commit 'fad6a1066616b69d7f5fef01178efdf014c59537'
git-subtree-dir: meshmc git-subtree-mainline: 934382c8a1ce738589dee9ee0f14e1cec812770e git-subtree-split: fad6a1066616b69d7f5fef01178efdf014c59537
Diffstat (limited to 'meshmc/launcher/ui/dialogs/ProfileSetupDialog.cpp')
-rw-r--r--meshmc/launcher/ui/dialogs/ProfileSetupDialog.cpp298
1 files changed, 298 insertions, 0 deletions
diff --git a/meshmc/launcher/ui/dialogs/ProfileSetupDialog.cpp b/meshmc/launcher/ui/dialogs/ProfileSetupDialog.cpp
new file mode 100644
index 0000000000..9798af8199
--- /dev/null
+++ b/meshmc/launcher/ui/dialogs/ProfileSetupDialog.cpp
@@ -0,0 +1,298 @@
+/* SPDX-FileCopyrightText: 2026 Project Tick
+ * SPDX-FileContributor: Project Tick
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ * MeshMC - A Custom Launcher for Minecraft
+ * Copyright (C) 2026 Project Tick
+ *
+ * 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, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ProfileSetupDialog.h"
+#include "ui_ProfileSetupDialog.h"
+
+#include <QPushButton>
+#include <QAction>
+#include <QRegularExpressionValidator>
+#include <QJsonDocument>
+#include <QDebug>
+
+#include "ui/dialogs/ProgressDialog.h"
+
+#include <Application.h>
+#include "minecraft/auth/AuthRequest.h"
+#include "minecraft/auth/Parsers.h"
+
+ProfileSetupDialog::ProfileSetupDialog(MinecraftAccountPtr accountToSetup,
+ QWidget* parent)
+ : QDialog(parent), m_accountToSetup(accountToSetup),
+ ui(new Ui::ProfileSetupDialog)
+{
+ ui->setupUi(this);
+ ui->errorLabel->setVisible(false);
+
+ goodIcon = APPLICATION->getThemedIcon("status-good");
+ yellowIcon = APPLICATION->getThemedIcon("status-yellow");
+ badIcon = APPLICATION->getThemedIcon("status-bad");
+
+ QRegularExpression permittedNames("[a-zA-Z0-9_]{3,16}");
+ auto nameEdit = ui->nameEdit;
+ nameEdit->setValidator(new QRegularExpressionValidator(permittedNames));
+ nameEdit->setClearButtonEnabled(true);
+ validityAction =
+ nameEdit->addAction(yellowIcon, QLineEdit::LeadingPosition);
+ connect(nameEdit, &QLineEdit::textEdited, this,
+ &ProfileSetupDialog::nameEdited);
+
+ checkStartTimer.setSingleShot(true);
+ connect(&checkStartTimer, &QTimer::timeout, this,
+ &ProfileSetupDialog::startCheck);
+
+ setNameStatus(NameStatus::NotSet, QString());
+}
+
+ProfileSetupDialog::~ProfileSetupDialog()
+{
+ delete ui;
+}
+
+void ProfileSetupDialog::on_buttonBox_accepted()
+{
+ setupProfile(currentCheck);
+}
+
+void ProfileSetupDialog::on_buttonBox_rejected()
+{
+ reject();
+}
+
+void ProfileSetupDialog::setNameStatus(ProfileSetupDialog::NameStatus status,
+ QString errorString = QString())
+{
+ nameStatus = status;
+ auto okButton = ui->buttonBox->button(QDialogButtonBox::Ok);
+ switch (nameStatus) {
+ case NameStatus::Available: {
+ validityAction->setIcon(goodIcon);
+ okButton->setEnabled(true);
+ } break;
+ case NameStatus::NotSet:
+ case NameStatus::Pending:
+ validityAction->setIcon(yellowIcon);
+ okButton->setEnabled(false);
+ break;
+ case NameStatus::Exists:
+ case NameStatus::Error:
+ validityAction->setIcon(badIcon);
+ okButton->setEnabled(false);
+ break;
+ }
+ if (!errorString.isEmpty()) {
+ ui->errorLabel->setText(errorString);
+ ui->errorLabel->setVisible(true);
+ } else {
+ ui->errorLabel->setVisible(false);
+ }
+}
+
+void ProfileSetupDialog::nameEdited(const QString& name)
+{
+ if (!ui->nameEdit->hasAcceptableInput()) {
+ setNameStatus(NameStatus::NotSet,
+ tr("Name is too short - must be between 3 and 16 "
+ "characters long."));
+ return;
+ }
+ scheduleCheck(name);
+}
+
+void ProfileSetupDialog::scheduleCheck(const QString& name)
+{
+ queuedCheck = name;
+ setNameStatus(NameStatus::Pending);
+ checkStartTimer.start(1000);
+}
+
+void ProfileSetupDialog::startCheck()
+{
+ if (isChecking) {
+ return;
+ }
+ if (queuedCheck.isNull()) {
+ return;
+ }
+ checkName(queuedCheck);
+}
+
+void ProfileSetupDialog::checkName(const QString& name)
+{
+ if (isChecking) {
+ return;
+ }
+
+ currentCheck = name;
+ isChecking = true;
+
+ auto token = m_accountToSetup->accessToken();
+
+ auto url = QString("https://api.minecraftservices.com/minecraft/profile/"
+ "name/%1/available")
+ .arg(name);
+ QNetworkRequest request = QNetworkRequest(url);
+ request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
+ request.setRawHeader("Accept", "application/json");
+ request.setRawHeader("Authorization",
+ QString("Bearer %1").arg(token).toUtf8());
+
+ AuthRequest* requestor = new AuthRequest(this);
+ connect(requestor, &AuthRequest::finished, this,
+ &ProfileSetupDialog::checkFinished);
+ requestor->get(request);
+}
+
+void ProfileSetupDialog::checkFinished(
+ QNetworkReply::NetworkError error, QByteArray data,
+ QList<QNetworkReply::RawHeaderPair> headers)
+{
+ auto requestor = qobject_cast<AuthRequest*>(QObject::sender());
+ requestor->deleteLater();
+
+ if (error == QNetworkReply::NoError) {
+ auto doc = QJsonDocument::fromJson(data);
+ auto root = doc.object();
+ auto statusValue = root.value("status").toString("INVALID");
+ if (statusValue == "AVAILABLE") {
+ setNameStatus(NameStatus::Available);
+ } else if (statusValue == "DUPLICATE") {
+ setNameStatus(NameStatus::Exists,
+ tr("Minecraft profile with name %1 already exists.")
+ .arg(currentCheck));
+ } else if (statusValue == "NOT_ALLOWED") {
+ setNameStatus(NameStatus::Exists,
+ tr("The name %1 is not allowed.").arg(currentCheck));
+ } else {
+ setNameStatus(
+ NameStatus::Error,
+ tr("Unhandled profile name status: %1").arg(statusValue));
+ }
+ } else {
+ setNameStatus(NameStatus::Error,
+ tr("Failed to check name availability."));
+ }
+ isChecking = false;
+}
+
+void ProfileSetupDialog::setupProfile(const QString& profileName)
+{
+ if (isWorking) {
+ return;
+ }
+
+ auto token = m_accountToSetup->accessToken();
+
+ auto url = QString("https://api.minecraftservices.com/minecraft/profile");
+ QNetworkRequest request = QNetworkRequest(url);
+ request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
+ request.setRawHeader("Accept", "application/json");
+ request.setRawHeader("Authorization",
+ QString("Bearer %1").arg(token).toUtf8());
+
+ QString payloadTemplate("{\"profileName\":\"%1\"}");
+ auto data = payloadTemplate.arg(profileName).toUtf8();
+
+ AuthRequest* requestor = new AuthRequest(this);
+ connect(requestor, &AuthRequest::finished, this,
+ &ProfileSetupDialog::setupProfileFinished);
+ requestor->post(request, data);
+ isWorking = true;
+
+ auto button = ui->buttonBox->button(QDialogButtonBox::Cancel);
+ button->setEnabled(false);
+}
+
+namespace
+{
+
+ struct MojangError {
+ static MojangError fromJSON(QByteArray data)
+ {
+ MojangError out;
+ out.error = QString::fromUtf8(data);
+ auto doc = QJsonDocument::fromJson(data, &out.parseError);
+ auto object = doc.object();
+
+ out.fullyParsed = true;
+ out.fullyParsed &=
+ Parsers::getString(object.value("path"), out.path);
+ out.fullyParsed &=
+ Parsers::getString(object.value("error"), out.error);
+ out.fullyParsed &= Parsers::getString(object.value("errorMessage"),
+ out.errorMessage);
+
+ return out;
+ }
+
+ QString rawError;
+ QJsonParseError parseError;
+ bool fullyParsed;
+
+ QString path;
+ QString error;
+ QString errorMessage;
+ };
+
+} // namespace
+
+void ProfileSetupDialog::setupProfileFinished(
+ QNetworkReply::NetworkError error, QByteArray data,
+ QList<QNetworkReply::RawHeaderPair> headers)
+{
+ auto requestor = qobject_cast<AuthRequest*>(QObject::sender());
+ requestor->deleteLater();
+
+ isWorking = false;
+ if (error == QNetworkReply::NoError) {
+ /*
+ * data contains the profile in the response
+ * ... we could parse it and update the account, but let's just return
+ * back to the normal login flow instead...
+ */
+ accept();
+ } else {
+ auto parsedError = MojangError::fromJSON(data);
+ ui->errorLabel->setVisible(true);
+ ui->errorLabel->setText(tr("The server returned the following error:") +
+ "\n\n" + parsedError.errorMessage);
+ qDebug() << parsedError.rawError;
+ auto button = ui->buttonBox->button(QDialogButtonBox::Cancel);
+ button->setEnabled(true);
+ }
+}