diff --git a/cockatrice/src/client/network/parsers/deck_link_to_api_transformer.cpp b/cockatrice/src/client/network/parsers/deck_link_to_api_transformer.cpp index 7127b0a29..2b307735d 100644 --- a/cockatrice/src/client/network/parsers/deck_link_to_api_transformer.cpp +++ b/cockatrice/src/client/network/parsers/deck_link_to_api_transformer.cpp @@ -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_SUFFIX = "/"; +static const QString DECKSTATS_BASE = "https://deckstats.net/decks/"; 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) { 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_-]+)"); + static QRegularExpression rxManabox("manabox\\.app/decks/([a-zA-Z0-9_-]+)"); QRegularExpressionMatch match; @@ -49,11 +53,18 @@ bool parseDeckUrl(const QString &url, ParsedDeckInfo &outInfo) return true; } else if ((match = rxDeckstats.match(url)).hasMatch()) { QString deckPath = match.captured(1); - outInfo = ParsedDeckInfo{.baseUrl = "https://deckstats.net/decks/", + outInfo = ParsedDeckInfo{.baseUrl = DECKSTATS_BASE, .deckID = deckPath, - .fullUrl = "https://deckstats.net/decks/" + deckPath + DECKSTATS_SUFFIX, + .fullUrl = DECKSTATS_BASE + deckPath + DECKSTATS_SUFFIX, .provider = DeckProvider::Deckstats}; 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; diff --git a/cockatrice/src/client/network/parsers/deck_link_to_api_transformer.h b/cockatrice/src/client/network/parsers/deck_link_to_api_transformer.h index bbdd70c97..d3bff976f 100644 --- a/cockatrice/src/client/network/parsers/deck_link_to_api_transformer.h +++ b/cockatrice/src/client/network/parsers/deck_link_to_api_transformer.h @@ -15,6 +15,7 @@ enum class DeckProvider Archidekt, Moxfield, Deckstats, + Manabox, Unknown }; diff --git a/cockatrice/src/client/network/parsers/interface_json_deck_parser.h b/cockatrice/src/client/network/parsers/interface_json_deck_parser.h index 87fde4d54..8f1defb7a 100644 --- a/cockatrice/src/client/network/parsers/interface_json_deck_parser.h +++ b/cockatrice/src/client/network/parsers/interface_json_deck_parser.h @@ -8,12 +8,19 @@ #define INTERFACE_JSON_DECK_PARSER_H #include "../../../interface/deck_loader/card_node_function.h" +#include "libcockatrice/deck_list/tree/inner_deck_list_node.h" #include #include +#include +#include +#include +#include #include #include +inline Q_LOGGING_CATEGORY(InterfaceJsonDeckParserLog, "interface_json_deck_parser"); + class IJsonDeckParser { public: @@ -32,9 +39,6 @@ public: QString deckName = obj.value("name").toString(); QString deckDescription = obj.value("description").toString(); - deckList.setName(deckName); - deckList.setComments(deckDescription); - QString outputText; QTextStream outStream(&outputText); @@ -52,6 +56,8 @@ public: deckList.loadFromStream_Plain(outStream, false, CardNameNormalizer()); deckList.forEachCard(CardNodeFunction::ResolveProviderId()); + deckList.setName(deckName); + deckList.setComments(deckDescription); return deckList; } @@ -67,9 +73,6 @@ public: QString deckName = obj.value("name").toString(); QString deckDescription = obj.value("description").toString(); - deckList.setName(deckName); - deckList.setComments(deckDescription); - QString outputText; QTextStream outStream(&outputText); @@ -99,6 +102,8 @@ public: deckList.loadFromStream_Plain(outStream, false, CardNameNormalizer()); deckList.forEachCard(CardNodeFunction::ResolveProviderId()); + deckList.setName(deckName); + deckList.setComments(deckDescription); QJsonObject commandersObj = obj.value("commanders").toObject(); 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(); + auto maybeboardList = QList(); + 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(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 diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_website.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_website.cpp index 1c1ada302..04b0db2e3 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_website.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_website.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -33,6 +34,7 @@ class QWidget; +static const QStringList SUPPORTED_PROVIDERS = {"Archidekt", "Deckstats", "ManaBox", "Moxfield", "TappedOut"}; DlgLoadDeckFromWebsite::DlgLoadDeckFromWebsite(QWidget *parent) : QDialog(parent) { nam = NetworkManagerFactory::create(this); @@ -41,6 +43,7 @@ DlgLoadDeckFromWebsite::DlgLoadDeckFromWebsite(QWidget *parent) : QDialog(parent setLayout(layout); instructionLabel = new QLabel(this); + instructionLabel->setWordWrap(true); layout->addWidget(instructionLabel); urlEdit = new QLineEdit(this); @@ -64,8 +67,8 @@ DlgLoadDeckFromWebsite::DlgLoadDeckFromWebsite(QWidget *parent) : QDialog(parent 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.)")); + instructionLabel->setText(tr("Paste a link to a decklist site here to import it.") + "\n" + + tr("Supported services: %1").arg(SUPPORTED_PROVIDERS.join(", "))); } bool DlgLoadDeckFromWebsite::testValidUrl() @@ -84,8 +87,8 @@ void DlgLoadDeckFromWebsite::accept() 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.)")); + tr("No parser available for this deck provider.") + "\n" + + tr("Supported services: %1").arg(SUPPORTED_PROVIDERS.join(", "))); QDialog::reject(); return; } @@ -152,6 +155,7 @@ void DlgLoadDeckFromWebsite::accept() "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://manabox.app/decks/XYZxxa-YZ99Yyyx_yzXzzz\n" "https://tappedout.net/mtg-decks/your-deck-name/")); QDialog::reject(); } @@ -164,7 +168,9 @@ QSharedPointer DlgLoadDeckFromWebsite::createParserForProvider( return QSharedPointer(new ArchidektJsonParser()); case DeckProvider::Moxfield: return QSharedPointer(new MoxfieldJsonParser()); + case DeckProvider::Manabox: + return QSharedPointer(new ManaboxJsonParser()); default: return QSharedPointer(nullptr); } -} \ No newline at end of file +} diff --git a/doc/doxygen/pages/user_documentation/deck_management/importing_decks.md b/doc/doxygen/pages/user_documentation/deck_management/importing_decks.md index 4aab7ad2c..fbfb8fe3c 100644 --- a/doc/doxygen/pages/user_documentation/deck_management/importing_decks.md +++ b/doc/doxygen/pages/user_documentation/deck_management/importing_decks.md @@ -70,4 +70,4 @@ confirm. 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. \ No newline at end of file +Currently supported services are Archidekt, Deckstats, ManaBox, Moxfield, and TappedOut.