mirror of
https://github.com/Sude-/lgogdownloader.git
synced 2025-02-02 05:52:31 +01:00
Add support for saving serial numbers
This commit is contained in:
parent
f039db5c65
commit
216cfaedfe
@ -42,6 +42,7 @@ class Config
|
|||||||
bool bSubDirectories;
|
bool bSubDirectories;
|
||||||
bool bUseCache;
|
bool bUseCache;
|
||||||
bool bUpdateCache;
|
bool bUpdateCache;
|
||||||
|
bool bSaveSerials;
|
||||||
std::string sGameRegex;
|
std::string sGameRegex;
|
||||||
std::string sDirectory;
|
std::string sDirectory;
|
||||||
std::string sCacheDirectory;
|
std::string sCacheDirectory;
|
||||||
|
@ -89,7 +89,10 @@ class Downloader
|
|||||||
int HTTP_Login(const std::string& email, const std::string& password);
|
int HTTP_Login(const std::string& email, const std::string& password);
|
||||||
std::vector<gameItem> getGames();
|
std::vector<gameItem> getGames();
|
||||||
std::vector<gameItem> getFreeGames();
|
std::vector<gameItem> getFreeGames();
|
||||||
std::vector<gameFile> getExtras(const std::string& gamename, const std::string& gameid);
|
std::vector<gameFile> getExtrasFromHTML(const std::string& html, const std::string& gamename, const std::string& gameid);
|
||||||
|
std::string getGameDetailsHTML(const std::string& gamename, const std::string& gameid);
|
||||||
|
std::string getSerialsFromHTML(const std::string& html);
|
||||||
|
void saveSerials(const std::string& serials, const std::string& filepath);
|
||||||
|
|
||||||
static int progressCallback(void *clientp, double dltotal, double dlnow, double ultotal, double ulnow);
|
static int progressCallback(void *clientp, double dltotal, double dlnow, double ultotal, double ulnow);
|
||||||
static size_t writeMemoryCallback(char *ptr, size_t size, size_t nmemb, void *userp);
|
static size_t writeMemoryCallback(char *ptr, size_t size, size_t nmemb, void *userp);
|
||||||
|
@ -20,14 +20,17 @@ class gameDetails
|
|||||||
std::vector<gameDetails> dlcs;
|
std::vector<gameDetails> dlcs;
|
||||||
std::string gamename;
|
std::string gamename;
|
||||||
std::string title;
|
std::string title;
|
||||||
std::string icon;;
|
std::string icon;
|
||||||
|
std::string serials;
|
||||||
void filterWithPriorities(const Config& config);
|
void filterWithPriorities(const Config& config);
|
||||||
void makeFilepaths(const Config& config);
|
void makeFilepaths(const Config& config);
|
||||||
|
std::string getSerialsFilepath();
|
||||||
Json::Value getDetailsAsJson();
|
Json::Value getDetailsAsJson();
|
||||||
virtual ~gameDetails();
|
virtual ~gameDetails();
|
||||||
protected:
|
protected:
|
||||||
void filterListWithPriorities(std::vector<gameFile>& list, const Config& config);
|
void filterListWithPriorities(std::vector<gameFile>& list, const Config& config);
|
||||||
private:
|
private:
|
||||||
|
std::string serialsFilepath;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // GAMEDETAILS_H
|
#endif // GAMEDETAILS_H
|
||||||
|
2
main.cpp
2
main.cpp
@ -186,7 +186,7 @@ int main(int argc, char *argv[])
|
|||||||
("cache-valid", bpo::value<int>(&config.iCacheValid)->default_value(2880), ("Set how long cached game details are valid (in minutes)\nDefault: 2880 minutes (48 hours)"))
|
("cache-valid", bpo::value<int>(&config.iCacheValid)->default_value(2880), ("Set how long cached game details are valid (in minutes)\nDefault: 2880 minutes (48 hours)"))
|
||||||
("language-priority", bpo::value<std::string>(&config.sLanguagePriority)->default_value(""), ("Set priority of systems" + priority_help_text + ", like \"4,1\" for French first, then English if no French version").c_str())
|
("language-priority", bpo::value<std::string>(&config.sLanguagePriority)->default_value(""), ("Set priority of systems" + priority_help_text + ", like \"4,1\" for French first, then English if no French version").c_str())
|
||||||
("platform-priority", bpo::value<std::string>(&config.sPlatformPriority)->default_value(""), ("Set priority of platforms" + priority_help_text + ", like \"4,1\" for Linux first, then Windows if no Linux version").c_str())
|
("platform-priority", bpo::value<std::string>(&config.sPlatformPriority)->default_value(""), ("Set priority of platforms" + priority_help_text + ", like \"4,1\" for Linux first, then Windows if no Linux version").c_str())
|
||||||
|
("save-serials", bpo::value<bool>(&config.bSaveSerials)->zero_tokens()->default_value(false), "Save serial numbers when downloading")
|
||||||
;
|
;
|
||||||
// Options read from config file
|
// Options read from config file
|
||||||
options_cfg_only.add_options()
|
options_cfg_only.add_options()
|
||||||
|
@ -261,21 +261,37 @@ int Downloader::getGameDetails()
|
|||||||
if (!gogAPI->getError())
|
if (!gogAPI->getError())
|
||||||
{
|
{
|
||||||
game.filterWithPriorities(config);
|
game.filterWithPriorities(config);
|
||||||
|
std::string gameDetailsHTML;
|
||||||
|
|
||||||
if (game.extras.empty() && config.bExtras) // Try to get extras from account page if API didn't return any extras
|
if (game.extras.empty() && config.bExtras) // Try to get extras from account page if API didn't return any extras
|
||||||
{
|
{
|
||||||
game.extras = this->getExtras(gameItems[i].name, gameItems[i].id);
|
gameDetailsHTML = this->getGameDetailsHTML(gameItems[i].name, gameItems[i].id);
|
||||||
|
game.extras = this->getExtrasFromHTML(gameDetailsHTML, gameItems[i].name, gameItems[i].id);
|
||||||
|
}
|
||||||
|
if (config.bSaveSerials)
|
||||||
|
{
|
||||||
|
if (gameDetailsHTML.empty())
|
||||||
|
gameDetailsHTML = this->getGameDetailsHTML(gameItems[i].name, gameItems[i].id);
|
||||||
|
game.serials = this->getSerialsFromHTML(gameDetailsHTML);
|
||||||
}
|
}
|
||||||
if (game.dlcs.empty() && bHasDLC && conf.bDLC)
|
if (game.dlcs.empty() && bHasDLC && conf.bDLC)
|
||||||
{
|
{
|
||||||
for (unsigned int j = 0; j < gameItems[i].dlcnames.size(); ++j)
|
for (unsigned int j = 0; j < gameItems[i].dlcnames.size(); ++j)
|
||||||
{
|
{
|
||||||
gameDetails dlc;
|
gameDetails dlc;
|
||||||
|
std::string gameDetailsHTML_dlc;
|
||||||
dlc = gogAPI->getGameDetails(gameItems[i].dlcnames[j], conf.iInstallerType, conf.iInstallerLanguage, config.bDuplicateHandler);
|
dlc = gogAPI->getGameDetails(gameItems[i].dlcnames[j], conf.iInstallerType, conf.iInstallerLanguage, config.bDuplicateHandler);
|
||||||
dlc.filterWithPriorities(config);
|
dlc.filterWithPriorities(config);
|
||||||
if (dlc.extras.empty() && config.bExtras) // Try to get extras from account page if API didn't return any extras
|
if (dlc.extras.empty() && config.bExtras) // Try to get extras from account page if API didn't return any extras
|
||||||
{
|
{
|
||||||
dlc.extras = this->getExtras(gameItems[i].dlcnames[j], gameItems[i].id);
|
gameDetailsHTML_dlc = this->getGameDetailsHTML(gameItems[i].dlcnames[j], gameItems[i].id);
|
||||||
|
dlc.extras = this->getExtrasFromHTML(gameDetailsHTML_dlc, gameItems[i].dlcnames[j], gameItems[i].id);
|
||||||
|
}
|
||||||
|
if (config.bSaveSerials)
|
||||||
|
{
|
||||||
|
if (gameDetailsHTML_dlc.empty())
|
||||||
|
gameDetailsHTML_dlc = this->getGameDetailsHTML(gameItems[i].dlcnames[j], gameItems[i].id);
|
||||||
|
dlc.serials = this->getSerialsFromHTML(gameDetailsHTML_dlc);
|
||||||
}
|
}
|
||||||
game.dlcs.push_back(dlc);
|
game.dlcs.push_back(dlc);
|
||||||
}
|
}
|
||||||
@ -326,6 +342,9 @@ void Downloader::listGames()
|
|||||||
std::cout << "gamename: " << games[i].gamename << std::endl
|
std::cout << "gamename: " << games[i].gamename << std::endl
|
||||||
<< "title: " << games[i].title << std::endl
|
<< "title: " << games[i].title << std::endl
|
||||||
<< "icon: " << "http://static.gog.com" << games[i].icon << std::endl;
|
<< "icon: " << "http://static.gog.com" << games[i].icon << std::endl;
|
||||||
|
if (!games[i].serials.empty())
|
||||||
|
std::cout << "serials:" << std::endl << games[i].serials << std::endl;
|
||||||
|
|
||||||
// List installers
|
// List installers
|
||||||
if (config.bInstallers)
|
if (config.bInstallers)
|
||||||
{
|
{
|
||||||
@ -436,6 +455,12 @@ void Downloader::listGames()
|
|||||||
std::cout << "DLCs: " << std::endl;
|
std::cout << "DLCs: " << std::endl;
|
||||||
for (unsigned int j = 0; j < games[i].dlcs.size(); ++j)
|
for (unsigned int j = 0; j < games[i].dlcs.size(); ++j)
|
||||||
{
|
{
|
||||||
|
if (!games[i].dlcs[j].serials.empty())
|
||||||
|
{
|
||||||
|
std::cout << "\tDLC gamename: " << games[i].dlcs[j].gamename << std::endl
|
||||||
|
<< "\tserials:" << games[i].dlcs[j].serials << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
for (unsigned int k = 0; k < games[i].dlcs[j].installers.size(); ++k)
|
for (unsigned int k = 0; k < games[i].dlcs[j].installers.size(); ++k)
|
||||||
{
|
{
|
||||||
std::string filepath = games[i].dlcs[j].installers[k].getFilepath();
|
std::string filepath = games[i].dlcs[j].installers[k].getFilepath();
|
||||||
@ -758,6 +783,12 @@ void Downloader::download()
|
|||||||
|
|
||||||
for (unsigned int i = 0; i < games.size(); ++i)
|
for (unsigned int i = 0; i < games.size(); ++i)
|
||||||
{
|
{
|
||||||
|
if (config.bSaveSerials && !games[i].serials.empty())
|
||||||
|
{
|
||||||
|
std::string filepath = games[i].getSerialsFilepath();
|
||||||
|
this->saveSerials(games[i].serials, filepath);
|
||||||
|
}
|
||||||
|
|
||||||
// Download covers
|
// Download covers
|
||||||
if (config.bCover && !config.bUpdateCheck)
|
if (config.bCover && !config.bUpdateCheck)
|
||||||
{
|
{
|
||||||
@ -935,6 +966,12 @@ void Downloader::download()
|
|||||||
{
|
{
|
||||||
for (unsigned int j = 0; j < games[i].dlcs.size(); ++j)
|
for (unsigned int j = 0; j < games[i].dlcs.size(); ++j)
|
||||||
{
|
{
|
||||||
|
if (config.bSaveSerials && !games[i].dlcs[j].serials.empty())
|
||||||
|
{
|
||||||
|
std::string filepath = games[i].dlcs[j].getSerialsFilepath();
|
||||||
|
this->saveSerials(games[i].dlcs[j].serials, filepath);
|
||||||
|
}
|
||||||
|
|
||||||
if (config.bInstallers)
|
if (config.bInstallers)
|
||||||
{
|
{
|
||||||
for (unsigned int k = 0; k < games[i].dlcs[j].installers.size(); ++k)
|
for (unsigned int k = 0; k < games[i].dlcs[j].installers.size(); ++k)
|
||||||
@ -2180,30 +2217,36 @@ std::vector<gameItem> Downloader::getFreeGames()
|
|||||||
return games;
|
return games;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<gameFile> Downloader::getExtras(const std::string& gamename, const std::string& gameid)
|
std::string Downloader::getGameDetailsHTML(const std::string& gamename, const std::string& gameid)
|
||||||
{
|
{
|
||||||
Json::Value root;
|
|
||||||
Json::Reader *jsonparser = new Json::Reader;
|
|
||||||
std::vector<gameFile> extras;
|
|
||||||
|
|
||||||
std::string gameDataUrl = "https://www.gog.com/account/ajax?a=gamesListDetails&g=" + gameid;
|
std::string gameDataUrl = "https://www.gog.com/account/ajax?a=gamesListDetails&g=" + gameid;
|
||||||
std::string json = this->getResponse(gameDataUrl);
|
std::string json = this->getResponse(gameDataUrl);
|
||||||
|
|
||||||
// Parse JSON
|
// Parse JSON
|
||||||
|
Json::Value root;
|
||||||
|
Json::Reader *jsonparser = new Json::Reader;
|
||||||
if (!jsonparser->parse(json, root))
|
if (!jsonparser->parse(json, root))
|
||||||
{
|
{
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
std::cerr << "DEBUG INFO (Downloader::getExtras)" << std::endl << json << std::endl;
|
std::cerr << "DEBUG INFO (Downloader::getGameDetailsHTML)" << std::endl << json << std::endl;
|
||||||
#endif
|
#endif
|
||||||
std::cout << jsonparser->getFormatedErrorMessages();
|
std::cout << jsonparser->getFormatedErrorMessages();
|
||||||
delete jsonparser;
|
delete jsonparser;
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
std::cerr << "DEBUG INFO (Downloader::getExtras)" << std::endl << root << std::endl;
|
std::cerr << "DEBUG INFO (Downloader::getGameDetailsHTML)" << std::endl << root << std::endl;
|
||||||
#endif
|
#endif
|
||||||
std::string html = root["details"]["html"].asString();
|
std::string html = root["details"]["html"].asString();
|
||||||
delete jsonparser;
|
delete jsonparser;
|
||||||
|
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<gameFile> Downloader::getExtrasFromHTML(const std::string& html, const std::string& gamename, const std::string& gameid)
|
||||||
|
{
|
||||||
|
std::vector<gameFile> extras;
|
||||||
|
|
||||||
htmlcxx::HTML::ParserDom parser;
|
htmlcxx::HTML::ParserDom parser;
|
||||||
tree<htmlcxx::HTML::Node> dom = parser.parseTree(html);
|
tree<htmlcxx::HTML::Node> dom = parser.parseTree(html);
|
||||||
tree<htmlcxx::HTML::Node>::iterator it = dom.begin();
|
tree<htmlcxx::HTML::Node>::iterator it = dom.begin();
|
||||||
@ -2240,7 +2283,7 @@ std::vector<gameFile> Downloader::getExtras(const std::string& gamename, const s
|
|||||||
if (name.empty())
|
if (name.empty())
|
||||||
{
|
{
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
std::cerr << "DEBUG INFO (Downloader::getExtras)" << std::endl;
|
std::cerr << "DEBUG INFO (getExtrasFromHTML)" << std::endl;
|
||||||
std::cerr << "Skipped file without a name (game: " << gamename << ", gameid: " << gameid << ", fileid: " << id << ")" << std::endl;
|
std::cerr << "Skipped file without a name (game: " << gamename << ", gameid: " << gameid << ", fileid: " << id << ")" << std::endl;
|
||||||
#endif
|
#endif
|
||||||
continue;
|
continue;
|
||||||
@ -2261,6 +2304,60 @@ std::vector<gameFile> Downloader::getExtras(const std::string& gamename, const s
|
|||||||
return extras;
|
return extras;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::string Downloader::getSerialsFromHTML(const std::string& html)
|
||||||
|
{
|
||||||
|
std::ostringstream serials;
|
||||||
|
|
||||||
|
htmlcxx::HTML::ParserDom parser;
|
||||||
|
tree<htmlcxx::HTML::Node> dom = parser.parseTree(html);
|
||||||
|
tree<htmlcxx::HTML::Node>::iterator it = dom.begin();
|
||||||
|
tree<htmlcxx::HTML::Node>::iterator end = dom.end();
|
||||||
|
for (; it != end; ++it)
|
||||||
|
{
|
||||||
|
if (it->tagName() == "div")
|
||||||
|
{
|
||||||
|
it->parseAttributes();
|
||||||
|
std::string classname = it->attribute("class").second;
|
||||||
|
if (classname == "list_serial_h")
|
||||||
|
{
|
||||||
|
for (unsigned int i = 0; i < dom.number_of_children(it); ++i)
|
||||||
|
{
|
||||||
|
tree<htmlcxx::HTML::Node>::iterator serials_it = dom.child(it, i);
|
||||||
|
if (!serials_it->isComment())
|
||||||
|
{
|
||||||
|
std::string tag_text;
|
||||||
|
if (!serials_it->isTag())
|
||||||
|
{
|
||||||
|
if (!serials_it->text().empty())
|
||||||
|
tag_text = serials_it->text();
|
||||||
|
}
|
||||||
|
else if (serials_it->tagName() == "span")
|
||||||
|
{
|
||||||
|
for (unsigned int j = 0; j < dom.number_of_children(serials_it); ++j)
|
||||||
|
{
|
||||||
|
tree<htmlcxx::HTML::Node>::iterator serials_span_it = dom.child(serials_it, j);
|
||||||
|
if (!serials_span_it->isTag() && !serials_span_it->isComment())
|
||||||
|
tag_text = serials_span_it->text();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tag_text.empty())
|
||||||
|
{
|
||||||
|
boost::regex expression("^\\h+|\\h+$");
|
||||||
|
std::string text = boost::regex_replace(tag_text, expression, "");
|
||||||
|
if (!text.empty())
|
||||||
|
serials << text << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return serials.str();
|
||||||
|
}
|
||||||
|
|
||||||
void Downloader::checkOrphans()
|
void Downloader::checkOrphans()
|
||||||
{
|
{
|
||||||
// Always check everything when checking for orphaned files
|
// Always check everything when checking for orphaned files
|
||||||
@ -2798,6 +2895,7 @@ std::vector<gameDetails> Downloader::getGameDetailsFromJsonNode(Json::Value root
|
|||||||
}
|
}
|
||||||
game.title = gameDetailsNode["title"].asString();
|
game.title = gameDetailsNode["title"].asString();
|
||||||
game.icon = gameDetailsNode["icon"].asString();
|
game.icon = gameDetailsNode["icon"].asString();
|
||||||
|
game.serials = gameDetailsNode["serials"].asString();
|
||||||
|
|
||||||
// Make a vector of valid node names to make things easier
|
// Make a vector of valid node names to make things easier
|
||||||
std::vector<std::string> nodes;
|
std::vector<std::string> nodes;
|
||||||
@ -2892,3 +2990,49 @@ void Downloader::updateCache()
|
|||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Save serials to file
|
||||||
|
void Downloader::saveSerials(const std::string& serials, const std::string& filepath)
|
||||||
|
{
|
||||||
|
bool bFileExists = boost::filesystem::exists(filepath);
|
||||||
|
|
||||||
|
if (bFileExists)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Get directory from filepath
|
||||||
|
boost::filesystem::path pathname = filepath;
|
||||||
|
std::string directory = pathname.parent_path().string();
|
||||||
|
|
||||||
|
// Check that directory exists and create subdirectories
|
||||||
|
boost::filesystem::path path = directory;
|
||||||
|
if (boost::filesystem::exists(path))
|
||||||
|
{
|
||||||
|
if (!boost::filesystem::is_directory(path))
|
||||||
|
{
|
||||||
|
std::cout << path << " is not directory" << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!boost::filesystem::create_directories(path))
|
||||||
|
{
|
||||||
|
std::cout << "Failed to create directory: " << path << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ofstream ofs(filepath);
|
||||||
|
if (ofs)
|
||||||
|
{
|
||||||
|
std::cout << "Saving serials: " << filepath << std::endl;
|
||||||
|
ofs << serials;
|
||||||
|
ofs.close();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::cout << "Failed to create file: " << filepath << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
@ -71,6 +71,7 @@ void gameDetails::makeFilepaths(const Config& config)
|
|||||||
std::string filepath;
|
std::string filepath;
|
||||||
std::string directory = config.sDirectory + "/" + config.sGameSubdir + "/";
|
std::string directory = config.sDirectory + "/" + config.sGameSubdir + "/";
|
||||||
std::string subdir;
|
std::string subdir;
|
||||||
|
this->serialsFilepath = Util::makeFilepath(directory, "serials.txt", this->gamename, subdir, 0);
|
||||||
|
|
||||||
for (unsigned int i = 0; i < this->installers.size(); ++i)
|
for (unsigned int i = 0; i < this->installers.size(); ++i)
|
||||||
{
|
{
|
||||||
@ -102,6 +103,8 @@ void gameDetails::makeFilepaths(const Config& config)
|
|||||||
|
|
||||||
for (unsigned int i = 0; i < this->dlcs.size(); ++i)
|
for (unsigned int i = 0; i < this->dlcs.size(); ++i)
|
||||||
{
|
{
|
||||||
|
subdir = config.bSubDirectories ? config.sDLCSubdir + "/" + config.sInstallersSubdir : "";
|
||||||
|
this->dlcs[i].serialsFilepath = Util::makeFilepath(directory, "serials.txt", this->gamename, subdir, 0);
|
||||||
for (unsigned int j = 0; j < this->dlcs[i].installers.size(); ++j)
|
for (unsigned int j = 0; j < this->dlcs[i].installers.size(); ++j)
|
||||||
{
|
{
|
||||||
subdir = config.bSubDirectories ? config.sDLCSubdir + "/" + config.sInstallersSubdir : "";
|
subdir = config.bSubDirectories ? config.sDLCSubdir + "/" + config.sInstallersSubdir : "";
|
||||||
@ -132,6 +135,7 @@ Json::Value gameDetails::getDetailsAsJson()
|
|||||||
json["gamename"] = this->gamename;
|
json["gamename"] = this->gamename;
|
||||||
json["title"] = this->title;
|
json["title"] = this->title;
|
||||||
json["icon"] = this->icon;
|
json["icon"] = this->icon;
|
||||||
|
json["serials"] = this->serials;
|
||||||
|
|
||||||
for (unsigned int i = 0; i < this->extras.size(); ++i)
|
for (unsigned int i = 0; i < this->extras.size(); ++i)
|
||||||
json["extras"].append(this->extras[i].getAsJson());
|
json["extras"].append(this->extras[i].getAsJson());
|
||||||
@ -152,3 +156,8 @@ Json::Value gameDetails::getDetailsAsJson()
|
|||||||
|
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string gameDetails::getSerialsFilepath()
|
||||||
|
{
|
||||||
|
return this->serialsFilepath;
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user