aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/GoogleCaptcha.cpp160
-rw-r--r--src/QuickMedia.cpp408
-rw-r--r--src/StringUtils.cpp2
-rw-r--r--src/plugins/Fourchan.cpp62
-rw-r--r--src/plugins/youtube/YoutubeMediaProxy.cpp2
5 files changed, 273 insertions, 361 deletions
diff --git a/src/GoogleCaptcha.cpp b/src/GoogleCaptcha.cpp
deleted file mode 100644
index 137eb24..0000000
--- a/src/GoogleCaptcha.cpp
+++ /dev/null
@@ -1,160 +0,0 @@
-#include "../include/GoogleCaptcha.hpp"
-#include "../include/StringUtils.hpp"
-#include "../include/DownloadUtils.hpp"
-
-namespace QuickMedia {
- static bool google_captcha_response_extract_id(const std::string& html, std::string& result)
- {
- size_t value_index = html.find("value=\"");
- if(value_index == std::string::npos)
- return false;
-
- size_t value_begin = value_index + 7;
- size_t value_end = html.find('"', value_begin);
- if(value_end == std::string::npos)
- return false;
-
- size_t value_length = value_end - value_begin;
- // The id is also only valid if it's in base64, but it might be overkill to verify if it's base64 here
- if(value_length < 300)
- return false;
-
- result = html.substr(value_begin, value_length);
- return true;
- }
-
- static bool google_captcha_response_extract_goal_description(const std::string& html, std::string& result)
- {
- size_t goal_description_begin = html.find("rc-imageselect-desc-no-canonical");
- if(goal_description_begin == std::string::npos) {
- goal_description_begin = html.find("rc-imageselect-desc");
- if(goal_description_begin == std::string::npos)
- return false;
- }
-
- goal_description_begin = html.find('>', goal_description_begin);
- if(goal_description_begin == std::string::npos)
- return false;
-
- goal_description_begin += 1;
-
- size_t goal_description_end = html.find("</div", goal_description_begin);
- if(goal_description_end == std::string::npos)
- return false;
-
- // The goal description with google captcha right now is "Select all images with <strong>subject</strong>".
- // TODO: Should the subject be extracted, so bold styling can be applied to it?
- result = html.substr(goal_description_begin, goal_description_end - goal_description_begin);
- string_replace_all(result, "<strong>", "");
- string_replace_all(result, "</strong>", "");
- return true;
- }
-
- static std::optional<GoogleCaptchaChallengeInfo> google_captcha_parse_request_challenge_response(const std::string& api_key, const std::string& html_source)
- {
- GoogleCaptchaChallengeInfo result;
- if(!google_captcha_response_extract_id(html_source, result.id))
- return std::nullopt;
- result.payload_url = "https://www.google.com/recaptcha/api2/payload?c=" + result.id + "&k=" + api_key;
- if(!google_captcha_response_extract_goal_description(html_source, result.description))
- return std::nullopt;
- return result;
- }
-
- // Note: This assumes strings (quoted data) in html tags dont contain '<' or '>'
- static std::string strip_html_tags(const std::string& text)
- {
- std::string result;
- size_t index = 0;
- while(true)
- {
- size_t tag_start_index = text.find('<', index);
- if(tag_start_index == std::string::npos)
- {
- result.append(text.begin() + index, text.end());
- break;
- }
-
- result.append(text.begin() + index, text.begin() + tag_start_index);
-
- size_t tag_end_index = text.find('>', tag_start_index + 1);
- if(tag_end_index == std::string::npos)
- break;
- index = tag_end_index + 1;
- }
- return result;
- }
-
- static std::optional<std::string> google_captcha_parse_submit_solution_response(const std::string& html_source)
- {
- size_t start_index = html_source.find("\"fbc-verification-token\">");
- if(start_index == std::string::npos)
- return std::nullopt;
-
- start_index += 25;
- size_t end_index = html_source.find("</", start_index);
- if(end_index == std::string::npos)
- return std::nullopt;
-
- return strip_html_tags(html_source.substr(start_index, end_index - start_index));
- }
-
- AsyncTask<bool> google_captcha_request_challenge(const std::string &api_key, const std::string &referer, RequestChallengeResponse challenge_response_callback) {
- return AsyncTask<bool>([challenge_response_callback, api_key, referer]() {
- std::string captcha_url = "https://www.google.com/recaptcha/api/fallback?k=" + api_key;
- std::string response;
- std::vector<CommandArg> additional_args = {
- CommandArg{"-H", "Referer: " + referer}
- };
- DownloadResult download_result = download_to_string(captcha_url, response, additional_args);
- if(download_result == DownloadResult::OK) {
- //fprintf(stderr, "Failed to get captcha, response: %s\n", response.c_str());
- challenge_response_callback(google_captcha_parse_request_challenge_response(api_key, response));
- return true;
- } else {
- fprintf(stderr, "Failed to get captcha, response: %s\n", response.c_str());
- challenge_response_callback(std::nullopt);
- return false;
- }
- });
- }
-
- static std::string build_post_solution_response(std::array<bool, 9> selected_images) {
- std::string result;
- for(size_t i = 0; i < selected_images.size(); ++i) {
- if(selected_images[i]) {
- if(!result.empty())
- result += "&";
- result += "response=" + std::to_string(i);
- }
- }
- return result;
- }
-
- AsyncTask<bool> google_captcha_post_solution(const std::string &api_key, const std::string &captcha_id, std::array<bool, 9> selected_images, PostSolutionResponse solution_response_callback) {
- return AsyncTask<bool>([solution_response_callback, api_key, captcha_id, selected_images]() {
- std::string captcha_url = "https://www.google.com/recaptcha/api/fallback?k=" + api_key;
- std::string response;
- std::vector<CommandArg> additional_args = {
- CommandArg{"-H", "Referer: " + captcha_url},
- CommandArg{"--data", "c=" + captcha_id + "&" + build_post_solution_response(selected_images)}
- };
- DownloadResult post_result = download_to_string(captcha_url, response, additional_args);
- if(post_result == DownloadResult::OK) {
- //fprintf(stderr, "Failed to post captcha solution, response: %s\n", response.c_str());
- std::optional<std::string> captcha_post_id = google_captcha_parse_submit_solution_response(response);
- if(captcha_post_id) {
- solution_response_callback(std::move(captcha_post_id), std::nullopt);
- } else {
- std::optional<GoogleCaptchaChallengeInfo> challenge_info = google_captcha_parse_request_challenge_response(api_key, response);
- solution_response_callback(std::nullopt, std::move(challenge_info));
- }
- return true;
- } else {
- fprintf(stderr, "Failed to post captcha solution, response: %s\n", response.c_str());
- solution_response_callback(std::nullopt, std::nullopt);
- return false;
- }
- });
- }
-} \ No newline at end of file
diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp
index f8566fa..5af793f 100644
--- a/src/QuickMedia.cpp
+++ b/src/QuickMedia.cpp
@@ -54,7 +54,6 @@
#include <X11/extensions/Xrandr.h>
#include <GL/glx.h>
-static const std::string fourchan_google_captcha_api_key = "6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc";
static int FPS_IDLE = 2;
static const double IDLE_TIMEOUT_SEC = 2.0;
static const sf::Vector2i AVATAR_THUMBNAIL_SIZE(std::floor(32), std::floor(32));
@@ -256,11 +255,11 @@ static sf::Color interpolate_colors(sf::Color source, sf::Color target, double p
source.a + diff_a * progress);
}
-static std::string base64_encode(const std::string &data) {
- return cppcodec::base64_url::encode(data);
+static std::string base64_url_encode(const std::string &data) {
+ return cppcodec::base64_url::encode<std::string>(data);
}
-static std::string base64_decode(const std::string &data) {
+static std::string base64_url_decode(const std::string &data) {
return cppcodec::base64_url::decode<std::string>(data);
}
@@ -999,7 +998,7 @@ namespace QuickMedia {
if(filename.size() > 18) // Ignore new manga ids
return true;
- std::string id_str = base64_decode(filename);
+ std::string id_str = base64_url_decode(filename);
char *endptr = nullptr;
errno = 0;
long id = strtol(id_str.c_str(), &endptr, 10);
@@ -1024,10 +1023,10 @@ namespace QuickMedia {
for(const auto &it : new_manga_ids) {
Path old_path = content_storage_dir;
- old_path.join(base64_encode(std::to_string(it.first)));
+ old_path.join(base64_url_encode(std::to_string(it.first)));
Path new_path = content_storage_dir;
- new_path.join(base64_encode(it.second));
+ new_path.join(base64_url_encode(it.second));
if(rename_atomic(old_path.data.c_str(), new_path.data.c_str()) != 0) {
show_notification("QuickMedia", "Failed to upgrade legacy mangadex ids", Urgency::CRITICAL);
abort();
@@ -1451,19 +1450,19 @@ namespace QuickMedia {
body_item->set_description_color(get_current_theme().faded_text_color);
if(strcmp(plugin_name, "manganelo") == 0)
- body_item->url = "https://manganelo.com/manga/" + base64_decode(filename.string());
+ body_item->url = "https://manganelo.com/manga/" + base64_url_decode(filename.string());
else if(strcmp(plugin_name, "manganelos") == 0)
- body_item->url = "http://manganelos.com/manga/" + base64_decode(filename.string());
+ body_item->url = "http://manganelos.com/manga/" + base64_url_decode(filename.string());
else if(strcmp(plugin_name, "mangadex") == 0)
- body_item->url = base64_decode(filename.string());
+ body_item->url = base64_url_decode(filename.string());
else if(strcmp(plugin_name, "mangatown") == 0)
- body_item->url = "https://mangatown.com/manga/" + base64_decode(filename.string());
+ body_item->url = "https://mangatown.com/manga/" + base64_url_decode(filename.string());
else if(strcmp(plugin_name, "mangakatana") == 0)
- body_item->url = "https://mangakatana.com/manga/" + base64_decode(filename.string());
+ body_item->url = "https://mangakatana.com/manga/" + base64_url_decode(filename.string());
else if(strcmp(plugin_name, "onimanga") == 0)
- body_item->url = "https://onimanga.com/" + base64_decode(filename.string());
+ body_item->url = "https://onimanga.com/" + base64_url_decode(filename.string());
else if(strcmp(plugin_name, "readm") == 0)
- body_item->url = "https://readm.org/manga/" + base64_decode(filename.string());
+ body_item->url = "https://readm.org/manga/" + base64_url_decode(filename.string());
else
fprintf(stderr, "Error: Not implemented: filename to manga chapter list\n");
@@ -1508,7 +1507,7 @@ namespace QuickMedia {
bool Program::load_manga_content_storage(const char *service_name, const std::string &manga_title, const std::string &manga_id) {
Path content_storage_dir = get_storage_dir().join(service_name);
- manga_id_base64 = base64_encode(manga_id);
+ manga_id_base64 = base64_url_encode(manga_id);
content_storage_file = content_storage_dir.join(manga_id_base64);
content_storage_json.clear();
content_storage_file_modified = true;
@@ -3377,7 +3376,7 @@ namespace QuickMedia {
int page_navigation = 0;
image_download_cancel = false;
- content_cache_dir = get_cache_dir().join(images_page->get_service_name()).join(manga_id_base64).join(base64_encode(images_page->get_chapter_name()));
+ content_cache_dir = get_cache_dir().join(images_page->get_service_name()).join(manga_id_base64).join(base64_url_encode(images_page->get_chapter_name()));
if(create_directory_recursive(content_cache_dir) != 0) {
show_notification("QuickMedia", "Failed to create directory: " + content_cache_dir.data, Urgency::CRITICAL);
current_page = pop_page_stack();
@@ -3585,7 +3584,7 @@ namespace QuickMedia {
void Program::image_continuous_page(MangaImagesPage *images_page) {
image_download_cancel = false;
- content_cache_dir = get_cache_dir().join(images_page->get_service_name()).join(manga_id_base64).join(base64_encode(images_page->get_chapter_name()));
+ content_cache_dir = get_cache_dir().join(images_page->get_service_name()).join(manga_id_base64).join(base64_url_encode(images_page->get_chapter_name()));
if(create_directory_recursive(content_cache_dir) != 0) {
show_notification("QuickMedia", "Failed to create directory: " + content_cache_dir.data, Urgency::CRITICAL);
current_page = pop_page_stack();
@@ -3670,8 +3669,8 @@ namespace QuickMedia {
enum class NavigationStage {
VIEWING_COMMENTS,
REPLYING,
+ REQUESTING_CAPTCHA,
SOLVING_POST_CAPTCHA,
- POSTING_SOLUTION,
POSTING_COMMENT,
VIEWING_ATTACHED_IMAGE
};
@@ -3679,85 +3678,34 @@ namespace QuickMedia {
thread_body->title_mark_urls = true;
NavigationStage navigation_stage = NavigationStage::VIEWING_COMMENTS;
- AsyncTask<bool> captcha_request_future;
- AsyncTask<bool> captcha_post_solution_future;
- AsyncTask<bool> post_comment_future;
- AsyncTask<std::string> load_image_future;
- bool downloading_image = false;
sf::Texture captcha_texture;
sf::Sprite captcha_sprite;
- std::mutex captcha_image_mutex;
+ sf::Texture captcha_bg_texture;
+ sf::Sprite captcha_bg_sprite;
+ bool has_captcha_bg = false;
+ float captcha_slide = 0.0f;
std::string attached_image_url;
+ ImageBoardCaptchaChallenge captcha_challenge;
+
+ 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;
+ background_color_darker.r = std::max(0, (int)background_color_darker.r - 20);
+ background_color_darker.g = std::max(0, (int)background_color_darker.g - 20);
+ background_color_darker.b = std::max(0, (int)background_color_darker.b - 20);
+ RoundedRectangle captcha_slide_bg(sf::Vector2f(1.0f, 1.0f), std::floor(10.0f * get_ui_scale()), background_color_darker, &rounded_rectangle_shader);
+ RoundedRectangle captcha_slide_fg(sf::Vector2f(1.0f, 1.0f), std::floor(10.0f * get_ui_scale() - captcha_slide_padding_y), get_current_theme().loading_bar_color, &rounded_rectangle_shader);
auto attached_image_texture = std::make_unique<sf::Texture>();
sf::Sprite attached_image_sprite;
- GoogleCaptchaChallengeInfo challenge_info;
- sf::Text challenge_description_text("", *FontLoader::get_font(FontLoader::FontType::LATIN), std::floor(24 * get_ui_scale()));
- challenge_description_text.setFillColor(get_current_theme().text_color);
- const size_t captcha_num_columns = 3;
- const size_t captcha_num_rows = 3;
- std::array<bool, captcha_num_columns * captcha_num_rows> selected_captcha_images;
- for(size_t i = 0; i < selected_captcha_images.size(); ++i) {
- selected_captcha_images[i] = false;
- }
- sf::RectangleShape captcha_selection_rect;
- captcha_selection_rect.setOutlineThickness(5.0f);
- captcha_selection_rect.setOutlineColor(sf::Color::Red);
- // TODO: Draw only the outline instead of a transparent rectangle
- captcha_selection_rect.setFillColor(sf::Color::Transparent);
-
- // Valid for 2 minutes after solving a captcha
std::string captcha_post_id;
+ std::string captcha_solution;
sf::Clock captcha_solved_time;
std::string comment_to_post;
-
- // TODO: Show a white image with "Loading..." text while the captcha image is downloading
-
- // TODO: Make google captcha images load texture in the main thread, otherwise high cpu usage. I guess its fine right now because of only 1 image?
-
- // TODO: Make this work with other sites than 4chan
- auto request_google_captcha_image = [&captcha_texture, &captcha_image_mutex, &navigation_stage, &captcha_sprite, &challenge_description_text](GoogleCaptchaChallengeInfo &challenge_info) {
- std::string payload_image_data;
- DownloadResult download_image_result = download_to_string(challenge_info.payload_url, payload_image_data, {});
- if(download_image_result == DownloadResult::OK) {
- std::lock_guard<std::mutex> lock(captcha_image_mutex);
- sf::Image captcha_image;
- if(load_image_from_memory(captcha_image, payload_image_data.data(), payload_image_data.size()) && captcha_texture.loadFromImage(captcha_image)) {
- captcha_texture.setSmooth(true);
- captcha_sprite.setTexture(captcha_texture, true);
- challenge_description_text.setString(challenge_info.description);
- } else {
- show_notification("QuickMedia", "Failed to load downloaded captcha image", Urgency::CRITICAL);
- navigation_stage = NavigationStage::VIEWING_COMMENTS;
- }
- } else {
- show_notification("QuickMedia", "Failed to download captcha image", Urgency::CRITICAL);
- navigation_stage = NavigationStage::VIEWING_COMMENTS;
- }
- };
-
- auto request_new_google_captcha_challenge = [&selected_captcha_images, &navigation_stage, &captcha_request_future, &request_google_captcha_image, &challenge_info]() {
- fprintf(stderr, "Solving captcha!\n");
- navigation_stage = NavigationStage::SOLVING_POST_CAPTCHA;
- for(size_t i = 0; i < selected_captcha_images.size(); ++i) {
- selected_captcha_images[i] = false;
- }
- const std::string referer = "https://boards.4chan.org/";
- captcha_request_future = google_captcha_request_challenge(fourchan_google_captcha_api_key, referer,
- [&navigation_stage, &request_google_captcha_image, &challenge_info](std::optional<GoogleCaptchaChallengeInfo> new_challenge_info) {
- if(navigation_stage != NavigationStage::SOLVING_POST_CAPTCHA)
- return;
-
- if(new_challenge_info) {
- challenge_info = new_challenge_info.value();
- request_google_captcha_image(challenge_info);
- } else {
- show_notification("QuickMedia", "Failed to get captcha challenge", Urgency::CRITICAL);
- navigation_stage = NavigationStage::VIEWING_COMMENTS;
- }
- });
- };
+ 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;
bool redraw = true;
@@ -3767,20 +3715,20 @@ namespace QuickMedia {
std::string selected_file_for_upload;
- auto post_comment = [&comment_input, &selected_file_for_upload, &navigation_stage, &thread_page, &captcha_post_id, &request_new_google_captcha_challenge](std::string comment_to_post, std::string file_to_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) {
comment_input.set_editable(false);
- navigation_stage = NavigationStage::POSTING_COMMENT;
- PostResult post_result = thread_page->post_comment(captcha_post_id, comment_to_post, file_to_upload);
+ PostResult post_result = thread_page->post_comment(captcha_post_id, captcha_solution, comment_to_post, file_to_upload);
if(post_result == PostResult::OK) {
show_notification("QuickMedia", "Comment posted!");
navigation_stage = NavigationStage::VIEWING_COMMENTS;
+ comment_input.set_text("");
// 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
} else if(post_result == PostResult::TRY_AGAIN) {
- show_notification("QuickMedia", "Error while posting, did the captcha expire? Please 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
- request_new_google_captcha_challenge();
+ 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);
navigation_stage = NavigationStage::VIEWING_COMMENTS;
@@ -3808,7 +3756,7 @@ namespace QuickMedia {
bool frame_skip_text_entry = false;
- comment_input.on_submit_callback = [&frame_skip_text_entry, &comment_input, &post_comment_future, &navigation_stage, &request_new_google_captcha_challenge, &comment_to_post, &captcha_post_id, &captcha_solved_time, &post_comment, &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, &captcha_solved_time, &solved_captcha_ttl, &selected_file_for_upload, &thread_page](std::string text) -> bool {
if(text.empty() && selected_file_for_upload.empty())
return false;
@@ -3817,20 +3765,12 @@ namespace QuickMedia {
assert(navigation_stage == NavigationStage::REPLYING);
comment_to_post = std::move(text);
- if(!captcha_post_id.empty() && captcha_solved_time.getElapsedTime().asSeconds() < 120) {
- post_comment_future = AsyncTask<bool>([&post_comment, comment_to_post, selected_file_for_upload]() -> bool {
- post_comment(comment_to_post, selected_file_for_upload);
- return true;
- });
+ if((!captcha_post_id.empty() && captcha_solved_time.getElapsedTime().asSeconds() < solved_captcha_ttl) || !thread_page->get_pass_id().empty()) {
+ navigation_stage = NavigationStage::POSTING_COMMENT;
} else if(thread_page->get_pass_id().empty()) {
- request_new_google_captcha_challenge();
- } else if(!thread_page->get_pass_id().empty()) {
- post_comment_future = AsyncTask<bool>([&post_comment, comment_to_post, selected_file_for_upload]() -> bool {
- post_comment(comment_to_post, selected_file_for_upload);
- return true;
- });
+ navigation_stage = NavigationStage::REQUESTING_CAPTCHA;
}
- return true;
+ return false;
};
sf::RectangleShape comment_input_shade;
@@ -3859,7 +3799,9 @@ namespace QuickMedia {
std::deque<int> comment_navigation_stack;
std::deque<int> comment_page_scroll_stack;
+ sf::Clock frame_timer;
while (current_page == PageType::IMAGE_BOARD_THREAD && window.isOpen()) {
+ const float frame_elapsed_time_sec = frame_timer.restart().asSeconds();
while (window.pollEvent(event)) {
if(navigation_stage == NavigationStage::REPLYING || navigation_stage == NavigationStage::VIEWING_COMMENTS) {
if(thread_body->on_event(window, event, navigation_stage == NavigationStage::VIEWING_COMMENTS))
@@ -3912,18 +3854,32 @@ namespace QuickMedia {
} else {
BodyItem *selected_item = thread_body->get_selected();
if(selected_item && !selected_item->url.empty()) {
- load_image_future.cancel();
- downloading_image = true;
navigation_stage = NavigationStage::VIEWING_ATTACHED_IMAGE;
attached_image_url = selected_item->url;
- load_image_future = AsyncTask<std::string>([attached_image_url]() {
+ sf::Image image;
+ TaskResult task_result = run_task_with_loading_screen([&attached_image_url, &image]{
std::string image_data;
if(download_to_string_cache(attached_image_url, image_data, {}) != DownloadResult::OK) {
show_notification("QuickMedia", "Failed to download image: " + attached_image_url, Urgency::CRITICAL);
- image_data.clear();
+ return false;
}
- return image_data;
+
+ if(!load_image_from_memory(image, image_data.data(), image_data.size())) {
+ show_notification("QuickMedia", "Failed to load image: " + attached_image_url, Urgency::CRITICAL);
+ return false;
+ }
+ return true;
});
+
+ if(task_result == TaskResult::TRUE) {
+ attached_image_texture = std::make_unique<sf::Texture>();
+ if(attached_image_texture->loadFromImage(image)) {
+ attached_image_sprite.setTexture(*attached_image_texture, true);
+ navigation_stage = NavigationStage::VIEWING_ATTACHED_IMAGE;
+ } else {
+ show_notification("QuickMedia", "Failed to load image: " + attached_image_url, Urgency::CRITICAL);
+ }
+ }
}
}
}
@@ -4035,48 +3991,29 @@ namespace QuickMedia {
}
if(event.type == sf::Event::KeyPressed && navigation_stage == NavigationStage::SOLVING_POST_CAPTCHA && !frame_skip_text_entry) {
- int num = -1;
- if(event.key.code >= sf::Keyboard::Num1 && event.key.code <= sf::Keyboard::Num9) {
- num = event.key.code - sf::Keyboard::Num1;
- } else if(event.key.code >= sf::Keyboard::Numpad1 && event.key.code <= sf::Keyboard::Numpad9) {
- num = event.key.code - sf::Keyboard::Numpad1;
- }
-
- constexpr int select_map[9] = { 6, 7, 8, 3, 4, 5, 0, 1, 2 };
- if(num != -1) {
- int index = select_map[num];
- selected_captcha_images[index] = !selected_captcha_images[index];
- }
-
if(event.key.code == sf::Keyboard::Escape) {
navigation_stage = NavigationStage::VIEWING_COMMENTS;
} else if(event.key.code == sf::Keyboard::Enter) {
- navigation_stage = NavigationStage::POSTING_SOLUTION;
- captcha_post_solution_future = google_captcha_post_solution(fourchan_google_captcha_api_key, challenge_info.id, selected_captcha_images,
- [&navigation_stage, &captcha_post_id, &captcha_solved_time, &selected_captcha_images, &challenge_info, &request_google_captcha_image, &post_comment, comment_to_post, selected_file_for_upload](std::optional<std::string> new_captcha_post_id, std::optional<GoogleCaptchaChallengeInfo> new_challenge_info) {
- if(navigation_stage != NavigationStage::POSTING_SOLUTION)
- return;
-
- if(new_captcha_post_id) {
- captcha_post_id = new_captcha_post_id.value();
- captcha_solved_time.restart();
- post_comment(comment_to_post, selected_file_for_upload);
- } else if(new_challenge_info) {
- show_notification("QuickMedia", "Failed to solve captcha, please try again");
- challenge_info = new_challenge_info.value();
- navigation_stage = NavigationStage::SOLVING_POST_CAPTCHA;
- for(size_t i = 0; i < selected_captcha_images.size(); ++i) {
- selected_captcha_images[i] = false;
- }
- request_google_captcha_image(challenge_info);
- }
- });
- }
- }
-
- if(event.type == sf::Event::KeyPressed && (navigation_stage == NavigationStage::POSTING_SOLUTION || navigation_stage == NavigationStage::POSTING_COMMENT)) {
- if(event.key.code == sf::Keyboard::Escape) {
- navigation_stage = NavigationStage::VIEWING_COMMENTS;
+ navigation_stage = NavigationStage::POSTING_COMMENT;
+ auto str8 = captcha_solution_text.getString().toUtf8();
+ captcha_solution.assign(str8.begin(), str8.end());
+ } else if(event.key.code == sf::Keyboard::BackSpace) {
+ auto str = captcha_solution_text.getString();
+ if(!str.isEmpty()) {
+ str.erase(str.getSize() - 1);
+ captcha_solution_text.setString(std::move(str));
+ }
+ } else {
+ const int alpha = (int)event.key.code - (int)sf::Keyboard::A;
+ const int num = (int)event.key.code - (int)sf::Keyboard::Num0;
+ const int numpad = (int)event.key.code - (int)sf::Keyboard::Numpad0;
+ if(alpha >= 0 && alpha <= sf::Keyboard::Z - sf::Keyboard::A) {
+ captcha_solution_text.setString(captcha_solution_text.getString() + (sf::Uint32)to_upper(alpha + 'a'));
+ } else if(num >= 0 && num <= sf::Keyboard::Num9 - sf::Keyboard::Num0) {
+ captcha_solution_text.setString(captcha_solution_text.getString() + (sf::Uint32)(num + '0'));
+ } else if(numpad >= 0 && numpad <= sf::Keyboard::Numpad9 - sf::Keyboard::Numpad0) {
+ captcha_solution_text.setString(captcha_solution_text.getString() + (sf::Uint32)(numpad + '0'));
+ }
}
}
@@ -4099,6 +4036,63 @@ namespace QuickMedia {
update_idle_state();
handle_x11_events();
+ if(navigation_stage == NavigationStage::REQUESTING_CAPTCHA) {
+ captcha_challenge = ImageBoardCaptchaChallenge();
+ TaskResult task_result = run_task_with_loading_screen([thread_page, &captcha_challenge]{
+ return thread_page->request_captcha_challenge(captcha_challenge) == PluginResult::OK;
+ });
+
+ if(task_result == TaskResult::TRUE) {
+ comment_input.set_editable(false);
+ captcha_post_id = std::move(captcha_challenge.challenge_id);
+ solved_captcha_ttl = 0;// captcha_challenge.ttl; // TODO: Support ttl to not have to solve captcha again
+ 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();
+
+ 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);
+ }
+ } else if(task_result == TaskResult::CANCEL) {
+ comment_input.set_editable(false);
+ navigation_stage = NavigationStage::VIEWING_COMMENTS;
+ } else if(task_result == TaskResult::FALSE) {
+ show_notification("QuickMedia", "Failed to get captcha", Urgency::CRITICAL);
+ comment_input.set_editable(false);
+ navigation_stage = NavigationStage::VIEWING_COMMENTS;
+ }
+ } else if(navigation_stage == NavigationStage::POSTING_COMMENT) {
+ TaskResult task_result = run_task_with_loading_screen([&post_comment, &comment_to_post, &selected_file_for_upload]{
+ post_comment(comment_to_post, selected_file_for_upload);
+ return true;
+ });
+
+ if(task_result == TaskResult::CANCEL) {
+ comment_input.set_editable(false);
+ navigation_stage = NavigationStage::VIEWING_COMMENTS;
+ }
+ }
+
if(selected_file_for_upload.empty()) {
if(file_to_upload_thumbnail_data) {
file_to_upload_thumbnail_data.reset();
@@ -4162,61 +4156,81 @@ namespace QuickMedia {
//comment_input.update();
window.clear(get_current_theme().background_color);
- if(navigation_stage == NavigationStage::SOLVING_POST_CAPTCHA) {
- std::lock_guard<std::mutex> lock(captcha_image_mutex);
- if(captcha_texture.getNativeHandle() != 0) {
- const float challenge_description_height = challenge_description_text.getCharacterSize() + 10.0f;
+ if(navigation_stage == NavigationStage::SOLVING_POST_CAPTCHA && captcha_texture.getNativeHandle() != 0) {
+ const float slide_speed = 0.5f;
+ const bool window_has_focus = window.hasFocus();
+ if(window_has_focus && sf::Keyboard::isKeyPressed(sf::Keyboard::Left)) {
+ captcha_slide -= (slide_speed * frame_elapsed_time_sec);
+ if(captcha_slide < 0.0f)
+ captcha_slide = 0.0f;
+ } else if(window_has_focus && sf::Keyboard::isKeyPressed(sf::Keyboard::Right)) {
+ captcha_slide += (slide_speed * frame_elapsed_time_sec);
+ if(captcha_slide > 1.0f)
+ captcha_slide = 1.0f;
+ }
+
+ sf::Vector2f content_size = window_size;
+ int image_height = 0;
+
+ sf::Vector2u captcha_texture_size = captcha_texture.getSize();
+ sf::Vector2f captcha_texture_size_f(captcha_texture_size.x, captcha_texture_size.y);
+ auto image_scale = get_ratio(captcha_texture_size_f, clamp_to_size(captcha_texture_size_f, content_size));
+ captcha_sprite.setScale(image_scale);
+
+ auto captcha_image_size = captcha_texture_size_f;
+ captcha_image_size.x *= image_scale.x;
+ captcha_image_size.y *= image_scale.y;
+ captcha_sprite.setPosition(std::floor(content_size.x * 0.5f - captcha_image_size.x * 0.5f), std::floor(content_size.y * 0.5f - captcha_image_size.y * 0.5f));
+ image_height = (int)captcha_image_size.y;
+
+ if(has_captcha_bg && captcha_bg_texture.getNativeHandle() != 0) {
sf::Vector2f content_size = window_size;
- content_size.y -= challenge_description_height;
- sf::Vector2u captcha_texture_size = captcha_texture.getSize();
- sf::Vector2f captcha_texture_size_f(captcha_texture_size.x, captcha_texture_size.y);
- auto image_scale = get_ratio(captcha_texture_size_f, clamp_to_size(captcha_texture_size_f, content_size));
- captcha_sprite.setScale(image_scale);
+ sf::Vector2u captcha_bg_texture_size = captcha_bg_texture.getSize();
+ sf::Vector2f captcha_bg_texture_size_f(captcha_bg_texture_size.x, captcha_bg_texture_size.y);
+ auto image_scale = get_ratio(captcha_bg_texture_size_f, clamp_to_size(captcha_bg_texture_size_f, content_size));
+ captcha_bg_sprite.setScale(image_scale);
- auto image_size = captcha_texture_size_f;
+ auto image_size = captcha_bg_texture_size_f;
image_size.x *= image_scale.x;
image_size.y *= image_scale.y;
- captcha_sprite.setPosition(std::floor(content_size.x * 0.5f - image_size.x * 0.5f), std::floor(challenge_description_height + content_size.y * 0.5f - image_size.y * 0.5f));
- window.draw(captcha_sprite);
-
- challenge_description_text.setPosition(captcha_sprite.getPosition() + sf::Vector2f(std::floor(image_size.x * 0.5f), 0.0f) - sf::Vector2f(std::floor(challenge_description_text.getLocalBounds().width * 0.5f), challenge_description_height));
- window.draw(challenge_description_text);
-
- for(size_t column = 0; column < captcha_num_columns; ++column) {
- for(size_t row = 0; row < captcha_num_rows; ++row) {
- if(selected_captcha_images[column + captcha_num_columns * row]) {
- captcha_selection_rect.setPosition(captcha_sprite.getPosition() + sf::Vector2f(image_size.x / captcha_num_columns * column, image_size.y / captcha_num_rows * row));
- captcha_selection_rect.setSize(sf::Vector2f(image_size.x / captcha_num_columns, image_size.y / captcha_num_rows));
- window.draw(captcha_selection_rect);
- }
- }
- }
+ const float width_diff = captcha_bg_texture_size_f.x - captcha_texture_size_f.x;
+ const float left_opening = 0.15f;
+ const float right_opening = 0.25f;
+ captcha_bg_sprite.setPosition(std::floor(captcha_sprite.getPosition().x + captcha_image_size.x * left_opening - captcha_slide * (width_diff + right_opening * captcha_image_size.x)), std::floor(captcha_sprite.getPosition().y));
+ window.draw(captcha_bg_sprite);
+
+ image_height = std::max(image_height, (int)image_size.y);
}
- } else if(navigation_stage == NavigationStage::POSTING_SOLUTION) {
- // TODO: Show "Posting..." when posting solution
- } else if(navigation_stage == NavigationStage::POSTING_COMMENT) {
- // TODO: Show "Posting..." when posting comment
+
+ window.draw(captcha_sprite);
+
+ // TODO: Cut off ends with sf::View instead
+ sf::RectangleShape cut_off_rectangle(captcha_image_size);
+ cut_off_rectangle.setFillColor(get_current_theme().background_color);
+ cut_off_rectangle.setPosition(captcha_sprite.getPosition() - sf::Vector2f(cut_off_rectangle.getSize().x, 0.0f));
+ window.draw(cut_off_rectangle);
+
+ cut_off_rectangle.setPosition(captcha_sprite.getPosition() + sf::Vector2f(captcha_image_size.x, 0.0f));
+ window.draw(cut_off_rectangle);
+
+ const float captcha_slide_bg_height = std::floor(20.0f * get_ui_scale());
+ captcha_slide_bg.set_size(sf::Vector2f(captcha_image_size.x, captcha_slide_bg_height));
+ captcha_slide_bg.set_position(sf::Vector2f(captcha_sprite.getPosition().x, captcha_sprite.getPosition().y + image_height + 10.0f));
+
+ const sf::Vector2f captcha_slide_fg_size = captcha_slide_bg.get_size() - sf::Vector2f(captcha_slide_padding_x * 2.0f, captcha_slide_padding_y * 2.0f);
+ captcha_slide_fg.set_size(sf::Vector2f(std::floor(captcha_slide_fg_size.x * captcha_slide), captcha_slide_fg_size.y));
+ captcha_slide_fg.set_position(captcha_slide_bg.get_position() + sf::Vector2f(captcha_slide_padding_x, captcha_slide_padding_y));
+
+ captcha_slide_bg.draw(window);
+ captcha_slide_fg.draw(window);
+
+ captcha_solution_text.setPosition(
+ sf::Vector2f(
+ std::floor(window_size.x * 0.5f - captcha_solution_text.getLocalBounds().width * 0.5f),
+ std::floor(captcha_slide_bg.get_position().y + captcha_slide_bg.get_size().y + 10.0f)));
+ window.draw(captcha_solution_text);
} else if(navigation_stage == NavigationStage::VIEWING_ATTACHED_IMAGE) {
- // TODO: Use image instead of data with string. texture->loadFromMemory creates a temporary image anyways that parses the string.
- if(downloading_image && load_image_future.ready()) {
- downloading_image = false;
- std::string image_data = load_image_future.get();
-
- sf::Image attached_image;
- if(load_image_from_memory(attached_image, image_data.data(), image_data.size()) && attached_image_texture->loadFromImage(attached_image)) {
- attached_image_texture->setSmooth(true);
- //attached_image_texture->generateMipmap();
- attached_image_sprite.setTexture(*attached_image_texture, true);
- } else {
- BodyItem *selected_item = thread_body->get_selected();
- std::string selected_item_attached_url;
- if(selected_item)
- selected_item_attached_url = selected_item->url;
- show_notification("QuickMedia", "Failed to load image downloaded from url: " + selected_item_attached_url, Urgency::CRITICAL);
- }
- }
-
if(attached_image_texture->getNativeHandle() != 0) {
auto content_size = window_size;
sf::Vector2u texture_size = attached_image_texture->getSize();
diff --git a/src/StringUtils.cpp b/src/StringUtils.cpp
index 041a12d..96fdaeb 100644
--- a/src/StringUtils.cpp
+++ b/src/StringUtils.cpp
@@ -110,7 +110,7 @@ namespace QuickMedia {
return it - str.begin();
}
- static char to_upper(char c) {
+ char to_upper(char c) {
if(c >= 'a' && c <= 'z')
return c - 32;
else
diff --git a/src/plugins/Fourchan.cpp b/src/plugins/Fourchan.cpp
index d4fb726..84a0675 100644
--- a/src/plugins/Fourchan.cpp
+++ b/src/plugins/Fourchan.cpp
@@ -3,6 +3,8 @@
#include "../../include/Storage.hpp"
#include "../../include/StringUtils.hpp"
#include "../../include/NetUtils.hpp"
+#include "../../include/Notification.hpp"
+#include "../../external/cppcodec/base64_rfc4648.hpp"
#include <HtmlParser.h>
#include <json/reader.h>
#include <string.h>
@@ -500,7 +502,7 @@ namespace QuickMedia {
return filepath.c_str() + index + 1;
}
- PostResult FourchanThreadPage::post_comment(const std::string &captcha_id, const std::string &comment, const std::string &filepath) {
+ PostResult FourchanThreadPage::post_comment(const std::string &captcha_id, const std::string &captcha_solution, const std::string &comment, const std::string &filepath) {
std::string url = "https://sys.4chan.org/" + board_id + "/post";
std::vector<CommandArg> additional_args = {
@@ -517,7 +519,9 @@ namespace QuickMedia {
}
if(pass_id.empty()) {
- additional_args.push_back(CommandArg{"--form-string", "g-recaptcha-response=" + captcha_id});
+ 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) {
@@ -558,4 +562,58 @@ namespace QuickMedia {
}
return pass_id;
}
+
+ static bool base64_decode(const Json::Value &json_to_decode, std::string &decoded) {
+ if(!json_to_decode.isString())
+ return false;
+
+ const char *start;
+ const char *end;
+ if(!json_to_decode.getString(&start, &end))
+ return false;
+
+ try {
+ decoded = cppcodec::base64_rfc4648::decode<std::string>(start, end - start);
+ return true;
+ } catch(std::exception&) {
+ return false;
+ }
+ }
+
+ PluginResult FourchanThreadPage::request_captcha_challenge(ImageBoardCaptchaChallenge &challenge_response) {
+ 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);
+ if(result != DownloadResult::OK) return download_result_to_plugin_result(result);
+
+ if(!json_root.isObject())
+ return PluginResult::ERR;
+
+ const Json::Value &error_json = json_root["error"];
+ if(error_json.isString()) {
+ show_notification("QuickMedia", "Failed to get captcha, error: " + error_json.asString(), Urgency::CRITICAL);
+ return PluginResult::ERR;
+ }
+
+ const Json::Value &challenge_json = json_root["challenge"];
+ 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())
+ return PluginResult::ERR;
+
+ challenge_response.challenge_id = challenge_json.asString();
+
+ 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(ttl_json.isInt())
+ challenge_response.ttl = ttl_json.asInt();
+ else
+ challenge_response.ttl = 120;
+
+ return PluginResult::OK;
+ }
} \ No newline at end of file
diff --git a/src/plugins/youtube/YoutubeMediaProxy.cpp b/src/plugins/youtube/YoutubeMediaProxy.cpp
index 2296d1a..d7ffb53 100644
--- a/src/plugins/youtube/YoutubeMediaProxy.cpp
+++ b/src/plugins/youtube/YoutubeMediaProxy.cpp
@@ -38,7 +38,7 @@ namespace QuickMedia {
std::string url = media_url + "&rn=" + std::to_string(rn) + "&rbuf=" + std::to_string(rbuf);
std::vector<const char*> args = { "curl",
//"-H", "Accept-Language: en-US,en;q=0.5", "-H", "Connection: keep-alive",
- //"-H", "user-agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36",
+ "-H", "user-agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36",
"-g", "-s", "-L", "-f" };
if(is_livestream) {