From 56acc142c6ef9a65147acdea6737acbfaeb7eca4 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Tue, 15 Oct 2019 19:57:24 +0200 Subject: Add tor support --- README.md | 17 ++++++++-- include/Body.hpp | 5 ++- include/QuickMedia.hpp | 2 ++ include/StringUtils.hpp | 11 +++++++ include/VideoPlayer.hpp | 3 +- plugins/Plugin.hpp | 4 ++- src/Body.cpp | 6 ++-- src/QuickMedia.cpp | 82 +++++++++++++++++++++++++++++++++++++------------ src/StringUtils.cpp | 17 ++++++++++ src/VideoPlayer.cpp | 23 +++++++++----- src/plugins/Plugin.cpp | 35 +++++++++++---------- 11 files changed, 154 insertions(+), 51 deletions(-) create mode 100644 include/StringUtils.hpp create mode 100644 src/StringUtils.cpp diff --git a/README.md b/README.md index 35ade6d..8fdfb33 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,19 @@ # QuickMedia -Native clients of websites with fast access to what you want to see. [Old video demo with manga](https://beta.lbry.tv/quickmedia_manga-2019-08-05_21.20.46/7).\ -Currently supported websites: `youtube`, `manganelo` and _others_.\ +Native clients of websites with fast access to what you want to see, **with TOR support**. See [old video demo with manga](https://beta.lbry.tv/quickmedia_manga-2019-08-05_21.20.46/7).\ +Currently supported websites: `youtube`, `manganelo` and _others_. **Note:** Manganelo doesn't work when used with TOR.\ Here is an example with YouTube:\ ![QuickMedia Youtube Picture](QuickMediaYoutube.png)\ Config data, including manga progress is stored under `$HOME/.config/quickmedia` +## Usage +``` +usage: QuickMedia [--tor] +OPTIONS: +plugin The plugin to use. Should be either 4chan, manganelo, pornhub or youtube +--tor Use tor. Disabled by default +EXAMPLES: +QuickMedia manganelo +QuickMedia youtube --tor +``` ## Controls Press `ESC` to go back to the previous menu.\ Press `Ctrl + T` when hovering over a manga chapter to start tracking manga after that chapter. This only works if AutoMedia is installed and @@ -19,7 +29,8 @@ See project.conf \[dependencies]. ### Optional `mpv` is required for playing videos. This is not required if you dont plan on playing videos.\ `youtube-dl` needs to be installed to play videos from youtube.\ -`notify-send` needs to be installed to show notifications (on Linux and other systems that uses d-bus notification system). +`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. # TODO If a search returns no results, then "No results found for ..." should be shown and navigation should go back to searching with suggestions.\ Give user the option to start where they left off or from the start or from the start.\ diff --git a/include/Body.hpp b/include/Body.hpp index a2db62a..c394519 100644 --- a/include/Body.hpp +++ b/include/Body.hpp @@ -8,6 +8,8 @@ #include namespace QuickMedia { + class Program; + class BodyItem { public: BodyItem(const std::string &_title): title(_title), visible(true) { @@ -24,7 +26,7 @@ namespace QuickMedia { class Body { public: - Body(sf::Font &font); + Body(Program *program, sf::Font &font); // Select previous item, ignoring invisible items void select_previous_item(); @@ -56,6 +58,7 @@ namespace QuickMedia { std::string url; std::shared_ptr texture; }; + Program *program; std::shared_ptr load_thumbnail_from_url(const std::string &url); std::vector item_thumbnail_textures; bool loading_thumbnail; diff --git a/include/QuickMedia.hpp b/include/QuickMedia.hpp index 8479d58..6906036 100644 --- a/include/QuickMedia.hpp +++ b/include/QuickMedia.hpp @@ -21,6 +21,8 @@ namespace QuickMedia { Program(); ~Program(); int run(int argc, char **argv); + + Plugin* get_current_plugin() { return current_plugin; } private: void base_event_handler(sf::Event &event, Page previous_page); void search_suggestion_page(); diff --git a/include/StringUtils.hpp b/include/StringUtils.hpp new file mode 100644 index 0000000..72210c9 --- /dev/null +++ b/include/StringUtils.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include +#include + +namespace QuickMedia { + // Return false to stop iterating + using StringSplitCallback = std::function; + + void string_split(const std::string &str, char delimiter, StringSplitCallback callback_func); +} \ No newline at end of file diff --git a/include/VideoPlayer.hpp b/include/VideoPlayer.hpp index 3839530..e1763f6 100644 --- a/include/VideoPlayer.hpp +++ b/include/VideoPlayer.hpp @@ -35,7 +35,7 @@ namespace QuickMedia { }; // @event_callback is called from another thread - VideoPlayer(EventCallbackFunc event_callback, VideoPlayerWindowCreateCallback window_create_callback); + VideoPlayer(bool use_tor, EventCallbackFunc event_callback, VideoPlayerWindowCreateCallback window_create_callback); ~VideoPlayer(); VideoPlayer(const VideoPlayer&) = delete; VideoPlayer& operator=(const VideoPlayer&) = delete; @@ -65,6 +65,7 @@ namespace QuickMedia { Error launch_video_process(const char *path, sf::WindowHandle parent_window); VideoPlayer::Error read_ipc_func(); private: + bool use_tor; pid_t video_process_id; int ipc_socket; bool connected_to_ipc; diff --git a/plugins/Plugin.hpp b/plugins/Plugin.hpp index dffe898..cfa235c 100644 --- a/plugins/Plugin.hpp +++ b/plugins/Plugin.hpp @@ -43,7 +43,6 @@ namespace QuickMedia { std::string value; }; - DownloadResult download_to_string(const std::string &url, std::string &result, const std::vector &additional_args = {}); std::string strip(const std::string &str); void string_replace_all(std::string &str, const std::string &old_str, const std::string &new_str); void html_unescape_sequences(std::string &str); @@ -76,7 +75,10 @@ namespace QuickMedia { virtual bool search_suggestion_is_search() const { return false; } virtual Page get_page_after_search() const = 0; + DownloadResult download_to_string(const std::string &url, std::string &result, const std::vector &additional_args = {}); + const std::string name; + bool use_tor = false; protected: std::string url_param_encode(const std::string ¶m) const; }; diff --git a/src/Body.cpp b/src/Body.cpp index fb44929..7ed1a7f 100644 --- a/src/Body.cpp +++ b/src/Body.cpp @@ -1,4 +1,5 @@ #include "../include/Body.hpp" +#include "../include/QuickMedia.hpp" #include "../plugins/Plugin.hpp" #include #include @@ -9,7 +10,8 @@ const sf::Color front_color(43, 45, 47); const sf::Color back_color(33, 35, 37); namespace QuickMedia { - Body::Body(sf::Font &font) : + Body::Body(Program *program, sf::Font &font) : + program(program), title_text("", font, 14), progress_text("", font, 14), selected_item(0), @@ -96,7 +98,7 @@ namespace QuickMedia { loading_thumbnail = true; thumbnail_load_thread = std::thread([this, result, url]() { std::string texture_data; - if(download_to_string(url, texture_data) == DownloadResult::OK) { + if(program->get_current_plugin()->download_to_string(url, texture_data) == DownloadResult::OK) { if(result->loadFromMemory(texture_data.data(), texture_data.size())) result->generateMipmap(); } diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index f277f46..6ac47bf 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -6,6 +6,7 @@ #include "../include/Scale.hpp" #include "../include/Program.h" #include "../include/VideoPlayer.hpp" +#include "../include/StringUtils.hpp" #include #include @@ -52,7 +53,7 @@ namespace QuickMedia { fprintf(stderr, "Failed to load font!\n"); abort(); } - body = new Body(font); + body = new Body(this, font); struct sigaction action; action.sa_handler = sigpipe_handler; @@ -83,9 +84,32 @@ namespace QuickMedia { } static void usage() { - fprintf(stderr, "usage: QuickMedia \n"); + fprintf(stderr, "usage: QuickMedia [--tor]\n"); fprintf(stderr, "OPTIONS:\n"); - fprintf(stderr, "plugin The plugin to use. Should be either manganelo, youtube or pornhub\n"); + fprintf(stderr, "plugin The plugin to use. Should be either 4chan, manganelo, pornhub or youtube\n"); + fprintf(stderr, "--tor Use tor. Disabled by default\n"); + fprintf(stderr, "EXAMPLES:\n"); + fprintf(stderr, "QuickMedia manganelo\n"); + fprintf(stderr, "QuickMedia youtube --tor\n"); + } + + static bool is_program_executable_by_name(const char *name) { + // TODO: Implement for Windows. Windows also uses semicolon instead of colon as a separator + char *env = getenv("PATH"); + std::unordered_set paths; + string_split(env, ':', [&paths](const char *str, size_t size) { + paths.insert(std::string(str, size)); + return true; + }); + + for(const std::string &path_str : paths) { + Path path(path_str); + path.join(name); + if(get_file_type(path) == FileType::REGULAR) + return true; + } + + return false; } int Program::run(int argc, char **argv) { @@ -94,24 +118,44 @@ namespace QuickMedia { return -1; } + current_plugin = nullptr; std::string plugin_logo_path; - if(strcmp(argv[1], "manganelo") == 0) { - current_plugin = new Manganelo(); - plugin_logo_path = "../../../images/manganelo_logo.png"; - } else if(strcmp(argv[1], "youtube") == 0) { - current_plugin = new Youtube(); - plugin_logo_path = "../../../images/yt_logo_rgb_dark_small.png"; - } else if(strcmp(argv[1], "pornhub") == 0) { - current_plugin = new Pornhub(); - plugin_logo_path = "../../../images/pornhub_logo.png"; - } else if(strcmp(argv[1], "4chan") == 0) { - current_plugin = new Fourchan(); - plugin_logo_path = "../../../images/4chan_logo.png"; - } else { + bool use_tor = false; + + for(int i = 1; i < argc; ++i) { + if(!current_plugin) { + if(strcmp(argv[i], "manganelo") == 0) { + current_plugin = new Manganelo(); + plugin_logo_path = "../../../images/manganelo_logo.png"; + } else if(strcmp(argv[i], "youtube") == 0) { + current_plugin = new Youtube(); + plugin_logo_path = "../../../images/yt_logo_rgb_dark_small.png"; + } else if(strcmp(argv[i], "pornhub") == 0) { + current_plugin = new Pornhub(); + plugin_logo_path = "../../../images/pornhub_logo.png"; + } else if(strcmp(argv[i], "4chan") == 0) { + current_plugin = new Fourchan(); + plugin_logo_path = "../../../images/4chan_logo.png"; + } + } + + if(strcmp(argv[i], "--tor") == 0) { + use_tor = true; + } + } + + if(!current_plugin) { usage(); return -1; } + if(use_tor && !is_program_executable_by_name("torsocks")) { + fprintf(stderr, "torsocks needs to be installed (and accessible from PATH environment variable) when using the --tor option\n"); + return -2; + } + + current_plugin->use_tor = use_tor; + if(!plugin_logo_path.empty()) { if(!plugin_logo.loadFromFile(plugin_logo_path)) { fprintf(stderr, "Failed to load plugin logo, path: %s\n", plugin_logo_path.c_str()); @@ -532,7 +576,7 @@ namespace QuickMedia { } }; - video_player = std::make_unique([this, &video_player, &seekable, &load_video_error_check](const char *event_name) { + video_player = std::make_unique(current_plugin->use_tor, [this, &video_player, &seekable, &load_video_error_check](const char *event_name) { bool end_of_file = false; if(strcmp(event_name, "pause") == 0) { double time_remaining = 0.0; @@ -861,7 +905,7 @@ namespace QuickMedia { return true; std::string image_content; - if(download_to_string(url, image_content) != DownloadResult::OK) { + if(current_plugin->download_to_string(url, image_content) != DownloadResult::OK) { show_notification("Manganelo", "Failed to download image: " + url, Urgency::CRITICAL); return false; } @@ -981,7 +1025,7 @@ namespace QuickMedia { } sf::Clock check_downloaded_timer; - const sf::Int32 check_downloaded_timeout_ms = 1000; + const sf::Int32 check_downloaded_timeout_ms = 500; // TODO: Show to user if a certain page is missing (by checking page name (number) and checking if some are skipped) while (current_page == Page::IMAGES) { diff --git a/src/StringUtils.cpp b/src/StringUtils.cpp new file mode 100644 index 0000000..deb4949 --- /dev/null +++ b/src/StringUtils.cpp @@ -0,0 +1,17 @@ +#include "../include/StringUtils.hpp" + +namespace QuickMedia { + void string_split(const std::string &str, char delimiter, StringSplitCallback callback_func) { + size_t index = 0; + while(true) { + size_t new_index = str.find(delimiter, index); + if(new_index == std::string::npos) + break; + + if(!callback_func(str.data() + index, new_index - index)) + break; + + index = new_index + 1; + } + } +} \ No newline at end of file diff --git a/src/VideoPlayer.cpp b/src/VideoPlayer.cpp index 2336367..dfe6603 100644 --- a/src/VideoPlayer.cpp +++ b/src/VideoPlayer.cpp @@ -14,11 +14,11 @@ const int RETRY_TIME_MS = 1000; const int MAX_RETRIES_CONNECT = 5; -const int MAX_RETRIES_FIND_WINDOW = 10; const int READ_TIMEOUT_MS = 200; namespace QuickMedia { - VideoPlayer::VideoPlayer(EventCallbackFunc _event_callback, VideoPlayerWindowCreateCallback _window_create_callback) : + VideoPlayer::VideoPlayer(bool use_tor, EventCallbackFunc _event_callback, VideoPlayerWindowCreateCallback _window_create_callback) : + use_tor(use_tor), video_process_id(-1), ipc_socket(-1), connected_to_ipc(false), @@ -64,15 +64,18 @@ namespace QuickMedia { } const std::string parent_window_str = std::to_string(parent_window); - const char *args[] = { "mpv", "--keep-open=yes", /*"--keep-open-pause=no",*/ "--input-ipc-server", ipc_server_path, + std::vector args; + if(use_tor) + args.push_back("torsocks"); + args.insert(args.end(), { "mpv", "--keep-open=yes", /*"--keep-open-pause=no",*/ "--input-ipc-server", ipc_server_path, "--no-config", "--no-input-default-bindings", "--input-vo-keyboard=no", "--no-input-cursor", "--cache-secs=120", "--demuxer-max-bytes=40M", "--demuxer-max-back-bytes=20M", "--no-input-terminal", "--no-osc", "--profile=gpu-hq", /*"--vo=gpu", "--hwdec=auto",*/ - "--wid", parent_window_str.c_str(), "--", path, nullptr }; - if(exec_program_async(args, &video_process_id) != 0) + "--wid", parent_window_str.c_str(), "--", path, nullptr }); + if(exec_program_async(args.data(), &video_process_id) != 0) return Error::FAIL_TO_LAUNCH_PROCESS; printf("mpv input ipc server: %s\n", ipc_server_path); @@ -125,13 +128,17 @@ namespace QuickMedia { } VideoPlayer::Error VideoPlayer::update() { + int max_retries_find_window = 10; + if(use_tor) + max_retries_find_window = 30; + if(ipc_socket == -1) return Error::INIT_FAILED; if(connect_tries == MAX_RETRIES_CONNECT) return Error::FAIL_TO_CONNECT_TIMEOUT; - if(find_window_tries == MAX_RETRIES_FIND_WINDOW) + if(find_window_tries == max_retries_find_window) return Error::FAIL_TO_FIND_WINDOW; if(!connected_to_ipc && retry_timer.getElapsedTime().asMilliseconds() >= RETRY_TIME_MS) { @@ -153,8 +160,8 @@ namespace QuickMedia { size_t num_children = child_windows.size(); if(num_children == 0) { ++find_window_tries; - if(find_window_tries == MAX_RETRIES_FIND_WINDOW) { - fprintf(stderr, "Failed to find mpv window after %d seconds\n", (RETRY_TIME_MS * MAX_RETRIES_FIND_WINDOW)/1000); + if(find_window_tries == max_retries_find_window) { + fprintf(stderr, "Failed to find mpv window after %d seconds\n", (RETRY_TIME_MS * max_retries_find_window)/1000); return Error::FAIL_TO_FIND_WINDOW_TIMEOUT; } } else if(num_children == 1) { diff --git a/src/plugins/Plugin.cpp b/src/plugins/Plugin.cpp index c56ff67..d1490d5 100644 --- a/src/plugins/Plugin.cpp +++ b/src/plugins/Plugin.cpp @@ -28,22 +28,6 @@ namespace QuickMedia { return {}; } - DownloadResult download_to_string(const std::string &url, std::string &result, const std::vector &additional_args) { - sf::Clock timer; - std::vector args = { "curl", "-f", "-H", "Accept-Language: en-US,en;q=0.5", "--compressed", "-s", "-L" }; - for(const CommandArg &arg : additional_args) { - args.push_back(arg.option.c_str()); - args.push_back(arg.value.c_str()); - } - args.push_back("--"); - args.push_back(url.c_str()); - args.push_back(nullptr); - if(exec_program(args.data(), accumulate_string, &result) != 0) - return DownloadResult::NET_ERR; - fprintf(stderr, "Download duration for %s: %d ms\n", url.c_str(), timer.getElapsedTime().asMilliseconds()); - return DownloadResult::OK; - } - static bool is_whitespace(char c) { return c == ' ' || c == '\n' || c == '\t' || c == '\v'; } @@ -112,4 +96,23 @@ namespace QuickMedia { return result.str(); } + + DownloadResult Plugin::download_to_string(const std::string &url, std::string &result, const std::vector &additional_args) { + sf::Clock timer; + std::vector args; + if(use_tor) + args.push_back("torsocks"); + args.insert(args.end(), { "curl", "-f", "-H", "Accept-Language: en-US,en;q=0.5", "--compressed", "-s", "-L" }); + for(const CommandArg &arg : additional_args) { + args.push_back(arg.option.c_str()); + args.push_back(arg.value.c_str()); + } + args.push_back("--"); + args.push_back(url.c_str()); + args.push_back(nullptr); + if(exec_program(args.data(), accumulate_string, &result) != 0) + return DownloadResult::NET_ERR; + fprintf(stderr, "Download duration for %s: %d ms\n", url.c_str(), timer.getElapsedTime().asMilliseconds()); + return DownloadResult::OK; + } } \ No newline at end of file -- cgit v1.2.3