From 3f0473c5aa472ac99d20a46bd7217ee9b6429f62 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Thu, 25 Nov 2021 19:52:11 +0100 Subject: Youtube: show like/dislike count for videos in the youtube description --- src/plugins/Youtube.cpp | 124 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 121 insertions(+), 3 deletions(-) (limited to 'src/plugins/Youtube.cpp') diff --git a/src/plugins/Youtube.cpp b/src/plugins/Youtube.cpp index 8e08062..42659b7 100644 --- a/src/plugins/Youtube.cpp +++ b/src/plugins/Youtube.cpp @@ -2036,6 +2036,109 @@ namespace QuickMedia { return ""; } + // Returns -1 if not found + static int64_t toggle_button_renderer_get_likes(const Json::Value &toggle_button_renderer_json) { + if(!toggle_button_renderer_json.isObject()) + return -1; + + const Json::Value &default_text_json = toggle_button_renderer_json["defaultText"]; + if(!default_text_json.isObject()) + return -1; + + const Json::Value &simple_text_json = default_text_json["simpleText"]; + if(!simple_text_json.isString()) + return -1; + + // The accessibility text for videos with 0 likes is "No Likes". We instead check for 0 in the simple text. + if(strcmp(simple_text_json.asCString(), "0") == 0) + return 0; + + const Json::Value &accessibility_json = default_text_json["accessibility"]; + if(!accessibility_json.isObject()) + return -1; + + const Json::Value &accessibility_data_json = accessibility_json["accessibilityData"]; + if(!accessibility_data_json.isObject()) + return -1; + + const Json::Value &label_json = accessibility_data_json["label"]; + if(!label_json.isString()) + return -1; + + std::string label = label_json.asString(); + size_t space_index = label.find(' '); + if(space_index != std::string::npos) + label.erase(space_index); + + string_replace_all(label, ",", ""); + + errno = 0; + char *endptr; + const int64_t likes = strtoll(label.c_str(), &endptr, 10); + if(endptr != label.c_str() && errno == 0) + return likes; + return -1; + } + + // Returns -1 if not found + static int64_t two_column_watch_next_results_get_video_likes(const Json::Value &tcwnr_json) { + if(!tcwnr_json.isObject()) + return -1; + + const Json::Value &results_json = tcwnr_json["results"]; + if(!results_json.isObject()) + return -1; + + const Json::Value &results2_json = results_json["results"]; + if(!results2_json.isObject()) + return -1; + + const Json::Value &contents_json = results2_json["contents"]; + if(!contents_json.isArray()) + return -1; + + for(const Json::Value &content_item_json : contents_json) { + if(!content_item_json.isObject()) + continue; + + const Json::Value &video_primary_info_renderer_json = content_item_json["videoPrimaryInfoRenderer"]; + if(!video_primary_info_renderer_json.isObject()) + continue; + + const Json::Value &video_actions_json = video_primary_info_renderer_json["videoActions"]; + if(!video_actions_json.isObject()) + continue; + + const Json::Value &menu_renderer_json = video_actions_json["menuRenderer"]; + if(!menu_renderer_json.isObject()) + continue; + + const Json::Value &top_level_buttons_json = menu_renderer_json["topLevelButtons"]; + if(!top_level_buttons_json.isArray()) + continue; + + for(const Json::Value &top_level_button_json : top_level_buttons_json) { + if(!top_level_button_json.isObject()) + continue; + + const Json::Value &toggle_button_renderer_json = top_level_button_json["toggleButtonRenderer"]; + if(!toggle_button_renderer_json.isObject()) + continue; + + const Json::Value &target_id_json = toggle_button_renderer_json["targetId"]; + if(!target_id_json.isString()) + continue; + + if(strcmp(target_id_json.asCString(), "watch-like") != 0) + continue; + + return toggle_button_renderer_get_likes(toggle_button_renderer_json); + } + } + + return -1; + } + static int youtube_url_timestamp_to_seconds(const std::string ×tamp) { int hours = 0; int minutes = 0; @@ -2080,6 +2183,7 @@ namespace QuickMedia { BodyItems YoutubeVideoPage::get_related_media(const std::string &url) { comments_continuation_token.clear(); + likes = -1; BodyItems result_items; std::string video_id; @@ -2125,6 +2229,9 @@ namespace QuickMedia { if(comments_continuation_token.empty()) comments_continuation_token = two_column_watch_next_results_get_comments_continuation_token(tcwnr_json); + if(likes == -1) + likes = two_column_watch_next_results_get_video_likes(tcwnr_json); + const Json::Value &secondary_results_json = tcwnr_json["secondaryResults"]; if(!secondary_results_json.isObject()) return result_items; @@ -2187,7 +2294,11 @@ namespace QuickMedia { return result; } - static std::shared_ptr video_details_to_body_item(const YoutubeVideoDetails &video_details) { + static int64_t round_double(double value) { + return value + 0.5; + } + + static std::shared_ptr video_details_to_body_item(const YoutubeVideoDetails &video_details, int64_t likes) { auto body_item = BodyItem::create(video_details.title); std::string description; @@ -2198,7 +2309,14 @@ namespace QuickMedia { if(!video_details.rating.empty()) { if(!description.empty()) description += " • "; - description += "rated " + video_details.rating.substr(0, 4) + "/5"; + + if(likes == -1) { + description += "rated " + video_details.rating.substr(0, 4) + "/5"; + } else { + fprintf(stderr, "video rating: %s\n", video_details.rating.c_str()); + int64_t num_dislikes = round_double((double)likes * ((5.0 - atof(video_details.rating.c_str())) / 5.0)); + description += "👍 " + std::to_string(likes) + " 👎 " + std::to_string(num_dislikes); + } } if(!video_details.author.empty()) { @@ -2221,7 +2339,7 @@ namespace QuickMedia { PluginResult YoutubeVideoPage::get_related_pages(const BodyItems &related_videos, const std::string &channel_url, std::vector &result_tabs) { auto description_page_body = create_body(); - description_page_body->append_item(video_details_to_body_item(video_details)); + description_page_body->append_item(video_details_to_body_item(video_details, likes)); auto related_page_body = create_body(false, true); related_page_body->set_items(related_videos); -- cgit v1.2.3