aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--images/spankbang_logo.pngbin0 -> 8153 bytes
-rw-r--r--plugins/Spankbang.hpp36
-rw-r--r--src/QuickMedia.cpp7
-rw-r--r--src/plugins/Spankbang.cpp164
4 files changed, 206 insertions, 1 deletions
diff --git a/images/spankbang_logo.png b/images/spankbang_logo.png
new file mode 100644
index 0000000..e02f5bf
--- /dev/null
+++ b/images/spankbang_logo.png
Binary files 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<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<LazyFetchPage> 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<const char*, const char*> 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 <plugin> [--no-video] [--use-system-mpv-config] [--dir <directory>] [-e <window>]\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<PornhubSearchPage>(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<SpankbangSearchPage>(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<SpotifyPodcastSearchPage>(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 <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(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<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