#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 #include #include 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 &new_func_calls, std::map &new_func_decls) { std::vector 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 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 new_func_calls; std::map 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([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 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 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 new_func_calls; std::map 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 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