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 }