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

0001 /***************************************************************************
0002     Copyright (C) 2021 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 "rpggeekfetcher.h"
0026 #include "../translators/xslthandler.h"
0027 #include "../translators/tellicoimporter.h"
0028 #include "../utils/string_utils.h"
0029 #include "../core/tellico_strings.h"
0030 #include "../gui/combobox.h"
0031 #include "../tellico_debug.h"
0032 
0033 #include <KLocalizedString>
0034 #include <KConfigGroup>
0035 
0036 #include <QLabel>
0037 #include <QFile>
0038 #include <QTextStream>
0039 #include <QGridLayout>
0040 #include <QTextCodec>
0041 #include <QUrlQuery>
0042 
0043 namespace {
0044   // a lot of overlap with boardgamegeekimporter.h
0045   static const int BGG_MAX_RETURNS_TOTAL = 10;
0046   // interchangeable with boardgamegeek.com
0047   static const char* BGG_SEARCH_URL  = "https://rpggeek.com/xmlapi2/search";
0048   static const char* BGG_THING_URL  = "https://rpggeek.com/xmlapi2/thing";
0049 }
0050 
0051 using namespace Tellico;
0052 using Tellico::Fetch::RPGGeekFetcher;
0053 
0054 RPGGeekFetcher::RPGGeekFetcher(QObject* parent_)
0055     : XMLFetcher(parent_), m_imageSize(SmallImage) {
0056   setLimit(BGG_MAX_RETURNS_TOTAL);
0057   setXSLTFilename(QStringLiteral("boardgamegeek2tellico.xsl"));
0058 }
0059 
0060 RPGGeekFetcher::~RPGGeekFetcher() {
0061 }
0062 
0063 QString RPGGeekFetcher::source() const {
0064   return m_name.isEmpty() ? defaultName() : m_name;
0065 }
0066 
0067 // https://boardgamegeek.com/wiki/page/XML_API_Terms_of_Use
0068 QString RPGGeekFetcher::attribution() const {
0069   return i18n(providedBy, QLatin1String("https://boardgamegeek.com"), QLatin1String("BoardGameGeek"));
0070 }
0071 
0072 bool RPGGeekFetcher::canSearch(Fetch::FetchKey k) const {
0073   return k == Title || k == Keyword;
0074 }
0075 
0076 bool RPGGeekFetcher::canFetch(int type) const {
0077   // it's a custom collection
0078   return type == Data::Collection::Base;
0079 }
0080 
0081 void RPGGeekFetcher::readConfigHook(const KConfigGroup& config_) {
0082   const int imageSize = config_.readEntry("Image Size", -1);
0083   if(imageSize > -1) {
0084     m_imageSize = static_cast<ImageSize>(imageSize);
0085   }
0086 }
0087 
0088 QUrl RPGGeekFetcher::searchUrl() {
0089   QUrl u(QString::fromLatin1(BGG_SEARCH_URL));
0090 
0091   QUrlQuery q;
0092   switch(request().key()) {
0093     case Title:
0094       q.addQueryItem(QStringLiteral("query"), request().value());
0095       q.addQueryItem(QStringLiteral("type"), QStringLiteral("rpgitem"));
0096       q.addQueryItem(QStringLiteral("exact"), QStringLiteral("1"));
0097       break;
0098 
0099     case Keyword:
0100       q.addQueryItem(QStringLiteral("query"), request().value());
0101       q.addQueryItem(QStringLiteral("type"), QStringLiteral("rpgitem"));
0102       break;
0103 
0104     case Raw:
0105       u.setUrl(QLatin1String(BGG_THING_URL));
0106       q.addQueryItem(QStringLiteral("id"), request().value());
0107       q.addQueryItem(QStringLiteral("type"), QStringLiteral("rpgitem"));
0108       break;
0109 
0110     default:
0111       myWarning() << source() << "- key not recognized:" << request().key();
0112       return QUrl();
0113   }
0114   u.setQuery(q);
0115 
0116 //  myDebug() << "url: " << u.url();
0117   return u;
0118 }
0119 
0120 Tellico::Data::EntryPtr RPGGeekFetcher::fetchEntryHookData(Data::EntryPtr entry_) {
0121   Q_ASSERT(entry_);
0122 
0123   const QString id = entry_->field(QStringLiteral("bggid"));
0124   if(id.isEmpty()) {
0125     myDebug() << "no bgg id found";
0126     return entry_;
0127   }
0128 
0129   QUrl u(QString::fromLatin1(BGG_THING_URL));
0130   QUrlQuery q;
0131   q.addQueryItem(QStringLiteral("id"), id);
0132   q.addQueryItem(QStringLiteral("type"), QStringLiteral("rpgitem"));
0133   u.setQuery(q);
0134 //  myDebug() << "url: " << u;
0135 
0136   // quiet
0137   QString output = FileHandler::readXMLFile(u, true);
0138 
0139 #if 0
0140   myWarning() << "Remove output debug from rpggeekfetcher.cpp";
0141   QFile f(QStringLiteral("/tmp/test-rpggeek.xml"));
0142   if(f.open(QIODevice::WriteOnly)) {
0143     QTextStream t(&f);
0144     t.setCodec("UTF-8");
0145     t << output;
0146   }
0147   f.close();
0148 #endif
0149 
0150   auto handler = xsltHandler();
0151   handler->addStringParam("image-size", QByteArray::number(m_imageSize));
0152 
0153   Import::TellicoImporter imp(handler->applyStylesheet(output));
0154   // be quiet when loading images
0155   imp.setOptions(imp.options() ^ Import::ImportShowImageErrors);
0156   Data::CollPtr coll = imp.collection();
0157   if(!coll) {
0158     myWarning() << "no collection pointer";
0159     return entry_;
0160   }
0161   if(coll->entryCount() == 0) {
0162     myWarning() << "no entries";
0163     return entry_;
0164   }
0165 
0166   if(coll->entryCount() > 1) {
0167     myDebug() << "weird, more than one entry found";
0168   }
0169 
0170   // don't want to include id
0171   coll->removeField(QStringLiteral("bggid"));
0172   return coll->entries().front();
0173 }
0174 
0175 Tellico::Fetch::FetchRequest RPGGeekFetcher::updateRequest(Data::EntryPtr entry_) {
0176   QString bggid = entry_->field(QStringLiteral("bggid"));
0177   if(!bggid.isEmpty()) {
0178     return FetchRequest(Raw, bggid);
0179   }
0180 
0181   QString title = entry_->field(QStringLiteral("title"));
0182   if(!title.isEmpty()) {
0183     return FetchRequest(Title, title);
0184   }
0185   return FetchRequest();
0186 }
0187 
0188 Tellico::Fetch::ConfigWidget* RPGGeekFetcher::configWidget(QWidget* parent_) const {
0189   return new RPGGeekFetcher::ConfigWidget(parent_, this);
0190 }
0191 
0192 QString RPGGeekFetcher::defaultName() {
0193   return QStringLiteral("RPGGeek");
0194 }
0195 
0196 QString RPGGeekFetcher::defaultIcon() {
0197   return favIcon("https://cf.geekdo-static.com/icons/favicon2.ico");
0198 }
0199 
0200 Tellico::StringHash RPGGeekFetcher::allOptionalFields() {
0201   StringHash hash;
0202   hash[QStringLiteral("genre")] = i18n("Genre");
0203   hash[QStringLiteral("year")] = i18n("Release Year");
0204   hash[QStringLiteral("publisher")] = i18n("Publisher");
0205   hash[QStringLiteral("artist")]  = i18nc("Comic Book Illustrator", "Artist");
0206   hash[QStringLiteral("designer")] = i18n("Designer");
0207   hash[QStringLiteral("producer")] = i18n("Producer");
0208   hash[QStringLiteral("mechanism")] = i18n("Mechanism");
0209   hash[QStringLiteral("description")] = i18n("Description");
0210   hash[QStringLiteral("rpggeek-link")] = i18n("RPGGeek Link");
0211   return hash;
0212 }
0213 
0214 RPGGeekFetcher::ConfigWidget::ConfigWidget(QWidget* parent_, const RPGGeekFetcher* fetcher_)
0215     : Fetch::ConfigWidget(parent_) {
0216   QGridLayout* l = new QGridLayout(optionsWidget());
0217   l->setSpacing(4);
0218   l->setColumnStretch(1, 10);
0219 
0220   int row = -1;
0221 
0222   QLabel* label = new QLabel(i18n("&Image size: "), optionsWidget());
0223   l->addWidget(label, ++row, 0);
0224   m_imageCombo = new GUI::ComboBox(optionsWidget());
0225   m_imageCombo->addItem(i18n("No Image"), NoImage);
0226   m_imageCombo->addItem(i18n("Small Image"), SmallImage);
0227   m_imageCombo->addItem(i18n("Large Image"), LargeImage);
0228   void (GUI::ComboBox::* activatedInt)(int) = &GUI::ComboBox::activated;
0229   connect(m_imageCombo, activatedInt, this, &ConfigWidget::slotSetModified);
0230   l->addWidget(m_imageCombo, row, 1);
0231   label->setBuddy(m_imageCombo);
0232 
0233   l->setRowStretch(++row, 10);
0234 
0235   // now add additional fields widget
0236   addFieldsWidget(RPGGeekFetcher::allOptionalFields(), fetcher_ ? fetcher_->optionalFields() : QStringList());
0237 
0238   if(fetcher_) {
0239     m_imageCombo->setCurrentData(fetcher_->m_imageSize);
0240   } else { // defaults
0241     m_imageCombo->setCurrentData(SmallImage);
0242   }
0243 }
0244 
0245 void RPGGeekFetcher::ConfigWidget::saveConfigHook(KConfigGroup& config_) {
0246   const int n = m_imageCombo->currentData().toInt();
0247   config_.writeEntry("Image Size", n);
0248 }
0249 
0250 QString RPGGeekFetcher::ConfigWidget::preferredName() const {
0251   return RPGGeekFetcher::defaultName();
0252 }