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

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