File indexing completed on 2024-05-12 05:09:35
0001 /*************************************************************************** 0002 Copyright (C) 2017-2020 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 "igdbfetcher.h" 0026 #include "../collections/gamecollection.h" 0027 #include "../images/imagefactory.h" 0028 #include "../utils/guiproxy.h" 0029 #include "../utils/mapvalue.h" 0030 #include "../utils/tellico_utils.h" 0031 #include "../core/tellico_strings.h" 0032 #include "../tellico_debug.h" 0033 0034 #include <KLocalizedString> 0035 #include <KConfigGroup> 0036 #include <KJob> 0037 #include <KJobUiDelegate> 0038 #include <KJobWidgets/KJobWidgets> 0039 #include <KIO/StoredTransferJob> 0040 0041 #include <QUrl> 0042 #include <QUrlQuery> 0043 #include <QLabel> 0044 #include <QFile> 0045 #include <QTextStream> 0046 #include <QVBoxLayout> 0047 #include <QTextCodec> 0048 #include <QJsonDocument> 0049 #include <QJsonArray> 0050 #include <QJsonObject> 0051 #include <QThread> 0052 #include <QTimer> 0053 0054 namespace { 0055 static const int IGDB_MAX_RETURNS_TOTAL = 20; 0056 static const char* IGDB_API_URL = "https://api.igdb.com/v4"; 0057 static const char* IGDB_CLIENT_ID = "hc7jojgdmkcc6divxmz0mxzzt22ehr"; 0058 static const char* IGDB_TOKEN_URL = "https://api.tellico-project.org/igdb/"; 0059 } 0060 0061 using namespace Tellico; 0062 using Tellico::Fetch::IGDBFetcher; 0063 0064 IGDBFetcher::IGDBFetcher(QObject* parent_) 0065 : Fetcher(parent_) 0066 , m_started(false) { 0067 m_requestTimer.start(); 0068 // delay reading the platform names from the cache file 0069 QTimer::singleShot(0, this, &IGDBFetcher::populateHashes); 0070 } 0071 0072 IGDBFetcher::~IGDBFetcher() { 0073 } 0074 0075 QString IGDBFetcher::source() const { 0076 return m_name.isEmpty() ? defaultName() : m_name; 0077 } 0078 0079 QString IGDBFetcher::attribution() const { 0080 return i18n(providedBy, QLatin1String("https://igdb.com"), QLatin1String("IGDB.com")); 0081 } 0082 0083 bool IGDBFetcher::canSearch(Fetch::FetchKey k) const { 0084 return k == Keyword; 0085 } 0086 0087 bool IGDBFetcher::canFetch(int type) const { 0088 return type == Data::Collection::Game; 0089 } 0090 0091 void IGDBFetcher::readConfigHook(const KConfigGroup& config_) { 0092 const QString k = config_.readEntry("Access Token"); 0093 if(!k.isEmpty()) { 0094 m_accessToken = k; 0095 } 0096 m_accessTokenExpires = config_.readEntry("Access Token Expires", QDateTime()); 0097 } 0098 0099 void IGDBFetcher::saveConfigHook(KConfigGroup& config_) { 0100 config_.writeEntry("Access Token", m_accessToken); 0101 config_.writeEntry("Access Token Expires", m_accessTokenExpires); 0102 } 0103 0104 void IGDBFetcher::search() { 0105 continueSearch(); 0106 } 0107 0108 void IGDBFetcher::continueSearch() { 0109 m_started = true; 0110 0111 QUrl u(QString::fromLatin1(IGDB_API_URL)); 0112 u.setPath(u.path() + QStringLiteral("/games")); 0113 0114 QStringList clauseList; 0115 switch(request().key()) { 0116 case Keyword: 0117 clauseList += QString(QStringLiteral("search \"%1\";")).arg(request().value()); 0118 break; 0119 0120 default: 0121 myWarning() << source() << "- key not recognized:" << request().key(); 0122 stop(); 0123 return; 0124 } 0125 clauseList += QStringLiteral("fields *,cover.url,screenshots.url,age_ratings.*,involved_companies.*;"); 0126 // exclude some of the bigger unused fields 0127 clauseList += QStringLiteral("exclude keywords,tags;"); 0128 clauseList += QString(QStringLiteral("limit %1;")).arg(QString::number(IGDB_MAX_RETURNS_TOTAL)); 0129 // myDebug() << u << clauseList.join(QStringLiteral(" ")); 0130 0131 m_job = igdbJob(u, clauseList.join(QStringLiteral(" "))); 0132 connect(m_job.data(), &KJob::result, this, &IGDBFetcher::slotComplete); 0133 markTime(); 0134 } 0135 0136 void IGDBFetcher::stop() { 0137 if(!m_started) { 0138 return; 0139 } 0140 if(m_job) { 0141 m_job->kill(); 0142 m_job = nullptr; 0143 } 0144 m_started = false; 0145 emit signalDone(this); 0146 } 0147 0148 Tellico::Data::EntryPtr IGDBFetcher::fetchEntryHook(uint uid_) { 0149 if(!m_entries.contains(uid_)) { 0150 myDebug() << "no entry ptr"; 0151 return Data::EntryPtr(); 0152 } 0153 0154 Data::EntryPtr entry = m_entries.value(uid_); 0155 0156 // image might still be a URL 0157 const QString coverString = QStringLiteral("cover"); 0158 const QString image_id = entry->field(coverString); 0159 if(image_id.contains(QLatin1Char('/'))) { 0160 const QString id = ImageFactory::addImage(QUrl::fromUserInput(image_id), true /* quiet */); 0161 if(id.isEmpty()) { 0162 message(i18n("The cover image could not be loaded."), MessageHandler::Warning); 0163 } 0164 // empty image ID is ok 0165 entry->setField(coverString, id); 0166 } 0167 0168 const QString screenshotString = QStringLiteral("screenshot"); 0169 const QString screenshot_id = entry->field(screenshotString); 0170 if(screenshot_id.contains(QLatin1Char('/'))) { 0171 const QString id = ImageFactory::addImage(QUrl::fromUserInput(screenshot_id), true /* quiet */); 0172 entry->setField(screenshotString, id); 0173 } 0174 0175 return entry; 0176 } 0177 0178 Tellico::Fetch::FetchRequest IGDBFetcher::updateRequest(Data::EntryPtr entry_) { 0179 QString title = entry_->field(QStringLiteral("title")); 0180 if(!title.isEmpty()) { 0181 return FetchRequest(Keyword, title); 0182 } 0183 return FetchRequest(); 0184 } 0185 0186 void IGDBFetcher::slotComplete(KJob* job_) { 0187 KIO::StoredTransferJob* job = static_cast<KIO::StoredTransferJob*>(job_); 0188 0189 if(job->error()) { 0190 job->uiDelegate()->showErrorMessage(); 0191 stop(); 0192 return; 0193 } 0194 0195 const QByteArray data = job->data(); 0196 if(data.isEmpty()) { 0197 myDebug() << "no data"; 0198 stop(); 0199 return; 0200 } 0201 // see bug 319662. If fetcher is cancelled, job is killed 0202 // if the pointer is retained, it gets double-deleted 0203 m_job = nullptr; 0204 0205 #if 0 0206 myWarning() << "Remove debug from igdbfetcher.cpp"; 0207 QFile file(QStringLiteral("/tmp/test.json")); 0208 if(file.open(QIODevice::WriteOnly)) { 0209 QTextStream t(&file); 0210 t.setCodec("UTF-8"); 0211 t << data; 0212 } 0213 file.close(); 0214 #endif 0215 0216 QJsonDocument doc = QJsonDocument::fromJson(data); 0217 if(doc.isObject()) { 0218 // probably an error message 0219 QJsonObject obj = doc.object(); 0220 const QString msg = obj.value(QLatin1String("message")).toString(); 0221 myDebug() << "IGDBFetcher -" << msg; 0222 message(msg, MessageHandler::Error); 0223 stop(); 0224 return; 0225 } 0226 0227 Data::CollPtr coll(new Data::GameCollection(true)); 0228 0229 foreach(const QVariant& result, doc.array().toVariantList()) { 0230 QVariantMap resultMap = result.toMap(); 0231 Data::EntryPtr baseEntry(new Data::Entry(coll)); 0232 populateEntry(baseEntry, resultMap); 0233 0234 // for multiple platforms, return a result for each one 0235 QVariantList platforms = resultMap.value(QStringLiteral("platforms")).toList(); 0236 foreach(const QVariant pVariant, platforms) { 0237 Data::EntryPtr entry(new Data::Entry(*baseEntry)); 0238 const int pId = pVariant.toInt(); 0239 if(!m_platformHash.contains(pId)) { 0240 readDataList(Platform); 0241 } 0242 const QString platform = Data::GameCollection::normalizePlatform(m_platformHash.value(pId)); 0243 // make the assumption that if the platform name isn't already in the allowed list, it should be added 0244 Data::FieldPtr f = coll->fieldByName(QStringLiteral("platform")); 0245 if(f && !f->allowed().contains(platform)) { 0246 f->setAllowed(QStringList(f->allowed()) << platform); 0247 } 0248 entry->setField(QStringLiteral("platform"), platform); 0249 FetchResult* r = new FetchResult(this, entry); 0250 m_entries.insert(r->uid, entry); 0251 emit signalResultFound(r); 0252 } 0253 0254 // also allow case of no platform 0255 if(platforms.isEmpty()) { 0256 FetchResult* r = new FetchResult(this, baseEntry); 0257 m_entries.insert(r->uid, baseEntry); 0258 emit signalResultFound(r); 0259 } 0260 } 0261 0262 stop(); 0263 } 0264 0265 void IGDBFetcher::populateEntry(Data::EntryPtr entry_, const QVariantMap& resultMap_) { 0266 entry_->setField(QStringLiteral("title"), mapValue(resultMap_, "name")); 0267 entry_->setField(QStringLiteral("description"), mapValue(resultMap_, "summary")); 0268 0269 QString cover = mapValue(resultMap_, "cover", "url"); 0270 if(cover.startsWith(QLatin1Char('/'))) { 0271 cover.prepend(QStringLiteral("https:")); 0272 } 0273 entry_->setField(QStringLiteral("cover"), cover); 0274 0275 const QString screenshotString = QStringLiteral("screenshot"); 0276 if(optionalFields().contains(screenshotString)) { 0277 if(!entry_->collection()->hasField(screenshotString)) { 0278 entry_->collection()->addField(Data::Field::createDefaultField(Data::Field::ScreenshotField)); 0279 } 0280 auto screenshotList = resultMap_.value(QStringLiteral("screenshots")).toList(); 0281 if(!screenshotList.isEmpty()) { 0282 QString screenshot = mapValue(screenshotList.at(0).toMap(), "url"); 0283 if(screenshot.startsWith(QLatin1Char('/'))) { 0284 screenshot.prepend(QStringLiteral("https:")); 0285 } 0286 entry_->setField(screenshotString, screenshot); 0287 } 0288 } 0289 0290 QVariantList genreIDs = resultMap_.value(QStringLiteral("genres")).toList(); 0291 QStringList genres; 0292 foreach(const QVariant& id, genreIDs) { 0293 const int genreId = id.toInt(); 0294 if(!m_genreHash.contains(genreId)) { 0295 readDataList(Genre); 0296 } 0297 const QString genre = m_genreHash.value(genreId); 0298 if(!genre.isEmpty()) { 0299 genres << genre; 0300 } 0301 } 0302 entry_->setField(QStringLiteral("genre"), genres.join(FieldFormat::delimiterString())); 0303 0304 qlonglong release_t = mapValue(resultMap_, "first_release_date").toLongLong(); 0305 if(release_t > 0) { 0306 // could use QDateTime::fromSecsSinceEpoch but that was introduced in Qt 5.8 0307 // while I still support Qt 5.6, in theory... 0308 QDateTime dt = QDateTime::fromMSecsSinceEpoch(release_t * 1000); 0309 entry_->setField(QStringLiteral("year"), QString::number(dt.date().year())); 0310 } 0311 0312 const QString pegiString = QStringLiteral("pegi"); 0313 const QVariantList ageRatingList = resultMap_.value(QStringLiteral("age_ratings")).toList(); 0314 foreach(const QVariant& ageRating, ageRatingList) { 0315 const QVariantMap ratingMap = ageRating.toMap(); 0316 // per Age Rating Enums, ESRB==1, PEGI==2 0317 const int category = ratingMap.value(QStringLiteral("category")).toInt(); 0318 const int rating = ratingMap.value(QStringLiteral("rating")).toInt(); 0319 if(category == 1) { 0320 if(m_esrbHash.contains(rating)) { 0321 entry_->setField(QStringLiteral("certification"), m_esrbHash.value(rating)); 0322 } else { 0323 myDebug() << "No ESRB rating for value =" << rating; 0324 } 0325 } else if(category == 2 && optionalFields().contains(pegiString)) { 0326 if(!entry_->collection()->hasField(pegiString)) { 0327 entry_->collection()->addField(Data::Field::createDefaultField(Data::Field::PegiField)); 0328 } 0329 entry_->setField(pegiString, m_pegiHash.value(rating)); 0330 } 0331 } 0332 0333 const QVariantList companyList = resultMap_.value(QStringLiteral("involved_companies")).toList(); 0334 0335 QList<int>companyIdList; 0336 foreach(const QVariant& company, companyList) { 0337 const QVariantMap companyMap = company.toMap(); 0338 const int companyId = companyMap.value(QStringLiteral("company")).toInt(); 0339 if(!m_companyHash.contains(companyId)) { 0340 companyIdList += companyId; 0341 } 0342 } 0343 if(!companyIdList.isEmpty()) { 0344 readDataList(Company, companyIdList); 0345 } 0346 0347 QStringList pubs, devs; 0348 foreach(const QVariant& company, companyList) { 0349 const QVariantMap companyMap = company.toMap(); 0350 const int companyId = companyMap.value(QStringLiteral("company")).toInt(); 0351 const QString companyName = m_companyHash.value(companyId); 0352 if(companyName.isEmpty()) { 0353 continue; 0354 } 0355 if(companyMap.value(QStringLiteral("publisher")).toBool()) { 0356 pubs += companyName; 0357 } else if(companyMap.value(QStringLiteral("developer")).toBool()) { 0358 devs += companyName; 0359 } 0360 } 0361 entry_->setField(QStringLiteral("publisher"), pubs.join(FieldFormat::delimiterString())); 0362 entry_->setField(QStringLiteral("developer"), devs.join(FieldFormat::delimiterString())); 0363 0364 const QString igdbString = QStringLiteral("igdb"); 0365 if(optionalFields().contains(igdbString)) { 0366 if(!entry_->collection()->hasField(igdbString)) { 0367 Data::FieldPtr field(new Data::Field(igdbString, i18n("IGDB Link"), Data::Field::URL)); 0368 field->setCategory(i18n("General")); 0369 entry_->collection()->addField(field); 0370 } 0371 entry_->setField(igdbString, mapValue(resultMap_, "url")); 0372 } 0373 } 0374 0375 Tellico::Fetch::ConfigWidget* IGDBFetcher::configWidget(QWidget* parent_) const { 0376 return new IGDBFetcher::ConfigWidget(parent_, this); 0377 } 0378 0379 // Use member hash for certain field names for now. 0380 // Don't expect IGDB values to change. This avoids exponentially multiplying the number of API calls 0381 void IGDBFetcher::populateHashes() { 0382 QFile genreFile(dataFileName(Genre)); 0383 if(genreFile.open(QIODevice::ReadOnly)) { 0384 updateData(Genre, genreFile.readAll()); 0385 } else if(genreFile.exists()) { // don't want errors for non-existing file 0386 myDebug() << "Failed to read genres from" << genreFile.fileName() << genreFile.errorString(); 0387 } 0388 0389 QFile platformFile(dataFileName(Platform)); 0390 if(platformFile.open(QIODevice::ReadOnly)) { 0391 updateData(Platform, platformFile.readAll()); 0392 } else if(platformFile.exists()) { // don't want errors for non-existing file 0393 myDebug() << "Failed to read from" << platformFile.fileName() << platformFile.errorString(); 0394 } 0395 0396 QFile companyFile(dataFileName(Company)); 0397 if(companyFile.open(QIODevice::ReadOnly)) { 0398 updateData(Company, companyFile.readAll()); 0399 } else if(companyFile.exists()) { // don't want errors for non-existing file 0400 myDebug() << "Failed to read from" << companyFile.fileName() << companyFile.errorString(); 0401 } 0402 0403 // grab i18n values for ESRB from default collection 0404 Data::CollPtr c(new Data::GameCollection(true)); 0405 QStringList esrb = c->fieldByName(QStringLiteral("certification"))->allowed(); 0406 Q_ASSERT(esrb.size() == 8); 0407 while(esrb.size() < 8) { 0408 esrb << QString(); 0409 } 0410 // see https://api-docs.igdb.com/#age-rating 0411 m_esrbHash.insert(12, esrb.at(1)); // adults only 0412 m_esrbHash.insert(11, esrb.at(2)); // mature 0413 m_esrbHash.insert(10, esrb.at(3)); // teen 0414 m_esrbHash.insert(9, esrb.at(4)); // e10 0415 m_esrbHash.insert(8, esrb.at(5)); // everyone 0416 m_esrbHash.insert(7, esrb.at(6)); // early childhood 0417 m_esrbHash.insert(6, esrb.at(7)); // pending 0418 0419 m_pegiHash.insert(1, QStringLiteral("PEGI 3")); 0420 m_pegiHash.insert(2, QStringLiteral("PEGI 7")); 0421 m_pegiHash.insert(3, QStringLiteral("PEGI 12")); 0422 m_pegiHash.insert(4, QStringLiteral("PEGI 16")); 0423 m_pegiHash.insert(5, QStringLiteral("PEGI 18")); 0424 } 0425 0426 void IGDBFetcher::updateData(IgdbDataType dataType_, const QByteArray& jsonData_) { 0427 const QString idString(QStringLiteral("id")); 0428 const QString nmString(QStringLiteral("name")); 0429 QHash<int, QString> dataHash; 0430 const QJsonArray array = QJsonDocument::fromJson(jsonData_).array(); 0431 for(int i = 0; i < array.size(); ++i) { 0432 QJsonObject obj = array.at(i).toObject(); 0433 dataHash.insert(obj.value(idString).toInt(), 0434 obj.value(nmString).toString()); 0435 } 0436 0437 // transfer read data into the correct local variable 0438 switch(dataType_) { 0439 case Genre: 0440 m_genreHash = dataHash; 0441 break; 0442 case Platform: 0443 m_platformHash = dataHash; 0444 break; 0445 case Company: 0446 // company list is bigger than request size, so rather than downloading all names 0447 // have to do it in chunks and then merge 0448 #if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0)) 0449 m_companyHash.unite(dataHash); 0450 #else 0451 m_companyHash.insert(dataHash); 0452 #endif 0453 break; 0454 } 0455 } 0456 0457 void IGDBFetcher::readDataList(IgdbDataType dataType_, const QList<int>& idList_) { 0458 QUrl u(QString::fromLatin1(IGDB_API_URL)); 0459 switch(dataType_) { 0460 case Genre: 0461 u.setPath(u.path() + QStringLiteral("/genres")); 0462 break; 0463 case Platform: 0464 u.setPath(u.path() + QStringLiteral("/platforms")); 0465 break; 0466 case Company: 0467 u.setPath(u.path() + QStringLiteral("/companies")); 0468 break; 0469 } 0470 0471 QStringList clauseList; 0472 clauseList += QStringLiteral("fields id,name;"); 0473 // if the id list is not empty, seach for specific data id values 0474 if(!idList_.isEmpty()) { 0475 // where id = (8,9,11); 0476 QString clause = QStringLiteral("where id = (") + QString::number(idList_.at(0)); 0477 for(int i = 1; i < idList_.size(); ++i) { 0478 clause += (QLatin1String(",") + QString::number(idList_.at(i))); 0479 } 0480 clause += QLatin1String(");"); 0481 clauseList += clause; 0482 } 0483 clauseList += QStringLiteral("limit 500;"); // biggest limit is 500 which should be enough for all 0484 0485 QPointer<KIO::StoredTransferJob> job = igdbJob(u, clauseList.join(QStringLiteral(" "))); 0486 markTime(); 0487 if(!job->exec()) { 0488 myDebug() << "IGDB: data request failed"; 0489 myDebug() << job->errorString() << u; 0490 return; 0491 } 0492 0493 const QByteArray data = job->data(); 0494 updateData(dataType_, data); 0495 0496 // now save the date, but instead of just writing the job->data() to the file 0497 // since the company data may have been merged, write the full set of hash values 0498 QByteArray dataToWrite; 0499 if(dataType_ == Company) { 0500 QJsonArray array; 0501 const QString idString(QStringLiteral("id")); 0502 const QString nmString(QStringLiteral("name")); 0503 QHashIterator<int, QString> it(m_companyHash); 0504 while(it.hasNext()) { 0505 it.next(); 0506 QJsonObject obj; 0507 obj.insert(idString, it.key()); 0508 obj.insert(nmString, it.value()); 0509 array.append(obj); 0510 } 0511 dataToWrite = QJsonDocument(array).toJson(); 0512 } else { 0513 dataToWrite = data; 0514 } 0515 0516 QFile file(dataFileName(dataType_)); 0517 if(!file.open(QIODevice::WriteOnly) || file.write(dataToWrite) == -1) { 0518 myDebug() << "unable to write to" << file.fileName() << file.errorString(); 0519 return; 0520 } 0521 file.close(); 0522 } 0523 0524 void IGDBFetcher::markTime() const { 0525 // rate limit is 4 requests per second 0526 if(m_requestTimer.elapsed() < 250) QThread::msleep(250); 0527 m_requestTimer.restart(); 0528 } 0529 0530 void IGDBFetcher::checkAccessToken() { 0531 const QDateTime now = QDateTime::currentDateTimeUtc(); 0532 if(!m_accessToken.isEmpty() && m_accessTokenExpires > now) { 0533 // access token should be fine, nothing to do 0534 return; 0535 } 0536 0537 QUrl u(QString::fromLatin1(IGDB_TOKEN_URL)); 0538 // myDebug() << "Downloading IGDN token from" << u.toString(); 0539 QPointer<KIO::StoredTransferJob> job = KIO::storedHttpPost(QByteArray(), u, KIO::HideProgressInfo); 0540 job->addMetaData(QStringLiteral("accept"), QStringLiteral("application/json")); 0541 KJobWidgets::setWindow(job, GUI::Proxy::widget()); 0542 if(!job->exec()) { 0543 myDebug() << "IGDB: access token request failed"; 0544 myDebug() << job->errorString() << u; 0545 return; 0546 } 0547 0548 QJsonDocument doc = QJsonDocument::fromJson(job->data()); 0549 if(doc.isNull()) { 0550 myDebug() << "IGDB: Invalid JSON"; 0551 return; 0552 } 0553 QJsonObject response = doc.object(); 0554 if(response.contains(QLatin1String("message"))) { 0555 myDebug() << "IGDB:" << response.value(QLatin1String("message")).toString(); 0556 } 0557 m_accessToken = response.value(QLatin1String("access_token")).toString(); 0558 const int expires = response.value(QLatin1String("expires_in")).toInt(); 0559 if(expires > 0) { 0560 m_accessTokenExpires = now.addSecs(expires); 0561 } 0562 // myDebug() << "Received access token" << m_accessToken << m_accessTokenExpires; 0563 } 0564 0565 QString IGDBFetcher::defaultName() { 0566 return i18n("Internet Game Database (IGDB.com)"); 0567 } 0568 0569 QString IGDBFetcher::defaultIcon() { 0570 // IGDB blocks favicon requests without a referer seemingly 0571 return favIcon(QUrl(QLatin1String("https://www.igdb.com")), 0572 QUrl(QLatin1String("https://tellico-project.org/img/igdb-favicon.ico"))); 0573 } 0574 0575 Tellico::StringHash IGDBFetcher::allOptionalFields() { 0576 StringHash hash; 0577 hash[QStringLiteral("pegi")] = i18n("PEGI Rating"); 0578 hash[QStringLiteral("igdb")] = i18n("IGDB Link"); 0579 hash[QStringLiteral("screenshot")] = i18n("Screenshot"); 0580 return hash; 0581 } 0582 0583 QString IGDBFetcher::dataFileName(IgdbDataType dataType_) { 0584 const QString dataDir = Tellico::saveLocation(QStringLiteral("igdb-data/")); 0585 QString fileName; 0586 switch(dataType_) { 0587 case Genre: 0588 fileName = dataDir + QLatin1String("genres.json"); 0589 break; 0590 case Platform: 0591 fileName = dataDir + QLatin1String("platforms.json"); 0592 break; 0593 case Company: 0594 fileName = dataDir + QLatin1String("companies.json"); 0595 break; 0596 } 0597 return fileName; 0598 } 0599 0600 QPointer<KIO::StoredTransferJob> IGDBFetcher::igdbJob(const QUrl& url_, const QString& query_) { 0601 checkAccessToken(); 0602 QPointer<KIO::StoredTransferJob> job = KIO::storedHttpPost(query_.toUtf8(), url_, KIO::HideProgressInfo); 0603 QStringList customHeaders; 0604 customHeaders += (QStringLiteral("Client-ID: ") + QString::fromLatin1(IGDB_CLIENT_ID)); 0605 customHeaders += (QStringLiteral("Authorization: ") + QLatin1String("Bearer ") + m_accessToken); 0606 job->addMetaData(QStringLiteral("customHTTPHeader"), customHeaders.join(QLatin1String("\r\n"))); 0607 job->addMetaData(QStringLiteral("accept"), QStringLiteral("application/json")); 0608 KJobWidgets::setWindow(job, GUI::Proxy::widget()); 0609 return job; 0610 } 0611 0612 IGDBFetcher::ConfigWidget::ConfigWidget(QWidget* parent_, const IGDBFetcher* fetcher_) 0613 : Fetch::ConfigWidget(parent_) { 0614 QVBoxLayout* l = new QVBoxLayout(optionsWidget()); 0615 l->addWidget(new QLabel(i18n("This source has no options."), optionsWidget())); 0616 l->addStretch(); 0617 0618 // now add additional fields widget 0619 addFieldsWidget(IGDBFetcher::allOptionalFields(), fetcher_ ? fetcher_->optionalFields() : QStringList()); 0620 } 0621 0622 void IGDBFetcher::ConfigWidget::saveConfigHook(KConfigGroup&) { 0623 } 0624 0625 QString IGDBFetcher::ConfigWidget::preferredName() const { 0626 return IGDBFetcher::defaultName(); 0627 }