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/Body.cpp | 8 +-- src/QuickMedia.cpp | 11 +++- src/plugins/Matrix.cpp | 147 +++++++++++++++++++++++++++++++++++++------------ 3 files changed, 127 insertions(+), 39 deletions(-) (limited to 'src') diff --git a/src/Body.cpp b/src/Body.cpp index 693086e..0804399 100644 --- a/src/Body.cpp +++ b/src/Body.cpp @@ -806,7 +806,7 @@ namespace QuickMedia { } void Body::update_dirty_state(BodyItem *body_item, float width) { - if(body_item->dirty || (body_size_changed && body_item->title_text)) { + if((body_item->dirty && !body_item->get_title().empty()) || (body_size_changed && body_item->title_text)) { body_item->dirty = false; // TODO: Find a way to optimize fromUtf8 sf::String str = sf::String::fromUtf8(body_item->get_title().begin(), body_item->get_title().end()); @@ -820,7 +820,7 @@ namespace QuickMedia { body_item->title_text->updateGeometry(); } - if(body_item->dirty_description || (body_size_changed && body_item->description_text)) { + if((body_item->dirty_description && !body_item->get_description().empty()) || (body_size_changed && body_item->description_text)) { body_item->dirty_description = false; sf::String str = sf::String::fromUtf8(body_item->get_description().begin(), body_item->get_description().end()); if(body_item->description_text) { @@ -833,7 +833,7 @@ namespace QuickMedia { body_item->description_text->updateGeometry(); } - if(body_item->dirty_author || (body_size_changed && body_item->author_text)) { + if((body_item->dirty_author && !body_item->get_author().empty()) || (body_size_changed && body_item->author_text)) { body_item->dirty_author = false; sf::String str = sf::String::fromUtf8(body_item->get_author().begin(), body_item->get_author().end()); if(body_item->author_text) { @@ -846,7 +846,7 @@ namespace QuickMedia { body_item->author_text->updateGeometry(); } - if(body_item->dirty_timestamp || (body_size_changed && body_item->timestamp_text)) { + if((body_item->dirty_timestamp && body_item->get_timestamp() != 0) || (body_size_changed && body_item->timestamp_text)) { body_item->dirty_timestamp = false; if(body_item->get_timestamp() != 0) { diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index 4a3003b..286e766 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -4526,6 +4526,12 @@ namespace QuickMedia { body_item->userdata = (void*)message; // Note: message has to be valid as long as body_item is used! if(message->related_event_type == RelatedEventType::REDACTION || message->related_event_type == RelatedEventType::EDIT || message->related_event_type == RelatedEventType::REACTION) body_item->visible = false; + if(is_system_message_type(message->type)) { + body_item->set_author("System"); + body_item->set_author_color(get_current_theme().text_color); + body_item->set_description_color(get_current_theme().faded_text_color); + body_item->thumbnail_url.clear(); + } if(message->user->user_id != my_user_id && (message_contains_user_mention(body_item->get_description(), my_display_name) || message_contains_user_mention(body_item->get_description(), my_user_id))) body_item->set_description_color(get_current_theme().attention_alert_text_color); return body_item; @@ -4577,6 +4583,9 @@ namespace QuickMedia { }; static void user_update_display_info(BodyItem *body_item, RoomData *room, Message *message) { + if(is_system_message_type(message->type)) + return; + body_item->set_author(extract_first_line_remove_newline_elipses(room->get_user_display_name(message->user), AUTHOR_MAX_LENGTH)); if(!is_visual_media_message_type(message->type)) { @@ -5413,7 +5422,7 @@ namespace QuickMedia { tabs[MESSAGES_TAB_INDEX].body->body_item_merge_handler = [](BodyItem *prev_item, BodyItem *this_item) { Message *message = static_cast(this_item->userdata); - if(!message || !prev_item || !prev_item->userdata) + if(!message || !prev_item || !prev_item->userdata || is_system_message_type(message->type)) return false; if(is_visual_media_message_type(message->type) && !this_item->thumbnail_url.empty()) 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