1#![allow(missing_docs)]
16
17use std::{
18 collections::{BTreeMap, BTreeSet},
19 sync::atomic::{AtomicU64, Ordering::SeqCst},
20 time::Duration,
21};
22
23use as_variant::as_variant;
24use matrix_sdk_common::deserialized_responses::{
25 TimelineEvent, UnableToDecryptInfo, UnableToDecryptReason,
26};
27use ruma::{
28 EventId, Int, MilliSecondsSinceUnixEpoch, MxcUri, OwnedEventId, OwnedMxcUri, OwnedRoomAliasId,
29 OwnedRoomId, OwnedTransactionId, OwnedUserId, OwnedVoipId, RoomId, RoomVersionId,
30 TransactionId, UInt, UserId, VoipVersionId,
31 events::{
32 AnyGlobalAccountDataEvent, AnyMessageLikeEvent, AnyStateEvent, AnyStrippedStateEvent,
33 AnySyncEphemeralRoomEvent, AnySyncMessageLikeEvent, AnySyncStateEvent,
34 AnySyncTimelineEvent, AnyTimelineEvent, BundledMessageLikeRelations,
35 EphemeralRoomEventContent, EventContentFromType, False, GlobalAccountDataEventContent,
36 Mentions, MessageLikeEvent, MessageLikeEventContent, PossiblyRedactedStateEventContent,
37 RedactContent, RedactedMessageLikeEventContent, RedactedStateEventContent, StateEvent,
38 StateEventContent, StaticEventContent, StaticStateEventContent, StrippedStateEvent,
39 SyncMessageLikeEvent, SyncStateEvent,
40 beacon::BeaconEventContent,
41 call::{SessionDescription, invite::CallInviteEventContent},
42 direct::{DirectEventContent, OwnedDirectUserIdentifier},
43 ignored_user_list::IgnoredUserListEventContent,
44 macros::EventContent,
45 member_hints::MemberHintsEventContent,
46 poll::{
47 unstable_end::UnstablePollEndEventContent,
48 unstable_response::UnstablePollResponseEventContent,
49 unstable_start::{
50 NewUnstablePollStartEventContent, ReplacementUnstablePollStartEventContent,
51 UnstablePollAnswer, UnstablePollStartContentBlock, UnstablePollStartEventContent,
52 },
53 },
54 push_rules::PushRulesEventContent,
55 reaction::ReactionEventContent,
56 receipt::{Receipt, ReceiptEventContent, ReceiptThread, ReceiptType},
57 relation::{Annotation, BundledThread, InReplyTo, Reference, Replacement, Thread},
58 room::{
59 ImageInfo,
60 avatar::{self, RoomAvatarEventContent},
61 canonical_alias::RoomCanonicalAliasEventContent,
62 create::{PreviousRoom, RoomCreateEventContent},
63 encrypted::{EncryptedEventScheme, RoomEncryptedEventContent},
64 member::{MembershipState, RoomMemberEventContent},
65 message::{
66 FormattedBody, GalleryItemType, GalleryMessageEventContent,
67 ImageMessageEventContent, MessageType, OriginalSyncRoomMessageEvent, Relation,
68 RelationWithoutReplacement, RoomMessageEventContent,
69 RoomMessageEventContentWithoutRelation,
70 },
71 name::RoomNameEventContent,
72 power_levels::RoomPowerLevelsEventContent,
73 redaction::RoomRedactionEventContent,
74 server_acl::RoomServerAclEventContent,
75 tombstone::RoomTombstoneEventContent,
76 topic::RoomTopicEventContent,
77 },
78 rtc::{
79 decline::RtcDeclineEventContent,
80 notification::{NotificationType, RtcNotificationEventContent},
81 },
82 space::{child::SpaceChildEventContent, parent::SpaceParentEventContent},
83 sticker::StickerEventContent,
84 typing::TypingEventContent,
85 },
86 push::Ruleset,
87 room::RoomType,
88 room_version_rules::AuthorizationRules,
89 serde::Raw,
90 server_name,
91};
92use serde::Serialize;
93use serde_json::json;
94
95pub trait TimestampArg {
96 fn to_milliseconds_since_unix_epoch(self) -> MilliSecondsSinceUnixEpoch;
97}
98
99impl TimestampArg for MilliSecondsSinceUnixEpoch {
100 fn to_milliseconds_since_unix_epoch(self) -> MilliSecondsSinceUnixEpoch {
101 self
102 }
103}
104
105impl TimestampArg for u64 {
106 fn to_milliseconds_since_unix_epoch(self) -> MilliSecondsSinceUnixEpoch {
107 MilliSecondsSinceUnixEpoch(UInt::try_from(self).unwrap())
108 }
109}
110
111#[derive(Debug, Serialize)]
113struct RedactedBecause {
114 content: RoomRedactionEventContent,
116
117 event_id: OwnedEventId,
119
120 sender: OwnedUserId,
122
123 origin_server_ts: MilliSecondsSinceUnixEpoch,
126}
127
128#[derive(Debug, Serialize)]
129struct Unsigned<C: StaticEventContent> {
130 #[serde(skip_serializing_if = "Option::is_none")]
131 prev_content: Option<C>,
132
133 #[serde(skip_serializing_if = "Option::is_none")]
134 transaction_id: Option<OwnedTransactionId>,
135
136 #[serde(rename = "m.relations", skip_serializing_if = "Option::is_none")]
137 relations: Option<BundledMessageLikeRelations<Raw<AnySyncTimelineEvent>>>,
138
139 #[serde(skip_serializing_if = "Option::is_none")]
140 redacted_because: Option<RedactedBecause>,
141
142 #[serde(skip_serializing_if = "Option::is_none")]
143 age: Option<Int>,
144}
145
146impl<C: StaticEventContent> Default for Unsigned<C> {
148 fn default() -> Self {
149 Self {
150 prev_content: None,
151 transaction_id: None,
152 relations: None,
153 redacted_because: None,
154 age: None,
155 }
156 }
157}
158
159#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
160enum EventFormat {
161 #[default]
164 Timeline,
165 SyncTimeline,
167 StrippedState,
169 Ephemeral,
171 GlobalAccountData,
173}
174
175impl EventFormat {
176 fn has_sender(self) -> bool {
178 matches!(self, Self::Timeline | Self::SyncTimeline | Self::StrippedState)
179 }
180
181 fn has_event_id(self) -> bool {
183 matches!(self, Self::Timeline | Self::SyncTimeline)
184 }
185
186 fn has_room_id(self) -> bool {
188 matches!(self, Self::Timeline)
189 }
190}
191
192#[derive(Debug)]
193pub struct EventBuilder<C: StaticEventContent<IsPrefix = False>> {
194 format: EventFormat,
198 sender: Option<OwnedUserId>,
199 room: Option<OwnedRoomId>,
200 event_id: Option<OwnedEventId>,
201 no_event_id: bool,
203 redacts: Option<OwnedEventId>,
204 content: C,
205 server_ts: MilliSecondsSinceUnixEpoch,
206 unsigned: Option<Unsigned<C>>,
207 state_key: Option<String>,
208}
209
210impl<E: StaticEventContent<IsPrefix = False>> EventBuilder<E> {
211 fn format(mut self, format: EventFormat) -> Self {
212 self.format = format;
213 self
214 }
215
216 pub fn room(mut self, room_id: &RoomId) -> Self {
217 self.room = Some(room_id.to_owned());
218 self
219 }
220
221 pub fn sender(mut self, sender: &UserId) -> Self {
222 self.sender = Some(sender.to_owned());
223 self
224 }
225
226 pub fn event_id(mut self, event_id: &EventId) -> Self {
227 self.event_id = Some(event_id.to_owned());
228 self.no_event_id = false;
229 self
230 }
231
232 pub fn no_event_id(mut self) -> Self {
233 self.event_id = None;
234 self.no_event_id = true;
235 self
236 }
237
238 pub fn server_ts(mut self, ts: impl TimestampArg) -> Self {
239 self.server_ts = ts.to_milliseconds_since_unix_epoch();
240 self
241 }
242
243 pub fn unsigned_transaction_id(mut self, transaction_id: &TransactionId) -> Self {
244 self.unsigned.get_or_insert_with(Default::default).transaction_id =
245 Some(transaction_id.to_owned());
246 self
247 }
248
249 pub fn age(mut self, age: impl Into<Int>) -> Self {
251 self.unsigned.get_or_insert_with(Default::default).age = Some(age.into());
252 self
253 }
254
255 pub fn with_bundled_thread_summary(
258 mut self,
259 latest_event: Raw<AnySyncMessageLikeEvent>,
260 count: usize,
261 current_user_participated: bool,
262 ) -> Self {
263 let relations = self
264 .unsigned
265 .get_or_insert_with(Default::default)
266 .relations
267 .get_or_insert_with(BundledMessageLikeRelations::new);
268 relations.thread = Some(Box::new(BundledThread::new(
269 latest_event,
270 UInt::try_from(count).unwrap(),
271 current_user_participated,
272 )));
273 self
274 }
275
276 pub fn with_bundled_edit(mut self, replacement: impl Into<Raw<AnySyncTimelineEvent>>) -> Self {
278 let relations = self
279 .unsigned
280 .get_or_insert_with(Default::default)
281 .relations
282 .get_or_insert_with(BundledMessageLikeRelations::new);
283 relations.replace = Some(Box::new(replacement.into()));
284 self
285 }
286
287 pub fn state_key(mut self, state_key: impl Into<String>) -> Self {
292 self.state_key = Some(state_key.into());
293 self
294 }
295}
296
297impl<E> EventBuilder<E>
298where
299 E: StaticEventContent<IsPrefix = False> + Serialize,
300{
301 #[inline(always)]
302 fn construct_json(self) -> serde_json::Value {
303 let mut json = json!({
304 "type": E::TYPE,
305 "content": self.content,
306 "origin_server_ts": self.server_ts,
307 });
308
309 let map = json.as_object_mut().unwrap();
310
311 if self.format.has_sender() {
312 let sender = self
315 .sender
316 .or_else(|| Some(self.unsigned.as_ref()?.redacted_because.as_ref()?.sender.clone())).expect("the sender must be known when building the JSON for a non read-receipt or global event");
317 map.insert("sender".to_owned(), json!(sender));
318 }
319
320 if self.format.has_event_id() && !self.no_event_id {
321 let event_id = self.event_id.unwrap_or_else(|| {
322 let server_name = self
323 .room
324 .as_ref()
325 .and_then(|room_id| room_id.server_name())
326 .unwrap_or(server_name!("dummy.org"));
327
328 EventId::new(server_name)
329 });
330
331 map.insert("event_id".to_owned(), json!(event_id));
332 }
333
334 if self.format.has_room_id() {
335 let room_id = self.room.expect("TimelineEvent requires a room id");
336 map.insert("room_id".to_owned(), json!(room_id));
337 }
338
339 if let Some(redacts) = self.redacts {
340 map.insert("redacts".to_owned(), json!(redacts));
341 }
342
343 if let Some(unsigned) = self.unsigned {
344 map.insert("unsigned".to_owned(), json!(unsigned));
345 }
346
347 if let Some(state_key) = self.state_key {
348 map.insert("state_key".to_owned(), json!(state_key));
349 }
350
351 json
352 }
353
354 pub fn into_raw<T>(self) -> Raw<T> {
360 Raw::new(&self.construct_json()).unwrap().cast_unchecked()
361 }
362
363 pub fn into_raw_timeline(self) -> Raw<AnyTimelineEvent> {
364 self.into_raw()
365 }
366
367 pub fn into_any_sync_message_like_event(self) -> AnySyncMessageLikeEvent {
368 self.format(EventFormat::SyncTimeline)
369 .into_raw()
370 .deserialize()
371 .expect("expected message like event")
372 }
373
374 pub fn into_original_sync_room_message_event(self) -> OriginalSyncRoomMessageEvent {
375 self.format(EventFormat::SyncTimeline)
376 .into_raw()
377 .deserialize()
378 .expect("expected original sync room message event")
379 }
380
381 pub fn into_raw_sync(self) -> Raw<AnySyncTimelineEvent> {
382 self.format(EventFormat::SyncTimeline).into_raw()
383 }
384
385 pub fn into_raw_sync_state(self) -> Raw<AnySyncStateEvent> {
386 self.format(EventFormat::SyncTimeline).into_raw()
387 }
388
389 pub fn into_event(self) -> TimelineEvent {
390 TimelineEvent::from_plaintext(self.into_raw_sync())
391 }
392}
393
394impl EventBuilder<RoomEncryptedEventContent> {
395 pub fn into_utd_sync_timeline_event(self) -> TimelineEvent {
398 let session_id = as_variant!(&self.content.scheme, EncryptedEventScheme::MegolmV1AesSha2)
399 .map(|content| content.session_id.clone());
400
401 TimelineEvent::from_utd(
402 self.into(),
403 UnableToDecryptInfo {
404 session_id,
405 reason: UnableToDecryptReason::MissingMegolmSession { withheld_code: None },
406 },
407 )
408 }
409}
410
411impl EventBuilder<RoomMessageEventContent> {
412 pub fn reply_to(mut self, event_id: &EventId) -> Self {
414 self.content.relates_to =
415 Some(Relation::Reply { in_reply_to: InReplyTo::new(event_id.to_owned()) });
416 self
417 }
418
419 pub fn in_thread(mut self, root: &EventId, latest_thread_event: &EventId) -> Self {
422 self.content.relates_to =
423 Some(Relation::Thread(Thread::plain(root.to_owned(), latest_thread_event.to_owned())));
424 self
425 }
426
427 pub fn in_thread_reply(mut self, root: &EventId, replied_to: &EventId) -> Self {
430 self.content.relates_to =
431 Some(Relation::Thread(Thread::reply(root.to_owned(), replied_to.to_owned())));
432 self
433 }
434
435 pub fn mentions(mut self, mentions: Mentions) -> Self {
437 self.content.mentions = Some(mentions);
438 self
439 }
440
441 pub fn edit(
444 mut self,
445 edited_event_id: &EventId,
446 new_content: RoomMessageEventContentWithoutRelation,
447 ) -> Self {
448 self.content.relates_to =
449 Some(Relation::Replacement(Replacement::new(edited_event_id.to_owned(), new_content)));
450 self
451 }
452
453 pub fn caption(
457 mut self,
458 caption: Option<String>,
459 formatted_caption: Option<FormattedBody>,
460 ) -> Self {
461 match &mut self.content.msgtype {
462 MessageType::Image(image) => {
463 let filename = image.filename().to_owned();
464 if let Some(caption) = caption {
465 image.body = caption;
466 image.filename = Some(filename);
467 } else {
468 image.body = filename;
469 image.filename = None;
470 }
471 image.formatted = formatted_caption;
472 }
473
474 MessageType::Audio(_) | MessageType::Video(_) | MessageType::File(_) => {
475 unimplemented!();
476 }
477
478 _ => panic!("unexpected event type for a caption"),
479 }
480
481 self
482 }
483}
484
485impl EventBuilder<UnstablePollStartEventContent> {
486 pub fn reply_to(mut self, event_id: &EventId) -> Self {
488 if let UnstablePollStartEventContent::New(content) = &mut self.content {
489 content.relates_to = Some(RelationWithoutReplacement::Reply {
490 in_reply_to: InReplyTo::new(event_id.to_owned()),
491 });
492 }
493 self
494 }
495
496 pub fn in_thread(mut self, root: &EventId, reply_to_event_id: &EventId) -> Self {
499 let thread = Thread::reply(root.to_owned(), reply_to_event_id.to_owned());
500
501 if let UnstablePollStartEventContent::New(content) = &mut self.content {
502 content.relates_to = Some(RelationWithoutReplacement::Thread(thread));
503 }
504 self
505 }
506}
507
508impl EventBuilder<RoomCreateEventContent> {
509 pub fn predecessor(mut self, room_id: &RoomId) -> Self {
511 self.content.predecessor = Some(PreviousRoom::new(room_id.to_owned()));
512 self
513 }
514
515 pub fn no_predecessor(mut self) -> Self {
517 self.content.predecessor = None;
518 self
519 }
520
521 pub fn with_space_type(mut self) -> Self {
523 self.content.room_type = Some(RoomType::Space);
524 self
525 }
526}
527
528impl EventBuilder<StickerEventContent> {
529 pub fn reply_thread(mut self, root: &EventId, reply_to_event: &EventId) -> Self {
531 self.content.relates_to =
532 Some(Relation::Thread(Thread::reply(root.to_owned(), reply_to_event.to_owned())));
533 self
534 }
535}
536
537impl<E: StaticEventContent<IsPrefix = False>> From<EventBuilder<E>> for Raw<AnySyncTimelineEvent>
538where
539 E: Serialize,
540{
541 fn from(val: EventBuilder<E>) -> Self {
542 val.into_raw_sync()
543 }
544}
545
546impl<E: StaticEventContent<IsPrefix = False>> From<EventBuilder<E>> for AnySyncTimelineEvent
547where
548 E: Serialize,
549{
550 fn from(val: EventBuilder<E>) -> Self {
551 Raw::<AnySyncTimelineEvent>::from(val).deserialize().expect("expected sync timeline event")
552 }
553}
554
555impl<E: StaticEventContent<IsPrefix = False>> From<EventBuilder<E>> for Raw<AnyTimelineEvent>
556where
557 E: Serialize,
558{
559 fn from(val: EventBuilder<E>) -> Self {
560 val.into_raw_timeline()
561 }
562}
563
564impl<E: StaticEventContent<IsPrefix = False>> From<EventBuilder<E>> for AnyTimelineEvent
565where
566 E: Serialize,
567{
568 fn from(val: EventBuilder<E>) -> Self {
569 Raw::<AnyTimelineEvent>::from(val).deserialize().expect("expected timeline event")
570 }
571}
572
573impl<E: StaticEventContent<IsPrefix = False>> From<EventBuilder<E>>
574 for Raw<AnyGlobalAccountDataEvent>
575where
576 E: Serialize,
577{
578 fn from(val: EventBuilder<E>) -> Self {
579 val.format(EventFormat::GlobalAccountData).into_raw()
580 }
581}
582
583impl<E: StaticEventContent<IsPrefix = False>> From<EventBuilder<E>> for AnyGlobalAccountDataEvent
584where
585 E: Serialize,
586{
587 fn from(val: EventBuilder<E>) -> Self {
588 Raw::<AnyGlobalAccountDataEvent>::from(val)
589 .deserialize()
590 .expect("expected global account data")
591 }
592}
593
594impl<E: StaticEventContent<IsPrefix = False>> From<EventBuilder<E>> for TimelineEvent
595where
596 E: Serialize,
597{
598 fn from(val: EventBuilder<E>) -> Self {
599 val.into_event()
600 }
601}
602
603impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
604 for Raw<AnySyncStateEvent>
605{
606 fn from(val: EventBuilder<E>) -> Self {
607 val.format(EventFormat::SyncTimeline).into_raw()
608 }
609}
610
611impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
612 for AnySyncStateEvent
613{
614 fn from(val: EventBuilder<E>) -> Self {
615 Raw::<AnySyncStateEvent>::from(val).deserialize().expect("expected sync state")
616 }
617}
618
619impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
620 for Raw<SyncStateEvent<E>>
621where
622 E: StaticStateEventContent + RedactContent,
623 E::Redacted: RedactedStateEventContent,
624{
625 fn from(val: EventBuilder<E>) -> Self {
626 val.format(EventFormat::SyncTimeline).into_raw()
627 }
628}
629
630impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
631 for SyncStateEvent<E>
632where
633 E: StaticStateEventContent + RedactContent + EventContentFromType,
634 E::Redacted: RedactedStateEventContent<StateKey = <E as StateEventContent>::StateKey>
635 + EventContentFromType,
636{
637 fn from(val: EventBuilder<E>) -> Self {
638 Raw::<SyncStateEvent<E>>::from(val).deserialize().expect("expected sync state")
639 }
640}
641
642impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
643 for Raw<AnyStateEvent>
644{
645 fn from(val: EventBuilder<E>) -> Self {
646 val.into_raw()
647 }
648}
649
650impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
651 for AnyStateEvent
652{
653 fn from(val: EventBuilder<E>) -> Self {
654 Raw::<AnyStateEvent>::from(val).deserialize().expect("expected state")
655 }
656}
657
658impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
659 for Raw<StateEvent<E>>
660where
661 E: StaticStateEventContent + RedactContent,
662 E::Redacted: RedactedStateEventContent,
663{
664 fn from(val: EventBuilder<E>) -> Self {
665 val.into_raw()
666 }
667}
668
669impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
670 for StateEvent<E>
671where
672 E: StaticStateEventContent + RedactContent + EventContentFromType,
673 E::Redacted: RedactedStateEventContent<StateKey = <E as StateEventContent>::StateKey>
674 + EventContentFromType,
675{
676 fn from(val: EventBuilder<E>) -> Self {
677 Raw::<StateEvent<E>>::from(val).deserialize().expect("expected state")
678 }
679}
680
681impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
682 for Raw<AnyStrippedStateEvent>
683{
684 fn from(val: EventBuilder<E>) -> Self {
685 val.format(EventFormat::StrippedState).into_raw()
686 }
687}
688
689impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
690 for AnyStrippedStateEvent
691{
692 fn from(val: EventBuilder<E>) -> Self {
693 Raw::<AnyStrippedStateEvent>::from(val).deserialize().expect("expected stripped state")
694 }
695}
696
697impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
698 for Raw<StrippedStateEvent<E::PossiblyRedacted>>
699where
700 E: StaticStateEventContent,
701{
702 fn from(val: EventBuilder<E>) -> Self {
703 val.format(EventFormat::StrippedState).into_raw()
704 }
705}
706
707impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
708 for StrippedStateEvent<E::PossiblyRedacted>
709where
710 E: StaticStateEventContent,
711 E::PossiblyRedacted: PossiblyRedactedStateEventContent + EventContentFromType,
712{
713 fn from(val: EventBuilder<E>) -> Self {
714 Raw::<StrippedStateEvent<E::PossiblyRedacted>>::from(val)
715 .deserialize()
716 .expect("expected stripped state")
717 }
718}
719
720impl<E: StaticEventContent<IsPrefix = False> + EphemeralRoomEventContent> From<EventBuilder<E>>
721 for Raw<AnySyncEphemeralRoomEvent>
722{
723 fn from(val: EventBuilder<E>) -> Self {
724 val.format(EventFormat::Ephemeral).into_raw()
725 }
726}
727
728impl<E: StaticEventContent<IsPrefix = False> + EphemeralRoomEventContent> From<EventBuilder<E>>
729 for AnySyncEphemeralRoomEvent
730{
731 fn from(val: EventBuilder<E>) -> Self {
732 Raw::<AnySyncEphemeralRoomEvent>::from(val).deserialize().expect("expected ephemeral")
733 }
734}
735
736impl<E: StaticEventContent<IsPrefix = False> + MessageLikeEventContent> From<EventBuilder<E>>
737 for Raw<AnySyncMessageLikeEvent>
738{
739 fn from(val: EventBuilder<E>) -> Self {
740 val.format(EventFormat::SyncTimeline).into_raw()
741 }
742}
743
744impl<E: StaticEventContent<IsPrefix = False> + MessageLikeEventContent> From<EventBuilder<E>>
745 for AnySyncMessageLikeEvent
746{
747 fn from(val: EventBuilder<E>) -> Self {
748 Raw::<AnySyncMessageLikeEvent>::from(val).deserialize().expect("expected sync message-like")
749 }
750}
751
752impl<E: StaticEventContent<IsPrefix = False> + MessageLikeEventContent> From<EventBuilder<E>>
753 for Raw<SyncMessageLikeEvent<E>>
754where
755 E: RedactContent,
756 E::Redacted: RedactedMessageLikeEventContent,
757{
758 fn from(val: EventBuilder<E>) -> Self {
759 val.format(EventFormat::SyncTimeline).into_raw()
760 }
761}
762
763impl<E: StaticEventContent<IsPrefix = False> + MessageLikeEventContent> From<EventBuilder<E>>
764 for SyncMessageLikeEvent<E>
765where
766 E: RedactContent + EventContentFromType,
767 E::Redacted: RedactedMessageLikeEventContent + EventContentFromType,
768{
769 fn from(val: EventBuilder<E>) -> Self {
770 Raw::<SyncMessageLikeEvent<E>>::from(val).deserialize().expect("expected sync message-like")
771 }
772}
773
774impl<E: StaticEventContent<IsPrefix = False> + MessageLikeEventContent> From<EventBuilder<E>>
775 for Raw<AnyMessageLikeEvent>
776{
777 fn from(val: EventBuilder<E>) -> Self {
778 val.into_raw()
779 }
780}
781
782impl<E: StaticEventContent<IsPrefix = False> + MessageLikeEventContent> From<EventBuilder<E>>
783 for AnyMessageLikeEvent
784{
785 fn from(val: EventBuilder<E>) -> Self {
786 Raw::<AnyMessageLikeEvent>::from(val).deserialize().expect("expected message-like")
787 }
788}
789
790impl<E: StaticEventContent<IsPrefix = False> + MessageLikeEventContent> From<EventBuilder<E>>
791 for Raw<MessageLikeEvent<E>>
792where
793 E: RedactContent,
794 E::Redacted: RedactedMessageLikeEventContent,
795{
796 fn from(val: EventBuilder<E>) -> Self {
797 val.into_raw()
798 }
799}
800
801impl<E: StaticEventContent<IsPrefix = False> + MessageLikeEventContent> From<EventBuilder<E>>
802 for MessageLikeEvent<E>
803where
804 E: RedactContent + EventContentFromType,
805 E::Redacted: RedactedMessageLikeEventContent + EventContentFromType,
806{
807 fn from(val: EventBuilder<E>) -> Self {
808 Raw::<MessageLikeEvent<E>>::from(val).deserialize().expect("expected message-like")
809 }
810}
811
812#[derive(Debug, Default)]
813pub struct EventFactory {
814 next_ts: AtomicU64,
815 sender: Option<OwnedUserId>,
816 room: Option<OwnedRoomId>,
817}
818
819impl EventFactory {
820 pub fn new() -> Self {
821 Self { next_ts: AtomicU64::new(0), sender: None, room: None }
822 }
823
824 pub fn room(mut self, room_id: &RoomId) -> Self {
825 self.room = Some(room_id.to_owned());
826 self
827 }
828
829 pub fn sender(mut self, sender: &UserId) -> Self {
830 self.sender = Some(sender.to_owned());
831 self
832 }
833
834 pub fn server_ts(self, ts: u64) -> Self {
835 self.next_ts.store(ts, SeqCst);
836 self
837 }
838
839 fn next_server_ts(&self) -> MilliSecondsSinceUnixEpoch {
840 MilliSecondsSinceUnixEpoch(
841 self.next_ts
842 .fetch_add(1, SeqCst)
843 .try_into()
844 .expect("server timestamp should fit in js_int::UInt"),
845 )
846 }
847
848 pub fn event<E: StaticEventContent<IsPrefix = False>>(&self, content: E) -> EventBuilder<E> {
850 EventBuilder {
851 format: EventFormat::Timeline,
852 sender: self.sender.clone(),
853 room: self.room.clone(),
854 server_ts: self.next_server_ts(),
855 event_id: None,
856 no_event_id: false,
857 redacts: None,
858 content,
859 unsigned: None,
860 state_key: None,
861 }
862 }
863
864 pub fn text_msg(&self, content: impl Into<String>) -> EventBuilder<RoomMessageEventContent> {
866 self.event(RoomMessageEventContent::text_plain(content.into()))
867 }
868
869 pub fn emote(&self, content: impl Into<String>) -> EventBuilder<RoomMessageEventContent> {
871 self.event(RoomMessageEventContent::emote_plain(content.into()))
872 }
873
874 pub fn member(&self, member: &UserId) -> EventBuilder<RoomMemberEventContent> {
904 let mut event = self.event(RoomMemberEventContent::new(MembershipState::Join));
905
906 if self.sender.is_some() {
907 event.sender = self.sender.clone();
908 } else {
909 event.sender = Some(member.to_owned());
910 }
911
912 event.state_key = Some(member.to_string());
913
914 event
915 }
916
917 pub fn room_tombstone(
919 &self,
920 body: impl Into<String>,
921 replacement: &RoomId,
922 ) -> EventBuilder<RoomTombstoneEventContent> {
923 let mut event =
924 self.event(RoomTombstoneEventContent::new(body.into(), replacement.to_owned()));
925 event.state_key = Some("".to_owned());
926 event
927 }
928
929 pub fn room_topic(&self, topic: impl Into<String>) -> EventBuilder<RoomTopicEventContent> {
931 let mut event = self.event(RoomTopicEventContent::new(topic.into()));
932 event.state_key = Some("".to_owned());
934 event
935 }
936
937 pub fn room_name(&self, name: impl Into<String>) -> EventBuilder<RoomNameEventContent> {
939 let mut event = self.event(RoomNameEventContent::new(name.into()));
940 event.state_key = Some("".to_owned());
942 event
943 }
944
945 pub fn room_avatar(&self) -> EventBuilder<RoomAvatarEventContent> {
947 let mut event = self.event(RoomAvatarEventContent::new());
948 event.state_key = Some("".to_owned());
950 event
951 }
952
953 pub fn member_hints(
974 &self,
975 service_members: BTreeSet<OwnedUserId>,
976 ) -> EventBuilder<MemberHintsEventContent> {
977 self.event(MemberHintsEventContent::new(service_members)).state_key("")
979 }
980
981 pub fn text_html(
983 &self,
984 plain: impl Into<String>,
985 html: impl Into<String>,
986 ) -> EventBuilder<RoomMessageEventContent> {
987 self.event(RoomMessageEventContent::text_html(plain, html))
988 }
989
990 pub fn notice(&self, content: impl Into<String>) -> EventBuilder<RoomMessageEventContent> {
992 self.event(RoomMessageEventContent::notice_plain(content))
993 }
994
995 pub fn reaction(
997 &self,
998 event_id: &EventId,
999 annotation: impl Into<String>,
1000 ) -> EventBuilder<ReactionEventContent> {
1001 self.event(ReactionEventContent::new(Annotation::new(
1002 event_id.to_owned(),
1003 annotation.into(),
1004 )))
1005 }
1006
1007 pub fn redaction(&self, event_id: &EventId) -> EventBuilder<RoomRedactionEventContent> {
1012 let mut builder = self.event(RoomRedactionEventContent::new_v11(event_id.to_owned()));
1013 builder.redacts = Some(event_id.to_owned());
1014 builder
1015 }
1016
1017 pub fn redacted<T: StaticEventContent<IsPrefix = False> + RedactedMessageLikeEventContent>(
1020 &self,
1021 redacter: &UserId,
1022 content: T,
1023 ) -> EventBuilder<T> {
1024 let mut builder = self.event(content);
1025
1026 let redacted_because = RedactedBecause {
1027 content: RoomRedactionEventContent::default(),
1028 event_id: EventId::new(server_name!("dummy.server")),
1029 sender: redacter.to_owned(),
1030 origin_server_ts: self.next_server_ts(),
1031 };
1032 builder.unsigned.get_or_insert_with(Default::default).redacted_because =
1033 Some(redacted_because);
1034
1035 builder
1036 }
1037
1038 pub fn redacted_state<T: StaticEventContent<IsPrefix = False> + RedactedStateEventContent>(
1041 &self,
1042 redacter: &UserId,
1043 state_key: impl Into<String>,
1044 content: T,
1045 ) -> EventBuilder<T> {
1046 let mut builder = self.event(content);
1047
1048 let redacted_because = RedactedBecause {
1049 content: RoomRedactionEventContent::default(),
1050 event_id: EventId::new(server_name!("dummy.server")),
1051 sender: redacter.to_owned(),
1052 origin_server_ts: self.next_server_ts(),
1053 };
1054 builder.unsigned.get_or_insert_with(Default::default).redacted_because =
1055 Some(redacted_because);
1056 builder.state_key = Some(state_key.into());
1057
1058 builder
1059 }
1060
1061 pub fn poll_start(
1064 &self,
1065 fallback_text: impl Into<String>,
1066 poll_question: impl Into<String>,
1067 answers: Vec<impl Into<String>>,
1068 ) -> EventBuilder<UnstablePollStartEventContent> {
1069 let answers: Vec<UnstablePollAnswer> = answers
1071 .into_iter()
1072 .enumerate()
1073 .map(|(idx, answer)| UnstablePollAnswer::new(idx.to_string(), answer))
1074 .collect();
1075 let poll_answers = answers.try_into().unwrap();
1076 let poll_start_content =
1077 UnstablePollStartEventContent::New(NewUnstablePollStartEventContent::plain_text(
1078 fallback_text,
1079 UnstablePollStartContentBlock::new(poll_question, poll_answers),
1080 ));
1081 self.event(poll_start_content)
1082 }
1083
1084 pub fn poll_edit(
1086 &self,
1087 edited_event_id: &EventId,
1088 poll_question: impl Into<String>,
1089 answers: Vec<impl Into<String>>,
1090 ) -> EventBuilder<ReplacementUnstablePollStartEventContent> {
1091 let answers: Vec<UnstablePollAnswer> = answers
1093 .into_iter()
1094 .enumerate()
1095 .map(|(idx, answer)| UnstablePollAnswer::new(idx.to_string(), answer))
1096 .collect();
1097 let poll_answers = answers.try_into().unwrap();
1098 let poll_start_content_block =
1099 UnstablePollStartContentBlock::new(poll_question, poll_answers);
1100 self.event(ReplacementUnstablePollStartEventContent::new(
1101 poll_start_content_block,
1102 edited_event_id.to_owned(),
1103 ))
1104 }
1105
1106 pub fn poll_response(
1109 &self,
1110 answers: Vec<impl Into<String>>,
1111 poll_start_id: &EventId,
1112 ) -> EventBuilder<UnstablePollResponseEventContent> {
1113 self.event(UnstablePollResponseEventContent::new(
1114 answers.into_iter().map(Into::into).collect(),
1115 poll_start_id.to_owned(),
1116 ))
1117 }
1118
1119 pub fn poll_end(
1122 &self,
1123 content: impl Into<String>,
1124 poll_start_id: &EventId,
1125 ) -> EventBuilder<UnstablePollEndEventContent> {
1126 self.event(UnstablePollEndEventContent::new(content.into(), poll_start_id.to_owned()))
1127 }
1128
1129 pub fn image(
1132 &self,
1133 filename: String,
1134 url: OwnedMxcUri,
1135 ) -> EventBuilder<RoomMessageEventContent> {
1136 let image_event_content = ImageMessageEventContent::plain(filename, url);
1137 self.event(RoomMessageEventContent::new(MessageType::Image(image_event_content)))
1138 }
1139
1140 pub fn gallery(
1143 &self,
1144 body: String,
1145 filename: String,
1146 url: OwnedMxcUri,
1147 ) -> EventBuilder<RoomMessageEventContent> {
1148 let gallery_event_content = GalleryMessageEventContent::new(
1149 body,
1150 None,
1151 vec![GalleryItemType::Image(ImageMessageEventContent::plain(filename, url))],
1152 );
1153 self.event(RoomMessageEventContent::new(MessageType::Gallery(gallery_event_content)))
1154 }
1155
1156 pub fn typing(&self, user_ids: Vec<&UserId>) -> EventBuilder<TypingEventContent> {
1158 self.event(TypingEventContent::new(user_ids.into_iter().map(ToOwned::to_owned).collect()))
1159 .format(EventFormat::Ephemeral)
1160 }
1161
1162 pub fn read_receipts(&self) -> ReadReceiptBuilder<'_> {
1164 ReadReceiptBuilder { factory: self, content: ReceiptEventContent(Default::default()) }
1165 }
1166
1167 pub fn create(
1169 &self,
1170 creator_user_id: &UserId,
1171 room_version: RoomVersionId,
1172 ) -> EventBuilder<RoomCreateEventContent> {
1173 let mut event = self.event(RoomCreateEventContent::new_v1(creator_user_id.to_owned()));
1174 event.content.room_version = room_version;
1175
1176 if self.sender.is_some() {
1177 event.sender = self.sender.clone();
1178 } else {
1179 event.sender = Some(creator_user_id.to_owned());
1180 }
1181
1182 event.state_key = Some("".to_owned());
1183
1184 event
1185 }
1186
1187 pub fn power_levels(
1189 &self,
1190 map: &mut BTreeMap<OwnedUserId, Int>,
1191 ) -> EventBuilder<RoomPowerLevelsEventContent> {
1192 let mut event = RoomPowerLevelsEventContent::new(&AuthorizationRules::V1);
1193 event.users.append(map);
1194 self.event(event)
1195 }
1196
1197 pub fn server_acl(
1199 &self,
1200 allow_ip_literals: bool,
1201 allow: Vec<String>,
1202 deny: Vec<String>,
1203 ) -> EventBuilder<RoomServerAclEventContent> {
1204 self.event(RoomServerAclEventContent::new(allow_ip_literals, allow, deny))
1205 }
1206
1207 pub fn canonical_alias(
1209 &self,
1210 alias: Option<OwnedRoomAliasId>,
1211 alt_aliases: Vec<OwnedRoomAliasId>,
1212 ) -> EventBuilder<RoomCanonicalAliasEventContent> {
1213 let mut event = RoomCanonicalAliasEventContent::new();
1214 event.alias = alias;
1215 event.alt_aliases = alt_aliases;
1216 self.event(event)
1217 }
1218
1219 pub fn beacon(
1245 &self,
1246 beacon_info_event_id: OwnedEventId,
1247 latitude: f64,
1248 longitude: f64,
1249 uncertainty: u32,
1250 ts: Option<MilliSecondsSinceUnixEpoch>,
1251 ) -> EventBuilder<BeaconEventContent> {
1252 let geo_uri = format!("geo:{latitude},{longitude};u={uncertainty}");
1253 self.event(BeaconEventContent::new(beacon_info_event_id, geo_uri, ts))
1254 }
1255
1256 pub fn sticker(
1258 &self,
1259 body: impl Into<String>,
1260 info: ImageInfo,
1261 url: OwnedMxcUri,
1262 ) -> EventBuilder<StickerEventContent> {
1263 self.event(StickerEventContent::new(body.into(), info, url))
1264 }
1265
1266 pub fn call_invite(
1268 &self,
1269 call_id: OwnedVoipId,
1270 lifetime: UInt,
1271 offer: SessionDescription,
1272 version: VoipVersionId,
1273 ) -> EventBuilder<CallInviteEventContent> {
1274 self.event(CallInviteEventContent::new(call_id, lifetime, offer, version))
1275 }
1276
1277 pub fn rtc_notification(
1279 &self,
1280 notification_type: NotificationType,
1281 ) -> EventBuilder<RtcNotificationEventContent> {
1282 self.event(RtcNotificationEventContent::new(
1283 MilliSecondsSinceUnixEpoch::now(),
1284 Duration::new(30, 0),
1285 notification_type,
1286 ))
1287 }
1288
1289 pub fn call_decline(
1291 &self,
1292 notification_event_id: &EventId,
1293 ) -> EventBuilder<RtcDeclineEventContent> {
1294 self.event(RtcDeclineEventContent::new(notification_event_id))
1295 }
1296
1297 pub fn direct(&self) -> EventBuilder<DirectEventContent> {
1299 self.global_account_data(DirectEventContent::default())
1300 }
1301
1302 pub fn ignored_user_list(
1304 &self,
1305 users: impl IntoIterator<Item = OwnedUserId>,
1306 ) -> EventBuilder<IgnoredUserListEventContent> {
1307 self.global_account_data(IgnoredUserListEventContent::users(users))
1308 }
1309
1310 pub fn push_rules(&self, rules: Ruleset) -> EventBuilder<PushRulesEventContent> {
1312 self.global_account_data(PushRulesEventContent::new(rules))
1313 }
1314
1315 pub fn space_child(
1317 &self,
1318 parent: OwnedRoomId,
1319 child: OwnedRoomId,
1320 ) -> EventBuilder<SpaceChildEventContent> {
1321 let mut event = self.event(SpaceChildEventContent::new(vec![]));
1322 event.room = Some(parent);
1323 event.state_key = Some(child.to_string());
1324 event
1325 }
1326
1327 pub fn space_parent(
1329 &self,
1330 parent: OwnedRoomId,
1331 child: OwnedRoomId,
1332 ) -> EventBuilder<SpaceParentEventContent> {
1333 let mut event = self.event(SpaceParentEventContent::new(vec![]));
1334 event.state_key = Some(parent.to_string());
1335 event.room = Some(child);
1336 event
1337 }
1338
1339 pub fn custom_message_like_event(&self) -> EventBuilder<CustomMessageLikeEventContent> {
1341 self.event(CustomMessageLikeEventContent)
1342 }
1343
1344 pub fn set_next_ts(&self, value: u64) {
1348 self.next_ts.store(value, SeqCst);
1349 }
1350
1351 pub fn global_account_data<C>(&self, content: C) -> EventBuilder<C>
1353 where
1354 C: GlobalAccountDataEventContent + StaticEventContent<IsPrefix = False>,
1355 {
1356 self.event(content).format(EventFormat::GlobalAccountData)
1357 }
1358}
1359
1360impl EventBuilder<DirectEventContent> {
1361 pub fn add_user(mut self, user_id: OwnedDirectUserIdentifier, room_id: &RoomId) -> Self {
1363 self.content.0.entry(user_id).or_default().push(room_id.to_owned());
1364 self
1365 }
1366}
1367
1368impl EventBuilder<RoomMemberEventContent> {
1369 pub fn membership(mut self, state: MembershipState) -> Self {
1374 self.content.membership = state;
1375 self
1376 }
1377
1378 pub fn invited(mut self, invited_user: &UserId) -> Self {
1381 assert_ne!(
1382 self.sender.as_deref().unwrap(),
1383 invited_user,
1384 "invited user and sender can't be the same person"
1385 );
1386 self.content.membership = MembershipState::Invite;
1387 self.state_key = Some(invited_user.to_string());
1388 self
1389 }
1390
1391 pub fn kicked(mut self, kicked_user: &UserId) -> Self {
1394 assert_ne!(
1395 self.sender.as_deref().unwrap(),
1396 kicked_user,
1397 "kicked user and sender can't be the same person, otherwise it's just a Leave"
1398 );
1399 self.content.membership = MembershipState::Leave;
1400 self.state_key = Some(kicked_user.to_string());
1401 self
1402 }
1403
1404 pub fn banned(mut self, banned_user: &UserId) -> Self {
1407 assert_ne!(
1408 self.sender.as_deref().unwrap(),
1409 banned_user,
1410 "a user can't ban itself" );
1412 self.content.membership = MembershipState::Ban;
1413 self.state_key = Some(banned_user.to_string());
1414 self
1415 }
1416
1417 pub fn display_name(mut self, display_name: impl Into<String>) -> Self {
1419 self.content.displayname = Some(display_name.into());
1420 self
1421 }
1422
1423 pub fn avatar_url(mut self, url: &MxcUri) -> Self {
1425 self.content.avatar_url = Some(url.to_owned());
1426 self
1427 }
1428
1429 pub fn reason(mut self, reason: impl Into<String>) -> Self {
1431 self.content.reason = Some(reason.into());
1432 self
1433 }
1434
1435 pub fn previous(mut self, previous: impl Into<PreviousMembership>) -> Self {
1437 let previous = previous.into();
1438
1439 let mut prev_content = RoomMemberEventContent::new(previous.state);
1440 if let Some(avatar_url) = previous.avatar_url {
1441 prev_content.avatar_url = Some(avatar_url);
1442 }
1443 if let Some(display_name) = previous.display_name {
1444 prev_content.displayname = Some(display_name);
1445 }
1446
1447 self.unsigned.get_or_insert_with(Default::default).prev_content = Some(prev_content);
1448 self
1449 }
1450}
1451
1452impl EventBuilder<RoomAvatarEventContent> {
1453 pub fn url(mut self, url: &MxcUri) -> Self {
1455 self.content.url = Some(url.to_owned());
1456 self
1457 }
1458
1459 pub fn info(mut self, image: avatar::ImageInfo) -> Self {
1461 self.content.info = Some(Box::new(image));
1462 self
1463 }
1464}
1465
1466impl EventBuilder<RtcNotificationEventContent> {
1467 pub fn mentions(mut self, users: impl IntoIterator<Item = OwnedUserId>) -> Self {
1468 self.content.mentions = Some(Mentions::with_user_ids(users));
1469 self
1470 }
1471
1472 pub fn relates_to_membership_state_event(mut self, event_id: OwnedEventId) -> Self {
1473 self.content.relates_to = Some(Reference::new(event_id));
1474 self
1475 }
1476
1477 pub fn lifetime(mut self, time_in_seconds: u64) -> Self {
1478 self.content.lifetime = Duration::from_secs(time_in_seconds);
1479 self
1480 }
1481}
1482
1483pub struct ReadReceiptBuilder<'a> {
1484 factory: &'a EventFactory,
1485 content: ReceiptEventContent,
1486}
1487
1488impl ReadReceiptBuilder<'_> {
1489 pub fn add(
1491 self,
1492 event_id: &EventId,
1493 user_id: &UserId,
1494 tyype: ReceiptType,
1495 thread: ReceiptThread,
1496 ) -> Self {
1497 let ts = self.factory.next_server_ts();
1498 self.add_with_timestamp(event_id, user_id, tyype, thread, Some(ts))
1499 }
1500
1501 pub fn add_with_timestamp(
1503 mut self,
1504 event_id: &EventId,
1505 user_id: &UserId,
1506 tyype: ReceiptType,
1507 thread: ReceiptThread,
1508 ts: Option<MilliSecondsSinceUnixEpoch>,
1509 ) -> Self {
1510 let by_event = self.content.0.entry(event_id.to_owned()).or_default();
1511 let by_type = by_event.entry(tyype).or_default();
1512
1513 let mut receipt = Receipt::default();
1514 if let Some(ts) = ts {
1515 receipt.ts = Some(ts);
1516 }
1517 receipt.thread = thread;
1518
1519 by_type.insert(user_id.to_owned(), receipt);
1520 self
1521 }
1522
1523 pub fn into_content(self) -> ReceiptEventContent {
1525 self.content
1526 }
1527
1528 pub fn into_event(self) -> EventBuilder<ReceiptEventContent> {
1530 self.factory.event(self.into_content()).format(EventFormat::Ephemeral)
1531 }
1532}
1533
1534pub struct PreviousMembership {
1535 state: MembershipState,
1536 avatar_url: Option<OwnedMxcUri>,
1537 display_name: Option<String>,
1538}
1539
1540impl PreviousMembership {
1541 pub fn new(state: MembershipState) -> Self {
1542 Self { state, avatar_url: None, display_name: None }
1543 }
1544
1545 pub fn avatar_url(mut self, url: &MxcUri) -> Self {
1546 self.avatar_url = Some(url.to_owned());
1547 self
1548 }
1549
1550 pub fn display_name(mut self, name: impl Into<String>) -> Self {
1551 self.display_name = Some(name.into());
1552 self
1553 }
1554}
1555
1556impl From<MembershipState> for PreviousMembership {
1557 fn from(state: MembershipState) -> Self {
1558 Self::new(state)
1559 }
1560}
1561
1562#[derive(Clone, Default, Debug, Serialize, EventContent)]
1563#[ruma_event(type = "rs.matrix-sdk.custom.test", kind = MessageLike)]
1564pub struct CustomMessageLikeEventContent;