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 }