From b2b4c81645ba057bb7769c17369581d2f9bc23e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= Date: Mon, 21 Aug 2023 17:13:07 +0200 Subject: [PATCH] events: Allow to add mentions automatically when generating reply --- crates/ruma-common/CHANGELOG.md | 4 ++- crates/ruma-common/src/events/room/message.rs | 35 +++++++++++++++++-- .../ruma-common/tests/events/room_message.rs | 28 ++++++++------- 3 files changed, 52 insertions(+), 15 deletions(-) diff --git a/crates/ruma-common/CHANGELOG.md b/crates/ruma-common/CHANGELOG.md index 37e2c72c..d4f47ec8 100644 --- a/crates/ruma-common/CHANGELOG.md +++ b/crates/ruma-common/CHANGELOG.md @@ -37,7 +37,9 @@ Breaking changes: MSC2176 / MSC3821 / MSC3820, for the following types: - `RedactedRoomRedactionEventContent`, - `RedactedRoomPowerLevelsEventContent`, - - `RedactedRoomMemberEventContent` + - `RedactedRoomMemberEventContent` +- `RoomMessageEventContent::make_reply_to()` and `make_for_thread()` have an extra parameter to + support the recommended behavior for intentional mentions in replies according to Matrix 1.7 Improvements: diff --git a/crates/ruma-common/src/events/room/message.rs b/crates/ruma-common/src/events/room/message.rs index a5d857cb..2a2bcdff 100644 --- a/crates/ruma-common/src/events/room/message.rs +++ b/crates/ruma-common/src/events/room/message.rs @@ -150,6 +150,7 @@ impl RoomMessageEventContent { mut self, original_message: &OriginalRoomMessageEvent, forward_thread: ForwardThread, + add_mentions: AddMentions, ) -> Self { let empty_formatted_body = || FormattedBody::html(String::new()); @@ -205,6 +206,17 @@ impl RoomMessageEventContent { }; self.relates_to = Some(relates_to); + if add_mentions == AddMentions::Yes { + // Copy the mentioned users. + let mut user_ids = match &original_message.content.mentions { + Some(m) => m.user_ids.clone(), + None => Default::default(), + }; + // Add the sender. + user_ids.insert(original_message.sender.clone()); + self.mentions = Some(Mentions { user_ids, ..Default::default() }); + } + self } @@ -227,9 +239,10 @@ impl RoomMessageEventContent { mut self, previous_message: &OriginalRoomMessageEvent, is_reply: ReplyWithinThread, + add_mentions: AddMentions, ) -> Self { if is_reply == ReplyWithinThread::Yes { - self = self.make_reply_to(previous_message, ForwardThread::No); + self = self.make_reply_to(previous_message, ForwardThread::No, add_mentions); } let thread_root = if let Some(Relation::Thread(Thread { event_id, .. })) = @@ -323,7 +336,7 @@ impl RoomMessageEventContent { // Add reply fallback if needed. if let Some(original_message) = replied_to_message { - self = self.make_reply_to(original_message, ForwardThread::No); + self = self.make_reply_to(original_message, ForwardThread::No, AddMentions::No); } self.relates_to = Some(relates_to); @@ -473,6 +486,24 @@ pub enum ForwardThread { No, } +/// Whether or not to add intentional [`Mentions`] when sending a reply. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[allow(clippy::exhaustive_enums)] +pub enum AddMentions { + /// Add automatic intentional mentions to the reply. + /// + /// Set this if your client supports intentional mentions. + /// + /// The sender of the original event will be added to the mentions of this message, along with + /// every user mentioned in the original event. + Yes, + + /// Do not add intentional mentions to the reply. + /// + /// Set this if your client does not support intentional mentions. + No, +} + /// Whether or not the message is a reply inside a thread. #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[allow(clippy::exhaustive_enums)] diff --git a/crates/ruma-common/tests/events/room_message.rs b/crates/ruma-common/tests/events/room_message.rs index d86613a0..be20cad0 100644 --- a/crates/ruma-common/tests/events/room_message.rs +++ b/crates/ruma-common/tests/events/room_message.rs @@ -7,10 +7,11 @@ use ruma_common::{ key::verification::VerificationMethod, room::{ message::{ - AudioMessageEventContent, EmoteMessageEventContent, FileMessageEventContent, - ForwardThread, ImageMessageEventContent, KeyVerificationRequestEventContent, - MessageType, OriginalRoomMessageEvent, OriginalSyncRoomMessageEvent, Relation, - RoomMessageEventContent, TextMessageEventContent, VideoMessageEventContent, + AddMentions, AudioMessageEventContent, EmoteMessageEventContent, + FileMessageEventContent, ForwardThread, ImageMessageEventContent, + KeyVerificationRequestEventContent, MessageType, OriginalRoomMessageEvent, + OriginalSyncRoomMessageEvent, Relation, RoomMessageEventContent, + TextMessageEventContent, VideoMessageEventContent, }, EncryptedFileInit, JsonWebKeyInit, MediaSource, }, @@ -265,8 +266,11 @@ fn escape_tags_in_plain_reply_body() { sender: owned_user_id!("@user:example.org"), unsigned: MessageLikeUnsigned::default(), }; - let second_message = RoomMessageEventContent::text_plain("Usage: rm ") - .make_reply_to(&first_message, ForwardThread::Yes); + let second_message = RoomMessageEventContent::text_plain("Usage: rm ").make_reply_to( + &first_message, + ForwardThread::Yes, + AddMentions::No, + ); assert_matches!( first_message.content.msgtype, @@ -324,7 +328,7 @@ fn reply_sanitize() { "This is the _second_ message", "This is the second message", ) - .make_reply_to(&first_message, ForwardThread::Yes), + .make_reply_to(&first_message, ForwardThread::Yes, AddMentions::No), event_id: owned_event_id!("$143273582443PhrSn:example.org"), origin_server_ts: MilliSecondsSinceUnixEpoch(uint!(10_000)), room_id: owned_room_id!("!testroomid:example.org"), @@ -335,7 +339,7 @@ fn reply_sanitize() { "This is **my** reply", "This is my reply", ) - .make_reply_to(&second_message, ForwardThread::Yes); + .make_reply_to(&second_message, ForwardThread::Yes, AddMentions::No); assert_matches!( first_message.content.msgtype, @@ -844,7 +848,7 @@ fn set_mentions() { let user_id = owned_user_id!("@you:localhost"); content = content.set_mentions(Mentions::with_user_ids(vec![user_id.clone()])); let mentions = content.mentions.unwrap(); - assert_eq!(mentions.user_ids.as_slice(), &[user_id]); + assert_eq!(mentions.user_ids, [user_id].into()); } #[test] @@ -878,14 +882,14 @@ fn make_replacement_set_mentions() { assert_matches!(content.mentions, None); assert_matches!(content.relates_to, Some(Relation::Replacement(replacement))); let mentions = replacement.new_content.mentions.unwrap(); - assert_eq!(mentions.user_ids.as_slice(), &[alice.clone()]); + assert_eq!(mentions.user_ids, [alice.clone()].into()); content = content_clone.set_mentions(Mentions::with_user_ids(vec![alice.clone(), bob.clone()])); let mentions = content.mentions.unwrap(); - assert_eq!(mentions.user_ids.as_slice(), &[bob.clone()]); + assert_eq!(mentions.user_ids, [bob.clone()].into()); assert_matches!(content.relates_to, Some(Relation::Replacement(replacement))); let mentions = replacement.new_content.mentions.unwrap(); - assert_eq!(mentions.user_ids.as_slice(), &[alice, bob]); + assert_eq!(mentions.user_ids, [alice, bob].into()); } #[test]