1#![allow(missing_docs)]
16
17use std::{
18 collections::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 member_hints::MemberHintsEventContent,
29 poll::{
30 unstable_end::UnstablePollEndEventContent,
31 unstable_response::UnstablePollResponseEventContent,
32 unstable_start::{
33 NewUnstablePollStartEventContent, ReplacementUnstablePollStartEventContent,
34 UnstablePollAnswer, UnstablePollStartContentBlock, UnstablePollStartEventContent,
35 },
36 },
37 reaction::ReactionEventContent,
38 receipt::{Receipt, ReceiptEventContent, ReceiptThread, ReceiptType},
39 relation::{Annotation, InReplyTo, Replacement, Thread},
40 room::{
41 avatar::{self, RoomAvatarEventContent},
42 encrypted::{EncryptedEventScheme, RoomEncryptedEventContent},
43 member::{MembershipState, RoomMemberEventContent},
44 message::{
45 FormattedBody, ImageMessageEventContent, MessageType, Relation,
46 RoomMessageEventContent, RoomMessageEventContentWithoutRelation,
47 },
48 name::RoomNameEventContent,
49 redaction::RoomRedactionEventContent,
50 tombstone::RoomTombstoneEventContent,
51 topic::RoomTopicEventContent,
52 },
53 AnySyncTimelineEvent, AnyTimelineEvent, BundledMessageLikeRelations, EventContent,
54 RedactedMessageLikeEventContent, RedactedStateEventContent,
55 },
56 serde::Raw,
57 server_name, EventId, MilliSecondsSinceUnixEpoch, MxcUri, OwnedEventId, OwnedMxcUri,
58 OwnedRoomId, OwnedTransactionId, OwnedUserId, RoomId, TransactionId, UInt, UserId,
59};
60use serde::Serialize;
61use serde_json::json;
62
63pub trait TimestampArg {
64 fn to_milliseconds_since_unix_epoch(self) -> MilliSecondsSinceUnixEpoch;
65}
66
67impl TimestampArg for MilliSecondsSinceUnixEpoch {
68 fn to_milliseconds_since_unix_epoch(self) -> MilliSecondsSinceUnixEpoch {
69 self
70 }
71}
72
73impl TimestampArg for u64 {
74 fn to_milliseconds_since_unix_epoch(self) -> MilliSecondsSinceUnixEpoch {
75 MilliSecondsSinceUnixEpoch(UInt::try_from(self).unwrap())
76 }
77}
78
79#[derive(Debug, Serialize)]
81struct RedactedBecause {
82 content: RoomRedactionEventContent,
84
85 event_id: OwnedEventId,
87
88 sender: OwnedUserId,
90
91 origin_server_ts: MilliSecondsSinceUnixEpoch,
94}
95
96#[derive(Debug, Serialize)]
97struct Unsigned<C: EventContent> {
98 #[serde(skip_serializing_if = "Option::is_none")]
99 prev_content: Option<C>,
100
101 #[serde(skip_serializing_if = "Option::is_none")]
102 transaction_id: Option<OwnedTransactionId>,
103
104 #[serde(rename = "m.relations", skip_serializing_if = "Option::is_none")]
105 relations: Option<BundledMessageLikeRelations<Raw<AnySyncTimelineEvent>>>,
106
107 #[serde(skip_serializing_if = "Option::is_none")]
108 redacted_because: Option<RedactedBecause>,
109}
110
111impl<C: EventContent> Default for Unsigned<C> {
113 fn default() -> Self {
114 Self { prev_content: None, transaction_id: None, relations: None, redacted_because: None }
115 }
116}
117
118#[derive(Debug)]
119pub struct EventBuilder<C: EventContent> {
120 sender: Option<OwnedUserId>,
121 room: Option<OwnedRoomId>,
122 event_id: Option<OwnedEventId>,
123 no_event_id: bool,
125 redacts: Option<OwnedEventId>,
126 content: C,
127 server_ts: MilliSecondsSinceUnixEpoch,
128 unsigned: Option<Unsigned<C>>,
129 state_key: Option<String>,
130}
131
132impl<E: EventContent> EventBuilder<E>
133where
134 E::EventType: Serialize,
135{
136 pub fn room(mut self, room_id: &RoomId) -> Self {
137 self.room = Some(room_id.to_owned());
138 self
139 }
140
141 pub fn sender(mut self, sender: &UserId) -> Self {
142 self.sender = Some(sender.to_owned());
143 self
144 }
145
146 pub fn event_id(mut self, event_id: &EventId) -> Self {
147 self.event_id = Some(event_id.to_owned());
148 self.no_event_id = false;
149 self
150 }
151
152 pub fn no_event_id(mut self) -> Self {
153 self.event_id = None;
154 self.no_event_id = true;
155 self
156 }
157
158 pub fn server_ts(mut self, ts: impl TimestampArg) -> Self {
159 self.server_ts = ts.to_milliseconds_since_unix_epoch();
160 self
161 }
162
163 pub fn unsigned_transaction_id(mut self, transaction_id: &TransactionId) -> Self {
164 self.unsigned.get_or_insert_with(Default::default).transaction_id =
165 Some(transaction_id.to_owned());
166 self
167 }
168
169 pub fn bundled_relations(
177 mut self,
178 relations: BundledMessageLikeRelations<Raw<AnySyncTimelineEvent>>,
179 ) -> Self {
180 self.unsigned.get_or_insert_with(Default::default).relations = Some(relations);
181 self
182 }
183
184 pub fn state_key(mut self, state_key: impl Into<String>) -> Self {
189 self.state_key = Some(state_key.into());
190 self
191 }
192
193 #[inline(always)]
194 fn construct_json(self, requires_room: bool) -> serde_json::Value {
195 let sender = self
198 .sender
199 .or_else(|| Some(self.unsigned.as_ref()?.redacted_because.as_ref()?.sender.clone()))
200 .expect("we should have a sender user id at this point");
201
202 let mut json = json!({
203 "type": self.content.event_type(),
204 "content": self.content,
205 "sender": sender,
206 "origin_server_ts": self.server_ts,
207 });
208
209 let map = json.as_object_mut().unwrap();
210
211 let event_id = self
212 .event_id
213 .or_else(|| {
214 self.room.as_ref().map(|room_id| EventId::new(room_id.server_name().unwrap()))
215 })
216 .or_else(|| (!self.no_event_id).then(|| EventId::new(server_name!("dummy.org"))));
217 if let Some(event_id) = event_id {
218 map.insert("event_id".to_owned(), json!(event_id));
219 }
220
221 if requires_room {
222 let room_id = self.room.expect("TimelineEvent requires a room id");
223 map.insert("room_id".to_owned(), json!(room_id));
224 }
225
226 if let Some(redacts) = self.redacts {
227 map.insert("redacts".to_owned(), json!(redacts));
228 }
229
230 if let Some(unsigned) = self.unsigned {
231 map.insert("unsigned".to_owned(), json!(unsigned));
232 }
233
234 if let Some(state_key) = self.state_key {
235 map.insert("state_key".to_owned(), json!(state_key));
236 }
237
238 json
239 }
240
241 pub fn into_raw<T>(self) -> Raw<T> {
247 Raw::new(&self.construct_json(true)).unwrap().cast()
248 }
249
250 pub fn into_raw_timeline(self) -> Raw<AnyTimelineEvent> {
251 Raw::new(&self.construct_json(true)).unwrap().cast()
252 }
253
254 pub fn into_raw_sync(self) -> Raw<AnySyncTimelineEvent> {
255 Raw::new(&self.construct_json(false)).unwrap().cast()
256 }
257
258 pub fn into_event(self) -> TimelineEvent {
259 TimelineEvent::new(self.into_raw_sync())
260 }
261}
262
263impl EventBuilder<RoomEncryptedEventContent> {
264 pub fn into_utd_sync_timeline_event(self) -> TimelineEvent {
267 let session_id = as_variant!(&self.content.scheme, EncryptedEventScheme::MegolmV1AesSha2)
268 .map(|content| content.session_id.clone());
269
270 TimelineEvent::new_utd_event(
271 self.into(),
272 UnableToDecryptInfo {
273 session_id,
274 reason: UnableToDecryptReason::MissingMegolmSession { withheld_code: None },
275 },
276 )
277 }
278}
279
280impl EventBuilder<RoomMessageEventContent> {
281 pub fn reply_to(mut self, event_id: &EventId) -> Self {
283 self.content.relates_to =
284 Some(Relation::Reply { in_reply_to: InReplyTo::new(event_id.to_owned()) });
285 self
286 }
287
288 pub fn in_thread(mut self, root: &EventId, latest_thread_event: &EventId) -> Self {
291 self.content.relates_to =
292 Some(Relation::Thread(Thread::plain(root.to_owned(), latest_thread_event.to_owned())));
293 self
294 }
295
296 pub fn edit(
299 mut self,
300 edited_event_id: &EventId,
301 new_content: RoomMessageEventContentWithoutRelation,
302 ) -> Self {
303 self.content.relates_to =
304 Some(Relation::Replacement(Replacement::new(edited_event_id.to_owned(), new_content)));
305 self
306 }
307
308 pub fn caption(
312 mut self,
313 caption: Option<String>,
314 formatted_caption: Option<FormattedBody>,
315 ) -> Self {
316 match &mut self.content.msgtype {
317 MessageType::Image(image) => {
318 let filename = image.filename().to_owned();
319 if let Some(caption) = caption {
320 image.body = caption;
321 image.filename = Some(filename);
322 } else {
323 image.body = filename;
324 image.filename = None;
325 }
326 image.formatted = formatted_caption;
327 }
328
329 MessageType::Audio(_) | MessageType::Video(_) | MessageType::File(_) => {
330 unimplemented!();
331 }
332
333 _ => panic!("unexpected event type for a caption"),
334 }
335
336 self
337 }
338}
339
340impl<E: EventContent> From<EventBuilder<E>> for Raw<AnySyncTimelineEvent>
341where
342 E::EventType: Serialize,
343{
344 fn from(val: EventBuilder<E>) -> Self {
345 val.into_raw_sync()
346 }
347}
348
349impl<E: EventContent> From<EventBuilder<E>> for Raw<AnyTimelineEvent>
350where
351 E::EventType: Serialize,
352{
353 fn from(val: EventBuilder<E>) -> Self {
354 val.into_raw_timeline()
355 }
356}
357
358impl<E: EventContent> From<EventBuilder<E>> for TimelineEvent
359where
360 E::EventType: Serialize,
361{
362 fn from(val: EventBuilder<E>) -> Self {
363 val.into_event()
364 }
365}
366
367#[derive(Debug, Default)]
368pub struct EventFactory {
369 next_ts: AtomicU64,
370 sender: Option<OwnedUserId>,
371 room: Option<OwnedRoomId>,
372}
373
374impl EventFactory {
375 pub fn new() -> Self {
376 Self { next_ts: AtomicU64::new(0), sender: None, room: None }
377 }
378
379 pub fn room(mut self, room_id: &RoomId) -> Self {
380 self.room = Some(room_id.to_owned());
381 self
382 }
383
384 pub fn sender(mut self, sender: &UserId) -> Self {
385 self.sender = Some(sender.to_owned());
386 self
387 }
388
389 fn next_server_ts(&self) -> MilliSecondsSinceUnixEpoch {
390 MilliSecondsSinceUnixEpoch(
391 self.next_ts
392 .fetch_add(1, SeqCst)
393 .try_into()
394 .expect("server timestamp should fit in js_int::UInt"),
395 )
396 }
397
398 pub fn event<E: EventContent>(&self, content: E) -> EventBuilder<E> {
400 EventBuilder {
401 sender: self.sender.clone(),
402 room: self.room.clone(),
403 server_ts: self.next_server_ts(),
404 event_id: None,
405 no_event_id: false,
406 redacts: None,
407 content,
408 unsigned: None,
409 state_key: None,
410 }
411 }
412
413 pub fn text_msg(&self, content: impl Into<String>) -> EventBuilder<RoomMessageEventContent> {
415 self.event(RoomMessageEventContent::text_plain(content.into()))
416 }
417
418 pub fn emote(&self, content: impl Into<String>) -> EventBuilder<RoomMessageEventContent> {
420 self.event(RoomMessageEventContent::emote_plain(content.into()))
421 }
422
423 pub fn member(&self, member: &UserId) -> EventBuilder<RoomMemberEventContent> {
453 let mut event = self.event(RoomMemberEventContent::new(MembershipState::Join));
454
455 if self.sender.is_some() {
456 event.sender = self.sender.clone();
457 } else {
458 event.sender = Some(member.to_owned());
459 }
460
461 event.state_key = Some(member.to_string());
462
463 event
464 }
465
466 pub fn room_tombstone(
468 &self,
469 body: impl Into<String>,
470 replacement: &RoomId,
471 ) -> EventBuilder<RoomTombstoneEventContent> {
472 let mut event =
473 self.event(RoomTombstoneEventContent::new(body.into(), replacement.to_owned()));
474 event.state_key = Some("".to_owned());
475 event
476 }
477
478 pub fn room_topic(&self, topic: impl Into<String>) -> EventBuilder<RoomTopicEventContent> {
480 let mut event = self.event(RoomTopicEventContent::new(topic.into()));
481 event.state_key = Some("".to_owned());
483 event
484 }
485
486 pub fn room_name(&self, name: impl Into<String>) -> EventBuilder<RoomNameEventContent> {
488 let mut event = self.event(RoomNameEventContent::new(name.into()));
489 event.state_key = Some("".to_owned());
491 event
492 }
493
494 pub fn room_avatar(&self) -> EventBuilder<RoomAvatarEventContent> {
496 let mut event = self.event(RoomAvatarEventContent::new());
497 event.state_key = Some("".to_owned());
499 event
500 }
501
502 pub fn member_hints(
523 &self,
524 service_members: BTreeSet<OwnedUserId>,
525 ) -> EventBuilder<MemberHintsEventContent> {
526 self.event(MemberHintsEventContent::new(service_members)).state_key("")
528 }
529
530 pub fn text_html(
532 &self,
533 plain: impl Into<String>,
534 html: impl Into<String>,
535 ) -> EventBuilder<RoomMessageEventContent> {
536 self.event(RoomMessageEventContent::text_html(plain, html))
537 }
538
539 pub fn notice(&self, content: impl Into<String>) -> EventBuilder<RoomMessageEventContent> {
541 self.event(RoomMessageEventContent::notice_plain(content))
542 }
543
544 pub fn reaction(
546 &self,
547 event_id: &EventId,
548 annotation: impl Into<String>,
549 ) -> EventBuilder<ReactionEventContent> {
550 self.event(ReactionEventContent::new(Annotation::new(
551 event_id.to_owned(),
552 annotation.into(),
553 )))
554 }
555
556 pub fn redaction(&self, event_id: &EventId) -> EventBuilder<RoomRedactionEventContent> {
561 let mut builder = self.event(RoomRedactionEventContent::new_v11(event_id.to_owned()));
562 builder.redacts = Some(event_id.to_owned());
563 builder
564 }
565
566 pub fn redacted<T: RedactedMessageLikeEventContent>(
569 &self,
570 redacter: &UserId,
571 content: T,
572 ) -> EventBuilder<T> {
573 let mut builder = self.event(content);
574
575 let redacted_because = RedactedBecause {
576 content: RoomRedactionEventContent::default(),
577 event_id: EventId::new(server_name!("dummy.server")),
578 sender: redacter.to_owned(),
579 origin_server_ts: self.next_server_ts(),
580 };
581 builder.unsigned.get_or_insert_with(Default::default).redacted_because =
582 Some(redacted_because);
583
584 builder
585 }
586
587 pub fn redacted_state<T: RedactedStateEventContent>(
590 &self,
591 redacter: &UserId,
592 state_key: impl Into<String>,
593 content: T,
594 ) -> EventBuilder<T> {
595 let mut builder = self.event(content);
596
597 let redacted_because = RedactedBecause {
598 content: RoomRedactionEventContent::default(),
599 event_id: EventId::new(server_name!("dummy.server")),
600 sender: redacter.to_owned(),
601 origin_server_ts: self.next_server_ts(),
602 };
603 builder.unsigned.get_or_insert_with(Default::default).redacted_because =
604 Some(redacted_because);
605 builder.state_key = Some(state_key.into());
606
607 builder
608 }
609
610 pub fn poll_start(
613 &self,
614 content: impl Into<String>,
615 poll_question: impl Into<String>,
616 answers: Vec<impl Into<String>>,
617 ) -> EventBuilder<UnstablePollStartEventContent> {
618 let answers: Vec<UnstablePollAnswer> = answers
620 .into_iter()
621 .enumerate()
622 .map(|(idx, answer)| UnstablePollAnswer::new(idx.to_string(), answer))
623 .collect();
624 let poll_answers = answers.try_into().unwrap();
625 let poll_start_content =
626 UnstablePollStartEventContent::New(NewUnstablePollStartEventContent::plain_text(
627 content,
628 UnstablePollStartContentBlock::new(poll_question, poll_answers),
629 ));
630 self.event(poll_start_content)
631 }
632
633 pub fn poll_edit(
635 &self,
636 edited_event_id: &EventId,
637 poll_question: impl Into<String>,
638 answers: Vec<impl Into<String>>,
639 ) -> EventBuilder<ReplacementUnstablePollStartEventContent> {
640 let answers: Vec<UnstablePollAnswer> = answers
642 .into_iter()
643 .enumerate()
644 .map(|(idx, answer)| UnstablePollAnswer::new(idx.to_string(), answer))
645 .collect();
646 let poll_answers = answers.try_into().unwrap();
647 let poll_start_content_block =
648 UnstablePollStartContentBlock::new(poll_question, poll_answers);
649 self.event(ReplacementUnstablePollStartEventContent::new(
650 poll_start_content_block,
651 edited_event_id.to_owned(),
652 ))
653 }
654
655 pub fn poll_response(
658 &self,
659 answers: Vec<impl Into<String>>,
660 poll_start_id: &EventId,
661 ) -> EventBuilder<UnstablePollResponseEventContent> {
662 self.event(UnstablePollResponseEventContent::new(
663 answers.into_iter().map(Into::into).collect(),
664 poll_start_id.to_owned(),
665 ))
666 }
667
668 pub fn poll_end(
671 &self,
672 content: impl Into<String>,
673 poll_start_id: &EventId,
674 ) -> EventBuilder<UnstablePollEndEventContent> {
675 self.event(UnstablePollEndEventContent::new(content.into(), poll_start_id.to_owned()))
676 }
677
678 pub fn image(
681 &self,
682 filename: String,
683 url: OwnedMxcUri,
684 ) -> EventBuilder<RoomMessageEventContent> {
685 let image_event_content = ImageMessageEventContent::plain(filename, url);
686 self.event(RoomMessageEventContent::new(MessageType::Image(image_event_content)))
687 }
688
689 pub fn read_receipts(&self) -> ReadReceiptBuilder<'_> {
691 ReadReceiptBuilder { factory: self, content: ReceiptEventContent(Default::default()) }
692 }
693
694 pub fn set_next_ts(&self, value: u64) {
698 self.next_ts.store(value, SeqCst);
699 }
700}
701
702impl EventBuilder<RoomMemberEventContent> {
703 pub fn membership(mut self, state: MembershipState) -> Self {
708 self.content.membership = state;
709 self
710 }
711
712 pub fn invited(mut self, invited_user: &UserId) -> Self {
715 assert_ne!(
716 self.sender.as_deref().unwrap(),
717 invited_user,
718 "invited user and sender can't be the same person"
719 );
720 self.content.membership = MembershipState::Invite;
721 self.state_key = Some(invited_user.to_string());
722 self
723 }
724
725 pub fn kicked(mut self, kicked_user: &UserId) -> Self {
728 assert_ne!(
729 self.sender.as_deref().unwrap(),
730 kicked_user,
731 "kicked user and sender can't be the same person, otherwise it's just a Leave"
732 );
733 self.content.membership = MembershipState::Leave;
734 self.state_key = Some(kicked_user.to_string());
735 self
736 }
737
738 pub fn banned(mut self, banned_user: &UserId) -> Self {
741 assert_ne!(
742 self.sender.as_deref().unwrap(),
743 banned_user,
744 "a user can't ban itself" );
746 self.content.membership = MembershipState::Ban;
747 self.state_key = Some(banned_user.to_string());
748 self
749 }
750
751 pub fn display_name(mut self, display_name: impl Into<String>) -> Self {
753 self.content.displayname = Some(display_name.into());
754 self
755 }
756
757 pub fn avatar_url(mut self, url: &MxcUri) -> Self {
759 self.content.avatar_url = Some(url.to_owned());
760 self
761 }
762
763 pub fn reason(mut self, reason: impl Into<String>) -> Self {
765 self.content.reason = Some(reason.into());
766 self
767 }
768
769 pub fn previous(mut self, previous: impl Into<PreviousMembership>) -> Self {
771 let previous = previous.into();
772
773 let mut prev_content = RoomMemberEventContent::new(previous.state);
774 if let Some(avatar_url) = previous.avatar_url {
775 prev_content.avatar_url = Some(avatar_url);
776 }
777 if let Some(display_name) = previous.display_name {
778 prev_content.displayname = Some(display_name);
779 }
780
781 self.unsigned.get_or_insert_with(Default::default).prev_content = Some(prev_content);
782 self
783 }
784}
785
786impl EventBuilder<RoomAvatarEventContent> {
787 pub fn url(mut self, url: &MxcUri) -> Self {
789 self.content.url = Some(url.to_owned());
790 self
791 }
792
793 pub fn info(mut self, image: avatar::ImageInfo) -> Self {
795 self.content.info = Some(Box::new(image));
796 self
797 }
798}
799
800pub struct ReadReceiptBuilder<'a> {
801 factory: &'a EventFactory,
802 content: ReceiptEventContent,
803}
804
805impl ReadReceiptBuilder<'_> {
806 pub fn add(
808 mut self,
809 event_id: &EventId,
810 user_id: &UserId,
811 tyype: ReceiptType,
812 thread: ReceiptThread,
813 ) -> Self {
814 let by_event = self.content.0.entry(event_id.to_owned()).or_default();
815 let by_type = by_event.entry(tyype).or_default();
816
817 let mut receipt = Receipt::new(self.factory.next_server_ts());
818 receipt.thread = thread;
819
820 by_type.insert(user_id.to_owned(), receipt);
821 self
822 }
823
824 pub fn build(self) -> ReceiptEventContent {
826 self.content
827 }
828}
829
830pub struct PreviousMembership {
831 state: MembershipState,
832 avatar_url: Option<OwnedMxcUri>,
833 display_name: Option<String>,
834}
835
836impl PreviousMembership {
837 pub fn new(state: MembershipState) -> Self {
838 Self { state, avatar_url: None, display_name: None }
839 }
840
841 pub fn avatar_url(mut self, url: &MxcUri) -> Self {
842 self.avatar_url = Some(url.to_owned());
843 self
844 }
845
846 pub fn display_name(mut self, name: impl Into<String>) -> Self {
847 self.display_name = Some(name.into());
848 self
849 }
850}
851
852impl From<MembershipState> for PreviousMembership {
853 fn from(state: MembershipState) -> Self {
854 Self::new(state)
855 }
856}