1#![allow(missing_docs)]
16
17use std::{
18 collections::{BTreeMap, BTreeSet},
19 sync::atomic::{AtomicU64, Ordering::SeqCst},
20};
21
22use as_variant::as_variant;
23use matrix_sdk_common::deserialized_responses::{
24 TimelineEvent, UnableToDecryptInfo, UnableToDecryptReason,
25};
26use ruma::{
27 events::{
28 beacon::BeaconEventContent,
29 call::{
30 invite::CallInviteEventContent,
31 notify::{ApplicationType, CallNotifyEventContent, NotifyType},
32 SessionDescription,
33 },
34 member_hints::MemberHintsEventContent,
35 poll::{
36 unstable_end::UnstablePollEndEventContent,
37 unstable_response::UnstablePollResponseEventContent,
38 unstable_start::{
39 NewUnstablePollStartEventContent, ReplacementUnstablePollStartEventContent,
40 UnstablePollAnswer, UnstablePollStartContentBlock, UnstablePollStartEventContent,
41 },
42 },
43 reaction::ReactionEventContent,
44 receipt::{Receipt, ReceiptEventContent, ReceiptThread, ReceiptType},
45 relation::{Annotation, BundledThread, InReplyTo, Replacement, Thread},
46 room::{
47 avatar::{self, RoomAvatarEventContent},
48 canonical_alias::RoomCanonicalAliasEventContent,
49 create::{PreviousRoom, RoomCreateEventContent},
50 encrypted::{EncryptedEventScheme, RoomEncryptedEventContent},
51 member::{MembershipState, RoomMemberEventContent},
52 message::{
53 FormattedBody, GalleryItemType, GalleryMessageEventContent,
54 ImageMessageEventContent, MessageType, Relation, RelationWithoutReplacement,
55 RoomMessageEventContent, RoomMessageEventContentWithoutRelation,
56 },
57 name::RoomNameEventContent,
58 power_levels::RoomPowerLevelsEventContent,
59 redaction::RoomRedactionEventContent,
60 server_acl::RoomServerAclEventContent,
61 tombstone::RoomTombstoneEventContent,
62 topic::RoomTopicEventContent,
63 ImageInfo,
64 },
65 sticker::StickerEventContent,
66 typing::TypingEventContent,
67 AnyMessageLikeEvent, AnyStateEvent, AnySyncStateEvent, AnySyncTimelineEvent,
68 AnyTimelineEvent, BundledMessageLikeRelations, Mentions, RedactedMessageLikeEventContent,
69 RedactedStateEventContent, StateEventContent, StaticEventContent,
70 },
71 serde::Raw,
72 server_name, EventId, Int, MilliSecondsSinceUnixEpoch, MxcUri, OwnedEventId, OwnedMxcUri,
73 OwnedRoomAliasId, OwnedRoomId, OwnedTransactionId, OwnedUserId, OwnedVoipId, RoomId,
74 RoomVersionId, TransactionId, UInt, UserId, VoipVersionId,
75};
76use serde::Serialize;
77use serde_json::json;
78
79pub trait TimestampArg {
80 fn to_milliseconds_since_unix_epoch(self) -> MilliSecondsSinceUnixEpoch;
81}
82
83impl TimestampArg for MilliSecondsSinceUnixEpoch {
84 fn to_milliseconds_since_unix_epoch(self) -> MilliSecondsSinceUnixEpoch {
85 self
86 }
87}
88
89impl TimestampArg for u64 {
90 fn to_milliseconds_since_unix_epoch(self) -> MilliSecondsSinceUnixEpoch {
91 MilliSecondsSinceUnixEpoch(UInt::try_from(self).unwrap())
92 }
93}
94
95#[derive(Debug, Serialize)]
97struct RedactedBecause {
98 content: RoomRedactionEventContent,
100
101 event_id: OwnedEventId,
103
104 sender: OwnedUserId,
106
107 origin_server_ts: MilliSecondsSinceUnixEpoch,
110}
111
112#[derive(Debug, Serialize)]
113struct Unsigned<C: StaticEventContent> {
114 #[serde(skip_serializing_if = "Option::is_none")]
115 prev_content: Option<C>,
116
117 #[serde(skip_serializing_if = "Option::is_none")]
118 transaction_id: Option<OwnedTransactionId>,
119
120 #[serde(rename = "m.relations", skip_serializing_if = "Option::is_none")]
121 relations: Option<BundledMessageLikeRelations<Raw<AnySyncTimelineEvent>>>,
122
123 #[serde(skip_serializing_if = "Option::is_none")]
124 redacted_because: Option<RedactedBecause>,
125
126 #[serde(skip_serializing_if = "Option::is_none")]
127 age: Option<Int>,
128}
129
130impl<C: StaticEventContent> Default for Unsigned<C> {
132 fn default() -> Self {
133 Self {
134 prev_content: None,
135 transaction_id: None,
136 relations: None,
137 redacted_because: None,
138 age: None,
139 }
140 }
141}
142
143#[derive(Debug)]
144pub struct EventBuilder<C: StaticEventContent> {
145 sender: Option<OwnedUserId>,
146 is_ephemeral: bool,
149 room: Option<OwnedRoomId>,
150 event_id: Option<OwnedEventId>,
151 no_event_id: bool,
153 redacts: Option<OwnedEventId>,
154 content: C,
155 server_ts: MilliSecondsSinceUnixEpoch,
156 unsigned: Option<Unsigned<C>>,
157 state_key: Option<String>,
158}
159
160impl<E: StaticEventContent> EventBuilder<E> {
161 pub fn room(mut self, room_id: &RoomId) -> Self {
162 self.room = Some(room_id.to_owned());
163 self
164 }
165
166 pub fn sender(mut self, sender: &UserId) -> Self {
167 self.sender = Some(sender.to_owned());
168 self
169 }
170
171 pub fn event_id(mut self, event_id: &EventId) -> Self {
172 self.event_id = Some(event_id.to_owned());
173 self.no_event_id = false;
174 self
175 }
176
177 pub fn no_event_id(mut self) -> Self {
178 self.event_id = None;
179 self.no_event_id = true;
180 self
181 }
182
183 pub fn server_ts(mut self, ts: impl TimestampArg) -> Self {
184 self.server_ts = ts.to_milliseconds_since_unix_epoch();
185 self
186 }
187
188 pub fn unsigned_transaction_id(mut self, transaction_id: &TransactionId) -> Self {
189 self.unsigned.get_or_insert_with(Default::default).transaction_id =
190 Some(transaction_id.to_owned());
191 self
192 }
193
194 pub fn age(mut self, age: impl Into<Int>) -> Self {
196 self.unsigned.get_or_insert_with(Default::default).age = Some(age.into());
197 self
198 }
199
200 pub fn with_bundled_thread_summary(
203 mut self,
204 latest_event: Raw<AnyMessageLikeEvent>,
205 count: usize,
206 current_user_participated: bool,
207 ) -> Self {
208 let relations = self
209 .unsigned
210 .get_or_insert_with(Default::default)
211 .relations
212 .get_or_insert_with(BundledMessageLikeRelations::new);
213 relations.thread = Some(Box::new(BundledThread::new(
214 latest_event,
215 UInt::try_from(count).unwrap(),
216 current_user_participated,
217 )));
218 self
219 }
220
221 pub fn with_bundled_edit(mut self, replacement: impl Into<Raw<AnySyncTimelineEvent>>) -> Self {
223 let relations = self
224 .unsigned
225 .get_or_insert_with(Default::default)
226 .relations
227 .get_or_insert_with(BundledMessageLikeRelations::new);
228 relations.replace = Some(Box::new(replacement.into()));
229 self
230 }
231
232 pub fn state_key(mut self, state_key: impl Into<String>) -> Self {
237 self.state_key = Some(state_key.into());
238 self
239 }
240
241 #[inline(always)]
242 fn construct_json(self, requires_room: bool) -> serde_json::Value {
243 let sender = self
246 .sender
247 .or_else(|| Some(self.unsigned.as_ref()?.redacted_because.as_ref()?.sender.clone()));
248
249 if sender.is_none() {
250 assert!(
251 self.is_ephemeral,
252 "the sender must be known when building the JSON for a non read-receipt event"
253 );
254 } else {
255 assert!(
256 !self.is_ephemeral,
257 "event builder set is_ephemeral, but also has a sender field"
258 );
259 }
260
261 let mut json = json!({
262 "type": E::TYPE,
263 "content": self.content,
264 "origin_server_ts": self.server_ts,
265 });
266
267 let map = json.as_object_mut().unwrap();
268
269 if let Some(sender) = sender {
270 map.insert("sender".to_owned(), json!(sender));
271 }
272
273 let event_id = self
274 .event_id
275 .or_else(|| {
276 self.room.as_ref().map(|room_id| EventId::new(room_id.server_name().unwrap()))
277 })
278 .or_else(|| (!self.no_event_id).then(|| EventId::new(server_name!("dummy.org"))));
279 if let Some(event_id) = event_id {
280 map.insert("event_id".to_owned(), json!(event_id));
281 }
282
283 if requires_room && !self.is_ephemeral {
284 let room_id = self.room.expect("TimelineEvent requires a room id");
285 map.insert("room_id".to_owned(), json!(room_id));
286 }
287
288 if let Some(redacts) = self.redacts {
289 map.insert("redacts".to_owned(), json!(redacts));
290 }
291
292 if let Some(unsigned) = self.unsigned {
293 map.insert("unsigned".to_owned(), json!(unsigned));
294 }
295
296 if let Some(state_key) = self.state_key {
297 map.insert("state_key".to_owned(), json!(state_key));
298 }
299
300 json
301 }
302
303 pub fn into_raw<T>(self) -> Raw<T> {
309 Raw::new(&self.construct_json(true)).unwrap().cast()
310 }
311
312 pub fn into_raw_timeline(self) -> Raw<AnyTimelineEvent> {
313 self.into_raw()
314 }
315
316 pub fn into_raw_sync(self) -> Raw<AnySyncTimelineEvent> {
317 Raw::new(&self.construct_json(false)).unwrap().cast()
318 }
319
320 pub fn into_event(self) -> TimelineEvent {
321 TimelineEvent::from_plaintext(self.into_raw_sync())
322 }
323}
324
325impl EventBuilder<RoomEncryptedEventContent> {
326 pub fn into_utd_sync_timeline_event(self) -> TimelineEvent {
329 let session_id = as_variant!(&self.content.scheme, EncryptedEventScheme::MegolmV1AesSha2)
330 .map(|content| content.session_id.clone());
331
332 TimelineEvent::from_utd(
333 self.into(),
334 UnableToDecryptInfo {
335 session_id,
336 reason: UnableToDecryptReason::MissingMegolmSession { withheld_code: None },
337 },
338 )
339 }
340}
341
342impl EventBuilder<RoomMessageEventContent> {
343 pub fn reply_to(mut self, event_id: &EventId) -> Self {
345 self.content.relates_to =
346 Some(Relation::Reply { in_reply_to: InReplyTo::new(event_id.to_owned()) });
347 self
348 }
349
350 pub fn in_thread(mut self, root: &EventId, latest_thread_event: &EventId) -> Self {
353 self.content.relates_to =
354 Some(Relation::Thread(Thread::plain(root.to_owned(), latest_thread_event.to_owned())));
355 self
356 }
357
358 pub fn in_thread_reply(mut self, root: &EventId, replied_to: &EventId) -> Self {
361 self.content.relates_to =
362 Some(Relation::Thread(Thread::reply(root.to_owned(), replied_to.to_owned())));
363 self
364 }
365
366 pub fn edit(
369 mut self,
370 edited_event_id: &EventId,
371 new_content: RoomMessageEventContentWithoutRelation,
372 ) -> Self {
373 self.content.relates_to =
374 Some(Relation::Replacement(Replacement::new(edited_event_id.to_owned(), new_content)));
375 self
376 }
377
378 pub fn caption(
382 mut self,
383 caption: Option<String>,
384 formatted_caption: Option<FormattedBody>,
385 ) -> Self {
386 match &mut self.content.msgtype {
387 MessageType::Image(image) => {
388 let filename = image.filename().to_owned();
389 if let Some(caption) = caption {
390 image.body = caption;
391 image.filename = Some(filename);
392 } else {
393 image.body = filename;
394 image.filename = None;
395 }
396 image.formatted = formatted_caption;
397 }
398
399 MessageType::Audio(_) | MessageType::Video(_) | MessageType::File(_) => {
400 unimplemented!();
401 }
402
403 _ => panic!("unexpected event type for a caption"),
404 }
405
406 self
407 }
408}
409
410impl EventBuilder<UnstablePollStartEventContent> {
411 pub fn reply_to(mut self, event_id: &EventId) -> Self {
413 if let UnstablePollStartEventContent::New(content) = &mut self.content {
414 content.relates_to = Some(RelationWithoutReplacement::Reply {
415 in_reply_to: InReplyTo::new(event_id.to_owned()),
416 });
417 }
418 self
419 }
420
421 pub fn in_thread(mut self, root: &EventId, reply_to_event_id: &EventId) -> Self {
424 let thread = Thread::reply(root.to_owned(), reply_to_event_id.to_owned());
425
426 if let UnstablePollStartEventContent::New(content) = &mut self.content {
427 content.relates_to = Some(RelationWithoutReplacement::Thread(thread));
428 }
429 self
430 }
431}
432
433impl EventBuilder<RoomCreateEventContent> {
434 pub fn predecessor(mut self, room_id: &RoomId, event_id: &EventId) -> Self {
436 self.content.predecessor = Some(PreviousRoom::new(room_id.to_owned(), event_id.to_owned()));
437 self
438 }
439
440 pub fn no_predecessor(mut self) -> Self {
442 self.content.predecessor = None;
443 self
444 }
445}
446
447impl EventBuilder<StickerEventContent> {
448 pub fn reply_thread(mut self, root: &EventId, reply_to_event: &EventId) -> Self {
450 self.content.relates_to =
451 Some(Relation::Thread(Thread::reply(root.to_owned(), reply_to_event.to_owned())));
452 self
453 }
454}
455
456impl<E: StaticEventContent> From<EventBuilder<E>> for Raw<AnySyncTimelineEvent> {
457 fn from(val: EventBuilder<E>) -> Self {
458 val.into_raw_sync()
459 }
460}
461
462impl<E: StaticEventContent> From<EventBuilder<E>> for Raw<AnyTimelineEvent> {
463 fn from(val: EventBuilder<E>) -> Self {
464 val.into_raw_timeline()
465 }
466}
467
468impl<E: StaticEventContent> From<EventBuilder<E>> for TimelineEvent {
469 fn from(val: EventBuilder<E>) -> Self {
470 val.into_event()
471 }
472}
473
474impl<E: StaticEventContent + StateEventContent> From<EventBuilder<E>> for Raw<AnySyncStateEvent> {
475 fn from(val: EventBuilder<E>) -> Self {
476 Raw::new(&val.construct_json(false)).unwrap().cast()
477 }
478}
479
480impl<E: StaticEventContent + StateEventContent> From<EventBuilder<E>> for Raw<AnyStateEvent> {
481 fn from(val: EventBuilder<E>) -> Self {
482 Raw::new(&val.construct_json(true)).unwrap().cast()
483 }
484}
485
486#[derive(Debug, Default)]
487pub struct EventFactory {
488 next_ts: AtomicU64,
489 sender: Option<OwnedUserId>,
490 room: Option<OwnedRoomId>,
491}
492
493impl EventFactory {
494 pub fn new() -> Self {
495 Self { next_ts: AtomicU64::new(0), sender: None, room: None }
496 }
497
498 pub fn room(mut self, room_id: &RoomId) -> Self {
499 self.room = Some(room_id.to_owned());
500 self
501 }
502
503 pub fn sender(mut self, sender: &UserId) -> Self {
504 self.sender = Some(sender.to_owned());
505 self
506 }
507
508 fn next_server_ts(&self) -> MilliSecondsSinceUnixEpoch {
509 MilliSecondsSinceUnixEpoch(
510 self.next_ts
511 .fetch_add(1, SeqCst)
512 .try_into()
513 .expect("server timestamp should fit in js_int::UInt"),
514 )
515 }
516
517 pub fn event<E: StaticEventContent>(&self, content: E) -> EventBuilder<E> {
519 EventBuilder {
520 sender: self.sender.clone(),
521 is_ephemeral: false,
522 room: self.room.clone(),
523 server_ts: self.next_server_ts(),
524 event_id: None,
525 no_event_id: false,
526 redacts: None,
527 content,
528 unsigned: None,
529 state_key: None,
530 }
531 }
532
533 pub fn text_msg(&self, content: impl Into<String>) -> EventBuilder<RoomMessageEventContent> {
535 self.event(RoomMessageEventContent::text_plain(content.into()))
536 }
537
538 pub fn emote(&self, content: impl Into<String>) -> EventBuilder<RoomMessageEventContent> {
540 self.event(RoomMessageEventContent::emote_plain(content.into()))
541 }
542
543 pub fn member(&self, member: &UserId) -> EventBuilder<RoomMemberEventContent> {
573 let mut event = self.event(RoomMemberEventContent::new(MembershipState::Join));
574
575 if self.sender.is_some() {
576 event.sender = self.sender.clone();
577 } else {
578 event.sender = Some(member.to_owned());
579 }
580
581 event.state_key = Some(member.to_string());
582
583 event
584 }
585
586 pub fn room_tombstone(
588 &self,
589 body: impl Into<String>,
590 replacement: &RoomId,
591 ) -> EventBuilder<RoomTombstoneEventContent> {
592 let mut event =
593 self.event(RoomTombstoneEventContent::new(body.into(), replacement.to_owned()));
594 event.state_key = Some("".to_owned());
595 event
596 }
597
598 pub fn room_topic(&self, topic: impl Into<String>) -> EventBuilder<RoomTopicEventContent> {
600 let mut event = self.event(RoomTopicEventContent::new(topic.into()));
601 event.state_key = Some("".to_owned());
603 event
604 }
605
606 pub fn room_name(&self, name: impl Into<String>) -> EventBuilder<RoomNameEventContent> {
608 let mut event = self.event(RoomNameEventContent::new(name.into()));
609 event.state_key = Some("".to_owned());
611 event
612 }
613
614 pub fn room_avatar(&self) -> EventBuilder<RoomAvatarEventContent> {
616 let mut event = self.event(RoomAvatarEventContent::new());
617 event.state_key = Some("".to_owned());
619 event
620 }
621
622 pub fn member_hints(
643 &self,
644 service_members: BTreeSet<OwnedUserId>,
645 ) -> EventBuilder<MemberHintsEventContent> {
646 self.event(MemberHintsEventContent::new(service_members)).state_key("")
648 }
649
650 pub fn text_html(
652 &self,
653 plain: impl Into<String>,
654 html: impl Into<String>,
655 ) -> EventBuilder<RoomMessageEventContent> {
656 self.event(RoomMessageEventContent::text_html(plain, html))
657 }
658
659 pub fn notice(&self, content: impl Into<String>) -> EventBuilder<RoomMessageEventContent> {
661 self.event(RoomMessageEventContent::notice_plain(content))
662 }
663
664 pub fn reaction(
666 &self,
667 event_id: &EventId,
668 annotation: impl Into<String>,
669 ) -> EventBuilder<ReactionEventContent> {
670 self.event(ReactionEventContent::new(Annotation::new(
671 event_id.to_owned(),
672 annotation.into(),
673 )))
674 }
675
676 pub fn redaction(&self, event_id: &EventId) -> EventBuilder<RoomRedactionEventContent> {
681 let mut builder = self.event(RoomRedactionEventContent::new_v11(event_id.to_owned()));
682 builder.redacts = Some(event_id.to_owned());
683 builder
684 }
685
686 pub fn redacted<T: StaticEventContent + RedactedMessageLikeEventContent>(
689 &self,
690 redacter: &UserId,
691 content: T,
692 ) -> EventBuilder<T> {
693 let mut builder = self.event(content);
694
695 let redacted_because = RedactedBecause {
696 content: RoomRedactionEventContent::default(),
697 event_id: EventId::new(server_name!("dummy.server")),
698 sender: redacter.to_owned(),
699 origin_server_ts: self.next_server_ts(),
700 };
701 builder.unsigned.get_or_insert_with(Default::default).redacted_because =
702 Some(redacted_because);
703
704 builder
705 }
706
707 pub fn redacted_state<T: StaticEventContent + RedactedStateEventContent>(
710 &self,
711 redacter: &UserId,
712 state_key: impl Into<String>,
713 content: T,
714 ) -> EventBuilder<T> {
715 let mut builder = self.event(content);
716
717 let redacted_because = RedactedBecause {
718 content: RoomRedactionEventContent::default(),
719 event_id: EventId::new(server_name!("dummy.server")),
720 sender: redacter.to_owned(),
721 origin_server_ts: self.next_server_ts(),
722 };
723 builder.unsigned.get_or_insert_with(Default::default).redacted_because =
724 Some(redacted_because);
725 builder.state_key = Some(state_key.into());
726
727 builder
728 }
729
730 pub fn poll_start(
733 &self,
734 content: impl Into<String>,
735 poll_question: impl Into<String>,
736 answers: Vec<impl Into<String>>,
737 ) -> EventBuilder<UnstablePollStartEventContent> {
738 let answers: Vec<UnstablePollAnswer> = answers
740 .into_iter()
741 .enumerate()
742 .map(|(idx, answer)| UnstablePollAnswer::new(idx.to_string(), answer))
743 .collect();
744 let poll_answers = answers.try_into().unwrap();
745 let poll_start_content =
746 UnstablePollStartEventContent::New(NewUnstablePollStartEventContent::plain_text(
747 content,
748 UnstablePollStartContentBlock::new(poll_question, poll_answers),
749 ));
750 self.event(poll_start_content)
751 }
752
753 pub fn poll_edit(
755 &self,
756 edited_event_id: &EventId,
757 poll_question: impl Into<String>,
758 answers: Vec<impl Into<String>>,
759 ) -> EventBuilder<ReplacementUnstablePollStartEventContent> {
760 let answers: Vec<UnstablePollAnswer> = answers
762 .into_iter()
763 .enumerate()
764 .map(|(idx, answer)| UnstablePollAnswer::new(idx.to_string(), answer))
765 .collect();
766 let poll_answers = answers.try_into().unwrap();
767 let poll_start_content_block =
768 UnstablePollStartContentBlock::new(poll_question, poll_answers);
769 self.event(ReplacementUnstablePollStartEventContent::new(
770 poll_start_content_block,
771 edited_event_id.to_owned(),
772 ))
773 }
774
775 pub fn poll_response(
778 &self,
779 answers: Vec<impl Into<String>>,
780 poll_start_id: &EventId,
781 ) -> EventBuilder<UnstablePollResponseEventContent> {
782 self.event(UnstablePollResponseEventContent::new(
783 answers.into_iter().map(Into::into).collect(),
784 poll_start_id.to_owned(),
785 ))
786 }
787
788 pub fn poll_end(
791 &self,
792 content: impl Into<String>,
793 poll_start_id: &EventId,
794 ) -> EventBuilder<UnstablePollEndEventContent> {
795 self.event(UnstablePollEndEventContent::new(content.into(), poll_start_id.to_owned()))
796 }
797
798 pub fn image(
801 &self,
802 filename: String,
803 url: OwnedMxcUri,
804 ) -> EventBuilder<RoomMessageEventContent> {
805 let image_event_content = ImageMessageEventContent::plain(filename, url);
806 self.event(RoomMessageEventContent::new(MessageType::Image(image_event_content)))
807 }
808
809 pub fn gallery(
812 &self,
813 body: String,
814 filename: String,
815 url: OwnedMxcUri,
816 ) -> EventBuilder<RoomMessageEventContent> {
817 let gallery_event_content = GalleryMessageEventContent::new(
818 body,
819 None,
820 vec![GalleryItemType::Image(ImageMessageEventContent::plain(filename, url))],
821 );
822 self.event(RoomMessageEventContent::new(MessageType::Gallery(gallery_event_content)))
823 }
824
825 pub fn typing(&self, user_ids: Vec<&UserId>) -> EventBuilder<TypingEventContent> {
827 let mut builder = self
828 .event(TypingEventContent::new(user_ids.into_iter().map(ToOwned::to_owned).collect()));
829 builder.is_ephemeral = true;
830 builder
831 }
832
833 pub fn read_receipts(&self) -> ReadReceiptBuilder<'_> {
835 ReadReceiptBuilder { factory: self, content: ReceiptEventContent(Default::default()) }
836 }
837
838 pub fn create(
840 &self,
841 creator_user_id: &UserId,
842 room_version: RoomVersionId,
843 ) -> EventBuilder<RoomCreateEventContent> {
844 let mut event = self.event(RoomCreateEventContent::new_v1(creator_user_id.to_owned()));
845 event.content.room_version = room_version;
846
847 if self.sender.is_some() {
848 event.sender = self.sender.clone();
849 } else {
850 event.sender = Some(creator_user_id.to_owned());
851 }
852
853 event.state_key = Some("".to_owned());
854
855 event
856 }
857
858 pub fn power_levels(
860 &self,
861 map: &mut BTreeMap<OwnedUserId, Int>,
862 ) -> EventBuilder<RoomPowerLevelsEventContent> {
863 let mut event = RoomPowerLevelsEventContent::new();
864 event.users.append(map);
865 self.event(event)
866 }
867
868 pub fn server_acl(
870 &self,
871 allow_ip_literals: bool,
872 allow: Vec<String>,
873 deny: Vec<String>,
874 ) -> EventBuilder<RoomServerAclEventContent> {
875 self.event(RoomServerAclEventContent::new(allow_ip_literals, allow, deny))
876 }
877
878 pub fn canonical_alias(
880 &self,
881 alias: Option<OwnedRoomAliasId>,
882 alt_aliases: Vec<OwnedRoomAliasId>,
883 ) -> EventBuilder<RoomCanonicalAliasEventContent> {
884 let mut event = RoomCanonicalAliasEventContent::new();
885 event.alias = alias;
886 event.alt_aliases = alt_aliases;
887 self.event(event)
888 }
889
890 pub fn beacon(
915 &self,
916 beacon_info_event_id: OwnedEventId,
917 latitude: f64,
918 longitude: f64,
919 uncertainty: u32,
920 ts: Option<MilliSecondsSinceUnixEpoch>,
921 ) -> EventBuilder<BeaconEventContent> {
922 let geo_uri = format!("geo:{latitude},{longitude};u={uncertainty}");
923 self.event(BeaconEventContent::new(beacon_info_event_id, geo_uri, ts))
924 }
925
926 pub fn sticker(
928 &self,
929 body: impl Into<String>,
930 info: ImageInfo,
931 url: OwnedMxcUri,
932 ) -> EventBuilder<StickerEventContent> {
933 self.event(StickerEventContent::new(body.into(), info, url))
934 }
935
936 pub fn call_invite(
938 &self,
939 call_id: OwnedVoipId,
940 lifetime: UInt,
941 offer: SessionDescription,
942 version: VoipVersionId,
943 ) -> EventBuilder<CallInviteEventContent> {
944 self.event(CallInviteEventContent::new(call_id, lifetime, offer, version))
945 }
946
947 pub fn call_notify(
949 &self,
950 call_id: String,
951 application: ApplicationType,
952 notify_type: NotifyType,
953 mentions: Mentions,
954 ) -> EventBuilder<CallNotifyEventContent> {
955 self.event(CallNotifyEventContent::new(call_id, application, notify_type, mentions))
956 }
957
958 pub fn set_next_ts(&self, value: u64) {
962 self.next_ts.store(value, SeqCst);
963 }
964}
965
966impl EventBuilder<RoomMemberEventContent> {
967 pub fn membership(mut self, state: MembershipState) -> Self {
972 self.content.membership = state;
973 self
974 }
975
976 pub fn invited(mut self, invited_user: &UserId) -> Self {
979 assert_ne!(
980 self.sender.as_deref().unwrap(),
981 invited_user,
982 "invited user and sender can't be the same person"
983 );
984 self.content.membership = MembershipState::Invite;
985 self.state_key = Some(invited_user.to_string());
986 self
987 }
988
989 pub fn kicked(mut self, kicked_user: &UserId) -> Self {
992 assert_ne!(
993 self.sender.as_deref().unwrap(),
994 kicked_user,
995 "kicked user and sender can't be the same person, otherwise it's just a Leave"
996 );
997 self.content.membership = MembershipState::Leave;
998 self.state_key = Some(kicked_user.to_string());
999 self
1000 }
1001
1002 pub fn banned(mut self, banned_user: &UserId) -> Self {
1005 assert_ne!(
1006 self.sender.as_deref().unwrap(),
1007 banned_user,
1008 "a user can't ban itself" );
1010 self.content.membership = MembershipState::Ban;
1011 self.state_key = Some(banned_user.to_string());
1012 self
1013 }
1014
1015 pub fn display_name(mut self, display_name: impl Into<String>) -> Self {
1017 self.content.displayname = Some(display_name.into());
1018 self
1019 }
1020
1021 pub fn avatar_url(mut self, url: &MxcUri) -> Self {
1023 self.content.avatar_url = Some(url.to_owned());
1024 self
1025 }
1026
1027 pub fn reason(mut self, reason: impl Into<String>) -> Self {
1029 self.content.reason = Some(reason.into());
1030 self
1031 }
1032
1033 pub fn previous(mut self, previous: impl Into<PreviousMembership>) -> Self {
1035 let previous = previous.into();
1036
1037 let mut prev_content = RoomMemberEventContent::new(previous.state);
1038 if let Some(avatar_url) = previous.avatar_url {
1039 prev_content.avatar_url = Some(avatar_url);
1040 }
1041 if let Some(display_name) = previous.display_name {
1042 prev_content.displayname = Some(display_name);
1043 }
1044
1045 self.unsigned.get_or_insert_with(Default::default).prev_content = Some(prev_content);
1046 self
1047 }
1048}
1049
1050impl EventBuilder<RoomAvatarEventContent> {
1051 pub fn url(mut self, url: &MxcUri) -> Self {
1053 self.content.url = Some(url.to_owned());
1054 self
1055 }
1056
1057 pub fn info(mut self, image: avatar::ImageInfo) -> Self {
1059 self.content.info = Some(Box::new(image));
1060 self
1061 }
1062}
1063
1064pub struct ReadReceiptBuilder<'a> {
1065 factory: &'a EventFactory,
1066 content: ReceiptEventContent,
1067}
1068
1069impl ReadReceiptBuilder<'_> {
1070 pub fn add(
1072 self,
1073 event_id: &EventId,
1074 user_id: &UserId,
1075 tyype: ReceiptType,
1076 thread: ReceiptThread,
1077 ) -> Self {
1078 let ts = self.factory.next_server_ts();
1079 self.add_with_timestamp(event_id, user_id, tyype, thread, Some(ts))
1080 }
1081
1082 pub fn add_with_timestamp(
1084 mut self,
1085 event_id: &EventId,
1086 user_id: &UserId,
1087 tyype: ReceiptType,
1088 thread: ReceiptThread,
1089 ts: Option<MilliSecondsSinceUnixEpoch>,
1090 ) -> Self {
1091 let by_event = self.content.0.entry(event_id.to_owned()).or_default();
1092 let by_type = by_event.entry(tyype).or_default();
1093
1094 let mut receipt = Receipt::default();
1095 if let Some(ts) = ts {
1096 receipt.ts = Some(ts);
1097 }
1098 receipt.thread = thread;
1099
1100 by_type.insert(user_id.to_owned(), receipt);
1101 self
1102 }
1103
1104 pub fn into_content(self) -> ReceiptEventContent {
1106 self.content
1107 }
1108
1109 pub fn into_event(self) -> EventBuilder<ReceiptEventContent> {
1111 let mut builder = self.factory.event(self.into_content());
1112 builder.is_ephemeral = true;
1113 builder
1114 }
1115}
1116
1117pub struct PreviousMembership {
1118 state: MembershipState,
1119 avatar_url: Option<OwnedMxcUri>,
1120 display_name: Option<String>,
1121}
1122
1123impl PreviousMembership {
1124 pub fn new(state: MembershipState) -> Self {
1125 Self { state, avatar_url: None, display_name: None }
1126 }
1127
1128 pub fn avatar_url(mut self, url: &MxcUri) -> Self {
1129 self.avatar_url = Some(url.to_owned());
1130 self
1131 }
1132
1133 pub fn display_name(mut self, name: impl Into<String>) -> Self {
1134 self.display_name = Some(name.into());
1135 self
1136 }
1137}
1138
1139impl From<MembershipState> for PreviousMembership {
1140 fn from(state: MembershipState) -> Self {
1141 Self::new(state)
1142 }
1143}