From 7dc7ee78094a3d76e8cb70d724a7054d59347369 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Thu, 29 Apr 2021 00:21:50 +0200 Subject: Add xvideos and xhamster --- icons/xhamster_launcher.png | Bin 0 -> 2731 bytes icons/xvideos_launcher.png | Bin 0 -> 2843 bytes images/xhamster_logo.png | Bin 0 -> 4768 bytes images/xvideos_logo.png | Bin 0 -> 2843 bytes plugins/MediaGeneric.hpp | 11 +++++ src/QuickMedia.cpp | 100 +++++++++++++++++++++++++++++++++++++++++-- src/plugins/MangaGeneric.cpp | 38 +++++++--------- src/plugins/MediaGeneric.cpp | 53 +++++++++++++++++------ 8 files changed, 163 insertions(+), 39 deletions(-) create mode 100644 icons/xhamster_launcher.png create mode 100644 icons/xvideos_launcher.png create mode 100644 images/xhamster_logo.png create mode 100644 images/xvideos_logo.png diff --git a/icons/xhamster_launcher.png b/icons/xhamster_launcher.png new file mode 100644 index 0000000..4c6b8a4 Binary files /dev/null and b/icons/xhamster_launcher.png differ diff --git a/icons/xvideos_launcher.png b/icons/xvideos_launcher.png new file mode 100644 index 0000000..d279cab Binary files /dev/null and b/icons/xvideos_launcher.png differ diff --git a/images/xhamster_logo.png b/images/xhamster_logo.png new file mode 100644 index 0000000..f015f7d Binary files /dev/null and b/images/xhamster_logo.png differ diff --git a/images/xvideos_logo.png b/images/xvideos_logo.png new file mode 100644 index 0000000..d279cab Binary files /dev/null and b/images/xvideos_logo.png differ diff --git a/plugins/MediaGeneric.hpp b/plugins/MediaGeneric.hpp index e4048a9..f7ff019 100644 --- a/plugins/MediaGeneric.hpp +++ b/plugins/MediaGeneric.hpp @@ -24,6 +24,14 @@ namespace QuickMedia { const char *field_contains = nullptr; }; + struct MediaRelatedItem { + std::string title; + std::string url; + std::string thumbnail_url; + }; + + using MediaRelatedCustomHandler = std::function(const std::string &html_source)>; + class MediaGenericSearchPage : public Page { public: MediaGenericSearchPage(Program *program, const char *website_url, sf::Vector2i thumbnail_max_size); @@ -49,6 +57,8 @@ namespace QuickMedia { MediaGenericSearchPage& related_media_text_handler(std::vector queries); // This is optional. MediaGenericSearchPage& related_media_thumbnail_handler(std::vector queries); + // This is optional. + MediaGenericSearchPage& related_media_custom_handler(MediaRelatedCustomHandler handler); private: std::string website_url; sf::Vector2i thumbnail_max_size; @@ -57,6 +67,7 @@ namespace QuickMedia { std::vector thumbnail_queries; std::vector related_media_text_queries; std::vector related_media_thumbnail_queries; + MediaRelatedCustomHandler related_custom_handler = nullptr; }; class MediaGenericRelatedPage : public RelatedVideosPage { diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index fd679ff..7402b97 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -67,6 +67,8 @@ static const std::pair valid_plugins[] = { std::make_pair("soundcloud", "soundcloud_logo.png"), std::make_pair("pornhub", "pornhub_logo.png"), std::make_pair("spankbang", "spankbang_logo.png"), + std::make_pair("xvideos", "xvideos_logo.png"), + std::make_pair("xhamster", "xhamster_logo.png"), std::make_pair("4chan", "4chan_logo.png"), std::make_pair("nyaa.si", "nyaa_si_logo.png"), std::make_pair("matrix", "matrix_logo.png"), @@ -444,7 +446,7 @@ namespace QuickMedia { static void usage() { fprintf(stderr, "usage: quickmedia [--no-video] [--use-system-mpv-config] [--dir ] [-e ]\n"); fprintf(stderr, "OPTIONS:\n"); - fprintf(stderr, " plugin The plugin to use. Should be either launcher, 4chan, manga, manganelo, manganelos, mangatown, mangakatana, mangadex, pornhub, spankbang, youtube, spotify, soundcloud, nyaa.si, matrix, file-manager or stdin\n"); + fprintf(stderr, " plugin The plugin to use. Should be either launcher, 4chan, manga, manganelo, manganelos, mangatown, mangakatana, mangadex, pornhub, spankbang, xvideos, xhamster, youtube, spotify, soundcloud, nyaa.si, matrix, file-manager or stdin\n"); fprintf(stderr, " --no-video Only play audio when playing a video. Disabled by default\n"); fprintf(stderr, " --use-system-mpv-config Use system mpv config instead of no config. Disabled by default\n"); fprintf(stderr, " --upscale-images Upscale low-resolution manga pages using waifu2x-ncnn-vulkan. Disabled by default\n"); @@ -804,6 +806,90 @@ namespace QuickMedia { .related_media_thumbnail_handler({{"//div[class='right']//div[class='video-item']//img", "data-src", nullptr}}); } + // Returns size_t(-1) if not found + static size_t find_end_of_json_array(const char *str, size_t start, size_t size) { + if(size <= start || str[start] != '[') + return size_t(-1); + + bool inside_string = false; + bool escape = false; + int array_depth = 0; + for(size_t i = start; i < size; ++i) { + char c = str[i]; + if(c == '"' && !escape) { + inside_string = !inside_string; + } else if(c == '\\') { + escape = !escape; + } else if(c == '[' && !inside_string && !escape) { + ++array_depth; + } else if(c == ']' && !inside_string && !escape) { + --array_depth; + if(array_depth == 0) + return i + 1; + } else { + escape = false; + } + } + + return size_t(-1); + } + + static void add_xvideos_handlers(MediaGenericSearchPage *media_generic_search_page) { + media_generic_search_page->search_handler("https://www.xvideos.com/?k=%s&p=%p", 0) + .text_handler({{"//div[id='content']//div[class='thumb-under']//a", "title", "href", "/video"}}) + .thumbnail_handler({{"//div[id='content']//div[class='thumb']//img", "data-src", "/videos/"}}) + .related_media_custom_handler([](const std::string &html_source) { + std::vector related_items; + size_t related_start = html_source.find("video_related=["); + if(related_start == std::string::npos) + return related_items; + + related_start += 14; // just before [ + size_t json_end = find_end_of_json_array(html_source.c_str(), related_start, html_source.size()); + if(json_end == size_t(-1)) + return related_items; + + Json::Value json_root; + Json::CharReaderBuilder json_builder; + std::unique_ptr json_reader(json_builder.newCharReader()); + std::string json_errors; + if(!json_reader->parse(html_source.c_str() + related_start, html_source.c_str() + json_end, &json_root, &json_errors)) { + fprintf(stderr, "Failed to parse xvideos related json, error: %s\n", json_errors.c_str()); + return related_items; + } + + if(!json_root.isArray()) + return related_items; + + for(const Json::Value &json_item : json_root) { + if(!json_item.isObject()) + continue; + + const Json::Value &title_json = json_item["tf"]; + const Json::Value &url_json = json_item["u"]; + const Json::Value &thumbnail_url_json = json_item["i"]; + if(!title_json.isString() || !url_json.isString() || !thumbnail_url_json.isString()) + continue; + + MediaRelatedItem related_item; + related_item.title = title_json.asString(); + related_item.url = url_json.asString(); + related_item.thumbnail_url = thumbnail_url_json.asString(); + related_items.push_back(std::move(related_item)); + } + + return related_items; + }); + } + + static void add_xhamster_handlers(MediaGenericSearchPage *media_generic_search_page) { + media_generic_search_page->search_handler("https://xhamster.com/search/%s?page=%p", 1) + .text_handler({{"//div[class='video-thumb-info']//a", "text", "href", "/videos/"}}) + .thumbnail_handler({{"//img", "src", "/thumb-"}}) + .related_media_text_handler({{"//div[class='video-thumb-info']//a", "text", "href", "/videos/"}}) + .related_media_thumbnail_handler({{"//img", "src", "/thumb-"}}); + } + void Program::load_plugin_by_name(std::vector &tabs, const char *start_dir) { if(!plugin_name || plugin_name[0] == '\0') return; @@ -935,13 +1021,21 @@ namespace QuickMedia { tabs.push_back(Tab{create_body(), std::move(recommended_page), std::move(recommended_search_bar)}); tabs.push_back(Tab{create_body(), std::make_unique(this), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); } else if(strcmp(plugin_name, "pornhub") == 0) { - auto search_page = std::make_unique(this, "https://www.pornhub.com/", sf::Vector2i(320, 180)); + auto search_page = std::make_unique(this, "https://www.pornhub.com/", sf::Vector2i(320/1.5f, 180/1.5f)); add_pornhub_handlers(search_page.get()); tabs.push_back(Tab{create_body(), std::move(search_page), create_search_bar("Search...", 500)}); } else if(strcmp(plugin_name, "spankbang") == 0) { - auto search_page = std::make_unique(this, "https://spankbang.com/", sf::Vector2i(500/2, 281/2)); + auto search_page = std::make_unique(this, "https://spankbang.com/", sf::Vector2i(500/2.5f, 281/2.5f)); add_spankbang_handlers(search_page.get()); tabs.push_back(Tab{create_body(), std::move(search_page), create_search_bar("Search...", 500)}); + } else if(strcmp(plugin_name, "xvideos") == 0) { + auto search_page = std::make_unique(this, "https://www.xvideos.com/", sf::Vector2i(352/1.5f, 198/1.5f)); + add_xvideos_handlers(search_page.get()); + tabs.push_back(Tab{create_body(), std::move(search_page), create_search_bar("Search...", 500)}); + } else if(strcmp(plugin_name, "xhamster") == 0) { + auto search_page = std::make_unique(this, "https://xhamster.com/", sf::Vector2i(240, 135)); + add_xhamster_handlers(search_page.get()); + tabs.push_back(Tab{create_body(), std::move(search_page), create_search_bar("Search...", 500)}); } else if(strcmp(plugin_name, "spotify") == 0) { tabs.push_back(Tab{create_body(), std::make_unique(this), create_search_bar("Search...", 350)}); no_video = true; diff --git a/src/plugins/MangaGeneric.cpp b/src/plugins/MangaGeneric.cpp index b3326fc..48533b8 100644 --- a/src/plugins/MangaGeneric.cpp +++ b/src/plugins/MangaGeneric.cpp @@ -49,6 +49,20 @@ namespace QuickMedia { return quickmedia_html_node_get_attribute_value(node, field_name); } + static void body_items_prepend_website_url(BodyItems &body_items, const std::string &website_url) { + for(auto &body_item : body_items) { + if(string_starts_with(body_item->url, "//")) + body_item->url = "https://" + body_item->url.substr(2); + else if(string_starts_with(body_item->url, "/")) + body_item->url = website_url + body_item->url.substr(1); + + if(string_starts_with(body_item->thumbnail_url, "//")) + body_item->thumbnail_url = "https://" + body_item->thumbnail_url.substr(2); + else if(string_starts_with(body_item->thumbnail_url, "/")) + body_item->thumbnail_url = website_url + body_item->thumbnail_url.substr(1); + } + } + static int html_append_search(QuickMediaHtmlSearch *html_search, const char *html_query, HtmlSearchUserdata *search_userdata) { return quickmedia_html_find_nodes_xpath(html_search, html_query, [](QuickMediaHtmlNode *node, void *userdata) { @@ -239,17 +253,7 @@ namespace QuickMedia { } } - for(auto &body_item : result_items) { - if(string_starts_with(body_item->url, "//")) - body_item->url = "https://" + body_item->url.substr(2); - else if(string_starts_with(body_item->url, "/")) - body_item->url = website_url + body_item->url.substr(1); - - if(string_starts_with(body_item->thumbnail_url, "//")) - body_item->thumbnail_url = "https://" + body_item->thumbnail_url.substr(2); - else if(string_starts_with(body_item->thumbnail_url, "/")) - body_item->thumbnail_url = website_url + body_item->thumbnail_url.substr(1); - } + body_items_prepend_website_url(result_items, website_url); cleanup: quickmedia_html_search_deinit(&html_search); @@ -333,17 +337,7 @@ namespace QuickMedia { } } - for(auto &body_item : chapters_items) { - if(string_starts_with(body_item->url, "//")) - body_item->url = "https://" + body_item->url.substr(2); - else if(string_starts_with(body_item->url, "/")) - body_item->url = website_url + body_item->url.substr(1); - - if(string_starts_with(body_item->thumbnail_url, "//")) - body_item->thumbnail_url = "https://" + body_item->thumbnail_url.substr(2); - else if(string_starts_with(body_item->thumbnail_url, "/")) - body_item->thumbnail_url = website_url + body_item->thumbnail_url.substr(1); - } + body_items_prepend_website_url(chapters_items, website_url); for(auto &it : creators) { if(string_starts_with(it.second, "//")) diff --git a/src/plugins/MediaGeneric.cpp b/src/plugins/MediaGeneric.cpp index c911e6b..19ad87c 100644 --- a/src/plugins/MediaGeneric.cpp +++ b/src/plugins/MediaGeneric.cpp @@ -18,7 +18,21 @@ namespace QuickMedia { return quickmedia_html_node_get_attribute_value(node, field_name); } - static PluginResult fetch_page_results(const std::string &url, const std::string &website_url, const std::vector &text_queries, const std::vector &thumbnail_queries, BodyItems &result_items) { + static void body_items_prepend_website_url(BodyItems &body_items, const std::string &website_url) { + for(auto &body_item : body_items) { + if(string_starts_with(body_item->url, "//")) + body_item->url = "https://" + body_item->url.substr(2); + else if(string_starts_with(body_item->url, "/")) + body_item->url = website_url + body_item->url.substr(1); + + if(string_starts_with(body_item->thumbnail_url, "//")) + body_item->thumbnail_url = "https://" + body_item->thumbnail_url.substr(2); + else if(string_starts_with(body_item->thumbnail_url, "/")) + body_item->thumbnail_url = website_url + body_item->thumbnail_url.substr(1); + } + } + + static PluginResult fetch_page_results(const std::string &url, const std::string &website_url, const std::vector &text_queries, const std::vector &thumbnail_queries, MediaRelatedCustomHandler *custom_handler, BodyItems &result_items) { std::vector args; if(!website_url.empty()) args.push_back({ "-H", "referer: " + website_url }); @@ -30,6 +44,18 @@ namespace QuickMedia { if(website_data.empty()) return PluginResult::OK; + if(custom_handler && *custom_handler) { + std::vector media_related_items = (*custom_handler)(website_data); + for(MediaRelatedItem &media_related_item : media_related_items) { + auto body_item = BodyItem::create(strip(media_related_item.title)); + body_item->url = std::move(media_related_item.url); + body_item->thumbnail_url = std::move(media_related_item.thumbnail_url); + result_items.push_back(std::move(body_item)); + } + body_items_prepend_website_url(result_items, website_url); + return PluginResult::OK; + } + QuickMediaHtmlSearch html_search; int result = quickmedia_html_search_init(&html_search, website_data.c_str()); if(result != 0) @@ -73,17 +99,7 @@ namespace QuickMedia { } } - for(auto &body_item : result_items) { - if(string_starts_with(body_item->url, "//")) - body_item->url = "https://" + body_item->url.substr(2); - else if(string_starts_with(body_item->url, "/")) - body_item->url = website_url + body_item->url.substr(1); - - if(string_starts_with(body_item->thumbnail_url, "//")) - body_item->thumbnail_url = "https://" + body_item->thumbnail_url.substr(2); - else if(string_starts_with(body_item->thumbnail_url, "/")) - body_item->thumbnail_url = website_url + body_item->thumbnail_url.substr(1); - } + body_items_prepend_website_url(result_items, website_url); cleanup: quickmedia_html_search_deinit(&html_search); @@ -112,7 +128,7 @@ namespace QuickMedia { std::string url = search_query.search_template; string_replace_all(url, "%s", url_param_encode(str)); string_replace_all(url, "%p", std::to_string(search_query.page_start + page)); - return fetch_page_results(url, website_url, text_queries, thumbnail_queries, result_items); + return fetch_page_results(url, website_url, text_queries, thumbnail_queries, nullptr, result_items); } PluginResult MediaGenericSearchPage::submit(const std::string&, const std::string &url, std::vector &result_tabs) { @@ -121,7 +137,7 @@ namespace QuickMedia { } PluginResult MediaGenericSearchPage::get_related_media(const std::string &url, BodyItems &result_items) { - return fetch_page_results(url, website_url, related_media_text_queries, related_media_thumbnail_queries, result_items); + return fetch_page_results(url, website_url, related_media_text_queries, related_media_thumbnail_queries, &related_custom_handler, result_items); } MediaGenericSearchPage& MediaGenericSearchPage::search_handler(const char *search_template, int page_start) { @@ -142,11 +158,20 @@ namespace QuickMedia { MediaGenericSearchPage& MediaGenericSearchPage::related_media_text_handler(std::vector queries) { related_media_text_queries = std::move(queries); + related_custom_handler = nullptr; return *this; } MediaGenericSearchPage& MediaGenericSearchPage::related_media_thumbnail_handler(std::vector queries) { related_media_thumbnail_queries = std::move(queries); + related_custom_handler = nullptr; + return *this; + } + + MediaGenericSearchPage& MediaGenericSearchPage::related_media_custom_handler(MediaRelatedCustomHandler handler) { + related_custom_handler = handler; + related_media_text_queries.clear(); + related_media_thumbnail_queries.clear(); return *this; } -- cgit v1.2.3