use as_variant::as_variant;
use ruma_common::{serde::Raw, OwnedEventId, OwnedUserId, RoomId, UserId};
use serde::{Deserialize, Serialize};
use super::{
AddMentions, ForwardThread, MessageType, OriginalRoomMessageEvent, Relation,
RoomMessageEventContent,
};
use crate::{
relation::{InReplyTo, Thread},
room::message::{reply::OriginalEventData, FormattedBody},
AnySyncTimelineEvent, Mentions,
};
#[derive(Clone, Debug, Serialize)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub struct RoomMessageEventContentWithoutRelation {
#[serde(flatten)]
pub msgtype: MessageType,
#[serde(rename = "m.mentions", skip_serializing_if = "Option::is_none")]
pub mentions: Option<Mentions>,
}
impl RoomMessageEventContentWithoutRelation {
pub fn new(msgtype: MessageType) -> Self {
Self { msgtype, mentions: None }
}
pub fn text_plain(body: impl Into<String>) -> Self {
Self::new(MessageType::text_plain(body))
}
pub fn text_html(body: impl Into<String>, html_body: impl Into<String>) -> Self {
Self::new(MessageType::text_html(body, html_body))
}
#[cfg(feature = "markdown")]
pub fn text_markdown(body: impl AsRef<str> + Into<String>) -> Self {
Self::new(MessageType::text_markdown(body))
}
pub fn notice_plain(body: impl Into<String>) -> Self {
Self::new(MessageType::notice_plain(body))
}
pub fn notice_html(body: impl Into<String>, html_body: impl Into<String>) -> Self {
Self::new(MessageType::notice_html(body, html_body))
}
#[cfg(feature = "markdown")]
pub fn notice_markdown(body: impl AsRef<str> + Into<String>) -> Self {
Self::new(MessageType::notice_markdown(body))
}
pub fn emote_plain(body: impl Into<String>) -> Self {
Self::new(MessageType::emote_plain(body))
}
pub fn emote_html(body: impl Into<String>, html_body: impl Into<String>) -> Self {
Self::new(MessageType::emote_html(body, html_body))
}
#[cfg(feature = "markdown")]
pub fn emote_markdown(body: impl AsRef<str> + Into<String>) -> Self {
Self::new(MessageType::emote_markdown(body))
}
pub fn with_relation(
self,
relates_to: Option<Relation<RoomMessageEventContentWithoutRelation>>,
) -> RoomMessageEventContent {
let Self { msgtype, mentions } = self;
RoomMessageEventContent { msgtype, relates_to, mentions }
}
#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/rich_reply.md"))]
#[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)
}
#[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")]
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?))
});
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)
}
pub fn add_mentions(mut self, mentions: Mentions) -> Self {
self.mentions.get_or_insert_with(Mentions::new).add(mentions);
self
}
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 {
fn from(msgtype: MessageType) -> Self {
Self::new(msgtype)
}
}
impl From<RoomMessageEventContent> for RoomMessageEventContentWithoutRelation {
fn from(value: RoomMessageEventContent) -> Self {
let RoomMessageEventContent { msgtype, mentions, .. } = value;
Self { msgtype, mentions }
}
}