From d123c41cd3ad4f0d55ae134be69e7ffd144dbb74 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Tue, 18 May 2021 22:05:19 +0200 Subject: Add mention autocomplete --- src/plugins/Matrix.cpp | 244 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 195 insertions(+), 49 deletions(-) (limited to 'src/plugins/Matrix.cpp') diff --git a/src/plugins/Matrix.cpp b/src/plugins/Matrix.cpp index 1d471fc..db31303 100644 --- a/src/plugins/Matrix.cpp +++ b/src/plugins/Matrix.cpp @@ -99,7 +99,7 @@ namespace QuickMedia { return std::abs(hash); } - static sf::Color user_id_to_color(const std::string &user_id) { + sf::Color user_id_to_color(const std::string &user_id) { const int num_colors = 8; const sf::Color colors[num_colors] = { sf::Color(54, 139, 214), @@ -115,13 +115,13 @@ namespace QuickMedia { } UserInfo::UserInfo(RoomData *room, std::string user_id) : - room(room), display_name_color(user_id_to_color(user_id)), user_id(user_id), resolve_state(UserResolveState::NOT_RESOLVED) + room(room), display_name_color(user_id_to_color(user_id)), user_id(user_id) { display_name = user_id; } UserInfo::UserInfo(RoomData *room, std::string user_id, std::string display_name, std::string avatar_url) : - room(room), display_name_color(user_id_to_color(user_id)), user_id(std::move(user_id)), resolve_state(UserResolveState::RESOLVED), display_name(std::move(display_name)), avatar_url(std::move(avatar_url)) { + room(room), display_name_color(user_id_to_color(user_id)), user_id(std::move(user_id)), display_name(std::move(display_name)), avatar_url(std::move(avatar_url)) { } @@ -168,13 +168,11 @@ namespace QuickMedia { user->display_name = std::move(display_name); if(user->display_name.empty()) user->display_name = user->user_id; - user->resolve_state = UserResolveState::RESOLVED; } void RoomData::set_user_avatar_url(std::shared_ptr &user, std::string avatar_url) { std::lock_guard lock(user_mutex); user->avatar_url = std::move(avatar_url); - user->resolve_state = UserResolveState::RESOLVED; } size_t RoomData::prepend_messages_reverse(const std::vector> &new_messages) { @@ -219,6 +217,16 @@ namespace QuickMedia { return message_it->second; } + std::vector> RoomData::get_users() { + std::lock_guard lock(user_mutex); + std::vector> users(user_info_by_user_id.size()); + size_t i = 0; + for(auto &[user_id, user] : user_info_by_user_id) { + users[i++] = user; + } + return users; + } + std::vector> RoomData::get_users_excluding_me(const std::string &my_user_id) { std::lock_guard lock(user_mutex); std::vector> users_excluding_me; @@ -1697,9 +1705,24 @@ namespace QuickMedia { //auto user_info = std::make_shared(room_data, user_id, std::move(display_name), std::move(avatar_url)); // Overwrites user data //room_data->add_user(user_info); - auto user_info = get_user_by_id(room_data, user_id); - room_data->set_user_display_name(user_info, std::move(display_name)); - room_data->set_user_avatar_url(user_info, std::move(avatar_url)); + bool is_new_user; + auto user_info = get_user_by_id(room_data, user_id, &is_new_user); + room_data->set_user_display_name(user_info, display_name); + room_data->set_user_avatar_url(user_info, avatar_url); + + MatrixEventUserInfo event_user_info; + event_user_info.user_id = user_id; + event_user_info.display_name = display_name; + event_user_info.avatar_url = avatar_url; + + if(is_new_user) { + auto event = std::make_unique(std::move(event_user_info)); + trigger_event(room_data, std::move(event)); + } else { + auto event = std::make_unique(std::move(event_user_info)); + trigger_event(room_data, std::move(event)); + } + return user_info; } @@ -1945,16 +1968,28 @@ namespace QuickMedia { if(!content_json->IsObject()) return nullptr; - auto user = get_user_by_id(room_data, sender_json_str); - if(!user) { - // Note: this is important because otherwise replying and such is broken - fprintf(stderr, "Warning: skipping unknown user: %s\n", sender_json_str.c_str()); - return nullptr; + bool is_new_user; + auto user = get_user_by_id(room_data, sender_json_str, &is_new_user); + + if(is_new_user) { + MatrixEventUserInfo user_info; + user_info.user_id = user->user_id; + auto event = std::make_unique(std::move(user_info)); + trigger_event(room_data, std::move(event)); } auto user_sender = user; - if(sent_by_somebody_else) - user_sender = get_user_by_id(room_data, sender_json_orig->GetString()); + if(sent_by_somebody_else) { + bool is_new_user; + user_sender = get_user_by_id(room_data, sender_json_orig->GetString(), &is_new_user); + + if(is_new_user) { + MatrixEventUserInfo user_info; + user_info.user_id = user_sender->user_id; + auto event = std::make_unique(std::move(user_info)); + trigger_event(room_data, std::move(event)); + } + } time_t timestamp = 0; const rapidjson::Value &origin_server_ts = GetMember(event_item_json, "origin_server_ts"); @@ -2053,6 +2088,10 @@ namespace QuickMedia { const rapidjson::Value &new_displayname_json = GetMember(*content_json, "displayname"); const rapidjson::Value &new_avatar_url_json = GetMember(*content_json, "avatar_url"); const rapidjson::Value &prev_membership_json = GetMember(prev_content_json, "membership"); + + std::optional new_display_name; + std::optional new_avatar_url; + if(prev_membership_json.IsString() && strcmp(prev_membership_json.GetString(), "leave") == 0) { body = user_display_name + " joined the room"; } else if(new_displayname_json.IsString() && new_displayname_json.GetStringLength() > 0 && (!prev_displayname_json.IsString() || strcmp(new_displayname_json.GetString(), prev_displayname_json.GetString()) != 0)) { @@ -2063,22 +2102,36 @@ namespace QuickMedia { else prev_displayname_str = sender_json_str; body = extract_first_line_remove_newline_elipses(prev_displayname_str, AUTHOR_MAX_LENGTH) + " changed their display name to " + extract_first_line_remove_newline_elipses(new_displayname_str, AUTHOR_MAX_LENGTH); + new_display_name = new_displayname_str; room_data->set_user_display_name(user, std::move(new_displayname_str)); } else if((!new_displayname_json.IsString() || new_displayname_json.GetStringLength() == 0) && prev_displayname_json.IsString()) { body = user_display_name + " removed their display name"; + new_display_name = ""; room_data->set_user_display_name(user, ""); } else if(new_avatar_url_json.IsString() && new_avatar_url_json.GetStringLength() > 0 && (!prev_avatar_url_json.IsString() || strcmp(new_avatar_url_json.GetString(), prev_avatar_url_json.GetString()) != 0)) { body = user_display_name + " changed their profile picture"; std::string new_avatar_url_str = thumbnail_url_extract_media_id(new_avatar_url_json.GetString()); if(!new_avatar_url_str.empty()) new_avatar_url_str = get_thumbnail_url(homeserver, new_avatar_url_str); // TODO: Remove the constant strings around to reduce memory usage (6.3mb) + new_avatar_url = new_avatar_url_str; room_data->set_user_avatar_url(user, std::move(new_avatar_url_str)); } else if((!new_avatar_url_json.IsString() || new_avatar_url_json.GetStringLength() == 0) && prev_avatar_url_json.IsString()) { body = user_display_name + " removed their profile picture"; + new_avatar_url = ""; room_data->set_user_avatar_url(user, ""); } else { body = user_display_name + " joined the room"; } + + if(new_display_name || new_avatar_url) { + MatrixEventUserInfo user_info; + user_info.user_id = user->user_id; + user_info.display_name = std::move(new_display_name); + user_info.avatar_url = std::move(new_avatar_url); + + auto event = std::make_unique(std::move(user_info)); + trigger_event(room_data, std::move(event)); + } } else { body = user_display_name + " joined the room"; } @@ -2483,6 +2536,8 @@ namespace QuickMedia { const rapidjson::Value &membership_json = GetMember(content_json, "membership"); if(membership_json.IsString() && strcmp(membership_json.GetString(), "invite") == 0) { + // TODO: Check this this room should be saved in the rooms list, which might be needed if the server doesn't give a non-invite events + // for the same data (user display name update, etc) Invite invite; RoomData invite_room; events_add_user_info(events_json, &invite_room); @@ -2490,10 +2545,6 @@ namespace QuickMedia { std::string sender_json_str(sender_json.GetString(), sender_json.GetStringLength()); auto invited_by = get_user_by_id(&invite_room, sender_json_str); - if(!invited_by) { - fprintf(stderr, "Invited by unknown user. Bug in homeserver?\n"); - break; - } set_room_info_to_users_if_empty(&invite_room, sender_json_str); @@ -2721,11 +2772,68 @@ namespace QuickMedia { } } - static std::string body_to_formatted_body(const std::string &body) { + void Matrix::replace_mentions(RoomData *room, std::string &text) { + size_t index = 0; + while(index < text.size()) { + index = text.find('@', index); + if(index == std::string::npos) + return; + + bool is_valid_user_id = false; + bool user_id_finished = false; + size_t user_id_start = index; + size_t user_id_end = 0; + index += 1; + for(size_t i = index; i < text.size() && !user_id_finished; ++i) { + char c = text[i]; + switch(c) { + case ':': { + if(is_valid_user_id) { + user_id_finished = true; + user_id_end = i; + index = i; + } + is_valid_user_id = true; + break; + } + case ' ': + case '\n': + case '\r': + case '\t': + case '@': { + user_id_finished = true; + user_id_end = i; + index = i; + break; + } + } + } + + if(user_id_end == 0) + user_id_end = text.size(); + + if(is_valid_user_id) { + std::string user_id = text.substr(user_id_start, user_id_end - user_id_start); + auto user = get_user_by_id(room, user_id, nullptr, false); + if(user) { + std::string user_id_escaped = user_id; + html_escape_sequences(user_id_escaped); + + std::string display_name_escaped = room->get_user_display_name(user); + html_escape_sequences(display_name_escaped); + + std::string mention_text = "" + display_name_escaped + ""; + text.replace(user_id_start, user_id.size(), mention_text); + } + } + } + } + + std::string Matrix::body_to_formatted_body(RoomData *room, const std::string &body) { std::string formatted_body; bool is_inside_code_block = false; bool is_first_line = true; - string_split(body, '\n', [&formatted_body, &is_inside_code_block, &is_first_line](const char *str, size_t size){ + string_split(body, '\n', [this, room, &formatted_body, &is_inside_code_block, &is_first_line](const char *str, size_t size){ if(!is_first_line) formatted_body += "
"; @@ -2747,13 +2855,16 @@ namespace QuickMedia { } else { if(!is_inside_code_block && size > 0 && str[0] == '>') { formatted_body += ""; + replace_mentions(room, line_str); formatted_body_add_line(formatted_body, line_str); formatted_body += ""; } else { - if(is_inside_code_block) + if(is_inside_code_block) { formatted_body += line_str; - else + } else { + replace_mentions(room, line_str); formatted_body_add_line(formatted_body, line_str); + } } is_first_line = false; } @@ -2772,7 +2883,7 @@ namespace QuickMedia { std::string formatted_body; if(!file_info) - formatted_body = body_to_formatted_body(body); + formatted_body = body_to_formatted_body(room, body); rapidjson::Document request_data(rapidjson::kObjectType); if(msgtype.empty()) @@ -2919,8 +3030,8 @@ namespace QuickMedia { return ""; } - static std::string create_formatted_body_for_message_reply(RoomData *room, const Message *message, const std::string &body) { - std::string formatted_body = body_to_formatted_body(body); + std::string Matrix::create_formatted_body_for_message_reply(RoomData *room, const Message *message, const std::string &body) { + std::string formatted_body = body_to_formatted_body(room, body); std::string related_to_body = get_reply_message(message); html_escape_sequences(related_to_body); // TODO: Add keybind to navigate to the reply message, which would also depend on this formatting. @@ -3001,7 +3112,7 @@ namespace QuickMedia { return PluginResult::ERR; my_events_transaction_ids.insert(transaction_id); - std::string formatted_body = body_to_formatted_body(body); + std::string formatted_body = body_to_formatted_body(room, body); rapidjson::Document new_content_json(rapidjson::kObjectType); new_content_json.AddMember("msgtype", "m.text", new_content_json.GetAllocator()); @@ -3908,34 +4019,25 @@ namespace QuickMedia { delegate->clear_data(); } - std::shared_ptr Matrix::get_user_by_id(RoomData *room, const std::string &user_id) { + std::shared_ptr Matrix::get_user_by_id(RoomData *room, const std::string &user_id, bool *is_new_user, bool create_if_not_found) { auto user = room->get_user_by_id(user_id); - if(user) + if(user) { + if(is_new_user) + *is_new_user = false; return user; + } + + if(!create_if_not_found) + return nullptr; //fprintf(stderr, "Unknown user: %s, creating locally... synapse bug?\n", user_id.c_str()); auto user_info = std::make_shared(room, user_id); room->add_user(user_info); + if(is_new_user) + *is_new_user = true; return user_info; } - void Matrix::update_user_with_latest_state(RoomData *room, const std::string &user_id) { - char url[512]; - snprintf(url, sizeof(url), "%s/_matrix/client/r0/profile/%s", homeserver.c_str(), user_id.c_str()); - - rapidjson::Document json_root; - DownloadResult download_result = download_json(json_root, url, {}, true); - if(download_result != DownloadResult::OK || !json_root.IsObject()) { - fprintf(stderr, "Fetching profile for user %s failed!\n", user_id.c_str()); - auto user = get_user_by_id(room, user_id); - assert(user); - user->resolve_state = UserResolveState::RESOLVED; - return; - } - - parse_user_info(json_root, user_id, room); - } - void Matrix::update_room_users(RoomData *room) { #if 1 std::vector additional_args = { @@ -3964,7 +4066,8 @@ namespace QuickMedia { const rapidjson::Value &display_name_json = GetMember(joined_obj.value, "display_name"); const rapidjson::Value &displayname_json = GetMember(joined_obj.value, "displayname"); // Construct bug... std::string user_id(joined_obj.name.GetString(), joined_obj.name.GetStringLength()); - auto user = get_user_by_id(room, user_id); + bool is_new_user; + auto user = get_user_by_id(room, user_id, &is_new_user); assert(user); std::string display_name; @@ -3980,8 +4083,21 @@ namespace QuickMedia { avatar_url = std::string(avatar_url_json.GetString(), avatar_url_json.GetStringLength()); if(!avatar_url.empty()) avatar_url = get_thumbnail_url(homeserver, thumbnail_url_extract_media_id(avatar_url)); // TODO: Remove the constant strings around to reduce memory usage (6.3mb) - room->set_user_avatar_url(user, std::move(avatar_url)); - room->set_user_display_name(user, std::move(display_name)); + room->set_user_avatar_url(user, avatar_url); + room->set_user_display_name(user, display_name); + + MatrixEventUserInfo user_info; + user_info.user_id = user_id; + user_info.display_name = display_name; + user_info.avatar_url = avatar_url; + + if(is_new_user) { + auto event = std::make_unique(std::move(user_info)); + trigger_event(room, std::move(event)); + } else { + auto event = std::make_unique(std::move(user_info)); + trigger_event(room, std::move(event)); + } } #else std::vector additional_args = { @@ -4051,4 +4167,34 @@ namespace QuickMedia { return INITIAL_FILTER; #endif } + + void Matrix::enable_event_queue(RoomData *room) { + std::lock_guard lock(event_queue_mutex); + assert(!current_event_queue_room); + current_event_queue_room = room; + } + + void Matrix::disable_event_queue() { + std::lock_guard lock(event_queue_mutex); + assert(current_event_queue_room); + current_event_queue_room = nullptr; + event_queue.clear(); + } + + std::unique_ptr Matrix::pop_event() { + std::lock_guard lock(event_queue_mutex); + if(!current_event_queue_room || event_queue.empty()) + return nullptr; + + auto event_data = std::move(event_queue.front()); + event_queue.pop_front(); + return event_data; + } + + void Matrix::trigger_event(RoomData *room, std::unique_ptr event) { + std::lock_guard lock(event_queue_mutex); + if(sync_is_cache || !current_event_queue_room || current_event_queue_room != room) + return; + event_queue.push_back(std::move(event)); + } } \ No newline at end of file -- cgit v1.2.3