diff --git a/NEWS.md b/NEWS.md index 81f394a5f..a912ff215 100644 --- a/NEWS.md +++ b/NEWS.md @@ -22,6 +22,7 @@ Many bugs have been fixed, and numerous features that were only partially implem * Game systems are now sorted by full names which makes much more sense from a user perspective * New game media file logic using a media directory with files matching the ROM names instead of pointing to the media files in gamelist.xml * Updated scraper to support additional media files, detailed configuration of what to scrape, semi-automatic mode etc. +* Added user account support when scraping using ScreenScraper * In the metadata editor, any values updated by the single-game scraper or by the user are now highlighted using a different font color * Files or folders can now be flagged for exclusion when scraping with the multi-scraper, and for folders it can be set to apply recursively * Gamelist sorting is now working as expected and is persistent throughout the application session diff --git a/USERGUIDE.md b/USERGUIDE.md index 236ebdee7..f4deda50c 100644 --- a/USERGUIDE.md +++ b/USERGUIDE.md @@ -495,6 +495,22 @@ Criteria for what games to include in the scraping. It can be set to 'All games' A selection of which systems to scrape for. It's possible to automatically scrape several or all systems in one go. +#### Account settings + +Setup of ScreenScraper account. + +**Use this account for ScreenScraper** + +Whether to use the account that has been setup here. If this is disabled, the username and password configured on this screen will be ignored during scraping. This can be useful if you have scraping issues and want to check whether it's related to your account or if it's a general problem. Note that screenscraper.fr does not seem to return a proper error message regarding incorrect username and password, but starting ES with the --debug flag will indicate in the log file whether the username was included in the server response. + +**ScreenScraper username** + +Username as registered on screenscraper.fr. + +**ScreenScraper password** + +The password as registered on screenscraper.fr. Note that the password is masked using asterisks on this screen, and the password input field will be blank when attempting to update an existing password. Entering a new password will of course work, and it will be saved accordingly. Be aware though that the es_settings.cfg file contains the password in clear text. + #### Content settings Describes the content types to include in the scraping. Most users will probably not need to adjust so many of these. diff --git a/es-app/src/guis/GuiScraperMenu.cpp b/es-app/src/guis/GuiScraperMenu.cpp index c2887fcb8..917fcb00c 100644 --- a/es-app/src/guis/GuiScraperMenu.cpp +++ b/es-app/src/guis/GuiScraperMenu.cpp @@ -72,6 +72,9 @@ GuiScraperMenu::GuiScraperMenu(Window* window, std::string title) } mMenu.addWithLabel("Systems", mSystems); + addEntry("ACCOUNT SETTINGS", 0x777777FF, true, [this] { + openAccountSettings(); + }); addEntry("CONTENT SETTINGS", 0x777777FF, true, [this] { // If the scraper service has been changed before entering this menu, then save the // settings so that the specific options supported by the respective scrapers @@ -115,6 +118,61 @@ GuiScraperMenu::~GuiScraperMenu() } } +void GuiScraperMenu::openAccountSettings() +{ + auto s = new GuiSettings(mWindow, "ACCOUNT SETTINGS"); + + // Whether to use the ScreenScraper account when scraping. + auto scraper_use_account_screenscraper = std::make_shared(mWindow); + scraper_use_account_screenscraper->setState(Settings::getInstance()-> + getBool("ScraperUseAccountScreenScraper")); + s->addWithLabel("USE THIS ACCOUNT FOR SCREENSCRAPER", scraper_use_account_screenscraper); + s->addSaveFunc([scraper_use_account_screenscraper, s] { + if (scraper_use_account_screenscraper->getState() != + Settings::getInstance()->getBool("ScraperUseAccountScreenScraper")) { + Settings::getInstance()->setBool("ScraperUseAccountScreenScraper", + scraper_use_account_screenscraper->getState()); + s->setNeedsSaving(); + } + }); + + // ScreenScraper username. + auto scraper_username_screenscraper = std::make_shared(mWindow, "", + Font::get(FONT_SIZE_MEDIUM), 0x777777FF, ALIGN_RIGHT); + s->addEditableTextComponent("SCREENSCRAPER USERNAME", scraper_username_screenscraper, + Settings::getInstance()->getString("ScraperUsernameScreenScraper")); + s->addSaveFunc([scraper_username_screenscraper, s] { + if (scraper_username_screenscraper->getValue() != + Settings::getInstance()->getString("ScraperUsernameScreenScraper")) { + Settings::getInstance()->setString("ScraperUsernameScreenScraper", + scraper_username_screenscraper->getValue()); + s->setNeedsSaving(); + } + }); + + // ScreenScraper password. + auto scraper_password_screenscraper = std::make_shared(mWindow, "", + Font::get(FONT_SIZE_MEDIUM), 0x777777FF, ALIGN_RIGHT); + std::string passwordMasked; + if (Settings::getInstance()->getString("ScraperPasswordScreenScraper") != "") { + passwordMasked = "********"; + scraper_password_screenscraper->setHiddenValue( + Settings::getInstance()->getString("ScraperPasswordScreenScraper")); + } + s->addEditableTextComponent("SCREENSCRAPER PASSWORD", + scraper_password_screenscraper, passwordMasked, "", true); + s->addSaveFunc([scraper_password_screenscraper, s] { + if (scraper_password_screenscraper->getHiddenValue() != + Settings::getInstance()->getString("ScraperPasswordScreenScraper")) { + Settings::getInstance()->setString("ScraperPasswordScreenScraper", + scraper_password_screenscraper->getHiddenValue()); + s->setNeedsSaving(); + } + }); + + mWindow->pushGui(s); +} + void GuiScraperMenu::openContentSettings() { auto s = new GuiSettings(mWindow, "SCRAPER CONTENT SETTINGS"); diff --git a/es-app/src/guis/GuiScraperMenu.h b/es-app/src/guis/GuiScraperMenu.h index a6daafb7e..0b6340e11 100644 --- a/es-app/src/guis/GuiScraperMenu.h +++ b/es-app/src/guis/GuiScraperMenu.h @@ -39,6 +39,7 @@ private: void addEntry(const char* name, unsigned int color, bool add_arrow, const std::function& func); + void openAccountSettings(); void openContentSettings(); void openOtherSettings(); diff --git a/es-app/src/guis/GuiSettings.cpp b/es-app/src/guis/GuiSettings.cpp index 962ed940c..5d56491bd 100644 --- a/es-app/src/guis/GuiSettings.cpp +++ b/es-app/src/guis/GuiSettings.cpp @@ -92,8 +92,12 @@ void GuiSettings::save() mWindow->invalidateCachedBackground(); } -void GuiSettings::addEditableTextComponent(const std::string label, - std::shared_ptr ed, std::string value, std::string defaultValue) +void GuiSettings::addEditableTextComponent( + const std::string label, + std::shared_ptr ed, + std::string value, + std::string defaultValue, + bool isPassword) { ComponentListRow row; row.elements.clear(); @@ -114,16 +118,33 @@ void GuiSettings::addEditableTextComponent(const std::string label, row.addElement(bracket, false); // OK callback (apply new value to ed). - auto updateVal = [ed, defaultValue](const std::string& newVal) { + auto updateVal = [ed, defaultValue, isPassword](const std::string& newVal) { // If the field is blank, apply the default value if it's been passes as an argument. - if (defaultValue != "" && newVal == "") + if (defaultValue != "" && newVal == "") { ed->setValue(defaultValue); - else + } + // If it's a password and actually set to something, then show a star mask. + else if (isPassword && newVal == "") { + ed->setValue(""); + ed->setHiddenValue(""); + } + else if (isPassword) { + ed->setValue("********"); + ed->setHiddenValue(newVal); + } + else { ed->setValue(newVal); + } }; - row.makeAcceptInputHandler([this, label, ed, updateVal] { - mWindow->pushGui(new GuiTextEditPopup(mWindow, getHelpStyle(), label, - ed->getValue(), updateVal, false)); + + row.makeAcceptInputHandler([this, label, ed, updateVal, isPassword] { + // Never display the value if it's a password, instead set it to blank. + if (isPassword) + mWindow->pushGui(new GuiTextEditPopup(mWindow, getHelpStyle(), label, + "", updateVal, false)); + else + mWindow->pushGui(new GuiTextEditPopup(mWindow, getHelpStyle(), label, + ed->getValue(), updateVal, false)); }); assert(ed); addRow(row); diff --git a/es-app/src/guis/GuiSettings.h b/es-app/src/guis/GuiSettings.h index fe1ebdb09..bf943b6ee 100644 --- a/es-app/src/guis/GuiSettings.h +++ b/es-app/src/guis/GuiSettings.h @@ -25,8 +25,12 @@ public: inline void addRow(const ComponentListRow& row) { mMenu.addRow(row); }; inline void addWithLabel(const std::string& label, const std::shared_ptr& comp) { mMenu.addWithLabel(label, comp); }; - void addEditableTextComponent(const std::string label, std::shared_ptr ed, - std::string value, std::string defaultValue = ""); + void addEditableTextComponent( + const std::string label, + std::shared_ptr ed, + std::string value, + std::string defaultValue = "", + bool isPassword = false); inline void addSaveFunc(const std::function& func) { mSaveFuncs.push_back(func); }; void setNeedsSaving() { mNeedsSaving = true; }; diff --git a/es-app/src/scrapers/ScreenScraper.cpp b/es-app/src/scrapers/ScreenScraper.cpp index fb3784515..59502f1b4 100644 --- a/es-app/src/scrapers/ScreenScraper.cpp +++ b/es-app/src/scrapers/ScreenScraper.cpp @@ -358,6 +358,24 @@ void ScreenScraperRequest::processGame(const pugi::xml_document& xmldoc, result.mdl.get("players"); } + // Username, if an account is used for scraping. + if (Settings::getInstance()->getBool("ScraperUseAccountScreenScraper") && + Settings::getInstance()->getString("ScraperUsernameScreenScraper") != "" && + Settings::getInstance()->getString("ScraperPasswordScreenScraper") != "") { + // Check if our username was included in the response. + std::string userID = data.child("ssuser").child("id").text().get(); + if (userID != "") { + LOG(LogDebug) << "ScreenScraperRequest::processGame(): Scraping using account '" << + userID << "'."; + } + else { + LOG(LogDebug) << "ScreenScraperRequest::processGame(): The configured account '" << + Settings::getInstance()->getString("ScraperUsernameScreenScraper") << + "' was not included in the scraper response, wrong username or password?"; + } + } + + // Scraping allowance. if (maxRequestsPerDay > 0) { LOG(LogDebug) << "ScreenScraperRequest::processGame(): Daily scraping allowance: " << requestsToday << "/" << maxRequestsPerDay << " (" << @@ -487,10 +505,21 @@ void ScreenScraperRequest::processList(const pugi::xml_document& xmldoc, std::string ScreenScraperRequest::ScreenScraperConfig::getGameSearchUrl( const std::string gameName) const { - return API_URL_BASE + std::string screenScraperURL = API_URL_BASE + "/jeuInfos.php?devid=" + Utils::String::scramble(API_DEV_U, API_DEV_KEY) + "&devpassword=" + Utils::String::scramble(API_DEV_P, API_DEV_KEY) + "&softname=" + HttpReq::urlEncode(API_SOFT_NAME) + "&output=xml" + "&romnom=" + HttpReq::urlEncode(gameName); + + // Username / password, if this has been setup and activated. + if (Settings::getInstance()->getBool("ScraperUseAccountScreenScraper")) { + std::string username = Settings::getInstance()->getString("ScraperUsernameScreenScraper"); + std::string password = Settings::getInstance()->getString("ScraperPasswordScreenScraper"); + if (!username.empty() && !password.empty()) + screenScraperURL += "&ssid=" + HttpReq::urlEncode(username) + "&sspassword=" + + HttpReq::urlEncode(password); + } + + return screenScraperURL; } diff --git a/es-core/src/Settings.cpp b/es-core/src/Settings.cpp index abe28bf2b..8541059aa 100644 --- a/es-core/src/Settings.cpp +++ b/es-core/src/Settings.cpp @@ -81,6 +81,9 @@ void Settings::setDefaults() // Scraper. mStringMap["Scraper"] = "screenscraper"; + mBoolMap["ScraperUseAccountScreenScraper"] = false; + mStringMap["ScraperUsernameScreenScraper"] = ""; + mStringMap["ScraperPasswordScreenScraper"] = ""; mBoolMap["ScrapeGameNames"] = true; mBoolMap["ScrapeRatings"] = true; mBoolMap["ScrapeMetadata"] = true;