event: Add reply-making methods to RoomMessageEventWithoutRelation
This commit is contained in:
parent
533beb600f
commit
440a563355
@ -6,6 +6,10 @@ Improvements:
|
|||||||
mentioned in the original message to mentions (only the sender of the original message)
|
mentioned in the original message to mentions (only the sender of the original message)
|
||||||
- Add convenience constructors like `text_plain` to `RoomMessageEventContentWithoutRelation`
|
- Add convenience constructors like `text_plain` to `RoomMessageEventContentWithoutRelation`
|
||||||
- These are the same that are already available on `RoomMessageEventContent`
|
- These are the same that are already available on `RoomMessageEventContent`
|
||||||
|
- Add methods on `RoomMessageEventWithoutRelation` that were previously only available on
|
||||||
|
`RoomMessageEventContent`:
|
||||||
|
- `make_reply_to`
|
||||||
|
- `make_reply_to_raw`
|
||||||
|
|
||||||
# 0.27.0
|
# 0.27.0
|
||||||
|
|
||||||
|
@ -4,16 +4,16 @@
|
|||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use as_variant::as_variant;
|
|
||||||
use ruma_common::{
|
use ruma_common::{
|
||||||
serde::{JsonObject, Raw, StringEnum},
|
serde::{JsonObject, Raw, StringEnum},
|
||||||
OwnedEventId, OwnedUserId, RoomId, UserId,
|
OwnedEventId, RoomId,
|
||||||
};
|
};
|
||||||
#[cfg(feature = "html")]
|
#[cfg(feature = "html")]
|
||||||
use ruma_html::{sanitize_html, HtmlSanitizerMode, RemoveReplyFallback};
|
use ruma_html::{sanitize_html, HtmlSanitizerMode, RemoveReplyFallback};
|
||||||
use ruma_macros::EventContent;
|
use ruma_macros::EventContent;
|
||||||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||||
use serde_json::Value as JsonValue;
|
use serde_json::Value as JsonValue;
|
||||||
|
use tracing::warn;
|
||||||
|
|
||||||
use self::reply::OriginalEventData;
|
use self::reply::OriginalEventData;
|
||||||
#[cfg(feature = "html")]
|
#[cfg(feature = "html")]
|
||||||
@ -157,29 +157,12 @@ 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 {
|
||||||
self.msgtype.add_reply_fallback(original_message.into());
|
self.without_relation().make_reply_to(original_message, forward_thread, add_mentions)
|
||||||
let original_event_id = original_message.event_id.clone();
|
|
||||||
|
|
||||||
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 sender_for_mentions =
|
|
||||||
(add_mentions == AddMentions::Yes).then_some(&*original_message.sender);
|
|
||||||
|
|
||||||
self.make_reply_tweaks(original_event_id, original_thread_id, sender_for_mentions)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Turns `self` into a reply to the given raw event.
|
/// Turns `self` into a reply to the given raw event.
|
||||||
@ -201,84 +184,20 @@ 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_raw(
|
pub fn make_reply_to_raw(
|
||||||
mut self,
|
self,
|
||||||
original_event: &Raw<AnySyncTimelineEvent>,
|
original_event: &Raw<AnySyncTimelineEvent>,
|
||||||
original_event_id: OwnedEventId,
|
original_event_id: OwnedEventId,
|
||||||
room_id: &RoomId,
|
room_id: &RoomId,
|
||||||
forward_thread: ForwardThread,
|
forward_thread: ForwardThread,
|
||||||
add_mentions: AddMentions,
|
add_mentions: AddMentions,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
#[derive(Deserialize)]
|
self.without_relation().make_reply_to_raw(
|
||||||
struct ContentDeHelper {
|
original_event,
|
||||||
body: Option<String>,
|
original_event_id,
|
||||||
#[serde(flatten)]
|
room_id,
|
||||||
formatted: Option<FormattedBody>,
|
forward_thread,
|
||||||
#[cfg(feature = "unstable-msc1767")]
|
add_mentions,
|
||||||
#[serde(rename = "org.matrix.msc1767")]
|
)
|
||||||
text: Option<String>,
|
|
||||||
#[serde(rename = "m.relates_to")]
|
|
||||||
relates_to: Option<crate::room::encrypted::Relation>,
|
|
||||||
}
|
|
||||||
|
|
||||||
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.
|
|
||||||
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.msgtype.add_reply_fallback(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 sender_for_mentions = sender.as_deref().filter(|_| add_mentions == AddMentions::Yes);
|
|
||||||
self.make_reply_tweaks(original_event_id, original_thread_id, sender_for_mentions)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn make_reply_tweaks(
|
|
||||||
mut self,
|
|
||||||
original_event_id: OwnedEventId,
|
|
||||||
original_thread_id: Option<OwnedEventId>,
|
|
||||||
sender_for_mentions: 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 {
|
|
||||||
Relation::Reply { in_reply_to: InReplyTo { event_id: original_event_id.to_owned() } }
|
|
||||||
};
|
|
||||||
self.relates_to = Some(relates_to);
|
|
||||||
|
|
||||||
if let Some(sender) = sender_for_mentions {
|
|
||||||
self.mentions.get_or_insert_with(Mentions::new).user_ids.insert(sender.to_owned());
|
|
||||||
}
|
|
||||||
|
|
||||||
self
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Turns `self` into a new message for a thread, that is optionally a reply.
|
/// Turns `self` into a new message for a thread, that is optionally a reply.
|
||||||
@ -485,6 +404,14 @@ impl RoomMessageEventContent {
|
|||||||
|
|
||||||
self.msgtype.sanitize(mode, remove_reply_fallback);
|
self.msgtype.sanitize(mode, remove_reply_fallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn without_relation(self) -> RoomMessageEventContentWithoutRelation {
|
||||||
|
if self.relates_to.is_some() {
|
||||||
|
warn!("Overwriting existing relates_to value");
|
||||||
|
}
|
||||||
|
|
||||||
|
self.into()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether or not to forward a [`Relation::Thread`] when sending a reply.
|
/// Whether or not to forward a [`Relation::Thread`] when sending a reply.
|
||||||
|
@ -1,7 +1,20 @@
|
|||||||
use serde::Serialize;
|
use as_variant::as_variant;
|
||||||
|
use ruma_common::{serde::Raw, OwnedEventId, OwnedUserId, RoomId, UserId};
|
||||||
|
#[cfg(doc)]
|
||||||
|
use ruma_html::{sanitize_html, RemoveReplyFallback};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use super::{MessageType, Relation, RoomMessageEventContent};
|
#[cfg(doc)]
|
||||||
use crate::Mentions;
|
use super::OriginalSyncRoomMessageEvent;
|
||||||
|
use super::{
|
||||||
|
AddMentions, ForwardThread, MessageType, OriginalRoomMessageEvent, Relation,
|
||||||
|
RoomMessageEventContent,
|
||||||
|
};
|
||||||
|
use crate::{
|
||||||
|
relation::{InReplyTo, Thread},
|
||||||
|
room::message::{reply::OriginalEventData, FormattedBody},
|
||||||
|
AnySyncTimelineEvent, Mentions,
|
||||||
|
};
|
||||||
|
|
||||||
/// Form of [`RoomMessageEventContent`] without relation.
|
/// Form of [`RoomMessageEventContent`] without relation.
|
||||||
#[derive(Clone, Debug, Serialize)]
|
#[derive(Clone, Debug, Serialize)]
|
||||||
@ -82,6 +95,142 @@ impl RoomMessageEventContentWithoutRelation {
|
|||||||
let Self { msgtype, mentions } = self;
|
let Self { msgtype, mentions } = self;
|
||||||
RoomMessageEventContent { msgtype, relates_to, mentions }
|
RoomMessageEventContent { msgtype, relates_to, mentions }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Turns `self` into a reply to the given message.
|
||||||
|
///
|
||||||
|
/// Takes the `body` / `formatted_body` (if any) in `self` for the main text and prepends a
|
||||||
|
/// quoted version of `original_message`. 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.
|
||||||
|
#[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.
|
||||||
|
#[track_caller]
|
||||||
|
pub fn make_reply_to(
|
||||||
|
mut self,
|
||||||
|
original_message: &OriginalRoomMessageEvent,
|
||||||
|
forward_thread: ForwardThread,
|
||||||
|
add_mentions: AddMentions,
|
||||||
|
) -> RoomMessageEventContent {
|
||||||
|
self.msgtype.add_reply_fallback(original_message.into());
|
||||||
|
let original_event_id = original_message.event_id.clone();
|
||||||
|
|
||||||
|
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 sender_for_mentions =
|
||||||
|
(add_mentions == AddMentions::Yes).then_some(&*original_message.sender);
|
||||||
|
|
||||||
|
self.make_reply_tweaks(original_event_id, original_thread_id, sender_for_mentions)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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(
|
||||||
|
mut self,
|
||||||
|
original_event: &Raw<AnySyncTimelineEvent>,
|
||||||
|
original_event_id: OwnedEventId,
|
||||||
|
room_id: &RoomId,
|
||||||
|
forward_thread: ForwardThread,
|
||||||
|
add_mentions: AddMentions,
|
||||||
|
) -> RoomMessageEventContent {
|
||||||
|
#[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>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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.
|
||||||
|
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.msgtype.add_reply_fallback(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 sender_for_mentions = sender.as_deref().filter(|_| add_mentions == AddMentions::Yes);
|
||||||
|
self.make_reply_tweaks(original_event_id, original_thread_id, sender_for_mentions)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_reply_tweaks(
|
||||||
|
mut self,
|
||||||
|
original_event_id: OwnedEventId,
|
||||||
|
original_thread_id: Option<OwnedEventId>,
|
||||||
|
sender_for_mentions: Option<&UserId>,
|
||||||
|
) -> RoomMessageEventContent {
|
||||||
|
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 {
|
||||||
|
Relation::Reply { in_reply_to: InReplyTo { event_id: original_event_id.to_owned() } }
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(sender) = sender_for_mentions {
|
||||||
|
self.mentions.get_or_insert_with(Mentions::new).user_ids.insert(sender.to_owned());
|
||||||
|
}
|
||||||
|
|
||||||
|
self.with_relation(Some(relates_to))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<MessageType> for RoomMessageEventContentWithoutRelation {
|
impl From<MessageType> for RoomMessageEventContentWithoutRelation {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user