aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--TODO2
-rw-r--r--include/BodyItem.hpp5
-rw-r--r--include/StringUtils.hpp2
-rw-r--r--plugins/Fourchan.hpp7
-rw-r--r--plugins/ImageBoard.hpp11
-rw-r--r--src/BodyItem.cpp3
-rw-r--r--src/QuickMedia.cpp123
-rw-r--r--src/StringUtils.cpp9
-rw-r--r--src/Text.cpp6
-rw-r--r--src/plugins/Fourchan.cpp49
-rw-r--r--src/plugins/Info.cpp10
11 files changed, 141 insertions, 86 deletions
diff --git a/TODO b/TODO
index ce73dc8..d91e19f 100644
--- a/TODO
+++ b/TODO
@@ -247,4 +247,4 @@ Atomic file operations should use a random generated name instead of .tmp, becau
TODO: https://github.com/matrix-org/synapse/issues/14444.
Use matrix /sync "since" param. Its beneficial even to quickmedia because synapse is written in such a way that using "since" is faster.
/encrypt should support formatted text like greentext, custom emoji, mentions etc.
-allow opening 4chan thread directly (with jumping to comment), allow navigating to cross-post/dead thread with ctrl+i, fallback to 4chan archive if thread is dead. \ No newline at end of file
+allow navigating to cross-post/dead thread with ctrl+i, fallback to 4chan archive if thread is dead, allow navigating to thread post in the same thread as well using archive to bring the post back alive. \ No newline at end of file
diff --git a/include/BodyItem.hpp b/include/BodyItem.hpp
index 89adc5d..a2dae24 100644
--- a/include/BodyItem.hpp
+++ b/include/BodyItem.hpp
@@ -188,11 +188,6 @@ namespace QuickMedia {
std::unique_ptr<Text> description_text;
std::unique_ptr<Text> author_text;
std::unique_ptr<mgl::Text> timestamp_text; // TODO: Remove
- // Used by image boards for example. The elements are indices to other body items
- std::vector<size_t> replies_to;
- // Used by image boards for example. The elements are indices to other body items
- std::vector<size_t> replies;
- std::string post_number;
void *userdata; // Not managed, should be deallocated by whoever sets this
float loaded_height = 0.0f;
float height = 0.0f;
diff --git a/include/StringUtils.hpp b/include/StringUtils.hpp
index 91de829..002b4c2 100644
--- a/include/StringUtils.hpp
+++ b/include/StringUtils.hpp
@@ -27,6 +27,8 @@ namespace QuickMedia {
bool strncase_equals(const char *str1, const char *str2, size_t length);
bool strcase_equals(const char *str1, const char *str2);
// Note: does not check for overflow
+ bool to_num(const char *str, size_t size, int64_t &num);
+ // Note: does not check for overflow
bool to_num(const char *str, size_t size, int &num);
// Note: does not check for overflow
bool to_num_hex(const char *str, size_t size, int &num);
diff --git a/plugins/Fourchan.hpp b/plugins/Fourchan.hpp
index 51fe880..8622a69 100644
--- a/plugins/Fourchan.hpp
+++ b/plugins/Fourchan.hpp
@@ -3,6 +3,9 @@
#include "ImageBoard.hpp"
namespace QuickMedia {
+ // |post_id| is optional
+ bool fourchan_extract_url(const std::string &url, std::string &board_id, std::string &thread_id, std::string &post_id);
+
class FourchanBoardsPage : public Page {
public:
FourchanBoardsPage(Program *program, std::string resources_root) : Page(program), resources_root(std::move(resources_root)) {}
@@ -57,8 +60,8 @@ namespace QuickMedia {
class FourchanThreadPage : public ImageBoardThreadPage {
public:
- FourchanThreadPage(Program *program, std::string board_id, std::string thread_id, std::string pass_id) :
- ImageBoardThreadPage(program, std::move(board_id), std::move(thread_id)), pass_id(std::move(pass_id)) {}
+ FourchanThreadPage(Program *program, std::string board_id, std::string thread_id, std::string post_id, std::string pass_id) :
+ ImageBoardThreadPage(program, std::move(board_id), std::move(thread_id), std::move(post_id)), pass_id(std::move(pass_id)) {}
PluginResult lazy_fetch(BodyItems &result_items) override;
diff --git a/plugins/ImageBoard.hpp b/plugins/ImageBoard.hpp
index e135532..6341622 100644
--- a/plugins/ImageBoard.hpp
+++ b/plugins/ImageBoard.hpp
@@ -22,6 +22,13 @@ namespace QuickMedia {
std::string err_msg;
};
+ class ImageBoardBodyItemData : public BodyItemExtra {
+ public:
+ std::vector<size_t> replies_to;
+ std::vector<size_t> replies;
+ int64_t post_id = 0;
+ };
+
// All fields are optional
struct ImageBoardCaptchaChallenge {
std::string challenge_id;
@@ -32,7 +39,8 @@ namespace QuickMedia {
class ImageBoardThreadPage : public LazyFetchPage {
public:
- ImageBoardThreadPage(Program *program, std::string board_id, std::string thread_id) : LazyFetchPage(program), board_id(std::move(board_id)), thread_id(std::move(thread_id)) {}
+ ImageBoardThreadPage(Program *program, std::string board_id, std::string thread_id, std::string post_id) :
+ LazyFetchPage(program), board_id(std::move(board_id)), thread_id(std::move(thread_id)), post_id(std::move(post_id)) {}
const char* get_title() const override { return ""; }
PageTypez get_type() const override { return PageTypez::IMAGE_BOARD_THREAD; }
@@ -46,6 +54,7 @@ namespace QuickMedia {
const std::string board_id;
const std::string thread_id;
+ const std::string post_id;
};
class ImageBoardVideoPage : public VideoPage {
diff --git a/src/BodyItem.cpp b/src/BodyItem.cpp
index d62efea..86f0dfb 100644
--- a/src/BodyItem.cpp
+++ b/src/BodyItem.cpp
@@ -43,9 +43,6 @@ namespace QuickMedia {
description_text.reset();
author_text.reset();
timestamp_text.reset();
- replies_to = other.replies_to;
- replies = other.replies;
- post_number = other.post_number;
userdata = other.userdata;
loaded_height = 0.0f;
loaded_image_size = mgl::vec2f(0.0f, 0.0f);
diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp
index 2e7feff..ca69248 100644
--- a/src/QuickMedia.cpp
+++ b/src/QuickMedia.cpp
@@ -368,44 +368,6 @@ namespace QuickMedia {
return true;
}
- // |comment_id| is optional
- static bool fourchan_extract_url(const std::string &url, std::string &board_id, std::string &thread_id, std::string &comment_id) {
- size_t len = 10;
- size_t index = url.find("4chan.org/");
- if(index == std::string::npos) {
- len = 13;
- index = url.find("4channel.org/");
- }
-
- if(index == std::string::npos)
- return false;
-
- index += len;
- size_t board_end = url.find('/', index);
- if(board_end == std::string::npos)
- return false;
-
- board_id = url.substr(index, board_end - index);
- index = board_end + 1;
-
- const std::string_view remaining(url.data() + index, url.size() - index);
- if(remaining.size() <= 7 || remaining.substr(0, 7) != "thread/")
- return false;
-
- index += 7;
- size_t thread_id_end = url.find('#', index);
- if(thread_id_end == std::string::npos)
- thread_id_end = url.size();
-
- thread_id = url.substr(index, thread_id_end - index);
- if(thread_id.empty())
- return false;
-
- index = thread_id_end;
- comment_id = url.substr(index);
- return true;
- }
-
int Program::run(int argc, char **argv) {
mgl_init();
@@ -1330,11 +1292,10 @@ namespace QuickMedia {
tabs.push_back(Tab{std::move(categories_sukebei_body), std::make_unique<NyaaSiCategoryPage>(this, true), create_search_bar("Search...", SEARCH_DELAY_FILTER)});
} else if(strcmp(plugin_name, "4chan") == 0) {
if(launch_url_type == LaunchUrlType::FOURCHAN_THREAD) {
- // TODO: Use comment id
- std::string board_id, thread_id, comment_id;
- fourchan_extract_url(launch_url, board_id, thread_id, comment_id);
+ std::string board_id, thread_id, post_id;
+ fourchan_extract_url(launch_url, board_id, thread_id, post_id);
auto body = create_body();
- auto thread_page = std::make_unique<FourchanThreadPage>(this, std::move(board_id), std::move(thread_id), "");
+ auto thread_page = std::make_unique<FourchanThreadPage>(this, std::move(board_id), std::move(thread_id), std::move(post_id), "");
page_stack.push(current_page);
current_page = PageType::IMAGE_BOARD_THREAD;
image_board_thread_page(thread_page.get(), body.get());
@@ -3461,8 +3422,6 @@ namespace QuickMedia {
const bool is_resume_go_back = !start_time.empty();
if(start_time.empty())
start_time = video_page->get_url_timestamp();
-
- fprintf(stderr, "start time: %s\n", start_time.c_str());
watched_videos.insert(video_page->get_url());
// TODO: Sync sequences
@@ -4553,6 +4512,46 @@ namespace QuickMedia {
thread_body->set_items(std::move(result_items));
+ std::deque<int> comment_navigation_stack;
+ std::deque<int> comment_page_scroll_stack;
+
+ auto focus_selected_post = [&]() {
+ BodyItem *selected_item = thread_body->get_selected();
+ if(!selected_item)
+ return;
+
+ thread_body->for_each_item([](std::shared_ptr<BodyItem> &body_item) {
+ body_item->visible = false;
+ });
+
+ selected_item->visible = true;
+ ImageBoardBodyItemData *image_board_post_data = static_cast<ImageBoardBodyItemData*>(selected_item->extra.get());
+
+ for(size_t reply_to_index : image_board_post_data->replies_to) {
+ thread_body->get_item_by_index(reply_to_index)->visible = true;
+ }
+
+ for(size_t reply_index : image_board_post_data->replies) {
+ thread_body->get_item_by_index(reply_index)->visible = true;
+ }
+
+ comment_navigation_stack.push_back(thread_body->get_selected_item());
+ comment_page_scroll_stack.push_back(thread_body->get_page_scroll());
+ //thread_body->clamp_selection();
+ thread_body->set_page_scroll(0.0f);
+ };
+
+ int64_t navigate_to_post_id = 0;
+ if(!thread_page->post_id.empty() && to_num(thread_page->post_id.c_str(), thread_page->post_id.size(), navigate_to_post_id)) {
+ const int found_body_item_index = thread_body->find_item_index([&](auto &body_item) {
+ const ImageBoardBodyItemData *image_board_post_data = static_cast<ImageBoardBodyItemData*>(body_item->extra.get());
+ return image_board_post_data->post_id == navigate_to_post_id;
+ });
+
+ if(found_body_item_index != -1)
+ thread_body->set_selected_item(found_body_item_index);
+ }
+
// TODO: Instead of using stage here, use different pages for each stage
enum class NavigationStage {
VIEWING_COMMENTS,
@@ -4713,9 +4712,6 @@ namespace QuickMedia {
mgl::vec2f body_size;
mgl::Event event;
- std::deque<int> comment_navigation_stack;
- std::deque<int> comment_page_scroll_stack;
-
bool moving_captcha_left = false;
bool moving_captcha_right = false;
@@ -4866,21 +4862,9 @@ namespace QuickMedia {
}
BodyItem *selected_item = thread_body->get_selected();
- if(event.key.code == mgl::Keyboard::Enter && selected_item && (comment_navigation_stack.empty() || thread_body->get_selected_item() != comment_navigation_stack.back()) && (!selected_item->replies_to.empty() || !selected_item->replies.empty())) {
- thread_body->for_each_item([](std::shared_ptr<BodyItem> &body_item) {
- body_item->visible = false;
- });
- selected_item->visible = true;
- for(size_t reply_to_index : selected_item->replies_to) {
- thread_body->get_item_by_index(reply_to_index)->visible = true;
- }
- for(size_t reply_index : selected_item->replies) {
- thread_body->get_item_by_index(reply_index)->visible = true;
- }
- comment_navigation_stack.push_back(thread_body->get_selected_item());
- comment_page_scroll_stack.push_back(thread_body->get_page_scroll());
- //thread_body->clamp_selection();
- thread_body->set_page_scroll(0.0f);
+ ImageBoardBodyItemData *image_board_post_data = static_cast<ImageBoardBodyItemData*>(selected_item->extra.get());
+ if(event.key.code == mgl::Keyboard::Enter && selected_item && (comment_navigation_stack.empty() || thread_body->get_selected_item() != comment_navigation_stack.back()) && (!image_board_post_data->replies_to.empty() || !image_board_post_data->replies.empty())) {
+ focus_selected_post();
} else if(event.key.code == mgl::Keyboard::Backspace && !comment_navigation_stack.empty()) {
size_t previous_selected = comment_navigation_stack.back();
float previous_page_scroll = comment_page_scroll_stack.back();
@@ -4899,17 +4883,17 @@ namespace QuickMedia {
thread_body->set_selected_item(previous_selected);
selected_item = thread_body->get_item_by_index(comment_navigation_stack.back()).get();
selected_item->visible = true;
- for(size_t reply_to_index : selected_item->replies_to) {
+ for(size_t reply_to_index : image_board_post_data->replies_to) {
thread_body->get_item_by_index(reply_to_index)->visible = true;
}
- for(size_t reply_index : selected_item->replies) {
+ for(size_t reply_index : image_board_post_data->replies) {
thread_body->get_item_by_index(reply_index)->visible = true;
}
thread_body->clamp_selection();
}
thread_body->set_page_scroll(previous_page_scroll);
} else if(event.key.code == mgl::Keyboard::R && selected_item) {
- std::string text_to_add = ">>" + selected_item->post_number + "\n";
+ std::string text_to_add = ">>" + std::to_string(image_board_post_data->post_id) + "\n";
comment_input.insert_text_at_caret_position(std::move(text_to_add));
comment_input.move_caret_to_end();
}
@@ -6578,6 +6562,7 @@ namespace QuickMedia {
std::string youtube_channel_id;
std::string youtube_channel_url;
std::string video_id;
+ std::string board_id, thread_id, post_id;
if(youtube_url_extract_channel_id(url, youtube_channel_id, youtube_channel_url)) {
std::vector<Tab> tabs;
YoutubeChannelPage::create_each_type(this, youtube_channel_url, "", "Channel", tabs);
@@ -6593,6 +6578,14 @@ namespace QuickMedia {
video_content_page(matrix_chat_page, youtube_video_page.get(), "", false, tabs[MESSAGES_TAB_INDEX].body.get(), tabs[MESSAGES_TAB_INDEX].body->get_selected_item());
redraw = true;
avatar_applied = false;
+ } else if(fourchan_extract_url(url, board_id, thread_id, post_id)) {
+ auto body = create_body();
+ auto thread_page = std::make_unique<FourchanThreadPage>(this, std::move(board_id), std::move(thread_id), std::move(post_id), "");
+ page_stack.push(current_page);
+ current_page = PageType::IMAGE_BOARD_THREAD;
+ image_board_thread_page(thread_page.get(), body.get());
+ redraw = true;
+ avatar_applied = false;
} else {
const char *launch_program = "xdg-open";
if(!is_program_executable_by_name("xdg-open")) {
diff --git a/src/StringUtils.cpp b/src/StringUtils.cpp
index 564f307..656b511 100644
--- a/src/StringUtils.cpp
+++ b/src/StringUtils.cpp
@@ -210,7 +210,7 @@ namespace QuickMedia {
}
}
- bool to_num(const char *str, size_t size, int &num) {
+ bool to_num(const char *str, size_t size, int64_t &num) {
if(size == 0)
return false;
@@ -233,6 +233,13 @@ namespace QuickMedia {
return true;
}
+ bool to_num(const char *str, size_t size, int &num) {
+ int64_t num_i64 = 0;
+ const bool res = to_num(str, size, num_i64);
+ num = num_i64;
+ return res;
+ }
+
bool to_num_hex(const char *str, size_t size, int &num) {
if(size == 0)
return false;
diff --git a/src/Text.cpp b/src/Text.cpp
index 28247e6..74a0625 100644
--- a/src/Text.cpp
+++ b/src/Text.cpp
@@ -770,7 +770,7 @@ namespace QuickMedia
const float latin_font_width = latin_font->get_glyph(' ').advance;
const float vspace = font_get_real_height(latin_font);
const float hspace_latin = latin_font_width + characterSpacing;
- const float emoji_spacing = 2.0f;
+ const float emoji_spacing = std::max(1, int(vspace / 10.0f));
int hspace_monospace = 0;
const mgl::Color url_color = get_theme().url_text_color;
@@ -814,7 +814,7 @@ namespace QuickMedia
int vertexStart = vertices[vertices_index].size();
if(prevCodePoint != 0)
- glyphPos.x += emoji_spacing;
+ glyphPos.x += emoji_spacing + characterSpacing;
const float font_height_offset = vspace;
mgl::vec2f vertexTopLeft(glyphPos.x, glyphPos.y + font_height_offset - textElement.size.y);
@@ -854,7 +854,7 @@ namespace QuickMedia
const mgl::vec2f emoji_size = { vspace, vspace };
if(prevCodePoint != 0)
- glyphPos.x += emoji_spacing;
+ glyphPos.x += emoji_spacing + characterSpacing;
const float font_height_offset = floor(vspace * 0.5f);
mgl::vec2f vertexTopLeft(glyphPos.x, glyphPos.y + font_height_offset - emoji_size.y * 0.5f);
diff --git a/src/plugins/Fourchan.cpp b/src/plugins/Fourchan.cpp
index cf229d4..1ce97c4 100644
--- a/src/plugins/Fourchan.cpp
+++ b/src/plugins/Fourchan.cpp
@@ -19,6 +19,43 @@ static const std::string fourchan_image_url = "https://i.4cdn.org/";
static const char *SERVICE_NAME = "4chan";
namespace QuickMedia {
+ bool fourchan_extract_url(const std::string &url, std::string &board_id, std::string &thread_id, std::string &post_id) {
+ size_t len = 10;
+ size_t index = url.find("4chan.org/");
+ if(index == std::string::npos) {
+ len = 13;
+ index = url.find("4channel.org/");
+ }
+
+ if(index == std::string::npos)
+ return false;
+
+ index += len;
+ size_t board_end = url.find('/', index);
+ if(board_end == std::string::npos)
+ return false;
+
+ board_id = url.substr(index, board_end - index);
+ index = board_end + 1;
+
+ const std::string_view remaining(url.data() + index, url.size() - index);
+ if(remaining.size() <= 7 || remaining.substr(0, 7) != "thread/")
+ return false;
+
+ index += 7;
+ size_t thread_id_end = url.find("#p", index);
+ if(thread_id_end == std::string::npos)
+ thread_id_end = url.size();
+
+ thread_id = url.substr(index, thread_id_end - index);
+ if(thread_id.empty())
+ return false;
+
+ index = thread_id_end + 2;
+ post_id = url.substr(index);
+ return true;
+ }
+
// Returns empty string on failure to read cookie
static std::string get_pass_id_from_cookies_file(const Path &cookies_filepath) {
std::string file_content;
@@ -271,9 +308,9 @@ namespace QuickMedia {
// TODO: Link this quote to a 4chan archive that still has the quoted comment (if available)
comment_text += Text::formatted_text(std::move(cp.text) + " (DEAD)", get_theme().attention_alert_text_color, FORMATTED_TEXT_FLAG_COLOR);
} else {
- result_items[body_item_index]->replies_to.push_back(it->second);
- result_items[it->second]->replies.push_back(body_item_index);
- result_items[it->second]->add_reaction(">>" + result_items[body_item_index]->post_number, nullptr, get_theme().replies_text_color);
+ static_cast<ImageBoardBodyItemData*>(result_items[body_item_index]->extra.get())->replies_to.push_back(it->second);
+ static_cast<ImageBoardBodyItemData*>(result_items[it->second]->extra.get())->replies.push_back(body_item_index);
+ result_items[it->second]->add_reaction(">>" + std::to_string(static_cast<ImageBoardBodyItemData*>(result_items[body_item_index]->extra.get())->post_id), nullptr, get_theme().replies_text_color);
if(it->second == 0)
comment_text += Text::formatted_text(std::move(cp.text) + " (OP)", get_theme().attention_alert_text_color, FORMATTED_TEXT_FLAG_COLOR);
else
@@ -391,7 +428,7 @@ namespace QuickMedia {
}
PluginResult FourchanThreadListPage::submit(const SubmitArgs &args, std::vector<Tab> &result_tabs) {
- result_tabs.push_back(Tab{create_body(), std::make_unique<FourchanThreadPage>(program, board_id, args.url, pass_id), nullptr});
+ result_tabs.push_back(Tab{create_body(), std::make_unique<FourchanThreadPage>(program, board_id, args.url, "", pass_id), nullptr});
return PluginResult::OK;
}
@@ -500,8 +537,10 @@ namespace QuickMedia {
int64_t post_num_int = post_num.asInt64();
comment_by_postno[post_num_int] = result_items.size();
+ auto image_board_post_data = std::make_shared<ImageBoardBodyItemData>();
+ image_board_post_data->post_id = post_num_int;
result_items.push_back(BodyItem::create(""));
- result_items.back()->post_number = std::to_string(post_num_int);
+ result_items.back()->extra = std::move(image_board_post_data);
}
size_t body_item_index = 0;
diff --git a/src/plugins/Info.cpp b/src/plugins/Info.cpp
index 1ad7320..d4ee5f1 100644
--- a/src/plugins/Info.cpp
+++ b/src/plugins/Info.cpp
@@ -1,6 +1,7 @@
#include "../../plugins/Info.hpp"
#include "../../plugins/Saucenao.hpp"
#include "../../plugins/Youtube.hpp"
+#include "../../plugins/Fourchan.hpp"
#include "../../include/StringUtils.hpp"
#include "../../include/Program.hpp"
#include "../../include/Notification.hpp"
@@ -40,6 +41,7 @@ namespace QuickMedia {
}
PluginResult InfoPage::submit(const SubmitArgs &args, std::vector<Tab> &result_tabs) {
+ std::string board_id, thread_id, post_id;
if(string_starts_with(args.url, REVERSE_IMAGE_SEARCH_URL)) {
std::string image_url = args.url.substr(strlen(REVERSE_IMAGE_SEARCH_URL));
result_tabs.push_back(Tab{create_body(), std::make_unique<SaucenaoPage>(program, image_url, false), nullptr});
@@ -54,6 +56,9 @@ namespace QuickMedia {
} else if(is_youtube_url(args.url)) {
result_tabs.push_back(Tab{nullptr, std::make_unique<YoutubeVideoPage>(program, args.url, false), nullptr});
return PluginResult::OK;
+ } else if(fourchan_extract_url(args.url, board_id, thread_id, post_id)) {
+ result_tabs.push_back(Tab{create_body(), std::make_unique<FourchanThreadPage>(program, std::move(board_id), std::move(thread_id), std::move(post_id), ""), nullptr});
+ return PluginResult::OK;
} else {
return open_with_browser(args.url);
}
@@ -75,13 +80,18 @@ namespace QuickMedia {
// static
std::shared_ptr<BodyItem> InfoPage::add_url(const std::string &url) {
+ std::string board_id, thread_id, comment_id;
std::string title;
+
if(is_youtube_channel_url(url))
title = "Open youtube channel " + url;
else if(is_youtube_url(url))
title = "Play " + url;
+ else if(fourchan_extract_url(url, board_id, thread_id, comment_id))
+ title = "Open " + url;
else
title = "Open " + url + " in a browser";
+
auto body_item = BodyItem::create(std::move(title));
body_item->url = url;
return body_item;