aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md4
-rw-r--r--icons/manganelos_launcher.pngbin0 -> 2482 bytes
-rw-r--r--icons/pornhub_launcher.pngbin11121 -> 0 bytes
-rw-r--r--images/manganelos_logo.pngbin0 -> 12337 bytes
-rw-r--r--plugins/Manganelos.hpp38
-rw-r--r--src/QuickMedia.cpp25
-rw-r--r--src/plugins/Manganelos.cpp180
7 files changed, 239 insertions, 8 deletions
diff --git a/README.md b/README.md
index cb70613..98758f7 100644
--- a/README.md
+++ b/README.md
@@ -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
new file mode 100644
index 0000000..0b07a22
--- /dev/null
+++ b/icons/manganelos_launcher.png
Binary files differ
diff --git a/icons/pornhub_launcher.png b/icons/pornhub_launcher.png
deleted file mode 100644
index 0ae5a76..0000000
--- a/icons/pornhub_launcher.png
+++ /dev/null
Binary files differ
diff --git a/images/manganelos_logo.png b/images/manganelos_logo.png
new file mode 100644
index 0000000..783501b
--- /dev/null
+++ b/images/manganelos_logo.png
Binary files differ
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