mirror of
https://github.com/PrismLauncher/PrismLauncher.git
synced 2025-05-24 02:42:15 +02:00
Improvements to upload logs (#1872)
This commit is contained in:
commit
a89fc7d1e0
@ -53,7 +53,6 @@ Config::Config()
|
|||||||
LAUNCHER_SVGFILENAME = "@Launcher_SVGFileName@";
|
LAUNCHER_SVGFILENAME = "@Launcher_SVGFileName@";
|
||||||
|
|
||||||
USER_AGENT = "@Launcher_UserAgent@";
|
USER_AGENT = "@Launcher_UserAgent@";
|
||||||
USER_AGENT_UNCACHED = USER_AGENT + " (Uncached)";
|
|
||||||
|
|
||||||
// Version information
|
// Version information
|
||||||
VERSION_MAJOR = @Launcher_VERSION_MAJOR@;
|
VERSION_MAJOR = @Launcher_VERSION_MAJOR@;
|
||||||
|
@ -107,9 +107,6 @@ class Config {
|
|||||||
/// User-Agent to use.
|
/// User-Agent to use.
|
||||||
QString USER_AGENT;
|
QString USER_AGENT;
|
||||||
|
|
||||||
/// User-Agent to use for uncached requests.
|
|
||||||
QString USER_AGENT_UNCACHED;
|
|
||||||
|
|
||||||
/// The git commit hash of this build
|
/// The git commit hash of this build
|
||||||
QString GIT_COMMIT;
|
QString GIT_COMMIT;
|
||||||
|
|
||||||
|
@ -1883,17 +1883,6 @@ QString Application::getUserAgent()
|
|||||||
return BuildConfig.USER_AGENT;
|
return BuildConfig.USER_AGENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Application::getUserAgentUncached()
|
|
||||||
{
|
|
||||||
QString uaOverride = m_settings->get("UserAgentOverride").toString();
|
|
||||||
if (!uaOverride.isEmpty()) {
|
|
||||||
uaOverride += " (Uncached)";
|
|
||||||
return uaOverride.replace("$LAUNCHER_VER", BuildConfig.printableVersionString());
|
|
||||||
}
|
|
||||||
|
|
||||||
return BuildConfig.USER_AGENT_UNCACHED;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Application::handleDataMigration(const QString& currentData,
|
bool Application::handleDataMigration(const QString& currentData,
|
||||||
const QString& oldData,
|
const QString& oldData,
|
||||||
const QString& name,
|
const QString& name,
|
||||||
|
@ -160,7 +160,6 @@ class Application : public QApplication {
|
|||||||
QString getFlameAPIKey();
|
QString getFlameAPIKey();
|
||||||
QString getModrinthAPIToken();
|
QString getModrinthAPIToken();
|
||||||
QString getUserAgent();
|
QString getUserAgent();
|
||||||
QString getUserAgentUncached();
|
|
||||||
|
|
||||||
/// this is the root of the 'installation'. Used for automatic updates
|
/// this is the root of the 'installation'. Used for automatic updates
|
||||||
const QString& root() { return m_rootPath; }
|
const QString& root() { return m_rootPath; }
|
||||||
|
@ -834,6 +834,10 @@ SET(LAUNCHER_SOURCES
|
|||||||
icons/IconList.h
|
icons/IconList.h
|
||||||
icons/IconList.cpp
|
icons/IconList.cpp
|
||||||
|
|
||||||
|
# log utils
|
||||||
|
logs/AnonymizeLog.cpp
|
||||||
|
logs/AnonymizeLog.h
|
||||||
|
|
||||||
# GUI - windows
|
# GUI - windows
|
||||||
ui/GuiUtil.h
|
ui/GuiUtil.h
|
||||||
ui/GuiUtil.cpp
|
ui/GuiUtil.cpp
|
||||||
|
68
launcher/logs/AnonymizeLog.cpp
Normal file
68
launcher/logs/AnonymizeLog.cpp
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* Prism Launcher - Minecraft Launcher
|
||||||
|
* Copyright (c) 2025 Trial97 <alexandru.tripon97@gmail.com>
|
||||||
|
*
|
||||||
|
* 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/>.
|
||||||
|
*
|
||||||
|
* 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 "AnonymizeLog.h"
|
||||||
|
|
||||||
|
#include <QRegularExpression>
|
||||||
|
|
||||||
|
struct RegReplace {
|
||||||
|
RegReplace(QRegularExpression r, QString w) : reg(r), with(w) { reg.optimize(); }
|
||||||
|
QRegularExpression reg;
|
||||||
|
QString with;
|
||||||
|
};
|
||||||
|
|
||||||
|
static const QVector<RegReplace> anonymizeRules = {
|
||||||
|
RegReplace(QRegularExpression("C:\\\\Users\\\\([^\\\\]+)\\\\", QRegularExpression::CaseInsensitiveOption),
|
||||||
|
"C:\\Users\\********\\"), // windows
|
||||||
|
RegReplace(QRegularExpression("C:\\/Users\\/([^\\/]+)\\/", QRegularExpression::CaseInsensitiveOption),
|
||||||
|
"C:/Users/********/"), // windows with forward slashes
|
||||||
|
RegReplace(QRegularExpression("(?<!\\\\w)\\/home\\/[^\\/]+\\/", QRegularExpression::CaseInsensitiveOption),
|
||||||
|
"/home/********/"), // linux
|
||||||
|
RegReplace(QRegularExpression("(?<!\\\\w)\\/Users\\/[^\\/]+\\/", QRegularExpression::CaseInsensitiveOption),
|
||||||
|
"/Users/********/"), // macos
|
||||||
|
RegReplace(QRegularExpression("\\(Session ID is [^\\)]+\\)", QRegularExpression::CaseInsensitiveOption),
|
||||||
|
"(Session ID is <SESSION_TOKEN>)"), // SESSION_TOKEN
|
||||||
|
RegReplace(QRegularExpression("new refresh token: \"[^\"]+\"", QRegularExpression::CaseInsensitiveOption),
|
||||||
|
"new refresh token: \"<TOKEN>\""), // refresh token
|
||||||
|
RegReplace(QRegularExpression("\"device_code\" : \"[^\"]+\"", QRegularExpression::CaseInsensitiveOption),
|
||||||
|
"\"device_code\" : \"<DEVICE_CODE>\""), // device code
|
||||||
|
};
|
||||||
|
|
||||||
|
void anonymizeLog(QString& log)
|
||||||
|
{
|
||||||
|
for (auto rule : anonymizeRules) {
|
||||||
|
log.replace(rule.reg, rule.with);
|
||||||
|
}
|
||||||
|
}
|
40
launcher/logs/AnonymizeLog.h
Normal file
40
launcher/logs/AnonymizeLog.h
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* Prism Launcher - Minecraft Launcher
|
||||||
|
* Copyright (c) 2025 Trial97 <alexandru.tripon97@gmail.com>
|
||||||
|
*
|
||||||
|
* 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/>.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
void anonymizeLog(QString& log);
|
@ -36,74 +36,44 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "PasteUpload.h"
|
#include "PasteUpload.h"
|
||||||
#include "Application.h"
|
|
||||||
#include "BuildConfig.h"
|
|
||||||
|
|
||||||
#include <QDebug>
|
|
||||||
#include <QFile>
|
|
||||||
#include <QHttpPart>
|
#include <QHttpPart>
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
|
#include <QRegularExpression>
|
||||||
#include <QUrlQuery>
|
#include <QUrlQuery>
|
||||||
|
#include "logs/AnonymizeLog.h"
|
||||||
|
|
||||||
#include "net/Logging.h"
|
const std::array<PasteUpload::PasteTypeInfo, 4> PasteUpload::PasteTypes = { { { "0x0.st", "https://0x0.st", "" },
|
||||||
|
|
||||||
std::array<PasteUpload::PasteTypeInfo, 4> PasteUpload::PasteTypes = { { { "0x0.st", "https://0x0.st", "" },
|
|
||||||
{ "hastebin", "https://hst.sh", "/documents" },
|
{ "hastebin", "https://hst.sh", "/documents" },
|
||||||
{ "paste.gg", "https://paste.gg", "/api/v1/pastes" },
|
{ "paste.gg", "https://paste.gg", "/api/v1/pastes" },
|
||||||
{ "mclo.gs", "https://api.mclo.gs", "/1/log" } } };
|
{ "mclo.gs", "https://api.mclo.gs", "/1/log" } } };
|
||||||
|
|
||||||
PasteUpload::PasteUpload(QWidget* window, QString text, QString baseUrl, PasteType pasteType)
|
QNetworkReply* PasteUpload::getReply(QNetworkRequest& request)
|
||||||
: m_window(window), m_baseUrl(baseUrl), m_pasteType(pasteType), m_text(text.toUtf8())
|
|
||||||
{
|
{
|
||||||
if (m_baseUrl == "")
|
switch (m_paste_type) {
|
||||||
m_baseUrl = PasteTypes.at(pasteType).defaultBase;
|
case PasteUpload::NullPointer: {
|
||||||
|
QHttpMultiPart* multiPart = new QHttpMultiPart{ QHttpMultiPart::FormDataType, this };
|
||||||
// HACK: Paste's docs say the standard API path is at /api/<version> but the official instance paste.gg doesn't follow that??
|
|
||||||
if (pasteType == PasteGG && m_baseUrl == PasteTypes.at(pasteType).defaultBase)
|
|
||||||
m_uploadUrl = "https://api.paste.gg/v1/pastes";
|
|
||||||
else
|
|
||||||
m_uploadUrl = m_baseUrl + PasteTypes.at(pasteType).endpointPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
PasteUpload::~PasteUpload() {}
|
|
||||||
|
|
||||||
void PasteUpload::executeTask()
|
|
||||||
{
|
|
||||||
QNetworkRequest request{ QUrl(m_uploadUrl) };
|
|
||||||
QNetworkReply* rep{};
|
|
||||||
|
|
||||||
request.setHeader(QNetworkRequest::UserAgentHeader, APPLICATION->getUserAgentUncached().toUtf8());
|
|
||||||
|
|
||||||
switch (m_pasteType) {
|
|
||||||
case NullPointer: {
|
|
||||||
QHttpMultiPart* multiPart = new QHttpMultiPart{ QHttpMultiPart::FormDataType };
|
|
||||||
|
|
||||||
QHttpPart filePart;
|
QHttpPart filePart;
|
||||||
filePart.setBody(m_text);
|
filePart.setBody(m_log.toUtf8());
|
||||||
filePart.setHeader(QNetworkRequest::ContentTypeHeader, "text/plain");
|
filePart.setHeader(QNetworkRequest::ContentTypeHeader, "text/plain");
|
||||||
filePart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"file\"; filename=\"log.txt\"");
|
filePart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"file\"; filename=\"log.txt\"");
|
||||||
multiPart->append(filePart);
|
multiPart->append(filePart);
|
||||||
|
|
||||||
rep = APPLICATION->network()->post(request, multiPart);
|
return m_network->post(request, multiPart);
|
||||||
multiPart->setParent(rep);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
case Hastebin: {
|
case PasteUpload::Hastebin: {
|
||||||
request.setHeader(QNetworkRequest::UserAgentHeader, APPLICATION->getUserAgentUncached().toUtf8());
|
return m_network->post(request, m_log.toUtf8());
|
||||||
rep = APPLICATION->network()->post(request, m_text);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
case Mclogs: {
|
case PasteUpload::Mclogs: {
|
||||||
QUrlQuery postData;
|
QUrlQuery postData;
|
||||||
postData.addQueryItem("content", m_text);
|
postData.addQueryItem("content", m_log);
|
||||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
||||||
rep = APPLICATION->network()->post(request, postData.toString().toUtf8());
|
return m_network->post(request, postData.toString().toUtf8());
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
case PasteGG: {
|
case PasteUpload::PasteGG: {
|
||||||
QJsonObject obj;
|
QJsonObject obj;
|
||||||
QJsonDocument doc;
|
QJsonDocument doc;
|
||||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||||
@ -114,7 +84,7 @@ void PasteUpload::executeTask()
|
|||||||
QJsonObject logFileInfo;
|
QJsonObject logFileInfo;
|
||||||
QJsonObject logFileContentInfo;
|
QJsonObject logFileContentInfo;
|
||||||
logFileContentInfo.insert("format", "text");
|
logFileContentInfo.insert("format", "text");
|
||||||
logFileContentInfo.insert("value", QString::fromUtf8(m_text));
|
logFileContentInfo.insert("value", m_log);
|
||||||
logFileInfo.insert("name", "log.txt");
|
logFileInfo.insert("name", "log.txt");
|
||||||
logFileInfo.insert("content", logFileContentInfo);
|
logFileInfo.insert("content", logFileContentInfo);
|
||||||
files.append(logFileInfo);
|
files.append(logFileInfo);
|
||||||
@ -122,108 +92,117 @@ void PasteUpload::executeTask()
|
|||||||
obj.insert("files", files);
|
obj.insert("files", files);
|
||||||
|
|
||||||
doc.setObject(obj);
|
doc.setObject(obj);
|
||||||
rep = APPLICATION->network()->post(request, doc.toJson());
|
return m_network->post(request, doc.toJson());
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
connect(rep, &QNetworkReply::uploadProgress, this, &Task::setProgress);
|
return nullptr;
|
||||||
connect(rep, &QNetworkReply::finished, this, &PasteUpload::downloadFinished);
|
};
|
||||||
|
|
||||||
connect(rep, &QNetworkReply::errorOccurred, this, &PasteUpload::downloadError);
|
auto PasteUpload::Sink::init(QNetworkRequest&) -> Task::State
|
||||||
|
|
||||||
m_reply = std::shared_ptr<QNetworkReply>(rep);
|
|
||||||
|
|
||||||
setStatus(tr("Uploading to %1").arg(m_uploadUrl));
|
|
||||||
}
|
|
||||||
|
|
||||||
void PasteUpload::downloadError(QNetworkReply::NetworkError error)
|
|
||||||
{
|
{
|
||||||
// error happened during download.
|
m_output.clear();
|
||||||
qCCritical(taskUploadLogC) << getUid().toString() << "Network error: " << error;
|
return Task::State::Running;
|
||||||
emitFailed(m_reply->errorString());
|
};
|
||||||
}
|
|
||||||
|
|
||||||
void PasteUpload::downloadFinished()
|
auto PasteUpload::Sink::write(QByteArray& data) -> Task::State
|
||||||
{
|
{
|
||||||
QByteArray data = m_reply->readAll();
|
m_output.append(data);
|
||||||
int statusCode = m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
return Task::State::Running;
|
||||||
|
|
||||||
if (m_reply->error() != QNetworkReply::NetworkError::NoError) {
|
|
||||||
emitFailed(tr("Network error: %1").arg(m_reply->errorString()));
|
|
||||||
m_reply.reset();
|
|
||||||
return;
|
|
||||||
} else if (statusCode != 200 && statusCode != 201) {
|
|
||||||
QString reasonPhrase = m_reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString();
|
|
||||||
emitFailed(tr("Error: %1 returned unexpected status code %2 %3").arg(m_uploadUrl).arg(statusCode).arg(reasonPhrase));
|
|
||||||
qCCritical(taskUploadLogC) << getUid().toString() << m_uploadUrl << " returned unexpected status code " << statusCode
|
|
||||||
<< " with body: " << data;
|
|
||||||
m_reply.reset();
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (m_pasteType) {
|
auto PasteUpload::Sink::abort() -> Task::State
|
||||||
case NullPointer:
|
{
|
||||||
m_pasteLink = QString::fromUtf8(data).trimmed();
|
m_output.clear();
|
||||||
|
return Task::State::Failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto PasteUpload::Sink::finalize(QNetworkReply&) -> Task::State
|
||||||
|
{
|
||||||
|
switch (m_paste_type) {
|
||||||
|
case PasteUpload::NullPointer:
|
||||||
|
m_result->link = QString::fromUtf8(m_output).trimmed();
|
||||||
break;
|
break;
|
||||||
case Hastebin: {
|
case PasteUpload::Hastebin: {
|
||||||
QJsonDocument jsonDoc{ QJsonDocument::fromJson(data) };
|
QJsonParseError jsonError;
|
||||||
QJsonObject jsonObj{ jsonDoc.object() };
|
auto doc = QJsonDocument::fromJson(m_output, &jsonError);
|
||||||
if (jsonObj.contains("key") && jsonObj["key"].isString()) {
|
if (jsonError.error != QJsonParseError::NoError) {
|
||||||
QString key = jsonDoc.object()["key"].toString();
|
qDebug() << "hastebin server did not reply with JSON" << jsonError.errorString();
|
||||||
m_pasteLink = m_baseUrl + "/" + key;
|
return Task::State::Failed;
|
||||||
|
}
|
||||||
|
auto obj = doc.object();
|
||||||
|
if (obj.contains("key") && obj["key"].isString()) {
|
||||||
|
QString key = doc.object()["key"].toString();
|
||||||
|
m_result->link = m_base_url + "/" + key;
|
||||||
} else {
|
} else {
|
||||||
emitFailed(tr("Error: %1 returned a malformed response body").arg(m_uploadUrl));
|
qDebug() << "Log upload failed:" << doc.toJson();
|
||||||
qCCritical(taskUploadLogC) << getUid().toString() << getUid().toString() << m_uploadUrl
|
return Task::State::Failed;
|
||||||
<< " returned malformed response body: " << data;
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Mclogs: {
|
case PasteUpload::Mclogs: {
|
||||||
QJsonDocument jsonDoc{ QJsonDocument::fromJson(data) };
|
QJsonParseError jsonError;
|
||||||
QJsonObject jsonObj{ jsonDoc.object() };
|
auto doc = QJsonDocument::fromJson(m_output, &jsonError);
|
||||||
if (jsonObj.contains("success") && jsonObj["success"].isBool()) {
|
if (jsonError.error != QJsonParseError::NoError) {
|
||||||
bool success = jsonObj["success"].toBool();
|
qDebug() << "mclogs server did not reply with JSON" << jsonError.errorString();
|
||||||
|
return Task::State::Failed;
|
||||||
|
}
|
||||||
|
auto obj = doc.object();
|
||||||
|
if (obj.contains("success") && obj["success"].isBool()) {
|
||||||
|
bool success = obj["success"].toBool();
|
||||||
if (success) {
|
if (success) {
|
||||||
m_pasteLink = jsonObj["url"].toString();
|
m_result->link = obj["url"].toString();
|
||||||
} else {
|
} else {
|
||||||
QString error = jsonObj["error"].toString();
|
m_result->error = obj["error"].toString();
|
||||||
emitFailed(tr("Error: %1 returned an error: %2").arg(m_uploadUrl, error));
|
|
||||||
qCCritical(taskUploadLogC) << getUid().toString() << m_uploadUrl << " returned error: " << error;
|
|
||||||
qCCritical(taskUploadLogC) << getUid().toString() << "Response body: " << data;
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
emitFailed(tr("Error: %1 returned a malformed response body").arg(m_uploadUrl));
|
qDebug() << "Log upload failed:" << doc.toJson();
|
||||||
qCCritical(taskUploadLogC) << getUid().toString() << m_uploadUrl << " returned malformed response body: " << data;
|
return Task::State::Failed;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case PasteGG:
|
case PasteUpload::PasteGG:
|
||||||
QJsonDocument jsonDoc{ QJsonDocument::fromJson(data) };
|
QJsonParseError jsonError;
|
||||||
QJsonObject jsonObj{ jsonDoc.object() };
|
auto doc = QJsonDocument::fromJson(m_output, &jsonError);
|
||||||
if (jsonObj.contains("status") && jsonObj["status"].isString()) {
|
if (jsonError.error != QJsonParseError::NoError) {
|
||||||
QString status = jsonObj["status"].toString();
|
qDebug() << "pastegg server did not reply with JSON" << jsonError.errorString();
|
||||||
|
return Task::State::Failed;
|
||||||
|
}
|
||||||
|
auto obj = doc.object();
|
||||||
|
if (obj.contains("status") && obj["status"].isString()) {
|
||||||
|
QString status = obj["status"].toString();
|
||||||
if (status == "success") {
|
if (status == "success") {
|
||||||
m_pasteLink = m_baseUrl + "/p/anonymous/" + jsonObj["result"].toObject()["id"].toString();
|
m_result->link = m_base_url + "/p/anonymous/" + obj["result"].toObject()["id"].toString();
|
||||||
} else {
|
} else {
|
||||||
QString error = jsonObj["error"].toString();
|
m_result->error = obj["error"].toString();
|
||||||
QString message =
|
m_result->extra_message = (obj.contains("message") && obj["message"].isString()) ? obj["message"].toString() : "none";
|
||||||
(jsonObj.contains("message") && jsonObj["message"].isString()) ? jsonObj["message"].toString() : "none";
|
|
||||||
emitFailed(tr("Error: %1 returned an error code: %2\nError message: %3").arg(m_uploadUrl, error, message));
|
|
||||||
qCCritical(taskUploadLogC) << getUid().toString() << m_uploadUrl << " returned error: " << error;
|
|
||||||
qCCritical(taskUploadLogC) << getUid().toString() << "Error message: " << message;
|
|
||||||
qCCritical(taskUploadLogC) << getUid().toString() << "Response body: " << data;
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
emitFailed(tr("Error: %1 returned a malformed response body").arg(m_uploadUrl));
|
qDebug() << "Log upload failed:" << doc.toJson();
|
||||||
qCCritical(taskUploadLogC) << getUid().toString() << m_uploadUrl << " returned malformed response body: " << data;
|
return Task::State::Failed;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
emitSucceeded();
|
return Task::State::Succeeded;
|
||||||
|
}
|
||||||
|
|
||||||
|
Net::NetRequest::Ptr PasteUpload::make(const QString& log, PasteUpload::PasteType pasteType, QString customBaseURL, ResultPtr result)
|
||||||
|
{
|
||||||
|
auto base = PasteUpload::PasteTypes.at(pasteType);
|
||||||
|
QString baseUrl = customBaseURL.isEmpty() ? base.defaultBase : customBaseURL;
|
||||||
|
auto up = makeShared<PasteUpload>(log, pasteType);
|
||||||
|
|
||||||
|
// HACK: Paste's docs say the standard API path is at /api/<version> but the official instance paste.gg doesn't follow that??
|
||||||
|
if (pasteType == PasteUpload::PasteGG && baseUrl == base.defaultBase)
|
||||||
|
up->m_url = "https://api.paste.gg/v1/pastes";
|
||||||
|
else
|
||||||
|
up->m_url = baseUrl + base.endpointPath;
|
||||||
|
|
||||||
|
up->m_sink.reset(new Sink(pasteType, baseUrl, result));
|
||||||
|
return up;
|
||||||
|
}
|
||||||
|
|
||||||
|
PasteUpload::PasteUpload(const QString& log, PasteType pasteType) : m_log(log), m_paste_type(pasteType)
|
||||||
|
{
|
||||||
|
anonymizeLog(m_log);
|
||||||
}
|
}
|
||||||
|
@ -35,15 +35,17 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QBuffer>
|
#include "net/NetRequest.h"
|
||||||
#include <QNetworkReply>
|
|
||||||
#include <QString>
|
|
||||||
#include <array>
|
|
||||||
#include <memory>
|
|
||||||
#include "tasks/Task.h"
|
#include "tasks/Task.h"
|
||||||
|
|
||||||
class PasteUpload : public Task {
|
#include <QNetworkReply>
|
||||||
Q_OBJECT
|
#include <QRegularExpression>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
class PasteUpload : public Net::NetRequest {
|
||||||
public:
|
public:
|
||||||
enum PasteType : int {
|
enum PasteType : int {
|
||||||
// 0x0.st
|
// 0x0.st
|
||||||
@ -58,32 +60,47 @@ class PasteUpload : public Task {
|
|||||||
First = NullPointer,
|
First = NullPointer,
|
||||||
Last = Mclogs
|
Last = Mclogs
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PasteTypeInfo {
|
struct PasteTypeInfo {
|
||||||
const QString name;
|
const QString name;
|
||||||
const QString defaultBase;
|
const QString defaultBase;
|
||||||
const QString endpointPath;
|
const QString endpointPath;
|
||||||
};
|
};
|
||||||
|
|
||||||
static std::array<PasteTypeInfo, 4> PasteTypes;
|
static const std::array<PasteTypeInfo, 4> PasteTypes;
|
||||||
|
struct Result {
|
||||||
|
QString link;
|
||||||
|
QString error;
|
||||||
|
QString extra_message;
|
||||||
|
};
|
||||||
|
|
||||||
PasteUpload(QWidget* window, QString text, QString url, PasteType pasteType);
|
using ResultPtr = std::shared_ptr<Result>;
|
||||||
virtual ~PasteUpload();
|
|
||||||
|
|
||||||
QString pasteLink() { return m_pasteLink; }
|
class Sink : public Net::Sink {
|
||||||
|
public:
|
||||||
|
Sink(const PasteType pasteType, const QString base_url, ResultPtr result)
|
||||||
|
: m_paste_type(pasteType), m_base_url(base_url), m_result(result) {};
|
||||||
|
virtual ~Sink() = default;
|
||||||
|
|
||||||
protected:
|
public:
|
||||||
virtual void executeTask();
|
auto init(QNetworkRequest& request) -> Task::State override;
|
||||||
|
auto write(QByteArray& data) -> Task::State override;
|
||||||
|
auto abort() -> Task::State override;
|
||||||
|
auto finalize(QNetworkReply& reply) -> Task::State override;
|
||||||
|
auto hasLocalData() -> bool override { return false; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QWidget* m_window;
|
const PasteType m_paste_type;
|
||||||
QString m_pasteLink;
|
const QString m_base_url;
|
||||||
QString m_baseUrl;
|
ResultPtr m_result;
|
||||||
QString m_uploadUrl;
|
QByteArray m_output;
|
||||||
PasteType m_pasteType;
|
};
|
||||||
QByteArray m_text;
|
PasteUpload(const QString& log, PasteType pasteType);
|
||||||
std::shared_ptr<QNetworkReply> m_reply;
|
virtual ~PasteUpload() = default;
|
||||||
public slots:
|
|
||||||
void downloadError(QNetworkReply::NetworkError);
|
static NetRequest::Ptr make(const QString& log, PasteType pasteType, QString baseURL, ResultPtr result);
|
||||||
void downloadFinished();
|
|
||||||
|
private:
|
||||||
|
virtual QNetworkReply* getReply(QNetworkRequest&) override;
|
||||||
|
QString m_log;
|
||||||
|
const PasteType m_paste_type;
|
||||||
};
|
};
|
@ -38,10 +38,16 @@
|
|||||||
#include "GuiUtil.h"
|
#include "GuiUtil.h"
|
||||||
|
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
|
#include <QBuffer>
|
||||||
#include <QClipboard>
|
#include <QClipboard>
|
||||||
#include <QFileDialog>
|
#include <QFileDialog>
|
||||||
#include <QStandardPaths>
|
#include <QStandardPaths>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "FileSystem.h"
|
||||||
|
#include "logs/AnonymizeLog.h"
|
||||||
|
#include "net/NetJob.h"
|
||||||
#include "net/PasteUpload.h"
|
#include "net/PasteUpload.h"
|
||||||
#include "ui/dialogs/CustomMessageBox.h"
|
#include "ui/dialogs/CustomMessageBox.h"
|
||||||
#include "ui/dialogs/ProgressDialog.h"
|
#include "ui/dialogs/ProgressDialog.h"
|
||||||
@ -74,33 +80,34 @@ QString truncateLogForMclogs(const QString& logContent)
|
|||||||
return logContent;
|
return logContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<QString> GuiUtil::uploadPaste(const QString& name, const QFileInfo& filePath, QWidget* parentWidget)
|
||||||
|
{
|
||||||
|
return uploadPaste(name, FS::read(filePath.absoluteFilePath()), parentWidget);
|
||||||
|
};
|
||||||
|
|
||||||
std::optional<QString> GuiUtil::uploadPaste(const QString& name, const QString& text, QWidget* parentWidget)
|
std::optional<QString> GuiUtil::uploadPaste(const QString& name, const QString& text, QWidget* parentWidget)
|
||||||
{
|
{
|
||||||
ProgressDialog dialog(parentWidget);
|
ProgressDialog dialog(parentWidget);
|
||||||
auto pasteTypeSetting = static_cast<PasteUpload::PasteType>(APPLICATION->settings()->get("PastebinType").toInt());
|
auto pasteType = static_cast<PasteUpload::PasteType>(APPLICATION->settings()->get("PastebinType").toInt());
|
||||||
auto pasteCustomAPIBaseSetting = APPLICATION->settings()->get("PastebinCustomAPIBase").toString();
|
auto baseURL = APPLICATION->settings()->get("PastebinCustomAPIBase").toString();
|
||||||
bool shouldTruncate = false;
|
bool shouldTruncate = false;
|
||||||
|
|
||||||
{
|
if (baseURL.isEmpty())
|
||||||
QUrl baseUrl;
|
baseURL = PasteUpload::PasteTypes[pasteType].defaultBase;
|
||||||
if (pasteCustomAPIBaseSetting.isEmpty())
|
|
||||||
baseUrl = PasteUpload::PasteTypes[pasteTypeSetting].defaultBase;
|
|
||||||
else
|
|
||||||
baseUrl = pasteCustomAPIBaseSetting;
|
|
||||||
|
|
||||||
if (baseUrl.isValid()) {
|
if (auto url = QUrl(baseURL); url.isValid()) {
|
||||||
auto response = CustomMessageBox::selectable(parentWidget, QObject::tr("Confirm Upload"),
|
auto response = CustomMessageBox::selectable(parentWidget, QObject::tr("Confirm Upload"),
|
||||||
QObject::tr("You are about to upload \"%1\" to %2.\n"
|
QObject::tr("You are about to upload \"%1\" to %2.\n"
|
||||||
"You should double-check for personal information.\n\n"
|
"You should double-check for personal information.\n\n"
|
||||||
"Are you sure?")
|
"Are you sure?")
|
||||||
.arg(name, baseUrl.host()),
|
.arg(name, url.host()),
|
||||||
QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
|
QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
|
||||||
->exec();
|
->exec();
|
||||||
|
|
||||||
if (response != QMessageBox::Yes)
|
if (response != QMessageBox::Yes)
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
if (baseUrl.toString() == "https://api.mclo.gs" && text.count("\n") > MaxMclogsLines) {
|
if (baseURL == "https://api.mclo.gs" && text.count("\n") > MaxMclogsLines) {
|
||||||
auto truncateResponse = CustomMessageBox::selectable(
|
auto truncateResponse = CustomMessageBox::selectable(
|
||||||
parentWidget, QObject::tr("Confirm Truncation"),
|
parentWidget, QObject::tr("Confirm Truncation"),
|
||||||
QObject::tr("The log has %1 lines, exceeding mclo.gs' limit of %2.\n"
|
QObject::tr("The log has %1 lines, exceeding mclo.gs' limit of %2.\n"
|
||||||
@ -121,33 +128,54 @@ std::optional<QString> GuiUtil::uploadPaste(const QString& name, const QString&
|
|||||||
shouldTruncate = truncateResponse == QMessageBox::Yes;
|
shouldTruncate = truncateResponse == QMessageBox::Yes;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
QString textToUpload = text;
|
QString textToUpload = text;
|
||||||
if (shouldTruncate) {
|
if (shouldTruncate) {
|
||||||
textToUpload = truncateLogForMclogs(text);
|
textToUpload = truncateLogForMclogs(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<PasteUpload> paste(new PasteUpload(parentWidget, textToUpload, pasteCustomAPIBaseSetting, pasteTypeSetting));
|
auto result = std::make_shared<PasteUpload::Result>();
|
||||||
|
auto job = NetJob::Ptr(new NetJob("Log Upload", APPLICATION->network()));
|
||||||
|
|
||||||
dialog.execWithTask(paste.get());
|
job->addNetAction(PasteUpload::make(textToUpload, pasteType, baseURL, result));
|
||||||
if (!paste->wasSuccessful()) {
|
QObject::connect(job.get(), &Task::failed, [parentWidget](QString reason) {
|
||||||
CustomMessageBox::selectable(parentWidget, QObject::tr("Upload failed"), paste->failReason(), QMessageBox::Critical)->exec();
|
CustomMessageBox::selectable(parentWidget, QObject::tr("Failed to upload logs!"), reason, QMessageBox::Critical)->show();
|
||||||
return QString();
|
});
|
||||||
} else {
|
QObject::connect(job.get(), &Task::aborted, [parentWidget] {
|
||||||
const QString link = paste->pasteLink();
|
CustomMessageBox::selectable(parentWidget, QObject::tr("Logs upload aborted"),
|
||||||
setClipboardText(link);
|
QObject::tr("The task has been aborted by the user."), QMessageBox::Information)
|
||||||
|
->show();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (dialog.execWithTask(job.get()) == QDialog::Accepted) {
|
||||||
|
if (!result->error.isEmpty() || !result->extra_message.isEmpty()) {
|
||||||
|
QString message = QObject::tr("Error: %1").arg(result->error);
|
||||||
|
if (!result->extra_message.isEmpty()) {
|
||||||
|
message += QObject::tr("\nError message: %1").arg(result->extra_message);
|
||||||
|
}
|
||||||
|
CustomMessageBox::selectable(parentWidget, QObject::tr("Failed to upload logs!"), message, QMessageBox::Critical)->show();
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
if (result->link.isEmpty()) {
|
||||||
|
CustomMessageBox::selectable(parentWidget, QObject::tr("Failed to upload logs!"), "The upload link is empty",
|
||||||
|
QMessageBox::Critical)
|
||||||
|
->show();
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
setClipboardText(result->link);
|
||||||
CustomMessageBox::selectable(
|
CustomMessageBox::selectable(
|
||||||
parentWidget, QObject::tr("Upload finished"),
|
parentWidget, QObject::tr("Upload finished"),
|
||||||
QObject::tr("The <a href=\"%1\">link to the uploaded log</a> has been placed in your clipboard.").arg(link),
|
QObject::tr("The <a href=\"%1\">link to the uploaded log</a> has been placed in your clipboard.").arg(result->link),
|
||||||
QMessageBox::Information)
|
QMessageBox::Information)
|
||||||
->exec();
|
->exec();
|
||||||
return link;
|
return result->link;
|
||||||
}
|
}
|
||||||
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
void GuiUtil::setClipboardText(const QString& text)
|
void GuiUtil::setClipboardText(QString text)
|
||||||
{
|
{
|
||||||
|
anonymizeLog(text);
|
||||||
QApplication::clipboard()->setText(text);
|
QApplication::clipboard()->setText(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <QFileInfo>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
||||||
namespace GuiUtil {
|
namespace GuiUtil {
|
||||||
std::optional<QString> uploadPaste(const QString& name, const QString& text, QWidget* parentWidget);
|
std::optional<QString> uploadPaste(const QString& name, const QFileInfo& filePath, QWidget* parentWidget);
|
||||||
void setClipboardText(const QString& text);
|
std::optional<QString> uploadPaste(const QString& name, const QString& data, QWidget* parentWidget);
|
||||||
|
void setClipboardText(QString text);
|
||||||
QStringList BrowseForFiles(QString context, QString caption, QString filter, QString defaultPath, QWidget* parentWidget);
|
QStringList BrowseForFiles(QString context, QString caption, QString filter, QString defaultPath, QWidget* parentWidget);
|
||||||
QString BrowseForFile(QString context, QString caption, QString filter, QString defaultPath, QWidget* parentWidget);
|
QString BrowseForFile(QString context, QString caption, QString filter, QString defaultPath, QWidget* parentWidget);
|
||||||
} // namespace GuiUtil
|
} // namespace GuiUtil
|
||||||
|
@ -90,6 +90,7 @@
|
|||||||
#include <updater/ExternalUpdater.h>
|
#include <updater/ExternalUpdater.h>
|
||||||
#include "InstanceWindow.h"
|
#include "InstanceWindow.h"
|
||||||
|
|
||||||
|
#include "ui/GuiUtil.h"
|
||||||
#include "ui/dialogs/AboutDialog.h"
|
#include "ui/dialogs/AboutDialog.h"
|
||||||
#include "ui/dialogs/CopyInstanceDialog.h"
|
#include "ui/dialogs/CopyInstanceDialog.h"
|
||||||
#include "ui/dialogs/CustomMessageBox.h"
|
#include "ui/dialogs/CustomMessageBox.h"
|
||||||
@ -255,6 +256,16 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi
|
|||||||
ui->actionViewJavaFolder->setEnabled(BuildConfig.JAVA_DOWNLOADER_ENABLED);
|
ui->actionViewJavaFolder->setEnabled(BuildConfig.JAVA_DOWNLOADER_ENABLED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{ // logs upload
|
||||||
|
|
||||||
|
auto menu = new QMenu(this);
|
||||||
|
for (auto file : QDir("logs").entryInfoList(QDir::Files)) {
|
||||||
|
auto action = menu->addAction(file.fileName());
|
||||||
|
connect(action, &QAction::triggered, this, [this, file] { GuiUtil::uploadPaste(file.fileName(), file, this); });
|
||||||
|
}
|
||||||
|
ui->actionUploadLog->setMenu(menu);
|
||||||
|
}
|
||||||
|
|
||||||
// add the toolbar toggles to the view menu
|
// add the toolbar toggles to the view menu
|
||||||
ui->viewMenu->addAction(ui->instanceToolBar->toggleViewAction());
|
ui->viewMenu->addAction(ui->instanceToolBar->toggleViewAction());
|
||||||
ui->viewMenu->addAction(ui->newsToolBar->toggleViewAction());
|
ui->viewMenu->addAction(ui->newsToolBar->toggleViewAction());
|
||||||
|
@ -215,6 +215,7 @@
|
|||||||
</property>
|
</property>
|
||||||
<addaction name="actionClearMetadata"/>
|
<addaction name="actionClearMetadata"/>
|
||||||
<addaction name="actionReportBug"/>
|
<addaction name="actionReportBug"/>
|
||||||
|
<addaction name="actionUploadLog"/>
|
||||||
<addaction name="actionAddToPATH"/>
|
<addaction name="actionAddToPATH"/>
|
||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
<addaction name="actionMATRIX"/>
|
<addaction name="actionMATRIX"/>
|
||||||
@ -662,6 +663,18 @@
|
|||||||
<string>Clear cached metadata</string>
|
<string>Clear cached metadata</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
|
<action name="actionUploadLog">
|
||||||
|
<property name="icon">
|
||||||
|
<iconset theme="log">
|
||||||
|
<normaloff>.</normaloff>.</iconset>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Upload logs</string>
|
||||||
|
</property>
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Upload launcher logs to the selected log provider</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
<action name="actionAddToPATH">
|
<action name="actionAddToPATH">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset theme="custom-commands"/>
|
<iconset theme="custom-commands"/>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user