From 3f9185ad357fb70138d3ea301cd9ac0ee0de0704 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Fri, 16 Oct 2020 23:45:05 +0200 Subject: Matrix: use room object instead of room id --- plugins/Matrix.hpp | 53 +++++---- src/QuickMedia.cpp | 289 +++++++++++++++++++++++++++-------------------- src/plugins/Matrix.cpp | 297 +++++++++++++++++++------------------------------ 3 files changed, 315 insertions(+), 324 deletions(-) diff --git a/plugins/Matrix.hpp b/plugins/Matrix.hpp index 09895b0..8d078a0 100644 --- a/plugins/Matrix.hpp +++ b/plugins/Matrix.hpp @@ -41,7 +41,15 @@ namespace QuickMedia { IMAGE, VIDEO, AUDIO, - FILE + FILE, + REDACTION + }; + + enum class RelatedEventType { + NONE, + REPLY, + EDIT, + REDACTION }; struct Message { @@ -50,7 +58,8 @@ namespace QuickMedia { std::string body; std::string url; std::string thumbnail_url; - std::string replaces_event_id; + std::string related_event_id; + RelatedEventType related_event_type; bool mentions_me = false; time_t timestamp = 0; MessageType type; @@ -63,10 +72,10 @@ namespace QuickMedia { void set_user_read_marker(std::shared_ptr &user, const std::string &event_id); std::string get_user_read_marker(std::shared_ptr &user); - // Ignores duplicates, returns the number of inserted elements - size_t prepend_messages_reverse(std::vector> new_messages); - // Ignores duplicates, returns the number of inserted elements - size_t append_messages(std::vector> new_messages); + // Ignores duplicates + void prepend_messages_reverse(std::vector> new_messages); + // Ignores duplicates + void append_messages(std::vector> new_messages); std::shared_ptr get_message_by_id(const std::string &id); @@ -82,7 +91,6 @@ namespace QuickMedia { std::string avatar_url; std::string prev_batch; bool initial_fetch_finished = false; - size_t last_read_index = 0; private: std::mutex user_mutex; std::mutex room_mutex; @@ -106,37 +114,38 @@ namespace QuickMedia { std::string content_uri; }; - using RoomSyncMessages = std::unordered_map, std::vector>>; + using Messages = std::vector>; + using RoomSyncMessages = std::unordered_map, Messages>; + using Rooms = std::vector>; class Matrix { public: PluginResult sync(RoomSyncMessages &room_messages); - PluginResult get_joined_rooms(BodyItems &result_items); - PluginResult get_all_synced_room_messages(const std::string &room_id, BodyItems &result_items); - PluginResult get_new_room_messages(const std::string &room_id, BodyItems &result_items); - PluginResult get_previous_room_messages(const std::string &room_id, BodyItems &result_items); + PluginResult get_joined_rooms(Rooms &rooms); + PluginResult get_all_synced_room_messages(std::shared_ptr room, Messages &messages); + PluginResult get_previous_room_messages(std::shared_ptr room, Messages &messages); // |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::optional &file_info, const std::optional &thumbnail_info); + PluginResult post_message(std::shared_ptr room, const std::string &body, const std::optional &file_info, const std::optional &thumbnail_info); // |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); + PluginResult post_reply(std::shared_ptr room, 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); + PluginResult post_edit(std::shared_ptr room, const std::string &body, void *relates_to); - PluginResult post_file(const std::string &room_id, const std::string &filepath, std::string &err_msg); + PluginResult post_file(std::shared_ptr room, const std::string &filepath, std::string &err_msg); PluginResult login(const std::string &username, const std::string &password, const std::string &homeserver, std::string &err_msg); PluginResult logout(); // |message| is from |BodyItem.userdata| and is of type |Message*| - PluginResult delete_message(const std::string &room_id, void *message, std::string &err_msg); + PluginResult delete_message(std::shared_ptr room, void *message, std::string &err_msg); PluginResult load_and_verify_cached_session(); - PluginResult on_start_typing(const std::string &room_id); - PluginResult on_stop_typing(const std::string &room_id); + PluginResult on_start_typing(std::shared_ptr room); + PluginResult on_stop_typing(std::shared_ptr room); - PluginResult set_read_marker(const std::string &room_id, const Message *message); + PluginResult set_read_marker(std::shared_ptr room, const Message *message); // |message| is from |BodyItem.userdata| and is of type |Message*| bool was_message_posted_by_me(void *message); @@ -146,7 +155,7 @@ namespace QuickMedia { // Cached PluginResult get_config(int *upload_size); - std::shared_ptr get_me(const std::string &room_id); + std::shared_ptr get_me(std::shared_ptr room); bool use_tor = false; private: @@ -156,7 +165,7 @@ namespace QuickMedia { void events_add_user_read_markers(const rapidjson::Value &events_json, RoomData *room_data); void events_add_messages(const rapidjson::Value &events_json, std::shared_ptr &room_data, MessageDirection message_dir, RoomSyncMessages *room_messages, bool has_unread_notifications); void events_set_room_name(const rapidjson::Value &events_json, RoomData *room_data); - PluginResult upload_file(const std::string &room_id, const std::string &filepath, UploadInfo &file_info, UploadInfo &thumbnail_info, std::string &err_msg); + PluginResult upload_file(std::shared_ptr room, const std::string &filepath, UploadInfo &file_info, UploadInfo &thumbnail_info, std::string &err_msg); std::shared_ptr get_edited_message_original_message(RoomData *room_data, std::shared_ptr message); diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index cec81c6..249f38f 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -2807,8 +2807,13 @@ namespace QuickMedia { while (current_page == PageType::CHAT_LOGIN) { while (window.pollEvent(event)) { - base_event_handler(event, PageType::EXIT, body.get(), nullptr, false, false); - if(event.type == sf::Event::Resized || event.type == sf::Event::GainedFocus) { + if(event.type == sf::Event::Resized) { + window_size.x = event.size.width; + window_size.y = event.size.height; + sf::FloatRect visible_area(0, 0, window_size.x, window_size.y); + window.setView(sf::View(visible_area)); + redraw = true; + } else if(event.type == sf::Event::GainedFocus) { redraw = true; } else if(event.type == sf::Event::TextEntered) { inputs[focused_input]->onTextEntered(event.text.unicode); @@ -2867,6 +2872,29 @@ namespace QuickMedia { } } + static BodyItems messages_to_body_items(const Messages &messages) { + BodyItems result_items(messages.size()); + for(size_t i = 0; i < messages.size(); ++i) { + auto &message = messages[i]; + auto body_item = BodyItem::create(""); + body_item->set_author(message->user->display_name); + body_item->set_description(message->body); + body_item->set_timestamp(message->timestamp); + if(!message->thumbnail_url.empty()) + body_item->thumbnail_url = message->thumbnail_url; + else if(!message->url.empty() && message->type == MessageType::IMAGE) + body_item->thumbnail_url = message->url; + else + body_item->thumbnail_url = message->user->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 = message->url; + body_item->author_color = message->user->display_name_color; + body_item->userdata = (void*)message.get(); // Note: message has to be valid as long as body_item is used! + result_items[i] = std::move(body_item); + } + return result_items; + } + void Program::chat_page() { assert(strcmp(plugin_name, "matrix") == 0); @@ -2910,13 +2938,13 @@ namespace QuickMedia { time_t last_read_message_timestamp; }; - std::unordered_map body_items_by_room_id; - std::string current_room_id; + std::unordered_map, RoomBodyData> body_items_by_room; + std::shared_ptr current_room; RoomBodyData *current_room_body_data = nullptr; bool is_window_focused = window.hasFocus(); - auto process_new_room_messages = [this, &body_items_by_room_id, ¤t_room_id, &is_window_focused](RoomSyncMessages &room_sync_messages, bool only_show_mentions) mutable { + auto process_new_room_messages = [this, &body_items_by_room, ¤t_room, &is_window_focused](RoomSyncMessages &room_sync_messages, bool only_show_mentions) mutable { for(auto &[room, messages] : room_sync_messages) { bool was_mentioned = false; for(auto &message : messages) { @@ -2924,20 +2952,20 @@ namespace QuickMedia { was_mentioned = true; message->mentions_me = false; // TODO: What if the message or username begins with "-"? also make the notification image be the avatar of the user - if(!is_window_focused || room->id != current_room_id) + if(!is_window_focused || room != current_room) show_notification("QuickMedia matrix - " + matrix->message_get_author_displayname(message.get()) + " (" + room->name + ")", message->body); } } - auto room_body_item_it = body_items_by_room_id.find(room->id); - if(room_body_item_it == body_items_by_room_id.end()) + auto room_body_item_it = body_items_by_room.find(room); + if(room_body_item_it == body_items_by_room.end()) continue; - // TODO: this wont always because we dont display all types of messages from server, such as "joined", "left", "kicked", "banned", "changed avatar", "changed display name", etc. + // TODO: this wont always work because we dont display all types of messages from server, such as "joined", "left", "kicked", "banned", "changed avatar", "changed display name", etc. // TODO: Update local marker when another client with our user sets read marker, in that case our read marker (room->get_user_read_marker) will be updated. bool unread_messages_previous_session = false; if(!messages.empty()) { - std::shared_ptr me = matrix->get_me(room->id); + std::shared_ptr me = matrix->get_me(room); if(me && room->get_user_read_marker(me) != messages.back()->event_id) unread_messages_previous_session = true; } @@ -2983,7 +3011,10 @@ namespace QuickMedia { chat_input.draw_background = false; chat_input.set_editable(false); - chat_input.on_submit_callback = [this, &chat_input, &tabs, &selected_tab, ¤t_room_id, &new_page, &chat_state, ¤tly_operating_on_item](const std::string &text) mutable { + chat_input.on_submit_callback = [this, &chat_input, &tabs, &selected_tab, ¤t_room, &new_page, &chat_state, ¤tly_operating_on_item](const std::string &text) mutable { + if(!current_room) + return false; + if(tabs[selected_tab].type == ChatTabType::MESSAGES) { if(text.empty()) return false; @@ -3010,7 +3041,7 @@ namespace QuickMedia { if(chat_state == ChatState::TYPING_MESSAGE) { // TODO: Make asynchronous - if(matrix->post_message(current_room_id, text, std::nullopt, std::nullopt) == PluginResult::OK) { + if(matrix->post_message(current_room, text, std::nullopt, std::nullopt) == PluginResult::OK) { chat_input.set_editable(false); chat_state = ChatState::NAVIGATING; return true; @@ -3020,7 +3051,7 @@ namespace QuickMedia { } } else if(chat_state == ChatState::REPLYING) { // TODO: Make asynchronous - if(matrix->post_reply(current_room_id, text, currently_operating_on_item->userdata) == PluginResult::OK) { + if(matrix->post_reply(current_room, text, currently_operating_on_item->userdata) == PluginResult::OK) { chat_input.set_editable(false); chat_state = ChatState::NAVIGATING; currently_operating_on_item = nullptr; @@ -3031,7 +3062,7 @@ namespace QuickMedia { } } else if(chat_state == ChatState::EDITING) { // TODO: Make asynchronous - if(matrix->post_edit(current_room_id, text, currently_operating_on_item->userdata) == PluginResult::OK) { + if(matrix->post_edit(current_room, text, currently_operating_on_item->userdata) == PluginResult::OK) { chat_input.set_editable(false); chat_state = ChatState::NAVIGATING; currently_operating_on_item = nullptr; @@ -3046,20 +3077,18 @@ namespace QuickMedia { }; struct SyncFutureResult { - BodyItems body_items; - BodyItems rooms_body_items; + Rooms rooms; RoomSyncMessages room_sync_messages; }; std::future sync_future; bool sync_running = false; - std::string sync_future_room_id; sf::Clock sync_timer; sf::Int32 sync_min_time_ms = 0; // Sync immediately the first time - std::future previous_messages_future; + std::future previous_messages_future; bool fetching_previous_messages_running = false; - std::string previous_messages_future_room_id; + std::shared_ptr previous_messages_future_room; const float tab_spacer_height = 0.0f; sf::Vector2f body_pos; @@ -3099,11 +3128,11 @@ namespace QuickMedia { auto room_avatar_thumbnail_data = std::make_shared(); AsyncImageLoader async_image_loader; - auto typing_async_func = [this](bool new_state, std::string room_id) { + auto typing_async_func = [this](bool new_state, std::shared_ptr room) { if(new_state) { - matrix->on_start_typing(room_id); + matrix->on_start_typing(room); } else { - matrix->on_stop_typing(room_id); + matrix->on_stop_typing(room); } }; std::vector> typing_futures; @@ -3187,6 +3216,57 @@ namespace QuickMedia { return result; }; + auto add_new_messages_to_current_room = [&tabs](Messages &messages) { + int num_items = tabs[MESSAGES_TAB_INDEX].body->items.size(); + bool scroll_to_end = (num_items == 0 || (num_items > 0 && tabs[MESSAGES_TAB_INDEX].body->get_selected_item() == num_items - 1)); + + BodyItem *selected_item = tabs[MESSAGES_TAB_INDEX].body->get_selected(); + tabs[MESSAGES_TAB_INDEX].body->insert_items_by_timestamps(messages_to_body_items(messages)); + if(selected_item && !scroll_to_end) { + int selected_item_index = tabs[MESSAGES_TAB_INDEX].body->get_index_by_body_item(selected_item); + if(selected_item_index != -1) + tabs[MESSAGES_TAB_INDEX].body->set_selected_item(selected_item_index); + } else if(scroll_to_end) { + tabs[MESSAGES_TAB_INDEX].body->select_last_item(); + } + }; + + auto add_new_rooms = [&tabs, &body_items_by_room, ¤t_room, ¤t_room_body_data, &room_name_text](Rooms &rooms) { + if(rooms.empty()) + return; + + for(size_t i = 0; i < rooms.size(); ++i) { + auto &room = rooms[i]; + std::string room_name = room->name; + if(room_name.empty()) + room_name = room->id; + + auto body_item = BodyItem::create(std::move(room_name)); + body_item->thumbnail_url = room->avatar_url; + body_item->userdata = room.get(); // Note: this has to be valid as long as the room list is valid! + tabs[ROOMS_TAB_INDEX].body->items.push_back(body_item); + body_items_by_room[room] = { body_item, true, 0 }; + } + + if(current_room) + return; + + current_room = rooms[0]; + auto room_body_item_it = body_items_by_room.find(current_room); + if(room_body_item_it != body_items_by_room.end()) { + current_room_body_data = &room_body_item_it->second; + room_name_text.setString(current_room_body_data->body_item->get_title()); + } + }; + + auto get_room_by_ptr = [&body_items_by_room](RoomData *room_ptr) -> std::shared_ptr { + for(auto &[room, body_data] : body_items_by_room) { + if(room.get() == room_ptr) + return room; + } + return nullptr; + }; + while (current_page == PageType::CHAT) { sf::Int32 frame_time_ms = frame_timer.restart().asMilliseconds(); while (window.pollEvent(event)) { @@ -3218,15 +3298,15 @@ namespace QuickMedia { hit_top = false; break; } - if(hit_top && !fetching_previous_messages_running && tabs[selected_tab].type == ChatTabType::MESSAGES) { + if(hit_top && !fetching_previous_messages_running && tabs[selected_tab].type == ChatTabType::MESSAGES && current_room) { gradient_inc = 0; fetching_previous_messages_running = true; - previous_messages_future_room_id = current_room_id; - previous_messages_future = std::async(std::launch::async, [this, &previous_messages_future_room_id]() { - BodyItems result_items; - if(matrix->get_previous_room_messages(previous_messages_future_room_id, result_items) != PluginResult::OK) - fprintf(stderr, "Failed to get previous matrix messages in room: %s\n", previous_messages_future_room_id.c_str()); - return result_items; + previous_messages_future_room = current_room; + previous_messages_future = std::async(std::launch::async, [this, &previous_messages_future_room]() { + Messages messages; + if(matrix->get_previous_room_messages(previous_messages_future_room, messages) != PluginResult::OK) + fprintf(stderr, "Failed to get previous matrix messages in room: %s\n", previous_messages_future_room->id.c_str()); + return messages; }); } } else if(event.key.code == sf::Keyboard::Down || event.key.code == sf::Keyboard::J) { @@ -3242,20 +3322,20 @@ namespace QuickMedia { selected_tab = std::max(0, selected_tab - 1); read_marker_timer.restart(); redraw = true; - if(typing) { + if(typing && current_room) { fprintf(stderr, "Stopped typing\n"); typing = false; - typing_futures.push_back(std::async(typing_async_func, false, current_room_id)); + typing_futures.push_back(std::async(typing_async_func, false, current_room)); } } else if((event.key.code == sf::Keyboard::Right || event.key.code == sf::Keyboard::L) && synced) { tabs[selected_tab].body->clear_cache(); selected_tab = std::min((int)tabs.size() - 1, selected_tab + 1); read_marker_timer.restart(); redraw = true; - if(typing) { + if(typing && current_room) { fprintf(stderr, "Stopped typing\n"); typing = false; - typing_futures.push_back(std::async(typing_async_func, false, current_room_id)); + typing_futures.push_back(std::async(typing_async_func, false, current_room)); } } @@ -3326,7 +3406,7 @@ namespace QuickMedia { continue; launch_url(selected_item->get_title()); } - } else if(event.type == sf::Event::KeyReleased && chat_state == ChatState::NAVIGATING && tabs[selected_tab].type == ChatTabType::MESSAGES) { + } else if(event.type == sf::Event::KeyReleased && chat_state == ChatState::NAVIGATING && tabs[selected_tab].type == ChatTabType::MESSAGES && current_room) { if(event.key.code == sf::Keyboard::U) { new_page = PageType::FILE_MANAGER; chat_input.set_editable(false); @@ -3341,7 +3421,7 @@ namespace QuickMedia { // TODO: Make asynchronous. // TODO: Upload multiple files. std::string err_msg; - if(matrix->post_file(current_room_id, sf::Clipboard::getString(), err_msg) != PluginResult::OK) { + if(matrix->post_file(current_room, sf::Clipboard::getString(), err_msg) != PluginResult::OK) { std::string desc = "Failed to upload media to room, error: " + err_msg; show_notification("QuickMedia", desc.c_str(), Urgency::CRITICAL); } @@ -3382,13 +3462,13 @@ namespace QuickMedia { show_notification("QuickMedia", "No message selected for editing"); } } - + if(event.key.code == sf::Keyboard::D) { BodyItem *selected = tabs[selected_tab].body->get_selected(); if(selected) { // TODO: Make asynchronous std::string err_msg; - if(matrix->delete_message(current_room_id, selected->userdata, err_msg) != PluginResult::OK) { + if(matrix->delete_message(current_room, selected->userdata, err_msg) != PluginResult::OK) { // TODO: Show inline notification show_notification("QuickMedia", "Failed to delete message, reason: " + err_msg, Urgency::CRITICAL); } @@ -3405,9 +3485,9 @@ namespace QuickMedia { // TODO: Also show typing event when ctrl+v pasting? if(event.text.unicode != 13) { // Return key start_typing_timer.restart(); - if(!typing) { + if(!typing && current_room) { fprintf(stderr, "Started typing\n"); - typing_futures.push_back(std::async(typing_async_func, true, current_room_id)); + typing_futures.push_back(std::async(typing_async_func, true, current_room)); } typing = true; } @@ -3416,10 +3496,10 @@ namespace QuickMedia { chat_input.set_text(""); chat_state = ChatState::NAVIGATING; currently_operating_on_item = nullptr; - if(typing) { + if(typing && current_room) { fprintf(stderr, "Stopped typing\n"); typing = false; - typing_futures.push_back(std::async(typing_async_func, false, current_room_id)); + typing_futures.push_back(std::async(typing_async_func, false, current_room)); } } //chat_input.on_event(event); @@ -3428,21 +3508,23 @@ namespace QuickMedia { BodyItem *selected_item = tabs[selected_tab].body->get_selected(); if(selected_item) { tabs[selected_tab].body->clear_cache(); - current_room_id = selected_item->url; + + current_room = get_room_by_ptr((RoomData*)selected_item->userdata); + assert(current_room); selected_tab = MESSAGES_TAB_INDEX; tabs[MESSAGES_TAB_INDEX].body->clear_items(); - BodyItems new_items; - if(matrix->get_all_synced_room_messages(current_room_id, new_items) == PluginResult::OK) { - tabs[MESSAGES_TAB_INDEX].body->insert_items_by_timestamps(std::move(new_items)); + Messages new_messages; + if(matrix->get_all_synced_room_messages(current_room, new_messages) == PluginResult::OK) { + tabs[MESSAGES_TAB_INDEX].body->insert_items_by_timestamps(messages_to_body_items(new_messages)); tabs[MESSAGES_TAB_INDEX].body->select_last_item(); } else { - std::string err_msg = "Failed to get messages in room: " + current_room_id; + std::string err_msg = "Failed to get messages in room: " + current_room->id; show_notification("QuickMedia", err_msg, Urgency::CRITICAL); } - auto room_body_item_it = body_items_by_room_id.find(current_room_id); - if(room_body_item_it != body_items_by_room_id.end()) { + auto room_body_item_it = body_items_by_room.find(current_room); + if(room_body_item_it != body_items_by_room.end()) { current_room_body_data = &room_body_item_it->second; room_name_text.setString(current_room_body_data->body_item->get_title()); room_avatar_thumbnail_data = std::make_shared(); @@ -3458,28 +3540,30 @@ namespace QuickMedia { case PageType::FILE_MANAGER: { new_page = PageType::CHAT; - auto file_manager_page = std::make_unique(this); - file_manager_page->set_current_directory(get_home_dir().data); - auto file_manager_body = create_body(); - file_manager_page->get_files_in_directory(file_manager_body->items); - std::vector tabs; - tabs.push_back(Tab{std::move(file_manager_body), std::move(file_manager_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); - - selected_files.clear(); - page_loop(std::move(tabs)); - - if(selected_files.empty()) { - fprintf(stderr, "No files selected!\n"); - } else { - // TODO: Make asynchronous. - // TODO: Upload multiple files. - std::string err_msg; - if(matrix->post_file(current_room_id, selected_files[0], err_msg) != PluginResult::OK) { - std::string desc = "Failed to upload media to room, error: " + err_msg; - show_notification("QuickMedia", desc.c_str(), Urgency::CRITICAL); + if(current_room) { + auto file_manager_page = std::make_unique(this); + file_manager_page->set_current_directory(get_home_dir().data); + auto file_manager_body = create_body(); + file_manager_page->get_files_in_directory(file_manager_body->items); + std::vector tabs; + tabs.push_back(Tab{std::move(file_manager_body), std::move(file_manager_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); + + selected_files.clear(); + page_loop(std::move(tabs)); + + if(selected_files.empty()) { + fprintf(stderr, "No files selected!\n"); + } else { + // TODO: Make asynchronous. + // TODO: Upload multiple files. + std::string err_msg; + if(matrix->post_file(current_room, selected_files[0], err_msg) != PluginResult::OK) { + std::string desc = "Failed to upload media to room, error: " + err_msg; + show_notification("QuickMedia", desc.c_str(), Urgency::CRITICAL); + } } + redraw = true; } - redraw = true; break; } case PageType::CHAT_LOGIN: { @@ -3502,10 +3586,10 @@ namespace QuickMedia { break; } - if(typing && start_typing_timer.getElapsedTime().asSeconds() >= typing_timeout_seconds) { + if(typing && start_typing_timer.getElapsedTime().asSeconds() >= typing_timeout_seconds && current_room) { fprintf(stderr, "Stopped typing\n"); typing = false; - typing_futures.push_back(std::async(typing_async_func, false, current_room_id)); + typing_futures.push_back(std::async(typing_async_func, false, current_room)); } for(auto it = typing_futures.begin(); it != typing_futures.end(); ) { @@ -3589,26 +3673,18 @@ namespace QuickMedia { sync_min_time_ms = 50; sync_running = true; sync_timer.restart(); - sync_future_room_id = current_room_id; - sync_future = std::async(std::launch::async, [this, &sync_future_room_id, synced]() { + sync_future = std::async(std::launch::async, [this, synced]() { SyncFutureResult result; if(matrix->sync(result.room_sync_messages) == PluginResult::OK) { fprintf(stderr, "Synced matrix\n"); if(!synced) { - if(matrix->get_joined_rooms(result.rooms_body_items) != PluginResult::OK) { + if(matrix->get_joined_rooms(result.rooms) != PluginResult::OK) { show_notification("QuickMedia", "Failed to get a list of joined rooms", Urgency::CRITICAL); current_page = PageType::EXIT; return result; } } - - if(sync_future_room_id.empty() && !result.rooms_body_items.empty()) - sync_future_room_id = result.rooms_body_items[0]->url; - - if(matrix->get_new_room_messages(sync_future_room_id, result.body_items) != PluginResult::OK) { - fprintf(stderr, "Failed to get new matrix messages in room: %s\n", sync_future_room_id.c_str()); - } } else { fprintf(stderr, "Failed to sync matrix\n"); } @@ -3619,40 +3695,14 @@ namespace QuickMedia { if(sync_future.valid() && sync_future.wait_for(std::chrono::seconds(0)) == std::future_status::ready) { SyncFutureResult sync_result = sync_future.get(); - // 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 || !synced) { - int num_items = tabs[MESSAGES_TAB_INDEX].body->items.size(); - bool scroll_to_end = (num_items == 0 || (num_items > 0 && tabs[MESSAGES_TAB_INDEX].body->get_selected_item() == num_items - 1)); - - BodyItem *selected_item = tabs[MESSAGES_TAB_INDEX].body->get_selected(); - tabs[MESSAGES_TAB_INDEX].body->insert_items_by_timestamps(std::move(sync_result.body_items)); - if(selected_item && !scroll_to_end) { - int selected_item_index = tabs[MESSAGES_TAB_INDEX].body->get_index_by_body_item(selected_item); - if(selected_item_index != -1) - tabs[MESSAGES_TAB_INDEX].body->set_selected_item(selected_item_index); - } else if(scroll_to_end) { - tabs[MESSAGES_TAB_INDEX].body->select_last_item(); - } - } - // Initial sync - if(!synced) { - tabs[ROOMS_TAB_INDEX].body->items = std::move(sync_result.rooms_body_items); + add_new_rooms(sync_result.rooms); - for(auto body_item : tabs[ROOMS_TAB_INDEX].body->items) { - body_items_by_room_id[body_item->url] = { body_item, true, 0 }; - } + auto room_messages_it = sync_result.room_sync_messages.find(current_room); + if(room_messages_it != sync_result.room_sync_messages.end()) + add_new_messages_to_current_room(room_messages_it->second); - // The room id should be saved in a file when changing viewed room. - if(!tabs[ROOMS_TAB_INDEX].body->items.empty()) - current_room_id = tabs[ROOMS_TAB_INDEX].body->items[0]->url; - - auto room_body_item_it = body_items_by_room_id.find(current_room_id); - if(room_body_item_it != body_items_by_room_id.end()) { - current_room_body_data = &room_body_item_it->second; - room_name_text.setString(current_room_body_data->body_item->get_title()); - } - } + //modify_related_messages() process_new_room_messages(sync_result.room_sync_messages, !synced); sync_running = false; @@ -3666,13 +3716,13 @@ namespace QuickMedia { } if(fetching_previous_messages_running && previous_messages_future.valid() && previous_messages_future.wait_for(std::chrono::seconds(0)) == std::future_status::ready) { - BodyItems new_body_items = previous_messages_future.get(); - fprintf(stderr, "Finished fetching older messages, num new messages: %zu\n", new_body_items.size()); + Messages new_messages = previous_messages_future.get(); + fprintf(stderr, "Finished fetching older messages, num new messages: %zu\n", new_messages.size()); // Ignore finished fetch of messages if it happened in another room. When we navigate back to the room we will get the messages again - size_t num_new_messages = new_body_items.size(); - if(previous_messages_future_room_id == current_room_id && num_new_messages > 0) { + size_t num_new_messages = new_messages.size(); + if(previous_messages_future_room == current_room && num_new_messages > 0) { BodyItem *selected_item = tabs[MESSAGES_TAB_INDEX].body->get_selected(); - tabs[MESSAGES_TAB_INDEX].body->insert_items_by_timestamps(std::move(new_body_items)); + tabs[MESSAGES_TAB_INDEX].body->insert_items_by_timestamps(messages_to_body_items(new_messages)); if(selected_item) { int selected_item_index = tabs[MESSAGES_TAB_INDEX].body->get_index_by_body_item(selected_item); if(selected_item_index != -1) @@ -3799,7 +3849,7 @@ namespace QuickMedia { window.draw(loading_text); } - if(tabs[selected_tab].type == ChatTabType::MESSAGES) { + if(tabs[selected_tab].type == ChatTabType::MESSAGES && current_room) { BodyItem *last_visible_item = tabs[selected_tab].body->get_last_fully_visible_item(); if(is_window_focused && chat_state != ChatState::URL_SELECTION && current_room_body_data && last_visible_item && !setting_read_marker && read_marker_timer.getElapsedTime().asMilliseconds() >= read_marker_timeout_ms) { Message *message = (Message*)last_visible_item->userdata; @@ -3809,8 +3859,9 @@ namespace QuickMedia { current_room_body_data->last_read_message_timestamp = message->timestamp; // TODO: What if the message is no longer valid? setting_read_marker = true; - set_read_marker_future = std::async(std::launch::async, [this, current_room_id, message]() mutable { - if(matrix->set_read_marker(current_room_id, message) != PluginResult::OK) { + std::shared_ptr room = current_room; + set_read_marker_future = std::async(std::launch::async, [this, room, message]() mutable { + if(matrix->set_read_marker(room, message) != PluginResult::OK) { fprintf(stderr, "Warning: failed to set read marker to %s\n", message->event_id.c_str()); } }); diff --git a/src/plugins/Matrix.cpp b/src/plugins/Matrix.cpp index 18a31a0..0866bac 100644 --- a/src/plugins/Matrix.cpp +++ b/src/plugins/Matrix.cpp @@ -53,30 +53,24 @@ namespace QuickMedia { return user->read_marker_event_id; } - size_t RoomData::prepend_messages_reverse(std::vector> new_messages) { + void RoomData::prepend_messages_reverse(std::vector> new_messages) { std::lock_guard lock(room_mutex); - size_t num_inserted = 0; for(auto it = new_messages.begin(); it != new_messages.end(); ++it) { if(message_by_event_id.find((*it)->event_id) == message_by_event_id.end()) { message_by_event_id.insert(std::make_pair((*it)->event_id, *it)); messages.insert(messages.begin(), std::move(*it)); - ++num_inserted; } } - return num_inserted; } - size_t RoomData::append_messages(std::vector> new_messages) { + void RoomData::append_messages(std::vector> new_messages) { std::lock_guard lock(room_mutex); - size_t num_inserted = 0; for(auto it = new_messages.begin(); it != new_messages.end(); ++it) { if(message_by_event_id.find((*it)->event_id) == message_by_event_id.end()) { message_by_event_id.insert(std::make_pair((*it)->event_id, *it)); messages.push_back(std::move(*it)); - ++num_inserted; } } - return num_inserted; } std::shared_ptr RoomData::get_message_by_id(const std::string &id) { @@ -141,7 +135,7 @@ namespace QuickMedia { return PluginResult::OK; } - PluginResult Matrix::get_joined_rooms(BodyItems &result_items) { + PluginResult Matrix::get_joined_rooms(Rooms &rooms) { std::vector additional_args = { { "-H", "Authorization: Bearer " + access_token } }; @@ -162,114 +156,28 @@ namespace QuickMedia { continue; std::string room_id_str = room_id_json.GetString(); - std::string room_name; - std::string avatar_url; - auto room = get_room_by_id(room_id_str); if(!room) { room = std::make_shared(); room->id = room_id_json.GetString(); add_room(std::move(room)); - room_name = room_id_str; fprintf(stderr, "Missing room %s from /sync, adding in joined_rooms\n", room_id_str.c_str()); - } else { - room_name = room->name; - if(room_name.empty()) - room_name = room_id_str; - avatar_url = room->avatar_url; } - 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)); - } - - return PluginResult::OK; - } - - static void room_messages_to_body_items(const std::shared_ptr *messages, size_t num_messages, BodyItems &result_items) { - for(size_t i = 0; i < num_messages; ++i) { - auto body_item = BodyItem::create(""); - body_item->set_author(messages[i]->user->display_name); - body_item->set_description(messages[i]->body); - body_item->set_timestamp(messages[i]->timestamp); - 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 = messages[i]->user->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->author_color = messages[i]->user->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)); - } - } - - // TODO: Merge common code with |get_new_room_messages| - PluginResult Matrix::get_all_synced_room_messages(const std::string &room_id, BodyItems &result_items) { - auto room = get_room_by_id(room_id); - if(!room) { - fprintf(stderr, "Error: no such room: %s\n", room_id.c_str()); - return PluginResult::ERR; + rooms.push_back(room); } - // TODO: Thread safe? - /* - if(!room->initial_fetch_finished) { - PluginResult result = get_previous_room_messages(room); - if(result == PluginResult::OK) { - room->initial_fetch_finished = true; - } else { - fprintf(stderr, "Initial sync failed for room: %s\n", room_id.c_str()); - return result; - } - } - */ - - room->acquire_room_lock(); - room_messages_to_body_items(room->get_messages_thread_unsafe().data(), room->get_messages_thread_unsafe().size(), result_items); - room->last_read_index = room->get_messages_thread_unsafe().size(); - room->release_room_lock(); return PluginResult::OK; } - PluginResult Matrix::get_new_room_messages(const std::string &room_id, BodyItems &result_items) { - auto room = get_room_by_id(room_id); - if(!room) { - fprintf(stderr, "Error: no such room: %s\n", room_id.c_str()); - return PluginResult::ERR; - } - - /* - if(!room->initial_fetch_finished) { - PluginResult result = get_previous_room_messages(room); - if(result == PluginResult::OK) { - room->initial_fetch_finished = true; - } else { - fprintf(stderr, "Initial sync failed for room: %s\n", room_id.c_str()); - return result; - } - } - */ - + PluginResult Matrix::get_all_synced_room_messages(std::shared_ptr room, Messages &messages) { room->acquire_room_lock(); - size_t num_new_messages = room->get_messages_thread_unsafe().size() - room->last_read_index; - room_messages_to_body_items(room->get_messages_thread_unsafe().data() + room->last_read_index, num_new_messages, result_items); - room->last_read_index = room->get_messages_thread_unsafe().size(); + messages = room->get_messages_thread_unsafe(); room->release_room_lock(); return PluginResult::OK; } - PluginResult Matrix::get_previous_room_messages(const std::string &room_id, BodyItems &result_items) { - auto room = get_room_by_id(room_id); - if(!room) { - fprintf(stderr, "Error: no such room: %s\n", room_id.c_str()); - return PluginResult::ERR; - } - + PluginResult Matrix::get_previous_room_messages(std::shared_ptr room, Messages &messages) { room->acquire_room_lock(); size_t num_messages_before = room->get_messages_thread_unsafe().size(); room->release_room_lock(); @@ -280,7 +188,7 @@ namespace QuickMedia { room->acquire_room_lock(); size_t num_messages_after = room->get_messages_thread_unsafe().size(); size_t num_new_messages = num_messages_after - num_messages_before; - room_messages_to_body_items(room->get_messages_thread_unsafe().data(), num_new_messages, result_items); + messages.insert(messages.end(), room->get_messages_thread_unsafe().begin(), room->get_messages_thread_unsafe().begin() + num_new_messages); room->release_room_lock(); return PluginResult::OK; } @@ -536,20 +444,12 @@ namespace QuickMedia { if(!events_json.IsArray()) return; - std::vector> *room_sync_messages = nullptr; - if(room_messages) - room_sync_messages = &(*room_messages)[room_data]; - std::vector> new_messages; for(const rapidjson::Value &event_item_json : events_json.GetArray()) { if(!event_item_json.IsObject()) continue; - const rapidjson::Value &type_json = GetMember(event_item_json, "type"); - if(!type_json.IsString() || strcmp(type_json.GetString(), "m.room.message") != 0) - continue; - const rapidjson::Value &sender_json = GetMember(event_item_json, "sender"); if(!sender_json.IsString()) continue; @@ -562,12 +462,8 @@ namespace QuickMedia { std::string event_id_str = event_id_json.GetString(); - const rapidjson::Value &content_json = GetMember(event_item_json, "content"); - if(!content_json.IsObject()) - continue; - - const rapidjson::Value &content_type = GetMember(content_json, "msgtype"); - if(!content_type.IsString()) + const rapidjson::Value *content_json = &GetMember(event_item_json, "content"); + if(!content_json->IsObject()) continue; auto user = room_data->get_user_by_id(sender_json_str); @@ -577,24 +473,71 @@ namespace QuickMedia { continue; } - const rapidjson::Value &body_json = GetMember(content_json, "body"); - if(!body_json.IsString()) - continue; - time_t timestamp = 0; const rapidjson::Value &origin_server_ts = GetMember(event_item_json, "origin_server_ts"); if(origin_server_ts.IsNumber()) timestamp = origin_server_ts.GetInt64(); - std::string replaces_event_id; - const rapidjson::Value &relates_to_json = GetMember(content_json, "m.relates_to"); + const rapidjson::Value &type_json = GetMember(event_item_json, "type"); + if(!type_json.IsString()) + continue; + + RelatedEventType related_event_type = RelatedEventType::NONE; + std::string related_event_id; + const rapidjson::Value &relates_to_json = GetMember(*content_json, "m.relates_to"); if(relates_to_json.IsObject()) { const rapidjson::Value &replaces_event_id_json = GetMember(relates_to_json, "event_id"); const rapidjson::Value &rel_type_json = GetMember(relates_to_json, "rel_type"); - if(replaces_event_id_json.IsString() && rel_type_json.IsString() && strcmp(rel_type_json.GetString(), "m.replace") == 0) - replaces_event_id = replaces_event_id_json.GetString(); + if(replaces_event_id_json.IsString() && rel_type_json.IsString() && strcmp(rel_type_json.GetString(), "m.replace") == 0) { + related_event_id = replaces_event_id_json.GetString(); + related_event_type = RelatedEventType::EDIT; + } else { + const rapidjson::Value &in_reply_to_json = GetMember(relates_to_json, "m.in_reply_to"); + if(in_reply_to_json.IsObject()) { + const rapidjson::Value &in_reply_to_event_id = GetMember(in_reply_to_json, "event_id"); + if(in_reply_to_event_id.IsString()) { + related_event_id = in_reply_to_event_id.GetString(); + related_event_type = RelatedEventType::REPLY; + } + } + } + } + + const rapidjson::Value &new_content_json = GetMember(*content_json, "m.new_content"); + if(new_content_json.IsObject()) + content_json = &new_content_json; + + const rapidjson::Value &content_type = GetMember(*content_json, "msgtype"); + if(!content_type.IsString() || strcmp(type_json.GetString(), "m.room.redaction") == 0) { + auto message = std::make_shared(); + message->type = MessageType::REDACTION; + message->user = user; + message->event_id = event_id_str; + message->body = "Message deleted"; + message->timestamp = timestamp; + message->related_event_type = RelatedEventType::REDACTION; + + const rapidjson::Value &reason_json = GetMember(*content_json, "reason"); + if(reason_json.IsString()) { + message->body += ", reason: "; + message->body += reason_json.GetString(); + } + + const rapidjson::Value &redacts_json = GetMember(event_item_json, "redacts"); + if(redacts_json.IsString()) + message->related_event_id = redacts_json.GetString(); + + new_messages.push_back(message); + continue; } + if(strcmp(type_json.GetString(), "m.room.message") != 0) + continue; + + const rapidjson::Value &body_json = GetMember(*content_json, "body"); + if(!body_json.IsString()) + continue; + auto message = std::make_shared(); std::string prefix; @@ -603,30 +546,30 @@ namespace QuickMedia { if(strcmp(content_type.GetString(), "m.text") == 0) { message->type = MessageType::TEXT; } else if(strcmp(content_type.GetString(), "m.image") == 0) { - const rapidjson::Value &url_json = GetMember(content_json, "url"); + const rapidjson::Value &url_json = GetMember(*content_json, "url"); if(!url_json.IsString() || strncmp(url_json.GetString(), "mxc://", 6) != 0) continue; message->url = homeserver + "/_matrix/media/r0/download/" + (url_json.GetString() + 6); - message->thumbnail_url = message_content_extract_thumbnail_url(content_json, homeserver); + message->thumbnail_url = message_content_extract_thumbnail_url(*content_json, homeserver); message->type = MessageType::IMAGE; } else if(strcmp(content_type.GetString(), "m.video") == 0) { - const rapidjson::Value &url_json = GetMember(content_json, "url"); + const rapidjson::Value &url_json = GetMember(*content_json, "url"); if(!url_json.IsString() || strncmp(url_json.GetString(), "mxc://", 6) != 0) continue; message->url = homeserver + "/_matrix/media/r0/download/" + (url_json.GetString() + 6); - message->thumbnail_url = message_content_extract_thumbnail_url(content_json, homeserver); + message->thumbnail_url = message_content_extract_thumbnail_url(*content_json, homeserver); message->type = MessageType::VIDEO; } else if(strcmp(content_type.GetString(), "m.audio") == 0) { - const rapidjson::Value &url_json = GetMember(content_json, "url"); + const rapidjson::Value &url_json = GetMember(*content_json, "url"); if(!url_json.IsString() || strncmp(url_json.GetString(), "mxc://", 6) != 0) continue; message->url = homeserver + "/_matrix/media/r0/download/" + (url_json.GetString() + 6); message->type = MessageType::AUDIO; } else if(strcmp(content_type.GetString(), "m.file") == 0) { - const rapidjson::Value &url_json = GetMember(content_json, "url"); + const rapidjson::Value &url_json = GetMember(*content_json, "url"); if(!url_json.IsString() || strncmp(url_json.GetString(), "mxc://", 6) != 0) continue; @@ -639,12 +582,12 @@ namespace QuickMedia { message->type = MessageType::TEXT; prefix = "* NOTICE * "; } else if(strcmp(content_type.GetString(), "m.location") == 0) { // TODO: show locations differently - const rapidjson::Value &geo_uri_json = GetMember(content_json, "geo_uri"); + const rapidjson::Value &geo_uri_json = GetMember(*content_json, "geo_uri"); if(geo_uri_json.IsString()) prefix = geo_uri_json.GetString() + std::string(" | "); message->type = MessageType::TEXT; - message->thumbnail_url = message_content_extract_thumbnail_url(content_json, homeserver); + message->thumbnail_url = message_content_extract_thumbnail_url(*content_json, homeserver); } else { continue; } @@ -652,22 +595,22 @@ namespace QuickMedia { message->user = user; message->event_id = event_id_str; message->body = prefix + body_json.GetString(); - message->replaces_event_id = std::move(replaces_event_id); + message->related_event_id = std::move(related_event_id); + message->related_event_type = related_event_type; // TODO: Is @room ok? shouldn't we also check if the user has permission to do @room? (only when notifications are limited to @mentions) if(has_unread_notifications && !username.empty()) message->mentions_me = message_contains_user_mention(message->body, username) || message_contains_user_mention(message->body, "@room"); message->timestamp = timestamp; new_messages.push_back(message); - if(room_sync_messages) - room_sync_messages->push_back(message); } + // TODO: Add directly to this instead when set? otherwise add to new_messages + if(room_messages) + (*room_messages)[room_data] = new_messages; + // TODO: Loop and std::move instead? doesn't insert create copies? if(message_dir == MessageDirection::BEFORE) { - size_t num_inserted_messages = room_data->prepend_messages_reverse(std::move(new_messages)); - // TODO: Is this thread-safe? - if(room_data->last_read_index != 0) - room_data->last_read_index += num_inserted_messages; + room_data->prepend_messages_reverse(std::move(new_messages)); } else if(message_dir == MessageDirection::AFTER) { room_data->append_messages(std::move(new_messages)); } @@ -860,7 +803,7 @@ namespace QuickMedia { return "m.file"; } - PluginResult Matrix::post_message(const std::string &room_id, const std::string &body, const std::optional &file_info, const std::optional &thumbnail_info) { + PluginResult Matrix::post_message(std::shared_ptr room, const std::string &body, const std::optional &file_info, const std::optional &thumbnail_info) { char random_characters[18]; if(!generate_random_characters(random_characters, sizeof(random_characters))) return PluginResult::ERR; @@ -940,7 +883,7 @@ namespace QuickMedia { }; 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()); + 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()); rapidjson::Document json_root; DownloadResult download_result = download_json(json_root, request_url, std::move(additional_args), true); @@ -987,9 +930,7 @@ namespace QuickMedia { std::string related_to_body; switch(message->type) { case MessageType::TEXT: { - if(!message->replaces_event_id.empty() && strncmp(message->body.c_str(), " * ", 3) == 0) - related_to_body = remove_reply_formatting(message->body.substr(3)); - else + if(message->related_event_type != RelatedEventType::NONE) related_to_body = remove_reply_formatting(message->body); break; } @@ -1005,6 +946,9 @@ namespace QuickMedia { case MessageType::FILE: related_to_body = "sent a file"; break; + case MessageType::REDACTION: + related_to_body = message->body; + break; } return related_to_body; } @@ -1027,13 +971,7 @@ namespace QuickMedia { } // TODO: Support greentext - PluginResult Matrix::post_reply(const std::string &room_id, const std::string &body, void *relates_to) { - auto room = get_room_by_id(room_id); - if(!room) { - fprintf(stderr, "Error: no such room: %s\n", room_id.c_str()); - return PluginResult::ERR; - } - + PluginResult Matrix::post_reply(std::shared_ptr room, const std::string &body, void *relates_to) { // TODO: Store shared_ptr instead of raw pointer... Message *relates_to_message_raw = (Message*)relates_to; std::shared_ptr relates_to_message_shared = room->get_message_by_id(relates_to_message_raw->event_id); @@ -1077,7 +1015,7 @@ namespace QuickMedia { }; 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()); + 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()); rapidjson::Document json_root; DownloadResult download_result = download_json(json_root, request_url, std::move(additional_args), true); @@ -1094,13 +1032,7 @@ namespace QuickMedia { return PluginResult::OK; } - PluginResult Matrix::post_edit(const std::string &room_id, const std::string &body, void *relates_to) { - auto room = get_room_by_id(room_id); - if(!room) { - fprintf(stderr, "Error: no such room: %s\n", room_id.c_str()); - return PluginResult::ERR; - } - + PluginResult Matrix::post_edit(std::shared_ptr room, const std::string &body, void *relates_to) { Message *relates_to_message_raw = (Message*)relates_to; std::shared_ptr relates_to_message_shared = room->get_message_by_id(relates_to_message_raw->event_id); std::shared_ptr relates_to_message_original = get_edited_message_original_message(room.get(), relates_to_message_shared); @@ -1173,7 +1105,7 @@ namespace QuickMedia { }; 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()); + 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()); rapidjson::Document json_root; DownloadResult download_result = download_json(json_root, request_url, std::move(additional_args), true); @@ -1193,10 +1125,10 @@ namespace QuickMedia { // TODO: Right now this recursively calls /rooms//context/ 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 Matrix::get_edited_message_original_message(RoomData *room_data, std::shared_ptr message) { - if(message->replaces_event_id.empty()) + if(message->related_event_type != RelatedEventType::EDIT) return message; - auto replaced_message = room_data->get_message_by_id(message->replaces_event_id); + auto replaced_message = room_data->get_message_by_id(message->related_event_id); if(!replaced_message) { rapidjson::Document request_data(rapidjson::kObjectType); request_data.AddMember("lazy_load_members", true, request_data.GetAllocator()); @@ -1237,13 +1169,16 @@ namespace QuickMedia { if(!body_json.IsString()) return nullptr; - std::string replaces_event_id; + RelatedEventType related_event_type = RelatedEventType::NONE; + std::string related_event_id; const rapidjson::Value &relates_to_json = GetMember(content_json, "m.relates_to"); if(relates_to_json.IsObject()) { const rapidjson::Value &event_id_json = GetMember(relates_to_json, "event_id"); const rapidjson::Value &rel_type_json = GetMember(relates_to_json, "rel_type"); - if(event_id_json.IsString() && rel_type_json.IsString() && strcmp(rel_type_json.GetString(), "m.replace") == 0) - replaces_event_id = event_id_json.GetString(); + if(event_id_json.IsString() && rel_type_json.IsString() && strcmp(rel_type_json.GetString(), "m.replace") == 0) { + related_event_id = event_id_json.GetString(); + related_event_type = RelatedEventType::EDIT; + } } const rapidjson::Value &content_type = GetMember(content_json, "msgtype"); @@ -1252,7 +1187,8 @@ namespace QuickMedia { auto new_message = std::make_shared(); new_message->event_id = event_id_json.GetString(); - new_message->replaces_event_id = std::move(replaces_event_id); + new_message->related_event_id = std::move(related_event_id); + new_message->related_event_type = related_event_type; if(strcmp(content_type.GetString(), "m.text") == 0) { new_message->type = MessageType::TEXT; } else if(strcmp(content_type.GetString(), "m.image") == 0) { @@ -1284,10 +1220,10 @@ namespace QuickMedia { return filename; } - PluginResult Matrix::post_file(const std::string &room_id, const std::string &filepath, std::string &err_msg) { + PluginResult Matrix::post_file(std::shared_ptr room, const std::string &filepath, std::string &err_msg) { UploadInfo file_info; UploadInfo thumbnail_info; - PluginResult upload_file_result = upload_file(room_id, filepath, file_info, thumbnail_info, err_msg); + PluginResult upload_file_result = upload_file(room, filepath, file_info, thumbnail_info, err_msg); if(upload_file_result != PluginResult::OK) return upload_file_result; @@ -1297,10 +1233,10 @@ namespace QuickMedia { thumbnail_info_opt = std::move(thumbnail_info); const char *filename = file_get_filename(filepath); - return post_message(room_id, filename, file_info_opt, thumbnail_info_opt); + return post_message(room, filename, file_info_opt, thumbnail_info_opt); } - PluginResult Matrix::upload_file(const std::string &room_id, const std::string &filepath, UploadInfo &file_info, UploadInfo &thumbnail_info, std::string &err_msg) { + PluginResult Matrix::upload_file(std::shared_ptr room, const std::string &filepath, UploadInfo &file_info, UploadInfo &thumbnail_info, std::string &err_msg) { FileAnalyzer file_analyzer; if(!file_analyzer.load_file(filepath.c_str())) { err_msg = "Failed to load " + filepath; @@ -1337,7 +1273,7 @@ namespace QuickMedia { if(tmp_file != -1) { if(video_get_first_frame(filepath.c_str(), tmp_filename)) { UploadInfo upload_info_ignored; // Ignore because it wont be set anyways. Thumbnails dont have thumbnails. - PluginResult upload_thumbnail_result = upload_file(room_id, tmp_filename, thumbnail_info, upload_info_ignored, err_msg); + PluginResult upload_thumbnail_result = upload_file(room, tmp_filename, thumbnail_info, upload_info_ignored, err_msg); if(upload_thumbnail_result != PluginResult::OK) { close(tmp_file); remove(tmp_filename); @@ -1489,7 +1425,7 @@ namespace QuickMedia { return PluginResult::OK; } - PluginResult Matrix::delete_message(const std::string &room_id, void *message, std::string &err_msg){ + PluginResult Matrix::delete_message(std::shared_ptr room, void *message, std::string &err_msg){ char random_characters[18]; if(!generate_random_characters(random_characters, sizeof(random_characters))) return PluginResult::ERR; @@ -1512,7 +1448,7 @@ namespace QuickMedia { }; char url[512]; - snprintf(url, sizeof(url), "%s/_matrix/client/r0/rooms/%s/redact/%s/m%ld.%.*s", homeserver.c_str(), room_id.c_str(), message_typed->event_id.c_str(), time(NULL), (int)random_readable_chars.size(), random_readable_chars.c_str()); + snprintf(url, sizeof(url), "%s/_matrix/client/r0/rooms/%s/redact/%s/m%ld.%.*s", homeserver.c_str(), room->id.c_str(), message_typed->event_id.c_str(), time(NULL), (int)random_readable_chars.size(), random_readable_chars.c_str()); rapidjson::Document json_root; DownloadResult download_result = download_json(json_root, url, std::move(additional_args), true, &err_msg); @@ -1595,7 +1531,7 @@ namespace QuickMedia { return PluginResult::OK; } - PluginResult Matrix::on_start_typing(const std::string &room_id) { + PluginResult Matrix::on_start_typing(std::shared_ptr room) { rapidjson::Document request_data(rapidjson::kObjectType); request_data.AddMember("typing", true, request_data.GetAllocator()); request_data.AddMember("timeout", 30000, request_data.GetAllocator()); // 30 sec timeout @@ -1612,13 +1548,13 @@ namespace QuickMedia { }; std::string server_response; - if(download_to_string(homeserver + "/_matrix/client/r0/rooms/" + room_id + "/typing/" + url_param_encode(user_id) , server_response, std::move(additional_args), use_tor, true) != DownloadResult::OK) + if(download_to_string(homeserver + "/_matrix/client/r0/rooms/" + room->id + "/typing/" + url_param_encode(user_id) , server_response, std::move(additional_args), use_tor, true) != DownloadResult::OK) return PluginResult::NET_ERR; return PluginResult::OK; } - PluginResult Matrix::on_stop_typing(const std::string &room_id) { + PluginResult Matrix::on_stop_typing(std::shared_ptr room) { rapidjson::Document request_data(rapidjson::kObjectType); request_data.AddMember("typing", false, request_data.GetAllocator()); @@ -1634,13 +1570,13 @@ namespace QuickMedia { }; std::string server_response; - if(download_to_string(homeserver + "/_matrix/client/r0/rooms/" + room_id + "/typing/" + url_param_encode(user_id), server_response, std::move(additional_args), use_tor, true) != DownloadResult::OK) + if(download_to_string(homeserver + "/_matrix/client/r0/rooms/" + room->id + "/typing/" + url_param_encode(user_id), server_response, std::move(additional_args), use_tor, true) != DownloadResult::OK) return PluginResult::NET_ERR; return PluginResult::OK; } - PluginResult Matrix::set_read_marker(const std::string &room_id, const Message *message) { + PluginResult Matrix::set_read_marker(std::shared_ptr room, const Message *message) { rapidjson::Document request_data(rapidjson::kObjectType); request_data.AddMember("m.fully_read", rapidjson::StringRef(message->event_id.c_str()), request_data.GetAllocator()); request_data.AddMember("m.read", rapidjson::StringRef(message->event_id.c_str()), request_data.GetAllocator()); @@ -1658,7 +1594,7 @@ namespace QuickMedia { }; std::string server_response; - if(download_to_string(homeserver + "/_matrix/client/r0/rooms/" + room_id + "/read_markers", server_response, std::move(additional_args), use_tor, true) != DownloadResult::OK) + if(download_to_string(homeserver + "/_matrix/client/r0/rooms/" + room->id + "/read_markers", server_response, std::move(additional_args), use_tor, true) != DownloadResult::OK) return PluginResult::NET_ERR; return PluginResult::OK; @@ -1706,12 +1642,7 @@ namespace QuickMedia { return PluginResult::OK; } - std::shared_ptr Matrix::get_me(const std::string &room_id) { - auto room = get_room_by_id(room_id); - if(!room) { - fprintf(stderr, "Error: no such room: %s\n", room_id.c_str()); - return nullptr; - } + std::shared_ptr Matrix::get_me(std::shared_ptr room) { return room->get_user_by_id(user_id); } -- cgit v1.2.3