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