aboutsummaryrefslogtreecommitdiff
path: root/src/plugins
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
parentde4825e548b990493b372237cbef9a790bf114c4 (diff)
Add MyAnimeList (wip)
Diffstat (limited to 'src/plugins')
-rw-r--r--src/plugins/MangaGeneric.cpp2
-rw-r--r--src/plugins/Mangadex.cpp1
-rw-r--r--src/plugins/Manganelo.cpp2
-rw-r--r--src/plugins/Matrix.cpp13
-rw-r--r--src/plugins/MediaGeneric.cpp10
-rw-r--r--src/plugins/MyAnimeList.cpp311
6 files changed, 328 insertions, 11 deletions
diff --git a/src/plugins/MangaGeneric.cpp b/src/plugins/MangaGeneric.cpp
index 90da0c2..738c6dc 100644
--- a/src/plugins/MangaGeneric.cpp
+++ b/src/plugins/MangaGeneric.cpp
@@ -104,8 +104,10 @@ namespace QuickMedia {
&& field_value.data && (!merge_userdata->field_contains || string_view_contains(field_value, merge_userdata->field_contains)))
{
std::string field_stripped(field_value.data, field_value.size);
+ html_unescape_sequences(field_stripped);
if(merge_userdata->type == MergeType::THUMBNAIL) {
(*body_item_image_context.body_items)[body_item_image_context.index]->thumbnail_url = std::move(field_stripped);
+ (*body_item_image_context.body_items)[body_item_image_context.index]->thumbnail_size = {101, 141};
} else if(merge_userdata->type == MergeType::DESCRIPTION) {
const char *prefix = merge_userdata->desc_prefix ? merge_userdata->desc_prefix : "";
(*body_item_image_context.body_items)[body_item_image_context.index]->set_description(prefix + std::move(field_stripped));
diff --git a/src/plugins/Mangadex.cpp b/src/plugins/Mangadex.cpp
index 98683c1..5b8debd 100644
--- a/src/plugins/Mangadex.cpp
+++ b/src/plugins/Mangadex.cpp
@@ -138,6 +138,7 @@ namespace QuickMedia {
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;
diff --git a/src/plugins/Manganelo.cpp b/src/plugins/Manganelo.cpp
index d3d7bfa..90003cb 100644
--- a/src/plugins/Manganelo.cpp
+++ b/src/plugins/Manganelo.cpp
@@ -139,6 +139,7 @@ namespace QuickMedia {
Json::Value image = child.get("image", "");
if(image.isString() && image.asCString()[0] != '\0')
item->thumbnail_url = image.asString();
+ item->thumbnail_size = {101, 141};
result_items.push_back(std::move(item));
}
}
@@ -228,6 +229,7 @@ namespace QuickMedia {
QuickMediaStringView src = quickmedia_html_node_get_attribute_value(node, "src");
if(src.data && item_data->index < item_data->body_items->size()) {
(*item_data->body_items)[item_data->index]->thumbnail_url.assign(src.data, src.size);
+ (*item_data->body_items)[item_data->index]->thumbnail_size = {101, 141};
item_data->index++;
}
return 0;
diff --git a/src/plugins/Matrix.cpp b/src/plugins/Matrix.cpp
index fce4135..775d172 100644
--- a/src/plugins/Matrix.cpp
+++ b/src/plugins/Matrix.cpp
@@ -1173,6 +1173,7 @@ namespace QuickMedia {
assert(!this->delegate);
assert(!access_token.empty()); // Need to be logged in
+ assert(delegate);
this->delegate = delegate;
Path matrix_cache_dir = get_cache_dir().join("matrix");
@@ -2033,13 +2034,11 @@ namespace QuickMedia {
}
}
- if(delegate) {
- bool cache_sync = sync_is_cache;
- bool is_initial_sync = next_batch.empty();
- ui_thread_tasks.push([this, room_data, cache_sync, new_messages{std::move(new_messages)}, is_initial_sync, message_dir]{
- delegate->room_add_new_messages(room_data, new_messages, is_initial_sync, cache_sync, message_dir);
- });
- }
+ bool cache_sync = sync_is_cache;
+ bool is_initial_sync = next_batch.empty();
+ ui_thread_tasks.push([this, room_data, cache_sync, new_messages{std::move(new_messages)}, is_initial_sync, message_dir]{
+ delegate->room_add_new_messages(room_data, new_messages, is_initial_sync, cache_sync, message_dir);
+ });
return num_new_messages;
}
diff --git a/src/plugins/MediaGeneric.cpp b/src/plugins/MediaGeneric.cpp
index d536a09..bcb8dc3 100644
--- a/src/plugins/MediaGeneric.cpp
+++ b/src/plugins/MediaGeneric.cpp
@@ -37,7 +37,7 @@ namespace QuickMedia {
}
}
- static PluginResult fetch_page_results(const std::string &url, const std::string &website_url, const std::vector<MediaTextQuery> &text_queries, const std::vector<MediaThumbnailQuery> &thumbnail_queries, MediaRelatedCustomHandler *custom_handler, BodyItems &result_items, bool cloudflare_bypass) {
+ static PluginResult fetch_page_results(const std::string &url, const std::string &website_url, const std::vector<MediaTextQuery> &text_queries, const std::vector<MediaThumbnailQuery> &thumbnail_queries, sf::Vector2i thumbnail_max_size, MediaRelatedCustomHandler *custom_handler, BodyItems &result_items, bool cloudflare_bypass) {
std::vector<CommandArg> args;
if(!website_url.empty())
args.push_back({ "-H", "referer: " + website_url });
@@ -55,6 +55,7 @@ namespace QuickMedia {
auto body_item = BodyItem::create(media_related_item.title);
body_item->url = std::move(media_related_item.url);
body_item->thumbnail_url = std::move(media_related_item.thumbnail_url);
+ body_item->thumbnail_size = thumbnail_max_size;
result_items.push_back(std::move(body_item));
}
body_items_prepend_website_url(result_items, website_url);
@@ -92,10 +93,11 @@ namespace QuickMedia {
assert(thumbnail_query.html_query && thumbnail_query.field_name);
if(thumbnail_query.html_query && thumbnail_query.field_name) {
size_t index = 0;
- result = quickmedia_html_find_nodes_xpath(&html_search, thumbnail_query.html_query, [&thumbnail_query, &result_items, &index](QuickMediaMatchNode *node) {
+ result = quickmedia_html_find_nodes_xpath(&html_search, thumbnail_query.html_query, [&thumbnail_query, &result_items, &index, thumbnail_max_size](QuickMediaMatchNode *node) {
QuickMediaStringView field_value = html_attr_or_inner_text(node, thumbnail_query.field_name);
if(index < result_items.size() && field_value.data && (!thumbnail_query.field_contains || string_view_contains(field_value, thumbnail_query.field_contains))) {
result_items[index]->thumbnail_url.assign(field_value.data, field_value.size);
+ result_items[index]->thumbnail_size = thumbnail_max_size;
++index;
}
});
@@ -133,7 +135,7 @@ namespace QuickMedia {
std::string url = search_query.search_template;
string_replace_all(url, "%s", url_param_encode(str));
string_replace_all(url, "%p", std::to_string(search_query.page_start + page));
- return fetch_page_results(url, website_url, text_queries, thumbnail_queries, nullptr, result_items, cloudflare_bypass);
+ return fetch_page_results(url, website_url, text_queries, thumbnail_queries, thumbnail_max_size, nullptr, result_items, cloudflare_bypass);
}
PluginResult MediaGenericSearchPage::submit(const std::string&, const std::string &url, std::vector<Tab> &result_tabs) {
@@ -142,7 +144,7 @@ namespace QuickMedia {
}
PluginResult MediaGenericSearchPage::get_related_media(const std::string &url, BodyItems &result_items) {
- return fetch_page_results(url, website_url, related_media_text_queries, related_media_thumbnail_queries, &related_custom_handler, result_items, cloudflare_bypass);
+ return fetch_page_results(url, website_url, related_media_text_queries, related_media_thumbnail_queries, thumbnail_max_size, &related_custom_handler, result_items, cloudflare_bypass);
}
MediaGenericSearchPage& MediaGenericSearchPage::search_handler(const char *search_template, int page_start) {
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