Repository: xbmc/xbmc
File: xbmc/addons/Scraper.cpp (C++)

Code before:
  else
  {
    sTitle = movieTitle;
    sYear = std::to_string( movieYear );
  }

  CLog::Log(LOGDEBUG,
            "{}: Searching for '{}' using {} scraper "
            "(path: '{}', content: '{}', version: '{}')",
            __FUNCTION__, sTitle, Name(), Path(), ADDON::TranslateContent(Content()),
            Version().asString());

  std::vector<CScraperUrl> vcscurl;
  if (IsNoop())
    return vcscurl;

  if (!fFirst)
    StringUtils::Replace(sTitle, '-', ' ');

  if (m_isPython)
  {
    if (!database.empty() && !identifier.empty())
    {
      CScraperUrl scurl;
      scurl.SetTitle(movieTitle);
      std::string json = fmt::format(R"({{"{}":"{}"}})", database, identifier);
      scurl.SetUrls({CScraperUrl::SUrlEntry(json)});
      return {scurl};
    }

    std::map<std::string, std::string> additionals{{"title", sTitle}};
    if (!sYear.empty())
      additionals.insert({"year", sYear});
    additionals.emplace("pathSettings", GetPathSettingsAsJSON());
    return PythonFind<CScraperUrl>(ID(), additionals);
  }

  std::vector<std::string> vcsIn(1);
  g_charsetConverter.utf8To(SearchStringEncoding(), sTitle, vcsIn[0]);
  vcsIn[0] = CURL::Encode(vcsIn[0]);
  if (fFirst && !sYear.empty())
    vcsIn.push_back(sYear);

  // request a search URL from the title/filename/etc.
  CScraperUrl scurl;
  std::vector<std::string> vcsOut = Run("CreateSearchUrl", scurl, fcurl, &vcsIn);
  if (vcsOut.empty())
  {
    CLog::Log(LOGDEBUG, "{}: CreateSearchUrl failed", __FUNCTION__);
    throw CScraperError();
  }

Review comment (suggestion):
I am not a big fan of building even a tiny JSON object string with string formatting. I'd suggest creating a CVariant object with a single property and use `CJSONVariantWriter` to write to a string.

Code after:
                          const std::string& key,
                          const std::string& url,
                          const std::string& action,
                          const std::string& pathSettings,
                          T& result)
{
  const std::unordered_map<std::string, std::string> ids;
  return PythonDetails(ID, key, url, action, pathSettings, ids, result);
}

// fetch list of matching movies sorted by relevance (may be empty);
// throws CScraperError on error; first called with fFirst set, then unset if first try fails
std::vector<CScraperUrl> CScraper::FindMovie(XFILE::CCurlFile &fcurl,
                                             const std::string &movieTitle, int movieYear,
                                             bool fFirst)
{
  // prepare parameters for URL creation
  std::string sTitle, sYear;
  if (movieYear < 0)
  {
    std::string sTitleYear;
    CUtil::CleanString(movieTitle, sTitle, sTitleYear, sYear, true /*fRemoveExt*/, fFirst);
  }
  else
  {
    sTitle = movieTitle;
    sYear = std::to_string( movieYear );
  }

  CLog::Log(LOGDEBUG,
            "{}: Searching for '{}' using {} scraper "
            "(path: '{}', content: '{}', version: '{}')",
            __FUNCTION__, sTitle, Name(), Path(), ADDON::TranslateContent(Content()),
            Version().asString());

  std::vector<CScraperUrl> vcscurl;
  if (IsNoop())
    return vcscurl;

  if (!fFirst)
    StringUtils::Replace(sTitle, '-', ' ');

  if (m_isPython)
  {
    std::map<std::string, std::string> additionals{{"title", sTitle}};
    if (!sYear.empty())
      additionals.insert({"year", sYear});
    additionals.emplace("pathSettings", GetPathSettingsAsJSON());
    return PythonFind<CScraperUrl>(ID(), additionals);
  }