diff options
-rw-r--r-- | src/plugins/Youtube.cpp | 256 |
1 files changed, 252 insertions, 4 deletions
diff --git a/src/plugins/Youtube.cpp b/src/plugins/Youtube.cpp index 3660810..4ddc549 100644 --- a/src/plugins/Youtube.cpp +++ b/src/plugins/Youtube.cpp @@ -34,7 +34,7 @@ namespace QuickMedia { url += url_param_encode(text); std::string server_response; - if(download_to_string(url, server_response) != DownloadResult::OK) + if(download_to_string(url, server_response, {}, use_tor, true) != DownloadResult::OK) return SuggestionResult::NET_ERR; size_t json_start = server_response.find_first_of('('); @@ -70,12 +70,12 @@ namespace QuickMedia { if(!found_search_text) result_items.insert(result_items.begin(), std::make_unique<BodyItem>(text)); return SuggestionResult::OK; - #endif + #elif 0 std::string url = "https://youtube.com/results?search_query="; url += url_param_encode(text); std::string website_data; - if(download_to_string(url, website_data, {}, use_tor) != DownloadResult::OK) + if(download_to_string(url, website_data, {}, use_tor, true) != DownloadResult::OK) return SuggestionResult::NET_ERR; struct ItemData { @@ -125,6 +125,119 @@ namespace QuickMedia { cleanup: quickmedia_html_search_deinit(&html_search); return result == 0 ? SuggestionResult::OK : SuggestionResult::ERR; + #else + std::string url = "https://youtube.com/results?search_query="; + url += url_param_encode(text); + + std::string website_data; + if(download_to_string(url, website_data, {}, use_tor, true) != DownloadResult::OK) + return SuggestionResult::NET_ERR; + + size_t data_start = website_data.find("window[\"ytInitialData\"] = {"); + if(data_start == std::string::npos) + return SuggestionResult::ERR; + + data_start = data_start + 26; + size_t data_end = 0; + int brace_count = 0; + for(size_t i = data_start; i < website_data.size(); ++i) { + char c = website_data[i]; + if(c == '{') + ++brace_count; + else if(c == '}') { + --brace_count; + if(brace_count == 0) { + data_end = i + 1; + break; + } + } + } + + if(data_end == 0) + return SuggestionResult::ERR; + + 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(&website_data[data_start], &website_data[data_end], &json_root, &json_errors)) { + fprintf(stderr, "Youtube search json error: %s\n", json_errors.c_str()); + return SuggestionResult::ERR; + } + + const Json::Value &contents_json = json_root["contents"]; + if(!contents_json.isObject()) + return SuggestionResult::ERR; + + const Json::Value &tcsrr_json = contents_json["twoColumnSearchResultsRenderer"]; + if(!tcsrr_json.isObject()) + return SuggestionResult::ERR; + + const Json::Value &primary_contents_json = tcsrr_json["primaryContents"]; + if(!primary_contents_json.isObject()) + return SuggestionResult::ERR; + + const Json::Value §ion_list_renderer_json = primary_contents_json["sectionListRenderer"]; + if(!section_list_renderer_json.isObject()) + return SuggestionResult::ERR; + + const Json::Value &contents2_json = section_list_renderer_json["contents"]; + if(!contents2_json.isArray()) + return SuggestionResult::ERR; + + for(const Json::Value &item_json : contents2_json) { + if(!item_json.isObject()) + continue; + + const Json::Value &item_section_renderer_json = item_json["itemSectionRenderer"]; + if(!item_section_renderer_json.isObject()) + continue; + + const Json::Value &item_contents_json = item_section_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; + + const Json::Value &video_renderer_json = content_item_json["videoRenderer"]; + if(!video_renderer_json.isObject()) + continue; + + const Json::Value &video_id_json = video_renderer_json["videoId"]; + if(!video_id_json.isString()) + continue; + + std::string video_id_str = video_id_json.asString(); + std::string thumbnail_url = "https://img.youtube.com/vi/" + video_id_str + "/hqdefault.jpg"; + + const char *title = nullptr; + const Json::Value &title_json = video_renderer_json["title"]; + if(title_json.isObject()) { + const Json::Value &runs_json = title_json["runs"]; + if(runs_json.isArray() && runs_json.size() > 0) { + const Json::Value &first_runs_json = runs_json[0]; + if(first_runs_json.isObject()) { + const Json::Value &text_json = first_runs_json["text"]; + if(text_json.isString()) + title = text_json.asCString(); + } + } + } + + if(!title) + continue; + + auto body_item = std::make_unique<BodyItem>(title); + body_item->url = "https://www.youtube.com/watch?v=" + video_id_str; + body_item->thumbnail_url = std::move(thumbnail_url); + result_items.push_back(std::move(body_item)); + } + } + + return SuggestionResult::OK; + #endif } static std::string get_playlist_id_from_url(const std::string &url) { @@ -143,9 +256,40 @@ namespace QuickMedia { return result.substr(0, index); } + static std::unique_ptr<BodyItem> parse_compact_video_renderer_json(const Json::Value &item_json) { + 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(); + std::string thumbnail_url = "https://img.youtube.com/vi/" + video_id_str + "/hqdefault.jpg"; + + 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 = std::make_unique<BodyItem>(title); + body_item->url = "https://www.youtube.com/watch?v=" + video_id_str; + body_item->thumbnail_url = std::move(thumbnail_url); + 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) { + #if 0 BodyItems result_items; struct ItemData { BodyItems &result_items; @@ -164,7 +308,7 @@ namespace QuickMedia { } std::string website_data; - if(download_to_string(modified_url, website_data, {}, use_tor) != DownloadResult::OK) + if(download_to_string(modified_url, website_data, {}, use_tor, false) != DownloadResult::OK) return result_items; QuickMediaHtmlSearch html_search; @@ -223,5 +367,109 @@ namespace QuickMedia { last_related_media_playlist_id = playlist_id; quickmedia_html_search_deinit(&html_search); return result_items; + #elif 1 + BodyItems result_items; + + std::string modified_url = remove_index_from_playlist_url(url); + std::string playlist_id = get_playlist_id_from_url(modified_url); + if(playlist_id == last_related_media_playlist_id) { + result_items.reserve(last_playlist_data.size()); + for(auto &data : last_playlist_data) { + result_items.push_back(std::make_unique<BodyItem>(*data)); + } + return result_items; + } + + std::string website_data; + if(download_to_string(modified_url, website_data, {}, use_tor, true) != DownloadResult::OK) + return result_items; + + size_t data_start = website_data.find("window[\"ytInitialData\"] = {"); + if(data_start == std::string::npos) + return result_items; + + data_start = data_start + 26; + size_t data_end = 0; + int brace_count = 0; + for(size_t i = data_start; i < website_data.size(); ++i) { + char c = website_data[i]; + if(c == '{') + ++brace_count; + else if(c == '}') { + --brace_count; + if(brace_count == 0) { + data_end = i + 1; + break; + } + } + } + + if(data_end == 0) + return result_items; + + 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(&website_data[data_start], &website_data[data_end], &json_root, &json_errors)) { + fprintf(stderr, "Youtube search json error: %s\n", json_errors.c_str()); + return result_items; + } + + FILE *ff = fopen("related.json", "wb"); + fwrite(&website_data[data_start], 1, data_end - data_start, ff); + fclose(ff); + + const Json::Value &contents_json = json_root["contents"]; + if(!contents_json.isObject()) + return result_items; + + 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; + + 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; + + for(const Json::Value &item_json : results_json) { + if(!item_json.isObject()) + continue; + + auto body_item = parse_compact_video_renderer_json(item_json); + 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; + + const Json::Value &compact_video_renderer_json = content_item_json["compactVideoRenderer"]; + if(!compact_video_renderer_json.isObject()) + continue; + + auto body_item = parse_compact_video_renderer_json(content_item_json); + result_items.push_back(std::move(body_item)); + } + } + + return result_items; + #endif } }
\ No newline at end of file |