aboutsummaryrefslogtreecommitdiff
path: root/src/plugins/Youtube.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/Youtube.cpp')
-rw-r--r--src/plugins/Youtube.cpp317
1 files changed, 83 insertions, 234 deletions
diff --git a/src/plugins/Youtube.cpp b/src/plugins/Youtube.cpp
index 7e1fc63..40b296d 100644
--- a/src/plugins/Youtube.cpp
+++ b/src/plugins/Youtube.cpp
@@ -1,22 +1,9 @@
#include "../../plugins/Youtube.hpp"
#include "../../include/Storage.hpp"
-#include <json/reader.h>
-#include <json/writer.h>
#include <string.h>
#include <unordered_set>
namespace QuickMedia {
- static void iterate_suggestion_result(const Json::Value &value, std::vector<std::string> &result_items, int &iterate_count) {
- ++iterate_count;
- if(value.isArray()) {
- for(const Json::Value &child : value) {
- iterate_suggestion_result(child, result_items, iterate_count);
- }
- } else if(value.isString() && iterate_count > 2) {
- result_items.push_back(value.asString());
- }
- }
-
static std::shared_ptr<BodyItem> parse_content_video_renderer(const Json::Value &content_item_json, std::unordered_set<std::string> &added_videos) {
if(!content_item_json.isObject())
return nullptr;
@@ -85,135 +72,6 @@ namespace QuickMedia {
return body_item;
}
- Youtube::Youtube() : Plugin("youtube") {
-
- }
-
- PluginResult Youtube::get_front_page(BodyItems &result_items) {
- bool disabled = true;
- if(disabled)
- return PluginResult::OK;
-
- std::string url = "https://youtube.com/";
-
- std::vector<CommandArg> additional_args = {
- { "-H", "x-spf-referer: " + url },
- { "-H", "x-youtube-client-name: 1" },
- { "-H", "x-youtube-client-version: 2.20200626.03.00" },
- { "-H", "referer: " + url }
- };
-
- //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 + "?pbj=1", std::move(additional_args), true);
- if(result != DownloadResult::OK) return download_result_to_plugin_result(result);
-
- if(!json_root.isArray())
- return PluginResult::ERR;
-
- std::unordered_set<std::string> added_videos;
-
- for(const Json::Value &json_item : json_root) {
- if(!json_item.isObject())
- continue;
-
- const Json::Value &response_json = json_item["response"];
- if(!response_json.isObject())
- continue;
-
- const Json::Value &contents_json = response_json["contents"];
- if(!contents_json.isObject())
- continue;
-
- const Json::Value &tcbrr_json = contents_json["twoColumnBrowseResultsRenderer"];
- if(!tcbrr_json.isObject())
- continue;
-
- const Json::Value &tabs_json = tcbrr_json["tabs"];
- if(!tabs_json.isArray())
- continue;
-
- for(const Json::Value &tab_item_json : tabs_json) {
- if(!tab_item_json.isObject())
- continue;
-
- const Json::Value &tab_renderer_json = tab_item_json["tabRenderer"];
- if(!tab_renderer_json.isObject())
- continue;
-
- const Json::Value &content_json = tab_renderer_json["content"];
- if(!content_json.isObject())
- continue;
-
- const Json::Value &rich_grid_renderer = content_json["richGridRenderer"];
- if(!rich_grid_renderer.isObject())
- continue;
-
- const Json::Value &contents2_json = rich_grid_renderer["contents"];
- if(!contents2_json.isArray())
- continue;
-
- for(const Json::Value &contents_item : contents2_json) {
- const Json::Value &rich_item_renderer_json = contents_item["richItemRenderer"];
- if(!rich_item_renderer_json.isObject())
- continue;
-
- const Json::Value &rich_item_contents = rich_item_renderer_json["content"];
- std::shared_ptr<BodyItem> body_item = parse_content_video_renderer(rich_item_contents, added_videos);
- if(body_item)
- result_items.push_back(std::move(body_item));
- }
- }
- }
-
- return PluginResult::OK;
- }
-
- std::string Youtube::autocomplete_search(const std::string &query) {
- // Return the last result if the query is a substring of the autocomplete result
- if(last_autocomplete_result.size() >= query.size() && memcmp(query.data(), last_autocomplete_result.data(), query.size()) == 0)
- return last_autocomplete_result;
-
- std::string url = "https://clients1.google.com/complete/search?client=youtube&hl=en&gs_rn=64&gs_ri=youtube&ds=yt&cp=7&gs_id=x&q=";
- url += url_param_encode(query);
-
- std::string server_response;
- if(download_to_string(url, server_response, {}, use_tor, true) != DownloadResult::OK)
- return query;
-
- size_t json_start = server_response.find_first_of('(');
- if(json_start == std::string::npos)
- return query;
- ++json_start;
-
- size_t json_end = server_response.find_last_of(')');
- if(json_end == std::string::npos)
- return query;
-
- if(json_end == 0 || json_start >= json_end)
- return query;
-
- Json::Value json_root;
- Json::CharReaderBuilder json_builder;
- std::unique_ptr<Json::CharReader> json_reader(json_builder.newCharReader());
- std::string json_errors;
- if(!json_reader->parse(&server_response[json_start], &server_response[json_end], &json_root, &json_errors)) {
- fprintf(stderr, "Youtube autocomplete search json error: %s\n", json_errors.c_str());
- return query;
- }
-
- int iterate_count = 0;
- std::vector<std::string> result_items;
- iterate_suggestion_result(json_root, result_items, iterate_count);
- if(result_items.empty())
- return query;
-
- last_autocomplete_result = result_items[0];
- return result_items[0];
- }
-
// Returns empty string if continuation token can't be found
static std::string item_section_renderer_get_continuation_token(const Json::Value &item_section_renderer_json) {
const Json::Value &continuations_json = item_section_renderer_json["continuations"];
@@ -277,9 +135,77 @@ namespace QuickMedia {
}
}
- SuggestionResult Youtube::update_search_suggestions(const std::string &text, BodyItems &result_items) {
+ static std::string remove_index_from_playlist_url(const std::string &url) {
+ std::string result = url;
+ size_t index = result.rfind("&index=");
+ if(index == std::string::npos)
+ return result;
+ return result.substr(0, index);
+ }
+
+ static std::shared_ptr<BodyItem> parse_compact_video_renderer_json(const Json::Value &item_json, std::unordered_set<std::string> &added_videos) {
+ const Json::Value &compact_video_renderer_json = item_json["compactVideoRenderer"];
+ if(!compact_video_renderer_json.isObject())
+ return nullptr;
+
+ const Json::Value &video_id_json = compact_video_renderer_json["videoId"];
+ if(!video_id_json.isString())
+ return nullptr;
+
+ std::string video_id_str = video_id_json.asString();
+ if(added_videos.find(video_id_str) != added_videos.end())
+ return nullptr;
+
+ std::string thumbnail_url = "https://img.youtube.com/vi/" + video_id_str + "/hqdefault.jpg";
+
+ const char *date = nullptr;
+ const Json::Value &published_time_text_json = compact_video_renderer_json["publishedTimeText"];
+ if(published_time_text_json.isObject()) {
+ const Json::Value &text_json = published_time_text_json["simpleText"];
+ if(text_json.isString())
+ date = text_json.asCString();
+ }
+
+ const char *length = nullptr;
+ const Json::Value &length_text_json = compact_video_renderer_json["lengthText"];
+ if(length_text_json.isObject()) {
+ const Json::Value &text_json = length_text_json["simpleText"];
+ if(text_json.isString())
+ length = text_json.asCString();
+ }
+
+ const char *title = nullptr;
+ const Json::Value &title_json = compact_video_renderer_json["title"];
+ if(title_json.isObject()) {
+ const Json::Value &simple_text_json = title_json["simpleText"];
+ if(simple_text_json.isString()) {
+ title = simple_text_json.asCString();
+ }
+ }
+
+ if(!title)
+ return nullptr;
+
+ auto body_item = BodyItem::create(title);
+ /* TODO: Make date a different color */
+ std::string date_str;
+ if(date)
+ date_str += date;
+ if(length) {
+ if(!date_str.empty())
+ date_str += '\n';
+ date_str += length;
+ }
+ body_item->set_description(std::move(date_str));
+ body_item->url = "https://www.youtube.com/watch?v=" + video_id_str;
+ body_item->thumbnail_url = std::move(thumbnail_url);
+ added_videos.insert(video_id_str);
+ return body_item;
+ }
+
+ SearchResult YoutubeSearchPage::search(const std::string &str, BodyItems &result_items) {
std::string url = "https://youtube.com/results?search_query=";
- url += url_param_encode(text);
+ url += url_param_encode(str);
std::vector<CommandArg> additional_args = {
{ "-H", "x-spf-referer: " + url },
@@ -292,11 +218,11 @@ namespace QuickMedia {
//additional_args.insert(additional_args.end(), cookies.begin(), cookies.end());
Json::Value json_root;
- DownloadResult result = download_json(json_root, url + "?pbj=1", std::move(additional_args), true);
- if(result != DownloadResult::OK) return download_result_to_suggestion_result(result);
+ DownloadResult result = download_json(json_root, url + "&pbj=1", std::move(additional_args), true);
+ if(result != DownloadResult::OK) return download_result_to_search_result(result);
if(!json_root.isArray())
- return SuggestionResult::ERR;
+ return SearchResult::ERR;
std::string continuation_token;
std::unordered_set<std::string> added_videos; /* The input contains duplicates, filter them out! */
@@ -345,10 +271,17 @@ namespace QuickMedia {
if(!continuation_token.empty())
search_suggestions_get_continuation(url, continuation_token, result_items);
- return SuggestionResult::OK;
+ return SearchResult::OK;
+ }
+
+ PluginResult YoutubeSearchPage::submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) {
+ (void)title;
+ (void)url;
+ result_tabs.push_back(Tab{create_body(), std::make_unique<YoutubeVideoPage>(program), nullptr});
+ return PluginResult::OK;
}
- void Youtube::search_suggestions_get_continuation(const std::string &url, const std::string &continuation_token, BodyItems &result_items) {
+ void YoutubeSearchPage::search_suggestions_get_continuation(const std::string &url, const std::string &continuation_token, BodyItems &result_items) {
std::string next_url = url + "&pbj=1&ctoken=" + continuation_token;
std::vector<CommandArg> additional_args = {
@@ -393,93 +326,9 @@ namespace QuickMedia {
}
}
- std::vector<CommandArg> Youtube::get_cookies() const {
- if(use_tor)
- return {};
-
- Path cookies_filepath;
- if(get_cookies_filepath(cookies_filepath, name) != 0) {
- fprintf(stderr, "Warning: Failed to create youtube cookies file\n");
- return {};
- }
-
- return {
- CommandArg{ "-b", cookies_filepath.data },
- CommandArg{ "-c", cookies_filepath.data }
- };
- }
-
- static std::string remove_index_from_playlist_url(const std::string &url) {
- std::string result = url;
- size_t index = result.rfind("&index=");
- if(index == std::string::npos)
- return result;
- return result.substr(0, index);
- }
-
- static std::shared_ptr<BodyItem> parse_compact_video_renderer_json(const Json::Value &item_json, std::unordered_set<std::string> &added_videos) {
- const Json::Value &compact_video_renderer_json = item_json["compactVideoRenderer"];
- if(!compact_video_renderer_json.isObject())
- return nullptr;
-
- const Json::Value &video_id_json = compact_video_renderer_json["videoId"];
- if(!video_id_json.isString())
- return nullptr;
-
- std::string video_id_str = video_id_json.asString();
- if(added_videos.find(video_id_str) != added_videos.end())
- return nullptr;
-
- std::string thumbnail_url = "https://img.youtube.com/vi/" + video_id_str + "/hqdefault.jpg";
-
- const char *date = nullptr;
- const Json::Value &published_time_text_json = compact_video_renderer_json["publishedTimeText"];
- if(published_time_text_json.isObject()) {
- const Json::Value &text_json = published_time_text_json["simpleText"];
- if(text_json.isString())
- date = text_json.asCString();
- }
-
- const char *length = nullptr;
- const Json::Value &length_text_json = compact_video_renderer_json["lengthText"];
- if(length_text_json.isObject()) {
- const Json::Value &text_json = length_text_json["simpleText"];
- if(text_json.isString())
- length = text_json.asCString();
- }
-
- const char *title = nullptr;
- const Json::Value &title_json = compact_video_renderer_json["title"];
- if(title_json.isObject()) {
- const Json::Value &simple_text_json = title_json["simpleText"];
- if(simple_text_json.isString()) {
- title = simple_text_json.asCString();
- }
- }
-
- if(!title)
- return nullptr;
-
- auto body_item = BodyItem::create(title);
- /* TODO: Make date a different color */
- std::string date_str;
- if(date)
- date_str += date;
- if(length) {
- if(!date_str.empty())
- date_str += '\n';
- date_str += length;
- }
- body_item->set_description(std::move(date_str));
- body_item->url = "https://www.youtube.com/watch?v=" + video_id_str;
- body_item->thumbnail_url = std::move(thumbnail_url);
- added_videos.insert(video_id_str);
- return body_item;
- }
-
// TODO: Make this faster by using string search instead of parsing html.
// TODO: If the result is a play
- BodyItems Youtube::get_related_media(const std::string &url) {
+ BodyItems YoutubeVideoPage::get_related_media(const std::string &url) {
BodyItems result_items;
std::string modified_url = remove_index_from_playlist_url(url);