mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-03-21 17:55:21 -05:00
Add the option to load decklists from Archidekt, Deckstats, Moxfield, TappedOut in deck editor and lobby (#6030)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / ${{matrix.distro}} ${{matrix.version}} (Debian, DEB, 12) (push) Blocked by required conditions
Build Desktop / ${{matrix.distro}} ${{matrix.version}} (Debian, DEB, skip, 11) (push) Blocked by required conditions
Build Desktop / ${{matrix.distro}} ${{matrix.version}} (Fedora, RPM, 42) (push) Blocked by required conditions
Build Desktop / ${{matrix.distro}} ${{matrix.version}} (Fedora, RPM, skip, 41) (push) Blocked by required conditions
Build Desktop / ${{matrix.distro}} ${{matrix.version}} (Ubuntu, DEB, 24.04) (push) Blocked by required conditions
Build Desktop / ${{matrix.distro}} ${{matrix.version}} (Ubuntu, DEB, skip, 22.04) (push) Blocked by required conditions
Build Desktop / ${{matrix.distro}} ${{matrix.version}} (yes, Arch, skip) (push) Blocked by required conditions
Build Desktop / macOS ${{matrix.target}}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }} (1, macos-13, Intel, 13, Release, 14.3.1) (push) Blocked by required conditions
Build Desktop / macOS ${{matrix.target}}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }} (1, macos-14, Apple, 14, Release, 15.4) (push) Blocked by required conditions
Build Desktop / macOS ${{matrix.target}}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }} (1, macos-15, Apple, 15, Release, 16.2) (push) Blocked by required conditions
Build Desktop / macOS ${{matrix.target}}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }} (macos-15, Apple, 15, Debug, 16.2) (push) Blocked by required conditions
Build Desktop / Windows ${{matrix.target}} (msvc2019_64, 5.15.*, 7) (push) Blocked by required conditions
Build Desktop / Windows ${{matrix.target}} (msvc2019_64, qtimageformats qtmultimedia qtwebsockets, 6.6.*, 10) (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / ${{matrix.distro}} ${{matrix.version}} (Debian, DEB, 12) (push) Blocked by required conditions
Build Desktop / ${{matrix.distro}} ${{matrix.version}} (Debian, DEB, skip, 11) (push) Blocked by required conditions
Build Desktop / ${{matrix.distro}} ${{matrix.version}} (Fedora, RPM, 42) (push) Blocked by required conditions
Build Desktop / ${{matrix.distro}} ${{matrix.version}} (Fedora, RPM, skip, 41) (push) Blocked by required conditions
Build Desktop / ${{matrix.distro}} ${{matrix.version}} (Ubuntu, DEB, 24.04) (push) Blocked by required conditions
Build Desktop / ${{matrix.distro}} ${{matrix.version}} (Ubuntu, DEB, skip, 22.04) (push) Blocked by required conditions
Build Desktop / ${{matrix.distro}} ${{matrix.version}} (yes, Arch, skip) (push) Blocked by required conditions
Build Desktop / macOS ${{matrix.target}}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }} (1, macos-13, Intel, 13, Release, 14.3.1) (push) Blocked by required conditions
Build Desktop / macOS ${{matrix.target}}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }} (1, macos-14, Apple, 14, Release, 15.4) (push) Blocked by required conditions
Build Desktop / macOS ${{matrix.target}}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }} (1, macos-15, Apple, 15, Release, 16.2) (push) Blocked by required conditions
Build Desktop / macOS ${{matrix.target}}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }} (macos-15, Apple, 15, Debug, 16.2) (push) Blocked by required conditions
Build Desktop / Windows ${{matrix.target}} (msvc2019_64, 5.15.*, 7) (push) Blocked by required conditions
Build Desktop / Windows ${{matrix.target}} (msvc2019_64, qtimageformats qtmultimedia qtwebsockets, 6.6.*, 10) (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
* Add the option to load decklists from Archidekt, Deckstats, Moxfield, TappedOut in deck editor and lobby. Took 3 hours 34 minutes Took 9 seconds Took 12 seconds * Properly set quantities. Took 11 minutes * Warnings. Took 5 minutes * Static regexes. Co-authored-by: RickyRister <42636155+RickyRister@users.noreply.github.com> * Category loggings and better warnings. Took 18 minutes Took 42 seconds * use loadFromStream_Plain instead of manually adding CardNodes to the DeckList. Took 30 minutes --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de> Co-authored-by: RickyRister <42636155+RickyRister@users.noreply.github.com>
This commit is contained in:
parent
83b90d472f
commit
e05dad4267
|
|
@ -16,6 +16,7 @@ set(cockatrice_SOURCES
|
|||
src/client/network/replay_timeline_widget.cpp
|
||||
src/client/network/sets_model.cpp
|
||||
src/client/network/spoiler_background_updater.cpp
|
||||
src/client/network/parsers/deck_link_to_api_transformer.cpp
|
||||
src/client/sound_engine.cpp
|
||||
src/client/tabs/abstract_tab_deck_editor.cpp
|
||||
src/client/tabs/api/edhrec/tab_edhrec.cpp
|
||||
|
|
@ -165,6 +166,7 @@ set(cockatrice_SOURCES
|
|||
src/dialogs/dlg_forgot_password_reset.cpp
|
||||
src/dialogs/dlg_load_deck.cpp
|
||||
src/dialogs/dlg_load_deck_from_clipboard.cpp
|
||||
src/dialogs/dlg_load_deck_from_website.cpp
|
||||
src/dialogs/dlg_load_remote_deck.cpp
|
||||
src/dialogs/dlg_manage_sets.cpp
|
||||
src/dialogs/dlg_move_top_cards_until.cpp
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
#tab_supervisor = true
|
||||
|
||||
#dlg_edit_avatar = true
|
||||
#dlg_load_deck_from_website = true
|
||||
#dlg_settings = true
|
||||
#dlg_tip_of_the_day = true
|
||||
#dlg_update = true
|
||||
|
|
|
|||
|
|
@ -54,6 +54,9 @@ DeckEditorMenu::DeckEditorMenu(AbstractTabDeckEditor *parent) : QMenu(parent), d
|
|||
aPrintDeck = new QAction(QString(), this);
|
||||
connect(aPrintDeck, &QAction::triggered, deckEditor, &AbstractTabDeckEditor::actPrintDeck);
|
||||
|
||||
aLoadDeckFromWebsite = new QAction(QString(), this);
|
||||
connect(aLoadDeckFromWebsite, &QAction::triggered, deckEditor, &AbstractTabDeckEditor::actLoadDeckFromWebsite);
|
||||
|
||||
aExportDeckDecklist = new QAction(QString(), this);
|
||||
connect(aExportDeckDecklist, &QAction::triggered, deckEditor, &AbstractTabDeckEditor::actExportDeckDecklist);
|
||||
|
||||
|
|
@ -97,6 +100,7 @@ DeckEditorMenu::DeckEditorMenu(AbstractTabDeckEditor *parent) : QMenu(parent), d
|
|||
addMenu(saveDeckToClipboardMenu);
|
||||
addSeparator();
|
||||
addAction(aPrintDeck);
|
||||
addAction(aLoadDeckFromWebsite);
|
||||
addMenu(analyzeDeckMenu);
|
||||
addSeparator();
|
||||
addAction(deckEditor->filterDockWidget->aClearFilterOne);
|
||||
|
|
@ -166,6 +170,7 @@ void DeckEditorMenu::retranslateUi()
|
|||
|
||||
aPrintDeck->setText(tr("&Print deck..."));
|
||||
|
||||
aLoadDeckFromWebsite->setText(tr("Load deck from online service..."));
|
||||
analyzeDeckMenu->setTitle(tr("&Send deck to online service"));
|
||||
aExportDeckDecklist->setText(tr("Create decklist (decklist.org)"));
|
||||
aExportDeckDecklistXyz->setText(tr("Create decklist (decklist.xyz)"));
|
||||
|
|
|
|||
|
|
@ -16,8 +16,8 @@ public:
|
|||
|
||||
QAction *aNewDeck, *aLoadDeck, *aClearRecents, *aSaveDeck, *aSaveDeckAs, *aLoadDeckFromClipboard,
|
||||
*aEditDeckInClipboard, *aEditDeckInClipboardRaw, *aSaveDeckToClipboard, *aSaveDeckToClipboardNoSetInfo,
|
||||
*aSaveDeckToClipboardRaw, *aSaveDeckToClipboardRawNoSetInfo, *aPrintDeck, *aExportDeckDecklist,
|
||||
*aExportDeckDecklistXyz, *aAnalyzeDeckDeckstats, *aAnalyzeDeckTappedout, *aClose;
|
||||
*aSaveDeckToClipboardRaw, *aSaveDeckToClipboardRawNoSetInfo, *aPrintDeck, *aLoadDeckFromWebsite,
|
||||
*aExportDeckDecklist, *aExportDeckDecklistXyz, *aAnalyzeDeckDeckstats, *aAnalyzeDeckTappedout, *aClose;
|
||||
QMenu *loadRecentDeckMenu, *analyzeDeckMenu, *editDeckInClipboardMenu, *saveDeckToClipboardMenu;
|
||||
|
||||
void setSaveStatus(bool newStatus);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,61 @@
|
|||
#include "deck_link_to_api_transformer.h"
|
||||
|
||||
#include <QRegularExpression>
|
||||
|
||||
namespace DeckLinkToApiTransformer
|
||||
{
|
||||
|
||||
static const QString TAPPEDOUT_BASE = "https://tappedout.net/mtg-decks/";
|
||||
static const QString TAPPEDOUT_SUFFIX = "/?fmt=txt";
|
||||
|
||||
static const QString ARCHIDEKT_BASE = "https://archidekt.com/api/decks/";
|
||||
static const QString ARCHIDEKT_SUFFIX = "/?format=json";
|
||||
|
||||
static const QString MOXFIELD_BASE = "https://api.moxfield.com/v2/decks/all/";
|
||||
static const QString MOXFIELD_SUFFIX = "/";
|
||||
|
||||
static const QString DECKSTATS_SUFFIX = "?include_comments=1&export_mtgarena=1";
|
||||
|
||||
bool parseDeckUrl(const QString &url, ParsedDeckInfo &outInfo)
|
||||
{
|
||||
static QRegularExpression rxTappedOut("tappedout\\.net/(?:mtg-decks/)?([^/?#]+)");
|
||||
static QRegularExpression rxArchidekt("archidekt\\.com/decks/(\\d+)");
|
||||
static QRegularExpression rxMoxfield("moxfield\\.com/decks/([a-zA-Z0-9_-]+)");
|
||||
static QRegularExpression rxDeckstats("deckstats\\.net/decks/(\\d+/[a-zA-Z0-9_-]+)");
|
||||
|
||||
QRegularExpressionMatch match;
|
||||
|
||||
if ((match = rxTappedOut.match(url)).hasMatch()) {
|
||||
QString slug = match.captured(1);
|
||||
outInfo = ParsedDeckInfo{.baseUrl = TAPPEDOUT_BASE,
|
||||
.deckID = slug,
|
||||
.fullUrl = TAPPEDOUT_BASE + slug + TAPPEDOUT_SUFFIX,
|
||||
.provider = DeckProvider::TappedOut};
|
||||
return true;
|
||||
} else if ((match = rxArchidekt.match(url)).hasMatch()) {
|
||||
QString deckID = match.captured(1);
|
||||
outInfo = ParsedDeckInfo{.baseUrl = ARCHIDEKT_BASE,
|
||||
.deckID = deckID,
|
||||
.fullUrl = ARCHIDEKT_BASE + deckID + ARCHIDEKT_SUFFIX,
|
||||
.provider = DeckProvider::Archidekt};
|
||||
return true;
|
||||
} else if ((match = rxMoxfield.match(url)).hasMatch()) {
|
||||
QString deckID = match.captured(1);
|
||||
outInfo = ParsedDeckInfo{.baseUrl = MOXFIELD_BASE,
|
||||
.deckID = deckID,
|
||||
.fullUrl = MOXFIELD_BASE + deckID + MOXFIELD_SUFFIX,
|
||||
.provider = DeckProvider::Moxfield};
|
||||
return true;
|
||||
} else if ((match = rxDeckstats.match(url)).hasMatch()) {
|
||||
QString deckPath = match.captured(1);
|
||||
outInfo = ParsedDeckInfo{.baseUrl = "https://deckstats.net/decks/",
|
||||
.deckID = deckPath,
|
||||
.fullUrl = "https://deckstats.net/decks/" + deckPath + DECKSTATS_SUFFIX,
|
||||
.provider = DeckProvider::Deckstats};
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace DeckLinkToApiTransformer
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
#ifndef DECK_LINK_TO_API_TRANSFORMER_H
|
||||
#define DECK_LINK_TO_API_TRANSFORMER_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
enum class DeckProvider
|
||||
{
|
||||
TappedOut,
|
||||
Archidekt,
|
||||
Moxfield,
|
||||
Deckstats,
|
||||
Unknown
|
||||
};
|
||||
|
||||
struct ParsedDeckInfo
|
||||
{
|
||||
QString baseUrl;
|
||||
QString deckID;
|
||||
QString fullUrl;
|
||||
DeckProvider provider;
|
||||
};
|
||||
|
||||
namespace DeckLinkToApiTransformer
|
||||
{
|
||||
|
||||
// Returns true if the input URL is recognized and fills outInfo.
|
||||
bool parseDeckUrl(const QString &url, ParsedDeckInfo &outInfo);
|
||||
|
||||
} // namespace DeckLinkToApiTransformer
|
||||
|
||||
#endif // DECK_LINK_TO_API_TRANSFORMER_H
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
#ifndef INTERFACE_JSON_DECK_PARSER_H
|
||||
#define INTERFACE_JSON_DECK_PARSER_H
|
||||
#include "../../../deck/deck_loader.h"
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
|
||||
class IJsonDeckParser
|
||||
{
|
||||
public:
|
||||
virtual ~IJsonDeckParser() = default;
|
||||
|
||||
virtual DeckLoader *parse(const QJsonObject &obj) = 0;
|
||||
};
|
||||
|
||||
class ArchidektJsonParser : public IJsonDeckParser
|
||||
{
|
||||
public:
|
||||
DeckLoader *parse(const QJsonObject &obj) override
|
||||
{
|
||||
DeckLoader *list = new DeckLoader();
|
||||
|
||||
QString deckName = obj.value("name").toString();
|
||||
QString deckDescription = obj.value("description").toString();
|
||||
|
||||
list->setName(deckName);
|
||||
list->setComments(deckDescription);
|
||||
|
||||
QString outputText;
|
||||
QTextStream outStream(&outputText);
|
||||
|
||||
for (auto entry : obj.value("cards").toArray()) {
|
||||
auto quantity = entry.toObject().value("quantity").toInt();
|
||||
|
||||
auto card = entry.toObject().value("card").toObject();
|
||||
auto oracleCard = card.value("oracleCard").toObject();
|
||||
QString cardName = oracleCard.value("name").toString();
|
||||
QString setName = card.value("edition").toObject().value("editioncode").toString().toUpper();
|
||||
QString collectorNumber = card.value("collectorNumber").toString();
|
||||
|
||||
outStream << quantity << ' ' << cardName << " (" << setName << ") " << collectorNumber << '\n';
|
||||
}
|
||||
|
||||
list->loadFromStream_Plain(outStream, false);
|
||||
list->resolveSetNameAndNumberToProviderID();
|
||||
|
||||
return list;
|
||||
}
|
||||
};
|
||||
|
||||
class MoxfieldJsonParser : public IJsonDeckParser
|
||||
{
|
||||
public:
|
||||
DeckLoader *parse(const QJsonObject &obj) override
|
||||
{
|
||||
DeckLoader *list = new DeckLoader();
|
||||
|
||||
QString deckName = obj.value("name").toString();
|
||||
QString deckDescription = obj.value("description").toString();
|
||||
|
||||
list->setName(deckName);
|
||||
list->setComments(deckDescription);
|
||||
|
||||
QString outputText;
|
||||
QTextStream outStream(&outputText);
|
||||
|
||||
for (auto entry : obj.value("mainboard").toObject()) {
|
||||
auto quantity = entry.toObject().value("quantity").toInt();
|
||||
|
||||
auto card = entry.toObject().value("card").toObject();
|
||||
QString cardName = card.value("name").toString();
|
||||
QString setName = card.value("set").toString().toUpper();
|
||||
QString collectorNumber = card.value("cn").toString();
|
||||
|
||||
outStream << quantity << ' ' << cardName << " (" << setName << ") " << collectorNumber << '\n';
|
||||
}
|
||||
|
||||
outStream << '\n';
|
||||
|
||||
for (auto entry : obj.value("sideboard").toObject()) {
|
||||
auto quantity = entry.toObject().value("quantity").toInt();
|
||||
|
||||
auto card = entry.toObject().value("card").toObject();
|
||||
QString cardName = card.value("name").toString();
|
||||
QString setName = card.value("set").toString().toUpper();
|
||||
QString collectorNumber = card.value("cn").toString();
|
||||
|
||||
outStream << quantity << ' ' << cardName << " (" << setName << ") " << collectorNumber << '\n';
|
||||
}
|
||||
|
||||
list->loadFromStream_Plain(outStream, false);
|
||||
list->resolveSetNameAndNumberToProviderID();
|
||||
|
||||
QJsonObject commandersObj = obj.value("commanders").toObject();
|
||||
if (!commandersObj.isEmpty()) {
|
||||
for (auto it = commandersObj.begin(); it != commandersObj.end(); ++it) {
|
||||
QJsonObject cardData = it.value().toObject().value("card").toObject();
|
||||
QString commanderName = cardData.value("name").toString();
|
||||
QString setName = cardData.value("set").toString().toUpper();
|
||||
QString collectorNumber = cardData.value("cn").toString();
|
||||
QString providerId = cardData.value("scryfall_id").toString();
|
||||
|
||||
list->setBannerCard(QPair<QString, QString>(commanderName, providerId));
|
||||
list->addCard(commanderName, DECK_ZONE_MAIN, -1, setName, collectorNumber, providerId);
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // INTERFACE_JSON_DECK_PARSER_H
|
||||
|
|
@ -6,6 +6,7 @@
|
|||
#include "../../deck/deck_stats_interface.h"
|
||||
#include "../../dialogs/dlg_load_deck.h"
|
||||
#include "../../dialogs/dlg_load_deck_from_clipboard.h"
|
||||
#include "../../dialogs/dlg_load_deck_from_website.h"
|
||||
#include "../../game/cards/card_database_manager.h"
|
||||
#include "../../game/cards/card_database_model.h"
|
||||
#include "../../server/pending_command.h"
|
||||
|
|
@ -467,6 +468,28 @@ void AbstractTabDeckEditor::actPrintDeck()
|
|||
dlg->exec();
|
||||
}
|
||||
|
||||
void AbstractTabDeckEditor::actLoadDeckFromWebsite()
|
||||
{
|
||||
auto deckOpenLocation = confirmOpen();
|
||||
|
||||
if (deckOpenLocation == CANCELLED) {
|
||||
return;
|
||||
}
|
||||
|
||||
DlgLoadDeckFromWebsite dlg(this);
|
||||
if (!dlg.exec())
|
||||
return;
|
||||
|
||||
if (deckOpenLocation == NEW_TAB) {
|
||||
emit openDeckEditor(dlg.getDeck());
|
||||
} else {
|
||||
setDeck(dlg.getDeck());
|
||||
setModified(true);
|
||||
}
|
||||
|
||||
deckMenu->setSaveStatus(true);
|
||||
}
|
||||
|
||||
void AbstractTabDeckEditor::exportToDecklistWebsite(DeckLoader::DecklistWebsite website)
|
||||
{
|
||||
// check if deck is not null
|
||||
|
|
|
|||
|
|
@ -102,6 +102,7 @@ protected slots:
|
|||
void actSaveDeckToClipboardRaw();
|
||||
void actSaveDeckToClipboardRawNoSetInfo();
|
||||
void actPrintDeck();
|
||||
void actLoadDeckFromWebsite();
|
||||
void actExportDeckDecklist();
|
||||
void actExportDeckDecklistXyz();
|
||||
void actAnalyzeDeckDeckstats();
|
||||
|
|
|
|||
145
cockatrice/src/dialogs/dlg_load_deck_from_website.cpp
Normal file
145
cockatrice/src/dialogs/dlg_load_deck_from_website.cpp
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
#include "dlg_load_deck_from_website.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QClipboard>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QEventLoop>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QMessageBox>
|
||||
#include <QNetworkReply>
|
||||
|
||||
DlgLoadDeckFromWebsite::DlgLoadDeckFromWebsite(QWidget *parent) : QDialog(parent)
|
||||
{
|
||||
nam = new QNetworkAccessManager(this);
|
||||
|
||||
layout = new QVBoxLayout(this);
|
||||
setLayout(layout);
|
||||
|
||||
instructionLabel = new QLabel(this);
|
||||
layout->addWidget(instructionLabel);
|
||||
|
||||
urlEdit = new QLineEdit(this);
|
||||
urlEdit->setText(QApplication::clipboard()->text());
|
||||
|
||||
layout->addWidget(urlEdit);
|
||||
|
||||
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||
connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||
connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
|
||||
layout->addWidget(buttonBox);
|
||||
|
||||
if (testValidUrl()) {
|
||||
QMetaObject::invokeMethod(this, "accept", Qt::QueuedConnection);
|
||||
hide();
|
||||
}
|
||||
|
||||
retranslateUi();
|
||||
}
|
||||
|
||||
void DlgLoadDeckFromWebsite::retranslateUi()
|
||||
{
|
||||
instructionLabel->setText(tr("Paste a link to a decklist site here to import it.\n(Archidekt, Deckstats, Moxfield, "
|
||||
"and TappedOut are supported.)"));
|
||||
}
|
||||
|
||||
bool DlgLoadDeckFromWebsite::testValidUrl()
|
||||
{
|
||||
ParsedDeckInfo info;
|
||||
return DeckLinkToApiTransformer::parseDeckUrl(urlEdit->text(), info);
|
||||
}
|
||||
|
||||
void DlgLoadDeckFromWebsite::accept()
|
||||
{
|
||||
ParsedDeckInfo info;
|
||||
if (DeckLinkToApiTransformer::parseDeckUrl(urlEdit->text(), info)) {
|
||||
qCInfo(DlgLoadDeckFromWebsiteLog) << info.baseUrl << info.deckID << info.fullUrl;
|
||||
|
||||
auto jsonParser = createParserForProvider(info.provider);
|
||||
if (!jsonParser && info.provider != DeckProvider::Deckstats && info.provider != DeckProvider::TappedOut) {
|
||||
qCWarning(DlgLoadDeckFromWebsiteLog) << "No parser found for provider";
|
||||
QMessageBox::warning(this, tr("Load Deck from Website"),
|
||||
tr("No parser available for this deck provider.\n (Archidekt, Deckstats, Moxfield, "
|
||||
"and TappedOut are supported.)"));
|
||||
QDialog::reject();
|
||||
return;
|
||||
}
|
||||
|
||||
QNetworkRequest request(QUrl(info.fullUrl));
|
||||
QNetworkReply *reply = nam->get(request);
|
||||
|
||||
QEventLoop loop;
|
||||
connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
|
||||
loop.exec();
|
||||
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
qCWarning(DlgLoadDeckFromWebsiteLog) << "Network error:" << reply->errorString();
|
||||
QMessageBox::warning(this, tr("Load Deck from Website"), tr("Network error: %1").arg(reply->errorString()));
|
||||
reply->deleteLater();
|
||||
QDialog::reject();
|
||||
return;
|
||||
}
|
||||
|
||||
QByteArray responseData = reply->readAll();
|
||||
reply->deleteLater();
|
||||
|
||||
// Special handling for Deckstats and TappedOut .txt
|
||||
if (info.provider == DeckProvider::Deckstats || info.provider == DeckProvider::TappedOut) {
|
||||
QString deckText = QString::fromUtf8(responseData);
|
||||
if (deckText.isEmpty()) {
|
||||
qCWarning(DlgLoadDeckFromWebsiteLog) << "Response is empty";
|
||||
QMessageBox::warning(this, tr("Load Deck from Website"), tr("Received empty deck data."));
|
||||
QDialog::reject();
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse the plain text deck here
|
||||
DeckLoader *loader = new DeckLoader();
|
||||
QTextStream stream(&deckText);
|
||||
loader->loadFromStream_Plain(stream, false);
|
||||
loader->resolveSetNameAndNumberToProviderID();
|
||||
deck = loader;
|
||||
|
||||
QDialog::accept();
|
||||
return;
|
||||
}
|
||||
|
||||
// Normal JSON parsing for other providers
|
||||
QJsonParseError parseError;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(responseData, &parseError);
|
||||
if (parseError.error != QJsonParseError::NoError) {
|
||||
qCWarning(DlgLoadDeckFromWebsiteLog) << "JSON parse error:" << parseError.errorString();
|
||||
QMessageBox::warning(this, tr("Load Deck from Website"),
|
||||
tr("Failed to parse deck data: %1").arg(parseError.errorString()));
|
||||
QDialog::reject();
|
||||
return;
|
||||
}
|
||||
|
||||
deck = jsonParser->parse(doc.object());
|
||||
QDialog::accept();
|
||||
|
||||
} else {
|
||||
qCInfo(DlgLoadDeckFromWebsiteLog) << "URL not recognized";
|
||||
QMessageBox::warning(this, tr("Load Deck from Website"),
|
||||
tr("The provided URL is not recognized as a valid deck URL.\n"
|
||||
"Valid deck URLs look like this:\n\n"
|
||||
"https://archidekt.com/decks/9999999\n"
|
||||
"https://deckstats.net/decks/99999/9999999-your-deck-name/en\n"
|
||||
"https://moxfield.com/decks/XYZxx-XYZ99Yyy-xyzXzzz\n"
|
||||
"https://tappedout.net/mtg-decks/your-deck-name/"));
|
||||
QDialog::reject();
|
||||
}
|
||||
}
|
||||
|
||||
QSharedPointer<IJsonDeckParser> DlgLoadDeckFromWebsite::createParserForProvider(DeckProvider provider)
|
||||
{
|
||||
switch (provider) {
|
||||
case DeckProvider::Archidekt:
|
||||
return QSharedPointer<IJsonDeckParser>(new ArchidektJsonParser());
|
||||
case DeckProvider::Moxfield:
|
||||
return QSharedPointer<IJsonDeckParser>(new MoxfieldJsonParser());
|
||||
default:
|
||||
return QSharedPointer<IJsonDeckParser>(nullptr);
|
||||
}
|
||||
}
|
||||
40
cockatrice/src/dialogs/dlg_load_deck_from_website.h
Normal file
40
cockatrice/src/dialogs/dlg_load_deck_from_website.h
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
#ifndef DLG_LOAD_DECK_FROM_WEBSITE_H
|
||||
#define DLG_LOAD_DECK_FROM_WEBSITE_H
|
||||
|
||||
#include "../client/network/parsers/deck_link_to_api_transformer.h"
|
||||
#include "../client/network/parsers/interface_json_deck_parser.h"
|
||||
|
||||
#include <QDialog>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
inline Q_LOGGING_CATEGORY(DlgLoadDeckFromWebsiteLog, "dlg_load_deck_from_website");
|
||||
|
||||
class DlgLoadDeckFromWebsite : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit DlgLoadDeckFromWebsite(QWidget *parent);
|
||||
void retranslateUi();
|
||||
bool testValidUrl();
|
||||
DeckLoader *deck;
|
||||
|
||||
DeckLoader *getDeck()
|
||||
{
|
||||
return deck;
|
||||
}
|
||||
|
||||
private:
|
||||
QNetworkAccessManager *nam;
|
||||
QVBoxLayout *layout;
|
||||
QLabel *instructionLabel;
|
||||
QLineEdit *urlEdit;
|
||||
|
||||
public slots:
|
||||
void accept() override;
|
||||
QSharedPointer<IJsonDeckParser> createParserForProvider(DeckProvider provider);
|
||||
};
|
||||
|
||||
#endif // DLG_LOAD_DECK_FROM_WEBSITE_H
|
||||
|
|
@ -5,6 +5,7 @@
|
|||
#include "../../deck/deck_loader.h"
|
||||
#include "../../dialogs/dlg_load_deck.h"
|
||||
#include "../../dialogs/dlg_load_deck_from_clipboard.h"
|
||||
#include "../../dialogs/dlg_load_deck_from_website.h"
|
||||
#include "../../dialogs/dlg_load_remote_deck.h"
|
||||
#include "../../server/pending_command.h"
|
||||
#include "../../settings/cache_settings.h"
|
||||
|
|
@ -54,6 +55,7 @@ DeckViewContainer::DeckViewContainer(int _playerId, TabGame *parent)
|
|||
loadLocalButton = new QPushButton;
|
||||
loadRemoteButton = new QPushButton;
|
||||
loadFromClipboardButton = new QPushButton;
|
||||
loadFromWebsiteButton = new QPushButton;
|
||||
unloadDeckButton = new QPushButton;
|
||||
readyStartButton = new ToggleButton;
|
||||
forceStartGameButton = new QPushButton;
|
||||
|
|
@ -62,6 +64,7 @@ DeckViewContainer::DeckViewContainer(int _playerId, TabGame *parent)
|
|||
connect(loadLocalButton, &QPushButton::clicked, this, &DeckViewContainer::loadLocalDeck);
|
||||
connect(loadRemoteButton, &QPushButton::clicked, this, &DeckViewContainer::loadRemoteDeck);
|
||||
connect(loadFromClipboardButton, &QPushButton::clicked, this, &DeckViewContainer::loadFromClipboard);
|
||||
connect(loadFromWebsiteButton, &QPushButton::clicked, this, &DeckViewContainer::loadFromWebsite);
|
||||
connect(readyStartButton, &QPushButton::clicked, this, &DeckViewContainer::readyStart);
|
||||
connect(unloadDeckButton, &QPushButton::clicked, this, &DeckViewContainer::unloadDeck);
|
||||
connect(forceStartGameButton, &QPushButton::clicked, this, &DeckViewContainer::forceStart);
|
||||
|
|
@ -72,6 +75,7 @@ DeckViewContainer::DeckViewContainer(int _playerId, TabGame *parent)
|
|||
buttonHBox->addWidget(loadLocalButton);
|
||||
buttonHBox->addWidget(loadRemoteButton);
|
||||
buttonHBox->addWidget(loadFromClipboardButton);
|
||||
buttonHBox->addWidget(loadFromWebsiteButton);
|
||||
buttonHBox->addWidget(unloadDeckButton);
|
||||
buttonHBox->addWidget(readyStartButton);
|
||||
buttonHBox->addWidget(sideboardLockButton);
|
||||
|
|
@ -123,6 +127,7 @@ void DeckViewContainer::retranslateUi()
|
|||
loadLocalButton->setText(tr("Load deck..."));
|
||||
loadRemoteButton->setText(tr("Load remote deck..."));
|
||||
loadFromClipboardButton->setText(tr("Load from clipboard..."));
|
||||
loadFromWebsiteButton->setText(tr("Load from website..."));
|
||||
unloadDeckButton->setText(tr("Unload deck"));
|
||||
readyStartButton->setText(tr("Ready to start"));
|
||||
forceStartGameButton->setText(tr("Force start"));
|
||||
|
|
@ -154,6 +159,7 @@ void DeckViewContainer::switchToDeckSelectView()
|
|||
setVisibility(loadLocalButton, true);
|
||||
setVisibility(loadRemoteButton, !parentGame->getIsLocalGame());
|
||||
setVisibility(loadFromClipboardButton, true);
|
||||
setVisibility(loadFromWebsiteButton, true);
|
||||
setVisibility(unloadDeckButton, false);
|
||||
setVisibility(readyStartButton, false);
|
||||
setVisibility(sideboardLockButton, false);
|
||||
|
|
@ -179,6 +185,7 @@ void DeckViewContainer::switchToDeckLoadedView()
|
|||
setVisibility(loadLocalButton, false);
|
||||
setVisibility(loadRemoteButton, false);
|
||||
setVisibility(loadFromClipboardButton, false);
|
||||
setVisibility(loadFromWebsiteButton, false);
|
||||
setVisibility(unloadDeckButton, true);
|
||||
setVisibility(readyStartButton, true);
|
||||
setVisibility(sideboardLockButton, true);
|
||||
|
|
@ -309,6 +316,18 @@ void DeckViewContainer::loadFromClipboard()
|
|||
loadDeckFromDeckLoader(deck);
|
||||
}
|
||||
|
||||
void DeckViewContainer::loadFromWebsite()
|
||||
{
|
||||
auto dlg = DlgLoadDeckFromWebsite(this);
|
||||
|
||||
if (!dlg.exec()) {
|
||||
return;
|
||||
}
|
||||
|
||||
DeckLoader *deck = dlg.getDeck();
|
||||
loadDeckFromDeckLoader(deck);
|
||||
}
|
||||
|
||||
void DeckViewContainer::deckSelectFinished(const Response &r)
|
||||
{
|
||||
const Response_DeckDownload &resp = r.GetExtension(Response_DeckDownload::ext);
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ class DeckViewContainer : public QWidget
|
|||
Q_OBJECT
|
||||
private:
|
||||
QVBoxLayout *deckViewLayout;
|
||||
QPushButton *loadLocalButton, *loadRemoteButton, *loadFromClipboardButton;
|
||||
QPushButton *loadLocalButton, *loadRemoteButton, *loadFromClipboardButton, *loadFromWebsiteButton;
|
||||
QPushButton *unloadDeckButton, *forceStartGameButton;
|
||||
ToggleButton *readyStartButton, *sideboardLockButton;
|
||||
DeckView *deckView;
|
||||
|
|
@ -60,6 +60,7 @@ private slots:
|
|||
void loadLocalDeck();
|
||||
void loadRemoteDeck();
|
||||
void loadFromClipboard();
|
||||
void loadFromWebsite();
|
||||
void unloadDeck();
|
||||
void readyStart();
|
||||
void forceStart();
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user