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/QuickMedia.cpp | 25 ++++++---- src/plugins/Info.cpp | 11 ++++- src/plugins/Matrix.cpp | 2 +- src/plugins/Youtube.cpp | 118 +++++++++++++++++++++++++++++++++--------------- 4 files changed, 110 insertions(+), 46 deletions(-) (limited to 'src') diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index 13931d2..69ac66e 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -318,7 +318,7 @@ namespace QuickMedia { } static void usage() { - fprintf(stderr, "usage: quickmedia [plugin] [--no-video] [--dir ] [-e ] [youtube-url]\n"); + fprintf(stderr, "usage: quickmedia [plugin] [--no-video] [--upscale-images] [--upscale-images-always] [--dir ] [--instance ] [-e ] [--video-max-height ] [youtube-url] [youtube-channel-url]\n"); fprintf(stderr, "OPTIONS:\n"); fprintf(stderr, " plugin The plugin to use. Should be either launcher, 4chan, manga, manganelo, manganelos, mangatown, mangakatana, mangadex, readm, onimanga, local-manga, local-anime, youtube, peertube, lbry, soundcloud, nyaa.si, matrix, saucenao, hotexamples, anilist, dramacool, file-manager, stdin, pornhub, spankbang, xvideos or xhamster\n"); fprintf(stderr, " --no-video Only play audio when playing a video. Disabled by default\n"); @@ -387,11 +387,17 @@ namespace QuickMedia { for(int i = 1; i < argc; ++i) { if(!plugin_name) { - std::string youtube_video_id_dummy; std::string youtube_url_converted = invidious_url_to_youtube_url(argv[i]); - if(youtube_url_extract_id(youtube_url_converted, youtube_video_id_dummy)) { + std::string youtube_channel_id; + std::string youtube_video_id_dummy; + + if(youtube_url_extract_channel_id(youtube_url_converted, youtube_channel_id, youtube_channel_url)) { + plugin_name = "youtube"; + continue; + } else if(youtube_url_extract_id(youtube_url_converted, youtube_video_id_dummy)) { youtube_url = std::move(youtube_url_converted); plugin_name = "youtube"; + continue; } for(const auto &valid_plugin : valid_plugins) { @@ -1308,7 +1314,14 @@ namespace QuickMedia { pipe_body->set_items(std::move(body_items)); tabs.push_back(Tab{std::move(pipe_body), std::make_unique(this), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); } else if(strcmp(plugin_name, "youtube") == 0) { - if(youtube_url.empty()) { + if(!youtube_channel_url.empty()) { + auto youtube_channel_page = std::make_unique(this, youtube_channel_url, "", "Channel videos"); + tabs.push_back(Tab{create_body(false, true), std::move(youtube_channel_page), create_search_bar("Search...", 350)}); + } else if(!youtube_url.empty()) { + current_page = PageType::VIDEO_CONTENT; + auto youtube_video_page = std::make_unique(this, youtube_url, false); + video_content_page(nullptr, youtube_video_page.get(), "", false, nullptr, 0); + } else { start_tab_index = 1; tabs.push_back(Tab{create_body(false, true), std::make_unique(this), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); tabs.push_back(Tab{create_body(false, true), std::make_unique(this), create_search_bar("Search...", 100)}); @@ -1316,10 +1329,6 @@ namespace QuickMedia { auto history_body = create_body(false, true); auto history_page = std::make_unique(this, tabs.front().page.get(), HistoryType::YOUTUBE); tabs.push_back(Tab{std::move(history_body), std::move(history_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); - } else { - current_page = PageType::VIDEO_CONTENT; - auto youtube_video_page = std::make_unique(this, youtube_url, false); - video_content_page(nullptr, youtube_video_page.get(), "", false, nullptr, 0); } } else if(strcmp(plugin_name, "peertube") == 0) { if(instance.empty()) { 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