GH-2859 remove twitch page and modpack import from URL

The functionality was broken, beyond repair and an ongoing maintenance
nightmare.
This commit is contained in:
Petr Mrázek 2019-11-18 00:38:36 +01:00
parent 47ed2f48d4
commit af5120c828
12 changed files with 2 additions and 522 deletions

View File

@ -455,8 +455,6 @@ set(FLAME_SOURCES
modplatform/flame/PackManifest.cpp modplatform/flame/PackManifest.cpp
modplatform/flame/FileResolvingTask.h modplatform/flame/FileResolvingTask.h
modplatform/flame/FileResolvingTask.cpp modplatform/flame/FileResolvingTask.cpp
modplatform/flame/UrlResolvingTask.h
modplatform/flame/UrlResolvingTask.cpp
) )
add_unit_test(Index add_unit_test(Index

View File

@ -1,175 +0,0 @@
#include "UrlResolvingTask.h"
#include <QtXml>
#include <Json.h>
namespace {
const char * metabase = "https://cursemeta.dries007.net";
}
Flame::UrlResolvingTask::UrlResolvingTask(const QString& toProcess)
: m_url(toProcess)
{
}
void Flame::UrlResolvingTask::executeTask()
{
resolveUrl();
}
void Flame::UrlResolvingTask::resolveUrl()
{
setStatus(tr("Resolving URL..."));
setProgress(0, 1);
QUrl actualUrl(m_url);
if(actualUrl.host() != "www.curseforge.com") {
emitFailed(tr("Not a Twitch URL."));
return;
}
m_dljob.reset(new NetJob("URL resolver"));
bool weAreDigging = false;
needle = QString();
if(m_url.startsWith("https://")) {
if(m_url.endsWith("?client=y")) {
// https://www.curseforge.com/minecraft/modpacks/ftb-sky-odyssey/download?client=y
// https://www.curseforge.com/minecraft/modpacks/ftb-sky-odyssey/download/2697088?client=y
m_url.chop(9);
// https://www.curseforge.com/minecraft/modpacks/ftb-sky-odyssey/download
// https://www.curseforge.com/minecraft/modpacks/ftb-sky-odyssey/download/2697088
}
if(m_url.endsWith("/download")) {
// https://www.curseforge.com/minecraft/modpacks/ftb-sky-odyssey/download -> need to dig inside html...
weAreDigging = true;
needle = m_url;
needle.replace("https://", "twitch://");
needle.replace("/download", "/download-client/");
m_url.append("?client=y");
} else if (m_url.contains("/download/")) {
// https://www.curseforge.com/minecraft/modpacks/ftb-sky-odyssey/download/2697088
m_url.replace("/download/", "/download-client/");
}
}
else if(m_url.startsWith("twitch://")) {
// twitch://www.curseforge.com/minecraft/modpacks/ftb-sky-odyssey/download-client/2697088
m_url.replace(0, 9, "https://");
// https://www.curseforge.com/minecraft/modpacks/ftb-sky-odyssey/download-client/2697088
}
auto dl = Net::Download::makeByteArray(QUrl(m_url), &results);
m_dljob->addNetAction(dl);
if(weAreDigging) {
connect(m_dljob.get(), &NetJob::finished, this, &Flame::UrlResolvingTask::processHTML);
} else {
connect(m_dljob.get(), &NetJob::finished, this, &Flame::UrlResolvingTask::processCCIP);
}
m_dljob->start();
}
void Flame::UrlResolvingTask::processHTML()
{
QString htmlDoc = QString::fromUtf8(results);
auto index = htmlDoc.indexOf(needle);
if(index < 0) {
emitFailed(tr("Couldn't find the needle in the haystack..."));
return;
}
auto indexStart = index;
int indexEnd = -1;
while((index + 1) < htmlDoc.size() && htmlDoc[index] != '"') {
index ++;
if(htmlDoc[index] == '"') {
indexEnd = index;
break;
}
}
if(indexEnd > 0) {
QString found = htmlDoc.mid(indexStart, indexEnd - indexStart);
qDebug() << "Found needle: " << found;
// twitch://www.curseforge.com/minecraft/modpacks/ftb-sky-odyssey/download-client/2697088
m_url = found;
resolveUrl();
return;
}
emitFailed(tr("Couldn't find the end of the needle in the haystack..."));
return;
}
void Flame::UrlResolvingTask::processCCIP()
{
QDomDocument doc;
if (!doc.setContent(results)) {
qDebug() << results;
emitFailed(tr("Resolving failed."));
return;
}
auto packageNode = doc.namedItem("package");
if(!packageNode.isElement()) {
emitFailed(tr("Resolving failed: missing package root element."));
return;
}
auto projectNode = packageNode.namedItem("project");
if(!projectNode.isElement()) {
emitFailed(tr("Resolving failed: missing project element."));
return;
}
auto attribs = projectNode.attributes();
auto projectIdNode = attribs.namedItem("id");
if(!projectIdNode.isAttr()) {
emitFailed(tr("Resolving failed: missing id attribute."));
return;
}
auto fileIdNode = attribs.namedItem("file");
if(!fileIdNode.isAttr()) {
emitFailed(tr("Resolving failed: missing file attribute."));
return;
}
auto projectId = projectIdNode.nodeValue();
auto fileId = fileIdNode.nodeValue();
bool success = true;
m_result.projectId = projectId.toInt(&success);
if(!success) {
emitFailed(tr("Failed to resolve projectId as a number."));
return;
}
m_result.fileId = fileId.toInt(&success);
if(!success) {
emitFailed(tr("Failed to resolve fileId as a number."));
return;
}
qDebug() << "Resolved" << m_url << "as" << m_result.projectId << "/" << m_result.fileId;
resolveIDs();
}
void Flame::UrlResolvingTask::resolveIDs()
{
setStatus(tr("Resolving mod IDs..."));
m_dljob.reset(new NetJob("Mod id resolver"));
auto projectIdStr = QString::number(m_result.projectId);
auto fileIdStr = QString::number(m_result.fileId);
QString metaurl = QString("%1/%2/%3.json").arg(metabase, projectIdStr, fileIdStr);
auto dl = Net::Download::makeByteArray(QUrl(metaurl), &results);
m_dljob->addNetAction(dl);
connect(m_dljob.get(), &NetJob::finished, this, &Flame::UrlResolvingTask::processCursemeta);
m_dljob->start();
}
void Flame::UrlResolvingTask::processCursemeta()
{
try {
if(m_result.parseFromBytes(results)) {
emitSucceeded();
qDebug() << results;
return;
}
} catch (const JSONValidationError &e) {
qCritical() << "Resolving of" << m_result.projectId << m_result.fileId << "failed because of a parsing error:";
qCritical() << e.cause();
qCritical() << "JSON:";
qCritical() << results;
}
emitFailed(tr("Failed to resolve the modpack file."));
}

View File

@ -1,43 +0,0 @@
#pragma once
#include "tasks/Task.h"
#include "net/NetJob.h"
#include "PackManifest.h"
#include "multimc_logic_export.h"
namespace Flame
{
class MULTIMC_LOGIC_EXPORT UrlResolvingTask : public Task
{
Q_OBJECT
public:
explicit UrlResolvingTask(const QString &toProcess);
virtual ~UrlResolvingTask() {};
const Flame::File &getResults() const
{
return m_result;
}
protected:
virtual void executeTask() override;
protected slots:
void processCCIP();
void processHTML();
void processCursemeta();
private:
void resolveUrl();
void resolveIDs();
private: /* data */
QString m_url;
QString needle;
Flame::File m_result;
QByteArray results;
NetJobPtr m_dljob;
};
}

View File

@ -133,8 +133,6 @@ SET(MULTIMC_SOURCES
pages/modplatform/legacy_ftb/Page.h pages/modplatform/legacy_ftb/Page.h
pages/modplatform/legacy_ftb/ListModel.h pages/modplatform/legacy_ftb/ListModel.h
pages/modplatform/legacy_ftb/ListModel.cpp pages/modplatform/legacy_ftb/ListModel.cpp
pages/modplatform/TwitchPage.cpp
pages/modplatform/TwitchPage.h
pages/modplatform/ImportPage.cpp pages/modplatform/ImportPage.cpp
pages/modplatform/ImportPage.h pages/modplatform/ImportPage.h
@ -277,7 +275,6 @@ SET(MULTIMC_UIS
) )
set(MULTIMC_QRCS set(MULTIMC_QRCS
resources/assets/assets.qrc
resources/backgrounds/backgrounds.qrc resources/backgrounds/backgrounds.qrc
resources/multimc/multimc.qrc resources/multimc/multimc.qrc
resources/pe_dark/pe_dark.qrc resources/pe_dark/pe_dark.qrc

View File

@ -35,7 +35,6 @@
#include "widgets/PageContainer.h" #include "widgets/PageContainer.h"
#include <pages/modplatform/VanillaPage.h> #include <pages/modplatform/VanillaPage.h>
#include <pages/modplatform/legacy_ftb/Page.h> #include <pages/modplatform/legacy_ftb/Page.h>
#include <pages/modplatform/TwitchPage.h>
#include <pages/modplatform/ImportPage.h> #include <pages/modplatform/ImportPage.h>
@ -95,14 +94,8 @@ NewInstanceDialog::NewInstanceDialog(const QString & initialGroup, const QString
if(!url.isEmpty()) if(!url.isEmpty())
{ {
QUrl actualUrl(url); QUrl actualUrl(url);
if(actualUrl.host() == "www.curseforge.com") { m_container->selectPage("import");
m_container->selectPage("twitch"); importPage->setUrl(url);
twitchPage->setUrl(url);
}
else {
m_container->selectPage("import");
importPage->setUrl(url);
}
} }
updateDialogState(); updateDialogState();
@ -126,13 +119,11 @@ void NewInstanceDialog::accept()
QList<BasePage *> NewInstanceDialog::getPages() QList<BasePage *> NewInstanceDialog::getPages()
{ {
importPage = new ImportPage(this); importPage = new ImportPage(this);
twitchPage = new TwitchPage(this);
return return
{ {
new VanillaPage(this), new VanillaPage(this),
importPage, importPage,
new LegacyFTB::Page(this), new LegacyFTB::Page(this),
twitchPage
}; };
} }

View File

@ -29,7 +29,6 @@ class NewInstanceDialog;
class PageContainer; class PageContainer;
class QDialogButtonBox; class QDialogButtonBox;
class ImportPage; class ImportPage;
class TwitchPage;
class NewInstanceDialog : public QDialog, public BasePageProvider class NewInstanceDialog : public QDialog, public BasePageProvider
{ {
@ -68,7 +67,6 @@ private:
QString InstIconKey; QString InstIconKey;
ImportPage *importPage = nullptr; ImportPage *importPage = nullptr;
TwitchPage *twitchPage = nullptr;
std::unique_ptr<InstanceTask> creationTask; std::unique_ptr<InstanceTask> creationTask;
bool importIcon = false; bool importIcon = false;

View File

@ -43,7 +43,6 @@ int main(int argc, char *argv[])
{ {
Q_INIT_RESOURCE(multimc); Q_INIT_RESOURCE(multimc);
Q_INIT_RESOURCE(backgrounds); Q_INIT_RESOURCE(backgrounds);
Q_INIT_RESOURCE(assets);
Q_INIT_RESOURCE(pe_dark); Q_INIT_RESOURCE(pe_dark);
Q_INIT_RESOURCE(pe_light); Q_INIT_RESOURCE(pe_light);

View File

@ -1,65 +0,0 @@
#include "TwitchPage.h"
#include "ui_TwitchPage.h"
#include "MultiMC.h"
#include "dialogs/NewInstanceDialog.h"
#include <InstanceImportTask.h>
TwitchPage::TwitchPage(NewInstanceDialog* dialog, QWidget *parent)
: QWidget(parent), ui(new Ui::TwitchPage), dialog(dialog)
{
ui->setupUi(this);
connect(ui->checkButton, &QPushButton::clicked, this, &TwitchPage::triggerCheck);
connect(ui->twitchLabel, &DropLabel::droppedURLs, [this](QList<QUrl> urls){
if(urls.size()) {
setUrl(urls[0].toString());
}
});
}
TwitchPage::~TwitchPage()
{
delete ui;
}
bool TwitchPage::shouldDisplay() const
{
return true;
}
void TwitchPage::openedImpl()
{
dialog->setSuggestedPack();
}
void TwitchPage::triggerCheck(bool)
{
if(m_modIdResolver) {
return;
}
auto task = new Flame::UrlResolvingTask(ui->lineEdit->text());
connect(task, &Task::finished, this, &TwitchPage::checkDone);
m_modIdResolver.reset(task);
task->start();
}
void TwitchPage::setUrl(const QString& url)
{
ui->lineEdit->setText(url);
triggerCheck(true);
}
void TwitchPage::checkDone()
{
auto result = m_modIdResolver->getResults();
auto formatted = QString("Project %1, File %2").arg(result.projectId).arg(result.fileId);
if(result.resolved && result.type == Flame::File::Type::Modpack) {
ui->twitchLabel->setText(formatted);
QFileInfo fi(result.fileName);
dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(result.url));
} else {
ui->twitchLabel->setPixmap(QPixmap(QString::fromUtf8(":/assets/deadglitch")));
dialog->setSuggestedPack();
}
m_modIdResolver.reset();
}

View File

@ -1,69 +0,0 @@
/* Copyright 2013-2019 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 <QWidget>
#include "pages/BasePage.h"
#include <MultiMC.h>
#include "tasks/Task.h"
#include "modplatform/flame/UrlResolvingTask.h"
namespace Ui
{
class TwitchPage;
}
class NewInstanceDialog;
class TwitchPage : public QWidget, public BasePage
{
Q_OBJECT
public:
explicit TwitchPage(NewInstanceDialog* dialog, QWidget *parent = 0);
virtual ~TwitchPage();
virtual QString displayName() const override
{
return tr("Twitch URL");
}
virtual QIcon icon() const override
{
return MMC->getThemedIcon("twitch");
}
virtual QString id() const override
{
return "twitch";
}
virtual QString helpPage() const override
{
return "Twitch-platform";
}
virtual bool shouldDisplay() const override;
void openedImpl() override;
void setUrl(const QString & url);
private slots:
void triggerCheck(bool checked);
void checkDone();
private:
Ui::TwitchPage *ui = nullptr;
NewInstanceDialog* dialog = nullptr;
shared_qobject_ptr<Flame::UrlResolvingTask> m_modIdResolver;
};

View File

@ -1,79 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>TwitchPage</class>
<widget class="QWidget" name="TwitchPage">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>666</width>
<height>424</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="0" colspan="3">
<widget class="DropLabel" name="twitchLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<pointsize>40</pointsize>
</font>
</property>
<property name="pixmap">
<pixmap resource="../../resources/assets/assets.qrc">:/assets/deadglitch</pixmap>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QPushButton" name="checkButton">
<property name="text">
<string>Check</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Twitch URL:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="lineEdit"/>
</item>
<item row="2" column="0" colspan="3">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Drag and drop an Install button from CurseForge into the area above.</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>DropLabel</class>
<extends>QLabel</extends>
<header>widgets/DropLabel.h</header>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>lineEdit</tabstop>
<tabstop>checkButton</tabstop>
</tabstops>
<resources>
<include location="../../resources/assets/assets.qrc"/>
</resources>
<connections/>
</ui>

View File

@ -1,6 +0,0 @@
<!DOCTYPE RCC>
<RCC version="1.0">
<qresource prefix="/assets">
<file alias="deadglitch">deadglitch.svg</file>
</qresource>
</RCC>

View File

@ -1,66 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
class="tw-svg__asset tw-svg__asset--deadglitch tw-svg__asset--inherit"
width="92px"
height="96px"
version="1.1"
viewBox="0 0 30 30"
x="0px"
y="0px"
id="svg8"
sodipodi:docname="deadglitch.svg"
inkscape:version="0.92.2 2405546, 2018-03-11">
<metadata
id="metadata14">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs12" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1353"
inkscape:window-height="828"
id="namedview10"
showgrid="false"
inkscape:zoom="4.9166667"
inkscape:cx="44.285787"
inkscape:cy="52.833458"
inkscape:window-x="2958"
inkscape:window-y="702"
inkscape:window-maximized="0"
inkscape:current-layer="svg8" />
<g
id="g6"
style="fill:#898395;fill-opacity:1">
<path
d="M26,17.4589613 L26,3 L4,3 L4,22.0601057 L10.0032868,22.0601057 L10.0032868,26 L14.0004537,22.0601057 L21.3322933,22.0601057 L26,17.4589613 L26,17.4589613 Z M21.0896458,26.0850335 L15.1583403,26.0850335 L11.2051771,30 L7.24798611,30 L7.24798611,26.0850335 L0,26.0850335 L0,5.21746493 L1.97773958,0 L29,0 L29,18.2620736 L21.0896458,26.0850335 L21.0896458,26.0850335 Z"
id="path2"
style="fill:#898395;fill-opacity:1" />
<path
d="M20.8587626,12.1710126 L22.4052753,13.7175252 L23.7175252,12.4052753 L22.1710126,10.8587626 L23.7175252,9.31224999 L22.4052753,8 L20.8587626,9.54651264 L19.31225,8 L18,9.31224999 L19.5465126,10.8587626 L18,12.4052753 L19.31225,13.7175252 L20.8587626,12.1710126 Z M11.8587626,12.1710126 L13.4052753,13.7175252 L14.7175252,12.4052753 L13.1710126,10.8587626 L14.7175252,9.31224999 L13.4052753,8 L11.8587626,9.54651264 L10.31225,8 L9,9.31224999 L10.5465126,10.8587626 L9,12.4052753 L10.31225,13.7175252 L11.8587626,12.1710126 Z"
id="path4"
style="fill:#898395;fill-opacity:1" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.6 KiB