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, AnyStateEvent, AnySyncMessageLikeEvent, AnySyncStateEvent,
33 AnySyncTimelineEvent, AnyTimelineEvent, BundledMessageLikeRelations, False,
34 GlobalAccountDataEventContent, Mentions, RedactedMessageLikeEventContent,
35 RedactedStateEventContent, StateEventContent, StaticEventContent,
36 beacon::BeaconEventContent,
37 call::{SessionDescription, invite::CallInviteEventContent},
38 direct::{DirectEventContent, OwnedDirectUserIdentifier},
39 ignored_user_list::IgnoredUserListEventContent,
40 member_hints::MemberHintsEventContent,
41 poll::{
42 unstable_end::UnstablePollEndEventContent,
43 unstable_response::UnstablePollResponseEventContent,
44 unstable_start::{
45 NewUnstablePollStartEventContent, ReplacementUnstablePollStartEventContent,
46 UnstablePollAnswer, UnstablePollStartContentBlock, UnstablePollStartEventContent,
47 },
48 },
49 push_rules::PushRulesEventContent,
50 reaction::ReactionEventContent,
51 receipt::{Receipt, ReceiptEventContent, ReceiptThread, ReceiptType},
52 relation::{Annotation, BundledThread, InReplyTo, Reference, Replacement, Thread},
53 room::{
54 ImageInfo,
55 avatar::{self, RoomAvatarEventContent},
56 canonical_alias::RoomCanonicalAliasEventContent,
57 create::{PreviousRoom, RoomCreateEventContent},
58 encrypted::{EncryptedEventScheme, RoomEncryptedEventContent},
59 member::{MembershipState, RoomMemberEventContent},
60 message::{
61 FormattedBody, GalleryItemType, GalleryMessageEventContent,
62 ImageMessageEventContent, MessageType, OriginalSyncRoomMessageEvent, Relation,
63 RelationWithoutReplacement, RoomMessageEventContent,
64 RoomMessageEventContentWithoutRelation,
65 },
66 name::RoomNameEventContent,
67 power_levels::RoomPowerLevelsEventContent,
68 redaction::RoomRedactionEventContent,
69 server_acl::RoomServerAclEventContent,
70 tombstone::RoomTombstoneEventContent,
71 topic::RoomTopicEventContent,
72 },
73 rtc::{
74 decline::RtcDeclineEventContent,
75 notification::{NotificationType, RtcNotificationEventContent},
76 },
77 space::{child::SpaceChildEventContent, parent::SpaceParentEventContent},
78 sticker::StickerEventContent,
79 typing::TypingEventContent,
80 },
81 push::Ruleset,
82 room::RoomType,
83 room_version_rules::AuthorizationRules,
84 serde::Raw,
85 server_name,
86};
87use serde::Serialize;
88use serde_json::json;
89
90pub trait TimestampArg {
91 fn to_milliseconds_since_unix_epoch(self) -> MilliSecondsSinceUnixEpoch;
92}
93
94impl TimestampArg for MilliSecondsSinceUnixEpoch {
95 fn to_milliseconds_since_unix_epoch(self) -> MilliSecondsSinceUnixEpoch {
96 self
97 }
98}
99
100impl TimestampArg for u64 {
101 fn to_milliseconds_since_unix_epoch(self) -> MilliSecondsSinceUnixEpoch {
102 MilliSecondsSinceUnixEpoch(UInt::try_from(self).unwrap())
103 }
104}
105
106#[derive(Debug, Serialize)]
108struct RedactedBecause {
109 content: RoomRedactionEventContent,
111
112 event_id: OwnedEventId,
114
115 sender: OwnedUserId,
117
118 origin_server_ts: MilliSecondsSinceUnixEpoch,
121}
122
123#[derive(Debug, Serialize)]
124struct Unsigned<C: StaticEventContent> {
125 #[serde(skip_serializing_if = "Option::is_none")]
126 prev_content: Option<C>,
127
128 #[serde(skip_serializing_if = "Option::is_none")]
129 transaction_id: Option<OwnedTransactionId>,
130
131 #[serde(rename = "m.relations", skip_serializing_if = "Option::is_none")]
132 relations: Option<BundledMessageLikeRelations<Raw<AnySyncTimelineEvent>>>,
133
134 #[serde(skip_serializing_if = "Option::is_none")]
135 redacted_because: Option<RedactedBecause>,
136
137 #[serde(skip_serializing_if = "Option::is_none")]
138 age: Option<Int>,
139}
140
141impl<C: StaticEventContent> Default for Unsigned<C> {
143 fn default() -> Self {
144 Self {
145 prev_content: None,
146 transaction_id: None,
147 relations: None,
148 redacted_because: None,
149 age: None,
150 }
151 }
152}
153
154#[derive(Debug)]
155pub struct EventBuilder<C: StaticEventContent<IsPrefix = False>> {
156 sender: Option<OwnedUserId>,
157 is_ephemeral: bool,
160 is_global: bool,
163 room: Option<OwnedRoomId>,
164 event_id: Option<OwnedEventId>,
165 no_event_id: bool,
167 redacts: Option<OwnedEventId>,
168 content: C,
169 server_ts: MilliSecondsSinceUnixEpoch,
170 unsigned: Option<Unsigned<C>>,
171 state_key: Option<String>,
172}
173
174impl<E: StaticEventContent<IsPrefix = False>> EventBuilder<E> {
175 pub fn room(mut self, room_id: &RoomId) -> Self {
176 self.room = Some(room_id.to_owned());
177 self
178 }
179
180 pub fn sender(mut self, sender: &UserId) -> Self {
181 self.sender = Some(sender.to_owned());
182 self
183 }
184
185 pub fn event_id(mut self, event_id: &EventId) -> Self {
186 self.event_id = Some(event_id.to_owned());
187 self.no_event_id = false;
188 self
189 }
190
191 pub fn no_event_id(mut self) -> Self {
192 self.event_id = None;
193 self.no_event_id = true;
194 self
195 }
196
197 pub fn server_ts(mut self, ts: impl TimestampArg) -> Self {
198 self.server_ts = ts.to_milliseconds_since_unix_epoch();
199 self
200 }
201
202 pub fn unsigned_transaction_id(mut self, transaction_id: &TransactionId) -> Self {
203 self.unsigned.get_or_insert_with(Default::default).transaction_id =
204 Some(transaction_id.to_owned());
205 self
206 }
207
208 pub fn age(mut self, age: impl Into<Int>) -> Self {
210 self.unsigned.get_or_insert_with(Default::default).age = Some(age.into());
211 self
212 }
213
214 pub fn with_bundled_thread_summary(
217 mut self,
218 latest_event: Raw<AnySyncMessageLikeEvent>,
219 count: usize,
220 current_user_participated: bool,
221 ) -> Self {
222 let relations = self
223 .unsigned
224 .get_or_insert_with(Default::default)
225 .relations
226 .get_or_insert_with(BundledMessageLikeRelations::new);
227 relations.thread = Some(Box::new(BundledThread::new(
228 latest_event,
229 UInt::try_from(count).unwrap(),
230 current_user_participated,
231 )));
232 self
233 }
234
235 pub fn with_bundled_edit(mut self, replacement: impl Into<Raw<AnySyncTimelineEvent>>) -> Self {
237 let relations = self
238 .unsigned
239 .get_or_insert_with(Default::default)
240 .relations
241 .get_or_insert_with(BundledMessageLikeRelations::new);
242 relations.replace = Some(Box::new(replacement.into()));
243 self
244 }
245
246 pub fn state_key(mut self, state_key: impl Into<String>) -> Self {
251 self.state_key = Some(state_key.into());
252 self
253 }
254}
255
256impl<E> EventBuilder<E>
257where
258 E: StaticEventContent<IsPrefix = False> + Serialize,
259{
260 #[inline(always)]
261 fn construct_json(self, requires_room: bool) -> serde_json::Value {
262 let sender = self
265 .sender
266 .or_else(|| Some(self.unsigned.as_ref()?.redacted_because.as_ref()?.sender.clone()));
267
268 let mut json = json!({
269 "type": E::TYPE,
270 "content": self.content,
271 "origin_server_ts": self.server_ts,
272 });
273
274 let map = json.as_object_mut().unwrap();
275
276 if let Some(sender) = sender {
277 if !self.is_ephemeral && !self.is_global {
278 map.insert("sender".to_owned(), json!(sender));
279 }
280 } else {
281 assert!(
282 self.is_ephemeral || self.is_global,
283 "the sender must be known when building the JSON for a non read-receipt or global event"
284 );
285 }
286
287 let event_id = self
288 .event_id
289 .or_else(|| {
290 self.room.as_ref().map(|room_id| EventId::new(room_id.server_name().unwrap()))
291 })
292 .or_else(|| (!self.no_event_id).then(|| EventId::new(server_name!("dummy.org"))));
293 if let Some(event_id) = event_id {
294 map.insert("event_id".to_owned(), json!(event_id));
295 }
296
297 if requires_room && !self.is_ephemeral && !self.is_global {
298 let room_id = self.room.expect("TimelineEvent requires a room id");
299 map.insert("room_id".to_owned(), json!(room_id));
300 }
301
302 if let Some(redacts) = self.redacts {
303 map.insert("redacts".to_owned(), json!(redacts));
304 }
305
306 if let Some(unsigned) = self.unsigned {
307 map.insert("unsigned".to_owned(), json!(unsigned));
308 }
309
310 if let Some(state_key) = self.state_key {
311 map.insert("state_key".to_owned(), json!(state_key));
312 }
313
314 json
315 }
316
317 pub fn into_raw<T>(self) -> Raw<T> {
323 Raw::new(&self.construct_json(true)).unwrap().cast_unchecked()
324 }
325
326 pub fn into_raw_timeline(self) -> Raw<AnyTimelineEvent> {
327 self.into_raw()
328 }
329
330 pub fn into_any_sync_message_like_event(self) -> AnySyncMessageLikeEvent {
331 self.into_raw().deserialize().expect("expected message like event")
332 }
333
334 pub fn into_original_sync_room_message_event(self) -> OriginalSyncRoomMessageEvent {
335 self.into_raw().deserialize().expect("expected original sync room message event")
336 }
337
338 pub fn into_raw_sync(self) -> Raw<AnySyncTimelineEvent> {
339 Raw::new(&self.construct_json(false)).unwrap().cast_unchecked()
340 }
341
342 pub fn into_event(self) -> TimelineEvent {
343 TimelineEvent::from_plaintext(self.into_raw_sync())
344 }
345}
346
347impl EventBuilder<RoomEncryptedEventContent> {
348 pub fn into_utd_sync_timeline_event(self) -> TimelineEvent {
351 let session_id = as_variant!(&self.content.scheme, EncryptedEventScheme::MegolmV1AesSha2)
352 .map(|content| content.session_id.clone());
353
354 TimelineEvent::from_utd(
355 self.into(),
356 UnableToDecryptInfo {
357 session_id,
358 reason: UnableToDecryptReason::MissingMegolmSession { withheld_code: None },
359 },
360 )
361 }
362}
363
364impl EventBuilder<RoomMessageEventContent> {
365 pub fn reply_to(mut self, event_id: &EventId) -> Self {
367 self.content.relates_to =
368 Some(Relation::Reply { in_reply_to: InReplyTo::new(event_id.to_owned()) });
369 self
370 }
371
372 pub fn in_thread(mut self, root: &EventId, latest_thread_event: &EventId) -> Self {
375 self.content.relates_to =
376 Some(Relation::Thread(Thread::plain(root.to_owned(), latest_thread_event.to_owned())));
377 self
378 }
379
380 pub fn in_thread_reply(mut self, root: &EventId, replied_to: &EventId) -> Self {
383 self.content.relates_to =
384 Some(Relation::Thread(Thread::reply(root.to_owned(), replied_to.to_owned())));
385 self
386 }
387
388 pub fn mentions(mut self, mentions: Mentions) -> Self {
390 self.content.mentions = Some(mentions);
391 self
392 }
393
394 pub fn edit(
397 mut self,
398 edited_event_id: &EventId,
399 new_content: RoomMessageEventContentWithoutRelation,
400 ) -> Self {
401 self.content.relates_to =
402 Some(Relation::Replacement(Replacement::new(edited_event_id.to_owned(), new_content)));
403 self
404 }
405
406 pub fn caption(
410 mut self,
411 caption: Option<String>,
412 formatted_caption: Option<FormattedBody>,
413 ) -> Self {
414 match &mut self.content.msgtype {
415 MessageType::Image(image) => {
416 let filename = image.filename().to_owned();
417 if let Some(caption) = caption {
418 image.body = caption;
419 image.filename = Some(filename);
420 } else {
421 image.body = filename;
422 image.filename = None;
423 }
424 image.formatted = formatted_caption;
425 }
426
427 MessageType::Audio(_) | MessageType::Video(_) | MessageType::File(_) => {
428 unimplemented!();
429 }
430
431 _ => panic!("unexpected event type for a caption"),
432 }
433
434 self
435 }
436}
437
438impl EventBuilder<UnstablePollStartEventContent> {
439 pub fn reply_to(mut self, event_id: &EventId) -> Self {
441 if let UnstablePollStartEventContent::New(content) = &mut self.content {
442 content.relates_to = Some(RelationWithoutReplacement::Reply {
443 in_reply_to: InReplyTo::new(event_id.to_owned()),
444 });
445 }
446 self
447 }
448
449 pub fn in_thread(mut self, root: &EventId, reply_to_event_id: &EventId) -> Self {
452 let thread = Thread::reply(root.to_owned(), reply_to_event_id.to_owned());
453
454 if let UnstablePollStartEventContent::New(content) = &mut self.content {
455 content.relates_to = Some(RelationWithoutReplacement::Thread(thread));
456 }
457 self
458 }
459}
460
461impl EventBuilder<RoomCreateEventContent> {
462 pub fn predecessor(mut self, room_id: &RoomId) -> Self {
464 self.content.predecessor = Some(PreviousRoom::new(room_id.to_owned()));
465 self
466 }
467
468 pub fn no_predecessor(mut self) -> Self {
470 self.content.predecessor = None;
471 self
472 }
473
474 pub fn with_space_type(mut self) -> Self {
476 self.content.room_type = Some(RoomType::Space);
477 self
478 }
479}
480
481impl EventBuilder<StickerEventContent> {
482 pub fn reply_thread(mut self, root: &EventId, reply_to_event: &EventId) -> Self {
484 self.content.relates_to =
485 Some(Relation::Thread(Thread::reply(root.to_owned(), reply_to_event.to_owned())));
486 self
487 }
488}
489
490impl<E: StaticEventContent<IsPrefix = False>> From<EventBuilder<E>> for Raw<AnySyncTimelineEvent>
491where
492 E: Serialize,
493{
494 fn from(val: EventBuilder<E>) -> Self {
495 val.into_raw_sync()
496 }
497}
498
499impl<E: StaticEventContent<IsPrefix = False>> From<EventBuilder<E>> for Raw<AnyTimelineEvent>
500where
501 E: Serialize,
502{
503 fn from(val: EventBuilder<E>) -> Self {
504 val.into_raw_timeline()
505 }
506}
507
508impl<E: StaticEventContent<IsPrefix = False>> From<EventBuilder<E>>
509 for Raw<AnyGlobalAccountDataEvent>
510where
511 E: Serialize,
512{
513 fn from(val: EventBuilder<E>) -> Self {
514 val.into_raw()
515 }
516}
517
518impl<E: StaticEventContent<IsPrefix = False>> From<EventBuilder<E>> for TimelineEvent
519where
520 E: Serialize,
521{
522 fn from(val: EventBuilder<E>) -> Self {
523 val.into_event()
524 }
525}
526
527impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
528 for Raw<AnySyncStateEvent>
529{
530 fn from(val: EventBuilder<E>) -> Self {
531 Raw::new(&val.construct_json(false)).unwrap().cast_unchecked()
532 }
533}
534
535impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
536 for Raw<AnyStateEvent>
537{
538 fn from(val: EventBuilder<E>) -> Self {
539 Raw::new(&val.construct_json(true)).unwrap().cast_unchecked()
540 }
541}
542
543#[derive(Debug, Default)]
544pub struct EventFactory {
545 next_ts: AtomicU64,
546 sender: Option<OwnedUserId>,
547 room: Option<OwnedRoomId>,
548}
549
550impl EventFactory {
551 pub fn new() -> Self {
552 Self { next_ts: AtomicU64::new(0), sender: None, room: None }
553 }
554
555 pub fn room(mut self, room_id: &RoomId) -> Self {
556 self.room = Some(room_id.to_owned());
557 self
558 }
559
560 pub fn sender(mut self, sender: &UserId) -> Self {
561 self.sender = Some(sender.to_owned());
562 self
563 }
564
565 pub fn server_ts(self, ts: u64) -> Self {
566 self.next_ts.store(ts, SeqCst);
567 self
568 }
569
570 fn next_server_ts(&self) -> MilliSecondsSinceUnixEpoch {
571 MilliSecondsSinceUnixEpoch(
572 self.next_ts
573 .fetch_add(1, SeqCst)
574 .try_into()
575 .expect("server timestamp should fit in js_int::UInt"),
576 )
577 }
578
579 pub fn event<E: StaticEventContent<IsPrefix = False>>(&self, content: E) -> EventBuilder<E> {
581 EventBuilder {
582 sender: self.sender.clone(),
583 is_ephemeral: false,
584 is_global: false,
585 room: self.room.clone(),
586 server_ts: self.next_server_ts(),
587 event_id: None,
588 no_event_id: false,
589 redacts: None,
590 content,
591 unsigned: None,
592 state_key: None,
593 }
594 }
595
596 pub fn text_msg(&self, content: impl Into<String>) -> EventBuilder<RoomMessageEventContent> {
598 self.event(RoomMessageEventContent::text_plain(content.into()))
599 }
600
601 pub fn emote(&self, content: impl Into<String>) -> EventBuilder<RoomMessageEventContent> {
603 self.event(RoomMessageEventContent::emote_plain(content.into()))
604 }
605
606 pub fn member(&self, member: &UserId) -> EventBuilder<RoomMemberEventContent> {
636 let mut event = self.event(RoomMemberEventContent::new(MembershipState::Join));
637
638 if self.sender.is_some() {
639 event.sender = self.sender.clone();
640 } else {
641 event.sender = Some(member.to_owned());
642 }
643
644 event.state_key = Some(member.to_string());
645
646 event
647 }
648
649 pub fn room_tombstone(
651 &self,
652 body: impl Into<String>,
653 replacement: &RoomId,
654 ) -> EventBuilder<RoomTombstoneEventContent> {
655 let mut event =
656 self.event(RoomTombstoneEventContent::new(body.into(), replacement.to_owned()));
657 event.state_key = Some("".to_owned());
658 event
659 }
660
661 pub fn room_topic(&self, topic: impl Into<String>) -> EventBuilder<RoomTopicEventContent> {
663 let mut event = self.event(RoomTopicEventContent::new(topic.into()));
664 event.state_key = Some("".to_owned());
666 event
667 }
668
669 pub fn room_name(&self, name: impl Into<String>) -> EventBuilder<RoomNameEventContent> {
671 let mut event = self.event(RoomNameEventContent::new(name.into()));
672 event.state_key = Some("".to_owned());
674 event
675 }
676
677 pub fn room_avatar(&self) -> EventBuilder<RoomAvatarEventContent> {
679 let mut event = self.event(RoomAvatarEventContent::new());
680 event.state_key = Some("".to_owned());
682 event
683 }
684
685 pub fn member_hints(
706 &self,
707 service_members: BTreeSet<OwnedUserId>,
708 ) -> EventBuilder<MemberHintsEventContent> {
709 self.event(MemberHintsEventContent::new(service_members)).state_key("")
711 }
712
713 pub fn text_html(
715 &self,
716 plain: impl Into<String>,
717 html: impl Into<String>,
718 ) -> EventBuilder<RoomMessageEventContent> {
719 self.event(RoomMessageEventContent::text_html(plain, html))
720 }
721
722 pub fn notice(&self, content: impl Into<String>) -> EventBuilder<RoomMessageEventContent> {
724 self.event(RoomMessageEventContent::notice_plain(content))
725 }
726
727 pub fn reaction(
729 &self,
730 event_id: &EventId,
731 annotation: impl Into<String>,
732 ) -> EventBuilder<ReactionEventContent> {
733 self.event(ReactionEventContent::new(Annotation::new(
734 event_id.to_owned(),
735 annotation.into(),
736 )))
737 }
738
739 pub fn redaction(&self, event_id: &EventId) -> EventBuilder<RoomRedactionEventContent> {
744 let mut builder = self.event(RoomRedactionEventContent::new_v11(event_id.to_owned()));
745 builder.redacts = Some(event_id.to_owned());
746 builder
747 }
748
749 pub fn redacted<T: StaticEventContent<IsPrefix = False> + RedactedMessageLikeEventContent>(
752 &self,
753 redacter: &UserId,
754 content: T,
755 ) -> EventBuilder<T> {
756 let mut builder = self.event(content);
757
758 let redacted_because = RedactedBecause {
759 content: RoomRedactionEventContent::default(),
760 event_id: EventId::new(server_name!("dummy.server")),
761 sender: redacter.to_owned(),
762 origin_server_ts: self.next_server_ts(),
763 };
764 builder.unsigned.get_or_insert_with(Default::default).redacted_because =
765 Some(redacted_because);
766
767 builder
768 }
769
770 pub fn redacted_state<T: StaticEventContent<IsPrefix = False> + RedactedStateEventContent>(
773 &self,
774 redacter: &UserId,
775 state_key: impl Into<String>,
776 content: T,
777 ) -> EventBuilder<T> {
778 let mut builder = self.event(content);
779
780 let redacted_because = RedactedBecause {
781 content: RoomRedactionEventContent::default(),
782 event_id: EventId::new(server_name!("dummy.server")),
783 sender: redacter.to_owned(),
784 origin_server_ts: self.next_server_ts(),
785 };
786 builder.unsigned.get_or_insert_with(Default::default).redacted_because =
787 Some(redacted_because);
788 builder.state_key = Some(state_key.into());
789
790 builder
791 }
792
793 pub fn poll_start(
796 &self,
797 content: impl Into<String>,
798 poll_question: impl Into<String>,
799 answers: Vec<impl Into<String>>,
800 ) -> EventBuilder<UnstablePollStartEventContent> {
801 let answers: Vec<UnstablePollAnswer> = answers
803 .into_iter()
804 .enumerate()
805 .map(|(idx, answer)| UnstablePollAnswer::new(idx.to_string(), answer))
806 .collect();
807 let poll_answers = answers.try_into().unwrap();
808 let poll_start_content =
809 UnstablePollStartEventContent::New(NewUnstablePollStartEventContent::plain_text(
810 content,
811 UnstablePollStartContentBlock::new(poll_question, poll_answers),
812 ));
813 self.event(poll_start_content)
814 }
815
816 pub fn poll_edit(
818 &self,
819 edited_event_id: &EventId,
820 poll_question: impl Into<String>,
821 answers: Vec<impl Into<String>>,
822 ) -> EventBuilder<ReplacementUnstablePollStartEventContent> {
823 let answers: Vec<UnstablePollAnswer> = answers
825 .into_iter()
826 .enumerate()
827 .map(|(idx, answer)| UnstablePollAnswer::new(idx.to_string(), answer))
828 .collect();
829 let poll_answers = answers.try_into().unwrap();
830 let poll_start_content_block =
831 UnstablePollStartContentBlock::new(poll_question, poll_answers);
832 self.event(ReplacementUnstablePollStartEventContent::new(
833 poll_start_content_block,
834 edited_event_id.to_owned(),
835 ))
836 }
837
838 pub fn poll_response(
841 &self,
842 answers: Vec<impl Into<String>>,
843 poll_start_id: &EventId,
844 ) -> EventBuilder<UnstablePollResponseEventContent> {
845 self.event(UnstablePollResponseEventContent::new(
846 answers.into_iter().map(Into::into).collect(),
847 poll_start_id.to_owned(),
848 ))
849 }
850
851 pub fn poll_end(
854 &self,
855 content: impl Into<String>,
856 poll_start_id: &EventId,
857 ) -> EventBuilder<UnstablePollEndEventContent> {
858 self.event(UnstablePollEndEventContent::new(content.into(), poll_start_id.to_owned()))
859 }
860
861 pub fn image(
864 &self,
865 filename: String,
866 url: OwnedMxcUri,
867 ) -> EventBuilder<RoomMessageEventContent> {
868 let image_event_content = ImageMessageEventContent::plain(filename, url);
869 self.event(RoomMessageEventContent::new(MessageType::Image(image_event_content)))
870 }
871
872 pub fn gallery(
875 &self,
876 body: String,
877 filename: String,
878 url: OwnedMxcUri,
879 ) -> EventBuilder<RoomMessageEventContent> {
880 let gallery_event_content = GalleryMessageEventContent::new(
881 body,
882 None,
883 vec![GalleryItemType::Image(ImageMessageEventContent::plain(filename, url))],
884 );
885 self.event(RoomMessageEventContent::new(MessageType::Gallery(gallery_event_content)))
886 }
887
888 pub fn typing(&self, user_ids: Vec<&UserId>) -> EventBuilder<TypingEventContent> {
890 let mut builder = self
891 .event(TypingEventContent::new(user_ids.into_iter().map(ToOwned::to_owned).collect()));
892 builder.is_ephemeral = true;
893 builder
894 }
895
896 pub fn read_receipts(&self) -> ReadReceiptBuilder<'_> {
898 ReadReceiptBuilder { factory: self, content: ReceiptEventContent(Default::default()) }
899 }
900
901 pub fn create(
903 &self,
904 creator_user_id: &UserId,
905 room_version: RoomVersionId,
906 ) -> EventBuilder<RoomCreateEventContent> {
907 let mut event = self.event(RoomCreateEventContent::new_v1(creator_user_id.to_owned()));
908 event.content.room_version = room_version;
909
910 if self.sender.is_some() {
911 event.sender = self.sender.clone();
912 } else {
913 event.sender = Some(creator_user_id.to_owned());
914 }
915
916 event.state_key = Some("".to_owned());
917
918 event
919 }
920
921 pub fn power_levels(
923 &self,
924 map: &mut BTreeMap<OwnedUserId, Int>,
925 ) -> EventBuilder<RoomPowerLevelsEventContent> {
926 let mut event = RoomPowerLevelsEventContent::new(&AuthorizationRules::V1);
927 event.users.append(map);
928 self.event(event)
929 }
930
931 pub fn server_acl(
933 &self,
934 allow_ip_literals: bool,
935 allow: Vec<String>,
936 deny: Vec<String>,
937 ) -> EventBuilder<RoomServerAclEventContent> {
938 self.event(RoomServerAclEventContent::new(allow_ip_literals, allow, deny))
939 }
940
941 pub fn canonical_alias(
943 &self,
944 alias: Option<OwnedRoomAliasId>,
945 alt_aliases: Vec<OwnedRoomAliasId>,
946 ) -> EventBuilder<RoomCanonicalAliasEventContent> {
947 let mut event = RoomCanonicalAliasEventContent::new();
948 event.alias = alias;
949 event.alt_aliases = alt_aliases;
950 self.event(event)
951 }
952
953 pub fn beacon(
979 &self,
980 beacon_info_event_id: OwnedEventId,
981 latitude: f64,
982 longitude: f64,
983 uncertainty: u32,
984 ts: Option<MilliSecondsSinceUnixEpoch>,
985 ) -> EventBuilder<BeaconEventContent> {
986 let geo_uri = format!("geo:{latitude},{longitude};u={uncertainty}");
987 self.event(BeaconEventContent::new(beacon_info_event_id, geo_uri, ts))
988 }
989
990 pub fn sticker(
992 &self,
993 body: impl Into<String>,
994 info: ImageInfo,
995 url: OwnedMxcUri,
996 ) -> EventBuilder<StickerEventContent> {
997 self.event(StickerEventContent::new(body.into(), info, url))
998 }
999
1000 pub fn call_invite(
1002 &self,
1003 call_id: OwnedVoipId,
1004 lifetime: UInt,
1005 offer: SessionDescription,
1006 version: VoipVersionId,
1007 ) -> EventBuilder<CallInviteEventContent> {
1008 self.event(CallInviteEventContent::new(call_id, lifetime, offer, version))
1009 }
1010
1011 pub fn rtc_notification(
1013 &self,
1014 notification_type: NotificationType,
1015 ) -> EventBuilder<RtcNotificationEventContent> {
1016 self.event(RtcNotificationEventContent::new(
1017 MilliSecondsSinceUnixEpoch::now(),
1018 Duration::new(30, 0),
1019 notification_type,
1020 ))
1021 }
1022
1023 pub fn call_decline(
1025 &self,
1026 notification_event_id: &EventId,
1027 ) -> EventBuilder<RtcDeclineEventContent> {
1028 self.event(RtcDeclineEventContent::new(notification_event_id))
1029 }
1030
1031 pub fn direct(&self) -> EventBuilder<DirectEventContent> {
1033 let mut builder = self.event(DirectEventContent::default());
1034 builder.is_global = true;
1035 builder
1036 }
1037
1038 pub fn ignored_user_list(
1040 &self,
1041 users: impl IntoIterator<Item = OwnedUserId>,
1042 ) -> EventBuilder<IgnoredUserListEventContent> {
1043 let mut builder = self.event(IgnoredUserListEventContent::users(users));
1044 builder.is_global = true;
1045 builder
1046 }
1047
1048 pub fn push_rules(&self, rules: Ruleset) -> EventBuilder<PushRulesEventContent> {
1050 let mut builder = self.event(PushRulesEventContent::new(rules));
1051 builder.is_global = true;
1052 builder
1053 }
1054
1055 pub fn space_child(
1057 &self,
1058 parent: OwnedRoomId,
1059 child: OwnedRoomId,
1060 ) -> EventBuilder<SpaceChildEventContent> {
1061 let mut event = self.event(SpaceChildEventContent::new(vec![]));
1062 event.room = Some(parent);
1063 event.state_key = Some(child.to_string());
1064 event
1065 }
1066
1067 pub fn space_parent(
1069 &self,
1070 parent: OwnedRoomId,
1071 child: OwnedRoomId,
1072 ) -> EventBuilder<SpaceParentEventContent> {
1073 let mut event = self.event(SpaceParentEventContent::new(vec![]));
1074 event.state_key = Some(parent.to_string());
1075 event.room = Some(child);
1076 event
1077 }
1078
1079 pub fn set_next_ts(&self, value: u64) {
1083 self.next_ts.store(value, SeqCst);
1084 }
1085
1086 pub fn global_account_data<C>(&self, content: C) -> EventBuilder<C>
1088 where
1089 C: GlobalAccountDataEventContent + StaticEventContent<IsPrefix = False>,
1090 {
1091 let mut event = self.event(content);
1092 event.is_global = true;
1093 event
1094 }
1095}
1096
1097impl EventBuilder<DirectEventContent> {
1098 pub fn add_user(mut self, user_id: OwnedDirectUserIdentifier, room_id: &RoomId) -> Self {
1100 self.content.0.entry(user_id).or_default().push(room_id.to_owned());
1101 self
1102 }
1103}
1104
1105impl EventBuilder<RoomMemberEventContent> {
1106 pub fn membership(mut self, state: MembershipState) -> Self {
1111 self.content.membership = state;
1112 self
1113 }
1114
1115 pub fn invited(mut self, invited_user: &UserId) -> Self {
1118 assert_ne!(
1119 self.sender.as_deref().unwrap(),
1120 invited_user,
1121 "invited user and sender can't be the same person"
1122 );
1123 self.content.membership = MembershipState::Invite;
1124 self.state_key = Some(invited_user.to_string());
1125 self
1126 }
1127
1128 pub fn kicked(mut self, kicked_user: &UserId) -> Self {
1131 assert_ne!(
1132 self.sender.as_deref().unwrap(),
1133 kicked_user,
1134 "kicked user and sender can't be the same person, otherwise it's just a Leave"
1135 );
1136 self.content.membership = MembershipState::Leave;
1137 self.state_key = Some(kicked_user.to_string());
1138 self
1139 }
1140
1141 pub fn banned(mut self, banned_user: &UserId) -> Self {
1144 assert_ne!(
1145 self.sender.as_deref().unwrap(),
1146 banned_user,
1147 "a user can't ban itself" );
1149 self.content.membership = MembershipState::Ban;
1150 self.state_key = Some(banned_user.to_string());
1151 self
1152 }
1153
1154 pub fn display_name(mut self, display_name: impl Into<String>) -> Self {
1156 self.content.displayname = Some(display_name.into());
1157 self
1158 }
1159
1160 pub fn avatar_url(mut self, url: &MxcUri) -> Self {
1162 self.content.avatar_url = Some(url.to_owned());
1163 self
1164 }
1165
1166 pub fn reason(mut self, reason: impl Into<String>) -> Self {
1168 self.content.reason = Some(reason.into());
1169 self
1170 }
1171
1172 pub fn previous(mut self, previous: impl Into<PreviousMembership>) -> Self {
1174 let previous = previous.into();
1175
1176 let mut prev_content = RoomMemberEventContent::new(previous.state);
1177 if let Some(avatar_url) = previous.avatar_url {
1178 prev_content.avatar_url = Some(avatar_url);
1179 }
1180 if let Some(display_name) = previous.display_name {
1181 prev_content.displayname = Some(display_name);
1182 }
1183
1184 self.unsigned.get_or_insert_with(Default::default).prev_content = Some(prev_content);
1185 self
1186 }
1187}
1188
1189impl EventBuilder<RoomAvatarEventContent> {
1190 pub fn url(mut self, url: &MxcUri) -> Self {
1192 self.content.url = Some(url.to_owned());
1193 self
1194 }
1195
1196 pub fn info(mut self, image: avatar::ImageInfo) -> Self {
1198 self.content.info = Some(Box::new(image));
1199 self
1200 }
1201}
1202
1203impl EventBuilder<RtcNotificationEventContent> {
1204 pub fn mentions(mut self, users: impl IntoIterator<Item = OwnedUserId>) -> Self {
1205 self.content.mentions = Some(Mentions::with_user_ids(users));
1206 self
1207 }
1208
1209 pub fn relates_to_membership_state_event(mut self, event_id: OwnedEventId) -> Self {
1210 self.content.relates_to = Some(Reference::new(event_id));
1211 self
1212 }
1213
1214 pub fn lifetime(mut self, time_in_seconds: u64) -> Self {
1215 self.content.lifetime = Duration::from_secs(time_in_seconds);
1216 self
1217 }
1218}
1219
1220pub struct ReadReceiptBuilder<'a> {
1221 factory: &'a EventFactory,
1222 content: ReceiptEventContent,
1223}
1224
1225impl ReadReceiptBuilder<'_> {
1226 pub fn add(
1228 self,
1229 event_id: &EventId,
1230 user_id: &UserId,
1231 tyype: ReceiptType,
1232 thread: ReceiptThread,
1233 ) -> Self {
1234 let ts = self.factory.next_server_ts();
1235 self.add_with_timestamp(event_id, user_id, tyype, thread, Some(ts))
1236 }
1237
1238 pub fn add_with_timestamp(
1240 mut self,
1241 event_id: &EventId,
1242 user_id: &UserId,
1243 tyype: ReceiptType,
1244 thread: ReceiptThread,
1245 ts: Option<MilliSecondsSinceUnixEpoch>,
1246 ) -> Self {
1247 let by_event = self.content.0.entry(event_id.to_owned()).or_default();
1248 let by_type = by_event.entry(tyype).or_default();
1249
1250 let mut receipt = Receipt::default();
1251 if let Some(ts) = ts {
1252 receipt.ts = Some(ts);
1253 }
1254 receipt.thread = thread;
1255
1256 by_type.insert(user_id.to_owned(), receipt);
1257 self
1258 }
1259
1260 pub fn into_content(self) -> ReceiptEventContent {
1262 self.content
1263 }
1264
1265 pub fn into_event(self) -> EventBuilder<ReceiptEventContent> {
1267 let mut builder = self.factory.event(self.into_content());
1268 builder.is_ephemeral = true;
1269 builder
1270 }
1271}
1272
1273pub struct PreviousMembership {
1274 state: MembershipState,
1275 avatar_url: Option<OwnedMxcUri>,
1276 display_name: Option<String>,
1277}
1278
1279impl PreviousMembership {
1280 pub fn new(state: MembershipState) -> Self {
1281 Self { state, avatar_url: None, display_name: None }
1282 }
1283
1284 pub fn avatar_url(mut self, url: &MxcUri) -> Self {
1285 self.avatar_url = Some(url.to_owned());
1286 self
1287 }
1288
1289 pub fn display_name(mut self, name: impl Into<String>) -> Self {
1290 self.display_name = Some(name.into());
1291 self
1292 }
1293}
1294
1295impl From<MembershipState> for PreviousMembership {
1296 fn from(state: MembershipState) -> Self {
1297 Self::new(state)
1298 }
1299}