aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Body.cpp1
-rw-r--r--src/QuickMedia.cpp44
-rw-r--r--src/plugins/Spotify.cpp2
-rw-r--r--src/plugins/Youtube.cpp412
4 files changed, 416 insertions, 43 deletions
diff --git a/src/Body.cpp b/src/Body.cpp
index ca91566..8a32f76 100644
--- a/src/Body.cpp
+++ b/src/Body.cpp
@@ -663,6 +663,7 @@ namespace QuickMedia {
update_dirty_state(item, size.x);
item->last_drawn_time = draw_timer.getElapsedTime().asMilliseconds();
sf::Vector2u window_size = window.getSize();
+ get_item_height(item, size.x, true, false);
glEnable(GL_SCISSOR_TEST);
glScissor(pos.x, (int)window_size.y - (int)pos.y - (int)size.y, size.x, size.y);
draw_item(window, item, pos, size, size.y + spacing_y, -1, Json::Value::nullSingleton(), include_embedded_item);
diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp
index 7e758b9..9f8c7bd 100644
--- a/src/QuickMedia.cpp
+++ b/src/QuickMedia.cpp
@@ -549,7 +549,7 @@ namespace QuickMedia {
}
if(use_tor && !is_program_executable_by_name("torsocks")) {
- fprintf(stderr, "torsocks needs to be installed (and accessible from PATH environment variable) when using the --tor option\n");
+ fprintf(stderr, "torsocks needs to be installed when using the --tor option\n");
return -2;
}
@@ -560,7 +560,7 @@ namespace QuickMedia {
}
if(!is_program_executable_by_name("waifu2x-ncnn-vulkan")) {
- fprintf(stderr, "waifu2x-ncnn-vulkan needs to be installed (and accessible from PATH environment variable) when using the --upscale-images/--upscale-images-always option\n");
+ fprintf(stderr, "waifu2x-ncnn-vulkan needs to be installed when using the --upscale-images/--upscale-images-always option\n");
return -2;
}
@@ -1023,7 +1023,7 @@ namespace QuickMedia {
}
}
- void Program::page_loop(std::vector<Tab> &tabs, int start_tab_index, std::function<void()> after_submit_handler) {
+ void Program::page_loop(std::vector<Tab> &tabs, int start_tab_index, PageLoopSubmitHandler after_submit_handler) {
if(tabs.empty()) {
show_notification("QuickMedia", "No tabs provided!", Urgency::CRITICAL);
return;
@@ -1077,9 +1077,6 @@ namespace QuickMedia {
return;
}
- if(after_submit_handler)
- after_submit_handler();
-
if(tabs[selected_tab].page->clear_search_after_submit() && tabs[selected_tab].search_bar) {
if(!tabs[selected_tab].search_bar->get_text().empty()) {
tabs[selected_tab].search_bar->clear();
@@ -1103,6 +1100,9 @@ namespace QuickMedia {
if(new_tabs.empty())
return;
+ if(after_submit_handler)
+ after_submit_handler(new_tabs);
+
for(Tab &tab : tabs) {
tab.body->clear_cache();
}
@@ -1407,7 +1407,7 @@ namespace QuickMedia {
associated_data.lazy_fetch_finished = true;
FetchResult fetch_result = associated_data.fetch_future.get();
tabs[i].body->items = std::move(fetch_result.body_items);
- tabs[i].body->filter_search_fuzzy(tabs[i].search_bar->get_text());
+ if(tabs[i].search_bar) tabs[i].body->filter_search_fuzzy(tabs[i].search_bar->get_text());
if(fetch_result.result != PluginResult::OK)
associated_data.search_result_text.setString("Failed to fetch page!");
else if(tabs[i].body->items.empty())
@@ -1760,7 +1760,7 @@ namespace QuickMedia {
current_page = previous_page;
} else {
channel_url.clear();
- // TODO: Make async. What if the server is frozen?
+ // TODO: Remove this and use lazy_fetch instead
related_videos = video_page->get_related_media(video_url, channel_url);
// TODO: Make this also work for other video plugins
@@ -1880,6 +1880,7 @@ namespace QuickMedia {
int search_delay = 0;
auto search_page = video_page->create_search_page(this, search_delay);
+ auto comments_page = video_page->create_comments_page(this);
auto related_videos_page = video_page->create_related_videos_page(this, video_url, video_title);
auto channels_page = video_page->create_channels_page(this, channel_url);
if(search_page || related_videos_page || channels_page) {
@@ -1890,6 +1891,9 @@ namespace QuickMedia {
if(search_page) {
tabs.push_back(Tab{create_body(), std::move(search_page), create_search_bar("Search...", search_delay)});
}
+ if(comments_page) {
+ tabs.push_back(Tab{create_body(), std::move(comments_page), nullptr});
+ }
if(related_videos_page) {
auto related_videos_body = create_body();
related_videos_body->items = related_videos;
@@ -1900,19 +1904,21 @@ namespace QuickMedia {
}
bool page_changed = false;
- page_loop(tabs, 1, [this, &video_player, &page_changed]() {
- window.setMouseCursorVisible(true);
- if(video_player) {
- video_player->quit_and_save_watch_later();
- while(true) {
- VideoPlayer::Error update_err = video_player->update();
- if(update_err != VideoPlayer::Error::OK)
- break;
- std::this_thread::sleep_for(std::chrono::milliseconds(20));
+ page_loop(tabs, 1, [this, &video_player, &page_changed](const std::vector<Tab> &new_tabs) {
+ if(new_tabs.size() == 1 && new_tabs[0].page->get_type() == PageTypez::VIDEO) {
+ window.setMouseCursorVisible(true);
+ if(video_player) {
+ video_player->quit_and_save_watch_later();
+ while(true) {
+ VideoPlayer::Error update_err = video_player->update();
+ if(update_err != VideoPlayer::Error::OK)
+ break;
+ std::this_thread::sleep_for(std::chrono::milliseconds(20));
+ }
+ video_player.reset();
}
- video_player.reset();
+ page_changed = true;
}
- page_changed = true;
});
if(page_changed) {
diff --git a/src/plugins/Spotify.cpp b/src/plugins/Spotify.cpp
index 3ce640a..14f9831 100644
--- a/src/plugins/Spotify.cpp
+++ b/src/plugins/Spotify.cpp
@@ -167,7 +167,7 @@ namespace QuickMedia {
if(result != PluginResult::OK)
return result;
- result_tabs.push_back(Tab{std::move(body), std::move(episode_list_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)});
+ result_tabs.push_back(Tab{std::move(body), std::move(episode_list_page), nullptr});
return result;
}
diff --git a/src/plugins/Youtube.cpp b/src/plugins/Youtube.cpp
index b3c9a60..ced6c45 100644
--- a/src/plugins/Youtube.cpp
+++ b/src/plugins/Youtube.cpp
@@ -5,6 +5,7 @@
#include "../../include/Scale.hpp"
#include <json/writer.h>
#include <string.h>
+#include <unistd.h>
namespace QuickMedia {
// This is a common setup of text in the youtube json
@@ -18,7 +19,7 @@ namespace QuickMedia {
const Json::Value &simple_text_json = text_json["simpleText"];
if(simple_text_json.isString()) {
- return simple_text_json.asCString();
+ return simple_text_json.asString();
} else {
const Json::Value &runs_json = text_json["runs"];
if(!runs_json.isArray() || runs_json.empty())
@@ -46,8 +47,13 @@ namespace QuickMedia {
int height;
};
+ enum class ThumbnailSize {
+ SMALLEST,
+ LARGEST
+ };
+
// TODO: Use this in |parse_common_video_item| when QuickMedia supports webp
- static std::optional<Thumbnail> yt_json_get_largest_thumbnail(const Json::Value &thumbnail_json) {
+ static std::optional<Thumbnail> yt_json_get_thumbnail(const Json::Value &thumbnail_json, ThumbnailSize thumbnail_size) {
if(!thumbnail_json.isObject())
return std::nullopt;
@@ -75,11 +81,22 @@ namespace QuickMedia {
thumbnails.push_back({ url_json.asCString(), width_json.asInt(), height_json.asInt() });
}
- return *std::max_element(thumbnails.begin(), thumbnails.end(), [](const Thumbnail &thumbnail1, const Thumbnail &thumbnail2) {
- int size1 = thumbnail1.width * thumbnail1.height;
- int size2 = thumbnail2.width * thumbnail2.height;
- return size1 < size2;
- });
+ switch(thumbnail_size) {
+ case ThumbnailSize::SMALLEST:
+ return *std::min_element(thumbnails.begin(), thumbnails.end(), [](const Thumbnail &thumbnail1, const Thumbnail &thumbnail2) {
+ int size1 = thumbnail1.width * thumbnail1.height;
+ int size2 = thumbnail2.width * thumbnail2.height;
+ return size1 < size2;
+ });
+ case ThumbnailSize::LARGEST:
+ return *std::max_element(thumbnails.begin(), thumbnails.end(), [](const Thumbnail &thumbnail1, const Thumbnail &thumbnail2) {
+ int size1 = thumbnail1.width * thumbnail1.height;
+ int size2 = thumbnail2.width * thumbnail2.height;
+ return size1 < size2;
+ });
+ }
+
+ return std::nullopt;
}
static std::shared_ptr<BodyItem> parse_common_video_item(const Json::Value &video_item_json, std::unordered_set<std::string> &added_videos) {
@@ -196,7 +213,7 @@ namespace QuickMedia {
std::optional<std::string> subscribers = yt_json_get_text(channel_renderer_json, "subscriberCountText");
const Json::Value &thumbnail_json = channel_renderer_json["thumbnail"];
- std::optional<Thumbnail> thumbnail = yt_json_get_largest_thumbnail(thumbnail_json);
+ std::optional<Thumbnail> thumbnail = yt_json_get_thumbnail(thumbnail_json, ThumbnailSize::LARGEST);
auto body_item = BodyItem::create(title.value());
std::string desc;
@@ -300,6 +317,37 @@ namespace QuickMedia {
}
}
+ static std::mutex cookies_mutex;
+ static std::string cookies_filepath;
+
+ static void remove_cookies_file_at_exit() {
+ std::lock_guard<std::mutex> lock(cookies_mutex);
+ if(!cookies_filepath.empty())
+ remove(cookies_filepath.c_str());
+ }
+
+ static std::vector<CommandArg> get_cookies() {
+ std::lock_guard<std::mutex> lock(cookies_mutex);
+ if(cookies_filepath.empty()) {
+ char filename[] = "quickmedia.youtube.cookie.XXXXXX";
+ int fd = mkstemp(filename);
+ if(fd == -1)
+ return {};
+ close(fd);
+
+ cookies_filepath = "/tmp/";
+ cookies_filepath += filename;
+ cookies_filepath += ".txt";
+
+ atexit(remove_cookies_file_at_exit);
+ }
+
+ return {
+ CommandArg{ "-b", cookies_filepath },
+ CommandArg{ "-c", cookies_filepath }
+ };
+ }
+
static std::string remove_index_from_playlist_url(const std::string &url) {
std::string result = url;
size_t index = result.rfind("&index=");
@@ -316,6 +364,30 @@ namespace QuickMedia {
return parse_common_video_item(compact_video_renderer_json, added_videos);
}
+ static std::string item_section_renderer_get_continuation(const Json::Value &item_section_renderer) {
+ if(!item_section_renderer.isObject())
+ return "";
+
+ const Json::Value &continuations_json = item_section_renderer["continuations"];
+ if(!continuations_json.isArray())
+ return "";
+
+ for(const Json::Value &json_item : continuations_json) {
+ if(!json_item.isObject())
+ continue;
+
+ const Json::Value &next_continuation_data_json = json_item["nextContinuationData"];
+ if(!next_continuation_data_json.isObject())
+ continue;
+
+ const Json::Value &continuation_json = next_continuation_data_json["continuation"];
+ if(continuation_json.isString())
+ return continuation_json.asString();
+ }
+
+ return "";
+ }
+
static BodyItems parse_channel_videos(const Json::Value &json_root, std::string &continuation_token, std::unordered_set<std::string> &added_videos) {
BodyItems body_items;
if(!json_root.isArray())
@@ -450,8 +522,8 @@ namespace QuickMedia {
{ "-H", "referer: " + search_url }
};
- //std::vector<CommandArg> cookies = get_cookies();
- //additional_args.insert(additional_args.end(), cookies.begin(), cookies.end());
+ std::vector<CommandArg> cookies = get_cookies();
+ additional_args.insert(additional_args.end(), cookies.begin(), cookies.end());
Json::Value json_root;
DownloadResult result = download_json(json_root, search_url + "&pbj=1", std::move(additional_args), true);
@@ -519,8 +591,8 @@ namespace QuickMedia {
{ "-H", "referer: " + url }
};
- //std::vector<CommandArg> cookies = get_cookies();
- //additional_args.insert(additional_args.end(), cookies.begin(), cookies.end());
+ std::vector<CommandArg> cookies = get_cookies();
+ additional_args.insert(additional_args.end(), cookies.begin(), cookies.end());
Json::Value json_root;
DownloadResult result = download_json(json_root, next_url, std::move(additional_args), true);
@@ -578,6 +650,263 @@ namespace QuickMedia {
return PluginResult::OK;
}
+ PluginResult YoutubeCommentsPage::get_page(const std::string&, int page, BodyItems &result_items) {
+ while(current_page < page) {
+ PluginResult plugin_result = lazy_fetch(result_items);
+ if(plugin_result != PluginResult::OK) return plugin_result;
+ ++current_page;
+ }
+ return PluginResult::OK;
+ }
+
+ PluginResult YoutubeCommentsPage::submit(const std::string&, const std::string &url, std::vector<Tab> &result_tabs) {
+ if(url.empty())
+ return PluginResult::OK;
+ result_tabs.push_back(Tab{create_body(), std::make_unique<YoutubeCommentRepliesPage>(program, xsrf_token, url), nullptr});
+ return PluginResult::OK;
+ }
+
+ static std::string comment_thread_renderer_get_replies_continuation(const Json::Value &comment_thread_renderer_json) {
+ if(!comment_thread_renderer_json.isObject())
+ return "";
+
+ const Json::Value &replies_json = comment_thread_renderer_json["replies"];
+ if(!replies_json.isObject())
+ return "";
+
+ const Json::Value &comment_replies_renderer = replies_json["commentRepliesRenderer"];
+ if(!comment_replies_renderer.isObject())
+ return "";
+
+ // item_section_renderer_get_continuation is compatible with commentRepliesRenderer
+ return item_section_renderer_get_continuation(comment_replies_renderer);
+ }
+
+ // Returns empty string if comment is not hearted
+ static std::string comment_renderer_get_hearted_tooltip(const Json::Value &comment_renderer_json) {
+ const Json::Value &action_buttons_json = comment_renderer_json["actionButtons"];
+ if(!action_buttons_json.isObject())
+ return "";
+
+ const Json::Value &comment_action_buttons_renderer_json = action_buttons_json["commentActionButtonsRenderer"];
+ if(!comment_action_buttons_renderer_json.isObject())
+ return "";
+
+ const Json::Value &creator_heart_json = comment_action_buttons_renderer_json["creatorHeart"];
+ if(!creator_heart_json.isObject())
+ return "";
+
+ const Json::Value &creator_heart_renderer_json = creator_heart_json["creatorHeartRenderer"];
+ if(!creator_heart_renderer_json.isObject())
+ return "";
+
+ const Json::Value &hearted_tooltip_json = creator_heart_renderer_json["heartedTooltip"];
+ if(!hearted_tooltip_json.isString())
+ return "";
+
+ return hearted_tooltip_json.asString();
+ }
+
+ static std::shared_ptr<BodyItem> comment_renderer_to_body_item(const Json::Value &comment_renderer_json) {
+ if(!comment_renderer_json.isObject())
+ return nullptr;
+
+ std::optional<std::string> author_text = yt_json_get_text(comment_renderer_json, "authorText");
+ if(!author_text)
+ return nullptr;
+
+ std::string title = author_text.value();
+ std::optional<std::string> published_time_text = yt_json_get_text(comment_renderer_json, "publishedTimeText");
+ if(published_time_text)
+ title += " - " + published_time_text.value();
+
+ auto body_item = BodyItem::create(std::move(title));
+ std::string description;
+
+ const Json::Value &author_is_channel_owner_json = comment_renderer_json["authorIsChannelOwner"];
+ if(author_is_channel_owner_json.isBool() && author_is_channel_owner_json.asBool())
+ body_item->set_title_color(sf::Color(150, 255, 150));
+
+ std::optional<std::string> comment = yt_json_get_text(comment_renderer_json, "contentText");
+ if(comment)
+ description = comment.value();
+
+ std::optional<Thumbnail> thumbnail = yt_json_get_thumbnail(comment_renderer_json["authorThumbnail"], ThumbnailSize::SMALLEST);
+ if(thumbnail) {
+ body_item->thumbnail_url = thumbnail->url;
+ body_item->thumbnail_mask_type = ThumbnailMaskType::CIRCLE;
+ body_item->thumbnail_size.x = thumbnail->width;
+ body_item->thumbnail_size.y = thumbnail->height;
+ body_item->thumbnail_size = body_item->thumbnail_size;
+ }
+
+ const Json::Value &like_count_json = comment_renderer_json["likeCount"];
+ if(like_count_json.isInt64()) {
+ if(!description.empty())
+ description += '\n';
+ description += "👍 " + std::to_string(like_count_json.asInt64());
+ }
+
+ const Json::Value &reply_count_json = comment_renderer_json["replyCount"];
+ if(reply_count_json.isInt64()) {
+ if(!description.empty())
+ description += '\n';
+ description += std::to_string(reply_count_json.asInt64()) + " replies";
+ }
+
+ std::string hearted_tooltip = comment_renderer_get_hearted_tooltip(comment_renderer_json);
+ if(!hearted_tooltip.empty()) {
+ if(!description.empty())
+ description += ' ';
+ description += std::move(hearted_tooltip);
+ }
+
+ body_item->set_description(std::move(description));
+ return body_item;
+ }
+
+ PluginResult YoutubeCommentsPage::lazy_fetch(BodyItems &result_items) {
+ std::string next_url = "https://www.youtube.com/comment_service_ajax?action_get_comments=1&pbj=1&ctoken=";
+ next_url += url_param_encode(continuation_token);
+ //next_url += "&continuation=";
+ //next_url += url_param_encode(comments_continuation_token);
+ next_url += "&type=next";
+
+ std::vector<CommandArg> additional_args = {
+ { "-H", "x-youtube-client-name: 1" },
+ { "-H", "x-youtube-client-version: 2.20210308.08.00" },
+ { "-F", "session_token=" + xsrf_token }
+ };
+
+ std::vector<CommandArg> cookies = get_cookies();
+ additional_args.insert(additional_args.end(), cookies.begin(), cookies.end());
+
+ Json::Value json_root;
+ DownloadResult result = download_json(json_root, next_url, std::move(additional_args), true);
+ if(result != DownloadResult::OK) return download_result_to_plugin_result(result);
+
+ if(!json_root.isObject())
+ return PluginResult::ERR;
+
+ const Json::Value &xsrf_token_json = json_root["xsrf_token"];
+ if(xsrf_token_json.isString())
+ xsrf_token = xsrf_token_json.asString();
+
+ const Json::Value &response_json = json_root["response"];
+ if(!response_json.isObject())
+ return PluginResult::ERR;
+
+ const Json::Value &continuation_contents_json = response_json["continuationContents"];
+ if(!continuation_contents_json.isObject())
+ return PluginResult::ERR;
+
+ const Json::Value &item_section_continuation_json = continuation_contents_json["itemSectionContinuation"];
+ if(!item_section_continuation_json.isObject())
+ return PluginResult::ERR;
+
+ const Json::Value &contents_json = item_section_continuation_json["contents"];
+ if(contents_json.isArray()) {
+ for(const Json::Value &json_item : contents_json) {
+ if(!json_item.isObject())
+ continue;
+
+ const Json::Value &comment_thread_renderer = json_item["commentThreadRenderer"];
+ if(!comment_thread_renderer.isObject())
+ continue;
+
+ const Json::Value &comment_json = comment_thread_renderer["comment"];
+ if(!comment_json.isObject())
+ continue;
+
+ auto body_item = comment_renderer_to_body_item(comment_json["commentRenderer"]);
+ if(body_item) {
+ body_item->url = comment_thread_renderer_get_replies_continuation(comment_thread_renderer);
+ result_items.push_back(std::move(body_item));
+ }
+ }
+ }
+
+ continuation_token = item_section_renderer_get_continuation(item_section_continuation_json);
+
+ return PluginResult::OK;
+ }
+
+ PluginResult YoutubeCommentRepliesPage::get_page(const std::string&, int page, BodyItems &result_items) {
+ while(current_page < page) {
+ PluginResult plugin_result = lazy_fetch(result_items);
+ if(plugin_result != PluginResult::OK) return plugin_result;
+ ++current_page;
+ }
+ return PluginResult::OK;
+ }
+
+ PluginResult YoutubeCommentRepliesPage::submit(const std::string&, const std::string&, std::vector<Tab>&) {
+ return PluginResult::OK;
+ }
+
+ PluginResult YoutubeCommentRepliesPage::lazy_fetch(BodyItems &result_items) {
+ std::string next_url = "https://www.youtube.com/comment_service_ajax?action_get_comment_replies=1&pbj=1&ctoken=";
+ next_url += url_param_encode(continuation_token);
+ //next_url += "&continuation=";
+ //next_url += url_param_encode(comments_continuation_token);
+ next_url += "&type=next";
+
+ std::vector<CommandArg> additional_args = {
+ { "-H", "x-youtube-client-name: 1" },
+ { "-H", "x-youtube-client-version: 2.20210308.08.00" },
+ { "-F", "session_token=" + xsrf_token }
+ };
+
+ std::vector<CommandArg> cookies = get_cookies();
+ additional_args.insert(additional_args.end(), cookies.begin(), cookies.end());
+
+ Json::Value json_root;
+ DownloadResult result = download_json(json_root, next_url, std::move(additional_args), true);
+ if(result != DownloadResult::OK) return download_result_to_plugin_result(result);
+
+ if(!json_root.isArray())
+ return PluginResult::ERR;
+
+ for(const Json::Value &json_item : json_root) {
+ if(!json_item.isObject())
+ continue;
+
+ const Json::Value &xsrf_token_json = json_item["xsrf_token"];
+ if(xsrf_token_json.isString())
+ xsrf_token = xsrf_token_json.asString();
+
+ const Json::Value &response_json = json_item["response"];
+ if(!response_json.isObject())
+ continue;
+
+ const Json::Value &continuation_contents_json = response_json["continuationContents"];
+ if(!continuation_contents_json.isObject())
+ continue;
+
+ const Json::Value &comment_replies_continuation_json = continuation_contents_json["commentRepliesContinuation"];
+ if(!comment_replies_continuation_json.isObject())
+ continue;
+
+ const Json::Value &contents_json = comment_replies_continuation_json["contents"];
+ if(contents_json.isArray()) {
+ for(const Json::Value &content_item_json : contents_json) {
+ if(!content_item_json.isObject())
+ continue;
+
+ auto body_item = comment_renderer_to_body_item(content_item_json["commentRenderer"]);
+ if(body_item)
+ result_items.push_back(std::move(body_item));
+ }
+ }
+
+ // item_section_renderer_get_continuation is compatible with commentRepliesContinuation
+ continuation_token = item_section_renderer_get_continuation(comment_replies_continuation_json);
+ return PluginResult::OK;
+ }
+
+ return PluginResult::ERR;
+ }
+
static std::string channel_url_extract_id(const std::string &channel_url) {
size_t index = channel_url.find("channel/");
if(index == std::string::npos)
@@ -634,8 +963,8 @@ 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());
+ std::vector<CommandArg> cookies = get_cookies();
+ additional_args.insert(additional_args.end(), cookies.begin(), cookies.end());
Json::Value json_root;
DownloadResult result = download_json(json_root, next_url, std::move(additional_args), true);
@@ -717,8 +1046,8 @@ 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());
+ std::vector<CommandArg> cookies = get_cookies();
+ additional_args.insert(additional_args.end(), cookies.begin(), cookies.end());
Json::Value json_root;
DownloadResult result = download_json(json_root, next_url, std::move(additional_args), true);
@@ -768,7 +1097,7 @@ namespace QuickMedia {
return PluginResult::OK;
}
- PluginResult YoutubeChannelPage::submit(const std::string&, const std::string &url, std::vector<Tab> &result_tabs) {
+ PluginResult YoutubeChannelPage::submit(const std::string&, const std::string&, std::vector<Tab> &result_tabs) {
if(url.empty())
return PluginResult::OK;
result_tabs.push_back(Tab{nullptr, std::make_unique<YoutubeVideoPage>(program), nullptr});
@@ -785,8 +1114,8 @@ namespace QuickMedia {
{ "-H", "referer: " + url }
};
- //std::vector<CommandArg> cookies = get_cookies();
- //additional_args.insert(additional_args.end(), cookies.begin(), cookies.end());
+ std::vector<CommandArg> cookies = get_cookies();
+ additional_args.insert(additional_args.end(), cookies.begin(), cookies.end());
Json::Value json_root;
DownloadResult result = download_json(json_root, url + "/videos?pbj=1", std::move(additional_args), true);
@@ -800,8 +1129,32 @@ namespace QuickMedia {
return PluginResult::OK;
}
- // TODO: Make this faster by using string search instead of parsing html.
- // TODO: If the result is a play
+ static std::string two_column_watch_next_results_get_comments_continuation_token(const Json::Value &tcwnr_json) {
+ const Json::Value &results_json = tcwnr_json["results"];
+ if(!results_json.isObject())
+ return "";
+
+ const Json::Value &results2_json = results_json["results"];
+ if(!results2_json.isObject())
+ return "";
+
+ const Json::Value &contents_json = results2_json["contents"];
+ if(!contents_json.isArray())
+ return "";
+
+ std::string comments_continuation_token;
+ for(const Json::Value &content_item_json : contents_json) {
+ if(!content_item_json.isObject())
+ continue;
+
+ comments_continuation_token = item_section_renderer_get_continuation(content_item_json["itemSectionRenderer"]);
+ if(!comments_continuation_token.empty())
+ return comments_continuation_token;
+ }
+
+ return "";
+ }
+
BodyItems YoutubeVideoPage::get_related_media(const std::string &url, std::string &channel_url) {
BodyItems result_items;
@@ -814,8 +1167,8 @@ namespace QuickMedia {
{ "-H", "referer: " + url }
};
- //std::vector<CommandArg> cookies = get_cookies();
- //additional_args.insert(additional_args.end(), cookies.begin(), cookies.end());
+ std::vector<CommandArg> cookies = get_cookies();
+ additional_args.insert(additional_args.end(), cookies.begin(), cookies.end());
Json::Value json_root;
DownloadResult result = download_json(json_root, modified_url + "&pbj=1", std::move(additional_args), true);
@@ -842,6 +1195,12 @@ namespace QuickMedia {
}
}
+ if(xsrf_token.empty()) {
+ const Json::Value &xsrf_token_json = json_item["xsrf_token"];
+ if(xsrf_token_json.isString())
+ xsrf_token = xsrf_token_json.asString();
+ }
+
const Json::Value &response_json = json_item["response"];
if(!response_json.isObject())
continue;
@@ -854,6 +1213,9 @@ namespace QuickMedia {
if(!tcwnr_json.isObject())
return result_items;
+ if(comments_continuation_token.empty())
+ comments_continuation_token = two_column_watch_next_results_get_comments_continuation_token(tcwnr_json);
+
const Json::Value &secondary_results_json = tcwnr_json["secondaryResults"];
if(!secondary_results_json.isObject())
return result_items;
@@ -901,6 +1263,10 @@ namespace QuickMedia {
return std::make_unique<YoutubeSearchPage>(program);
}
+ std::unique_ptr<Page> YoutubeVideoPage::create_comments_page(Program *program) {
+ return std::make_unique<YoutubeCommentsPage>(program, xsrf_token, comments_continuation_token);
+ }
+
std::unique_ptr<RelatedVideosPage> YoutubeVideoPage::create_related_videos_page(Program *program, const std::string&, const std::string&) {
return std::make_unique<YoutubeRelatedVideosPage>(program);
}