diff options
author | dec05eba <dec05eba@protonmail.com> | 2021-08-01 03:09:19 +0200 |
---|---|---|
committer | dec05eba <dec05eba@protonmail.com> | 2021-08-01 03:09:19 +0200 |
commit | 738411fcb52f2dce050f200eeff307c7a0a1f13c (patch) | |
tree | e0162daa3a86f43769e502a86033a388bcefba6b | |
parent | aead89cca00cba7bef8752163752f6a9c213ae6f (diff) |
Matrix: implement power level change messages
-rw-r--r-- | TODO | 4 | ||||
-rw-r--r-- | plugins/Matrix.hpp | 2 | ||||
-rw-r--r-- | src/Body.cpp | 8 | ||||
-rw-r--r-- | src/QuickMedia.cpp | 11 | ||||
-rw-r--r-- | src/plugins/Matrix.cpp | 147 |
5 files changed, 132 insertions, 40 deletions
@@ -185,4 +185,6 @@ Sort users in matrix users list (efficiently and only once!). Optimize matrix insert position for room sorting (and user sorting). Cache solved 4chan captcha solution (and challenge id) on disk. ttl is (always?) 120 seconds so restarting quickmedia under that period should use the already solved captcha. Posting on bant doesn't work for some reason. The request is sent successfully but the comment never appears on the website. -4chan_pass cookie is needed to get captcha without slider. How to get this without an account? 4chan website sets this for anon too.
\ No newline at end of file +4chan_pass cookie is needed to get captcha without slider. How to get this without an account? 4chan website sets this for anon too. +Handle replaces_state in matrix to get correct order for states? +Handle users_default power level in matrix.
\ No newline at end of file diff --git a/plugins/Matrix.hpp b/plugins/Matrix.hpp index fad90da..dd633e5 100644 --- a/plugins/Matrix.hpp +++ b/plugins/Matrix.hpp @@ -44,10 +44,12 @@ namespace QuickMedia { REDACTION, REACTION, MEMBERSHIP, + SYSTEM, UNIMPLEMENTED }; bool is_visual_media_message_type(MessageType message_type); + bool is_system_message_type(MessageType message_type); enum class RelatedEventType { NONE, 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<Message*>(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<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; |