aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--plugins/Youtube.hpp11
-rw-r--r--src/QuickMedia.cpp6
-rw-r--r--src/plugins/Info.cpp2
-rw-r--r--src/plugins/Youtube.cpp107
4 files changed, 108 insertions, 18 deletions
diff --git a/plugins/Youtube.hpp b/plugins/Youtube.hpp
index de8a0c3..1088df9 100644
--- a/plugins/Youtube.hpp
+++ b/plugins/Youtube.hpp
@@ -103,7 +103,15 @@ namespace QuickMedia {
class YoutubeChannelPage : public LazyFetchPage, public TrackablePage {
public:
- YoutubeChannelPage(Program *program, std::string url, std::string continuation_token, std::string title) : LazyFetchPage(program), TrackablePage(title, url), url(url), continuation_token(std::move(continuation_token)), title(title) {}
+ enum class Type {
+ VIDEOS,
+ SHORTS,
+ LIVE
+ };
+
+ static void create_each_type(Program *program, std::string url, std::string continuation_token, std::string title, std::vector<Tab> &tabs);
+
+ YoutubeChannelPage(Program *program, std::string url, std::string continuation_token, std::string title, Type type);
const char* get_title() const override { return title.c_str(); }
bool search_is_filter() override { return false; }
SearchResult search(const std::string &str, BodyItems &result_items) override;
@@ -122,6 +130,7 @@ namespace QuickMedia {
std::string continuation_token;
const std::string title;
int current_page = 0;
+ Type type;
};
struct YoutubeSubscriptionTaskResult {
diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp
index ea74c09..ae3d720 100644
--- a/src/QuickMedia.cpp
+++ b/src/QuickMedia.cpp
@@ -1323,8 +1323,7 @@ namespace QuickMedia {
tabs.push_back(Tab{std::move(pipe_body), std::make_unique<PipePage>(this), create_search_bar("Search...", SEARCH_DELAY_FILTER)});
} else if(strcmp(plugin_name, "youtube") == 0) {
if(!youtube_channel_url.empty()) {
- auto youtube_channel_page = std::make_unique<YoutubeChannelPage>(this, youtube_channel_url, "", "Channel videos");
- tabs.push_back(Tab{create_body(false, true), std::move(youtube_channel_page), create_search_bar("Search...", 350)});
+ YoutubeChannelPage::create_each_type(this, youtube_channel_url, "", "Channel", tabs);
} else if(!youtube_url.empty()) {
current_page = PageType::VIDEO_CONTENT;
auto youtube_video_page = std::make_unique<YoutubeVideoPage>(this, youtube_url, false);
@@ -6459,8 +6458,7 @@ namespace QuickMedia {
std::string video_id;
if(youtube_url_extract_channel_id(url, youtube_channel_id, youtube_channel_url)) {
std::vector<Tab> tabs;
- auto youtube_channel_page = std::make_unique<YoutubeChannelPage>(this, youtube_channel_url, "", "Channel videos");
- tabs.push_back(Tab{create_body(false, true), std::move(youtube_channel_page), create_search_bar("Search...", 350)});
+ YoutubeChannelPage::create_each_type(this, youtube_channel_url, "", "Channel", tabs);
page_loop(tabs);
redraw = true;
avatar_applied = false;
diff --git a/src/plugins/Info.cpp b/src/plugins/Info.cpp
index 629a08f..ed63aae 100644
--- a/src/plugins/Info.cpp
+++ b/src/plugins/Info.cpp
@@ -46,7 +46,7 @@ namespace QuickMedia {
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<YoutubeChannelPage>(program, args.url, "", "Channel videos"), create_search_bar("Search...", 350)});
+ YoutubeChannelPage::create_each_type(program, args.url, "", "Channel", result_tabs);
return PluginResult::OK;
} else if(is_youtube_url(args.url)) {
result_tabs.push_back(Tab{nullptr, std::make_unique<YoutubeVideoPage>(program, args.url, false), nullptr});
diff --git a/src/plugins/Youtube.cpp b/src/plugins/Youtube.cpp
index d3d498e..c6e8533 100644
--- a/src/plugins/Youtube.cpp
+++ b/src/plugins/Youtube.cpp
@@ -477,6 +477,34 @@ namespace QuickMedia {
return false;
}
+ static std::optional<std::string> video_item_get_reel_published_date(const Json::Value &video_item_json) {
+ const Json::Value &navigation_endpoint_json = video_item_json["navigationEndpoint"];
+ if(!navigation_endpoint_json.isObject())
+ return std::nullopt;
+
+ const Json::Value &reel_watch_endpoint_json = navigation_endpoint_json["reelWatchEndpoint"];
+ if(!reel_watch_endpoint_json.isObject())
+ return std::nullopt;
+
+ const Json::Value &overlay_json = reel_watch_endpoint_json["overlay"];
+ if(!overlay_json.isObject())
+ return std::nullopt;
+
+ const Json::Value &reel_player_overlay_renderer_json = overlay_json["reelPlayerOverlayRenderer"];
+ if(!reel_player_overlay_renderer_json.isObject())
+ return std::nullopt;
+
+ const Json::Value &reel_player_header_supported_renderers_json = reel_player_overlay_renderer_json["reelPlayerHeaderSupportedRenderers"];
+ if(!reel_player_header_supported_renderers_json.isObject())
+ return std::nullopt;
+
+ const Json::Value &reel_player_header_renderer_json = reel_player_header_supported_renderers_json["reelPlayerHeaderRenderer"];
+ if(!reel_player_header_renderer_json.isObject())
+ return std::nullopt;
+
+ return yt_json_get_text(reel_player_header_renderer_json, "timestampText");
+ }
+
static std::shared_ptr<BodyItem> parse_common_video_item(const Json::Value &video_item_json, std::unordered_set<std::string> &added_videos) {
const Json::Value &video_id_json = video_item_json["videoId"];
if(!video_id_json.isString())
@@ -487,9 +515,13 @@ namespace QuickMedia {
return nullptr;
std::optional<std::string> title = yt_json_get_text(video_item_json, "title");
- if(!title)
+ std::optional<std::string> headline = yt_json_get_text(video_item_json, "headline");
+ if(!title && !headline)
return nullptr;
+ if(!title)
+ title = std::move(headline);
+
std::optional<std::string> date = yt_json_get_text(video_item_json, "publishedTimeText");
std::optional<std::string> view_count_text = yt_json_get_text(video_item_json, "viewCountText");
std::optional<std::string> owner_text = yt_json_get_text(video_item_json, "shortBylineText");
@@ -504,6 +536,9 @@ namespace QuickMedia {
}
}
+ if(!date)
+ date = video_item_get_reel_published_date(video_item_json);
+
std::string scheduled_text;
const Json::Value &upcoming_event_data_json = video_item_json["upcomingEventData"];
if(upcoming_event_data_json.isObject()) {
@@ -802,11 +837,14 @@ namespace QuickMedia {
if(!item_content_json.isObject())
continue;
- const Json::Value &video_renderer_json = item_content_json["videoRenderer"];
- if(!video_renderer_json.isObject())
- continue;
+ const Json::Value *video_renderer_json = &item_content_json["videoRenderer"];
+ if(!video_renderer_json->isObject()) {
+ video_renderer_json = &item_content_json["reelItemRenderer"];
+ if(!video_renderer_json->isObject())
+ continue;
+ }
- auto body_item = parse_common_video_item(video_renderer_json, added_videos);
+ auto body_item = parse_common_video_item(*video_renderer_json, added_videos);
if(body_item)
body_items.push_back(std::move(body_item));
}
@@ -970,7 +1008,7 @@ namespace QuickMedia {
if(strncmp(args.url.c_str(), "https://www.youtube.com/channel/", 32) == 0) {
// TODO: Make all pages (for all services) lazy fetch in a similar manner!
- result_tabs.push_back(Tab{create_body(false, true), std::make_unique<YoutubeChannelPage>(program, args.url, "", args.title), create_search_bar("Search...", 350)});
+ YoutubeChannelPage::create_each_type(program, args.url, "", args.title, result_tabs);
} else {
result_tabs.push_back(Tab{nullptr, std::make_unique<YoutubeVideoPage>(program, args.url), nullptr});
}
@@ -1486,6 +1524,52 @@ namespace QuickMedia {
return fetch_comments(this, video_url, continuation_token, result_items);
}
+ static const char* youtube_channel_page_type_to_api_params(YoutubeChannelPage::Type type) {
+ switch(type) {
+ case YoutubeChannelPage::Type::VIDEOS:
+ return "EgZ2aWRlb3PyBgQKAjoA";
+ case YoutubeChannelPage::Type::SHORTS:
+ return "EgZzaG9ydHPyBgUKA5oBAA%3D%3D";
+ case YoutubeChannelPage::Type::LIVE:
+ return "EgdzdHJlYW1z8gYECgJ6AA%3D%3D";
+ }
+ return "";
+ }
+
+ static const char* youtube_channel_page_type_get_endpoint(YoutubeChannelPage::Type type) {
+ switch(type) {
+ case YoutubeChannelPage::Type::VIDEOS:
+ return "videos";
+ case YoutubeChannelPage::Type::SHORTS:
+ return "shorts";
+ case YoutubeChannelPage::Type::LIVE:
+ return "streams";
+ }
+ return "";
+ }
+
+ static std::string youtube_channel_page_type_to_title(const std::string &channel_name, YoutubeChannelPage::Type type) {
+ switch(type) {
+ case YoutubeChannelPage::Type::VIDEOS:
+ return channel_name + " Videos";
+ case YoutubeChannelPage::Type::SHORTS:
+ return channel_name + " Shorts";
+ case YoutubeChannelPage::Type::LIVE:
+ return channel_name + " Live videos";
+ }
+ return "";
+ }
+
+ // static
+ void YoutubeChannelPage::create_each_type(Program *program, std::string url, std::string continuation_token, std::string title, std::vector<Tab> &tabs) {
+ tabs.push_back(Tab{program->create_body(false, true), std::make_unique<YoutubeChannelPage>(program, url, continuation_token, title, Type::VIDEOS), program->create_search_bar("Search...", 350)});
+ tabs.push_back(Tab{program->create_body(false, true), std::make_unique<YoutubeChannelPage>(program, url, continuation_token, title, Type::SHORTS), program->create_search_bar("Search...", 350)});
+ tabs.push_back(Tab{program->create_body(false, true), std::make_unique<YoutubeChannelPage>(program, url, continuation_token, title, Type::LIVE), program->create_search_bar("Search...", 350)});
+ }
+
+ YoutubeChannelPage::YoutubeChannelPage(Program *program, std::string url, std::string continuation_token, std::string title, Type type) :
+ LazyFetchPage(program), TrackablePage(title, url), url(url), continuation_token(std::move(continuation_token)), title(youtube_channel_page_type_to_title(title, type)), type(type) {}
+
SearchResult YoutubeChannelPage::search(const std::string &str, BodyItems &result_items) {
added_videos.clear();
continuation_token.clear();
@@ -1515,13 +1599,12 @@ namespace QuickMedia {
client_json["clientVersion"] = "2.20210622.10.00";
client_json["osName"] = "X11";
client_json["osVersion"] = "";
- client_json["originalUrl"] = url + "/videos";
+ client_json["originalUrl"] = url + "/search?query=" + url_param_encode(str);
context_json["client"] = std::move(client_json);
request_json["context"] = std::move(context_json);
request_json["browseId"] = channel_id;
request_json["query"] = str;
- request_json["params"] = "EgZzZWFyY2g%3D";
- //request_json["continuation"] = current_continuation_token;
+ request_json["params"] = "EgZzZWFyY2jyBgQKAloA";
Json::StreamWriterBuilder json_builder;
json_builder["commentStyle"] = "None";
@@ -1533,7 +1616,7 @@ namespace QuickMedia {
{ "-H", "content-type: application/json" },
{ "-H", "x-youtube-client-name: 1" },
{ "-H", youtube_client_version },
- { "-H", "referer: " + url + "/videos" },
+ { "-H", "referer: " + url + "/search?query=" + url_param_encode(str) },
{ "--data-raw", Json::writeString(json_builder, request_json) }
};
@@ -1730,7 +1813,7 @@ namespace QuickMedia {
additional_args.insert(additional_args.end(), cookies.begin(), cookies.end());
Json::Value json_root;
- DownloadResult result = download_json(json_root, url + "/videos?pbj=1&gl=US&hl=en", std::move(additional_args), true);
+ DownloadResult result = download_json(json_root, url + "/" + youtube_channel_page_type_get_endpoint(type) + "?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;
@@ -2195,7 +2278,7 @@ namespace QuickMedia {
result_tabs.push_back(Tab{std::move(description_page_body), std::make_unique<YoutubeDescriptionPage>(program), nullptr});
result_tabs.push_back(Tab{create_body(), std::make_unique<YoutubeCommentsPage>(program, url, comments_continuation_token), nullptr});
result_tabs.push_back(Tab{std::move(related_page_body), std::make_unique<YoutubeRelatedVideosPage>(program), create_search_bar("Search...", SEARCH_DELAY_FILTER)});
- result_tabs.push_back(Tab{create_body(false, true), std::make_unique<YoutubeChannelPage>(program, channel_url, "", "Channel videos"), create_search_bar("Search...", 350)});
+ YoutubeChannelPage::create_each_type(program, channel_url, "", "Channel", result_tabs);
return PluginResult::OK;
}