diff --git a/oracle/src/oracleimporter.cpp b/oracle/src/oracleimporter.cpp index 468aa0b06..72eaaed26 100644 --- a/oracle/src/oracleimporter.cpp +++ b/oracle/src/oracleimporter.cpp @@ -3,16 +3,15 @@ #include "carddbparser/cockatricexml4.h" #include "qt-json/json.h" -#include #include #include #include -SplitCardPart::SplitCardPart(const int _index, +SplitCardPart::SplitCardPart(const QString &_name, const QString &_text, const QVariantHash &_properties, const CardInfoPerSet _setInfo) - : index(_index), text(_text), properties(_properties), setInfo(_setInfo) + : name(_name), text(_text), properties(_properties), setInfo(_setInfo) { } @@ -25,7 +24,7 @@ bool OracleImporter::readSetsFromByteArray(const QByteArray &data) QList newSetList; bool ok; - setsMap = QtJson::Json::parse(QString(data), ok).toMap(); + setsMap = QtJson::Json::parse(QString(data), ok).toMap().value("data").toMap(); if (!ok) { qDebug() << "error: QtJson::Json::parse()"; return false; @@ -162,14 +161,9 @@ CardInfoPtr OracleImporter::addCard(QString name, // upsideDown (flip cards) bool upsideDown = false; - QStringList additionalNames = properties.value("names").toStringList(); QString layout = properties.value("layout").toString(); if (layout == "flip") { - if (properties.value("side").toString() != "front") { - upsideDown = true; - } - // reset the side property, since the card has no back image - properties.insert("side", "front"); + upsideDown = properties.value("side").toString() != "a"; } // insert the card and its properties @@ -179,36 +173,41 @@ CardInfoPtr OracleImporter::addCard(QString name, CardInfoPtr newCard = CardInfo::newInstance(name, text, isToken, properties, relatedCards, reverseRelatedCards, setsInfo, cipt, tableRow, upsideDown); + if (name.isEmpty()) { + qDebug() << "warning: an empty card was added to set" << setInfo.getPtr()->getShortName(); + } cards.insert(name, newCard); + return newCard; } -QString OracleImporter::getStringPropertyFromMap(QVariantMap card, QString propertyName) +QString OracleImporter::getStringPropertyFromMap(const QVariantMap &card, const QString &propertyName) { return card.contains(propertyName) ? card.value(propertyName).toString() : QString(""); } -int OracleImporter::importCardsFromSet(CardSetPtr currentSet, const QList &cardsList, bool skipSpecialCards) +int OracleImporter::importCardsFromSet(const CardSetPtr ¤tSet, + const QList &cardsList, + bool skipSpecialCards) { + // mtgjson name => xml name static const QMap cardProperties{ - // mtgjson name => xml name {"manaCost", "manacost"}, {"convertedManaCost", "cmc"}, {"type", "type"}, {"loyalty", "loyalty"}, {"layout", "layout"}, {"side", "side"}, }; - static const QMap setInfoProperties{// mtgjson name => xml name - {"multiverseId", "muid"}, - {"scryfallId", "uuid"}, - {"number", "num"}, - {"rarity", "rarity"}}; + // mtgjson name => xml name + static const QMap setInfoProperties{{"number", "num"}, {"rarity", "rarity"}}; + + // mtgjson name => xml name + static const QMap identifierProperties{{"multiverseId", "muid"}, {"scryfallId", "uuid"}}; int numCards = 0; QMultiMap splitCards; QString ptSeparator("/"); QVariantMap card; - QString layout, name, text, colors, colorIdentity, maintype, power, toughness; + QString layout, name, text, colors, colorIdentity, maintype, power, toughness, faceName; static const bool isToken = false; - QStringList additionalNames; QVariantHash properties; CardInfoPerSet setInfo; QList relatedCards; @@ -219,6 +218,11 @@ int OracleImporter::importCardsFromSet(CardSetPtr currentSet, const QList it3(identifierProperties); + while (it3.hasNext()) { + it3.next(); + auto mtgjsonProperty = it3.key(); + auto xmlPropertyName = it3.value(); + auto propertyValue = getStringPropertyFromMap(card.value("identifiers").toMap(), mtgjsonProperty); + if (!propertyValue.isEmpty()) { + setInfo.setProperty(xmlPropertyName, propertyValue); + } } + QString numComponent{}; if (skipSpecialCards) { - // skip promo cards if it's not the only print - if (allNameProps.contains(name)) { - continue; + QString numProperty = setInfo.getProperty("num"); + // skip promo cards if it's not the only print, cards with two faces are different cards + if (allNameProps.contains(faceName)) { + // check for alternative versions + if (layout != "normal") + continue; + + // alternative versions have a letter in the end of num like abc + // note this will also catch p and s, those will get removed later anyway + QChar lastChar = numProperty.at(numProperty.size() - 1); + if (!lastChar.isLetter()) + continue; + + numComponent = " (" + QString(lastChar) + ")"; + faceName += numComponent; // add to facename to make it unique } if (getStringPropertyFromMap(card, "isPromo") == "true") { - specialPromoCards.insert(name, cardVar); + specialPromoCards.insert(faceName, cardVar); continue; } - QString numProperty = setInfo.getProperty("num"); bool skip = false; // skip cards containing special stuff in the collectors number like promo cards for (const QString &specialChar : specialNumChars) { @@ -282,10 +309,10 @@ int OracleImporter::importCardsFromSet(CardSetPtr currentSet, const QList 1) { - for (const QString &additionalName : additionalNames) { - if (additionalName != name) + + // add other face for split cards as card relation + if (!getStringPropertyFromMap(card, "side").isEmpty()) { + properties["cmc"] = getStringPropertyFromMap(card, "faceConvertedManaCost"); + if (layout == "meld") { // meld cards don't work + QRegularExpression meldNameRegex{"then meld them into ([\\.]*)"}; + QString additionalName = meldNameRegex.match(text).captured(1); + if (!additionalName.isNull()) { relatedCards.append(new CardRelation(additionalName, true)); + } + } else { + for (const QString &additionalName : name.split(" // ")) { + if (additionalName != faceName) { + relatedCards.append(new CardRelation(additionalName, true)); + } + } } + name = faceName; } - CardInfoPtr newCard = addCard(name, text, isToken, properties, relatedCards, setInfo); + CardInfoPtr newCard = addCard(name + numComponent, text, isToken, properties, relatedCards, setInfo); numCards++; } } @@ -350,21 +385,22 @@ int OracleImporter::importCardsFromSet(CardSetPtr currentSet, const QList splitCardParts = splitCards.values(nameSplit); - // sort them by index (aka position) + // sort them by face name std::sort(splitCardParts.begin(), splitCardParts.end(), - [](const SplitCardPart &a, const SplitCardPart &b) -> bool { return a.getIndex() < b.getIndex(); }); + [](const SplitCardPart &a, const SplitCardPart &b) -> bool { return a.getName() < b.getName(); }); text = QString(""); properties.clear(); relatedCards.clear(); - int lastIndex = -1; + QString lastName{}; for (const SplitCardPart &tmp : splitCardParts) { // some sets have 2 different variations of the same split card, // eg. Fire // Ice in WC02. Avoid adding duplicates. - if (lastIndex == tmp.getIndex()) + if (lastName == tmp.getName()) continue; - lastIndex = tmp.getIndex(); + + lastName = tmp.getName(); if (!text.isEmpty()) text.append(splitCardTextSeparator); @@ -375,7 +411,6 @@ int OracleImporter::importCardsFromSet(CardSetPtr currentSet, const QList &cards, bool skipSpecialNums = true); + int importCardsFromSet(const CardSetPtr ¤tSet, const QList &cards, bool skipSpecialNums = true); QList &getSets() { return allSets; @@ -123,7 +123,7 @@ public: void clear(); protected: - inline QString getStringPropertyFromMap(QVariantMap card, QString propertyName); + inline QString getStringPropertyFromMap(const QVariantMap &card, const QString &propertyName); void sortAndReduceColors(QString &colors); }; diff --git a/oracle/src/oraclewizard.cpp b/oracle/src/oraclewizard.cpp index 675c6f585..0da8ecaaf 100644 --- a/oracle/src/oraclewizard.cpp +++ b/oracle/src/oraclewizard.cpp @@ -20,7 +20,6 @@ #include #include #include -#include #include #include #include @@ -38,15 +37,16 @@ #define ZIP_SIGNATURE "PK" // Xz stream header: 0xFD + "7zXZ" #define XZ_SIGNATURE "\xFD\x37\x7A\x58\x5A" -#define ALLSETS_URL_FALLBACK "https://www.mtgjson.com/files/AllPrintings.json" -#define MTGJSON_VERSION_URL "https://www.mtgjson.com/files/version.json" +#define MTGJSON_V4_URL_COMPONENT "mtgjson.com/files/" +#define ALLSETS_URL_FALLBACK "https://www.mtgjson.com/api/v5/AllPrintings.json" +#define MTGJSON_VERSION_URL "https://www.mtgjson.com/api/v5/Meta.json" #ifdef HAS_LZMA -#define ALLSETS_URL "https://www.mtgjson.com/files/AllPrintings.json.xz" +#define ALLSETS_URL "https://www.mtgjson.com/api/v5/AllPrintings.json.xz" #elif defined(HAS_ZLIB) -#define ALLSETS_URL "https://www.mtgjson.com/files/AllPrintings.json.zip" +#define ALLSETS_URL "https://www.mtgjson.com/api/v5/AllPrintings.json.zip" #else -#define ALLSETS_URL "https://www.mtgjson.com/files/AllPrintings.json" +#define ALLSETS_URL "https://www.mtgjson.com/api/v5/AllPrintings.json" #endif #define TOKENS_URL "https://raw.githubusercontent.com/Cockatrice/Magic-Token/master/tokens.xml" @@ -299,7 +299,13 @@ bool LoadSetsPage::validatePage() // else, try to import sets if (urlRadioButton->isChecked()) { - QUrl url = QUrl::fromUserInput(urlLineEdit->text()); + // If a user attempts to download from V4, redirect them to V5 + if (urlLineEdit->text().contains(MTGJSON_V4_URL_COMPONENT)) { + actRestoreDefaultUrl(); + } + + const auto url = QUrl::fromUserInput(urlLineEdit->text()); + if (!url.isValid()) { QMessageBox::critical(this, tr("Error"), tr("The provided URL is not valid.")); return false; @@ -342,23 +348,24 @@ bool LoadSetsPage::validatePage() } #include -void LoadSetsPage::downloadSetsFile(QUrl url) +void LoadSetsPage::downloadSetsFile(const QUrl &url) { wizard()->setCardSourceVersion("unknown"); - QString urlString = url.toString(); + const auto urlString = url.toString(); if (urlString == ALLSETS_URL || urlString == ALLSETS_URL_FALLBACK) { - QUrl versionUrl = QUrl::fromUserInput(MTGJSON_VERSION_URL); - QNetworkReply *versionReply = wizard()->nam->get(QNetworkRequest(versionUrl)); + const auto versionUrl = QUrl::fromUserInput(MTGJSON_VERSION_URL); + auto *versionReply = wizard()->nam->get(QNetworkRequest(versionUrl)); connect(versionReply, &QNetworkReply::finished, [this, versionReply]() { if (versionReply->error() == QNetworkReply::NoError) { - QByteArray jsonData = versionReply->readAll(); - QJsonParseError jsonError; - QJsonDocument jsonResponse = QJsonDocument::fromJson(jsonData, &jsonError); + auto jsonData = versionReply->readAll(); + QJsonParseError jsonError{}; + auto jsonResponse = QJsonDocument::fromJson(jsonData, &jsonError); if (jsonError.error == QJsonParseError::NoError) { - QVariantMap jsonMap = jsonResponse.toVariant().toMap(); - QString versionString = jsonMap["version"].toString(); + const auto jsonMap = jsonResponse.toVariant().toMap(); + + auto versionString = jsonMap.value("meta").toMap().value("version").toString(); if (versionString.isEmpty()) { versionString = "unknown"; } @@ -372,7 +379,7 @@ void LoadSetsPage::downloadSetsFile(QUrl url) wizard()->setCardSourceUrl(url.toString()); - QNetworkReply *reply = wizard()->nam->get(QNetworkRequest(url)); + auto *reply = wizard()->nam->get(QNetworkRequest(url)); connect(reply, SIGNAL(finished()), this, SLOT(actDownloadFinishedSetsFile())); connect(reply, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(actDownloadProgressSetsFile(qint64, qint64))); @@ -391,7 +398,7 @@ void LoadSetsPage::actDownloadFinishedSetsFile() { // check for a reply auto *reply = dynamic_cast(sender()); - QNetworkReply::NetworkError errorCode = reply->error(); + auto errorCode = reply->error(); if (errorCode != QNetworkReply::NoError) { QMessageBox::critical(this, tr("Error"), tr("Network error: %1.").arg(reply->errorString())); @@ -402,9 +409,9 @@ void LoadSetsPage::actDownloadFinishedSetsFile() return; } - int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + auto statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); if (statusCode == 301 || statusCode == 302) { - QUrl redirectUrl = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl(); + const auto redirectUrl = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl(); qDebug() << "following redirect url:" << redirectUrl.toString(); downloadSetsFile(redirectUrl); reply->deleteLater(); @@ -414,7 +421,7 @@ void LoadSetsPage::actDownloadFinishedSetsFile() progressLabel->hide(); progressBar->hide(); - // save allsets.json url, but only if the user customized it and download was successfull + // save AllPrintings.json url, but only if the user customized it and download was successful if (urlLineEdit->text() != QString(ALLSETS_URL)) { wizard()->settings->setValue("allsetsurl", urlLineEdit->text()); } else { diff --git a/oracle/src/oraclewizard.h b/oracle/src/oraclewizard.h index 28f0d2e08..362c19def 100644 --- a/oracle/src/oraclewizard.h +++ b/oracle/src/oraclewizard.h @@ -113,7 +113,7 @@ protected: void initializePage() override; bool validatePage() override; void readSetsFromByteArray(QByteArray data); - void downloadSetsFile(QUrl url); + void downloadSetsFile(const QUrl &url); private: QRadioButton *urlRadioButton; diff --git a/oracle/src/pagetemplates.cpp b/oracle/src/pagetemplates.cpp index b7d3fa65c..b4d8d358b 100644 --- a/oracle/src/pagetemplates.cpp +++ b/oracle/src/pagetemplates.cpp @@ -125,7 +125,7 @@ void SimpleDownloadFilePage::actDownloadFinished() return; } - // save downlaoded file url, but only if the user customized it and download was successfull + // save downloaded file url, but only if the user customized it and download was successful if (urlLineEdit->text() != getDefaultUrl()) { wizard()->settings->setValue(getCustomUrlSettingsKey(), urlLineEdit->text()); } else {