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, OwnedDeviceId, OwnedEventId, OwnedMxcUri,
29 OwnedRoomAliasId, OwnedRoomId, OwnedTransactionId, OwnedUserId, OwnedVoipId, RoomId,
30 RoomVersionId, 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::{
64 EncryptedEventScheme, MegolmV1AesSha2ContentInit, RoomEncryptedEventContent,
65 },
66 member::{MembershipState, RoomMemberEventContent},
67 message::{
68 FormattedBody, GalleryItemType, GalleryMessageEventContent,
69 ImageMessageEventContent, MessageType, OriginalSyncRoomMessageEvent, Relation,
70 RelationWithoutReplacement, RoomMessageEventContent,
71 RoomMessageEventContentWithoutRelation,
72 },
73 name::RoomNameEventContent,
74 power_levels::RoomPowerLevelsEventContent,
75 redaction::RoomRedactionEventContent,
76 server_acl::RoomServerAclEventContent,
77 tombstone::RoomTombstoneEventContent,
78 topic::RoomTopicEventContent,
79 },
80 rtc::{
81 decline::RtcDeclineEventContent,
82 notification::{NotificationType, RtcNotificationEventContent},
83 },
84 space::{child::SpaceChildEventContent, parent::SpaceParentEventContent},
85 sticker::StickerEventContent,
86 typing::TypingEventContent,
87 },
88 push::Ruleset,
89 room::RoomType,
90 room_version_rules::AuthorizationRules,
91 serde::Raw,
92 server_name,
93};
94use serde::Serialize;
95use serde_json::json;
96
97pub trait TimestampArg {
98 fn to_milliseconds_since_unix_epoch(self) -> MilliSecondsSinceUnixEpoch;
99}
100
101impl TimestampArg for MilliSecondsSinceUnixEpoch {
102 fn to_milliseconds_since_unix_epoch(self) -> MilliSecondsSinceUnixEpoch {
103 self
104 }
105}
106
107impl TimestampArg for u64 {
108 fn to_milliseconds_since_unix_epoch(self) -> MilliSecondsSinceUnixEpoch {
109 MilliSecondsSinceUnixEpoch(UInt::try_from(self).unwrap())
110 }
111}
112
113#[derive(Debug, Serialize)]
115struct RedactedBecause {
116 content: RoomRedactionEventContent,
118
119 event_id: OwnedEventId,
121
122 sender: OwnedUserId,
124
125 origin_server_ts: MilliSecondsSinceUnixEpoch,
128}
129
130#[derive(Debug, Serialize)]
131struct Unsigned<C: StaticEventContent> {
132 #[serde(skip_serializing_if = "Option::is_none")]
133 prev_content: Option<C>,
134
135 #[serde(skip_serializing_if = "Option::is_none")]
136 transaction_id: Option<OwnedTransactionId>,
137
138 #[serde(rename = "m.relations", skip_serializing_if = "Option::is_none")]
139 relations: Option<BundledMessageLikeRelations<Raw<AnySyncTimelineEvent>>>,
140
141 #[serde(skip_serializing_if = "Option::is_none")]
142 redacted_because: Option<RedactedBecause>,
143
144 #[serde(skip_serializing_if = "Option::is_none")]
145 age: Option<Int>,
146}
147
148impl<C: StaticEventContent> Default for Unsigned<C> {
150 fn default() -> Self {
151 Self {
152 prev_content: None,
153 transaction_id: None,
154 relations: None,
155 redacted_because: None,
156 age: None,
157 }
158 }
159}
160
161#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
162enum EventFormat {
163 #[default]
166 Timeline,
167 SyncTimeline,
169 StrippedState,
171 Ephemeral,
173 GlobalAccountData,
175}
176
177impl EventFormat {
178 fn has_sender(self) -> bool {
180 matches!(self, Self::Timeline | Self::SyncTimeline | Self::StrippedState)
181 }
182
183 fn has_event_id(self) -> bool {
185 matches!(self, Self::Timeline | Self::SyncTimeline)
186 }
187
188 fn has_room_id(self) -> bool {
190 matches!(self, Self::Timeline)
191 }
192}
193
194#[derive(Debug)]
195pub struct EventBuilder<C: StaticEventContent<IsPrefix = False>> {
196 format: EventFormat,
200 sender: Option<OwnedUserId>,
201 room: Option<OwnedRoomId>,
202 event_id: Option<OwnedEventId>,
203 no_event_id: bool,
205 redacts: Option<OwnedEventId>,
206 content: C,
207 server_ts: MilliSecondsSinceUnixEpoch,
208 unsigned: Option<Unsigned<C>>,
209 state_key: Option<String>,
210}
211
212impl<E: StaticEventContent<IsPrefix = False>> EventBuilder<E> {
213 fn format(mut self, format: EventFormat) -> Self {
214 self.format = format;
215 self
216 }
217
218 pub fn room(mut self, room_id: &RoomId) -> Self {
219 self.room = Some(room_id.to_owned());
220 self
221 }
222
223 pub fn sender(mut self, sender: &UserId) -> Self {
224 self.sender = Some(sender.to_owned());
225 self
226 }
227
228 pub fn event_id(mut self, event_id: &EventId) -> Self {
229 self.event_id = Some(event_id.to_owned());
230 self.no_event_id = false;
231 self
232 }
233
234 pub fn no_event_id(mut self) -> Self {
235 self.event_id = None;
236 self.no_event_id = true;
237 self
238 }
239
240 pub fn server_ts(mut self, ts: impl TimestampArg) -> Self {
241 self.server_ts = ts.to_milliseconds_since_unix_epoch();
242 self
243 }
244
245 pub fn unsigned_transaction_id(mut self, transaction_id: &TransactionId) -> Self {
246 self.unsigned.get_or_insert_with(Default::default).transaction_id =
247 Some(transaction_id.to_owned());
248 self
249 }
250
251 pub fn age(mut self, age: impl Into<Int>) -> Self {
253 self.unsigned.get_or_insert_with(Default::default).age = Some(age.into());
254 self
255 }
256
257 pub fn with_bundled_thread_summary(
260 mut self,
261 latest_event: Raw<AnySyncMessageLikeEvent>,
262 count: usize,
263 current_user_participated: bool,
264 ) -> Self {
265 let relations = self
266 .unsigned
267 .get_or_insert_with(Default::default)
268 .relations
269 .get_or_insert_with(BundledMessageLikeRelations::new);
270 relations.thread = Some(Box::new(BundledThread::new(
271 latest_event,
272 UInt::try_from(count).unwrap(),
273 current_user_participated,
274 )));
275 self
276 }
277
278 pub fn with_bundled_edit(mut self, replacement: impl Into<Raw<AnySyncTimelineEvent>>) -> Self {
280 let relations = self
281 .unsigned
282 .get_or_insert_with(Default::default)
283 .relations
284 .get_or_insert_with(BundledMessageLikeRelations::new);
285 relations.replace = Some(Box::new(replacement.into()));
286 self
287 }
288
289 pub fn state_key(mut self, state_key: impl Into<String>) -> Self {
294 self.state_key = Some(state_key.into());
295 self
296 }
297}
298
299impl<E> EventBuilder<E>
300where
301 E: StaticEventContent<IsPrefix = False> + Serialize,
302{
303 #[inline(always)]
304 fn construct_json(self) -> serde_json::Value {
305 let mut json = json!({
306 "type": E::TYPE,
307 "content": self.content,
308 "origin_server_ts": self.server_ts,
309 });
310
311 let map = json.as_object_mut().unwrap();
312
313 if self.format.has_sender() {
314 let sender = self
317 .sender
318 .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");
319 map.insert("sender".to_owned(), json!(sender));
320 }
321
322 if self.format.has_event_id() && !self.no_event_id {
323 let event_id = self.event_id.unwrap_or_else(|| {
324 let server_name = self
325 .room
326 .as_ref()
327 .and_then(|room_id| room_id.server_name())
328 .unwrap_or(server_name!("dummy.org"));
329
330 EventId::new(server_name)
331 });
332
333 map.insert("event_id".to_owned(), json!(event_id));
334 }
335
336 if self.format.has_room_id() {
337 let room_id = self.room.expect("TimelineEvent requires a room id");
338 map.insert("room_id".to_owned(), json!(room_id));
339 }
340
341 if let Some(redacts) = self.redacts {
342 map.insert("redacts".to_owned(), json!(redacts));
343 }
344
345 if let Some(unsigned) = self.unsigned {
346 map.insert("unsigned".to_owned(), json!(unsigned));
347 }
348
349 if let Some(state_key) = self.state_key {
350 map.insert("state_key".to_owned(), json!(state_key));
351 }
352
353 json
354 }
355
356 pub fn into_raw<T>(self) -> Raw<T> {
362 Raw::new(&self.construct_json()).unwrap().cast_unchecked()
363 }
364
365 pub fn into_raw_timeline(self) -> Raw<AnyTimelineEvent> {
366 self.into_raw()
367 }
368
369 pub fn into_any_sync_message_like_event(self) -> AnySyncMessageLikeEvent {
370 self.format(EventFormat::SyncTimeline)
371 .into_raw()
372 .deserialize()
373 .expect("expected message like event")
374 }
375
376 pub fn into_original_sync_room_message_event(self) -> OriginalSyncRoomMessageEvent {
377 self.format(EventFormat::SyncTimeline)
378 .into_raw()
379 .deserialize()
380 .expect("expected original sync room message event")
381 }
382
383 pub fn into_raw_sync(self) -> Raw<AnySyncTimelineEvent> {
384 self.format(EventFormat::SyncTimeline).into_raw()
385 }
386
387 pub fn into_raw_sync_state(self) -> Raw<AnySyncStateEvent> {
388 self.format(EventFormat::SyncTimeline).into_raw()
389 }
390
391 pub fn into_event(self) -> TimelineEvent {
392 TimelineEvent::from_plaintext(self.into_raw_sync())
393 }
394}
395
396impl EventBuilder<RoomEncryptedEventContent> {
397 pub fn into_utd_sync_timeline_event(self) -> TimelineEvent {
400 let session_id = as_variant!(&self.content.scheme, EncryptedEventScheme::MegolmV1AesSha2)
401 .map(|content| content.session_id.clone());
402
403 TimelineEvent::from_utd(
404 self.into(),
405 UnableToDecryptInfo {
406 session_id,
407 reason: UnableToDecryptReason::MissingMegolmSession { withheld_code: None },
408 },
409 )
410 }
411}
412
413impl EventBuilder<RoomMessageEventContent> {
414 pub fn reply_to(mut self, event_id: &EventId) -> Self {
416 self.content.relates_to =
417 Some(Relation::Reply { in_reply_to: InReplyTo::new(event_id.to_owned()) });
418 self
419 }
420
421 pub fn in_thread(mut self, root: &EventId, latest_thread_event: &EventId) -> Self {
424 self.content.relates_to =
425 Some(Relation::Thread(Thread::plain(root.to_owned(), latest_thread_event.to_owned())));
426 self
427 }
428
429 pub fn in_thread_reply(mut self, root: &EventId, replied_to: &EventId) -> Self {
432 self.content.relates_to =
433 Some(Relation::Thread(Thread::reply(root.to_owned(), replied_to.to_owned())));
434 self
435 }
436
437 pub fn mentions(mut self, mentions: Mentions) -> Self {
439 self.content.mentions = Some(mentions);
440 self
441 }
442
443 pub fn edit(
446 mut self,
447 edited_event_id: &EventId,
448 new_content: RoomMessageEventContentWithoutRelation,
449 ) -> Self {
450 self.content.relates_to =
451 Some(Relation::Replacement(Replacement::new(edited_event_id.to_owned(), new_content)));
452 self
453 }
454
455 pub fn caption(
459 mut self,
460 caption: Option<String>,
461 formatted_caption: Option<FormattedBody>,
462 ) -> Self {
463 match &mut self.content.msgtype {
464 MessageType::Image(image) => {
465 let filename = image.filename().to_owned();
466 if let Some(caption) = caption {
467 image.body = caption;
468 image.filename = Some(filename);
469 } else {
470 image.body = filename;
471 image.filename = None;
472 }
473 image.formatted = formatted_caption;
474 }
475
476 MessageType::Audio(_) | MessageType::Video(_) | MessageType::File(_) => {
477 unimplemented!();
478 }
479
480 _ => panic!("unexpected event type for a caption"),
481 }
482
483 self
484 }
485}
486
487impl EventBuilder<UnstablePollStartEventContent> {
488 pub fn reply_to(mut self, event_id: &EventId) -> Self {
490 if let UnstablePollStartEventContent::New(content) = &mut self.content {
491 content.relates_to = Some(RelationWithoutReplacement::Reply {
492 in_reply_to: InReplyTo::new(event_id.to_owned()),
493 });
494 }
495 self
496 }
497
498 pub fn in_thread(mut self, root: &EventId, reply_to_event_id: &EventId) -> Self {
501 let thread = Thread::reply(root.to_owned(), reply_to_event_id.to_owned());
502
503 if let UnstablePollStartEventContent::New(content) = &mut self.content {
504 content.relates_to = Some(RelationWithoutReplacement::Thread(thread));
505 }
506 self
507 }
508}
509
510impl EventBuilder<RoomCreateEventContent> {
511 pub fn predecessor(mut self, room_id: &RoomId) -> Self {
513 self.content.predecessor = Some(PreviousRoom::new(room_id.to_owned()));
514 self
515 }
516
517 pub fn no_predecessor(mut self) -> Self {
519 self.content.predecessor = None;
520 self
521 }
522
523 pub fn with_space_type(mut self) -> Self {
525 self.content.room_type = Some(RoomType::Space);
526 self
527 }
528}
529
530impl EventBuilder<StickerEventContent> {
531 pub fn reply_thread(mut self, root: &EventId, reply_to_event: &EventId) -> Self {
533 self.content.relates_to =
534 Some(Relation::Thread(Thread::reply(root.to_owned(), reply_to_event.to_owned())));
535 self
536 }
537}
538
539impl<E: StaticEventContent<IsPrefix = False>> From<EventBuilder<E>> for Raw<AnySyncTimelineEvent>
540where
541 E: Serialize,
542{
543 fn from(val: EventBuilder<E>) -> Self {
544 val.into_raw_sync()
545 }
546}
547
548impl<E: StaticEventContent<IsPrefix = False>> From<EventBuilder<E>> for AnySyncTimelineEvent
549where
550 E: Serialize,
551{
552 fn from(val: EventBuilder<E>) -> Self {
553 Raw::<AnySyncTimelineEvent>::from(val).deserialize().expect("expected sync timeline event")
554 }
555}
556
557impl<E: StaticEventContent<IsPrefix = False>> From<EventBuilder<E>> for Raw<AnyTimelineEvent>
558where
559 E: Serialize,
560{
561 fn from(val: EventBuilder<E>) -> Self {
562 val.into_raw_timeline()
563 }
564}
565
566impl<E: StaticEventContent<IsPrefix = False>> From<EventBuilder<E>> for AnyTimelineEvent
567where
568 E: Serialize,
569{
570 fn from(val: EventBuilder<E>) -> Self {
571 Raw::<AnyTimelineEvent>::from(val).deserialize().expect("expected timeline event")
572 }
573}
574
575impl<E: StaticEventContent<IsPrefix = False>> From<EventBuilder<E>>
576 for Raw<AnyGlobalAccountDataEvent>
577where
578 E: Serialize,
579{
580 fn from(val: EventBuilder<E>) -> Self {
581 val.format(EventFormat::GlobalAccountData).into_raw()
582 }
583}
584
585impl<E: StaticEventContent<IsPrefix = False>> From<EventBuilder<E>> for AnyGlobalAccountDataEvent
586where
587 E: Serialize,
588{
589 fn from(val: EventBuilder<E>) -> Self {
590 Raw::<AnyGlobalAccountDataEvent>::from(val)
591 .deserialize()
592 .expect("expected global account data")
593 }
594}
595
596impl<E: StaticEventContent<IsPrefix = False>> From<EventBuilder<E>> for TimelineEvent
597where
598 E: Serialize,
599{
600 fn from(val: EventBuilder<E>) -> Self {
601 val.into_event()
602 }
603}
604
605impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
606 for Raw<AnySyncStateEvent>
607{
608 fn from(val: EventBuilder<E>) -> Self {
609 val.format(EventFormat::SyncTimeline).into_raw()
610 }
611}
612
613impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
614 for AnySyncStateEvent
615{
616 fn from(val: EventBuilder<E>) -> Self {
617 Raw::<AnySyncStateEvent>::from(val).deserialize().expect("expected sync state")
618 }
619}
620
621impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
622 for Raw<SyncStateEvent<E>>
623where
624 E: StaticStateEventContent + RedactContent,
625 E::Redacted: RedactedStateEventContent,
626{
627 fn from(val: EventBuilder<E>) -> Self {
628 val.format(EventFormat::SyncTimeline).into_raw()
629 }
630}
631
632impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
633 for SyncStateEvent<E>
634where
635 E: StaticStateEventContent + RedactContent + EventContentFromType,
636 E::Redacted: RedactedStateEventContent<StateKey = <E as StateEventContent>::StateKey>
637 + EventContentFromType,
638{
639 fn from(val: EventBuilder<E>) -> Self {
640 Raw::<SyncStateEvent<E>>::from(val).deserialize().expect("expected sync state")
641 }
642}
643
644impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
645 for Raw<AnyStateEvent>
646{
647 fn from(val: EventBuilder<E>) -> Self {
648 val.into_raw()
649 }
650}
651
652impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
653 for AnyStateEvent
654{
655 fn from(val: EventBuilder<E>) -> Self {
656 Raw::<AnyStateEvent>::from(val).deserialize().expect("expected state")
657 }
658}
659
660impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
661 for Raw<StateEvent<E>>
662where
663 E: StaticStateEventContent + RedactContent,
664 E::Redacted: RedactedStateEventContent,
665{
666 fn from(val: EventBuilder<E>) -> Self {
667 val.into_raw()
668 }
669}
670
671impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
672 for StateEvent<E>
673where
674 E: StaticStateEventContent + RedactContent + EventContentFromType,
675 E::Redacted: RedactedStateEventContent<StateKey = <E as StateEventContent>::StateKey>
676 + EventContentFromType,
677{
678 fn from(val: EventBuilder<E>) -> Self {
679 Raw::<StateEvent<E>>::from(val).deserialize().expect("expected state")
680 }
681}
682
683impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
684 for Raw<AnyStrippedStateEvent>
685{
686 fn from(val: EventBuilder<E>) -> Self {
687 val.format(EventFormat::StrippedState).into_raw()
688 }
689}
690
691impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
692 for AnyStrippedStateEvent
693{
694 fn from(val: EventBuilder<E>) -> Self {
695 Raw::<AnyStrippedStateEvent>::from(val).deserialize().expect("expected stripped state")
696 }
697}
698
699impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
700 for Raw<StrippedStateEvent<E::PossiblyRedacted>>
701where
702 E: StaticStateEventContent,
703{
704 fn from(val: EventBuilder<E>) -> Self {
705 val.format(EventFormat::StrippedState).into_raw()
706 }
707}
708
709impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
710 for StrippedStateEvent<E::PossiblyRedacted>
711where
712 E: StaticStateEventContent,
713 E::PossiblyRedacted: PossiblyRedactedStateEventContent + EventContentFromType,
714{
715 fn from(val: EventBuilder<E>) -> Self {
716 Raw::<StrippedStateEvent<E::PossiblyRedacted>>::from(val)
717 .deserialize()
718 .expect("expected stripped state")
719 }
720}
721
722impl<E: StaticEventContent<IsPrefix = False> + EphemeralRoomEventContent> From<EventBuilder<E>>
723 for Raw<AnySyncEphemeralRoomEvent>
724{
725 fn from(val: EventBuilder<E>) -> Self {
726 val.format(EventFormat::Ephemeral).into_raw()
727 }
728}
729
730impl<E: StaticEventContent<IsPrefix = False> + EphemeralRoomEventContent> From<EventBuilder<E>>
731 for AnySyncEphemeralRoomEvent
732{
733 fn from(val: EventBuilder<E>) -> Self {
734 Raw::<AnySyncEphemeralRoomEvent>::from(val).deserialize().expect("expected ephemeral")
735 }
736}
737
738impl<E: StaticEventContent<IsPrefix = False> + MessageLikeEventContent> From<EventBuilder<E>>
739 for Raw<AnySyncMessageLikeEvent>
740{
741 fn from(val: EventBuilder<E>) -> Self {
742 val.format(EventFormat::SyncTimeline).into_raw()
743 }
744}
745
746impl<E: StaticEventContent<IsPrefix = False> + MessageLikeEventContent> From<EventBuilder<E>>
747 for AnySyncMessageLikeEvent
748{
749 fn from(val: EventBuilder<E>) -> Self {
750 Raw::<AnySyncMessageLikeEvent>::from(val).deserialize().expect("expected sync message-like")
751 }
752}
753
754impl<E: StaticEventContent<IsPrefix = False> + MessageLikeEventContent> From<EventBuilder<E>>
755 for Raw<SyncMessageLikeEvent<E>>
756where
757 E: RedactContent,
758 E::Redacted: RedactedMessageLikeEventContent,
759{
760 fn from(val: EventBuilder<E>) -> Self {
761 val.format(EventFormat::SyncTimeline).into_raw()
762 }
763}
764
765impl<E: StaticEventContent<IsPrefix = False> + MessageLikeEventContent> From<EventBuilder<E>>
766 for SyncMessageLikeEvent<E>
767where
768 E: RedactContent + EventContentFromType,
769 E::Redacted: RedactedMessageLikeEventContent + EventContentFromType,
770{
771 fn from(val: EventBuilder<E>) -> Self {
772 Raw::<SyncMessageLikeEvent<E>>::from(val).deserialize().expect("expected sync message-like")
773 }
774}
775
776impl<E: StaticEventContent<IsPrefix = False> + MessageLikeEventContent> From<EventBuilder<E>>
777 for Raw<AnyMessageLikeEvent>
778{
779 fn from(val: EventBuilder<E>) -> Self {
780 val.into_raw()
781 }
782}
783
784impl<E: StaticEventContent<IsPrefix = False> + MessageLikeEventContent> From<EventBuilder<E>>
785 for AnyMessageLikeEvent
786{
787 fn from(val: EventBuilder<E>) -> Self {
788 Raw::<AnyMessageLikeEvent>::from(val).deserialize().expect("expected message-like")
789 }
790}
791
792impl<E: StaticEventContent<IsPrefix = False> + MessageLikeEventContent> From<EventBuilder<E>>
793 for Raw<MessageLikeEvent<E>>
794where
795 E: RedactContent,
796 E::Redacted: RedactedMessageLikeEventContent,
797{
798 fn from(val: EventBuilder<E>) -> Self {
799 val.into_raw()
800 }
801}
802
803impl<E: StaticEventContent<IsPrefix = False> + MessageLikeEventContent> From<EventBuilder<E>>
804 for MessageLikeEvent<E>
805where
806 E: RedactContent + EventContentFromType,
807 E::Redacted: RedactedMessageLikeEventContent + EventContentFromType,
808{
809 fn from(val: EventBuilder<E>) -> Self {
810 Raw::<MessageLikeEvent<E>>::from(val).deserialize().expect("expected message-like")
811 }
812}
813
814#[derive(Debug, Default)]
815pub struct EventFactory {
816 next_ts: AtomicU64,
817 sender: Option<OwnedUserId>,
818 room: Option<OwnedRoomId>,
819}
820
821impl EventFactory {
822 pub fn new() -> Self {
823 Self { next_ts: AtomicU64::new(0), sender: None, room: None }
824 }
825
826 pub fn room(mut self, room_id: &RoomId) -> Self {
827 self.room = Some(room_id.to_owned());
828 self
829 }
830
831 pub fn sender(mut self, sender: &UserId) -> Self {
832 self.sender = Some(sender.to_owned());
833 self
834 }
835
836 pub fn server_ts(self, ts: u64) -> Self {
837 self.next_ts.store(ts, SeqCst);
838 self
839 }
840
841 fn next_server_ts(&self) -> MilliSecondsSinceUnixEpoch {
842 MilliSecondsSinceUnixEpoch(
843 self.next_ts
844 .fetch_add(1, SeqCst)
845 .try_into()
846 .expect("server timestamp should fit in js_int::UInt"),
847 )
848 }
849
850 pub fn event<E: StaticEventContent<IsPrefix = False>>(&self, content: E) -> EventBuilder<E> {
852 EventBuilder {
853 format: EventFormat::Timeline,
854 sender: self.sender.clone(),
855 room: self.room.clone(),
856 server_ts: self.next_server_ts(),
857 event_id: None,
858 no_event_id: false,
859 redacts: None,
860 content,
861 unsigned: None,
862 state_key: None,
863 }
864 }
865
866 pub fn text_msg(&self, content: impl Into<String>) -> EventBuilder<RoomMessageEventContent> {
868 self.event(RoomMessageEventContent::text_plain(content.into()))
869 }
870
871 pub fn emote(&self, content: impl Into<String>) -> EventBuilder<RoomMessageEventContent> {
873 self.event(RoomMessageEventContent::emote_plain(content.into()))
874 }
875
876 pub fn encrypted(
879 &self,
880 ciphertext: impl Into<String>,
881 sender_key: impl Into<String>,
882 device_id: impl Into<OwnedDeviceId>,
883 session_id: impl Into<String>,
884 ) -> EventBuilder<RoomEncryptedEventContent> {
885 self.event(RoomEncryptedEventContent::new(
886 EncryptedEventScheme::MegolmV1AesSha2(
887 MegolmV1AesSha2ContentInit {
888 ciphertext: ciphertext.into(),
889 sender_key: sender_key.into(),
890 device_id: device_id.into(),
891 session_id: session_id.into(),
892 }
893 .into(),
894 ),
895 None,
896 ))
897 }
898
899 pub fn member(&self, member: &UserId) -> EventBuilder<RoomMemberEventContent> {
929 let mut event = self.event(RoomMemberEventContent::new(MembershipState::Join));
930
931 if self.sender.is_some() {
932 event.sender = self.sender.clone();
933 } else {
934 event.sender = Some(member.to_owned());
935 }
936
937 event.state_key = Some(member.to_string());
938
939 event
940 }
941
942 pub fn room_tombstone(
944 &self,
945 body: impl Into<String>,
946 replacement: &RoomId,
947 ) -> EventBuilder<RoomTombstoneEventContent> {
948 let mut event =
949 self.event(RoomTombstoneEventContent::new(body.into(), replacement.to_owned()));
950 event.state_key = Some("".to_owned());
951 event
952 }
953
954 pub fn room_topic(&self, topic: impl Into<String>) -> EventBuilder<RoomTopicEventContent> {
956 let mut event = self.event(RoomTopicEventContent::new(topic.into()));
957 event.state_key = Some("".to_owned());
959 event
960 }
961
962 pub fn room_name(&self, name: impl Into<String>) -> EventBuilder<RoomNameEventContent> {
964 let mut event = self.event(RoomNameEventContent::new(name.into()));
965 event.state_key = Some("".to_owned());
967 event
968 }
969
970 pub fn room_avatar(&self) -> EventBuilder<RoomAvatarEventContent> {
972 let mut event = self.event(RoomAvatarEventContent::new());
973 event.state_key = Some("".to_owned());
975 event
976 }
977
978 pub fn member_hints(
999 &self,
1000 service_members: BTreeSet<OwnedUserId>,
1001 ) -> EventBuilder<MemberHintsEventContent> {
1002 self.event(MemberHintsEventContent::new(service_members)).state_key("")
1004 }
1005
1006 pub fn text_html(
1008 &self,
1009 plain: impl Into<String>,
1010 html: impl Into<String>,
1011 ) -> EventBuilder<RoomMessageEventContent> {
1012 self.event(RoomMessageEventContent::text_html(plain, html))
1013 }
1014
1015 pub fn notice(&self, content: impl Into<String>) -> EventBuilder<RoomMessageEventContent> {
1017 self.event(RoomMessageEventContent::notice_plain(content))
1018 }
1019
1020 pub fn reaction(
1022 &self,
1023 event_id: &EventId,
1024 annotation: impl Into<String>,
1025 ) -> EventBuilder<ReactionEventContent> {
1026 self.event(ReactionEventContent::new(Annotation::new(
1027 event_id.to_owned(),
1028 annotation.into(),
1029 )))
1030 }
1031
1032 pub fn redaction(&self, event_id: &EventId) -> EventBuilder<RoomRedactionEventContent> {
1037 let mut builder = self.event(RoomRedactionEventContent::new_v11(event_id.to_owned()));
1038 builder.redacts = Some(event_id.to_owned());
1039 builder
1040 }
1041
1042 pub fn redacted<T: StaticEventContent<IsPrefix = False> + RedactedMessageLikeEventContent>(
1045 &self,
1046 redacter: &UserId,
1047 content: T,
1048 ) -> EventBuilder<T> {
1049 let mut builder = self.event(content);
1050
1051 let redacted_because = RedactedBecause {
1052 content: RoomRedactionEventContent::default(),
1053 event_id: EventId::new(server_name!("dummy.server")),
1054 sender: redacter.to_owned(),
1055 origin_server_ts: self.next_server_ts(),
1056 };
1057 builder.unsigned.get_or_insert_with(Default::default).redacted_because =
1058 Some(redacted_because);
1059
1060 builder
1061 }
1062
1063 pub fn redacted_state<T: StaticEventContent<IsPrefix = False> + RedactedStateEventContent>(
1066 &self,
1067 redacter: &UserId,
1068 state_key: impl Into<String>,
1069 content: T,
1070 ) -> EventBuilder<T> {
1071 let mut builder = self.event(content);
1072
1073 let redacted_because = RedactedBecause {
1074 content: RoomRedactionEventContent::default(),
1075 event_id: EventId::new(server_name!("dummy.server")),
1076 sender: redacter.to_owned(),
1077 origin_server_ts: self.next_server_ts(),
1078 };
1079 builder.unsigned.get_or_insert_with(Default::default).redacted_because =
1080 Some(redacted_because);
1081 builder.state_key = Some(state_key.into());
1082
1083 builder
1084 }
1085
1086 pub fn poll_start(
1089 &self,
1090 fallback_text: impl Into<String>,
1091 poll_question: impl Into<String>,
1092 answers: Vec<impl Into<String>>,
1093 ) -> EventBuilder<UnstablePollStartEventContent> {
1094 let answers: Vec<UnstablePollAnswer> = answers
1096 .into_iter()
1097 .enumerate()
1098 .map(|(idx, answer)| UnstablePollAnswer::new(idx.to_string(), answer))
1099 .collect();
1100 let poll_answers = answers.try_into().unwrap();
1101 let poll_start_content =
1102 UnstablePollStartEventContent::New(NewUnstablePollStartEventContent::plain_text(
1103 fallback_text,
1104 UnstablePollStartContentBlock::new(poll_question, poll_answers),
1105 ));
1106 self.event(poll_start_content)
1107 }
1108
1109 pub fn poll_edit(
1111 &self,
1112 edited_event_id: &EventId,
1113 poll_question: impl Into<String>,
1114 answers: Vec<impl Into<String>>,
1115 ) -> EventBuilder<ReplacementUnstablePollStartEventContent> {
1116 let answers: Vec<UnstablePollAnswer> = answers
1118 .into_iter()
1119 .enumerate()
1120 .map(|(idx, answer)| UnstablePollAnswer::new(idx.to_string(), answer))
1121 .collect();
1122 let poll_answers = answers.try_into().unwrap();
1123 let poll_start_content_block =
1124 UnstablePollStartContentBlock::new(poll_question, poll_answers);
1125 self.event(ReplacementUnstablePollStartEventContent::new(
1126 poll_start_content_block,
1127 edited_event_id.to_owned(),
1128 ))
1129 }
1130
1131 pub fn poll_response(
1134 &self,
1135 answers: Vec<impl Into<String>>,
1136 poll_start_id: &EventId,
1137 ) -> EventBuilder<UnstablePollResponseEventContent> {
1138 self.event(UnstablePollResponseEventContent::new(
1139 answers.into_iter().map(Into::into).collect(),
1140 poll_start_id.to_owned(),
1141 ))
1142 }
1143
1144 pub fn poll_end(
1147 &self,
1148 content: impl Into<String>,
1149 poll_start_id: &EventId,
1150 ) -> EventBuilder<UnstablePollEndEventContent> {
1151 self.event(UnstablePollEndEventContent::new(content.into(), poll_start_id.to_owned()))
1152 }
1153
1154 pub fn image(
1157 &self,
1158 filename: String,
1159 url: OwnedMxcUri,
1160 ) -> EventBuilder<RoomMessageEventContent> {
1161 let image_event_content = ImageMessageEventContent::plain(filename, url);
1162 self.event(RoomMessageEventContent::new(MessageType::Image(image_event_content)))
1163 }
1164
1165 pub fn gallery(
1168 &self,
1169 body: String,
1170 filename: String,
1171 url: OwnedMxcUri,
1172 ) -> EventBuilder<RoomMessageEventContent> {
1173 let gallery_event_content = GalleryMessageEventContent::new(
1174 body,
1175 None,
1176 vec![GalleryItemType::Image(ImageMessageEventContent::plain(filename, url))],
1177 );
1178 self.event(RoomMessageEventContent::new(MessageType::Gallery(gallery_event_content)))
1179 }
1180
1181 pub fn typing(&self, user_ids: Vec<&UserId>) -> EventBuilder<TypingEventContent> {
1183 self.event(TypingEventContent::new(user_ids.into_iter().map(ToOwned::to_owned).collect()))
1184 .format(EventFormat::Ephemeral)
1185 }
1186
1187 pub fn read_receipts(&self) -> ReadReceiptBuilder<'_> {
1189 ReadReceiptBuilder { factory: self, content: ReceiptEventContent(Default::default()) }
1190 }
1191
1192 pub fn create(
1194 &self,
1195 creator_user_id: &UserId,
1196 room_version: RoomVersionId,
1197 ) -> EventBuilder<RoomCreateEventContent> {
1198 let mut event = self.event(RoomCreateEventContent::new_v1(creator_user_id.to_owned()));
1199 event.content.room_version = room_version;
1200
1201 if self.sender.is_some() {
1202 event.sender = self.sender.clone();
1203 } else {
1204 event.sender = Some(creator_user_id.to_owned());
1205 }
1206
1207 event.state_key = Some("".to_owned());
1208
1209 event
1210 }
1211
1212 pub fn power_levels(
1214 &self,
1215 map: &mut BTreeMap<OwnedUserId, Int>,
1216 ) -> EventBuilder<RoomPowerLevelsEventContent> {
1217 let mut event = RoomPowerLevelsEventContent::new(&AuthorizationRules::V1);
1218 event.users.append(map);
1219 self.event(event)
1220 }
1221
1222 pub fn server_acl(
1224 &self,
1225 allow_ip_literals: bool,
1226 allow: Vec<String>,
1227 deny: Vec<String>,
1228 ) -> EventBuilder<RoomServerAclEventContent> {
1229 self.event(RoomServerAclEventContent::new(allow_ip_literals, allow, deny))
1230 }
1231
1232 pub fn canonical_alias(
1234 &self,
1235 alias: Option<OwnedRoomAliasId>,
1236 alt_aliases: Vec<OwnedRoomAliasId>,
1237 ) -> EventBuilder<RoomCanonicalAliasEventContent> {
1238 let mut event = RoomCanonicalAliasEventContent::new();
1239 event.alias = alias;
1240 event.alt_aliases = alt_aliases;
1241 self.event(event)
1242 }
1243
1244 pub fn beacon(
1270 &self,
1271 beacon_info_event_id: OwnedEventId,
1272 latitude: f64,
1273 longitude: f64,
1274 uncertainty: u32,
1275 ts: Option<MilliSecondsSinceUnixEpoch>,
1276 ) -> EventBuilder<BeaconEventContent> {
1277 let geo_uri = format!("geo:{latitude},{longitude};u={uncertainty}");
1278 self.event(BeaconEventContent::new(beacon_info_event_id, geo_uri, ts))
1279 }
1280
1281 pub fn sticker(
1283 &self,
1284 body: impl Into<String>,
1285 info: ImageInfo,
1286 url: OwnedMxcUri,
1287 ) -> EventBuilder<StickerEventContent> {
1288 self.event(StickerEventContent::new(body.into(), info, url))
1289 }
1290
1291 pub fn call_invite(
1293 &self,
1294 call_id: OwnedVoipId,
1295 lifetime: UInt,
1296 offer: SessionDescription,
1297 version: VoipVersionId,
1298 ) -> EventBuilder<CallInviteEventContent> {
1299 self.event(CallInviteEventContent::new(call_id, lifetime, offer, version))
1300 }
1301
1302 pub fn rtc_notification(
1304 &self,
1305 notification_type: NotificationType,
1306 ) -> EventBuilder<RtcNotificationEventContent> {
1307 self.event(RtcNotificationEventContent::new(
1308 MilliSecondsSinceUnixEpoch::now(),
1309 Duration::new(30, 0),
1310 notification_type,
1311 ))
1312 }
1313
1314 pub fn call_decline(
1316 &self,
1317 notification_event_id: &EventId,
1318 ) -> EventBuilder<RtcDeclineEventContent> {
1319 self.event(RtcDeclineEventContent::new(notification_event_id))
1320 }
1321
1322 pub fn direct(&self) -> EventBuilder<DirectEventContent> {
1324 self.global_account_data(DirectEventContent::default())
1325 }
1326
1327 pub fn ignored_user_list(
1329 &self,
1330 users: impl IntoIterator<Item = OwnedUserId>,
1331 ) -> EventBuilder<IgnoredUserListEventContent> {
1332 self.global_account_data(IgnoredUserListEventContent::users(users))
1333 }
1334
1335 pub fn push_rules(&self, rules: Ruleset) -> EventBuilder<PushRulesEventContent> {
1337 self.global_account_data(PushRulesEventContent::new(rules))
1338 }
1339
1340 pub fn space_child(
1342 &self,
1343 parent: OwnedRoomId,
1344 child: OwnedRoomId,
1345 ) -> EventBuilder<SpaceChildEventContent> {
1346 let mut event = self.event(SpaceChildEventContent::new(vec![]));
1347 event.room = Some(parent);
1348 event.state_key = Some(child.to_string());
1349 event
1350 }
1351
1352 pub fn space_parent(
1354 &self,
1355 parent: OwnedRoomId,
1356 child: OwnedRoomId,
1357 ) -> EventBuilder<SpaceParentEventContent> {
1358 let mut event = self.event(SpaceParentEventContent::new(vec![]));
1359 event.state_key = Some(parent.to_string());
1360 event.room = Some(child);
1361 event
1362 }
1363
1364 pub fn custom_message_like_event(&self) -> EventBuilder<CustomMessageLikeEventContent> {
1366 self.event(CustomMessageLikeEventContent)
1367 }
1368
1369 pub fn set_next_ts(&self, value: u64) {
1373 self.next_ts.store(value, SeqCst);
1374 }
1375
1376 pub fn global_account_data<C>(&self, content: C) -> EventBuilder<C>
1378 where
1379 C: GlobalAccountDataEventContent + StaticEventContent<IsPrefix = False>,
1380 {
1381 self.event(content).format(EventFormat::GlobalAccountData)
1382 }
1383}
1384
1385impl EventBuilder<DirectEventContent> {
1386 pub fn add_user(mut self, user_id: OwnedDirectUserIdentifier, room_id: &RoomId) -> Self {
1388 self.content.0.entry(user_id).or_default().push(room_id.to_owned());
1389 self
1390 }
1391}
1392
1393impl EventBuilder<RoomMemberEventContent> {
1394 pub fn membership(mut self, state: MembershipState) -> Self {
1399 self.content.membership = state;
1400 self
1401 }
1402
1403 pub fn invited(mut self, invited_user: &UserId) -> Self {
1406 assert_ne!(
1407 self.sender.as_deref().unwrap(),
1408 invited_user,
1409 "invited user and sender can't be the same person"
1410 );
1411 self.content.membership = MembershipState::Invite;
1412 self.state_key = Some(invited_user.to_string());
1413 self
1414 }
1415
1416 pub fn kicked(mut self, kicked_user: &UserId) -> Self {
1419 assert_ne!(
1420 self.sender.as_deref().unwrap(),
1421 kicked_user,
1422 "kicked user and sender can't be the same person, otherwise it's just a Leave"
1423 );
1424 self.content.membership = MembershipState::Leave;
1425 self.state_key = Some(kicked_user.to_string());
1426 self
1427 }
1428
1429 pub fn banned(mut self, banned_user: &UserId) -> Self {
1432 assert_ne!(
1433 self.sender.as_deref().unwrap(),
1434 banned_user,
1435 "a user can't ban itself" );
1437 self.content.membership = MembershipState::Ban;
1438 self.state_key = Some(banned_user.to_string());
1439 self
1440 }
1441
1442 pub fn display_name(mut self, display_name: impl Into<String>) -> Self {
1444 self.content.displayname = Some(display_name.into());
1445 self
1446 }
1447
1448 pub fn avatar_url(mut self, url: &MxcUri) -> Self {
1450 self.content.avatar_url = Some(url.to_owned());
1451 self
1452 }
1453
1454 pub fn reason(mut self, reason: impl Into<String>) -> Self {
1456 self.content.reason = Some(reason.into());
1457 self
1458 }
1459
1460 pub fn previous(mut self, previous: impl Into<PreviousMembership>) -> Self {
1462 let previous = previous.into();
1463
1464 let mut prev_content = RoomMemberEventContent::new(previous.state);
1465 if let Some(avatar_url) = previous.avatar_url {
1466 prev_content.avatar_url = Some(avatar_url);
1467 }
1468 if let Some(display_name) = previous.display_name {
1469 prev_content.displayname = Some(display_name);
1470 }
1471
1472 self.unsigned.get_or_insert_with(Default::default).prev_content = Some(prev_content);
1473 self
1474 }
1475}
1476
1477impl EventBuilder<RoomAvatarEventContent> {
1478 pub fn url(mut self, url: &MxcUri) -> Self {
1480 self.content.url = Some(url.to_owned());
1481 self
1482 }
1483
1484 pub fn info(mut self, image: avatar::ImageInfo) -> Self {
1486 self.content.info = Some(Box::new(image));
1487 self
1488 }
1489}
1490
1491impl EventBuilder<RtcNotificationEventContent> {
1492 pub fn mentions(mut self, users: impl IntoIterator<Item = OwnedUserId>) -> Self {
1493 self.content.mentions = Some(Mentions::with_user_ids(users));
1494 self
1495 }
1496
1497 pub fn relates_to_membership_state_event(mut self, event_id: OwnedEventId) -> Self {
1498 self.content.relates_to = Some(Reference::new(event_id));
1499 self
1500 }
1501
1502 pub fn lifetime(mut self, time_in_seconds: u64) -> Self {
1503 self.content.lifetime = Duration::from_secs(time_in_seconds);
1504 self
1505 }
1506}
1507
1508pub struct ReadReceiptBuilder<'a> {
1509 factory: &'a EventFactory,
1510 content: ReceiptEventContent,
1511}
1512
1513impl ReadReceiptBuilder<'_> {
1514 pub fn add(
1516 self,
1517 event_id: &EventId,
1518 user_id: &UserId,
1519 tyype: ReceiptType,
1520 thread: ReceiptThread,
1521 ) -> Self {
1522 let ts = self.factory.next_server_ts();
1523 self.add_with_timestamp(event_id, user_id, tyype, thread, Some(ts))
1524 }
1525
1526 pub fn add_with_timestamp(
1528 mut self,
1529 event_id: &EventId,
1530 user_id: &UserId,
1531 tyype: ReceiptType,
1532 thread: ReceiptThread,
1533 ts: Option<MilliSecondsSinceUnixEpoch>,
1534 ) -> Self {
1535 let by_event = self.content.0.entry(event_id.to_owned()).or_default();
1536 let by_type = by_event.entry(tyype).or_default();
1537
1538 let mut receipt = Receipt::default();
1539 if let Some(ts) = ts {
1540 receipt.ts = Some(ts);
1541 }
1542 receipt.thread = thread;
1543
1544 by_type.insert(user_id.to_owned(), receipt);
1545 self
1546 }
1547
1548 pub fn into_content(self) -> ReceiptEventContent {
1550 self.content
1551 }
1552
1553 pub fn into_event(self) -> EventBuilder<ReceiptEventContent> {
1555 self.factory.event(self.into_content()).format(EventFormat::Ephemeral)
1556 }
1557}
1558
1559pub struct PreviousMembership {
1560 state: MembershipState,
1561 avatar_url: Option<OwnedMxcUri>,
1562 display_name: Option<String>,
1563}
1564
1565impl PreviousMembership {
1566 pub fn new(state: MembershipState) -> Self {
1567 Self { state, avatar_url: None, display_name: None }
1568 }
1569
1570 pub fn avatar_url(mut self, url: &MxcUri) -> Self {
1571 self.avatar_url = Some(url.to_owned());
1572 self
1573 }
1574
1575 pub fn display_name(mut self, name: impl Into<String>) -> Self {
1576 self.display_name = Some(name.into());
1577 self
1578 }
1579}
1580
1581impl From<MembershipState> for PreviousMembership {
1582 fn from(state: MembershipState) -> Self {
1583 Self::new(state)
1584 }
1585}
1586
1587#[derive(Clone, Default, Debug, Serialize, EventContent)]
1588#[ruma_event(type = "rs.matrix-sdk.custom.test", kind = MessageLike)]
1589pub struct CustomMessageLikeEventContent;