diff options
-rw-r--r-- | README.md | 6 | ||||
m--------- | depends/html-search | 0 | ||||
-rw-r--r-- | include/QuickMedia.hpp | 5 | ||||
-rw-r--r-- | plugins/Saucenao.hpp | 18 | ||||
-rw-r--r-- | src/QuickMedia.cpp | 59 | ||||
-rw-r--r-- | src/plugins/Saucenao.cpp | 56 |
6 files changed, 131 insertions, 13 deletions
@@ -1,6 +1,6 @@ # QuickMedia A dmenu-inspired native client for web services. -Currently supported web services: `youtube`, `spotify (podcasts)`, `soundcloud`, `nyaa.si`, `manganelo`, `manganelos`, `mangatown`, `mangakatana`, `mangadex`, `readm`, `4chan`, `matrix` and _others_.\ +Currently supported web services: `youtube`, `spotify (podcasts)`, `soundcloud`, `nyaa.si`, `manganelo`, `manganelos`, `mangatown`, `mangakatana`, `mangadex`, `readm`, `4chan`, `matrix`, `saucenao` and _others_.\ **Note:** file-manager is early in progress.\ Config data, including manga progress is stored under `$HOME/.config/quickmedia`.\ Cache is stored under `$HOME/.cache/quickmedia`. @@ -8,7 +8,7 @@ Cache is stored under `$HOME/.cache/quickmedia`. ``` usage: quickmedia <plugin> [--use-system-mpv-config] [--dir <directory>] [-e <window>] OPTIONS: - plugin The plugin to use. Should be either launcher, 4chan, manga, manganelo, manganelos, mangatown, mangakatana, mangadex, readm, youtube, spotify, soundcloud, nyaa.si, matrix, file-manager or stdin + plugin The plugin to use. Should be either launcher, 4chan, manga, manganelo, manganelos, mangatown, mangakatana, mangadex, readm, youtube, spotify, soundcloud, nyaa.si, matrix, saucenao, file-manager or stdin --no-video Only play audio when playing a video. 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 @@ -53,7 +53,7 @@ Press `I` to begin writing a message in a matrix room, press `ESC` to cancel.\ Press `R` to reply to a message on matrix, press `ESC` to cancel.\ Press `E` to edit a message on matrix, press `ESC` to cancel. Currently only works for your own messages.\ Press `Ctrl + D` to delete a message on matrix. Currently deleting a message only deletes the event, so if you delete an edit then the original message wont be deleted.\ -Press `Ctrl + C` to copy the message of the selected item in matrix to the clipboard.\ +Press `Ctrl + C` to copy the text of the selected item to the clipboard.\ Press `U` in matrix or in a 4chan thread to bring up the file manager to choose a file to upload.\ Press `Ctrl + V` to upload media to room in matrix if the clipboard contains a valid absolute filepath.\ Press `Ctrl + D` to remove the file that was previously selected with `U` in a 4chan thread.\ diff --git a/depends/html-search b/depends/html-search -Subproject 0578bfd08637d3e113d28507ea73fa9a649f2f2 +Subproject 3408efcc9d379d8ce8eb8c29db327e436ee5adb diff --git a/include/QuickMedia.hpp b/include/QuickMedia.hpp index 492003f..cea9b88 100644 --- a/include/QuickMedia.hpp +++ b/include/QuickMedia.hpp @@ -7,6 +7,8 @@ #include "Tab.hpp" #include "MessageQueue.hpp" #include "AsyncTask.hpp" +#include "../plugins/Plugin.hpp" +#include "../plugins/FileManager.hpp" #include <vector> #include <memory> #include <SFML/Graphics/Font.hpp> @@ -17,7 +19,6 @@ #include <future> #include <thread> #include <stack> -#include "../plugins/Plugin.hpp" #include <X11/Xlib.h> #include <X11/Xatom.h> @@ -99,7 +100,7 @@ namespace QuickMedia { Json::Value load_video_history_json(); private: void init(Window parent_window); - void load_plugin_by_name(std::vector<Tab> &tabs, const char *start_dir, int &start_tab_index); + void load_plugin_by_name(std::vector<Tab> &tabs, const char *start_dir, int &start_tab_index, FileManagerMimeType fm_mime_type); // Returns true if the window was closed bool handle_window_close(); void base_event_handler(sf::Event &event, PageType previous_page, Body *body, SearchBar *search_bar, bool handle_key_press = true, bool handle_searchbar = true); diff --git a/plugins/Saucenao.hpp b/plugins/Saucenao.hpp new file mode 100644 index 0000000..a5aadf9 --- /dev/null +++ b/plugins/Saucenao.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include "Page.hpp" + +namespace QuickMedia { + class SaucenaoPage : public LazyFetchPage { + public: + SaucenaoPage(Program *program, const std::string &upload_filepath) : LazyFetchPage(program), upload_filepath(upload_filepath) {} + const char* get_title() const override { return "SauceNAO"; } + PluginResult submit(const std::string&, const std::string&, std::vector<Tab>&) override { + return PluginResult::OK; + } + PluginResult lazy_fetch(BodyItems &result_items) override; + bool is_single_page() const override { return true; } + private: + std::string upload_filepath; + }; +}
\ No newline at end of file diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index 594b93a..c9789cf 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -13,6 +13,7 @@ #include "../plugins/Soundcloud.hpp" #include "../plugins/FileManager.hpp" #include "../plugins/Pipe.hpp" +#include "../plugins/Saucenao.hpp" #include "../include/Scale.hpp" #include "../include/Program.hpp" #include "../include/VideoPlayer.hpp" @@ -76,7 +77,8 @@ static const std::pair<const char*, const char*> valid_plugins[] = { std::make_pair("mastodon", "pleroma_logo.png"), std::make_pair("pleroma", "pleroma_logo.png"), std::make_pair("file-manager", nullptr), - std::make_pair("stdin", nullptr) + std::make_pair("stdin", nullptr), + std::make_pair("saucenao", nullptr) }; static const char* get_plugin_logo_name(const char *plugin_name) { @@ -301,12 +303,12 @@ namespace QuickMedia { static void usage() { fprintf(stderr, "usage: quickmedia <plugin> [--no-video] [--use-system-mpv-config] [--dir <directory>] [-e <window>]\n"); fprintf(stderr, "OPTIONS:\n"); - fprintf(stderr, " plugin The plugin to use. Should be either launcher, 4chan, manga, manganelo, manganelos, mangatown, mangakatana, mangadex, readm, youtube, spotify, soundcloud, nyaa.si, matrix, file-manager, stdin, pornhub, spankbang, xvideos or xhamster\n"); + fprintf(stderr, " plugin The plugin to use. Should be either launcher, 4chan, manga, manganelo, manganelos, mangatown, mangakatana, mangadex, readm, youtube, spotify, soundcloud, nyaa.si, matrix, saucenao, file-manager, stdin, pornhub, spankbang, xvideos or xhamster\n"); fprintf(stderr, " --no-video Only play audio when playing a video. 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"); fprintf(stderr, " --upscale-images-always Upscale manga pages using waifu2x-ncnn-vulkan, no matter what the original image resolution is. Disabled by default\n"); - fprintf(stderr, " --dir <directory> Set the start directory when using file-manager\n"); + fprintf(stderr, " --dir <directory> Set the start directory when using file-manager. Default is the user home directory\n"); fprintf(stderr, " -e <window> Embed QuickMedia into another window\n"); fprintf(stderr, "EXAMPLES:\n"); fprintf(stderr, " quickmedia launcher\n"); @@ -450,10 +452,20 @@ namespace QuickMedia { return -1; } + Path home_dir = get_home_dir(); + if(!start_dir) + start_dir = home_dir.data.c_str(); + int start_tab_index = 0; + FileManagerMimeType fm_mine_type = FILE_MANAGER_MIME_TYPE_ALL; init(parent_window); - load_plugin_by_name(tabs, start_dir, start_tab_index); + bool is_saucenao = (strcmp(plugin_name, "saucenao") == 0); + if(is_saucenao) { + plugin_name = "file-manager"; + fm_mine_type = FILE_MANAGER_MIME_TYPE_IMAGE; + } + load_plugin_by_name(tabs, start_dir, start_tab_index, fm_mine_type); while(!tabs.empty() || matrix) { if(matrix) { @@ -473,7 +485,10 @@ namespace QuickMedia { if(strcmp(plugin_name, "launcher") == 0) { plugin_name = pipe_selected_text.c_str(); - load_plugin_by_name(tabs, start_dir, start_tab_index); + load_plugin_by_name(tabs, start_dir, start_tab_index, fm_mine_type); + } else if(strcmp(plugin_name, "file-manager") == 0 && is_saucenao && !selected_files.empty()) { + plugin_name = "saucenao"; + load_plugin_by_name(tabs, start_dir, start_tab_index, fm_mine_type); } } @@ -787,7 +802,7 @@ namespace QuickMedia { .related_media_thumbnail_handler({{"//img", "src", "/thumb-"}}); } - void Program::load_plugin_by_name(std::vector<Tab> &tabs, const char *start_dir, int &start_tab_index) { + void Program::load_plugin_by_name(std::vector<Tab> &tabs, const char *start_dir, int &start_tab_index, FileManagerMimeType fm_mime_type) { if(!plugin_name || plugin_name[0] == '\0') return; @@ -898,7 +913,7 @@ namespace QuickMedia { boards_page->get_boards(boards_body->items); tabs.push_back(Tab{std::move(boards_body), std::move(boards_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); } else if(strcmp(plugin_name, "file-manager") == 0) { - auto file_manager_page = std::make_unique<FileManagerPage>(this); + auto file_manager_page = std::make_unique<FileManagerPage>(this, fm_mime_type); if(start_dir && !file_manager_page->set_current_directory(start_dir)) { fprintf(stderr, "Invalid directory provided with --dir: %s\n", start_dir); exit_code = -3; @@ -911,6 +926,8 @@ namespace QuickMedia { auto pipe_body = create_body(); PipePage::load_body_items_from_stdin(pipe_body->items); tabs.push_back(Tab{std::move(pipe_body), std::make_unique<PipePage>(this), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); + } else if(strcmp(plugin_name, "saucenao") == 0) { + tabs.push_back(Tab{create_body(), std::make_unique<SaucenaoPage>(this, selected_files[0]), nullptr}); } else if(strcmp(plugin_name, "youtube") == 0) { start_tab_index = 1; tabs.push_back(Tab{create_body(), std::make_unique<YoutubeSubscriptionsPage>(this), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); @@ -1378,7 +1395,7 @@ namespace QuickMedia { } if(tabs[selected_tab].page->is_single_page()) { - tabs[selected_tab].search_bar->clear(); + if(tabs[selected_tab].search_bar) tabs[selected_tab].search_bar->clear(); if(new_tabs.size() == 1) tabs[selected_tab].body = std::move(new_tabs[0].body); else @@ -1615,6 +1632,19 @@ namespace QuickMedia { TrackablePage *trackable_page = dynamic_cast<TrackablePage*>(tabs[selected_tab].page.get()); trackable_page->track(selected_item->get_title()); } + } else if(event.key.code == sf::Keyboard::C && event.key.control) { + BodyItem *selected_item = tabs[selected_tab].body->get_selected(); + if(selected_item) { + std::string title = selected_item->get_title(); + std::string description = selected_item->get_description(); + std::string clipboard = title; + if(!clipboard.empty()) { + clipboard += '\n'; + clipboard += std::move(description); + } + if(!clipboard.empty()) + sf::Clipboard::setString(sf::String::fromUtf8(clipboard.begin(), clipboard.end())); + } } } } @@ -3089,6 +3119,19 @@ namespace QuickMedia { frame_skip_text_entry = true; } else if(event.key.code == sf::Keyboard::D && event.key.control) { selected_file_for_upload.clear(); + } else if(event.key.code == sf::Keyboard::C && event.key.control) { + BodyItem *selected_item = thread_body->get_selected(); + if(selected_item) { + std::string title = selected_item->get_title(); + std::string description = selected_item->get_description(); + std::string clipboard = title; + if(!clipboard.empty()) { + clipboard += '\n'; + clipboard += std::move(description); + } + if(!clipboard.empty()) + sf::Clipboard::setString(sf::String::fromUtf8(clipboard.begin(), clipboard.end())); + } } BodyItem *selected_item = thread_body->get_selected(); diff --git a/src/plugins/Saucenao.cpp b/src/plugins/Saucenao.cpp new file mode 100644 index 0000000..302f25e --- /dev/null +++ b/src/plugins/Saucenao.cpp @@ -0,0 +1,56 @@ +#include "../../plugins/Saucenao.hpp" +#include "../../include/StringUtils.hpp" +#include <quickmedia/HtmlSearch.h> + +namespace QuickMedia { + PluginResult SaucenaoPage::lazy_fetch(BodyItems &result_items) { + std::string website_data; + DownloadResult download_result = download_to_string("https://saucenao.com/search.php", website_data, {{ "-F", "file=@" + upload_filepath }}, 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()); + if(result != 0) + goto cleanup; + + result = quickmedia_html_find_nodes_xpath(&html_search, "//td[class='resulttablecontent']", + [](QuickMediaHtmlNode *node, void *userdata) { + BodyItems *item_data = (BodyItems*)userdata; + const char *text = quickmedia_html_node_get_text(node); + if(text) { + std::string title = text; + size_t p_index = title.find("%"); + if(p_index != std::string::npos) + title = title.erase(0, p_index + 1); + auto item = BodyItem::create(strip(title)); + item_data->push_back(std::move(item)); + } + }, &result_items); + + BodyItemContext body_item_context; + body_item_context.body_items = &result_items; + body_item_context.index = 0; + + quickmedia_html_find_nodes_xpath(&html_search, "//td[class='resulttableimage']//img", + [](QuickMediaHtmlNode *node, void *userdata) { + BodyItemContext *item_data = (BodyItemContext*)userdata; + const char *src = quickmedia_html_node_get_attribute_value(node, "src"); + const char *data_src = quickmedia_html_node_get_attribute_value(node, "data-src"); + const char *image_url = data_src ? data_src : src; + if(image_url && item_data->index < item_data->body_items->size()) { + (*item_data->body_items)[item_data->index]->thumbnail_url = strip(image_url); + (*item_data->body_items)[item_data->index]->thumbnail_size = sf::Vector2i(150, 147); + item_data->index++; + } + }, &body_item_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 |