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::Serialize;
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: &UserId) -> Self {
251 self.sender = Some(sender.to_owned());
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();
375 EventId::new_v2_or_v3(&base64_sha256_hash(&bytes)).unwrap()
376 });
377
378 map.insert("event_id".to_owned(), json!(event_id));
379 }
380
381 if let Some(unsigned) = self.unsigned {
382 map.insert("unsigned".to_owned(), json!(unsigned));
383 }
384
385 json
386 }
387
388 pub fn into_raw<T>(self) -> Raw<T> {
394 Raw::new(&self.construct_json()).unwrap().cast_unchecked()
395 }
396
397 pub fn into_raw_timeline(self) -> Raw<AnyTimelineEvent> {
398 self.into_raw()
399 }
400
401 pub fn into_any_sync_message_like_event(self) -> AnySyncMessageLikeEvent {
402 self.format(EventFormat::SyncTimeline)
403 .into_raw()
404 .deserialize()
405 .expect("expected message like event")
406 }
407
408 pub fn into_original_sync_room_message_event(self) -> OriginalSyncRoomMessageEvent {
409 self.format(EventFormat::SyncTimeline)
410 .into_raw()
411 .deserialize()
412 .expect("expected original sync room message event")
413 }
414
415 pub fn into_raw_sync(self) -> Raw<AnySyncTimelineEvent> {
416 self.format(EventFormat::SyncTimeline).into_raw()
417 }
418
419 pub fn into_raw_sync_state(self) -> Raw<AnySyncStateEvent> {
420 self.format(EventFormat::SyncTimeline).into_raw()
421 }
422
423 pub fn into_event(self) -> TimelineEvent {
424 TimelineEvent::from_plaintext(self.into_raw_sync())
425 }
426
427 pub fn into_content(self) -> serde_json::Value {
432 json!(self.content)
433 }
434}
435
436impl EventBuilder<RoomEncryptedEventContent> {
437 pub fn into_utd_sync_timeline_event(self) -> TimelineEvent {
440 let session_id = as_variant!(&self.content.scheme, EncryptedEventScheme::MegolmV1AesSha2)
441 .map(|content| content.session_id.clone());
442
443 TimelineEvent::from_utd(
444 self.into(),
445 UnableToDecryptInfo {
446 session_id,
447 reason: UnableToDecryptReason::MissingMegolmSession { withheld_code: None },
448 },
449 )
450 }
451}
452
453impl EventBuilder<RoomMessageEventContent> {
454 pub fn reply_to(mut self, event_id: &EventId) -> Self {
456 self.content.relates_to = Some(Relation::Reply(Reply::with_event_id(event_id.to_owned())));
457 self
458 }
459
460 pub fn in_thread(mut self, root: &EventId, latest_thread_event: &EventId) -> Self {
463 self.content.relates_to =
464 Some(Relation::Thread(Thread::plain(root.to_owned(), latest_thread_event.to_owned())));
465 self
466 }
467
468 pub fn in_thread_reply(mut self, root: &EventId, replied_to: &EventId) -> Self {
471 self.content.relates_to =
472 Some(Relation::Thread(Thread::reply(root.to_owned(), replied_to.to_owned())));
473 self
474 }
475
476 pub fn mentions(mut self, mentions: Mentions) -> Self {
478 self.content.mentions = Some(mentions);
479 self
480 }
481
482 pub fn edit(
485 mut self,
486 edited_event_id: &EventId,
487 new_content: RoomMessageEventContentWithoutRelation,
488 ) -> Self {
489 self.content.relates_to =
490 Some(Relation::Replacement(Replacement::new(edited_event_id.to_owned(), new_content)));
491 self
492 }
493
494 pub fn caption(
498 mut self,
499 caption: Option<String>,
500 formatted_caption: Option<FormattedBody>,
501 ) -> Self {
502 match &mut self.content.msgtype {
503 MessageType::Image(image) => {
504 let filename = image.filename().to_owned();
505 if let Some(caption) = caption {
506 image.body = caption;
507 image.filename = Some(filename);
508 } else {
509 image.body = filename;
510 image.filename = None;
511 }
512 image.formatted = formatted_caption;
513 }
514
515 MessageType::Audio(_) | MessageType::Video(_) | MessageType::File(_) => {
516 unimplemented!();
517 }
518
519 _ => panic!("unexpected event type for a caption"),
520 }
521
522 self
523 }
524}
525
526impl EventBuilder<UnstablePollStartEventContent> {
527 pub fn reply_to(mut self, event_id: &EventId) -> Self {
529 if let UnstablePollStartEventContent::New(content) = &mut self.content {
530 content.relates_to =
531 Some(RelationWithoutReplacement::Reply(Reply::with_event_id(event_id.to_owned())));
532 }
533 self
534 }
535
536 pub fn in_thread(mut self, root: &EventId, reply_to_event_id: &EventId) -> Self {
539 let thread = Thread::reply(root.to_owned(), reply_to_event_id.to_owned());
540
541 if let UnstablePollStartEventContent::New(content) = &mut self.content {
542 content.relates_to = Some(RelationWithoutReplacement::Thread(thread));
543 }
544 self
545 }
546}
547
548impl EventBuilder<RoomCreateEventContent> {
549 pub fn predecessor(mut self, room_id: &RoomId) -> Self {
551 self.content.predecessor = Some(PreviousRoom::new(room_id.to_owned()));
552 self
553 }
554
555 pub fn no_predecessor(mut self) -> Self {
557 self.content.predecessor = None;
558 self
559 }
560
561 pub fn with_space_type(mut self) -> Self {
563 self.content.room_type = Some(RoomType::Space);
564 self
565 }
566}
567
568impl EventBuilder<StickerEventContent> {
569 pub fn reply_thread(mut self, root: &EventId, reply_to_event: &EventId) -> Self {
571 self.content.relates_to =
572 Some(Relation::Thread(Thread::reply(root.to_owned(), reply_to_event.to_owned())));
573 self
574 }
575}
576
577impl<E: StaticEventContent<IsPrefix = False>> From<EventBuilder<E>> for Raw<AnySyncTimelineEvent>
578where
579 E: Serialize,
580{
581 fn from(val: EventBuilder<E>) -> Self {
582 val.into_raw_sync()
583 }
584}
585
586impl<E: StaticEventContent<IsPrefix = False>> From<EventBuilder<E>> for AnySyncTimelineEvent
587where
588 E: Serialize,
589{
590 fn from(val: EventBuilder<E>) -> Self {
591 Raw::<AnySyncTimelineEvent>::from(val).deserialize().expect("expected sync timeline event")
592 }
593}
594
595impl<E: StaticEventContent<IsPrefix = False>> From<EventBuilder<E>> for Raw<AnyTimelineEvent>
596where
597 E: Serialize,
598{
599 fn from(val: EventBuilder<E>) -> Self {
600 val.into_raw_timeline()
601 }
602}
603
604impl<E: StaticEventContent<IsPrefix = False>> From<EventBuilder<E>> for AnyTimelineEvent
605where
606 E: Serialize,
607{
608 fn from(val: EventBuilder<E>) -> Self {
609 Raw::<AnyTimelineEvent>::from(val).deserialize().expect("expected timeline event")
610 }
611}
612
613impl<E: StaticEventContent<IsPrefix = False>> From<EventBuilder<E>>
614 for Raw<AnyGlobalAccountDataEvent>
615where
616 E: Serialize,
617{
618 fn from(val: EventBuilder<E>) -> Self {
619 val.format(EventFormat::GlobalAccountData).into_raw()
620 }
621}
622
623impl<E: StaticEventContent<IsPrefix = False>> From<EventBuilder<E>> for AnyGlobalAccountDataEvent
624where
625 E: Serialize,
626{
627 fn from(val: EventBuilder<E>) -> Self {
628 Raw::<AnyGlobalAccountDataEvent>::from(val)
629 .deserialize()
630 .expect("expected global account data")
631 }
632}
633
634impl<E: StaticEventContent<IsPrefix = False>> From<EventBuilder<E>> for Raw<AnyRoomAccountDataEvent>
635where
636 E: Serialize,
637{
638 fn from(val: EventBuilder<E>) -> Self {
639 val.format(EventFormat::RoomAccountData).into_raw()
640 }
641}
642
643impl<E: StaticEventContent<IsPrefix = False>> From<EventBuilder<E>> for AnyRoomAccountDataEvent
644where
645 E: Serialize,
646{
647 fn from(val: EventBuilder<E>) -> Self {
648 Raw::<AnyRoomAccountDataEvent>::from(val).deserialize().expect("expected room account data")
649 }
650}
651
652impl<E: StaticEventContent<IsPrefix = False>> From<EventBuilder<E>> for TimelineEvent
653where
654 E: Serialize,
655{
656 fn from(val: EventBuilder<E>) -> Self {
657 val.into_event()
658 }
659}
660
661impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
662 for Raw<AnySyncStateEvent>
663{
664 fn from(val: EventBuilder<E>) -> Self {
665 val.format(EventFormat::SyncTimeline).into_raw()
666 }
667}
668
669impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
670 for AnySyncStateEvent
671{
672 fn from(val: EventBuilder<E>) -> Self {
673 Raw::<AnySyncStateEvent>::from(val).deserialize().expect("expected sync state")
674 }
675}
676
677impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
678 for Raw<SyncStateEvent<E>>
679where
680 E: StaticStateEventContent + RedactContent,
681 E::Redacted: RedactedStateEventContent,
682{
683 fn from(val: EventBuilder<E>) -> Self {
684 val.format(EventFormat::SyncTimeline).into_raw()
685 }
686}
687
688impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
689 for SyncStateEvent<E>
690where
691 E: StaticStateEventContent + RedactContent + EventContentFromType,
692 E::Redacted: RedactedStateEventContent<StateKey = <E as StateEventContent>::StateKey>
693 + EventContentFromType,
694{
695 fn from(val: EventBuilder<E>) -> Self {
696 Raw::<SyncStateEvent<E>>::from(val).deserialize().expect("expected sync state")
697 }
698}
699
700impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
701 for Raw<AnyStateEvent>
702{
703 fn from(val: EventBuilder<E>) -> Self {
704 val.into_raw()
705 }
706}
707
708impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
709 for AnyStateEvent
710{
711 fn from(val: EventBuilder<E>) -> Self {
712 Raw::<AnyStateEvent>::from(val).deserialize().expect("expected state")
713 }
714}
715
716impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
717 for Raw<StateEvent<E>>
718where
719 E: StaticStateEventContent + RedactContent,
720 E::Redacted: RedactedStateEventContent,
721{
722 fn from(val: EventBuilder<E>) -> Self {
723 val.into_raw()
724 }
725}
726
727impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
728 for StateEvent<E>
729where
730 E: StaticStateEventContent + RedactContent + EventContentFromType,
731 E::Redacted: RedactedStateEventContent<StateKey = <E as StateEventContent>::StateKey>
732 + EventContentFromType,
733{
734 fn from(val: EventBuilder<E>) -> Self {
735 Raw::<StateEvent<E>>::from(val).deserialize().expect("expected state")
736 }
737}
738
739impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
740 for Raw<AnyStrippedStateEvent>
741{
742 fn from(val: EventBuilder<E>) -> Self {
743 val.format(EventFormat::StrippedState).into_raw()
744 }
745}
746
747impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
748 for AnyStrippedStateEvent
749{
750 fn from(val: EventBuilder<E>) -> Self {
751 Raw::<AnyStrippedStateEvent>::from(val).deserialize().expect("expected stripped state")
752 }
753}
754
755impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
756 for Raw<StrippedStateEvent<E::PossiblyRedacted>>
757where
758 E: StaticStateEventContent,
759{
760 fn from(val: EventBuilder<E>) -> Self {
761 val.format(EventFormat::StrippedState).into_raw()
762 }
763}
764
765impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
766 for StrippedStateEvent<E::PossiblyRedacted>
767where
768 E: StaticStateEventContent,
769 E::PossiblyRedacted: PossiblyRedactedStateEventContent + EventContentFromType,
770{
771 fn from(val: EventBuilder<E>) -> Self {
772 Raw::<StrippedStateEvent<E::PossiblyRedacted>>::from(val)
773 .deserialize()
774 .expect("expected stripped state")
775 }
776}
777
778impl<E: StaticEventContent<IsPrefix = False> + EphemeralRoomEventContent> From<EventBuilder<E>>
779 for Raw<AnySyncEphemeralRoomEvent>
780{
781 fn from(val: EventBuilder<E>) -> Self {
782 val.format(EventFormat::Ephemeral).into_raw()
783 }
784}
785
786impl<E: StaticEventContent<IsPrefix = False> + EphemeralRoomEventContent> From<EventBuilder<E>>
787 for AnySyncEphemeralRoomEvent
788{
789 fn from(val: EventBuilder<E>) -> Self {
790 Raw::<AnySyncEphemeralRoomEvent>::from(val).deserialize().expect("expected ephemeral")
791 }
792}
793
794impl<E: StaticEventContent<IsPrefix = False> + MessageLikeEventContent> From<EventBuilder<E>>
795 for Raw<AnySyncMessageLikeEvent>
796{
797 fn from(val: EventBuilder<E>) -> Self {
798 val.format(EventFormat::SyncTimeline).into_raw()
799 }
800}
801
802impl<E: StaticEventContent<IsPrefix = False> + MessageLikeEventContent> From<EventBuilder<E>>
803 for AnySyncMessageLikeEvent
804{
805 fn from(val: EventBuilder<E>) -> Self {
806 Raw::<AnySyncMessageLikeEvent>::from(val).deserialize().expect("expected sync message-like")
807 }
808}
809
810impl<E: StaticEventContent<IsPrefix = False> + MessageLikeEventContent> From<EventBuilder<E>>
811 for Raw<SyncMessageLikeEvent<E>>
812where
813 E: RedactContent,
814 E::Redacted: RedactedMessageLikeEventContent,
815{
816 fn from(val: EventBuilder<E>) -> Self {
817 val.format(EventFormat::SyncTimeline).into_raw()
818 }
819}
820
821impl<E: StaticEventContent<IsPrefix = False> + MessageLikeEventContent> From<EventBuilder<E>>
822 for SyncMessageLikeEvent<E>
823where
824 E: RedactContent + EventContentFromType,
825 E::Redacted: RedactedMessageLikeEventContent + EventContentFromType,
826{
827 fn from(val: EventBuilder<E>) -> Self {
828 Raw::<SyncMessageLikeEvent<E>>::from(val).deserialize().expect("expected sync message-like")
829 }
830}
831
832impl<E: StaticEventContent<IsPrefix = False> + MessageLikeEventContent> From<EventBuilder<E>>
833 for Raw<AnyMessageLikeEvent>
834{
835 fn from(val: EventBuilder<E>) -> Self {
836 val.into_raw()
837 }
838}
839
840impl<E: StaticEventContent<IsPrefix = False> + MessageLikeEventContent> From<EventBuilder<E>>
841 for AnyMessageLikeEvent
842{
843 fn from(val: EventBuilder<E>) -> Self {
844 Raw::<AnyMessageLikeEvent>::from(val).deserialize().expect("expected message-like")
845 }
846}
847
848impl<E: StaticEventContent<IsPrefix = False> + MessageLikeEventContent> From<EventBuilder<E>>
849 for Raw<MessageLikeEvent<E>>
850where
851 E: RedactContent,
852 E::Redacted: RedactedMessageLikeEventContent,
853{
854 fn from(val: EventBuilder<E>) -> Self {
855 val.into_raw()
856 }
857}
858
859impl<E: StaticEventContent<IsPrefix = False> + MessageLikeEventContent> From<EventBuilder<E>>
860 for MessageLikeEvent<E>
861where
862 E: RedactContent + EventContentFromType,
863 E::Redacted: RedactedMessageLikeEventContent + EventContentFromType,
864{
865 fn from(val: EventBuilder<E>) -> Self {
866 Raw::<MessageLikeEvent<E>>::from(val).deserialize().expect("expected message-like")
867 }
868}
869
870#[derive(Debug, Default)]
871pub struct EventFactory {
872 next_ts: AtomicU64,
873 sender: Option<OwnedUserId>,
874 room: Option<OwnedRoomId>,
875}
876
877impl EventFactory {
878 pub fn new() -> Self {
879 Self { next_ts: AtomicU64::new(0), sender: None, room: None }
880 }
881
882 pub fn room(mut self, room_id: &RoomId) -> Self {
883 self.room = Some(room_id.to_owned());
884 self
885 }
886
887 pub fn sender(mut self, sender: &UserId) -> Self {
888 self.sender = Some(sender.to_owned());
889 self
890 }
891
892 pub fn server_ts(self, ts: u64) -> Self {
893 self.next_ts.store(ts, SeqCst);
894 self
895 }
896
897 fn next_server_ts(&self) -> MilliSecondsSinceUnixEpoch {
898 MilliSecondsSinceUnixEpoch(
899 self.next_ts
900 .fetch_add(1, SeqCst)
901 .try_into()
902 .expect("server timestamp should fit in js_int::UInt"),
903 )
904 }
905
906 pub fn event<E: StaticEventContent<IsPrefix = False>>(&self, content: E) -> EventBuilder<E> {
908 EventBuilder {
909 format: EventFormat::Timeline,
910 sender: self.sender.clone(),
911 room: self.room.clone(),
912 server_ts: self.next_server_ts(),
913 event_id: None,
914 no_event_id: false,
915 redacts: None,
916 content,
917 unsigned: None,
918 state_key: None,
919 }
920 }
921
922 pub fn text_msg(&self, content: impl Into<String>) -> EventBuilder<RoomMessageEventContent> {
924 self.event(RoomMessageEventContent::text_plain(content.into()))
925 }
926
927 pub fn emote(&self, content: impl Into<String>) -> EventBuilder<RoomMessageEventContent> {
929 self.event(RoomMessageEventContent::emote_plain(content.into()))
930 }
931
932 pub fn encrypted(
935 &self,
936 ciphertext: impl Into<String>,
937 sender_key: impl Into<String>,
938 device_id: impl Into<OwnedDeviceId>,
939 session_id: impl Into<String>,
940 ) -> EventBuilder<RoomEncryptedEventContent> {
941 self.event(RoomEncryptedEventContent::new(
942 EncryptedEventScheme::MegolmV1AesSha2(
943 MegolmV1AesSha2ContentInit {
944 ciphertext: ciphertext.into(),
945 sender_key: sender_key.into(),
946 device_id: device_id.into(),
947 session_id: session_id.into(),
948 }
949 .into(),
950 ),
951 None,
952 ))
953 }
954
955 pub fn member(&self, member: &UserId) -> EventBuilder<RoomMemberEventContent> {
985 let mut event = self.event(RoomMemberEventContent::new(MembershipState::Join));
986
987 if self.sender.is_some() {
988 event.sender = self.sender.clone();
989 } else {
990 event.sender = Some(member.to_owned());
991 }
992
993 event.state_key = Some(member.to_string());
994
995 event
996 }
997
998 pub fn room_tombstone(
1000 &self,
1001 body: impl Into<String>,
1002 replacement: &RoomId,
1003 ) -> EventBuilder<RoomTombstoneEventContent> {
1004 let mut event =
1005 self.event(RoomTombstoneEventContent::new(body.into(), replacement.to_owned()));
1006 event.state_key = Some("".to_owned());
1007 event
1008 }
1009
1010 pub fn room_topic(&self, topic: impl Into<String>) -> EventBuilder<RoomTopicEventContent> {
1012 let mut event = self.event(RoomTopicEventContent::new(topic.into()));
1013 event.state_key = Some("".to_owned());
1015 event
1016 }
1017
1018 pub fn room_name(&self, name: impl Into<String>) -> EventBuilder<RoomNameEventContent> {
1020 let mut event = self.event(RoomNameEventContent::new(name.into()));
1021 event.state_key = Some("".to_owned());
1023 event
1024 }
1025
1026 pub fn room_avatar(&self) -> EventBuilder<RoomAvatarEventContent> {
1028 let mut event = self.event(RoomAvatarEventContent::new());
1029 event.state_key = Some("".to_owned());
1031 event
1032 }
1033
1034 pub fn room_encryption(&self) -> EventBuilder<RoomEncryptionEventContent> {
1039 let mut event = self.event(RoomEncryptionEventContent::with_recommended_defaults());
1040 event.state_key = Some("".to_owned());
1042 event
1043 }
1044
1045 #[cfg(feature = "experimental-encrypted-state-events")]
1048 pub fn room_encryption_with_state_encryption(
1049 &self,
1050 ) -> EventBuilder<RoomEncryptionEventContent> {
1051 let mut content = RoomEncryptionEventContent::with_recommended_defaults();
1052 content.encrypt_state_events = true;
1053 let mut event = self.event(content);
1054 event.state_key = Some("".to_owned());
1055 event
1056 }
1057
1058 pub fn room_history_visibility(
1063 &self,
1064 visibility: HistoryVisibility,
1065 ) -> EventBuilder<RoomHistoryVisibilityEventContent> {
1066 let mut event = self.event(RoomHistoryVisibilityEventContent::new(visibility));
1067 event.state_key = Some("".to_owned());
1068 event
1069 }
1070
1071 pub fn room_join_rules(&self, join_rule: JoinRule) -> EventBuilder<RoomJoinRulesEventContent> {
1075 let mut event = self.event(RoomJoinRulesEventContent::new(join_rule));
1076 event.state_key = Some("".to_owned());
1077 event
1078 }
1079
1080 pub fn room_pinned_events(
1082 &self,
1083 pinned: Vec<OwnedEventId>,
1084 ) -> EventBuilder<RoomPinnedEventsEventContent> {
1085 self.event(RoomPinnedEventsEventContent::new(pinned)).state_key("")
1086 }
1087
1088 pub fn member_hints(
1109 &self,
1110 service_members: BTreeSet<OwnedUserId>,
1111 ) -> EventBuilder<MemberHintsEventContent> {
1112 self.event(MemberHintsEventContent::new(service_members)).state_key("")
1114 }
1115
1116 pub fn text_html(
1118 &self,
1119 plain: impl Into<String>,
1120 html: impl Into<String>,
1121 ) -> EventBuilder<RoomMessageEventContent> {
1122 self.event(RoomMessageEventContent::text_html(plain, html))
1123 }
1124
1125 pub fn notice(&self, content: impl Into<String>) -> EventBuilder<RoomMessageEventContent> {
1127 self.event(RoomMessageEventContent::notice_plain(content))
1128 }
1129
1130 pub fn reaction(
1132 &self,
1133 event_id: &EventId,
1134 annotation: impl Into<String>,
1135 ) -> EventBuilder<ReactionEventContent> {
1136 self.event(ReactionEventContent::new(Annotation::new(
1137 event_id.to_owned(),
1138 annotation.into(),
1139 )))
1140 }
1141
1142 pub fn redaction(&self, event_id: &EventId) -> EventBuilder<RoomRedactionEventContent> {
1147 let mut builder = self.event(RoomRedactionEventContent::new_v11(event_id.to_owned()));
1148 builder.redacts = Some(event_id.to_owned());
1149 builder
1150 }
1151
1152 pub fn redacted<T: StaticEventContent<IsPrefix = False> + RedactedMessageLikeEventContent>(
1155 &self,
1156 redacter: &UserId,
1157 content: T,
1158 ) -> EventBuilder<T> {
1159 let mut builder = self.event(content);
1160
1161 let redacted_because = RedactedBecause {
1162 content: RoomRedactionEventContent::default(),
1163 event_id: EventId::new_v1(server_name!("dummy.server")),
1164 sender: redacter.to_owned(),
1165 origin_server_ts: self.next_server_ts(),
1166 };
1167 builder.unsigned.get_or_insert_with(Default::default).redacted_because =
1168 Some(redacted_because);
1169
1170 builder
1171 }
1172
1173 pub fn redacted_state<T: StaticEventContent<IsPrefix = False> + RedactedStateEventContent>(
1176 &self,
1177 redacter: &UserId,
1178 state_key: impl Into<String>,
1179 content: T,
1180 ) -> EventBuilder<T> {
1181 let mut builder = self.event(content);
1182
1183 let redacted_because = RedactedBecause {
1184 content: RoomRedactionEventContent::default(),
1185 event_id: EventId::new_v1(server_name!("dummy.server")),
1186 sender: redacter.to_owned(),
1187 origin_server_ts: self.next_server_ts(),
1188 };
1189 builder.unsigned.get_or_insert_with(Default::default).redacted_because =
1190 Some(redacted_because);
1191 builder.state_key = Some(state_key.into());
1192
1193 builder
1194 }
1195
1196 pub fn poll_start(
1199 &self,
1200 fallback_text: impl Into<String>,
1201 poll_question: impl Into<String>,
1202 answers: Vec<impl Into<String>>,
1203 ) -> EventBuilder<UnstablePollStartEventContent> {
1204 let answers: Vec<UnstablePollAnswer> = answers
1206 .into_iter()
1207 .enumerate()
1208 .map(|(idx, answer)| UnstablePollAnswer::new(idx.to_string(), answer))
1209 .collect();
1210 let poll_answers = answers.try_into().unwrap();
1211 let poll_start_content =
1212 UnstablePollStartEventContent::New(NewUnstablePollStartEventContent::plain_text(
1213 fallback_text,
1214 UnstablePollStartContentBlock::new(poll_question, poll_answers),
1215 ));
1216 self.event(poll_start_content)
1217 }
1218
1219 pub fn poll_edit(
1221 &self,
1222 edited_event_id: &EventId,
1223 poll_question: impl Into<String>,
1224 answers: Vec<impl Into<String>>,
1225 ) -> EventBuilder<ReplacementUnstablePollStartEventContent> {
1226 let answers: Vec<UnstablePollAnswer> = answers
1228 .into_iter()
1229 .enumerate()
1230 .map(|(idx, answer)| UnstablePollAnswer::new(idx.to_string(), answer))
1231 .collect();
1232 let poll_answers = answers.try_into().unwrap();
1233 let poll_start_content_block =
1234 UnstablePollStartContentBlock::new(poll_question, poll_answers);
1235 self.event(ReplacementUnstablePollStartEventContent::new(
1236 poll_start_content_block,
1237 edited_event_id.to_owned(),
1238 ))
1239 }
1240
1241 pub fn poll_response(
1244 &self,
1245 answers: Vec<impl Into<String>>,
1246 poll_start_id: &EventId,
1247 ) -> EventBuilder<UnstablePollResponseEventContent> {
1248 self.event(UnstablePollResponseEventContent::new(
1249 answers.into_iter().map(Into::into).collect(),
1250 poll_start_id.to_owned(),
1251 ))
1252 }
1253
1254 pub fn poll_end(
1257 &self,
1258 content: impl Into<String>,
1259 poll_start_id: &EventId,
1260 ) -> EventBuilder<UnstablePollEndEventContent> {
1261 self.event(UnstablePollEndEventContent::new(content.into(), poll_start_id.to_owned()))
1262 }
1263
1264 pub fn image(
1267 &self,
1268 filename: String,
1269 url: OwnedMxcUri,
1270 ) -> EventBuilder<RoomMessageEventContent> {
1271 let image_event_content = ImageMessageEventContent::plain(filename, url);
1272 self.event(RoomMessageEventContent::new(MessageType::Image(image_event_content)))
1273 }
1274
1275 pub fn gallery(
1278 &self,
1279 body: String,
1280 filename: String,
1281 url: OwnedMxcUri,
1282 ) -> EventBuilder<RoomMessageEventContent> {
1283 let gallery_event_content = GalleryMessageEventContent::new(
1284 body,
1285 None,
1286 vec![GalleryItemType::Image(ImageMessageEventContent::plain(filename, url))],
1287 );
1288 self.event(RoomMessageEventContent::new(MessageType::Gallery(gallery_event_content)))
1289 }
1290
1291 pub fn typing(&self, user_ids: Vec<&UserId>) -> EventBuilder<TypingEventContent> {
1293 self.event(TypingEventContent::new(user_ids.into_iter().map(ToOwned::to_owned).collect()))
1294 .format(EventFormat::Ephemeral)
1295 }
1296
1297 pub fn read_receipts(&self) -> ReadReceiptBuilder<'_> {
1299 ReadReceiptBuilder { factory: self, content: ReceiptEventContent(Default::default()) }
1300 }
1301
1302 pub fn create(
1304 &self,
1305 creator_user_id: &UserId,
1306 room_version: RoomVersionId,
1307 ) -> EventBuilder<RoomCreateEventContent> {
1308 let mut event = self.event(RoomCreateEventContent::new_v1(creator_user_id.to_owned()));
1309 event.content.room_version = room_version;
1310
1311 if self.sender.is_some() {
1312 event.sender = self.sender.clone();
1313 } else {
1314 event.sender = Some(creator_user_id.to_owned());
1315 }
1316
1317 event.state_key = Some("".to_owned());
1318
1319 event
1320 }
1321
1322 pub fn power_levels(
1324 &self,
1325 map: &mut BTreeMap<OwnedUserId, Int>,
1326 ) -> EventBuilder<RoomPowerLevelsEventContent> {
1327 let mut content = RoomPowerLevelsEventContent::new(&AuthorizationRules::V1);
1328 content.users.append(map);
1329 let mut event = self.event(content);
1330 event.state_key = Some("".to_owned());
1331 event
1332 }
1333
1334 pub fn default_power_levels(&self) -> EventBuilder<RoomPowerLevelsEventContent> {
1336 self.power_levels(&mut BTreeMap::new())
1337 }
1338
1339 pub fn server_acl(
1341 &self,
1342 allow_ip_literals: bool,
1343 allow: Vec<String>,
1344 deny: Vec<String>,
1345 ) -> EventBuilder<RoomServerAclEventContent> {
1346 self.event(RoomServerAclEventContent::new(allow_ip_literals, allow, deny))
1347 }
1348
1349 pub fn canonical_alias(
1351 &self,
1352 alias: Option<OwnedRoomAliasId>,
1353 alt_aliases: Vec<OwnedRoomAliasId>,
1354 ) -> EventBuilder<RoomCanonicalAliasEventContent> {
1355 let mut content = RoomCanonicalAliasEventContent::new();
1356 content.alias = alias;
1357 content.alt_aliases = alt_aliases;
1358 let mut event = self.event(content);
1359 event.state_key = Some("".to_owned());
1361 event
1362 }
1363
1364 pub fn beacon(
1390 &self,
1391 beacon_info_event_id: OwnedEventId,
1392 latitude: f64,
1393 longitude: f64,
1394 uncertainty: u32,
1395 ts: Option<MilliSecondsSinceUnixEpoch>,
1396 ) -> EventBuilder<BeaconEventContent> {
1397 let geo_uri = format!("geo:{latitude},{longitude};u={uncertainty}");
1398 self.event(BeaconEventContent::new(beacon_info_event_id, geo_uri, ts))
1399 }
1400
1401 pub fn beacon_info(
1428 &self,
1429 description: Option<String>,
1430 duration: Duration,
1431 live: bool,
1432 ts: Option<MilliSecondsSinceUnixEpoch>,
1433 ) -> EventBuilder<BeaconInfoEventContent> {
1434 self.event(BeaconInfoEventContent::new(description, duration, live, ts))
1435 }
1436
1437 pub fn sticker(
1439 &self,
1440 body: impl Into<String>,
1441 info: ImageInfo,
1442 url: OwnedMxcUri,
1443 ) -> EventBuilder<StickerEventContent> {
1444 self.event(StickerEventContent::new(body.into(), info, url))
1445 }
1446
1447 pub fn call_invite(
1449 &self,
1450 call_id: OwnedVoipId,
1451 lifetime: UInt,
1452 offer: SessionDescription,
1453 version: VoipVersionId,
1454 ) -> EventBuilder<CallInviteEventContent> {
1455 self.event(CallInviteEventContent::new(call_id, lifetime, offer, version))
1456 }
1457
1458 pub fn rtc_notification(
1460 &self,
1461 notification_type: NotificationType,
1462 ) -> EventBuilder<RtcNotificationEventContent> {
1463 self.event(RtcNotificationEventContent::new(
1464 MilliSecondsSinceUnixEpoch::now(),
1465 Duration::new(30, 0),
1466 notification_type,
1467 ))
1468 }
1469
1470 pub fn call_decline(
1472 &self,
1473 notification_event_id: &EventId,
1474 ) -> EventBuilder<RtcDeclineEventContent> {
1475 self.event(RtcDeclineEventContent::new(notification_event_id))
1476 }
1477
1478 pub fn call_membership_state(
1499 &self,
1500 user_id: OwnedUserId,
1501 device_id: String,
1502 ) -> EventBuilder<CallMemberEventContent> {
1503 let event = self.event(CallMemberEventContent::new(
1504 Application::Call(CallApplicationContent::new("".to_owned(), CallScope::Room)),
1505 owned_device_id!(device_id.clone()),
1506 ActiveFocus::Livekit(ActiveLivekitFocus::new()),
1507 vec![],
1508 Some(MilliSecondsSinceUnixEpoch::now()),
1509 Some(Duration::from_secs(3600)),
1510 ));
1511 event
1512 .sender(&user_id)
1513 .state_key(CallMemberStateKey::new(user_id, device_id.into(), true).as_ref())
1514 }
1515
1516 pub fn direct(&self) -> EventBuilder<DirectEventContent> {
1518 self.global_account_data(DirectEventContent::default())
1519 }
1520
1521 pub fn ignored_user_list(
1523 &self,
1524 users: impl IntoIterator<Item = OwnedUserId>,
1525 ) -> EventBuilder<IgnoredUserListEventContent> {
1526 self.global_account_data(IgnoredUserListEventContent::users(users))
1527 }
1528
1529 pub fn push_rules(&self, rules: Ruleset) -> EventBuilder<PushRulesEventContent> {
1531 self.global_account_data(PushRulesEventContent::new(rules))
1532 }
1533
1534 pub fn space_child(
1536 &self,
1537 parent: OwnedRoomId,
1538 child: OwnedRoomId,
1539 ) -> EventBuilder<SpaceChildEventContent> {
1540 let mut event = self.event(SpaceChildEventContent::new(vec![]));
1541 event.room = Some(parent);
1542 event.state_key = Some(child.to_string());
1543 event
1544 }
1545
1546 pub fn space_parent(
1548 &self,
1549 parent: OwnedRoomId,
1550 child: OwnedRoomId,
1551 ) -> EventBuilder<SpaceParentEventContent> {
1552 let mut event = self.event(SpaceParentEventContent::new(vec![]));
1553 event.state_key = Some(parent.to_string());
1554 event.room = Some(child);
1555 event
1556 }
1557
1558 pub fn custom_message_like_event(&self) -> EventBuilder<CustomMessageLikeEventContent> {
1560 self.event(CustomMessageLikeEventContent)
1561 }
1562
1563 pub fn set_next_ts(&self, value: u64) {
1567 self.next_ts.store(value, SeqCst);
1568 }
1569
1570 pub fn global_account_data<C>(&self, content: C) -> EventBuilder<C>
1572 where
1573 C: GlobalAccountDataEventContent + StaticEventContent<IsPrefix = False>,
1574 {
1575 self.event(content).format(EventFormat::GlobalAccountData)
1576 }
1577
1578 pub fn room_account_data<C>(&self, content: C) -> EventBuilder<C>
1580 where
1581 C: RoomAccountDataEventContent + StaticEventContent<IsPrefix = False>,
1582 {
1583 self.event(content).format(EventFormat::RoomAccountData)
1584 }
1585
1586 pub fn fully_read(&self, event_id: &EventId) -> EventBuilder<FullyReadEventContent> {
1588 self.room_account_data(FullyReadEventContent::new(event_id.to_owned()))
1589 }
1590
1591 pub fn marked_unread(&self, unread: bool) -> EventBuilder<MarkedUnreadEventContent> {
1593 self.room_account_data(MarkedUnreadEventContent::new(unread))
1594 }
1595
1596 pub fn tag(&self, tags: Tags) -> EventBuilder<TagEventContent> {
1598 self.room_account_data(tags.into())
1599 }
1600
1601 pub fn space_order(&self, order: &str) -> EventBuilder<SpaceOrderEventContent> {
1604 let order = ruma::SpaceChildOrder::parse(order).expect("order should be valid");
1605 self.room_account_data(SpaceOrderEventContent::new(order))
1606 }
1607
1608 pub fn presence(&self, state: PresenceState) -> PresenceBuilder {
1613 PresenceBuilder { sender: self.sender.clone(), content: PresenceEventContent::new(state) }
1614 }
1615}
1616
1617#[derive(Debug)]
1619pub struct PresenceBuilder {
1620 sender: Option<OwnedUserId>,
1621 content: PresenceEventContent,
1622}
1623
1624impl PresenceBuilder {
1625 pub fn sender(mut self, sender: &UserId) -> Self {
1627 self.sender = Some(sender.to_owned());
1628 self
1629 }
1630
1631 pub fn avatar_url(mut self, url: &MxcUri) -> Self {
1633 self.content.avatar_url = Some(url.to_owned());
1634 self
1635 }
1636
1637 pub fn currently_active(mut self, active: bool) -> Self {
1639 self.content.currently_active = Some(active);
1640 self
1641 }
1642
1643 pub fn last_active_ago(mut self, ms: u64) -> Self {
1645 self.content.last_active_ago = Some(UInt::try_from(ms).unwrap());
1646 self
1647 }
1648
1649 pub fn status_msg(mut self, msg: impl Into<String>) -> Self {
1651 self.content.status_msg = Some(msg.into());
1652 self
1653 }
1654
1655 pub fn display_name(mut self, name: impl Into<String>) -> Self {
1657 self.content.displayname = Some(name.into());
1658 self
1659 }
1660}
1661
1662impl From<PresenceBuilder> for Raw<PresenceEvent> {
1663 fn from(builder: PresenceBuilder) -> Self {
1664 let sender = builder.sender.expect("sender must be set for presence events");
1665 let event = PresenceEvent { content: builder.content, sender };
1666 Raw::new(&event).unwrap().cast_unchecked()
1667 }
1668}
1669
1670impl EventBuilder<DirectEventContent> {
1671 pub fn add_user(mut self, user_id: OwnedDirectUserIdentifier, room_id: &RoomId) -> Self {
1673 self.content.0.entry(user_id).or_default().push(room_id.to_owned());
1674 self
1675 }
1676}
1677
1678impl EventBuilder<RoomMemberEventContent> {
1679 pub fn membership(mut self, state: MembershipState) -> Self {
1684 self.content.membership = state;
1685 self
1686 }
1687
1688 pub fn invited(mut self, invited_user: &UserId) -> Self {
1691 assert_ne!(
1692 self.sender.as_deref().unwrap(),
1693 invited_user,
1694 "invited user and sender can't be the same person"
1695 );
1696 self.content.membership = MembershipState::Invite;
1697 self.state_key = Some(invited_user.to_string());
1698 self
1699 }
1700
1701 pub fn leave(mut self) -> Self {
1705 self.content.membership = MembershipState::Leave;
1706 self.state_key = Some(self.sender.as_ref().expect("sender must be set").to_string());
1707 self
1708 }
1709
1710 pub fn kicked(mut self, kicked_user: &UserId) -> Self {
1713 assert_ne!(
1714 self.sender.as_deref().unwrap(),
1715 kicked_user,
1716 "kicked user and sender can't be the same person, otherwise it's just a Leave"
1717 );
1718 self.content.membership = MembershipState::Leave;
1719 self.state_key = Some(kicked_user.to_string());
1720 self
1721 }
1722
1723 pub fn banned(mut self, banned_user: &UserId) -> Self {
1726 assert_ne!(
1727 self.sender.as_deref().unwrap(),
1728 banned_user,
1729 "a user can't ban itself" );
1731 self.content.membership = MembershipState::Ban;
1732 self.state_key = Some(banned_user.to_string());
1733 self
1734 }
1735
1736 pub fn display_name(mut self, display_name: impl Into<String>) -> Self {
1738 self.content.displayname = Some(display_name.into());
1739 self
1740 }
1741
1742 pub fn avatar_url(mut self, url: &MxcUri) -> Self {
1744 self.content.avatar_url = Some(url.to_owned());
1745 self
1746 }
1747
1748 pub fn reason(mut self, reason: impl Into<String>) -> Self {
1750 self.content.reason = Some(reason.into());
1751 self
1752 }
1753
1754 pub fn previous(mut self, previous: impl Into<PreviousMembership>) -> Self {
1756 let previous = previous.into();
1757
1758 let mut prev_content = RoomMemberEventContent::new(previous.state);
1759 if let Some(avatar_url) = previous.avatar_url {
1760 prev_content.avatar_url = Some(avatar_url);
1761 }
1762 if let Some(display_name) = previous.display_name {
1763 prev_content.displayname = Some(display_name);
1764 }
1765
1766 self.unsigned.get_or_insert_with(Default::default).prev_content = Some(prev_content);
1767 self
1768 }
1769
1770 pub fn invite_room_state<I, E>(mut self, events: I) -> Self
1775 where
1776 I: IntoIterator<Item = E>,
1777 E: Into<Raw<AnyStrippedStateEvent>>,
1778 {
1779 self.unsigned.get_or_insert_with(Default::default).invite_room_state =
1780 Some(events.into_iter().map(Into::into).collect());
1781 self
1782 }
1783}
1784
1785impl EventBuilder<RoomAvatarEventContent> {
1786 pub fn url(mut self, url: &MxcUri) -> Self {
1788 self.content.url = Some(url.to_owned());
1789 self
1790 }
1791
1792 pub fn info(mut self, image: avatar::ImageInfo) -> Self {
1794 self.content.info = Some(Box::new(image));
1795 self
1796 }
1797}
1798
1799impl EventBuilder<RtcNotificationEventContent> {
1800 pub fn mentions(mut self, users: impl IntoIterator<Item = OwnedUserId>) -> Self {
1801 self.content.mentions = Some(Mentions::with_user_ids(users));
1802 self
1803 }
1804
1805 pub fn call_intent(mut self, call_intent: CallIntent) -> Self {
1806 self.content.call_intent = Some(call_intent);
1807 self
1808 }
1809
1810 pub fn relates_to_membership_state_event(mut self, event_id: OwnedEventId) -> Self {
1811 self.content.relates_to = Some(Reference::new(event_id));
1812 self
1813 }
1814
1815 pub fn lifetime(mut self, time_in_seconds: u64) -> Self {
1816 self.content.lifetime = Duration::from_secs(time_in_seconds);
1817 self
1818 }
1819}
1820
1821impl EventBuilder<CallMemberEventContent> {
1822 pub fn set_livekit_focus(mut self, alias: String, service_url: String) -> Self {
1824 if let CallMemberEventContent::SessionContent(session_data) = &mut self.content {
1825 session_data.foci_preferred =
1826 vec![Focus::Livekit(LivekitFocus::new(alias, service_url))];
1827 } else {
1828 panic!("focus() called on a non-SessionContent call member event");
1829 }
1830 self
1831 }
1832
1833 pub fn application(mut self, call_id: String, scope: CallScope) -> Self {
1835 if let CallMemberEventContent::SessionContent(session_data) = &mut self.content {
1836 session_data.application =
1837 Application::Call(CallApplicationContent::new(call_id, scope));
1838 } else {
1839 panic!("application() called on a non-SessionContent call member event");
1840 }
1841 self
1842 }
1843
1844 pub fn leave(mut self) -> Self {
1846 self.content = CallMemberEventContent::new_empty(None);
1847 self
1848 }
1849}
1850pub struct ReadReceiptBuilder<'a> {
1851 factory: &'a EventFactory,
1852 content: ReceiptEventContent,
1853}
1854
1855impl ReadReceiptBuilder<'_> {
1856 pub fn add(
1858 self,
1859 event_id: &EventId,
1860 user_id: &UserId,
1861 tyype: ReceiptType,
1862 thread: ReceiptThread,
1863 ) -> Self {
1864 let ts = self.factory.next_server_ts();
1865 self.add_with_timestamp(event_id, user_id, tyype, thread, Some(ts))
1866 }
1867
1868 pub fn add_with_timestamp(
1870 mut self,
1871 event_id: &EventId,
1872 user_id: &UserId,
1873 tyype: ReceiptType,
1874 thread: ReceiptThread,
1875 ts: Option<MilliSecondsSinceUnixEpoch>,
1876 ) -> Self {
1877 let by_event = self.content.0.entry(event_id.to_owned()).or_default();
1878 let by_type = by_event.entry(tyype).or_default();
1879
1880 let mut receipt = Receipt::default();
1881 if let Some(ts) = ts {
1882 receipt.ts = Some(ts);
1883 }
1884 receipt.thread = thread;
1885
1886 by_type.insert(user_id.to_owned(), receipt);
1887 self
1888 }
1889
1890 pub fn into_content(self) -> ReceiptEventContent {
1892 self.content
1893 }
1894
1895 pub fn into_event(self) -> EventBuilder<ReceiptEventContent> {
1897 self.factory.event(self.into_content()).format(EventFormat::Ephemeral)
1898 }
1899}
1900
1901pub struct PreviousMembership {
1902 state: MembershipState,
1903 avatar_url: Option<OwnedMxcUri>,
1904 display_name: Option<String>,
1905}
1906
1907impl PreviousMembership {
1908 pub fn new(state: MembershipState) -> Self {
1909 Self { state, avatar_url: None, display_name: None }
1910 }
1911
1912 pub fn avatar_url(mut self, url: &MxcUri) -> Self {
1913 self.avatar_url = Some(url.to_owned());
1914 self
1915 }
1916
1917 pub fn display_name(mut self, name: impl Into<String>) -> Self {
1918 self.display_name = Some(name.into());
1919 self
1920 }
1921}
1922
1923impl From<MembershipState> for PreviousMembership {
1924 fn from(state: MembershipState) -> Self {
1925 Self::new(state)
1926 }
1927}
1928
1929#[derive(Clone, Default, Debug, Serialize, EventContent)]
1930#[ruma_event(type = "rs.matrix-sdk.custom.test", kind = MessageLike)]
1931pub struct CustomMessageLikeEventContent;