aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md3
-rw-r--r--TODO3
-rw-r--r--images/mangadex_logo.pngbin4094 -> 4215 bytes
-rw-r--r--include/Body.hpp14
-rw-r--r--plugins/Matrix.hpp10
-rw-r--r--src/Body.cpp36
-rw-r--r--src/QuickMedia.cpp108
-rw-r--r--src/plugins/Dmenu.cpp2
-rw-r--r--src/plugins/FileManager.cpp2
-rw-r--r--src/plugins/Fourchan.cpp10
-rw-r--r--src/plugins/Mangadex.cpp4
-rw-r--r--src/plugins/Manganelo.cpp6
-rw-r--r--src/plugins/Mangatown.cpp10
-rw-r--r--src/plugins/Matrix.cpp172
-rw-r--r--src/plugins/NyaaSi.cpp14
-rw-r--r--src/plugins/Pornhub.cpp4
-rw-r--r--src/plugins/Youtube.cpp14
17 files changed, 313 insertions, 99 deletions
diff --git a/README.md b/README.md
index 17954ca..5f3afd3 100644
--- a/README.md
+++ b/README.md
@@ -47,7 +47,8 @@ Press `Tab` to autocomplete a search when autocomplete is available (currently o
Press `Tab` to switch between username/password field in login panel.\
Press `Ctrl + V` to paste the content of your clipboard into the search bar.\
Press `Ctrl + P` to view image/video attached to matrix message.\
-Press `Ctrl + C` to copy the url of the currently playing video to the clipboard (with timestamp).
+Press `Ctrl + C` to copy the url of the currently playing video to the clipboard (with timestamp).\
+Press `Ctrl + R` to reply to a message on matrix, press `ESC` to cancel.
## Matrix commands
`/upload` to upload an image. TODO: Support regular files and videos.\
`/logout` to logout.
diff --git a/TODO b/TODO
index 553abe1..c8dc748 100644
--- a/TODO
+++ b/TODO
@@ -50,4 +50,5 @@ Add search bar for matrix rooms.
Put rooms with recent messages at the top and the ones that mention us further at the top (matrix).
Allow setting matrix room priority (if it should always be at top).
Use Entry instead of SearchBar for 4chan commenting as well.
-Only add related videos to recommendations if its the first time we watch the video. This is to prevent rewatching a video multiple times from messing up recommendations. \ No newline at end of file
+Only add related videos to recommendations if its the first time we watch the video. This is to prevent rewatching a video multiple times from messing up recommendations.
+Fix incorrect body visible rows count (draws incorrect number of items and incorrect counted, also messed up pg(up/down)). \ No newline at end of file
diff --git a/images/mangadex_logo.png b/images/mangadex_logo.png
index 698a879..32d0307 100644
--- a/images/mangadex_logo.png
+++ b/images/mangadex_logo.png
Binary files differ
diff --git a/include/Body.hpp b/include/Body.hpp
index a5c346e..60b976d 100644
--- a/include/Body.hpp
+++ b/include/Body.hpp
@@ -22,6 +22,8 @@ namespace QuickMedia {
BodyItem(std::string _title);
BodyItem(const BodyItem &other);
+ static std::shared_ptr<BodyItem> create(std::string title) { return std::make_shared<BodyItem>(std::move(title)); }
+
void set_title(std::string new_title) {
if(title.empty() && new_title.empty())
return;
@@ -74,13 +76,14 @@ namespace QuickMedia {
std::string post_number;
sf::Color title_color;
sf::Color author_color;
+ void *userdata; // Not managed, should be deallocated by whoever sets this
private:
std::string title;
std::string description;
std::string author;
};
- using BodyItems = std::vector<std::unique_ptr<BodyItem>>;
+ using BodyItems = std::vector<std::shared_ptr<BodyItem>>;
class Body {
public:
@@ -108,10 +111,18 @@ namespace QuickMedia {
void clear_thumbnails();
BodyItem* get_selected() const;
+ std::shared_ptr<BodyItem> get_selected_shared();
void clamp_selection();
void draw(sf::RenderWindow &window, sf::Vector2f pos, sf::Vector2f size);
void draw(sf::RenderWindow &window, sf::Vector2f pos, sf::Vector2f size, const Json::Value &content_progress);
+ // |size| is the clip size, another outside this will be cut off.
+ // Note: this should be called after |draw|, or thumbnails will be messed up. TODO: find a way to solve this issue in a clean way.
+ // This happens because of |draw| sets thumbnails as unreferenced at the beginning and cleans them up at the end if they are not drawn in the same function call.
+ void draw_item(sf::RenderWindow &window, BodyItem *item, sf::Vector2f pos, sf::Vector2f size);
+
+ float get_item_height(BodyItem *item);
+ float get_spacing_y() const;
static bool string_find_case_insensitive(const std::string &str, const std::string &substr);
// TODO: Make this actually fuzzy... Right now it's just a case insensitive string find.
@@ -142,7 +153,6 @@ namespace QuickMedia {
sf::Color line_seperator_color;
private:
void draw_item(sf::RenderWindow &window, BodyItem *item, const sf::Vector2f &pos, const sf::Vector2f &size, const float item_height, const int item_index, const Json::Value &content_progress);
- float get_item_height(BodyItem *item);
private:
enum class LoadingState {
NOT_LOADED,
diff --git a/plugins/Matrix.hpp b/plugins/Matrix.hpp
index 97ba3cc..07c6f61 100644
--- a/plugins/Matrix.hpp
+++ b/plugins/Matrix.hpp
@@ -6,6 +6,7 @@
namespace QuickMedia {
struct UserInfo {
+ std::string user_id;
std::string display_name;
std::string avatar_url;
sf::Color display_name_color;
@@ -20,10 +21,12 @@ namespace QuickMedia {
struct Message {
// Index into |RoomData.user_info|
size_t user_id;
+ std::string event_id;
std::string body;
std::string url;
std::string thumbnail_url;
MessageType type;
+ bool is_reply;
};
struct MessageInfo {
@@ -39,7 +42,8 @@ namespace QuickMedia {
// The value is an index to |user_info|.
std::unordered_map<std::string, size_t> user_info_by_user_id;
std::vector<UserInfo> user_info;
- std::vector<Message> messages;
+ std::vector<std::shared_ptr<Message>> messages;
+ std::unordered_map<std::string, std::shared_ptr<Message>> message_by_event_id;
std::string name;
std::string avatar_url;
std::string prev_batch;
@@ -73,6 +77,10 @@ namespace QuickMedia {
// |url| should only be set when uploading media.
// TODO: Make api better.
PluginResult post_message(const std::string &room_id, const std::string &body, const std::string &url = "", MessageType msgtype = MessageType::TEXT, MessageInfo *info = nullptr);
+ // |relates_to| is from |BodyItem.userdata| and is of type |Message*|
+ PluginResult post_reply(const std::string &room_id, const std::string &body, void *relates_to);
+
+ // TODO: Make this work for all image types and videos and regular files
PluginResult post_file(const std::string &room_id, const std::string &filepath);
PluginResult login(const std::string &username, const std::string &password, const std::string &homeserver, std::string &err_msg);
PluginResult logout();
diff --git a/src/Body.cpp b/src/Body.cpp
index 718baee..5927831 100644
--- a/src/Body.cpp
+++ b/src/Body.cpp
@@ -24,7 +24,8 @@ namespace QuickMedia {
dirty_author(false),
thumbnail_is_local(false),
title_color(sf::Color::White),
- author_color(sf::Color::White)
+ author_color(sf::Color::White),
+ userdata(nullptr)
{
if(!_title.empty())
set_title(std::move(_title));
@@ -58,6 +59,7 @@ namespace QuickMedia {
post_number = other.post_number;
title_color = other.title_color;
author_color = other.author_color;
+ userdata = other.userdata;
}
Body::Body(Program *program, sf::Font *font, sf::Font *bold_font, sf::Font *cjk_font) :
@@ -233,6 +235,12 @@ namespace QuickMedia {
return items[selected_item].get();
}
+ std::shared_ptr<BodyItem> Body::get_selected_shared() {
+ if(selected_item < 0 || selected_item >= (int)items.size() || !items[selected_item]->visible)
+ return nullptr;
+ return items[selected_item];
+ }
+
void Body::clamp_selection() {
int num_items = (int)items.size();
if(items.empty())
@@ -373,8 +381,7 @@ namespace QuickMedia {
}
void Body::draw(sf::RenderWindow &window, sf::Vector2f pos, sf::Vector2f size) {
- Json::Value empty_object(Json::objectValue);
- draw(window, pos, size, empty_object);
+ draw(window, pos, size, Json::nullValue);
}
// TODO: Use a render target for the whole body so all images can be put into one.
@@ -502,9 +509,9 @@ namespace QuickMedia {
continue;
float item_height = get_item_height(item.get());
- prev_pos.y -= item_height + spacing_y;
+ prev_pos.y -= (item_height + spacing_y);
- if(prev_pos.y + item_height <= 0.0f)
+ if(prev_pos.y + item_height + spacing_y < start_y)
break;
draw_item(window, item.get(), prev_pos, size, item_height, i, content_progress);
@@ -521,10 +528,10 @@ namespace QuickMedia {
float item_height = get_item_height(item.get());
- if(after_pos.y + item_height > start_y + size.y)
+ if((after_pos.y - start_y) + item_height + spacing_y > size.y)
last_item_fully_visible = false;
- if(after_pos.y >= start_y + size.y)
+ if(after_pos.y - start_y >= size.y)
break;
draw_item(window, item.get(), after_pos, size, item_height, i, content_progress);
@@ -542,6 +549,14 @@ namespace QuickMedia {
}
}
+ void Body::draw_item(sf::RenderWindow &window, BodyItem *item, sf::Vector2f pos, sf::Vector2f size) {
+ //sf::Vector2u window_size = window.getSize();
+ // glEnable(GL_SCISSOR_TEST);
+ //glScissor(pos.x, (int)window_size.y - (int)pos.y - (int)pos.y, size.x, size.y);
+ draw_item(window, item, pos, size, get_item_height(item) + spacing_y, -1, Json::nullValue);
+ //glDisable(GL_SCISSOR_TEST);
+ }
+
void Body::draw_item(sf::RenderWindow &window, BodyItem *item, const sf::Vector2f &pos, const sf::Vector2f &size, const float item_height, const int item_index, const Json::Value &content_progress) {
// TODO: Instead of generating a new hash everytime to access textures, cache the hash of the thumbnail url
std::shared_ptr<ThumbnailData> item_thumbnail;
@@ -646,6 +661,9 @@ namespace QuickMedia {
item->description_text->draw(window);
}
+ if(!content_progress.isObject())
+ return;
+
// TODO: Do the same for non-manga content.
// TODO: Cache this instead of hash access every item every frame.
const Json::Value &item_progress = content_progress[item->get_title()];
@@ -692,6 +710,10 @@ namespace QuickMedia {
return item_height + padding_y * 2.0f;
}
+ float Body::get_spacing_y() const {
+ return spacing_y;
+ }
+
//static
bool Body::string_find_case_insensitive(const std::string &str, const std::string &substr) {
auto it = std::search(str.begin(), str.end(), substr.begin(), substr.end(),
diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp
index 8a7cbf6..c3e61fe 100644
--- a/src/QuickMedia.cpp
+++ b/src/QuickMedia.cpp
@@ -678,7 +678,7 @@ namespace QuickMedia {
if(!timestamp.isNumeric())
continue;
- auto body_item = std::make_unique<BodyItem>(std::move(title_str));
+ auto body_item = BodyItem::create(std::move(title_str));
body_item->url = "https://www.youtube.com/watch?v=" + video_id_str;
body_item->thumbnail_url = "https://img.youtube.com/vi/" + video_id_str + "/hqdefault.jpg";
body_item->set_description(timestamp_to_relative_time_str(std::max(0l, time_now - timestamp.asInt64())));
@@ -746,7 +746,7 @@ namespace QuickMedia {
if(!recommended_title_json.isString())
continue;
- auto body_item = std::make_unique<BodyItem>(recommended_title_json.asString());
+ auto body_item = BodyItem::create(recommended_title_json.asString());
body_item->url = "https://www.youtube.com/watch?v=" + recommended_item_id;
body_item->thumbnail_url = "https://img.youtube.com/vi/" + recommended_item_id + "/hqdefault.jpg";
body_items.push_back(std::move(body_item));
@@ -837,7 +837,7 @@ namespace QuickMedia {
const Json::Value &manga_name = body["name"];
if(!filename.empty() && manga_name.isString()) {
// TODO: Add thumbnail
- auto body_item = std::make_unique<BodyItem>(manga_name.asString());
+ auto body_item = BodyItem::create(manga_name.asString());
if(plugin->name == "manganelo")
body_item->url = "https://manganelo.com/manga/" + base64_decode(filename.string());
else if(plugin->name == "mangadex")
@@ -3343,14 +3343,23 @@ namespace QuickMedia {
// get_all_room_messages is not needed here because its done in the loop, where the initial timeout is 0ms
+ enum class ChatState {
+ NAVIGATING,
+ TYPING_MESSAGE,
+ REPLYING
+ };
+
Page new_page = Page::CHAT;
- bool typing_message = false;
+ ChatState chat_state = ChatState::NAVIGATING;
+
+ std::shared_ptr<BodyItem> replying_to_message;
+ sf::Text replying_to_text("Replying to:", *font, 18);
sf::Sprite logo_sprite(plugin_logo);
Entry chat_input("Press ctrl+m to begin writing a message...", font.get(), cjk_font.get());
chat_input.set_editable(false);
- chat_input.on_submit_callback = [matrix, &chat_input, &tabs, &selected_tab, &current_room_id, &new_page, &typing_message](const sf::String &text) {
+ chat_input.on_submit_callback = [matrix, &chat_input, &tabs, &selected_tab, &current_room_id, &new_page, &chat_state, &replying_to_message](const sf::String &text) mutable {
if(tabs[selected_tab].type == ChatTabType::MESSAGES) {
if(text.isEmpty())
return false;
@@ -3361,12 +3370,12 @@ namespace QuickMedia {
if(command == "/upload") {
new_page = Page::FILE_MANAGER;
chat_input.set_editable(false);
- typing_message = false;
+ chat_state = ChatState::NAVIGATING;
return true;
} else if(command == "/logout") {
new_page = Page::CHAT_LOGIN;
chat_input.set_editable(false);
- typing_message = false;
+ chat_state = ChatState::NAVIGATING;
return true;
} else {
fprintf(stderr, "Error: invalid command: %s, expected /upload\n", command.c_str());
@@ -3374,14 +3383,29 @@ namespace QuickMedia {
}
}
- // TODO: Make asynchronous
- if(matrix->post_message(current_room_id, text) == PluginResult::OK) {
- chat_input.set_editable(false);
- typing_message = false;
- return true;
- } else {
- show_notification("QuickMedia", "Failed to post matrix message", Urgency::CRITICAL);
- return false;
+ tabs[selected_tab].body->select_last_item();
+
+ if(chat_state == ChatState::TYPING_MESSAGE) {
+ // TODO: Make asynchronous
+ if(matrix->post_message(current_room_id, text) == PluginResult::OK) {
+ chat_input.set_editable(false);
+ chat_state = ChatState::NAVIGATING;
+ return true;
+ } else {
+ show_notification("QuickMedia", "Failed to post matrix message", Urgency::CRITICAL);
+ return false;
+ }
+ } else if(chat_state == ChatState::REPLYING) {
+ // TODO: Make asynchronous
+ if(matrix->post_reply(current_room_id, text, replying_to_message->userdata) == PluginResult::OK) {
+ chat_input.set_editable(false);
+ chat_state = ChatState::NAVIGATING;
+ replying_to_message = nullptr;
+ return true;
+ } else {
+ show_notification("QuickMedia", "Failed to post matrix reply", Urgency::CRITICAL);
+ return false;
+ }
}
}
return false;
@@ -3437,6 +3461,7 @@ namespace QuickMedia {
sf::Clock frame_timer;
float prev_chat_height = chat_input.get_height();
+ float chat_input_height_full = 0.0f;
while (current_page == Page::CHAT) {
sf::Int32 frame_time_ms = frame_timer.restart().asMilliseconds();
@@ -3444,7 +3469,7 @@ namespace QuickMedia {
base_event_handler(event, Page::EXIT, false, false, false);
if(event.type == sf::Event::Resized || event.type == sf::Event::GainedFocus) {
redraw = true;
- } else if(event.type == sf::Event::KeyPressed && !typing_message) {
+ } else if(event.type == sf::Event::KeyPressed && chat_state == ChatState::NAVIGATING) {
if(event.key.code == sf::Keyboard::Up || event.key.code == sf::Keyboard::PageUp || event.key.code == sf::Keyboard::Home) {
bool hit_top = false;
switch(event.key.code) {
@@ -3516,15 +3541,25 @@ namespace QuickMedia {
current_page = Page::VIDEO_CONTENT;
video_content_page();
redraw = true;
+ } else if(tabs[selected_tab].type == ChatTabType::MESSAGES && event.key.control && event.key.code == sf::Keyboard::R) {
+ std::shared_ptr<BodyItem> selected = tabs[selected_tab].body->get_selected_shared();
+ if(selected) {
+ chat_state = ChatState::REPLYING;
+ replying_to_message = selected;
+ chat_input.set_editable(true);
+ } else {
+ // TODO: Show inline notification
+ show_notification("QuickMedia", "No message selected for replying");
+ }
}
}
- if(!typing_message && tabs[selected_tab].type == ChatTabType::MESSAGES && event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::M && event.key.control) {
+ if(chat_state == ChatState::NAVIGATING && tabs[selected_tab].type == ChatTabType::MESSAGES && event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::M && event.key.control) {
chat_input.set_editable(true);
- typing_message = true;
+ chat_state = ChatState::TYPING_MESSAGE;
}
- if(typing_message && tabs[selected_tab].type == ChatTabType::MESSAGES) {
+ if((chat_state == ChatState::TYPING_MESSAGE || chat_state == ChatState::REPLYING) && tabs[selected_tab].type == ChatTabType::MESSAGES) {
if(event.type == sf::Event::TextEntered) {
//chat_input.onTextEntered(event.text.unicode);
// TODO: Also show typing event when ctrl+v pasting?
@@ -3538,7 +3573,8 @@ namespace QuickMedia {
}
} else if(event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Escape) {
chat_input.set_editable(false);
- typing_message = false;
+ chat_state = ChatState::NAVIGATING;
+ replying_to_message = nullptr;
}
//chat_input.on_event(event);
chat_input.process_event(event);
@@ -3649,7 +3685,8 @@ namespace QuickMedia {
body_padding_horizontal = 0.0f;
}
- chat_input_shade.setSize(sf::Vector2f(window_size.x, chat_input.get_height() + chat_input_padding_y * 2.0f));
+ chat_input_height_full = chat_input.get_height() + chat_input_padding_y * 2.0f;
+ chat_input_shade.setSize(sf::Vector2f(window_size.x, chat_input_height_full));
chat_input_shade.setPosition(0.0f, window_size.y - chat_input_shade.getSize().y);
body_pos = sf::Vector2f(body_padding_horizontal, body_padding_vertical + tab_shade_height);
@@ -3675,7 +3712,7 @@ namespace QuickMedia {
BodyItems result_items;
if(matrix->sync() == PluginResult::OK) {
fprintf(stderr, "Synced matrix\n");
- if(matrix->get_new_room_messages(sync_future_room_id, result_items) == PluginResult::OK) {
+ if(matrix->get_new_room_messages(sync_future_room_id, result_items) != PluginResult::OK) {
fprintf(stderr, "Failed to get new matrix messages in room: %s\n", sync_future_room_id.c_str());
}
} else {
@@ -3691,7 +3728,7 @@ namespace QuickMedia {
// Ignore finished sync if it happened in another room. When we navigate back to the room we will get the messages again
if(sync_future_room_id == current_room_id) {
int num_items = tabs[MESSAGES_TAB_INDEX].body->items.size();
- bool scroll_to_end = (num_items > 0 && tabs[MESSAGES_TAB_INDEX].body->get_selected_item() == num_items - 1);
+ bool scroll_to_end = (num_items == 0 || (num_items > 0 && tabs[MESSAGES_TAB_INDEX].body->get_selected_item() == num_items - 1));
tabs[MESSAGES_TAB_INDEX].body->append_items(std::move(new_body_items));
if(scroll_to_end)
tabs[MESSAGES_TAB_INDEX].body->select_last_item();
@@ -3762,6 +3799,31 @@ namespace QuickMedia {
window.draw(gradient_points, 4, sf::Quads); // Note: sf::Quads doesn't work with egl
}
+ if(chat_state == ChatState::REPLYING) {
+ const float margin = 5.0f;
+ const float replying_to_text_height = replying_to_text.getLocalBounds().height + margin;
+
+ const float item_height = std::min(body_size.y - replying_to_text_height - margin, tabs[MESSAGES_TAB_INDEX].body->get_item_height(replying_to_message.get()) + margin);
+
+ sf::RectangleShape overlay(sf::Vector2f(window_size.x, window_size.y - tab_shade_height - chat_input_height_full));
+ overlay.setPosition(0.0f, tab_shade_height);
+ overlay.setFillColor(sf::Color(0, 0, 0, 240));
+ window.draw(overlay);
+
+ sf::Vector2f body_item_pos(body_pos.x, window_size.y - chat_input_height_full - item_height);
+ sf::Vector2f body_item_size(body_size.x, item_height);
+
+ sf::RectangleShape item_background(sf::Vector2f(window_size.x, body_item_size.y + replying_to_text_height + margin));
+ item_background.setPosition(sf::Vector2f(0.0f, body_item_pos.y - replying_to_text_height - margin));
+ item_background.setFillColor(back_color);
+ window.draw(item_background);
+
+ replying_to_text.setPosition(body_item_pos.x, body_item_pos.y - replying_to_text_height);
+ window.draw(replying_to_text);
+
+ tabs[MESSAGES_TAB_INDEX].body->draw_item(window, replying_to_message.get(), body_item_pos, body_item_size);
+ }
+
if(tabs[selected_tab].type == ChatTabType::MESSAGES && !tabs[selected_tab].body->is_last_item_fully_visible()) {
window.draw(more_messages_below_rect);
}
diff --git a/src/plugins/Dmenu.cpp b/src/plugins/Dmenu.cpp
index 5c46841..9a8b5b8 100644
--- a/src/plugins/Dmenu.cpp
+++ b/src/plugins/Dmenu.cpp
@@ -11,7 +11,7 @@ namespace QuickMedia {
PluginResult Dmenu::get_front_page(BodyItems &result_items) {
for(const std::string &line_data : stdin_data) {
- result_items.push_back(std::make_unique<BodyItem>(line_data));
+ result_items.push_back(BodyItem::create(line_data));
}
return PluginResult::OK;
}
diff --git a/src/plugins/FileManager.cpp b/src/plugins/FileManager.cpp
index c68bb94..d8a0612 100644
--- a/src/plugins/FileManager.cpp
+++ b/src/plugins/FileManager.cpp
@@ -42,7 +42,7 @@ namespace QuickMedia {
});
for(auto &p : paths) {
- auto body_item = std::make_unique<BodyItem>(p.path().filename().string());
+ auto body_item = BodyItem::create(p.path().filename().string());
// TODO: Check file magic number instead of extension?
if(p.is_regular_file() && is_image_ext(get_ext(p.path()))) {
body_item->thumbnail_is_local = true;
diff --git a/src/plugins/Fourchan.cpp b/src/plugins/Fourchan.cpp
index 5da2961..fa5e9e3 100644
--- a/src/plugins/Fourchan.cpp
+++ b/src/plugins/Fourchan.cpp
@@ -105,7 +105,7 @@ namespace QuickMedia {
if(board_id.isString() && board_title.isString() && board_description.isString()) {
std::string board_description_str = board_description.asString();
html_unescape_sequences(board_description_str);
- auto body_item = std::make_unique<BodyItem>("/" + board_id.asString() + "/ " + board_title.asString());
+ auto body_item = BodyItem::create("/" + board_id.asString() + "/ " + board_title.asString());
body_item->url = board_id.asString();
result_items.emplace_back(std::move(body_item));
}
@@ -320,7 +320,7 @@ namespace QuickMedia {
}
}
}
- auto body_item = std::make_unique<BodyItem>(std::move(comment_text));
+ auto body_item = BodyItem::create(std::move(comment_text));
body_item->url = std::to_string(thread_num.asInt64());
const Json::Value &ext = thread["ext"];
@@ -382,7 +382,7 @@ namespace QuickMedia {
std::lock_guard<std::mutex> lock(board_list_mutex);
BodyItems body_items;
for(auto &cached_body_item : cached_thread_list_items) {
- body_items.push_back(std::make_unique<BodyItem>(*cached_body_item));
+ body_items.push_back(std::make_shared<BodyItem>(*cached_body_item));
}
return body_items;
}
@@ -432,7 +432,7 @@ namespace QuickMedia {
int64_t post_num_int = post_num.asInt64();
comment_by_postno[post_num_int] = result_items.size();
- result_items.push_back(std::make_unique<BodyItem>(""));
+ result_items.push_back(BodyItem::create(""));
result_items.back()->post_number = std::to_string(post_num_int);
}
}
@@ -602,7 +602,7 @@ namespace QuickMedia {
++it;
for(; it != cached_media_urls.end(); ++it) {
- auto body_item = std::make_unique<BodyItem>("");
+ auto body_item = BodyItem::create("");
body_item->url = *it;
body_items.push_back(std::move(body_item));
}
diff --git a/src/plugins/Mangadex.cpp b/src/plugins/Mangadex.cpp
index fcbd23e..e9487bc 100644
--- a/src/plugins/Mangadex.cpp
+++ b/src/plugins/Mangadex.cpp
@@ -115,7 +115,7 @@ namespace QuickMedia {
if(chapter_title_json.isString() && strlen(chapter_title_json.asCString()) > 0)
chapter_name += std::string(" - ") + chapter_title_json.asCString();
- auto item = std::make_unique<BodyItem>(std::move(chapter_name));
+ auto item = BodyItem::create(std::move(chapter_name));
item->url = std::move(chapter_url);
result_items.push_back(std::move(item));
}
@@ -194,7 +194,7 @@ namespace QuickMedia {
const char *href = quickmedia_html_node_get_attribute_value(node, "href");
const char *title = quickmedia_html_node_get_attribute_value(node, "title");
if(title && href && strncmp(href, "/title/", 7) == 0) {
- auto item = std::make_unique<BodyItem>(strip(title));
+ auto item = BodyItem::create(strip(title));
item->url = mangadex_url + href;
item_data->push_back(std::move(item));
}
diff --git a/src/plugins/Manganelo.cpp b/src/plugins/Manganelo.cpp
index 6363991..5be64ca 100644
--- a/src/plugins/Manganelo.cpp
+++ b/src/plugins/Manganelo.cpp
@@ -27,7 +27,7 @@ namespace QuickMedia {
const char *href = quickmedia_html_node_get_attribute_value(node, "href");
const char *text = quickmedia_html_node_get_text(node);
if(href && text) {
- auto item = std::make_unique<BodyItem>(strip(text));
+ auto item = BodyItem::create(strip(text));
item->url = href;
item_data->push_back(std::move(item));
}
@@ -101,7 +101,7 @@ namespace QuickMedia {
if(name.isString() && name.asCString()[0] != '\0' && nameunsigned.isString() && nameunsigned.asCString()[0] != '\0') {
std::string name_str = name.asString();
while(remove_html_span(name_str)) {}
- auto item = std::make_unique<BodyItem>(strip(name_str));
+ auto item = BodyItem::create(strip(name_str));
item->url = "https://manganelo.com/manga/" + url_param_encode(nameunsigned.asString());
Json::Value image = child.get("image", "");
if(image.isString() && image.asCString()[0] != '\0')
@@ -228,7 +228,7 @@ namespace QuickMedia {
const char *href = quickmedia_html_node_get_attribute_value(node, "href");
const char *title = quickmedia_html_node_get_attribute_value(node, "title");
if(href && title && strstr(href, "/manga/")) {
- auto body_item = std::make_unique<BodyItem>(title);
+ auto body_item = BodyItem::create(title);
body_item->url = href;
item_data->push_back(std::move(body_item));
}
diff --git a/src/plugins/Mangatown.cpp b/src/plugins/Mangatown.cpp
index 67f4c29..0964310 100644
--- a/src/plugins/Mangatown.cpp
+++ b/src/plugins/Mangatown.cpp
@@ -22,7 +22,7 @@ namespace QuickMedia {
const char *href = quickmedia_html_node_get_attribute_value(node, "href");
const char *text = quickmedia_html_node_get_text(node);
if(href && text && strncmp(href, "/manga/", 7) == 0) {
- auto item = std::make_unique<BodyItem>(strip(text));
+ auto item = BodyItem::create(strip(text));
item->url = mangatown_url + href;
item_data->push_back(std::move(item));
}
@@ -76,10 +76,10 @@ namespace QuickMedia {
for(const Json::Value &child : json_suggestions) {
if(!child.isString()) {
- result_items.push_back(std::make_unique<BodyItem>(""));
+ result_items.push_back(BodyItem::create(""));
continue;
}
- result_items.push_back(std::make_unique<BodyItem>(child.asString()));
+ result_items.push_back(BodyItem::create(child.asString()));
}
size_t index = 0;
@@ -88,7 +88,7 @@ namespace QuickMedia {
if(index < result_items.size()) {
body_item = result_items[index].get();
} else {
- result_items.push_back(std::make_unique<BodyItem>(""));
+ result_items.push_back(BodyItem::create(""));
body_item = result_items.back().get();
}
@@ -123,7 +123,7 @@ namespace QuickMedia {
const char *href = quickmedia_html_node_get_attribute_value(node, "href");
const char *title = quickmedia_html_node_get_attribute_value(node, "title");
if(href && title && strncmp(href, "/manga/", 7) == 0) {
- auto item = std::make_unique<BodyItem>(strip(title));
+ auto item = BodyItem::create(strip(title));
item->url = mangatown_url + href;
item_data->push_back(std::move(item));
}
diff --git a/src/plugins/Matrix.cpp b/src/plugins/Matrix.cpp
index 513a9fb..50be2de 100644
--- a/src/plugins/Matrix.cpp
+++ b/src/plugins/Matrix.cpp
@@ -150,7 +150,7 @@ namespace QuickMedia {
avatar_url = room_it->second->avatar_url;
}
- auto body_item = std::make_unique<BodyItem>(std::move(room_name));
+ auto body_item = BodyItem::create(std::move(room_name));
body_item->url = room_id_str;
body_item->thumbnail_url = std::move(avatar_url);
result_items.push_back(std::move(body_item));
@@ -159,21 +159,22 @@ namespace QuickMedia {
return PluginResult::OK;
}
- static void room_messages_to_body_items(RoomData *room_data, Message *messages, size_t num_messages, BodyItems &result_items) {
+ static void room_messages_to_body_items(RoomData *room_data, std::shared_ptr<Message> *messages, size_t num_messages, BodyItems &result_items) {
for(size_t i = 0; i < num_messages; ++i) {
- const UserInfo &user_info = room_data->user_info[messages[i].user_id];
- auto body_item = std::make_unique<BodyItem>("");
+ const UserInfo &user_info = room_data->user_info[messages[i]->user_id];
+ auto body_item = BodyItem::create("");
body_item->set_author(user_info.display_name);
- body_item->set_description(messages[i].body);
- if(!messages[i].thumbnail_url.empty())
- body_item->thumbnail_url = messages[i].thumbnail_url;
- else if(!messages[i].url.empty() && messages->type == MessageType::IMAGE)
- body_item->thumbnail_url = messages[i].url;
+ body_item->set_description(messages[i]->body);
+ if(!messages[i]->thumbnail_url.empty())
+ body_item->thumbnail_url = messages[i]->thumbnail_url;
+ else if(!messages[i]->url.empty() && messages[i]->type == MessageType::IMAGE)
+ body_item->thumbnail_url = messages[i]->url;
else
body_item->thumbnail_url = user_info.avatar_url;
// TODO: Show image thumbnail inline instead of url to image and showing it as the thumbnail of the body item
- body_item->url = messages[i].url;
+ body_item->url = messages[i]->url;
body_item->author_color = user_info.display_name_color;
+ body_item->userdata = (void*)messages[i].get(); // Note: messages[i] has to be valid as long as body_item is used!
result_items.push_back(std::move(body_item));
}
}
@@ -368,6 +369,7 @@ namespace QuickMedia {
continue;
UserInfo user_info;
+ user_info.user_id = sender_json_str;
user_info.avatar_url = avatar_url_json.asString();
if(strncmp(user_info.avatar_url.c_str(), "mxc://", 6) == 0)
user_info.avatar_url.erase(user_info.avatar_url.begin(), user_info.avatar_url.begin() + 6);
@@ -375,7 +377,7 @@ namespace QuickMedia {
user_info.avatar_url = homeserver + "/_matrix/media/r0/thumbnail/" + user_info.avatar_url + "?width=32&height=32&method=crop";
user_info.display_name = display_name_json.asString();
user_info.display_name_color = user_id_to_color(sender_json_str);
- room_data->user_info.push_back(std::move(user_info));
+ room_data->user_info.push_back(user_info);
room_data->user_info_by_user_id.insert(std::make_pair(sender_json_str, room_data->user_info.size() - 1));
}
}
@@ -399,7 +401,7 @@ namespace QuickMedia {
if(!events_json.isArray())
return;
- std::vector<Message> new_messages;
+ std::vector<std::shared_ptr<Message>> new_messages;
for(const Json::Value &event_item_json : events_json) {
if(!event_item_json.isObject())
@@ -414,6 +416,12 @@ namespace QuickMedia {
continue;
std::string sender_json_str = sender_json.asString();
+
+ const Json::Value &event_id_json = event_item_json["event_id"];
+ if(!event_id_json.isString())
+ continue;
+
+ std::string event_id_str = event_id_json.asString();
const Json::Value &content_json = event_item_json["content"];
if(!content_json.isObject())
@@ -425,6 +433,7 @@ namespace QuickMedia {
auto user_it = room_data->user_info_by_user_id.find(sender_json_str);
if(user_it == room_data->user_info_by_user_id.end()) {
+ // Note: this is important because otherwise replying and such is broken
fprintf(stderr, "Warning: skipping unknown user: %s\n", sender_json_str.c_str());
continue;
}
@@ -433,36 +442,52 @@ namespace QuickMedia {
if(!body_json.isString())
continue;
+ bool is_reply = false;
+ const Json::Value &relates_to_json = content_json["m.relates_to"];
+ if(relates_to_json.isObject()) {
+ const Json::Value &in_reply_to_json = relates_to_json["m.in_reply_to"];
+ is_reply = in_reply_to_json.isObject();
+ }
+
if(strcmp(content_type.asCString(), "m.text") == 0) {
- Message message;
- message.user_id = user_it->second;
- message.body = body_json.asString();
- message.type = MessageType::TEXT;
- new_messages.push_back(std::move(message));
+ auto message = std::make_shared<Message>();
+ message->user_id = user_it->second;
+ message->event_id = event_id_str;
+ message->body = body_json.asString();
+ message->type = MessageType::TEXT;
+ message->is_reply = is_reply;
+ new_messages.push_back(message);
+ room_data->message_by_event_id[event_id_str] = message;
} else if(strcmp(content_type.asCString(), "m.image") == 0) {
const Json::Value &url_json = content_json["url"];
if(!url_json.isString() || strncmp(url_json.asCString(), "mxc://", 6) != 0)
continue;
- Message message;
- message.user_id = user_it->second;
- message.body = body_json.asString();
- message.url = homeserver + "/_matrix/media/r0/download/" + url_json.asString().substr(6);
- message.thumbnail_url = message_content_extract_thumbnail_url(content_json, homeserver);
- message.type = MessageType::IMAGE;
- new_messages.push_back(std::move(message));
+ auto message = std::make_shared<Message>();
+ message->user_id = user_it->second;
+ message->event_id = event_id_str;
+ message->body = body_json.asString();
+ message->url = homeserver + "/_matrix/media/r0/download/" + url_json.asString().substr(6);
+ message->thumbnail_url = message_content_extract_thumbnail_url(content_json, homeserver);
+ message->type = MessageType::IMAGE;
+ message->is_reply = is_reply;
+ new_messages.push_back(message);
+ room_data->message_by_event_id[event_id_str] = message;
} else if(strcmp(content_type.asCString(), "m.video") == 0) {
const Json::Value &url_json = content_json["url"];
if(!url_json.isString() || strncmp(url_json.asCString(), "mxc://", 6) != 0)
continue;
- Message message;
- message.user_id = user_it->second;
- message.body = body_json.asString();
- message.url = homeserver + "/_matrix/media/r0/download/" + url_json.asString().substr(6);
- message.thumbnail_url = message_content_extract_thumbnail_url(content_json, homeserver);
- message.type = MessageType::VIDEO;
- new_messages.push_back(std::move(message));
+ auto message = std::make_shared<Message>();
+ message->event_id = event_id_str;
+ message->user_id = user_it->second;
+ message->body = body_json.asString();
+ message->url = homeserver + "/_matrix/media/r0/download/" + url_json.asString().substr(6);
+ message->thumbnail_url = message_content_extract_thumbnail_url(content_json, homeserver);
+ message->type = MessageType::VIDEO;
+ message->is_reply = is_reply;
+ new_messages.push_back(message);
+ room_data->message_by_event_id[event_id_str] = message;
}
}
@@ -746,6 +771,91 @@ namespace QuickMedia {
return PluginResult::OK;
}
+ static std::string create_body_for_message_reply(const RoomData *room_data, const Message *message, const std::string &body) {
+ std::string related_to_body;
+ switch(message->type) {
+ case MessageType::TEXT:
+ related_to_body = message->body;
+ break;
+ case MessageType::IMAGE:
+ related_to_body = "sent an image";
+ break;
+ case MessageType::VIDEO:
+ related_to_body = "sent a video";
+ break;
+ }
+ return "> <" + room_data->user_info[message->user_id].user_id + "> " + std::move(related_to_body) + "\n\n" + body;
+ }
+
+ // TODO: Add formatted_body just like element does with <mx-reply><blockquote... and also support greentext with that
+ PluginResult Matrix::post_reply(const std::string &room_id, const std::string &body, void *relates_to) {
+ auto room_it = room_data_by_id.find(room_id);
+ if(room_it == room_data_by_id.end()) {
+ fprintf(stderr, "Error: no such room: %s\n", room_id.c_str());
+ return PluginResult::ERR;
+ }
+
+ Message *relates_to_message = (Message*)relates_to;
+
+ char random_characters[18];
+ if(!generate_random_characters(random_characters, sizeof(random_characters)))
+ return PluginResult::ERR;
+
+ std::string random_readable_chars = random_characters_to_readable_string(random_characters, sizeof(random_characters));
+
+ Json::Value in_reply_to_json(Json::objectValue);
+ in_reply_to_json["event_id"] = relates_to_message->event_id;
+
+ Json::Value relates_to_json(Json::objectValue);
+ relates_to_json["m.in_reply_to"] = std::move(in_reply_to_json);
+
+ Json::Value request_data(Json::objectValue);
+ request_data["msgtype"] = message_type_to_request_msg_type_str(MessageType::TEXT);
+ request_data["body"] = create_body_for_message_reply(room_it->second.get(), relates_to_message, body);
+ request_data["m.relates_to"] = std::move(relates_to_json);
+
+ Json::StreamWriterBuilder builder;
+ builder["commentStyle"] = "None";
+ builder["indentation"] = "";
+
+ std::vector<CommandArg> additional_args = {
+ { "-X", "PUT" },
+ { "-H", "content-type: application/json" },
+ { "-H", "Authorization: Bearer " + access_token },
+ { "--data-binary", Json::writeString(builder, std::move(request_data)) }
+ };
+
+ char request_url[512];
+ snprintf(request_url, sizeof(request_url), "%s/_matrix/client/r0/rooms/%s/send/m.room.message/m%ld.%.*s", homeserver.c_str(), room_id.c_str(), time(NULL), (int)random_readable_chars.size(), random_readable_chars.c_str());
+ fprintf(stderr, "Post message to |%s|\n", request_url);
+
+ std::string server_response;
+ if(download_to_string(request_url, server_response, std::move(additional_args), use_tor, true) != DownloadResult::OK)
+ return PluginResult::NET_ERR;
+
+ if(server_response.empty())
+ return PluginResult::ERR;
+
+ Json::Value json_root;
+ Json::CharReaderBuilder json_builder;
+ std::unique_ptr<Json::CharReader> json_reader(json_builder.newCharReader());
+ std::string json_errors;
+ if(!json_reader->parse(&server_response[0], &server_response[server_response.size()], &json_root, &json_errors)) {
+ fprintf(stderr, "Matrix post message response parse error: %s\n", json_errors.c_str());
+ return PluginResult::ERR;
+ }
+
+ if(!json_root.isObject())
+ return PluginResult::ERR;
+
+ const Json::Value &event_id_json = json_root["event_id"];
+ if(!event_id_json.isString())
+ return PluginResult::ERR;
+
+ fprintf(stderr, "Matrix post reply, response event id: %s\n", event_id_json.asCString());
+ return PluginResult::OK;
+ }
+
// Returns empty string on error
static const char* file_get_filename(const std::string &filepath) {
size_t index = filepath.rfind('/');
diff --git a/src/plugins/NyaaSi.cpp b/src/plugins/NyaaSi.cpp
index 04a71f2..186852b 100644
--- a/src/plugins/NyaaSi.cpp
+++ b/src/plugins/NyaaSi.cpp
@@ -26,8 +26,8 @@ namespace QuickMedia {
}
- static std::unique_ptr<BodyItem> create_front_page_item(const std::string &title, const std::string &category) {
- auto body_item = std::make_unique<BodyItem>(title);
+ static std::shared_ptr<BodyItem> create_front_page_item(const std::string &title, const std::string &category) {
+ auto body_item = BodyItem::create(title);
body_item->url = category;
return body_item;
}
@@ -120,7 +120,7 @@ namespace QuickMedia {
return SearchResult::ERR;
}
- auto body_item = std::make_unique<BodyItem>(std::move(title));
+ auto body_item = BodyItem::create(std::move(title));
body_item->url = std::move(link);
body_item->thumbnail_url = "https://nyaa.si/static/img/icons/nyaa/" + category_id + ".png";
body_item->set_description("Published: " + pub_date + "\nSeeders: " + seeders + "\nLeechers: " + leechers + "\nDownloads: " + downloads + "\nSize: " + size + "\nComments: " + comments);
@@ -171,9 +171,9 @@ namespace QuickMedia {
// }
// std::string torrent_url = "https://nyaa.si/download/" + id + ".torrent";
- // auto torrent_item = std::make_unique<BodyItem>("Download torrent");
+ // auto torrent_item = BodyItem::create("Download torrent");
// torrent_item->url = "https://nyaa.si/download/" + id + ".torrent";
- auto torrent_item = std::make_unique<BodyItem>("Download magnet");
+ auto torrent_item = BodyItem::create("Download magnet");
std::string magnet_url;
std::string description;
@@ -192,7 +192,7 @@ namespace QuickMedia {
const char *href = quickmedia_html_node_get_attribute_value(node, "href");
const char *text = quickmedia_html_node_get_text(node);
if(item_data->empty() && href && text && strncmp(href, "/user/", 6) == 0) {
- auto body_item = std::make_unique<BodyItem>("Submitter: " + strip(text));
+ auto body_item = BodyItem::create("Submitter: " + strip(text));
body_item->url = "https://nyaa.si/" + std::string(href);
item_data->push_back(std::move(body_item));
}
@@ -250,7 +250,7 @@ namespace QuickMedia {
const char *href = quickmedia_html_node_get_attribute_value(node, "href");
const char *text = quickmedia_html_node_get_text(node);
if(href && text && strncmp(href, "/user/", 6) == 0) {
- auto body_item = std::make_unique<BodyItem>(strip(text));
+ auto body_item = BodyItem::create(strip(text));
//body_item->url = "https://nyaa.si/" + std::string(href);
item_data->push_back(std::move(body_item));
}
diff --git a/src/plugins/Pornhub.cpp b/src/plugins/Pornhub.cpp
index 9a7242a..b093c64 100644
--- a/src/plugins/Pornhub.cpp
+++ b/src/plugins/Pornhub.cpp
@@ -38,7 +38,7 @@ namespace QuickMedia {
const char *href = quickmedia_html_node_get_attribute_value(node, "href");
const char *title = quickmedia_html_node_get_attribute_value(node, "title");
if(href && title && begins_with(href, "/view_video.php?viewkey")) {
- auto item = std::make_unique<BodyItem>(strip(title));
+ auto item = BodyItem::create(strip(title));
item->url = std::string("https://www.pornhub.com") + href;
result_items->push_back(std::move(item));
}
@@ -93,7 +93,7 @@ namespace QuickMedia {
const char *href = quickmedia_html_node_get_attribute_value(node, "href");
const char *title = quickmedia_html_node_get_attribute_value(node, "title");
if(href && title && begins_with(href, "/view_video.php?viewkey")) {
- auto item = std::make_unique<BodyItem>(strip(title));
+ auto item = BodyItem::create(strip(title));
item->url = std::string("https://www.pornhub.com") + href;
result_items->push_back(std::move(item));
}
diff --git a/src/plugins/Youtube.cpp b/src/plugins/Youtube.cpp
index 2827d83..4126532 100644
--- a/src/plugins/Youtube.cpp
+++ b/src/plugins/Youtube.cpp
@@ -17,7 +17,7 @@ namespace QuickMedia {
}
}
- static std::unique_ptr<BodyItem> parse_content_video_renderer(const Json::Value &content_item_json, std::unordered_set<std::string> &added_videos) {
+ static std::shared_ptr<BodyItem> parse_content_video_renderer(const Json::Value &content_item_json, std::unordered_set<std::string> &added_videos) {
if(!content_item_json.isObject())
return nullptr;
@@ -68,7 +68,7 @@ namespace QuickMedia {
if(!title)
return nullptr;
- auto body_item = std::make_unique<BodyItem>(title);
+ auto body_item = BodyItem::create(title);
/* TODO: Make date a different color */
std::string date_str;
if(date)
@@ -170,7 +170,7 @@ namespace QuickMedia {
continue;
const Json::Value &rich_item_contents = rich_item_renderer_json["content"];
- std::unique_ptr<BodyItem> body_item = parse_content_video_renderer(rich_item_contents, added_videos);
+ std::shared_ptr<BodyItem> body_item = parse_content_video_renderer(rich_item_contents, added_videos);
if(body_item)
result_items.push_back(std::move(body_item));
}
@@ -273,14 +273,14 @@ namespace QuickMedia {
continue;
for(const Json::Value &item_json : items_json) {
- std::unique_ptr<BodyItem> body_item = parse_content_video_renderer(item_json, added_videos);
+ std::shared_ptr<BodyItem> body_item = parse_content_video_renderer(item_json, added_videos);
if(body_item)
result_items.push_back(std::move(body_item));
}
}
for(const Json::Value &content_item_json : item_contents_json) {
- std::unique_ptr<BodyItem> body_item = parse_content_video_renderer(content_item_json, added_videos);
+ std::shared_ptr<BodyItem> body_item = parse_content_video_renderer(content_item_json, added_videos);
if(body_item)
result_items.push_back(std::move(body_item));
}
@@ -444,7 +444,7 @@ namespace QuickMedia {
return result.substr(0, index);
}
- static std::unique_ptr<BodyItem> parse_compact_video_renderer_json(const Json::Value &item_json, std::unordered_set<std::string> &added_videos) {
+ static std::shared_ptr<BodyItem> parse_compact_video_renderer_json(const Json::Value &item_json, std::unordered_set<std::string> &added_videos) {
const Json::Value &compact_video_renderer_json = item_json["compactVideoRenderer"];
if(!compact_video_renderer_json.isObject())
return nullptr;
@@ -487,7 +487,7 @@ namespace QuickMedia {
if(!title)
return nullptr;
- auto body_item = std::make_unique<BodyItem>(title);
+ auto body_item = BodyItem::create(title);
/* TODO: Make date a different color */
std::string date_str;
if(date)