From 9c1d43e772efb8f5af4b7ef5562fb433c8985697 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Sat, 29 Oct 2022 21:33:18 +0200 Subject: Youtube: allow opening youtube channels directly from url. Same with ctrl+i info menu --- src/plugins/Info.cpp | 11 ++++- src/plugins/Matrix.cpp | 2 +- src/plugins/Youtube.cpp | 118 +++++++++++++++++++++++++++++++++--------------- 3 files changed, 93 insertions(+), 38 deletions(-) (limited to 'src/plugins') diff --git a/src/plugins/Info.cpp b/src/plugins/Info.cpp index 252cbea..629a08f 100644 --- a/src/plugins/Info.cpp +++ b/src/plugins/Info.cpp @@ -14,6 +14,10 @@ namespace QuickMedia { return url.find("youtube.com/") != std::string::npos || url.find("youtu.be/") != std::string::npos; } + static bool is_youtube_channel_url(const std::string &url) { + return url.find("youtube.com/c/") != std::string::npos || url.find("youtu.be/c/") != std::string::npos || url.find("youtube.com/channel/") != std::string::npos || url.find("youtu.be/channel/") != std::string::npos; + } + static PluginResult open_with_browser(const std::string &url) { const char *launch_program = "xdg-open"; if(!is_program_executable_by_name("xdg-open")) { @@ -41,6 +45,9 @@ namespace QuickMedia { const std::string search_term = args.url.substr(strlen(GOOGLE_SEARCH_URL)); const std::string search_url = "https://www.google.com/search?q=" + url_param_encode(search_term); return open_with_browser(search_url); + } else if(is_youtube_channel_url(args.url)) { + result_tabs.push_back(Tab{create_body(false, true), std::make_unique(program, args.url, "", "Channel videos"), create_search_bar("Search...", 350)}); + return PluginResult::OK; } else if(is_youtube_url(args.url)) { result_tabs.push_back(Tab{nullptr, std::make_unique(program, args.url, false), nullptr}); return PluginResult::OK; @@ -66,7 +73,9 @@ namespace QuickMedia { // static std::shared_ptr InfoPage::add_url(const std::string &url) { std::string title; - if(is_youtube_url(url)) + if(is_youtube_channel_url(url)) + title = "Open youtube channel " + url; + else if(is_youtube_url(url)) title = "Play " + url; else title = "Open " + url + " in a browser"; diff --git a/src/plugins/Matrix.cpp b/src/plugins/Matrix.cpp index 13d42bb..7318383 100644 --- a/src/plugins/Matrix.cpp +++ b/src/plugins/Matrix.cpp @@ -3411,7 +3411,7 @@ namespace QuickMedia { rapidjson::Document request_data(rapidjson::kObjectType); request_data.AddMember("msgtype", rapidjson::StringRef(file_info ? content_type_to_message_type(file_info->content_type) : "m.text"), request_data.GetAllocator()); - request_data.AddMember("body", rapidjson::StringRef(message_reply_body.c_str()), request_data.GetAllocator()); + request_data.AddMember("body", rapidjson::StringRef(file_info ? body.c_str() : message_reply_body.c_str()), request_data.GetAllocator()); request_data.AddMember("format", "org.matrix.custom.html", request_data.GetAllocator()); request_data.AddMember("formatted_body", rapidjson::StringRef(formatted_message_reply_body.c_str()), request_data.GetAllocator()); request_data.AddMember("m.relates_to", std::move(relates_to_json), request_data.GetAllocator()); diff --git a/src/plugins/Youtube.cpp b/src/plugins/Youtube.cpp index 4dbb6c4..f2fb36e 100644 --- a/src/plugins/Youtube.cpp +++ b/src/plugins/Youtube.cpp @@ -91,6 +91,54 @@ namespace QuickMedia { return false; } + bool youtube_url_extract_channel_id(const std::string &youtube_url, std::string &channel_id, std::string &channel_url) { + size_t index = youtube_url.find("youtube.com/c/"); + if(index != std::string::npos) { + index += 14; + size_t end_index = youtube_url.find("/", index); + if(end_index == std::string::npos) + end_index = youtube_url.size(); + channel_id = youtube_url.substr(index, end_index - index); + channel_url = "https://www.youtube.com/c/" + channel_id; + return true; + } + + index = youtube_url.find("youtu.be/c/"); + if(index != std::string::npos) { + index += 11; + size_t end_index = youtube_url.find("/", index); + if(end_index == std::string::npos) + end_index = youtube_url.size(); + channel_id = youtube_url.substr(index, end_index - index); + channel_url = "https://www.youtube.com/c/" + channel_id; + return true; + } + + index = youtube_url.find("youtube.com/channel/"); + if(index != std::string::npos) { + index += 20; + size_t end_index = youtube_url.find("/", index); + if(end_index == std::string::npos) + end_index = youtube_url.size(); + channel_id = youtube_url.substr(index, end_index - index); + channel_url = "https://www.youtube.com/channel/" + channel_id; + return true; + } + + index = youtube_url.find("youtu.be/channel/"); + if(index != std::string::npos) { + index += 17; + size_t end_index = youtube_url.find("/", index); + if(end_index == std::string::npos) + end_index = youtube_url.size(); + channel_id = youtube_url.substr(index, end_index - index); + channel_url = "https://www.youtube.com/channel/" + channel_id; + return true; + } + + return false; + } + static std::mutex cookies_mutex; static std::string cookies_filepath; static std::string api_key; @@ -687,10 +735,20 @@ namespace QuickMedia { return ""; } - static void parse_channel_videos(const Json::Value &json_root, std::string &continuation_token, std::unordered_set &added_videos, BodyItems &body_items) { + static void parse_channel_videos(const Json::Value &json_root, std::string &continuation_token, std::unordered_set &added_videos, std::string &browse_id, BodyItems &body_items) { if(!json_root.isObject()) return; + const Json::Value &endpoint_json = json_root["endpoint"]; + if(endpoint_json.isObject()) { + const Json::Value &browse_endpoint_json = endpoint_json["browseEndpoint"]; + if(browse_endpoint_json.isObject()) { + const Json::Value &browse_id_json = browse_endpoint_json["browseId"]; + if(browse_id_json.isString()) + browse_id = browse_id_json.asString(); + } + } + const Json::Value *response_json = &json_root["response"]; if(!response_json->isObject()) response_json = &json_root; @@ -1415,19 +1473,6 @@ namespace QuickMedia { return fetch_comments(this, video_url, continuation_token, result_items); } - static std::string channel_url_extract_id(const std::string &channel_url) { - size_t index = channel_url.find("channel/"); - if(index == std::string::npos) - return ""; - - index += 8; - size_t end_index = channel_url.find('/', index); - if(end_index == std::string::npos) - return channel_url.substr(index); - - return channel_url.substr(index, end_index - index); - } - SearchResult YoutubeChannelPage::search(const std::string &str, BodyItems &result_items) { added_videos.clear(); continuation_token.clear(); @@ -1435,6 +1480,13 @@ namespace QuickMedia { if(str.empty()) return plugin_result_to_search_result(lazy_fetch(result_items)); + std::string channel_id; + std::string channel_url; + if(!youtube_url_extract_channel_id(url, channel_id, channel_url)) { + fprintf(stderr, "Error: failed to extract youtube channel id from url: %s\n", url.c_str()); + return SearchResult::ERR; + } + std::vector cookies = get_cookies(); std::string next_url = "https://www.youtube.com/youtubei/v1/browse?key=" + url_param_encode(api_key) + "&gl=US&hl=en&prettyPrint=false"; @@ -1453,7 +1505,7 @@ namespace QuickMedia { client_json["originalUrl"] = url + "/videos"; context_json["client"] = std::move(client_json); request_json["context"] = std::move(context_json); - request_json["browseId"] = channel_url_extract_id(url); + request_json["browseId"] = channel_id; request_json["query"] = str; request_json["params"] = "EgZzZWFyY2g%3D"; //request_json["continuation"] = current_continuation_token; @@ -1668,35 +1720,29 @@ namespace QuickMedia { DownloadResult result = download_json(json_root, url + "/videos?pbj=1&gl=US&hl=en", std::move(additional_args), true); if(result != DownloadResult::OK) return download_result_to_plugin_result(result); + std::string browse_id; if(json_root.isObject()) { - search_page_submit_suggestion_handler(json_root, continuation_token, result_items, added_videos); - parse_channel_videos(json_root, continuation_token, added_videos, result_items); - return PluginResult::OK; - } - - if(!json_root.isArray()) + //search_page_submit_suggestion_handler(json_root, continuation_token, result_items, added_videos); + parse_channel_videos(json_root, continuation_token, added_videos, browse_id, result_items); + } else if(json_root.isArray()) { + for(const Json::Value &json_item : json_root) { + //search_page_submit_suggestion_handler(json_root, continuation_token, result_items, added_videos); + parse_channel_videos(json_item, continuation_token, added_videos, browse_id, result_items); + } + } else { return PluginResult::ERR; - - for(const Json::Value &json_item : json_root) { - search_page_submit_suggestion_handler(json_root, continuation_token, result_items, added_videos); - parse_channel_videos(json_item, continuation_token, added_videos, result_items); } + + if(!browse_id.empty()) + url = "https://www.youtube.com/channel/" + std::move(browse_id); return PluginResult::OK; } TrackResult YoutubeChannelPage::track(const std::string&) { - size_t channel_id_start = url.find("/channel/"); - if(channel_id_start == std::string::npos) { - show_notification("QuickMedia", "Unable to get channel id from " + url, Urgency::CRITICAL); - return TrackResult::ERR; - } - - channel_id_start += 9; - size_t channel_id_end = url.find('/', channel_id_start); - if(channel_id_end == std::string::npos) channel_id_end = url.size(); - std::string channel_id = url.substr(channel_id_start, channel_id_end - channel_id_start); - if(channel_id.empty()) { + std::string channel_id; + std::string channel_url; + if(!youtube_url_extract_channel_id(url, channel_id, channel_url)) { show_notification("QuickMedia", "Unable to get channel id from " + url, Urgency::CRITICAL); return TrackResult::ERR; } -- cgit v1.2.3