diff options
author | dec05eba <dec05eba@protonmail.com> | 2021-04-28 22:50:42 +0200 |
---|---|---|
committer | dec05eba <dec05eba@protonmail.com> | 2021-04-28 22:50:42 +0200 |
commit | 67618e51ed44effba871447255a5e7389969ccaa (patch) | |
tree | 5831ef112d484c792b51794603740e0f8fc11284 | |
parent | 4694f8544c4cbd7e7d92323976b63cc77fdadb27 (diff) |
Create generic media plugin
m--------- | depends/html-parser | 0 | ||||
m--------- | depends/html-search | 0 | ||||
-rw-r--r-- | plugins/MediaGeneric.hpp | 85 | ||||
-rw-r--r-- | plugins/Pornhub.hpp | 36 | ||||
-rw-r--r-- | plugins/Spankbang.hpp | 36 | ||||
-rw-r--r-- | src/QuickMedia.cpp | 79 | ||||
-rw-r--r-- | src/plugins/MangaGeneric.cpp | 13 | ||||
-rw-r--r-- | src/plugins/MediaGeneric.cpp | 172 | ||||
-rw-r--r-- | src/plugins/Pornhub.cpp | 164 | ||||
-rw-r--r-- | src/plugins/Spankbang.cpp | 157 |
10 files changed, 307 insertions, 435 deletions
diff --git a/depends/html-parser b/depends/html-parser -Subproject efdd24c40d9d6ffa5207ddc369b03eba86e9e22 +Subproject 917f810d7f196fef5959bc3096ce7360df961fc diff --git a/depends/html-search b/depends/html-search -Subproject 4e5de201c070352837d22f3700b3ea47d9ed304 +Subproject 0578bfd08637d3e113d28507ea73fa9a649f2f2 diff --git a/plugins/MediaGeneric.hpp b/plugins/MediaGeneric.hpp new file mode 100644 index 0000000..e4048a9 --- /dev/null +++ b/plugins/MediaGeneric.hpp @@ -0,0 +1,85 @@ +#pragma once + +#include "Page.hpp" +#include <functional> + +namespace QuickMedia { + struct MediaSearchQuery { + const char *search_template = nullptr; + int page_start = 0; + }; + + // If |url_contains| is null, then any matching query is added. If |title_field| is "text", then the inner text is used. + // If |url_field| is null, then the current page is used instead. + struct MediaTextQuery { + const char *html_query = nullptr; + const char *title_field = nullptr; + const char *url_field = nullptr; + const char *url_contains = nullptr; + }; + + struct MediaThumbnailQuery { + const char *html_query = nullptr; + const char *field_name = nullptr; + const char *field_contains = nullptr; + }; + + class MediaGenericSearchPage : public Page { + public: + MediaGenericSearchPage(Program *program, const char *website_url, sf::Vector2i thumbnail_max_size); + const char* get_title() const override { return "All"; } + bool search_is_filter() override { return false; } + SearchResult search(const std::string &str, BodyItems &result_items) override; + PluginResult get_page(const std::string &str, int page, BodyItems &result_items) override; + PluginResult submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) override; + sf::Vector2i get_thumbnail_max_size() override { return thumbnail_max_size; }; + + PluginResult get_related_media(const std::string &url, BodyItems &result_items); + + // Add a %s where the search query should be inserted into |search_template| and add a %p where the page number should be inserted, for example: + // example.com/search?q=%s&page=%p + // This is required. + MediaGenericSearchPage& search_handler(const char *search_template, int page_start); + // This is required. + MediaGenericSearchPage& text_handler(std::vector<MediaTextQuery> queries); + // This is optional. + MediaGenericSearchPage& thumbnail_handler(std::vector<MediaThumbnailQuery> queries); + + // This is optional. + MediaGenericSearchPage& related_media_text_handler(std::vector<MediaTextQuery> queries); + // This is optional. + MediaGenericSearchPage& related_media_thumbnail_handler(std::vector<MediaThumbnailQuery> queries); + private: + std::string website_url; + sf::Vector2i thumbnail_max_size; + MediaSearchQuery search_query; + std::vector<MediaTextQuery> text_queries; + std::vector<MediaThumbnailQuery> thumbnail_queries; + std::vector<MediaTextQuery> related_media_text_queries; + std::vector<MediaThumbnailQuery> related_media_thumbnail_queries; + }; + + class MediaGenericRelatedPage : public RelatedVideosPage { + public: + MediaGenericRelatedPage(Program *program, MediaGenericSearchPage *search_page) : RelatedVideosPage(program), search_page(search_page) {} + PluginResult submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) override; + private: + MediaGenericSearchPage *search_page; + }; + + class MediaGenericVideoPage : public VideoPage { + public: + MediaGenericVideoPage(Program *program, MediaGenericSearchPage *search_page, const std::string &url) : VideoPage(program), search_page(search_page), url(url) {} + const char* get_title() const override { return ""; } + BodyItems get_related_media(const std::string &url, std::string &channel_url) override; + std::unique_ptr<Page> create_search_page(Program *program, int &search_delay) override; + std::unique_ptr<RelatedVideosPage> create_related_videos_page(Program *program, const std::string &video_url, const std::string &video_title) override; + std::unique_ptr<Page> create_channels_page(Program*, const std::string&) override { + return nullptr; + } + std::string get_url() override { return url; } + private: + MediaGenericSearchPage *search_page; + std::string url; + }; +}
\ No newline at end of file diff --git a/plugins/Pornhub.hpp b/plugins/Pornhub.hpp deleted file mode 100644 index 8f6f563..0000000 --- a/plugins/Pornhub.hpp +++ /dev/null @@ -1,36 +0,0 @@ -#pragma once - -#include "Page.hpp" - -namespace QuickMedia { - class PornhubSearchPage : public Page { - public: - PornhubSearchPage(Program *program) : Page(program) {} - const char* get_title() const override { return "All"; } - bool search_is_filter() override { return false; } - SearchResult search(const std::string &str, BodyItems &result_items) override; - PluginResult get_page(const std::string &str, int page, BodyItems &result_items) override; - PluginResult submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) override; - }; - - class PornhubRelatedVideosPage : public RelatedVideosPage { - public: - PornhubRelatedVideosPage(Program *program) : RelatedVideosPage(program) {} - PluginResult submit(const std::string&, const std::string&, std::vector<Tab> &result_tabs) override; - }; - - class PornhubVideoPage : public VideoPage { - public: - PornhubVideoPage(Program *program, const std::string &url) : VideoPage(program), url(url) {} - const char* get_title() const override { return ""; } - BodyItems get_related_media(const std::string &url, std::string &channel_url) override; - std::unique_ptr<Page> create_search_page(Program *program, int &search_delay) override; - std::unique_ptr<RelatedVideosPage> create_related_videos_page(Program *program, const std::string &video_url, const std::string &video_title) override; - std::unique_ptr<Page> create_channels_page(Program*, const std::string&) override { - return nullptr; - } - std::string get_url() override { return url; } - private: - std::string url; - }; -}
\ No newline at end of file diff --git a/plugins/Spankbang.hpp b/plugins/Spankbang.hpp deleted file mode 100644 index 06ea509..0000000 --- a/plugins/Spankbang.hpp +++ /dev/null @@ -1,36 +0,0 @@ -#pragma once - -#include "Page.hpp" - -namespace QuickMedia { - class SpankbangSearchPage : public Page { - public: - SpankbangSearchPage(Program *program) : Page(program) {} - const char* get_title() const override { return "All"; } - bool search_is_filter() override { return false; } - SearchResult search(const std::string &str, BodyItems &result_items) override; - PluginResult get_page(const std::string &str, int page, BodyItems &result_items) override; - PluginResult submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) override; - }; - - class SpankbangRelatedVideosPage : public RelatedVideosPage { - public: - SpankbangRelatedVideosPage(Program *program) : RelatedVideosPage(program) {} - PluginResult submit(const std::string&, const std::string&, std::vector<Tab> &result_tabs) override; - }; - - class SpankbangVideoPage : public VideoPage { - public: - SpankbangVideoPage(Program *program, const std::string &url) : VideoPage(program), url(url) {} - const char* get_title() const override { return ""; } - BodyItems get_related_media(const std::string &url, std::string &channel_url) override; - std::unique_ptr<Page> create_search_page(Program *program, int &search_delay) override; - std::unique_ptr<RelatedVideosPage> create_related_videos_page(Program *program, const std::string &video_url, const std::string &video_title) override; - std::unique_ptr<Page> create_channels_page(Program*, const std::string&) override { - return nullptr; - } - std::string get_url() override { return url; } - private: - std::string url; - }; -}
\ No newline at end of file diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index 4b645d4..fd679ff 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -3,9 +3,8 @@ #include "../plugins/Mangadex.hpp" #include "../plugins/MangaGeneric.hpp" #include "../plugins/MangaCombined.hpp" +#include "../plugins/MediaGeneric.hpp" #include "../plugins/Youtube.hpp" -#include "../plugins/Pornhub.hpp" -#include "../plugins/Spankbang.hpp" #include "../plugins/Fourchan.hpp" #include "../plugins/NyaaSi.hpp" #include "../plugins/Matrix.hpp" @@ -789,6 +788,22 @@ namespace QuickMedia { .manga_id_handler("/manga/", nullptr); } + static void add_pornhub_handlers(MediaGenericSearchPage *media_generic_search_page) { + media_generic_search_page->search_handler("https://www.pornhub.com/video/search?search=%s&page=%p", 1) + .text_handler({{"//div[class='nf-videos']//div[class='phimage']//a", "title", "href", "/view_video.php"}}) + .thumbnail_handler({{"//div[class='nf-videos']//div[class='phimage']//img", "data-src", "/videos/"}}) + .related_media_text_handler({{"//div[class='phimage']//a", "title", "href", "/view_video.php"}}) + .related_media_thumbnail_handler({{"//div[class='phimage']//img", "data-src", nullptr}}); + } + + static void add_spankbang_handlers(MediaGenericSearchPage *media_generic_search_page) { + media_generic_search_page->search_handler("https://spankbang.com/s/%s/%p/", 1) + .text_handler({{"//div[class='main_results']//div[class='video-item']//a[class='n']", "text", "href", "/video/"}}) + .thumbnail_handler({{"//div[class='main_results']//div[class='video-item']//img", "data-src", nullptr}}) + .related_media_text_handler({{"//div[class='right']//div[class='video-item']//a[class='n']", "text", "href", "/video/"}}) + .related_media_thumbnail_handler({{"//div[class='right']//div[class='video-item']//img", "data-src", nullptr}}); + } + void Program::load_plugin_by_name(std::vector<Tab> &tabs, const char *start_dir) { if(!plugin_name || plugin_name[0] == '\0') return; @@ -831,58 +846,48 @@ namespace QuickMedia { pipe_body->items.push_back(create_launcher_body_item("YouTube (audio only)", "youtube-audio", resources_root + "icons/yt_launcher.png")); tabs.push_back(Tab{std::move(pipe_body), std::make_unique<PipePage>(this, "Select plugin to launch"), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); } else if(strcmp(plugin_name, "manganelo") == 0) { - auto search_body = create_body(); - tabs.push_back(Tab{std::move(search_body), std::make_unique<ManganeloSearchPage>(this), create_search_bar("Search...", 400)}); + tabs.push_back(Tab{create_body(), std::make_unique<ManganeloSearchPage>(this), create_search_bar("Search...", 400)}); - auto history_body = create_body(); auto search_bar = create_search_bar("Search...", SEARCH_DELAY_FILTER); auto history_page = std::make_unique<HistoryPage>(this, tabs.front().page.get(), search_bar.get(), HistoryType::MANGA); - tabs.push_back(Tab{std::move(history_body), std::move(history_page), std::move(search_bar)}); + tabs.push_back(Tab{create_body(), std::move(history_page), std::move(search_bar)}); } else if(strcmp(plugin_name, "manganelos") == 0) { - auto search_body = create_body(); auto search_page = std::make_unique<MangaGenericSearchPage>(this, plugin_name, "http://manganelos.com/"); add_manganelos_handlers(search_page.get()); - tabs.push_back(Tab{std::move(search_body), std::move(search_page), create_search_bar("Search...", 400)}); + tabs.push_back(Tab{create_body(), std::move(search_page), create_search_bar("Search...", 400)}); - auto history_body = create_body(); auto search_bar = create_search_bar("Search...", SEARCH_DELAY_FILTER); auto history_page = std::make_unique<HistoryPage>(this, tabs.front().page.get(), search_bar.get(), HistoryType::MANGA); - tabs.push_back(Tab{std::move(history_body), std::move(history_page), std::move(search_bar)}); + tabs.push_back(Tab{create_body(), std::move(history_page), std::move(search_bar)}); } else if(strcmp(plugin_name, "mangatown") == 0) { - auto search_body = create_body(); - auto search_page = std::make_unique<MangaGenericSearchPage>(this, plugin_name, "https://www.mangatown.com"); + auto search_page = std::make_unique<MangaGenericSearchPage>(this, plugin_name, "https://www.mangatown.com/"); add_mangatown_handlers(search_page.get()); - tabs.push_back(Tab{std::move(search_body), std::move(search_page), create_search_bar("Search...", 400)}); + tabs.push_back(Tab{create_body(), std::move(search_page), create_search_bar("Search...", 400)}); - auto history_body = create_body(); auto search_bar = create_search_bar("Search...", SEARCH_DELAY_FILTER); auto history_page = std::make_unique<HistoryPage>(this, tabs.front().page.get(), search_bar.get(), HistoryType::MANGA); - tabs.push_back(Tab{std::move(history_body), std::move(history_page), std::move(search_bar)}); + tabs.push_back(Tab{create_body(), std::move(history_page), std::move(search_bar)}); } else if(strcmp(plugin_name, "mangakatana") == 0) { - auto search_body = create_body(); - auto search_page = std::make_unique<MangaGenericSearchPage>(this, plugin_name, "https://mangakatana.com", false); + auto search_page = std::make_unique<MangaGenericSearchPage>(this, plugin_name, "https://mangakatana.com/", false); add_mangakatana_handlers(search_page.get()); - tabs.push_back(Tab{std::move(search_body), std::move(search_page), create_search_bar("Search...", 400)}); + tabs.push_back(Tab{create_body(), std::move(search_page), create_search_bar("Search...", 400)}); - auto history_body = create_body(); auto search_bar = create_search_bar("Search...", SEARCH_DELAY_FILTER); auto history_page = std::make_unique<HistoryPage>(this, tabs.front().page.get(), search_bar.get(), HistoryType::MANGA); - tabs.push_back(Tab{std::move(history_body), std::move(history_page), std::move(search_bar)}); + tabs.push_back(Tab{create_body(), std::move(history_page), std::move(search_bar)}); } else if(strcmp(plugin_name, "mangadex") == 0) { - auto search_body = create_body(); - tabs.push_back(Tab{std::move(search_body), std::make_unique<MangadexSearchPage>(this), create_search_bar("Search...", 400)}); + tabs.push_back(Tab{create_body(), std::make_unique<MangadexSearchPage>(this), create_search_bar("Search...", 400)}); - auto history_body = create_body(); auto search_bar = create_search_bar("Search...", SEARCH_DELAY_FILTER); auto history_page = std::make_unique<HistoryPage>(this, tabs.front().page.get(), search_bar.get(), HistoryType::MANGA); - tabs.push_back(Tab{std::move(history_body), std::move(history_page), std::move(search_bar)}); + tabs.push_back(Tab{create_body(), std::move(history_page), std::move(search_bar)}); } else if(strcmp(plugin_name, "manga") == 0) { auto manganelo = std::make_unique<ManganeloSearchPage>(this); auto manganelos = std::make_unique<MangaGenericSearchPage>(this, "manganelos", "http://manganelos.com/"); add_manganelos_handlers(manganelos.get()); - auto mangatown = std::make_unique<MangaGenericSearchPage>(this, "mangatown", "https://www.mangatown.com"); + auto mangatown = std::make_unique<MangaGenericSearchPage>(this, "mangatown", "https://www.mangatown.com/"); add_mangatown_handlers(mangatown.get()); - auto mangakatana = std::make_unique<MangaGenericSearchPage>(this, "mangakatana", "https://mangakatana.com", false); + auto mangakatana = std::make_unique<MangaGenericSearchPage>(this, "mangakatana", "https://mangakatana.com/", false); add_mangakatana_handlers(mangakatana.get()); std::vector<MangaPlugin> pages; @@ -918,8 +923,7 @@ namespace QuickMedia { PipePage::load_body_items_from_stdin(pipe_body->items); tabs.push_back(Tab{std::move(pipe_body), std::make_unique<PipePage>(this), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); } else if(strcmp(plugin_name, "youtube") == 0) { - auto search_body = create_body(); - tabs.push_back(Tab{std::move(search_body), std::make_unique<YoutubeSearchPage>(this), create_search_bar("Search...", 350)}); + tabs.push_back(Tab{create_body(), std::make_unique<YoutubeSearchPage>(this), create_search_bar("Search...", 350)}); auto history_body = create_body(); auto history_search_bar = create_search_bar("Search...", SEARCH_DELAY_FILTER); @@ -931,23 +935,22 @@ 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<YoutubeSubscriptionsPage>(this), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); } else if(strcmp(plugin_name, "pornhub") == 0) { - auto search_body = create_body(); - tabs.push_back(Tab{std::move(search_body), std::make_unique<PornhubSearchPage>(this), create_search_bar("Search...", 500)}); + auto search_page = std::make_unique<MediaGenericSearchPage>(this, "https://www.pornhub.com/", sf::Vector2i(320, 180)); + 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_body = create_body(); - tabs.push_back(Tab{std::move(search_body), std::make_unique<SpankbangSearchPage>(this), create_search_bar("Search...", 500)}); + auto search_page = std::make_unique<MediaGenericSearchPage>(this, "https://spankbang.com/", sf::Vector2i(500/2, 281/2)); + 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, "spotify") == 0) { - auto search_body = create_body(); - tabs.push_back(Tab{std::move(search_body), std::make_unique<SpotifyPodcastSearchPage>(this), create_search_bar("Search...", 350)}); + tabs.push_back(Tab{create_body(), std::make_unique<SpotifyPodcastSearchPage>(this), create_search_bar("Search...", 350)}); no_video = true; } else if(strcmp(plugin_name, "soundcloud") == 0) { - auto search_body = create_body(); - tabs.push_back(Tab{std::move(search_body), std::make_unique<SoundcloudSearchPage>(this), create_search_bar("Search...", 500)}); + tabs.push_back(Tab{create_body(), std::make_unique<SoundcloudSearchPage>(this), create_search_bar("Search...", 500)}); no_video = true; } else if(strcmp(plugin_name, "mastodon") == 0 || strcmp(plugin_name, "pleroma") == 0) { auto pleroma = std::make_shared<Pleroma>(); - auto search_body = create_body(); - tabs.push_back(Tab{std::move(search_body), std::make_unique<PleromaHomePage>(this, pleroma), create_search_bar("Search...", 350)}); + tabs.push_back(Tab{create_body(), std::make_unique<PleromaHomePage>(this, pleroma), create_search_bar("Search...", 350)}); } else if(strcmp(plugin_name, "matrix") == 0) { assert(!matrix); matrix = new Matrix(); diff --git a/src/plugins/MangaGeneric.cpp b/src/plugins/MangaGeneric.cpp index bf8a4c4..b3326fc 100644 --- a/src/plugins/MangaGeneric.cpp +++ b/src/plugins/MangaGeneric.cpp @@ -57,13 +57,17 @@ namespace QuickMedia { if(search_userdata->field2) { const char *field2_value = html_attr_or_inner_text(node, search_userdata->field2); if(field1_value && field2_value && (!search_userdata->field2_contains || strstr(field2_value, search_userdata->field2_contains))) { - auto item = BodyItem::create(strip(field1_value)); + std::string field1_fixed = strip(field1_value); + html_unescape_sequences(field1_fixed); + auto item = BodyItem::create(std::move(field1_fixed)); item->url = strip(field2_value); search_userdata->body_items->push_back(std::move(item)); } } else { if(field1_value) { - auto item = BodyItem::create(strip(field1_value)); + std::string field1_fixed = strip(field1_value); + html_unescape_sequences(field1_fixed); + auto item = BodyItem::create(std::move(field1_fixed)); search_userdata->body_items->push_back(std::move(item)); } } @@ -163,9 +167,10 @@ namespace QuickMedia { goto cleanup; for(const TextQuery &text_query : text_queries) { - if(!search_query.search_template || !text_query.html_query || !text_query.title_field) { + if(!text_query.html_query || !text_query.title_field) { assert(false); - return PluginResult::ERR; + result = -1; + goto cleanup; } BodyItems new_result_items; diff --git a/src/plugins/MediaGeneric.cpp b/src/plugins/MediaGeneric.cpp new file mode 100644 index 0000000..c911e6b --- /dev/null +++ b/src/plugins/MediaGeneric.cpp @@ -0,0 +1,172 @@ +#include "../../plugins/MediaGeneric.hpp" +#include "../../include/StringUtils.hpp" +#include <quickmedia/HtmlSearch.h> + +namespace QuickMedia { + using HtmlPathCallback = std::function<void(QuickMediaHtmlNode*)>; + static int quickmedia_html_find_nodes_xpath(QuickMediaHtmlSearch *self, const char *xpath, HtmlPathCallback callback) { + return quickmedia_html_find_nodes_xpath(self, xpath, [](QuickMediaHtmlNode *node, void *userdata) { + HtmlPathCallback *callback = (HtmlPathCallback*)userdata; + (*callback)(node); + }, &callback); + } + + static const char* html_attr_or_inner_text(QuickMediaHtmlNode *node, const char *field_name) { + if(strcmp(field_name, "text") == 0) + return quickmedia_html_node_get_text(node); + else + 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<MediaTextQuery> &text_queries, const std::vector<MediaThumbnailQuery> &thumbnail_queries, BodyItems &result_items) { + std::vector<CommandArg> args; + if(!website_url.empty()) + args.push_back({ "-H", "referer: " + website_url }); + + std::string website_data; + if(download_to_string(url, website_data, args, true) != DownloadResult::OK) + return PluginResult::NET_ERR; + + if(website_data.empty()) + return PluginResult::OK; + + QuickMediaHtmlSearch html_search; + int result = quickmedia_html_search_init(&html_search, website_data.c_str()); + if(result != 0) + goto cleanup; + + for(const MediaTextQuery &text_query : text_queries) { + if(!text_query.html_query || !text_query.title_field) { + assert(false); + result = -1; + goto cleanup; + } + + result = quickmedia_html_find_nodes_xpath(&html_search, text_query.html_query, [&text_query, &result_items](QuickMediaHtmlNode *node) { + const char *title_value = html_attr_or_inner_text(node, text_query.title_field); + const char *url_value = html_attr_or_inner_text(node, text_query.url_field); + if(title_value && url_value && (!text_query.url_contains || strstr(url_value, text_query.url_contains))) { + std::string field1_fixed = strip(title_value); + html_unescape_sequences(field1_fixed); + auto item = BodyItem::create(std::move(field1_fixed)); + item->url = strip(url_value); + result_items.push_back(std::move(item)); + } + }); + if(result != 0) + goto cleanup; + } + + for(const MediaThumbnailQuery &thumbnail_query : thumbnail_queries) { + assert(thumbnail_query.html_query && thumbnail_query.field_name); + if(thumbnail_query.html_query && thumbnail_query.field_name) { + size_t index = 0; + result = quickmedia_html_find_nodes_xpath(&html_search, thumbnail_query.html_query, [&thumbnail_query, &result_items, &index](QuickMediaHtmlNode *node) { + const char *field_value = html_attr_or_inner_text(node, thumbnail_query.field_name); + if(index < result_items.size() && field_value && (!thumbnail_query.field_contains || strstr(field_value, thumbnail_query.field_contains))) { + result_items[index]->thumbnail_url = strip(field_value); + ++index; + } + }); + if(result != 0) + goto cleanup; + } + } + + 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); + } + + cleanup: + quickmedia_html_search_deinit(&html_search); + if(result == 0) { + return PluginResult::OK; + } else { + result_items.clear(); + return PluginResult::ERR; + } + } + + MediaGenericSearchPage::MediaGenericSearchPage(Program *program, const char *website_url, sf::Vector2i thumbnail_max_size) : + Page(program), website_url(website_url ? website_url : ""), thumbnail_max_size(thumbnail_max_size) + { + if(!this->website_url.empty()) { + if(this->website_url.back() != '/') + this->website_url.push_back('/'); + } + } + + SearchResult MediaGenericSearchPage::search(const std::string &str, BodyItems &result_items) { + return plugin_result_to_search_result(get_page(str, 0, result_items)); + } + + PluginResult MediaGenericSearchPage::get_page(const std::string &str, int page, BodyItems &result_items) { + 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); + } + + PluginResult MediaGenericSearchPage::submit(const std::string&, const std::string &url, std::vector<Tab> &result_tabs) { + result_tabs.push_back(Tab{nullptr, std::make_unique<MediaGenericVideoPage>(program, this, url), nullptr}); + return PluginResult::OK; + } + + 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); + } + + MediaGenericSearchPage& MediaGenericSearchPage::search_handler(const char *search_template, int page_start) { + search_query.search_template = search_template; + search_query.page_start = page_start; + return *this; + } + + MediaGenericSearchPage& MediaGenericSearchPage::text_handler(std::vector<MediaTextQuery> queries) { + text_queries = std::move(queries); + return *this; + } + + MediaGenericSearchPage& MediaGenericSearchPage::thumbnail_handler(std::vector<MediaThumbnailQuery> queries) { + thumbnail_queries = std::move(queries); + return *this; + } + + MediaGenericSearchPage& MediaGenericSearchPage::related_media_text_handler(std::vector<MediaTextQuery> queries) { + related_media_text_queries = std::move(queries); + return *this; + } + + MediaGenericSearchPage& MediaGenericSearchPage::related_media_thumbnail_handler(std::vector<MediaThumbnailQuery> queries) { + related_media_thumbnail_queries = std::move(queries); + return *this; + } + + PluginResult MediaGenericRelatedPage::submit(const std::string&, const std::string &url, std::vector<Tab> &result_tabs) { + result_tabs.push_back(Tab{nullptr, std::make_unique<MediaGenericVideoPage>(program, search_page, url), nullptr}); + return PluginResult::OK; + } + + BodyItems MediaGenericVideoPage::get_related_media(const std::string &url, std::string&) { + BodyItems result_items; + search_page->get_related_media(url, result_items); + return result_items; + } + + std::unique_ptr<Page> MediaGenericVideoPage::create_search_page(Program*, int &search_delay) { + search_delay = 500; // TODO: Make configurable? + return std::make_unique<MediaGenericSearchPage>(*search_page); + } + + std::unique_ptr<RelatedVideosPage> MediaGenericVideoPage::create_related_videos_page(Program *program, const std::string&, const std::string&) { + return std::make_unique<MediaGenericRelatedPage>(program, search_page); + } +}
\ No newline at end of file diff --git a/src/plugins/Pornhub.cpp b/src/plugins/Pornhub.cpp deleted file mode 100644 index 1aa0851..0000000 --- a/src/plugins/Pornhub.cpp +++ /dev/null @@ -1,164 +0,0 @@ -#include "../../plugins/Pornhub.hpp" -#include "../../include/StringUtils.hpp" -#include "../../include/NetUtils.hpp" -extern "C" { -#include <HtmlParser.h> -} -#include <string.h> - -namespace { - // TODO: Optimize by using HtmlStringView instead of std::string - struct HtmlElement { - std::string tag_name; - std::map<std::string, std::string> attributes; - std::vector<HtmlElement*> children; - HtmlElement *parent = nullptr; // ref - }; - - struct HtmlParseUserdata { - HtmlElement *current_html_element; - }; -} - -namespace QuickMedia { - static bool begins_with(const char *str, const char *begin_with) { - return strncmp(str, begin_with, strlen(begin_with)) == 0; - } - - static void html_cleanup(HtmlElement *html_element_root) { - for(HtmlElement *child_html_element : html_element_root->children) { - html_cleanup(child_html_element); - } - delete html_element_root; - } - - static const std::string& html_get_attribute_or(HtmlElement *html_element, const std::string &attr_key, const std::string &default_value) { - auto it = html_element->attributes.find(attr_key); - if(it != html_element->attributes.end()) - return it->second; - else - return default_value; - } - - static void html_page_callback(HtmlParser *html_parser, HtmlParseType parse_type, void *userdata) { - HtmlParseUserdata *parse_userdata = (HtmlParseUserdata*)userdata; - if(parse_type == HTML_PARSE_TAG_START) { - auto new_html_element = new HtmlElement(); - new_html_element->tag_name.assign(html_parser->tag_name.data, html_parser->tag_name.size); - new_html_element->parent = parse_userdata->current_html_element; - - parse_userdata->current_html_element->children.push_back(new_html_element); - parse_userdata->current_html_element = new_html_element; - } else if(parse_type == HTML_PARSE_TAG_END) { - if(parse_userdata->current_html_element->parent) - parse_userdata->current_html_element = parse_userdata->current_html_element->parent; - } else if(parse_type == HTML_PARSE_ATTRIBUTE) { - std::string attr_key(html_parser->attribute_key.data, html_parser->attribute_key.size); - std::string attr_value(html_parser->attribute_value.data, html_parser->attribute_value.size); - parse_userdata->current_html_element->attributes.insert(std::make_pair(std::move(attr_key), std::move(attr_value))); - } - } - - static HtmlElement* html_parse(const char *source, size_t size) { - HtmlElement *html_element_root = new HtmlElement(); - HtmlParseUserdata parse_userdata; - parse_userdata.current_html_element = html_element_root; - html_parser_parse(source, size, html_page_callback, &parse_userdata); - return html_element_root; - } - - using HtmlFindTagsCallback = std::function<void(HtmlElement *html_element)>; - static void html_find_tags_with_class(HtmlElement *html_element, const std::string &tag_name, const std::string &class_value, const HtmlFindTagsCallback &callback) { - if(html_element->tag_name == tag_name) { - if(html_get_attribute_or(html_element, "class", "") == class_value) - callback(html_element); - } - for(HtmlElement *child_html_element : html_element->children) { - html_find_tags_with_class(child_html_element, tag_name, class_value, callback); - } - } - - static void html_find_tags(HtmlElement *html_element, const std::string &tag_name, const HtmlFindTagsCallback &callback) { - if(html_element->tag_name == tag_name) - callback(html_element); - for(HtmlElement *child_html_element : html_element->children) { - html_find_tags(child_html_element, tag_name, callback); - } - } - - static SearchResult get_videos_in_page(const std::string &url, BodyItems &result_items) { - std::string website_data; - if(download_to_string(url, website_data, {}) != DownloadResult::OK) - return SearchResult::NET_ERR; - - HtmlElement *html_root = html_parse(website_data.data(), website_data.size()); - html_find_tags_with_class(html_root, "div", "phimage", [&result_items](HtmlElement *html_element) { - auto it = html_element->attributes.find("data-entrycode"); - if(it == html_element->attributes.end() || it->second != "VidPg-premVid-videoPage") { - html_find_tags(html_element, "a", [&result_items](HtmlElement *html_element) { - const std::string &href = html_get_attribute_or(html_element, "href", ""); - const std::string &title = html_get_attribute_or(html_element, "title", ""); - if(!href.empty() && !title.empty() && begins_with(href.c_str(), "/view_video.php?viewkey")) { - std::string title_fixed = strip(title); - html_unescape_sequences(title_fixed); - auto item = BodyItem::create(std::move(title_fixed)); - item->url = std::string("https://www.pornhub.com") + href; - item->thumbnail_size = sf::Vector2i(192, 108); - result_items.push_back(std::move(item)); - - html_find_tags(html_element, "img", [&result_items](HtmlElement *html_element) { - const std::string &src = html_get_attribute_or(html_element, "data-src", ""); - if(src.find("phncdn.com/videos") != std::string::npos) - result_items.back()->thumbnail_url = src; - }); - } - }); - } - }); - html_cleanup(html_root); - - // Attempt to skip promoted videos (that are not related to the search term) - if(result_items.size() >= 4) - result_items.erase(result_items.begin(), result_items.begin() + 4); - - return SearchResult::OK; - } - - SearchResult PornhubSearchPage::search(const std::string &str, BodyItems &result_items) { - std::string url = "https://www.pornhub.com/video/search?search="; - url += url_param_encode(str); - return get_videos_in_page(url, result_items); - } - - PluginResult PornhubSearchPage::get_page(const std::string &str, int page, BodyItems &result_items) { - std::string url = "https://www.pornhub.com/video/search?search="; - url += url_param_encode(str); - url += "&page=" + std::to_string(1 + page); - return search_result_to_plugin_result(get_videos_in_page(url, result_items)); - } - - PluginResult PornhubSearchPage::submit(const std::string&, const std::string &url, std::vector<Tab> &result_tabs) { - result_tabs.push_back(Tab{nullptr, std::make_unique<PornhubVideoPage>(program, url), nullptr}); - return PluginResult::OK; - } - - PluginResult PornhubRelatedVideosPage::submit(const std::string&, const std::string &url, std::vector<Tab> &result_tabs) { - result_tabs.push_back(Tab{nullptr, std::make_unique<PornhubVideoPage>(program, url), nullptr}); - return PluginResult::OK; - } - - BodyItems PornhubVideoPage::get_related_media(const std::string &url, std::string&) { - BodyItems result_items; - get_videos_in_page(url, result_items); - return result_items; - } - - std::unique_ptr<Page> PornhubVideoPage::create_search_page(Program *program, int &search_delay) { - search_delay = 500; - return std::make_unique<PornhubSearchPage>(program); - } - - std::unique_ptr<RelatedVideosPage> PornhubVideoPage::create_related_videos_page(Program *program, const std::string&, const std::string&) { - return std::make_unique<PornhubRelatedVideosPage>(program); - } -}
\ No newline at end of file diff --git a/src/plugins/Spankbang.cpp b/src/plugins/Spankbang.cpp deleted file mode 100644 index ac0e371..0000000 --- a/src/plugins/Spankbang.cpp +++ /dev/null @@ -1,157 +0,0 @@ -#include "../../plugins/Spankbang.hpp" -#include "../../include/StringUtils.hpp" -#include "../../include/NetUtils.hpp" -extern "C" { -#include <HtmlParser.h> -} -#include <string.h> - -namespace { - // TODO: Optimize by using HtmlStringView instead of std::string - struct HtmlElement { - std::string tag_name; - std::map<std::string, std::string> attributes; - std::vector<HtmlElement*> children; - HtmlElement *parent = nullptr; // ref - }; - - struct HtmlParseUserdata { - HtmlElement *current_html_element; - }; -} - -namespace QuickMedia { - static void html_cleanup(HtmlElement *html_element_root) { - for(HtmlElement *child_html_element : html_element_root->children) { - html_cleanup(child_html_element); - } - delete html_element_root; - } - - static const std::string& html_get_attribute_or(HtmlElement *html_element, const std::string &attr_key, const std::string &default_value) { - auto it = html_element->attributes.find(attr_key); - if(it != html_element->attributes.end()) - return it->second; - else - return default_value; - } - - static void html_page_callback(HtmlParser *html_parser, HtmlParseType parse_type, void *userdata) { - HtmlParseUserdata *parse_userdata = (HtmlParseUserdata*)userdata; - if(parse_type == HTML_PARSE_TAG_START) { - auto new_html_element = new HtmlElement(); - new_html_element->tag_name.assign(html_parser->tag_name.data, html_parser->tag_name.size); - new_html_element->parent = parse_userdata->current_html_element; - - parse_userdata->current_html_element->children.push_back(new_html_element); - parse_userdata->current_html_element = new_html_element; - } else if(parse_type == HTML_PARSE_TAG_END) { - if(parse_userdata->current_html_element->parent) - parse_userdata->current_html_element = parse_userdata->current_html_element->parent; - } else if(parse_type == HTML_PARSE_ATTRIBUTE) { - std::string attr_key(html_parser->attribute_key.data, html_parser->attribute_key.size); - std::string attr_value(html_parser->attribute_value.data, html_parser->attribute_value.size); - parse_userdata->current_html_element->attributes.insert(std::make_pair(std::move(attr_key), std::move(attr_value))); - } - } - - static HtmlElement* html_parse(const char *source, size_t size) { - HtmlElement *html_element_root = new HtmlElement(); - HtmlParseUserdata parse_userdata; - parse_userdata.current_html_element = html_element_root; - html_parser_parse(source, size, html_page_callback, &parse_userdata); - return html_element_root; - } - - using HtmlFindTagsCallback = std::function<void(HtmlElement *html_element)>; - static void html_find_tags_with_class(HtmlElement *html_element, const std::string &tag_name, const std::string &class_value, const HtmlFindTagsCallback &callback) { - if(html_element->tag_name == tag_name) { - if(html_get_attribute_or(html_element, "class", "") == class_value) - callback(html_element); - } - for(HtmlElement *child_html_element : html_element->children) { - html_find_tags_with_class(child_html_element, tag_name, class_value, callback); - } - } - - static void html_find_tags(HtmlElement *html_element, const std::string &tag_name, const HtmlFindTagsCallback &callback) { - if(html_element->tag_name == tag_name) - callback(html_element); - for(HtmlElement *child_html_element : html_element->children) { - html_find_tags(child_html_element, tag_name, callback); - } - } - - static SearchResult get_videos_in_page(const std::string &url, BodyItems &result_items) { - std::string website_data; - if(download_to_string(url, website_data, {}) != DownloadResult::OK) - return SearchResult::NET_ERR; - - HtmlElement *html_root = html_parse(website_data.data(), website_data.size()); - html_find_tags_with_class(html_root, "a", "thumb ", [&result_items](HtmlElement *html_element) { - const std::string &href = html_get_attribute_or(html_element, "href", ""); - auto item = BodyItem::create(""); - item->url = std::string("https://spankbang.com") + href; - result_items.push_back(std::move(item)); - html_find_tags(html_element, "img", [&result_items](HtmlElement *html_element) { - const std::string &title = html_get_attribute_or(html_element, "alt", ""); - if(!title.empty()) { - std::string title_fixed = strip(title); - html_unescape_sequences(title_fixed); - //result_items.back()->title_text = title_fixed; - result_items.back()->set_title(title_fixed); - result_items.back()->thumbnail_size = sf::Vector2i(800, 450); - - const std::string &src = html_get_attribute_or(html_element, "data-src", ""); - if(!src.empty()) - result_items.back()->thumbnail_url = src; - } - }); - }); - html_cleanup(html_root); - - // Attempt to skip promoted videos (that are not related to the search term) - //if(result_items.size() >= 4) - // result_items.erase(result_items.begin(), result_items.begin() + 4); - - return SearchResult::OK; - } - - SearchResult SpankbangSearchPage::search(const std::string &str, BodyItems &result_items) { - std::string url = "https://spankbang.com/s/"; - url += url_param_encode(str); - return get_videos_in_page(url, result_items); - } - - PluginResult SpankbangSearchPage::get_page(const std::string &str, int page, BodyItems &result_items) { - std::string url = "https://spankbang.com/s/"; - url += url_param_encode(str); - url += "/" + std::to_string(1 + page); - return search_result_to_plugin_result(get_videos_in_page(url, result_items)); - } - - PluginResult SpankbangSearchPage::submit(const std::string&, const std::string &url, std::vector<Tab> &result_tabs) { - result_tabs.push_back(Tab{nullptr, std::make_unique<SpankbangVideoPage>(program, url), nullptr}); - return PluginResult::OK; - } - - PluginResult SpankbangRelatedVideosPage::submit(const std::string&, const std::string &url, std::vector<Tab> &result_tabs) { - result_tabs.push_back(Tab{nullptr, std::make_unique<SpankbangVideoPage>(program, url), nullptr}); - return PluginResult::OK; - } - - BodyItems SpankbangVideoPage::get_related_media(const std::string &url, std::string&) { - BodyItems result_items; - get_videos_in_page(url, result_items); - return result_items; - } - - std::unique_ptr<Page> SpankbangVideoPage::create_search_page(Program *program, int &search_delay) { - search_delay = 500; - return std::make_unique<SpankbangSearchPage>(program); - } - - std::unique_ptr<RelatedVideosPage> SpankbangVideoPage::create_related_videos_page(Program *program, const std::string&, const std::string&) { - return std::make_unique<SpankbangRelatedVideosPage>(program); - } -}
\ No newline at end of file |