events: Allow to build reply to raw events
This commit is contained in:
parent
bc48eb2162
commit
5040aa2a93
@ -63,6 +63,7 @@ Improvements:
|
|||||||
- `RedactedRoomRedactionEventContent`,
|
- `RedactedRoomRedactionEventContent`,
|
||||||
- `RedactedRoomPowerLevelsEventContent`,
|
- `RedactedRoomPowerLevelsEventContent`,
|
||||||
- `RedactedRoomMemberEventContent`
|
- `RedactedRoomMemberEventContent`
|
||||||
|
- Add `RoomMessageEventContent::make_reply_to_raw` to build replies to any event
|
||||||
|
|
||||||
# 0.26.1
|
# 0.26.1
|
||||||
|
|
||||||
|
@ -2,11 +2,12 @@
|
|||||||
//!
|
//!
|
||||||
//! [`m.room.message`]: https://spec.matrix.org/latest/client-server-api/#mroommessage
|
//! [`m.room.message`]: https://spec.matrix.org/latest/client-server-api/#mroommessage
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::{borrow::Cow, collections::BTreeSet};
|
||||||
|
|
||||||
|
use as_variant::as_variant;
|
||||||
use ruma_common::{
|
use ruma_common::{
|
||||||
serde::{JsonObject, StringEnum},
|
serde::{JsonObject, Raw, StringEnum},
|
||||||
EventId, OwnedEventId,
|
EventId, OwnedEventId, OwnedUserId, RoomId, UserId,
|
||||||
};
|
};
|
||||||
#[cfg(feature = "html")]
|
#[cfg(feature = "html")]
|
||||||
use ruma_html::{sanitize_html, HtmlSanitizerMode, RemoveReplyFallback};
|
use ruma_html::{sanitize_html, HtmlSanitizerMode, RemoveReplyFallback};
|
||||||
@ -16,7 +17,7 @@ use serde_json::Value as JsonValue;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
relation::{CustomRelation, InReplyTo, RelationType, Replacement, Thread},
|
relation::{CustomRelation, InReplyTo, RelationType, Replacement, Thread},
|
||||||
Mentions, PrivOwnedStr,
|
AnySyncTimelineEvent, Mentions, PrivOwnedStr,
|
||||||
};
|
};
|
||||||
|
|
||||||
mod audio;
|
mod audio;
|
||||||
@ -50,6 +51,8 @@ pub use server_notice::{LimitType, ServerNoticeMessageEventContent, ServerNotice
|
|||||||
pub use text::TextMessageEventContent;
|
pub use text::TextMessageEventContent;
|
||||||
pub use video::{VideoInfo, VideoMessageEventContent};
|
pub use video::{VideoInfo, VideoMessageEventContent};
|
||||||
|
|
||||||
|
use self::reply::OriginalEventData;
|
||||||
|
|
||||||
/// The content of an `m.room.message` event.
|
/// The content of an `m.room.message` event.
|
||||||
///
|
///
|
||||||
/// This event is used when sending messages in a room.
|
/// This event is used when sending messages in a room.
|
||||||
@ -149,11 +152,132 @@ impl RoomMessageEventContent {
|
|||||||
/// Panics if `self` has a `formatted_body` with a format other than HTML.
|
/// Panics if `self` has a `formatted_body` with a format other than HTML.
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn make_reply_to(
|
pub fn make_reply_to(
|
||||||
mut self,
|
self,
|
||||||
original_message: &OriginalRoomMessageEvent,
|
original_message: &OriginalRoomMessageEvent,
|
||||||
forward_thread: ForwardThread,
|
forward_thread: ForwardThread,
|
||||||
add_mentions: AddMentions,
|
add_mentions: AddMentions,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
let reply = self.make_reply_fallback(original_message.into());
|
||||||
|
|
||||||
|
let original_thread_id = if forward_thread == ForwardThread::Yes {
|
||||||
|
original_message
|
||||||
|
.content
|
||||||
|
.relates_to
|
||||||
|
.as_ref()
|
||||||
|
.and_then(as_variant!(Relation::Thread))
|
||||||
|
.map(|thread| thread.event_id.clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let original_user_mentions = (add_mentions == AddMentions::Yes).then(|| {
|
||||||
|
original_message
|
||||||
|
.content
|
||||||
|
.mentions
|
||||||
|
.as_ref()
|
||||||
|
.map(|m| m.user_ids.clone())
|
||||||
|
.unwrap_or_default()
|
||||||
|
});
|
||||||
|
|
||||||
|
reply.make_reply_tweaks(
|
||||||
|
original_message.event_id.clone(),
|
||||||
|
original_thread_id,
|
||||||
|
original_user_mentions,
|
||||||
|
Some(&original_message.sender),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Turns `self` into a reply to the given raw event.
|
||||||
|
///
|
||||||
|
/// Takes the `body` / `formatted_body` (if any) in `self` for the main text and prepends a
|
||||||
|
/// quoted version of the `body` of `original_event` (if any). Also sets the `in_reply_to` field
|
||||||
|
/// inside `relates_to`, and optionally the `rel_type` to `m.thread` if the
|
||||||
|
/// `original_message is in a thread and thread forwarding is enabled.
|
||||||
|
///
|
||||||
|
/// It is recommended to use [`Self::make_reply_to()`] for replies to `m.room.message` events,
|
||||||
|
/// as the generated fallback is better for some `msgtype`s.
|
||||||
|
///
|
||||||
|
/// Note that except for the panic below, this is infallible. Which means that if a field is
|
||||||
|
/// missing when deserializing the data, the changes that require it will not be applied. It
|
||||||
|
/// will still at least apply the `m.in_reply_to` relation to this content.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if `self` has a `formatted_body` with a format other than HTML.
|
||||||
|
#[track_caller]
|
||||||
|
pub fn make_reply_to_raw(
|
||||||
|
self,
|
||||||
|
original_event: &Raw<AnySyncTimelineEvent>,
|
||||||
|
original_event_id: OwnedEventId,
|
||||||
|
room_id: &RoomId,
|
||||||
|
forward_thread: ForwardThread,
|
||||||
|
add_mentions: AddMentions,
|
||||||
|
) -> Self {
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct ContentDeHelper {
|
||||||
|
body: Option<String>,
|
||||||
|
#[serde(flatten)]
|
||||||
|
formatted: Option<FormattedBody>,
|
||||||
|
#[cfg(feature = "unstable-msc1767")]
|
||||||
|
#[serde(rename = "org.matrix.msc1767")]
|
||||||
|
text: Option<String>,
|
||||||
|
#[serde(rename = "m.relates_to")]
|
||||||
|
relates_to: Option<crate::room::encrypted::Relation>,
|
||||||
|
#[serde(rename = "m.mentions")]
|
||||||
|
mentions: Option<Mentions>,
|
||||||
|
}
|
||||||
|
|
||||||
|
let sender = original_event.get_field::<OwnedUserId>("sender").ok().flatten();
|
||||||
|
let content = original_event.get_field::<ContentDeHelper>("content").ok().flatten();
|
||||||
|
let relates_to = content.as_ref().and_then(|c| c.relates_to.as_ref());
|
||||||
|
|
||||||
|
let content_body = content.as_ref().and_then(|c| {
|
||||||
|
let body = c.body.as_deref();
|
||||||
|
#[cfg(feature = "unstable-msc1767")]
|
||||||
|
let body = body.or(c.text.as_deref());
|
||||||
|
|
||||||
|
Some((c, body?))
|
||||||
|
});
|
||||||
|
|
||||||
|
// Only apply fallback if we managed to deserialize raw event.
|
||||||
|
let reply = if let (Some(sender), Some((content, body))) = (&sender, content_body) {
|
||||||
|
let is_reply =
|
||||||
|
matches!(content.relates_to, Some(crate::room::encrypted::Relation::Reply { .. }));
|
||||||
|
let data = OriginalEventData {
|
||||||
|
body,
|
||||||
|
formatted: content.formatted.as_ref(),
|
||||||
|
is_emote: false,
|
||||||
|
is_reply,
|
||||||
|
room_id,
|
||||||
|
event_id: &original_event_id,
|
||||||
|
sender,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.make_reply_fallback(data)
|
||||||
|
} else {
|
||||||
|
self
|
||||||
|
};
|
||||||
|
|
||||||
|
let original_thread_id = if forward_thread == ForwardThread::Yes {
|
||||||
|
relates_to
|
||||||
|
.and_then(as_variant!(crate::room::encrypted::Relation::Thread))
|
||||||
|
.map(|thread| thread.event_id.clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let original_user_mentions = (add_mentions == AddMentions::Yes)
|
||||||
|
.then(|| content.and_then(|c| c.mentions).map(|m| m.user_ids).unwrap_or_default());
|
||||||
|
|
||||||
|
reply.make_reply_tweaks(
|
||||||
|
original_event_id,
|
||||||
|
original_thread_id,
|
||||||
|
original_user_mentions,
|
||||||
|
sender.as_deref(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_reply_fallback(mut self, original_event: OriginalEventData<'_>) -> Self {
|
||||||
let empty_formatted_body = || FormattedBody::html(String::new());
|
let empty_formatted_body = || FormattedBody::html(String::new());
|
||||||
|
|
||||||
let (body, formatted) = {
|
let (body, formatted) = {
|
||||||
@ -190,32 +314,30 @@ impl RoomMessageEventContent {
|
|||||||
(*body, *formatted_body) = reply::plain_and_formatted_reply_body(
|
(*body, *formatted_body) = reply::plain_and_formatted_reply_body(
|
||||||
body.as_str(),
|
body.as_str(),
|
||||||
(!formatted_body.is_empty()).then_some(formatted_body.as_str()),
|
(!formatted_body.is_empty()).then_some(formatted_body.as_str()),
|
||||||
original_message,
|
original_event,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let relates_to = if let Some(Relation::Thread(Thread { event_id, .. })) = original_message
|
self
|
||||||
.content
|
}
|
||||||
.relates_to
|
|
||||||
.as_ref()
|
fn make_reply_tweaks(
|
||||||
.filter(|_| forward_thread == ForwardThread::Yes)
|
mut self,
|
||||||
{
|
original_event_id: OwnedEventId,
|
||||||
Relation::Thread(Thread::plain(event_id.clone(), original_message.event_id.clone()))
|
original_thread_id: Option<OwnedEventId>,
|
||||||
|
original_user_mentions: Option<BTreeSet<OwnedUserId>>,
|
||||||
|
original_sender: Option<&UserId>,
|
||||||
|
) -> Self {
|
||||||
|
let relates_to = if let Some(event_id) = original_thread_id {
|
||||||
|
Relation::Thread(Thread::plain(event_id.to_owned(), original_event_id.to_owned()))
|
||||||
} else {
|
} else {
|
||||||
Relation::Reply {
|
Relation::Reply { in_reply_to: InReplyTo { event_id: original_event_id.to_owned() } }
|
||||||
in_reply_to: InReplyTo { event_id: original_message.event_id.clone() },
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
self.relates_to = Some(relates_to);
|
self.relates_to = Some(relates_to);
|
||||||
|
|
||||||
if add_mentions == AddMentions::Yes {
|
if let (Some(sender), Some(mut user_ids)) = (original_sender, original_user_mentions) {
|
||||||
// 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.
|
// Add the sender.
|
||||||
user_ids.insert(original_message.sender.clone());
|
user_ids.insert(sender.to_owned());
|
||||||
self.mentions = Some(Mentions { user_ids, ..Default::default() });
|
self.mentions = Some(Mentions { user_ids, ..Default::default() });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use std::fmt::{self, Write};
|
use std::fmt::{self, Write};
|
||||||
|
|
||||||
|
use ruma_common::{EventId, RoomId, UserId};
|
||||||
#[cfg(feature = "html")]
|
#[cfg(feature = "html")]
|
||||||
use ruma_html::Html;
|
use ruma_html::Html;
|
||||||
|
|
||||||
@ -8,47 +9,64 @@ use super::{
|
|||||||
Relation,
|
Relation,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn get_message_quote_fallbacks(original_message: &OriginalRoomMessageEvent) -> (String, String) {
|
pub(super) struct OriginalEventData<'a> {
|
||||||
let get_quotes = |body: &str, formatted: Option<&FormattedBody>, is_emote: bool| {
|
pub(super) body: &'a str,
|
||||||
let OriginalRoomMessageEvent { room_id, event_id, sender, content, .. } = original_message;
|
pub(super) formatted: Option<&'a FormattedBody>,
|
||||||
|
pub(super) is_emote: bool,
|
||||||
|
pub(super) is_reply: bool,
|
||||||
|
pub(super) room_id: &'a RoomId,
|
||||||
|
pub(super) event_id: &'a EventId,
|
||||||
|
pub(super) sender: &'a UserId,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<&'a OriginalRoomMessageEvent> for OriginalEventData<'a> {
|
||||||
|
fn from(message: &'a OriginalRoomMessageEvent) -> Self {
|
||||||
|
let OriginalRoomMessageEvent { room_id, event_id, sender, content, .. } = message;
|
||||||
let is_reply = matches!(content.relates_to, Some(Relation::Reply { .. }));
|
let is_reply = matches!(content.relates_to, Some(Relation::Reply { .. }));
|
||||||
let emote_sign = is_emote.then_some("* ").unwrap_or_default();
|
|
||||||
let body = is_reply.then(|| remove_plain_reply_fallback(body)).unwrap_or(body);
|
|
||||||
#[cfg(feature = "html")]
|
|
||||||
let html_body = FormattedOrPlainBody { formatted, body, is_reply };
|
|
||||||
#[cfg(not(feature = "html"))]
|
|
||||||
let html_body = FormattedOrPlainBody { formatted, body };
|
|
||||||
|
|
||||||
(
|
let (body, formatted, is_emote) = match &content.msgtype {
|
||||||
format!("> {emote_sign}<{sender}> {body}").replace('\n', "\n> "),
|
MessageType::Audio(_) => ("sent an audio file.", None, false),
|
||||||
format!(
|
MessageType::Emote(c) => (&*c.body, c.formatted.as_ref(), true),
|
||||||
"<mx-reply>\
|
MessageType::File(_) => ("sent a file.", None, false),
|
||||||
<blockquote>\
|
MessageType::Image(_) => ("sent an image.", None, false),
|
||||||
<a href=\"https://matrix.to/#/{room_id}/{event_id}\">In reply to</a> \
|
MessageType::Location(_) => ("sent a location.", None, false),
|
||||||
{emote_sign}<a href=\"https://matrix.to/#/{sender}\">{sender}</a>\
|
MessageType::Notice(c) => (&*c.body, c.formatted.as_ref(), false),
|
||||||
<br>\
|
MessageType::ServerNotice(c) => (&*c.body, None, false),
|
||||||
{html_body}\
|
MessageType::Text(c) => (&*c.body, c.formatted.as_ref(), false),
|
||||||
</blockquote>\
|
MessageType::Video(_) => ("sent a video.", None, false),
|
||||||
</mx-reply>"
|
MessageType::VerificationRequest(c) => (&*c.body, None, false),
|
||||||
),
|
MessageType::_Custom(c) => (&*c.body, None, false),
|
||||||
)
|
};
|
||||||
};
|
|
||||||
|
|
||||||
match &original_message.content.msgtype {
|
Self { body, formatted, is_emote, is_reply, room_id, event_id, sender }
|
||||||
MessageType::Audio(_) => get_quotes("sent an audio file.", None, false),
|
|
||||||
MessageType::Emote(c) => get_quotes(&c.body, c.formatted.as_ref(), true),
|
|
||||||
MessageType::File(_) => get_quotes("sent a file.", None, false),
|
|
||||||
MessageType::Image(_) => get_quotes("sent an image.", None, false),
|
|
||||||
MessageType::Location(_) => get_quotes("sent a location.", None, false),
|
|
||||||
MessageType::Notice(c) => get_quotes(&c.body, c.formatted.as_ref(), false),
|
|
||||||
MessageType::ServerNotice(c) => get_quotes(&c.body, None, false),
|
|
||||||
MessageType::Text(c) => get_quotes(&c.body, c.formatted.as_ref(), false),
|
|
||||||
MessageType::Video(_) => get_quotes("sent a video.", None, false),
|
|
||||||
MessageType::VerificationRequest(content) => get_quotes(&content.body, None, false),
|
|
||||||
MessageType::_Custom(content) => get_quotes(&content.body, None, false),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_message_quote_fallbacks(original_event: OriginalEventData<'_>) -> (String, String) {
|
||||||
|
let OriginalEventData { body, formatted, is_emote, is_reply, room_id, event_id, sender } =
|
||||||
|
original_event;
|
||||||
|
let emote_sign = is_emote.then_some("* ").unwrap_or_default();
|
||||||
|
let body = is_reply.then(|| remove_plain_reply_fallback(body)).unwrap_or(body);
|
||||||
|
#[cfg(feature = "html")]
|
||||||
|
let html_body = FormattedOrPlainBody { formatted, body, is_reply };
|
||||||
|
#[cfg(not(feature = "html"))]
|
||||||
|
let html_body = FormattedOrPlainBody { formatted, body };
|
||||||
|
|
||||||
|
(
|
||||||
|
format!("> {emote_sign}<{sender}> {body}").replace('\n', "\n> "),
|
||||||
|
format!(
|
||||||
|
"<mx-reply>\
|
||||||
|
<blockquote>\
|
||||||
|
<a href=\"https://matrix.to/#/{room_id}/{event_id}\">In reply to</a> \
|
||||||
|
{emote_sign}<a href=\"https://matrix.to/#/{sender}\">{sender}</a>\
|
||||||
|
<br>\
|
||||||
|
{html_body}\
|
||||||
|
</blockquote>\
|
||||||
|
</mx-reply>"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
struct EscapeHtmlEntities<'a>(&'a str);
|
struct EscapeHtmlEntities<'a>(&'a str);
|
||||||
|
|
||||||
impl fmt::Display for EscapeHtmlEntities<'_> {
|
impl fmt::Display for EscapeHtmlEntities<'_> {
|
||||||
@ -108,12 +126,12 @@ impl fmt::Display for FormattedOrPlainBody<'_> {
|
|||||||
///
|
///
|
||||||
/// [HTML tags and attributes]: https://spec.matrix.org/latest/client-server-api/#mroommessage-msgtypes
|
/// [HTML tags and attributes]: https://spec.matrix.org/latest/client-server-api/#mroommessage-msgtypes
|
||||||
/// [rich reply fallbacks]: https://spec.matrix.org/latest/client-server-api/#fallbacks-for-rich-replies
|
/// [rich reply fallbacks]: https://spec.matrix.org/latest/client-server-api/#fallbacks-for-rich-replies
|
||||||
pub(crate) fn plain_and_formatted_reply_body(
|
pub(super) fn plain_and_formatted_reply_body(
|
||||||
body: &str,
|
body: &str,
|
||||||
formatted: Option<impl fmt::Display>,
|
formatted: Option<impl fmt::Display>,
|
||||||
original_message: &OriginalRoomMessageEvent,
|
original_event: OriginalEventData<'_>,
|
||||||
) -> (String, String) {
|
) -> (String, String) {
|
||||||
let (quoted, quoted_html) = get_message_quote_fallbacks(original_message);
|
let (quoted, quoted_html) = get_message_quote_fallbacks(original_event);
|
||||||
|
|
||||||
let plain = format!("{quoted}\n\n{body}");
|
let plain = format!("{quoted}\n\n{body}");
|
||||||
let html = match formatted {
|
let html = match formatted {
|
||||||
@ -133,15 +151,17 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn fallback_multiline() {
|
fn fallback_multiline() {
|
||||||
let (plain_quote, html_quote) =
|
let (plain_quote, html_quote) = super::get_message_quote_fallbacks(
|
||||||
super::get_message_quote_fallbacks(&OriginalRoomMessageEvent {
|
(&OriginalRoomMessageEvent {
|
||||||
content: RoomMessageEventContent::text_plain("multi\nline"),
|
content: RoomMessageEventContent::text_plain("multi\nline"),
|
||||||
event_id: owned_event_id!("$1598361704261elfgc:localhost"),
|
event_id: owned_event_id!("$1598361704261elfgc:localhost"),
|
||||||
sender: owned_user_id!("@alice:example.com"),
|
sender: owned_user_id!("@alice:example.com"),
|
||||||
origin_server_ts: MilliSecondsSinceUnixEpoch::now(),
|
origin_server_ts: MilliSecondsSinceUnixEpoch::now(),
|
||||||
room_id: owned_room_id!("!n8f893n9:example.com"),
|
room_id: owned_room_id!("!n8f893n9:example.com"),
|
||||||
unsigned: MessageLikeUnsigned::new(),
|
unsigned: MessageLikeUnsigned::new(),
|
||||||
});
|
})
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(plain_quote, "> <@alice:example.com> multi\n> line");
|
assert_eq!(plain_quote, "> <@alice:example.com> multi\n> line");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
use std::borrow::Cow;
|
use std::{borrow::Cow, collections::BTreeSet};
|
||||||
|
|
||||||
use assert_matches2::assert_matches;
|
use assert_matches2::assert_matches;
|
||||||
use js_int::uint;
|
use js_int::uint;
|
||||||
use ruma_common::{
|
use ruma_common::{
|
||||||
mxc_uri, owned_event_id, owned_room_id, owned_user_id, serde::Base64, user_id,
|
mxc_uri, owned_event_id, owned_room_id, owned_user_id, room_id,
|
||||||
MilliSecondsSinceUnixEpoch, OwnedDeviceId,
|
serde::{Base64, Raw},
|
||||||
|
user_id, MilliSecondsSinceUnixEpoch, OwnedDeviceId,
|
||||||
};
|
};
|
||||||
use ruma_events::{
|
use ruma_events::{
|
||||||
key::verification::VerificationMethod,
|
key::verification::VerificationMethod,
|
||||||
@ -18,7 +19,7 @@ use ruma_events::{
|
|||||||
},
|
},
|
||||||
EncryptedFileInit, JsonWebKeyInit, MediaSource,
|
EncryptedFileInit, JsonWebKeyInit, MediaSource,
|
||||||
},
|
},
|
||||||
Mentions, MessageLikeUnsigned,
|
AnySyncTimelineEvent, Mentions, MessageLikeUnsigned,
|
||||||
};
|
};
|
||||||
use serde_json::{from_value as from_json_value, json, to_value as to_json_value};
|
use serde_json::{from_value as from_json_value, json, to_value as to_json_value};
|
||||||
|
|
||||||
@ -483,6 +484,215 @@ fn reply_add_mentions() {
|
|||||||
assert!(mentions.room);
|
assert!(mentions.room);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reply_to_raw() {
|
||||||
|
let room_id = room_id!("!roomid:notareal.hs");
|
||||||
|
let event_id = owned_event_id!("$143273582443PhrSn");
|
||||||
|
|
||||||
|
let original_message: Raw<AnySyncTimelineEvent> = from_json_value(json!({
|
||||||
|
"content": {
|
||||||
|
"body": "Hello, World!",
|
||||||
|
"msgtype": "m.text",
|
||||||
|
},
|
||||||
|
"event_id": event_id,
|
||||||
|
"origin_server_ts": 134_829_848,
|
||||||
|
"sender": "@user:notareal.hs",
|
||||||
|
"type": "m.room.message",
|
||||||
|
}))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let reply = RoomMessageEventContent::text_html(
|
||||||
|
"This is **my** reply",
|
||||||
|
"This is <strong>my</strong> reply",
|
||||||
|
)
|
||||||
|
.make_reply_to_raw(
|
||||||
|
&original_message,
|
||||||
|
event_id.clone(),
|
||||||
|
room_id,
|
||||||
|
ForwardThread::Yes,
|
||||||
|
AddMentions::No,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_matches!(reply.relates_to, Some(Relation::Reply { in_reply_to }));
|
||||||
|
assert_eq!(in_reply_to.event_id, event_id);
|
||||||
|
|
||||||
|
assert_matches!(reply.msgtype, MessageType::Text(text_msg));
|
||||||
|
assert_eq!(
|
||||||
|
text_msg.body,
|
||||||
|
"> <@user:notareal.hs> Hello, World!\n\
|
||||||
|
\n\
|
||||||
|
This is **my** reply"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
text_msg.formatted.unwrap().body,
|
||||||
|
"<mx-reply>\
|
||||||
|
<blockquote>\
|
||||||
|
<a href=\"https://matrix.to/#/!roomid:notareal.hs/$143273582443PhrSn\">In reply to</a> \
|
||||||
|
<a href=\"https://matrix.to/#/@user:notareal.hs\">@user:notareal.hs</a>\
|
||||||
|
<br>\
|
||||||
|
Hello, World!\
|
||||||
|
</blockquote>\
|
||||||
|
</mx-reply>\
|
||||||
|
This is <strong>my</strong> reply"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reply_to_raw_no_body() {
|
||||||
|
let room_id = room_id!("!roomid:notareal.hs");
|
||||||
|
let event_id = owned_event_id!("$143273582443PhrSn");
|
||||||
|
|
||||||
|
let original_message: Raw<AnySyncTimelineEvent> = from_json_value(json!({
|
||||||
|
"content": {},
|
||||||
|
"event_id": event_id,
|
||||||
|
"origin_server_ts": 134_829_848,
|
||||||
|
"sender": "@user:notareal.hs",
|
||||||
|
"type": "m.room.message",
|
||||||
|
}))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let reply = RoomMessageEventContent::text_html(
|
||||||
|
"This is **my** reply",
|
||||||
|
"This is <strong>my</strong> reply",
|
||||||
|
)
|
||||||
|
.make_reply_to_raw(
|
||||||
|
&original_message,
|
||||||
|
event_id.clone(),
|
||||||
|
room_id,
|
||||||
|
ForwardThread::Yes,
|
||||||
|
AddMentions::No,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_matches!(reply.relates_to, Some(Relation::Reply { in_reply_to }));
|
||||||
|
assert_eq!(in_reply_to.event_id, event_id);
|
||||||
|
|
||||||
|
assert_matches!(reply.msgtype, MessageType::Text(text_msg));
|
||||||
|
assert_eq!(text_msg.body, "This is **my** reply");
|
||||||
|
assert_eq!(text_msg.formatted.unwrap().body, "This is <strong>my</strong> reply");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reply_to_raw_no_sender() {
|
||||||
|
let room_id = room_id!("!roomid:notareal.hs");
|
||||||
|
let event_id = owned_event_id!("$143273582443PhrSn");
|
||||||
|
|
||||||
|
let original_message: Raw<AnySyncTimelineEvent> = from_json_value(json!({
|
||||||
|
"content": {
|
||||||
|
"body": "Hello, World!",
|
||||||
|
"msgtype": "m.text",
|
||||||
|
},
|
||||||
|
"event_id": event_id,
|
||||||
|
"origin_server_ts": 134_829_848,
|
||||||
|
"type": "m.room.message",
|
||||||
|
}))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let reply = RoomMessageEventContent::text_html(
|
||||||
|
"This is **my** reply",
|
||||||
|
"This is <strong>my</strong> reply",
|
||||||
|
)
|
||||||
|
.make_reply_to_raw(
|
||||||
|
&original_message,
|
||||||
|
event_id.clone(),
|
||||||
|
room_id,
|
||||||
|
ForwardThread::Yes,
|
||||||
|
AddMentions::No,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_matches!(reply.relates_to, Some(Relation::Reply { in_reply_to }));
|
||||||
|
assert_eq!(in_reply_to.event_id, event_id);
|
||||||
|
|
||||||
|
assert_matches!(reply.msgtype, MessageType::Text(text_msg));
|
||||||
|
assert_eq!(text_msg.body, "This is **my** reply");
|
||||||
|
assert_eq!(text_msg.formatted.unwrap().body, "This is <strong>my</strong> reply");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reply_to_raw_forward_thread() {
|
||||||
|
let room_id = room_id!("!roomid:notareal.hs");
|
||||||
|
let event_id = owned_event_id!("$143273582443PhrSn");
|
||||||
|
|
||||||
|
let original_message: Raw<AnySyncTimelineEvent> = from_json_value(json!({
|
||||||
|
"content": {
|
||||||
|
"m.relates_to": {
|
||||||
|
"rel_type": "m.thread",
|
||||||
|
"event_id": "$threadroot",
|
||||||
|
"m.in_reply_to": {
|
||||||
|
"event_id": "$repliedto",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"event_id": event_id,
|
||||||
|
"origin_server_ts": 134_829_848,
|
||||||
|
"sender": "@user:notareal.hs",
|
||||||
|
"type": "m.room.message",
|
||||||
|
}))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let reply = RoomMessageEventContent::text_html(
|
||||||
|
"This is **my** reply",
|
||||||
|
"This is <strong>my</strong> reply",
|
||||||
|
)
|
||||||
|
.make_reply_to_raw(
|
||||||
|
&original_message,
|
||||||
|
event_id.clone(),
|
||||||
|
room_id,
|
||||||
|
ForwardThread::Yes,
|
||||||
|
AddMentions::No,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_matches!(reply.relates_to, Some(Relation::Thread(thread)));
|
||||||
|
assert_eq!(thread.event_id, "$threadroot");
|
||||||
|
assert_eq!(thread.in_reply_to.unwrap().event_id, event_id);
|
||||||
|
|
||||||
|
assert_matches!(reply.msgtype, MessageType::Text(text_msg));
|
||||||
|
assert_eq!(text_msg.body, "This is **my** reply");
|
||||||
|
assert_eq!(text_msg.formatted.unwrap().body, "This is <strong>my</strong> reply");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reply_to_raw_add_mentions() {
|
||||||
|
let room_id = room_id!("!roomid:notareal.hs");
|
||||||
|
let event_id = owned_event_id!("$143273582443PhrSn");
|
||||||
|
|
||||||
|
let user_id = owned_user_id!("@user:notareal.hs");
|
||||||
|
let other_user_id = owned_user_id!("@other_user:notareal.hs");
|
||||||
|
|
||||||
|
let original_message: Raw<AnySyncTimelineEvent> = from_json_value(json!({
|
||||||
|
"content": {
|
||||||
|
"m.mentions": {
|
||||||
|
"user_ids": [other_user_id],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"event_id": event_id,
|
||||||
|
"origin_server_ts": 134_829_848,
|
||||||
|
"sender": user_id,
|
||||||
|
"type": "m.room.message",
|
||||||
|
}))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let reply = RoomMessageEventContent::text_html(
|
||||||
|
"This is **my** reply",
|
||||||
|
"This is <strong>my</strong> reply",
|
||||||
|
)
|
||||||
|
.make_reply_to_raw(
|
||||||
|
&original_message,
|
||||||
|
event_id.clone(),
|
||||||
|
room_id,
|
||||||
|
ForwardThread::Yes,
|
||||||
|
AddMentions::Yes,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_matches!(reply.relates_to, Some(Relation::Reply { in_reply_to }));
|
||||||
|
assert_eq!(in_reply_to.event_id, event_id);
|
||||||
|
|
||||||
|
assert_matches!(reply.msgtype, MessageType::Text(text_msg));
|
||||||
|
assert_eq!(text_msg.body, "This is **my** reply");
|
||||||
|
assert_eq!(text_msg.formatted.unwrap().body, "This is <strong>my</strong> reply");
|
||||||
|
|
||||||
|
assert_eq!(reply.mentions.unwrap().user_ids, BTreeSet::from([user_id, other_user_id]));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn make_replacement_no_reply() {
|
fn make_replacement_no_reply() {
|
||||||
let content = RoomMessageEventContent::text_html(
|
let content = RoomMessageEventContent::text_html(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user