aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2023-01-12 22:54:07 +0100
committerdec05eba <dec05eba@protonmail.com>2023-01-12 23:04:58 +0100
commit3bfa7ea4beac7710ac5484c46ce181027131ebf8 (patch)
tree6b54dedf9c38d498062e796fc2b7d9e22c59bd8d /src
parentd0c13982cba9a3d04f0d3f4d2178b244c9b61ce9 (diff)
Matrix: do not trust synapse when it comes to unread messages
Diffstat (limited to 'src')
-rw-r--r--src/QuickMedia.cpp1
-rw-r--r--src/plugins/Matrix.cpp154
2 files changed, 107 insertions, 48 deletions
diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp
index 9e360b7..c2b2b95 100644
--- a/src/QuickMedia.cpp
+++ b/src/QuickMedia.cpp
@@ -7788,7 +7788,6 @@ namespace QuickMedia {
current_room->body_item->set_title_color(get_theme().text_color);
current_room->last_message_read = true;
// TODO: Maybe set this instead when the mention is visible on the screen?
- current_room->unread_notification_count = 0;
Message *read_message = get_latest_message_in_edit_chain(static_cast<Message*>(body_items[last_timeline_message]->userdata));
// TODO: What if two messages have the same timestamp?
diff --git a/src/plugins/Matrix.cpp b/src/plugins/Matrix.cpp
index 0da73eb..1734fb0 100644
--- a/src/plugins/Matrix.cpp
+++ b/src/plugins/Matrix.cpp
@@ -648,13 +648,21 @@ namespace QuickMedia {
if(message->notification_mentions_me) {
std::string body = remove_reply_formatting(matrix, message->body);
bool read = true;
+ bool count_unread = true;
+
// TODO: What if the message or username begins with "-"? also make the notification image be the avatar of the user
if((!is_window_focused || room != current_room) && message->related_event_type != RelatedEventType::EDIT && message->related_event_type != RelatedEventType::REDACTION) {
- if(notifications_shown.insert(message->event_id).second)
+ if(notifications_shown.insert(message->event_id).second) {
show_notification("QuickMedia matrix - " + extract_first_line_remove_newline_elipses(matrix->message_get_author_displayname(message.get()), AUTHOR_MAX_LENGTH) + " (" + room->get_name() + ")", body);
+ } else {
+ count_unread = false;
+ }
read = false;
}
+ if(count_unread)
+ ++unread_mention_count_by_room[room];
+
MatrixNotification notification;
notification.room = room;
notification.event_id = message->event_id;
@@ -694,8 +702,11 @@ namespace QuickMedia {
}
void MatrixQuickMedia::add_unread_notification(MatrixNotification notification) {
- if(notifications_shown.insert(notification.event_id).second)
+ if(notifications_shown.insert(notification.event_id).second) {
show_notification("QuickMedia matrix - " + notification.sender_user_id + " (" + notification.room->get_name() + ")", notification.body);
+ ++unread_mention_count_by_room[notification.room];
+ update_room_description(notification.room, {}, false);
+ }
}
void MatrixQuickMedia::add_user(MatrixEventUserInfo user_info) {
@@ -756,6 +767,11 @@ namespace QuickMedia {
void MatrixQuickMedia::set_room_as_read(RoomData *room) {
notifications_page->set_room_as_read(room);
+ int &unread_mention_count = unread_mention_count_by_room[room];
+ if(unread_mention_count > 0) {
+ unread_mention_count = 0;
+ update_room_description(room, {}, false);
+ }
}
static void sort_room_body_items(std::vector<std::shared_ptr<BodyItem>> &room_body_items) {
@@ -903,7 +919,7 @@ namespace QuickMedia {
bool unread_mentions = false;
std::string room_desc;
- const int unread_notification_count = room->unread_notification_count;
+ const int unread_notification_count = unread_mention_count_by_room[room];
if(unread_notification_count > 0 && set_room_as_unread) {
unread_mentions = true;
room_desc += "** " + std::to_string(unread_notification_count) + " unread mention(s) **"; // TODO: Better notification?
@@ -1794,6 +1810,9 @@ namespace QuickMedia {
filter_encoded = url_param_encode(CONTINUE_FILTER); // TODO: limit messages in this continue filter?
// TODO: This ignores new rooms that are not part of the previous sync message. Fix this.
+ // This is needed for system notifications for new messages because on initial sync
+ // there might be messages that were sent 200 messages ago that mentioned us and such
+ // messages wont be part of the intial /sync, so we want to see such messages anyways.
notification_thread = std::thread([this, initial_sync_show_notifications]() {
get_previous_notifications([this, initial_sync_show_notifications](const MatrixNotification &notification) {
if(!initial_sync_show_notifications || notification.read)
@@ -2109,18 +2128,35 @@ namespace QuickMedia {
RoomData *room = get_room_by_id(room_id);
if(!room) {
//fprintf(stderr, "Warning: got notification in unknown room %s\n", room_id.c_str());
+ // TODO:
continue;
}
std::string event_id(event_id_json.GetString(), event_id_json.GetStringLength());
if(notifications_by_event_id.insert(event_id).second) {
+ auto me = get_me(room);
+ time_t read_marker_message_timestamp = 0;
+ if(me) {
+ auto read_marker_message = room->get_message_by_id(room->get_user_read_marker(me));
+ if(read_marker_message)
+ read_marker_message_timestamp = read_marker_message->timestamp;
+ }
+
+ bool actually_read = read_json.GetBool();
+ // TODO: Make sure |events_set_user_read_marker| is called before |events_add_messages| so this is set
+ const int64_t qm_read_marker = room->read_marker_event_timestamp;
+ if(read_marker_message_timestamp == 0 || read_marker_message_timestamp < qm_read_marker) {
+ read_marker_message_timestamp = qm_read_marker;
+ actually_read = read_marker_message_timestamp >= timestamp;
+ }
+
MatrixNotification notification;
notification.room = room;
notification.event_id = std::move(event_id);
notification.sender_user_id.assign(sender_json.GetString(), sender_json.GetStringLength());
notification.body = remove_reply_formatting(this, body_json.GetString());
notification.timestamp = timestamp;
- notification.read = read_json.GetBool();
+ notification.read = actually_read; //read_json.GetBool(); // Intentionally ignore servers read_json value because it's invalid because of synapse cache bug
callback_func(notification);
notifications.push_back(std::move(notification));
}
@@ -2279,11 +2315,8 @@ namespace QuickMedia {
const rapidjson::Value &unread_notification_json = GetMember(it.value, "unread_notifications");
if(unread_notification_json.IsObject()) {
const rapidjson::Value &highlight_count_json = GetMember(unread_notification_json, "highlight_count");
- if(highlight_count_json.IsInt64() && (highlight_count_json.GetInt64() > 0 || initial_sync)) {
- room->unread_notification_count = highlight_count_json.GetInt64();
- if(highlight_count_json.GetInt64() > 0)
- has_unread_notifications = true;
- }
+ if(highlight_count_json.IsInt64() && highlight_count_json.GetInt64() > 0)
+ has_unread_notifications = true;
}
const rapidjson::Value &events_json = GetMember(timeline_json, "events");
@@ -2545,6 +2578,24 @@ namespace QuickMedia {
return "";
}
+ // 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) + ")";
+ }
+ }
+
+ static bool power_level_is_at_least_admin(int power_level, RoomData *room) {
+ return power_level >= room->notification_power_level;
+ }
+
// TODO: Is this really the proper way to check for username mentions?
static bool is_username_seperating_character(char c) {
switch(c) {
@@ -2606,12 +2657,12 @@ namespace QuickMedia {
bool message_contains_user_mention(Matrix *matrix, const Message *message, const std::string &username, const std::string &user_id) {
const std::string formatted_text = message_to_qm_text(matrix, message, false);
- return message_contains_user_mention(formatted_text, username) || message_contains_user_mention(formatted_text, user_id);
+ return message_contains_user_mention(formatted_text, username) || message_contains_user_mention(formatted_text, user_id) || (power_level_is_at_least_admin(message->user->power_level, message->user->room) && message_contains_user_mention(formatted_text, "@room"));
}
bool message_contains_user_mention(const BodyItem *body_item, const std::string &username, const std::string &user_id) {
const std::string formatted_text = Text::to_printable_string(body_item->get_description());
- return message_contains_user_mention(formatted_text, username) || message_contains_user_mention(formatted_text, user_id);
+ return message_contains_user_mention(formatted_text, username) || message_contains_user_mention(formatted_text, user_id) || (body_item->userdata && power_level_is_at_least_admin(static_cast<Message*>(body_item->userdata)->user->power_level, static_cast<Message*>(body_item->userdata)->user->room) && message_contains_user_mention(formatted_text, "®room"));
}
bool message_is_timeline(Message *message) {
@@ -2627,6 +2678,8 @@ namespace QuickMedia {
});
}
+ // Note: has_unread_notifications cant really be trusted because it's from synapse and synapse can return old cached data...
+ // But its better than nothing when its the first time we launch quickmedia (or the first time we see a room) and have no read markers in a room.
size_t Matrix::events_add_messages(const rapidjson::Value &events_json, RoomData *room_data, MessageDirection message_dir, bool has_unread_notifications) {
if(!events_json.IsArray())
return 0;
@@ -2652,27 +2705,30 @@ namespace QuickMedia {
num_new_messages = room_data->append_messages(new_messages);
}
- if(has_unread_notifications) {
- time_t read_marker_message_timestamp = 0;
- if(me) {
- auto read_marker_message = room_data->get_message_by_id(room_data->get_user_read_marker(me));
- if(read_marker_message)
- read_marker_message_timestamp = read_marker_message->timestamp;
- }
+ time_t read_marker_message_timestamp = 0;
+ if(me) {
+ auto read_marker_message = room_data->get_message_by_id(room_data->get_user_read_marker(me));
+ if(read_marker_message)
+ read_marker_message_timestamp = read_marker_message->timestamp;
+ }
- // TODO: Make sure |events_set_user_read_marker| is called before |events_add_messages| so this is set
- const int64_t qm_read_marker = room_data->read_marker_event_timestamp;
- if(read_marker_message_timestamp == 0 || read_marker_message_timestamp < qm_read_marker)
- read_marker_message_timestamp = qm_read_marker;
-
- std::lock_guard<std::mutex> lock(notifications_mutex);
- for(auto &message : new_messages) {
- // TODO: Is @room ok? shouldn't we also check if the user has permission to do @room? (only when notifications are limited to @mentions)
- // TODO: Is comparing against read marker timestamp ok enough?
- if(message_is_timeline(message.get()) && me && message->timestamp > read_marker_message_timestamp) {
- std::string message_str = message_to_qm_text(this, message.get(), false);
- message->notification_mentions_me = message_contains_user_mention(message_str, my_display_name) || message_contains_user_mention(message_str, me->user_id) || message_contains_user_mention(message_str, "@room");
- }
+ // TODO: Make sure |events_set_user_read_marker| is called before |events_add_messages| so this is set
+ const int64_t qm_read_marker = room_data->read_marker_event_timestamp;
+ if(read_marker_message_timestamp == 0 || read_marker_message_timestamp < qm_read_marker)
+ read_marker_message_timestamp = qm_read_marker;
+
+ const bool may_have_unread_notifications = read_marker_message_timestamp > 0 || has_unread_notifications;
+
+ std::lock_guard<std::mutex> lock(notifications_mutex);
+ for(auto &message : new_messages) {
+ // TODO: Is @room ok? shouldn't we also check if the user has permission to do @room? (only when notifications are limited to @mentions)
+ // TODO: Is comparing against read marker timestamp ok enough?
+ if(message_is_timeline(message.get()) && me && message->timestamp > read_marker_message_timestamp && may_have_unread_notifications) {
+ std::string message_str = message_to_qm_text(this, message.get(), false);
+ message->notification_mentions_me = message_contains_user_mention(message_str, my_display_name)
+ || message_contains_user_mention(message_str, me->user_id)
+ || (has_unread_notifications && message_contains_user_mention(message_str, "@room")); // TODO: ...
+ //|| (power_level_is_at_least_admin(message->user->power_level, room_data) && message_contains_user_mention(message_str, "@room"));
}
}
@@ -2684,20 +2740,6 @@ 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;
@@ -3188,6 +3230,15 @@ namespace QuickMedia {
message->transaction_id = std::move(transaction_id);
return message;
} else if(strcmp(type_json.GetString(), "m.room.power_levels") == 0) {
+ const rapidjson::Value &notifications_json = GetMember(*content_json, "notifications");
+ if(notifications_json.IsObject()) {
+ const rapidjson::Value &room_json = GetMember(notifications_json, "room");
+ if(room_json.IsInt())
+ room_data->notification_power_level = room_json.GetInt();
+ }
+
+ // TODO: Implement users_default
+
const rapidjson::Value &users_json = GetMember(*content_json, "users");
if(!users_json.IsObject())
return nullptr;
@@ -3196,7 +3247,10 @@ namespace QuickMedia {
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();
+
+ std::string user_id(user_json.name.GetString(), user_json.name.GetStringLength());
+ get_user_by_id(room_data, user_id)->power_level = user_json.value.GetInt();
+ power_level_changes[std::move(user_id)].new_power_level = user_json.value.GetInt();
}
if(unsigned_json.IsObject()) {
@@ -3208,7 +3262,9 @@ namespace QuickMedia {
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();
+
+ std::string user_id(user_json.name.GetString(), user_json.name.GetStringLength());
+ power_level_changes[std::move(user_id)].old_power_level = user_json.value.GetInt();
}
}
}
@@ -4049,6 +4105,10 @@ namespace QuickMedia {
decrypt_task.push(std::move(decrypt_job));
}
+ MatrixDelegate* Matrix::get_delegate() {
+ return delegate;
+ }
+
PluginResult Matrix::post_message(RoomData *room, const std::string &body, std::string &event_id_response, const std::optional<UploadInfo> &file_info, const std::optional<UploadInfo> &thumbnail_info, const std::string &msgtype, const std::string &custom_transaction_id) {
std::string transaction_id = custom_transaction_id;
if(transaction_id.empty())