aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--icons/xhamster_launcher.pngbin0 -> 2731 bytes
-rw-r--r--icons/xvideos_launcher.pngbin0 -> 2843 bytes
-rw-r--r--images/xhamster_logo.pngbin0 -> 4768 bytes
-rw-r--r--images/xvideos_logo.pngbin0 -> 2843 bytes
-rw-r--r--plugins/MediaGeneric.hpp11
-rw-r--r--src/QuickMedia.cpp100
-rw-r--r--src/plugins/MangaGeneric.cpp38
-rw-r--r--src/plugins/MediaGeneric.cpp53
8 files changed, 163 insertions, 39 deletions
diff --git a/icons/xhamster_launcher.png b/icons/xhamster_launcher.png
new file mode 100644
index 0000000..4c6b8a4
--- /dev/null
+++ b/icons/xhamster_launcher.png
Binary files differ
diff --git a/icons/xvideos_launcher.png b/icons/xvideos_launcher.png
new file mode 100644
index 0000000..d279cab
--- /dev/null
+++ b/icons/xvideos_launcher.png
Binary files differ
diff --git a/images/xhamster_logo.png b/images/xhamster_logo.png
new file mode 100644
index 0000000..f015f7d
--- /dev/null
+++ b/images/xhamster_logo.png
Binary files differ
diff --git a/images/xvideos_logo.png b/images/xvideos_logo.png
new file mode 100644
index 0000000..d279cab
--- /dev/null
+++ b/images/xvideos_logo.png
Binary files 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<std::vector<MediaRelatedItem>(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<MediaTextQuery> queries);
// This is optional.
MediaGenericSearchPage& related_media_thumbnail_handler(std::vector<MediaThumbnailQuery> 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<MediaThumbnailQuery> thumbnail_queries;
std::vector<MediaTextQuery> related_media_text_queries;
std::vector<MediaThumbnailQuery> 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<const char*, const char*> 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 <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, 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<MediaRelatedItem> 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::CharReader> 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<Tab> &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<YoutubeSubscriptionsPage>(this), create_search_bar("Search...", SEARCH_DELAY_FILTER)});
} else if(strcmp(plugin_name, "pornhub") == 0) {
- auto search_page = std::make_unique<MediaGenericSearchPage>(this, "https://www.pornhub.com/", sf::Vector2i(320, 180));
+ auto search_page = std::make_unique<MediaGenericSearchPage>(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<MediaGenericSearchPage>(this, "https://spankbang.com/", sf::Vector2i(500/2, 281/2));
+ auto search_page = std::make_unique<MediaGenericSearchPage>(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<MediaGenericSearchPage>(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<MediaGenericSearchPage>(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<SpotifyPodcastSearchPage>(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<MediaTextQuery> &text_queries, const std::vector<MediaThumbnailQuery> &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<MediaTextQuery> &text_queries, const std::vector<MediaThumbnailQuery> &thumbnail_queries, MediaRelatedCustomHandler *custom_handler, BodyItems &result_items) {
std::vector<CommandArg> 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<MediaRelatedItem> 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<Tab> &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<MediaTextQuery> queries) {
related_media_text_queries = std::move(queries);
+ related_custom_handler = nullptr;
return *this;
}
MediaGenericSearchPage& MediaGenericSearchPage::related_media_thumbnail_handler(std::vector<MediaThumbnailQuery> 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;
}