diff options
author | dec05eba <dec05eba@protonmail.com> | 2021-07-28 15:50:26 +0200 |
---|---|---|
committer | dec05eba <dec05eba@protonmail.com> | 2021-07-28 15:50:26 +0200 |
commit | 60f22a9cba69a8443ed1442c5294a0102ed6f1a3 (patch) | |
tree | e78736a46c878002ce266042731b085c9987435d | |
parent | b8e40694b539e74f1778b380087764bb76213fc5 (diff) |
4chan: add timeout for posting without pass, handle noop captcha challenge
-rw-r--r-- | include/Utils.hpp | 1 | ||||
-rw-r--r-- | plugins/ImageBoard.hpp | 7 | ||||
-rw-r--r-- | src/Downloader.cpp | 2 | ||||
-rw-r--r-- | src/QuickMedia.cpp | 122 | ||||
-rw-r--r-- | src/Utils.cpp | 9 | ||||
-rw-r--r-- | src/plugins/Fourchan.cpp | 53 | ||||
-rw-r--r-- | src/plugins/MangaGeneric.cpp | 2 | ||||
-rw-r--r-- | src/plugins/NyaaSi.cpp | 9 | ||||
-rw-r--r-- | src/plugins/Youtube.cpp | 4 | ||||
-rw-r--r-- | src/plugins/youtube/Signature.cpp | 2 | ||||
-rw-r--r-- | src/plugins/youtube/YoutubeMediaProxy.cpp | 2 |
11 files changed, 147 insertions, 66 deletions
diff --git a/include/Utils.hpp b/include/Utils.hpp index cdef844..7aec9e6 100644 --- a/include/Utils.hpp +++ b/include/Utils.hpp @@ -11,4 +11,5 @@ namespace QuickMedia { bool is_running_wayland(); time_t iso_utc_to_unix_time(const char *time_str); std::string unix_time_to_local_time_str(time_t unix_time); + int64_t get_unix_time_monotonic(); }
\ No newline at end of file diff --git a/plugins/ImageBoard.hpp b/plugins/ImageBoard.hpp index 835d92a..5b299b3 100644 --- a/plugins/ImageBoard.hpp +++ b/plugins/ImageBoard.hpp @@ -6,6 +6,7 @@ namespace QuickMedia { enum class PostResult { OK, TRY_AGAIN, + INVALID_CAPTCHA, BANNED, //FILE_TOO_LARGE, NO_SUCH_FILE, @@ -14,11 +15,12 @@ namespace QuickMedia { ERR }; + // All fields are optional struct ImageBoardCaptchaChallenge { std::string challenge_id; std::string img_data; - std::string bg_data; // optional - int ttl = 0; // optional + std::string bg_data; + int ttl = 0; }; class ImageBoardThreadPage : public VideoPage { @@ -37,7 +39,6 @@ namespace QuickMedia { virtual PostResult post_comment(const std::string &captcha_id, const std::string &captcha_solution, const std::string &comment, const std::string &filepath = "") = 0; virtual const std::string& get_pass_id(); - // |bg_data|, |bg_size| and |ttl| are optional virtual PluginResult request_captcha_challenge(ImageBoardCaptchaChallenge &challenge_response) = 0; const std::string board_id; diff --git a/src/Downloader.cpp b/src/Downloader.cpp index 68bbf32..6f6d709 100644 --- a/src/Downloader.cpp +++ b/src/Downloader.cpp @@ -61,7 +61,7 @@ namespace QuickMedia { if(!content_length_str.empty()) { errno = 0; char *endptr; - const long content_length_tmp = strtol(content_length_str.c_str(), &endptr, 10); + const long content_length_tmp = strtoll(content_length_str.c_str(), &endptr, 10); if(endptr != content_length_str.c_str() && errno == 0) { std::lock_guard<std::mutex> lock(content_length_mutex); content_length = content_length_tmp; diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index f4cc2dd..b52678d 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -437,7 +437,7 @@ namespace QuickMedia { } } else if(strcmp(argv[i], "-e") == 0) { if(i < argc - 1) { - parent_window = strtol(argv[i + 1], nullptr, 0); + parent_window = strtoll(argv[i + 1], nullptr, 0); if(parent_window == None && errno == EINVAL) { fprintf(stderr, "Invalid -e argument. Argument has to be a number\n"); usage(); @@ -1001,7 +1001,7 @@ namespace QuickMedia { std::string id_str = base64_url_decode(filename); char *endptr = nullptr; errno = 0; - long id = strtol(id_str.c_str(), &endptr, 10); + long id = strtoll(id_str.c_str(), &endptr, 10); if(endptr != id_str.c_str() && errno == 0) legacy_manga_ids.push_back(id); return true; @@ -3663,6 +3663,15 @@ namespace QuickMedia { window_size.y = window.getSize().y; } + static bool get_image_board_last_posted_filepath(const char *plugin_name, Path &path) { + Path dir = get_storage_dir().join(plugin_name); + if(create_directory_recursive(dir) != 0) + return false; + path = std::move(dir); + path.join("last_posted_time"); + return true; + } + void Program::image_board_thread_page(ImageBoardThreadPage *thread_page, Body *thread_body) { // TODO: Instead of using stage here, use different pages for each stage enum class NavigationStage { @@ -3686,6 +3695,9 @@ namespace QuickMedia { std::string attached_image_url; ImageBoardCaptchaChallenge captcha_challenge; + captcha_texture.setSmooth(true); + captcha_bg_texture.setSmooth(true); + const float captcha_slide_padding_x = std::floor(4.0f * get_ui_scale()); const float captcha_slide_padding_y = std::floor(4.0f * get_ui_scale()); sf::Color background_color_darker = get_current_theme().background_color; @@ -3700,11 +3712,26 @@ namespace QuickMedia { std::string captcha_post_id; std::string captcha_solution; - sf::Clock captcha_solved_time; std::string comment_to_post; const int captcha_solution_text_height = 18 * get_ui_scale(); sf::Text captcha_solution_text("", *FontLoader::get_font(FontLoader::FontType::LATIN_BOLD), captcha_solution_text_height); - int solved_captcha_ttl = 120; + int solved_captcha_ttl = 0; + int64_t last_posted_time = get_unix_time_monotonic(); + int64_t seconds_until_post_again = 60; // TODO: Timeout for other imageboards + bool has_post_timeout = thread_page->get_pass_id().empty(); + + bool has_posted_before = false; + Path last_posted_time_filepath; + if(get_image_board_last_posted_filepath(plugin_name, last_posted_time_filepath)) { + std::string d; + if(file_get_content(last_posted_time_filepath, d) == 0) { + last_posted_time = strtoll(d.c_str(), nullptr, 10); + has_posted_before = true; + } + } + + if(!has_posted_before) + last_posted_time -= (seconds_until_post_again + 1); bool redraw = true; @@ -3714,7 +3741,7 @@ namespace QuickMedia { std::string selected_file_for_upload; - auto post_comment = [&comment_input, &selected_file_for_upload, &navigation_stage, &thread_page, &captcha_post_id, &captcha_solution](std::string comment_to_post, std::string file_to_upload) { + auto post_comment = [this, &comment_input, &selected_file_for_upload, &navigation_stage, &thread_page, &captcha_post_id, &captcha_solution, &last_posted_time, &has_post_timeout](std::string comment_to_post, std::string file_to_upload) { comment_input.set_editable(false); PostResult post_result = thread_page->post_comment(captcha_post_id, captcha_solution, comment_to_post, file_to_upload); if(post_result == PostResult::OK) { @@ -3724,9 +3751,17 @@ namespace QuickMedia { // TODO: Append posted comment to the thread so the user can see their posted comment. // TODO: Asynchronously update the thread periodically to show new comments. selected_file_for_upload.clear(); // TODO: Remove from here, this is async + has_post_timeout = thread_page->get_pass_id().empty(); + last_posted_time = get_unix_time_monotonic(); + + Path last_posted_time_filepath; + if(get_image_board_last_posted_filepath(plugin_name, last_posted_time_filepath)) + file_overwrite_atomic(last_posted_time_filepath, std::to_string(last_posted_time)); } else if(post_result == PostResult::TRY_AGAIN) { - show_notification("QuickMedia", "Invalid captcha, try again"); - // TODO: Check if the response contains a new captcha instead of requesting a new one manually + show_notification("QuickMedia", "Please wait before you post again"); + navigation_stage = NavigationStage::SOLVING_POST_CAPTCHA; + } else if(post_result == PostResult::INVALID_CAPTCHA) { + show_notification("QuickMedia", "Invalid captcha, please try again"); navigation_stage = NavigationStage::SOLVING_POST_CAPTCHA; } else if(post_result == PostResult::BANNED) { show_notification("QuickMedia", "Failed to post comment because you are banned", Urgency::CRITICAL); @@ -3755,16 +3790,19 @@ namespace QuickMedia { bool frame_skip_text_entry = false; - comment_input.on_submit_callback = [&frame_skip_text_entry, &comment_input, &navigation_stage, &comment_to_post, &captcha_post_id, &captcha_solved_time, &solved_captcha_ttl, &selected_file_for_upload, &thread_page](std::string text) -> bool { + comment_input.on_submit_callback = [&frame_skip_text_entry, &comment_input, &navigation_stage, &comment_to_post, &captcha_post_id, &selected_file_for_upload, &thread_page, &has_post_timeout, &seconds_until_post_again, &last_posted_time](std::string text) -> bool { if(text.empty() && selected_file_for_upload.empty()) return false; + + if(has_post_timeout && seconds_until_post_again - (get_unix_time_monotonic() - last_posted_time) > 0) + return false; comment_input.set_editable(false); frame_skip_text_entry = true; assert(navigation_stage == NavigationStage::REPLYING); comment_to_post = std::move(text); - if((!captcha_post_id.empty() && captcha_solved_time.getElapsedTime().asSeconds() < solved_captcha_ttl) || !thread_page->get_pass_id().empty()) { + if(!captcha_post_id.empty() || !thread_page->get_pass_id().empty()) { navigation_stage = NavigationStage::POSTING_COMMENT; } else if(thread_page->get_pass_id().empty()) { navigation_stage = NavigationStage::REQUESTING_CAPTCHA; @@ -3873,6 +3911,7 @@ namespace QuickMedia { if(task_result == TaskResult::TRUE) { attached_image_texture = std::make_unique<sf::Texture>(); if(attached_image_texture->loadFromImage(image)) { + attached_image_texture->setSmooth(true); attached_image_sprite.setTexture(*attached_image_texture, true); navigation_stage = NavigationStage::VIEWING_ATTACHED_IMAGE; } else { @@ -4048,29 +4087,34 @@ namespace QuickMedia { captcha_solution_text.setString(""); captcha_slide = 0.0f; - bool failed = false; - sf::Image image; - if(!load_image_from_memory(image, captcha_challenge.img_data.data(), captcha_challenge.img_data.size()) || !captcha_texture.loadFromImage(image)) { - show_notification("QuickMedia", "Failed to load captcha image", Urgency::CRITICAL); - failed = true; - } - captcha_challenge.img_data = std::string(); + if(captcha_post_id == "noop") { // TODO: Fix for other imageboard than 4chan in the future + captcha_solution.clear(); + navigation_stage = NavigationStage::POSTING_COMMENT; + } else { + bool failed = false; + sf::Image image; + if(!load_image_from_memory(image, captcha_challenge.img_data.data(), captcha_challenge.img_data.size()) || !captcha_texture.loadFromImage(image)) { + show_notification("QuickMedia", "Failed to load captcha image", Urgency::CRITICAL); + failed = true; + } + captcha_challenge.img_data = std::string(); - has_captcha_bg = !failed && !captcha_challenge.bg_data.empty(); - sf::Image bg_Image; - if(has_captcha_bg && (!load_image_from_memory(bg_Image, captcha_challenge.bg_data.data(), captcha_challenge.bg_data.size()) || !captcha_bg_texture.loadFromImage(bg_Image))) { - show_notification("QuickMedia", "Failed to load captcha image", Urgency::CRITICAL); - failed = true; - } - captcha_challenge.bg_data = std::string(); + has_captcha_bg = !failed && !captcha_challenge.bg_data.empty(); + sf::Image bg_Image; + if(has_captcha_bg && (!load_image_from_memory(bg_Image, captcha_challenge.bg_data.data(), captcha_challenge.bg_data.size()) || !captcha_bg_texture.loadFromImage(bg_Image))) { + show_notification("QuickMedia", "Failed to load captcha image", Urgency::CRITICAL); + failed = true; + } + captcha_challenge.bg_data = std::string(); - if(failed) { - navigation_stage = NavigationStage::VIEWING_COMMENTS; - } else { - navigation_stage = NavigationStage::SOLVING_POST_CAPTCHA; - captcha_sprite.setTexture(captcha_texture, true); - if(has_captcha_bg) - captcha_bg_sprite.setTexture(captcha_bg_texture, true); + if(failed) { + navigation_stage = NavigationStage::VIEWING_COMMENTS; + } else { + navigation_stage = NavigationStage::SOLVING_POST_CAPTCHA; + captcha_sprite.setTexture(captcha_texture, true); + if(has_captcha_bg) + captcha_bg_sprite.setTexture(captcha_bg_texture, true); + } } } else if(task_result == TaskResult::CANCEL) { comment_input.set_editable(false); @@ -4270,6 +4314,24 @@ namespace QuickMedia { comment_input.draw(window); thread_body->draw(window, body_pos, body_size); } + + if((navigation_stage == NavigationStage::REPLYING || navigation_stage == NavigationStage::VIEWING_COMMENTS) && has_post_timeout) { + int64_t time_left_until_post_again = seconds_until_post_again - (get_unix_time_monotonic() - last_posted_time); + if(time_left_until_post_again > 0) { + sf::RectangleShape time_left_bg(comment_input_shade.getSize()); + time_left_bg.setPosition(comment_input_shade.getPosition()); + time_left_bg.setFillColor(sf::Color(0, 0, 0, 100)); + window.draw(time_left_bg); + + sf::Text time_left_text("Wait " + std::to_string(time_left_until_post_again) + " second(s) before posting again", *FontLoader::get_font(FontLoader::FontType::LATIN), std::floor(18 * get_ui_scale())); + time_left_text.setPosition(time_left_bg.getPosition() + + sf::Vector2f( + std::floor(time_left_bg.getSize().x * 0.5f - time_left_text.getLocalBounds().width * 0.5f), + std::floor(time_left_bg.getSize().y * 0.5f - time_left_text.getLocalBounds().height * 0.5f))); + window.draw(time_left_text); + } + } + AsyncImageLoader::get_instance().update(); window.display(); } diff --git a/src/Utils.cpp b/src/Utils.cpp index c73932b..3032f22 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -126,4 +126,13 @@ namespace QuickMedia { strftime(time_str, sizeof(time_str) - 1, "%Y %b %d, %a %H:%M", &time_tm); return time_str; } + + int64_t get_unix_time_monotonic() { + struct timespec t; + if(clock_gettime(CLOCK_MONOTONIC, &t) == -1) { + fprintf(stderr, "This kernel version doesn't support CLOCK_MONOTONIC\n"); + abort(); + } + return t.tv_sec; + } }
\ No newline at end of file diff --git a/src/plugins/Fourchan.cpp b/src/plugins/Fourchan.cpp index 84a0675..24886c6 100644 --- a/src/plugins/Fourchan.cpp +++ b/src/plugins/Fourchan.cpp @@ -503,6 +503,12 @@ namespace QuickMedia { } PostResult FourchanThreadPage::post_comment(const std::string &captcha_id, const std::string &captcha_solution, const std::string &comment, const std::string &filepath) { + Path cookies_filepath; + if(get_cookies_filepath(cookies_filepath, SERVICE_NAME) != 0) { + fprintf(stderr, "Failed to get 4chan cookies filepath\n"); + return PostResult::ERR; + } + std::string url = "https://sys.4chan.org/" + board_id + "/post"; std::vector<CommandArg> additional_args = { @@ -510,7 +516,9 @@ namespace QuickMedia { CommandArg{"-H", "Origin: https://boards.4chan.org"}, CommandArg{"--form-string", "resto=" + thread_id}, CommandArg{"--form-string", "com=" + comment}, - CommandArg{"--form-string", "mode=regist"} + CommandArg{"--form-string", "mode=regist"}, + CommandArg{"-c", cookies_filepath.data}, + CommandArg{"-b", cookies_filepath.data} }; if(!filepath.empty()) { @@ -520,17 +528,7 @@ namespace QuickMedia { if(pass_id.empty()) { additional_args.push_back(CommandArg{"--form-string", "t-challenge=" + captcha_id}); - if(!captcha_solution.empty()) - additional_args.push_back(CommandArg{"--form-string", "t-response=" + captcha_solution}); - } else { - Path cookies_filepath; - if(get_cookies_filepath(cookies_filepath, SERVICE_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}); - } + additional_args.push_back(CommandArg{"--form-string", "t-response=" + captcha_solution}); } std::string response; @@ -541,8 +539,10 @@ namespace QuickMedia { return PostResult::OK; if(response.find("banned") != std::string::npos) return PostResult::BANNED; - if(response.find("try again") != std::string::npos || response.find("No valid captcha") != std::string::npos) + if(response.find("try again") != std::string::npos) return PostResult::TRY_AGAIN; + if(response.find("No valid captcha") != std::string::npos) + return PostResult::INVALID_CAPTCHA; if(response.find("Audio streams are not allowed") != std::string::npos) return PostResult::FILE_TYPE_NOT_ALLOWED; if(response.find("Error: Upload failed") != std::string::npos) @@ -581,8 +581,19 @@ namespace QuickMedia { } PluginResult FourchanThreadPage::request_captcha_challenge(ImageBoardCaptchaChallenge &challenge_response) { + Path cookies_filepath; + if(get_cookies_filepath(cookies_filepath, SERVICE_NAME) != 0) { + fprintf(stderr, "Failed to get 4chan cookies filepath\n"); + return PluginResult::ERR; + } + + std::vector<CommandArg> additional_args = { + CommandArg{"-c", cookies_filepath.data}, + CommandArg{"-b", cookies_filepath.data} + }; + Json::Value json_root; - DownloadResult result = download_json(json_root, "https://sys.4chan.org/captcha?board=" + url_param_encode(board_id) + "&thread_id=" + thread_id, {}, true); + DownloadResult result = download_json(json_root, "https://sys.4chan.org/captcha?board=" + url_param_encode(board_id) + "&thread_id=" + thread_id, std::move(additional_args), true); if(result != DownloadResult::OK) return download_result_to_plugin_result(result); if(!json_root.isObject()) @@ -598,16 +609,20 @@ namespace QuickMedia { const Json::Value &img_json = json_root["img"]; const Json::Value &bg_json = json_root["bg"]; const Json::Value &ttl_json = json_root["ttl"]; - if(!challenge_json.isString() || !img_json.isString()) + if(!challenge_json.isString()) return PluginResult::ERR; challenge_response.challenge_id = challenge_json.asString(); + if(strcmp(challenge_json.asCString(), "noop") != 0) { + if(!img_json.isString()) + return PluginResult::ERR; - if(!base64_decode(img_json, challenge_response.img_data)) - return PluginResult::ERR; + if(!base64_decode(img_json, challenge_response.img_data)) + return PluginResult::ERR; - if(bg_json.isString() && !base64_decode(bg_json, challenge_response.bg_data)) - return PluginResult::ERR; + if(bg_json.isString() && !base64_decode(bg_json, challenge_response.bg_data)) + return PluginResult::ERR; + } if(ttl_json.isInt()) challenge_response.ttl = ttl_json.asInt(); diff --git a/src/plugins/MangaGeneric.cpp b/src/plugins/MangaGeneric.cpp index 29480da..4668970 100644 --- a/src/plugins/MangaGeneric.cpp +++ b/src/plugins/MangaGeneric.cpp @@ -487,7 +487,7 @@ namespace QuickMedia { if(field1_value.data) { std::string field_value_stripped(field1_value.data, field1_value.size); if(is_number(field_value_stripped.c_str())) - page_count_userdata->num_pages = strtol(field_value_stripped.c_str(), nullptr, 10); + page_count_userdata->num_pages = strtoll(field_value_stripped.c_str(), nullptr, 10); } return 0; }, &page_count_userdata); diff --git a/src/plugins/NyaaSi.cpp b/src/plugins/NyaaSi.cpp index 0202322..ccf027d 100644 --- a/src/plugins/NyaaSi.cpp +++ b/src/plugins/NyaaSi.cpp @@ -4,6 +4,7 @@ #include "../../include/Notification.hpp" #include "../../include/StringUtils.hpp" #include "../../include/NetUtils.hpp" +#include "../../include/Utils.hpp" #include <quickmedia/HtmlSearch.h> namespace QuickMedia { @@ -99,14 +100,6 @@ namespace QuickMedia { return timegm(&time); } - static std::string unix_time_to_local_time_str(time_t unix_time) { - struct tm time_tm; - localtime_r(&unix_time, &time_tm); - char time_str[128] = {0}; - strftime(time_str, sizeof(time_str) - 1, "%Y %b %d, %a %H:%M", &time_tm); - return time_str; - } - static const std::array<const char*, 10> sort_type_names = { "Size desc 🡇", "Uploaded date desc 🡇", diff --git a/src/plugins/Youtube.cpp b/src/plugins/Youtube.cpp index 7e5bd7b..f81d934 100644 --- a/src/plugins/Youtube.cpp +++ b/src/plugins/Youtube.cpp @@ -288,7 +288,7 @@ R"END( errno = 0; char *endptr; - content_length = strtol(content_length_str.c_str(), &endptr, 10); + content_length = strtoll(content_length_str.c_str(), &endptr, 10); if(endptr == content_length_str.c_str() || errno != 0) return ""; @@ -488,7 +488,7 @@ R"END( if(!upcoming_event_text) return nullptr; - time_t start_time = strtol(start_time_json.asCString(), nullptr, 10); + time_t start_time = strtoll(start_time_json.asCString(), nullptr, 10); struct tm message_tm; localtime_r(&start_time, &message_tm); char time_str[128] = {0}; diff --git a/src/plugins/youtube/Signature.cpp b/src/plugins/youtube/Signature.cpp index 59385ad..394abf0 100644 --- a/src/plugins/youtube/Signature.cpp +++ b/src/plugins/youtube/Signature.cpp @@ -77,7 +77,7 @@ namespace QuickMedia { errno = 0; char *endptr; - const long value_int = strtol(value_args.c_str(), &endptr, 10); + const long value_int = strtoll(value_args.c_str(), &endptr, 10); if(endptr != value_args.c_str() && errno == 0) new_func_calls.push_back({ std::move(func_name), value_int }); else diff --git a/src/plugins/youtube/YoutubeMediaProxy.cpp b/src/plugins/youtube/YoutubeMediaProxy.cpp index d7ffb53..6642b86 100644 --- a/src/plugins/youtube/YoutubeMediaProxy.cpp +++ b/src/plugins/youtube/YoutubeMediaProxy.cpp @@ -690,7 +690,7 @@ namespace QuickMedia { if(sequence_num.empty()) fprintf(stderr, "YoutubeLiveStreamMediaProxy::handle_download: missing sequence num from server\n"); else - livestream_sequence_num = strtol(sequence_num.c_str(), nullptr, 10); + livestream_sequence_num = strtoll(sequence_num.c_str(), nullptr, 10); } } else { if(download_header.size() > MAX_BUFFER_SIZE) { |