dropout-dl/src/main.cpp

408 lines
11 KiB
C++
Raw Normal View History

2022-09-11 00:19:41 +00:00
#include <iostream>
#include "series.h"
#include <regex>
#ifdef DROPOUT_DL_SQLITE
#include <sqlite3.h>
#endif
2022-09-11 00:19:41 +00:00
namespace dropout_dl {
2022-12-22 03:41:53 +00:00
/**
* A class for handling and storing the program arguments.
*/
class options {
public:
std::string url;
bool verbose = false;
bool cookies_forced = false;
bool is_series = false;
bool is_season = false;
bool is_episode = false;
2022-12-22 03:41:53 +00:00
std::string quality;
std::string filename;
std::string output_directory;
std::string episode;
std::vector<cookie> cookies;
/**
*
* @param argc - The number of provided program arguments
* @param argv - The provided program arguments
* @return A vector of arguments in the c++ string format
*
* Converts the C style program arguments to a vector of strings
*/
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;
}
/**
*
* @param argc - The number of provided program arguments
* @param argv - The provided program arguments
*
* Parses and handles the program arguments and creates an options object.
*/
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") {
is_series = true;
2022-12-22 03:41:53 +00:00
}
else if (arg == "season") {
is_season = true;
}
else if (arg == "episode") {
is_episode = true;
2022-12-22 03:41:53 +00:00
}
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 ((is_season && is_series) || (is_season && is_episode) || (is_series && is_episode)) {
std::cerr << "ARGUMENT PARSE ERROR: Mulitple parse type arguments used\n";
2022-12-22 03:41:53 +00:00
}
if (quality.empty()) {
quality = "1080p";
}
if (!(is_season || is_series || is_episode)) {
std::regex season_regex("season:\\d+\\/?$", std::regex::ECMAScript);
std::regex episode_regex("season:\\d+\\/.+$", std::regex::ECMAScript);
if (std::regex_search(url, season_regex)) {
is_season = true;
}
else if (std::regex_search(url, episode_regex)) {
is_episode = true;
}
else {
is_series = true;
}
}
else {
std::cout << is_season << is_series << is_episode << '\n';
}
2022-12-22 03:41:53 +00:00
}
};
}
#ifdef DROPOUT_DL_SQLITE
2022-09-30 05:20:23 +00:00
/**
*
* @param firefox_profile_path - The path to a firefox profile
* @param verbose - Whether or not to be verbose
* @return A vector of cookies
*
* Gets the needed cookies from the firefox sqlite database associated with the path provided.
*/
std::vector<dropout_dl::cookie> get_cookies_from_firefox(const std::filesystem::path& firefox_profile_path, bool verbose = false) {
2022-12-22 03:41:53 +00:00
std::fstream firefox_profile_file(firefox_profile_path);
std::string firefox_profile;
2022-09-11 00:19:41 +00:00
2022-12-22 03:41:53 +00:00
dropout_dl::cookie auth("__cf_bm");
dropout_dl::cookie session("_session");
2022-09-11 00:19:41 +00:00
2022-12-22 03:41:53 +00:00
std::vector<dropout_dl::cookie> out;
2022-12-22 03:41:53 +00:00
firefox_profile_file >> firefox_profile;
2022-12-22 03:41:53 +00:00
if (std::filesystem::is_directory(firefox_profile)) {
2022-12-22 03:41:53 +00:00
sqlite3 *db;
2022-12-22 03:41:53 +00:00
if (verbose) {
std::cout << "Getting firefox cookies from firefox sqlite db\n";
}
2022-12-22 03:41:53 +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");
2022-12-22 03:41:53 +00:00
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-12-22 03:41:53 +00:00
std::string len;
2022-12-22 03:41:53 +00:00
auth.get_value_from_db(db, "FROM moz_cookies WHERE host LIKE '%dropout.tv%'", "value");
2022-12-22 03:41:53 +00:00
session.get_value_from_db(db, "FROM moz_cookies WHERE host LIKE '%dropout.tv%'", "value");
2022-12-22 03:41:53 +00:00
sqlite3_close(db);
2022-12-22 03:41:53 +00:00
std::filesystem::remove("tmp/firefox_cookies.sqlite");
2022-12-22 03:41:53 +00:00
if (std::filesystem::is_empty("tmp")) {
std::filesystem::remove("tmp/");
}
}
else {
std::cerr << "FIREFOX COOKIE ERROR: Attempted to get cookies from firefox without profile." << std::endl;
exit(4);
}
2022-12-22 03:41:53 +00:00
if (verbose) {
std::cout << auth.name << ": " << auth.len << ": " << auth.value << '\n';
2022-12-22 03:41:53 +00:00
std::cout << session.name << ": " << session.len << ": " << session.value << '\n';
}
2022-12-22 03:41:53 +00:00
out.push_back(auth);
out.push_back(session);
2022-12-22 03:41:53 +00:00
return out;
}
#ifdef DROPOUT_DL_GCRYPT
2022-09-30 05:20:23 +00:00
/**
*
* @param chrome_profile_path - The path to a chrome profile
* @param verbose - Whether or not to be verbose
* @return A vector of cookies
*
* Gets the needed cookies from the chrome sqlite database associated with the path provided and decrypts them using the libgcrypt library.
* This function does not work for windows and must be modified slightly for mac os.
* For mac os the calls to cookie::chrome_decrypt must be passed the parameters detailed in it's documentation.
*/
std::vector<dropout_dl::cookie> get_cookies_from_chrome(const std::filesystem::path& chrome_profile_path, bool verbose = false) {
2022-12-22 03:41:53 +00:00
std::fstream chrome_profile_file(chrome_profile_path);
std::string chrome_profile;
2022-12-22 03:41:53 +00:00
dropout_dl::cookie auth("__cf_bm");
dropout_dl::cookie session("_session");
2022-12-22 03:41:53 +00:00
std::vector<dropout_dl::cookie> out;
2022-12-22 03:41:53 +00:00
getline(chrome_profile_file, chrome_profile);
2022-12-22 03:41:53 +00:00
if (std::filesystem::is_directory(chrome_profile)) {
2022-12-22 03:41:53 +00:00
sqlite3 *db;
2022-12-22 03:41:53 +00:00
if (verbose) {
std::cout << "Getting chrome cookies from chrome sqlite db\n";
}
2022-12-22 03:41:53 +00:00
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 {
if (verbose) {
std::cout << "Chrome database opened successfully\n";
}
}
2022-12-22 03:41:53 +00:00
std::string len;
2022-12-22 03:41:53 +00:00
auth.get_value_from_db(db, "FROM cookies WHERE host_key LIKE '%dropout.tv%'", "encrypted_value");
2022-12-22 03:41:53 +00:00
session.get_value_from_db(db, "FROM cookies WHERE host_key LIKE '%dropout.tv%'", "encrypted_value");
2022-12-22 03:41:53 +00:00
sqlite3_close(db);
2022-12-22 03:41:53 +00:00
}
else {
std::cerr << "CHROME COOKIE ERROR: Attempted to get cookies from chrome without profile." << std::endl;
exit(4);
}
2022-12-22 03:41:53 +00:00
auth.chrome_decrypt();
2022-12-22 03:41:53 +00:00
session.chrome_decrypt();
2022-12-22 03:41:53 +00:00
if (verbose) {
std::cout << auth.name << ": " << auth.len << ": " << auth.value << '\n';
2022-12-22 03:41:53 +00:00
std::cout << session.name << ": " << session.len << ": " << session.value << '\n';
}
2022-12-22 03:41:53 +00:00
out.push_back(auth);
out.push_back(session);
2022-12-22 03:41:53 +00:00
return out;
}
#endif
#endif
2022-09-30 05:20:23 +00:00
/**
*
* @param verbose - Whether or not to be verbose
* @return A vector of cookies
*
* Determines whether to get cookies from firefox or chrome. This function should not be run if cookies are forced using the `--force-cookies` option.
* This function checks firefox first so if both firefox and chrome profiles are provided it will use firefox.
*/
std::vector<dropout_dl::cookie> get_cookies(bool verbose = false) {
std::filesystem::path firefox_profile("firefox_profile");
2022-12-22 03:41:53 +00:00
std::filesystem::path chrome_profile("chrome_profile");
if (std::filesystem::exists(firefox_profile)) {
#ifdef DROPOUT_DL_SQLITE
return get_cookies_from_firefox(firefox_profile, verbose);
#else
std::cout << "WARNING: Firefox profile file exists but sqlite is not installed" << std::endl;
#endif
}
if (std::filesystem::exists(chrome_profile)) {
#if defined(DROPOUT_DL_GCRYPT) & defined(DROPOUT_DL_SQLITE)
return get_cookies_from_chrome(chrome_profile, verbose);
#else
std::cout << "WARNING: Chrome profile file exists but libgcrypt or sqlite is not installed" << std::endl;
#endif
}
{
std::cerr << "ERROR: dropout.tv cookies could not be found" << std::endl;
exit(7);
}
}
int main(int argc, char** argv) {
2022-12-22 03:41:53 +00:00
dropout_dl::options options(argc, argv);
2022-12-22 03:41:53 +00:00
if (options.verbose) {
std::cout << "quality: " << options.quality << std::endl;
std::cout << "verbose: " << options.verbose << std::endl;
std::cout << "url: \"" << options.url << '"' << std::endl;
}
2022-12-22 03:41:53 +00:00
std::string firefox_profile;
std::string chrome_profile;
2022-12-22 03:41:53 +00:00
std::string video_data;
2022-09-11 00:19:41 +00:00
2022-12-22 03:41:53 +00:00
if (options.url.empty()) {
std::cout << "Enter episode url: ";
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-12-22 03:41:53 +00:00
if (!options.cookies_forced) {
options.cookies = get_cookies(options.verbose);
}
2022-09-11 00:19:41 +00:00
if (options.is_series) {
if (options.verbose) {
std::cout << "Getting series\n";
}
2022-12-22 03:41:53 +00:00
dropout_dl::series series(options.url, options.cookies);
2022-12-22 03:41:53 +00:00
series.download(options.quality, options.output_directory);
}
else if (options.is_season) {
if (options.verbose) {
std::cout << "Getting season\n";
}
2022-12-22 03:41:53 +00:00
dropout_dl::season season = dropout_dl::series::get_season(options.url, options.cookies);
season.download(options.quality, options.output_directory + "/" + season.series_name);
2022-12-22 03:41:53 +00:00
}
else if (options.is_episode) {
if (options.verbose) {
std::cout << "Getting episode\n";
}
2022-12-22 03:41:53 +00:00
dropout_dl::episode ep(options.url, options.cookies, options.verbose);
2022-12-22 03:41:53 +00:00
if (options.verbose) {
std::cout << "filename: " << options.filename << '\n';
}
2022-09-11 00:19:41 +00:00
2022-12-22 03:41:53 +00:00
if (!std::filesystem::is_directory(options.output_directory)) {
std::filesystem::create_directories(options.output_directory);
if (options.verbose) {
std::cout << "Creating series directory" << '\n';
}
}
2022-12-22 03:41:53 +00:00
ep.download(options.quality, options.output_directory, options.filename);
}
else {
std::cerr << "ERROR: Could not determine parsing type\n";
}
2022-09-11 00:19:41 +00:00
2022-12-22 03:41:53 +00:00
return 0;
2022-09-11 00:19:41 +00:00
}