aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2021-04-30 21:04:47 +0200
committerdec05eba <dec05eba@protonmail.com>2021-04-30 21:18:06 +0200
commit50afa93c577bed3eb9911d141d46f2e0d3ba2572 (patch)
treef910aa9617bca27435325fb72eec36d8e9aea808
parent47bb22a4aee886deb54ca432bdb14747bf2e9160 (diff)
Add 4chan file upload
-rw-r--r--README.md2
-rw-r--r--TODO8
-rw-r--r--include/Body.hpp1
-rw-r--r--plugins/FileManager.hpp11
-rw-r--r--plugins/Fourchan.hpp6
-rw-r--r--plugins/ImageBoard.hpp11
-rw-r--r--src/Body.cpp1
-rw-r--r--src/QuickMedia.cpp148
-rw-r--r--src/plugins/FileManager.cpp72
-rw-r--r--src/plugins/Fourchan.cpp45
-rw-r--r--src/plugins/ImageBoard.cpp15
11 files changed, 228 insertions, 92 deletions
diff --git a/README.md b/README.md
index bd7cd0b..a7a4a2d 100644
--- a/README.md
+++ b/README.md
@@ -54,7 +54,9 @@ Press `R` to reply to a message on matrix, press `ESC` to cancel.\
Press `E` to edit a message on matrix, press `ESC` to cancel. Currently only works for your own messages.\
Press `Ctrl + D` to delete a message on matrix. Currently deleting a message only deletes the event, so if you delete an edit then the original message wont be deleted.\
Press `Ctrl + C` to copy the message of the selected item in matrix to the clipboard.\
+Press `U` in matrix or in a 4chan thread to bring up the file manager to choose a file to upload.\
Press `Ctrl + V` to upload media to room in matrix if the clipboard contains a valid absolute filepath.\
+Press `Ctrl + D` to remove the file that was previously selected with `U` in a 4chan thread.\
Press `Ctrl+Alt+Arrow up` / `Ctrl+Alt+Arrow down` or `Ctrl+Alt+K` / `Ctrl+Alt+J` to view the room above/below the selected room in matrix.
In matrix you can select a message with enter to open the url in the message (or if there are multiple urls then a menu will appear for selecting which to open).
diff --git a/TODO b/TODO
index 6d06090..28708b6 100644
--- a/TODO
+++ b/TODO
@@ -18,7 +18,7 @@ Use fallback cjk font for regular sf::Text as well (search, tabs, chapter name w
Fix some japanese fonts not rendering (half width alphanumeric?).
Extract thumbnail from images that are being downloaded, while its downloading and show that while the full image is downloading (upscaled, or with blurhash).
Add setting to disable sending typing events to the server (matrix).
-Take code from dchat to support gifs (inline in text).
+Take code from dchat to support gifs (inline in text) and support animated webp (either animated or as a static thumbnail).
Scrolling in images still messes up the |current| page sometimes, need a way to fix this.
Add ctrl+i keybind when viewing an image on 4chan to reverse image search it (using google, yandex and saucenao).
Show filename at the bottom when viewing an image/video on 4chan.
@@ -55,7 +55,6 @@ Add tabs. Using tabs with tabbed is not as good of a solution as it would use mu
Remove related videos that have already been watched (except the first related video, which is the "watch next" video, usually the next part of a serie).
Add F5 to refresh page.
Allow choosing which translation/scanlation to use on mangadex. Right now it uses the latest one, which is most likely to be the best.
-Add file upload to 4chan.
Retry download if it fails, at least 3 times (observed to be needed for mangadex images).
Readd autocomplete, but make it better with a proper list. Also readd 4chan login page.
Modify sfml to use GL_COMPRESSED_LUMINANCE and other texture compression modes (in sf::Texture). This reduces memory usage by half.
@@ -134,4 +133,7 @@ Replace cppcodec with another library for base64 url encoding/decoding. Its way
Revert back to old fuzzy search code or use levenshtein distance, then reorder items by best match. This could be done by having a second vector of indices and use that vector everywhere body items by index is accessed (including selected_item). Also perform the search in Body::draw when search term has been modified. This allows us to automatically update that new vector.
Using a VertexBuffer in Text makes the text quickly glitch out. Why does this happen?
Update subscriptions page either with f5 and automatically update it when adding/removing subscriptions.
-Update room name and topic text in ui when they change. \ No newline at end of file
+Update room name and topic text in ui when they change.
+Support webp directly without using ffmpeg to convert it to a png.
+Add client side 4chan file size limit (note, webm has different limit than images).
+Add client side 4chan max comment chars limit. \ No newline at end of file
diff --git a/include/Body.hpp b/include/Body.hpp
index 58e1414..39ad19d 100644
--- a/include/Body.hpp
+++ b/include/Body.hpp
@@ -127,7 +127,6 @@ namespace QuickMedia {
// TODO: Use a list of strings instead, not all plugins need all of these fields
std::string url;
std::string thumbnail_url;
- std::string attached_content_url; // TODO: Remove and use |url| instead
bool visible;
bool dirty;
bool dirty_description;
diff --git a/plugins/FileManager.hpp b/plugins/FileManager.hpp
index 38babc1..2e93a8e 100644
--- a/plugins/FileManager.hpp
+++ b/plugins/FileManager.hpp
@@ -4,9 +4,17 @@
#include <filesystem>
namespace QuickMedia {
+ enum FileManagerMimeType {
+ FILE_MANAGER_MIME_TYPE_IMAGE = (1 << 0),
+ FILE_MANAGER_MIME_TYPE_VIDEO = (1 << 1),
+ FILE_MANAGER_MIME_TYPE_OTHER = (1 << 2)
+ };
+
+ static const FileManagerMimeType FILE_MANAGER_MIME_TYPE_ALL = (FileManagerMimeType)(FILE_MANAGER_MIME_TYPE_IMAGE|FILE_MANAGER_MIME_TYPE_VIDEO|FILE_MANAGER_MIME_TYPE_OTHER);
+
class FileManagerPage : public Page {
public:
- FileManagerPage(Program *program) : Page(program), current_dir("/") {}
+ FileManagerPage(Program *program, FileManagerMimeType mime_type = FILE_MANAGER_MIME_TYPE_ALL) : Page(program), current_dir("/"), mime_type(mime_type) {}
const char* get_title() const override { return current_dir.c_str(); }
PluginResult submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) override;
bool is_single_page() const override { return true; }
@@ -15,5 +23,6 @@ namespace QuickMedia {
PluginResult get_files_in_directory(BodyItems &result_items);
private:
std::filesystem::path current_dir;
+ FileManagerMimeType mime_type;
};
} \ No newline at end of file
diff --git a/plugins/Fourchan.hpp b/plugins/Fourchan.hpp
index 903d534..e2b4671 100644
--- a/plugins/Fourchan.hpp
+++ b/plugins/Fourchan.hpp
@@ -23,16 +23,14 @@ namespace QuickMedia {
const std::string title;
const std::string board_id;
- private:
- std::vector<std::string> cached_media_urls;
};
class FourchanThreadPage : public ImageBoardThreadPage {
public:
- FourchanThreadPage(Program *program, std::string board_id, std::string thread_id, std::vector<std::string> cached_media_urls) : ImageBoardThreadPage(program, std::move(board_id), std::move(thread_id), std::move(cached_media_urls)) {}
+ FourchanThreadPage(Program *program, std::string board_id, std::string thread_id) : ImageBoardThreadPage(program, std::move(board_id), std::move(thread_id)) {}
PluginResult login(const std::string &token, const std::string &pin, std::string &response_msg) override;
- PostResult post_comment(const std::string &captcha_id, const std::string &comment) override;
+ PostResult post_comment(const std::string &captcha_id, const std::string &comment, const std::string &filepath = "") override;
const std::string& get_pass_id() override;
private:
std::string pass_id;
diff --git a/plugins/ImageBoard.hpp b/plugins/ImageBoard.hpp
index 9d4c123..b37448b 100644
--- a/plugins/ImageBoard.hpp
+++ b/plugins/ImageBoard.hpp
@@ -7,28 +7,31 @@ namespace QuickMedia {
OK,
TRY_AGAIN,
BANNED,
+ //FILE_TOO_LARGE,
+ NO_SUCH_FILE,
+ FILE_TYPE_NOT_ALLOWED,
ERR
};
class ImageBoardThreadPage : public VideoPage {
public:
- ImageBoardThreadPage(Program *program, std::string board_id, std::string thread_id, std::vector<std::string> cached_media_urls) : VideoPage(program), board_id(std::move(board_id)), thread_id(std::move(thread_id)), cached_media_urls(std::move(cached_media_urls)) {}
+ ImageBoardThreadPage(Program *program, std::string board_id, std::string thread_id) : VideoPage(program), board_id(std::move(board_id)), thread_id(std::move(thread_id)) {}
const char* get_title() const override { return ""; }
PageTypez get_type() const override { return PageTypez::IMAGE_BOARD_THREAD; }
- BodyItems get_related_media(const std::string &url, std::string &channel_url) override;
+ bool autoplay_next_item() override { return true; }
std::unique_ptr<RelatedVideosPage> create_related_videos_page(Program *program, const std::string &video_url, const std::string &video_title) override;
std::unique_ptr<Page> create_channels_page(Program*, const std::string&) override {
return nullptr;
}
std::string get_url() override { return video_url; }
virtual PluginResult login(const std::string &token, const std::string &pin, std::string &response_msg);
- virtual PostResult post_comment(const std::string &captcha_id, const std::string &comment) = 0;
+ // If |filepath| is empty then no file is uploaded
+ virtual PostResult post_comment(const std::string &captcha_id, const std::string &comment, const std::string &filepath = "") = 0;
virtual const std::string& get_pass_id();
const std::string board_id;
const std::string thread_id;
- const std::vector<std::string> cached_media_urls;
std::string video_url;
};
} \ No newline at end of file
diff --git a/src/Body.cpp b/src/Body.cpp
index aa8d1d6..be9d1fd 100644
--- a/src/Body.cpp
+++ b/src/Body.cpp
@@ -50,7 +50,6 @@ namespace QuickMedia {
BodyItem& BodyItem::operator=(BodyItem &other) {
url = other.url;
thumbnail_url = other.thumbnail_url;
- attached_content_url = other.attached_content_url;
visible = other.visible;
dirty = other.dirty;
dirty_description = other.dirty_description;
diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp
index 4ed11b2..b9f0bc8 100644
--- a/src/QuickMedia.cpp
+++ b/src/QuickMedia.cpp
@@ -2151,7 +2151,7 @@ namespace QuickMedia {
std::unique_ptr<VideoPlayer> video_player;
BodyItems related_videos;
- if(video_page->autoplay_next_item())
+ if(video_page->autoplay_next_item() && play_index + 1 >= 0 && play_index + 1 < (int)next_play_items.size())
related_videos.insert(related_videos.end(), next_play_items.begin() + play_index + 1, next_play_items.end());
std::string channel_url;
@@ -2380,14 +2380,17 @@ namespace QuickMedia {
std::string new_video_title;
// Find video that hasn't been played before in this video session
- for(auto it = related_videos.begin(), end = related_videos.end(); it != end; ++it) {
- if(watched_videos.find((*it)->url) == watched_videos.end() && !video_page->video_should_be_skipped((*it)->url)) {
- new_video_url = (*it)->url;
- new_video_title = (*it)->get_title();
- related_videos.erase(it);
- break;
+ auto find_next_video = [this, &related_videos, &video_page, &new_video_url, &new_video_title]() {
+ for(auto it = related_videos.begin(), end = related_videos.end(); it != end; ++it) {
+ if(!(*it)->url.empty() && watched_videos.find((*it)->url) == watched_videos.end() && !video_page->video_should_be_skipped((*it)->url)) {
+ new_video_url = (*it)->url;
+ new_video_title = (*it)->get_title();
+ related_videos.erase(it);
+ break;
+ }
}
- }
+ };
+ find_next_video();
if(new_video_url.empty() && parent_page && parent_body_page) {
BodyItems new_body_items;
@@ -2406,16 +2409,7 @@ namespace QuickMedia {
next_play_items.insert(next_play_items.end(), new_body_items.begin(), new_body_items.end());
(*parent_body_page)++;
related_videos = std::move(new_body_items);
-
- // Find video that hasn't been played before in this video session
- for(auto it = related_videos.begin(), end = related_videos.end(); it != end; ++it) {
- if(watched_videos.find((*it)->url) == watched_videos.end() && !video_page->video_should_be_skipped((*it)->url)) {
- new_video_url = (*it)->url;
- new_video_title = (*it)->get_title();
- related_videos.erase(it);
- break;
- }
- }
+ find_next_video();
}
}
@@ -3073,15 +3067,18 @@ namespace QuickMedia {
comment_input.draw_background = false;
comment_input.set_editable(false);
- auto post_comment = [this, &comment_input, &navigation_stage, &thread_page, &captcha_post_id, &comment_to_post, &request_new_google_captcha_challenge]() {
+ 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) {
comment_input.set_editable(false);
navigation_stage = NavigationStage::POSTING_COMMENT;
- PostResult post_result = thread_page->post_comment(captcha_post_id, comment_to_post);
+ PostResult post_result = thread_page->post_comment(captcha_post_id, comment_to_post, file_to_upload);
if(post_result == PostResult::OK) {
show_notification("QuickMedia", "Comment posted!");
navigation_stage = NavigationStage::VIEWING_COMMENTS;
// 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");
// TODO: Check if the response contains a new captcha instead of requesting a new one manually
@@ -3089,8 +3086,17 @@ namespace QuickMedia {
} else if(post_result == PostResult::BANNED) {
show_notification("QuickMedia", "Failed to post comment because you are banned", Urgency::CRITICAL);
navigation_stage = NavigationStage::VIEWING_COMMENTS;
+ //} else if(post_result == PostResult::FILE_TOO_LARGE) {
+ // show_notification("QuickMedia", "Failed to post comment because the file you are trying to upload is larger than " + std::to_string((double)thread_page->get_max_upload_file_size() * 1024.0 * 1024.0) + " mb", Urgency::CRITICAL);
+ // navigation_stage = NavigationStage::VIEWING_COMMENTS;
+ } else if(post_result == PostResult::NO_SUCH_FILE) {
+ show_notification("QuickMedia", "Failed to post comment because the file you are trying to upload no longer exists", Urgency::CRITICAL);
+ navigation_stage = NavigationStage::VIEWING_COMMENTS;
+ } else if(post_result == PostResult::FILE_TYPE_NOT_ALLOWED) {
+ show_notification("QuickMedia", "Failed to post comment because you are trying to upload a file of a type that is not allowed", Urgency::CRITICAL);
+ navigation_stage = NavigationStage::VIEWING_COMMENTS;
} else if(post_result == PostResult::ERR) {
- show_notification("QuickMedia", "Failed to post comment. Is " + std::string(plugin_name) + " down or is your internet down?", Urgency::CRITICAL);
+ show_notification("QuickMedia", "Failed to post comment", Urgency::CRITICAL);
navigation_stage = NavigationStage::VIEWING_COMMENTS;
} else {
assert(false && "Unhandled post result");
@@ -3101,7 +3107,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, &thread_page](std::string text) -> bool {
+ 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 {
if(text.empty())
return false;
@@ -3111,15 +3117,15 @@ namespace QuickMedia {
comment_to_post = std::move(text);
if(!captcha_post_id.empty() && captcha_solved_time.getElapsedTime().asSeconds() < 120) {
- post_comment_future = AsyncTask<bool>([&post_comment]() -> bool {
- post_comment();
+ 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;
});
} 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]() -> bool {
- post_comment();
+ 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;
});
}
@@ -3131,7 +3137,12 @@ namespace QuickMedia {
sf::Sprite logo_sprite(plugin_logo);
logo_sprite.setScale(0.8f * get_ui_scale(), 0.8f * get_ui_scale());
- sf::Vector2f logo_size(plugin_logo.getSize().x * logo_sprite.getScale().x, plugin_logo.getSize().y * logo_sprite.getScale().y);
+ sf::Vector2f logo_size(std::floor(plugin_logo.getSize().x * logo_sprite.getScale().x), std::floor(plugin_logo.getSize().y * logo_sprite.getScale().y));
+
+ sf::Sprite file_to_upload_sprite;
+ auto file_to_upload_thumbnail_data = std::make_shared<ThumbnailData>();
+
+ const float logo_file_to_upload_spacing = std::floor(10.0f * get_ui_scale());
float prev_chat_height = comment_input.get_height();
float chat_input_height_full = 0.0f;
@@ -3193,15 +3204,15 @@ namespace QuickMedia {
current_page = pop_page_stack();
} else if(event.key.code == sf::Keyboard::P) {
BodyItem *selected_item = thread_body->get_selected();
- if(selected_item && !selected_item->attached_content_url.empty()) {
- if(is_url_video(selected_item->attached_content_url)) {
+ if(selected_item && !selected_item->url.empty()) {
+ if(is_url_video(selected_item->url)) {
page_stack.push(PageType::IMAGE_BOARD_THREAD);
current_page = PageType::VIDEO_CONTENT;
watched_videos.clear();
- thread_page->video_url = selected_item->attached_content_url;
+ thread_page->video_url = selected_item->url;
BodyItems next_items;
// TODO: Use real title
- video_content_page(thread_page, thread_page, "", true, next_items, 0);
+ video_content_page(thread_page, thread_page, selected_item->get_title(), true, thread_body->items, thread_body->get_selected_item());
redraw = true;
} else {
load_image_future.cancel();
@@ -3210,17 +3221,41 @@ namespace QuickMedia {
load_image_future = AsyncTask<std::string>([&thread_body]() {
std::string image_data;
BodyItem *selected_item = thread_body->get_selected();
- if(!selected_item || selected_item->attached_content_url.empty()) {
+ if(!selected_item || selected_item->url.empty()) {
return image_data;
}
- if(download_to_string_cache(selected_item->attached_content_url, image_data, {}) != DownloadResult::OK) {
- show_notification("QuickMedia", "Failed to download image: " + selected_item->attached_content_url, Urgency::CRITICAL);
+ if(download_to_string_cache(selected_item->url, image_data, {}) != DownloadResult::OK) {
+ show_notification("QuickMedia", "Failed to download image: " + selected_item->url, Urgency::CRITICAL);
image_data.clear();
}
return image_data;
});
}
}
+ } else if(event.key.code == sf::Keyboard::U) {
+ auto file_manager_page = std::make_unique<FileManagerPage>(this, (FileManagerMimeType)(FILE_MANAGER_MIME_TYPE_IMAGE|FILE_MANAGER_MIME_TYPE_VIDEO));
+ file_manager_page->set_current_directory(get_home_dir().data);
+ auto file_manager_body = create_body();
+ file_manager_page->get_files_in_directory(file_manager_body->items);
+ std::vector<Tab> file_manager_tabs;
+ file_manager_tabs.push_back(Tab{std::move(file_manager_body), std::move(file_manager_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)});
+
+ sf::Event event;
+ while(window.pollEvent(event)) {}
+
+ selected_files.clear();
+ page_loop(file_manager_tabs);
+
+ if(selected_files.empty()) {
+ fprintf(stderr, "No files selected!\n");
+ } else {
+ selected_file_for_upload = selected_files[0];
+ }
+
+ redraw = true;
+ frame_skip_text_entry = true;
+ } else if(event.key.code == sf::Keyboard::D && event.key.control) {
+ selected_file_for_upload.clear();
}
BodyItem *selected_item = thread_body->get_selected();
@@ -3293,14 +3328,14 @@ namespace QuickMedia {
} 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](std::optional<std::string> new_captcha_post_id, std::optional<GoogleCaptchaChallengeInfo> new_challenge_info) {
+ [&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();
+ 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();
@@ -3331,7 +3366,35 @@ namespace QuickMedia {
update_idle_state();
handle_window_close();
- chat_input_height_full = comment_input.get_height() + chat_input_padding_y * 2.0f;
+ if(!selected_file_for_upload.empty() && file_to_upload_thumbnail_data->loading_state == LoadingState::NOT_LOADED)
+ AsyncImageLoader::get_instance().load_thumbnail(selected_file_for_upload, true, sf::Vector2i(logo_size.x, logo_size.y * 4), file_to_upload_thumbnail_data);
+
+ if(selected_file_for_upload.empty() && file_to_upload_thumbnail_data->loading_state == LoadingState::APPLIED_TO_TEXTURE) {
+ file_to_upload_thumbnail_data = std::make_unique<ThumbnailData>();
+ redraw = true;
+ }
+
+ if(file_to_upload_thumbnail_data->loading_state == LoadingState::FINISHED_LOADING && file_to_upload_thumbnail_data->image->getSize().x > 0 && file_to_upload_thumbnail_data->image->getSize().y > 0) {
+ if(!file_to_upload_thumbnail_data->texture.loadFromImage(*file_to_upload_thumbnail_data->image))
+ fprintf(stderr, "Warning: failed to load texture for attached file\n");
+ file_to_upload_thumbnail_data->texture.setSmooth(true);
+ //room_avatar_thumbnail_data->texture.generateMipmap();
+ file_to_upload_thumbnail_data->image.reset();
+ file_to_upload_thumbnail_data->loading_state = LoadingState::APPLIED_TO_TEXTURE;
+ file_to_upload_sprite.setTexture(file_to_upload_thumbnail_data->texture, true);
+
+ sf::Vector2f texture_size_f(file_to_upload_thumbnail_data->texture.getSize().x, file_to_upload_thumbnail_data->texture.getSize().y);
+ sf::Vector2f image_scale = get_ratio(texture_size_f, clamp_to_size_x(texture_size_f, logo_size));
+ file_to_upload_sprite.setScale(image_scale);
+ redraw = true;
+ }
+
+ float chat_input_height_full_images = logo_size.y;
+ if(file_to_upload_thumbnail_data->loading_state == LoadingState::APPLIED_TO_TEXTURE) {
+ const float file_to_upload_height = std::floor(logo_file_to_upload_spacing + file_to_upload_sprite.getTexture()->getSize().y * file_to_upload_sprite.getScale().y);
+ chat_input_height_full_images += file_to_upload_height;
+ }
+ chat_input_height_full = chat_input_padding_y + std::max(comment_input.get_height(), chat_input_height_full_images) + chat_input_padding_y;
const float chat_height = comment_input.get_height();
if(std::abs(chat_height - prev_chat_height) > 1.0f) {
@@ -3359,7 +3422,8 @@ namespace QuickMedia {
body_pos = sf::Vector2f(body_padding_horizontal, comment_input_shade.getSize().y + body_padding_vertical);
body_size = sf::Vector2f(body_width, window_size.y - comment_input_shade.getSize().y - body_padding_vertical);
- logo_sprite.setPosition(logo_padding_x, std::floor(comment_input_shade.getSize().y * 0.5f - logo_size.y * 0.5f));
+ logo_sprite.setPosition(logo_padding_x, chat_input_padding_y);
+ file_to_upload_sprite.setPosition(logo_sprite.getPosition() + sf::Vector2f(0.0f, logo_size.y + logo_file_to_upload_spacing));
}
//comment_input.update();
@@ -3416,8 +3480,8 @@ namespace QuickMedia {
BodyItem *selected_item = thread_body->get_selected();
std::string selected_item_attached_url;
if(selected_item)
- selected_item_attached_url = selected_item->attached_content_url;
- show_notification("QuickMedia", "Failed to load image downloaded from url: " + selected_item->attached_content_url, Urgency::CRITICAL);
+ selected_item_attached_url = selected_item->url;
+ show_notification("QuickMedia", "Failed to load image downloaded from url: " + selected_item_attached_url, Urgency::CRITICAL);
}
}
@@ -3446,11 +3510,15 @@ namespace QuickMedia {
} else if(navigation_stage == NavigationStage::REPLYING) {
window.draw(comment_input_shade);
window.draw(logo_sprite);
+ if(file_to_upload_thumbnail_data->loading_state == LoadingState::APPLIED_TO_TEXTURE)
+ window.draw(file_to_upload_sprite);
comment_input.draw(window);
thread_body->draw(window, body_pos, body_size);
} else if(navigation_stage == NavigationStage::VIEWING_COMMENTS) {
window.draw(comment_input_shade);
window.draw(logo_sprite);
+ if(file_to_upload_thumbnail_data->loading_state == LoadingState::APPLIED_TO_TEXTURE)
+ window.draw(file_to_upload_sprite);
comment_input.draw(window);
thread_body->draw(window, body_pos, body_size);
}
diff --git a/src/plugins/FileManager.cpp b/src/plugins/FileManager.cpp
index f15deae..814ee70 100644
--- a/src/plugins/FileManager.cpp
+++ b/src/plugins/FileManager.cpp
@@ -14,12 +14,31 @@ namespace QuickMedia {
return "";
}
- static std::filesystem::file_time_type file_get_filetime_or(const std::filesystem::directory_entry &path, std::filesystem::file_time_type default_value) {
- try {
- return path.last_write_time();
- } catch(const std::filesystem::filesystem_error &err) {
+ static std::filesystem::file_time_type file_get_last_modified_time(const std::filesystem::directory_entry &path, std::filesystem::file_time_type default_value) {
+ std::error_code err;
+ auto last_write_time = path.last_write_time(err);
+ if(err)
return default_value;
- }
+ else
+ return last_write_time;
+ }
+
+ static std::string file_size_to_human_readable_string(size_t bytes) {
+ double kb = (double)bytes / 1024.0;
+ double mb = (double)bytes / 1024.0 / 1024.0;
+ double gb = (double)bytes / 1024.0 / 1024.0 / 1024.0;
+ char result[32];
+
+ if(gb >= 1.0)
+ snprintf(result, sizeof(result), "%.2f GiB", gb);
+ else if(mb >= 1.0)
+ snprintf(result, sizeof(result), "%.2f MiB", mb);
+ else if(kb >= 1.0)
+ snprintf(result, sizeof(result), "%.2f KiB", kb);
+ else
+ snprintf(result, sizeof(result), "%zu bytes", bytes);
+
+ return result;
}
PluginResult FileManagerPage::submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) {
@@ -71,7 +90,7 @@ namespace QuickMedia {
}
std::sort(paths.begin(), paths.end(), [](const std::filesystem::directory_entry &path1, std::filesystem::directory_entry &path2) {
- return file_get_filetime_or(path1, std::filesystem::file_time_type::min()) > file_get_filetime_or(path2, std::filesystem::file_time_type::min());
+ return file_get_last_modified_time(path1, std::filesystem::file_time_type::min()) > file_get_last_modified_time(path2, std::filesystem::file_time_type::min());
});
if(current_dir != "/") {
@@ -79,14 +98,51 @@ namespace QuickMedia {
result_items.push_back(std::move(parent_item));
}
+ char time_str[128] = {0};
for(auto &p : paths) {
- auto body_item = BodyItem::create(p.path().filename().string());
+ std::error_code regular_file_err;
+ bool is_regular_file = p.is_regular_file(regular_file_err);
+ if(regular_file_err)
+ is_regular_file = true;
+
// TODO: Check file magic number instead of extension?
const char *ext = get_ext(p.path());
- if(p.is_regular_file() && (is_image_ext(ext) || is_video_ext(ext))) {
+ FileManagerMimeType file_mime_type = FILE_MANAGER_MIME_TYPE_OTHER;
+ if(is_regular_file) {
+ if(is_image_ext(ext))
+ file_mime_type = FILE_MANAGER_MIME_TYPE_IMAGE;
+ else if(is_video_ext(ext))
+ file_mime_type = FILE_MANAGER_MIME_TYPE_VIDEO;
+ }
+
+ if(is_regular_file && !(mime_type & file_mime_type))
+ continue;
+
+ auto body_item = BodyItem::create(p.path().filename().string());
+ if(file_mime_type == FILE_MANAGER_MIME_TYPE_IMAGE || file_mime_type == FILE_MANAGER_MIME_TYPE_VIDEO) {
body_item->thumbnail_is_local = true;
body_item->thumbnail_url = p.path().string();
}
+
+ time_t last_modified_time = std::chrono::duration_cast<std::chrono::seconds>(file_get_last_modified_time(p, std::filesystem::file_time_type::min()).time_since_epoch()).count();
+ struct tm last_modified_tm;
+ localtime_r(&last_modified_time, &last_modified_tm);
+ strftime(time_str, sizeof(time_str) - 1, "%a %b %d %H:%M", &last_modified_tm);
+
+ std::error_code file_size_err;
+ size_t file_size = p.file_size(file_size_err);
+ if(file_size_err)
+ file_size = 0;
+
+ std::string description = "Modified: ";
+ description += time_str;
+ if(is_regular_file) {
+ description += "\n";
+ description += "Size: " + file_size_to_human_readable_string(file_size);
+ }
+ body_item->set_description(std::move(description));
+ body_item->set_description_color(sf::Color(179, 179, 179));
+
result_items.push_back(std::move(body_item));
}
diff --git a/src/plugins/Fourchan.cpp b/src/plugins/Fourchan.cpp
index 7cad54f..5e6a970 100644
--- a/src/plugins/Fourchan.cpp
+++ b/src/plugins/Fourchan.cpp
@@ -208,11 +208,8 @@ namespace QuickMedia {
}
}
- // TODO: Merge with get_threads_internal
- PluginResult FourchanThreadListPage::submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) {
- (void)title;
- cached_media_urls.clear();
-
+ // TODO: Merge with lazy fetch
+ PluginResult FourchanThreadListPage::submit(const std::string&, const std::string &url, std::vector<Tab> &result_tabs) {
Json::Value json_root;
DownloadResult result = download_json(json_root, fourchan_url + board_id + "/thread/" + url + ".json", {}, true);
if(result != DownloadResult::OK) return download_result_to_plugin_result(result);
@@ -299,8 +296,8 @@ namespace QuickMedia {
comment_text.append(cp.text.data, cp.text.size);
break;
case CommentPiece::Type::QUOTE:
- comment_text += '>';
- comment_text.append(cp.text.data, cp.text.size);
+ //comment_text += '>';
+ //comment_text.append(cp.text.data, cp.text.size);
//comment_text += '\n';
break;
case CommentPiece::Type::QUOTELINK: {
@@ -342,8 +339,7 @@ namespace QuickMedia {
// thumbnails always has .jpg extension even if they are gifs or webm.
std::string tim_str = std::to_string(tim.asInt64());
body_item->thumbnail_url = fourchan_image_url + board_id + "/" + tim_str + "s.jpg";
- body_item->attached_content_url = fourchan_image_url + board_id + "/" + tim_str + ext_str;
- cached_media_urls.push_back(body_item->attached_content_url);
+ body_item->url = fourchan_image_url + board_id + "/" + tim_str + ext_str;
sf::Vector2i thumbnail_size(64, 64);
const Json::Value &tn_w = post["tn_w"];
@@ -358,7 +354,7 @@ namespace QuickMedia {
auto body = create_body();
body->items = std::move(result_items);
- result_tabs.push_back(Tab{std::move(body), std::make_unique<FourchanThreadPage>(program, board_id, url, std::move(cached_media_urls)), nullptr});
+ result_tabs.push_back(Tab{std::move(body), std::make_unique<FourchanThreadPage>(program, board_id, url), nullptr});
return PluginResult::OK;
}
@@ -404,8 +400,8 @@ namespace QuickMedia {
title_text.append(cp.text.data, cp.text.size);
break;
case CommentPiece::Type::QUOTE:
- title_text += '>';
- title_text.append(cp.text.data, cp.text.size);
+ //title_text += '>';
+ //title_text.append(cp.text.data, cp.text.size);
//comment_text += '\n';
break;
case CommentPiece::Type::QUOTELINK: {
@@ -433,8 +429,8 @@ namespace QuickMedia {
comment_text.append(cp.text.data, cp.text.size);
break;
case CommentPiece::Type::QUOTE:
- comment_text += '>';
- comment_text.append(cp.text.data, cp.text.size);
+ //comment_text += '>';
+ //comment_text.append(cp.text.data, cp.text.size);
//comment_text += '\n';
break;
case CommentPiece::Type::QUOTELINK: {
@@ -535,7 +531,14 @@ namespace QuickMedia {
}
}
- PostResult FourchanThreadPage::post_comment(const std::string &captcha_id, const std::string &comment) {
+ static std::string file_get_filename(const std::string &filepath) {
+ size_t index = filepath.rfind('/');
+ if(index == std::string::npos)
+ return filepath.c_str();
+ return filepath.c_str() + index + 1;
+ }
+
+ PostResult FourchanThreadPage::post_comment(const std::string &captcha_id, const std::string &comment, const std::string &filepath) {
std::string url = "https://sys.4chan.org/" + board_id + "/post";
std::vector<CommandArg> additional_args = {
@@ -546,6 +549,15 @@ namespace QuickMedia {
CommandArg{"-F", "mode=regist"}
};
+ if(!filepath.empty()) {
+ std::string filename = file_get_filename(filepath);
+ if(filename[0] == '@')
+ filename = "\\" + filename;
+
+ additional_args.push_back({ "-F", "upfile=@" + filepath });
+ additional_args.push_back({ "-F", "filename=" + filename });
+ }
+
if(pass_id.empty()) {
additional_args.push_back(CommandArg{"-F", "g-recaptcha-response=" + captcha_id});
} else {
@@ -569,6 +581,9 @@ namespace QuickMedia {
return PostResult::BANNED;
if(response.find("try again") != std::string::npos || response.find("No valid captcha") != std::string::npos)
return PostResult::TRY_AGAIN;
+ if(response.find("Audio streams are not allowed") != std::string::npos)
+ return PostResult::FILE_TYPE_NOT_ALLOWED;
+
return PostResult::ERR;
}
diff --git a/src/plugins/ImageBoard.cpp b/src/plugins/ImageBoard.cpp
index 6f97082..881d8ff 100644
--- a/src/plugins/ImageBoard.cpp
+++ b/src/plugins/ImageBoard.cpp
@@ -1,21 +1,6 @@
#include "../../plugins/ImageBoard.hpp"
namespace QuickMedia {
- BodyItems ImageBoardThreadPage::get_related_media(const std::string &url, std::string&) {
- BodyItems body_items;
- auto it = std::find(cached_media_urls.begin(), cached_media_urls.end(), url);
- if(it == cached_media_urls.end())
- return body_items;
-
- ++it;
- for(; it != cached_media_urls.end(); ++it) {
- auto body_item = BodyItem::create("");
- body_item->url = *it;
- body_items.push_back(std::move(body_item));
- }
- return body_items;
- }
-
std::unique_ptr<RelatedVideosPage> ImageBoardThreadPage::create_related_videos_page(Program*, const std::string&, const std::string&) {
return nullptr;
}