diff options
-rw-r--r-- | README.md | 7 | ||||
-rw-r--r-- | TODO | 4 | ||||
-rw-r--r-- | include/Entry.hpp | 2 | ||||
-rw-r--r-- | include/Text.hpp | 2 | ||||
-rw-r--r-- | plugins/Matrix.hpp | 10 | ||||
-rw-r--r-- | src/Body.cpp | 8 | ||||
-rw-r--r-- | src/Entry.cpp | 11 | ||||
-rw-r--r-- | src/QuickMedia.cpp | 57 | ||||
-rw-r--r-- | src/Text.cpp | 4 | ||||
-rw-r--r-- | src/plugins/Matrix.cpp | 271 |
10 files changed, 326 insertions, 50 deletions
@@ -38,17 +38,18 @@ Press `Ctrl + T` when hovering over a manga chapter to start tracking manga afte Press `Backspace` to return to the preview item when reading replies in image board threads.\ Press `R` to paste the post number of the selected post into the post field (image boards).\ Press `Ctrl + M` to begin writing a post to a thread (image boards), press `ESC` to cancel.\ -Press `Ctrl + M` to begin writing a message in a matrix room, press `ESC` to cancel.\ Press `1 to 9` or `Numpad 1 to 9` to select google captcha image when posting a comment on 4chan.\ Press `P` to preview the 4chan image of the selected row in full screen view, press `ESC` or `Backspace` to go back.\ Press `I` to switch between single image and scroll image view mode when reading manga.\ Press `Middle mouse button` to "autoscroll" in scrolling image view mode.\ Press `Tab` to autocomplete a search when autocomplete is available (currently only available for youtube).\ Press `Tab` to switch between username/password field in login panel.\ +Press `Ctrl + C` to copy the url of the currently playing video to the clipboard (with timestamp).\ 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 + R` to reply to a message on matrix, press `ESC` to cancel. +Press `Ctrl + M` to begin writing a message in a matrix room, press `ESC` to cancel.\ +Press `Ctrl + R` to reply to a message on matrix, press `ESC` to cancel.\ +Press `Ctrl + E` to edit a message on matrix, press `ESC` to cancel. Currently only works for your own messages.\ ## Matrix commands `/upload` to upload an image. TODO: Support regular files and videos.\ `/logout` to logout. @@ -51,4 +51,6 @@ Put rooms with recent messages at the top and the ones that mention us further a 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. -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 +Fix incorrect body visible rows count (draws incorrect number of items and incorrect counted, also messed up pg(up/down)). +Make editing others matrix room messages work if you have the privileges to do so. +Replace messages on matrix instead of appending edits (messages that begin with " * "). This also fixes edit of already edited messages.
\ No newline at end of file diff --git a/include/Entry.hpp b/include/Entry.hpp index 6f96e58..18a860c 100644 --- a/include/Entry.hpp +++ b/include/Entry.hpp @@ -22,8 +22,10 @@ namespace QuickMedia { void draw(sf::RenderWindow &window); void set_editable(bool editable); + void set_text(std::string text); void set_position(const sf::Vector2f &pos); void set_max_width(float width); + void move_caret_to_end(); float get_height(); diff --git a/include/Text.hpp b/include/Text.hpp index 0244ba1..59693a2 100644 --- a/include/Text.hpp +++ b/include/Text.hpp @@ -80,6 +80,8 @@ namespace QuickMedia void setCharacterSpacing(float characterSpacing); void setEditable(bool editable); bool isEditable() const; + // Note: only call after initial updateGeometry or draw. TODO: Automatically do this internally + void moveCaretToEnd(); // Note: won't update until @draw is called float getWidth() const; diff --git a/plugins/Matrix.hpp b/plugins/Matrix.hpp index 07c6f61..7a588e8 100644 --- a/plugins/Matrix.hpp +++ b/plugins/Matrix.hpp @@ -25,8 +25,8 @@ namespace QuickMedia { std::string body; std::string url; std::string thumbnail_url; + std::string replaces_event_id; MessageType type; - bool is_reply; }; struct MessageInfo { @@ -37,6 +37,7 @@ namespace QuickMedia { }; struct RoomData { + std::string id; // Each room has its own list of user data, even if multiple rooms has the same user // because users can have different display names and avatars in different rooms. // The value is an index to |user_info|. @@ -79,6 +80,8 @@ namespace QuickMedia { 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); + // |relates_to| is from |BodyItem.userdata| and is of type |Message*| + PluginResult post_edit(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); @@ -89,12 +92,17 @@ namespace QuickMedia { PluginResult on_start_typing(const std::string &room_id); PluginResult on_stop_typing(const std::string &room_id); + + // |message| is from |BodyItem.userdata| and is of type |Message*| + bool was_message_posted_by_me(const std::string &room_id, void *message) const; private: PluginResult sync_response_to_body_items(const Json::Value &root); PluginResult get_previous_room_messages(const std::string &room_id, RoomData *room_data); void events_add_user_info(const Json::Value &events_json, RoomData *room_data); void events_add_messages(const Json::Value &events_json, RoomData *room_data, MessageDirection message_dir); void events_set_room_name(const Json::Value &events_json, RoomData *room_data); + + std::shared_ptr<Message> get_edited_message_original_message(RoomData *room_data, std::shared_ptr<Message> message); private: std::unordered_map<std::string, std::unique_ptr<RoomData>> room_data_by_id; std::string user_id; diff --git a/src/Body.cpp b/src/Body.cpp index 5927831..13e3d7d 100644 --- a/src/Body.cpp +++ b/src/Body.cpp @@ -550,11 +550,11 @@ 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); + sf::Vector2u window_size = window.getSize(); + glEnable(GL_SCISSOR_TEST); + glScissor(pos.x, (int)window_size.y - (int)pos.y - (int)size.y, size.x, size.y); draw_item(window, item, pos, size, get_item_height(item) + spacing_y, -1, Json::nullValue); - //glDisable(GL_SCISSOR_TEST); + 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) { diff --git a/src/Entry.cpp b/src/Entry.cpp index 977feab..a3c576f 100644 --- a/src/Entry.cpp +++ b/src/Entry.cpp @@ -13,7 +13,7 @@ namespace QuickMedia { on_submit_callback(nullptr), text("", font, cjk_font, 18, 0.0f), width(0.0f), - background(sf::Vector2f(1.0f, 1.0f), 10.0f, 10), + background(sf::Vector2f(1.0f, 1.0f), 7.0f, 10), placeholder(placeholder_text, *font, 18) { text.setEditable(true); @@ -54,6 +54,15 @@ namespace QuickMedia { text.setEditable(editable); } + void Entry::set_text(std::string new_text) { + text.setString(std::move(new_text)); + } + + void Entry::move_caret_to_end() { + text.updateGeometry(); + text.moveCaretToEnd(); + } + void Entry::set_position(const sf::Vector2f &pos) { background.setPosition(pos); text.setPosition(pos + sf::Vector2f(background_margin_horizontal, background_margin_vertical)); diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index c3e61fe..ecb44d5 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -3346,20 +3346,21 @@ namespace QuickMedia { enum class ChatState { NAVIGATING, TYPING_MESSAGE, - REPLYING + REPLYING, + EDITING }; Page new_page = Page::CHAT; ChatState chat_state = ChatState::NAVIGATING; - std::shared_ptr<BodyItem> replying_to_message; + std::shared_ptr<BodyItem> currently_operating_on_item; 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, ¤t_room_id, &new_page, &chat_state, &replying_to_message](const sf::String &text) mutable { + chat_input.on_submit_callback = [matrix, &chat_input, &tabs, &selected_tab, ¤t_room_id, &new_page, &chat_state, ¤tly_operating_on_item](const sf::String &text) mutable { if(tabs[selected_tab].type == ChatTabType::MESSAGES) { if(text.isEmpty()) return false; @@ -3397,15 +3398,26 @@ namespace QuickMedia { } } else if(chat_state == ChatState::REPLYING) { // TODO: Make asynchronous - if(matrix->post_reply(current_room_id, text, replying_to_message->userdata) == PluginResult::OK) { + if(matrix->post_reply(current_room_id, text, currently_operating_on_item->userdata) == PluginResult::OK) { chat_input.set_editable(false); chat_state = ChatState::NAVIGATING; - replying_to_message = nullptr; + currently_operating_on_item = nullptr; return true; } else { show_notification("QuickMedia", "Failed to post matrix reply", Urgency::CRITICAL); return false; } + } else if(chat_state == ChatState::EDITING) { + // TODO: Make asynchronous + if(matrix->post_edit(current_room_id, text, currently_operating_on_item->userdata) == PluginResult::OK) { + chat_input.set_editable(false); + chat_state = ChatState::NAVIGATING; + currently_operating_on_item = nullptr; + return true; + } else { + show_notification("QuickMedia", "Failed to post matrix edit", Urgency::CRITICAL); + return false; + } } } return false; @@ -3545,12 +3557,34 @@ namespace QuickMedia { std::shared_ptr<BodyItem> selected = tabs[selected_tab].body->get_selected_shared(); if(selected) { chat_state = ChatState::REPLYING; - replying_to_message = selected; + currently_operating_on_item = selected; chat_input.set_editable(true); + replying_to_text.setString("Replying to:"); } else { // TODO: Show inline notification show_notification("QuickMedia", "No message selected for replying"); } + } else if(tabs[selected_tab].type == ChatTabType::MESSAGES && event.key.control && event.key.code == sf::Keyboard::E) { + std::shared_ptr<BodyItem> selected = tabs[selected_tab].body->get_selected_shared(); + if(selected) { + if(!selected->url.empty()) { // cant edit messages that are image/video posts + // TODO: Show inline notification + show_notification("QuickMedia", "You can only edit messages with no file attached to it"); + } else if(!matrix->was_message_posted_by_me(current_room_id, selected->userdata)) { + // TODO: Show inline notification + show_notification("QuickMedia", "You can't edit a message that was posted by somebody else"); + } else { + chat_state = ChatState::EDITING; + currently_operating_on_item = selected; + chat_input.set_editable(true); + chat_input.set_text(selected->get_description()); // TODO: Description? it may change in the future, in which case this should be edited + chat_input.move_caret_to_end(); + replying_to_text.setString("Editing message:"); + } + } else { + // TODO: Show inline notification + show_notification("QuickMedia", "No message selected for editing"); + } } } @@ -3559,7 +3593,7 @@ namespace QuickMedia { chat_state = ChatState::TYPING_MESSAGE; } - if((chat_state == ChatState::TYPING_MESSAGE || chat_state == ChatState::REPLYING) && tabs[selected_tab].type == ChatTabType::MESSAGES) { + if((chat_state == ChatState::TYPING_MESSAGE || chat_state == ChatState::REPLYING || chat_state == ChatState::EDITING) && 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? @@ -3573,8 +3607,9 @@ namespace QuickMedia { } } else if(event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Escape) { chat_input.set_editable(false); + chat_input.set_text(""); chat_state = ChatState::NAVIGATING; - replying_to_message = nullptr; + currently_operating_on_item = nullptr; } //chat_input.on_event(event); chat_input.process_event(event); @@ -3799,11 +3834,11 @@ namespace QuickMedia { window.draw(gradient_points, 4, sf::Quads); // Note: sf::Quads doesn't work with egl } - if(chat_state == ChatState::REPLYING) { + if(chat_state == ChatState::REPLYING || chat_state == ChatState::EDITING) { 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); + const float item_height = std::min(body_size.y - replying_to_text_height - margin, tabs[MESSAGES_TAB_INDEX].body->get_item_height(currently_operating_on_item.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); @@ -3821,7 +3856,7 @@ namespace QuickMedia { 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); + tabs[MESSAGES_TAB_INDEX].body->draw_item(window, currently_operating_on_item.get(), body_item_pos, body_item_size); } if(tabs[selected_tab].type == ChatTabType::MESSAGES && !tabs[selected_tab].body->is_last_item_fully_visible()) { diff --git a/src/Text.cpp b/src/Text.cpp index 5e0ad87..fb1373c 100644 --- a/src/Text.cpp +++ b/src/Text.cpp @@ -164,6 +164,10 @@ namespace QuickMedia return editable; } + void Text::moveCaretToEnd() { + caretIndex = vertices_linear.size(); + } + float Text::getWidth() const { return boundingBox.width; diff --git a/src/plugins/Matrix.cpp b/src/plugins/Matrix.cpp index 50be2de..b93164e 100644 --- a/src/plugins/Matrix.cpp +++ b/src/plugins/Matrix.cpp @@ -140,6 +140,7 @@ namespace QuickMedia { auto room_it = room_data_by_id.find(room_id_str); if(room_it == room_data_by_id.end()) { auto room_data = std::make_unique<RoomData>(); + room_data->id = room_id_json.asString(); room_data_by_id.insert(std::make_pair(room_id_str, std::move(room_data))); room_name = room_id_str; fprintf(stderr, "Missing room %s from /sync, adding in joined_rooms\n", room_id_str.c_str()); @@ -268,6 +269,7 @@ namespace QuickMedia { auto room_it = room_data_by_id.find(room_id_str); if(room_it == room_data_by_id.end()) { auto room_data = std::make_unique<RoomData>(); + room_data->id = room_id_str; room_data_by_id.insert(std::make_pair(room_id_str, std::move(room_data))); room_it = room_data_by_id.find(room_id_str); // TODO: Get iterator from above insert } @@ -294,6 +296,7 @@ namespace QuickMedia { auto room_it = room_data_by_id.find(room_id_str); if(room_it == room_data_by_id.end()) { auto room_data = std::make_unique<RoomData>(); + room_data->id = room_id_str; room_data_by_id.insert(std::make_pair(room_id_str, std::move(room_data))); room_it = room_data_by_id.find(room_id_str); // TODO: Get iterator from above insert } @@ -442,53 +445,45 @@ namespace QuickMedia { if(!body_json.isString()) continue; - bool is_reply = false; + std::string replaces_event_id; 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(); + const Json::Value &replaces_event_id_json = relates_to_json["event_id"]; + const Json::Value &rel_type_json = relates_to_json["rel_type"]; + if(replaces_event_id_json.isString() && rel_type_json.isString() && strcmp(rel_type_json.asCString(), "m.replace") == 0) + replaces_event_id = replaces_event_id_json.asString(); } + auto message = std::make_shared<Message>(); + if(strcmp(content_type.asCString(), "m.text") == 0) { - 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; - 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; - 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; + } else { + continue; } + + message->user_id = user_it->second; + message->event_id = event_id_str; + message->body = body_json.asString(); + message->replaces_event_id = std::move(replaces_event_id); + new_messages.push_back(message); + room_data->message_by_event_id[event_id_str] = message; } // TODO: Loop and std::move instead? doesn't insert create copies? @@ -774,9 +769,13 @@ namespace QuickMedia { 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; + case MessageType::TEXT: { + if(!message->replaces_event_id.empty() && strncmp(message->body.c_str(), " * ", 3) == 0) + related_to_body = message->body.substr(3); + else + related_to_body = message->body; break; + } case MessageType::IMAGE: related_to_body = "sent an image"; break; @@ -795,7 +794,13 @@ namespace QuickMedia { return PluginResult::ERR; } - Message *relates_to_message = (Message*)relates_to; + Message *relates_to_message_raw = (Message*)relates_to; + std::shared_ptr<Message> relates_to_message_shared = room_it->second->message_by_event_id[relates_to_message_raw->event_id]; + std::shared_ptr<Message> relates_to_message_original = get_edited_message_original_message(room_it->second.get(), relates_to_message_shared); + if(!relates_to_message_original) { + fprintf(stderr, "Failed to get the original message for message with event id: %s\n", relates_to_message_raw->event_id.c_str()); + return PluginResult::ERR; + } char random_characters[18]; if(!generate_random_characters(random_characters, sizeof(random_characters))) @@ -804,14 +809,14 @@ namespace QuickMedia { 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; + in_reply_to_json["event_id"] = relates_to_message_original->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["body"] = create_body_for_message_reply(room_it->second.get(), relates_to_message_raw, body); // Yes, the reply is to the edited message but the event_id reference is to the original message... request_data["m.relates_to"] = std::move(relates_to_json); Json::StreamWriterBuilder builder; @@ -856,6 +861,204 @@ namespace QuickMedia { return PluginResult::OK; } + PluginResult Matrix::post_edit(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_raw = (Message*)relates_to; + std::shared_ptr<Message> relates_to_message_shared = room_it->second->message_by_event_id[relates_to_message_raw->event_id]; + std::shared_ptr<Message> relates_to_message_original = get_edited_message_original_message(room_it->second.get(), relates_to_message_shared); + if(!relates_to_message_original) { + fprintf(stderr, "Failed to get the original message for message with event id: %s\n", relates_to_message_raw->event_id.c_str()); + return PluginResult::ERR; + } + + 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)); + + std::string formatted_body; + bool contains_formatted_text = false; + int line = 0; + string_split(body, '\n', [&formatted_body, &contains_formatted_text, &line](const char *str, size_t size){ + if(line > 0) + formatted_body += "<br/>"; + if(size > 0 && str[0] == '>') { + std::string line(str, size); + html_escape_sequences(line); + formatted_body += "<font color=\"#789922\">"; + formatted_body += line; + formatted_body += "</font>"; + contains_formatted_text = true; + } else { + formatted_body.append(str, size); + } + ++line; + return true; + }); + + Json::Value new_content_json(Json::objectValue); + new_content_json["msgtype"] = "m.text"; + new_content_json["body"] = body; + if(contains_formatted_text) { + new_content_json["format"] = "org.matrix.custom.html"; + new_content_json["formatted_body"] = formatted_body; + } + + Json::Value relates_to_json(Json::objectValue); + relates_to_json["event_id"] = relates_to_message_original->event_id; + relates_to_json["rel_type"] = "m.replace"; + + Json::Value request_data(Json::objectValue); + request_data["msgtype"] = message_type_to_request_msg_type_str(MessageType::TEXT); + request_data["body"] = " * " + body; + if(contains_formatted_text) { + request_data["format"] = "org.matrix.custom.html"; + request_data["formatted_body"] = " * " + formatted_body; + } + request_data["m.new_content"] = std::move(new_content_json); + 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 edit, response event id: %s\n", event_id_json.asCString()); + return PluginResult::OK; + } + + // TODO: Right now this recursively calls /rooms/<room_id>/context/<event_id> and trusts server to not make it recursive. To make this robust, check iteration count and do not trust server. + // TODO: Optimize? + std::shared_ptr<Message> Matrix::get_edited_message_original_message(RoomData *room_data, std::shared_ptr<Message> message) { + if(message->replaces_event_id.empty()) + return message; + + auto message_it = room_data->message_by_event_id.find(message->replaces_event_id); + if(message_it == room_data->message_by_event_id.end()) { + Json::Value request_data(Json::objectValue); + request_data["lazy_load_members"] = true; + + Json::StreamWriterBuilder builder; + builder["commentStyle"] = "None"; + builder["indentation"] = ""; + + std::vector<CommandArg> additional_args = { + { "-H", "Authorization: Bearer " + access_token } + }; + + std::string filter = url_param_encode(Json::writeString(builder, std::move(request_data))); + + char url[512]; + snprintf(url, sizeof(url), "%s/_matrix/client/r0/rooms/%s/context/%s?limit=0&filter=%s", homeserver.c_str(), room_data->id.c_str(), message->event_id.c_str(), filter.c_str()); + fprintf(stderr, "get message context, url: |%s|\n", url); + + std::string server_response; + if(download_to_string(url, server_response, std::move(additional_args), use_tor, true) != DownloadResult::OK) + return nullptr; + + if(server_response.empty()) + return nullptr; + + 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 /rooms/<room_id>/context/<event_id> response parse error: %s\n", json_errors.c_str()); + return nullptr; + } + + if(!json_root.isObject()) + return nullptr; + + const Json::Value &event_json = json_root["event"]; + if(!event_json.isObject()) + return nullptr; + + const Json::Value &event_id_json = event_json["event_id"]; + if(!event_id_json.isString()) + return nullptr; + + const Json::Value &content_json = event_json["content"]; + if(!content_json.isObject()) + return nullptr; + + const Json::Value &body_json = content_json["body"]; + if(!body_json.isString()) + return nullptr; + + std::string replaces_event_id; + const Json::Value &relates_to_json = content_json["m.relates_to"]; + if(relates_to_json.isObject()) { + const Json::Value &event_id_json = relates_to_json["event_id"]; + const Json::Value &rel_type_json = relates_to_json["rel_type"]; + if(event_id_json.isString() && rel_type_json.isString() && strcmp(rel_type_json.asCString(), "m.replace") == 0) + replaces_event_id = event_id_json.asString(); + } + + const Json::Value &content_type = content_json["msgtype"]; + if(!content_type.isString()) + return nullptr; + + auto new_message = std::make_shared<Message>(); + new_message->user_id = -1; + new_message->event_id = event_id_json.asString(); + new_message->replaces_event_id = std::move(replaces_event_id); + if(strcmp(content_type.asCString(), "m.text") == 0) { + new_message->type = MessageType::TEXT; + } else if(strcmp(content_type.asCString(), "m.image") == 0) { + new_message->type = MessageType::IMAGE; + } else if(strcmp(content_type.asCString(), "m.video") == 0) { + new_message->type = MessageType::VIDEO; + } else { + return nullptr; + } + + return get_edited_message_original_message(room_data, std::move(new_message)); + } else { + return get_edited_message_original_message(room_data, message_it->second); + } + } + // Returns empty string on error static const char* file_get_filename(const std::string &filepath) { size_t index = filepath.rfind('/'); @@ -1155,4 +1358,14 @@ namespace QuickMedia { return PluginResult::OK; } + + bool Matrix::was_message_posted_by_me(const std::string &room_id, void *message) const { + 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 false; + } + Message *message_typed = (Message*)message; + return user_id == room_it->second->user_info[message_typed->user_id].user_id; + } }
\ No newline at end of file |