aboutsummaryrefslogtreecommitdiff
path: root/src/plugins
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2021-08-01 03:09:19 +0200
committerdec05eba <dec05eba@protonmail.com>2021-08-01 03:09:19 +0200
commit738411fcb52f2dce050f200eeff307c7a0a1f13c (patch)
treee0162daa3a86f43769e502a86033a388bcefba6b /src/plugins
parentaead89cca00cba7bef8752163752f6a9c213ae6f (diff)
Matrix: implement power level change messages
Diffstat (limited to 'src/plugins')
-rw-r--r--src/plugins/Matrix.cpp147
1 files changed, 113 insertions, 34 deletions
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<UserInfo> RoomData::get_user_by_id(const std::string &user_id) {
std::lock_guard<std::recursive_mutex> 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<UserInfo> &changed_by, const std::map<std::string, UserPowerLevelChange> &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<Message> Matrix::parse_message_event(const rapidjson::Value &event_item_json, RoomData *room_data) {
if(!event_item_json.IsObject())
return nullptr;
@@ -2301,6 +2339,47 @@ namespace QuickMedia {
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<std::string, UserPowerLevelChange> 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>();
+ 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<Message>();
message->type = MessageType::UNIMPLEMENTED;