Skip to main content

ruma_events/room/message/
without_relation.rs

1use serde::Serialize;
2
3use super::{
4    AddMentions, ForwardThread, MessageType, Relation, ReplacementMetadata, ReplyMetadata,
5    ReplyWithinThread, RoomMessageEventContent,
6};
7#[cfg(feature = "unstable-msc4471")]
8use crate::stream::StreamDescriptor;
9use crate::{
10    Mentions,
11    relation::{InReplyTo, Replacement, Reply, Thread},
12};
13
14/// Form of [`RoomMessageEventContent`] without relation.
15#[derive(Clone, Debug, Serialize)]
16#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
17pub struct RoomMessageEventContentWithoutRelation {
18    /// A key which identifies the type of message being sent.
19    ///
20    /// This also holds the specific content of each message.
21    #[serde(flatten)]
22    pub msgtype: MessageType,
23
24    /// The [mentions] of this event.
25    ///
26    /// [mentions]: https://spec.matrix.org/v1.18/client-server-api/#user-and-room-mentions
27    #[serde(rename = "m.mentions", skip_serializing_if = "Option::is_none")]
28    pub mentions: Option<Mentions>,
29
30    /// See [`RoomMessageEventContent::stream`].
31    ///
32    /// [`RoomMessageEventContent::stream`]: super::RoomMessageEventContent::stream
33    #[cfg(feature = "unstable-msc4471")]
34    #[serde(rename = "org.matrix.msc4471.stream", skip_serializing_if = "Option::is_none")]
35    pub stream: Option<StreamDescriptor>,
36}
37
38impl RoomMessageEventContentWithoutRelation {
39    /// Creates a new `RoomMessageEventContentWithoutRelation` with the given `MessageType`.
40    pub fn new(msgtype: MessageType) -> Self {
41        Self {
42            msgtype,
43            mentions: None,
44            #[cfg(feature = "unstable-msc4471")]
45            stream: None,
46        }
47    }
48
49    /// A constructor to create a plain text message.
50    pub fn text_plain(body: impl Into<String>) -> Self {
51        Self::new(MessageType::text_plain(body))
52    }
53
54    /// A constructor to create an html message.
55    pub fn text_html(body: impl Into<String>, html_body: impl Into<String>) -> Self {
56        Self::new(MessageType::text_html(body, html_body))
57    }
58
59    /// A constructor to create a markdown message.
60    #[cfg(feature = "markdown")]
61    pub fn text_markdown(body: impl AsRef<str> + Into<String>) -> Self {
62        Self::new(MessageType::text_markdown(body))
63    }
64
65    /// A constructor to create a plain text notice.
66    pub fn notice_plain(body: impl Into<String>) -> Self {
67        Self::new(MessageType::notice_plain(body))
68    }
69
70    /// A constructor to create an html notice.
71    pub fn notice_html(body: impl Into<String>, html_body: impl Into<String>) -> Self {
72        Self::new(MessageType::notice_html(body, html_body))
73    }
74
75    /// A constructor to create a markdown notice.
76    #[cfg(feature = "markdown")]
77    pub fn notice_markdown(body: impl AsRef<str> + Into<String>) -> Self {
78        Self::new(MessageType::notice_markdown(body))
79    }
80
81    /// A constructor to create a plain text emote.
82    pub fn emote_plain(body: impl Into<String>) -> Self {
83        Self::new(MessageType::emote_plain(body))
84    }
85
86    /// A constructor to create an html emote.
87    pub fn emote_html(body: impl Into<String>, html_body: impl Into<String>) -> Self {
88        Self::new(MessageType::emote_html(body, html_body))
89    }
90
91    /// A constructor to create a markdown emote.
92    #[cfg(feature = "markdown")]
93    pub fn emote_markdown(body: impl AsRef<str> + Into<String>) -> Self {
94        Self::new(MessageType::emote_markdown(body))
95    }
96
97    /// Transform `self` into a `RoomMessageEventContent` with the given relation.
98    pub fn with_relation(
99        self,
100        relates_to: Option<Relation<RoomMessageEventContentWithoutRelation>>,
101    ) -> RoomMessageEventContent {
102        let Self {
103            msgtype,
104            mentions,
105            #[cfg(feature = "unstable-msc4471")]
106            stream,
107        } = self;
108        RoomMessageEventContent {
109            msgtype,
110            relates_to,
111            mentions,
112            #[cfg(feature = "unstable-msc4471")]
113            stream,
114        }
115    }
116
117    /// Turns `self` into a [rich reply] to the message using the given metadata.
118    ///
119    /// Sets the `in_reply_to` field inside `relates_to`, and optionally the `rel_type` to
120    /// `m.thread` if the metadata has a `thread` and `ForwardThread::Yes` is used.
121    ///
122    /// If `AddMentions::Yes` is used, the `sender` in the metadata is added as a user mention.
123    ///
124    /// [rich reply]: https://spec.matrix.org/v1.18/client-server-api/#rich-replies
125    #[track_caller]
126    pub fn make_reply_to<'a>(
127        mut self,
128        metadata: impl Into<ReplyMetadata<'a>>,
129        forward_thread: ForwardThread,
130        add_mentions: AddMentions,
131    ) -> RoomMessageEventContent {
132        let metadata = metadata.into();
133        let original_event_id = metadata.event_id.to_owned();
134
135        let original_thread_id = metadata
136            .thread
137            .filter(|_| forward_thread == ForwardThread::Yes)
138            .map(|thread| thread.event_id.clone());
139        let relates_to = if let Some(event_id) = original_thread_id {
140            Relation::Thread(Thread::plain(event_id.to_owned(), original_event_id.to_owned()))
141        } else {
142            Relation::Reply(Reply::with_event_id(original_event_id.to_owned()))
143        };
144
145        if add_mentions == AddMentions::Yes {
146            self.mentions
147                .get_or_insert_with(Mentions::new)
148                .user_ids
149                .insert(metadata.sender.to_owned());
150        }
151
152        self.with_relation(Some(relates_to))
153    }
154
155    /// Turns `self` into a new message for a [thread], that is optionally a reply.
156    ///
157    /// Looks for the `thread` in the given metadata. If it exists, this message will be in the same
158    /// thread. If it doesn't, a new thread is created with the `event_id` in the metadata as the
159    /// root.
160    ///
161    /// It also sets the `in_reply_to` field inside `relates_to` to point the `event_id`
162    /// in the metadata. If `ReplyWithinThread::Yes` is used, the metadata should be constructed
163    /// from the event to make a reply to, otherwise it should be constructed from the latest
164    /// event in the thread.
165    ///
166    /// If `AddMentions::Yes` is used, the `sender` in the metadata is added as a user mention.
167    ///
168    /// [thread]: https://spec.matrix.org/v1.18/client-server-api/#threading
169    pub fn make_for_thread<'a>(
170        self,
171        metadata: impl Into<ReplyMetadata<'a>>,
172        is_reply: ReplyWithinThread,
173        add_mentions: AddMentions,
174    ) -> RoomMessageEventContent {
175        let metadata = metadata.into();
176
177        let mut content = if is_reply == ReplyWithinThread::Yes {
178            self.make_reply_to(metadata, ForwardThread::No, add_mentions)
179        } else {
180            self.into()
181        };
182
183        let thread_root = if let Some(Thread { event_id, .. }) = &metadata.thread {
184            event_id.to_owned()
185        } else {
186            metadata.event_id.to_owned()
187        };
188
189        content.relates_to = Some(Relation::Thread(Thread {
190            event_id: thread_root,
191            in_reply_to: Some(InReplyTo { event_id: metadata.event_id.to_owned() }),
192            is_falling_back: is_reply == ReplyWithinThread::No,
193        }));
194
195        content
196    }
197
198    /// Turns `self` into a [replacement] (or edit) for a given message.
199    ///
200    /// The first argument after `self` can be `&OriginalRoomMessageEvent` or
201    /// `&OriginalSyncRoomMessageEvent` if you don't want to create `ReplacementMetadata` separately
202    /// before calling this function.
203    ///
204    /// This takes the content and sets it in `m.new_content`, and modifies the `content` to include
205    /// a fallback.
206    ///
207    /// If this message contains [`Mentions`], they are copied into `m.new_content` to keep the same
208    /// mentions, but the ones in `content` are filtered with the ones in the
209    /// [`ReplacementMetadata`] so only new mentions will trigger a notification.
210    ///
211    /// # Panics
212    ///
213    /// Panics if `self` has a `formatted_body` with a format other than HTML.
214    ///
215    /// [replacement]: https://spec.matrix.org/v1.18/client-server-api/#event-replacements
216    #[track_caller]
217    pub fn make_replacement(
218        mut self,
219        metadata: impl Into<ReplacementMetadata>,
220    ) -> RoomMessageEventContent {
221        let metadata = metadata.into();
222
223        let mentions = self.mentions.take();
224
225        // Only set mentions that were not there before.
226        if let Some(mentions) = &mentions {
227            let new_mentions = metadata
228                .mentions
229                .map(|old_mentions| {
230                    let mut new_mentions = Mentions::new();
231
232                    new_mentions.user_ids = mentions
233                        .user_ids
234                        .iter()
235                        .filter(|u| !old_mentions.user_ids.contains(*u))
236                        .cloned()
237                        .collect();
238
239                    new_mentions.room = mentions.room && !old_mentions.room;
240
241                    new_mentions
242                })
243                .unwrap_or_else(|| mentions.clone());
244
245            self.mentions = Some(new_mentions);
246        }
247
248        // Prepare relates_to with the untouched msgtype.
249        let relates_to = Relation::Replacement(Replacement {
250            event_id: metadata.event_id,
251            new_content: RoomMessageEventContentWithoutRelation {
252                msgtype: self.msgtype.clone(),
253                mentions,
254                #[cfg(feature = "unstable-msc4471")]
255                stream: self.stream.clone(),
256            },
257        });
258
259        self.msgtype.make_replacement_body();
260
261        let mut content = RoomMessageEventContent::from(self);
262        content.relates_to = Some(relates_to);
263
264        content
265    }
266
267    /// Add the given [mentions] to this event.
268    ///
269    /// If no [`Mentions`] was set on this events, this sets it. Otherwise, this updates the current
270    /// mentions by extending the previous `user_ids` with the new ones, and applies a logical OR to
271    /// the values of `room`.
272    ///
273    /// [mentions]: https://spec.matrix.org/v1.18/client-server-api/#user-and-room-mentions
274    pub fn add_mentions(mut self, mentions: Mentions) -> Self {
275        self.mentions.get_or_insert_with(Mentions::new).add(mentions);
276        self
277    }
278}
279
280impl From<MessageType> for RoomMessageEventContentWithoutRelation {
281    fn from(msgtype: MessageType) -> Self {
282        Self::new(msgtype)
283    }
284}
285
286impl From<RoomMessageEventContent> for RoomMessageEventContentWithoutRelation {
287    fn from(value: RoomMessageEventContent) -> Self {
288        let RoomMessageEventContent {
289            msgtype,
290            mentions,
291            #[cfg(feature = "unstable-msc4471")]
292            stream,
293            ..
294        } = value;
295        Self {
296            msgtype,
297            mentions,
298            #[cfg(feature = "unstable-msc4471")]
299            stream,
300        }
301    }
302}
303
304impl From<RoomMessageEventContentWithoutRelation> for RoomMessageEventContent {
305    fn from(value: RoomMessageEventContentWithoutRelation) -> Self {
306        let RoomMessageEventContentWithoutRelation {
307            msgtype,
308            mentions,
309            #[cfg(feature = "unstable-msc4471")]
310            stream,
311        } = value;
312        Self {
313            msgtype,
314            relates_to: None,
315            mentions,
316            #[cfg(feature = "unstable-msc4471")]
317            stream,
318        }
319    }
320}