From 9b6a875bb65000115451279ed17c7c51c4173fa7 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Wed, 5 May 2021 15:04:32 +0200 Subject: Prepare youtube for auto fetch of api key, add author name for subscriptions item --- src/plugins/Youtube.cpp | 115 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 92 insertions(+), 23 deletions(-) (limited to 'src/plugins/Youtube.cpp') 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 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 get_cookies() { std::lock_guard 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 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 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 ¤t_continuation_token, BodyItems &result_items) { - std::string next_url = "https://www.youtube.com/youtubei/v1/browse?key=AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8"; + std::vector 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 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 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 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 &subscription_data_list = *(std::vector*)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 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; }); -- cgit v1.2.3