File indexing completed on 2024-05-12 05:09:29

0001 /***************************************************************************
0002     Copyright (C) 2019 Robby Stephenson <robby@periapsis.org>
0003  ***************************************************************************/
0004 
0005 /***************************************************************************
0006  *                                                                         *
0007  *   This program is free software; you can redistribute it and/or         *
0008  *   modify it under the terms of the GNU General Public License as        *
0009  *   published by the Free Software Foundation; either version 2 of        *
0010  *   the License or (at your option) version 3 or any later version        *
0011  *   accepted by the membership of KDE e.V. (or its successor approved     *
0012  *   by the membership of KDE e.V.), which shall act as a proxy            *
0013  *   defined in Section 14 of version 3 of the license.                    *
0014  *                                                                         *
0015  *   This program is distributed in the hope that it will be useful,       *
0016  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
0017  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
0018  *   GNU General Public License for more details.                          *
0019  *                                                                         *
0020  *   You should have received a copy of the GNU General Public License     *
0021  *   along with this program.  If not, see <http://www.gnu.org/licenses/>. *
0022  *                                                                         *
0023  ***************************************************************************/
0024 
0025 #include "comicvinefetcher.h"
0026 #include "../translators/xslthandler.h"
0027 #include "../translators/tellicoimporter.h"
0028 #include "../utils/mapvalue.h"
0029 #include "../core/tellico_strings.h"
0030 #include "../tellico_debug.h"
0031 
0032 #include <KLocalizedString>
0033 #include <KConfigGroup>
0034 
0035 #include <QLabel>
0036 #include <QFile>
0037 #include <QTextStream>
0038 #include <QGridLayout>
0039 #include <QDomDocument>
0040 #include <QTextCodec>
0041 #include <QUrlQuery>
0042 #include <QJsonDocument>
0043 #include <QJsonObject>
0044 
0045 namespace {
0046   static const int COMICVINE_MAX_RETURNS_TOTAL = 20;
0047   static const char* COMICVINE_API_URL = "https://www.comicvine.com/api";
0048   static const char* COMICVINE_API_KEY = "6e4b19eeb8ccec8e2f026169d19adf57850d378e";
0049 }
0050 
0051 using namespace Tellico;
0052 using Tellico::Fetch::ComicVineFetcher;
0053 
0054 ComicVineFetcher::ComicVineFetcher(QObject* parent_)
0055     : XMLFetcher(parent_)
0056     , m_total(-1)
0057     , m_apiKey(QLatin1String(COMICVINE_API_KEY)) {
0058   setLimit(COMICVINE_MAX_RETURNS_TOTAL);
0059   setXSLTFilename(QStringLiteral("comicvine2tellico.xsl"));
0060 }
0061 
0062 ComicVineFetcher::~ComicVineFetcher() {
0063 }
0064 
0065 QString ComicVineFetcher::source() const {
0066   return m_name.isEmpty() ? defaultName() : m_name;
0067 }
0068 
0069 QString ComicVineFetcher::attribution() const {
0070   return i18n(providedBy, QLatin1String("https://comicvine.gamespot.com"), QLatin1String("Comic Vine"));
0071 }
0072 
0073 bool ComicVineFetcher::canFetch(int type) const {
0074   return type == Data::Collection::ComicBook;
0075 }
0076 
0077 void ComicVineFetcher::readConfigHook(const KConfigGroup& config_) {
0078   QString k = config_.readEntry("API Key", COMICVINE_API_KEY);
0079   if(!k.isEmpty()) {
0080     m_apiKey = k;
0081   }
0082 }
0083 
0084 void ComicVineFetcher::resetSearch() {
0085   m_total = -1;
0086 }
0087 
0088 QUrl ComicVineFetcher::searchUrl() {
0089   QUrl u(QString::fromLatin1(COMICVINE_API_URL));
0090   QUrlQuery q;
0091   q.addQueryItem(QStringLiteral("format"), QStringLiteral("xml"));
0092   q.addQueryItem(QStringLiteral("api_key"), m_apiKey);
0093 
0094   switch(request().key()) {
0095     case Keyword:
0096       u.setPath(u.path() + QStringLiteral("/search"));
0097       q.addQueryItem(QStringLiteral("query"), request().value());
0098       q.addQueryItem(QStringLiteral("resources"), QStringLiteral("issue"));
0099       break;
0100 
0101     default:
0102       myWarning() << source() << "- key not recognized:" << request().key();
0103       return QUrl();
0104   }
0105   u.setQuery(q);
0106 
0107 //  myDebug() << "url: " << u.url();
0108   return u;
0109 }
0110 
0111 void ComicVineFetcher::parseData(QByteArray& data_) {
0112   Q_UNUSED(data_);
0113 }
0114 
0115 Tellico::Data::EntryPtr ComicVineFetcher::fetchEntryHookData(Data::EntryPtr entry_) {
0116   Q_ASSERT(entry_);
0117 
0118   const QString url = entry_->field(QStringLiteral("comicvine-api"));
0119   if(url.isEmpty()) {
0120     myDebug() << "no comicvine api url found";
0121     return entry_;
0122   }
0123 
0124   QUrl u(url);
0125   QUrlQuery q;
0126   q.addQueryItem(QStringLiteral("format"), QStringLiteral("xml"));
0127   q.addQueryItem(QStringLiteral("api_key"), m_apiKey);
0128   u.setQuery(q);
0129 //  myDebug() << "url: " << u;
0130 
0131   // quiet
0132   QString output = FileHandler::readXMLFile(u, true);
0133 
0134 #if 0
0135   myWarning() << "Remove output debug from comicvinefetcher.cpp";
0136   QFile f(QStringLiteral("/tmp/test2.xml"));
0137   if(f.open(QIODevice::WriteOnly)) {
0138     QTextStream t(&f);
0139     t.setCodec("UTF-8");
0140     t << output;
0141   }
0142   f.close();
0143 #endif
0144 
0145   Import::TellicoImporter imp(xsltHandler()->applyStylesheet(output));
0146   // be quiet when loading images
0147   imp.setOptions(imp.options() ^ Import::ImportShowImageErrors);
0148   Data::CollPtr coll = imp.collection();
0149   if(!coll || coll->entryCount() == 0) {
0150     myWarning() << "no collection pointer";
0151     return entry_;
0152   }
0153 
0154   if(coll->entryCount() > 1) {
0155     myDebug() << "weird, more than one entry found";
0156   }
0157 
0158   // grab the publisher from the volume link
0159   const QString volUrl = entry_->field(QStringLiteral("comicvine-volume-api"));
0160   if(!volUrl.isEmpty()) {
0161     QUrl vu(volUrl);
0162     // easier to use JSON here
0163     QUrlQuery q;
0164     q.addQueryItem(QStringLiteral("format"), QStringLiteral("json"));
0165     q.addQueryItem(QStringLiteral("api_key"), m_apiKey);
0166     vu.setQuery(q);
0167 //    myDebug() << "volume url: " << vu;
0168 
0169     QByteArray data = FileHandler::readDataFile(vu, true /* quiet */);
0170 #if 0
0171     myWarning() << "Remove JSON output debug from comicvinefetcher.cpp";
0172     QFile f2(QStringLiteral("/tmp/test2.json"));
0173     if(f2.open(QIODevice::WriteOnly)) {
0174       QTextStream t(&f2);
0175       t << data;
0176     }
0177     f2.close();
0178 #endif
0179     QJsonDocument doc = QJsonDocument::fromJson(data);
0180     QVariantMap map = doc.object().toVariantMap().value(QLatin1String("results")).toMap();
0181     const QString pub = mapValue(map, "publisher", "name");
0182     if(!pub.isEmpty()) {
0183       Data::EntryPtr e = coll->entries().front();
0184       if(e) {
0185         e->setField(QStringLiteral("publisher"), pub);
0186       }
0187     }
0188   }
0189 
0190   // don't want to include api link
0191   coll->removeField(QStringLiteral("comicvine-api"));
0192   coll->removeField(QStringLiteral("comicvine-volume-api"));
0193   return coll->entries().front();
0194 }
0195 
0196 Tellico::Fetch::FetchRequest ComicVineFetcher::updateRequest(Data::EntryPtr entry_) {
0197   QString title = entry_->field(QStringLiteral("title"));
0198   if(!title.isEmpty()) {
0199     return FetchRequest(Keyword, title);
0200   }
0201   return FetchRequest();
0202 }
0203 
0204 Tellico::Fetch::ConfigWidget* ComicVineFetcher::configWidget(QWidget* parent_) const {
0205   return new ComicVineFetcher::ConfigWidget(parent_, this);
0206 }
0207 
0208 QString ComicVineFetcher::defaultName() {
0209   return QStringLiteral("Comic Vine");
0210 }
0211 
0212 QString ComicVineFetcher::defaultIcon() {
0213   return favIcon("https://comicvine.gamespot.com");
0214 }
0215 
0216 Tellico::StringHash ComicVineFetcher::allOptionalFields() {
0217   StringHash hash;
0218 //  hash[QStringLiteral("colorist")]  = i18n("Colorist");
0219   hash[QStringLiteral("comicvine")] = i18n("Comic Vine Link");
0220   return hash;
0221 }
0222 
0223 ComicVineFetcher::ConfigWidget::ConfigWidget(QWidget* parent_, const ComicVineFetcher* fetcher_)
0224     : Fetch::ConfigWidget(parent_) {
0225   QGridLayout* l = new QGridLayout(optionsWidget());
0226   l->setSpacing(4);
0227   l->setColumnStretch(1, 10);
0228 
0229   int row = -1;
0230 
0231   QLabel* al = new QLabel(i18n("Registration is required for accessing this data source. "
0232                                "If you agree to the terms and conditions, <a href='%1'>sign "
0233                                "up for an account</a>, and enter your information below.",
0234                                 QLatin1String("http://api.comicvine.com")),
0235                           optionsWidget());
0236   al->setOpenExternalLinks(true);
0237   al->setWordWrap(true);
0238   ++row;
0239   l->addWidget(al, row, 0, 1, 2);
0240   // richtext gets weird with size
0241   al->setMinimumWidth(al->sizeHint().width());
0242 
0243   QLabel* label = new QLabel(i18n("Access key: "), optionsWidget());
0244   l->addWidget(label, ++row, 0);
0245 
0246   m_apiKeyEdit = new QLineEdit(optionsWidget());
0247   connect(m_apiKeyEdit, &QLineEdit::textChanged, this, &ConfigWidget::slotSetModified);
0248   l->addWidget(m_apiKeyEdit, row, 1);
0249   QString w = i18n("The default Tellico key may be used, but searching may fail due to reaching access limits.");
0250   label->setWhatsThis(w);
0251   m_apiKeyEdit->setWhatsThis(w);
0252   label->setBuddy(m_apiKeyEdit);
0253 
0254   l->setRowStretch(++row, 10);
0255 
0256   // now add additional fields widget
0257   addFieldsWidget(ComicVineFetcher::allOptionalFields(), fetcher_ ? fetcher_->optionalFields() : QStringList());
0258 
0259   if(fetcher_) {
0260     // only show the key if it is not the default Tellico one...
0261     // that way the user is prompted to apply for their own
0262     if(fetcher_->m_apiKey != QLatin1String(COMICVINE_API_KEY)) {
0263       m_apiKeyEdit->setText(fetcher_->m_apiKey);
0264     }
0265   }
0266 }
0267 
0268 void ComicVineFetcher::ConfigWidget::saveConfigHook(KConfigGroup& config_) {
0269   QString apiKey = m_apiKeyEdit->text().trimmed();
0270   if(!apiKey.isEmpty()) {
0271     config_.writeEntry("API Key", apiKey);
0272   }
0273 }
0274 
0275 QString ComicVineFetcher::ConfigWidget::preferredName() const {
0276   return ComicVineFetcher::defaultName();
0277 }