aboutsummaryrefslogtreecommitdiff
path: root/src/plugins/MyAnimeList.cpp
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2021-08-16 21:13:24 +0200
committerdec05eba <dec05eba@protonmail.com>2021-08-16 21:13:24 +0200
commit5cc735b22570f1667d62958e59ce4910b529f5af (patch)
tree75128a8926a48a612bc892d266032bd7afd9c2cf /src/plugins/MyAnimeList.cpp
parentde4825e548b990493b372237cbef9a790bf114c4 (diff)
Add MyAnimeList (wip)
Diffstat (limited to 'src/plugins/MyAnimeList.cpp')
-rw-r--r--src/plugins/MyAnimeList.cpp311
1 files changed, 311 insertions, 0 deletions
diff --git a/src/plugins/MyAnimeList.cpp b/src/plugins/MyAnimeList.cpp
new file mode 100644
index 0000000..dd80297
--- /dev/null
+++ b/src/plugins/MyAnimeList.cpp
@@ -0,0 +1,311 @@
+#include "../../plugins/MyAnimeList.hpp"
+#include "../../include/Theme.hpp"
+#include "../../include/NetUtils.hpp"
+#include "../../include/StringUtils.hpp"
+#include <quickmedia/HtmlSearch.h>
+
+namespace QuickMedia {
+ // Returns {0, 0} if unknown
+ static sf::Vector2i thumbnail_url_get_resolution(const std::string &url) {
+ const size_t index = url.find("/r/");
+ if(index == std::string::npos)
+ return {0, 0};
+
+ const size_t width_index = index + 3;
+ const size_t x_index = url.find('x', width_index);
+ if(x_index == std::string::npos)
+ return {0, 0};
+
+ const size_t height_index = x_index + 1;
+ const size_t size_end_index = url.find('/', height_index);
+ if(size_end_index == std::string::npos)
+ return {0, 0};
+
+ sf::Vector2i size;
+ if(!to_num(url.c_str() + width_index, (x_index - width_index), size.x) || !to_num(url.c_str() + height_index, (size_end_index - height_index), size.y))
+ return {0, 0};
+
+ return size;
+ }
+
+ static std::shared_ptr<BodyItem> search_item_to_body_item(const Json::Value &search_item_json) {
+ if(!search_item_json.isObject())
+ return nullptr;
+
+ const Json::Value &name_json = search_item_json["name"];
+ const Json::Value &url_json = search_item_json["url"];
+ const Json::Value &payload_json = search_item_json["payload"];
+ if(!name_json.isString() || !url_json.isString() || !payload_json.isObject())
+ return nullptr;
+
+ std::string name = name_json.asString();
+ const Json::Value &media_type_json = payload_json["media_type"];
+ if(media_type_json.isString())
+ name += " (" + media_type_json.asString() + ")";
+
+ auto body_item = BodyItem::create("");
+ body_item->url = url_json.asString();
+ body_item->set_author(std::move(name));
+
+ const Json::Value &image_url_json = search_item_json["thumbnail_url"];
+ body_item->thumbnail_size = {116, 76};
+ if(image_url_json.isString()) {
+ body_item->thumbnail_url = image_url_json.asString();
+ body_item->thumbnail_size = thumbnail_url_get_resolution(body_item->thumbnail_url);
+ }
+
+ std::string description;
+ const Json::Value &aired_json = payload_json["aired"];
+ if(aired_json.isString()) {
+ if(!description.empty())
+ description += '\n';
+ description += "Aired: " + aired_json.asString();
+ }
+
+ const Json::Value &published_json = payload_json["published"];
+ if(published_json.isString()) {
+ if(!description.empty())
+ description += '\n';
+ description += "Published: " + published_json.asString();
+ }
+
+ const Json::Value &score_json = payload_json["score"];
+ if(score_json.isString()) {
+ if(!description.empty())
+ description += '\n';
+ description += "Score: " + score_json.asString();
+ }
+
+ const Json::Value &status_json = payload_json["status"];
+ if(status_json.isString()) {
+ if(!description.empty())
+ description += '\n';
+ description += "Status: " + status_json.asString();
+ }
+
+ if(!description.empty()) {
+ body_item->set_description(std::move(description));
+ body_item->set_description_color(get_current_theme().faded_text_color);
+ }
+
+ return body_item;
+ }
+
+ SearchResult MyAnimeListSearchPage::search(const std::string &str, BodyItems &result_items) {
+ std::string url = "https://myanimelist.net/search/prefix.json?type=all&keyword=";
+ url += url_param_encode(str) + "&v=1";
+
+ Json::Value json_root;
+ DownloadResult download_result = download_json(json_root, url, {}, true);
+ if(download_result != DownloadResult::OK) return download_result_to_search_result(download_result);
+
+ if(!json_root.isObject())
+ return SearchResult::ERR;
+
+ const Json::Value &categories_json = json_root["categories"];
+ if(!categories_json.isArray())
+ return SearchResult::ERR;
+
+ BodyItems anime_items;
+ BodyItems manga_items;
+ for(const Json::Value &category_json : categories_json) {
+ if(!category_json.isObject())
+ continue;
+
+ const Json::Value &type_json = category_json["type"];
+ const Json::Value &items_json = category_json["items"];
+ if(!type_json.isString() || !items_json.isArray())
+ continue;
+
+ if(strcmp(type_json.asCString(), "anime") == 0) {
+ for(const Json::Value &item_json : items_json) {
+ auto body_item = search_item_to_body_item(item_json);
+ if(body_item)
+ anime_items.push_back(std::move(body_item));
+ }
+ } else if(strcmp(type_json.asCString(), "manga") == 0) {
+ for(const Json::Value &item_json : items_json) {
+ auto body_item = search_item_to_body_item(item_json);
+ if(body_item)
+ manga_items.push_back(std::move(body_item));
+ }
+ }
+ }
+
+ 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()));
+
+ return SearchResult::OK;
+ }
+
+ PluginResult MyAnimeListSearchPage::submit(const std::string&, const std::string &url, std::vector<Tab> &result_tabs) {
+ if(url.empty())
+ return PluginResult::OK;
+
+ result_tabs.push_back({ create_body(), std::make_unique<MyAnimeListDetailsPage>(program, url), nullptr });
+ result_tabs.push_back({ create_body(false, true), std::make_unique<MyAnimeListRecommendationsPage>(program, url), nullptr });
+ return PluginResult::OK;
+ }
+
+ PluginResult MyAnimeListDetailsPage::submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) {
+ return PluginResult::OK;
+ }
+
+ PluginResult MyAnimeListDetailsPage::lazy_fetch(BodyItems &result_items) {
+ std::string website_data;
+ DownloadResult download_result = download_to_string(url, website_data, {}, true);
+ if(download_result != DownloadResult::OK) return download_result_to_plugin_result(download_result);
+
+ std::string thumbnail_url;
+ std::string description;
+
+ QuickMediaHtmlSearch html_search;
+ int result = quickmedia_html_search_init(&html_search, website_data.c_str(), website_data.size());
+ if(result != 0)
+ return PluginResult::ERR;
+
+ quickmedia_html_find_nodes_xpath(&html_search, "//img[itemprop='image']",
+ [](QuickMediaMatchNode *node, void *userdata) {
+ std::string *thumbnail_url = (std::string*)userdata;
+ QuickMediaStringView data_src = quickmedia_html_node_get_attribute_value(node, "data-src");
+ if(data_src.data) {
+ thumbnail_url->assign(data_src.data, data_src.size);
+ html_unescape_sequences(*thumbnail_url);
+ }
+ return 1;
+ }, &thumbnail_url);
+
+ quickmedia_html_find_nodes_xpath(&html_search, "//p[itemprop='description']",
+ [](QuickMediaMatchNode *node, void *userdata) {
+ std::string *description = (std::string*)userdata;
+ QuickMediaStringView text = quickmedia_html_node_get_text(node);
+ if(text.data) {
+ *description = "Synopsis:\n";
+ description->append(text.data, text.size);
+ html_unescape_sequences(*description);
+ }
+ return 1;
+ }, &description);
+
+ quickmedia_html_search_deinit(&html_search);
+
+ auto synopsis_body_item = BodyItem::create("");
+ synopsis_body_item->set_description(std::move(description));
+ synopsis_body_item->thumbnail_url = std::move(thumbnail_url);
+ synopsis_body_item->thumbnail_size = {225, 337};
+ result_items.push_back(std::move(synopsis_body_item));
+
+ return PluginResult::OK;
+ }
+
+ PluginResult MyAnimeListRecommendationsPage::submit(const std::string&, const std::string &url, std::vector<Tab> &result_tabs) {
+ result_tabs.push_back({ create_body(), std::make_unique<MyAnimeListDetailsPage>(program, url), nullptr });
+ result_tabs.push_back({ create_body(false, true), std::make_unique<MyAnimeListRecommendationsPage>(program, url), nullptr });
+ return PluginResult::OK;
+ }
+
+ static std::string img_alt_get_title(const std::string &alt) {
+ size_t index = alt.find(':');
+ if(index == std::string::npos)
+ return alt;
+ return alt.substr(index + 2);
+ }
+
+ static QuickMediaStringView quickmedia_html_node_get_attribute_value(QuickMediaHtmlNode *node, const char *attribute_name) {
+ QuickMediaMatchNode match_node;
+ match_node.node = node;
+ return quickmedia_html_node_get_attribute_value(&match_node, attribute_name);
+ }
+
+ PluginResult MyAnimeListRecommendationsPage::lazy_fetch(BodyItems &result_items) {
+ std::string website_data;
+ DownloadResult download_result = download_to_string(url + "/userrecs", website_data, {}, true);
+ if(download_result != DownloadResult::OK) return download_result_to_plugin_result(download_result);
+
+ QuickMediaHtmlSearch html_search;
+ int result = quickmedia_html_search_init(&html_search, website_data.c_str(), website_data.size());
+ if(result != 0)
+ return PluginResult::ERR;
+
+ quickmedia_html_find_nodes_xpath(&html_search, "//div[class='picSurround']/a",
+ [](QuickMediaMatchNode *node, void *userdata) {
+ BodyItems *result_items = (BodyItems*)userdata;
+ QuickMediaStringView href = quickmedia_html_node_get_attribute_value(node, "href");
+ if(!href.data)
+ return 0;
+
+ auto body_item = BodyItem::create("");
+ body_item->url.assign(href.data, href.size);
+ html_unescape_sequences(body_item->url);
+ result_items->push_back(body_item);
+ if(!node->node->first_child || !node->node->first_child->node.name.data || node->node->first_child->node.name.size != 3 || memcmp(node->node->first_child->node.name.data, "img", 3) != 0)
+ return 0;
+
+ QuickMediaStringView data_src = quickmedia_html_node_get_attribute_value(&node->node->first_child->node, "data-src");
+ QuickMediaStringView alt = quickmedia_html_node_get_attribute_value(&node->node->first_child->node, "alt");
+ if(data_src.data && alt.data) {
+ std::string title = img_alt_get_title(std::string(alt.data, alt.size));
+ html_unescape_sequences(title);
+ body_item->set_author(std::move(title));
+
+ body_item->thumbnail_url.assign(data_src.data, data_src.size);
+ html_unescape_sequences(body_item->thumbnail_url);
+
+ if(body_item->thumbnail_url.empty())
+ body_item->thumbnail_size = {50, 70};
+ else
+ body_item->thumbnail_size = thumbnail_url_get_resolution(body_item->thumbnail_url);
+ }
+ return 0;
+ }, &result_items);
+
+ BodyItemContext body_item_image_context;
+ body_item_image_context.body_items = &result_items;
+ body_item_image_context.index = 0;
+
+ // TODO: Fix, incorrect descriptions!
+ #if 0
+ quickmedia_html_find_nodes_xpath(&html_search, "//div[class='*detail-user-recs-text']",
+ [](QuickMediaMatchNode *node, void *userdata) {
+ BodyItemContext *item_data = (BodyItemContext*)userdata;
+ QuickMediaStringView text = quickmedia_html_node_get_text(node);
+ if(text.data && item_data->index < item_data->body_items->size()) {
+ std::string description(text.data, text.size);
+ html_unescape_sequences(description);
+ (*item_data->body_items)[item_data->index]->set_description(std::move(description));
+ (*item_data->body_items)[item_data->index]->set_description_color(get_current_theme().faded_text_color);
+ item_data->index++;
+ }
+ return 0;
+ }, &body_item_image_context);
+ #endif
+
+ /*
+ quickmedia_html_find_nodes_xpath(&html_search, "//div[class='borderClass']//div[class='*detail-user-recs-text']",
+ [](QuickMediaMatchNode *node, void *userdata) {
+ BodyItems *result_items = (BodyItems*)userdata;
+ QuickMediaStringView text = quickmedia_html_node_get_text(node);
+ if(text.data) {
+ std::string description(text.data, text.size);
+ html_unescape_sequences(description);
+
+ auto body_item = BodyItem::create("");
+ body_item->set_description(std::move(description));
+ body_item->set_description_color(get_current_theme().faded_text_color);
+ result_items->push_back(std::move(body_item));
+ }
+ return 0;
+ }, &result_items);
+ */
+
+ quickmedia_html_search_deinit(&html_search);
+ return PluginResult::OK;
+ }
+} \ No newline at end of file