aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md5
-rw-r--r--TODO2
-rw-r--r--icons/nyaa_si_launcher.pngbin0 -> 3091 bytes
-rw-r--r--images/nyaa_si_logo.pngbin0 -> 2306 bytes
-rw-r--r--launcher/QuickMedia-nyaa.si.desktop9
-rw-r--r--plugins/NyaaSi.hpp26
-rw-r--r--plugins/Plugin.hpp3
-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
12 files changed, 385 insertions, 17 deletions
diff --git a/README.md b/README.md
index 8315e79..b97b323 100644
--- a/README.md
+++ b/README.md
@@ -11,7 +11,7 @@ Cache is stored under `$HOME/.cache/quickmedia`.
```
usage: QuickMedia <plugin> [--tor] [--use-system-mpv-config] [-p placeholder-text]
OPTIONS:
- plugin The plugin to use. Should be either 4chan, manganelo, mangatown, mangadex, youtube or dmenu
+ plugin The plugin to use. Should be either 4chan, manganelo, mangatown, mangadex, youtube, nyaa.si or dmenu
--tor Use tor. Disabled by default
--use-system-mpv-config Use system mpv config instead of no config. Disabled by default
--upscale-images Upscale low-resolution manga pages using waifu2x-ncnn-vulkan. Disabled by default
@@ -63,7 +63,8 @@ See project.conf \[dependencies].
`notify-send` needs to be installed to show notifications (on Linux and other systems that uses d-bus notification system).\
`torsocks` needs to be installed when using the `--tor` option.\
[automedia](https://git.dec05eba.com/AutoMedia/) needs to be installed when tracking manga with `Ctrl + T`.\
-`waifu2x-ncnn-vulkan` needs to be installed when using the `--upscale-images` option.
+`waifu2x-ncnn-vulkan` needs to be installed when using the `--upscale-images` option.\
+`xdg-utils` which provides `xdg-open` needs to be installed when downloading torrents with `nyaa.si` plugin.
# Screenshots
## Youtube search
![](https://www.dec05eba.com/images/youtube-search.png)
diff --git a/TODO b/TODO
index 08f620b..8f759b6 100644
--- a/TODO
+++ b/TODO
@@ -17,3 +17,5 @@ Some text is not visible on 4chan, such as code blocks.
Show indication when search is in progress (or when something is loading). Some sites such as youtube and mangadex are slow at searching.
Add login page for mangadex instead of having to manually add remember_me token to config file.
Allow deleting watch history with delete key (and show confirmation).
+Add pagination to nyaa.si results.
+Add navigation to nyaa.si submitter torrents. \ No newline at end of file
diff --git a/icons/nyaa_si_launcher.png b/icons/nyaa_si_launcher.png
new file mode 100644
index 0000000..c29320a
--- /dev/null
+++ b/icons/nyaa_si_launcher.png
Binary files differ
diff --git a/images/nyaa_si_logo.png b/images/nyaa_si_logo.png
new file mode 100644
index 0000000..f146931
--- /dev/null
+++ b/images/nyaa_si_logo.png
Binary files differ
diff --git a/launcher/QuickMedia-nyaa.si.desktop b/launcher/QuickMedia-nyaa.si.desktop
new file mode 100644
index 0000000..45d3087
--- /dev/null
+++ b/launcher/QuickMedia-nyaa.si.desktop
@@ -0,0 +1,9 @@
+[Desktop Entry]
+Type=Application
+Name=QuickMedia Nyaa.si
+GenericName=Nyaa.si browser
+Comment=Nyaa.si search and torrent downloading
+Icon=/usr/share/quickmedia/icons/nyaa_si_launcher.png
+Exec=QuickMedia nyaa.si
+Terminal=false
+Keywords=nyaa.si;torrent;quickmedia;anime;
diff --git a/plugins/NyaaSi.hpp b/plugins/NyaaSi.hpp
new file mode 100644
index 0000000..428d8b5
--- /dev/null
+++ b/plugins/NyaaSi.hpp
@@ -0,0 +1,26 @@
+#pragma once
+
+#include "Plugin.hpp"
+#include <thread>
+#include <mutex>
+#include <condition_variable>
+
+namespace QuickMedia {
+ class Program;
+
+ class NyaaSi : public Plugin {
+ public:
+ NyaaSi();
+ ~NyaaSi() override;
+ PluginResult get_front_page(BodyItems &result_items) override;
+ SearchResult content_list_search(const std::string &list_url, const std::string &text, BodyItems &result_items) override;
+ bool search_suggestions_has_thumbnails() const override { return true; }
+ bool search_results_has_thumbnails() const override { return false; }
+ int get_search_delay() const override { return 150; }
+ Page get_page_after_search() const override { return Page::CONTENT_LIST; }
+ bool search_is_filter() override { return true; }
+ bool content_list_search_is_filter() const override { return false; }
+ PluginResult get_content_list(const std::string &url, BodyItems &result_items) override;
+ PluginResult get_content_details(const std::string &list_url, const std::string &url, BodyItems &result_items) override;
+ };
+} \ No newline at end of file
diff --git a/plugins/Plugin.hpp b/plugins/Plugin.hpp
index 54ce67d..7d052c2 100644
--- a/plugins/Plugin.hpp
+++ b/plugins/Plugin.hpp
@@ -51,6 +51,7 @@ namespace QuickMedia {
}
virtual SearchResult search(const std::string &text, BodyItems &result_items);
virtual SuggestionResult update_search_suggestions(const std::string &text, BodyItems &result_items);
+ virtual SearchResult content_list_search(const std::string &list_url, const std::string &text, BodyItems &result_items);
virtual BodyItems get_related_media(const std::string &url);
virtual PluginResult get_content_list(const std::string &url, BodyItems &result_items) {
(void)url;
@@ -68,7 +69,9 @@ namespace QuickMedia {
virtual std::string autocomplete_search(const std::string &query) { return query; }
virtual int get_autocomplete_delay() const { return 100; }
virtual int get_search_delay() const = 0;
+ virtual int get_content_list_search_delay() const { return 350; }
virtual bool search_suggestion_is_search() const { return false; }
+ virtual bool content_list_search_is_filter() const { return true; }
virtual Page get_page_after_search() const = 0;
const std::string name;
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 {};