events: Add method to construct a replacement

This commit is contained in:
Kévin Commaille 2022-10-19 12:29:33 +02:00 committed by Kévin Commaille
parent 764e96a254
commit a3675e61bf
2 changed files with 146 additions and 2 deletions

View File

@ -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,

View File

@ -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 <em>an edited</em> 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 <em>an edited</em> message.");
}
#[test]
fn make_replacement_with_reply() {
let replied_to_message = OriginalRoomMessageEvent {
content: RoomMessageEventContent::text_html(
"# This is the first message",
"<h1>This is the first message</h1>",
),
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 <em>an edited</em> 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,
"\
<mx-reply>\
<blockquote>\
<a href=\"https://matrix.to/#/!testroomid:example.org/$143273582443PhrSn:example.org\">In reply to</a> \
<a href=\"https://matrix.to/#/@user:example.org\">@user:example.org</a>\
<br>\
<h1>This is the first message</h1>\
</blockquote>\
</mx-reply>\
* This is <em>an edited</em> reply.\
"
);
}