aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--plugins/AniList.hpp1
-rw-r--r--src/plugins/AniList.cpp307
-rw-r--r--src/plugins/MyAnimeList.cpp22
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;
}