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