#include "../../plugins/NyaaSi.hpp" #include "../../include/Program.h" #include namespace QuickMedia { // Returns empty string on error static std::string get_rss_item_text(const std::string &data, size_t start, size_t end, const std::string &tag_start, const std::string &tag_end) { size_t item_begin = data.find(tag_start, start); if(item_begin == std::string::npos || item_begin >= end) return ""; size_t item_end = data.find(tag_end, item_begin + tag_start.size()); if(item_end == std::string::npos || item_end >= end) return ""; std::string result = data.substr(item_begin + tag_start.size(), item_end - (item_begin + tag_start.size())); html_unescape_sequences(result); return strip(result); } NyaaSi::NyaaSi() : Plugin("nyaa.si") { } NyaaSi::~NyaaSi() { } static std::unique_ptr create_front_page_item(const std::string &title, const std::string &category) { auto body_item = std::make_unique(title); body_item->url = category; return body_item; } PluginResult NyaaSi::get_front_page(BodyItems &result_items) { result_items.push_back(create_front_page_item("All categories", "0_0")); result_items.push_back(create_front_page_item("Anime", "1_0")); result_items.push_back(create_front_page_item(" Anime - Music video", "1_1")); result_items.push_back(create_front_page_item(" Anime - English translated", "1_2")); result_items.push_back(create_front_page_item(" Anime - Non-english translated", "1_3")); result_items.push_back(create_front_page_item(" Anime - Raw", "1_4")); result_items.push_back(create_front_page_item("Audio", "2_0")); result_items.push_back(create_front_page_item(" Audio - Lossless", "2_1")); result_items.push_back(create_front_page_item(" Anime - Lossy", "2_2")); result_items.push_back(create_front_page_item("Literature", "3_0")); result_items.push_back(create_front_page_item(" Literature - English translated", "3_1")); result_items.push_back(create_front_page_item(" Literature - Non-english translated", "3_1")); result_items.push_back(create_front_page_item(" Literature - Raw", "3_3")); result_items.push_back(create_front_page_item("Live Action", "4_0")); result_items.push_back(create_front_page_item(" Live Action - English translated", "4_1")); result_items.push_back(create_front_page_item(" Live Action - Non-english translated", "4_3")); result_items.push_back(create_front_page_item(" Live Action - Idol/Promotional video", "4_2")); result_items.push_back(create_front_page_item(" Live Action - Raw", "4_4")); result_items.push_back(create_front_page_item("Pictures", "5_0")); result_items.push_back(create_front_page_item(" Pictures - Graphics", "5_1")); result_items.push_back(create_front_page_item(" Pictures - Photos", "5_2")); result_items.push_back(create_front_page_item("Software", "6_0")); result_items.push_back(create_front_page_item(" Software - Applications", "6_1")); result_items.push_back(create_front_page_item(" Software - Games", "6_2")); return PluginResult::OK; } SearchResult NyaaSi::content_list_search(const std::string &list_url, const std::string &text, BodyItems &result_items) { std::string full_url = "https://nyaa.si/?page=rss&c=" + list_url + "&f=0&p=1&q="; full_url += url_param_encode(text); std::string website_data; if(download_to_string(full_url, website_data, {}, use_tor) != DownloadResult::OK) return SearchResult::NET_ERR; const std::string title_tag_begin = ""; const std::string title_tag_end = ""; const std::string link_tag_begin = ""; const std::string link_tag_end = ""; const std::string pub_date_tag_begin = ""; const std::string pub_date_tag_end = ""; const std::string seeders_tag_begin = ""; const std::string seeders_tag_end = ""; const std::string leechers_tag_begin = ""; const std::string leechers_tag_end = ""; const std::string downloads_tag_begin = ""; const std::string downloads_tag_end = ""; const std::string category_id_tag_begin = ""; const std::string category_id_tag_end = ""; const std::string size_tag_begin = ""; const std::string size_tag_end = ""; size_t index = 0; while(index < website_data.size()) { size_t item_start = website_data.find("", index); if(item_start == std::string::npos) break; index = item_start + 6; size_t item_end = website_data.find("", index); if(item_end == std::string::npos) return SearchResult::ERR; std::string title = get_rss_item_text(website_data, index, item_end, title_tag_begin, title_tag_end); std::string link = get_rss_item_text(website_data, index, item_end, link_tag_begin, link_tag_end); std::string pub_date = get_rss_item_text(website_data, index, item_end, pub_date_tag_begin, pub_date_tag_end); std::string seeders = get_rss_item_text(website_data, index, item_end, seeders_tag_begin, seeders_tag_end); std::string leechers = get_rss_item_text(website_data, index, item_end, leechers_tag_begin, leechers_tag_end); std::string downloads = get_rss_item_text(website_data, index, item_end, downloads_tag_begin, downloads_tag_end); std::string category_id = get_rss_item_text(website_data, index, item_end, category_id_tag_begin, category_id_tag_end); std::string size = get_rss_item_text(website_data, index, item_end, size_tag_begin, size_tag_end); if(title.empty() || link.empty() || pub_date.empty() || seeders.empty() || leechers.empty() || downloads.empty() || category_id.empty() || size.empty()) { fprintf(stderr, "Error: failed to parse nyaa.si rss items\n"); return SearchResult::ERR; } auto body_item = std::make_unique(std::move(title)); body_item->url = std::move(link); body_item->thumbnail_url = "https://nyaa.si/static/img/icons/nyaa/" + category_id + ".png"; body_item->set_description("Published: " + pub_date + "\nSeeders: " + seeders + "\nLeechers: " + leechers + "\nDownloads: " + downloads + "\nSize: " + size); result_items.push_back(std::move(body_item)); index = item_end + 7; } return SearchResult::OK; } static PluginResult search_result_to_plugin_result(SearchResult search_result) { switch(search_result) { case SearchResult::OK: return PluginResult::OK; case SearchResult::ERR: return PluginResult::ERR; case SearchResult::NET_ERR: return PluginResult::NET_ERR; } return PluginResult::ERR; } PluginResult NyaaSi::get_content_list(const std::string &url, BodyItems &result_items) { return search_result_to_plugin_result(content_list_search(url, "", result_items)); } struct BodyItemImageContext { BodyItems *body_items; size_t index; }; // Returns empty string on error // static std::string view_url_get_id(const std::string &url) { // size_t index = url.rfind('/'); // if(index == std::string::npos) // return ""; // return url.substr(index); // } PluginResult NyaaSi::get_content_details(const std::string &list_url, const std::string &url, BodyItems &result_items) { size_t comments_start_index; // std::string id = view_url_get_id(url); // if(id.empty()) { // fprintf(stderr, "Error: nyaa.si: failed to extract id from url %s\n", url.c_str()); // return PluginResult::ERR; // } // std::string torrent_url = "https://nyaa.si/download/" + id + ".torrent"; // auto torrent_item = std::make_unique("Download torrent"); // torrent_item->url = "https://nyaa.si/download/" + id + ".torrent"; auto torrent_item = std::make_unique("Download magnet"); std::string magnet_url; std::string website_data; if(download_to_string(url, website_data, {}, use_tor, true) != DownloadResult::OK) return PluginResult::NET_ERR; QuickMediaHtmlSearch html_search; int result = quickmedia_html_search_init(&html_search, website_data.c_str()); if(result != 0) goto cleanup; result = quickmedia_html_find_nodes_xpath(&html_search, "//div[class='panel-body']//a", [](QuickMediaHtmlNode *node, void *userdata) { auto *item_data = (BodyItems*)userdata; const char *href = quickmedia_html_node_get_attribute_value(node, "href"); const char *title = quickmedia_html_node_get_attribute_value(node, "title"); const char *text = quickmedia_html_node_get_text(node); if(item_data->empty() && href && title && text && strcmp(title, "User") == 0 && strncmp(href, "/user/", 6) == 0) { auto body_item = std::make_unique("Submitter: " + strip(text)); body_item->url = "https://nyaa.si/" + std::string(href); item_data->push_back(std::move(body_item)); } }, &result_items); if(result != 0) goto cleanup; if(result_items.empty()) { fprintf(stderr, "Error: nyaa.si: failed to get submitter\n"); result = -1; goto cleanup; } result = quickmedia_html_find_nodes_xpath(&html_search, "//div[class='container']//a", [](QuickMediaHtmlNode *node, void *userdata) { std::string *magnet_url = (std::string*)userdata; const char *href = quickmedia_html_node_get_attribute_value(node, "href"); if(magnet_url->empty() && href && strncmp(href, "magnet:?", 8) == 0) { *magnet_url = href; } }, &magnet_url); if(result != 0) goto cleanup; if(magnet_url.empty()) { fprintf(stderr, "Error: nyaa.si: failed to get magnet link\n"); result = -1; goto cleanup; } torrent_item->url = std::move(magnet_url); result_items.push_back(std::move(torrent_item)); comments_start_index = result_items.size(); result = quickmedia_html_find_nodes_xpath(&html_search, "//div[id='comments']//a", [](QuickMediaHtmlNode *node, void *userdata) { auto *item_data = (BodyItems*)userdata; const char *href = quickmedia_html_node_get_attribute_value(node, "href"); const char *title = quickmedia_html_node_get_attribute_value(node, "title"); const char *text = quickmedia_html_node_get_text(node); if(href && title && text && strcmp(title, "User") == 0) { auto body_item = std::make_unique(strip(text)); //body_item->url = "https://nyaa.si/" + std::string(href); item_data->push_back(std::move(body_item)); } }, &result_items); if(result != 0 || result_items.size() == comments_start_index) goto cleanup; BodyItemImageContext body_item_image_context; body_item_image_context.body_items = &result_items; body_item_image_context.index = comments_start_index; result = quickmedia_html_find_nodes_xpath(&html_search, "//div[id='comments']//img[class='avatar']", [](QuickMediaHtmlNode *node, void *userdata) { auto *item_data = (BodyItemImageContext*)userdata; const char *src = quickmedia_html_node_get_attribute_value(node, "src"); if(src && item_data->index < item_data->body_items->size()) { (*item_data->body_items)[item_data->index]->thumbnail_url = src; item_data->index++; } }, &body_item_image_context); if(result != 0) goto cleanup; body_item_image_context.index = comments_start_index; result = quickmedia_html_find_nodes_xpath(&html_search, "//div[id='comments']//div[class='comment-content']", [](QuickMediaHtmlNode *node, void *userdata) { auto *item_data = (BodyItemImageContext*)userdata; const char *text = quickmedia_html_node_get_text(node); if(text && item_data->index < item_data->body_items->size()) { (*item_data->body_items)[item_data->index]->set_description(strip(text)); item_data->index++; } }, &body_item_image_context); cleanup: quickmedia_html_search_deinit(&html_search); if(result != 0) { result_items.clear(); return PluginResult::ERR; } return PluginResult::OK; } }