mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-05 01:23:24 -08:00
[Client] Add option to load decks from ManaBox (#7642)
* [Client] Add option to load decks from ManaBox * [Client] Fix PR comments * [Client] Fix UI and transaltion requests * [Client] Fix for UI and parser not doing what expected when loading maybe board * Add ManaBox support to docs As pointed out in review comments * [Client] Fix final PR comments --------- Co-authored-by: tooomm <tooomm@users.noreply.github.com>
This commit is contained in:
parent
c5b776ca46
commit
381dd21e8c
5 changed files with 126 additions and 14 deletions
|
|
@ -15,14 +15,18 @@ static const QString ARCHIDEKT_SUFFIX = "/?format=json";
|
||||||
static const QString MOXFIELD_BASE = "https://api.moxfield.com/v2/decks/all/";
|
static const QString MOXFIELD_BASE = "https://api.moxfield.com/v2/decks/all/";
|
||||||
static const QString MOXFIELD_SUFFIX = "/";
|
static const QString MOXFIELD_SUFFIX = "/";
|
||||||
|
|
||||||
|
static const QString DECKSTATS_BASE = "https://deckstats.net/decks/";
|
||||||
static const QString DECKSTATS_SUFFIX = "?include_comments=1&export_mtgarena=1";
|
static const QString DECKSTATS_SUFFIX = "?include_comments=1&export_mtgarena=1";
|
||||||
|
|
||||||
|
static const QString MANABOX_BASE = "https://cloud.manabox.app/decks/";
|
||||||
|
|
||||||
bool parseDeckUrl(const QString &url, ParsedDeckInfo &outInfo)
|
bool parseDeckUrl(const QString &url, ParsedDeckInfo &outInfo)
|
||||||
{
|
{
|
||||||
static QRegularExpression rxTappedOut("tappedout\\.net/(?:mtg-decks/)?([^/?#]+)");
|
static QRegularExpression rxTappedOut("tappedout\\.net/(?:mtg-decks/)?([^/?#]+)");
|
||||||
static QRegularExpression rxArchidekt("archidekt\\.com/decks/(\\d+)");
|
static QRegularExpression rxArchidekt("archidekt\\.com/decks/(\\d+)");
|
||||||
static QRegularExpression rxMoxfield("moxfield\\.com/decks/([a-zA-Z0-9_-]+)");
|
static QRegularExpression rxMoxfield("moxfield\\.com/decks/([a-zA-Z0-9_-]+)");
|
||||||
static QRegularExpression rxDeckstats("deckstats\\.net/decks/(\\d+/[a-zA-Z0-9_-]+)");
|
static QRegularExpression rxDeckstats("deckstats\\.net/decks/(\\d+/[a-zA-Z0-9_-]+)");
|
||||||
|
static QRegularExpression rxManabox("manabox\\.app/decks/([a-zA-Z0-9_-]+)");
|
||||||
|
|
||||||
QRegularExpressionMatch match;
|
QRegularExpressionMatch match;
|
||||||
|
|
||||||
|
|
@ -49,11 +53,18 @@ bool parseDeckUrl(const QString &url, ParsedDeckInfo &outInfo)
|
||||||
return true;
|
return true;
|
||||||
} else if ((match = rxDeckstats.match(url)).hasMatch()) {
|
} else if ((match = rxDeckstats.match(url)).hasMatch()) {
|
||||||
QString deckPath = match.captured(1);
|
QString deckPath = match.captured(1);
|
||||||
outInfo = ParsedDeckInfo{.baseUrl = "https://deckstats.net/decks/",
|
outInfo = ParsedDeckInfo{.baseUrl = DECKSTATS_BASE,
|
||||||
.deckID = deckPath,
|
.deckID = deckPath,
|
||||||
.fullUrl = "https://deckstats.net/decks/" + deckPath + DECKSTATS_SUFFIX,
|
.fullUrl = DECKSTATS_BASE + deckPath + DECKSTATS_SUFFIX,
|
||||||
.provider = DeckProvider::Deckstats};
|
.provider = DeckProvider::Deckstats};
|
||||||
return true;
|
return true;
|
||||||
|
} else if ((match = rxManabox.match(url)).hasMatch()) {
|
||||||
|
QString deckPath = match.captured(1);
|
||||||
|
outInfo = ParsedDeckInfo{.baseUrl = MANABOX_BASE,
|
||||||
|
.deckID = deckPath,
|
||||||
|
.fullUrl = MANABOX_BASE + deckPath,
|
||||||
|
.provider = DeckProvider::Manabox};
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ enum class DeckProvider
|
||||||
Archidekt,
|
Archidekt,
|
||||||
Moxfield,
|
Moxfield,
|
||||||
Deckstats,
|
Deckstats,
|
||||||
|
Manabox,
|
||||||
Unknown
|
Unknown
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,12 +8,19 @@
|
||||||
#define INTERFACE_JSON_DECK_PARSER_H
|
#define INTERFACE_JSON_DECK_PARSER_H
|
||||||
|
|
||||||
#include "../../../interface/deck_loader/card_node_function.h"
|
#include "../../../interface/deck_loader/card_node_function.h"
|
||||||
|
#include "libcockatrice/deck_list/tree/inner_deck_list_node.h"
|
||||||
|
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
|
#include <QList>
|
||||||
|
#include <QLoggingCategory>
|
||||||
|
#include <QString>
|
||||||
|
#include <QTextStream>
|
||||||
#include <libcockatrice/card/import/card_name_normalizer.h>
|
#include <libcockatrice/card/import/card_name_normalizer.h>
|
||||||
#include <libcockatrice/deck_list/deck_list.h>
|
#include <libcockatrice/deck_list/deck_list.h>
|
||||||
|
|
||||||
|
inline Q_LOGGING_CATEGORY(InterfaceJsonDeckParserLog, "interface_json_deck_parser");
|
||||||
|
|
||||||
class IJsonDeckParser
|
class IJsonDeckParser
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
@ -32,9 +39,6 @@ public:
|
||||||
QString deckName = obj.value("name").toString();
|
QString deckName = obj.value("name").toString();
|
||||||
QString deckDescription = obj.value("description").toString();
|
QString deckDescription = obj.value("description").toString();
|
||||||
|
|
||||||
deckList.setName(deckName);
|
|
||||||
deckList.setComments(deckDescription);
|
|
||||||
|
|
||||||
QString outputText;
|
QString outputText;
|
||||||
QTextStream outStream(&outputText);
|
QTextStream outStream(&outputText);
|
||||||
|
|
||||||
|
|
@ -52,6 +56,8 @@ public:
|
||||||
|
|
||||||
deckList.loadFromStream_Plain(outStream, false, CardNameNormalizer());
|
deckList.loadFromStream_Plain(outStream, false, CardNameNormalizer());
|
||||||
deckList.forEachCard(CardNodeFunction::ResolveProviderId());
|
deckList.forEachCard(CardNodeFunction::ResolveProviderId());
|
||||||
|
deckList.setName(deckName);
|
||||||
|
deckList.setComments(deckDescription);
|
||||||
|
|
||||||
return deckList;
|
return deckList;
|
||||||
}
|
}
|
||||||
|
|
@ -67,9 +73,6 @@ public:
|
||||||
QString deckName = obj.value("name").toString();
|
QString deckName = obj.value("name").toString();
|
||||||
QString deckDescription = obj.value("description").toString();
|
QString deckDescription = obj.value("description").toString();
|
||||||
|
|
||||||
deckList.setName(deckName);
|
|
||||||
deckList.setComments(deckDescription);
|
|
||||||
|
|
||||||
QString outputText;
|
QString outputText;
|
||||||
QTextStream outStream(&outputText);
|
QTextStream outStream(&outputText);
|
||||||
|
|
||||||
|
|
@ -99,6 +102,8 @@ public:
|
||||||
|
|
||||||
deckList.loadFromStream_Plain(outStream, false, CardNameNormalizer());
|
deckList.loadFromStream_Plain(outStream, false, CardNameNormalizer());
|
||||||
deckList.forEachCard(CardNodeFunction::ResolveProviderId());
|
deckList.forEachCard(CardNodeFunction::ResolveProviderId());
|
||||||
|
deckList.setName(deckName);
|
||||||
|
deckList.setComments(deckDescription);
|
||||||
|
|
||||||
QJsonObject commandersObj = obj.value("commanders").toObject();
|
QJsonObject commandersObj = obj.value("commanders").toObject();
|
||||||
if (!commandersObj.isEmpty()) {
|
if (!commandersObj.isEmpty()) {
|
||||||
|
|
@ -118,4 +123,93 @@ public:
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class ManaboxJsonParser : public IJsonDeckParser
|
||||||
|
{
|
||||||
|
enum BoardType : quint32
|
||||||
|
{
|
||||||
|
MainBoard = 0,
|
||||||
|
SideBoard = 1,
|
||||||
|
Commander = 2,
|
||||||
|
MaybeBoard = 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
DeckList parse(const QJsonObject &obj) override
|
||||||
|
{
|
||||||
|
DeckList deckList;
|
||||||
|
|
||||||
|
QString deckName = obj.value("name").toString();
|
||||||
|
|
||||||
|
QString outputText;
|
||||||
|
QString mainboardText;
|
||||||
|
QString sideboardText;
|
||||||
|
QTextStream mainboardStream(&mainboardText);
|
||||||
|
QTextStream sideboardStream(&sideboardText);
|
||||||
|
QTextStream outStream(&outputText);
|
||||||
|
|
||||||
|
auto commanderList = QList<QJsonObject>();
|
||||||
|
auto maybeboardList = QList<QJsonObject>();
|
||||||
|
for (auto entry : obj.value("cards").toArray()) {
|
||||||
|
auto card = entry.toObject();
|
||||||
|
|
||||||
|
auto collectorNumber = card.value("collectorNumber").toString();
|
||||||
|
auto cardName = card.value("name").toString();
|
||||||
|
auto boardType = static_cast<BoardType>(card.value("boardCategory").toInt());
|
||||||
|
auto setName = card.value("setId").toString();
|
||||||
|
auto quantity = card.value("quantity").toInt();
|
||||||
|
auto providerId = card.value("scryfallId").toString();
|
||||||
|
|
||||||
|
switch (boardType) {
|
||||||
|
case MainBoard:
|
||||||
|
mainboardStream << quantity << ' ' << cardName << " (" << setName << ") " << collectorNumber
|
||||||
|
<< '\n';
|
||||||
|
break;
|
||||||
|
case SideBoard:
|
||||||
|
sideboardStream << "SB: " << quantity << ' ' << cardName << " (" << setName << ") "
|
||||||
|
<< collectorNumber << '\n';
|
||||||
|
break;
|
||||||
|
case MaybeBoard:
|
||||||
|
maybeboardList.append(card);
|
||||||
|
break;
|
||||||
|
case Commander:
|
||||||
|
commanderList.append(card);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
qCWarning(InterfaceJsonDeckParserLog)
|
||||||
|
<< "Unknown ManaBox Zone" << card.value("boardCategory").toInt() << "for" << cardName;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
outStream << mainboardText << sideboardText;
|
||||||
|
|
||||||
|
deckList.loadFromStream_Plain(outStream, false, CardNameNormalizer());
|
||||||
|
deckList.setName(deckName);
|
||||||
|
deckList.forEachCard(CardNodeFunction::ResolveProviderId());
|
||||||
|
|
||||||
|
for (const auto &maybeboardCard : maybeboardList) {
|
||||||
|
QString cardName = maybeboardCard.value("name").toString();
|
||||||
|
QString setName = maybeboardCard.value("setId").toString().toUpper();
|
||||||
|
QString collectorNumber = maybeboardCard.value("collectorNumber").toString();
|
||||||
|
QString providerId = maybeboardCard.value("scryfallId").toString();
|
||||||
|
|
||||||
|
for (int x = 0; x < maybeboardCard.value("quantity").toInt(); x++) {
|
||||||
|
deckList.addCard(cardName, DECK_ZONE_MAYBEBOARD, -1, setName, collectorNumber, providerId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const auto &commanderCard : commanderList) {
|
||||||
|
|
||||||
|
QString commanderName = commanderCard.value("name").toString();
|
||||||
|
QString setName = commanderCard.value("setId").toString().toUpper();
|
||||||
|
QString collectorNumber = commanderCard.value("collectorNumber").toString();
|
||||||
|
QString providerId = commanderCard.value("scryfallId").toString();
|
||||||
|
|
||||||
|
deckList.setBannerCard({commanderName, providerId});
|
||||||
|
deckList.addCard(commanderName, DECK_ZONE_MAIN, -1, setName, collectorNumber, providerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return deckList;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
#endif // INTERFACE_JSON_DECK_PARSER_H
|
#endif // INTERFACE_JSON_DECK_PARSER_H
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@
|
||||||
#include <QNetworkReply>
|
#include <QNetworkReply>
|
||||||
#include <QNetworkRequest>
|
#include <QNetworkRequest>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
#include <QStringList>
|
||||||
#include <QTextStream>
|
#include <QTextStream>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
|
|
@ -33,6 +34,7 @@
|
||||||
|
|
||||||
class QWidget;
|
class QWidget;
|
||||||
|
|
||||||
|
static const QStringList SUPPORTED_PROVIDERS = {"Archidekt", "Deckstats", "ManaBox", "Moxfield", "TappedOut"};
|
||||||
DlgLoadDeckFromWebsite::DlgLoadDeckFromWebsite(QWidget *parent) : QDialog(parent)
|
DlgLoadDeckFromWebsite::DlgLoadDeckFromWebsite(QWidget *parent) : QDialog(parent)
|
||||||
{
|
{
|
||||||
nam = NetworkManagerFactory::create(this);
|
nam = NetworkManagerFactory::create(this);
|
||||||
|
|
@ -41,6 +43,7 @@ DlgLoadDeckFromWebsite::DlgLoadDeckFromWebsite(QWidget *parent) : QDialog(parent
|
||||||
setLayout(layout);
|
setLayout(layout);
|
||||||
|
|
||||||
instructionLabel = new QLabel(this);
|
instructionLabel = new QLabel(this);
|
||||||
|
instructionLabel->setWordWrap(true);
|
||||||
layout->addWidget(instructionLabel);
|
layout->addWidget(instructionLabel);
|
||||||
|
|
||||||
urlEdit = new QLineEdit(this);
|
urlEdit = new QLineEdit(this);
|
||||||
|
|
@ -64,8 +67,8 @@ DlgLoadDeckFromWebsite::DlgLoadDeckFromWebsite(QWidget *parent) : QDialog(parent
|
||||||
|
|
||||||
void DlgLoadDeckFromWebsite::retranslateUi()
|
void DlgLoadDeckFromWebsite::retranslateUi()
|
||||||
{
|
{
|
||||||
instructionLabel->setText(tr("Paste a link to a decklist site here to import it.\n(Archidekt, Deckstats, Moxfield, "
|
instructionLabel->setText(tr("Paste a link to a decklist site here to import it.") + "\n" +
|
||||||
"and TappedOut are supported.)"));
|
tr("Supported services: %1").arg(SUPPORTED_PROVIDERS.join(", ")));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DlgLoadDeckFromWebsite::testValidUrl()
|
bool DlgLoadDeckFromWebsite::testValidUrl()
|
||||||
|
|
@ -84,8 +87,8 @@ void DlgLoadDeckFromWebsite::accept()
|
||||||
if (!jsonParser && info.provider != DeckProvider::Deckstats && info.provider != DeckProvider::TappedOut) {
|
if (!jsonParser && info.provider != DeckProvider::Deckstats && info.provider != DeckProvider::TappedOut) {
|
||||||
qCWarning(DlgLoadDeckFromWebsiteLog) << "No parser found for provider";
|
qCWarning(DlgLoadDeckFromWebsiteLog) << "No parser found for provider";
|
||||||
QMessageBox::warning(this, tr("Load Deck from Website"),
|
QMessageBox::warning(this, tr("Load Deck from Website"),
|
||||||
tr("No parser available for this deck provider.\n (Archidekt, Deckstats, Moxfield, "
|
tr("No parser available for this deck provider.") + "\n" +
|
||||||
"and TappedOut are supported.)"));
|
tr("Supported services: %1").arg(SUPPORTED_PROVIDERS.join(", ")));
|
||||||
QDialog::reject();
|
QDialog::reject();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -152,6 +155,7 @@ void DlgLoadDeckFromWebsite::accept()
|
||||||
"https://archidekt.com/decks/9999999\n"
|
"https://archidekt.com/decks/9999999\n"
|
||||||
"https://deckstats.net/decks/99999/9999999-your-deck-name/en\n"
|
"https://deckstats.net/decks/99999/9999999-your-deck-name/en\n"
|
||||||
"https://moxfield.com/decks/XYZxx-XYZ99Yyy-xyzXzzz\n"
|
"https://moxfield.com/decks/XYZxx-XYZ99Yyy-xyzXzzz\n"
|
||||||
|
"https://manabox.app/decks/XYZxxa-YZ99Yyyx_yzXzzz\n"
|
||||||
"https://tappedout.net/mtg-decks/your-deck-name/"));
|
"https://tappedout.net/mtg-decks/your-deck-name/"));
|
||||||
QDialog::reject();
|
QDialog::reject();
|
||||||
}
|
}
|
||||||
|
|
@ -164,7 +168,9 @@ QSharedPointer<IJsonDeckParser> DlgLoadDeckFromWebsite::createParserForProvider(
|
||||||
return QSharedPointer<IJsonDeckParser>(new ArchidektJsonParser());
|
return QSharedPointer<IJsonDeckParser>(new ArchidektJsonParser());
|
||||||
case DeckProvider::Moxfield:
|
case DeckProvider::Moxfield:
|
||||||
return QSharedPointer<IJsonDeckParser>(new MoxfieldJsonParser());
|
return QSharedPointer<IJsonDeckParser>(new MoxfieldJsonParser());
|
||||||
|
case DeckProvider::Manabox:
|
||||||
|
return QSharedPointer<IJsonDeckParser>(new ManaboxJsonParser());
|
||||||
default:
|
default:
|
||||||
return QSharedPointer<IJsonDeckParser>(nullptr);
|
return QSharedPointer<IJsonDeckParser>(nullptr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -70,4 +70,4 @@ confirm.
|
||||||
|
|
||||||
The action will automatically import the deck from the online service without any other required user action.
|
The action will automatically import the deck from the online service without any other required user action.
|
||||||
|
|
||||||
Currently supported services are Archidekt, Deckstats, Moxfield, and TappedOut.
|
Currently supported services are Archidekt, Deckstats, ManaBox, Moxfield, and TappedOut.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue