From 738411fcb52f2dce050f200eeff307c7a0a1f13c Mon Sep 17 00:00:00 2001 From: dec05eba Date: Sun, 1 Aug 2021 03:09:19 +0200 Subject: Matrix: implement power level change messages --- src/plugins/Matrix.cpp | 147 +++++++++++++++++++++++++++++++++++++------------ 1 file changed, 113 insertions(+), 34 deletions(-) (limited to 'src/plugins/Matrix.cpp') diff --git a/src/plugins/Matrix.cpp b/src/plugins/Matrix.cpp index ca211e1..2e758c5 100644 --- a/src/plugins/Matrix.cpp +++ b/src/plugins/Matrix.cpp @@ -25,43 +25,42 @@ // TODO: Verify if buffer of size 512 is enough for endpoints // Remove older messages (outside screen) to save memory. Reload them when the selected body item is the top/bottom one. -static const char* SERVICE_NAME = "matrix"; -static const char* OTHERS_ROOM_TAG = "tld.name.others"; -// Filter without account data -static const char* INITIAL_FILTER = "{\"presence\":{\"limit\":0,\"types\":[\"\"]},\"account_data\":{\"limit\":0,\"types\":[\"\"]},\"room\":{\"state\":{\"not_types\":[\"m.room.related_groups\",\"m.room.power_levels\",\"m.room.join_rules\",\"m.room.history_visibility\"],\"lazy_load_members\":true},\"timeline\":{\"types\":[\"m.room.message\"],\"limit\":1,\"lazy_load_members\":true},\"ephemeral\":{\"limit\":0,\"types\":[\"\"],\"lazy_load_members\":true},\"account_data\":{\"limit\":1,\"types\":[\"m.fully_read\",\"m.tag\",\"qm.last_read_message_timestamp\"],\"lazy_load_members\":true}}}"; -static const char* ADDITIONAL_MESSAGES_FILTER = "{\"presence\":{\"limit\":0,\"types\":[\"\"]},\"account_data\":{\"limit\":0,\"types\":[\"\"]},\"room\":{\"state\":{\"not_types\":[\"m.room.related_groups\",\"m.room.power_levels\",\"m.room.join_rules\",\"m.room.history_visibility\"],\"lazy_load_members\":true},\"timeline\":{\"limit\":20,\"lazy_load_members\":true},\"ephemeral\":{\"limit\":0,\"types\":[\"\"],\"lazy_load_members\":true},\"account_data\":{\"limit\":0,\"types\":[\"\"],\"lazy_load_members\":true}}}"; -static const char* CONTINUE_FILTER = "{\"presence\":{\"limit\":0,\"types\":[\"\"]},\"account_data\":{\"limit\":0,\"types\":[\"\"]},\"room\":{\"state\":{\"not_types\":[\"m.room.related_groups\",\"m.room.power_levels\",\"m.room.join_rules\",\"m.room.history_visibility\"],\"lazy_load_members\":true},\"timeline\":{\"lazy_load_members\":true},\"ephemeral\":{\"limit\":0,\"types\":[\"\"],\"lazy_load_members\":true},\"account_data\":{\"types\":[\"m.fully_read\",\"m.tag\",\"qm.last_read_message_timestamp\"],\"lazy_load_members\":true}}}"; - -static std::string capitalize(const std::string &str) { - if(str.size() >= 1) - return QuickMedia::to_upper(str[0]) + str.substr(1); - else - return ""; -} - -// TODO: According to spec: "Any tag in the tld.name.* form but not matching the namespace of the current client should be ignored", -// should we follow this? -static std::string tag_get_name(const std::string &tag) { - if(tag.size() >= 2 && memcmp(tag.data(), "m.", 2) == 0) { - if(strcmp(tag.c_str() + 2, "favourite") == 0) - return "Favorites"; - else if(strcmp(tag.c_str() + 2, "lowpriority") == 0) - return "Low priority"; - else if(strcmp(tag.c_str() + 2, "server_notice") == 0) - return "Server notice"; +namespace QuickMedia { + static const sf::Vector2i thumbnail_max_size(600, 337); + static const char* SERVICE_NAME = "matrix"; + static const char* OTHERS_ROOM_TAG = "tld.name.others"; + // Filter without account data + static const char* INITIAL_FILTER = "{\"presence\":{\"limit\":0,\"types\":[\"\"]},\"account_data\":{\"limit\":0,\"types\":[\"\"]},\"room\":{\"state\":{\"not_types\":[\"m.room.related_groups\",\"m.room.power_levels\",\"m.room.join_rules\",\"m.room.history_visibility\"],\"lazy_load_members\":true},\"timeline\":{\"types\":[\"m.room.message\"],\"limit\":1,\"lazy_load_members\":true},\"ephemeral\":{\"limit\":0,\"types\":[\"\"],\"lazy_load_members\":true},\"account_data\":{\"limit\":1,\"types\":[\"m.fully_read\",\"m.tag\",\"qm.last_read_message_timestamp\"],\"lazy_load_members\":true}}}"; + static const char* ADDITIONAL_MESSAGES_FILTER = "{\"presence\":{\"limit\":0,\"types\":[\"\"]},\"account_data\":{\"limit\":0,\"types\":[\"\"]},\"room\":{\"state\":{\"not_types\":[\"m.room.related_groups\",\"m.room.join_rules\",\"m.room.history_visibility\"],\"lazy_load_members\":true},\"timeline\":{\"limit\":20,\"lazy_load_members\":true},\"ephemeral\":{\"limit\":0,\"types\":[\"\"],\"lazy_load_members\":true},\"account_data\":{\"limit\":0,\"types\":[\"\"],\"lazy_load_members\":true}}}"; + static const char* CONTINUE_FILTER = "{\"presence\":{\"limit\":0,\"types\":[\"\"]},\"account_data\":{\"limit\":0,\"types\":[\"\"]},\"room\":{\"state\":{\"not_types\":[\"m.room.related_groups\",\"m.room.join_rules\",\"m.room.history_visibility\"],\"lazy_load_members\":true},\"timeline\":{\"lazy_load_members\":true},\"ephemeral\":{\"limit\":0,\"types\":[\"\"],\"lazy_load_members\":true},\"account_data\":{\"types\":[\"m.fully_read\",\"m.tag\",\"qm.last_read_message_timestamp\"],\"lazy_load_members\":true}}}"; + + static std::string capitalize(const std::string &str) { + if(str.size() >= 1) + return QuickMedia::to_upper(str[0]) + str.substr(1); else - return capitalize(tag.substr(2)); - } else if(tag.size() >= 2 && memcmp(tag.data(), "u.", 2) == 0) { - return capitalize(tag.substr(2)); - } else if(tag.size() >= 9 && memcmp(tag.data(), "tld.name.", 9) == 0) { - return capitalize(tag.substr(9)); - } else { - return ""; + return ""; } -} -namespace QuickMedia { - static const sf::Vector2i thumbnail_max_size(600, 337); + // TODO: According to spec: "Any tag in the tld.name.* form but not matching the namespace of the current client should be ignored", + // should we follow this? + static std::string tag_get_name(const std::string &tag) { + if(tag.size() >= 2 && memcmp(tag.data(), "m.", 2) == 0) { + if(strcmp(tag.c_str() + 2, "favourite") == 0) + return "Favorites"; + else if(strcmp(tag.c_str() + 2, "lowpriority") == 0) + return "Low priority"; + else if(strcmp(tag.c_str() + 2, "server_notice") == 0) + return "Server notice"; + else + return capitalize(tag.substr(2)); + } else if(tag.size() >= 2 && memcmp(tag.data(), "u.", 2) == 0) { + return capitalize(tag.substr(2)); + } else if(tag.size() >= 9 && memcmp(tag.data(), "tld.name.", 9) == 0) { + return capitalize(tag.substr(9)); + } else { + return ""; + } + } std::string extract_first_line_remove_newline_elipses(const std::string &str, size_t max_length) { std::string result = str; @@ -132,6 +131,10 @@ namespace QuickMedia { return message_type == MessageType::VIDEO || message_type == MessageType::IMAGE; } + bool is_system_message_type(MessageType message_type) { + return message_type >= MessageType::MEMBERSHIP && message_type <= MessageType::SYSTEM; + } + std::shared_ptr RoomData::get_user_by_id(const std::string &user_id) { std::lock_guard lock(room_mutex); auto user_it = user_info_by_user_id.find(user_id); @@ -2038,6 +2041,41 @@ namespace QuickMedia { return num_new_messages; } + // TODO: Custom names for power levels + static std::string power_level_to_name(int power_level) { + switch(power_level) { + case 0: + return "Default"; + case 50: + return "Moderator"; + case 100: + return "Administrator"; + default: + return "Custom (" + std::to_string(power_level) + ")"; + } + } + + struct UserPowerLevelChange { + int new_power_level = 0; + int old_power_level = 0; + }; + + // TODO: Use user display names instead of id. Also update display names after retrieving them + static std::string power_levels_change_to_string(RoomData *room, const std::shared_ptr &changed_by, const std::map &power_levels_change) { + const std::string changed_by_name = room->get_user_display_name(changed_by); + std::string result; + for(const auto &change : power_levels_change) { + if(change.second.new_power_level == change.second.old_power_level) + continue; + + if(!result.empty()) + result += '\n'; + + result += changed_by_name + " changed the power level of " + change.first + " from " + power_level_to_name(change.second.old_power_level) + " to " + power_level_to_name(change.second.new_power_level); + } + return result; + } + std::shared_ptr Matrix::parse_message_event(const rapidjson::Value &event_item_json, RoomData *room_data) { if(!event_item_json.IsObject()) return nullptr; @@ -2300,6 +2338,47 @@ namespace QuickMedia { message->related_event_type = related_event_type; message->timestamp = timestamp; message->transaction_id = std::move(transaction_id); + return message; + } else if(strcmp(type_json.GetString(), "m.room.power_levels") == 0) { + const rapidjson::Value &users_json = GetMember(*content_json, "users"); + if(!users_json.IsObject()) + return nullptr; + + std::map power_level_changes; + for(auto const &user_json : users_json.GetObject()) { + if(!user_json.name.IsString() || !user_json.value.IsInt()) + continue; + power_level_changes[std::string(user_json.name.GetString(), user_json.name.GetStringLength())].new_power_level = user_json.value.GetInt(); + } + + if(unsigned_json.IsObject()) { + // TODO: What about top level prev_content? + const rapidjson::Value &unsigned_prev_content_json = GetMember(unsigned_json, "prev_content"); + if(unsigned_prev_content_json.IsObject()) { + const rapidjson::Value &prev_content_users_json = GetMember(unsigned_prev_content_json, "users"); + if(prev_content_users_json.IsObject()) { + for(auto const &user_json : prev_content_users_json.GetObject()) { + if(!user_json.name.IsString() || !user_json.value.IsInt()) + continue; + power_level_changes[std::string(user_json.name.GetString(), user_json.name.GetStringLength())].old_power_level = user_json.value.GetInt(); + } + } + } + } + + auto message = std::make_shared(); + message->type = MessageType::SYSTEM; + message->user = user; + message->event_id = event_id_str; + message->body = power_levels_change_to_string(room_data, user_sender, power_level_changes); + message->related_event_id = std::move(related_event_id); + message->related_event_type = related_event_type; + message->timestamp = timestamp; + message->transaction_id = std::move(transaction_id); + + if(message->body.empty()) + return nullptr; + return message; } else { auto message = std::make_shared(); -- cgit v1.2.3