diff options
Diffstat (limited to 'src/plugins')
-rw-r--r-- | src/plugins/Spankbang.cpp | 164 |
1 files changed, 164 insertions, 0 deletions
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 |