From a9d69f57a0cd4f9cccff07b2890a860695d1e7ed Mon Sep 17 00:00:00 2001 From: dec05eba Date: Sun, 12 Sep 2021 15:33:49 +0200 Subject: Mangadex: add author/artist tabs for manga, optimize search (covers) --- src/plugins/Mangadex.cpp | 235 ++++++++++++++++++++++++--------------------- src/plugins/Soundcloud.cpp | 4 + 2 files changed, 131 insertions(+), 108 deletions(-) (limited to 'src/plugins') diff --git a/src/plugins/Mangadex.cpp b/src/plugins/Mangadex.cpp index 34bcfc7..9209828 100644 --- a/src/plugins/Mangadex.cpp +++ b/src/plugins/Mangadex.cpp @@ -59,91 +59,8 @@ namespace QuickMedia { return PluginResult::OK; } - - static std::shared_ptr relationship_get_body_item(const Json::Value &json, BodyItems &body_items) { - for(const Json::Value &item_json : json) { - if(!item_json.isObject()) - continue; - - const Json::Value &type_json = item_json["type"]; - if(!type_json.isString() || strcmp(type_json.asCString(), "manga") != 0) - continue; - - const Json::Value &id_json = item_json["id"]; - if(!id_json.isString()) - continue; - - std::string id_str = id_json.asString(); - auto it = std::find_if(body_items.begin(), body_items.end(), [&id_str](const std::shared_ptr &body_item) { - return body_item->url == id_str; - }); - - if(it == body_items.end()) - continue; - - return *it; - } - return nullptr; - } - - PluginResult MangadexSearchPage::get_cover_urls(BodyItems &body_items) { - if(body_items.empty()) - return PluginResult::OK; - - std::string url = "https://api.mangadex.org/cover?limit=100&order[updatedAt]=desc"; - for(size_t i = 0; i < body_items.size(); ++i) { - if(!body_items[i]->thumbnail_url.empty()) - url += "&ids[]=" + body_items[i]->thumbnail_url; - } - - Json::Value json_root; - if(download_json(json_root, url, {}, true) != DownloadResult::OK) - return PluginResult::NET_ERR; - - if(!json_root.isObject()) - return PluginResult::OK; - - const Json::Value &results_json = json_root["results"]; - if(!results_json.isArray()) - return PluginResult::OK; - - std::shared_ptr body_item; - for(const Json::Value &result_item_json : results_json) { - if(!result_item_json.isObject()) - continue; - - const Json::Value &result_json = result_item_json["result"]; - if(!result_json.isString() || strcmp(result_json.asCString(), "ok") != 0) - continue; - - const Json::Value &relationships_json = result_item_json["relationships"]; - if(!relationships_json.isArray()) - continue; - - body_item = relationship_get_body_item(relationships_json, body_items); - if(!body_item) - continue; - - const Json::Value &data_json = result_item_json["data"]; - if(!data_json.isObject()) - continue; - - const Json::Value &attributes_json = data_json["attributes"]; - if(!attributes_json.isObject()) - continue; - - const Json::Value &filename_json = attributes_json["fileName"]; - if(!filename_json.isString()) - continue; - - body_item->thumbnail_url = "https://uploads.mangadex.org/covers/" + body_item->url + "/" + filename_json.asString() + ".256.jpg"; - body_item->thumbnail_size = {101, 141}; - } - - return PluginResult::OK; - } - static std::string relationships_get_cover_art(const Json::Value &relationships_json) { + static std::string relationships_get_cover_art_filename(const Json::Value &relationships_json) { std::string result; if(!relationships_json.isArray()) return result; @@ -152,31 +69,53 @@ namespace QuickMedia { if(!relationship_json.isObject()) continue; - const Json::Value &id_json = relationship_json["id"]; const Json::Value &relationship_type_json = relationship_json["type"]; - if(!id_json.isString() || !relationship_type_json.isString() || strcmp(relationship_type_json.asCString(), "cover_art") != 0) + if(!relationship_type_json.isString() || strcmp(relationship_type_json.asCString(), "cover_art") != 0) + continue; + + const Json::Value &attributes_json = relationship_json["attributes"]; + if(!attributes_json.isObject()) continue; - result = id_json.asString(); + const Json::Value &file_name_json = attributes_json["fileName"]; + if(!file_name_json.isString()) + continue; + + result = file_name_json.asString(); break; } return result; } - SearchResult MangadexSearchPage::search(const std::string &str, int page, BodyItems &result_items) { - std::string url = "https://api.mangadex.org/manga?title=" + url_param_encode(str) + "&limit=20&offset=" + std::to_string(page * 20); + enum class SearchType { + TITLE, + AUTHOR + }; + + static PluginResult search_manga(Page *plugin_page, SearchType search_type, const std::string &query, int page, BodyItems &result_items) { + std::string url = "https://api.mangadex.org/manga?limit=20&includes[]=cover_art&offset=" + std::to_string(page * 20); + const std::string query_encoded = url_param_encode(query); + switch(search_type) { + case SearchType::TITLE: + url += "&title=" + query_encoded; + break; + case SearchType::AUTHOR: + url += "&authors[]=" + query_encoded; + url += "&artists[]=" + query_encoded; + break; + } Json::Value json_root; - if(download_json(json_root, url, {}, true) != DownloadResult::OK) - return SearchResult::NET_ERR; + if(plugin_page->download_json(json_root, url, {}, true) != DownloadResult::OK) + return PluginResult::NET_ERR; if(!json_root.isObject()) - return SearchResult::OK; + return PluginResult::OK; const Json::Value &results_json = json_root["results"]; if(!results_json.isArray()) - return SearchResult::OK; + return PluginResult::OK; for(const Json::Value &result_item_json : results_json) { if(!result_item_json.isObject()) @@ -220,22 +159,22 @@ namespace QuickMedia { } } - body_item->thumbnail_url = relationships_get_cover_art(result_item_json["relationships"]); + std::string cover_art_filename = relationships_get_cover_art_filename(data_json["relationships"]); + if(!cover_art_filename.empty()) + body_item->thumbnail_url = "https://uploads.mangadex.org/covers/" + body_item->url + "/" + std::move(cover_art_filename) + ".256.jpg"; + body_item->thumbnail_size = {101, 141}; result_items.push_back(std::move(body_item)); } - // Intentionally ignore errors. This api shouldn't fail if we fail to get covers - get_cover_urls(result_items); - - return SearchResult::OK; + return PluginResult::OK; } SearchResult MangadexSearchPage::search(const std::string &str, BodyItems &result_items) { - return search(str, 0, result_items); + return plugin_result_to_search_result(search_manga(this, SearchType::TITLE, str, 0, result_items)); } PluginResult MangadexSearchPage::get_page(const std::string &str, int page, BodyItems &result_items) { - return search_result_to_plugin_result(search(str, page, result_items)); + return search_manga(this, SearchType::TITLE, str, page, result_items); } static PluginResult get_chapters_for_manga(Page *page, const std::string &manga_id, int page_num, BodyItems &result_items, ChapterImageUrls &chapter_image_urls) { @@ -327,23 +266,103 @@ namespace QuickMedia { return PluginResult::OK; } + static void get_creators(const Json::Value &relationships_json, std::vector &creators) { + if(!relationships_json.isArray()) + return; + + for(const Json::Value &relationship_json : relationships_json) { + if(!relationship_json.isObject()) + continue; + + const Json::Value &id_json = relationship_json["id"]; + if(!id_json.isString()) + continue; + + const Json::Value &type_json = relationship_json["type"]; + if(!type_json.isString() || (strcmp(type_json.asCString(), "author") != 0 && strcmp(type_json.asCString(), "artist") != 0)) + continue; + + const Json::Value &attributes_json = relationship_json["attributes"]; + if(!attributes_json.isObject()) + continue; + + const Json::Value &name_json = attributes_json["name"]; + if(!name_json.isString()) + continue; + + Creator creator; + creator.name = name_json.asString(); + creator.url = id_json.asString(); + creators.push_back(std::move(creator)); + } + } + + static PluginResult get_creators_for_manga(Page *page, const std::string &manga_id, std::vector &creators) { + std::string request_url = "https://api.mangadex.org/manga/" + manga_id + "?includes[]=artist&includes[]=author"; + + Json::Value json_root; + if(page->download_json(json_root, request_url, {}, true) != DownloadResult::OK) + return PluginResult::NET_ERR; + + if(!json_root.isObject()) + return PluginResult::OK; + + const Json::Value &result_json = json_root["result"]; + if(!result_json.isString() || strcmp(result_json.asCString(), "ok") != 0) + return PluginResult::ERR; + + const Json::Value &data_json = json_root["data"]; + if(!data_json.isObject()) + return PluginResult::ERR; + + get_creators(data_json["relationships"], creators); + return PluginResult::OK; + } + + // TODO: Do requests in parallel PluginResult MangadexSearchPage::submit(const std::string &title, const std::string &url, std::vector &result_tabs) { - chapter_image_urls.clear(); - auto body = create_body(); + PluginResult result; + BodyItems body_items; - get_chapters_for_manga(this, url, 0, body_items, chapter_image_urls); + ChapterImageUrls chapter_image_urls; + result = get_chapters_for_manga(this, url, 0, body_items, chapter_image_urls); + if(result != PluginResult::OK) + return result; + auto body = create_body(); body->set_items(std::move(body_items)); - result_tabs.push_back(Tab{std::move(body), std::make_unique(program, this, title, url), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); + result_tabs.push_back(Tab{std::move(body), std::make_unique(program, this, title, url, std::move(chapter_image_urls)), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); + + std::vector creators; + result = get_creators_for_manga(this, url, creators); + if(result != PluginResult::OK) + return result; + + for(Creator &creator : creators) { + result_tabs.push_back(Tab{create_body(), std::make_unique(program, this, std::move(creator)), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); + } + return PluginResult::OK; } PluginResult MangadexChaptersPage::submit(const std::string &title, const std::string &url, std::vector &result_tabs) { - result_tabs.push_back(Tab{nullptr, std::make_unique(program, search_page, content_title, url, title), nullptr}); + result_tabs.push_back(Tab{nullptr, std::make_unique(program, search_page, content_title, url, title, &chapter_image_urls), nullptr}); return PluginResult::OK; } PluginResult MangadexChaptersPage::get_page(const std::string&, int page, BodyItems &result_items) { - return get_chapters_for_manga(this, content_url, page, result_items, search_page->chapter_image_urls); + return get_chapters_for_manga(this, content_url, page, result_items, chapter_image_urls); + } + + PluginResult MangadexCreatorPage::submit(const std::string &title, const std::string &url, std::vector &result_tabs) { + return search_page->submit(title, url, result_tabs); + } + + PluginResult MangadexCreatorPage::get_page(const std::string&, int page, BodyItems &result_items) { + return search_manga(this, SearchType::AUTHOR, creator.url, page, result_items); + } + + PluginResult MangadexCreatorPage::lazy_fetch(BodyItems &result_items) { + return search_manga(this, SearchType::AUTHOR, creator.url, 0, result_items); } bool MangadexChaptersPage::extract_id_from_url(const std::string &url, std::string &manga_id) const { @@ -401,8 +420,8 @@ namespace QuickMedia { if(!base_url.empty() && base_url.back() != '/') base_url += '/'; - auto it = search_page->chapter_image_urls.find(url); - if(it == search_page->chapter_image_urls.end()) + auto it = chapter_image_url_templates->find(url); + if(it == chapter_image_url_templates->end()) return ImageResult::ERR; chapter_image_urls.resize(it->second.size()); diff --git a/src/plugins/Soundcloud.cpp b/src/plugins/Soundcloud.cpp index a9b192e..3a9a6e1 100644 --- a/src/plugins/Soundcloud.cpp +++ b/src/plugins/Soundcloud.cpp @@ -291,9 +291,13 @@ namespace QuickMedia { SearchResult SoundcloudSearchPage::search(const std::string &str, BodyItems &result_items) { query_urn.clear(); + if(str.empty()) + return SearchResult::OK; + PluginResult result = get_page(str, 0, result_items); if(result != PluginResult::OK) return SearchResult::ERR; + return SearchResult::OK; } -- cgit v1.2.3