aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/QuickMedia.cpp54
-rw-r--r--src/plugins/MangaGeneric.cpp166
2 files changed, 152 insertions, 68 deletions
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 = &current_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;