diff options
-rw-r--r-- | include/StringUtils.hpp | 1 | ||||
-rw-r--r-- | plugins/youtube/Signature.hpp | 47 | ||||
-rw-r--r-- | src/StringUtils.cpp | 21 | ||||
-rw-r--r-- | src/plugins/Youtube.cpp | 79 | ||||
-rw-r--r-- | src/plugins/youtube/Signature.cpp | 316 |
5 files changed, 82 insertions, 382 deletions
diff --git a/include/StringUtils.hpp b/include/StringUtils.hpp index d2457e8..d31713d 100644 --- a/include/StringUtils.hpp +++ b/include/StringUtils.hpp @@ -16,6 +16,7 @@ namespace QuickMedia { // Returns the number of replaced substrings size_t string_replace_all(std::string &str, const std::string &old_str, const std::string &new_str); std::string strip(const std::string &str); + void strip(const char *str, size_t size, size_t *new_size); bool string_starts_with(const std::string &str, const char *sub); bool string_ends_with(const std::string &str, const std::string &ends_with_str); size_t str_find_case_insensitive(const std::string &str, size_t start_index, const char *substr, size_t substr_len); diff --git a/plugins/youtube/Signature.hpp b/plugins/youtube/Signature.hpp deleted file mode 100644 index 265bbce..0000000 --- a/plugins/youtube/Signature.hpp +++ /dev/null @@ -1,47 +0,0 @@ -#pragma once - -#ifdef YOUTUBE_SIGNATURE_DECRYPTOR -#include "../../include/AsyncTask.hpp" -#include <string> -#include <vector> -#include <map> -#include <time.h> - -namespace QuickMedia { - // Thread safe - class YoutubeSignatureDecryptor { - public: - static YoutubeSignatureDecryptor& get_instance(); - bool decrypt(const std::string &s, const std::string &sp, std::string &sig_key, std::string &sig_value); - private: - YoutubeSignatureDecryptor(); - YoutubeSignatureDecryptor(const YoutubeSignatureDecryptor&) = delete; - YoutubeSignatureDecryptor& operator=(const YoutubeSignatureDecryptor&) = delete; - - struct DecryptFuncCall { - std::string func_name; - long arg; - }; - - enum class DecryptFunction { - REVERSE, - SPLICE, - SWAP - }; - - bool js_code_to_operations(const std::string &function_body_str, const std::string &var_body_str, std::vector<DecryptFuncCall> &new_func_calls, std::map<std::string, DecryptFunction> &new_func_decls); - int update_decrypt_function(); - private: - // TODO: Remove this task and instead add the network download task to a queue and add a poll function to check if it has finished downloading - // or if it needs to be updated. - AsyncTask<void> poll_task; - std::string decryption_function; - time_t decrypt_function_last_updated = 0; - bool running = false; - bool up_to_date = false; - - std::vector<DecryptFuncCall> func_calls; - std::map<std::string, DecryptFunction> func_decls; - }; -} -#endif
\ No newline at end of file diff --git a/src/StringUtils.cpp b/src/StringUtils.cpp index 8d8b62e..81ea1eb 100644 --- a/src/StringUtils.cpp +++ b/src/StringUtils.cpp @@ -90,6 +90,27 @@ namespace QuickMedia { return str.substr(start, end - start + 1); } + void strip(const char *str, size_t size, size_t *new_size) { + if(size == 0) { + *new_size = 0; + return; + } + + int start = 0; + for(; start < (int)size; ++start) { + if(!is_whitespace(str[start])) + break; + } + + int end = (int)size - 1; + for(; end >= start; --end) { + if(!is_whitespace(str[end])) + break; + } + + *new_size = end - start + 1; + } + bool string_starts_with(const std::string &str, const char *sub) { size_t sub_len = strlen(sub); return sub_len == 0 || (str.size() >= sub_len && memcmp(str.c_str(), sub, sub_len) == 0); diff --git a/src/plugins/Youtube.cpp b/src/plugins/Youtube.cpp index 9a9ed81..b86112d 100644 --- a/src/plugins/Youtube.cpp +++ b/src/plugins/Youtube.cpp @@ -1,5 +1,4 @@ #include "../../plugins/Youtube.hpp" -#include "../../plugins/youtube/Signature.hpp" #include "../../include/Storage.hpp" #include "../../include/NetUtils.hpp" #include "../../include/StringUtils.hpp" @@ -115,8 +114,6 @@ namespace QuickMedia { static std::vector<CommandArg> get_cookies() { std::lock_guard<std::mutex> lock(cookies_mutex); if(cookies_filepath.empty()) { - //YoutubeSignatureDecryptor::get_instance(); - cpn.resize(16); generate_random_characters(cpn.data(), cpn.size(), "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_", 64); @@ -2268,20 +2265,75 @@ namespace QuickMedia { return -1; } + static int get_start_of_comment_timestamp(const char *str, size_t size) { + for(int i = (int)size - 1; i >= 0; --i) { + char c = str[i]; + if(c == ' ' || c == '\t') + return i + 1; + } + return -1; + } + + static int start_of_timestamp_title(const char *str, size_t size) { + for(int i = 0; i < (int)size; ++i) { + char c = str[i]; + if(c != '-' && c != ':' && c != '.' && c != ' ' && c != '\t') + return i; + } + return -1; + } + + static int end_of_timestamp_title(const char *str, size_t size) { + for(int i = (int)size - 1; i >= 0; --i) { + char c = str[i]; + if(c != '-' && c != ':' && c != '.' && c != ' ' && c != '\t') + return i + 1; + } + return -1; + } + static std::vector<MediaChapter> youtube_description_extract_chapters(const std::string &description) { std::vector<MediaChapter> result; string_split(description, '\n', [&result](const char *str, size_t size) { - const void *first_space_p = memchr(str, ' ', size); - if(!first_space_p) + strip(str, size, &size); + if(size == 0) return true; - int timestamp_seconds = youtube_comment_timestamp_to_seconds(str, (const char*)first_space_p - str); - if(timestamp_seconds == -1) + const char *first_space_p = (const char*)memchr(str, ' ', size); + if(!first_space_p) return true; + int timestamp_seconds = youtube_comment_timestamp_to_seconds(str, first_space_p - str); + if(timestamp_seconds != -1) { + // Timestamp at the start of the line + size -= (first_space_p - str); + str = first_space_p; + const int timestamp_title_start = start_of_timestamp_title(str, size); + if(timestamp_title_start == -1) + return true; + + str += timestamp_title_start; + size -= timestamp_title_start; + } else { + // Timestamp at the end of the line + const int timestamp_start = get_start_of_comment_timestamp(str, size); + if(timestamp_start == -1) + return true; + + timestamp_seconds = youtube_comment_timestamp_to_seconds(str + timestamp_start, size - timestamp_start); + if(timestamp_seconds == -1) + return true; + + const int timestamp_title_end = end_of_timestamp_title(str, timestamp_start); + if(timestamp_title_end == -1) + return true; + + size = timestamp_title_end; + } + MediaChapter chapter; chapter.start_seconds = timestamp_seconds; - chapter.title.assign((const char*)first_space_p, (str + size) - (const char*)first_space_p); + chapter.title.assign(str, size); chapter.title = strip(chapter.title); result.push_back(std::move(chapter)); return true; @@ -2593,17 +2645,6 @@ R"END( return true; } - //std::string url_decoded = url_param_decode(url); - //url_decoded += "&alr=yes&cver=2.20210615.01.00&cpn=" + cpn; - -#ifdef YOUTUBE_SIGNATURE_DECRYPTOR - const std::string &s = cipher_params["s"]; - const std::string &sp = cipher_params["sp"]; - std::string sig_key; - std::string sig_value; - if(YoutubeSignatureDecryptor::get_instance().decrypt(s, sp, sig_key, sig_value)) - url += "&" + std::move(sig_key) + "=" + std::move(sig_value); -#endif youtube_format.url = std::move(url); return true; } diff --git a/src/plugins/youtube/Signature.cpp b/src/plugins/youtube/Signature.cpp deleted file mode 100644 index 91796d0..0000000 --- a/src/plugins/youtube/Signature.cpp +++ /dev/null @@ -1,316 +0,0 @@ -#ifdef YOUTUBE_SIGNATURE_DECRYPTOR -#include "../../../plugins/youtube/Signature.hpp" -#include "../../../include/Storage.hpp" -#include "../../../include/Notification.hpp" -#include "../../../include/DownloadUtils.hpp" -#include "../../../include/StringUtils.hpp" -#include "../../../include/Program.hpp" -#include <regex> -#include <mutex> -#include <unistd.h> - -namespace QuickMedia { - enum UpdateDecryptFunctionError { - U_DEC_FUN_NET_ERR = 1, - U_DEC_FUN_FAILED_MATCH_ERR = 2 - }; - - static YoutubeSignatureDecryptor *instance = nullptr; - static std::mutex update_signature_mutex; - static const int timeout_default_sec = 60 * 10; // 10 minutes - - static bool is_whitespace(char c) { - switch(c) { - case ' ': - case '\r': - case '\n': - case '\t': - return true; - } - return false; - } - - static std::string remove_whitespaces(const std::string &str) { - std::string result; - for(char c : str) { - if(!is_whitespace(c)) - result += c; - } - return result; - } - - bool YoutubeSignatureDecryptor::js_code_to_operations(const std::string &function_body_str, const std::string &var_body_str, std::vector<DecryptFuncCall> &new_func_calls, std::map<std::string, DecryptFunction> &new_func_decls) { - std::vector<std::string> function_body; - string_split(function_body_str, ';', [&function_body](const char *str, size_t size) { - function_body.push_back(std::string(str, size)); - return true; - }); - - if(function_body.empty()) - return false; - - std::vector<std::string> var_body; - string_split(var_body_str, "},", [&var_body](const char *str, size_t size) { - var_body.push_back(std::string(str, size)); - return true; - }); - - if(var_body.empty()) - return false; - - //fprintf(stderr, "function body: %s\n", function_body_str.c_str()); - for(const std::string &func_call_str : function_body) { - const size_t var_name_split_index = func_call_str.find('.'); - if(var_name_split_index == std::string::npos) return false; - - const size_t func_name_end_index = func_call_str.find('(', var_name_split_index + 1); - if(func_name_end_index == std::string::npos) return false; - - const size_t values_index = func_call_str.find(',', func_name_end_index + 1); - if(values_index == std::string::npos) return false; - - const size_t values_end_index = func_call_str.find(')', values_index + 1); - if(values_end_index == std::string::npos) return false; - - std::string func_name = func_call_str.substr(var_name_split_index + 1, func_name_end_index - (var_name_split_index + 1)); - func_name = strip(func_name); - std::string value_args = func_call_str.substr(values_index + 1, values_end_index - (values_index + 1)); - - errno = 0; - char *endptr; - const int64_t value_int = strtoll(value_args.c_str(), &endptr, 10); - if(endptr != value_args.c_str() && errno == 0) - new_func_calls.push_back({ std::move(func_name), value_int }); - else - return false; - - //fprintf(stderr, "func_call: %s, value: %ld\n", new_func_calls.back().func_name.c_str(), value_int); - } - - //fprintf(stderr, "declaration body: %s\n", var_body_str.c_str()); - for(const std::string &func_decl_str : var_body) { - const size_t func_name_split_index = func_decl_str.find(':'); - if(func_name_split_index == std::string::npos) return false; - - const size_t func_start_index = func_decl_str.find('{', func_name_split_index + 1); - if(func_start_index == std::string::npos) return false; - - std::string func_name = func_decl_str.substr(0, func_name_split_index); - func_name = strip(func_name); - std::string function_body = func_decl_str.substr(func_start_index + 1); - function_body = strip(remove_whitespaces(function_body)); - if(!function_body.empty() && function_body.back() == '}') - function_body.pop_back(); - - DecryptFunction decrypt_function; - if(function_body == "a.reverse()") - decrypt_function = DecryptFunction::REVERSE; - else if(function_body == "a.splice(0,b)") - decrypt_function = DecryptFunction::SPLICE; - else if(function_body.find("a[0]=a[b%a.length]") != std::string::npos) - decrypt_function = DecryptFunction::SWAP; - else { - fprintf(stderr, "Unexpected decryption function body: |%s|\n", function_body.c_str()); - return false; - } - //fprintf(stderr, "declared function: %s, body: |%s|\n", func_name.c_str(), function_body.c_str()); - new_func_decls[std::move(func_name)] = decrypt_function; - } - - for(const auto &func_call : new_func_calls) { - if(new_func_decls.find(func_call.func_name) == new_func_decls.end()) { - fprintf(stderr, "YoutubeSignatureDecryptor: declaration for decryption function %s not found\n", func_call.func_name.c_str()); - fprintf(stderr, "YoutubeSignatureDecryptor: Regex match 10 invalid. Youtube likely updated and QuickMedia needs to be fixed?\n"); - return false; - } - } - - return true; - } - - YoutubeSignatureDecryptor::YoutubeSignatureDecryptor() { - { - Path youtube_cache_dir = get_cache_dir().join("youtube"); - if(create_directory_recursive(youtube_cache_dir) != 0) { - show_notification("QuickMedia", "Failed to create youtube cache directory", Urgency::CRITICAL); - return; - } - - youtube_cache_dir.join("decryption_function"); - if(file_get_content(youtube_cache_dir, decryption_function) == 0) { - file_get_last_modified_time_seconds(youtube_cache_dir.data.c_str(), &decrypt_function_last_updated); - - size_t newline_index = decryption_function.find('\n'); - if(newline_index != std::string::npos) { - std::string function_body_str = decryption_function.substr(0, newline_index); - std::string var_body_str = decryption_function.substr(newline_index + 1); - - std::vector<DecryptFuncCall> new_func_calls; - std::map<std::string, DecryptFunction> new_func_decls; - if(js_code_to_operations(function_body_str, var_body_str, new_func_calls, new_func_decls)) { - func_calls = std::move(new_func_calls); - func_decls = std::move(new_func_decls); - - time_t time_now = time(nullptr); - if(time_now - decrypt_function_last_updated <= timeout_default_sec) - up_to_date = true; - } - } - } - } - - if(up_to_date) - return; - - running = true; - poll_task = AsyncTask<void>([this]() mutable { - int update_res = update_decrypt_function(); - if(update_res != 0 && !program_is_dead_in_current_thread()) { - if(update_res == U_DEC_FUN_NET_ERR) { - show_notification("QuickMedia", "Failed to decrypt youtube signature. Is your internet down?", Urgency::CRITICAL); - } else if(update_res == U_DEC_FUN_FAILED_MATCH_ERR) { - show_notification("QuickMedia", "Failed to decrypt youtube signature. Make sure you are running the latest version of QuickMedia", Urgency::CRITICAL); - } - } - running = false; - }); - } - - // static - YoutubeSignatureDecryptor& YoutubeSignatureDecryptor::get_instance() { - std::lock_guard<std::mutex> lock(update_signature_mutex); - if(!instance) - instance = new YoutubeSignatureDecryptor(); - return *instance; - } - - bool YoutubeSignatureDecryptor::decrypt(const std::string &s, const std::string &sp, std::string &sig_key, std::string &sig_value) { - if(s.empty() || sp.empty()) - return false; - - if(!up_to_date) { - int num_tries = 0; - const int max_tries = 30; - while(running && num_tries < max_tries && !program_is_dead_in_current_thread()) { // 6 seconds in total - if(up_to_date) - break; - ++num_tries; - usleep(200 * 1000); // 200 milliseconds - } - - if(num_tries == max_tries) { - show_notification("QuickMedia", "Failed to get decryption function for youtube. Make sure your internet is working and that you are running the latest version of QuickMedia", Urgency::CRITICAL); - return false; - } - } - - std::lock_guard<std::mutex> lock(update_signature_mutex); - std::string sig = s; - for(const auto &func_call : func_calls) { - auto func_decl_it = func_decls.find(func_call.func_name); - assert(func_decl_it != func_decls.end()); - - switch(func_decl_it->second) { - case DecryptFunction::REVERSE: { - std::reverse(sig.begin(), sig.end()); - break; - } - case DecryptFunction::SPLICE: { - long int erase_index = func_call.arg; - if(erase_index > 0 && (size_t)erase_index < sig.size()) - sig.erase(0, erase_index); - break; - } - case DecryptFunction::SWAP: { - if(sig.empty() || func_call.arg < 0) { - fprintf(stderr, "YoutubeSignatureDecryptor: sig unexpectedly empty in swap\n"); - } else { - char c = sig[0]; - sig[0] = sig[func_call.arg % sig.size()]; - sig[func_call.arg % sig.size()] = c; - } - break; - } - } - } - - sig_key = sp; - sig_value = sig;//url_param_encode(sig); - return true; - } - - int YoutubeSignatureDecryptor::update_decrypt_function() { - std::string response; - DownloadResult download_result = download_to_string("https://www.youtube.com/watch?v=jNQXAC9IVRw&gl=US&hl=en", response, {}, true); - if(download_result != DownloadResult::OK) { - fprintf(stderr, "YoutubeSignatureDecryptor::update_decrypt_function failed. Failed to get youtube page\n"); - return U_DEC_FUN_NET_ERR; - } - - std::smatch base_js_match; - if(!std::regex_search(response, base_js_match, std::regex(R"END((\/s\/player\/[^\/]+\/player_ias[^\/]+\/en_US\/base\.js))END", std::regex::ECMAScript)) || base_js_match.size() != 2) { - fprintf(stderr, "YoutubeSignatureDecryptor: Regex match 1 invalid. Youtube likely updated and QuickMedia needs to be fixed?\n"); - return U_DEC_FUN_FAILED_MATCH_ERR; - } - - const std::string &url = base_js_match[1].str(); - download_result = download_to_string("https://www.youtube.com" + url, response, {}, true); - if(download_result != DownloadResult::OK) { - fprintf(stderr, "YoutubeSignatureDecryptor::update_decrypt_function failed. Failed to get https://www.youtube.com%s\n", url.c_str()); - return U_DEC_FUN_NET_ERR; - } - - std::smatch function_match; - if(!std::regex_search(response, function_match, std::regex(R"END((^|\n)\w+=function\(\w\)\{\w=\w\.split\(""\);([^\}]*)\})END", std::regex::ECMAScript)) || function_match.size() != 3) { - fprintf(stderr, "YoutubeSignatureDecryptor: Regex match 2 invalid. Youtube likely updated and QuickMedia needs to be fixed?\n"); - return U_DEC_FUN_FAILED_MATCH_ERR; - } - - std::string function_body_str = function_match[2].str(); - size_t last_semicolon_index = function_body_str.rfind(';'); - if(last_semicolon_index == std::string::npos) { - fprintf(stderr, "YoutubeSignatureDecryptor: Regex match 3 invalid. Youtube likely updated and QuickMedia needs to be fixed?\n"); - return U_DEC_FUN_FAILED_MATCH_ERR; - } - - function_body_str.erase(last_semicolon_index, function_body_str.size()); - string_replace_all(function_body_str, '\n', ' '); - - size_t var_dot_index = function_body_str.find('.'); - if(var_dot_index == std::string::npos) { - fprintf(stderr, "YoutubeSignatureDecryptor: Regex match 5 invalid. Youtube likely updated and QuickMedia needs to be fixed?\n"); - return U_DEC_FUN_FAILED_MATCH_ERR; - } - - std::string var_name = function_body_str.substr(0, var_dot_index); - string_replace_all(response, '\n', ' '); - std::smatch var_body_match; - if(!std::regex_search(response, var_body_match, std::regex("var " + var_name + "=\\{(.*?)\\};", std::regex::ECMAScript)) || var_body_match.size() != 2) { - fprintf(stderr, "YoutubeSignatureDecryptor: Regex match 6 invalid. Youtube likely updated and QuickMedia needs to be fixed?\n"); - return U_DEC_FUN_FAILED_MATCH_ERR; - } - - std::string var_body_str = var_body_match[1].str(); - string_replace_all(var_body_str, '\n', ' '); - - std::vector<DecryptFuncCall> new_func_calls; - std::map<std::string, DecryptFunction> new_func_decls; - if(!js_code_to_operations(function_body_str, var_body_str, new_func_calls, new_func_decls)) { - fprintf(stderr, "YoutubeSignatureDecryptor: Regex match 7 invalid. Youtube likely updated and QuickMedia needs to be fixed?\n"); - return U_DEC_FUN_FAILED_MATCH_ERR; - } - - { - std::lock_guard<std::mutex> lock(update_signature_mutex); - decryption_function = function_body_str + "\n" + var_body_str; - decrypt_function_last_updated = time(nullptr); - up_to_date = true; - func_calls = std::move(new_func_calls); - func_decls = std::move(new_func_decls); - } - - file_overwrite_atomic(get_cache_dir().join("youtube").join("decryption_function"), decryption_function); - return 0; - } -} -#endif
\ No newline at end of file |