aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2020-09-18 01:10:18 +0200
committerdec05eba <dec05eba@protonmail.com>2020-09-18 17:07:47 +0200
commit12c59fddcf1201536c3bee8db4e0d6ac8964fde6 (patch)
treeeeb7b5dcd1aa24599fb73743a840547c8d8035c8 /src
parent94d217c957183844c6267fa34d6b5778123f88f8 (diff)
Initial nyaa.si support
Diffstat (limited to 'src')
-rw-r--r--src/Body.cpp2
-rw-r--r--src/QuickMedia.cpp74
-rw-r--r--src/plugins/Manganelo.cpp2
-rw-r--r--src/plugins/NyaaSi.cpp272
-rw-r--r--src/plugins/Plugin.cpp7
5 files changed, 342 insertions, 15 deletions
diff --git a/src/Body.cpp b/src/Body.cpp
index 2233f92..fd279a8 100644
--- a/src/Body.cpp
+++ b/src/Body.cpp
@@ -211,7 +211,7 @@ namespace QuickMedia {
if(body_item->title_text)
body_item->title_text->setString(body_item->get_title());
else
- body_item->title_text = std::make_unique<Text>(body_item->get_title(), font, 14, size.x - 50 - image_padding_x * 2.0f);
+ body_item->title_text = std::make_unique<Text>(body_item->get_title(), font, 16, size.x - 50 - image_padding_x * 2.0f);
body_item->title_text->updateGeometry();
}
diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp
index f5aaea5..01a82c6 100644
--- a/src/QuickMedia.cpp
+++ b/src/QuickMedia.cpp
@@ -6,6 +6,7 @@
#include "../plugins/Pornhub.hpp"
#include "../plugins/Fourchan.hpp"
#include "../plugins/Dmenu.hpp"
+#include "../plugins/NyaaSi.hpp"
#include "../include/Scale.hpp"
#include "../include/Program.h"
#include "../include/VideoPlayer.hpp"
@@ -161,9 +162,9 @@ namespace QuickMedia {
if (!disp)
throw std::runtime_error("Failed to open display to X11 server");
- resources_root = "../../../";
- if(get_file_type("/usr/share/quickmedia/") == FileType::DIRECTORY) {
- resources_root = "/usr/share/quickmedia/";
+ resources_root = "/usr/share/quickmedia/";
+ if(get_file_type("../../../images/manganelo_logo.png") == FileType::REGULAR) {
+ resources_root = "../../../";
}
if(!font.loadFromFile(resources_root + "fonts/Lato-Regular.ttf")) {
@@ -236,7 +237,7 @@ namespace QuickMedia {
static void usage() {
fprintf(stderr, "usage: QuickMedia <plugin> [--tor] [--use-system-mpv-config] [-p placeholder-text]\n");
fprintf(stderr, "OPTIONS:\n");
- fprintf(stderr, " plugin The plugin to use. Should be either 4chan, manganelo, mangatown, mangadex, pornhub, youtube or dmenu\n");
+ fprintf(stderr, " plugin The plugin to use. Should be either 4chan, manganelo, mangatown, mangadex, pornhub, youtube, nyaa.si or dmenu\n");
fprintf(stderr, " --tor Use tor. Disabled by default\n");
fprintf(stderr, " --use-system-mpv-config Use system mpv config instead of no config. Disabled by default\n");
fprintf(stderr, " --upscale-images Upscale low-resolution manga pages using waifu2x-ncnn-vulkan. Disabled by default\n");
@@ -296,6 +297,9 @@ namespace QuickMedia {
} else if(strcmp(argv[i], "4chan") == 0) {
current_plugin = new Fourchan(resources_root);
plugin_logo_path = resources_root + "images/4chan_logo.png";
+ } else if(strcmp(argv[i], "nyaa.si") == 0) {
+ current_plugin = new NyaaSi();
+ plugin_logo_path = resources_root + "images/nyaa_si_logo.png";
} else if(strcmp(argv[i], "dmenu") == 0) {
current_plugin = new Dmenu();
} else {
@@ -827,7 +831,7 @@ namespace QuickMedia {
return false;
Page next_page = current_plugin->get_page_after_search();
- bool skip_search = next_page == Page::VIDEO_CONTENT;
+ bool skip_search = (next_page == Page::VIDEO_CONTENT || next_page == Page::CONTENT_LIST);
// TODO: This shouldn't be done if search_selected_suggestion fails
if(search_selected_suggestion(input_body, output_body, current_plugin, content_title, content_url, skip_search) != SearchResult::OK) {
show_notification("Search", "Search failed!", Urgency::CRITICAL);
@@ -2291,15 +2295,28 @@ namespace QuickMedia {
}
void Program::content_list_page() {
+ std::string update_search_text;
+ bool search_running = false;
+ std::future<BodyItems> search_future;
+
+ if(!current_plugin->content_list_search_is_filter())
+ search_bar->text_autosearch_delay = current_plugin->get_content_list_search_delay();
+
+ body->clear_items();
+ body->clear_thumbnails();
if(current_plugin->get_content_list(content_list_url, body->items) != PluginResult::OK) {
show_notification("Content list", "Failed to get content list for url: " + content_list_url, Urgency::CRITICAL);
current_page = Page::SEARCH_SUGGESTION;
return;
}
- search_bar->onTextUpdateCallback = [this](const std::string &text) {
- body->filter_search_fuzzy(text);
- body->select_first_item();
+ search_bar->onTextUpdateCallback = [this, &update_search_text](const std::string &text) {
+ if(current_plugin->content_list_search_is_filter()) {
+ body->filter_search_fuzzy(text);
+ body->clamp_selection();
+ } else {
+ update_search_text = text;
+ }
};
search_bar->onTextSubmitCallback = [this](const std::string &text) -> bool {
@@ -2335,11 +2352,35 @@ namespace QuickMedia {
search_bar->update();
+ if(!update_search_text.empty() && !search_running) {
+ search_future = std::async(std::launch::async, [this, update_search_text]() {
+ BodyItems result;
+ if(current_plugin->content_list_search(content_list_url, update_search_text, result) != SearchResult::OK) {
+ show_notification("Search", "Search failed!", Urgency::CRITICAL);
+ }
+ return result;
+ });
+ update_search_text.clear();
+ search_running = true;
+ }
+
+ if(search_running && search_future.valid() && search_future.wait_for(std::chrono::seconds(0)) == std::future_status::ready) {
+ if(update_search_text.empty()) {
+ body->items = search_future.get();
+ body->clamp_selection();
+ } else {
+ search_future.get();
+ }
+ search_running = false;
+ }
+
window.clear(back_color);
body->draw(window, body_pos, body_size);
search_bar->draw(window);
window.display();
}
+
+ search_bar->text_autosearch_delay = current_plugin->get_search_delay();
}
void Program::content_details_page() {
@@ -2352,15 +2393,22 @@ namespace QuickMedia {
return;
}
- // Instead of using search bar to searching, use it for commenting.
// TODO: Have an option for the search bar to be multi-line.
search_bar->onTextUpdateCallback = nullptr;
search_bar->onTextSubmitCallback = [this](const std::string &text) -> bool {
- if(text.empty())
- return false;
-
- return true;
+ if(current_plugin->name == "nyaa.si") {
+ BodyItem *selected_item = body->get_selected();
+ if(selected_item && strncmp(selected_item->url.c_str(), "magnet:?", 8) == 0) {
+ if(!is_program_executable_by_name("xdg-open")) {
+ show_notification("Nyaa.si", "xdg-utils which provides xdg-open needs to be installed to download torrents", Urgency::CRITICAL);
+ return false;
+ }
+ const char *args[] = { "xdg-open", selected_item->url.c_str(), nullptr };
+ exec_program_async(args, nullptr);
+ }
+ }
+ return false;
};
sf::Vector2f body_pos;
diff --git a/src/plugins/Manganelo.cpp b/src/plugins/Manganelo.cpp
index a772601..6363991 100644
--- a/src/plugins/Manganelo.cpp
+++ b/src/plugins/Manganelo.cpp
@@ -33,7 +33,7 @@ namespace QuickMedia {
}
}, &result_items);
- result = quickmedia_html_find_nodes_xpath(&html_search, "//a[class='a-h']",
+ quickmedia_html_find_nodes_xpath(&html_search, "//a[class='a-h']",
[](QuickMediaHtmlNode *node, void *userdata) {
std::vector<Creator> *creators = (std::vector<Creator>*)userdata;
const char *href = quickmedia_html_node_get_attribute_value(node, "href");
diff --git a/src/plugins/NyaaSi.cpp b/src/plugins/NyaaSi.cpp
new file mode 100644
index 0000000..18dd53a
--- /dev/null
+++ b/src/plugins/NyaaSi.cpp
@@ -0,0 +1,272 @@
+#include "../../plugins/NyaaSi.hpp"
+#include "../../include/Program.h"
+#include <quickmedia/HtmlSearch.h>
+
+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<BodyItem> create_front_page_item(const std::string &title, const std::string &category) {
+ auto body_item = std::make_unique<BodyItem>(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 = "<title>";
+ const std::string title_tag_end = "</title>";
+ const std::string link_tag_begin = "<guid isPermaLink=\"true\">";
+ const std::string link_tag_end = "</guid>";
+ const std::string pub_date_tag_begin = "<pubDate>";
+ const std::string pub_date_tag_end = "</pubDate>";
+ const std::string seeders_tag_begin = "<nyaa:seeders>";
+ const std::string seeders_tag_end = "</nyaa:seeders>";
+ const std::string leechers_tag_begin = "<nyaa:leechers>";
+ const std::string leechers_tag_end = "</nyaa:leechers>";
+ const std::string downloads_tag_begin = "<nyaa:downloads>";
+ const std::string downloads_tag_end = "</nyaa:downloads>";
+ const std::string category_id_tag_begin = "<nyaa:categoryId>";
+ const std::string category_id_tag_end = "</nyaa:categoryId>";
+ const std::string size_tag_begin = "<nyaa:size>";
+ const std::string size_tag_end = "</nyaa:size>";
+
+ size_t index = 0;
+ while(index < website_data.size()) {
+ size_t item_start = website_data.find("<item>", index);
+ if(item_start == std::string::npos)
+ break;
+
+ index = item_start + 6;
+
+ size_t item_end = website_data.find("</item>", 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<BodyItem>(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<BodyItem>("Download torrent");
+ // torrent_item->url = "https://nyaa.si/download/" + id + ".torrent";
+ auto torrent_item = std::make_unique<BodyItem>("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<BodyItem>("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<BodyItem>(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;
+ }
+} \ No newline at end of file
diff --git a/src/plugins/Plugin.cpp b/src/plugins/Plugin.cpp
index a9adf15..8690964 100644
--- a/src/plugins/Plugin.cpp
+++ b/src/plugins/Plugin.cpp
@@ -16,6 +16,13 @@ namespace QuickMedia {
return SuggestionResult::OK;
}
+ SearchResult Plugin::content_list_search(const std::string &list_url, const std::string &text, BodyItems &result_items) {
+ (void)list_url;
+ (void)text;
+ (void)result_items;
+ return SearchResult::OK;
+ }
+
BodyItems Plugin::get_related_media(const std::string &url) {
(void)url;
return {};