diff options
-rw-r--r-- | plugins/AniList.hpp | 1 | ||||
-rw-r--r-- | src/plugins/AniList.cpp | 307 | ||||
-rw-r--r-- | src/plugins/MyAnimeList.cpp | 22 |
3 files changed, 307 insertions, 23 deletions
diff --git a/plugins/AniList.hpp b/plugins/AniList.hpp index 3d71f5c..c877b8e 100644 --- a/plugins/AniList.hpp +++ b/plugins/AniList.hpp @@ -45,6 +45,7 @@ namespace QuickMedia { bool submit_is_async() const override { return false; } PluginResult submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) override; PluginResult lazy_fetch(BodyItems &result_items) override; + PluginResult get_page(const std::string &str, int page, BodyItems &result_items) override; private: std::string id; }; diff --git a/src/plugins/AniList.cpp b/src/plugins/AniList.cpp index 5bf069c..c4286e0 100644 --- a/src/plugins/AniList.cpp +++ b/src/plugins/AniList.cpp @@ -75,6 +75,83 @@ query ($id: Int) { } )END"; + static const std::string related_query_graphql = R"END( +query ($id: Int) { + Media (id: $id) { + relations { + edges { + relationType (version: 2) + node { + id + type + format + status (version: 2) + startDate { + year + month + day + } + endDate { + year + month + day + } + averageScore + episodes + chapters + genres + coverImage { + medium + } + title { + romaji + } + synonyms + } + } + } + } +} + )END"; + + static const std::string recommendations_query_graphql = R"END( +query ($id: Int, $page: Int, $perPage: Int) { + Media (id: $id) { + recommendations (page: $page, perPage: $perPage) { + nodes { + mediaRecommendation { + id + type + format + status (version: 2) + startDate { + year + month + day + } + endDate { + year + month + day + } + averageScore + episodes + chapters + genres + coverImage { + medium + } + title { + romaji + } + synonyms + } + } + } + } +} + )END"; + enum class AniListMediaType { ANIME, MANGA @@ -325,6 +402,36 @@ query ($id: Int) { return body_item; } + static const char* media_relation_to_readable(const char *media_relation) { + if(strcmp(media_relation, "ADAPTATION") == 0) + return "Adaption"; + if(strcmp(media_relation, "PREQUEL") == 0) + return "Prequel"; + if(strcmp(media_relation, "SEQUEL") == 0) + return "Sequel"; + if(strcmp(media_relation, "PARENT") == 0) + return "Parent"; + if(strcmp(media_relation, "SIDE_STORY") == 0) + return "Side story"; + if(strcmp(media_relation, "CHARACTER") == 0) + return "Character"; + if(strcmp(media_relation, "SUMMARY") == 0) + return "Summary"; + if(strcmp(media_relation, "ALTERNATIVE") == 0) + return "Alternative"; + if(strcmp(media_relation, "SPIN_OFF") == 0) + return "Spin-off"; + if(strcmp(media_relation, "OTHER") == 0) + return "Other"; + if(strcmp(media_relation, "SOURCE") == 0) + return "Source"; + if(strcmp(media_relation, "COMPILATION") == 0) + return "Compilation"; + if(strcmp(media_relation, "CONTAINS") == 0) + return "Contains"; + return media_relation; + } + SearchResult AniListSearchPage::search_page(const std::string &str, int page, BodyItems &result_items) { Json::Value variables_json(Json::objectValue); variables_json["page"] = page; @@ -391,18 +498,19 @@ query ($id: Int) { manga_items.push_back(std::move(body_item)); } - if(anime_items.empty() && manga_items.empty()) - return SearchResult::OK; - - auto anime_title_item = BodyItem::create(""); - anime_title_item->set_author("------------------------ Anime ------------------------"); - result_items.push_back(std::move(anime_title_item)); - result_items.insert(result_items.end(), std::move_iterator(anime_items.begin()), std::move_iterator(anime_items.end())); + if(!anime_items.empty()) { + auto anime_title_item = BodyItem::create(""); + anime_title_item->set_author("------------------------ Anime ------------------------"); + result_items.push_back(std::move(anime_title_item)); + result_items.insert(result_items.end(), std::move_iterator(anime_items.begin()), std::move_iterator(anime_items.end())); + } - auto manga_title_item = BodyItem::create(""); - manga_title_item->set_author("------------------------ Manga ------------------------"); - result_items.push_back(std::move(manga_title_item)); - result_items.insert(result_items.end(), std::move_iterator(manga_items.begin()), std::move_iterator(manga_items.end())); + if(!manga_items.empty()) { + auto manga_title_item = BodyItem::create(""); + manga_title_item->set_author("------------------------ Manga ------------------------"); + result_items.push_back(std::move(manga_title_item)); + result_items.insert(result_items.end(), std::move_iterator(manga_items.begin()), std::move_iterator(manga_items.end())); + } return SearchResult::OK; } @@ -415,8 +523,8 @@ query ($id: Int) { if(url.empty()) return PluginResult::OK; - result_tabs.push_back({ create_body(), std::make_unique<AniListRelatedPage>(program, url), nullptr }); result_tabs.push_back({ create_body(), std::make_unique<AniListDetailsPage>(program, url), nullptr }); + result_tabs.push_back({ create_body(), std::make_unique<AniListRelatedPage>(program, url), nullptr }); result_tabs.push_back({ create_body(), std::make_unique<AniListRecommendationsPage>(program, url), nullptr }); return PluginResult::OK; } @@ -429,13 +537,94 @@ query ($id: Int) { if(url.empty()) return PluginResult::OK; - result_tabs.push_back({ create_body(), std::make_unique<AniListRelatedPage>(program, url), nullptr }); result_tabs.push_back({ create_body(), std::make_unique<AniListDetailsPage>(program, url), nullptr }); + result_tabs.push_back({ create_body(), std::make_unique<AniListRelatedPage>(program, url), nullptr }); result_tabs.push_back({ create_body(), std::make_unique<AniListRecommendationsPage>(program, url), nullptr }); return PluginResult::OK; } PluginResult AniListRelatedPage::lazy_fetch(BodyItems &result_items) { + Json::Value variables_json(Json::objectValue); + variables_json["id"] = atoi(id.c_str()); + + Json::Value request_json(Json::objectValue); + request_json["query"] = related_query_graphql; + request_json["variables"] = std::move(variables_json); + + Json::StreamWriterBuilder json_builder; + json_builder["commentStyle"] = "None"; + json_builder["indentation"] = ""; + + std::vector<CommandArg> additional_args = { + { "-X", "POST" }, + { "-H", "content-type: application/json" }, + { "--data-binary", Json::writeString(json_builder, request_json) } + }; + + Json::Value json_root; + std::string err_msg; + DownloadResult download_result = download_json(json_root, "https://graphql.anilist.co", std::move(additional_args), true, &err_msg); + if(download_result != DownloadResult::OK) return download_result_to_plugin_result(download_result); + + if(!json_root.isObject()) + return PluginResult::ERR; + + const Json::Value &errors_json = json_root["errors"]; + if(errors_json.isArray() && errors_json.size() > 0) { + const Json::Value &error_json = errors_json[0]; + if(error_json.isObject()) { + const Json::Value &error_message_json = error_json["message"]; + if(error_message_json.isString()) { + show_notification("QuickMedia", "AniList search failed, error: " + error_message_json.asString(), Urgency::CRITICAL); + return PluginResult::ERR; + } + } + } + + const Json::Value &data_json = json_root["data"]; + if(!data_json.isObject()) + return PluginResult::ERR; + + const Json::Value &media_json = data_json["Media"]; + if(!media_json.isObject()) + return PluginResult::ERR; + + const Json::Value &relations_json = media_json["relations"]; + if(!relations_json.isObject()) + return PluginResult::ERR; + + const Json::Value &edges_json = relations_json["edges"]; + if(!edges_json.isArray()) + return PluginResult::ERR; + + std::map<std::string, BodyItems> body_items_by_relation_type; + for(const Json::Value &edge_json : edges_json) { + if(!edge_json.isObject()) + continue; + + const Json::Value &relation_type_json = edge_json["relationType"]; + if(!relation_type_json.isString()) + continue; + + AniListMediaType media_type; + auto body_item = media_json_to_body_item(edge_json["node"], ThumbnailSize::MEDIUM, media_type); + if(!body_item) + continue; + + const char *relation_type_readable = media_relation_to_readable(relation_type_json.asCString()); + body_items_by_relation_type[relation_type_readable].push_back(std::move(body_item)); + } + + for(auto &it : body_items_by_relation_type) { + if(it.second.empty()) + continue; + + auto anime_title_item = BodyItem::create(""); + anime_title_item->set_author("------------------------ " + it.first + " ------------------------"); + result_items.push_back(std::move(anime_title_item)); + result_items.insert(result_items.end(), std::move_iterator(it.second.begin()), std::move_iterator(it.second.end())); + } + return PluginResult::OK; } @@ -502,13 +691,103 @@ query ($id: Int) { if(url.empty()) return PluginResult::OK; - result_tabs.push_back({ create_body(), std::make_unique<AniListRelatedPage>(program, url), nullptr }); result_tabs.push_back({ create_body(), std::make_unique<AniListDetailsPage>(program, url), nullptr }); + result_tabs.push_back({ create_body(), std::make_unique<AniListRelatedPage>(program, url), nullptr }); result_tabs.push_back({ create_body(), std::make_unique<AniListRecommendationsPage>(program, url), nullptr }); return PluginResult::OK; } PluginResult AniListRecommendationsPage::lazy_fetch(BodyItems &result_items) { + return get_page("", 0, result_items); + } + + PluginResult AniListRecommendationsPage::get_page(const std::string&, int page, BodyItems &result_items) { + Json::Value variables_json(Json::objectValue); + variables_json["page"] = 1 + page; + variables_json["perPage"] = 20; + variables_json["id"] = atoi(id.c_str()); + + Json::Value request_json(Json::objectValue); + request_json["query"] = recommendations_query_graphql; + request_json["variables"] = std::move(variables_json); + + Json::StreamWriterBuilder json_builder; + json_builder["commentStyle"] = "None"; + json_builder["indentation"] = ""; + + std::vector<CommandArg> additional_args = { + { "-X", "POST" }, + { "-H", "content-type: application/json" }, + { "--data-binary", Json::writeString(json_builder, request_json) } + }; + + Json::Value json_root; + std::string err_msg; + DownloadResult download_result = download_json(json_root, "https://graphql.anilist.co", std::move(additional_args), true, &err_msg); + if(download_result != DownloadResult::OK) return download_result_to_plugin_result(download_result); + + if(!json_root.isObject()) + return PluginResult::ERR; + + const Json::Value &errors_json = json_root["errors"]; + if(errors_json.isArray() && errors_json.size() > 0) { + const Json::Value &error_json = errors_json[0]; + if(error_json.isObject()) { + const Json::Value &error_message_json = error_json["message"]; + if(error_message_json.isString()) { + show_notification("QuickMedia", "AniList search failed, error: " + error_message_json.asString(), Urgency::CRITICAL); + return PluginResult::ERR; + } + } + } + + const Json::Value &data_json = json_root["data"]; + if(!data_json.isObject()) + return PluginResult::ERR; + + const Json::Value &media_json = data_json["Media"]; + if(!media_json.isObject()) + return PluginResult::ERR; + + const Json::Value &recommendations_json = media_json["recommendations"]; + if(!recommendations_json.isObject()) + return PluginResult::ERR; + + const Json::Value &nodes_json = recommendations_json["nodes"]; + if(!nodes_json.isArray()) + return PluginResult::ERR; + + BodyItems anime_items; + BodyItems manga_items; + for(const Json::Value &node_json : nodes_json) { + if(!node_json.isObject()) + continue; + + AniListMediaType media_type; + auto body_item = media_json_to_body_item(node_json["mediaRecommendation"], ThumbnailSize::MEDIUM, media_type); + if(!body_item) + continue; + + if(media_type == AniListMediaType::ANIME) + anime_items.push_back(std::move(body_item)); + else if(media_type == AniListMediaType::MANGA) + manga_items.push_back(std::move(body_item)); + } + + if(!anime_items.empty()) { + auto anime_title_item = BodyItem::create(""); + anime_title_item->set_author("------------------------ Anime ------------------------"); + result_items.push_back(std::move(anime_title_item)); + result_items.insert(result_items.end(), std::move_iterator(anime_items.begin()), std::move_iterator(anime_items.end())); + } + + if(!manga_items.empty()) { + auto manga_title_item = BodyItem::create(""); + manga_title_item->set_author("------------------------ Manga ------------------------"); + result_items.push_back(std::move(manga_title_item)); + result_items.insert(result_items.end(), std::move_iterator(manga_items.begin()), std::move_iterator(manga_items.end())); + } + return PluginResult::OK; } }
\ No newline at end of file diff --git a/src/plugins/MyAnimeList.cpp b/src/plugins/MyAnimeList.cpp index dd80297..5f5ae40 100644 --- a/src/plugins/MyAnimeList.cpp +++ b/src/plugins/MyAnimeList.cpp @@ -132,15 +132,19 @@ namespace QuickMedia { } } - auto anime_title_item = BodyItem::create(""); - anime_title_item->set_author("------------------------ Anime ------------------------"); - result_items.push_back(std::move(anime_title_item)); - result_items.insert(result_items.end(), std::move_iterator(anime_items.begin()), std::move_iterator(anime_items.end())); - - auto manga_title_item = BodyItem::create(""); - manga_title_item->set_author("------------------------ Manga ------------------------"); - result_items.push_back(std::move(manga_title_item)); - result_items.insert(result_items.end(), std::move_iterator(manga_items.begin()), std::move_iterator(manga_items.end())); + if(!anime_items.empty()) { + auto anime_title_item = BodyItem::create(""); + anime_title_item->set_author("------------------------ Anime ------------------------"); + result_items.push_back(std::move(anime_title_item)); + result_items.insert(result_items.end(), std::move_iterator(anime_items.begin()), std::move_iterator(anime_items.end())); + } + + if(!manga_items.empty()) { + auto manga_title_item = BodyItem::create(""); + manga_title_item->set_author("------------------------ Manga ------------------------"); + result_items.push_back(std::move(manga_title_item)); + result_items.insert(result_items.end(), std::move_iterator(manga_items.begin()), std::move_iterator(manga_items.end())); + } return SearchResult::OK; } |