aboutsummaryrefslogtreecommitdiff
path: root/src/plugins/Youtube.cpp
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2021-05-05 15:04:32 +0200
committerdec05eba <dec05eba@protonmail.com>2021-05-05 15:04:32 +0200
commit9b6a875bb65000115451279ed17c7c51c4173fa7 (patch)
tree04323fed8063c816631e000cd382e172d50e3c7b /src/plugins/Youtube.cpp
parentb6b4db460ce5bfdfe7c4e46a89ef370b6cc31752 (diff)
Prepare youtube for auto fetch of api key, add author name for subscriptions item
Diffstat (limited to 'src/plugins/Youtube.cpp')
-rw-r--r--src/plugins/Youtube.cpp115
1 files changed, 92 insertions, 23 deletions
diff --git a/src/plugins/Youtube.cpp b/src/plugins/Youtube.cpp
index cbe6b81..09568f6 100644
--- a/src/plugins/Youtube.cpp
+++ b/src/plugins/Youtube.cpp
@@ -333,6 +333,11 @@ namespace QuickMedia {
static std::mutex cookies_mutex;
static std::string cookies_filepath;
+ static std::string api_key;
+
+ static bool is_whitespace(char c) {
+ return (c >= 8 && c <= 13) || c == ' ';
+ }
static void remove_cookies_file_at_exit() {
std::lock_guard<std::mutex> lock(cookies_mutex);
@@ -340,6 +345,45 @@ namespace QuickMedia {
remove(cookies_filepath.c_str());
}
+ // TODO: Cache this and redownload it when a network request fails with this api key?
+ static std::string youtube_page_find_api_key() {
+ size_t api_key_index;
+ size_t api_key_index_end;
+ size_t api_key_length;
+ std::string website_result;
+ std::string::iterator api_key_start;
+
+ if(download_to_string("https://www.youtube.com", website_result, {}, true) != DownloadResult::OK)
+ goto fallback;
+
+ api_key_index = website_result.find("INNERTUBE_API_KEY");
+ if(api_key_index == std::string::npos)
+ goto fallback;
+
+ api_key_index += 17;
+ api_key_start = std::find_if(website_result.begin() + api_key_index, website_result.end(), [](char c) {
+ return c != '"' && c != ':' && !is_whitespace(c);
+ });
+
+ if(api_key_start == website_result.end())
+ goto fallback;
+
+ api_key_index = api_key_start - website_result.begin();
+ api_key_index_end = website_result.find('"', api_key_index);
+ if(api_key_index_end == std::string::npos)
+ goto fallback;
+
+ api_key_length = api_key_index_end - api_key_index;
+ if(api_key_length > 512) // sanity check
+ goto fallback;
+
+ return website_result.substr(api_key_index, api_key_length);
+
+ fallback:
+ fprintf(stderr, "Failed to fetch youtube api key, fallback to %s\n", api_key.c_str());
+ return "AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8";
+ }
+
static std::vector<CommandArg> get_cookies() {
std::lock_guard<std::mutex> lock(cookies_mutex);
if(cookies_filepath.empty()) {
@@ -352,6 +396,13 @@ namespace QuickMedia {
cookies_filepath = filename;
atexit(remove_cookies_file_at_exit);
+ // TODO: Re-enable this if the api key ever changes in the future
+ #if 0
+ //api_key = youtube_page_find_api_key();
+ #else
+ api_key = "AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8";
+ #endif
+
// TODO: Is there any way to bypass this? this is needed to set VISITOR_INFO1_LIVE which is required to read comments
const char *args[] = { "curl", "-I", "-s", "-b", cookies_filepath.c_str(), "-c", cookies_filepath.c_str(), "https://www.youtube.com/subscription_manager?disable_polymer=1", nullptr };
if(exec_program(args, nullptr, nullptr) != 0)
@@ -954,7 +1005,8 @@ namespace QuickMedia {
if(str.empty())
return plugin_result_to_search_result(lazy_fetch(result_items));
- std::string next_url = "https://www.youtube.com/youtubei/v1/browse?key=AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8";
+ std::vector<CommandArg> cookies = get_cookies();
+ std::string next_url = "https://www.youtube.com/youtubei/v1/browse?key=" + url_param_encode(api_key);
Json::Value request_json(Json::objectValue);
Json::Value context_json(Json::objectValue);
@@ -990,7 +1042,6 @@ namespace QuickMedia {
{ "--data-raw", Json::writeString(json_builder, request_json) }
};
- std::vector<CommandArg> cookies = get_cookies();
additional_args.insert(additional_args.end(), cookies.begin(), cookies.end());
Json::Value json_root;
@@ -1040,7 +1091,8 @@ namespace QuickMedia {
}
PluginResult YoutubeChannelPage::search_get_continuation(const std::string &url, const std::string &current_continuation_token, BodyItems &result_items) {
- std::string next_url = "https://www.youtube.com/youtubei/v1/browse?key=AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8";
+ std::vector<CommandArg> cookies = get_cookies();
+ std::string next_url = "https://www.youtube.com/youtubei/v1/browse?key=" + url_param_encode(api_key);
Json::Value request_json(Json::objectValue);
Json::Value context_json(Json::objectValue);
@@ -1073,7 +1125,6 @@ namespace QuickMedia {
{ "--data-raw", Json::writeString(json_builder, request_json) }
};
- std::vector<CommandArg> cookies = get_cookies();
additional_args.insert(additional_args.end(), cookies.begin(), cookies.end());
Json::Value json_root;
@@ -1223,7 +1274,9 @@ namespace QuickMedia {
};
struct SubscriptionData {
- SubscriptionEntry subscription_entry;
+ std::vector<SubscriptionEntry> subscription_entry;
+ std::string author;
+ bool inside_title = false;
bool inside_entry = false;
};
@@ -1285,27 +1338,38 @@ namespace QuickMedia {
return {YoutubeSubscriptionTaskResult{body_item, 0}};
}
- std::vector<SubscriptionData> subscription_data_list;
+ SubscriptionData subscription_data;
html_parser_parse(website_data.data(), website_data.size(), [](HtmlParser *html_parser, HtmlParseType parse_type, void *userdata) {
- std::vector<SubscriptionData> &subscription_data_list = *(std::vector<SubscriptionData>*)userdata;
+ SubscriptionData &subscription_data = *(SubscriptionData*)userdata;
+
+ if(!subscription_data.inside_entry && subscription_data.author.empty()) {
+ if(parse_type == HTML_PARSE_TAG_START && string_view_equals(&html_parser->tag_name, "title")) {
+ subscription_data.inside_title = true;
+ return;
+ } else if(parse_type == HTML_PARSE_TAG_END && string_view_equals(&html_parser->tag_name, "title")) {
+ subscription_data.inside_title = false;
+ subscription_data.author.assign(html_parser->text_stripped.data, html_parser->text_stripped.size);
+ return;
+ }
+ }
if(parse_type == HTML_PARSE_TAG_START && string_view_equals(&html_parser->tag_name, "entry")) {
- subscription_data_list.push_back({});
- subscription_data_list.back().inside_entry = true;
+ subscription_data.subscription_entry.push_back({});
+ subscription_data.inside_entry = true;
return;
} else if(parse_type == HTML_PARSE_TAG_END && string_view_equals(&html_parser->tag_name, "entry")) {
- subscription_data_list.back().inside_entry = false;
+ subscription_data.inside_entry = false;
return;
}
- if(subscription_data_list.empty() || !subscription_data_list.back().inside_entry)
+ if(!subscription_data.inside_entry)
return;
if(string_view_equals(&html_parser->tag_name, "title") && parse_type == HTML_PARSE_TAG_END) {
- subscription_data_list.back().subscription_entry.title.assign(html_parser->text_stripped.data, html_parser->text_stripped.size);
+ subscription_data.subscription_entry.back().title.assign(html_parser->text_stripped.data, html_parser->text_stripped.size);
} else if(string_view_equals(&html_parser->tag_name, "yt:videoId") && parse_type == HTML_PARSE_TAG_END) {
- subscription_data_list.back().subscription_entry.video_id.assign(html_parser->text_stripped.data, html_parser->text_stripped.size);
+ subscription_data.subscription_entry.back().video_id.assign(html_parser->text_stripped.data, html_parser->text_stripped.size);
} else if(string_view_equals(&html_parser->tag_name, "published") && parse_type == HTML_PARSE_TAG_END) {
std::string published_str(html_parser->text_stripped.data, html_parser->text_stripped.size);
@@ -1327,23 +1391,28 @@ namespace QuickMedia {
time.tm_min = minute;
time.tm_sec = second;
- subscription_data_list.back().subscription_entry.published = timegm(&time);
+ subscription_data.subscription_entry.back().published = timegm(&time);
}
- }, &subscription_data_list);
+ }, &subscription_data);
std::vector<YoutubeSubscriptionTaskResult> results;
- for(SubscriptionData &subscription_data : subscription_data_list) {
- if(subscription_data.subscription_entry.title.empty() || subscription_data.subscription_entry.video_id.empty() || subscription_data.subscription_entry.published == 0)
+ for(SubscriptionEntry &subscription_entry : subscription_data.subscription_entry) {
+ if(subscription_entry.title.empty() || subscription_entry.video_id.empty() || subscription_entry.published == 0)
continue;
- html_unescape_sequences(subscription_data.subscription_entry.title);
- auto body_item = BodyItem::create(std::move(subscription_data.subscription_entry.title));
- body_item->set_description("Uploaded " + seconds_to_relative_time_str(time_now - subscription_data.subscription_entry.published));
+ html_unescape_sequences(subscription_entry.title);
+ auto body_item = BodyItem::create(std::move(subscription_entry.title));
+ std::string description = "Uploaded " + seconds_to_relative_time_str(time_now - subscription_entry.published);
+ if(!subscription_data.author.empty()) {
+ description += '\n';
+ description += subscription_data.author;
+ }
+ body_item->set_description(std::move(description));
body_item->set_description_color(sf::Color(179, 179, 179));
- body_item->url = "https://www.youtube.com/watch?v=" + subscription_data.subscription_entry.video_id;
- body_item->thumbnail_url = "https://img.youtube.com/vi/" + subscription_data.subscription_entry.video_id + "/mqdefault.jpg";
+ body_item->url = "https://www.youtube.com/watch?v=" + subscription_entry.video_id;
+ body_item->thumbnail_url = "https://img.youtube.com/vi/" + subscription_entry.video_id + "/mqdefault.jpg";
body_item->thumbnail_size = sf::Vector2i(192, 108);
- results.push_back({std::move(body_item), subscription_data.subscription_entry.published});
+ results.push_back({std::move(body_item), subscription_entry.published});
}
return results;
});