#include "../../plugins/Mangadex.hpp" #include namespace QuickMedia { PluginResult legacy_mangadex_id_to_new_manga_id(Page *page, const std::vector &manga_ids, std::vector> &new_manga_ids) { Json::Value request_json(Json::objectValue); request_json["type"] = "manga"; Json::Value manga_ids_json(Json::arrayValue); for(int manga_id : manga_ids) { manga_ids_json.append(manga_id); } request_json["ids"] = std::move(manga_ids_json); Json::StreamWriterBuilder json_builder; json_builder["commentStyle"] = "None"; json_builder["indentation"] = ""; std::vector additional_args = { { "-X", "POST" }, { "-H", "content-type: application/json" }, { "--data-binary", Json::writeString(json_builder, request_json) } }; Json::Value json_root; if(page->download_json(json_root, "https://api.mangadex.org/legacy/mapping", std::move(additional_args), true) != DownloadResult::OK) return PluginResult::NET_ERR; if(!json_root.isArray()) return PluginResult::OK; for(const Json::Value &result_item_json : json_root) { 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 &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 &legacy_id_json = attributes_json["legacyId"]; if(!legacy_id_json.isInt()) continue; const Json::Value &new_id_json = attributes_json["newId"]; if(!new_id_json.isString()) continue; new_manga_ids.push_back(std::make_pair(legacy_id_json.asInt(), new_id_json.asString())); } return PluginResult::OK; } class MangadexChapterImagesList : public BodyItemExtra { public: std::vector image_urls; }; 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=100&offset=" + std::to_string(page * 100); Json::Value json_root; if(download_json(json_root, url, {}, true) != DownloadResult::OK) return SearchResult::NET_ERR; if(!json_root.isObject()) return SearchResult::OK; const Json::Value &results_json = json_root["results"]; if(!results_json.isArray()) return SearchResult::OK; 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 &data_json = result_item_json["data"]; if(!data_json.isObject()) continue; const Json::Value &id_json = data_json["id"]; if(!id_json.isString()) continue; const Json::Value &attributes_json = data_json["attributes"]; if(!attributes_json.isObject()) continue; const Json::Value &title_json = attributes_json["title"]; if(!title_json.isObject()) continue; std::string title; const Json::Value &title_en_json = title_json["en"]; if(title_en_json.isString()) title = title_en_json.asString(); else title = "No title"; // TODO: Verify if this happens. If it happens, get the title in any available language auto body_item = BodyItem::create(std::move(title)); body_item->url = id_json.asString(); result_items.push_back(std::move(body_item)); } return SearchResult::OK; } SearchResult MangadexSearchPage::search(const std::string &str, BodyItems &result_items) { return search(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)); } static PluginResult get_chapters_for_manga(Page *page, const std::string &manga_id, int page_num, BodyItems &result_items) { std::string request_url = "https://api.mangadex.org/manga/" + manga_id + "/feed?order[chapter]=desc&limit=100&locales[]=en&offset=" + std::to_string(page_num * 100); 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 &results_json = json_root["results"]; if(!results_json.isArray()) return PluginResult::OK; 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 &data_json = result_item_json["data"]; if(!data_json.isObject()) continue; const Json::Value &id_json = data_json["id"]; if(!id_json.isString()) continue; const Json::Value &attributes_json = data_json["attributes"]; if(!attributes_json.isObject()) continue; const Json::Value &translated_language_json = attributes_json["translatedLanguage"]; if(!translated_language_json.isString() || strcmp(translated_language_json.asCString(), "en") != 0) continue; const Json::Value &chapter_json = attributes_json["chapter"]; if(!chapter_json.isString()) continue; std::string title = "Ch. " + chapter_json.asString(); const Json::Value &title_json = attributes_json["title"]; if(title_json.isString() && title_json.asCString()[0] != '\0') title += " - " + title_json.asString(); const Json::Value &hash_json = attributes_json["hash"]; if(!hash_json.isString()) continue; const Json::Value &attributes_data_json = attributes_json["data"]; if(!attributes_data_json.isArray()) continue; auto body_item = BodyItem::create(std::move(title)); body_item->url = id_json.asString(); auto images_list = std::make_shared(); for(const Json::Value &data_item_json : attributes_data_json) { if(!data_item_json.isString()) continue; std::string url = hash_json.asString() + "/" + data_item_json.asString(); images_list->image_urls.push_back(std::move(url)); } body_item->extra = std::move(images_list); result_items.push_back(std::move(body_item)); } return PluginResult::OK; } PluginResult MangadexSearchPage::submit(const std::string &title, const std::string &url, std::vector &result_tabs) { auto body = create_body(); get_chapters_for_manga(this, url, 0, body->items); result_tabs.push_back(Tab{std::move(body), std::make_unique(program, title, url), 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) { Json::Value json_root; if(download_json(json_root, "https://api.mangadex.org/at-home/server/" + url, {}, true) != DownloadResult::OK) return PluginResult::NET_ERR; if(!json_root.isObject()) return PluginResult::ERR; const Json::Value &base_url_json = json_root["baseUrl"]; if(!base_url_json.isString()) return PluginResult::ERR; std::string base_url = base_url_json.asString(); if(!base_url.empty() && base_url.back() != '/') base_url += '/'; auto image_urls = static_cast(submit_body_item->extra.get())->image_urls; for(auto &image_url : image_urls) { image_url = base_url + "data/" + image_url; } result_tabs.push_back(Tab{nullptr, std::make_unique(program, content_title, title, content_url, 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); } bool MangadexChaptersPage::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) { manga_id = url; return true; } start_index += 6; 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 MangadexImagesPage::get_number_of_images(int &num_images) { num_images = image_urls.size(); return ImageResult::OK; } ImageResult MangadexImagesPage::for_each_page_in_chapter(PageCallback callback) { for(const std::string &url : image_urls) { if(!callback(url)) break; } return ImageResult::OK; } }