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::{SessionDescription, invite::CallInviteEventContent},
43 direct::{DirectEventContent, OwnedDirectUserIdentifier},
44 fully_read::FullyReadEventContent,
45 ignored_user_list::IgnoredUserListEventContent,
46 macros::EventContent,
47 marked_unread::MarkedUnreadEventContent,
48 member_hints::MemberHintsEventContent,
49 poll::{
50 unstable_end::UnstablePollEndEventContent,
51 unstable_response::UnstablePollResponseEventContent,
52 unstable_start::{
53 NewUnstablePollStartEventContent, ReplacementUnstablePollStartEventContent,
54 UnstablePollAnswer, UnstablePollStartContentBlock, UnstablePollStartEventContent,
55 },
56 },
57 presence::{PresenceEvent, PresenceEventContent},
58 push_rules::PushRulesEventContent,
59 reaction::ReactionEventContent,
60 receipt::{Receipt, ReceiptEventContent, ReceiptThread, ReceiptType},
61 relation::{Annotation, BundledThread, Reference, Replacement, Reply, Thread},
62 room::{
63 ImageInfo,
64 avatar::{self, RoomAvatarEventContent},
65 canonical_alias::RoomCanonicalAliasEventContent,
66 create::{PreviousRoom, RoomCreateEventContent},
67 encrypted::{
68 EncryptedEventScheme, MegolmV1AesSha2ContentInit, RoomEncryptedEventContent,
69 },
70 encryption::RoomEncryptionEventContent,
71 history_visibility::{HistoryVisibility, RoomHistoryVisibilityEventContent},
72 join_rules::{JoinRule, RoomJoinRulesEventContent},
73 member::{MembershipState, RoomMemberEventContent},
74 message::{
75 FormattedBody, GalleryItemType, GalleryMessageEventContent,
76 ImageMessageEventContent, MessageType, OriginalSyncRoomMessageEvent, Relation,
77 RelationWithoutReplacement, RoomMessageEventContent,
78 RoomMessageEventContentWithoutRelation,
79 },
80 name::RoomNameEventContent,
81 pinned_events::RoomPinnedEventsEventContent,
82 power_levels::RoomPowerLevelsEventContent,
83 redaction::RoomRedactionEventContent,
84 server_acl::RoomServerAclEventContent,
85 tombstone::RoomTombstoneEventContent,
86 topic::RoomTopicEventContent,
87 },
88 rtc::{
89 decline::RtcDeclineEventContent,
90 notification::{CallIntent, NotificationType, RtcNotificationEventContent},
91 },
92 space::{child::SpaceChildEventContent, parent::SpaceParentEventContent},
93 space_order::SpaceOrderEventContent,
94 sticker::StickerEventContent,
95 tag::{TagEventContent, Tags},
96 typing::TypingEventContent,
97 },
98 presence::PresenceState,
99 push::Ruleset,
100 room::RoomType,
101 room_version_rules::AuthorizationRules,
102 serde::Raw,
103 server_name,
104};
105use serde::Serialize;
106use serde_json::json;
107
108use crate::base64_sha256_hash;
109
110pub trait TimestampArg {
111 fn to_milliseconds_since_unix_epoch(self) -> MilliSecondsSinceUnixEpoch;
112}
113
114impl TimestampArg for MilliSecondsSinceUnixEpoch {
115 fn to_milliseconds_since_unix_epoch(self) -> MilliSecondsSinceUnixEpoch {
116 self
117 }
118}
119
120impl TimestampArg for u64 {
121 fn to_milliseconds_since_unix_epoch(self) -> MilliSecondsSinceUnixEpoch {
122 MilliSecondsSinceUnixEpoch(UInt::try_from(self).unwrap())
123 }
124}
125
126#[derive(Debug, Serialize)]
128struct RedactedBecause {
129 content: RoomRedactionEventContent,
131
132 event_id: OwnedEventId,
134
135 sender: OwnedUserId,
137
138 origin_server_ts: MilliSecondsSinceUnixEpoch,
141}
142
143#[derive(Debug, Serialize)]
144struct Unsigned<C: StaticEventContent> {
145 #[serde(skip_serializing_if = "Option::is_none")]
146 prev_content: Option<C>,
147
148 #[serde(skip_serializing_if = "Option::is_none")]
149 transaction_id: Option<OwnedTransactionId>,
150
151 #[serde(rename = "m.relations", skip_serializing_if = "Option::is_none")]
152 relations: Option<BundledMessageLikeRelations<Raw<AnySyncTimelineEvent>>>,
153
154 #[serde(skip_serializing_if = "Option::is_none")]
155 redacted_because: Option<RedactedBecause>,
156
157 #[serde(skip_serializing_if = "Option::is_none")]
158 age: Option<Int>,
159
160 #[serde(skip_serializing_if = "Option::is_none")]
161 invite_room_state: Option<Vec<Raw<AnyStrippedStateEvent>>>,
162}
163
164impl<C: StaticEventContent> Default for Unsigned<C> {
166 fn default() -> Self {
167 Self {
168 prev_content: None,
169 transaction_id: None,
170 relations: None,
171 redacted_because: None,
172 age: None,
173 invite_room_state: None,
174 }
175 }
176}
177
178#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
179enum EventFormat {
180 #[default]
183 Timeline,
184 SyncTimeline,
186 StrippedState,
188 Ephemeral,
190 GlobalAccountData,
192 RoomAccountData,
194}
195
196impl EventFormat {
197 fn has_sender(self) -> bool {
199 matches!(self, Self::Timeline | Self::SyncTimeline | Self::StrippedState)
200 }
201
202 fn has_event_id(self) -> bool {
204 matches!(self, Self::Timeline | Self::SyncTimeline)
205 }
206
207 fn has_room_id(self) -> bool {
209 matches!(self, Self::Timeline)
210 }
211}
212
213#[derive(Debug)]
214pub struct EventBuilder<C: StaticEventContent<IsPrefix = False>> {
215 format: EventFormat,
219 sender: Option<OwnedUserId>,
220 room: Option<OwnedRoomId>,
221 event_id: Option<OwnedEventId>,
222 no_event_id: bool,
224 redacts: Option<OwnedEventId>,
225 content: C,
226 server_ts: MilliSecondsSinceUnixEpoch,
227 unsigned: Option<Unsigned<C>>,
228 state_key: Option<String>,
229}
230
231impl<E: StaticEventContent<IsPrefix = False>> EventBuilder<E> {
232 fn format(mut self, format: EventFormat) -> Self {
233 self.format = format;
234 self
235 }
236
237 pub fn room(mut self, room_id: &RoomId) -> Self {
238 self.room = Some(room_id.to_owned());
239 self
240 }
241
242 pub fn sender(mut self, sender: &UserId) -> Self {
243 self.sender = Some(sender.to_owned());
244 self
245 }
246
247 pub fn event_id(mut self, event_id: &EventId) -> Self {
248 self.event_id = Some(event_id.to_owned());
249 self.no_event_id = false;
250 self
251 }
252
253 pub fn no_event_id(mut self) -> Self {
254 self.event_id = None;
255 self.no_event_id = true;
256 self
257 }
258
259 pub fn server_ts(mut self, ts: impl TimestampArg) -> Self {
260 self.server_ts = ts.to_milliseconds_since_unix_epoch();
261 self
262 }
263
264 pub fn unsigned_transaction_id(mut self, transaction_id: &TransactionId) -> Self {
265 self.unsigned.get_or_insert_with(Default::default).transaction_id =
266 Some(transaction_id.to_owned());
267 self
268 }
269
270 pub fn age(mut self, age: impl Into<Int>) -> Self {
272 self.unsigned.get_or_insert_with(Default::default).age = Some(age.into());
273 self
274 }
275
276 pub fn prev_content(mut self, prev: E) -> Self {
278 self.unsigned.get_or_insert_with(Default::default).prev_content = Some(prev);
279 self
280 }
281
282 pub fn with_bundled_thread_summary(
285 mut self,
286 latest_event: Raw<AnySyncMessageLikeEvent>,
287 count: usize,
288 current_user_participated: bool,
289 ) -> Self {
290 let relations = self
291 .unsigned
292 .get_or_insert_with(Default::default)
293 .relations
294 .get_or_insert_with(BundledMessageLikeRelations::new);
295 relations.thread = Some(Box::new(BundledThread::new(
296 latest_event,
297 UInt::try_from(count).unwrap(),
298 current_user_participated,
299 )));
300 self
301 }
302
303 pub fn with_bundled_edit(mut self, replacement: impl Into<Raw<AnySyncTimelineEvent>>) -> Self {
305 let relations = self
306 .unsigned
307 .get_or_insert_with(Default::default)
308 .relations
309 .get_or_insert_with(BundledMessageLikeRelations::new);
310 relations.replace = Some(Box::new(replacement.into()));
311 self
312 }
313
314 pub fn state_key(mut self, state_key: impl Into<String>) -> Self {
319 self.state_key = Some(state_key.into());
320 self
321 }
322}
323
324impl<E> EventBuilder<E>
325where
326 E: StaticEventContent<IsPrefix = False> + Serialize,
327{
328 #[inline(always)]
329 fn construct_json(self) -> serde_json::Value {
330 let mut json = json!({
331 "type": E::TYPE,
332 "content": self.content,
333 "origin_server_ts": self.server_ts,
334 });
335
336 let map = json.as_object_mut().unwrap();
337
338 if self.format.has_sender() {
339 let sender = self
342 .sender
343 .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");
344 map.insert("sender".to_owned(), json!(sender));
345 }
346
347 if self.format.has_room_id() {
348 let room_id = self.room.expect("TimelineEvent requires a room id");
349 map.insert("room_id".to_owned(), json!(room_id));
350 }
351
352 if let Some(redacts) = self.redacts {
353 map.insert("redacts".to_owned(), json!(redacts));
354 }
355
356 if let Some(state_key) = self.state_key {
357 map.insert("state_key".to_owned(), json!(state_key));
358 }
359
360 if self.format.has_event_id() && !self.no_event_id {
361 let event_id = self.event_id.unwrap_or_else(|| {
362 let bytes = serde_json::to_vec(&map).unwrap();
367 EventId::new_v2_or_v3(&base64_sha256_hash(&bytes)).unwrap()
368 });
369
370 map.insert("event_id".to_owned(), json!(event_id));
371 }
372
373 if let Some(unsigned) = self.unsigned {
374 map.insert("unsigned".to_owned(), json!(unsigned));
375 }
376
377 json
378 }
379
380 pub fn into_raw<T>(self) -> Raw<T> {
386 Raw::new(&self.construct_json()).unwrap().cast_unchecked()
387 }
388
389 pub fn into_raw_timeline(self) -> Raw<AnyTimelineEvent> {
390 self.into_raw()
391 }
392
393 pub fn into_any_sync_message_like_event(self) -> AnySyncMessageLikeEvent {
394 self.format(EventFormat::SyncTimeline)
395 .into_raw()
396 .deserialize()
397 .expect("expected message like event")
398 }
399
400 pub fn into_original_sync_room_message_event(self) -> OriginalSyncRoomMessageEvent {
401 self.format(EventFormat::SyncTimeline)
402 .into_raw()
403 .deserialize()
404 .expect("expected original sync room message event")
405 }
406
407 pub fn into_raw_sync(self) -> Raw<AnySyncTimelineEvent> {
408 self.format(EventFormat::SyncTimeline).into_raw()
409 }
410
411 pub fn into_raw_sync_state(self) -> Raw<AnySyncStateEvent> {
412 self.format(EventFormat::SyncTimeline).into_raw()
413 }
414
415 pub fn into_event(self) -> TimelineEvent {
416 TimelineEvent::from_plaintext(self.into_raw_sync())
417 }
418
419 pub fn into_content(self) -> serde_json::Value {
424 json!(self.content)
425 }
426}
427
428impl EventBuilder<RoomEncryptedEventContent> {
429 pub fn into_utd_sync_timeline_event(self) -> TimelineEvent {
432 let session_id = as_variant!(&self.content.scheme, EncryptedEventScheme::MegolmV1AesSha2)
433 .map(|content| content.session_id.clone());
434
435 TimelineEvent::from_utd(
436 self.into(),
437 UnableToDecryptInfo {
438 session_id,
439 reason: UnableToDecryptReason::MissingMegolmSession { withheld_code: None },
440 },
441 )
442 }
443}
444
445impl EventBuilder<RoomMessageEventContent> {
446 pub fn reply_to(mut self, event_id: &EventId) -> Self {
448 self.content.relates_to = Some(Relation::Reply(Reply::with_event_id(event_id.to_owned())));
449 self
450 }
451
452 pub fn in_thread(mut self, root: &EventId, latest_thread_event: &EventId) -> Self {
455 self.content.relates_to =
456 Some(Relation::Thread(Thread::plain(root.to_owned(), latest_thread_event.to_owned())));
457 self
458 }
459
460 pub fn in_thread_reply(mut self, root: &EventId, replied_to: &EventId) -> Self {
463 self.content.relates_to =
464 Some(Relation::Thread(Thread::reply(root.to_owned(), replied_to.to_owned())));
465 self
466 }
467
468 pub fn mentions(mut self, mentions: Mentions) -> Self {
470 self.content.mentions = Some(mentions);
471 self
472 }
473
474 pub fn edit(
477 mut self,
478 edited_event_id: &EventId,
479 new_content: RoomMessageEventContentWithoutRelation,
480 ) -> Self {
481 self.content.relates_to =
482 Some(Relation::Replacement(Replacement::new(edited_event_id.to_owned(), new_content)));
483 self
484 }
485
486 pub fn caption(
490 mut self,
491 caption: Option<String>,
492 formatted_caption: Option<FormattedBody>,
493 ) -> Self {
494 match &mut self.content.msgtype {
495 MessageType::Image(image) => {
496 let filename = image.filename().to_owned();
497 if let Some(caption) = caption {
498 image.body = caption;
499 image.filename = Some(filename);
500 } else {
501 image.body = filename;
502 image.filename = None;
503 }
504 image.formatted = formatted_caption;
505 }
506
507 MessageType::Audio(_) | MessageType::Video(_) | MessageType::File(_) => {
508 unimplemented!();
509 }
510
511 _ => panic!("unexpected event type for a caption"),
512 }
513
514 self
515 }
516}
517
518impl EventBuilder<UnstablePollStartEventContent> {
519 pub fn reply_to(mut self, event_id: &EventId) -> Self {
521 if let UnstablePollStartEventContent::New(content) = &mut self.content {
522 content.relates_to =
523 Some(RelationWithoutReplacement::Reply(Reply::with_event_id(event_id.to_owned())));
524 }
525 self
526 }
527
528 pub fn in_thread(mut self, root: &EventId, reply_to_event_id: &EventId) -> Self {
531 let thread = Thread::reply(root.to_owned(), reply_to_event_id.to_owned());
532
533 if let UnstablePollStartEventContent::New(content) = &mut self.content {
534 content.relates_to = Some(RelationWithoutReplacement::Thread(thread));
535 }
536 self
537 }
538}
539
540impl EventBuilder<RoomCreateEventContent> {
541 pub fn predecessor(mut self, room_id: &RoomId) -> Self {
543 self.content.predecessor = Some(PreviousRoom::new(room_id.to_owned()));
544 self
545 }
546
547 pub fn no_predecessor(mut self) -> Self {
549 self.content.predecessor = None;
550 self
551 }
552
553 pub fn with_space_type(mut self) -> Self {
555 self.content.room_type = Some(RoomType::Space);
556 self
557 }
558}
559
560impl EventBuilder<StickerEventContent> {
561 pub fn reply_thread(mut self, root: &EventId, reply_to_event: &EventId) -> Self {
563 self.content.relates_to =
564 Some(Relation::Thread(Thread::reply(root.to_owned(), reply_to_event.to_owned())));
565 self
566 }
567}
568
569impl<E: StaticEventContent<IsPrefix = False>> From<EventBuilder<E>> for Raw<AnySyncTimelineEvent>
570where
571 E: Serialize,
572{
573 fn from(val: EventBuilder<E>) -> Self {
574 val.into_raw_sync()
575 }
576}
577
578impl<E: StaticEventContent<IsPrefix = False>> From<EventBuilder<E>> for AnySyncTimelineEvent
579where
580 E: Serialize,
581{
582 fn from(val: EventBuilder<E>) -> Self {
583 Raw::<AnySyncTimelineEvent>::from(val).deserialize().expect("expected sync timeline event")
584 }
585}
586
587impl<E: StaticEventContent<IsPrefix = False>> From<EventBuilder<E>> for Raw<AnyTimelineEvent>
588where
589 E: Serialize,
590{
591 fn from(val: EventBuilder<E>) -> Self {
592 val.into_raw_timeline()
593 }
594}
595
596impl<E: StaticEventContent<IsPrefix = False>> From<EventBuilder<E>> for AnyTimelineEvent
597where
598 E: Serialize,
599{
600 fn from(val: EventBuilder<E>) -> Self {
601 Raw::<AnyTimelineEvent>::from(val).deserialize().expect("expected timeline event")
602 }
603}
604
605impl<E: StaticEventContent<IsPrefix = False>> From<EventBuilder<E>>
606 for Raw<AnyGlobalAccountDataEvent>
607where
608 E: Serialize,
609{
610 fn from(val: EventBuilder<E>) -> Self {
611 val.format(EventFormat::GlobalAccountData).into_raw()
612 }
613}
614
615impl<E: StaticEventContent<IsPrefix = False>> From<EventBuilder<E>> for AnyGlobalAccountDataEvent
616where
617 E: Serialize,
618{
619 fn from(val: EventBuilder<E>) -> Self {
620 Raw::<AnyGlobalAccountDataEvent>::from(val)
621 .deserialize()
622 .expect("expected global account data")
623 }
624}
625
626impl<E: StaticEventContent<IsPrefix = False>> From<EventBuilder<E>> for Raw<AnyRoomAccountDataEvent>
627where
628 E: Serialize,
629{
630 fn from(val: EventBuilder<E>) -> Self {
631 val.format(EventFormat::RoomAccountData).into_raw()
632 }
633}
634
635impl<E: StaticEventContent<IsPrefix = False>> From<EventBuilder<E>> for AnyRoomAccountDataEvent
636where
637 E: Serialize,
638{
639 fn from(val: EventBuilder<E>) -> Self {
640 Raw::<AnyRoomAccountDataEvent>::from(val).deserialize().expect("expected room account data")
641 }
642}
643
644impl<E: StaticEventContent<IsPrefix = False>> From<EventBuilder<E>> for TimelineEvent
645where
646 E: Serialize,
647{
648 fn from(val: EventBuilder<E>) -> Self {
649 val.into_event()
650 }
651}
652
653impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
654 for Raw<AnySyncStateEvent>
655{
656 fn from(val: EventBuilder<E>) -> Self {
657 val.format(EventFormat::SyncTimeline).into_raw()
658 }
659}
660
661impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
662 for AnySyncStateEvent
663{
664 fn from(val: EventBuilder<E>) -> Self {
665 Raw::<AnySyncStateEvent>::from(val).deserialize().expect("expected sync state")
666 }
667}
668
669impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
670 for Raw<SyncStateEvent<E>>
671where
672 E: StaticStateEventContent + RedactContent,
673 E::Redacted: RedactedStateEventContent,
674{
675 fn from(val: EventBuilder<E>) -> Self {
676 val.format(EventFormat::SyncTimeline).into_raw()
677 }
678}
679
680impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
681 for SyncStateEvent<E>
682where
683 E: StaticStateEventContent + RedactContent + EventContentFromType,
684 E::Redacted: RedactedStateEventContent<StateKey = <E as StateEventContent>::StateKey>
685 + EventContentFromType,
686{
687 fn from(val: EventBuilder<E>) -> Self {
688 Raw::<SyncStateEvent<E>>::from(val).deserialize().expect("expected sync state")
689 }
690}
691
692impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
693 for Raw<AnyStateEvent>
694{
695 fn from(val: EventBuilder<E>) -> Self {
696 val.into_raw()
697 }
698}
699
700impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
701 for AnyStateEvent
702{
703 fn from(val: EventBuilder<E>) -> Self {
704 Raw::<AnyStateEvent>::from(val).deserialize().expect("expected state")
705 }
706}
707
708impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
709 for Raw<StateEvent<E>>
710where
711 E: StaticStateEventContent + RedactContent,
712 E::Redacted: RedactedStateEventContent,
713{
714 fn from(val: EventBuilder<E>) -> Self {
715 val.into_raw()
716 }
717}
718
719impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
720 for StateEvent<E>
721where
722 E: StaticStateEventContent + RedactContent + EventContentFromType,
723 E::Redacted: RedactedStateEventContent<StateKey = <E as StateEventContent>::StateKey>
724 + EventContentFromType,
725{
726 fn from(val: EventBuilder<E>) -> Self {
727 Raw::<StateEvent<E>>::from(val).deserialize().expect("expected state")
728 }
729}
730
731impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
732 for Raw<AnyStrippedStateEvent>
733{
734 fn from(val: EventBuilder<E>) -> Self {
735 val.format(EventFormat::StrippedState).into_raw()
736 }
737}
738
739impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
740 for AnyStrippedStateEvent
741{
742 fn from(val: EventBuilder<E>) -> Self {
743 Raw::<AnyStrippedStateEvent>::from(val).deserialize().expect("expected stripped state")
744 }
745}
746
747impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
748 for Raw<StrippedStateEvent<E::PossiblyRedacted>>
749where
750 E: StaticStateEventContent,
751{
752 fn from(val: EventBuilder<E>) -> Self {
753 val.format(EventFormat::StrippedState).into_raw()
754 }
755}
756
757impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
758 for StrippedStateEvent<E::PossiblyRedacted>
759where
760 E: StaticStateEventContent,
761 E::PossiblyRedacted: PossiblyRedactedStateEventContent + EventContentFromType,
762{
763 fn from(val: EventBuilder<E>) -> Self {
764 Raw::<StrippedStateEvent<E::PossiblyRedacted>>::from(val)
765 .deserialize()
766 .expect("expected stripped state")
767 }
768}
769
770impl<E: StaticEventContent<IsPrefix = False> + EphemeralRoomEventContent> From<EventBuilder<E>>
771 for Raw<AnySyncEphemeralRoomEvent>
772{
773 fn from(val: EventBuilder<E>) -> Self {
774 val.format(EventFormat::Ephemeral).into_raw()
775 }
776}
777
778impl<E: StaticEventContent<IsPrefix = False> + EphemeralRoomEventContent> From<EventBuilder<E>>
779 for AnySyncEphemeralRoomEvent
780{
781 fn from(val: EventBuilder<E>) -> Self {
782 Raw::<AnySyncEphemeralRoomEvent>::from(val).deserialize().expect("expected ephemeral")
783 }
784}
785
786impl<E: StaticEventContent<IsPrefix = False> + MessageLikeEventContent> From<EventBuilder<E>>
787 for Raw<AnySyncMessageLikeEvent>
788{
789 fn from(val: EventBuilder<E>) -> Self {
790 val.format(EventFormat::SyncTimeline).into_raw()
791 }
792}
793
794impl<E: StaticEventContent<IsPrefix = False> + MessageLikeEventContent> From<EventBuilder<E>>
795 for AnySyncMessageLikeEvent
796{
797 fn from(val: EventBuilder<E>) -> Self {
798 Raw::<AnySyncMessageLikeEvent>::from(val).deserialize().expect("expected sync message-like")
799 }
800}
801
802impl<E: StaticEventContent<IsPrefix = False> + MessageLikeEventContent> From<EventBuilder<E>>
803 for Raw<SyncMessageLikeEvent<E>>
804where
805 E: RedactContent,
806 E::Redacted: RedactedMessageLikeEventContent,
807{
808 fn from(val: EventBuilder<E>) -> Self {
809 val.format(EventFormat::SyncTimeline).into_raw()
810 }
811}
812
813impl<E: StaticEventContent<IsPrefix = False> + MessageLikeEventContent> From<EventBuilder<E>>
814 for SyncMessageLikeEvent<E>
815where
816 E: RedactContent + EventContentFromType,
817 E::Redacted: RedactedMessageLikeEventContent + EventContentFromType,
818{
819 fn from(val: EventBuilder<E>) -> Self {
820 Raw::<SyncMessageLikeEvent<E>>::from(val).deserialize().expect("expected sync message-like")
821 }
822}
823
824impl<E: StaticEventContent<IsPrefix = False> + MessageLikeEventContent> From<EventBuilder<E>>
825 for Raw<AnyMessageLikeEvent>
826{
827 fn from(val: EventBuilder<E>) -> Self {
828 val.into_raw()
829 }
830}
831
832impl<E: StaticEventContent<IsPrefix = False> + MessageLikeEventContent> From<EventBuilder<E>>
833 for AnyMessageLikeEvent
834{
835 fn from(val: EventBuilder<E>) -> Self {
836 Raw::<AnyMessageLikeEvent>::from(val).deserialize().expect("expected message-like")
837 }
838}
839
840impl<E: StaticEventContent<IsPrefix = False> + MessageLikeEventContent> From<EventBuilder<E>>
841 for Raw<MessageLikeEvent<E>>
842where
843 E: RedactContent,
844 E::Redacted: RedactedMessageLikeEventContent,
845{
846 fn from(val: EventBuilder<E>) -> Self {
847 val.into_raw()
848 }
849}
850
851impl<E: StaticEventContent<IsPrefix = False> + MessageLikeEventContent> From<EventBuilder<E>>
852 for MessageLikeEvent<E>
853where
854 E: RedactContent + EventContentFromType,
855 E::Redacted: RedactedMessageLikeEventContent + EventContentFromType,
856{
857 fn from(val: EventBuilder<E>) -> Self {
858 Raw::<MessageLikeEvent<E>>::from(val).deserialize().expect("expected message-like")
859 }
860}
861
862#[derive(Debug, Default)]
863pub struct EventFactory {
864 next_ts: AtomicU64,
865 sender: Option<OwnedUserId>,
866 room: Option<OwnedRoomId>,
867}
868
869impl EventFactory {
870 pub fn new() -> Self {
871 Self { next_ts: AtomicU64::new(0), sender: None, room: None }
872 }
873
874 pub fn room(mut self, room_id: &RoomId) -> Self {
875 self.room = Some(room_id.to_owned());
876 self
877 }
878
879 pub fn sender(mut self, sender: &UserId) -> Self {
880 self.sender = Some(sender.to_owned());
881 self
882 }
883
884 pub fn server_ts(self, ts: u64) -> Self {
885 self.next_ts.store(ts, SeqCst);
886 self
887 }
888
889 fn next_server_ts(&self) -> MilliSecondsSinceUnixEpoch {
890 MilliSecondsSinceUnixEpoch(
891 self.next_ts
892 .fetch_add(1, SeqCst)
893 .try_into()
894 .expect("server timestamp should fit in js_int::UInt"),
895 )
896 }
897
898 pub fn event<E: StaticEventContent<IsPrefix = False>>(&self, content: E) -> EventBuilder<E> {
900 EventBuilder {
901 format: EventFormat::Timeline,
902 sender: self.sender.clone(),
903 room: self.room.clone(),
904 server_ts: self.next_server_ts(),
905 event_id: None,
906 no_event_id: false,
907 redacts: None,
908 content,
909 unsigned: None,
910 state_key: None,
911 }
912 }
913
914 pub fn text_msg(&self, content: impl Into<String>) -> EventBuilder<RoomMessageEventContent> {
916 self.event(RoomMessageEventContent::text_plain(content.into()))
917 }
918
919 pub fn emote(&self, content: impl Into<String>) -> EventBuilder<RoomMessageEventContent> {
921 self.event(RoomMessageEventContent::emote_plain(content.into()))
922 }
923
924 pub fn encrypted(
927 &self,
928 ciphertext: impl Into<String>,
929 sender_key: impl Into<String>,
930 device_id: impl Into<OwnedDeviceId>,
931 session_id: impl Into<String>,
932 ) -> EventBuilder<RoomEncryptedEventContent> {
933 self.event(RoomEncryptedEventContent::new(
934 EncryptedEventScheme::MegolmV1AesSha2(
935 MegolmV1AesSha2ContentInit {
936 ciphertext: ciphertext.into(),
937 sender_key: sender_key.into(),
938 device_id: device_id.into(),
939 session_id: session_id.into(),
940 }
941 .into(),
942 ),
943 None,
944 ))
945 }
946
947 pub fn member(&self, member: &UserId) -> EventBuilder<RoomMemberEventContent> {
977 let mut event = self.event(RoomMemberEventContent::new(MembershipState::Join));
978
979 if self.sender.is_some() {
980 event.sender = self.sender.clone();
981 } else {
982 event.sender = Some(member.to_owned());
983 }
984
985 event.state_key = Some(member.to_string());
986
987 event
988 }
989
990 pub fn room_tombstone(
992 &self,
993 body: impl Into<String>,
994 replacement: &RoomId,
995 ) -> EventBuilder<RoomTombstoneEventContent> {
996 let mut event =
997 self.event(RoomTombstoneEventContent::new(body.into(), replacement.to_owned()));
998 event.state_key = Some("".to_owned());
999 event
1000 }
1001
1002 pub fn room_topic(&self, topic: impl Into<String>) -> EventBuilder<RoomTopicEventContent> {
1004 let mut event = self.event(RoomTopicEventContent::new(topic.into()));
1005 event.state_key = Some("".to_owned());
1007 event
1008 }
1009
1010 pub fn room_name(&self, name: impl Into<String>) -> EventBuilder<RoomNameEventContent> {
1012 let mut event = self.event(RoomNameEventContent::new(name.into()));
1013 event.state_key = Some("".to_owned());
1015 event
1016 }
1017
1018 pub fn room_avatar(&self) -> EventBuilder<RoomAvatarEventContent> {
1020 let mut event = self.event(RoomAvatarEventContent::new());
1021 event.state_key = Some("".to_owned());
1023 event
1024 }
1025
1026 pub fn room_encryption(&self) -> EventBuilder<RoomEncryptionEventContent> {
1031 let mut event = self.event(RoomEncryptionEventContent::with_recommended_defaults());
1032 event.state_key = Some("".to_owned());
1034 event
1035 }
1036
1037 #[cfg(feature = "experimental-encrypted-state-events")]
1040 pub fn room_encryption_with_state_encryption(
1041 &self,
1042 ) -> EventBuilder<RoomEncryptionEventContent> {
1043 let mut content = RoomEncryptionEventContent::with_recommended_defaults();
1044 content.encrypt_state_events = true;
1045 let mut event = self.event(content);
1046 event.state_key = Some("".to_owned());
1047 event
1048 }
1049
1050 pub fn room_history_visibility(
1055 &self,
1056 visibility: HistoryVisibility,
1057 ) -> EventBuilder<RoomHistoryVisibilityEventContent> {
1058 let mut event = self.event(RoomHistoryVisibilityEventContent::new(visibility));
1059 event.state_key = Some("".to_owned());
1060 event
1061 }
1062
1063 pub fn room_join_rules(&self, join_rule: JoinRule) -> EventBuilder<RoomJoinRulesEventContent> {
1067 let mut event = self.event(RoomJoinRulesEventContent::new(join_rule));
1068 event.state_key = Some("".to_owned());
1069 event
1070 }
1071
1072 pub fn room_pinned_events(
1074 &self,
1075 pinned: Vec<OwnedEventId>,
1076 ) -> EventBuilder<RoomPinnedEventsEventContent> {
1077 self.event(RoomPinnedEventsEventContent::new(pinned)).state_key("")
1078 }
1079
1080 pub fn member_hints(
1101 &self,
1102 service_members: BTreeSet<OwnedUserId>,
1103 ) -> EventBuilder<MemberHintsEventContent> {
1104 self.event(MemberHintsEventContent::new(service_members)).state_key("")
1106 }
1107
1108 pub fn text_html(
1110 &self,
1111 plain: impl Into<String>,
1112 html: impl Into<String>,
1113 ) -> EventBuilder<RoomMessageEventContent> {
1114 self.event(RoomMessageEventContent::text_html(plain, html))
1115 }
1116
1117 pub fn notice(&self, content: impl Into<String>) -> EventBuilder<RoomMessageEventContent> {
1119 self.event(RoomMessageEventContent::notice_plain(content))
1120 }
1121
1122 pub fn reaction(
1124 &self,
1125 event_id: &EventId,
1126 annotation: impl Into<String>,
1127 ) -> EventBuilder<ReactionEventContent> {
1128 self.event(ReactionEventContent::new(Annotation::new(
1129 event_id.to_owned(),
1130 annotation.into(),
1131 )))
1132 }
1133
1134 pub fn redaction(&self, event_id: &EventId) -> EventBuilder<RoomRedactionEventContent> {
1139 let mut builder = self.event(RoomRedactionEventContent::new_v11(event_id.to_owned()));
1140 builder.redacts = Some(event_id.to_owned());
1141 builder
1142 }
1143
1144 pub fn redacted<T: StaticEventContent<IsPrefix = False> + RedactedMessageLikeEventContent>(
1147 &self,
1148 redacter: &UserId,
1149 content: T,
1150 ) -> EventBuilder<T> {
1151 let mut builder = self.event(content);
1152
1153 let redacted_because = RedactedBecause {
1154 content: RoomRedactionEventContent::default(),
1155 event_id: EventId::new_v1(server_name!("dummy.server")),
1156 sender: redacter.to_owned(),
1157 origin_server_ts: self.next_server_ts(),
1158 };
1159 builder.unsigned.get_or_insert_with(Default::default).redacted_because =
1160 Some(redacted_because);
1161
1162 builder
1163 }
1164
1165 pub fn redacted_state<T: StaticEventContent<IsPrefix = False> + RedactedStateEventContent>(
1168 &self,
1169 redacter: &UserId,
1170 state_key: impl Into<String>,
1171 content: T,
1172 ) -> EventBuilder<T> {
1173 let mut builder = self.event(content);
1174
1175 let redacted_because = RedactedBecause {
1176 content: RoomRedactionEventContent::default(),
1177 event_id: EventId::new_v1(server_name!("dummy.server")),
1178 sender: redacter.to_owned(),
1179 origin_server_ts: self.next_server_ts(),
1180 };
1181 builder.unsigned.get_or_insert_with(Default::default).redacted_because =
1182 Some(redacted_because);
1183 builder.state_key = Some(state_key.into());
1184
1185 builder
1186 }
1187
1188 pub fn poll_start(
1191 &self,
1192 fallback_text: impl Into<String>,
1193 poll_question: impl Into<String>,
1194 answers: Vec<impl Into<String>>,
1195 ) -> EventBuilder<UnstablePollStartEventContent> {
1196 let answers: Vec<UnstablePollAnswer> = answers
1198 .into_iter()
1199 .enumerate()
1200 .map(|(idx, answer)| UnstablePollAnswer::new(idx.to_string(), answer))
1201 .collect();
1202 let poll_answers = answers.try_into().unwrap();
1203 let poll_start_content =
1204 UnstablePollStartEventContent::New(NewUnstablePollStartEventContent::plain_text(
1205 fallback_text,
1206 UnstablePollStartContentBlock::new(poll_question, poll_answers),
1207 ));
1208 self.event(poll_start_content)
1209 }
1210
1211 pub fn poll_edit(
1213 &self,
1214 edited_event_id: &EventId,
1215 poll_question: impl Into<String>,
1216 answers: Vec<impl Into<String>>,
1217 ) -> EventBuilder<ReplacementUnstablePollStartEventContent> {
1218 let answers: Vec<UnstablePollAnswer> = answers
1220 .into_iter()
1221 .enumerate()
1222 .map(|(idx, answer)| UnstablePollAnswer::new(idx.to_string(), answer))
1223 .collect();
1224 let poll_answers = answers.try_into().unwrap();
1225 let poll_start_content_block =
1226 UnstablePollStartContentBlock::new(poll_question, poll_answers);
1227 self.event(ReplacementUnstablePollStartEventContent::new(
1228 poll_start_content_block,
1229 edited_event_id.to_owned(),
1230 ))
1231 }
1232
1233 pub fn poll_response(
1236 &self,
1237 answers: Vec<impl Into<String>>,
1238 poll_start_id: &EventId,
1239 ) -> EventBuilder<UnstablePollResponseEventContent> {
1240 self.event(UnstablePollResponseEventContent::new(
1241 answers.into_iter().map(Into::into).collect(),
1242 poll_start_id.to_owned(),
1243 ))
1244 }
1245
1246 pub fn poll_end(
1249 &self,
1250 content: impl Into<String>,
1251 poll_start_id: &EventId,
1252 ) -> EventBuilder<UnstablePollEndEventContent> {
1253 self.event(UnstablePollEndEventContent::new(content.into(), poll_start_id.to_owned()))
1254 }
1255
1256 pub fn image(
1259 &self,
1260 filename: String,
1261 url: OwnedMxcUri,
1262 ) -> EventBuilder<RoomMessageEventContent> {
1263 let image_event_content = ImageMessageEventContent::plain(filename, url);
1264 self.event(RoomMessageEventContent::new(MessageType::Image(image_event_content)))
1265 }
1266
1267 pub fn gallery(
1270 &self,
1271 body: String,
1272 filename: String,
1273 url: OwnedMxcUri,
1274 ) -> EventBuilder<RoomMessageEventContent> {
1275 let gallery_event_content = GalleryMessageEventContent::new(
1276 body,
1277 None,
1278 vec![GalleryItemType::Image(ImageMessageEventContent::plain(filename, url))],
1279 );
1280 self.event(RoomMessageEventContent::new(MessageType::Gallery(gallery_event_content)))
1281 }
1282
1283 pub fn typing(&self, user_ids: Vec<&UserId>) -> EventBuilder<TypingEventContent> {
1285 self.event(TypingEventContent::new(user_ids.into_iter().map(ToOwned::to_owned).collect()))
1286 .format(EventFormat::Ephemeral)
1287 }
1288
1289 pub fn read_receipts(&self) -> ReadReceiptBuilder<'_> {
1291 ReadReceiptBuilder { factory: self, content: ReceiptEventContent(Default::default()) }
1292 }
1293
1294 pub fn create(
1296 &self,
1297 creator_user_id: &UserId,
1298 room_version: RoomVersionId,
1299 ) -> EventBuilder<RoomCreateEventContent> {
1300 let mut event = self.event(RoomCreateEventContent::new_v1(creator_user_id.to_owned()));
1301 event.content.room_version = room_version;
1302
1303 if self.sender.is_some() {
1304 event.sender = self.sender.clone();
1305 } else {
1306 event.sender = Some(creator_user_id.to_owned());
1307 }
1308
1309 event.state_key = Some("".to_owned());
1310
1311 event
1312 }
1313
1314 pub fn power_levels(
1316 &self,
1317 map: &mut BTreeMap<OwnedUserId, Int>,
1318 ) -> EventBuilder<RoomPowerLevelsEventContent> {
1319 let mut content = RoomPowerLevelsEventContent::new(&AuthorizationRules::V1);
1320 content.users.append(map);
1321 let mut event = self.event(content);
1322 event.state_key = Some("".to_owned());
1323 event
1324 }
1325
1326 pub fn default_power_levels(&self) -> EventBuilder<RoomPowerLevelsEventContent> {
1328 self.power_levels(&mut BTreeMap::new())
1329 }
1330
1331 pub fn server_acl(
1333 &self,
1334 allow_ip_literals: bool,
1335 allow: Vec<String>,
1336 deny: Vec<String>,
1337 ) -> EventBuilder<RoomServerAclEventContent> {
1338 self.event(RoomServerAclEventContent::new(allow_ip_literals, allow, deny))
1339 }
1340
1341 pub fn canonical_alias(
1343 &self,
1344 alias: Option<OwnedRoomAliasId>,
1345 alt_aliases: Vec<OwnedRoomAliasId>,
1346 ) -> EventBuilder<RoomCanonicalAliasEventContent> {
1347 let mut content = RoomCanonicalAliasEventContent::new();
1348 content.alias = alias;
1349 content.alt_aliases = alt_aliases;
1350 let mut event = self.event(content);
1351 event.state_key = Some("".to_owned());
1353 event
1354 }
1355
1356 pub fn beacon(
1382 &self,
1383 beacon_info_event_id: OwnedEventId,
1384 latitude: f64,
1385 longitude: f64,
1386 uncertainty: u32,
1387 ts: Option<MilliSecondsSinceUnixEpoch>,
1388 ) -> EventBuilder<BeaconEventContent> {
1389 let geo_uri = format!("geo:{latitude},{longitude};u={uncertainty}");
1390 self.event(BeaconEventContent::new(beacon_info_event_id, geo_uri, ts))
1391 }
1392
1393 pub fn beacon_info(
1420 &self,
1421 description: Option<String>,
1422 duration: Duration,
1423 live: bool,
1424 ts: Option<MilliSecondsSinceUnixEpoch>,
1425 ) -> EventBuilder<BeaconInfoEventContent> {
1426 self.event(BeaconInfoEventContent::new(description, duration, live, ts))
1427 }
1428
1429 pub fn sticker(
1431 &self,
1432 body: impl Into<String>,
1433 info: ImageInfo,
1434 url: OwnedMxcUri,
1435 ) -> EventBuilder<StickerEventContent> {
1436 self.event(StickerEventContent::new(body.into(), info, url))
1437 }
1438
1439 pub fn call_invite(
1441 &self,
1442 call_id: OwnedVoipId,
1443 lifetime: UInt,
1444 offer: SessionDescription,
1445 version: VoipVersionId,
1446 ) -> EventBuilder<CallInviteEventContent> {
1447 self.event(CallInviteEventContent::new(call_id, lifetime, offer, version))
1448 }
1449
1450 pub fn rtc_notification(
1452 &self,
1453 notification_type: NotificationType,
1454 ) -> EventBuilder<RtcNotificationEventContent> {
1455 self.event(RtcNotificationEventContent::new(
1456 MilliSecondsSinceUnixEpoch::now(),
1457 Duration::new(30, 0),
1458 notification_type,
1459 ))
1460 }
1461
1462 pub fn call_decline(
1464 &self,
1465 notification_event_id: &EventId,
1466 ) -> EventBuilder<RtcDeclineEventContent> {
1467 self.event(RtcDeclineEventContent::new(notification_event_id))
1468 }
1469
1470 pub fn direct(&self) -> EventBuilder<DirectEventContent> {
1472 self.global_account_data(DirectEventContent::default())
1473 }
1474
1475 pub fn ignored_user_list(
1477 &self,
1478 users: impl IntoIterator<Item = OwnedUserId>,
1479 ) -> EventBuilder<IgnoredUserListEventContent> {
1480 self.global_account_data(IgnoredUserListEventContent::users(users))
1481 }
1482
1483 pub fn push_rules(&self, rules: Ruleset) -> EventBuilder<PushRulesEventContent> {
1485 self.global_account_data(PushRulesEventContent::new(rules))
1486 }
1487
1488 pub fn space_child(
1490 &self,
1491 parent: OwnedRoomId,
1492 child: OwnedRoomId,
1493 ) -> EventBuilder<SpaceChildEventContent> {
1494 let mut event = self.event(SpaceChildEventContent::new(vec![]));
1495 event.room = Some(parent);
1496 event.state_key = Some(child.to_string());
1497 event
1498 }
1499
1500 pub fn space_parent(
1502 &self,
1503 parent: OwnedRoomId,
1504 child: OwnedRoomId,
1505 ) -> EventBuilder<SpaceParentEventContent> {
1506 let mut event = self.event(SpaceParentEventContent::new(vec![]));
1507 event.state_key = Some(parent.to_string());
1508 event.room = Some(child);
1509 event
1510 }
1511
1512 pub fn custom_message_like_event(&self) -> EventBuilder<CustomMessageLikeEventContent> {
1514 self.event(CustomMessageLikeEventContent)
1515 }
1516
1517 pub fn set_next_ts(&self, value: u64) {
1521 self.next_ts.store(value, SeqCst);
1522 }
1523
1524 pub fn global_account_data<C>(&self, content: C) -> EventBuilder<C>
1526 where
1527 C: GlobalAccountDataEventContent + StaticEventContent<IsPrefix = False>,
1528 {
1529 self.event(content).format(EventFormat::GlobalAccountData)
1530 }
1531
1532 pub fn room_account_data<C>(&self, content: C) -> EventBuilder<C>
1534 where
1535 C: RoomAccountDataEventContent + StaticEventContent<IsPrefix = False>,
1536 {
1537 self.event(content).format(EventFormat::RoomAccountData)
1538 }
1539
1540 pub fn fully_read(&self, event_id: &EventId) -> EventBuilder<FullyReadEventContent> {
1542 self.room_account_data(FullyReadEventContent::new(event_id.to_owned()))
1543 }
1544
1545 pub fn marked_unread(&self, unread: bool) -> EventBuilder<MarkedUnreadEventContent> {
1547 self.room_account_data(MarkedUnreadEventContent::new(unread))
1548 }
1549
1550 pub fn tag(&self, tags: Tags) -> EventBuilder<TagEventContent> {
1552 self.room_account_data(tags.into())
1553 }
1554
1555 pub fn space_order(&self, order: &str) -> EventBuilder<SpaceOrderEventContent> {
1558 let order = ruma::SpaceChildOrder::parse(order).expect("order should be valid");
1559 self.room_account_data(SpaceOrderEventContent::new(order))
1560 }
1561
1562 pub fn presence(&self, state: PresenceState) -> PresenceBuilder {
1567 PresenceBuilder { sender: self.sender.clone(), content: PresenceEventContent::new(state) }
1568 }
1569}
1570
1571#[derive(Debug)]
1573pub struct PresenceBuilder {
1574 sender: Option<OwnedUserId>,
1575 content: PresenceEventContent,
1576}
1577
1578impl PresenceBuilder {
1579 pub fn sender(mut self, sender: &UserId) -> Self {
1581 self.sender = Some(sender.to_owned());
1582 self
1583 }
1584
1585 pub fn avatar_url(mut self, url: &MxcUri) -> Self {
1587 self.content.avatar_url = Some(url.to_owned());
1588 self
1589 }
1590
1591 pub fn currently_active(mut self, active: bool) -> Self {
1593 self.content.currently_active = Some(active);
1594 self
1595 }
1596
1597 pub fn last_active_ago(mut self, ms: u64) -> Self {
1599 self.content.last_active_ago = Some(UInt::try_from(ms).unwrap());
1600 self
1601 }
1602
1603 pub fn status_msg(mut self, msg: impl Into<String>) -> Self {
1605 self.content.status_msg = Some(msg.into());
1606 self
1607 }
1608
1609 pub fn display_name(mut self, name: impl Into<String>) -> Self {
1611 self.content.displayname = Some(name.into());
1612 self
1613 }
1614}
1615
1616impl From<PresenceBuilder> for Raw<PresenceEvent> {
1617 fn from(builder: PresenceBuilder) -> Self {
1618 let sender = builder.sender.expect("sender must be set for presence events");
1619 let event = PresenceEvent { content: builder.content, sender };
1620 Raw::new(&event).unwrap().cast_unchecked()
1621 }
1622}
1623
1624impl EventBuilder<DirectEventContent> {
1625 pub fn add_user(mut self, user_id: OwnedDirectUserIdentifier, room_id: &RoomId) -> Self {
1627 self.content.0.entry(user_id).or_default().push(room_id.to_owned());
1628 self
1629 }
1630}
1631
1632impl EventBuilder<RoomMemberEventContent> {
1633 pub fn membership(mut self, state: MembershipState) -> Self {
1638 self.content.membership = state;
1639 self
1640 }
1641
1642 pub fn invited(mut self, invited_user: &UserId) -> Self {
1645 assert_ne!(
1646 self.sender.as_deref().unwrap(),
1647 invited_user,
1648 "invited user and sender can't be the same person"
1649 );
1650 self.content.membership = MembershipState::Invite;
1651 self.state_key = Some(invited_user.to_string());
1652 self
1653 }
1654
1655 pub fn leave(mut self) -> Self {
1659 self.content.membership = MembershipState::Leave;
1660 self.state_key = Some(self.sender.as_ref().expect("sender must be set").to_string());
1661 self
1662 }
1663
1664 pub fn kicked(mut self, kicked_user: &UserId) -> Self {
1667 assert_ne!(
1668 self.sender.as_deref().unwrap(),
1669 kicked_user,
1670 "kicked user and sender can't be the same person, otherwise it's just a Leave"
1671 );
1672 self.content.membership = MembershipState::Leave;
1673 self.state_key = Some(kicked_user.to_string());
1674 self
1675 }
1676
1677 pub fn banned(mut self, banned_user: &UserId) -> Self {
1680 assert_ne!(
1681 self.sender.as_deref().unwrap(),
1682 banned_user,
1683 "a user can't ban itself" );
1685 self.content.membership = MembershipState::Ban;
1686 self.state_key = Some(banned_user.to_string());
1687 self
1688 }
1689
1690 pub fn display_name(mut self, display_name: impl Into<String>) -> Self {
1692 self.content.displayname = Some(display_name.into());
1693 self
1694 }
1695
1696 pub fn avatar_url(mut self, url: &MxcUri) -> Self {
1698 self.content.avatar_url = Some(url.to_owned());
1699 self
1700 }
1701
1702 pub fn reason(mut self, reason: impl Into<String>) -> Self {
1704 self.content.reason = Some(reason.into());
1705 self
1706 }
1707
1708 pub fn previous(mut self, previous: impl Into<PreviousMembership>) -> Self {
1710 let previous = previous.into();
1711
1712 let mut prev_content = RoomMemberEventContent::new(previous.state);
1713 if let Some(avatar_url) = previous.avatar_url {
1714 prev_content.avatar_url = Some(avatar_url);
1715 }
1716 if let Some(display_name) = previous.display_name {
1717 prev_content.displayname = Some(display_name);
1718 }
1719
1720 self.unsigned.get_or_insert_with(Default::default).prev_content = Some(prev_content);
1721 self
1722 }
1723
1724 pub fn invite_room_state<I, E>(mut self, events: I) -> Self
1729 where
1730 I: IntoIterator<Item = E>,
1731 E: Into<Raw<AnyStrippedStateEvent>>,
1732 {
1733 self.unsigned.get_or_insert_with(Default::default).invite_room_state =
1734 Some(events.into_iter().map(Into::into).collect());
1735 self
1736 }
1737}
1738
1739impl EventBuilder<RoomAvatarEventContent> {
1740 pub fn url(mut self, url: &MxcUri) -> Self {
1742 self.content.url = Some(url.to_owned());
1743 self
1744 }
1745
1746 pub fn info(mut self, image: avatar::ImageInfo) -> Self {
1748 self.content.info = Some(Box::new(image));
1749 self
1750 }
1751}
1752
1753impl EventBuilder<RtcNotificationEventContent> {
1754 pub fn mentions(mut self, users: impl IntoIterator<Item = OwnedUserId>) -> Self {
1755 self.content.mentions = Some(Mentions::with_user_ids(users));
1756 self
1757 }
1758
1759 pub fn call_intent(mut self, call_intent: CallIntent) -> Self {
1760 self.content.call_intent = Some(call_intent);
1761 self
1762 }
1763
1764 pub fn relates_to_membership_state_event(mut self, event_id: OwnedEventId) -> Self {
1765 self.content.relates_to = Some(Reference::new(event_id));
1766 self
1767 }
1768
1769 pub fn lifetime(mut self, time_in_seconds: u64) -> Self {
1770 self.content.lifetime = Duration::from_secs(time_in_seconds);
1771 self
1772 }
1773}
1774
1775pub struct ReadReceiptBuilder<'a> {
1776 factory: &'a EventFactory,
1777 content: ReceiptEventContent,
1778}
1779
1780impl ReadReceiptBuilder<'_> {
1781 pub fn add(
1783 self,
1784 event_id: &EventId,
1785 user_id: &UserId,
1786 tyype: ReceiptType,
1787 thread: ReceiptThread,
1788 ) -> Self {
1789 let ts = self.factory.next_server_ts();
1790 self.add_with_timestamp(event_id, user_id, tyype, thread, Some(ts))
1791 }
1792
1793 pub fn add_with_timestamp(
1795 mut self,
1796 event_id: &EventId,
1797 user_id: &UserId,
1798 tyype: ReceiptType,
1799 thread: ReceiptThread,
1800 ts: Option<MilliSecondsSinceUnixEpoch>,
1801 ) -> Self {
1802 let by_event = self.content.0.entry(event_id.to_owned()).or_default();
1803 let by_type = by_event.entry(tyype).or_default();
1804
1805 let mut receipt = Receipt::default();
1806 if let Some(ts) = ts {
1807 receipt.ts = Some(ts);
1808 }
1809 receipt.thread = thread;
1810
1811 by_type.insert(user_id.to_owned(), receipt);
1812 self
1813 }
1814
1815 pub fn into_content(self) -> ReceiptEventContent {
1817 self.content
1818 }
1819
1820 pub fn into_event(self) -> EventBuilder<ReceiptEventContent> {
1822 self.factory.event(self.into_content()).format(EventFormat::Ephemeral)
1823 }
1824}
1825
1826pub struct PreviousMembership {
1827 state: MembershipState,
1828 avatar_url: Option<OwnedMxcUri>,
1829 display_name: Option<String>,
1830}
1831
1832impl PreviousMembership {
1833 pub fn new(state: MembershipState) -> Self {
1834 Self { state, avatar_url: None, display_name: None }
1835 }
1836
1837 pub fn avatar_url(mut self, url: &MxcUri) -> Self {
1838 self.avatar_url = Some(url.to_owned());
1839 self
1840 }
1841
1842 pub fn display_name(mut self, name: impl Into<String>) -> Self {
1843 self.display_name = Some(name.into());
1844 self
1845 }
1846}
1847
1848impl From<MembershipState> for PreviousMembership {
1849 fn from(state: MembershipState) -> Self {
1850 Self::new(state)
1851 }
1852}
1853
1854#[derive(Clone, Default, Debug, Serialize, EventContent)]
1855#[ruma_event(type = "rs.matrix-sdk.custom.test", kind = MessageLike)]
1856pub struct CustomMessageLikeEventContent;