aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2022-01-23 18:33:28 +0100
committerdec05eba <dec05eba@protonmail.com>2022-01-23 18:33:28 +0100
commit8a05b8cdd48acca84fc52981d8cb989c849ea3cc (patch)
tree74f12222c6c063954d768bbe0437fa8dca85fddb
parent4ac963533bd7e538febf001cc158fcbd46f0267a (diff)
Youtube: show timestamps for video comments with timestamp at end of line, remove unused signature code for now
-rw-r--r--include/StringUtils.hpp1
-rw-r--r--plugins/youtube/Signature.hpp47
-rw-r--r--src/StringUtils.cpp21
-rw-r--r--src/plugins/Youtube.cpp79
-rw-r--r--src/plugins/youtube/Signature.cpp316
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