From 4028d87367710a4cd6501314adea58678408351f Mon Sep 17 00:00:00 2001 From: dec05eba Date: Thu, 17 Jun 2021 03:28:09 +0200 Subject: Fix related video --- src/plugins/Youtube.cpp | 274 ++++++++++++++++++++++++------------------------ 1 file changed, 138 insertions(+), 136 deletions(-) (limited to 'src/plugins') diff --git a/src/plugins/Youtube.cpp b/src/plugins/Youtube.cpp index 7e71830..ed30aec 100644 --- a/src/plugins/Youtube.cpp +++ b/src/plugins/Youtube.cpp @@ -16,6 +16,65 @@ extern "C" { #include namespace QuickMedia { + static const char *youtube_client_version = "x-youtube-client-version: 2.20210615.01.00"; + static const std::string key_api_request_data = +R"END( +{ + "context": { + "client": { + "hl": "en", + "gl": "US", + "deviceMake": "", + "deviceModel": "", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; rv:78.0) Gecko/20100101 Firefox/78.0,gzip(gfe)", + "clientName": "WEB", + "clientVersion": "2.20210615.01.00", + "osName": "Windows", + "osVersion": "10.0", + "originalUrl": "https://www.youtube.com/watch?v=%VIDEO_ID%", + "platform": "DESKTOP", + "clientFormFactor": "UNKNOWN_FORM_FACTOR", + "timeZone": "UTC", + "browserName": "Firefox", + "browserVersion": "78.0", + "screenWidthPoints": 1054, + "screenHeightPoints": 289, + "screenPixelDensity": 1, + "screenDensityFloat": 1, + "utcOffsetMinutes": 0, + "userInterfaceTheme": "USER_INTERFACE_THEME_LIGHT", + "clientScreen": "WATCH", + "mainAppWebInfo": { + "graftUrl": "/watch?v=%VIDEO_ID%", + "webDisplayMode": "WEB_DISPLAY_MODE_BROWSER", + "isWebNativeShareAvailable": false + } + }, + "user": { + "lockedSafetyMode": false + }, + "request": { + "useSsl": true, + "internalExperimentFlags": [], + "consistencyTokenJars": [] + } + }, + "videoId": "%VIDEO_ID%", + "playbackContext": { + "contentPlaybackContext": { + "currentUrl": "/watch?v=%VIDEO_ID%", + "vis": 0, + "splay": false, + "autoCaptionsDefaultOn": false, + "autonavState": "STATE_NONE", + "html5Preference": "HTML5_PREF_WANTS" + } + }, + "racyCheckOk": false, + "contentCheckOk": false +} +)END"; + bool youtube_url_extract_id(const std::string &youtube_url, std::string &youtube_video_id) { size_t index = youtube_url.find("youtube.com/watch?v="); if(index != std::string::npos) { @@ -626,7 +685,7 @@ namespace QuickMedia { std::vector additional_args = { { "-H", "x-spf-referer: " + search_url }, { "-H", "x-youtube-client-name: 1" }, - { "-H", "x-youtube-client-version: 2.20200626.03.00" }, + { "-H", youtube_client_version }, { "-H", "referer: " + search_url } }; @@ -634,7 +693,7 @@ namespace QuickMedia { 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); + DownloadResult result = download_json(json_root, search_url + "&pbj=1&gl=US&hl=en", std::move(additional_args), true); if(result != DownloadResult::OK) return download_result_to_search_result(result); if(!json_root.isArray()) @@ -694,13 +753,13 @@ namespace QuickMedia { } PluginResult YoutubeSearchPage::search_get_continuation(const std::string &url, const std::string ¤t_continuation_token, BodyItems &result_items) { - std::string next_url = url + "&pbj=1&ctoken=" + current_continuation_token; + std::string next_url = url + "&pbj=1&gl=US&hl=en&ctoken=" + current_continuation_token; std::vector additional_args = { { "-H", "x-spf-referer: " + url }, { "-H", "x-youtube-client-name: 1" }, { "-H", "x-spf-previous: " + url }, - { "-H", "x-youtube-client-version: 2.20200626.03.00" }, + { "-H", youtube_client_version }, { "-H", "referer: " + url } }; @@ -893,7 +952,7 @@ namespace QuickMedia { std::vector additional_args = { { "-H", "x-youtube-client-name: 1" }, - { "-H", "x-youtube-client-version: 2.20210308.08.00" }, + { "-H", youtube_client_version }, { "-F", "session_token=" + xsrf_token } }; @@ -975,7 +1034,7 @@ namespace QuickMedia { std::vector additional_args = { { "-H", "x-youtube-client-name: 1" }, - { "-H", "x-youtube-client-version: 2.20210308.08.00" }, + { "-H", youtube_client_version }, { "-F", "session_token=" + xsrf_token } }; @@ -1081,7 +1140,7 @@ namespace QuickMedia { { "-H", "x-origin: https://www.youtube.com" }, { "-H", "content-type: application/json" }, { "-H", "x-youtube-client-name: 1" }, - { "-H", "x-youtube-client-version: 2.20200626.03.00" }, + { "-H", youtube_client_version }, { "-H", "referer: " + url + "/videos" }, { "--data-raw", Json::writeString(json_builder, request_json) } }; @@ -1164,7 +1223,7 @@ namespace QuickMedia { { "-H", "x-origin: https://www.youtube.com" }, { "-H", "content-type: application/json" }, { "-H", "x-youtube-client-name: 1" }, - { "-H", "x-youtube-client-version: 2.20200626.03.00" }, + { "-H", youtube_client_version }, { "-H", "referer: " + url + "/videos" }, { "--data-raw", Json::writeString(json_builder, request_json) } }; @@ -1232,7 +1291,7 @@ namespace QuickMedia { { "-H", "x-spf-referer: " + url }, { "-H", "x-youtube-client-name: 1" }, { "-H", "x-spf-previous: " + url }, - { "-H", "x-youtube-client-version: 2.20200626.03.00" }, + { "-H", youtube_client_version }, { "-H", "referer: " + url } }; @@ -1240,7 +1299,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", std::move(additional_args), true); + 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); result_items = parse_channel_videos(json_root, continuation_token, added_videos); return PluginResult::OK; @@ -1486,13 +1545,13 @@ namespace QuickMedia { } PluginResult YoutubeRecommendedPage::search_get_continuation(const std::string ¤t_continuation_token, BodyItems &result_items) { - std::string next_url = "https://www.youtube.com/?pbj=1&ctoken=" + current_continuation_token; + std::string next_url = "https://www.youtube.com/?pbj=1&gl=US&hl=en&ctoken=" + current_continuation_token; std::vector additional_args = { { "-H", "x-spf-referer: https://www.youtube.com/" }, { "-H", "x-youtube-client-name: 1" }, { "-H", "x-spf-previous: https://www.youtube.com/" }, - { "-H", "x-youtube-client-version: 2.20200626.03.00" }, + { "-H", youtube_client_version }, { "-H", "referer: https://www.youtube.com/" } }; @@ -1575,14 +1634,14 @@ namespace QuickMedia { std::vector additional_args = { { "-H", "x-youtube-client-name: 1" }, - { "-H", "x-youtube-client-version: 2.20200626.03.00" } + { "-H", youtube_client_version } }; std::vector cookies = get_cookies(); additional_args.insert(additional_args.end(), cookies.begin(), cookies.end()); Json::Value json_root; - DownloadResult result = download_json(json_root, "https://www.youtube.com/?pbj=1", std::move(additional_args), true); + DownloadResult result = download_json(json_root, "https://www.youtube.com/?pbj=1&gl=US&hl=en", std::move(additional_args), true); if(result != DownloadResult::OK) return download_result_to_plugin_result(result); if(!json_root.isArray()) @@ -1695,90 +1754,84 @@ namespace QuickMedia { BodyItems YoutubeVideoPage::get_related_media(const std::string &url) { BodyItems result_items; - std::string modified_url = remove_index_from_playlist_url(url); + std::string video_id; + if(!youtube_url_extract_id(url, video_id)) { + fprintf(stderr, "Failed to extract youtube id from %s\n", url.c_str()); + return result_items; + } + + std::string request_data = key_api_request_data; + string_replace_all(request_data, "%VIDEO_ID%", video_id); + std::vector additional_args = { - { "-H", "x-spf-referer: " + url }, + { "-H", "Content-Type: application/json" }, { "-H", "x-youtube-client-name: 1" }, - { "-H", "x-spf-previous: " + url }, - { "-H", "x-youtube-client-version: 2.20200626.03.00" }, - { "-H", "referer: " + url } + { "-H", youtube_client_version }, + { "--data-raw", std::move(request_data) } }; std::vector 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); - if(result != DownloadResult::OK) return result_items; + DownloadResult download_result = download_json(json_root, "https://www.youtube.com/youtubei/v1/next?key=" + api_key, additional_args, true); + if(download_result != DownloadResult::OK) return result_items; - if(!json_root.isArray()) + if(!json_root.isObject()) return result_items; std::unordered_set added_videos; xsrf_token.clear(); comments_continuation_token.clear(); - for(const Json::Value &json_item : json_root) { - if(!json_item.isObject()) - continue; + // TODO: Find xsrf_token somehow. Maybe use /embed/ endpoint to find it? xsrf_token is needed for comments - 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 &contents_json = json_root["contents"]; + if(!contents_json.isObject()) + return result_items; - const Json::Value &response_json = json_item["response"]; - if(!response_json.isObject()) - continue; + const Json::Value &tcwnr_json = contents_json["twoColumnWatchNextResults"]; + if(!tcwnr_json.isObject()) + return result_items; - const Json::Value &contents_json = response_json["contents"]; - if(!contents_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 &tcwnr_json = contents_json["twoColumnWatchNextResults"]; - if(!tcwnr_json.isObject()) - return result_items; + const Json::Value &secondary_results_json = tcwnr_json["secondaryResults"]; + if(!secondary_results_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_results2_json = secondary_results_json["secondaryResults"]; + if(!secondary_results2_json.isObject()) + return result_items; + + const Json::Value &results_json = secondary_results2_json["results"]; + if(!results_json.isArray()) + return result_items; - const Json::Value &secondary_results_json = tcwnr_json["secondaryResults"]; - if(!secondary_results_json.isObject()) - return result_items; + for(const Json::Value &item_json : results_json) { + if(!item_json.isObject()) + continue; - const Json::Value &secondary_results2_json = secondary_results_json["secondaryResults"]; - if(!secondary_results2_json.isObject()) - return result_items; + auto body_item = parse_compact_video_renderer_json(item_json, added_videos); + if(body_item) + result_items.push_back(std::move(body_item)); - const Json::Value &results_json = secondary_results2_json["results"]; - if(!results_json.isArray()) - return result_items; - - for(const Json::Value &item_json : results_json) { - if(!item_json.isObject()) + const Json::Value &compact_autoplay_renderer_json = item_json["compactAutoplayRenderer"]; + if(!compact_autoplay_renderer_json.isObject()) + continue; + + const Json::Value &item_contents_json = compact_autoplay_renderer_json["contents"]; + if(!item_contents_json.isArray()) + continue; + + for(const Json::Value &content_item_json : item_contents_json) { + if(!content_item_json.isObject()) continue; - - auto body_item = parse_compact_video_renderer_json(item_json, added_videos); + + auto body_item = parse_compact_video_renderer_json(content_item_json, added_videos); if(body_item) result_items.push_back(std::move(body_item)); - - const Json::Value &compact_autoplay_renderer_json = item_json["compactAutoplayRenderer"]; - if(!compact_autoplay_renderer_json.isObject()) - continue; - - const Json::Value &item_contents_json = compact_autoplay_renderer_json["contents"]; - if(!item_contents_json.isArray()) - continue; - - for(const Json::Value &content_item_json : item_contents_json) { - if(!content_item_json.isObject()) - continue; - - auto body_item = parse_compact_video_renderer_json(content_item_json, added_videos); - if(body_item) - result_items.push_back(std::move(body_item)); - } } } @@ -1903,76 +1956,20 @@ namespace QuickMedia { return PluginResult::ERR; } + std::string request_data = key_api_request_data; + string_replace_all(request_data, "%VIDEO_ID%", video_id); + std::vector additional_args = { + { "-H", "Content-Type: application/json" }, { "-H", "x-youtube-client-name: 1" }, - { "-H", "x-youtube-client-version: 2.20200626.03.00" } + { "-H", youtube_client_version }, + { "--data-raw", std::move(request_data) } }; std::vector cookies = get_cookies(); additional_args.insert(additional_args.end(), cookies.begin(), cookies.end()); Json::Value json_root; - std::string request_data = -R"END( -{ - "context": { - "client": { - "hl": "en", - "gl": "US", - "deviceMake": "", - "deviceModel": "", - "userAgent": "Mozilla/5.0 (Windows NT 10.0; rv:78.0) Gecko/20100101 Firefox/78.0,gzip(gfe)", - "clientName": "WEB", - "clientVersion": "2.20210615.01.00", - "osName": "Windows", - "osVersion": "10.0", - "originalUrl": "https://www.youtube.com/watch?v=%VIDEO_ID%", - "platform": "DESKTOP", - "clientFormFactor": "UNKNOWN_FORM_FACTOR", - "timeZone": "UTC", - "browserName": "Firefox", - "browserVersion": "78.0", - "screenWidthPoints": 1054, - "screenHeightPoints": 289, - "screenPixelDensity": 1, - "screenDensityFloat": 1, - "utcOffsetMinutes": 0, - "userInterfaceTheme": "USER_INTERFACE_THEME_LIGHT", - "clientScreen": "WATCH", - "mainAppWebInfo": { - "graftUrl": "/watch?v=%VIDEO_ID%", - "webDisplayMode": "WEB_DISPLAY_MODE_BROWSER", - "isWebNativeShareAvailable": false - } - }, - "user": { - "lockedSafetyMode": false - }, - "request": { - "useSsl": true, - "internalExperimentFlags": [], - "consistencyTokenJars": [] - } - }, - "videoId": "%VIDEO_ID%", - "playbackContext": { - "contentPlaybackContext": { - "currentUrl": "/watch?v=%VIDEO_ID%", - "vis": 0, - "splay": false, - "autoCaptionsDefaultOn": false, - "autonavState": "STATE_NONE", - "html5Preference": "HTML5_PREF_WANTS" - } - }, - "racyCheckOk": false, - "contentCheckOk": false -} -)END"; - string_replace_all(request_data, "%VIDEO_ID%", video_id); - additional_args.push_back({ "-H", "Content-Type: application/json" }); - additional_args.push_back({ "--data-raw", std::move(request_data) }); - DownloadResult download_result = download_json(json_root, "https://www.youtube.com/youtubei/v1/player?key=" + api_key, additional_args, true); if(download_result != DownloadResult::OK) return download_result_to_plugin_result(download_result); @@ -2076,7 +2073,7 @@ R"END( std::vector additional_args = { { "-H", "x-youtube-client-name: 1" }, - { "-H", "x-youtube-client-version: 2.20200626.03.00" } + { "-H", youtube_client_version } }; std::vector cookies = get_cookies(); @@ -2109,7 +2106,12 @@ R"END( if(cipher_params.empty() || url.empty()) return false; + std::string cpn; + cpn.resize(16); + generate_random_characters(cpn.data(), cpn.size(), "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_", 64); + std::string url_decoded = url_param_decode(url); + url_decoded += "&alr=yes&cver=2.20210615.01.00&altitags=395,394&cpn=" + cpn; const std::string &s = cipher_params["s"]; const std::string &sp = cipher_params["sp"]; @@ -2210,6 +2212,6 @@ R"END( parse_format(formats_json, false); const Json::Value &adaptive_formats_json = streaming_data_json["adaptiveFormats"]; - parse_format(adaptive_formats_json, false); + parse_format(adaptive_formats_json, true); } } \ No newline at end of file -- cgit v1.2.3