diff --git a/src/episode.cpp b/src/episode.cpp index a6edad4..eb3b086 100644 --- a/src/episode.cpp +++ b/src/episode.cpp @@ -4,643 +4,644 @@ #include "episode.h" + namespace dropout_dl { - // dropout-dl helpers - bool substr_is(const std::string& string, int start, const std::string& test_str) { - if (test_str.size() != test_str.size()) - return false; + // dropout-dl helpers + bool substr_is(const std::string& string, int start, const std::string& test_str) { + if (test_str.size() != test_str.size()) + return false; - for (int i = start, j = 0; i < start + test_str.size(); i++, j++) { - if (string[i] != test_str[j]) { - return false; - } - } - return true; - } + for (int i = start, j = 0; i < start + test_str.size(); i++, j++) { + if (string[i] != test_str[j]) { + return false; + } + } + return true; + } - void replace_all(std::string& str, const std::string& from, const std::string& to) { - size_t start_pos = 0; - while((start_pos = str.find(from, start_pos)) != std::string::npos) { - str.replace(start_pos, from.length(), to); - start_pos += to.length(); // Handles case where 'to' is a substring of 'from' - } - } + void replace_all(std::string& str, const std::string& from, const std::string& to) { + size_t start_pos = 0; + while((start_pos = str.find(from, start_pos)) != std::string::npos) { + str.replace(start_pos, from.length(), to); + start_pos += to.length(); // Handles case where 'to' is a substring of 'from' + } + } - std::string remove_leading_and_following_whitespace(const std::string& str) { - int start = 0; - int end = str.length() - 1; + std::string remove_leading_and_following_whitespace(const std::string& str) { + int start = 0; + int end = str.length() - 1; - for (; str[start] == ' ' || str[start] == '\t' || str[start] == '\n'; start++); - for (; str[end] == ' ' || str[end] == '\t' || str[end] == '\n'; end--); + for (; str[start] == ' ' || str[start] == '\t' || str[start] == '\n'; start++); + for (; str[end] == ' ' || str[end] == '\t' || str[end] == '\n'; end--); - return str.substr(start, end - start + 1); - } + return str.substr(start, end - start + 1); + } - std::string replace_html_character_codes(const std::string& str) { - std::string out; + std::string replace_html_character_codes(const std::string& str) { + std::string out; - for (int i = 0; i < str.size(); i++) { - if (substr_is(str, i, "&#")) { - i += 2; - char code = 0; + for (int i = 0; i < str.size(); i++) { + if (substr_is(str, i, "&#")) { + i += 2; + char code = 0; - if (i > str.size() - 4) { - if (str[str.size() - 1] == ';') { - // Numerical character code length is two at the end of the string + if (i > str.size() - 4) { + if (str[str.size() - 1] == ';') { + // Numerical character code length is two at the end of the string - code = str[str.size() - 2] - '0'; - code += (str[str.size() - 3] - '0') * 10; - i += 2; - } - } - else { - if (str[i + 3] == ';') { - // Numerical character code length is three - code = str[i + 2] - '0'; - code += (str[i + 1] - '0') * 10; - code += (str[i] - '0') * 10; - i += 3; - } - else if (str[i + 2] == ';'){ - code = str[i + 1] - '0'; - code += (str[i] - '0') * 10; - i += 2; - } - else { - std::cerr << "HTML CHAR CODE ERROR: Code with numerical length of one used\n"; - exit(11); - } - } + code = str[str.size() - 2] - '0'; + code += (str[str.size() - 3] - '0') * 10; + i += 2; + } + } + else { + if (str[i + 3] == ';') { + // Numerical character code length is three + code = str[i + 2] - '0'; + code += (str[i + 1] - '0') * 10; + code += (str[i] - '0') * 10; + i += 3; + } + else if (str[i + 2] == ';'){ + code = str[i + 1] - '0'; + code += (str[i] - '0') * 10; + i += 2; + } + else { + std::cerr << "HTML CHAR CODE ERROR: Code with numerical length of one used\n"; + exit(11); + } + } - if (code < 32) { - std::cerr << "HTML CHAR CODE ERROR: Control Character Decoded. This is not supported and likely an error.\n"; - exit(11); - } + if (code < 32) { + std::cerr << "HTML CHAR CODE ERROR: Control Character Decoded. This is not supported and likely an error.\n"; + exit(11); + } - out += code; - } - else { - out += str[i]; - } - } + out += code; + } + else { + out += str[i]; + } + } - return out; - } + return out; + } - std::string format_name_string(const std::string& str) { - return replace_html_character_codes(remove_leading_and_following_whitespace(str)); - } + std::string format_name_string(const std::string& str) { + return replace_html_character_codes(remove_leading_and_following_whitespace(str)); + } - std::string format_filename(const std::string& str) { - std::string out; + std::string format_filename(const std::string& str) { + std::string out; - for (int i = 0; i < str.size(); i++) { - char c = str[i]; + for (int i = 0; i < str.size(); i++) { + char c = str[i]; - if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '.' || c == '/' || c == '-' || c == '_') { - out += c; - } - else if (c == ',' && str[i + 1] == ' ') { - out+= '-'; - i++; - } - else if (c == ',' || c == '\'' || c == ' ') { - out += '-'; - } - } + if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '.' || c == '/' || c == '-' || c == '_') { + out += c; + } + else if (c == ',' && str[i + 1] == ' ') { + out+= '-'; + i++; + } + else if (c == ',' || c == '\'' || c == ' ') { + out += '-'; + } + } - return out; - } + return out; + } #if defined(__WIN32__) - #include - msec_t time_ms(void) - { - return timeGetTime(); - } - #else - #include - long time_ms() - { - timeval tv{}; - gettimeofday(&tv, nullptr); - return tv.tv_sec * 1000 + tv.tv_usec / 1000; - } - #endif - - - long current_time; - long last_progress_timestamp; - - int curl_progress_func(void* filename, double total_to_download, double downloaded, double total_to_upload, double uploaded) { - const double number_chars = 50; - const char* full_character = "▓"; - const char* empty_character = "░"; - - current_time = time_ms(); - if (current_time - 50 > last_progress_timestamp) { - double percent_done = (downloaded / total_to_download) * number_chars; - double percent_done_clone = percent_done; - std::cout << *(std::string*)filename << " ["; - while (percent_done_clone-- > 0) { - std::cout << full_character; - } - while (percent_done++ < number_chars) { - std::cout << empty_character; - } - std::cout << "] " << downloaded / 1048576 << "MiB / " << total_to_download / 1048576 << "MiB "; - putchar('\r'); - last_progress_timestamp = time_ms(); - std::cout.flush(); - } - return 0; - } - - size_t WriteCallback(void *contents, size_t size, size_t nmemb, void *userp) - { - ((std::string*)userp)->append((char*)contents, size * nmemb); - return size * nmemb; - } - - // episode statics - std::string episode::get_series_name(const std::string& html_data) { - std::string series_title("series-title"); - std::string open_a_tag(""); - std::string close_a(""); - - for (int i = 0; i < html_data.size(); i++) { - if (substr_is(html_data, i, series_title)) { - for (int j = i + series_title.size(); j < html_data.size(); j++) { - if (html_data[j] == '\n' || html_data[j] == ' ' || html_data[j] == '\t') continue; - if (substr_is(html_data, j, open_a_tag)) { - for (int k = j + open_a_tag.size(); k < html_data.size(); k++) { - if (substr_is(html_data, k, close_tag)) { - k++; - for (int l = 0; l < html_data.size() - k; l++) { - if (substr_is(html_data, k + l, close_a)) { - return format_name_string(html_data.substr(k, l)); - } - } - } - } - } - } - } - } - return "ERROR"; - } - - std::string episode::get_episode_name(const std::string& html_data) { - int title_start = -1; - std::string video_title("video-title"); - std::string open_strong(""); - std::string close_strong(""); - for (int i = 0; i < html_data.size(); i++) { - if (substr_is(html_data, i, video_title)) { - for (int j = i; j < html_data.size(); j++) { - if (substr_is(html_data, j, open_strong)) { - title_start = j + open_strong.size(); - break; - } - } - for (int j = 0; j < html_data.size() - title_start; j++) { - if (substr_is(html_data, title_start + j, close_strong)) { - return format_name_string(html_data.substr(title_start, j)); - } - } - } - } - return "ERROR"; - } - - std::string episode::get_episode_number(const std::string& html_data) { - std::string episode("Episode"); - std::string close_a(""); - std::string episode_num; - for (int i = 0; i < html_data.size(); i++) { - if (substr_is(html_data, i, episode)) { - for (int j = i + 8; j < html_data.size(); j++) { - if (html_data[j] == '\n' || html_data[j] == ' ' || html_data[j] == '\t') continue; - if (substr_is(html_data, j, close_a)) { - return episode_num; - } - episode_num += html_data[j]; - } - } - } - return "-1"; - } - - std::string episode::get_embed_url(const std::string& html_data) { - std::string config("window.VHX.config"); - std::string embed_url("embed_url: "); - for (int i = 0; i < html_data.size(); i++) { - if (substr_is(html_data, i, config)) { - for (int j = i + config.size(); j < html_data.size(); j++) { - if (substr_is(html_data, j, embed_url)) { - for (int k = 0; k < html_data.size(); k++) { - if (html_data[k + j + embed_url.size() + 1] == '"') { - return html_data.substr(j + embed_url.size() + 1, k); - } - } - } - } - } - } - return ""; - } - - std::string episode::get_config_url(const std::string& html_data) { - std::string OTTdata("OTTData"); - std::string config_url("\"config_url\""); - int remaining_quotes = 1; - int url_start = -1; - for (int i = 0; i < html_data.size(); i++) { - if (substr_is(html_data, i, OTTdata)) { - for (int j = i + OTTdata.size(); j < html_data.size(); j++) { - if (substr_is(html_data, j, config_url)) { - for (int k = 0; k < html_data.size() - (i + OTTdata.size()); k++) { - char c = html_data[j + k + config_url.size()]; - if (remaining_quotes != 0) { - if (html_data[j + k + config_url.size()] == '"') { - remaining_quotes--; - } - continue; - } - else if (url_start == -1) { - url_start = j + k + config_url.size(); - } - - if (html_data[url_start + k] == '"') { - return html_data.substr(url_start, k); - } - } - } - } - } - } - return ""; - } - - std::string episode::get_episode_page(const std::string& url, const std::string& auth_cookie, const std::string& session_cookie, bool verbose) { - CURLcode ret; - CURL *hnd; - struct curl_slist *slist1; - - std::string episode_data; - - slist1 = nullptr; - slist1 = curl_slist_append(slist1, "Accept: text/html"); - slist1 = curl_slist_append(slist1, "Accept-Language: en-US,en"); - slist1 = curl_slist_append(slist1, "Accept-Encoding: utf-8"); - slist1 = curl_slist_append(slist1, "DNT: 1"); - slist1 = curl_slist_append(slist1, "Connection: keep-alive"); - slist1 = curl_slist_append(slist1, ("Cookie: locale_det=en; _session=" + session_cookie + "; __cf_bm=" + auth_cookie).c_str()); - slist1 = curl_slist_append(slist1, "Upgrade-Insecure-Requests: 1"); - slist1 = curl_slist_append(slist1, "Sec-Fetch-Dest: document"); - slist1 = curl_slist_append(slist1, "Sec-Fetch-Mode: navigate"); - slist1 = curl_slist_append(slist1, "Sec-Fetch-Site: cross-site"); - slist1 = curl_slist_append(slist1, "Sec-GPC: 1"); - - hnd = curl_easy_init(); - curl_easy_setopt(hnd, CURLOPT_BUFFERSIZE, 102400L); - curl_easy_setopt(hnd, CURLOPT_URL, url.c_str()); - curl_easy_setopt(hnd, CURLOPT_NOPROGRESS, 1L); - curl_easy_setopt(hnd, CURLOPT_HTTPHEADER, slist1); - curl_easy_setopt(hnd, CURLOPT_MAXREDIRS, 50L); - curl_easy_setopt(hnd, CURLOPT_HTTP_VERSION, (long)CURL_HTTP_VERSION_2TLS); - curl_easy_setopt(hnd, CURLOPT_FTP_SKIP_PASV_IP, 1L); - curl_easy_setopt(hnd, CURLOPT_TCP_KEEPALIVE, 1L); - curl_easy_setopt(hnd, CURLOPT_VERBOSE, verbose); - - curl_easy_setopt(hnd, CURLOPT_WRITEFUNCTION, WriteCallback); - curl_easy_setopt(hnd, CURLOPT_WRITEDATA, &episode_data); - /* Here is a list of options the curl code used that cannot get generated - as source easily. You may choose to either not use them or implement - them yourself. - - CURLOPT_WRITEDATA set to a objectpointer - CURLOPT_INTERLEAVEDATA set to a objectpointer - CURLOPT_WRITEFUNCTION set to a functionpointer - CURLOPT_READDATA set to a objectpointer - CURLOPT_READFUNCTION set to a functionpointer - CURLOPT_SEEKDATA set to a objectpointer - CURLOPT_SEEKFUNCTION set to a functionpointer - CURLOPT_ERRORBUFFER set to a objectpointer - CURLOPT_STDERR set to a objectpointer - CURLOPT_HEADERFUNCTION set to a functionpointer - CURLOPT_HEADERDATA set to a objectpointer - - */ - - ret = curl_easy_perform(hnd); - - curl_easy_cleanup(hnd); - hnd = nullptr; - curl_slist_free_all(slist1); - slist1 = nullptr; - - return episode_data; - } - - std::string get_generic_page(const std::string& url, bool verbose) { - CURL *hnd; - struct curl_slist *slist1; - - std::string config_page; - - slist1 = nullptr; - slist1 = curl_slist_append(slist1, "Accept: text/html"); - slist1 = curl_slist_append(slist1, "Accept-Language: en-US,en"); - slist1 = curl_slist_append(slist1, "Accept-Encoding: utf-8"); - slist1 = curl_slist_append(slist1, "DNT: 1"); - slist1 = curl_slist_append(slist1, "Connection: keep-alive"); - slist1 = curl_slist_append(slist1, "Referer: https://www.dropout.tv/"); - slist1 = curl_slist_append(slist1, "Upgrade-Insecure-Requests: 1"); - slist1 = curl_slist_append(slist1, "Sec-Fetch-Dest: iframe"); - slist1 = curl_slist_append(slist1, "Sec-Fetch-Mode: navigate"); - slist1 = curl_slist_append(slist1, "Sec-Fetch-Site: cross-site"); - slist1 = curl_slist_append(slist1, "Sec-GPC: 1"); - - hnd = curl_easy_init(); - curl_easy_setopt(hnd, CURLOPT_BUFFERSIZE, 102400L); - curl_easy_setopt(hnd, CURLOPT_URL, url.c_str()); - curl_easy_setopt(hnd, CURLOPT_NOPROGRESS, 1L); - curl_easy_setopt(hnd, CURLOPT_HTTPHEADER, slist1); - curl_easy_setopt(hnd, CURLOPT_MAXREDIRS, 50L); - curl_easy_setopt(hnd, CURLOPT_HTTP_VERSION, (long)CURL_HTTP_VERSION_2TLS); - curl_easy_setopt(hnd, CURLOPT_FTP_SKIP_PASV_IP, 1L); - curl_easy_setopt(hnd, CURLOPT_TCP_KEEPALIVE, 1L); - curl_easy_setopt(hnd, CURLOPT_VERBOSE, verbose); - - curl_easy_setopt(hnd, CURLOPT_WRITEFUNCTION, WriteCallback); - curl_easy_setopt(hnd, CURLOPT_WRITEDATA, &config_page); - - curl_easy_perform(hnd); - - curl_easy_cleanup(hnd); - hnd = nullptr; - curl_slist_free_all(slist1); - slist1 = nullptr; - - return config_page; - } - - std::vector episode::get_qualities() { - if (!qualities.empty()) { - return qualities; - } - int i = 0; - bool video_section = false; - - const std::string quality_marker = R"("quality":")"; - for (; i < config_data.size(); i++ ) { - // std::cout << i << "/" << javascript_data.size() << ": " << javascript_data[i] << ": " << javascript_data.substr(i, 17) << ": " << video_section << "\n"; - if (config_data.substr(i, 9) == "video/mp4") { - video_section = true; - } - - if (video_section && config_data.substr(i, quality_marker.size()) == quality_marker) { - i += quality_marker.size(); - for (int j = 0; j + i < config_data.size(); j++) { - if (config_data[i + j] == '"') { - this->qualities.push_back(config_data.substr(i, j)); - if (this->verbose) { - std::cout << "Found quality (" << i << " + " << j << "): " << qualities.back() << std::endl; - } - break; - } - } - for (int j = i; j > 0; j--) { - // std::cout << i << ": " << javascript_data[i] << ": " << javascript_data.substr(i-7, 7) << "\n"; - if (this->config_data.substr(j-7, 7) == R"("url":")") { - for (int k = 0; k < i - j; k++) { - if (config_data[j + k] == '"') { - this->quality_urls.emplace_back(config_data.substr(j, k)); - if (this->verbose) { - std::cout << "Found url (" << j << " + " << k << "): " << quality_urls.back() - << std::endl; - } - break; - } - } - break; - } - } - } - } - return qualities; - } - - std::string episode::get_video_url(const std::string& quality) { - for (int i = 0; i < qualities.size(); i++) { - if (qualities[i] == quality) { - return quality_urls[i]; - } - } - std::cerr << "ERROR: quality of " << quality << " not found\nPossible qualities: "; - for (int i = 0; i < qualities.size(); i++) { - std::cerr << qualities[i]; - if (i != qualities.size() - 1) { - std::cerr << ", "; - } - } - exit(6); - } - - std::string episode::get_video_data(const std::string &quality, const std::string& filename) { - CURL* curl = curl_easy_init(); - CURLcode res; - if(curl) { - std::string out; - - - - curl_easy_setopt(curl, CURLOPT_URL, get_video_url(quality).c_str()); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, dropout_dl::WriteCallback); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &out); - curl_easy_setopt(curl, CURLOPT_NOPROGRESS, false); - curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, dropout_dl::curl_progress_func); - curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, &filename); - res = curl_easy_perform(curl); - curl_easy_cleanup(curl); - - return out; - } - return "CURL ERROR"; - } - - - void episode::download(const std::string& quality, const std::string& series_directory, std::string filename) { - if (filename.empty()) { - filename = "E" + (this->episode_number.size() < 2 ? "0" + this->episode_number : this->episode_number) + this->name + - ".mp4"; - - filename = format_filename(filename); - } - - if (quality == "all") { - for (const auto &possible_quality: this->qualities) { - if (!std::filesystem::is_directory(series_directory + "/" + possible_quality)) { - std::filesystem::create_directories(series_directory + "/" + possible_quality); - if (this->verbose) { - std::cout << "Creating quality directory" << '\n'; - } - } - std::fstream out(series_directory + "/" + possible_quality + "/" + filename, - std::ios_base::in | std::ios_base::out | std::ios_base::trunc); - - out << this->get_video_data(possible_quality, series_directory + "/" + possible_quality + "/" + filename) << std::endl; - } - } else { - if (!std::filesystem::is_directory(series_directory)) { - std::filesystem::create_directories(series_directory); - if (this->verbose) { - std::cout << "Creating quality directory" << '\n'; - } - } - - std::fstream out(series_directory + "/" + filename, - std::ios_base::in | std::ios_base::out | std::ios_base::trunc); - - out << this->get_video_data(quality, series_directory + "/" + filename) << std::endl; - } - } - - - - // Cookie functions - - void cookie::get_value_from_db(sqlite3 *db, const std::string &sql_query_base, const std::string& value, bool verbose, int (*callback)(void*,int,char**,char**)) { - #ifdef DROPOUT_DL_SQLITE - std::string sql_mod_base = sql_query_base; - - if (sql_mod_base.find("WHERE") == std::string::npos) { - sql_mod_base += " WHERE "; - } - else { - sql_mod_base += " AND "; - } - - sql_mod_base += "name='" + this->name + "';"; - - std::string sql_value_query = "SELECT " + value + ' ' + sql_mod_base; - std::string sql_length_query = "SELECT length(" + value + ") " + sql_mod_base; - std::string tmp; - char *err_code = nullptr; - int rc; - - if (verbose) { - std::cout << sql_value_query << '\n' << sql_length_query << std::endl; - } - - rc = sqlite3_exec(db, sql_length_query.c_str(), callback, &tmp, &err_code); - - if (rc != SQLITE_OK) { - fprintf(stderr, "SQL error: %s\n", err_code); - sqlite3_free(err_code); - sqlite3_close(db); - exit(3); - } else if (verbose) { - std::cout << "Got " << this->name << " cookie length\n"; - } - - if (tmp.empty()) { - std::cerr << "COOKIE SQLITE ERROR: No Cookie With Name " << this->name << " Exists\n"; - exit(0); - } - this->len = std::stoi(tmp); + #include + msec_t time_ms(void) + { + return timeGetTime(); + } + #else + #include + long time_ms() + { + timeval tv{}; + gettimeofday(&tv, nullptr); + return tv.tv_sec * 1000 + tv.tv_usec / 1000; + } + #endif + + + long current_time; + long last_progress_timestamp; + + int curl_progress_func(void* filename, double total_to_download, double downloaded, double total_to_upload, double uploaded) { + const double number_chars = 50; + const char* full_character = "▓"; + const char* empty_character = "░"; + + current_time = time_ms(); + if (current_time - 50 > last_progress_timestamp) { + double percent_done = (downloaded / total_to_download) * number_chars; + double percent_done_clone = percent_done; + std::cout << *(std::string*)filename << " ["; + while (percent_done_clone-- > 0) { + std::cout << full_character; + } + while (percent_done++ < number_chars) { + std::cout << empty_character; + } + std::cout << "] " << downloaded / 1048576 << "MiB / " << total_to_download / 1048576 << "MiB "; + putchar('\r'); + last_progress_timestamp = time_ms(); + std::cout.flush(); + } + return 0; + } + + size_t WriteCallback(void *contents, size_t size, size_t nmemb, void *userp) + { + ((std::string*)userp)->append((char*)contents, size * nmemb); + return size * nmemb; + } + + // episode statics + std::string episode::get_series_name(const std::string& html_data) { + std::string series_title("series-title"); + std::string open_a_tag(""); + std::string close_a(""); + + for (int i = 0; i < html_data.size(); i++) { + if (substr_is(html_data, i, series_title)) { + for (int j = i + series_title.size(); j < html_data.size(); j++) { + if (html_data[j] == '\n' || html_data[j] == ' ' || html_data[j] == '\t') continue; + if (substr_is(html_data, j, open_a_tag)) { + for (int k = j + open_a_tag.size(); k < html_data.size(); k++) { + if (substr_is(html_data, k, close_tag)) { + k++; + for (int l = 0; l < html_data.size() - k; l++) { + if (substr_is(html_data, k + l, close_a)) { + return format_name_string(html_data.substr(k, l)); + } + } + } + } + } + } + } + } + return "ERROR"; + } + + std::string episode::get_episode_name(const std::string& html_data) { + int title_start = -1; + std::string video_title("video-title"); + std::string open_strong(""); + std::string close_strong(""); + for (int i = 0; i < html_data.size(); i++) { + if (substr_is(html_data, i, video_title)) { + for (int j = i; j < html_data.size(); j++) { + if (substr_is(html_data, j, open_strong)) { + title_start = j + open_strong.size(); + break; + } + } + for (int j = 0; j < html_data.size() - title_start; j++) { + if (substr_is(html_data, title_start + j, close_strong)) { + return format_name_string(html_data.substr(title_start, j)); + } + } + } + } + return "ERROR"; + } + + std::string episode::get_episode_number(const std::string& html_data) { + std::string episode("Episode"); + std::string close_a(""); + std::string episode_num; + for (int i = 0; i < html_data.size(); i++) { + if (substr_is(html_data, i, episode)) { + for (int j = i + 8; j < html_data.size(); j++) { + if (html_data[j] == '\n' || html_data[j] == ' ' || html_data[j] == '\t') continue; + if (substr_is(html_data, j, close_a)) { + return episode_num; + } + episode_num += html_data[j]; + } + } + } + return "-1"; + } + + std::string episode::get_embed_url(const std::string& html_data) { + std::string config("window.VHX.config"); + std::string embed_url("embed_url: "); + for (int i = 0; i < html_data.size(); i++) { + if (substr_is(html_data, i, config)) { + for (int j = i + config.size(); j < html_data.size(); j++) { + if (substr_is(html_data, j, embed_url)) { + for (int k = 0; k < html_data.size(); k++) { + if (html_data[k + j + embed_url.size() + 1] == '"') { + return html_data.substr(j + embed_url.size() + 1, k); + } + } + } + } + } + } + return ""; + } + + std::string episode::get_config_url(const std::string& html_data) { + std::string OTTdata("OTTData"); + std::string config_url("\"config_url\""); + int remaining_quotes = 1; + int url_start = -1; + for (int i = 0; i < html_data.size(); i++) { + if (substr_is(html_data, i, OTTdata)) { + for (int j = i + OTTdata.size(); j < html_data.size(); j++) { + if (substr_is(html_data, j, config_url)) { + for (int k = 0; k < html_data.size() - (i + OTTdata.size()); k++) { + char c = html_data[j + k + config_url.size()]; + if (remaining_quotes != 0) { + if (html_data[j + k + config_url.size()] == '"') { + remaining_quotes--; + } + continue; + } + else if (url_start == -1) { + url_start = j + k + config_url.size(); + } + + if (html_data[url_start + k] == '"') { + return html_data.substr(url_start, k); + } + } + } + } + } + } + return ""; + } + + std::string episode::get_episode_page(const std::string& url, const std::string& auth_cookie, const std::string& session_cookie, bool verbose) { + CURLcode ret; + CURL *hnd; + struct curl_slist *slist1; + + std::string episode_data; + + slist1 = nullptr; + slist1 = curl_slist_append(slist1, "Accept: text/html"); + slist1 = curl_slist_append(slist1, "Accept-Language: en-US,en"); + slist1 = curl_slist_append(slist1, "Accept-Encoding: utf-8"); + slist1 = curl_slist_append(slist1, "DNT: 1"); + slist1 = curl_slist_append(slist1, "Connection: keep-alive"); + slist1 = curl_slist_append(slist1, ("Cookie: locale_det=en; _session=" + session_cookie + "; __cf_bm=" + auth_cookie).c_str()); + slist1 = curl_slist_append(slist1, "Upgrade-Insecure-Requests: 1"); + slist1 = curl_slist_append(slist1, "Sec-Fetch-Dest: document"); + slist1 = curl_slist_append(slist1, "Sec-Fetch-Mode: navigate"); + slist1 = curl_slist_append(slist1, "Sec-Fetch-Site: cross-site"); + slist1 = curl_slist_append(slist1, "Sec-GPC: 1"); + + hnd = curl_easy_init(); + curl_easy_setopt(hnd, CURLOPT_BUFFERSIZE, 102400L); + curl_easy_setopt(hnd, CURLOPT_URL, url.c_str()); + curl_easy_setopt(hnd, CURLOPT_NOPROGRESS, 1L); + curl_easy_setopt(hnd, CURLOPT_HTTPHEADER, slist1); + curl_easy_setopt(hnd, CURLOPT_MAXREDIRS, 50L); + curl_easy_setopt(hnd, CURLOPT_HTTP_VERSION, (long)CURL_HTTP_VERSION_2TLS); + curl_easy_setopt(hnd, CURLOPT_FTP_SKIP_PASV_IP, 1L); + curl_easy_setopt(hnd, CURLOPT_TCP_KEEPALIVE, 1L); + curl_easy_setopt(hnd, CURLOPT_VERBOSE, verbose); + + curl_easy_setopt(hnd, CURLOPT_WRITEFUNCTION, WriteCallback); + curl_easy_setopt(hnd, CURLOPT_WRITEDATA, &episode_data); + /* Here is a list of options the curl code used that cannot get generated + as source easily. You may choose to either not use them or implement + them yourself. + + CURLOPT_WRITEDATA set to a objectpointer + CURLOPT_INTERLEAVEDATA set to a objectpointer + CURLOPT_WRITEFUNCTION set to a functionpointer + CURLOPT_READDATA set to a objectpointer + CURLOPT_READFUNCTION set to a functionpointer + CURLOPT_SEEKDATA set to a objectpointer + CURLOPT_SEEKFUNCTION set to a functionpointer + CURLOPT_ERRORBUFFER set to a objectpointer + CURLOPT_STDERR set to a objectpointer + CURLOPT_HEADERFUNCTION set to a functionpointer + CURLOPT_HEADERDATA set to a objectpointer + + */ + + ret = curl_easy_perform(hnd); + + curl_easy_cleanup(hnd); + hnd = nullptr; + curl_slist_free_all(slist1); + slist1 = nullptr; + + return episode_data; + } + + std::string get_generic_page(const std::string& url, bool verbose) { + CURL *hnd; + struct curl_slist *slist1; + + std::string config_page; + + slist1 = nullptr; + slist1 = curl_slist_append(slist1, "Accept: text/html"); + slist1 = curl_slist_append(slist1, "Accept-Language: en-US,en"); + slist1 = curl_slist_append(slist1, "Accept-Encoding: utf-8"); + slist1 = curl_slist_append(slist1, "DNT: 1"); + slist1 = curl_slist_append(slist1, "Connection: keep-alive"); + slist1 = curl_slist_append(slist1, "Referer: https://www.dropout.tv/"); + slist1 = curl_slist_append(slist1, "Upgrade-Insecure-Requests: 1"); + slist1 = curl_slist_append(slist1, "Sec-Fetch-Dest: iframe"); + slist1 = curl_slist_append(slist1, "Sec-Fetch-Mode: navigate"); + slist1 = curl_slist_append(slist1, "Sec-Fetch-Site: cross-site"); + slist1 = curl_slist_append(slist1, "Sec-GPC: 1"); + + hnd = curl_easy_init(); + curl_easy_setopt(hnd, CURLOPT_BUFFERSIZE, 102400L); + curl_easy_setopt(hnd, CURLOPT_URL, url.c_str()); + curl_easy_setopt(hnd, CURLOPT_NOPROGRESS, 1L); + curl_easy_setopt(hnd, CURLOPT_HTTPHEADER, slist1); + curl_easy_setopt(hnd, CURLOPT_MAXREDIRS, 50L); + curl_easy_setopt(hnd, CURLOPT_HTTP_VERSION, (long)CURL_HTTP_VERSION_2TLS); + curl_easy_setopt(hnd, CURLOPT_FTP_SKIP_PASV_IP, 1L); + curl_easy_setopt(hnd, CURLOPT_TCP_KEEPALIVE, 1L); + curl_easy_setopt(hnd, CURLOPT_VERBOSE, verbose); + + curl_easy_setopt(hnd, CURLOPT_WRITEFUNCTION, WriteCallback); + curl_easy_setopt(hnd, CURLOPT_WRITEDATA, &config_page); + + curl_easy_perform(hnd); + + curl_easy_cleanup(hnd); + hnd = nullptr; + curl_slist_free_all(slist1); + slist1 = nullptr; + + return config_page; + } + + std::vector episode::get_qualities() { + if (!qualities.empty()) { + return qualities; + } + int i = 0; + bool video_section = false; + + const std::string quality_marker = R"("quality":")"; + for (; i < config_data.size(); i++ ) { + // std::cout << i << "/" << javascript_data.size() << ": " << javascript_data[i] << ": " << javascript_data.substr(i, 17) << ": " << video_section << "\n"; + if (config_data.substr(i, 9) == "video/mp4") { + video_section = true; + } + + if (video_section && config_data.substr(i, quality_marker.size()) == quality_marker) { + i += quality_marker.size(); + for (int j = 0; j + i < config_data.size(); j++) { + if (config_data[i + j] == '"') { + this->qualities.push_back(config_data.substr(i, j)); + if (this->verbose) { + std::cout << "Found quality (" << i << " + " << j << "): " << qualities.back() << std::endl; + } + break; + } + } + for (int j = i; j > 0; j--) { + // std::cout << i << ": " << javascript_data[i] << ": " << javascript_data.substr(i-7, 7) << "\n"; + if (this->config_data.substr(j-7, 7) == R"("url":")") { + for (int k = 0; k < i - j; k++) { + if (config_data[j + k] == '"') { + this->quality_urls.emplace_back(config_data.substr(j, k)); + if (this->verbose) { + std::cout << "Found url (" << j << " + " << k << "): " << quality_urls.back() + << std::endl; + } + break; + } + } + break; + } + } + } + } + return qualities; + } + + std::string episode::get_video_url(const std::string& quality) { + for (int i = 0; i < qualities.size(); i++) { + if (qualities[i] == quality) { + return quality_urls[i]; + } + } + std::cerr << "ERROR: quality of " << quality << " not found\nPossible qualities: "; + for (int i = 0; i < qualities.size(); i++) { + std::cerr << qualities[i]; + if (i != qualities.size() - 1) { + std::cerr << ", "; + } + } + exit(6); + } + + std::string episode::get_video_data(const std::string &quality, const std::string& filename) { + CURL* curl = curl_easy_init(); + CURLcode res; + if(curl) { + std::string out; + + + + curl_easy_setopt(curl, CURLOPT_URL, get_video_url(quality).c_str()); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, dropout_dl::WriteCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &out); + curl_easy_setopt(curl, CURLOPT_NOPROGRESS, false); + curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, dropout_dl::curl_progress_func); + curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, &filename); + res = curl_easy_perform(curl); + curl_easy_cleanup(curl); + + return out; + } + return "CURL ERROR"; + } + + + void episode::download(const std::string& quality, const std::string& series_directory, std::string filename) { + if (filename.empty()) { + filename = "E" + (this->episode_number.size() < 2 ? "0" + this->episode_number : this->episode_number) + this->name + + ".mp4"; + + filename = format_filename(filename); + } + + if (quality == "all") { + for (const auto &possible_quality: this->qualities) { + if (!std::filesystem::is_directory(series_directory + "/" + possible_quality)) { + std::filesystem::create_directories(series_directory + "/" + possible_quality); + if (this->verbose) { + std::cout << "Creating quality directory" << '\n'; + } + } + std::fstream out(series_directory + "/" + possible_quality + "/" + filename, + std::ios_base::in | std::ios_base::out | std::ios_base::trunc); + + out << this->get_video_data(possible_quality, series_directory + "/" + possible_quality + "/" + filename) << std::endl; + } + } else { + if (!std::filesystem::is_directory(series_directory)) { + std::filesystem::create_directories(series_directory); + if (this->verbose) { + std::cout << "Creating quality directory" << '\n'; + } + } + + std::fstream out(series_directory + "/" + filename, + std::ios_base::in | std::ios_base::out | std::ios_base::trunc); + + out << this->get_video_data(quality, series_directory + "/" + filename) << std::endl; + } + } + + + + // Cookie functions + + void cookie::get_value_from_db(sqlite3 *db, const std::string &sql_query_base, const std::string& value, bool verbose, int (*callback)(void*,int,char**,char**)) { + #ifdef DROPOUT_DL_SQLITE + std::string sql_mod_base = sql_query_base; + + if (sql_mod_base.find("WHERE") == std::string::npos) { + sql_mod_base += " WHERE "; + } + else { + sql_mod_base += " AND "; + } + + sql_mod_base += "name='" + this->name + "';"; + + std::string sql_value_query = "SELECT " + value + ' ' + sql_mod_base; + std::string sql_length_query = "SELECT length(" + value + ") " + sql_mod_base; + std::string tmp; + char *err_code = nullptr; + int rc; + + if (verbose) { + std::cout << sql_value_query << '\n' << sql_length_query << std::endl; + } + + rc = sqlite3_exec(db, sql_length_query.c_str(), callback, &tmp, &err_code); + + if (rc != SQLITE_OK) { + fprintf(stderr, "SQL error: %s\n", err_code); + sqlite3_free(err_code); + sqlite3_close(db); + exit(3); + } else if (verbose) { + std::cout << "Got " << this->name << " cookie length\n"; + } + + if (tmp.empty()) { + std::cerr << "COOKIE SQLITE ERROR: No Cookie With Name " << this->name << " Exists\n"; + exit(0); + } + this->len = std::stoi(tmp); - rc = sqlite3_exec(db, sql_value_query.c_str(), callback, &tmp, &err_code); + rc = sqlite3_exec(db, sql_value_query.c_str(), callback, &tmp, &err_code); - if (rc != SQLITE_OK) { - fprintf(stderr, "SQL error: %s\n", err_code); - sqlite3_free(err_code); - sqlite3_close(db); - exit(3); - } else if (verbose) { - std::cout << "Got " << this->name << " cookie\n"; - } + if (rc != SQLITE_OK) { + fprintf(stderr, "SQL error: %s\n", err_code); + sqlite3_free(err_code); + sqlite3_close(db); + exit(3); + } else if (verbose) { + std::cout << "Got " << this->name << " cookie\n"; + } - this->value = tmp; - #else - std::cerr << "COOKIE ERROR: Attempted to get cookies from sqlite without having sqlite installed\n"; - exit(12); - #endif - } + this->value = tmp; + #else + std::cerr << "COOKIE ERROR: Attempted to get cookies from sqlite without having sqlite installed\n"; + exit(12); + #endif + } - void cookie::format_from_chrome() { - this->value = this->value.substr(3); - this->len -= 3; - } + void cookie::format_from_chrome() { + this->value = this->value.substr(3); + this->len -= 3; + } - void cookie::chrome_decrypt(const std::string &password, int iterations, const std::string &salt, int length) { + void cookie::chrome_decrypt(const std::string &password, int iterations, const std::string &salt, int length) { - #ifdef DROPOUT_DL_GCRYPT - this->format_from_chrome(); + #ifdef DROPOUT_DL_GCRYPT + this->format_from_chrome(); - uint8_t key[32]; - - char output[this->len + 2]; + uint8_t key[32]; + + char output[this->len + 2]; - char iv[16]; + char iv[16]; - for (char& c : iv) { - c = ' '; - } + for (char& c : iv) { + c = ' '; + } - for (char& c : output) { - c = 0; - } + for (char& c : output) { + c = 0; + } - gcry_kdf_derive(password.c_str(), password.size(), GCRY_KDF_PBKDF2, GCRY_KDF_ARGON2ID, salt.c_str(), salt.size(), iterations, length, key); + gcry_kdf_derive(password.c_str(), password.size(), GCRY_KDF_PBKDF2, GCRY_KDF_ARGON2ID, salt.c_str(), salt.size(), iterations, length, key); - gcry_cipher_hd_t handle; + gcry_cipher_hd_t handle; - gcry_cipher_open(&handle, GCRY_CIPHER_AES, GCRY_CIPHER_MODE_CBC, 0); + gcry_cipher_open(&handle, GCRY_CIPHER_AES, GCRY_CIPHER_MODE_CBC, 0); - gcry_cipher_setkey(handle, (const void*) &key, length); + gcry_cipher_setkey(handle, (const void*) &key, length); - gcry_cipher_setiv(handle, (const void*)&iv, 16); + gcry_cipher_setiv(handle, (const void*)&iv, 16); - unsigned long err = gcry_cipher_decrypt(handle, output, this->len, this->value.c_str(), this->len); + unsigned long err = gcry_cipher_decrypt(handle, output, this->len, this->value.c_str(), this->len); - if (err) { - std::cout << gcry_strerror(err) << std::endl; - exit(2); - } + if (err) { + std::cout << gcry_strerror(err) << std::endl; + exit(2); + } - this->value = output; + this->value = output; - this->url_decode(); + this->url_decode(); - this->value = this->value.substr(0, this->len - 7); - this->len -= 7; - #else - std::cerr << "CHROME COOKIE ERROR: Attempted to Decrypt Chrome Cookie Without libgcrypt\n"; - exit(12); - #endif - } + this->value = this->value.substr(0, this->len - 7); + this->len -= 7; + #else + std::cerr << "CHROME COOKIE ERROR: Attempted to Decrypt Chrome Cookie Without libgcrypt\n"; + exit(12); + #endif + } - void cookie::url_decode() { - std::string out; + void cookie::url_decode() { + std::string out; - for (int i = 0; i < this->value.size() - 3; i++) { - if (substr_is(this->value, i, "%3D")) { - out += "="; - i += 2; - } - else { - out += this->value[i]; - } - } + for (int i = 0; i < this->value.size() - 3; i++) { + if (substr_is(this->value, i, "%3D")) { + out += "="; + i += 2; + } + else { + out += this->value[i]; + } + } - this->value = out; - this->len = out.size(); - } -} // dropout_dl \ No newline at end of file + this->value = out; + this->len = out.size(); + } +} // dropout_dl diff --git a/src/episode.h b/src/episode.h index d6f3820..0c6b8fe 100644 --- a/src/episode.h +++ b/src/episode.h @@ -16,444 +16,444 @@ namespace dropout_dl { - class cookie { - public: - /** - * - * @param data - A string to write to return value of the command to - * @param argc - The number of results from the command - * @param argv - The results of the command - * @param azColName - The column names from the command - * @return 0 on success or -1 on failure - * - * Used by sqlite. Write the first result of the sqlite command to the data string. If there are no results print an error and return -1. - */ - static int sqlite_write_callback(void* data, int argc, char** argv, char** azColName) - { - if (argc < 1) { - std::cerr << "SQLITE ERROR: sqlite could not find desired cookie" << std::endl; - return -1; - } - else { - *(std::string*)data = argv[0]; - return 0; - } - } + class cookie { + public: + /** + * + * @param data - A string to write to return value of the command to + * @param argc - The number of results from the command + * @param argv - The results of the command + * @param azColName - The column names from the command + * @return 0 on success or -1 on failure + * + * Used by sqlite. Write the first result of the sqlite command to the data string. If there are no results print an error and return -1. + */ + static int sqlite_write_callback(void* data, int argc, char** argv, char** azColName) + { + if (argc < 1) { + std::cerr << "SQLITE ERROR: sqlite could not find desired cookie" << std::endl; + return -1; + } + else { + *(std::string*)data = argv[0]; + return 0; + } + } - /** - * The name of the value from the sqlite database or "?" if not set. - */ - std::string name; - /** - * The value of the cookie - */ - std::string value; - /** - * The length of the value of the cookie - */ - int len; + /** + * The name of the value from the sqlite database or "?" if not set. + */ + std::string name; + /** + * The value of the cookie + */ + std::string value; + /** + * The length of the value of the cookie + */ + int len; - /** - * - * @param name - Name of the value from the sqlite database - * - * Create a cookie with no value and length of 0 - */ - explicit cookie(const std::string& name) { - this->name = name; - this->value = ""; - this->len = 0; - } + /** + * + * @param name - Name of the value from the sqlite database + * + * Create a cookie with no value and length of 0 + */ + explicit cookie(const std::string& name) { + this->name = name; + this->value = ""; + this->len = 0; + } - /** - * - * @param name - Name of the value from the sqlite database - * @param cookie - Value of the cookie - * - * Sets the name and value using the parameters and gets the length from the value - */ - cookie(const std::string& name, const std::string& cookie) { - this->name = name; - this->value = cookie; - this->len = cookie.size(); - } + /** + * + * @param name - Name of the value from the sqlite database + * @param cookie - Value of the cookie + * + * Sets the name and value using the parameters and gets the length from the value + */ + cookie(const std::string& name, const std::string& cookie) { + this->name = name; + this->value = cookie; + this->len = cookie.size(); + } - /** - * - * @param cookie - Value of the cookie - * @param length - Length of the cookie - * - * Sets the value and length using the parameters and sets the name as "?" - */ - cookie(const std::string& cookie, int length) { - this->value = cookie; - this->name = "?"; - this->len = length; - } + /** + * + * @param cookie - Value of the cookie + * @param length - Length of the cookie + * + * Sets the value and length using the parameters and sets the name as "?" + */ + cookie(const std::string& cookie, int length) { + this->value = cookie; + this->name = "?"; + this->len = length; + } - /** - * - * @param name - Name of the value from the sqlite database - * @param cookie - Value of the cookie - * @param length - Length of the cookie - * - * Sets the name, value, and length using the parameters leaving nothing unset. - */ - cookie(const std::string& name, const std::string& cookie, int length) { - this->name = name; - this->value = cookie; - this->len = length; - } + /** + * + * @param name - Name of the value from the sqlite database + * @param cookie - Value of the cookie + * @param length - Length of the cookie + * + * Sets the name, value, and length using the parameters leaving nothing unset. + */ + cookie(const std::string& name, const std::string& cookie, int length) { + this->name = name; + this->value = cookie; + this->len = length; + } - /** - * - * @param db - An sqlite3 database - * @param sql_query_base - A base without the name search e.g. "FROM cookies" this function would then append the text "SELECT " and "WHERE name=''" - * @param value - The name of the value to fill the cookie with - * - * Retrieve the value of a cookie from the provided sqlite database. - * - */ - void get_value_from_db(sqlite3* db, const std::string& sql_query_base, const std::string& value, bool verbose = false, int (*callback)(void*,int,char**,char**) = sqlite_write_callback); + /** + * + * @param db - An sqlite3 database + * @param sql_query_base - A base without the name search e.g. "FROM cookies" this function would then append the text "SELECT " and "WHERE name=''" + * @param value - The name of the value to fill the cookie with + * + * Retrieve the value of a cookie from the provided sqlite database. + * + */ + void get_value_from_db(sqlite3* db, const std::string& sql_query_base, const std::string& value, bool verbose = false, int (*callback)(void*,int,char**,char**) = sqlite_write_callback); - /** - * - * @param password - Default is "peanuts". This works for linux. The password should be keychain password on MacOS - * @param salt - Salt is "saltysalt" for both MacOS and Linux - * @param length - Length of 16 is standard for both MacOS and Linux - * @param iterations - 1 on linux and 1003 on MacOS - * - * Decrypt chrome cookies and format them to be usable as regular cookies. Currently this has only been tested for the _session and __cf_bm cookies from dropout.tv but I see no reason this would not work for anything else. - */ - void chrome_decrypt(const std::string& password = "peanuts", int iterations = 1, const std::string& salt = "saltysalt", int length = 16); + /** + * + * @param password - Default is "peanuts". This works for linux. The password should be keychain password on MacOS + * @param salt - Salt is "saltysalt" for both MacOS and Linux + * @param length - Length of 16 is standard for both MacOS and Linux + * @param iterations - 1 on linux and 1003 on MacOS + * + * Decrypt chrome cookies and format them to be usable as regular cookies. Currently this has only been tested for the _session and __cf_bm cookies from dropout.tv but I see no reason this would not work for anything else. + */ + void chrome_decrypt(const std::string& password = "peanuts", int iterations = 1, const std::string& salt = "saltysalt", int length = 16); - private: - /** - * Remove url encoded text from a cookie. Currently this only checks for %3D ('=') as that is the only thing I've come across in cookies during the entirety of this project. - */ - void url_decode(); + private: + /** + * Remove url encoded text from a cookie. Currently this only checks for %3D ('=') as that is the only thing I've come across in cookies during the entirety of this project. + */ + void url_decode(); - /** - * Remove the leading version (e.g. "v10") from the cookie - */ - void format_from_chrome(); - }; + /** + * Remove the leading version (e.g. "v10") from the cookie + */ + void format_from_chrome(); + }; - /** - * - * @param string - The string which is being searched - * @param start - The starting index of the substring - * @param test_str - The string which is being tested - * @return whether or not the substring is at the start index - * - * Checks if test_str is a substring of string at the start index - */ - bool substr_is(const std::string& string, int start, const std::string& test_str); + /** + * + * @param string - The string which is being searched + * @param start - The starting index of the substring + * @param test_str - The string which is being tested + * @return whether or not the substring is at the start index + * + * Checks if test_str is a substring of string at the start index + */ + bool substr_is(const std::string& string, int start, const std::string& test_str); - /** - * - * @param str - The base string which is being modified - * @param from - what is being replaced - * @param to - what to place it with - * - * Replaces every instance of the from string with the to string. - */ - void replace_all(std::string& str, const std::string& from, const std::string& to); + /** + * + * @param str - The base string which is being modified + * @param from - what is being replaced + * @param to - what to place it with + * + * Replaces every instance of the from string with the to string. + */ + void replace_all(std::string& str, const std::string& from, const std::string& to); - /** - * - * @param str - A string - * @return str with any leading or following whitespace - * - * Removes leading and following whitespace from a string and returns the modified string - */ - std::string remove_leading_and_following_whitespace(const std::string& str); + /** + * + * @param str - A string + * @return str with any leading or following whitespace + * + * Removes leading and following whitespace from a string and returns the modified string + */ + std::string remove_leading_and_following_whitespace(const std::string& str); - /** - * - * @param str - A string - * @return str with any html character codes replaced with their ascii equivalent. - * - * E.G. \' would be replaced with ' - */ - std::string replace_html_character_codes(const std::string& str); + /** + * + * @param str - A string + * @return str with any html character codes replaced with their ascii equivalent. + * + * E.G. \' would be replaced with ' + */ + std::string replace_html_character_codes(const std::string& str); - /** - * - * @param str - A string - * @return str with junk removed or replace - * - * Removed leading and following whitespace and replaces html character codes - */ - std::string format_name_string(const std::string& str); + /** + * + * @param str - A string + * @return str with junk removed or replace + * + * Removed leading and following whitespace and replaces html character codes + */ + std::string format_name_string(const std::string& str); - /** - * - * @param str - A string - * @return str properly formatted to be a filename - * - * Removes non-alphanumeric characters and spaces - */ - std::string format_filename(const std::string& str); + /** + * + * @param str - A string + * @return str properly formatted to be a filename + * + * Removes non-alphanumeric characters and spaces + */ + std::string format_filename(const std::string& str); - #if defined(__WIN32__) - #include - msec_t time_ms(void); - #else - #include - /** - * - * @return The time in milliseconds - */ - long time_ms(); - #endif + #if defined(__WIN32__) + #include + msec_t time_ms(void); + #else + #include + /** + * + * @return The time in milliseconds + */ + long time_ms(); + #endif - /** - * - * @param filename - Name of the file that is being downloaded - * @param total_to_download - The total amount of bytes that are being downloaded - * @param downloaded - The current amount of bytes that have been downloaded - * @param total_to_upload - The total amount of bytes that are being uploaded (This project does not upload so this is not used) - * @param uploaded - The current amount of bytes that have been uploaded (This project does not upload so this is not used) - * @return 0 - * - * Used by curl. Displays the filename followed by a bar which show the percent downloaded followed by the number of Mib downloaded out of the total. - * The function takes the upload amount because that is required by curl but they are just ignored for this since we never upload anything. - */ - static int curl_progress_func(void* filename, double total_to_download, double downloaded, double total_to_upload, double uploaded); + /** + * + * @param filename - Name of the file that is being downloaded + * @param total_to_download - The total amount of bytes that are being downloaded + * @param downloaded - The current amount of bytes that have been downloaded + * @param total_to_upload - The total amount of bytes that are being uploaded (This project does not upload so this is not used) + * @param uploaded - The current amount of bytes that have been uploaded (This project does not upload so this is not used) + * @return 0 + * + * Used by curl. Displays the filename followed by a bar which show the percent downloaded followed by the number of Mib downloaded out of the total. + * The function takes the upload amount because that is required by curl but they are just ignored for this since we never upload anything. + */ + static int curl_progress_func(void* filename, double total_to_download, double downloaded, double total_to_upload, double uploaded); - /** - * - * @param contents - What we're writing - * @param size - The amount that is being written - * @param nmemb - The number of bytes per unit of size - * @param userp - Where the information in contents is written to - * @return size * nmemb - * - * Used by curl. Writes the information gathered by curl into the userp string. This function was not written by me. - */ - size_t WriteCallback(void *contents, size_t size, size_t nmemb, void *userp); + /** + * + * @param contents - What we're writing + * @param size - The amount that is being written + * @param nmemb - The number of bytes per unit of size + * @param userp - Where the information in contents is written to + * @return size * nmemb + * + * Used by curl. Writes the information gathered by curl into the userp string. This function was not written by me. + */ + size_t WriteCallback(void *contents, size_t size, size_t nmemb, void *userp); - /** - * - * @param url - Url which is being downloaded - * @param verbose - Whether or not to be verbose (not recommended) - * @return The page data as a string - * - * This function downloads the provided url and returns it as a string. Does not use cookies. This was ripped directly from a firefox network request for an episode page and modified minimally. - */ - std::string get_generic_page(const std::string& url, bool verbose = false); + /** + * + * @param url - Url which is being downloaded + * @param verbose - Whether or not to be verbose (not recommended) + * @return The page data as a string + * + * This function downloads the provided url and returns it as a string. Does not use cookies. This was ripped directly from a firefox network request for an episode page and modified minimally. + */ + std::string get_generic_page(const std::string& url, bool verbose = false); - /** - * A class for handling all episode information. This class is wildly overkill if downloading an entire series as it gather the series name and season for every episode. This is not an issue here because all the information it gathers it already available while gathering the video url and the majority of the time taken while parsing an episode is from downloading the three required webpages. - */ - class episode { + /** + * A class for handling all episode information. This class is wildly overkill if downloading an entire series as it gather the series name and season for every episode. This is not an issue here because all the information it gathers it already available while gathering the video url and the majority of the time taken while parsing an episode is from downloading the three required webpages. + */ + class episode { - public: - /// The name of the series that the episode belongs to - std::string series; - /// The directory for the series - std::string series_directory; - /// The name of the episode - std::string name; - /// The number of the episode in the season. This can be a number or a string - std::string episode_number; - /// The url for the main episode page - std::string episode_url; - /// The data of the main episode page - std::string episode_data; - /// The url for the main embedded page. This contains page the link to the config page - std::string embedded_url; - /// The data of the main embedded page. This contains the link to the config page - std::string embedded_page_data; - /// The url for the main config page. This contains page the link to the mp4 video of the episode - std::string config_url; - /// The data of the main config page. This contains the link to the mp4 video of the episode - std::string config_data; - /// The list of the qualities available for the episode. This is a parallel array with the quality_urls vector - std::vector qualities; - /// The list of the urls correlating with the qualities array. - std::vector quality_urls; + public: + /// The name of the series that the episode belongs to + std::string series; + /// The directory for the series + std::string series_directory; + /// The name of the episode + std::string name; + /// The number of the episode in the season. This can be a number or a string + std::string episode_number; + /// The url for the main episode page + std::string episode_url; + /// The data of the main episode page + std::string episode_data; + /// The url for the main embedded page. This contains page the link to the config page + std::string embedded_url; + /// The data of the main embedded page. This contains the link to the config page + std::string embedded_page_data; + /// The url for the main config page. This contains page the link to the mp4 video of the episode + std::string config_url; + /// The data of the main config page. This contains the link to the mp4 video of the episode + std::string config_data; + /// The list of the qualities available for the episode. This is a parallel array with the quality_urls vector + std::vector qualities; + /// The list of the urls correlating with the qualities array. + std::vector quality_urls; - /// Whether or not to be verbose - bool verbose = false; + /// Whether or not to be verbose + bool verbose = false; - // Curl + // Curl - /** - * - * @param url - The url of the episode page - * @param auth_cookie - The authentication cookie with name "__cf_bm" - * @param session_cookie - The session cookie with name "_session" - * @param verbose - Whether or not to be verbose (not recommended) - * @return The episode page data - */ - static std::string get_episode_page(const std::string& url, const std::string& auth_cookie, const std::string& session_cookie, bool verbose = false); + /** + * + * @param url - The url of the episode page + * @param auth_cookie - The authentication cookie with name "__cf_bm" + * @param session_cookie - The session cookie with name "_session" + * @param verbose - Whether or not to be verbose (not recommended) + * @return The episode page data + */ + static std::string get_episode_page(const std::string& url, const std::string& auth_cookie, const std::string& session_cookie, bool verbose = false); - // Parsing - /** - * - * @param html_data - Episode page data - * @return The name of the series - * - * Get the name of the series from the episode page - */ - static std::string get_series_name(const std::string& html_data); + // Parsing + /** + * + * @param html_data - Episode page data + * @return The name of the series + * + * Get the name of the series from the episode page + */ + static std::string get_series_name(const std::string& html_data); - /** - * - * @param html_data - Episode page data - * @return The name of the episode - * - * Get the name of the episode from the episode page - */ - static std::string get_episode_name(const std::string& html_data); + /** + * + * @param html_data - Episode page data + * @return The name of the episode + * + * Get the name of the episode from the episode page + */ + static std::string get_episode_name(const std::string& html_data); - /** - * - * @param html_data - Episode page data - * @return The number of the episode - * - * Get the number of the episode from the episode page - */ - static std::string get_episode_number(const std::string& html_data); + /** + * + * @param html_data - Episode page data + * @return The number of the episode + * + * Get the number of the episode from the episode page + */ + static std::string get_episode_number(const std::string& html_data); - /** - * - * @param html_data - Episode page data - * @return The url of the embedded page - * - * Get the url of the embedded page from the episode page - */ - static std::string get_embed_url(const std::string& html_data); + /** + * + * @param html_data - Episode page data + * @return The url of the embedded page + * + * Get the url of the embedded page from the episode page + */ + static std::string get_embed_url(const std::string& html_data); - /** - * - * @param html_data - Embedded page data - * @return The url of the config page - * - * Get the url of the config page from the embedded page data - */ - static std::string get_config_url(const std::string& html_data); + /** + * + * @param html_data - Embedded page data + * @return The url of the config page + * + * Get the url of the config page from the embedded page data + */ + static std::string get_config_url(const std::string& html_data); - /** - * - * @return A vector of qualities - * - * Gets the available qualities for the episode and populate the qualities and quality_urls vectors. - * If this function has already been run it simply returns the already populated qualities vector unless said vector has been cleared. - */ - std::vector get_qualities(); + /** + * + * @return A vector of qualities + * + * Gets the available qualities for the episode and populate the qualities and quality_urls vectors. + * If this function has already been run it simply returns the already populated qualities vector unless said vector has been cleared. + */ + std::vector get_qualities(); - /** - * - * @param quality - The quality of the video - * @return The url to the video - * - * Get a link to the video of the episode with the given quality. Quality must be contained in the qualities vector otherwise this function will give an error and exit the program after listing the available qualities. - */ - std::string get_video_url(const std::string& quality); + /** + * + * @param quality - The quality of the video + * @return The url to the video + * + * Get a link to the video of the episode with the given quality. Quality must be contained in the qualities vector otherwise this function will give an error and exit the program after listing the available qualities. + */ + std::string get_video_url(const std::string& quality); - /** - * - * @param quality - The quality of the video - * @param filename - The filename which will be displayed will downloading the video - * @return The video data - * - * Download the episode with the given quality and return the raw video data as a string. The filename parameter is only used for displaying while downloading the video so that the user knows what is being downloaded. The filename argument is entirely optional and this function will not place the video into a file whether the value is given or not. - */ - std::string get_video_data(const std::string& quality, const std::string& filename = ""); + /** + * + * @param quality - The quality of the video + * @param filename - The filename which will be displayed will downloading the video + * @return The video data + * + * Download the episode with the given quality and return the raw video data as a string. The filename parameter is only used for displaying while downloading the video so that the user knows what is being downloaded. The filename argument is entirely optional and this function will not place the video into a file whether the value is given or not. + */ + std::string get_video_data(const std::string& quality, const std::string& filename = ""); - /** - * - * @param quality - The quality of the video - * @param series_directory - The directory which the episode is downloaded into - * @param filename - The name of the file (Will default if empty) - * - * Downloads the episode using the get_video_data function and places it into the filename file in the series_directory directory. - * If the filename parameter is left empty it will default to the E\\.mp4 format. - */ - void download(const std::string& quality, const std::string& series_directory, std::string filename = ""); + /** + * + * @param quality - The quality of the video + * @param series_directory - The directory which the episode is downloaded into + * @param filename - The name of the file (Will default if empty) + * + * Downloads the episode using the get_video_data function and places it into the filename file in the series_directory directory. + * If the filename parameter is left empty it will default to the E\\.mp4 format. + */ + void download(const std::string& quality, const std::string& series_directory, std::string filename = ""); - /** - * - * @param episode_url - Link to the episode - * @param cookies - The current cookies from the browser - * @param verbose - Whether or not be verbose - * - * Create an episode object from the link using to cookies to get all the necessary information. - * This constructor initializes all the object data. - */ - episode(const std::string& episode_url, std::vector cookies, bool verbose = false) { + /** + * + * @param episode_url - Link to the episode + * @param cookies - The current cookies from the browser + * @param verbose - Whether or not be verbose + * + * Create an episode object from the link using to cookies to get all the necessary information. + * This constructor initializes all the object data. + */ + episode(const std::string& episode_url, std::vector cookies, bool verbose = false) { - this->episode_url = episode_url; - this->verbose = verbose; + this->episode_url = episode_url; + this->verbose = verbose; - episode_data = get_episode_page(episode_url, cookies[0].value, cookies[1].value); + episode_data = get_episode_page(episode_url, cookies[0].value, cookies[1].value); - name = get_episode_name(episode_data); + name = get_episode_name(episode_data); - if (verbose) { - std::cout << "Got name: " << name << '\n'; - } + if (verbose) { + std::cout << "Got name: " << name << '\n'; + } - if (name == "ERROR") { - std::cerr << "EPISODE ERROR: Invalid Episode URL\n"; - exit(6); - } + if (name == "ERROR") { + std::cerr << "EPISODE ERROR: Invalid Episode URL\n"; + exit(6); + } - this->episode_number = get_episode_number(episode_data); + this->episode_number = get_episode_number(episode_data); - if (verbose) { - std::cout << "Got episode: " << this->episode_number << '\n'; - } + if (verbose) { + std::cout << "Got episode: " << this->episode_number << '\n'; + } - this->series = get_series_name(episode_data); + this->series = get_series_name(episode_data); - if (verbose) { - std::cout << "Got series: " << this->series << '\n'; - } + if (verbose) { + std::cout << "Got series: " << this->series << '\n'; + } - this->series_directory = format_filename(this->series); + this->series_directory = format_filename(this->series); - if (verbose) { - std::cout << "Got series directory: " << this->series_directory << '\n'; - } + if (verbose) { + std::cout << "Got series directory: " << this->series_directory << '\n'; + } - this->embedded_url = get_embed_url(episode_data); + this->embedded_url = get_embed_url(episode_data); - replace_all(this->embedded_url, "&", "&"); + replace_all(this->embedded_url, "&", "&"); - if (verbose) { - std::cout << "Got embedded url: " << this->embedded_url << '\n'; - } + if (verbose) { + std::cout << "Got embedded url: " << this->embedded_url << '\n'; + } - this->embedded_page_data = get_generic_page(this->embedded_url); + this->embedded_page_data = get_generic_page(this->embedded_url); - if (this->embedded_page_data.find("you are not authorized") != std::string::npos) { - std::cerr << "ERROR: Could not access video. Try refreshing cookies.\n"; - exit(6); - } + if (this->embedded_page_data.find("you are not authorized") != std::string::npos) { + std::cerr << "ERROR: Could not access video. Try refreshing cookies.\n"; + exit(6); + } - this->config_url = get_config_url(this->embedded_page_data); + this->config_url = get_config_url(this->embedded_page_data); - replace_all(this->config_url, "\\u0026", "&"); + replace_all(this->config_url, "\\u0026", "&"); - if (verbose) { - std::cout << "Got config url: " << this->config_url << '\n'; - } + if (verbose) { + std::cout << "Got config url: " << this->config_url << '\n'; + } - this->config_data = get_generic_page(this->config_url); + this->config_data = get_generic_page(this->config_url); - this->get_qualities(); - } + this->get_qualities(); + } - /** - * Creates an episode object with no data. This should only be used for invalid states. - */ - episode() = default; - }; + /** + * Creates an episode object with no data. This should only be used for invalid states. + */ + episode() = default; + }; } // dropout_dl diff --git a/src/main.cpp b/src/main.cpp index 8426a37..3f21464 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -8,126 +8,126 @@ namespace dropout_dl { - /** - * A class for handling and storing the program arguments. - */ - class options { - public: + /** + * A class for handling and storing the program arguments. + */ + 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 cookies; + 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 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 convert_program_args(int argc, char** argv) { - std::vector 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 + * @return A vector of arguments in the c++ string format + * + * Converts the C style program arguments to a vector of strings + */ + static std::vector convert_program_args(int argc, char** argv) { + std::vector 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 args = convert_program_args(argc, argv); + /** + * + * @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 args = convert_program_args(argc, argv); - for (int i = 0; i < args.size(); i++) { - std::string arg = args[i]; + 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] [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; + 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] [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); - } - } + exit(0); + } + } - if (output_directory.empty()) { - output_directory = "."; - } + 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"; - } - } - }; + if (season && series) { + std::cerr << "ARGUMENT PARSE ERROR: Season and Series arguments used\n"; + } + if (quality.empty()) { + quality = "1080p"; + } + } + }; } #ifdef DROPOUT_DL_SQLITE @@ -141,69 +141,69 @@ namespace dropout_dl { */ std::vector get_cookies_from_firefox(const std::filesystem::path& firefox_profile_path, bool verbose = false) { - std::fstream firefox_profile_file(firefox_profile_path); - std::string firefox_profile; + std::fstream firefox_profile_file(firefox_profile_path); + std::string firefox_profile; - dropout_dl::cookie auth("__cf_bm"); - dropout_dl::cookie session("_session"); + dropout_dl::cookie auth("__cf_bm"); + dropout_dl::cookie session("_session"); - std::vector out; + std::vector out; - firefox_profile_file >> firefox_profile; + firefox_profile_file >> firefox_profile; - if (std::filesystem::is_directory(firefox_profile)) { + if (std::filesystem::is_directory(firefox_profile)) { - sqlite3 *db; + sqlite3 *db; - if (verbose) { - std::cout << "Getting firefox cookies from firefox sqlite db\n"; - } + if (verbose) { + std::cout << "Getting firefox cookies from firefox sqlite db\n"; + } - 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"); + 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"; - } - } + 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"; + } + } - std::string len; + std::string len; - auth.get_value_from_db(db, "FROM moz_cookies WHERE host LIKE '%dropout.tv%'", "value"); + auth.get_value_from_db(db, "FROM moz_cookies WHERE host LIKE '%dropout.tv%'", "value"); - session.get_value_from_db(db, "FROM moz_cookies WHERE host LIKE '%dropout.tv%'", "value"); + session.get_value_from_db(db, "FROM moz_cookies WHERE host LIKE '%dropout.tv%'", "value"); - sqlite3_close(db); + sqlite3_close(db); - std::filesystem::remove("tmp/firefox_cookies.sqlite"); + std::filesystem::remove("tmp/firefox_cookies.sqlite"); - 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); - } + 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); + } - if (verbose) { - std::cout << auth.name << ": " << auth.len << ": " << auth.value << '\n'; + if (verbose) { + std::cout << auth.name << ": " << auth.len << ": " << auth.value << '\n'; - std::cout << session.name << ": " << session.len << ": " << session.value << '\n'; - } + std::cout << session.name << ": " << session.len << ": " << session.value << '\n'; + } - out.push_back(auth); - out.push_back(session); + out.push_back(auth); + out.push_back(session); - return out; + return out; } #ifdef DROPOUT_DL_GCRYPT @@ -219,62 +219,62 @@ std::vector get_cookies_from_firefox(const std::filesystem:: */ std::vector get_cookies_from_chrome(const std::filesystem::path& chrome_profile_path, bool verbose = false) { - std::fstream chrome_profile_file(chrome_profile_path); - std::string chrome_profile; + std::fstream chrome_profile_file(chrome_profile_path); + std::string chrome_profile; - dropout_dl::cookie auth("__cf_bm"); - dropout_dl::cookie session("_session"); + dropout_dl::cookie auth("__cf_bm"); + dropout_dl::cookie session("_session"); - std::vector out; + std::vector out; - getline(chrome_profile_file, chrome_profile); + getline(chrome_profile_file, chrome_profile); - if (std::filesystem::is_directory(chrome_profile)) { + if (std::filesystem::is_directory(chrome_profile)) { - sqlite3 *db; + sqlite3 *db; - if (verbose) { - std::cout << "Getting chrome cookies from chrome sqlite db\n"; - } + 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 { - if (verbose) { - std::cout << "Chrome database opened successfully\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 { + if (verbose) { + std::cout << "Chrome database opened successfully\n"; + } + } - std::string len; + std::string len; - auth.get_value_from_db(db, "FROM cookies WHERE host_key LIKE '%dropout.tv%'", "encrypted_value"); + auth.get_value_from_db(db, "FROM cookies WHERE host_key LIKE '%dropout.tv%'", "encrypted_value"); - session.get_value_from_db(db, "FROM cookies WHERE host_key LIKE '%dropout.tv%'", "encrypted_value"); + session.get_value_from_db(db, "FROM cookies WHERE host_key LIKE '%dropout.tv%'", "encrypted_value"); - sqlite3_close(db); + sqlite3_close(db); - } - else { - std::cerr << "CHROME COOKIE ERROR: Attempted to get cookies from chrome without profile." << std::endl; - exit(4); - } + } + else { + std::cerr << "CHROME COOKIE ERROR: Attempted to get cookies from chrome without profile." << std::endl; + exit(4); + } - auth.chrome_decrypt(); + auth.chrome_decrypt(); - session.chrome_decrypt(); + session.chrome_decrypt(); - if (verbose) { - std::cout << auth.name << ": " << auth.len << ": " << auth.value << '\n'; + if (verbose) { + std::cout << auth.name << ": " << auth.len << ": " << auth.value << '\n'; - std::cout << session.name << ": " << session.len << ": " << session.value << '\n'; - } + std::cout << session.name << ": " << session.len << ": " << session.value << '\n'; + } - out.push_back(auth); - out.push_back(session); + out.push_back(auth); + out.push_back(session); - return out; + return out; } #endif #endif @@ -289,93 +289,93 @@ std::vector get_cookies_from_chrome(const std::filesystem::p */ std::vector get_cookies(bool verbose = false) { - std::filesystem::path firefox_profile("firefox_profile"); - std::filesystem::path chrome_profile("chrome_profile"); + std::filesystem::path firefox_profile("_firefox_profile"); + std::filesystem::path chrome_profile("chrome_profile"); - if (std::filesystem::exists(firefox_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 - } + #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); - } + { + std::cerr << "ERROR: dropout.tv cookies could not be found" << std::endl; + exit(7); + } } int main(int argc, char** argv) { - dropout_dl::options options(argc, argv); + dropout_dl::options options(argc, argv); - if (options.verbose) { - std::cout << "quality: " << options.quality << std::endl; - std::cout << "verbose: " << options.verbose << std::endl; - std::cout << "url: \"" << options.url << '"' << std::endl; - } + if (options.verbose) { + std::cout << "quality: " << options.quality << std::endl; + std::cout << "verbose: " << options.verbose << std::endl; + std::cout << "url: \"" << options.url << '"' << std::endl; + } - std::string firefox_profile; - std::string chrome_profile; + std::string firefox_profile; + std::string chrome_profile; - std::string video_data; + std::string video_data; - 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"; - } + 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"; + } - if (!options.cookies_forced) { - options.cookies = get_cookies(options.verbose); - } + if (!options.cookies_forced) { + options.cookies = get_cookies(options.verbose); + } - if (options.series) { - dropout_dl::series series(options.url, options.cookies); + if (options.series) { + dropout_dl::series series(options.url, options.cookies); - series.download(options.quality, options.output_directory); - } - else if (options.season) { - dropout_dl::season season = dropout_dl::series::get_season(options.url, options.cookies); + series.download(options.quality, options.output_directory); + } + else if (options.season) { + dropout_dl::season season = dropout_dl::series::get_season(options.url, options.cookies); - std::string series_directory = dropout_dl::format_filename(season.series_name); + std::string series_directory = dropout_dl::format_filename(season.series_name); - std::cout << "ser: " << season.series_name << "\ndir: " << series_directory << '\n'; + std::cout << "ser: " << season.series_name << "\ndir: " << series_directory << '\n'; - season.download(options.quality, options.output_directory + "/" + series_directory); - } - else { - dropout_dl::episode ep(options.url, options.cookies, options.verbose); + season.download(options.quality, options.output_directory + "/" + series_directory); + } + else { + dropout_dl::episode ep(options.url, options.cookies, options.verbose); - if (options.verbose) { - std::cout << "filename: " << options.filename << '\n'; - } + if (options.verbose) { + std::cout << "filename: " << options.filename << '\n'; + } - if (!std::filesystem::is_directory(options.output_directory)) { - std::filesystem::create_directories(options.output_directory); - if (options.verbose) { - std::cout << "Creating series directory" << '\n'; - } - } + if (!std::filesystem::is_directory(options.output_directory)) { + std::filesystem::create_directories(options.output_directory); + if (options.verbose) { + std::cout << "Creating series directory" << '\n'; + } + } - if (options.filename.empty()) { - options.filename = dropout_dl::format_filename(ep.name + ".mp4"); - } + if (options.filename.empty()) { + options.filename = dropout_dl::format_filename(ep.name + ".mp4"); + } - ep.download(options.quality, options.output_directory, options.filename); - } + ep.download(options.quality, options.output_directory, options.filename); + } - return 0; + return 0; } diff --git a/src/season.cpp b/src/season.cpp index d868fe1..7aa13aa 100644 --- a/src/season.cpp +++ b/src/season.cpp @@ -5,64 +5,64 @@ #include "season.h" namespace dropout_dl { - episode get_episode(const std::string& html_data, int& start_point, const std::vector& cookies) { - int link_start = 0; - for (int i = start_point; i > 0; i--) { - if (substr_is(html_data, i, "& cookies) { + int link_start = 0; + for (int i = start_point; i > 0; i--) { + if (substr_is(html_data, i, " season::get_episodes(const std::string &html_data, const std::vector& cookies) { - std::vector out; + std::vector season::get_episodes(const std::string &html_data, const std::vector& cookies) { + std::vector out; - std::string site_video(R"(class="browse-item-link" data-track-event="site_video")"); + std::string site_video(R"(class="browse-item-link" data-track-event="site_video")"); - for (int i = 0; i < html_data.size(); i++) { - if (substr_is(html_data, i, site_video)) { - episode e = get_episode(html_data, i, cookies); - if (e.episode_url.empty()) { - continue; - } - std::cout << '\t' << e.episode_number << ": " << e.name << '\n'; - out.push_back(e); - } - } + for (int i = 0; i < html_data.size(); i++) { + if (substr_is(html_data, i, site_video)) { + episode e = get_episode(html_data, i, cookies); + if (e.episode_url.empty()) { + continue; + } + std::cout << '\t' << e.episode_number << ": " << e.name << '\n'; + out.push_back(e); + } + } - return out; - } + return out; + } - void season::download(const std::string &quality, const std::string &series_directory) { - if (!std::filesystem::is_directory(series_directory)) { - std::filesystem::create_directories(series_directory); - std::cout << "Creating series directory" << '\n'; - } + void season::download(const std::string &quality, const std::string &series_directory) { + if (!std::filesystem::is_directory(series_directory)) { + std::filesystem::create_directories(series_directory); + std::cout << "Creating series directory" << '\n'; + } - std::string dir = format_filename(series_directory + "/" + this->name); + std::string dir = format_filename(series_directory + "/" + this->name); - for (auto& ep : episodes) { - ep.download(quality, dir); - } - } -} // dropout_dl \ No newline at end of file + for (auto& ep : episodes) { + ep.download(quality, dir); + } + } +} // dropout_dl diff --git a/src/season.h b/src/season.h index ae0c596..a1eee6c 100644 --- a/src/season.h +++ b/src/season.h @@ -10,56 +10,56 @@ namespace dropout_dl { - class season { - public: - /// The name of the season - std::string name; - /// The name of the series - std::string series_name; - /// The link to the season page - std::string url; - /// The season page data - std::string page_data; - /// The list of all the episodes in the season - std::vector episodes; + class season { + public: + /// The name of the season + std::string name; + /// The name of the series + std::string series_name; + /// The link to the season page + std::string url; + /// The season page data + std::string page_data; + /// The list of all the episodes in the season + std::vector episodes; - /** - * - * @param html_data - The season page data - * @param cookies - The browser cookies - * @return A vector of all episodes in the season - * - * Gets all the episodes of the season and returns in a vector - */ - static std::vector get_episodes(const std::string& html_data, const std::vector& cookies); + /** + * + * @param html_data - The season page data + * @param cookies - The browser cookies + * @return A vector of all episodes in the season + * + * Gets all the episodes of the season and returns in a vector + */ + static std::vector get_episodes(const std::string& html_data, const std::vector& cookies); - /** - * - * @param quality - The quality of the videos - * @param series_directory - The directory of the series - * - * Downloads all the episodes of the season. Appends the season to the series directory - */ - void download(const std::string& quality, const std::string& series_directory); + /** + * + * @param quality - The quality of the videos + * @param series_directory - The directory of the series + * + * Downloads all the episodes of the season. Appends the season to the series directory + */ + void download(const std::string& quality, const std::string& series_directory); - /** - * - * @param url - The url to the webpage of the season - * @param name - The name of the season - * @param cookies - The browser cookies - * @param series_name - The name of the series - * - * Creates a season object and populates the needed information. - */ - season(const std::string& url, const std::string& name, const std::vector& cookies, const std::string& series_name = "") { - this->url = url; - this->name = name; - this->series_name = series_name; - std::cout << series_name << ": " << name << ": " << "\n"; - this->page_data = get_generic_page(url); - this->episodes = get_episodes(page_data, cookies); - } - }; + /** + * + * @param url - The url to the webpage of the season + * @param name - The name of the season + * @param cookies - The browser cookies + * @param series_name - The name of the series + * + * Creates a season object and populates the needed information. + */ + season(const std::string& url, const std::string& name, const std::vector& cookies, const std::string& series_name = "") { + this->url = url; + this->name = name; + this->series_name = series_name; + std::cout << series_name << ": " << name << ": " << "\n"; + this->page_data = get_generic_page(url); + this->episodes = get_episodes(page_data, cookies); + } + }; } // dropout_dl diff --git a/src/series.cpp b/src/series.cpp index 98df667..28ee2e8 100644 --- a/src/series.cpp +++ b/src/series.cpp @@ -6,216 +6,216 @@ namespace dropout_dl { - std::string series::get_series_name(const std::string& html_data) { - std::string collection_title("collection-title"); - std::string close_tag(">"); - std::string close_a(""); + std::string series::get_series_name(const std::string& html_data) { + std::string collection_title("collection-title"); + std::string close_tag(">"); + std::string close_a(""); - for (int i = 0; i < html_data.size(); i++) { - if (substr_is(html_data, i, collection_title)) { - for (int j = i + collection_title.size(); j < html_data.size(); j++) { - if (html_data[j] == '\n' || html_data[j] == ' ' || html_data[j] == '\t') continue; - if (substr_is(html_data, j, close_tag)) { - for (int l = 0; l < html_data.size() - j; l++) { - if (substr_is(html_data, j + l, close_a)) { - return format_name_string(html_data.substr(j + 1, l - 1)); - } - } - } - } - } - } - return "ERROR"; - } + for (int i = 0; i < html_data.size(); i++) { + if (substr_is(html_data, i, collection_title)) { + for (int j = i + collection_title.size(); j < html_data.size(); j++) { + if (html_data[j] == '\n' || html_data[j] == ' ' || html_data[j] == '\t') continue; + if (substr_is(html_data, j, close_tag)) { + for (int l = 0; l < html_data.size() - j; l++) { + if (substr_is(html_data, j + l, close_a)) { + return format_name_string(html_data.substr(j + 1, l - 1)); + } + } + } + } + } + } + return "ERROR"; + } - std::vector series::get_seasons(const std::string &html_data, const std::vector& cookies) { - std::vector out; + std::vector series::get_seasons(const std::string &html_data, const std::vector& cookies) { + std::vector out; - std::string search_class("js-switch-season"); - std::string open_select(""); - std::string close_select(""); + std::string search_class("js-switch-season"); + std::string open_select(""); + std::string close_select(""); - std::string open_option(""); - std::string value("value="); + std::string open_option(""); + std::string value("value="); - bool seasons_dropdown = false; - std::string season_url; - std::string season_name; - for (int i = 0; i < html_data.size(); i++) { - if (substr_is(html_data, i, open_select)) { - for (int j = i; j < html_data.size(); j++) { - if (substr_is(html_data, j, search_class)) { - i = j; - seasons_dropdown = true; - break; - } - else if (substr_is(html_data, j, close_tag)) { - break; - } - } - } - if (seasons_dropdown) { - if (substr_is(html_data, i, value)) { - i += value.size() + 1; - for (int j = 0; j + i < html_data.size(); j++) { - if (html_data[i + j] == '"') { - season_url = html_data.substr(i, j); - i += j; - break; - } - } - } - else if (!season_url.empty() && substr_is(html_data, i, close_tag)) { - i += close_tag.size() + 1; - for (int j = 0; i + j < html_data.size(); j++) { - if (html_data[i + j] == '\n') { - season_name = html_data.substr(i, j); + bool seasons_dropdown = false; + std::string season_url; + std::string season_name; + for (int i = 0; i < html_data.size(); i++) { + if (substr_is(html_data, i, open_select)) { + for (int j = i; j < html_data.size(); j++) { + if (substr_is(html_data, j, search_class)) { + i = j; + seasons_dropdown = true; + break; + } + else if (substr_is(html_data, j, close_tag)) { + break; + } + } + } + if (seasons_dropdown) { + if (substr_is(html_data, i, value)) { + i += value.size() + 1; + for (int j = 0; j + i < html_data.size(); j++) { + if (html_data[i + j] == '"') { + season_url = html_data.substr(i, j); + i += j; + break; + } + } + } + else if (!season_url.empty() && substr_is(html_data, i, close_tag)) { + i += close_tag.size() + 1; + for (int j = 0; i + j < html_data.size(); j++) { + if (html_data[i + j] == '\n') { + season_name = html_data.substr(i, j); - // Remove leading and trailing whitespace - bool leading_whitespace = true; - int name_start; - int name_end; - for (int k = 0; k < season_name.size(); k++) { - if (season_name[k] != ' ' && season_name[k] != '\t' && season_name[k] != '\n') { - name_start = k; - break; - } - } - for (int k = season_name.size() - 1; k > 0; k--) { - if (season_name[k] != ' ' && season_name[k] != '\t' && season_name[k] != '\n') { - name_end = k; - break; - } - } - season_name = season_name.substr(name_start, season_name.size() - name_start - name_end); + // Remove leading and trailing whitespace + bool leading_whitespace = true; + int name_start; + int name_end; + for (int k = 0; k < season_name.size(); k++) { + if (season_name[k] != ' ' && season_name[k] != '\t' && season_name[k] != '\n') { + name_start = k; + break; + } + } + for (int k = season_name.size() - 1; k > 0; k--) { + if (season_name[k] != ' ' && season_name[k] != '\t' && season_name[k] != '\n') { + name_end = k; + break; + } + } + season_name = season_name.substr(name_start, season_name.size() - name_start - name_end); - out.emplace_back(season_url, season_name, cookies); + out.emplace_back(season_url, season_name, cookies); - std::cout << out.back().name << ": " << out.back().url << '\n'; + std::cout << out.back().name << ": " << out.back().url << '\n'; - season_url.clear(); - season_name.clear(); + season_url.clear(); + season_name.clear(); - i = i + j; + i = i + j; - break; - } - } - } + break; + } + } + } - if (substr_is(html_data, i, close_select)) { - break; - } - } - } + if (substr_is(html_data, i, close_select)) { + break; + } + } + } - return out; - } + return out; + } - season series::get_season(const std::string &url, const std::vector& cookies) { - std::string html_data = get_generic_page(url); + season series::get_season(const std::string &url, const std::vector& cookies) { + std::string html_data = get_generic_page(url); - std::string search_class("js-switch-season"); - std::string open_select(""); - std::string close_select(""); + std::string search_class("js-switch-season"); + std::string open_select(""); + std::string close_select(""); - std::string open_option(""); - std::string value("value="); + std::string open_option(""); + std::string value("value="); - bool seasons_dropdown = false; - bool selected = false; - std::string season_url; - std::string season_name; - for (int i = 0; i < html_data.size(); i++) { - if (substr_is(html_data, i, open_select)) { - for (int j = i; j < html_data.size(); j++) { - if (substr_is(html_data, j, search_class)) { - i = j; - seasons_dropdown = true; - break; - } - else if (substr_is(html_data, j, close_tag)) { - break; - } - } - } - if (seasons_dropdown) { - if (substr_is(html_data, i, value)) { - i += value.size() + 1; - for (int j = 0; j + i < html_data.size(); j++) { - if (html_data[i + j] == '"') { - season_url = html_data.substr(i, j); - i += j; - break; - } - } - } - else if (!season_url.empty() && substr_is(html_data, i, "selected")) { - selected = true; - } - else if (!season_url.empty() && substr_is(html_data, i, close_tag)) { - i += close_tag.size() + 1; - for (int j = 0; i + j < html_data.size(); j++) { - if (html_data[i + j] == '\n') { - if (selected) { - season_name = html_data.substr(i, j); + bool seasons_dropdown = false; + bool selected = false; + std::string season_url; + std::string season_name; + for (int i = 0; i < html_data.size(); i++) { + if (substr_is(html_data, i, open_select)) { + for (int j = i; j < html_data.size(); j++) { + if (substr_is(html_data, j, search_class)) { + i = j; + seasons_dropdown = true; + break; + } + else if (substr_is(html_data, j, close_tag)) { + break; + } + } + } + if (seasons_dropdown) { + if (substr_is(html_data, i, value)) { + i += value.size() + 1; + for (int j = 0; j + i < html_data.size(); j++) { + if (html_data[i + j] == '"') { + season_url = html_data.substr(i, j); + i += j; + break; + } + } + } + else if (!season_url.empty() && substr_is(html_data, i, "selected")) { + selected = true; + } + else if (!season_url.empty() && substr_is(html_data, i, close_tag)) { + i += close_tag.size() + 1; + for (int j = 0; i + j < html_data.size(); j++) { + if (html_data[i + j] == '\n') { + if (selected) { + season_name = html_data.substr(i, j); - // Remove leading and trailing whitespace - bool leading_whitespace = true; - int name_start; - int name_end; - for (int k = 0; k < season_name.size(); k++) { - if (season_name[k] != ' ' && season_name[k] != '\t' && season_name[k] != '\n') { - name_start = k; - break; - } - } - for (int k = season_name.size() - 1; k > 0; k--) { - if (season_name[k] != ' ' && season_name[k] != '\t' && season_name[k] != '\n') { - name_end = k; - break; - } - } - season_name = season_name.substr(name_start, - season_name.size() - name_start - name_end); + // Remove leading and trailing whitespace + bool leading_whitespace = true; + int name_start; + int name_end; + for (int k = 0; k < season_name.size(); k++) { + if (season_name[k] != ' ' && season_name[k] != '\t' && season_name[k] != '\n') { + name_start = k; + break; + } + } + for (int k = season_name.size() - 1; k > 0; k--) { + if (season_name[k] != ' ' && season_name[k] != '\t' && season_name[k] != '\n') { + name_end = k; + break; + } + } + season_name = season_name.substr(name_start, + season_name.size() - name_start - name_end); - return {season_url, season_name, cookies, get_series_name(html_data)}; - } + return {season_url, season_name, cookies, get_series_name(html_data)}; + } - season_url.clear(); - season_name.clear(); + season_url.clear(); + season_name.clear(); - i = i + j; + i = i + j; - break; - } - } - } + break; + } + } + } - if (substr_is(html_data, i, close_select)) { - break; - } - } - } + if (substr_is(html_data, i, close_select)) { + break; + } + } + } - std::cerr << "SEASON PARSE ERROR: No selected season found\n"; - exit(9); - } + std::cerr << "SEASON PARSE ERROR: No selected season found\n"; + exit(9); + } - void series::download(const std::string &quality, const std::string& base) { - if (!std::filesystem::is_directory(base + "/" + series_directory)) { - std::filesystem::create_directories(base + "/" + series_directory); - std::cout << "Creating series directory" << '\n'; - } + void series::download(const std::string &quality, const std::string& base) { + if (!std::filesystem::is_directory(base + "/" + series_directory)) { + std::filesystem::create_directories(base + "/" + series_directory); + std::cout << "Creating series directory" << '\n'; + } - for (auto& season : seasons) { - season.download(quality, base + "/" + series_directory); - } - } + for (auto& season : seasons) { + season.download(quality, base + "/" + series_directory); + } + } -} // dropout_dl \ No newline at end of file +} // dropout_dl diff --git a/src/series.h b/src/series.h index 3e7156a..ecdbc11 100644 --- a/src/series.h +++ b/src/series.h @@ -11,80 +11,80 @@ namespace dropout_dl { - /// A class for handling all series information and functions. - class series { - public: - /// The name of the series - std::string name; - /// The link to the series page - std::string url; - /// The series page data - std::string page_data; - /// The directory which will contain the seasons of the series - std::string series_directory; - /// A vector containing all the season that this series include - std::vector seasons; + /// A class for handling all series information and functions. + class series { + public: + /// The name of the series + std::string name; + /// The link to the series page + std::string url; + /// The series page data + std::string page_data; + /// The directory which will contain the seasons of the series + std::string series_directory; + /// A vector containing all the season that this series include + std::vector seasons; - /** - * - * @param html_data - The series page data - * @return The name of the series - * - * Scrapes the series page for the name of the series - */ - static std::string get_series_name(const std::string& html_data); + /** + * + * @param html_data - The series page data + * @return The name of the series + * + * Scrapes the series page for the name of the series + */ + static std::string get_series_name(const std::string& html_data); - /** - * - * @param html_data - The series page data - * @param cookies - The cookies from a browser - * @return A list of all seasons in the series - * - * Scrapes the series page for the names and link of all the season. Creates season objects for each of these. - * These season object contain all the episodes of the season as episode objects. - * The cookies this function takes are passed to the episode objects. - */ - static std::vector get_seasons(const std::string& html_data, const std::vector& cookies); + /** + * + * @param html_data - The series page data + * @param cookies - The cookies from a browser + * @return A list of all seasons in the series + * + * Scrapes the series page for the names and link of all the season. Creates season objects for each of these. + * These season object contain all the episodes of the season as episode objects. + * The cookies this function takes are passed to the episode objects. + */ + static std::vector get_seasons(const std::string& html_data, const std::vector& cookies); - /** - * - * @param url - The url to the season - * @param cookies - The browser cookies - * @return A season object - * - * Gets the season page, which is really just a series page, and creates a season object with all the episodes of the season - */ - static season get_season(const std::string& url, const std::vector& cookies); + /** + * + * @param url - The url to the season + * @param cookies - The browser cookies + * @return A season object + * + * Gets the season page, which is really just a series page, and creates a season object with all the episodes of the season + */ + static season get_season(const std::string& url, const std::vector& cookies); - /** - * - * @param quality - The quality of the video - * @param base - The base directory to download to - * - * Downloads the series into the base directory with the format \/\/\/\ - */ - void download(const std::string& quality, const std::string& base = "."); + /** + * + * @param quality - The quality of the video + * @param base - The base directory to download to + * + * Downloads the series into the base directory with the format \/\/\/\ + */ + void download(const std::string& quality, const std::string& base = "."); - /** - * - * @param url - The link to the series page - * @param cookies - The browser cookies - * - * Creates a series object and populates the needed variables - */ - explicit series(const std::string& url, const std::vector& cookies) { - this->url = url; - this->page_data = get_generic_page(url); - this->name = get_series_name(page_data); - if (name == "ERROR") { - std::cerr << "SERIES PARSE ERROR: Could not parse series name\n"; - exit(10); - } + /** + * + * @param url - The link to the series page + * @param cookies - The browser cookies + * + * Creates a series object and populates the needed variables + */ + explicit series(const std::string& url, const std::vector& cookies) { + this->url = url; + this->page_data = get_generic_page(url); + this->name = get_series_name(page_data); + if (name == "ERROR") { + std::cerr << "SERIES PARSE ERROR: Could not parse series name\n"; + exit(10); + } - this->series_directory = format_filename(name); + this->series_directory = format_filename(name); - this->seasons = get_seasons(page_data, cookies); - } - }; + this->seasons = get_seasons(page_data, cookies); + } + }; } // dropout_dl