diff options
-rw-r--r-- | README.md | 4 | ||||
-rw-r--r-- | icons/manganelos_launcher.png | bin | 0 -> 2482 bytes | |||
-rw-r--r-- | icons/pornhub_launcher.png | bin | 11121 -> 0 bytes | |||
-rw-r--r-- | images/manganelos_logo.png | bin | 0 -> 12337 bytes | |||
-rw-r--r-- | plugins/Manganelos.hpp | 38 | ||||
-rw-r--r-- | src/QuickMedia.cpp | 25 | ||||
-rw-r--r-- | src/plugins/Manganelos.cpp | 180 |
7 files changed, 239 insertions, 8 deletions
@@ -1,6 +1,6 @@ # QuickMedia A dmenu-inspired native client for web services. -Currently supported web services: `youtube`, `spotify (podcasts)`, `soundcloud`, `nyaa.si`, `manganelo`, `mangatown`, `mangadex`, `4chan`, `matrix` and _others_.\ +Currently supported web services: `youtube`, `spotify (podcasts)`, `soundcloud`, `nyaa.si`, `manganelo`, `manganelos`, `mangatown`, `mangadex`, `4chan`, `matrix` and _others_.\ **Note:** file-manager is early in progress.\ Config data, including manga progress is stored under `$HOME/.config/quickmedia`.\ Cache is stored under `$HOME/.cache/quickmedia`. @@ -8,7 +8,7 @@ Cache is stored under `$HOME/.cache/quickmedia`. ``` usage: quickmedia <plugin> [--use-system-mpv-config] [--dir <directory>] [-e <window>] OPTIONS: - plugin The plugin to use. Should be either launcher, 4chan, manganelo, mangatown, mangadex, youtube, spotify, soundcloud, nyaa.si, matrix, file-manager or stdin + plugin The plugin to use. Should be either launcher, 4chan, manganelo, manganelos, mangatown, mangadex, youtube, spotify, soundcloud, nyaa.si, matrix, file-manager or stdin --no-video Only play audio when playing a video. Disabled by default --use-system-mpv-config Use system mpv config instead of no config. Disabled by default --upscale-images Upscale low-resolution manga pages using waifu2x-ncnn-vulkan. Disabled by default diff --git a/icons/manganelos_launcher.png b/icons/manganelos_launcher.png Binary files differnew file mode 100644 index 0000000..0b07a22 --- /dev/null +++ b/icons/manganelos_launcher.png diff --git a/icons/pornhub_launcher.png b/icons/pornhub_launcher.png Binary files differdeleted file mode 100644 index 0ae5a76..0000000 --- a/icons/pornhub_launcher.png +++ /dev/null diff --git a/images/manganelos_logo.png b/images/manganelos_logo.png Binary files differnew file mode 100644 index 0000000..783501b --- /dev/null +++ b/images/manganelos_logo.png diff --git a/plugins/Manganelos.hpp b/plugins/Manganelos.hpp new file mode 100644 index 0000000..ede2712 --- /dev/null +++ b/plugins/Manganelos.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include "Manga.hpp" +#include <functional> + +namespace QuickMedia { + class ManganelosSearchPage : public Page { + public: + ManganelosSearchPage(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; + sf::Vector2i get_thumbnail_max_size() override { return sf::Vector2i(101, 141); }; + }; + + class ManganelosChaptersPage : public MangaChaptersPage { + public: + ManganelosChaptersPage(Program *program, std::string manga_name, std::string manga_url) : MangaChaptersPage(program, std::move(manga_name), std::move(manga_url)) {} + PluginResult submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) override; + protected: + bool extract_id_from_url(const std::string &url, std::string &manga_id) const override; + const char* get_service_name() const override { return "manganelos"; } + }; + + class ManganelosImagesPage : public MangaImagesPage { + public: + ManganelosImagesPage(Program *program, std::string manga_name, std::string chapter_name, std::string url) : MangaImagesPage(program, std::move(manga_name), std::move(chapter_name), std::move(url)) {} + + ImageResult get_number_of_images(int &num_images) override; + ImageResult for_each_page_in_chapter(PageCallback callback) override; + + const char* get_service_name() const override { return "manganelos"; } + private: + ImageResult get_image_urls_for_chapter(const std::string &url); + }; +}
\ No newline at end of file diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index c478876..e49cb74 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -1,5 +1,6 @@ #include "../include/QuickMedia.hpp" #include "../plugins/Manganelo.hpp" +#include "../plugins/Manganelos.hpp" #include "../plugins/Mangatown.hpp" #include "../plugins/Mangadex.hpp" #include "../plugins/Youtube.hpp" @@ -60,6 +61,7 @@ static const sf::Vector2i AVATAR_THUMBNAIL_SIZE(std::floor(32 * QuickMedia::get_ static const std::pair<const char*, const char*> valid_plugins[] = { std::make_pair("launcher", nullptr), std::make_pair("manganelo", "manganelo_logo.png"), + std::make_pair("manganelos", "manganelos_logo.png"), std::make_pair("mangatown", "mangatown_logo.png"), std::make_pair("mangadex", "mangadex_logo.png"), std::make_pair("youtube", "yt_logo_rgb_dark_small.png"), @@ -419,7 +421,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, spankbang, youtube, spotify, soundcloud, nyaa.si, matrix, file-manager or stdin\n"); + fprintf(stderr, " plugin The plugin to use. Should be either launcher, 4chan, manganelo, manganelos, 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"); @@ -434,7 +436,7 @@ namespace QuickMedia { } static bool is_manga_plugin(const char *plugin_name) { - return strcmp(plugin_name, "manganelo") == 0 || strcmp(plugin_name, "mangatown") == 0 || strcmp(plugin_name, "mangadex") == 0; + return strcmp(plugin_name, "manganelo") == 0 || strcmp(plugin_name, "manganelos") == 0 || strcmp(plugin_name, "mangatown") == 0 || strcmp(plugin_name, "mangadex") == 0; } static std::shared_ptr<BodyItem> create_launcher_body_item(const char *title, const char *plugin_name, const std::string &thumbnail_url) { @@ -520,7 +522,7 @@ namespace QuickMedia { if(upscale_image_action != UpscaleImageAction::NO) { if(!is_manga_plugin(plugin_name)) { - fprintf(stderr, "Option --upscale-images/-upscale-images-force is only valid for manganelo, mangatown and mangadex\n"); + fprintf(stderr, "Option --upscale-images/-upscale-images-force is only valid for manganelo, manganelos, mangatown and mangadex\n"); return -2; } @@ -701,6 +703,7 @@ namespace QuickMedia { pipe_body->items.push_back(create_launcher_body_item("4chan", "4chan", resources_root + "icons/4chan_launcher.png")); pipe_body->items.push_back(create_launcher_body_item("Mangadex", "mangadex", resources_root + "icons/mangadex_launcher.png")); pipe_body->items.push_back(create_launcher_body_item("Manganelo", "manganelo", resources_root + "icons/manganelo_launcher.png")); + pipe_body->items.push_back(create_launcher_body_item("Manganelos", "manganelos", resources_root + "icons/manganelos_launcher.png")); pipe_body->items.push_back(create_launcher_body_item("Mangatown", "mangatown", resources_root + "icons/mangatown_launcher.png")); pipe_body->items.push_back(create_launcher_body_item("Matrix", "matrix", resources_root + "icons/matrix_launcher.png")); pipe_body->items.push_back(create_launcher_body_item("Nyaa.si", "nyaa.si", resources_root + "icons/nyaa_si_launcher.png")); @@ -710,7 +713,15 @@ namespace QuickMedia { 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...", 200)}); + tabs.push_back(Tab{std::move(search_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)}); + } else if(strcmp(plugin_name, "manganelos") == 0) { + auto search_body = create_body(); + tabs.push_back(Tab{std::move(search_body), std::make_unique<ManganelosSearchPage>(this), create_search_bar("Search...", 400)}); auto history_body = create_body(); auto search_bar = create_search_bar("Search...", SEARCH_DELAY_FILTER); @@ -718,7 +729,7 @@ namespace QuickMedia { tabs.push_back(Tab{std::move(history_body), std::move(history_page), std::move(search_bar)}); } else if(strcmp(plugin_name, "mangatown") == 0) { auto search_body = create_body(); - tabs.push_back(Tab{std::move(search_body), std::make_unique<MangatownSearchPage>(this), create_search_bar("Search...", 200)}); + tabs.push_back(Tab{std::move(search_body), std::make_unique<MangatownSearchPage>(this), create_search_bar("Search...", 400)}); auto history_body = create_body(); auto search_bar = create_search_bar("Search...", SEARCH_DELAY_FILTER); @@ -726,7 +737,7 @@ namespace QuickMedia { tabs.push_back(Tab{std::move(history_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...", 300)}); + tabs.push_back(Tab{std::move(search_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); @@ -993,6 +1004,8 @@ namespace QuickMedia { auto body_item = BodyItem::create(manga_name.asString()); if(strcmp(plugin_name, "manganelo") == 0) body_item->url = "https://manganelo.com/manga/" + base64_decode(filename.string()); + else if(strcmp(plugin_name, "manganelos") == 0) + body_item->url = "http://manganelos.com/manga/" + base64_decode(filename.string()); else if(strcmp(plugin_name, "mangadex") == 0) body_item->url = "https://mangadex.org/title/" + base64_decode(filename.string()); else if(strcmp(plugin_name, "mangatown") == 0) diff --git a/src/plugins/Manganelos.cpp b/src/plugins/Manganelos.cpp new file mode 100644 index 0000000..f67c313 --- /dev/null +++ b/src/plugins/Manganelos.cpp @@ -0,0 +1,180 @@ +#include "../../plugins/Manganelos.hpp" +#include "../../include/Notification.hpp" +#include "../../include/StringUtils.hpp" +#include "../../include/NetUtils.hpp" +#include <quickmedia/HtmlSearch.h> + +namespace QuickMedia { + static SearchResult search_page(const std::string &str, int page, BodyItems &result_items) { + std::string url = "http://manganelos.com/search?q="; + url += url_param_encode(str); + url += "&page=" + std::to_string(page); + + std::string website_data; + if(download_to_string(url, website_data, {}, true) != DownloadResult::OK) + return SearchResult::NET_ERR; + + if(website_data.empty()) + return SearchResult::OK; + + QuickMediaHtmlSearch html_search; + int result = quickmedia_html_search_init(&html_search, website_data.c_str()); + if(result != 0) + goto cleanup; + + result = quickmedia_html_find_nodes_xpath(&html_search, "//div[class='media-left cover-manga']//a", + [](QuickMediaHtmlNode *node, void *userdata) { + auto *item_data = (BodyItems*)userdata; + const char *href = quickmedia_html_node_get_attribute_value(node, "href"); + const char *title = quickmedia_html_node_get_attribute_value(node, "title"); + if(href && title && strstr(href, "/manga/")) { + auto item = BodyItem::create(strip(title)); + item->url = href; + item_data->push_back(std::move(item)); + } + }, &result_items); + + BodyItemContext body_item_image_context; + body_item_image_context.body_items = &result_items; + body_item_image_context.index = 0; + + result = quickmedia_html_find_nodes_xpath(&html_search, "//div[class='media-left cover-manga']//img[class='media-object']", + [](QuickMediaHtmlNode *node, void *userdata) { + auto *item_data = (BodyItemContext*)userdata; + const char *src = quickmedia_html_node_get_attribute_value(node, "src"); + if(src && strstr(src, "/mangaimage/") && item_data->index < item_data->body_items->size()) { + (*item_data->body_items)[item_data->index]->thumbnail_url = src; + item_data->index++; + } + }, &body_item_image_context); + + cleanup: + quickmedia_html_search_deinit(&html_search); + return SearchResult::OK; + } + + SearchResult ManganelosSearchPage::search(const std::string &str, BodyItems &result_items) { + return search_page(str, 1, result_items); + } + + PluginResult ManganelosSearchPage::get_page(const std::string &str, int page, BodyItems &result_items) { + return search_result_to_plugin_result(search_page(str, 1 + page, result_items)); + } + + PluginResult ManganelosSearchPage::submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) { + BodyItems chapters_items; + + std::string website_data; + if(download_to_string(url, website_data, {}, true) != DownloadResult::OK) + return PluginResult::NET_ERR; + + QuickMediaHtmlSearch html_search; + int result = quickmedia_html_search_init(&html_search, website_data.c_str()); + if(result != 0) + goto cleanup; + + result = quickmedia_html_find_nodes_xpath(&html_search, "//section[id='examples']//div[class='chapter-list']//a", + [](QuickMediaHtmlNode *node, void *userdata) { + auto *item_data = (BodyItems*)userdata; + const char *href = quickmedia_html_node_get_attribute_value(node, "href"); + const char *text = quickmedia_html_node_get_text(node); + if(href && text) { + auto item = BodyItem::create(strip(text)); + item->url = href; + item_data->push_back(std::move(item)); + } + }, &chapters_items); + + cleanup: + quickmedia_html_search_deinit(&html_search); + if(result != 0) + return PluginResult::ERR; + + auto body = create_body(); + body->items = std::move(chapters_items); + result_tabs.push_back(Tab{std::move(body), std::make_unique<ManganelosChaptersPage>(program, title, url), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); + return PluginResult::OK; + } + + PluginResult ManganelosChaptersPage::submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) { + result_tabs.push_back(Tab{nullptr, std::make_unique<ManganelosImagesPage>(program, content_title, title, url), nullptr}); + return PluginResult::OK; + } + + bool ManganelosChaptersPage::extract_id_from_url(const std::string &url, std::string &manga_id) const { + size_t start_index = url.find("/manga/"); + if(start_index == std::string::npos) + return false; + + start_index += 7; + size_t end_index = url.find("?", start_index); + if(end_index == std::string::npos) { + manga_id = url.substr(start_index); + return true; + } + + manga_id = url.substr(start_index, end_index - start_index); + return true; + } + + ImageResult ManganelosImagesPage::get_number_of_images(int &num_images) { + num_images = 0; + ImageResult image_result = get_image_urls_for_chapter(url); + if(image_result != ImageResult::OK) + return image_result; + + num_images = chapter_image_urls.size(); + return ImageResult::OK; + } + + ImageResult ManganelosImagesPage::for_each_page_in_chapter(PageCallback callback) { + std::vector<std::string> image_urls; + ImageResult image_result = get_image_urls_for_chapter(url); + if(image_result != ImageResult::OK) + return image_result; + + image_urls = chapter_image_urls; + + for(const std::string &url : image_urls) { + if(!callback(url)) + break; + } + + return ImageResult::OK; + } + + ImageResult ManganelosImagesPage::get_image_urls_for_chapter(const std::string &url) { + if(!chapter_image_urls.empty()) + return ImageResult::OK; + + std::string website_data; + if(download_to_string(url, website_data, {}, true) != DownloadResult::OK) + return ImageResult::NET_ERR; + + QuickMediaHtmlSearch html_search; + int result = quickmedia_html_search_init(&html_search, website_data.c_str()); + if(result != 0) + goto cleanup; + + result = quickmedia_html_find_nodes_xpath(&html_search, "//p[id='arraydata']", + [](QuickMediaHtmlNode *node, void *userdata) { + std::vector<std::string> *chapter_image_urls = (std::vector<std::string>*)userdata; + const char *text = quickmedia_html_node_get_text(node); + string_split(text, ',', [chapter_image_urls](const char *str, size_t size) { + std::string url(str, size); + chapter_image_urls->push_back(std::move(url)); + return true; + }); + }, &chapter_image_urls); + + cleanup: + quickmedia_html_search_deinit(&html_search); + if(result != 0) { + chapter_image_urls.clear(); + return ImageResult::ERR; + } + if(chapter_image_urls.empty()) + return ImageResult::ERR; + return ImageResult::OK; + } +}
\ No newline at end of file |