From a3675e61bf6dd86dbd2f50a238e73b4c441b2dc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= Date: Wed, 19 Oct 2022 12:29:33 +0200 Subject: [PATCH] events: Add method to construct a replacement --- crates/ruma-common/src/events/room/message.rs | 75 +++++++++++++++++++ .../ruma-common/tests/events/room_message.rs | 73 +++++++++++++++++- 2 files changed, 146 insertions(+), 2 deletions(-) diff --git a/crates/ruma-common/src/events/room/message.rs b/crates/ruma-common/src/events/room/message.rs index e9c0660a..13d7bd56 100644 --- a/crates/ruma-common/src/events/room/message.rs +++ b/crates/ruma-common/src/events/room/message.rs @@ -218,6 +218,81 @@ impl RoomMessageEventContent { self } + /// Turns `self` into a [replacement] (or edit) for the message with the given event ID. + /// + /// This takes the content and sets it in `m.new_content`, and modifies the `content` to include + /// a fallback. + /// + /// If the message that is replaced is a reply to another message, the latter should also be + /// provided to be able to generate a rich reply fallback that takes the `body` / + /// `formatted_body` (if any) in `self` for the main text and prepends a quoted version of + /// `original_message`. + #[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/rich_reply.md"))] + /// + /// # Panics + /// + /// Panics if `self` has a `formatted_body` with a format other than HTML. + /// + /// [replacement]: https://spec.matrix.org/v1.4/client-server-api/#event-replacements + #[track_caller] + pub fn make_replacement( + mut self, + original_message_id: OwnedEventId, + replied_to_message: Option<&OriginalRoomMessageEvent>, + ) -> Self { + // Prepare relates_to with the untouched msgtype. + let relates_to = Relation::Replacement(Replacement { + event_id: original_message_id, + new_content: self.msgtype.clone(), + }); + + let empty_formatted_body = || FormattedBody::html(String::new()); + + let (body, formatted) = { + match &mut self.msgtype { + MessageType::Emote(m) => { + (&mut m.body, Some(m.formatted.get_or_insert_with(empty_formatted_body))) + } + MessageType::Notice(m) => { + (&mut m.body, Some(m.formatted.get_or_insert_with(empty_formatted_body))) + } + MessageType::Text(m) => { + (&mut m.body, Some(m.formatted.get_or_insert_with(empty_formatted_body))) + } + MessageType::Audio(m) => (&mut m.body, None), + MessageType::File(m) => (&mut m.body, None), + MessageType::Image(m) => (&mut m.body, None), + MessageType::Location(m) => (&mut m.body, None), + MessageType::ServerNotice(m) => (&mut m.body, None), + MessageType::Video(m) => (&mut m.body, None), + MessageType::VerificationRequest(m) => (&mut m.body, None), + MessageType::_Custom(m) => (&mut m.body, None), + } + }; + + // Add replacement fallback. + *body = format!("* {body}"); + + if let Some(f) = formatted { + assert_eq!( + f.format, + MessageFormat::Html, + "make_replacement can't handle non-HTML formatted messages" + ); + + f.body = format!("* {}", f.body); + } + + // Add reply fallback if needed. + if let Some(original_message) = replied_to_message { + self = self.make_reply_to(original_message, ForwardThread::No); + } + + self.relates_to = Some(relates_to); + + self + } + /// Returns a reference to the `msgtype` string. /// /// If you want to access the message type-specific data rather than the message type itself, diff --git a/crates/ruma-common/tests/events/room_message.rs b/crates/ruma-common/tests/events/room_message.rs index d9d734e0..48c8f1a2 100644 --- a/crates/ruma-common/tests/events/room_message.rs +++ b/crates/ruma-common/tests/events/room_message.rs @@ -9,7 +9,7 @@ use ruma_common::{ room::{ message::{ AudioMessageEventContent, KeyVerificationRequestEventContent, MessageType, - OriginalRoomMessageEvent, RoomMessageEventContent, + OriginalRoomMessageEvent, RoomMessageEventContent, TextMessageEventContent, }, MediaSource, }, @@ -437,7 +437,7 @@ fn content_deserialization_failure() { #[test] #[cfg(feature = "unstable-sanitize")] fn reply_sanitize() { - use ruma_common::events::room::message::{ForwardThread, TextMessageEventContent}; + use ruma_common::events::room::message::ForwardThread; let first_message = OriginalRoomMessageEvent { content: RoomMessageEventContent::text_html( @@ -530,3 +530,72 @@ fn reply_sanitize() { " ); } + +#[test] +fn make_replacement_no_reply() { + let content = RoomMessageEventContent::text_html( + "This is _an edited_ message.", + "This is an edited message.", + ); + let event_id = event_id!("$143273582443PhrSn:example.org").to_owned(); + + let content = content.make_replacement(event_id, None); + + let (body, formatted) = assert_matches!( + content.msgtype, + MessageType::Text(TextMessageEventContent { body, formatted, .. }) => (body, formatted) + ); + assert_eq!(body, "* This is _an edited_ message."); + let formatted = formatted.unwrap(); + assert_eq!(formatted.body, "* This is an edited message."); +} + +#[test] +fn make_replacement_with_reply() { + let replied_to_message = OriginalRoomMessageEvent { + content: RoomMessageEventContent::text_html( + "# This is the first message", + "

This is the first message

", + ), + event_id: event_id!("$143273582443PhrSn:example.org").to_owned(), + origin_server_ts: MilliSecondsSinceUnixEpoch(uint!(10_000)), + room_id: room_id!("!testroomid:example.org").to_owned(), + sender: user_id!("@user:example.org").to_owned(), + unsigned: MessageLikeUnsigned::default(), + }; + + let content = RoomMessageEventContent::text_html( + "This is _an edited_ reply.", + "This is an edited reply.", + ); + let event_id = event_id!("$143273582443PhrSn:example.org").to_owned(); + + let content = content.make_replacement(event_id, Some(&replied_to_message)); + + let (body, formatted) = assert_matches!( + content.msgtype, + MessageType::Text(TextMessageEventContent { body, formatted, .. }) => (body, formatted) + ); + assert_eq!( + body, + "\ + > <@user:example.org> # This is the first message\n\ + * This is _an edited_ reply.\ + " + ); + let formatted = formatted.unwrap(); + assert_eq!( + formatted.body, + "\ + \ +
\ + In reply to \ + @user:example.org\ +
\ +

This is the first message

\ +
\ +
\ + * This is an edited reply.\ + " + ); +}