diff options
-rw-r--r-- | README.md | 4 | ||||
-rw-r--r-- | icons/mangakatana_launcher.png | bin | 0 -> 2486 bytes | |||
-rw-r--r-- | images/mangakatana_logo.png | bin | 0 -> 6583 bytes | |||
-rw-r--r-- | plugins/MangaGeneric.hpp | 51 | ||||
-rw-r--r-- | src/QuickMedia.cpp | 54 | ||||
-rw-r--r-- | src/plugins/MangaGeneric.cpp | 166 |
6 files changed, 183 insertions, 92 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`, `manganelos`, `mangatown`, `mangadex`, `4chan`, `matrix` and _others_.\ +Currently supported web services: `youtube`, `spotify (podcasts)`, `soundcloud`, `nyaa.si`, `manganelo`, `manganelos`, `mangatown`, `mangakatana`, `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, manganelos, 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, mangakatana, 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/mangakatana_launcher.png b/icons/mangakatana_launcher.png Binary files differnew file mode 100644 index 0000000..b5a9010 --- /dev/null +++ b/icons/mangakatana_launcher.png diff --git a/images/mangakatana_logo.png b/images/mangakatana_logo.png Binary files differnew file mode 100644 index 0000000..8e9d065 --- /dev/null +++ b/images/mangakatana_logo.png diff --git a/plugins/MangaGeneric.hpp b/plugins/MangaGeneric.hpp index 7d63622..d58672a 100644 --- a/plugins/MangaGeneric.hpp +++ b/plugins/MangaGeneric.hpp @@ -5,8 +5,7 @@ namespace QuickMedia { struct SearchQuery { - const char *search_prefix = nullptr; - const char *page_prefix = nullptr; + const char *search_template = nullptr; int page_start = 0; }; @@ -42,13 +41,9 @@ namespace QuickMedia { ListPageImagesQueryPost post_handler = nullptr; }; - // Return the actual number of pages - using ListPagePaginationPagesPost = std::function<int(int num_pages)>; struct ListPagePaginationQuery { const char *pages_html_query = nullptr; const char *pages_field_name = nullptr; - const char *pages_field_contains = nullptr; - ListPagePaginationPagesPost pages_post_handler = nullptr; const char *image_html_query = nullptr; const char *image_field_name = nullptr; @@ -59,15 +54,23 @@ namespace QuickMedia { const char *next_page_field_contains = nullptr; }; + // Return the image sources + using ListPageCustomHandler = std::function<std::vector<std::string>(const std::string &html_source)>; + struct ListPageCustomQuery { + ListPageCustomHandler handler; + }; + enum class ListPageQueryType { IMAGES, - PAGINATION + PAGINATION, + CUSTOM }; struct ListPageQuery { ListPageQueryType type = ListPageQueryType::IMAGES; ListPageImagesQuery images_query; ListPagePaginationQuery pagination_query; + ListPageCustomQuery custom_query; }; struct MangaIdExtractor { @@ -77,7 +80,7 @@ namespace QuickMedia { class MangaGenericSearchPage : public Page { public: - MangaGenericSearchPage(Program *program, const char *service_name, const char *website_url); + MangaGenericSearchPage(Program *program, const char *service_name, const char *website_url, bool fail_on_http_error = true); 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; @@ -85,12 +88,10 @@ namespace QuickMedia { 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); }; - // Add a %s where the query or page number should be inserted into |search_prefix| and |page_prefix|, for example: - // search_prefix: example.com/search?q=%s - // page_prefix: &page=%s - // |page_start| is the first page, so the result page is |page_start| + |page| where page is the current page we are navigating on. + // Add a %s where the search query should be inserted into |search_template| and add a %p where the page number should be inserted, for example: + // example.com/search?q=%s&page=%p // This is required. - MangaGenericSearchPage& search_handler(const char *search_prefix, const char *page_prefix, int page_start); + MangaGenericSearchPage& search_handler(const char *search_template, int page_start); // If |url_contains| is null, then any matching query is added. If |title_field| is "text", then the inner text is used. // This is required. MangaGenericSearchPage& text_handler(const char *html_query, const char *title_field, const char *url_field, const char *url_contains); @@ -106,16 +107,20 @@ namespace QuickMedia { MangaGenericSearchPage& list_chapters_uploaded_time_handler(const char *html_query, const char *field_name, const char *field_contains); // If |field_contains| is null, then any matching query is added. If |field_name| is "text", then the inner text is used. - // This or |list_page_images_pagination_handler| is required. + // This or |list_page_images_pagination_handler| or |list_page_images_custom_handler| is required. MangaGenericSearchPage& list_page_images_handler(const char *html_query, const char *field_name, const char *field_contains, ListPageImagesQueryPost post_handler = nullptr); - // If |pages_field_contains| or |image_field_contains| is null, then any matching query is added. If |pages_field_name| or |image_field_name| is "text", then the inner text is used. - // This or |list_page_images_handler| is required. + // If |image_field_contains| is null, then any matching query is added. If |pages_field_name| or |image_field_name| is "text", then the inner text is used. + // The last matching pages html query item is chosen as the number of pages. + // This or |list_page_images_handler| or |list_page_images_custom_handler| is required. MangaGenericSearchPage& list_page_images_pagination_handler( - const char *pages_html_query, const char *pages_field_name, const char *pages_field_contains, ListPagePaginationPagesPost pages_post_handler, + const char *pages_html_query, const char *pages_field_name, const char *image_html_query, const char *image_field_name, const char *image_field_contains, const char *next_page_html_query, const char *next_page_field_name, const char *next_page_field_contains); + // This or |list_page_images_handler| or |list_page_images_pagination_handler| is required. + MangaGenericSearchPage& list_page_images_custom_handler(ListPageCustomHandler handler); + // For example: mangasite.com/manga/204353&f=23 // /manga/ here would be the |prefix| and & would be |end|. |end| is optional. // The goal is to extract 204353 from the manga chapter page url. @@ -123,6 +128,7 @@ namespace QuickMedia { private: const char *service_name; std::string website_url; + bool fail_on_http_error; SearchQuery search_query; TextQuery text_query; ThumbnailQuery thumbnail_query; @@ -133,8 +139,8 @@ namespace QuickMedia { class MangaGenericChaptersPage : public MangaChaptersPage { public: - MangaGenericChaptersPage(Program *program, std::string manga_name, std::string manga_url, const MangaIdExtractor &manga_id_extractor, const char *service_name, const std::string &website_url, const ListPageQuery *list_page_query) : - MangaChaptersPage(program, std::move(manga_name), std::move(manga_url)), manga_id_extractor(manga_id_extractor), service_name(service_name), website_url(website_url), list_page_query(list_page_query) {} + MangaGenericChaptersPage(Program *program, std::string manga_name, std::string manga_url, const MangaIdExtractor &manga_id_extractor, const char *service_name, const std::string &website_url, const ListPageQuery *list_page_query, bool fail_on_http_error) : + MangaChaptersPage(program, std::move(manga_name), std::move(manga_url)), manga_id_extractor(manga_id_extractor), service_name(service_name), website_url(website_url), list_page_query(list_page_query), fail_on_http_error(fail_on_http_error) {} 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; @@ -144,12 +150,13 @@ namespace QuickMedia { const char *service_name; std::string website_url; const ListPageQuery *list_page_query; + bool fail_on_http_error; }; class MangaGenericImagesPage : public MangaImagesPage { public: - MangaGenericImagesPage(Program *program, std::string manga_name, std::string chapter_name, std::string url, const char *service_name, const std::string &website_url, const ListPageQuery *list_page_query) : - MangaImagesPage(program, std::move(manga_name), std::move(chapter_name), std::move(url)), service_name(service_name), website_url(website_url), list_page_query(list_page_query) {} + MangaGenericImagesPage(Program *program, std::string manga_name, std::string chapter_name, std::string url, const char *service_name, const std::string &website_url, const ListPageQuery *list_page_query, bool fail_on_http_error) : + MangaImagesPage(program, std::move(manga_name), std::move(chapter_name), std::move(url)), service_name(service_name), website_url(website_url), list_page_query(list_page_query), fail_on_http_error(fail_on_http_error) {} 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 service_name; } @@ -159,7 +166,7 @@ namespace QuickMedia { const char *service_name; std::string website_url; const ListPageQuery *list_page_query; - std::string prev_chapter_url; + bool fail_on_http_error; std::string current_image_url; std::string next_page_url; }; diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index 26760ec..955a189 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -62,6 +62,7 @@ static const std::pair<const char*, const char*> valid_plugins[] = { std::make_pair("manganelo", "manganelo_logo.png"), std::make_pair("manganelos", "manganelos_logo.png"), std::make_pair("mangatown", "mangatown_logo.png"), + std::make_pair("mangakatana", "mangakatana_logo.png"), std::make_pair("mangadex", "mangadex_logo.png"), std::make_pair("youtube", "yt_logo_rgb_dark_small.png"), std::make_pair("spotify", "spotify_logo.png"), @@ -420,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, manganelos, 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, mangakatana, 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"); @@ -435,7 +436,7 @@ namespace QuickMedia { } static bool is_manga_plugin(const char *plugin_name) { - return strcmp(plugin_name, "manganelo") == 0 || strcmp(plugin_name, "manganelos") == 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, "mangakatana") == 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) { @@ -679,7 +680,7 @@ namespace QuickMedia { } static void add_manganelos_handlers(MangaGenericSearchPage *manga_generic_search_page) { - manga_generic_search_page->search_handler("http://manganelos.com/search?q=", "&page=", 1) + manga_generic_search_page->search_handler("http://manganelos.com/search?q=%s&page=%p", 1) .text_handler("//div[class='media-left cover-manga']//a", "title", "href", "/manga/") .thumbnail_handler("//div[class='media-left cover-manga']//img[class='media-object']", "src", "/mangaimage/") .list_chapters_handler("//section[id='examples']//div[class='chapter-list']//a", "text", "href", nullptr) @@ -701,18 +702,48 @@ namespace QuickMedia { } static void add_mangatown_handlers(MangaGenericSearchPage *manga_generic_search_page) { - manga_generic_search_page->search_handler("https://www.mangatown.com/search?name=", "&page=", 1) + manga_generic_search_page->search_handler("https://mangatown.com/search?name=%s&page=%p", 1) .text_handler("//p[class='title']/a", "title", "href", "/manga/") .thumbnail_handler("//a[class='manga_cover']/img", "src", nullptr) .list_chapters_handler("//ul[class='chapter_list']//a", "text", "href", "/manga/") .list_chapters_uploaded_time_handler("//ul[class='chapter_list']//span[class='time']", "text", nullptr) .list_page_images_pagination_handler( - "//div[class='page_select']//option", "value", "/manga/", [](int num_pages){ return std::max(0, (num_pages /= 2) - 1); }, + "//div[class='page_select']//option", "text", "//img[id='image']", "src", nullptr, "//a[class='next_page']", "href", nullptr) .manga_id_handler("/manga/", "/"); } + static void add_mangakatana_handlers(MangaGenericSearchPage *manga_generic_search_page) { + manga_generic_search_page->search_handler("https://mangakatana.com/page/%p?search=%s&search_by=book_name", 1) + .text_handler("//div[id='book_list']//h3[class='title']//a", "text", "href", "/manga/") + .thumbnail_handler("//div[id='book_list']//img", "src", "/cover/") + .list_chapters_handler("//div[class='chapters']//div[class='chapter']//a", "text", "href", "/manga/") + .list_chapters_uploaded_time_handler("//div[class='chapters']//div[class='update_time']", "text", nullptr) + .list_page_images_custom_handler([](const std::string &html_source) { + std::vector<std::string> urls; + size_t sources_start = html_source.find("ytaw=["); + if(sources_start == std::string::npos) + return urls; + + sources_start += 6; + size_t sources_end = html_source.find("]", sources_start); + if(sources_end == std::string::npos) + return urls; + + std::string urls_str = html_source.substr(sources_start, sources_end - sources_start); + string_replace_all(urls_str, "'", ""); + + string_split(urls_str, ',', [&urls](const char *str, size_t size) { + std::string url(str, size); + urls.push_back(std::move(url)); + return true; + }); + return urls; + }) + .manga_id_handler("/manga/", nullptr); + } + void Program::load_plugin_by_name(std::vector<Tab> &tabs, const char *start_dir) { if(!plugin_name || plugin_name[0] == '\0') return; @@ -742,6 +773,7 @@ namespace QuickMedia { auto pipe_body = create_body(); 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("Mangakatana", "mangakatana", resources_root + "icons/mangakatana_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")); @@ -780,6 +812,16 @@ namespace QuickMedia { 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, "mangakatana") == 0) { + auto search_body = create_body(); + auto search_page = std::make_unique<MangaGenericSearchPage>(this, plugin_name, "https://mangakatana.com", false); + add_mangakatana_handlers(search_page.get()); + tabs.push_back(Tab{std::move(search_body), std::move(search_page), 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, "mangadex") == 0) { auto search_body = create_body(); tabs.push_back(Tab{std::move(search_body), std::make_unique<MangadexSearchPage>(this), create_search_bar("Search...", 400)}); @@ -1055,6 +1097,8 @@ namespace QuickMedia { body_item->url = "https://mangadex.org/title/" + base64_decode(filename.string()); else if(strcmp(plugin_name, "mangatown") == 0) body_item->url = "https://mangatown.com/manga/" + base64_decode(filename.string()); + else if(strcmp(plugin_name, "mangakatana") == 0) + body_item->url = "https://mangakatana.com/manga/" + base64_decode(filename.string()); else fprintf(stderr, "Error: Not implemented: filename to manga chapter list\n"); history_items.push_back(std::move(body_item)); diff --git a/src/plugins/MangaGeneric.cpp b/src/plugins/MangaGeneric.cpp index a359698..607488f 100644 --- a/src/plugins/MangaGeneric.cpp +++ b/src/plugins/MangaGeneric.cpp @@ -98,7 +98,8 @@ namespace QuickMedia { }, page_image_userdata); } - MangaGenericSearchPage::MangaGenericSearchPage(Program *program, const char *service_name, const char *website_url) : Page(program), service_name(service_name), website_url(website_url ? website_url : "") + MangaGenericSearchPage::MangaGenericSearchPage(Program *program, const char *service_name, const char *website_url, bool fail_on_http_error) : + Page(program), service_name(service_name), website_url(website_url ? website_url : ""), fail_on_http_error(fail_on_http_error) { if(!this->website_url.empty()) { if(this->website_url.back() != '/') @@ -111,7 +112,7 @@ namespace QuickMedia { } PluginResult MangaGenericSearchPage::get_page(const std::string &str, int page, BodyItems &result_items) { - if(!search_query.search_prefix || !search_query.page_prefix || !text_query.html_query || !text_query.title_field || !text_query.url_field) { + if(!search_query.search_template || !text_query.html_query || !text_query.title_field || !text_query.url_field) { assert(false); return PluginResult::ERR; } @@ -122,12 +123,12 @@ namespace QuickMedia { search_userdata.field2 = text_query.url_field; search_userdata.field2_contains = text_query.url_contains; - std::string url = search_query.search_prefix; - url += url_param_encode(str); - url += search_query.page_prefix + std::to_string(search_query.page_start + page); + 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)); std::string website_data; - if(download_to_string(url, website_data, {}, true) != DownloadResult::OK) + if(download_to_string(url, website_data, {}, true, fail_on_http_error) != DownloadResult::OK) return PluginResult::NET_ERR; if(website_data.empty()) @@ -189,7 +190,7 @@ namespace QuickMedia { search_userdata.field2_contains = list_chapters_query.url_contains; std::string website_data; - if(download_to_string(url, website_data, {}, true) != DownloadResult::OK) + if(download_to_string(url, website_data, {}, true, fail_on_http_error) != DownloadResult::OK) return PluginResult::NET_ERR; QuickMediaHtmlSearch html_search; @@ -231,12 +232,12 @@ namespace QuickMedia { auto body = create_body(); body->items = std::move(chapters_items); - result_tabs.push_back(Tab{std::move(body), std::make_unique<MangaGenericChaptersPage>(program, title, url, manga_id_extractor, service_name, website_url, &list_page_query), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); + result_tabs.push_back(Tab{std::move(body), std::make_unique<MangaGenericChaptersPage>(program, title, url, manga_id_extractor, service_name, website_url, &list_page_query, fail_on_http_error), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); return PluginResult::OK; } PluginResult MangaGenericChaptersPage::submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) { - result_tabs.push_back(Tab{nullptr, std::make_unique<MangaGenericImagesPage>(program, content_title, title, url, service_name, website_url, list_page_query), nullptr}); + result_tabs.push_back(Tab{nullptr, std::make_unique<MangaGenericImagesPage>(program, content_title, title, url, service_name, website_url, list_page_query, fail_on_http_error), nullptr}); return PluginResult::OK; } @@ -245,12 +246,12 @@ namespace QuickMedia { if(start_index == std::string::npos) return false; + start_index += strlen(manga_id_extractor.prefix); if(!manga_id_extractor.end) { manga_id = url.substr(start_index); return true; } - start_index += strlen(manga_id_extractor.prefix); size_t end_index = url.find(manga_id_extractor.end, start_index); if(end_index == std::string::npos) { manga_id = url.substr(start_index); @@ -261,9 +262,18 @@ namespace QuickMedia { return true; } + static bool is_number(const char *str) { + while(*str) { + char c = *str; + if(c < '0' || c > '9') + return false; + ++str; + } + return true; + } + ImageResult MangaGenericImagesPage::get_number_of_images(int &num_images) { num_images = 0; - chapter_num_pages = -1; switch(list_page_query->type) { case ListPageQueryType::IMAGES: { ImageResult result = get_page_image_urls(); @@ -292,7 +302,7 @@ namespace QuickMedia { HtmlPageCountUserdata page_count_userdata; page_count_userdata.num_pages = 0; page_count_userdata.field_name = list_page_pagination_query->pages_field_name; - page_count_userdata.field_contains = list_page_pagination_query->pages_field_contains; + page_count_userdata.field_contains = nullptr; HtmlPageImageUserdata page_image_userdata; page_image_userdata.url = ¤t_image_url; @@ -305,7 +315,7 @@ namespace QuickMedia { next_page_userdata.field_contains = list_page_pagination_query->next_page_field_contains; std::string website_data; - if(download_to_string(url, website_data, {}, true) != DownloadResult::OK) + if(download_to_string(url, website_data, {}, true, fail_on_http_error) != DownloadResult::OK) return ImageResult::NET_ERR; QuickMediaHtmlSearch html_search; @@ -317,15 +327,13 @@ namespace QuickMedia { [](QuickMediaHtmlNode *node, void *userdata) { HtmlPageCountUserdata *page_count_userdata = (HtmlPageCountUserdata*)userdata; const char *field1_value = html_attr_or_inner_text(node, page_count_userdata->field_name); - if(field1_value && (!page_count_userdata->field_contains || strstr(field1_value, page_count_userdata->field_contains))) { - page_count_userdata->num_pages++; + if(field1_value) { + std::string field_value_stripped = strip(field1_value); + if(is_number(field_value_stripped.c_str())) + page_count_userdata->num_pages = strtol(field_value_stripped.c_str(), nullptr, 10); } }, &page_count_userdata); - if(result == 0 && list_page_pagination_query->pages_post_handler) { - page_count_userdata.num_pages = list_page_pagination_query->pages_post_handler(page_count_userdata.num_pages); - } - if(result != 0 || page_count_userdata.num_pages == 0) { result = -1; goto cleanup; @@ -358,6 +366,12 @@ namespace QuickMedia { chapter_num_pages = num_images; return ImageResult::OK; } + case ListPageQueryType::CUSTOM: { + ImageResult result = get_page_image_urls(); + if(result != ImageResult::OK) return result; + num_images = chapter_image_urls.size(); + return ImageResult::OK; + } } return ImageResult::OK; } @@ -406,7 +420,10 @@ namespace QuickMedia { std::string image_src; std::string website_data; - if(download_to_string_cache(full_url, website_data, {}, true) != DownloadResult::OK) + DownloadErrorHandler error_callback = [](std::string&){ return true; }; + if(fail_on_http_error) + error_callback = nullptr; + if(download_to_string_cache(full_url, website_data, {}, true, error_callback) != DownloadResult::OK) return ImageResult::ERR; QuickMediaHtmlSearch html_search; @@ -431,46 +448,73 @@ namespace QuickMedia { return ImageResult::OK; } + case ListPageQueryType::CUSTOM: { + ImageResult result = get_page_image_urls(); + if(result != ImageResult::OK) return result; + for(const std::string &url : chapter_image_urls) { + if(!callback(url)) + break; + } + return ImageResult::OK; + } } return ImageResult::OK; } ImageResult MangaGenericImagesPage::get_page_image_urls() { - if(!prev_chapter_url.empty()) + if(!chapter_image_urls.empty()) return ImageResult::OK; - assert(list_page_query->type == ListPageQueryType::IMAGES); - const ListPageImagesQuery *list_page_images_query = &list_page_query->images_query; - if(!list_page_images_query->html_query || !list_page_images_query->field_name) { - assert(false); - return ImageResult::ERR; - } - - HtmlListPageImagesUserdata list_page_images_userdata; - list_page_images_userdata.urls = &chapter_image_urls; - list_page_images_userdata.field_name = list_page_images_query->field_name; - list_page_images_userdata.field_contains = list_page_images_query->field_contains; - std::string website_data; - if(download_to_string(url, website_data, {}, true) != DownloadResult::OK) + if(download_to_string(url, website_data, {}, true, fail_on_http_error) != 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; + if(list_page_query->type == ListPageQueryType::IMAGES) { + const ListPageImagesQuery *list_page_images_query = &list_page_query->images_query; + if(!list_page_images_query->html_query || !list_page_images_query->field_name) { + assert(false); + return ImageResult::ERR; + } - result = quickmedia_html_find_nodes_xpath(&html_search, list_page_images_query->html_query, - [](QuickMediaHtmlNode *node, void *userdata) { - HtmlListPageImagesUserdata *list_page_images_userdata = (HtmlListPageImagesUserdata*)userdata; - const char *field1_value = html_attr_or_inner_text(node, list_page_images_userdata->field_name); - if(field1_value && (!list_page_images_userdata->field_contains || strstr(field1_value, list_page_images_userdata->field_contains))) { - list_page_images_userdata->urls->push_back(strip(field1_value)); - } - }, &list_page_images_userdata); + HtmlListPageImagesUserdata list_page_images_userdata; + list_page_images_userdata.urls = &chapter_image_urls; + list_page_images_userdata.field_name = list_page_images_query->field_name; + list_page_images_userdata.field_contains = list_page_images_query->field_contains; + + 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, list_page_images_query->html_query, + [](QuickMediaHtmlNode *node, void *userdata) { + HtmlListPageImagesUserdata *list_page_images_userdata = (HtmlListPageImagesUserdata*)userdata; + const char *field1_value = html_attr_or_inner_text(node, list_page_images_userdata->field_name); + if(field1_value && (!list_page_images_userdata->field_contains || strstr(field1_value, list_page_images_userdata->field_contains))) { + list_page_images_userdata->urls->push_back(strip(field1_value)); + } + }, &list_page_images_userdata); - if(result == 0 && !chapter_image_urls.empty() && list_page_images_query->post_handler) - list_page_images_query->post_handler(chapter_image_urls); + if(result == 0 && !chapter_image_urls.empty() && list_page_images_query->post_handler) + list_page_images_query->post_handler(chapter_image_urls); + + cleanup: + quickmedia_html_search_deinit(&html_search); + if(result != 0 || chapter_image_urls.empty()) { + chapter_image_urls.clear(); + return ImageResult::ERR; + } + } else if(list_page_query->type == ListPageQueryType::CUSTOM) { + const ListPageCustomQuery *list_page_custom_query = &list_page_query->custom_query; + if(!list_page_custom_query->handler) { + assert(false); + return ImageResult::ERR; + } + chapter_image_urls = list_page_custom_query->handler(website_data); + } else { + assert(false); + return ImageResult::ERR; + } for(std::string &url : chapter_image_urls) { if(starts_with(url, "//")) @@ -479,21 +523,13 @@ namespace QuickMedia { url = website_url + url.substr(1); } - cleanup: - quickmedia_html_search_deinit(&html_search); - if(result != 0 || chapter_image_urls.empty()) { - chapter_image_urls.clear(); - return ImageResult::ERR; - } - - prev_chapter_url = url; + chapter_num_pages = chapter_image_urls.size(); return ImageResult::OK; } - MangaGenericSearchPage& MangaGenericSearchPage::search_handler(const char *search_prefix, const char *page_prefix, int page_start) { - search_query.search_prefix = search_prefix; - search_query.page_prefix = page_prefix; + MangaGenericSearchPage& MangaGenericSearchPage::search_handler(const char *search_template, int page_start) { + search_query.search_template = search_template; search_query.page_start = page_start; return *this; } @@ -538,16 +574,13 @@ namespace QuickMedia { } MangaGenericSearchPage& MangaGenericSearchPage::list_page_images_pagination_handler( - const char *pages_html_query, const char *pages_field_name, const char *pages_field_contains, ListPagePaginationPagesPost pages_post_handler, + const char *pages_html_query, const char *pages_field_name, const char *image_html_query, const char *image_field_name, const char *image_field_contains, const char *next_page_html_query, const char *next_page_field_name, const char *next_page_field_contains) { - assert(pages_post_handler); list_page_query.type = ListPageQueryType::PAGINATION; list_page_query.pagination_query.pages_html_query = pages_html_query; list_page_query.pagination_query.pages_field_name = pages_field_name; - list_page_query.pagination_query.pages_field_contains = pages_field_contains; - list_page_query.pagination_query.pages_post_handler = pages_post_handler; list_page_query.pagination_query.image_html_query = image_html_query; list_page_query.pagination_query.image_field_name = image_field_name; @@ -559,6 +592,13 @@ namespace QuickMedia { return *this; } + MangaGenericSearchPage& MangaGenericSearchPage::list_page_images_custom_handler(ListPageCustomHandler handler) { + assert(handler); + list_page_query.type = ListPageQueryType::CUSTOM; + list_page_query.custom_query.handler = handler; + return *this; + } + MangaGenericSearchPage& MangaGenericSearchPage::manga_id_handler(const char *prefix, const char *end) { manga_id_extractor.prefix = prefix; manga_id_extractor.end = end; |