From 20a437763e56e5429ebd7f38940c4107418e3dee Mon Sep 17 00:00:00 2001 From: dec05eba Date: Fri, 9 Apr 2021 03:15:17 +0200 Subject: Add spankbang. Commit by coomest --- images/spankbang_logo.png | Bin 0 -> 8153 bytes plugins/Spankbang.hpp | 36 ++++++++++ src/QuickMedia.cpp | 7 +- src/plugins/Spankbang.cpp | 164 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 206 insertions(+), 1 deletion(-) create mode 100644 images/spankbang_logo.png create mode 100644 plugins/Spankbang.hpp create mode 100644 src/plugins/Spankbang.cpp diff --git a/images/spankbang_logo.png b/images/spankbang_logo.png new file mode 100644 index 0000000..e02f5bf Binary files /dev/null and b/images/spankbang_logo.png differ diff --git a/plugins/Spankbang.hpp b/plugins/Spankbang.hpp new file mode 100644 index 0000000..95b8820 --- /dev/null +++ b/plugins/Spankbang.hpp @@ -0,0 +1,36 @@ +#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 &result_tabs) override; + }; + + class SpankbangRelatedVideosPage : public RelatedVideosPage { + public: + SpankbangRelatedVideosPage(Program *program) : RelatedVideosPage(program) {} + PluginResult submit(const std::string&, const std::string&, std::vector &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 create_search_page(Program *program, int &search_delay) override; + std::unique_ptr create_related_videos_page(Program *program, const std::string &video_url, const std::string &video_title) override; + std::unique_ptr 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 e092584..4598742 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -4,6 +4,7 @@ #include "../plugins/Mangadex.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" @@ -65,6 +66,7 @@ static const std::pair valid_plugins[] = { std::make_pair("spotify", "spotify_logo.png"), std::make_pair("soundcloud", "soundcloud_logo.png"), std::make_pair("pornhub", "pornhub_logo.png"), + std::make_pair("spankbang", "spankbang_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"), @@ -417,7 +419,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, manganelo, mangatown, mangadex, pornhub, youtube, spotify, soundcloud, nyaa.si, matrix, file-manager or stdin\n"); + fprintf(stderr, " plugin The plugin to use. Should be either launcher, 4chan, manganelo, mangatown, mangadex, pornhub, spankbang, 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"); @@ -769,6 +771,9 @@ namespace QuickMedia { } else if(strcmp(plugin_name, "pornhub") == 0) { auto search_body = create_body(); tabs.push_back(Tab{std::move(search_body), std::make_unique(this), 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(this), 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(this), create_search_bar("Search...", 250)}); diff --git a/src/plugins/Spankbang.cpp b/src/plugins/Spankbang.cpp new file mode 100644 index 0000000..cc35d39 --- /dev/null +++ b/src/plugins/Spankbang.cpp @@ -0,0 +1,164 @@ +#include "../../plugins/Spankbang.hpp" +#include "../../include/StringUtils.hpp" +#include "../../include/NetUtils.hpp" +extern "C" { +#include +} +#include + +namespace { + // TODO: Optimize by using HtmlStringView instead of std::string + struct HtmlElement { + std::string tag_name; + std::map attributes; + std::vector 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(char *source, size_t size) { + HtmlElement *html_element_root = new HtmlElement(); + HtmlParseUserdata parse_userdata; + parse_userdata.current_html_element = html_element_root; + HtmlParser html_parser; + html_parser_init(&html_parser, source, size, html_page_callback, &parse_userdata); + html_parser_parse(&html_parser); + html_parser_deinit(&html_parser); + return html_element_root; + } + + using HtmlFindTagsCallback = std::function; + 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 &result_tabs) { + result_tabs.push_back(Tab{nullptr, std::make_unique(program, url), nullptr}); + return PluginResult::OK; + } + + PluginResult SpankbangRelatedVideosPage::submit(const std::string&, const std::string &url, std::vector &result_tabs) { + result_tabs.push_back(Tab{nullptr, std::make_unique(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 SpankbangVideoPage::create_search_page(Program *program, int &search_delay) { + search_delay = 500; + return std::make_unique(program); + } + + std::unique_ptr SpankbangVideoPage::create_related_videos_page(Program *program, const std::string&, const std::string&) { + return std::make_unique(program); + } +} \ No newline at end of file -- cgit v1.2.3