1#![allow(missing_docs)]
16
17use std::{
18 collections::{BTreeMap, BTreeSet},
19 sync::atomic::{AtomicU64, Ordering::SeqCst},
20 time::Duration,
21};
22
23use as_variant::as_variant;
24use matrix_sdk_common::deserialized_responses::{
25 TimelineEvent, UnableToDecryptInfo, UnableToDecryptReason,
26};
27use ruma::{
28 EventId, Int, MilliSecondsSinceUnixEpoch, MxcUri, OwnedDeviceId, OwnedEventId, OwnedMxcUri,
29 OwnedRoomAliasId, OwnedRoomId, OwnedTransactionId, OwnedUserId, OwnedVoipId, RoomId,
30 RoomVersionId, TransactionId, UInt, UserId, VoipVersionId,
31 events::{
32 AnyGlobalAccountDataEvent, AnyMessageLikeEvent, AnyRoomAccountDataEvent, AnyStateEvent,
33 AnyStrippedStateEvent, AnySyncEphemeralRoomEvent, AnySyncMessageLikeEvent,
34 AnySyncStateEvent, AnySyncTimelineEvent, AnyTimelineEvent, BundledMessageLikeRelations,
35 EphemeralRoomEventContent, EventContentFromType, False, GlobalAccountDataEventContent,
36 Mentions, MessageLikeEvent, MessageLikeEventContent, PossiblyRedactedStateEventContent,
37 RedactContent, RedactedMessageLikeEventContent, RedactedStateEventContent,
38 RoomAccountDataEventContent, StateEvent, StateEventContent, StaticEventContent,
39 StaticStateEventContent, StrippedStateEvent, SyncMessageLikeEvent, SyncStateEvent,
40 beacon::BeaconEventContent,
41 beacon_info::BeaconInfoEventContent,
42 call::{
43 SessionDescription,
44 invite::CallInviteEventContent,
45 member::{
46 ActiveFocus, ActiveLivekitFocus, Application, CallApplicationContent,
47 CallMemberEventContent, CallMemberStateKey, CallScope, Focus, LivekitFocus,
48 },
49 },
50 direct::{DirectEventContent, OwnedDirectUserIdentifier},
51 fully_read::FullyReadEventContent,
52 ignored_user_list::IgnoredUserListEventContent,
53 macros::EventContent,
54 marked_unread::MarkedUnreadEventContent,
55 member_hints::MemberHintsEventContent,
56 poll::{
57 unstable_end::UnstablePollEndEventContent,
58 unstable_response::UnstablePollResponseEventContent,
59 unstable_start::{
60 NewUnstablePollStartEventContent, ReplacementUnstablePollStartEventContent,
61 UnstablePollAnswer, UnstablePollStartContentBlock, UnstablePollStartEventContent,
62 },
63 },
64 presence::{PresenceEvent, PresenceEventContent},
65 push_rules::PushRulesEventContent,
66 reaction::ReactionEventContent,
67 receipt::{Receipt, ReceiptEventContent, ReceiptThread, ReceiptType},
68 relation::{Annotation, BundledThread, Reference, Replacement, Reply, Thread},
69 room::{
70 ImageInfo,
71 avatar::{self, RoomAvatarEventContent},
72 canonical_alias::RoomCanonicalAliasEventContent,
73 create::{PreviousRoom, RoomCreateEventContent},
74 encrypted::{
75 EncryptedEventScheme, MegolmV1AesSha2ContentInit, RoomEncryptedEventContent,
76 },
77 encryption::RoomEncryptionEventContent,
78 history_visibility::{HistoryVisibility, RoomHistoryVisibilityEventContent},
79 join_rules::{JoinRule, RoomJoinRulesEventContent},
80 member::{MembershipState, RoomMemberEventContent},
81 message::{
82 FormattedBody, GalleryItemType, GalleryMessageEventContent,
83 ImageMessageEventContent, MessageType, OriginalSyncRoomMessageEvent, Relation,
84 RelationWithoutReplacement, RoomMessageEventContent,
85 RoomMessageEventContentWithoutRelation,
86 },
87 name::RoomNameEventContent,
88 pinned_events::RoomPinnedEventsEventContent,
89 power_levels::RoomPowerLevelsEventContent,
90 redaction::RoomRedactionEventContent,
91 server_acl::RoomServerAclEventContent,
92 tombstone::RoomTombstoneEventContent,
93 topic::RoomTopicEventContent,
94 },
95 rtc::{
96 decline::RtcDeclineEventContent,
97 notification::{CallIntent, NotificationType, RtcNotificationEventContent},
98 },
99 space::{child::SpaceChildEventContent, parent::SpaceParentEventContent},
100 space_order::SpaceOrderEventContent,
101 sticker::StickerEventContent,
102 tag::{TagEventContent, Tags},
103 typing::TypingEventContent,
104 },
105 owned_device_id,
106 presence::PresenceState,
107 push::Ruleset,
108 room::RoomType,
109 room_version_rules::AuthorizationRules,
110 serde::Raw,
111 server_name,
112};
113use serde::{Deserialize, Serialize, de::DeserializeOwned};
114use serde_json::json;
115
116use crate::base64_sha256_hash;
117
118pub trait TimestampArg {
119 fn to_milliseconds_since_unix_epoch(self) -> MilliSecondsSinceUnixEpoch;
120}
121
122impl TimestampArg for MilliSecondsSinceUnixEpoch {
123 fn to_milliseconds_since_unix_epoch(self) -> MilliSecondsSinceUnixEpoch {
124 self
125 }
126}
127
128impl TimestampArg for u64 {
129 fn to_milliseconds_since_unix_epoch(self) -> MilliSecondsSinceUnixEpoch {
130 MilliSecondsSinceUnixEpoch(UInt::try_from(self).unwrap())
131 }
132}
133
134#[derive(Debug, Serialize)]
136struct RedactedBecause {
137 content: RoomRedactionEventContent,
139
140 event_id: OwnedEventId,
142
143 sender: OwnedUserId,
145
146 origin_server_ts: MilliSecondsSinceUnixEpoch,
149}
150
151#[derive(Debug, Serialize)]
152struct Unsigned<C: StaticEventContent> {
153 #[serde(skip_serializing_if = "Option::is_none")]
154 prev_content: Option<C>,
155
156 #[serde(skip_serializing_if = "Option::is_none")]
157 transaction_id: Option<OwnedTransactionId>,
158
159 #[serde(rename = "m.relations", skip_serializing_if = "Option::is_none")]
160 relations: Option<BundledMessageLikeRelations<Raw<AnySyncTimelineEvent>>>,
161
162 #[serde(skip_serializing_if = "Option::is_none")]
163 redacted_because: Option<RedactedBecause>,
164
165 #[serde(skip_serializing_if = "Option::is_none")]
166 age: Option<Int>,
167
168 #[serde(skip_serializing_if = "Option::is_none")]
169 invite_room_state: Option<Vec<Raw<AnyStrippedStateEvent>>>,
170}
171
172impl<C: StaticEventContent> Default for Unsigned<C> {
174 fn default() -> Self {
175 Self {
176 prev_content: None,
177 transaction_id: None,
178 relations: None,
179 redacted_because: None,
180 age: None,
181 invite_room_state: None,
182 }
183 }
184}
185
186#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
187enum EventFormat {
188 #[default]
191 Timeline,
192 SyncTimeline,
194 StrippedState,
196 Ephemeral,
198 GlobalAccountData,
200 RoomAccountData,
202}
203
204impl EventFormat {
205 fn has_sender(self) -> bool {
207 matches!(self, Self::Timeline | Self::SyncTimeline | Self::StrippedState)
208 }
209
210 fn has_event_id(self) -> bool {
212 matches!(self, Self::Timeline | Self::SyncTimeline)
213 }
214
215 fn has_room_id(self) -> bool {
217 matches!(self, Self::Timeline)
218 }
219}
220
221#[derive(Debug)]
222pub struct EventBuilder<C: StaticEventContent<IsPrefix = False>> {
223 format: EventFormat,
227 sender: Option<OwnedUserId>,
228 room: Option<OwnedRoomId>,
229 event_id: Option<OwnedEventId>,
230 no_event_id: bool,
232 redacts: Option<OwnedEventId>,
233 content: C,
234 server_ts: MilliSecondsSinceUnixEpoch,
235 unsigned: Option<Unsigned<C>>,
236 state_key: Option<String>,
237}
238
239impl<E: StaticEventContent<IsPrefix = False>> EventBuilder<E> {
240 fn format(mut self, format: EventFormat) -> Self {
241 self.format = format;
242 self
243 }
244
245 pub fn room(mut self, room_id: &RoomId) -> Self {
246 self.room = Some(room_id.to_owned());
247 self
248 }
249
250 pub fn sender(mut self, sender: impl Into<OwnedUserId>) -> Self {
251 self.sender = Some(sender.into());
252 self
253 }
254
255 pub fn event_id(mut self, event_id: &EventId) -> Self {
256 self.event_id = Some(event_id.to_owned());
257 self.no_event_id = false;
258 self
259 }
260
261 pub fn no_event_id(mut self) -> Self {
262 self.event_id = None;
263 self.no_event_id = true;
264 self
265 }
266
267 pub fn server_ts(mut self, ts: impl TimestampArg) -> Self {
268 self.server_ts = ts.to_milliseconds_since_unix_epoch();
269 self
270 }
271
272 pub fn unsigned_transaction_id(mut self, transaction_id: &TransactionId) -> Self {
273 self.unsigned.get_or_insert_with(Default::default).transaction_id =
274 Some(transaction_id.to_owned());
275 self
276 }
277
278 pub fn age(mut self, age: impl Into<Int>) -> Self {
280 self.unsigned.get_or_insert_with(Default::default).age = Some(age.into());
281 self
282 }
283
284 pub fn prev_content(mut self, prev: E) -> Self {
286 self.unsigned.get_or_insert_with(Default::default).prev_content = Some(prev);
287 self
288 }
289
290 pub fn with_bundled_thread_summary(
293 mut self,
294 latest_event: Raw<AnySyncMessageLikeEvent>,
295 count: usize,
296 current_user_participated: bool,
297 ) -> Self {
298 let relations = self
299 .unsigned
300 .get_or_insert_with(Default::default)
301 .relations
302 .get_or_insert_with(BundledMessageLikeRelations::new);
303 relations.thread = Some(Box::new(BundledThread::new(
304 latest_event,
305 UInt::try_from(count).unwrap(),
306 current_user_participated,
307 )));
308 self
309 }
310
311 pub fn with_bundled_edit(mut self, replacement: impl Into<Raw<AnySyncTimelineEvent>>) -> Self {
313 let relations = self
314 .unsigned
315 .get_or_insert_with(Default::default)
316 .relations
317 .get_or_insert_with(BundledMessageLikeRelations::new);
318 relations.replace = Some(Box::new(replacement.into()));
319 self
320 }
321
322 pub fn state_key(mut self, state_key: impl Into<String>) -> Self {
327 self.state_key = Some(state_key.into());
328 self
329 }
330}
331
332impl<E> EventBuilder<E>
333where
334 E: StaticEventContent<IsPrefix = False> + Serialize,
335{
336 #[inline(always)]
337 fn construct_json(self) -> serde_json::Value {
338 let mut json = json!({
339 "type": E::TYPE,
340 "content": self.content,
341 "origin_server_ts": self.server_ts,
342 });
343
344 let map = json.as_object_mut().unwrap();
345
346 if self.format.has_sender() {
347 let sender = self
350 .sender
351 .or_else(|| Some(self.unsigned.as_ref()?.redacted_because.as_ref()?.sender.clone())).expect("the sender must be known when building the JSON for a non read-receipt or global event");
352 map.insert("sender".to_owned(), json!(sender));
353 }
354
355 if self.format.has_room_id() {
356 let room_id = self.room.expect("TimelineEvent requires a room id");
357 map.insert("room_id".to_owned(), json!(room_id));
358 }
359
360 if let Some(redacts) = self.redacts {
361 map.insert("redacts".to_owned(), json!(redacts));
362 }
363
364 if let Some(state_key) = self.state_key {
365 map.insert("state_key".to_owned(), json!(state_key));
366 }
367
368 if self.format.has_event_id() && !self.no_event_id {
369 let event_id = self.event_id.unwrap_or_else(|| {
370 let bytes = serde_json::to_vec(&map).unwrap();
377 EventId::new_v2_or_v3(&base64_sha256_hash(&bytes)).unwrap()
378 });
379
380 map.insert("event_id".to_owned(), json!(event_id));
381 }
382
383 if let Some(unsigned) = self.unsigned {
384 map.insert("unsigned".to_owned(), json!(unsigned));
385 }
386
387 json
388 }
389
390 pub fn into_raw<T>(self) -> Raw<T> {
396 Raw::new(&self.construct_json()).unwrap().cast_unchecked()
397 }
398
399 pub fn into_raw_timeline(self) -> Raw<AnyTimelineEvent> {
400 self.into_raw()
401 }
402
403 pub fn into_any_sync_message_like_event(self) -> AnySyncMessageLikeEvent {
404 self.format(EventFormat::SyncTimeline)
405 .into_raw()
406 .deserialize()
407 .expect("expected message like event")
408 }
409
410 pub fn into_original_sync_room_message_event(self) -> OriginalSyncRoomMessageEvent {
411 self.format(EventFormat::SyncTimeline)
412 .into_raw()
413 .deserialize()
414 .expect("expected original sync room message event")
415 }
416
417 pub fn into_raw_sync(self) -> Raw<AnySyncTimelineEvent> {
418 self.format(EventFormat::SyncTimeline).into_raw()
419 }
420
421 pub fn into_raw_sync_state(self) -> Raw<AnySyncStateEvent> {
422 self.format(EventFormat::SyncTimeline).into_raw()
423 }
424
425 pub fn into_event(self) -> TimelineEvent {
426 TimelineEvent::from_plaintext(self.into_raw_sync())
427 }
428
429 pub fn into_content(self) -> serde_json::Value {
434 json!(self.content)
435 }
436}
437
438impl EventBuilder<RoomEncryptedEventContent> {
439 pub fn into_utd_sync_timeline_event(self) -> TimelineEvent {
442 let session_id = as_variant!(&self.content.scheme, EncryptedEventScheme::MegolmV1AesSha2)
443 .map(|content| content.session_id.clone());
444
445 TimelineEvent::from_utd(
446 self.into(),
447 UnableToDecryptInfo {
448 session_id,
449 reason: UnableToDecryptReason::MissingMegolmSession { withheld_code: None },
450 },
451 )
452 }
453}
454
455impl EventBuilder<RoomMessageEventContent> {
456 pub fn reply_to(mut self, event_id: &EventId) -> Self {
458 self.content.relates_to = Some(Relation::Reply(Reply::with_event_id(event_id.to_owned())));
459 self
460 }
461
462 pub fn in_thread(mut self, root: &EventId, latest_thread_event: &EventId) -> Self {
465 self.content.relates_to =
466 Some(Relation::Thread(Thread::plain(root.to_owned(), latest_thread_event.to_owned())));
467 self
468 }
469
470 pub fn in_thread_reply(mut self, root: &EventId, replied_to: &EventId) -> Self {
473 self.content.relates_to =
474 Some(Relation::Thread(Thread::reply(root.to_owned(), replied_to.to_owned())));
475 self
476 }
477
478 pub fn mentions(mut self, mentions: Mentions) -> Self {
480 self.content.mentions = Some(mentions);
481 self
482 }
483
484 pub fn edit(
487 mut self,
488 edited_event_id: &EventId,
489 new_content: RoomMessageEventContentWithoutRelation,
490 ) -> Self {
491 self.content.relates_to =
492 Some(Relation::Replacement(Replacement::new(edited_event_id.to_owned(), new_content)));
493 self
494 }
495
496 pub fn caption(
500 mut self,
501 caption: Option<String>,
502 formatted_caption: Option<FormattedBody>,
503 ) -> Self {
504 match &mut self.content.msgtype {
505 MessageType::Image(image) => {
506 let filename = image.filename().to_owned();
507 if let Some(caption) = caption {
508 image.body = caption;
509 image.filename = Some(filename);
510 } else {
511 image.body = filename;
512 image.filename = None;
513 }
514 image.formatted = formatted_caption;
515 }
516
517 MessageType::Audio(_) | MessageType::Video(_) | MessageType::File(_) => {
518 unimplemented!();
519 }
520
521 _ => panic!("unexpected event type for a caption"),
522 }
523
524 self
525 }
526}
527
528impl EventBuilder<UnstablePollStartEventContent> {
529 pub fn reply_to(mut self, event_id: &EventId) -> Self {
531 if let UnstablePollStartEventContent::New(content) = &mut self.content {
532 content.relates_to =
533 Some(RelationWithoutReplacement::Reply(Reply::with_event_id(event_id.to_owned())));
534 }
535 self
536 }
537
538 pub fn in_thread(mut self, root: &EventId, reply_to_event_id: &EventId) -> Self {
541 let thread = Thread::reply(root.to_owned(), reply_to_event_id.to_owned());
542
543 if let UnstablePollStartEventContent::New(content) = &mut self.content {
544 content.relates_to = Some(RelationWithoutReplacement::Thread(thread));
545 }
546 self
547 }
548}
549
550impl EventBuilder<RoomCreateEventContent> {
551 pub fn predecessor(mut self, room_id: &RoomId) -> Self {
553 self.content.predecessor = Some(PreviousRoom::new(room_id.to_owned()));
554 self
555 }
556
557 pub fn no_predecessor(mut self) -> Self {
559 self.content.predecessor = None;
560 self
561 }
562
563 pub fn with_space_type(mut self) -> Self {
565 self.content.room_type = Some(RoomType::Space);
566 self
567 }
568}
569
570impl EventBuilder<StickerEventContent> {
571 pub fn reply_thread(mut self, root: &EventId, reply_to_event: &EventId) -> Self {
573 self.content.relates_to =
574 Some(Relation::Thread(Thread::reply(root.to_owned(), reply_to_event.to_owned())));
575 self
576 }
577}
578
579impl<E: StaticEventContent<IsPrefix = False>> From<EventBuilder<E>> for Raw<AnySyncTimelineEvent>
580where
581 E: Serialize,
582{
583 fn from(val: EventBuilder<E>) -> Self {
584 val.into_raw_sync()
585 }
586}
587
588impl<E: StaticEventContent<IsPrefix = False>> From<EventBuilder<E>> for AnySyncTimelineEvent
589where
590 E: Serialize,
591{
592 fn from(val: EventBuilder<E>) -> Self {
593 Raw::<AnySyncTimelineEvent>::from(val).deserialize().expect("expected sync timeline event")
594 }
595}
596
597impl<E: StaticEventContent<IsPrefix = False>> From<EventBuilder<E>> for Raw<AnyTimelineEvent>
598where
599 E: Serialize,
600{
601 fn from(val: EventBuilder<E>) -> Self {
602 val.into_raw_timeline()
603 }
604}
605
606impl<E: StaticEventContent<IsPrefix = False>> From<EventBuilder<E>> for AnyTimelineEvent
607where
608 E: Serialize,
609{
610 fn from(val: EventBuilder<E>) -> Self {
611 Raw::<AnyTimelineEvent>::from(val).deserialize().expect("expected timeline event")
612 }
613}
614
615impl<E: StaticEventContent<IsPrefix = False>> From<EventBuilder<E>>
616 for Raw<AnyGlobalAccountDataEvent>
617where
618 E: Serialize,
619{
620 fn from(val: EventBuilder<E>) -> Self {
621 val.format(EventFormat::GlobalAccountData).into_raw()
622 }
623}
624
625impl<E: StaticEventContent<IsPrefix = False>> From<EventBuilder<E>> for AnyGlobalAccountDataEvent
626where
627 E: Serialize,
628{
629 fn from(val: EventBuilder<E>) -> Self {
630 Raw::<AnyGlobalAccountDataEvent>::from(val)
631 .deserialize()
632 .expect("expected global account data")
633 }
634}
635
636impl<E: StaticEventContent<IsPrefix = False>> From<EventBuilder<E>> for Raw<AnyRoomAccountDataEvent>
637where
638 E: Serialize,
639{
640 fn from(val: EventBuilder<E>) -> Self {
641 val.format(EventFormat::RoomAccountData).into_raw()
642 }
643}
644
645impl<E: StaticEventContent<IsPrefix = False>> From<EventBuilder<E>> for AnyRoomAccountDataEvent
646where
647 E: Serialize,
648{
649 fn from(val: EventBuilder<E>) -> Self {
650 Raw::<AnyRoomAccountDataEvent>::from(val).deserialize().expect("expected room account data")
651 }
652}
653
654impl<E: StaticEventContent<IsPrefix = False>> From<EventBuilder<E>> for TimelineEvent
655where
656 E: Serialize,
657{
658 fn from(val: EventBuilder<E>) -> Self {
659 val.into_event()
660 }
661}
662
663impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
664 for Raw<AnySyncStateEvent>
665{
666 fn from(val: EventBuilder<E>) -> Self {
667 val.format(EventFormat::SyncTimeline).into_raw()
668 }
669}
670
671impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
672 for AnySyncStateEvent
673{
674 fn from(val: EventBuilder<E>) -> Self {
675 Raw::<AnySyncStateEvent>::from(val).deserialize().expect("expected sync state")
676 }
677}
678
679impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
680 for Raw<SyncStateEvent<E>>
681where
682 E: StaticStateEventContent + RedactContent,
683 E::Redacted: RedactedStateEventContent,
684{
685 fn from(val: EventBuilder<E>) -> Self {
686 val.format(EventFormat::SyncTimeline).into_raw()
687 }
688}
689
690impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
691 for SyncStateEvent<E>
692where
693 E: StaticStateEventContent + RedactContent + EventContentFromType,
694 E::Redacted: RedactedStateEventContent<StateKey = <E as StateEventContent>::StateKey>
695 + EventContentFromType,
696 E::Unsigned: DeserializeOwned,
697{
698 fn from(val: EventBuilder<E>) -> Self {
699 Raw::<SyncStateEvent<E>>::from(val).deserialize().expect("expected sync state")
700 }
701}
702
703impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
704 for Raw<AnyStateEvent>
705{
706 fn from(val: EventBuilder<E>) -> Self {
707 val.into_raw()
708 }
709}
710
711impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
712 for AnyStateEvent
713{
714 fn from(val: EventBuilder<E>) -> Self {
715 Raw::<AnyStateEvent>::from(val).deserialize().expect("expected state")
716 }
717}
718
719impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
720 for Raw<StateEvent<E>>
721where
722 E: StaticStateEventContent + RedactContent,
723 E::Redacted: RedactedStateEventContent,
724{
725 fn from(val: EventBuilder<E>) -> Self {
726 val.into_raw()
727 }
728}
729
730impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
731 for StateEvent<E>
732where
733 E: StaticStateEventContent + RedactContent + EventContentFromType,
734 E::Redacted: RedactedStateEventContent<StateKey = <E as StateEventContent>::StateKey>
735 + EventContentFromType,
736 E::Unsigned: DeserializeOwned,
737{
738 fn from(val: EventBuilder<E>) -> Self {
739 Raw::<StateEvent<E>>::from(val).deserialize().expect("expected state")
740 }
741}
742
743impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
744 for Raw<AnyStrippedStateEvent>
745{
746 fn from(val: EventBuilder<E>) -> Self {
747 val.format(EventFormat::StrippedState).into_raw()
748 }
749}
750
751impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
752 for AnyStrippedStateEvent
753{
754 fn from(val: EventBuilder<E>) -> Self {
755 Raw::<AnyStrippedStateEvent>::from(val).deserialize().expect("expected stripped state")
756 }
757}
758
759impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
760 for Raw<StrippedStateEvent<E::PossiblyRedacted>>
761where
762 E: StaticStateEventContent,
763{
764 fn from(val: EventBuilder<E>) -> Self {
765 val.format(EventFormat::StrippedState).into_raw()
766 }
767}
768
769impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
770 for StrippedStateEvent<E::PossiblyRedacted>
771where
772 E: StaticStateEventContent,
773 E::PossiblyRedacted: PossiblyRedactedStateEventContent + EventContentFromType,
774{
775 fn from(val: EventBuilder<E>) -> Self {
776 Raw::<StrippedStateEvent<E::PossiblyRedacted>>::from(val)
777 .deserialize()
778 .expect("expected stripped state")
779 }
780}
781
782impl<E: StaticEventContent<IsPrefix = False> + EphemeralRoomEventContent> From<EventBuilder<E>>
783 for Raw<AnySyncEphemeralRoomEvent>
784{
785 fn from(val: EventBuilder<E>) -> Self {
786 val.format(EventFormat::Ephemeral).into_raw()
787 }
788}
789
790impl<E: StaticEventContent<IsPrefix = False> + EphemeralRoomEventContent> From<EventBuilder<E>>
791 for AnySyncEphemeralRoomEvent
792{
793 fn from(val: EventBuilder<E>) -> Self {
794 Raw::<AnySyncEphemeralRoomEvent>::from(val).deserialize().expect("expected ephemeral")
795 }
796}
797
798impl<E: StaticEventContent<IsPrefix = False> + MessageLikeEventContent> From<EventBuilder<E>>
799 for Raw<AnySyncMessageLikeEvent>
800{
801 fn from(val: EventBuilder<E>) -> Self {
802 val.format(EventFormat::SyncTimeline).into_raw()
803 }
804}
805
806impl<E: StaticEventContent<IsPrefix = False> + MessageLikeEventContent> From<EventBuilder<E>>
807 for AnySyncMessageLikeEvent
808{
809 fn from(val: EventBuilder<E>) -> Self {
810 Raw::<AnySyncMessageLikeEvent>::from(val).deserialize().expect("expected sync message-like")
811 }
812}
813
814impl<E: StaticEventContent<IsPrefix = False> + MessageLikeEventContent> From<EventBuilder<E>>
815 for Raw<SyncMessageLikeEvent<E>>
816where
817 E: RedactContent,
818 E::Redacted: RedactedMessageLikeEventContent,
819{
820 fn from(val: EventBuilder<E>) -> Self {
821 val.format(EventFormat::SyncTimeline).into_raw()
822 }
823}
824
825impl<E: StaticEventContent<IsPrefix = False> + MessageLikeEventContent> From<EventBuilder<E>>
826 for SyncMessageLikeEvent<E>
827where
828 E: RedactContent + EventContentFromType,
829 E::Redacted: RedactedMessageLikeEventContent + EventContentFromType,
830{
831 fn from(val: EventBuilder<E>) -> Self {
832 Raw::<SyncMessageLikeEvent<E>>::from(val).deserialize().expect("expected sync message-like")
833 }
834}
835
836impl<E: StaticEventContent<IsPrefix = False> + MessageLikeEventContent> From<EventBuilder<E>>
837 for Raw<AnyMessageLikeEvent>
838{
839 fn from(val: EventBuilder<E>) -> Self {
840 val.into_raw()
841 }
842}
843
844impl<E: StaticEventContent<IsPrefix = False> + MessageLikeEventContent> From<EventBuilder<E>>
845 for AnyMessageLikeEvent
846{
847 fn from(val: EventBuilder<E>) -> Self {
848 Raw::<AnyMessageLikeEvent>::from(val).deserialize().expect("expected message-like")
849 }
850}
851
852impl<E: StaticEventContent<IsPrefix = False> + MessageLikeEventContent> From<EventBuilder<E>>
853 for Raw<MessageLikeEvent<E>>
854where
855 E: RedactContent,
856 E::Redacted: RedactedMessageLikeEventContent,
857{
858 fn from(val: EventBuilder<E>) -> Self {
859 val.into_raw()
860 }
861}
862
863impl<E: StaticEventContent<IsPrefix = False> + MessageLikeEventContent> From<EventBuilder<E>>
864 for MessageLikeEvent<E>
865where
866 E: RedactContent + EventContentFromType,
867 E::Redacted: RedactedMessageLikeEventContent + EventContentFromType,
868{
869 fn from(val: EventBuilder<E>) -> Self {
870 Raw::<MessageLikeEvent<E>>::from(val).deserialize().expect("expected message-like")
871 }
872}
873
874#[derive(Debug, Default)]
875pub struct EventFactory {
876 next_ts: AtomicU64,
877 sender: Option<OwnedUserId>,
878 room: Option<OwnedRoomId>,
879}
880
881impl EventFactory {
882 pub fn new() -> Self {
883 Self { next_ts: AtomicU64::new(0), sender: None, room: None }
884 }
885
886 pub fn room(mut self, room_id: &RoomId) -> Self {
887 self.room = Some(room_id.to_owned());
888 self
889 }
890
891 pub fn sender(mut self, sender: impl Into<OwnedUserId>) -> Self {
892 self.sender = Some(sender.into());
893 self
894 }
895
896 pub fn server_ts(self, ts: u64) -> Self {
897 self.next_ts.store(ts, SeqCst);
898 self
899 }
900
901 fn next_server_ts(&self) -> MilliSecondsSinceUnixEpoch {
902 MilliSecondsSinceUnixEpoch(
903 self.next_ts
904 .fetch_add(1, SeqCst)
905 .try_into()
906 .expect("server timestamp should fit in js_int::UInt"),
907 )
908 }
909
910 pub fn event<E: StaticEventContent<IsPrefix = False>>(&self, content: E) -> EventBuilder<E> {
912 EventBuilder {
913 format: EventFormat::Timeline,
914 sender: self.sender.clone(),
915 room: self.room.clone(),
916 server_ts: self.next_server_ts(),
917 event_id: None,
918 no_event_id: false,
919 redacts: None,
920 content,
921 unsigned: None,
922 state_key: None,
923 }
924 }
925
926 pub fn text_msg(&self, content: impl Into<String>) -> EventBuilder<RoomMessageEventContent> {
928 self.event(RoomMessageEventContent::text_plain(content.into()))
929 }
930
931 pub fn emote(&self, content: impl Into<String>) -> EventBuilder<RoomMessageEventContent> {
933 self.event(RoomMessageEventContent::emote_plain(content.into()))
934 }
935
936 pub fn encrypted(
939 &self,
940 ciphertext: impl Into<String>,
941 sender_key: impl Into<String>,
942 device_id: impl Into<OwnedDeviceId>,
943 session_id: impl Into<String>,
944 ) -> EventBuilder<RoomEncryptedEventContent> {
945 self.event(RoomEncryptedEventContent::new(
946 EncryptedEventScheme::MegolmV1AesSha2(
947 MegolmV1AesSha2ContentInit {
948 ciphertext: ciphertext.into(),
949 sender_key: sender_key.into(),
950 device_id: device_id.into(),
951 session_id: session_id.into(),
952 }
953 .into(),
954 ),
955 None,
956 ))
957 }
958
959 pub fn member(&self, member: impl Into<OwnedUserId>) -> EventBuilder<RoomMemberEventContent> {
989 let member = member.into();
990 let mut event = self.event(RoomMemberEventContent::new(MembershipState::Join));
991
992 if self.sender.is_some() {
993 event.sender = self.sender.clone();
994 } else {
995 event.sender = Some(member.clone());
996 }
997
998 event.state_key = Some(member.into());
999
1000 event
1001 }
1002
1003 pub fn room_tombstone(
1005 &self,
1006 body: impl Into<String>,
1007 replacement: &RoomId,
1008 ) -> EventBuilder<RoomTombstoneEventContent> {
1009 let mut event =
1010 self.event(RoomTombstoneEventContent::new(body.into(), replacement.to_owned()));
1011 event.state_key = Some("".to_owned());
1012 event
1013 }
1014
1015 pub fn room_topic(&self, topic: impl Into<String>) -> EventBuilder<RoomTopicEventContent> {
1017 let mut event = self.event(RoomTopicEventContent::new(topic.into()));
1018 event.state_key = Some("".to_owned());
1020 event
1021 }
1022
1023 pub fn room_name(&self, name: impl Into<String>) -> EventBuilder<RoomNameEventContent> {
1025 let mut event = self.event(RoomNameEventContent::new(name.into()));
1026 event.state_key = Some("".to_owned());
1028 event
1029 }
1030
1031 pub fn room_avatar(&self) -> EventBuilder<RoomAvatarEventContent> {
1033 let mut event = self.event(RoomAvatarEventContent::new());
1034 event.state_key = Some("".to_owned());
1036 event
1037 }
1038
1039 pub fn room_encryption(&self) -> EventBuilder<RoomEncryptionEventContent> {
1044 let mut event = self.event(RoomEncryptionEventContent::with_recommended_defaults());
1045 event.state_key = Some("".to_owned());
1047 event
1048 }
1049
1050 #[cfg(feature = "experimental-encrypted-state-events")]
1053 pub fn room_encryption_with_state_encryption(
1054 &self,
1055 ) -> EventBuilder<RoomEncryptionEventContent> {
1056 let mut content = RoomEncryptionEventContent::with_recommended_defaults();
1057 content.encrypt_state_events = true;
1058 let mut event = self.event(content);
1059 event.state_key = Some("".to_owned());
1060 event
1061 }
1062
1063 pub fn room_history_visibility(
1068 &self,
1069 visibility: HistoryVisibility,
1070 ) -> EventBuilder<RoomHistoryVisibilityEventContent> {
1071 let mut event = self.event(RoomHistoryVisibilityEventContent::new(visibility));
1072 event.state_key = Some("".to_owned());
1073 event
1074 }
1075
1076 pub fn room_join_rules(&self, join_rule: JoinRule) -> EventBuilder<RoomJoinRulesEventContent> {
1080 let mut event = self.event(RoomJoinRulesEventContent::new(join_rule));
1081 event.state_key = Some("".to_owned());
1082 event
1083 }
1084
1085 pub fn room_pinned_events(
1087 &self,
1088 pinned: Vec<OwnedEventId>,
1089 ) -> EventBuilder<RoomPinnedEventsEventContent> {
1090 self.event(RoomPinnedEventsEventContent::new(pinned)).state_key("")
1091 }
1092
1093 pub fn member_hints(
1114 &self,
1115 service_members: BTreeSet<OwnedUserId>,
1116 ) -> EventBuilder<MemberHintsEventContent> {
1117 self.event(MemberHintsEventContent::new(service_members)).state_key("")
1120 }
1121
1122 pub fn text_html(
1124 &self,
1125 plain: impl Into<String>,
1126 html: impl Into<String>,
1127 ) -> EventBuilder<RoomMessageEventContent> {
1128 self.event(RoomMessageEventContent::text_html(plain, html))
1129 }
1130
1131 pub fn notice(&self, content: impl Into<String>) -> EventBuilder<RoomMessageEventContent> {
1133 self.event(RoomMessageEventContent::notice_plain(content))
1134 }
1135
1136 pub fn reaction(
1138 &self,
1139 event_id: &EventId,
1140 annotation: impl Into<String>,
1141 ) -> EventBuilder<ReactionEventContent> {
1142 self.event(ReactionEventContent::new(Annotation::new(
1143 event_id.to_owned(),
1144 annotation.into(),
1145 )))
1146 }
1147
1148 pub fn redaction(&self, event_id: &EventId) -> EventBuilder<RoomRedactionEventContent> {
1153 let mut builder = self.event(RoomRedactionEventContent::new_v11(event_id.to_owned()));
1154 builder.redacts = Some(event_id.to_owned());
1155 builder
1156 }
1157
1158 pub fn redacted<T: StaticEventContent<IsPrefix = False> + RedactedMessageLikeEventContent>(
1161 &self,
1162 redacter: impl Into<OwnedUserId>,
1163 content: T,
1164 ) -> EventBuilder<T> {
1165 let mut builder = self.event(content);
1166
1167 let redacted_because = RedactedBecause {
1168 content: RoomRedactionEventContent::default(),
1169 event_id: EventId::new_v1(server_name!("dummy.server")),
1170 sender: redacter.into(),
1171 origin_server_ts: self.next_server_ts(),
1172 };
1173 builder.unsigned.get_or_insert_with(Default::default).redacted_because =
1174 Some(redacted_because);
1175
1176 builder
1177 }
1178
1179 pub fn redacted_state<T: StaticEventContent<IsPrefix = False> + RedactedStateEventContent>(
1182 &self,
1183 redacter: impl Into<OwnedUserId>,
1184 state_key: impl Into<String>,
1185 content: T,
1186 ) -> EventBuilder<T> {
1187 let mut builder = self.event(content);
1188
1189 let redacted_because = RedactedBecause {
1190 content: RoomRedactionEventContent::default(),
1191 event_id: EventId::new_v1(server_name!("dummy.server")),
1192 sender: redacter.into(),
1193 origin_server_ts: self.next_server_ts(),
1194 };
1195 builder.unsigned.get_or_insert_with(Default::default).redacted_because =
1196 Some(redacted_because);
1197 builder.state_key = Some(state_key.into());
1198
1199 builder
1200 }
1201
1202 pub fn poll_start(
1205 &self,
1206 fallback_text: impl Into<String>,
1207 poll_question: impl Into<String>,
1208 answers: Vec<impl Into<String>>,
1209 ) -> EventBuilder<UnstablePollStartEventContent> {
1210 let answers: Vec<UnstablePollAnswer> = answers
1213 .into_iter()
1214 .enumerate()
1215 .map(|(idx, answer)| UnstablePollAnswer::new(idx.to_string(), answer))
1216 .collect();
1217 let poll_answers = answers.try_into().unwrap();
1218 let poll_start_content =
1219 UnstablePollStartEventContent::New(NewUnstablePollStartEventContent::plain_text(
1220 fallback_text,
1221 UnstablePollStartContentBlock::new(poll_question, poll_answers),
1222 ));
1223 self.event(poll_start_content)
1224 }
1225
1226 pub fn poll_edit(
1228 &self,
1229 edited_event_id: &EventId,
1230 poll_question: impl Into<String>,
1231 answers: Vec<impl Into<String>>,
1232 ) -> EventBuilder<ReplacementUnstablePollStartEventContent> {
1233 let answers: Vec<UnstablePollAnswer> = answers
1236 .into_iter()
1237 .enumerate()
1238 .map(|(idx, answer)| UnstablePollAnswer::new(idx.to_string(), answer))
1239 .collect();
1240 let poll_answers = answers.try_into().unwrap();
1241 let poll_start_content_block =
1242 UnstablePollStartContentBlock::new(poll_question, poll_answers);
1243 self.event(ReplacementUnstablePollStartEventContent::new(
1244 poll_start_content_block,
1245 edited_event_id.to_owned(),
1246 ))
1247 }
1248
1249 pub fn poll_response(
1252 &self,
1253 answers: Vec<impl Into<String>>,
1254 poll_start_id: &EventId,
1255 ) -> EventBuilder<UnstablePollResponseEventContent> {
1256 self.event(UnstablePollResponseEventContent::new(
1257 answers.into_iter().map(Into::into).collect(),
1258 poll_start_id.to_owned(),
1259 ))
1260 }
1261
1262 pub fn poll_end(
1265 &self,
1266 content: impl Into<String>,
1267 poll_start_id: &EventId,
1268 ) -> EventBuilder<UnstablePollEndEventContent> {
1269 self.event(UnstablePollEndEventContent::new(content.into(), poll_start_id.to_owned()))
1270 }
1271
1272 pub fn image(
1275 &self,
1276 filename: String,
1277 url: OwnedMxcUri,
1278 ) -> EventBuilder<RoomMessageEventContent> {
1279 let image_event_content = ImageMessageEventContent::plain(filename, url);
1280 self.event(RoomMessageEventContent::new(MessageType::Image(image_event_content)))
1281 }
1282
1283 pub fn gallery(
1286 &self,
1287 body: String,
1288 filename: String,
1289 url: OwnedMxcUri,
1290 ) -> EventBuilder<RoomMessageEventContent> {
1291 let gallery_event_content = GalleryMessageEventContent::new(
1292 body,
1293 None,
1294 vec![GalleryItemType::Image(ImageMessageEventContent::plain(filename, url))],
1295 );
1296 self.event(RoomMessageEventContent::new(MessageType::Gallery(gallery_event_content)))
1297 }
1298
1299 pub fn typing(&self, user_ids: Vec<&UserId>) -> EventBuilder<TypingEventContent> {
1301 self.event(TypingEventContent::new(user_ids.into_iter().map(ToOwned::to_owned).collect()))
1302 .format(EventFormat::Ephemeral)
1303 }
1304
1305 pub fn read_receipts(&self) -> ReadReceiptBuilder<'_> {
1307 ReadReceiptBuilder { factory: self, content: ReceiptEventContent(Default::default()) }
1308 }
1309
1310 pub fn create(
1312 &self,
1313 creator_user_id: &UserId,
1314 room_version: RoomVersionId,
1315 ) -> EventBuilder<RoomCreateEventContent> {
1316 let mut event = self.event(RoomCreateEventContent::new_v1(creator_user_id.to_owned()));
1317 event.content.room_version = room_version;
1318
1319 if self.sender.is_some() {
1320 event.sender = self.sender.clone();
1321 } else {
1322 event.sender = Some(creator_user_id.to_owned());
1323 }
1324
1325 event.state_key = Some("".to_owned());
1326
1327 event
1328 }
1329
1330 pub fn power_levels(
1332 &self,
1333 map: &mut BTreeMap<OwnedUserId, Int>,
1334 ) -> EventBuilder<RoomPowerLevelsEventContent> {
1335 let mut content = RoomPowerLevelsEventContent::new(&AuthorizationRules::V1);
1336 content.users.append(map);
1337 let mut event = self.event(content);
1338 event.state_key = Some("".to_owned());
1339 event
1340 }
1341
1342 pub fn default_power_levels(&self) -> EventBuilder<RoomPowerLevelsEventContent> {
1344 self.power_levels(&mut BTreeMap::new())
1345 }
1346
1347 pub fn server_acl(
1349 &self,
1350 allow_ip_literals: bool,
1351 allow: Vec<String>,
1352 deny: Vec<String>,
1353 ) -> EventBuilder<RoomServerAclEventContent> {
1354 self.event(RoomServerAclEventContent::new(allow_ip_literals, allow, deny))
1355 }
1356
1357 pub fn canonical_alias(
1359 &self,
1360 alias: Option<OwnedRoomAliasId>,
1361 alt_aliases: Vec<OwnedRoomAliasId>,
1362 ) -> EventBuilder<RoomCanonicalAliasEventContent> {
1363 let mut content = RoomCanonicalAliasEventContent::new();
1364 content.alias = alias;
1365 content.alt_aliases = alt_aliases;
1366 let mut event = self.event(content);
1367 event.state_key = Some("".to_owned());
1369 event
1370 }
1371
1372 pub fn beacon(
1398 &self,
1399 beacon_info_event_id: OwnedEventId,
1400 latitude: f64,
1401 longitude: f64,
1402 uncertainty: u32,
1403 ts: Option<MilliSecondsSinceUnixEpoch>,
1404 ) -> EventBuilder<BeaconEventContent> {
1405 let geo_uri = format!("geo:{latitude},{longitude};u={uncertainty}");
1406 self.event(BeaconEventContent::new(beacon_info_event_id, geo_uri, ts))
1407 }
1408
1409 pub fn beacon_info(
1436 &self,
1437 description: Option<String>,
1438 duration: Duration,
1439 live: bool,
1440 ts: Option<MilliSecondsSinceUnixEpoch>,
1441 ) -> EventBuilder<BeaconInfoEventContent> {
1442 self.event(BeaconInfoEventContent::new(description, duration, live, ts))
1443 }
1444
1445 pub fn sticker(
1447 &self,
1448 body: impl Into<String>,
1449 info: ImageInfo,
1450 url: OwnedMxcUri,
1451 ) -> EventBuilder<StickerEventContent> {
1452 self.event(StickerEventContent::new(body.into(), info, url))
1453 }
1454
1455 pub fn call_invite(
1457 &self,
1458 call_id: OwnedVoipId,
1459 lifetime: UInt,
1460 offer: SessionDescription,
1461 version: VoipVersionId,
1462 ) -> EventBuilder<CallInviteEventContent> {
1463 self.event(CallInviteEventContent::new(call_id, lifetime, offer, version))
1464 }
1465
1466 pub fn rtc_notification(
1468 &self,
1469 notification_type: NotificationType,
1470 ) -> EventBuilder<RtcNotificationEventContent> {
1471 self.event(RtcNotificationEventContent::new(
1472 MilliSecondsSinceUnixEpoch::now(),
1473 Duration::new(30, 0),
1474 notification_type,
1475 ))
1476 }
1477
1478 pub fn call_decline(
1480 &self,
1481 notification_event_id: &EventId,
1482 ) -> EventBuilder<RtcDeclineEventContent> {
1483 self.event(RtcDeclineEventContent::new(notification_event_id))
1484 }
1485
1486 pub fn call_membership_state(
1507 &self,
1508 user_id: OwnedUserId,
1509 device_id: String,
1510 ) -> EventBuilder<CallMemberEventContent> {
1511 let event = self.event(CallMemberEventContent::new(
1512 Application::Call(CallApplicationContent::new("".to_owned(), CallScope::Room)),
1513 owned_device_id!(device_id.clone()),
1514 ActiveFocus::Livekit(ActiveLivekitFocus::new()),
1515 vec![],
1516 Some(MilliSecondsSinceUnixEpoch::now()),
1517 Some(Duration::from_secs(3600)),
1518 ));
1519 event
1520 .sender(&user_id)
1521 .state_key(CallMemberStateKey::new(user_id, device_id.into(), true).as_ref())
1522 }
1523
1524 pub fn direct(&self) -> EventBuilder<DirectEventContent> {
1526 self.global_account_data(DirectEventContent::default())
1527 }
1528
1529 pub fn ignored_user_list(
1531 &self,
1532 users: impl IntoIterator<Item = OwnedUserId>,
1533 ) -> EventBuilder<IgnoredUserListEventContent> {
1534 self.global_account_data(IgnoredUserListEventContent::users(users))
1535 }
1536
1537 pub fn push_rules(&self, rules: Ruleset) -> EventBuilder<PushRulesEventContent> {
1539 self.global_account_data(PushRulesEventContent::new(rules))
1540 }
1541
1542 pub fn space_child(
1544 &self,
1545 parent: OwnedRoomId,
1546 child: OwnedRoomId,
1547 ) -> EventBuilder<SpaceChildEventContent> {
1548 let mut event = self.event(SpaceChildEventContent::new(vec![]));
1549 event.room = Some(parent);
1550 event.state_key = Some(child.to_string());
1551 event
1552 }
1553
1554 pub fn space_parent(
1556 &self,
1557 parent: OwnedRoomId,
1558 child: OwnedRoomId,
1559 ) -> EventBuilder<SpaceParentEventContent> {
1560 let mut event = self.event(SpaceParentEventContent::new(vec![]));
1561 event.state_key = Some(parent.to_string());
1562 event.room = Some(child);
1563 event
1564 }
1565
1566 pub fn custom_message_like_event(&self) -> EventBuilder<CustomMessageLikeEventContent> {
1568 self.event(CustomMessageLikeEventContent)
1569 }
1570
1571 pub fn custom_state_event(&self) -> EventBuilder<CustomStateEventContent> {
1573 self.event(CustomStateEventContent).state_key("test")
1574 }
1575
1576 pub fn set_next_ts(&self, value: u64) {
1580 self.next_ts.store(value, SeqCst);
1581 }
1582
1583 pub fn global_account_data<C>(&self, content: C) -> EventBuilder<C>
1585 where
1586 C: GlobalAccountDataEventContent + StaticEventContent<IsPrefix = False>,
1587 {
1588 self.event(content).format(EventFormat::GlobalAccountData)
1589 }
1590
1591 pub fn room_account_data<C>(&self, content: C) -> EventBuilder<C>
1593 where
1594 C: RoomAccountDataEventContent + StaticEventContent<IsPrefix = False>,
1595 {
1596 self.event(content).format(EventFormat::RoomAccountData)
1597 }
1598
1599 pub fn fully_read(&self, event_id: &EventId) -> EventBuilder<FullyReadEventContent> {
1601 self.room_account_data(FullyReadEventContent::new(event_id.to_owned()))
1602 }
1603
1604 pub fn marked_unread(&self, unread: bool) -> EventBuilder<MarkedUnreadEventContent> {
1606 self.room_account_data(MarkedUnreadEventContent::new(unread))
1607 }
1608
1609 pub fn tag(&self, tags: Tags) -> EventBuilder<TagEventContent> {
1611 self.room_account_data(tags.into())
1612 }
1613
1614 pub fn space_order(&self, order: &str) -> EventBuilder<SpaceOrderEventContent> {
1617 let order = ruma::SpaceChildOrder::parse(order).expect("order should be valid");
1618 self.room_account_data(SpaceOrderEventContent::new(order))
1619 }
1620
1621 pub fn presence(&self, state: PresenceState) -> PresenceBuilder {
1626 PresenceBuilder { sender: self.sender.clone(), content: PresenceEventContent::new(state) }
1627 }
1628}
1629
1630#[derive(Debug)]
1632pub struct PresenceBuilder {
1633 sender: Option<OwnedUserId>,
1634 content: PresenceEventContent,
1635}
1636
1637impl PresenceBuilder {
1638 pub fn sender(mut self, sender: &UserId) -> Self {
1640 self.sender = Some(sender.to_owned());
1641 self
1642 }
1643
1644 pub fn avatar_url(mut self, url: &MxcUri) -> Self {
1646 self.content.avatar_url = Some(url.to_owned());
1647 self
1648 }
1649
1650 pub fn currently_active(mut self, active: bool) -> Self {
1652 self.content.currently_active = Some(active);
1653 self
1654 }
1655
1656 pub fn last_active_ago(mut self, ms: u64) -> Self {
1658 self.content.last_active_ago = Some(UInt::try_from(ms).unwrap());
1659 self
1660 }
1661
1662 pub fn status_msg(mut self, msg: impl Into<String>) -> Self {
1664 self.content.status_msg = Some(msg.into());
1665 self
1666 }
1667
1668 pub fn display_name(mut self, name: impl Into<String>) -> Self {
1670 self.content.displayname = Some(name.into());
1671 self
1672 }
1673}
1674
1675impl From<PresenceBuilder> for Raw<PresenceEvent> {
1676 fn from(builder: PresenceBuilder) -> Self {
1677 let sender = builder.sender.expect("sender must be set for presence events");
1678 let event = PresenceEvent { content: builder.content, sender };
1679 Raw::new(&event).unwrap().cast_unchecked()
1680 }
1681}
1682
1683impl EventBuilder<DirectEventContent> {
1684 pub fn add_user(
1686 mut self,
1687 user_id: impl Into<OwnedDirectUserIdentifier>,
1688 room_id: &RoomId,
1689 ) -> Self {
1690 self.content.0.entry(user_id.into()).or_default().push(room_id.to_owned());
1691 self
1692 }
1693}
1694
1695impl EventBuilder<RoomMemberEventContent> {
1696 pub fn membership(mut self, state: MembershipState) -> Self {
1701 self.content.membership = state;
1702 self
1703 }
1704
1705 pub fn invited(mut self, invited_user: impl Into<OwnedUserId> + Into<String>) -> Self {
1708 let invited_user: String = invited_user.into();
1709 assert_ne!(
1710 self.sender.as_deref().unwrap(),
1711 invited_user.as_str(),
1712 "invited user and sender can't be the same person"
1713 );
1714 self.content.membership = MembershipState::Invite;
1715 self.state_key = Some(invited_user);
1716 self
1717 }
1718
1719 pub fn leave(mut self) -> Self {
1723 self.content.membership = MembershipState::Leave;
1724 self.state_key = Some(self.sender.as_ref().expect("sender must be set").to_string());
1725 self
1726 }
1727
1728 pub fn kicked(mut self, kicked_user: impl Into<OwnedUserId> + Into<String>) -> Self {
1731 let kicked_user: String = kicked_user.into();
1732 assert_ne!(
1733 self.sender.as_deref().unwrap(),
1734 kicked_user.as_str(),
1735 "kicked user and sender can't be the same person, otherwise it's just a Leave"
1736 );
1737 self.content.membership = MembershipState::Leave;
1738 self.state_key = Some(kicked_user);
1739 self
1740 }
1741
1742 pub fn banned(mut self, banned_user: impl Into<OwnedUserId> + Into<String>) -> Self {
1745 let banned_user: String = banned_user.into();
1746 assert_ne!(
1747 self.sender.as_deref().unwrap(),
1748 banned_user.as_str(),
1749 "a user can't ban itself" );
1751 self.content.membership = MembershipState::Ban;
1752 self.state_key = Some(banned_user);
1753 self
1754 }
1755
1756 pub fn display_name(mut self, display_name: impl Into<String>) -> Self {
1758 self.content.displayname = Some(display_name.into());
1759 self
1760 }
1761
1762 pub fn avatar_url(mut self, url: &MxcUri) -> Self {
1764 self.content.avatar_url = Some(url.to_owned());
1765 self
1766 }
1767
1768 pub fn reason(mut self, reason: impl Into<String>) -> Self {
1770 self.content.reason = Some(reason.into());
1771 self
1772 }
1773
1774 pub fn previous(mut self, previous: impl Into<PreviousMembership>) -> Self {
1776 let previous = previous.into();
1777
1778 let mut prev_content = RoomMemberEventContent::new(previous.state);
1779 if let Some(avatar_url) = previous.avatar_url {
1780 prev_content.avatar_url = Some(avatar_url);
1781 }
1782 if let Some(display_name) = previous.display_name {
1783 prev_content.displayname = Some(display_name);
1784 }
1785
1786 self.unsigned.get_or_insert_with(Default::default).prev_content = Some(prev_content);
1787 self
1788 }
1789
1790 pub fn invite_room_state<I, E>(mut self, events: I) -> Self
1795 where
1796 I: IntoIterator<Item = E>,
1797 E: Into<Raw<AnyStrippedStateEvent>>,
1798 {
1799 self.unsigned.get_or_insert_with(Default::default).invite_room_state =
1800 Some(events.into_iter().map(Into::into).collect());
1801 self
1802 }
1803}
1804
1805impl EventBuilder<RoomAvatarEventContent> {
1806 pub fn url(mut self, url: &MxcUri) -> Self {
1808 self.content.url = Some(url.to_owned());
1809 self
1810 }
1811
1812 pub fn info(mut self, image: avatar::ImageInfo) -> Self {
1814 self.content.info = Some(Box::new(image));
1815 self
1816 }
1817}
1818
1819impl EventBuilder<RtcNotificationEventContent> {
1820 pub fn mentions(mut self, users: impl IntoIterator<Item = OwnedUserId>) -> Self {
1821 self.content.mentions = Some(Mentions::with_user_ids(users));
1822 self
1823 }
1824
1825 pub fn call_intent(mut self, call_intent: CallIntent) -> Self {
1826 self.content.call_intent = Some(call_intent);
1827 self
1828 }
1829
1830 pub fn relates_to_membership_state_event(mut self, event_id: OwnedEventId) -> Self {
1831 self.content.relates_to = Some(Reference::new(event_id));
1832 self
1833 }
1834
1835 pub fn lifetime(mut self, time_in_seconds: u64) -> Self {
1836 self.content.lifetime = Duration::from_secs(time_in_seconds);
1837 self
1838 }
1839}
1840
1841impl EventBuilder<CallMemberEventContent> {
1842 pub fn set_livekit_focus(mut self, alias: String, service_url: String) -> Self {
1844 if let CallMemberEventContent::SessionContent(session_data) = &mut self.content {
1845 session_data.foci_preferred =
1846 vec![Focus::Livekit(LivekitFocus::new(alias, service_url))];
1847 } else {
1848 panic!("focus() called on a non-SessionContent call member event");
1849 }
1850 self
1851 }
1852
1853 pub fn application(mut self, call_id: String, scope: CallScope) -> Self {
1855 if let CallMemberEventContent::SessionContent(session_data) = &mut self.content {
1856 session_data.application =
1857 Application::Call(CallApplicationContent::new(call_id, scope));
1858 } else {
1859 panic!("application() called on a non-SessionContent call member event");
1860 }
1861 self
1862 }
1863
1864 pub fn leave(mut self) -> Self {
1866 self.content = CallMemberEventContent::new_empty(None);
1867 self
1868 }
1869}
1870pub struct ReadReceiptBuilder<'a> {
1871 factory: &'a EventFactory,
1872 content: ReceiptEventContent,
1873}
1874
1875impl ReadReceiptBuilder<'_> {
1876 pub fn add(
1878 self,
1879 event_id: &EventId,
1880 user_id: impl Into<OwnedUserId>,
1881 tyype: ReceiptType,
1882 thread: ReceiptThread,
1883 ) -> Self {
1884 let ts = self.factory.next_server_ts();
1885 self.add_with_timestamp(event_id, user_id, tyype, thread, Some(ts))
1886 }
1887
1888 pub fn add_with_timestamp(
1890 mut self,
1891 event_id: &EventId,
1892 user_id: impl Into<OwnedUserId>,
1893 tyype: ReceiptType,
1894 thread: ReceiptThread,
1895 ts: Option<MilliSecondsSinceUnixEpoch>,
1896 ) -> Self {
1897 let by_event = self.content.0.entry(event_id.to_owned()).or_default();
1898 let by_type = by_event.entry(tyype).or_default();
1899
1900 let mut receipt = Receipt::default();
1901 if let Some(ts) = ts {
1902 receipt.ts = Some(ts);
1903 }
1904 receipt.thread = thread;
1905
1906 by_type.insert(user_id.into(), receipt);
1907 self
1908 }
1909
1910 pub fn into_content(self) -> ReceiptEventContent {
1912 self.content
1913 }
1914
1915 pub fn into_event(self) -> EventBuilder<ReceiptEventContent> {
1917 self.factory.event(self.into_content()).format(EventFormat::Ephemeral)
1918 }
1919}
1920
1921pub struct PreviousMembership {
1922 state: MembershipState,
1923 avatar_url: Option<OwnedMxcUri>,
1924 display_name: Option<String>,
1925}
1926
1927impl PreviousMembership {
1928 pub fn new(state: MembershipState) -> Self {
1929 Self { state, avatar_url: None, display_name: None }
1930 }
1931
1932 pub fn avatar_url(mut self, url: &MxcUri) -> Self {
1933 self.avatar_url = Some(url.to_owned());
1934 self
1935 }
1936
1937 pub fn display_name(mut self, name: impl Into<String>) -> Self {
1938 self.display_name = Some(name.into());
1939 self
1940 }
1941}
1942
1943impl From<MembershipState> for PreviousMembership {
1944 fn from(state: MembershipState) -> Self {
1945 Self::new(state)
1946 }
1947}
1948
1949#[derive(Clone, Default, Debug, Serialize, EventContent)]
1950#[ruma_event(type = "rs.matrix-sdk.custom.test", kind = MessageLike)]
1951pub struct CustomMessageLikeEventContent;
1952
1953#[derive(Clone, Default, Debug, Serialize, Deserialize, EventContent)]
1954#[ruma_event(type = "rs.matrix-sdk.custom-state.test", kind = State, state_key_type = String)]
1955pub struct CustomStateEventContent;