2022-09-11 00:19:41 +00:00
|
|
|
#include <iostream>
|
2022-09-29 00:56:10 +00:00
|
|
|
|
2022-09-29 07:04:57 +00:00
|
|
|
#include "series.h"
|
2022-09-29 00:02:10 +00:00
|
|
|
|
|
|
|
#ifdef DROPOUT_DL_SQLITE
|
2022-09-11 17:21:24 +00:00
|
|
|
#include <sqlite3.h>
|
2022-09-29 00:02:10 +00:00
|
|
|
#endif
|
2022-09-11 00:19:41 +00:00
|
|
|
|
2022-09-30 02:42:45 +00:00
|
|
|
namespace dropout_dl {
|
|
|
|
|
|
|
|
class options {
|
|
|
|
public:
|
|
|
|
|
|
|
|
std::string url;
|
|
|
|
bool verbose = false;
|
|
|
|
bool cookies_forced = false;
|
|
|
|
bool series = false;
|
|
|
|
bool season = false;
|
|
|
|
std::string quality;
|
|
|
|
std::string filename;
|
|
|
|
std::string output_directory;
|
|
|
|
std::string episode;
|
|
|
|
std::vector<cookie> cookies;
|
|
|
|
|
|
|
|
static std::vector<std::string> convert_program_args(int argc, char** argv) {
|
|
|
|
std::vector<std::string> out;
|
|
|
|
for (int i = 1; i < argc; i++) {
|
|
|
|
out.emplace_back(argv[i]);
|
|
|
|
}
|
|
|
|
return out;
|
|
|
|
}
|
|
|
|
|
|
|
|
options(int argc, char** argv) {
|
|
|
|
std::vector<std::string> args = convert_program_args(argc, argv);
|
|
|
|
|
|
|
|
for (int i = 0; i < args.size(); i++) {
|
|
|
|
std::string arg = args[i];
|
|
|
|
|
|
|
|
if (arg.substr(0, 2) != "--") {
|
|
|
|
url = arg;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
arg = arg.substr(2);
|
|
|
|
if (arg == "verbose") {
|
|
|
|
verbose = true;
|
|
|
|
} else if (arg == "quality") {
|
|
|
|
if (i + 1 >= args.size()) {
|
|
|
|
std::cerr << "ARGUMENT PARSE ERROR: --quality used with too few following arguments\n";
|
|
|
|
exit(8);
|
|
|
|
}
|
|
|
|
quality = args[++i];
|
|
|
|
}
|
|
|
|
else if (arg == "force-cookies") {
|
|
|
|
if (i + 2 >= args.size()) {
|
|
|
|
std::cerr << "ARGUMENT PARSE ERROR: --force-cookies used with too few following arguments\n";
|
|
|
|
exit(8);
|
|
|
|
}
|
|
|
|
cookies.emplace_back(args[++i]);
|
|
|
|
cookies.emplace_back(args[++i]);
|
|
|
|
cookies_forced = true;
|
|
|
|
}
|
|
|
|
else if (arg == "output") {
|
|
|
|
if (i + 1 >= args.size()) {
|
|
|
|
std::cerr << "ARGUMENT PARSE ERROR: --output used with too few following arguments\n";
|
|
|
|
exit(8);
|
|
|
|
}
|
|
|
|
filename = args[++i];
|
|
|
|
}
|
|
|
|
else if (arg == "output-directory") {
|
|
|
|
if (i + 1 >= args.size()) {
|
|
|
|
std::cerr << "ARGUMENT PARSE ERROR: --output-directory used with too few following arguments\n";
|
|
|
|
exit(8);
|
|
|
|
}
|
|
|
|
output_directory = args[++i];
|
|
|
|
}
|
|
|
|
else if (arg == "series") {
|
|
|
|
series = true;
|
|
|
|
}
|
|
|
|
else if (arg == "season") {
|
|
|
|
season = true;
|
|
|
|
}
|
|
|
|
else if (arg == "help") {
|
|
|
|
std::cout << "Usage: dropout-dl [OPTIONS] <url> [OPTIONS]\n"
|
|
|
|
"\n"
|
|
|
|
"Options:\n"
|
|
|
|
"\t--help Display this message\n"
|
|
|
|
"\t--quality Set the quality of the downloaded video. Quality can be set to 'all' which\n"
|
|
|
|
"\t will download all qualities and place them into separate folders\n"
|
|
|
|
"\t--output Set the output filename. Only works for single episode downloads\n"
|
|
|
|
"\t--output-directory Set the directory where files are output\n"
|
|
|
|
"\t--verbose Display debug information while running\n"
|
|
|
|
"\t--force-cookies Interpret the next to arguments as authentication cookie and session cookie\n"
|
|
|
|
"\t--series Interpret the url as a link to a series and download all episodes from all seasons\n"
|
|
|
|
"\t--season Interpret the url as a link to a season and download all episodes from all seasons\n"
|
|
|
|
<< std::endl;
|
|
|
|
|
|
|
|
exit(0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (output_directory.empty()) {
|
|
|
|
output_directory = ".";
|
|
|
|
}
|
|
|
|
|
|
|
|
if (season && series) {
|
|
|
|
std::cerr << "ARGUMENT PARSE ERROR: Season and Series arguments used\n";
|
|
|
|
}
|
|
|
|
if (quality.empty()) {
|
|
|
|
quality = "1080p";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
2022-09-11 17:21:24 +00:00
|
|
|
}
|
|
|
|
|
2022-09-29 00:02:10 +00:00
|
|
|
#ifdef DROPOUT_DL_SQLITE
|
2022-09-30 02:42:45 +00:00
|
|
|
std::vector<dropout_dl::cookie> get_cookies_from_firefox(const std::filesystem::path& firefox_profile_path, bool verbose = false) {
|
2022-09-11 17:21:24 +00:00
|
|
|
|
2022-09-29 01:02:56 +00:00
|
|
|
std::fstream firefox_profile_file(firefox_profile_path);
|
2022-09-29 00:02:10 +00:00
|
|
|
std::string firefox_profile;
|
2022-09-11 00:19:41 +00:00
|
|
|
|
2022-09-30 02:42:45 +00:00
|
|
|
dropout_dl::cookie auth("__cf_bm");
|
|
|
|
dropout_dl::cookie session("_session");
|
2022-09-11 00:19:41 +00:00
|
|
|
|
2022-09-30 02:42:45 +00:00
|
|
|
std::vector<dropout_dl::cookie> out;
|
2022-09-11 17:21:24 +00:00
|
|
|
|
2022-09-29 00:02:10 +00:00
|
|
|
firefox_profile_file >> firefox_profile;
|
2022-09-11 17:21:24 +00:00
|
|
|
|
2022-09-29 00:02:10 +00:00
|
|
|
if (std::filesystem::is_directory(firefox_profile)) {
|
2022-09-11 17:21:24 +00:00
|
|
|
|
2022-09-29 00:02:10 +00:00
|
|
|
sqlite3 *db;
|
2022-09-11 17:21:24 +00:00
|
|
|
|
2022-09-29 00:02:10 +00:00
|
|
|
if (verbose) {
|
|
|
|
std::cout << "Getting firefox cookies from firefox sqlite db\n";
|
|
|
|
}
|
2022-09-11 17:21:24 +00:00
|
|
|
|
2022-09-29 00:02:10 +00:00
|
|
|
if (!std::filesystem::is_directory("tmp"))
|
|
|
|
std::filesystem::create_directories("tmp");
|
|
|
|
std::filesystem::remove("tmp/firefox_cookies.sqlite");
|
|
|
|
std::filesystem::copy_file(firefox_profile + "/cookies.sqlite", "tmp/firefox_cookies.sqlite");
|
|
|
|
|
|
|
|
int rc = sqlite3_open("tmp/firefox_cookies.sqlite", &db);
|
|
|
|
if (rc) {
|
|
|
|
std::cerr << "Can't open database: " << sqlite3_errmsg(db) << '\n';
|
|
|
|
exit(1);
|
|
|
|
} else {
|
|
|
|
if (verbose) {
|
|
|
|
std::cout << "Firefox database opened successfully\n";
|
|
|
|
}
|
|
|
|
}
|
2022-09-11 17:21:24 +00:00
|
|
|
|
2022-09-30 02:42:45 +00:00
|
|
|
std::string len;
|
2022-09-11 17:21:24 +00:00
|
|
|
|
2022-09-30 02:42:45 +00:00
|
|
|
auth.get_value_from_db(db, "FROM moz_cookies WHERE host LIKE '%dropout.tv%'", "value");
|
2022-09-11 17:21:24 +00:00
|
|
|
|
2022-09-30 02:42:45 +00:00
|
|
|
session.get_value_from_db(db, "FROM moz_cookies WHERE host LIKE '%dropout.tv%'", "value");
|
2022-09-14 01:01:55 +00:00
|
|
|
|
2022-09-30 02:42:45 +00:00
|
|
|
sqlite3_close(db);
|
|
|
|
}
|
2022-09-29 00:02:10 +00:00
|
|
|
|
2022-09-30 02:42:45 +00:00
|
|
|
if (verbose) {
|
|
|
|
std::cout << auth.name << ": " << auth.len << ": " << auth.str << '\n';
|
2022-09-29 00:02:10 +00:00
|
|
|
|
2022-09-30 02:42:45 +00:00
|
|
|
std::cout << session.name << ": " << session.len << ": " << session.str << '\n';
|
|
|
|
}
|
2022-09-29 00:02:10 +00:00
|
|
|
|
2022-09-30 02:42:45 +00:00
|
|
|
out.push_back(auth);
|
|
|
|
out.push_back(session);
|
2022-09-29 00:02:10 +00:00
|
|
|
|
|
|
|
|
|
|
|
return out;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef DROPOUT_DL_GCRYPT
|
2022-09-30 02:42:45 +00:00
|
|
|
std::vector<dropout_dl::cookie> get_cookies_from_chrome(const std::filesystem::path& chrome_profile_path, bool verbose = false) {
|
2022-09-29 00:02:10 +00:00
|
|
|
|
2022-09-29 01:02:56 +00:00
|
|
|
std::fstream chrome_profile_file(chrome_profile_path);
|
2022-09-29 00:02:10 +00:00
|
|
|
std::string chrome_profile;
|
|
|
|
|
2022-09-30 02:42:45 +00:00
|
|
|
dropout_dl::cookie auth("__cf_bm");
|
|
|
|
dropout_dl::cookie session("_session");
|
2022-09-29 00:02:10 +00:00
|
|
|
|
2022-09-30 02:42:45 +00:00
|
|
|
std::vector<dropout_dl::cookie> out;
|
2022-09-29 00:02:10 +00:00
|
|
|
|
|
|
|
getline(chrome_profile_file, chrome_profile);
|
|
|
|
|
|
|
|
if (std::filesystem::is_directory(chrome_profile)) {
|
|
|
|
|
|
|
|
sqlite3 *db;
|
|
|
|
|
|
|
|
if (verbose) {
|
|
|
|
std::cout << "Getting chrome cookies from chrome sqlite db\n";
|
|
|
|
}
|
|
|
|
|
|
|
|
int rc = sqlite3_open((chrome_profile + "/Cookies").c_str(), &db);
|
|
|
|
if (rc) {
|
|
|
|
std::cerr << "Can't open database: " << sqlite3_errmsg(db) << '\n';
|
|
|
|
exit(1);
|
|
|
|
} else {
|
2022-09-14 01:01:55 +00:00
|
|
|
if (verbose) {
|
2022-09-29 00:02:10 +00:00
|
|
|
std::cout << "Chrome database opened successfully\n";
|
2022-09-14 01:01:55 +00:00
|
|
|
}
|
2022-09-29 00:02:10 +00:00
|
|
|
}
|
2022-09-11 17:21:24 +00:00
|
|
|
|
2022-09-29 00:02:10 +00:00
|
|
|
std::string len;
|
2022-09-11 17:21:24 +00:00
|
|
|
|
2022-09-30 02:42:45 +00:00
|
|
|
auth.get_value_from_db(db, "FROM cookies WHERE host_key LIKE '%dropout.tv%'", "encrypted_value");
|
2022-09-29 00:02:10 +00:00
|
|
|
|
2022-09-30 02:42:45 +00:00
|
|
|
session.get_value_from_db(db, "FROM cookies WHERE host_key LIKE '%dropout.tv%'", "encrypted_value");
|
2022-09-29 00:02:10 +00:00
|
|
|
|
|
|
|
sqlite3_close(db);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2022-09-30 02:42:45 +00:00
|
|
|
auth.chrome_decrypt();
|
2022-09-29 00:02:10 +00:00
|
|
|
|
2022-09-30 02:42:45 +00:00
|
|
|
session.chrome_decrypt();
|
2022-09-29 00:02:10 +00:00
|
|
|
|
2022-09-30 02:42:45 +00:00
|
|
|
session.url_decode();
|
2022-09-29 00:02:10 +00:00
|
|
|
|
2022-09-30 02:42:45 +00:00
|
|
|
if (verbose) {
|
|
|
|
std::cout << auth.name << ": " << auth.len << ": " << auth.str << '\n';
|
2022-09-29 00:02:10 +00:00
|
|
|
|
2022-09-30 02:42:45 +00:00
|
|
|
std::cout << session.name << ": " << session.len << ": " << session.str << '\n';
|
2022-09-11 17:21:24 +00:00
|
|
|
}
|
|
|
|
|
2022-09-30 02:42:45 +00:00
|
|
|
out.push_back(auth);
|
|
|
|
out.push_back(session);
|
2022-09-29 00:02:10 +00:00
|
|
|
|
|
|
|
return out;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
#endif
|
|
|
|
|
2022-09-30 02:42:45 +00:00
|
|
|
std::vector<dropout_dl::cookie> get_cookies_from_files(const std::filesystem::path& auth_cookie_path, const std::filesystem::path& session_cookie_path, bool verbose = false) {
|
2022-09-29 00:02:10 +00:00
|
|
|
std::fstream auth_cookie_file("auth_cookie");
|
|
|
|
std::fstream session_cookie_file("session_cookie");
|
|
|
|
|
|
|
|
std::string auth_cookie;
|
|
|
|
std::string session_cookie;
|
|
|
|
|
2022-09-30 02:42:45 +00:00
|
|
|
std::vector<dropout_dl::cookie> out;
|
2022-09-29 00:02:10 +00:00
|
|
|
|
|
|
|
auth_cookie_file >> auth_cookie;
|
|
|
|
if (verbose) {
|
|
|
|
std::cout << "Got __cf_bm cookie from auth_cookie file db\n";
|
|
|
|
}
|
|
|
|
|
|
|
|
out.emplace_back(auth_cookie);
|
|
|
|
|
|
|
|
session_cookie_file >> session_cookie;
|
|
|
|
if (verbose) {
|
|
|
|
std::cout << "Got _session cookie from auth_cookie file db\n";
|
|
|
|
}
|
|
|
|
|
|
|
|
out.emplace_back(session_cookie);
|
|
|
|
|
|
|
|
return out;
|
|
|
|
}
|
|
|
|
|
2022-09-30 02:42:45 +00:00
|
|
|
std::vector<dropout_dl::cookie> get_cookies(bool verbose = false) {
|
2022-09-29 00:02:10 +00:00
|
|
|
|
2022-09-29 01:02:56 +00:00
|
|
|
#ifdef DROPOUT_DL_SQLITE
|
|
|
|
std::filesystem::path firefox_profile("firefox_profile");
|
|
|
|
#ifdef DROPOUT_DL_GCRYPT
|
|
|
|
std::filesystem::path chrome_profile("chrome_profile");
|
|
|
|
#endif
|
|
|
|
#endif
|
|
|
|
std::filesystem::path auth_cookie("auth_cookie");
|
|
|
|
std::filesystem::path session_cookie("session_cookie");
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef DROPOUT_DL_SQLITE
|
|
|
|
if (std::filesystem::exists(firefox_profile)) {
|
|
|
|
return get_cookies_from_firefox(firefox_profile, verbose);
|
|
|
|
} else
|
|
|
|
#ifdef DROPOUT_DL_GCRYPT
|
|
|
|
if (std::filesystem::exists(chrome_profile)) {
|
|
|
|
return get_cookies_from_chrome(chrome_profile, verbose);
|
|
|
|
} else
|
|
|
|
#endif
|
|
|
|
#endif
|
|
|
|
if (std::filesystem::exists(auth_cookie) && std::filesystem::exists(session_cookie)){
|
|
|
|
return get_cookies_from_files(auth_cookie, session_cookie, verbose);
|
2022-09-29 00:02:10 +00:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
std::cerr << "ERROR: dropout.tv cookies could not be found" << std::endl;
|
2022-09-29 01:02:56 +00:00
|
|
|
exit(7);
|
2022-09-29 00:02:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int main(int argc, char** argv) {
|
2022-09-30 02:42:45 +00:00
|
|
|
dropout_dl::options options(argc, argv);
|
2022-09-29 03:10:35 +00:00
|
|
|
|
2022-09-30 02:42:45 +00:00
|
|
|
// std::cout << "quality: " << options.quality << std::endl;
|
|
|
|
// std::cout << "verbose: " << options.verbose << std::endl;
|
|
|
|
// std::cout << "url: \"" << options.url << '"' << std::endl;
|
2022-09-29 00:02:10 +00:00
|
|
|
|
|
|
|
std::string firefox_profile;
|
|
|
|
std::string chrome_profile;
|
|
|
|
|
2022-09-11 00:19:41 +00:00
|
|
|
std::string video_data;
|
|
|
|
|
2022-09-29 03:10:35 +00:00
|
|
|
if (options.url.empty()) {
|
2022-09-11 00:19:41 +00:00
|
|
|
std::cout << "Enter episode url: ";
|
2022-09-29 03:10:35 +00:00
|
|
|
std::cin >> options.url;
|
|
|
|
}
|
|
|
|
else if (options.verbose) {
|
|
|
|
std::cout << "Got episode url: " << options.url << " from program arguments\n";
|
2022-09-11 00:19:41 +00:00
|
|
|
}
|
|
|
|
|
2022-09-29 03:10:35 +00:00
|
|
|
if (!options.cookies_forced) {
|
|
|
|
options.cookies = get_cookies(options.verbose);
|
|
|
|
}
|
2022-09-11 00:19:41 +00:00
|
|
|
|
2022-09-29 07:04:57 +00:00
|
|
|
if (options.series) {
|
|
|
|
dropout_dl::series series(options.url, options.cookies);
|
2022-09-11 17:21:24 +00:00
|
|
|
|
2022-09-29 22:04:04 +00:00
|
|
|
series.download(options.quality, options.output_directory);
|
2022-09-29 03:27:48 +00:00
|
|
|
}
|
2022-09-29 18:33:56 +00:00
|
|
|
else if (options.season) {
|
|
|
|
dropout_dl::season season = dropout_dl::series::get_season(options.url, options.cookies);
|
|
|
|
|
2022-09-29 22:04:04 +00:00
|
|
|
std::string series_directory = season.series_name;
|
2022-09-29 18:33:56 +00:00
|
|
|
|
2022-09-29 22:04:04 +00:00
|
|
|
std::replace(series_directory.begin(), series_directory.end(), ' ', '_');
|
2022-09-29 18:33:56 +00:00
|
|
|
|
2022-09-29 22:04:04 +00:00
|
|
|
std::replace(series_directory.begin(), series_directory.end(), ',', '_');
|
2022-09-29 18:33:56 +00:00
|
|
|
|
2022-09-29 22:04:04 +00:00
|
|
|
season.download(options.quality, options.output_directory + "/" + series_directory);
|
2022-09-29 18:33:56 +00:00
|
|
|
}
|
2022-09-29 07:04:57 +00:00
|
|
|
else {
|
|
|
|
dropout_dl::episode ep(options.url, options.cookies, options.verbose);
|
2022-09-29 03:27:48 +00:00
|
|
|
|
2022-09-29 07:04:57 +00:00
|
|
|
if (options.filename.empty()) {
|
|
|
|
options.filename = "S" + (ep.season_number.size() < 2 ? "0" + ep.season_number : ep.season_number) + "E" +
|
|
|
|
(ep.episode_number.size() < 2 ? "0" + ep.episode_number : ep.episode_number) + ep.name +
|
|
|
|
".mp4";
|
|
|
|
|
|
|
|
std::replace(options.filename.begin(), options.filename.end(), ' ', '_');
|
|
|
|
|
|
|
|
std::replace(options.filename.begin(), options.filename.end(), ',', '_');
|
|
|
|
}
|
2022-09-29 03:27:48 +00:00
|
|
|
|
2022-09-29 03:10:35 +00:00
|
|
|
if (options.verbose) {
|
2022-09-29 07:04:57 +00:00
|
|
|
std::cout << "filename: " << options.filename << '\n';
|
2022-09-11 00:19:41 +00:00
|
|
|
}
|
|
|
|
|
2022-09-29 07:04:57 +00:00
|
|
|
if (!std::filesystem::is_directory(ep.series)) {
|
|
|
|
std::filesystem::create_directories(ep.series);
|
|
|
|
if (options.verbose) {
|
|
|
|
std::cout << "Creating series directory" << '\n';
|
|
|
|
}
|
2022-09-29 02:22:20 +00:00
|
|
|
}
|
|
|
|
|
2022-09-29 22:04:04 +00:00
|
|
|
ep.download(options.quality, options.output_directory + "/" + ep.series, options.filename);
|
2022-09-29 02:22:20 +00:00
|
|
|
}
|
2022-09-11 00:19:41 +00:00
|
|
|
|
2022-09-29 07:04:57 +00:00
|
|
|
|
2022-09-11 00:19:41 +00:00
|
|
|
return 0;
|
|
|
|
}
|