@@ -9,9 +9,12 @@ use serde::{de::DeserializeOwned, Deserialize, Serialize};
9
9
use serde_json:: Value as JsonValue ;
10
10
11
11
use crate :: {
12
- events:: relation:: { CustomRelation , InReplyTo , RelationType , Replacement , Thread } ,
12
+ events:: {
13
+ relation:: { CustomRelation , InReplyTo , RelationType , Replacement , Thread } ,
14
+ Mentions ,
15
+ } ,
13
16
serde:: { JsonObject , StringEnum } ,
14
- EventId , OwnedEventId , PrivOwnedStr ,
17
+ EventId , PrivOwnedStr ,
15
18
} ;
16
19
17
20
mod audio;
@@ -64,13 +67,23 @@ pub struct RoomMessageEventContent {
64
67
///
65
68
/// [related messages]: https://spec.matrix.org/latest/client-server-api/#forming-relationships-between-events
66
69
#[ serde( flatten, skip_serializing_if = "Option::is_none" ) ]
67
- pub relates_to : Option < Relation < MessageType > > ,
70
+ pub relates_to : Option < Relation < RoomMessageEventContentWithoutRelation > > ,
71
+
72
+ /// The [mentions] of this event.
73
+ ///
74
+ /// This should always be set to avoid triggering the legacy mention push rules. It is
75
+ /// recommended to use [`Self::set_mentions()`] to set this field, that will take care of
76
+ /// populating the fields correctly if this is a replacement.
77
+ ///
78
+ /// [mentions]: https://spec.matrix.org/latest/client-server-api/#user-and-room-mentions
79
+ #[ serde( rename = "m.mentions" , skip_serializing_if = "Option::is_none" ) ]
80
+ pub mentions : Option < Mentions > ,
68
81
}
69
82
70
83
impl RoomMessageEventContent {
71
84
/// Create a `RoomMessageEventContent` with the given `MessageType`.
72
85
pub fn new ( msgtype : MessageType ) -> Self {
73
- Self { msgtype, relates_to : None }
86
+ Self { msgtype, relates_to : None , mentions : None }
74
87
}
75
88
76
89
/// A constructor to create a plain text message.
@@ -247,6 +260,10 @@ impl RoomMessageEventContent {
247
260
/// `original_message`.
248
261
#[ doc = include_str ! ( concat!( env!( "CARGO_MANIFEST_DIR" ) , "/src/doc/rich_reply.md" ) ) ]
249
262
///
263
+ /// If the message that is replaced contains [`Mentions`], they are copied into
264
+ /// `m.new_content` to keep the same mentions, but not into `content` to avoid repeated
265
+ /// notifications.
266
+ ///
250
267
/// # Panics
251
268
///
252
269
/// Panics if `self` has a `formatted_body` with a format other than HTML.
@@ -255,13 +272,16 @@ impl RoomMessageEventContent {
255
272
#[ track_caller]
256
273
pub fn make_replacement (
257
274
mut self ,
258
- original_message_id : OwnedEventId ,
275
+ original_message : & OriginalSyncRoomMessageEvent ,
259
276
replied_to_message : Option < & OriginalRoomMessageEvent > ,
260
277
) -> Self {
261
278
// Prepare relates_to with the untouched msgtype.
262
279
let relates_to = Relation :: Replacement ( Replacement {
263
- event_id : original_message_id,
264
- new_content : self . msgtype . clone ( ) ,
280
+ event_id : original_message. event_id . clone ( ) ,
281
+ new_content : RoomMessageEventContentWithoutRelation {
282
+ msgtype : self . msgtype . clone ( ) ,
283
+ mentions : original_message. content . mentions . clone ( ) ,
284
+ } ,
265
285
} ) ;
266
286
267
287
let empty_formatted_body = || FormattedBody :: html ( String :: new ( ) ) ;
@@ -311,6 +331,43 @@ impl RoomMessageEventContent {
311
331
self
312
332
}
313
333
334
+ /// Set the [mentions] of this event.
335
+ ///
336
+ /// If this event is a replacement, it will update the mentions both in the `content` and the
337
+ /// `m.new_content` so only new mentions will trigger a notification. As such, this needs to be
338
+ /// called after [`Self::make_replacement()`].
339
+ ///
340
+ /// [mentions]: https://spec.matrix.org/latest/client-server-api/#user-and-room-mentions
341
+ pub fn set_mentions ( mut self , mentions : Mentions ) -> Self {
342
+ if let Some ( Relation :: Replacement ( replacement) ) = & mut self . relates_to {
343
+ let old_mentions = & replacement. new_content . mentions ;
344
+
345
+ let new_mentions = if let Some ( old_mentions) = old_mentions {
346
+ let mut new_mentions = Mentions :: new ( ) ;
347
+
348
+ new_mentions. user_ids = mentions
349
+ . user_ids
350
+ . iter ( )
351
+ . filter ( |u| !old_mentions. user_ids . contains ( * u) )
352
+ . cloned ( )
353
+ . collect ( ) ;
354
+
355
+ new_mentions. room = if old_mentions. room { false } else { mentions. room } ;
356
+
357
+ new_mentions
358
+ } else {
359
+ mentions. clone ( )
360
+ } ;
361
+
362
+ replacement. new_content . mentions = Some ( mentions) ;
363
+ self . mentions = Some ( new_mentions) ;
364
+ } else {
365
+ self . mentions = Some ( mentions) ;
366
+ }
367
+
368
+ self
369
+ }
370
+
314
371
/// Returns a reference to the `msgtype` string.
315
372
///
316
373
/// If you want to access the message type-specific data rather than the message type itself,
@@ -352,6 +409,44 @@ impl RoomMessageEventContent {
352
409
}
353
410
}
354
411
412
+ /// Form of [`RoomMessageEventContent`] without relation.
413
+ ///
414
+ /// To construct this type, construct a [`RoomMessageEventContent`] and then use one of its ::from()
415
+ /// / .into() methods.
416
+ #[ derive( Clone , Debug , Serialize ) ]
417
+ #[ cfg_attr( not( feature = "unstable-exhaustive-types" ) , non_exhaustive) ]
418
+ pub struct RoomMessageEventContentWithoutRelation {
419
+ /// A key which identifies the type of message being sent.
420
+ ///
421
+ /// This also holds the specific content of each message.
422
+ #[ serde( flatten) ]
423
+ pub msgtype : MessageType ,
424
+
425
+ /// The [mentions] of this event.
426
+ ///
427
+ /// [mentions]: https://spec.matrix.org/latest/client-server-api/#user-and-room-mentions
428
+ #[ serde( rename = "m.mentions" , skip_serializing_if = "Option::is_none" ) ]
429
+ pub mentions : Option < Mentions > ,
430
+ }
431
+
432
+ impl RoomMessageEventContentWithoutRelation {
433
+ /// Transform `self` into a `RoomMessageEventContent` with the given relation.
434
+ pub fn with_relation (
435
+ self ,
436
+ relates_to : Option < Relation < RoomMessageEventContentWithoutRelation > > ,
437
+ ) -> RoomMessageEventContent {
438
+ let Self { msgtype, mentions } = self ;
439
+ RoomMessageEventContent { msgtype, relates_to, mentions }
440
+ }
441
+ }
442
+
443
+ impl From < RoomMessageEventContent > for RoomMessageEventContentWithoutRelation {
444
+ fn from ( value : RoomMessageEventContent ) -> Self {
445
+ let RoomMessageEventContent { msgtype, mentions, .. } = value;
446
+ Self { msgtype, mentions }
447
+ }
448
+ }
449
+
355
450
/// Whether or not to forward a [`Relation::Thread`] when sending a reply.
356
451
#[ derive( Clone , Copy , Debug , PartialEq , Eq ) ]
357
452
#[ allow( clippy:: exhaustive_enums) ]
0 commit comments