aboutsummaryrefslogtreecommitdiff
path: root/src/plugins/youtube/Signature.cpp
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2021-06-11 04:51:03 +0200
committerdec05eba <dec05eba@protonmail.com>2021-06-11 04:51:03 +0200
commit89383cff1ba5d8a928262fcb4c40382a981c78c8 (patch)
treeda8b6062cc6770fd37e7a6f7b8c09fb61f46f7b2 /src/plugins/youtube/Signature.cpp
parent5b3becc79461d4ecf015e33515871cc09e26e04e (diff)
Remove dependency on youtube-dl for streaming youtube, resulting in faster video startup
Diffstat (limited to 'src/plugins/youtube/Signature.cpp')
-rw-r--r--src/plugins/youtube/Signature.cpp307
1 files changed, 307 insertions, 0 deletions
diff --git a/src/plugins/youtube/Signature.cpp b/src/plugins/youtube/Signature.cpp
new file mode 100644
index 0000000..30093d5
--- /dev/null
+++ b/src/plugins/youtube/Signature.cpp
@@ -0,0 +1,307 @@
+#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 <unistd.h>
+
+namespace QuickMedia {
+ enum UpdateDecryptFunctionError {
+ U_DEC_FUN_NET_ERR = 1,
+ U_DEC_FUN_FAILED_MATCH_ERR = 2
+ };
+
+ static YoutubeSignatureDecryptor *instance = nullptr;
+ static const int timeout_default_sec = 60;
+
+ 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 long value_int = strtol(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(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
+ decrypt_function = DecryptFunction::SWAP;
+ //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;
+ }
+ }
+ }
+ }
+
+ running = true;
+ poll_task = AsyncTask<void>([this]() mutable {
+ bool has_notified_error = false;
+ int decrypt_function_update_timeout_seconds = timeout_default_sec;
+ while(running) {
+ time_t time_now = time(nullptr);
+ if(time_now - decrypt_function_last_updated > decrypt_function_update_timeout_seconds) {
+ int update_res = update_decrypt_function();
+ if(update_res != 0) {
+ if(!has_notified_error) {
+ if(program_is_dead_in_current_thread()) {
+ running = false;
+ return;
+ } else if(update_res == U_DEC_FUN_NET_ERR) {
+ show_notification("QuickMedia", "Failed to decrypt youtube signature. Is your internet down?", Urgency::CRITICAL);
+ decrypt_function_update_timeout_seconds = 10;
+ } 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;
+ return;
+ }
+ }
+ has_notified_error = true;
+ } else {
+ decrypt_function_update_timeout_seconds = timeout_default_sec;
+ }
+ }
+ usleep(1 * 1000 * 1000); // 1 second
+ }
+ });
+ }
+
+ YoutubeSignatureDecryptor::~YoutubeSignatureDecryptor() {
+ running = false;
+ }
+
+ // static
+ YoutubeSignatureDecryptor& YoutubeSignatureDecryptor::get_instance() {
+ 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
+ std::lock_guard<std::mutex> lock(update_signature_mutex);
+ 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 up 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()) {
+ 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;
+ return true;
+ }
+
+ int YoutubeSignatureDecryptor::update_decrypt_function() {
+ std::string response;
+ DownloadResult download_result = download_to_string("https://www.youtube.com/watch?v=CvFH_6DNRCY&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;
+ }
+} \ No newline at end of file