aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/Utils.hpp1
-rw-r--r--plugins/ImageBoard.hpp7
-rw-r--r--src/Downloader.cpp2
-rw-r--r--src/QuickMedia.cpp122
-rw-r--r--src/Utils.cpp9
-rw-r--r--src/plugins/Fourchan.cpp53
-rw-r--r--src/plugins/MangaGeneric.cpp2
-rw-r--r--src/plugins/NyaaSi.cpp9
-rw-r--r--src/plugins/Youtube.cpp4
-rw-r--r--src/plugins/youtube/Signature.cpp2
-rw-r--r--src/plugins/youtube/YoutubeMediaProxy.cpp2
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) {