[lgogdownloader] 01/05: Imported Upstream version 2.28
Stephen Kitt
skitt at moszumanska.debian.org
Mon May 9 21:36:23 UTC 2016
This is an automated email from the git hooks/post-receive script.
skitt pushed a commit to branch master
in repository lgogdownloader.
commit c3aa4c0c5f9059d025a2c844c747f26c2fb1991c
Author: Stephen Kitt <steve at sk2.org>
Date: Mon May 9 23:27:42 2016 +0200
Imported Upstream version 2.28
---
CMakeLists.txt | 5 +-
include/downloader.h | 17 +-
include/gamedetails.h | 6 +
include/gamefile.h | 15 +-
include/globalconstants.h | 2 +
include/progressbar.h | 1 +
include/util.h | 24 ++
include/website.h | 39 +++
src/api.cpp | 107 ++++---
src/downloader.cpp | 722 ++++++----------------------------------------
src/gamedetails.cpp | 6 +
src/gamefile.cpp | 25 +-
src/progressbar.cpp | 29 +-
src/util.cpp | 4 +-
src/website.cpp | 708 +++++++++++++++++++++++++++++++++++++++++++++
15 files changed, 989 insertions(+), 721 deletions(-)
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 743f955..1670254 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.0.0 FATAL_ERROR)
-project (lgogdownloader LANGUAGES CXX VERSION 2.27)
+project (lgogdownloader LANGUAGES CXX VERSION 2.28)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUG=1")
@@ -12,7 +12,7 @@ find_package(Boost
program_options
date_time
)
-find_package(CURL REQUIRED)
+find_package(CURL 7.32.0 REQUIRED)
find_package(OAuth REQUIRED)
find_package(Jsoncpp REQUIRED)
find_package(Htmlcxx REQUIRED)
@@ -22,6 +22,7 @@ find_package(Rhash REQUIRED)
file(GLOB SRC_FILES
main.cpp
src/api.cpp
+ src/website.cpp
src/downloader.cpp
src/progressbar.cpp
src/util.cpp
diff --git a/include/downloader.h b/include/downloader.h
index b52153c..a85a1f8 100644
--- a/include/downloader.h
+++ b/include/downloader.h
@@ -24,6 +24,7 @@
#include "config.h"
#include "api.h"
#include "progressbar.h"
+#include "website.h"
#include <curl/curl.h>
#include <json/json.h>
#include <ctime>
@@ -47,14 +48,6 @@ class Timer
struct timeval last_update;
};
-class gameItem {
- public:
- std::string name;
- std::string id;
- std::vector<std::string> dlcnames;
- Json::Value gamedetailsjson;
-};
-
class Downloader
{
public:
@@ -91,22 +84,18 @@ class Downloader
int loadGameDetailsCache();
int saveGameDetailsCache();
std::vector<gameDetails> getGameDetailsFromJsonNode(Json::Value root, const int& recursion_level = 0);
- int HTTP_Login(const std::string& email, const std::string& password);
- std::vector<gameItem> getGames();
- std::vector<gameItem> getFreeGames();
std::vector<gameFile> getExtrasFromJSON(const Json::Value& json, const std::string& gamename);
- Json::Value getGameDetailsJSON(const std::string& gameid);
std::string getSerialsFromJSON(const Json::Value& json);
void saveSerials(const std::string& serials, const std::string& filepath);
std::string getChangelogFromJSON(const Json::Value& json);
void saveChangelog(const std::string& changelog, const std::string& filepath);
- static int progressCallback(void *clientp, double dltotal, double dlnow, double ultotal, double ulnow);
+ static int progressCallback(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow);
static size_t writeMemoryCallback(char *ptr, size_t size, size_t nmemb, void *userp);
static size_t writeData(void *ptr, size_t size, size_t nmemb, FILE *stream);
static size_t readData(void *ptr, size_t size, size_t nmemb, FILE *stream);
-
+ Website *gogWebsite;
API *gogAPI;
std::vector<gameItem> gameItems;
std::vector<gameDetails> games;
diff --git a/include/gamedetails.h b/include/gamedetails.h
index 1737fff..abda193 100644
--- a/include/gamedetails.h
+++ b/include/gamedetails.h
@@ -1,3 +1,9 @@
+/* This program is free software. It comes without any warranty, to
+ * the extent permitted by applicable law. You can redistribute it
+ * and/or modify it under the terms of the Do What The Fuck You Want
+ * To Public License, Version 2, as published by Sam Hocevar. See
+ * http://www.wtfpl.net/ for more details. */
+
#ifndef GAMEDETAILS_H
#define GAMEDETAILS_H
diff --git a/include/gamefile.h b/include/gamefile.h
index 2c32100..80a37ef 100644
--- a/include/gamefile.h
+++ b/include/gamefile.h
@@ -1,3 +1,9 @@
+/* This program is free software. It comes without any warranty, to
+ * the extent permitted by applicable law. You can redistribute it
+ * and/or modify it under the terms of the Do What The Fuck You Want
+ * To Public License, Version 2, as published by Sam Hocevar. See
+ * http://www.wtfpl.net/ for more details. */
+
#ifndef GAMEFILE_H
#define GAMEFILE_H
@@ -7,18 +13,25 @@
#include <vector>
#include <json/json.h>
+// Game file types
+const unsigned int GFTYPE_INSTALLER = 1 << 0;
+const unsigned int GFTYPE_EXTRA = 1 << 1;
+const unsigned int GFTYPE_PATCH = 1 << 2;
+const unsigned int GFTYPE_LANGPACK = 1 << 3;
+
class gameFile
{
public:
gameFile();
- gameFile(const int& t_updated, const std::string& t_id, const std::string& t_name, const std::string& t_path, const std::string& t_size, const unsigned int& t_language = GlobalConstants::LANGUAGE_EN, const unsigned int& t_platform = GlobalConstants::PLATFORM_WINDOWS, const int& t_silent = 0);
int updated;
+ std::string gamename;
std::string id;
std::string name;
std::string path;
std::string size;
unsigned int platform;
unsigned int language;
+ unsigned int type;
int score;
int silent;
void setFilepath(const std::string& path);
diff --git a/include/globalconstants.h b/include/globalconstants.h
index 2ae0f96..935ac2f 100644
--- a/include/globalconstants.h
+++ b/include/globalconstants.h
@@ -12,6 +12,8 @@
namespace GlobalConstants
{
+ const int GAMEDETAILS_CACHE_VERSION = 1;
+
struct optionsStruct {const unsigned int id; const std::string code; const std::string str; const std::string regexp;};
const std::string PROTOCOL_PREFIX = "gogdownloader://";
diff --git a/include/progressbar.h b/include/progressbar.h
index c95230f..4d2f88d 100644
--- a/include/progressbar.h
+++ b/include/progressbar.h
@@ -16,6 +16,7 @@ class ProgressBar
ProgressBar(bool bUnicode, bool bColor);
virtual ~ProgressBar();
void draw(unsigned int length, double fraction);
+ std::string createBarString(unsigned int length, double fraction);
protected:
private:
std::vector<std::string> const m_bar_chars;
diff --git a/include/util.h b/include/util.h
index 8f77162..7135a5a 100644
--- a/include/util.h
+++ b/include/util.h
@@ -43,6 +43,30 @@ struct gameSpecificConfig
std::vector<unsigned int> vPlatformPriority;
};
+struct gameItem
+{
+ std::string name;
+ std::string id;
+ std::vector<std::string> dlcnames;
+ Json::Value gamedetailsjson;
+};
+
+struct wishlistItem
+{
+ std::string title;
+ unsigned int platform;
+ std::vector<std::string> tags;
+ time_t release_date_time;
+ std::string currency;
+ std::string price;
+ std::string discount_percent;
+ std::string discount;
+ std::string store_credit;
+ std::string url;
+ bool bIsBonusStoreCreditIncluded;
+ bool bIsDiscounted;
+};
+
namespace Util
{
std::string makeFilepath(const std::string& directory, const std::string& path, const std::string& gamename, std::string subdirectory = "", const unsigned int& platformId = 0, const std::string& dlcname = "");
diff --git a/include/website.h b/include/website.h
new file mode 100644
index 0000000..0664784
--- /dev/null
+++ b/include/website.h
@@ -0,0 +1,39 @@
+/* This program is free software. It comes without any warranty, to
+ * the extent permitted by applicable law. You can redistribute it
+ * and/or modify it under the terms of the Do What The Fuck You Want
+ * To Public License, Version 2, as published by Sam Hocevar. See
+ * http://www.wtfpl.net/ for more details. */
+
+#ifndef WEBSITE_H
+#define WEBSITE_H
+
+#include "config.h"
+#include "util.h"
+#include <curl/curl.h>
+#include <json/json.h>
+#include <fstream>
+
+class Website
+{
+ public:
+ Website(Config &conf);
+ int Login(const std::string& email, const std::string& password);
+ std::string getResponse(const std::string& url);
+ Json::Value getGameDetailsJSON(const std::string& gameid);
+ std::vector<gameItem> getGames();
+ std::vector<gameItem> getFreeGames();
+ std::vector<wishlistItem> getWishlistItems();
+ bool IsLoggedIn();
+ void setConfig(Config &conf);
+ virtual ~Website();
+ protected:
+ private:
+ static size_t writeMemoryCallback(char *ptr, size_t size, size_t nmemb, void *userp);
+ CURL* curlhandle;
+ Config config;
+ bool IsloggedInSimple();
+ bool IsLoggedInComplex(const std::string& email);
+ int retries;
+};
+
+#endif // WEBSITE_H
diff --git a/src/api.cpp b/src/api.cpp
index 13ca1ba..0ada213 100644
--- a/src/api.cpp
+++ b/src/api.cpp
@@ -349,17 +349,19 @@ gameDetails API::getGameDetails(const std::string& game_name, const unsigned int
continue;
}
- game.installers.push_back(
- gameFile( installer["notificated"].isInt() ? installer["notificated"].asInt() : std::stoi(installer["notificated"].asString()),
- installer["id"].isInt() ? std::to_string(installer["id"].asInt()) : installer["id"].asString(),
- installer["name"].asString(),
- installer["link"].asString(),
- installer["size"].asString(),
- language,
- installers[i].platform,
- installer["silent"].isInt() ? installer["silent"].asInt() : std::stoi(installer["silent"].asString())
- )
- );
+ gameFile gf;
+ gf.type = GFTYPE_INSTALLER;
+ gf.gamename = game.gamename;
+ gf.updated = installer["notificated"].isInt() ? installer["notificated"].asInt() : std::stoi(installer["notificated"].asString());
+ gf.id = installer["id"].isInt() ? std::to_string(installer["id"].asInt()) : installer["id"].asString();
+ gf.name = installer["name"].asString();
+ gf.path = installer["link"].asString();
+ gf.size = installer["size"].asString();
+ gf.language = language;
+ gf.platform = installers[i].platform;
+ gf.silent = installer["silent"].isInt() ? installer["silent"].asInt() : std::stoi(installer["silent"].asString());
+
+ game.installers.push_back(gf);
}
}
@@ -369,14 +371,16 @@ gameDetails API::getGameDetails(const std::string& game_name, const unsigned int
{
Json::Value extra = extras[index];
- game.extras.push_back(
- gameFile( false, /* extras don't have "updated" flag */
- extra["id"].isInt() ? std::to_string(extra["id"].asInt()) : extra["id"].asString(),
- extra["name"].asString(),
- extra["link"].asString(),
- extra["size_mb"].asString()
- )
- );
+ gameFile gf;
+ gf.type = GFTYPE_EXTRA;
+ gf.gamename = game.gamename;
+ gf.updated = false; // extras don't have "updated" flag
+ gf.id = extra["id"].isInt() ? std::to_string(extra["id"].asInt()) : extra["id"].asString();
+ gf.name = extra["name"].asString();
+ gf.path = extra["link"].asString();
+ gf.size = extra["size_mb"].asString();
+
+ game.extras.push_back(gf);
}
// Patch details
@@ -434,16 +438,18 @@ gameDetails API::getGameDetails(const std::string& game_name, const unsigned int
continue;
}
- game.patches.push_back(
- gameFile( patch["notificated"].isInt() ? patch["notificated"].asInt() : std::stoi(patch["notificated"].asString()),
- patch["id"].isInt() ? std::to_string(patch["id"].asInt()) : patch["id"].asString(),
- patch["name"].asString(),
- patch["link"].asString(),
- patch["size"].asString(),
- GlobalConstants::LANGUAGES[i].id,
- patches[j].platform
- )
- );
+ gameFile gf;
+ gf.type = GFTYPE_PATCH;
+ gf.gamename = game.gamename;
+ gf.updated = patch["notificated"].isInt() ? patch["notificated"].asInt() : std::stoi(patch["notificated"].asString());
+ gf.id = patch["id"].isInt() ? std::to_string(patch["id"].asInt()) : patch["id"].asString();
+ gf.name = patch["name"].asString();
+ gf.path = patch["link"].asString();
+ gf.size = patch["size"].asString();
+ gf.language = GlobalConstants::LANGUAGES[i].id;
+ gf.platform = patches[j].platform;
+
+ game.patches.push_back(gf);
}
}
else // Patch is a single file
@@ -465,16 +471,18 @@ gameDetails API::getGameDetails(const std::string& game_name, const unsigned int
continue;
}
- game.patches.push_back(
- gameFile( patchnode["notificated"].isInt() ? patchnode["notificated"].asInt() : std::stoi(patchnode["notificated"].asString()),
- patchnode["id"].isInt() ? std::to_string(patchnode["id"].asInt()) : patchnode["id"].asString(),
- patchnode["name"].asString(),
- patchnode["link"].asString(),
- patchnode["size"].asString(),
- GlobalConstants::LANGUAGES[i].id,
- patches[j].platform
- )
- );
+ gameFile gf;
+ gf.type = GFTYPE_PATCH;
+ gf.gamename = game.gamename;
+ gf.updated = patchnode["notificated"].isInt() ? patchnode["notificated"].asInt() : std::stoi(patchnode["notificated"].asString());
+ gf.id = patchnode["id"].isInt() ? std::to_string(patchnode["id"].asInt()) : patchnode["id"].asString();
+ gf.name = patchnode["name"].asString();
+ gf.path = patchnode["link"].asString();
+ gf.size = patchnode["size"].asString();
+ gf.language = GlobalConstants::LANGUAGES[i].id;
+ gf.platform = patches[j].platform;
+
+ game.patches.push_back(gf);
}
}
}
@@ -500,15 +508,18 @@ gameDetails API::getGameDetails(const std::string& game_name, const unsigned int
for (unsigned int j = 0; j < langpacknames.size(); ++j)
{
Json::Value langpack = root["game"][langpacknames[j]];
- game.languagepacks.push_back(
- gameFile( false, /* language packs don't have "updated" flag */
- langpack["id"].isInt() ? std::to_string(langpack["id"].asInt()) : langpack["id"].asString(),
- langpack["name"].asString(),
- langpack["link"].asString(),
- langpack["size"].asString(),
- GlobalConstants::LANGUAGES[i].id
- )
- );
+
+ gameFile gf;
+ gf.type = GFTYPE_LANGPACK;
+ gf.gamename = game.gamename;
+ gf.updated = false; // language packs don't have "updated" flag
+ gf.id = langpack["id"].isInt() ? std::to_string(langpack["id"].asInt()) : langpack["id"].asString();
+ gf.name = langpack["name"].asString();
+ gf.path = langpack["link"].asString();
+ gf.size = langpack["size"].asString();
+ gf.language = GlobalConstants::LANGUAGES[i].id;
+
+ game.languagepacks.push_back(gf);
}
}
}
diff --git a/src/downloader.cpp b/src/downloader.cpp
index 650da80..ccb43d1 100644
--- a/src/downloader.cpp
+++ b/src/downloader.cpp
@@ -23,7 +23,6 @@
#include <json/json.h>
#include <htmlcxx/html/ParserDom.h>
#include <htmlcxx/html/Uri.h>
-#include <boost/algorithm/string/case_conv.hpp>
namespace bptime = boost::posix_time;
@@ -42,6 +41,7 @@ Downloader::~Downloader()
this->report_ofs.close();
delete progressbar;
delete gogAPI;
+ delete gogWebsite;
curl_easy_cleanup(curlhandle);
curl_global_cleanup();
// Make sure that cookie file is only readable/writable by owner
@@ -65,21 +65,23 @@ int Downloader::init()
curl_easy_setopt(curlhandle, CURLOPT_USERAGENT, config.sVersionString.c_str());
curl_easy_setopt(curlhandle, CURLOPT_NOPROGRESS, 0);
curl_easy_setopt(curlhandle, CURLOPT_CONNECTTIMEOUT, config.iTimeout);
- curl_easy_setopt(curlhandle, CURLOPT_PROGRESSDATA, this);
curl_easy_setopt(curlhandle, CURLOPT_FAILONERROR, true);
- curl_easy_setopt(curlhandle, CURLOPT_COOKIEFILE, config.sCookiePath.c_str());
- curl_easy_setopt(curlhandle, CURLOPT_COOKIEJAR, config.sCookiePath.c_str());
curl_easy_setopt(curlhandle, CURLOPT_SSL_VERIFYPEER, config.bVerifyPeer);
curl_easy_setopt(curlhandle, CURLOPT_VERBOSE, config.bVerbose);
curl_easy_setopt(curlhandle, CURLOPT_WRITEFUNCTION, Downloader::writeData);
curl_easy_setopt(curlhandle, CURLOPT_READFUNCTION, Downloader::readData);
- curl_easy_setopt(curlhandle, CURLOPT_PROGRESSFUNCTION, Downloader::progressCallback);
curl_easy_setopt(curlhandle, CURLOPT_MAX_RECV_SPEED_LARGE, config.iDownloadRate);
+ curl_easy_setopt(curlhandle, CURLOPT_XFERINFOFUNCTION, Downloader::progressCallback);
+ curl_easy_setopt(curlhandle, CURLOPT_XFERINFODATA, this);
// Assume that we have connection error and abort transfer with CURLE_OPERATION_TIMEDOUT if download speed is less than 200 B/s for 30 seconds
curl_easy_setopt(curlhandle, CURLOPT_LOW_SPEED_TIME, 30);
curl_easy_setopt(curlhandle, CURLOPT_LOW_SPEED_LIMIT, 200);
+ // Create new GOG website handle
+ gogWebsite = new Website(config);
+ bool bWebsiteIsLoggedIn = gogWebsite->IsLoggedIn();
+
// Create new API handle and set curl options for the API
gogAPI = new API(config.sToken, config.sSecret);
gogAPI->curlSetOpt(CURLOPT_VERBOSE, config.bVerbose);
@@ -89,7 +91,7 @@ int Downloader::init()
progressbar = new ProgressBar(config.bUnicode, config.bColor);
bool bInitOK = gogAPI->init(); // Initialize the API
- if (!bInitOK || config.bLoginHTTP || config.bLoginAPI)
+ if (!bInitOK || !bWebsiteIsLoggedIn || config.bLoginHTTP || config.bLoginAPI)
return 1;
if (config.bCover && config.bDownload && !config.bUpdateCheck)
@@ -139,7 +141,7 @@ int Downloader::login()
// Login to website
if (config.bLoginHTTP)
{
- if (!HTTP_Login(email, password))
+ if (!gogWebsite->Login(email, password))
{
std::cerr << "HTTP: Login failed" << std::endl;
return 0;
@@ -197,11 +199,11 @@ void Downloader::getGameList()
{
if (config.sGameRegex == "free")
{
- gameItems = this->getFreeGames();
+ gameItems = gogWebsite->getFreeGames();
}
else
{
- gameItems = this->getGames();
+ gameItems = gogWebsite->getGames();
}
}
@@ -254,6 +256,11 @@ int Downloader::getGameDetails()
std::cerr << "Cache is too old." << std::endl;
std::cerr << "Update cache with --update-cache or use bigger --cache-valid" << std::endl;
}
+ else if (result == 5)
+ {
+ std::cerr << "Cache version doesn't match current version." << std::endl;
+ std::cerr << "Update cache with --update-cache" << std::endl;
+ }
return 1;
}
}
@@ -321,19 +328,19 @@ int Downloader::getGameDetails()
if (game.extras.empty() && config.bExtras) // Try to get extras from account page if API didn't return any extras
{
if (gameDetailsJSON.empty())
- gameDetailsJSON = this->getGameDetailsJSON(gameItems[i].id);
+ gameDetailsJSON = gogWebsite->getGameDetailsJSON(gameItems[i].id);
game.extras = this->getExtrasFromJSON(gameDetailsJSON, gameItems[i].name);
}
if (config.bSaveSerials)
{
if (gameDetailsJSON.empty())
- gameDetailsJSON = this->getGameDetailsJSON(gameItems[i].id);
+ gameDetailsJSON = gogWebsite->getGameDetailsJSON(gameItems[i].id);
game.serials = this->getSerialsFromJSON(gameDetailsJSON);
}
if (config.bSaveChangelogs)
{
if (gameDetailsJSON.empty())
- gameDetailsJSON = this->getGameDetailsJSON(gameItems[i].id);
+ gameDetailsJSON = gogWebsite->getGameDetailsJSON(gameItems[i].id);
game.changelog = this->getChangelogFromJSON(gameDetailsJSON);
}
@@ -341,7 +348,7 @@ int Downloader::getGameDetails()
if (game.dlcs.empty() && !bHasDLC && conf.bDLC && conf.bIgnoreDLCCount)
{
if (gameDetailsJSON.empty())
- gameDetailsJSON = this->getGameDetailsJSON(gameItems[i].id);
+ gameDetailsJSON = gogWebsite->getGameDetailsJSON(gameItems[i].id);
gameItems[i].dlcnames = Util::getDLCNamesFromJSON(gameDetailsJSON["dlcs"]);
bHasDLC = !gameItems[i].dlcnames.empty();
@@ -357,7 +364,7 @@ int Downloader::getGameDetails()
if (dlc.extras.empty() && config.bExtras) // Try to get extras from account page if API didn't return any extras
{
if (gameDetailsJSON.empty())
- gameDetailsJSON = this->getGameDetailsJSON(gameItems[i].id);
+ gameDetailsJSON = gogWebsite->getGameDetailsJSON(gameItems[i].id);
// Make sure we get extras for the right DLC
for (unsigned int k = 0; k < gameDetailsJSON["dlcs"].size(); ++k)
@@ -379,7 +386,7 @@ int Downloader::getGameDetails()
if (config.bSaveSerials)
{
if (gameDetailsJSON.empty())
- gameDetailsJSON = this->getGameDetailsJSON(gameItems[i].id);
+ gameDetailsJSON = gogWebsite->getGameDetailsJSON(gameItems[i].id);
// Make sure we save serial for the right DLC
for (unsigned int k = 0; k < gameDetailsJSON["dlcs"].size(); ++k)
@@ -404,7 +411,7 @@ int Downloader::getGameDetails()
if (config.bSaveChangelogs)
{
if (gameDetailsJSON.empty())
- gameDetailsJSON = this->getGameDetailsJSON(gameItems[i].id);
+ gameDetailsJSON = gogWebsite->getGameDetailsJSON(gameItems[i].id);
// Make sure we save changelog for the right DLC
for (unsigned int k = 0; k < gameDetailsJSON["dlcs"].size(); ++k)
@@ -1862,7 +1869,7 @@ std::string Downloader::getResponse(const std::string& url)
return response;
}
-int Downloader::progressCallback(void *clientp, double dltotal, double dlnow, double ultotal, double ulnow)
+int Downloader::progressCallback(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow)
{
// on entry: dltotal - how much remains to download till the end of the file (bytes)
// dlnow - how much was downloaded from the start of the program (bytes)
@@ -1879,7 +1886,7 @@ int Downloader::progressCallback(void *clientp, double dltotal, double dlnow, do
// and there is no way to calculate the fraction, so we set to 0 (otherwise it'd be 1).
// This is to prevent the progress bar from jumping to 100% and then to lower value.
// It's visually better to jump from 0% to higher one.
- bool starting = ((0.0 == dlnow) && (0.0 == dltotal));
+ bool starting = ((0 == dlnow) && (0 == dltotal));
// (Shmerl): DEBUG: strange thing - when resuming a file which is already downloaded, dlnow is correctly 0.0
// but dltotal is 389.0! This messes things up in the progress bar not showing the very last bar as full.
@@ -1889,10 +1896,10 @@ int Downloader::progressCallback(void *clientp, double dltotal, double dlnow, do
//
// For now making a quirky workaround and setting dltotal to 0.0 in that case.
// It's probably better to find a real fix.
- if ((0.0 == dlnow) && (389.0 == dltotal)) dltotal = 0.0;
+ if ((0 == dlnow) && (389 == dltotal)) dltotal = 0;
// setting full dlwnow and dltotal
- double offset = static_cast<double>(downloader->getResumePosition());
+ curl_off_t offset = static_cast<curl_off_t>(downloader->getResumePosition());
if (offset>0)
{
dlnow += offset;
@@ -1944,7 +1951,7 @@ int Downloader::progressCallback(void *clientp, double dltotal, double dlnow, do
}
// Create progressbar
- double fraction = starting ? 0.0 : dlnow / dltotal;
+ double fraction = starting ? 0.0 : static_cast<double>(dlnow) / static_cast<double>(dltotal);
// assuming that config is provided.
printf("\033[0K\r%3.0f%% ", fraction * 100);
@@ -1962,7 +1969,7 @@ int Downloader::progressCallback(void *clientp, double dltotal, double dlnow, do
rate_unit = "kB/s";
}
char status_text[200]; // We're probably never going to go as high as 200 characters but it's better to use too big number here than too small
- sprintf(status_text, " %0.2f/%0.2fMB @ %0.2f%s ETA: %s\r", dlnow/1024/1024, dltotal/1024/1024, rate, rate_unit.c_str(), eta_ss.str().c_str());
+ sprintf(status_text, " %0.2f/%0.2fMB @ %0.2f%s ETA: %s\r", static_cast<double>(dlnow)/1024/1024, static_cast<double>(dltotal)/1024/1024, rate, rate_unit.c_str(), eta_ss.str().c_str());
int status_text_length = strlen(status_text) + 6;
if ((status_text_length + bar_length) > iTermWidth)
@@ -2000,468 +2007,6 @@ uintmax_t Downloader::getResumePosition()
return this->resume_position;
}
-// Login to GOG website
-int Downloader::HTTP_Login(const std::string& email, const std::string& password)
-{
- int res = 0;
- std::string postdata;
- std::ostringstream memory;
- std::string token;
- std::string tagname_username;
- std::string tagname_password;
- std::string tagname_login;
- std::string tagname_token;
-
- // Get login token
- std::string html = this->getResponse("https://www.gog.com/");
- 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();
- // Find auth_url
- bool bFoundAuthUrl = false;
- for (; it != end; ++it)
- {
- if (it->tagName()=="script")
- {
- std::string auth_url;
- for (unsigned int i = 0; i < dom.number_of_children(it); ++i)
- {
- tree<htmlcxx::HTML::Node>::iterator script_it = dom.child(it, i);
- if (!script_it->isTag() && !script_it->isComment())
- {
- if (script_it->text().find("GalaxyAccounts") != std::string::npos)
- {
- boost::match_results<std::string::const_iterator> what;
- boost::regex expression(".*'(https://auth.gog.com/.*?)'.*");
- boost::regex_match(script_it->text(), what, expression);
- auth_url = what[1];
- break;
- }
- }
- }
-
- if (!auth_url.empty())
- { // Found auth_url, get the necessary info for login
- bFoundAuthUrl = true;
- std::string login_form_html = this->getResponse(auth_url);
- #ifdef DEBUG
- std::cerr << "DEBUG INFO (Downloader::HTTP_Login)" << std::endl;
- std::cerr << login_form_html << std::endl;
- #endif
- if (login_form_html.find("google.com/recaptcha") != std::string::npos)
- {
- std::cout << "Login form contains reCAPTCHA (https://www.google.com/recaptcha/)" << std::endl
- << "Login with browser and export cookies to \"" << config.sCookiePath << "\"" << std::endl;
- return res = 0;
- }
-
- tree<htmlcxx::HTML::Node> login_dom = parser.parseTree(login_form_html);
- tree<htmlcxx::HTML::Node>::iterator login_it = login_dom.begin();
- tree<htmlcxx::HTML::Node>::iterator login_it_end = login_dom.end();
- for (; login_it != login_it_end; ++login_it)
- {
- if (login_it->tagName()=="input")
- {
- login_it->parseAttributes();
- std::string id_login = login_it->attribute("id").second;
- if (id_login == "login_username")
- {
- tagname_username = login_it->attribute("name").second;
- }
- else if (id_login == "login_password")
- {
- tagname_password = login_it->attribute("name").second;
- }
- else if (id_login == "login__token")
- {
- token = login_it->attribute("value").second; // login token
- tagname_token = login_it->attribute("name").second;
- }
- }
- else if (login_it->tagName()=="button")
- {
- login_it->parseAttributes();
- std::string id_login = login_it->attribute("id").second;
- if (id_login == "login_login")
- {
- tagname_login = login_it->attribute("name").second;
- }
- }
- }
- break;
- }
- }
- }
-
- if (!bFoundAuthUrl)
- {
- std::cout << "Failed to find url for login form" << std::endl;
- }
-
- if (token.empty())
- {
- std::cout << "Failed to get login token" << std::endl;
- return res = 0;
- }
-
- //Create postdata - escape characters in email/password to support special characters
- postdata = (std::string)curl_easy_escape(curlhandle, tagname_username.c_str(), tagname_username.size()) + "=" + (std::string)curl_easy_escape(curlhandle, email.c_str(), email.size())
- + "&" + (std::string)curl_easy_escape(curlhandle, tagname_password.c_str(), tagname_password.size()) + "=" + (std::string)curl_easy_escape(curlhandle, password.c_str(), password.size())
- + "&" + (std::string)curl_easy_escape(curlhandle, tagname_login.c_str(), tagname_login.size()) + "="
- + "&" + (std::string)curl_easy_escape(curlhandle, tagname_token.c_str(), tagname_token.size()) + "=" + (std::string)curl_easy_escape(curlhandle, token.c_str(), token.size());
- curl_easy_setopt(curlhandle, CURLOPT_URL, "https://login.gog.com/login_check");
- curl_easy_setopt(curlhandle, CURLOPT_POST, 1);
- curl_easy_setopt(curlhandle, CURLOPT_POSTFIELDS, postdata.c_str());
- curl_easy_setopt(curlhandle, CURLOPT_WRITEFUNCTION, Downloader::writeMemoryCallback);
- curl_easy_setopt(curlhandle, CURLOPT_WRITEDATA, &memory);
- curl_easy_setopt(curlhandle, CURLOPT_NOPROGRESS, 1);
- curl_easy_setopt(curlhandle, CURLOPT_MAXREDIRS, 0);
- curl_easy_setopt(curlhandle, CURLOPT_POSTREDIR, CURL_REDIR_POST_ALL);
-
- // Don't follow to redirect location because it doesn't work properly. Must clean up the redirect url first.
- curl_easy_setopt(curlhandle, CURLOPT_FOLLOWLOCATION, 0);
- CURLcode result = curl_easy_perform(curlhandle);
- memory.str(std::string());
-
- if (result != CURLE_OK)
- {
- // Expected to hit maximum amount of redirects so don't print error on it
- if (result != CURLE_TOO_MANY_REDIRECTS)
- std::cout << curl_easy_strerror(result) << std::endl;
- }
-
- // Get redirect url
- char *redirect_url;
- curl_easy_getinfo(curlhandle, CURLINFO_REDIRECT_URL, &redirect_url);
-
- // Handle two step authorization
- if (std::string(redirect_url).find("two_step") != std::string::npos)
- {
- std::string security_code, tagname_two_step_send, tagname_two_step_auth_letter_1, tagname_two_step_auth_letter_2, tagname_two_step_auth_letter_3, tagname_two_step_auth_letter_4, tagname_two_step_token, token_two_step;
- std::string two_step_html = this->getResponse(redirect_url);
- redirect_url = NULL;
-
- tree<htmlcxx::HTML::Node> two_step_dom = parser.parseTree(two_step_html);
- tree<htmlcxx::HTML::Node>::iterator two_step_it = two_step_dom.begin();
- tree<htmlcxx::HTML::Node>::iterator two_step_it_end = two_step_dom.end();
- for (; two_step_it != two_step_it_end; ++two_step_it)
- {
- if (two_step_it->tagName()=="input")
- {
- two_step_it->parseAttributes();
- std::string id_two_step = two_step_it->attribute("id").second;
- if (id_two_step == "second_step_authentication_token_letter_1")
- {
- tagname_two_step_auth_letter_1 = two_step_it->attribute("name").second;
- }
- else if (id_two_step == "second_step_authentication_token_letter_2")
- {
- tagname_two_step_auth_letter_2 = two_step_it->attribute("name").second;
- }
- else if (id_two_step == "second_step_authentication_token_letter_3")
- {
- tagname_two_step_auth_letter_3 = two_step_it->attribute("name").second;
- }
- else if (id_two_step == "second_step_authentication_token_letter_4")
- {
- tagname_two_step_auth_letter_4 = two_step_it->attribute("name").second;
- }
- else if (id_two_step == "second_step_authentication__token")
- {
- token_two_step = two_step_it->attribute("value").second; // two step token
- tagname_two_step_token = two_step_it->attribute("name").second;
- }
- }
- else if (two_step_it->tagName()=="button")
- {
- two_step_it->parseAttributes();
- std::string id_two_step = two_step_it->attribute("id").second;
- if (id_two_step == "second_step_authentication_send")
- {
- tagname_two_step_send = two_step_it->attribute("name").second;
- }
- }
- }
- std::cerr << "Security code: ";
- std::getline(std::cin,security_code);
- if (security_code.size() != 4)
- {
- std::cerr << "Security code must be 4 characters long" << std::endl;
- exit(1);
- }
- postdata = (std::string)curl_easy_escape(curlhandle, tagname_two_step_auth_letter_1.c_str(), tagname_two_step_auth_letter_1.size()) + "=" + security_code[0]
- + "&" + (std::string)curl_easy_escape(curlhandle, tagname_two_step_auth_letter_2.c_str(), tagname_two_step_auth_letter_2.size()) + "=" + security_code[1]
- + "&" + (std::string)curl_easy_escape(curlhandle, tagname_two_step_auth_letter_3.c_str(), tagname_two_step_auth_letter_3.size()) + "=" + security_code[2]
- + "&" + (std::string)curl_easy_escape(curlhandle, tagname_two_step_auth_letter_4.c_str(), tagname_two_step_auth_letter_4.size()) + "=" + security_code[3]
- + "&" + (std::string)curl_easy_escape(curlhandle, tagname_two_step_send.c_str(), tagname_two_step_send.size()) + "="
- + "&" + (std::string)curl_easy_escape(curlhandle, tagname_two_step_token.c_str(), tagname_two_step_token.size()) + "=" + (std::string)curl_easy_escape(curlhandle, token_two_step.c_str(), token_two_step.size());
-
- curl_easy_setopt(curlhandle, CURLOPT_URL, "https://login.gog.com/login/two_step");
- curl_easy_setopt(curlhandle, CURLOPT_POST, 1);
- curl_easy_setopt(curlhandle, CURLOPT_POSTFIELDS, postdata.c_str());
- curl_easy_setopt(curlhandle, CURLOPT_WRITEFUNCTION, Downloader::writeMemoryCallback);
- curl_easy_setopt(curlhandle, CURLOPT_WRITEDATA, &memory);
- curl_easy_setopt(curlhandle, CURLOPT_NOPROGRESS, 1);
- curl_easy_setopt(curlhandle, CURLOPT_MAXREDIRS, 0);
- curl_easy_setopt(curlhandle, CURLOPT_POSTREDIR, CURL_REDIR_POST_ALL);
-
- // Don't follow to redirect location because it doesn't work properly. Must clean up the redirect url first.
- curl_easy_setopt(curlhandle, CURLOPT_FOLLOWLOCATION, 0);
- result = curl_easy_perform(curlhandle);
- memory.str(std::string());
- curl_easy_getinfo(curlhandle, CURLINFO_REDIRECT_URL, &redirect_url);
- }
-
- curl_easy_setopt(curlhandle, CURLOPT_URL, redirect_url);
- curl_easy_setopt(curlhandle, CURLOPT_HTTPGET, 1);
- curl_easy_setopt(curlhandle, CURLOPT_MAXREDIRS, -1);
- curl_easy_setopt(curlhandle, CURLOPT_FOLLOWLOCATION, 1);
- result = curl_easy_perform(curlhandle);
-
- if (result != CURLE_OK)
- {
- std::cout << curl_easy_strerror(result) << std::endl;
- }
-
- html = this->getResponse("https://www.gog.com/account/settings/personal");
-
- std::string email_lowercase = boost::algorithm::to_lower_copy(email); // boost::algorithm::to_lower does in-place modification but "email" is read-only so we need to make a copy of it
- dom = parser.parseTree(html);
- it = dom.begin();
- end = dom.end();
- for (; it != end; ++it)
- {
- if (it->tagName()=="strong")
- {
- it->parseAttributes();
- if (it->attribute("class").second == "settings-item__value settings-item__section")
- {
- for (unsigned int i = 0; i < dom.number_of_children(it); ++i)
- {
- tree<htmlcxx::HTML::Node>::iterator tag_it = dom.child(it, i);
- if (!tag_it->isTag() && !tag_it->isComment())
- {
- std::string tag_text = boost::algorithm::to_lower_copy(tag_it->text());
- if (tag_text == email_lowercase)
- {
- res = 1; // Login successful
- break;
- }
- }
- }
- }
- }
- if (res == 1) // Login was successful so no need to go through the remaining tags
- break;
- }
-
- // Simple login check if complex check failed. Check login by trying to get account page. If response code isn't 200 then login failed.
- if (res == 0)
- {
- std::string url = "https://www.gog.com/account";
- curl_easy_setopt(curlhandle, CURLOPT_URL, url.c_str());
- curl_easy_setopt(curlhandle, CURLOPT_FOLLOWLOCATION, 0);
- curl_easy_setopt(curlhandle, CURLOPT_WRITEFUNCTION, Downloader::writeMemoryCallback);
- curl_easy_setopt(curlhandle, CURLOPT_WRITEDATA, &memory);
- curl_easy_setopt(curlhandle, CURLOPT_NOPROGRESS, 1);
- curl_easy_perform(curlhandle);
- memory.str(std::string());
- long int response_code = 0;
- curl_easy_getinfo(curlhandle, CURLINFO_RESPONSE_CODE, &response_code);
- curl_easy_setopt(curlhandle, CURLOPT_FOLLOWLOCATION, 1);
- if (response_code == 200)
- res = 1; // Login successful
- }
-
- return res;
-}
-
-// Get list of games from account page
-std::vector<gameItem> Downloader::getGames()
-{
- std::vector<gameItem> games;
- Json::Value root;
- Json::Reader *jsonparser = new Json::Reader;
- int i = 1;
- bool bAllPagesParsed = false;
-
- do
- {
- std::string response = this->getResponse("https://www.gog.com/account/getFilteredProducts?hasHiddenProducts=false&hiddenFlag=0&isUpdated=0&mediaType=1&sortBy=title&system=&page=" + std::to_string(i));
-
- // Parse JSON
- if (!jsonparser->parse(response, root))
- {
- #ifdef DEBUG
- std::cerr << "DEBUG INFO (Downloader::getGames)" << std::endl << response << std::endl;
- #endif
- std::cout << jsonparser->getFormattedErrorMessages();
- delete jsonparser;
- if (!response.empty())
- {
- if(response[0] != '{')
- {
- // Response was not JSON. Assume that cookies have expired.
- std::cerr << "Response was not JSON. Cookies have most likely expired. Try --login first." << std::endl;
- }
- }
- exit(1);
- }
- #ifdef DEBUG
- std::cerr << "DEBUG INFO (Downloader::getGames)" << std::endl << root << std::endl;
- #endif
- if (root["page"].asInt() == root["totalPages"].asInt())
- bAllPagesParsed = true;
- if (root["products"].isArray())
- {
- for (unsigned int i = 0; i < root["products"].size(); ++i)
- {
- Json::Value product = root["products"][i];
- gameItem game;
- game.name = product["slug"].asString();
- game.id = product["id"].isInt() ? std::to_string(product["id"].asInt()) : product["id"].asString();
-
- unsigned int platform = 0;
- if (product["worksOn"]["Windows"].asBool())
- platform |= GlobalConstants::PLATFORM_WINDOWS;
- if (product["worksOn"]["Mac"].asBool())
- platform |= GlobalConstants::PLATFORM_MAC;
- if (product["worksOn"]["Linux"].asBool())
- platform |= GlobalConstants::PLATFORM_LINUX;
-
- // Skip if platform doesn't match
- if (config.bPlatformDetection && !(platform & config.iInstallerPlatform))
- continue;
-
- // Filter the game list
- if (!config.sGameRegex.empty())
- {
- // GameRegex filter aliases
- if (config.sGameRegex == "all")
- config.sGameRegex = ".*";
-
- boost::regex expression(config.sGameRegex);
- boost::match_results<std::string::const_iterator> what;
- if (!boost::regex_search(game.name, what, expression)) // Check if name matches the specified regex
- continue;
- }
-
- if (config.bDLC)
- {
- int dlcCount = product["dlcCount"].asInt();
-
- bool bDownloadDLCInfo = (dlcCount != 0);
-
- if (!bDownloadDLCInfo && !config.sIgnoreDLCCountRegex.empty())
- {
- boost::regex expression(config.sIgnoreDLCCountRegex);
- boost::match_results<std::string::const_iterator> what;
- if (boost::regex_search(game.name, what, expression)) // Check if name matches the specified regex
- {
- bDownloadDLCInfo = true;
- }
- }
-
- // Check game specific config
- if (!config.bUpdateCache) // Disable game specific config files for cache update
- {
- gameSpecificConfig conf;
- conf.bIgnoreDLCCount = false; // Assume false
- Util::getGameSpecificConfig(game.name, &conf);
- if (conf.bIgnoreDLCCount)
- bDownloadDLCInfo = true;
- }
-
- if (bDownloadDLCInfo && !config.sGameRegex.empty())
- {
- // don't download unnecessary info if user is only interested in a subset of his account
- boost::regex expression(config.sGameRegex);
- boost::match_results<std::string::const_iterator> what;
- if (!boost::regex_search(game.name, what, expression))
- {
- bDownloadDLCInfo = false;
- }
- }
-
- if (bDownloadDLCInfo)
- {
- game.gamedetailsjson = this->getGameDetailsJSON(game.id);
- if (!game.gamedetailsjson.empty())
- game.dlcnames = Util::getDLCNamesFromJSON(game.gamedetailsjson["dlcs"]);
- }
- }
- games.push_back(game);
- }
- }
- i++;
- } while (!bAllPagesParsed);
-
- delete jsonparser;
-
- return games;
-}
-
-// Get list of free games
-std::vector<gameItem> Downloader::getFreeGames()
-{
- Json::Value root;
- Json::Reader *jsonparser = new Json::Reader;
- std::vector<gameItem> games;
- std::string json = this->getResponse("https://www.gog.com/games/ajax/filtered?mediaType=game&page=1&price=free&sort=title");
-
- // Parse JSON
- if (!jsonparser->parse(json, root))
- {
- #ifdef DEBUG
- std::cerr << "DEBUG INFO (Downloader::getFreeGames)" << std::endl << json << std::endl;
- #endif
- std::cout << jsonparser->getFormattedErrorMessages();
- delete jsonparser;
- exit(1);
- }
- #ifdef DEBUG
- std::cerr << "DEBUG INFO (Downloader::getFreeGames)" << std::endl << root << std::endl;
- #endif
-
- Json::Value products = root["products"];
- for (unsigned int i = 0; i < products.size(); ++i)
- {
- gameItem game;
- game.name = products[i]["slug"].asString();
- game.id = products[i]["id"].isInt() ? std::to_string(products[i]["id"].asInt()) : products[i]["id"].asString();
- games.push_back(game);
- }
- delete jsonparser;
-
- return games;
-}
-
-Json::Value Downloader::getGameDetailsJSON(const std::string& gameid)
-{
- std::string gameDataUrl = "https://www.gog.com/account/gameDetails/" + gameid + ".json";
- std::string json = this->getResponse(gameDataUrl);
-
- // Parse JSON
- Json::Value root;
- Json::Reader *jsonparser = new Json::Reader;
- if (!jsonparser->parse(json, root))
- {
- #ifdef DEBUG
- std::cerr << "DEBUG INFO (Downloader::getGameDetailsJSON)" << std::endl << json << std::endl;
- #endif
- std::cout << jsonparser->getFormattedErrorMessages();
- delete jsonparser;
- exit(1);
- }
- #ifdef DEBUG
- std::cerr << "DEBUG INFO (Downloader::getGameDetailsJSON)" << std::endl << root << std::endl;
- #endif
- delete jsonparser;
-
- return root;
-}
-
std::vector<gameFile> Downloader::getExtrasFromJSON(const Json::Value& json, const std::string& gamename)
{
std::vector<gameFile> extras;
@@ -2516,14 +2061,15 @@ std::vector<gameFile> Downloader::getExtrasFromJSON(const Json::Value& json, con
continue;
}
- extras.push_back(
- gameFile ( false,
- id,
- name,
- path,
- std::string()
- )
- );
+ gameFile gf;
+ gf.type = GFTYPE_EXTRA;
+ gf.gamename = gamename;
+ gf.updated = false;
+ gf.id = id;
+ gf.name = name;
+ gf.path = path;
+
+ extras.push_back(gf);
}
return extras;
@@ -3069,6 +2615,7 @@ std::string Downloader::getRemoteFileHash(const std::string& gamename, const std
returns 2 if JSON parsing failed
returns 3 if cache is too old
returns 4 if JSON doesn't contain "games" node
+ returns 5 if cache version doesn't match
*/
int Downloader::loadGameDetailsCache()
{
@@ -3101,14 +2648,25 @@ int Downloader::loadGameDetailsCache()
}
}
- if (root.isMember("games"))
+ int iCacheVersion = 0;
+ if (root.isMember("gamedetails-cache-version"))
+ iCacheVersion = root["gamedetails-cache-version"].asInt();
+
+ if (iCacheVersion != GlobalConstants::GAMEDETAILS_CACHE_VERSION)
{
- this->games = getGameDetailsFromJsonNode(root["games"]);
- res = 0;
+ res = 5;
}
else
{
- res = 4;
+ if (root.isMember("games"))
+ {
+ this->games = getGameDetailsFromJsonNode(root["games"]);
+ res = 0;
+ }
+ else
+ {
+ res = 4;
+ }
}
}
else
@@ -3141,6 +2699,7 @@ int Downloader::saveGameDetailsCache()
Json::Value json;
+ json["gamedetails-cache-version"] = GlobalConstants::GAMEDETAILS_CACHE_VERSION;
json["version-string"] = config.sVersionString;
json["version-number"] = config.sVersionNumber;
json["date"] = bptime::to_iso_string(bptime::second_clock::local_time());
@@ -3224,6 +2783,8 @@ std::vector<gameDetails> Downloader::getGameDetailsFromJsonNode(Json::Value root
fileDetails.platform = fileDetailsNode["platform"].asUInt();
fileDetails.language = fileDetailsNode["language"].asUInt();
fileDetails.silent = fileDetailsNode["silent"].asInt();
+ fileDetails.gamename = fileDetailsNode["gamename"].asString();
+ fileDetails.type = fileDetailsNode["type"].asUInt();
if (nodeName != "extras" && !(fileDetails.platform & conf.iInstallerPlatform))
continue;
@@ -3270,6 +2831,7 @@ void Downloader::updateCache()
config.vLanguagePriority.clear();
config.vPlatformPriority.clear();
config.sIgnoreDLCCountRegex = ".*"; // Ignore DLC count for all games because GOG doesn't report DLC count correctly
+ gogWebsite->setConfig(config); // Make sure that website handle has updated config
this->getGameList();
this->getGameDetails();
@@ -3387,6 +2949,8 @@ void Downloader::downloadFileWithId(const std::string& fileid_string, const std:
url = gogAPI->getInstallerLink(gamename, fileid);
else if (fileid.find("patch") != std::string::npos)
url = gogAPI->getPatchLink(gamename, fileid);
+ else if (fileid.find("langpack") != std::string::npos)
+ url = gogAPI->getLanguagePackLink(gamename, fileid);
else
url = gogAPI->getExtraLink(gamename, fileid);
@@ -3414,141 +2978,37 @@ void Downloader::downloadFileWithId(const std::string& fileid_string, const std:
void Downloader::showWishlist()
{
- Json::Value root;
- Json::Reader *jsonparser = new Json::Reader;
- int i = 1;
- bool bAllPagesParsed = false;
-
- do
- {
- std::string response = this->getResponse("https://www.gog.com/account/wishlist/search?hasHiddenProducts=false&hiddenFlag=0&isUpdated=0&mediaType=0&sortBy=title&system=&page=" + std::to_string(i));
-
- // Parse JSON
- if (!jsonparser->parse(response, root))
- {
- #ifdef DEBUG
- std::cerr << "DEBUG INFO (Downloader::showWishlist)" << std::endl << response << std::endl;
- #endif
- std::cout << jsonparser->getFormattedErrorMessages();
- delete jsonparser;
- exit(1);
- }
- #ifdef DEBUG
- std::cerr << "DEBUG INFO (Downloader::showWishlist)" << std::endl << root << std::endl;
- #endif
- if (root["page"].asInt() >= root["totalPages"].asInt())
- bAllPagesParsed = true;
- if (root["products"].isArray())
- {
- for (unsigned int i = 0; i < root["products"].size(); ++i)
- {
- Json::Value product = root["products"][i];
-
- unsigned int platform = 0;
- std::string platforms_text;
- bool bIsMovie = product["isMovie"].asBool();
- if (!bIsMovie)
- {
- if (product["worksOn"]["Windows"].asBool())
- platform |= GlobalConstants::PLATFORM_WINDOWS;
- if (product["worksOn"]["Mac"].asBool())
- platform |= GlobalConstants::PLATFORM_MAC;
- if (product["worksOn"]["Linux"].asBool())
- platform |= GlobalConstants::PLATFORM_LINUX;
-
- // Skip if platform doesn't match
- if (config.bPlatformDetection && !(platform & config.iInstallerPlatform))
- continue;
-
- platforms_text = Util::getOptionNameString(platform, GlobalConstants::PLATFORMS);
- }
-
- std::vector<std::string> tags;
- if (product["isComingSoon"].asBool())
- tags.push_back("Coming soon");
- if (product["isDiscounted"].asBool())
- tags.push_back("Discount");
- if (bIsMovie)
- tags.push_back("Movie");
-
- std::string tags_text;
- for (unsigned int j = 0; j < tags.size(); ++j)
- {
- tags_text += (tags_text.empty() ? "" : ", ")+tags[j];
- }
- if (!tags_text.empty())
- tags_text = "[" + tags_text + "]";
-
- time_t release_date_time;
- std::string release_date;
- bool bShowReleaseDate = false;
- if (product.isMember("releaseDate") && product["isComingSoon"].asBool())
- {
- if (!product["releaseDate"].empty())
- {
- if (product["releaseDate"].isInt())
- {
- release_date_time = product["releaseDate"].asInt();
- bShowReleaseDate = true;
- }
- else
- {
- std::string release_date_time_string = product["releaseDate"].asString();
- if (!release_date_time_string.empty())
- {
- try
- {
- release_date_time = std::stoi(release_date_time_string);
- bShowReleaseDate = true;
- }
- catch (std::invalid_argument& e)
- {
- bShowReleaseDate = false;
- }
- }
- }
- }
-
- if (bShowReleaseDate)
- release_date = bptime::to_simple_string(bptime::from_time_t(release_date_time));
- }
-
- std::string price_text;
- std::string currency = product["price"]["symbol"].asString();
- std::string price = product["price"]["finalAmount"].isDouble() ? std::to_string(product["price"]["finalAmount"].asDouble()) + currency : product["price"]["finalAmount"].asString() + currency;
- std::string discount_percent = product["price"]["discountPercentage"].isInt() ? std::to_string(product["price"]["discountPercentage"].asInt()) + "%" : product["price"]["discountPercentage"].asString() + "%";
- std::string discount = product["price"]["discountDifference"].isDouble() ? std::to_string(product["price"]["discountDifference"].asDouble()) + currency : product["price"]["discountDifference"].asString() + currency;
- std::string store_credit = product["price"]["bonusStoreCreditAmount"].isDouble() ? std::to_string(product["price"]["bonusStoreCreditAmount"].asDouble()) + currency : product["price"]["bonusStoreCreditAmount"].asString() + currency;
- price_text = price;
- if (product["isDiscounted"].asBool())
- price_text += " (-" + discount_percent + " | -" + discount + ")";
-
- std::string url = product["url"].asString();
- if (url.find("/game/") == 0)
- url = "https://www.gog.com" + url;
- else if (url.find("/movie/") == 0)
- url = "https://www.gog.com" + url;
-
- std::cout << product["title"].asString();
- if (!tags_text.empty())
- std::cout << " " << tags_text;
- std::cout << std::endl;
- std::cout << "\t" << url << std::endl;
- if (!bIsMovie)
- std::cout << "\tPlatforms: " << platforms_text << std::endl;
- if (bShowReleaseDate)
- std::cout << "\tRelease date: " << release_date << std::endl;
- std::cout << "\tPrice: " << price_text << std::endl;
- if (product["price"]["isBonusStoreCreditIncluded"].asBool())
- std::cout << "\tStore credit: " << store_credit << std::endl;
-
- std::cout << std::endl;
- }
- }
- i++;
- } while (!bAllPagesParsed);
-
- delete jsonparser;
+ std::vector<wishlistItem> wishlistItems = gogWebsite->getWishlistItems();
+ for (unsigned int i = 0; i < wishlistItems.size(); ++i)
+ {
+ wishlistItem item = wishlistItems[i];
+ std::string platforms_text = Util::getOptionNameString(item.platform, GlobalConstants::PLATFORMS);
+ std::string tags_text;
+ for (unsigned int j = 0; j < item.tags.size(); ++j)
+ {
+ tags_text += (tags_text.empty() ? "" : ", ")+item.tags[j];
+ }
+ if (!tags_text.empty())
+ tags_text = "[" + tags_text + "]";
+
+ std::string price_text = item.price;
+ if (item.bIsDiscounted)
+ price_text += " (-" + item.discount_percent + " | -" + item.discount + ")";
+
+ std::cout << item.title;
+ if (!tags_text.empty())
+ std::cout << " " << tags_text;
+ std::cout << std::endl;
+ std::cout << "\t" << item.url << std::endl;
+ if (item.platform != 0)
+ std::cout << "\tPlatforms: " << platforms_text << std::endl;
+ if (item.release_date_time != 0)
+ std::cout << "\tRelease date: " << bptime::to_simple_string(bptime::from_time_t(item.release_date_time)) << std::endl;
+ std::cout << "\tPrice: " << price_text << std::endl;
+ if (item.bIsBonusStoreCreditIncluded)
+ std::cout << "\tStore credit: " << item.store_credit << std::endl;
+ std::cout << std::endl;
+ }
return;
}
diff --git a/src/gamedetails.cpp b/src/gamedetails.cpp
index 2ac9067..c3d6a5b 100644
--- a/src/gamedetails.cpp
+++ b/src/gamedetails.cpp
@@ -1,3 +1,9 @@
+/* This program is free software. It comes without any warranty, to
+ * the extent permitted by applicable law. You can redistribute it
+ * and/or modify it under the terms of the Do What The Fuck You Want
+ * To Public License, Version 2, as published by Sam Hocevar. See
+ * http://www.wtfpl.net/ for more details. */
+
#include "gamedetails.h"
gameDetails::gameDetails()
diff --git a/src/gamefile.cpp b/src/gamefile.cpp
index 86ef3c0..d78623e 100644
--- a/src/gamefile.cpp
+++ b/src/gamefile.cpp
@@ -1,20 +1,17 @@
-#include "gamefile.h"
+/* This program is free software. It comes without any warranty, to
+ * the extent permitted by applicable law. You can redistribute it
+ * and/or modify it under the terms of the Do What The Fuck You Want
+ * To Public License, Version 2, as published by Sam Hocevar. See
+ * http://www.wtfpl.net/ for more details. */
-gameFile::gameFile(const int& t_updated, const std::string& t_id, const std::string& t_name, const std::string& t_path, const std::string& t_size, const unsigned int& t_language, const unsigned int& t_platform, const int& t_silent)
-{
- this->updated = t_updated;
- this->id = t_id;
- this->name = t_name;
- this->path = t_path;
- this->size = t_size;
- this->platform = t_platform;
- this->language = t_language;
- this->silent = t_silent;
-}
+#include "gamefile.h"
gameFile::gameFile()
{
- //ctor
+ this->platform = GlobalConstants::PLATFORM_WINDOWS;
+ this->language = GlobalConstants::LANGUAGE_EN;
+ this->silent = 0;
+ this->type = 0;
}
gameFile::~gameFile()
@@ -44,6 +41,8 @@ Json::Value gameFile::getAsJson()
json["platform"] = this->platform;
json["language"] = this->language;
json["silent"] = this->silent;
+ json["gamename"] = this->gamename;
+ json["type"] = this->type;
return json;
}
diff --git a/src/progressbar.cpp b/src/progressbar.cpp
index 7f36b2b..1baf3e2 100644
--- a/src/progressbar.cpp
+++ b/src/progressbar.cpp
@@ -6,6 +6,7 @@
#include "progressbar.h"
#include <cmath>
+#include <sstream>
ProgressBar::ProgressBar(bool bUnicode, bool bColor)
:
@@ -46,6 +47,12 @@ ProgressBar::~ProgressBar()
void ProgressBar::draw(unsigned int length, double fraction)
{
+ std::cout << createBarString(length, fraction);
+}
+
+std::string ProgressBar::createBarString(unsigned int length, double fraction)
+{
+ std::ostringstream ss;
// validation
if (!std::isnormal(fraction) || (fraction < 0.0)) fraction = 0.0;
else if (fraction > 1.0) fraction = 1.0;
@@ -57,29 +64,31 @@ void ProgressBar::draw(unsigned int length, double fraction)
unsigned int partial_bar_char_index = (unsigned int) std::floor((bar_part - whole_bar_chars) * 8.0);
// left border
- if (m_use_color) std::cout << m_border_color;
- std::cout << (m_use_unicode ? m_left_border : m_simple_left_border);
+ if (m_use_color) ss << m_border_color;
+ ss << (m_use_unicode ? m_left_border : m_simple_left_border);
// whole completed bars
- if (m_use_color) std::cout << m_bar_color;
+ if (m_use_color) ss << m_bar_color;
unsigned int i = 0;
for (; i < whole_bar_chars_i; i++)
{
- std::cout << (m_use_unicode ? m_bar_chars[8] : m_simple_bar_char);
+ ss << (m_use_unicode ? m_bar_chars[8] : m_simple_bar_char);
}
// partial completed bar
- if (i < length) std::cout << (m_use_unicode ? m_bar_chars[partial_bar_char_index] : m_simple_empty_fill);
+ if (i < length) ss << (m_use_unicode ? m_bar_chars[partial_bar_char_index] : m_simple_empty_fill);
// whole unfinished bars
- if (m_use_color) std::cout << COLOR_RESET;
+ if (m_use_color) ss << COLOR_RESET;
for (i = whole_bar_chars_i + 1; i < length; i++)
{ // first entry in m_bar_chars is assumed to be the empty bar
- std::cout << (m_use_unicode ? m_bar_chars[0] : m_simple_empty_fill);
+ ss << (m_use_unicode ? m_bar_chars[0] : m_simple_empty_fill);
}
// right border
- if (m_use_color) std::cout << m_border_color;
- std::cout << (m_use_unicode ? m_right_border : m_simple_right_border);
- if (m_use_color) std::cout << COLOR_RESET;
+ if (m_use_color) ss << m_border_color;
+ ss << (m_use_unicode ? m_right_border : m_simple_right_border);
+ if (m_use_color) ss << COLOR_RESET;
+
+ return ss.str();
}
diff --git a/src/util.cpp b/src/util.cpp
index 4b98c45..3d9dfed 100644
--- a/src/util.cpp
+++ b/src/util.cpp
@@ -388,11 +388,11 @@ int Util::getTerminalWidth()
void Util::getDownloaderUrlsFromJSON(const Json::Value &root, std::vector<std::string> &urls)
{
if(root.size() > 0) {
- for(Json::ValueIterator it = root.begin() ; it != root.end() ; ++it)
+ for(Json::ValueConstIterator it = root.begin() ; it != root.end() ; ++it)
{
if (it.key() == "downloaderUrl")
{
- Json::Value& url = *it;
+ Json::Value url = *it;
urls.push_back(url.asString());
}
else
diff --git a/src/website.cpp b/src/website.cpp
new file mode 100644
index 0000000..2e68bed
--- /dev/null
+++ b/src/website.cpp
@@ -0,0 +1,708 @@
+/* This program is free software. It comes without any warranty, to
+ * the extent permitted by applicable law. You can redistribute it
+ * and/or modify it under the terms of the Do What The Fuck You Want
+ * To Public License, Version 2, as published by Sam Hocevar. See
+ * http://www.wtfpl.net/ for more details. */
+
+#include "website.h"
+#include "globalconstants.h"
+
+#include <htmlcxx/html/ParserDom.h>
+#include <boost/algorithm/string/case_conv.hpp>
+
+Website::Website(Config &conf)
+{
+ this->config = conf;
+ this->retries = 0;
+
+ curlhandle = curl_easy_init();
+ curl_easy_setopt(curlhandle, CURLOPT_FOLLOWLOCATION, 1);
+ curl_easy_setopt(curlhandle, CURLOPT_USERAGENT, config.sVersionString.c_str());
+ curl_easy_setopt(curlhandle, CURLOPT_NOPROGRESS, 1);
+ curl_easy_setopt(curlhandle, CURLOPT_CONNECTTIMEOUT, config.iTimeout);
+ curl_easy_setopt(curlhandle, CURLOPT_FAILONERROR, true);
+ curl_easy_setopt(curlhandle, CURLOPT_COOKIEFILE, config.sCookiePath.c_str());
+ curl_easy_setopt(curlhandle, CURLOPT_COOKIEJAR, config.sCookiePath.c_str());
+ curl_easy_setopt(curlhandle, CURLOPT_SSL_VERIFYPEER, config.bVerifyPeer);
+ curl_easy_setopt(curlhandle, CURLOPT_VERBOSE, config.bVerbose);
+ curl_easy_setopt(curlhandle, CURLOPT_MAX_RECV_SPEED_LARGE, config.iDownloadRate);
+
+ // Assume that we have connection error and abort transfer with CURLE_OPERATION_TIMEDOUT if download speed is less than 200 B/s for 30 seconds
+ curl_easy_setopt(curlhandle, CURLOPT_LOW_SPEED_TIME, 30);
+ curl_easy_setopt(curlhandle, CURLOPT_LOW_SPEED_LIMIT, 200);
+
+}
+
+Website::~Website()
+{
+ curl_easy_cleanup(curlhandle);
+}
+
+size_t Website::writeMemoryCallback(char *ptr, size_t size, size_t nmemb, void *userp)
+{
+ std::ostringstream *stream = (std::ostringstream*)userp;
+ size_t count = size * nmemb;
+ stream->write(ptr, count);
+ return count;
+}
+
+std::string Website::getResponse(const std::string& url)
+{
+ std::ostringstream memory;
+ std::string response;
+
+ curl_easy_setopt(curlhandle, CURLOPT_URL, url.c_str());
+ curl_easy_setopt(curlhandle, CURLOPT_WRITEFUNCTION, Website::writeMemoryCallback);
+ curl_easy_setopt(curlhandle, CURLOPT_WRITEDATA, &memory);
+
+ CURLcode result;
+ do
+ {
+ if (config.iWait > 0)
+ usleep(config.iWait); // Delay the request by specified time
+ result = curl_easy_perform(curlhandle);
+ response = memory.str();
+ memory.str(std::string());
+ }
+ while ((result != CURLE_OK) && response.empty() && (this->retries++ < config.iRetries));
+ this->retries = 0; // reset retries counter
+
+ if (result != CURLE_OK)
+ {
+ std::cout << curl_easy_strerror(result) << std::endl;
+ if (result == CURLE_HTTP_RETURNED_ERROR)
+ {
+ long int response_code = 0;
+ result = curl_easy_getinfo(curlhandle, CURLINFO_RESPONSE_CODE, &response_code);
+ std::cout << "HTTP ERROR: ";
+ if (result == CURLE_OK)
+ std::cout << response_code << " (" << url << ")" << std::endl;
+ else
+ std::cout << "failed to get error code: " << curl_easy_strerror(result) << " (" << url << ")" << std::endl;
+ }
+ }
+
+ return response;
+}
+
+Json::Value Website::getGameDetailsJSON(const std::string& gameid)
+{
+ std::string gameDataUrl = "https://www.gog.com/account/gameDetails/" + gameid + ".json";
+ std::string json = this->getResponse(gameDataUrl);
+
+ // Parse JSON
+ Json::Value root;
+ Json::Reader *jsonparser = new Json::Reader;
+ if (!jsonparser->parse(json, root))
+ {
+ #ifdef DEBUG
+ std::cerr << "DEBUG INFO (Website::getGameDetailsJSON)" << std::endl << json << std::endl;
+ #endif
+ std::cout << jsonparser->getFormattedErrorMessages();
+ delete jsonparser;
+ exit(1);
+ }
+ #ifdef DEBUG
+ std::cerr << "DEBUG INFO (Website::getGameDetailsJSON)" << std::endl << root << std::endl;
+ #endif
+ delete jsonparser;
+
+ return root;
+}
+
+// Get list of games from account page
+std::vector<gameItem> Website::getGames()
+{
+ std::vector<gameItem> games;
+ Json::Value root;
+ Json::Reader *jsonparser = new Json::Reader;
+ int i = 1;
+ bool bAllPagesParsed = false;
+
+ do
+ {
+ std::string response = this->getResponse("https://www.gog.com/account/getFilteredProducts?hasHiddenProducts=false&hiddenFlag=0&isUpdated=0&mediaType=1&sortBy=title&system=&page=" + std::to_string(i));
+
+ // Parse JSON
+ if (!jsonparser->parse(response, root))
+ {
+ #ifdef DEBUG
+ std::cerr << "DEBUG INFO (Website::getGames)" << std::endl << response << std::endl;
+ #endif
+ std::cout << jsonparser->getFormattedErrorMessages();
+ delete jsonparser;
+ if (!response.empty())
+ {
+ if(response[0] != '{')
+ {
+ // Response was not JSON. Assume that cookies have expired.
+ std::cerr << "Response was not JSON. Cookies have most likely expired. Try --login first." << std::endl;
+ }
+ }
+ exit(1);
+ }
+ #ifdef DEBUG
+ std::cerr << "DEBUG INFO (Website::getGames)" << std::endl << root << std::endl;
+ #endif
+ if (root["page"].asInt() == root["totalPages"].asInt())
+ bAllPagesParsed = true;
+ if (root["products"].isArray())
+ {
+ for (unsigned int i = 0; i < root["products"].size(); ++i)
+ {
+ Json::Value product = root["products"][i];
+ gameItem game;
+ game.name = product["slug"].asString();
+ game.id = product["id"].isInt() ? std::to_string(product["id"].asInt()) : product["id"].asString();
+
+ unsigned int platform = 0;
+ if (product["worksOn"]["Windows"].asBool())
+ platform |= GlobalConstants::PLATFORM_WINDOWS;
+ if (product["worksOn"]["Mac"].asBool())
+ platform |= GlobalConstants::PLATFORM_MAC;
+ if (product["worksOn"]["Linux"].asBool())
+ platform |= GlobalConstants::PLATFORM_LINUX;
+
+ // Skip if platform doesn't match
+ if (config.bPlatformDetection && !(platform & config.iInstallerPlatform))
+ continue;
+
+ // Filter the game list
+ if (!config.sGameRegex.empty())
+ {
+ // GameRegex filter aliases
+ if (config.sGameRegex == "all")
+ config.sGameRegex = ".*";
+
+ boost::regex expression(config.sGameRegex);
+ boost::match_results<std::string::const_iterator> what;
+ if (!boost::regex_search(game.name, what, expression)) // Check if name matches the specified regex
+ continue;
+ }
+
+ if (config.bDLC)
+ {
+ int dlcCount = product["dlcCount"].asInt();
+
+ bool bDownloadDLCInfo = (dlcCount != 0);
+
+ if (!bDownloadDLCInfo && !config.sIgnoreDLCCountRegex.empty())
+ {
+ boost::regex expression(config.sIgnoreDLCCountRegex);
+ boost::match_results<std::string::const_iterator> what;
+ if (boost::regex_search(game.name, what, expression)) // Check if name matches the specified regex
+ {
+ bDownloadDLCInfo = true;
+ }
+ }
+
+ // Check game specific config
+ if (!config.bUpdateCache) // Disable game specific config files for cache update
+ {
+ gameSpecificConfig conf;
+ conf.bIgnoreDLCCount = false; // Assume false
+ Util::getGameSpecificConfig(game.name, &conf);
+ if (conf.bIgnoreDLCCount)
+ bDownloadDLCInfo = true;
+ }
+
+ if (bDownloadDLCInfo && !config.sGameRegex.empty())
+ {
+ // don't download unnecessary info if user is only interested in a subset of his account
+ boost::regex expression(config.sGameRegex);
+ boost::match_results<std::string::const_iterator> what;
+ if (!boost::regex_search(game.name, what, expression))
+ {
+ bDownloadDLCInfo = false;
+ }
+ }
+
+ if (bDownloadDLCInfo)
+ {
+ game.gamedetailsjson = this->getGameDetailsJSON(game.id);
+ if (!game.gamedetailsjson.empty())
+ game.dlcnames = Util::getDLCNamesFromJSON(game.gamedetailsjson["dlcs"]);
+ }
+ }
+ games.push_back(game);
+ }
+ }
+ i++;
+ } while (!bAllPagesParsed);
+
+ delete jsonparser;
+
+ return games;
+}
+
+// Get list of free games
+std::vector<gameItem> Website::getFreeGames()
+{
+ Json::Value root;
+ Json::Reader *jsonparser = new Json::Reader;
+ std::vector<gameItem> games;
+ std::string json = this->getResponse("https://www.gog.com/games/ajax/filtered?mediaType=game&page=1&price=free&sort=title");
+
+ // Parse JSON
+ if (!jsonparser->parse(json, root))
+ {
+ #ifdef DEBUG
+ std::cerr << "DEBUG INFO (Website::getFreeGames)" << std::endl << json << std::endl;
+ #endif
+ std::cout << jsonparser->getFormattedErrorMessages();
+ delete jsonparser;
+ exit(1);
+ }
+ #ifdef DEBUG
+ std::cerr << "DEBUG INFO (Website::getFreeGames)" << std::endl << root << std::endl;
+ #endif
+
+ Json::Value products = root["products"];
+ for (unsigned int i = 0; i < products.size(); ++i)
+ {
+ gameItem game;
+ game.name = products[i]["slug"].asString();
+ game.id = products[i]["id"].isInt() ? std::to_string(products[i]["id"].asInt()) : products[i]["id"].asString();
+ games.push_back(game);
+ }
+ delete jsonparser;
+
+ return games;
+}
+
+// Login to GOG website
+int Website::Login(const std::string& email, const std::string& password)
+{
+ int res = 0;
+ std::string postdata;
+ std::ostringstream memory;
+ std::string token;
+ std::string tagname_username;
+ std::string tagname_password;
+ std::string tagname_login;
+ std::string tagname_token;
+
+ // Get login token
+ std::string html = this->getResponse("https://www.gog.com/");
+ 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();
+ // Find auth_url
+ bool bFoundAuthUrl = false;
+ for (; it != end; ++it)
+ {
+ if (it->tagName()=="script")
+ {
+ std::string auth_url;
+ for (unsigned int i = 0; i < dom.number_of_children(it); ++i)
+ {
+ tree<htmlcxx::HTML::Node>::iterator script_it = dom.child(it, i);
+ if (!script_it->isTag() && !script_it->isComment())
+ {
+ if (script_it->text().find("GalaxyAccounts") != std::string::npos)
+ {
+ boost::match_results<std::string::const_iterator> what;
+ boost::regex expression(".*'(https://auth.gog.com/.*?)'.*");
+ boost::regex_match(script_it->text(), what, expression);
+ auth_url = what[1];
+ break;
+ }
+ }
+ }
+
+ if (!auth_url.empty())
+ { // Found auth_url, get the necessary info for login
+ bFoundAuthUrl = true;
+ std::string login_form_html = this->getResponse(auth_url);
+ #ifdef DEBUG
+ std::cerr << "DEBUG INFO (Website::Login)" << std::endl;
+ std::cerr << login_form_html << std::endl;
+ #endif
+ if (login_form_html.find("google.com/recaptcha") != std::string::npos)
+ {
+ std::cout << "Login form contains reCAPTCHA (https://www.google.com/recaptcha/)" << std::endl
+ << "Login with browser and export cookies to \"" << config.sCookiePath << "\"" << std::endl;
+ return res = 0;
+ }
+
+ tree<htmlcxx::HTML::Node> login_dom = parser.parseTree(login_form_html);
+ tree<htmlcxx::HTML::Node>::iterator login_it = login_dom.begin();
+ tree<htmlcxx::HTML::Node>::iterator login_it_end = login_dom.end();
+ for (; login_it != login_it_end; ++login_it)
+ {
+ if (login_it->tagName()=="input")
+ {
+ login_it->parseAttributes();
+ std::string id_login = login_it->attribute("id").second;
+ if (id_login == "login_username")
+ {
+ tagname_username = login_it->attribute("name").second;
+ }
+ else if (id_login == "login_password")
+ {
+ tagname_password = login_it->attribute("name").second;
+ }
+ else if (id_login == "login__token")
+ {
+ token = login_it->attribute("value").second; // login token
+ tagname_token = login_it->attribute("name").second;
+ }
+ }
+ else if (login_it->tagName()=="button")
+ {
+ login_it->parseAttributes();
+ std::string id_login = login_it->attribute("id").second;
+ if (id_login == "login_login")
+ {
+ tagname_login = login_it->attribute("name").second;
+ }
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ if (!bFoundAuthUrl)
+ {
+ std::cout << "Failed to find url for login form" << std::endl;
+ }
+
+ if (token.empty())
+ {
+ std::cout << "Failed to get login token" << std::endl;
+ return res = 0;
+ }
+
+ //Create postdata - escape characters in email/password to support special characters
+ postdata = (std::string)curl_easy_escape(curlhandle, tagname_username.c_str(), tagname_username.size()) + "=" + (std::string)curl_easy_escape(curlhandle, email.c_str(), email.size())
+ + "&" + (std::string)curl_easy_escape(curlhandle, tagname_password.c_str(), tagname_password.size()) + "=" + (std::string)curl_easy_escape(curlhandle, password.c_str(), password.size())
+ + "&" + (std::string)curl_easy_escape(curlhandle, tagname_login.c_str(), tagname_login.size()) + "="
+ + "&" + (std::string)curl_easy_escape(curlhandle, tagname_token.c_str(), tagname_token.size()) + "=" + (std::string)curl_easy_escape(curlhandle, token.c_str(), token.size());
+ curl_easy_setopt(curlhandle, CURLOPT_URL, "https://login.gog.com/login_check");
+ curl_easy_setopt(curlhandle, CURLOPT_POST, 1);
+ curl_easy_setopt(curlhandle, CURLOPT_POSTFIELDS, postdata.c_str());
+ curl_easy_setopt(curlhandle, CURLOPT_WRITEFUNCTION, Website::writeMemoryCallback);
+ curl_easy_setopt(curlhandle, CURLOPT_WRITEDATA, &memory);
+ curl_easy_setopt(curlhandle, CURLOPT_NOPROGRESS, 1);
+ curl_easy_setopt(curlhandle, CURLOPT_MAXREDIRS, 0);
+ curl_easy_setopt(curlhandle, CURLOPT_POSTREDIR, CURL_REDIR_POST_ALL);
+
+ // Don't follow to redirect location because we need to check it for two step authorization.
+ curl_easy_setopt(curlhandle, CURLOPT_FOLLOWLOCATION, 0);
+ CURLcode result = curl_easy_perform(curlhandle);
+ memory.str(std::string());
+
+ if (result != CURLE_OK)
+ {
+ // Expected to hit maximum amount of redirects so don't print error on it
+ if (result != CURLE_TOO_MANY_REDIRECTS)
+ std::cout << curl_easy_strerror(result) << std::endl;
+ }
+
+ // Get redirect url
+ char *redirect_url;
+ curl_easy_getinfo(curlhandle, CURLINFO_REDIRECT_URL, &redirect_url);
+
+ // Handle two step authorization
+ if (std::string(redirect_url).find("two_step") != std::string::npos)
+ {
+ std::string security_code, tagname_two_step_send, tagname_two_step_auth_letter_1, tagname_two_step_auth_letter_2, tagname_two_step_auth_letter_3, tagname_two_step_auth_letter_4, tagname_two_step_token, token_two_step;
+ std::string two_step_html = this->getResponse(redirect_url);
+ redirect_url = NULL;
+
+ tree<htmlcxx::HTML::Node> two_step_dom = parser.parseTree(two_step_html);
+ tree<htmlcxx::HTML::Node>::iterator two_step_it = two_step_dom.begin();
+ tree<htmlcxx::HTML::Node>::iterator two_step_it_end = two_step_dom.end();
+ for (; two_step_it != two_step_it_end; ++two_step_it)
+ {
+ if (two_step_it->tagName()=="input")
+ {
+ two_step_it->parseAttributes();
+ std::string id_two_step = two_step_it->attribute("id").second;
+ if (id_two_step == "second_step_authentication_token_letter_1")
+ {
+ tagname_two_step_auth_letter_1 = two_step_it->attribute("name").second;
+ }
+ else if (id_two_step == "second_step_authentication_token_letter_2")
+ {
+ tagname_two_step_auth_letter_2 = two_step_it->attribute("name").second;
+ }
+ else if (id_two_step == "second_step_authentication_token_letter_3")
+ {
+ tagname_two_step_auth_letter_3 = two_step_it->attribute("name").second;
+ }
+ else if (id_two_step == "second_step_authentication_token_letter_4")
+ {
+ tagname_two_step_auth_letter_4 = two_step_it->attribute("name").second;
+ }
+ else if (id_two_step == "second_step_authentication__token")
+ {
+ token_two_step = two_step_it->attribute("value").second; // two step token
+ tagname_two_step_token = two_step_it->attribute("name").second;
+ }
+ }
+ else if (two_step_it->tagName()=="button")
+ {
+ two_step_it->parseAttributes();
+ std::string id_two_step = two_step_it->attribute("id").second;
+ if (id_two_step == "second_step_authentication_send")
+ {
+ tagname_two_step_send = two_step_it->attribute("name").second;
+ }
+ }
+ }
+ std::cerr << "Security code: ";
+ std::getline(std::cin,security_code);
+ if (security_code.size() != 4)
+ {
+ std::cerr << "Security code must be 4 characters long" << std::endl;
+ exit(1);
+ }
+ postdata = (std::string)curl_easy_escape(curlhandle, tagname_two_step_auth_letter_1.c_str(), tagname_two_step_auth_letter_1.size()) + "=" + security_code[0]
+ + "&" + (std::string)curl_easy_escape(curlhandle, tagname_two_step_auth_letter_2.c_str(), tagname_two_step_auth_letter_2.size()) + "=" + security_code[1]
+ + "&" + (std::string)curl_easy_escape(curlhandle, tagname_two_step_auth_letter_3.c_str(), tagname_two_step_auth_letter_3.size()) + "=" + security_code[2]
+ + "&" + (std::string)curl_easy_escape(curlhandle, tagname_two_step_auth_letter_4.c_str(), tagname_two_step_auth_letter_4.size()) + "=" + security_code[3]
+ + "&" + (std::string)curl_easy_escape(curlhandle, tagname_two_step_send.c_str(), tagname_two_step_send.size()) + "="
+ + "&" + (std::string)curl_easy_escape(curlhandle, tagname_two_step_token.c_str(), tagname_two_step_token.size()) + "=" + (std::string)curl_easy_escape(curlhandle, token_two_step.c_str(), token_two_step.size());
+
+ curl_easy_setopt(curlhandle, CURLOPT_URL, "https://login.gog.com/login/two_step");
+ curl_easy_setopt(curlhandle, CURLOPT_POST, 1);
+ curl_easy_setopt(curlhandle, CURLOPT_POSTFIELDS, postdata.c_str());
+ curl_easy_setopt(curlhandle, CURLOPT_WRITEFUNCTION, Website::writeMemoryCallback);
+ curl_easy_setopt(curlhandle, CURLOPT_WRITEDATA, &memory);
+ curl_easy_setopt(curlhandle, CURLOPT_NOPROGRESS, 1);
+ curl_easy_setopt(curlhandle, CURLOPT_MAXREDIRS, 0);
+ curl_easy_setopt(curlhandle, CURLOPT_POSTREDIR, CURL_REDIR_POST_ALL);
+
+ // Don't follow to redirect location because it doesn't work properly. Must clean up the redirect url first.
+ curl_easy_setopt(curlhandle, CURLOPT_FOLLOWLOCATION, 0);
+ result = curl_easy_perform(curlhandle);
+ memory.str(std::string());
+ curl_easy_getinfo(curlhandle, CURLINFO_REDIRECT_URL, &redirect_url);
+ }
+
+ curl_easy_setopt(curlhandle, CURLOPT_URL, redirect_url);
+ curl_easy_setopt(curlhandle, CURLOPT_HTTPGET, 1);
+ curl_easy_setopt(curlhandle, CURLOPT_MAXREDIRS, -1);
+ curl_easy_setopt(curlhandle, CURLOPT_FOLLOWLOCATION, 1);
+ result = curl_easy_perform(curlhandle);
+
+ if (result != CURLE_OK)
+ {
+ std::cout << curl_easy_strerror(result) << std::endl;
+ }
+
+ if (this->IsLoggedInComplex(email))
+ {
+ res = 1; // Login was successful
+ }
+ else
+ {
+ if (this->IsloggedInSimple())
+ res = 1; // Login was successful
+ }
+
+ if (res == 1)
+ {
+ curl_easy_setopt(curlhandle, CURLOPT_COOKIELIST, "FLUSH"); // Write all known cookies to the file specified by CURLOPT_COOKIEJAR
+ }
+
+ return res;
+}
+
+bool Website::IsLoggedIn()
+{
+ return this->IsloggedInSimple();
+}
+
+/* Complex login check. Check login by checking email address on the account settings page.
+ returns true if we are logged in
+ returns false if we are not logged in
+*/
+bool Website::IsLoggedInComplex(const std::string& email)
+{
+ bool bIsLoggedIn = false;
+ std::string html = this->getResponse("https://www.gog.com/account/settings/personal");
+ std::string email_lowercase = boost::algorithm::to_lower_copy(email); // boost::algorithm::to_lower does in-place modification but "email" is read-only so we need to make a copy of it
+
+ 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();
+ dom = parser.parseTree(html);
+ it = dom.begin();
+ end = dom.end();
+ for (; it != end; ++it)
+ {
+ if (it->tagName()=="strong")
+ {
+ it->parseAttributes();
+ if (it->attribute("class").second == "settings-item__value settings-item__section")
+ {
+ for (unsigned int i = 0; i < dom.number_of_children(it); ++i)
+ {
+ tree<htmlcxx::HTML::Node>::iterator tag_it = dom.child(it, i);
+ if (!tag_it->isTag() && !tag_it->isComment())
+ {
+ std::string tag_text = boost::algorithm::to_lower_copy(tag_it->text());
+ if (tag_text == email_lowercase)
+ {
+ bIsLoggedIn = true; // We are logged in
+ break;
+ }
+ }
+ }
+ }
+ }
+ if (bIsLoggedIn) // We are logged in so no need to go through the remaining tags
+ break;
+ }
+
+ return bIsLoggedIn;
+}
+
+/* Simple login check. Check login by trying to get account page. If response code isn't 200 then login failed.
+ returns true if we are logged in
+ returns false if we are not logged in
+*/
+bool Website::IsloggedInSimple()
+{
+ bool bIsLoggedIn = false;
+ std::ostringstream memory;
+ std::string url = "https://www.gog.com/account";
+ long int response_code = 0;
+
+ curl_easy_setopt(curlhandle, CURLOPT_URL, url.c_str());
+ curl_easy_setopt(curlhandle, CURLOPT_FOLLOWLOCATION, 0);
+ curl_easy_setopt(curlhandle, CURLOPT_WRITEFUNCTION, Website::writeMemoryCallback);
+ curl_easy_setopt(curlhandle, CURLOPT_WRITEDATA, &memory);
+ curl_easy_setopt(curlhandle, CURLOPT_NOPROGRESS, 1);
+ curl_easy_perform(curlhandle);
+ memory.str(std::string());
+
+ curl_easy_getinfo(curlhandle, CURLINFO_RESPONSE_CODE, &response_code);
+ curl_easy_setopt(curlhandle, CURLOPT_FOLLOWLOCATION, 1);
+ if (response_code == 200)
+ bIsLoggedIn = true; // We are logged in
+
+ return bIsLoggedIn;
+}
+
+std::vector<wishlistItem> Website::getWishlistItems()
+{
+ Json::Value root;
+ Json::Reader *jsonparser = new Json::Reader;
+ int i = 1;
+ bool bAllPagesParsed = false;
+ std::vector<wishlistItem> wishlistItems;
+
+ do
+ {
+ std::string response = this->getResponse("https://www.gog.com/account/wishlist/search?hasHiddenProducts=false&hiddenFlag=0&isUpdated=0&mediaType=0&sortBy=title&system=&page=" + std::to_string(i));
+
+ // Parse JSON
+ if (!jsonparser->parse(response, root))
+ {
+ #ifdef DEBUG
+ std::cerr << "DEBUG INFO (Website::getWishlistItems)" << std::endl << response << std::endl;
+ #endif
+ std::cout << jsonparser->getFormattedErrorMessages();
+ delete jsonparser;
+ exit(1);
+ }
+ #ifdef DEBUG
+ std::cerr << "DEBUG INFO (Website::getWishlistItems)" << std::endl << root << std::endl;
+ #endif
+ if (root["page"].asInt() >= root["totalPages"].asInt())
+ bAllPagesParsed = true;
+ if (root["products"].isArray())
+ {
+ for (unsigned int i = 0; i < root["products"].size(); ++i)
+ {
+ wishlistItem item;
+ Json::Value product = root["products"][i];
+
+ item.platform = 0;
+ std::string platforms_text;
+ bool bIsMovie = product["isMovie"].asBool();
+ if (!bIsMovie)
+ {
+ if (product["worksOn"]["Windows"].asBool())
+ item.platform |= GlobalConstants::PLATFORM_WINDOWS;
+ if (product["worksOn"]["Mac"].asBool())
+ item.platform |= GlobalConstants::PLATFORM_MAC;
+ if (product["worksOn"]["Linux"].asBool())
+ item.platform |= GlobalConstants::PLATFORM_LINUX;
+
+ // Skip if platform doesn't match
+ if (config.bPlatformDetection && !(item.platform & config.iInstallerPlatform))
+ continue;
+ }
+
+ if (product["isComingSoon"].asBool())
+ item.tags.push_back("Coming soon");
+ if (product["isDiscounted"].asBool())
+ item.tags.push_back("Discount");
+ if (bIsMovie)
+ item.tags.push_back("Movie");
+
+ item.release_date_time = 0;
+ if (product.isMember("releaseDate") && product["isComingSoon"].asBool())
+ {
+ if (!product["releaseDate"].empty())
+ {
+ if (product["releaseDate"].isInt())
+ {
+ item.release_date_time = product["releaseDate"].asInt();
+ }
+ else
+ {
+ std::string release_date_time_string = product["releaseDate"].asString();
+ if (!release_date_time_string.empty())
+ {
+ try
+ {
+ item.release_date_time = std::stoi(release_date_time_string);
+ }
+ catch (std::invalid_argument& e)
+ {
+ item.release_date_time = 0;
+ }
+ }
+ }
+ }
+ }
+
+ item.currency = product["price"]["symbol"].asString();
+ item.price = product["price"]["finalAmount"].isDouble() ? std::to_string(product["price"]["finalAmount"].asDouble()) + item.currency : product["price"]["finalAmount"].asString() + item.currency;
+ item.discount_percent = product["price"]["discountPercentage"].isInt() ? std::to_string(product["price"]["discountPercentage"].asInt()) + "%" : product["price"]["discountPercentage"].asString() + "%";
+ item.discount = product["price"]["discountDifference"].isDouble() ? std::to_string(product["price"]["discountDifference"].asDouble()) + item.currency : product["price"]["discountDifference"].asString() + item.currency;
+ item.store_credit = product["price"]["bonusStoreCreditAmount"].isDouble() ? std::to_string(product["price"]["bonusStoreCreditAmount"].asDouble()) + item.currency : product["price"]["bonusStoreCreditAmount"].asString() + item.currency;
+
+ item.url = product["url"].asString();
+ if (item.url.find("/game/") == 0)
+ item.url = "https://www.gog.com" + item.url;
+ else if (item.url.find("/movie/") == 0)
+ item.url = "https://www.gog.com" + item.url;
+
+ item.title = product["title"].asString();
+ item.bIsBonusStoreCreditIncluded = product["price"]["isBonusStoreCreditIncluded"].asBool();
+ item.bIsDiscounted = product["isDiscounted"].asBool();
+
+ wishlistItems.push_back(item);
+ }
+ }
+ i++;
+ } while (!bAllPagesParsed);
+
+ delete jsonparser;
+
+ return wishlistItems;
+}
+
+void Website::setConfig(Config &conf)
+{
+ this->config = conf;
+}
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-games/lgogdownloader.git
More information about the Pkg-games-commits
mailing list