From d6131b8ba482414be76f2478aea90bd7a4a2379b Mon Sep 17 00:00:00 2001 From: dec05eba Date: Mon, 14 Sep 2020 18:37:00 +0200 Subject: Add support for 4chan pass --- README.md | 1 + include/SearchBar.hpp | 8 ++- plugins/Fourchan.hpp | 4 ++ plugins/ImageBoard.hpp | 1 + src/QuickMedia.cpp | 130 +++++++++++++++++++++++++++++++++++++++-------- src/SearchBar.cpp | 57 +++++++++++++++------ src/plugins/Fourchan.cpp | 92 +++++++++++++++++++++++++++++++++ src/plugins/Mangadex.cpp | 2 +- 8 files changed, 256 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index 6778106..5d998c9 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ Press `P` to preview the attached item of the selected row in full screen view. Press `I` to switch between single image and scroll image view mode when reading manga.\ Press `Middle mouse button` to "autoscroll" in scrolling image view mode.\ Press `Tab` to autocomplete a search when autocomplete is available (currently only available for youtube).\ +Press `Tab` to switch between username/password field in login panel.\ Press `Ctrl + V` to paste the content of your clipboard into the search bar. ## Video controls Press `space` to pause/unpause video. `Double-click` video to fullscreen or leave fullscreen. diff --git a/include/SearchBar.hpp b/include/SearchBar.hpp index 9efbf9c..ac15a90 100644 --- a/include/SearchBar.hpp +++ b/include/SearchBar.hpp @@ -16,7 +16,7 @@ namespace QuickMedia { class SearchBar { public: - SearchBar(sf::Font &font, sf::Texture &plugin_logo, const std::string &placeholder); + SearchBar(sf::Font &font, sf::Texture *plugin_logo, const std::string &placeholder, bool input_masked = false); void draw(sf::RenderWindow &window, bool draw_shadow = true); void on_event(sf::Event &event); void update(); @@ -27,16 +27,20 @@ namespace QuickMedia { bool is_cursor_at_start_of_line() const; void set_to_autocomplete(); void set_autocomplete_text(const std::string &text); + void set_vertical_position(float vertical_pos); float getBottom() const; float getBottomWithoutShadow() const; + std::string get_text() const; + TextUpdateCallback onTextUpdateCallback; TextSubmitCallback onTextSubmitCallback; TextBeginTypingCallback onTextBeginTypingCallback; AutocompleteRequestCallback onAutocompleteRequestCallback; int text_autosearch_delay; int autocomplete_search_delay; + bool caret_visible; private: void clear_autocomplete_if_text_not_substring(); void clear_autocomplete_if_last_char_not_substr(); @@ -54,6 +58,8 @@ namespace QuickMedia { bool updated_autocomplete; bool draw_logo; bool needs_update; + bool input_masked; + float vertical_pos; sf::Clock time_since_search_update; }; } \ No newline at end of file diff --git a/plugins/Fourchan.hpp b/plugins/Fourchan.hpp index c9d649f..564271a 100644 --- a/plugins/Fourchan.hpp +++ b/plugins/Fourchan.hpp @@ -24,6 +24,9 @@ namespace QuickMedia { Page get_page_after_search() const override { return Page::IMAGE_BOARD_THREAD_LIST; } bool search_is_filter() override { return true; } BodyItems get_related_media(const std::string &url) override; + + PluginResult login(const std::string &token, const std::string &pin, std::string &response_msg); + const std::string& get_pass_id() const override; private: PluginResult get_threads_internal(const std::string &url, BodyItems &result_items); void set_board_url(const std::string &new_url); @@ -43,5 +46,6 @@ namespace QuickMedia { bool running = true; std::vector cached_media_urls; std::string resources_root; + std::string pass_id; }; } \ No newline at end of file diff --git a/plugins/ImageBoard.hpp b/plugins/ImageBoard.hpp index e2a43a9..abab22e 100644 --- a/plugins/ImageBoard.hpp +++ b/plugins/ImageBoard.hpp @@ -16,6 +16,7 @@ namespace QuickMedia { virtual ~ImageBoard() = default; bool is_image_board() override { return true; } + virtual const std::string& get_pass_id() const = 0; virtual PluginResult get_threads(const std::string &url, BodyItems &result_items) = 0; virtual PluginResult get_thread_comments(const std::string &list_url, const std::string &url, BodyItems &result_items) = 0; diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index 051fdc7..b464956 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -318,7 +318,7 @@ namespace QuickMedia { plugin_logo.setSmooth(true); } - search_bar = std::make_unique(font, plugin_logo, search_placeholder); + search_bar = std::make_unique(font, &plugin_logo, search_placeholder); search_bar->text_autosearch_delay = current_plugin->get_search_delay(); while(window.isOpen()) { @@ -421,8 +421,9 @@ namespace QuickMedia { } } else if(handle_searchbar && event.type == sf::Event::TextEntered) { search_bar->onTextEntered(event.text.unicode); + } else if(handle_searchbar) { + search_bar->on_event(event); } - search_bar->on_event(event); } static std::string base64_encode(const std::string &data) { @@ -471,7 +472,8 @@ namespace QuickMedia { enum class SearchSuggestionTab { ALL, HISTORY, - RECOMMENDED + RECOMMENDED, + LOGIN }; // Returns relative time as a string (approximation) @@ -734,10 +736,30 @@ namespace QuickMedia { body_size = sf::Vector2f(body_width, window_size.y - body_padding_vertical - related_videos_text_height); } + class LoginTab { + public: + LoginTab(sf::Font &font) : + username(std::make_unique(font, nullptr, "Token...")), + password(std::make_unique(font, nullptr, "PIN...", true)) + { + + } + std::unique_ptr username; + std::unique_ptr password; + }; + + struct Tab { + Body *body; + std::unique_ptr login_tab; + SearchSuggestionTab tab; + sf::Text *text; + }; + void Program::search_suggestion_page() { std::string update_search_text; bool search_running = false; bool typing = false; + bool is_fourchan = current_plugin->name == "4chan"; std::string autocomplete_text; bool autocomplete_running = false; @@ -747,6 +769,8 @@ namespace QuickMedia { sf::Text all_tab_text("All", font, tab_text_size); sf::Text history_tab_text("History", font, tab_text_size); sf::Text recommended_tab_text("Recommended", font, tab_text_size); + sf::Text login_tab_text("Login", font, tab_text_size); + SearchBar *focused_login_input = nullptr; if(current_plugin->name == "youtube") { recommended_body = std::make_unique(this, &font, &bold_font); @@ -754,17 +778,48 @@ namespace QuickMedia { fill_recommended_items_from_json(load_recommended_json(current_plugin), recommended_body->items); } - struct Tab { - Body *body; - SearchSuggestionTab tab; - sf::Text *text; + std::vector tabs; + int selected_tab = 0; + + auto login_submit_callback = [this, &tabs, &selected_tab](const std::string &text) -> bool { + if(!tabs[selected_tab].body) { + std::string username = tabs[selected_tab].login_tab->username->get_text(); + std::string password = tabs[selected_tab].login_tab->password->get_text(); + if(current_plugin->name == "4chan") { + std::string response_msg; + PluginResult result = static_cast(current_plugin)->login(username, password, response_msg); + if(result == PluginResult::NET_ERR) { + show_notification("4chan", "Login failed!", Urgency::CRITICAL); + } else if(result == PluginResult::ERR) { + std::string desc = "Login failed, reason: "; + if(response_msg.empty()) + desc += "Unknown"; + else + desc += response_msg; + show_notification("4chan", desc, Urgency::CRITICAL); + } else if(result == PluginResult::OK) { + show_notification("4chan", "Successfully logged in!", Urgency::LOW); + selected_tab = 0; + } + } + } + return false; }; - std::vector tabs = { Tab{body, SearchSuggestionTab::ALL, &all_tab_text}, Tab{&history_body, SearchSuggestionTab::HISTORY, &history_tab_text} }; + tabs.push_back(Tab{body, nullptr, SearchSuggestionTab::ALL, &all_tab_text}); + tabs.push_back(Tab{&history_body, nullptr, SearchSuggestionTab::HISTORY, &history_tab_text}); if(recommended_body) - tabs.push_back(Tab{recommended_body.get(), SearchSuggestionTab::RECOMMENDED, &recommended_tab_text}); + tabs.push_back(Tab{recommended_body.get(), nullptr, SearchSuggestionTab::RECOMMENDED, &recommended_tab_text}); + if(is_fourchan) { + tabs.push_back(Tab{nullptr, std::make_unique(font), SearchSuggestionTab::LOGIN, &login_tab_text}); + focused_login_input = tabs.back().login_tab->username.get(); - int selected_tab = 0; + tabs.back().login_tab->username->caret_visible = true; + tabs.back().login_tab->password->caret_visible = false; + + tabs.back().login_tab->username->onTextSubmitCallback = login_submit_callback; + tabs.back().login_tab->password->onTextSubmitCallback = login_submit_callback; + } plugin_get_watch_history(current_plugin, history_body.items); if(current_plugin->name == "youtube") @@ -878,14 +933,14 @@ namespace QuickMedia { //int fps = 0; while (current_page == Page::SEARCH_SUGGESTION) { while (window.pollEvent(event)) { - base_event_handler(event, Page::EXIT, false); + base_event_handler(event, Page::EXIT, false, true, tabs[selected_tab].body != nullptr); if(event.type == sf::Event::Resized || event.type == sf::Event::GainedFocus) redraw = true; else if(event.type == sf::Event::KeyPressed) { if(event.key.code == sf::Keyboard::Up) { - tabs[selected_tab].body->select_previous_item(); + if(tabs[selected_tab].body ) tabs[selected_tab].body->select_previous_item(); } else if(event.key.code == sf::Keyboard::Down) { - tabs[selected_tab].body->select_next_item(); + if(tabs[selected_tab].body) tabs[selected_tab].body->select_next_item(); } else if(event.key.code == sf::Keyboard::Escape) { current_page = Page::EXIT; exit_code = 1; @@ -896,7 +951,22 @@ namespace QuickMedia { selected_tab = std::min((int)tabs.size() - 1, selected_tab + 1); search_bar->clear(); } else if(event.key.code == sf::Keyboard::Tab) { - search_bar->set_to_autocomplete(); + if(tabs[selected_tab].body) search_bar->set_to_autocomplete(); + } + } + + if(!tabs[selected_tab].body) { + if(event.type == sf::Event::TextEntered) + focused_login_input->onTextEntered(event.text.unicode); + focused_login_input->on_event(event); + + if(event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Tab) { + focused_login_input->caret_visible = false; + if(focused_login_input == tabs[selected_tab].login_tab->username.get()) + focused_login_input = tabs[selected_tab].login_tab->password.get(); + else + focused_login_input = tabs[selected_tab].login_tab->username.get(); + focused_login_input->caret_visible = true; } } } @@ -907,7 +977,8 @@ namespace QuickMedia { get_body_dimensions(window_size, search_bar.get(), body_pos, body_size, true); } - search_bar->update(); + if(tabs[selected_tab].body) + search_bar->update(); if(!update_search_text.empty() && !search_running) { search_suggestion_future = std::async(std::launch::async, [this, update_search_text]() { @@ -959,10 +1030,19 @@ namespace QuickMedia { //window.draw(tab_spacing_rect); const float width_per_tab = window_size.x / tabs.size(); - const float tab_y = tab_spacer_height + std::floor(search_bar->getBottomWithoutShadow() + tab_height * 0.5f - (tab_text_size + 5.0f) * 0.5f); sf::RectangleShape tab_background(sf::Vector2f(std::floor(width_per_tab), tab_height)); - tabs[selected_tab].body->draw(window, body_pos, body_size); + float tab_vertical_offset = search_bar->getBottomWithoutShadow(); + if(tabs[selected_tab].body) { + tabs[selected_tab].body->draw(window, body_pos, body_size); + } else { + tabs[selected_tab].login_tab->username->draw(window, false); + tabs[selected_tab].login_tab->password->draw(window, false); + tabs[selected_tab].login_tab->password->set_vertical_position(tabs[selected_tab].login_tab->username->getBottomWithoutShadow()); + tab_vertical_offset = tabs[selected_tab].login_tab->username->getBottomWithoutShadow() + tabs[selected_tab].login_tab->password->getBottomWithoutShadow(); + } + const float tab_y = tab_spacer_height + std::floor(tab_vertical_offset + tab_height * 0.5f - (tab_text_size + 5.0f) * 0.5f); + int i = 0; for(Tab &tab : tabs) { if(i == selected_tab) { @@ -970,7 +1050,7 @@ namespace QuickMedia { } else { tab_background.setFillColor(tab_unselected_color); } - tab_background.setPosition(std::floor(i * width_per_tab), tab_spacer_height + std::floor(search_bar->getBottomWithoutShadow())); + tab_background.setPosition(std::floor(i * width_per_tab), tab_spacer_height + std::floor(tab_vertical_offset)); window.draw(tab_background); const float center = (i * width_per_tab) + (width_per_tab * 0.5f); tab.text->setPosition(std::floor(center - tab.text->getLocalBounds().width * 0.5f), tab_y); @@ -979,10 +1059,11 @@ namespace QuickMedia { } tab_drop_shadow.setSize(sf::Vector2f(window_size.x, 5.0f)); - tab_drop_shadow.setPosition(0.0f, std::floor(search_bar->getBottomWithoutShadow() + tab_height)); + tab_drop_shadow.setPosition(0.0f, std::floor(tab_vertical_offset + tab_height)); window.draw(tab_drop_shadow); } - search_bar->draw(window, false); + if(tabs[selected_tab].body) + search_bar->draw(window, false); // fps++; // if(tt.getElapsedTime().asMilliseconds() >= 1000) { @@ -2251,7 +2332,7 @@ namespace QuickMedia { // 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 = [&post_comment_future, &navigation_stage, &request_new_google_captcha_challenge, &comment_to_post, &captcha_post_id, &captcha_solved_time, &post_comment](const std::string &text) -> bool { + search_bar->onTextSubmitCallback = [&post_comment_future, &navigation_stage, &request_new_google_captcha_challenge, &comment_to_post, &captcha_post_id, &captcha_solved_time, &post_comment, &image_board](const std::string &text) -> bool { if(text.empty()) return false; @@ -2262,8 +2343,13 @@ namespace QuickMedia { post_comment(); return true; }); - } else { + } else if(image_board->get_pass_id().empty()) { request_new_google_captcha_challenge(); + } else if(!image_board->get_pass_id().empty()) { + post_comment_future = std::async(std::launch::async, [&post_comment]() -> bool { + post_comment(); + return true; + }); } return true; }; diff --git a/src/SearchBar.cpp b/src/SearchBar.cpp index 803eaee..62a0196 100644 --- a/src/SearchBar.cpp +++ b/src/SearchBar.cpp @@ -13,7 +13,7 @@ const float PADDING_HORIZONTAL = 50.0f; const float padding_vertical = 20.0f; namespace QuickMedia { - SearchBar::SearchBar(sf::Font &font, sf::Texture &plugin_logo, const std::string &placeholder) : + SearchBar::SearchBar(sf::Font &font, sf::Texture *plugin_logo, const std::string &placeholder, bool input_masked) : onTextUpdateCallback(nullptr), onTextSubmitCallback(nullptr), onTextBeginTypingCallback(nullptr), @@ -27,7 +27,10 @@ namespace QuickMedia { updated_search(false), updated_autocomplete(false), draw_logo(false), - needs_update(true) + needs_update(true), + input_masked(input_masked), + caret_visible(true), + vertical_pos(0.0f) { text.setFillColor(text_placeholder_color); autocomplete_text.setFillColor(text_placeholder_color); @@ -37,8 +40,8 @@ namespace QuickMedia { shade.setFillColor(sf::Color(0, 85, 119)); //background.setOutlineThickness(1.0f); //background.setOutlineColor(sf::Color(13, 15, 17)); - if(plugin_logo.getNativeHandle() != 0) - plugin_logo_sprite.setTexture(plugin_logo, true); + if(plugin_logo && plugin_logo->getNativeHandle() != 0) + plugin_logo_sprite.setTexture(*plugin_logo, true); } void SearchBar::draw(sf::RenderWindow &window, bool draw_shadow) { @@ -53,13 +56,23 @@ namespace QuickMedia { window.draw(background); // TODO: Render starting from the character after text length window.draw(autocomplete_text); - window.draw(text); - if(show_placeholder || text.getString().isEmpty()) - caret.setPosition(text.getPosition() - sf::Vector2f(2.0f, 0.0f)); - else - caret.setPosition(text.findCharacterPos(text.getString().getSize())); + if(input_masked && !show_placeholder) { + std::string masked_str(text.getString().getSize(), '*'); + sf::Text masked_text(std::move(masked_str), *text.getFont(), text.getCharacterSize()); + masked_text.setPosition(text.getPosition()); + window.draw(masked_text); + caret.setPosition(masked_text.findCharacterPos(masked_text.getString().getSize())); + } else { + window.draw(text); + if(show_placeholder || text.getString().isEmpty()) + caret.setPosition(text.getPosition() - sf::Vector2f(2.0f, 0.0f)); + else + caret.setPosition(text.findCharacterPos(text.getString().getSize())); + } - window.draw(caret); + if(caret_visible) + window.draw(caret); + if(draw_logo) window.draw(plugin_logo_sprite); } @@ -104,19 +117,20 @@ namespace QuickMedia { sf::Vector2f texture_size_f(texture_size.x, texture_size.y); sf::Vector2f new_size = wrap_to_size(texture_size_f, sf::Vector2f(200.0f, one_line_height)); plugin_logo_sprite.setScale(get_ratio(texture_size_f, new_size)); - plugin_logo_sprite.setPosition(25.0f, padding_vertical); + plugin_logo_sprite.setPosition(25.0f, padding_vertical + vertical_pos); offset_x = 25.0f + new_size.x + 25.0f; } const float width = std::floor(window_size.x - offset_x - padding_horizontal); background.setSize(sf::Vector2f(width, rect_height)); shade.setSize(sf::Vector2f(window_size.x, padding_vertical + rect_height + padding_vertical)); - caret.setSize(sf::Vector2f(2.0f, text.getLocalBounds().height + 12.0f)); + caret.setSize(sf::Vector2f(2.0f, text.getCharacterSize() + 8.0f)); background_shadow.setSize(sf::Vector2f(window_size.x, 5.0f)); - background.setPosition(offset_x, padding_vertical); - background_shadow.setPosition(0.0f, std::floor(shade.getSize().y)); - sf::Vector2f font_position(std::floor(offset_x + background_margin_horizontal), std::floor(padding_vertical + background_margin_vertical)); + background.setPosition(offset_x, padding_vertical + vertical_pos); + shade.setPosition(0.0f, vertical_pos); + background_shadow.setPosition(0.0f, std::floor(shade.getSize().y + vertical_pos)); + sf::Vector2f font_position(std::floor(offset_x + background_margin_horizontal), std::floor(padding_vertical + background_margin_vertical + vertical_pos)); autocomplete_text.setPosition(font_position); text.setPosition(font_position); } @@ -221,6 +235,13 @@ namespace QuickMedia { autocomplete_text.setString(text); } + void SearchBar::set_vertical_position(float vertical_pos) { + if(std::abs(this->vertical_pos - vertical_pos) > 1.0f) { + this->vertical_pos = vertical_pos; + needs_update = true; + } + } + void SearchBar::clear_autocomplete_if_text_not_substring() { const sf::String &text_str = text.getString(); const sf::String &autocomplete_str = autocomplete_text.getString(); @@ -258,4 +279,10 @@ namespace QuickMedia { float SearchBar::getBottomWithoutShadow() const { return shade.getSize().y; } + + std::string SearchBar::get_text() const { + if(show_placeholder) + return ""; + return text.getString(); + } } \ No newline at end of file diff --git a/src/plugins/Fourchan.cpp b/src/plugins/Fourchan.cpp index 42bb54e..5932144 100644 --- a/src/plugins/Fourchan.cpp +++ b/src/plugins/Fourchan.cpp @@ -45,7 +45,34 @@ namespace QuickMedia { thread_list_update_thread.join(); } + // Returns empty string on failure to read cookie + static std::string get_pass_id_from_cookies_file(const Path &cookies_filepath) { + std::string file_content; + if(file_get_content(cookies_filepath, file_content) != 0) + return ""; + + size_t pass_id_index = file_content.find("pass_id"); + if(pass_id_index == std::string::npos) + return ""; + + pass_id_index += 7; + size_t line_end = file_content.find('\n', pass_id_index); + if(line_end == std::string::npos) + line_end = file_content.size(); + + return strip(file_content.substr(pass_id_index, line_end - pass_id_index)); + } + PluginResult Fourchan::get_front_page(BodyItems &result_items) { + if(pass_id.empty()) { + Path cookies_filepath; + if(get_cookies_filepath(cookies_filepath, name) != 0) { + fprintf(stderr, "Failed to get 4chan cookies filepath\n"); + } else { + pass_id = get_pass_id_from_cookies_file(cookies_filepath); + } + } + #if 0 std::string server_response; if(download_to_string(fourchan_url + "boards.json", server_response, {}, use_tor) != DownloadResult::OK) @@ -543,6 +570,20 @@ namespace QuickMedia { CommandArg{"-H", "Content-Type: multipart/form-data; boundary=---------------------------119561554312148213571335532670"}, CommandArg{"-H", "Origin: https://boards.4chan.org"} }; + + if(pass_id.empty()) { + form_data.push_back(FormData{"g-recaptcha-response", captcha_id}); + } else { + Path cookies_filepath; + if(get_cookies_filepath(cookies_filepath, name) != 0) { + fprintf(stderr, "Failed to get 4chan cookies filepath\n"); + return PostResult::ERR; + } else { + additional_args.push_back(CommandArg{"-c", cookies_filepath.data}); + additional_args.push_back(CommandArg{"-b", cookies_filepath.data}); + } + } + std::vector form_data_args = create_command_args_from_form_data(form_data); additional_args.insert(additional_args.end(), form_data_args.begin(), form_data_args.end()); @@ -573,4 +614,55 @@ namespace QuickMedia { } return body_items; } + + PluginResult Fourchan::login(const std::string &token, const std::string &pin, std::string &response_msg) { + response_msg.clear(); + + Path cookies_filepath; + if(get_cookies_filepath(cookies_filepath, name) != 0) { + fprintf(stderr, "Failed to get 4chan cookies filepath\n"); + return PluginResult::ERR; + } + + std::vector additional_args = { + CommandArg{"-F", "id=" + token}, + CommandArg{"-F", "pin=" + pin}, + CommandArg{"-F", "xhr=1"}, + CommandArg{"-c", cookies_filepath.data} + }; + + std::string response; + if(download_to_string("https://sys.4chan.org/auth", response, std::move(additional_args), use_tor, true) != DownloadResult::OK) + return PluginResult::NET_ERR; + + Json::Value json_root; + Json::CharReaderBuilder json_builder; + std::unique_ptr json_reader(json_builder.newCharReader()); + std::string json_errors; + if(!json_reader->parse(&response[0], &response[response.size()], &json_root, &json_errors) || !json_root.isObject()) { + fprintf(stderr, "Youtube get front page error: %s\n", json_errors.c_str()); + return PluginResult::ERR; + } + + const Json::Value &status_json = json_root["status"]; + if(!status_json.isNumeric()) + return PluginResult::ERR; + + const Json::Value &message_json = json_root["message"]; + if(message_json.isString()) + response_msg = message_json.asString(); + + if(status_json.asInt64() == 1) { + pass_id = get_pass_id_from_cookies_file(cookies_filepath); + if(pass_id.empty()) + return PluginResult::ERR; + return PluginResult::OK; + } else { + return PluginResult::ERR; + } + } + + const std::string& Fourchan::get_pass_id() const { + return pass_id; + } } \ No newline at end of file diff --git a/src/plugins/Mangadex.cpp b/src/plugins/Mangadex.cpp index 10f689f..4afa89b 100644 --- a/src/plugins/Mangadex.cpp +++ b/src/plugins/Mangadex.cpp @@ -241,7 +241,7 @@ namespace QuickMedia { } bool Mangadex::save_mangadex_cookies(const std::string &url, const std::string &cookie_filepath) { - CommandArg cookie_arg = { "-c", std::move(cookie_filepath) }; + CommandArg cookie_arg = { "-c", cookie_filepath }; std::string server_response; if(download_to_string(url, server_response, {std::move(cookie_arg)}, use_tor, true) != DownloadResult::OK) return false; -- cgit v1.2.3