matrix_sdk_test/
event_factory.rs

1// Copyright 2024 The Matrix.org Foundation C.I.C.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#![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, OwnedEventId, OwnedMxcUri, OwnedRoomAliasId,
29    OwnedRoomId, OwnedTransactionId, OwnedUserId, OwnedVoipId, RoomId, RoomVersionId,
30    TransactionId, UInt, UserId, VoipVersionId,
31    events::{
32        AnyGlobalAccountDataEvent, AnyMessageLikeEvent, AnyStateEvent, AnyStrippedStateEvent,
33        AnySyncEphemeralRoomEvent, AnySyncMessageLikeEvent, AnySyncStateEvent,
34        AnySyncTimelineEvent, AnyTimelineEvent, BundledMessageLikeRelations,
35        EphemeralRoomEventContent, EventContentFromType, False, GlobalAccountDataEventContent,
36        Mentions, MessageLikeEvent, MessageLikeEventContent, PossiblyRedactedStateEventContent,
37        RedactContent, RedactedMessageLikeEventContent, RedactedStateEventContent, StateEvent,
38        StateEventContent, StaticEventContent, StaticStateEventContent, StrippedStateEvent,
39        SyncMessageLikeEvent, SyncStateEvent,
40        beacon::BeaconEventContent,
41        call::{SessionDescription, invite::CallInviteEventContent},
42        direct::{DirectEventContent, OwnedDirectUserIdentifier},
43        ignored_user_list::IgnoredUserListEventContent,
44        macros::EventContent,
45        member_hints::MemberHintsEventContent,
46        poll::{
47            unstable_end::UnstablePollEndEventContent,
48            unstable_response::UnstablePollResponseEventContent,
49            unstable_start::{
50                NewUnstablePollStartEventContent, ReplacementUnstablePollStartEventContent,
51                UnstablePollAnswer, UnstablePollStartContentBlock, UnstablePollStartEventContent,
52            },
53        },
54        push_rules::PushRulesEventContent,
55        reaction::ReactionEventContent,
56        receipt::{Receipt, ReceiptEventContent, ReceiptThread, ReceiptType},
57        relation::{Annotation, BundledThread, InReplyTo, Reference, Replacement, Thread},
58        room::{
59            ImageInfo,
60            avatar::{self, RoomAvatarEventContent},
61            canonical_alias::RoomCanonicalAliasEventContent,
62            create::{PreviousRoom, RoomCreateEventContent},
63            encrypted::{EncryptedEventScheme, RoomEncryptedEventContent},
64            member::{MembershipState, RoomMemberEventContent},
65            message::{
66                FormattedBody, GalleryItemType, GalleryMessageEventContent,
67                ImageMessageEventContent, MessageType, OriginalSyncRoomMessageEvent, Relation,
68                RelationWithoutReplacement, RoomMessageEventContent,
69                RoomMessageEventContentWithoutRelation,
70            },
71            name::RoomNameEventContent,
72            power_levels::RoomPowerLevelsEventContent,
73            redaction::RoomRedactionEventContent,
74            server_acl::RoomServerAclEventContent,
75            tombstone::RoomTombstoneEventContent,
76            topic::RoomTopicEventContent,
77        },
78        rtc::{
79            decline::RtcDeclineEventContent,
80            notification::{NotificationType, RtcNotificationEventContent},
81        },
82        space::{child::SpaceChildEventContent, parent::SpaceParentEventContent},
83        sticker::StickerEventContent,
84        typing::TypingEventContent,
85    },
86    push::Ruleset,
87    room::RoomType,
88    room_version_rules::AuthorizationRules,
89    serde::Raw,
90    server_name,
91};
92use serde::Serialize;
93use serde_json::json;
94
95pub trait TimestampArg {
96    fn to_milliseconds_since_unix_epoch(self) -> MilliSecondsSinceUnixEpoch;
97}
98
99impl TimestampArg for MilliSecondsSinceUnixEpoch {
100    fn to_milliseconds_since_unix_epoch(self) -> MilliSecondsSinceUnixEpoch {
101        self
102    }
103}
104
105impl TimestampArg for u64 {
106    fn to_milliseconds_since_unix_epoch(self) -> MilliSecondsSinceUnixEpoch {
107        MilliSecondsSinceUnixEpoch(UInt::try_from(self).unwrap())
108    }
109}
110
111/// A thin copy of [`ruma::events::UnsignedRoomRedactionEvent`].
112#[derive(Debug, Serialize)]
113struct RedactedBecause {
114    /// Data specific to the event type.
115    content: RoomRedactionEventContent,
116
117    /// The globally unique event identifier for the user who sent the event.
118    event_id: OwnedEventId,
119
120    /// The fully-qualified ID of the user who sent this event.
121    sender: OwnedUserId,
122
123    /// Timestamp in milliseconds on originating homeserver when this event was
124    /// sent.
125    origin_server_ts: MilliSecondsSinceUnixEpoch,
126}
127
128#[derive(Debug, Serialize)]
129struct Unsigned<C: StaticEventContent> {
130    #[serde(skip_serializing_if = "Option::is_none")]
131    prev_content: Option<C>,
132
133    #[serde(skip_serializing_if = "Option::is_none")]
134    transaction_id: Option<OwnedTransactionId>,
135
136    #[serde(rename = "m.relations", skip_serializing_if = "Option::is_none")]
137    relations: Option<BundledMessageLikeRelations<Raw<AnySyncTimelineEvent>>>,
138
139    #[serde(skip_serializing_if = "Option::is_none")]
140    redacted_because: Option<RedactedBecause>,
141
142    #[serde(skip_serializing_if = "Option::is_none")]
143    age: Option<Int>,
144}
145
146// rustc can't derive Default because C isn't marked as `Default` 🤔 oh well.
147impl<C: StaticEventContent> Default for Unsigned<C> {
148    fn default() -> Self {
149        Self {
150            prev_content: None,
151            transaction_id: None,
152            relations: None,
153            redacted_because: None,
154            age: None,
155        }
156    }
157}
158
159#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
160enum EventFormat {
161    /// An event that can be received in the timeline, via `/messages` for
162    /// example.
163    #[default]
164    Timeline,
165    /// An event that can be received in the timeline, via `/sync`.
166    SyncTimeline,
167    /// An event that is received in stripped state.
168    StrippedState,
169    /// An ephemeral event, like a read receipt.
170    Ephemeral,
171    /// A global account data.
172    GlobalAccountData,
173}
174
175impl EventFormat {
176    /// Whether this format has a `sender` field.
177    fn has_sender(self) -> bool {
178        matches!(self, Self::Timeline | Self::SyncTimeline | Self::StrippedState)
179    }
180
181    /// Whether this format has an `event_id` field.
182    fn has_event_id(self) -> bool {
183        matches!(self, Self::Timeline | Self::SyncTimeline)
184    }
185
186    /// Whether this format ha an `room_id` field.
187    fn has_room_id(self) -> bool {
188        matches!(self, Self::Timeline)
189    }
190}
191
192#[derive(Debug)]
193pub struct EventBuilder<C: StaticEventContent<IsPrefix = False>> {
194    /// The format of the event.
195    ///
196    /// It will decide which fields are added to the JSON.
197    format: EventFormat,
198    sender: Option<OwnedUserId>,
199    room: Option<OwnedRoomId>,
200    event_id: Option<OwnedEventId>,
201    /// Whether the event should *not* have an event id. False by default.
202    no_event_id: bool,
203    redacts: Option<OwnedEventId>,
204    content: C,
205    server_ts: MilliSecondsSinceUnixEpoch,
206    unsigned: Option<Unsigned<C>>,
207    state_key: Option<String>,
208}
209
210impl<E: StaticEventContent<IsPrefix = False>> EventBuilder<E> {
211    fn format(mut self, format: EventFormat) -> Self {
212        self.format = format;
213        self
214    }
215
216    pub fn room(mut self, room_id: &RoomId) -> Self {
217        self.room = Some(room_id.to_owned());
218        self
219    }
220
221    pub fn sender(mut self, sender: &UserId) -> Self {
222        self.sender = Some(sender.to_owned());
223        self
224    }
225
226    pub fn event_id(mut self, event_id: &EventId) -> Self {
227        self.event_id = Some(event_id.to_owned());
228        self.no_event_id = false;
229        self
230    }
231
232    pub fn no_event_id(mut self) -> Self {
233        self.event_id = None;
234        self.no_event_id = true;
235        self
236    }
237
238    pub fn server_ts(mut self, ts: impl TimestampArg) -> Self {
239        self.server_ts = ts.to_milliseconds_since_unix_epoch();
240        self
241    }
242
243    pub fn unsigned_transaction_id(mut self, transaction_id: &TransactionId) -> Self {
244        self.unsigned.get_or_insert_with(Default::default).transaction_id =
245            Some(transaction_id.to_owned());
246        self
247    }
248
249    /// Add age to unsigned data in this event.
250    pub fn age(mut self, age: impl Into<Int>) -> Self {
251        self.unsigned.get_or_insert_with(Default::default).age = Some(age.into());
252        self
253    }
254
255    /// Create a bundled thread summary in the unsigned bundled relations of
256    /// this event.
257    pub fn with_bundled_thread_summary(
258        mut self,
259        latest_event: Raw<AnySyncMessageLikeEvent>,
260        count: usize,
261        current_user_participated: bool,
262    ) -> Self {
263        let relations = self
264            .unsigned
265            .get_or_insert_with(Default::default)
266            .relations
267            .get_or_insert_with(BundledMessageLikeRelations::new);
268        relations.thread = Some(Box::new(BundledThread::new(
269            latest_event,
270            UInt::try_from(count).unwrap(),
271            current_user_participated,
272        )));
273        self
274    }
275
276    /// Create a bundled edit in the unsigned bundled relations of this event.
277    pub fn with_bundled_edit(mut self, replacement: impl Into<Raw<AnySyncTimelineEvent>>) -> Self {
278        let relations = self
279            .unsigned
280            .get_or_insert_with(Default::default)
281            .relations
282            .get_or_insert_with(BundledMessageLikeRelations::new);
283        relations.replace = Some(Box::new(replacement.into()));
284        self
285    }
286
287    /// For state events manually created, define the state key.
288    ///
289    /// For other state events created in the [`EventFactory`], this is
290    /// automatically filled upon creation or update of the events.
291    pub fn state_key(mut self, state_key: impl Into<String>) -> Self {
292        self.state_key = Some(state_key.into());
293        self
294    }
295}
296
297impl<E> EventBuilder<E>
298where
299    E: StaticEventContent<IsPrefix = False> + Serialize,
300{
301    #[inline(always)]
302    fn construct_json(self) -> serde_json::Value {
303        let mut json = json!({
304            "type": E::TYPE,
305            "content": self.content,
306            "origin_server_ts": self.server_ts,
307        });
308
309        let map = json.as_object_mut().unwrap();
310
311        if self.format.has_sender() {
312            // Use the `sender` preferably, or resort to the `redacted_because` sender if
313            // none has been set.
314            let sender = self
315                .sender
316                .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");
317            map.insert("sender".to_owned(), json!(sender));
318        }
319
320        if self.format.has_event_id() && !self.no_event_id {
321            let event_id = self.event_id.unwrap_or_else(|| {
322                let server_name = self
323                    .room
324                    .as_ref()
325                    .and_then(|room_id| room_id.server_name())
326                    .unwrap_or(server_name!("dummy.org"));
327
328                EventId::new(server_name)
329            });
330
331            map.insert("event_id".to_owned(), json!(event_id));
332        }
333
334        if self.format.has_room_id() {
335            let room_id = self.room.expect("TimelineEvent requires a room id");
336            map.insert("room_id".to_owned(), json!(room_id));
337        }
338
339        if let Some(redacts) = self.redacts {
340            map.insert("redacts".to_owned(), json!(redacts));
341        }
342
343        if let Some(unsigned) = self.unsigned {
344            map.insert("unsigned".to_owned(), json!(unsigned));
345        }
346
347        if let Some(state_key) = self.state_key {
348            map.insert("state_key".to_owned(), json!(state_key));
349        }
350
351        json
352    }
353
354    /// Build an event from the [`EventBuilder`] and convert it into a
355    /// serialized and [`Raw`] event.
356    ///
357    /// The generic argument `T` allows you to automatically cast the [`Raw`]
358    /// event into any desired type.
359    pub fn into_raw<T>(self) -> Raw<T> {
360        Raw::new(&self.construct_json()).unwrap().cast_unchecked()
361    }
362
363    pub fn into_raw_timeline(self) -> Raw<AnyTimelineEvent> {
364        self.into_raw()
365    }
366
367    pub fn into_any_sync_message_like_event(self) -> AnySyncMessageLikeEvent {
368        self.format(EventFormat::SyncTimeline)
369            .into_raw()
370            .deserialize()
371            .expect("expected message like event")
372    }
373
374    pub fn into_original_sync_room_message_event(self) -> OriginalSyncRoomMessageEvent {
375        self.format(EventFormat::SyncTimeline)
376            .into_raw()
377            .deserialize()
378            .expect("expected original sync room message event")
379    }
380
381    pub fn into_raw_sync(self) -> Raw<AnySyncTimelineEvent> {
382        self.format(EventFormat::SyncTimeline).into_raw()
383    }
384
385    pub fn into_raw_sync_state(self) -> Raw<AnySyncStateEvent> {
386        self.format(EventFormat::SyncTimeline).into_raw()
387    }
388
389    pub fn into_event(self) -> TimelineEvent {
390        TimelineEvent::from_plaintext(self.into_raw_sync())
391    }
392}
393
394impl EventBuilder<RoomEncryptedEventContent> {
395    /// Turn this event into a [`TimelineEvent`] representing a decryption
396    /// failure
397    pub fn into_utd_sync_timeline_event(self) -> TimelineEvent {
398        let session_id = as_variant!(&self.content.scheme, EncryptedEventScheme::MegolmV1AesSha2)
399            .map(|content| content.session_id.clone());
400
401        TimelineEvent::from_utd(
402            self.into(),
403            UnableToDecryptInfo {
404                session_id,
405                reason: UnableToDecryptReason::MissingMegolmSession { withheld_code: None },
406            },
407        )
408    }
409}
410
411impl EventBuilder<RoomMessageEventContent> {
412    /// Adds a reply relation to the current event.
413    pub fn reply_to(mut self, event_id: &EventId) -> Self {
414        self.content.relates_to =
415            Some(Relation::Reply { in_reply_to: InReplyTo::new(event_id.to_owned()) });
416        self
417    }
418
419    /// Adds a thread relation to the root event, setting the reply fallback to
420    /// the latest in-thread event.
421    pub fn in_thread(mut self, root: &EventId, latest_thread_event: &EventId) -> Self {
422        self.content.relates_to =
423            Some(Relation::Thread(Thread::plain(root.to_owned(), latest_thread_event.to_owned())));
424        self
425    }
426
427    /// Adds a thread relation to the root event, that's a non-fallback reply to
428    /// another thread event.
429    pub fn in_thread_reply(mut self, root: &EventId, replied_to: &EventId) -> Self {
430        self.content.relates_to =
431            Some(Relation::Thread(Thread::reply(root.to_owned(), replied_to.to_owned())));
432        self
433    }
434
435    /// Adds the given mentions to the current event.
436    pub fn mentions(mut self, mentions: Mentions) -> Self {
437        self.content.mentions = Some(mentions);
438        self
439    }
440
441    /// Adds a replacement relation to the current event, with the new content
442    /// passed.
443    pub fn edit(
444        mut self,
445        edited_event_id: &EventId,
446        new_content: RoomMessageEventContentWithoutRelation,
447    ) -> Self {
448        self.content.relates_to =
449            Some(Relation::Replacement(Replacement::new(edited_event_id.to_owned(), new_content)));
450        self
451    }
452
453    /// Adds a caption to a media event.
454    ///
455    /// Will crash if the event isn't a media room message.
456    pub fn caption(
457        mut self,
458        caption: Option<String>,
459        formatted_caption: Option<FormattedBody>,
460    ) -> Self {
461        match &mut self.content.msgtype {
462            MessageType::Image(image) => {
463                let filename = image.filename().to_owned();
464                if let Some(caption) = caption {
465                    image.body = caption;
466                    image.filename = Some(filename);
467                } else {
468                    image.body = filename;
469                    image.filename = None;
470                }
471                image.formatted = formatted_caption;
472            }
473
474            MessageType::Audio(_) | MessageType::Video(_) | MessageType::File(_) => {
475                unimplemented!();
476            }
477
478            _ => panic!("unexpected event type for a caption"),
479        }
480
481        self
482    }
483}
484
485impl EventBuilder<UnstablePollStartEventContent> {
486    /// Adds a reply relation to the current event.
487    pub fn reply_to(mut self, event_id: &EventId) -> Self {
488        if let UnstablePollStartEventContent::New(content) = &mut self.content {
489            content.relates_to = Some(RelationWithoutReplacement::Reply {
490                in_reply_to: InReplyTo::new(event_id.to_owned()),
491            });
492        }
493        self
494    }
495
496    /// Adds a thread relation to the root event, setting the reply to
497    /// event id as well.
498    pub fn in_thread(mut self, root: &EventId, reply_to_event_id: &EventId) -> Self {
499        let thread = Thread::reply(root.to_owned(), reply_to_event_id.to_owned());
500
501        if let UnstablePollStartEventContent::New(content) = &mut self.content {
502            content.relates_to = Some(RelationWithoutReplacement::Thread(thread));
503        }
504        self
505    }
506}
507
508impl EventBuilder<RoomCreateEventContent> {
509    /// Define the predecessor fields.
510    pub fn predecessor(mut self, room_id: &RoomId) -> Self {
511        self.content.predecessor = Some(PreviousRoom::new(room_id.to_owned()));
512        self
513    }
514
515    /// Erase the predecessor if any.
516    pub fn no_predecessor(mut self) -> Self {
517        self.content.predecessor = None;
518        self
519    }
520
521    /// Sets the `m.room.create` `type` field to `m.space`.
522    pub fn with_space_type(mut self) -> Self {
523        self.content.room_type = Some(RoomType::Space);
524        self
525    }
526}
527
528impl EventBuilder<StickerEventContent> {
529    /// Add reply [`Thread`] relation to root event and set replied-to event id.
530    pub fn reply_thread(mut self, root: &EventId, reply_to_event: &EventId) -> Self {
531        self.content.relates_to =
532            Some(Relation::Thread(Thread::reply(root.to_owned(), reply_to_event.to_owned())));
533        self
534    }
535}
536
537impl<E: StaticEventContent<IsPrefix = False>> From<EventBuilder<E>> for Raw<AnySyncTimelineEvent>
538where
539    E: Serialize,
540{
541    fn from(val: EventBuilder<E>) -> Self {
542        val.into_raw_sync()
543    }
544}
545
546impl<E: StaticEventContent<IsPrefix = False>> From<EventBuilder<E>> for AnySyncTimelineEvent
547where
548    E: Serialize,
549{
550    fn from(val: EventBuilder<E>) -> Self {
551        Raw::<AnySyncTimelineEvent>::from(val).deserialize().expect("expected sync timeline event")
552    }
553}
554
555impl<E: StaticEventContent<IsPrefix = False>> From<EventBuilder<E>> for Raw<AnyTimelineEvent>
556where
557    E: Serialize,
558{
559    fn from(val: EventBuilder<E>) -> Self {
560        val.into_raw_timeline()
561    }
562}
563
564impl<E: StaticEventContent<IsPrefix = False>> From<EventBuilder<E>> for AnyTimelineEvent
565where
566    E: Serialize,
567{
568    fn from(val: EventBuilder<E>) -> Self {
569        Raw::<AnyTimelineEvent>::from(val).deserialize().expect("expected timeline event")
570    }
571}
572
573impl<E: StaticEventContent<IsPrefix = False>> From<EventBuilder<E>>
574    for Raw<AnyGlobalAccountDataEvent>
575where
576    E: Serialize,
577{
578    fn from(val: EventBuilder<E>) -> Self {
579        val.format(EventFormat::GlobalAccountData).into_raw()
580    }
581}
582
583impl<E: StaticEventContent<IsPrefix = False>> From<EventBuilder<E>> for AnyGlobalAccountDataEvent
584where
585    E: Serialize,
586{
587    fn from(val: EventBuilder<E>) -> Self {
588        Raw::<AnyGlobalAccountDataEvent>::from(val)
589            .deserialize()
590            .expect("expected global account data")
591    }
592}
593
594impl<E: StaticEventContent<IsPrefix = False>> From<EventBuilder<E>> for TimelineEvent
595where
596    E: Serialize,
597{
598    fn from(val: EventBuilder<E>) -> Self {
599        val.into_event()
600    }
601}
602
603impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
604    for Raw<AnySyncStateEvent>
605{
606    fn from(val: EventBuilder<E>) -> Self {
607        val.format(EventFormat::SyncTimeline).into_raw()
608    }
609}
610
611impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
612    for AnySyncStateEvent
613{
614    fn from(val: EventBuilder<E>) -> Self {
615        Raw::<AnySyncStateEvent>::from(val).deserialize().expect("expected sync state")
616    }
617}
618
619impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
620    for Raw<SyncStateEvent<E>>
621where
622    E: StaticStateEventContent + RedactContent,
623    E::Redacted: RedactedStateEventContent,
624{
625    fn from(val: EventBuilder<E>) -> Self {
626        val.format(EventFormat::SyncTimeline).into_raw()
627    }
628}
629
630impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
631    for SyncStateEvent<E>
632where
633    E: StaticStateEventContent + RedactContent + EventContentFromType,
634    E::Redacted: RedactedStateEventContent<StateKey = <E as StateEventContent>::StateKey>
635        + EventContentFromType,
636{
637    fn from(val: EventBuilder<E>) -> Self {
638        Raw::<SyncStateEvent<E>>::from(val).deserialize().expect("expected sync state")
639    }
640}
641
642impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
643    for Raw<AnyStateEvent>
644{
645    fn from(val: EventBuilder<E>) -> Self {
646        val.into_raw()
647    }
648}
649
650impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
651    for AnyStateEvent
652{
653    fn from(val: EventBuilder<E>) -> Self {
654        Raw::<AnyStateEvent>::from(val).deserialize().expect("expected state")
655    }
656}
657
658impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
659    for Raw<StateEvent<E>>
660where
661    E: StaticStateEventContent + RedactContent,
662    E::Redacted: RedactedStateEventContent,
663{
664    fn from(val: EventBuilder<E>) -> Self {
665        val.into_raw()
666    }
667}
668
669impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
670    for StateEvent<E>
671where
672    E: StaticStateEventContent + RedactContent + EventContentFromType,
673    E::Redacted: RedactedStateEventContent<StateKey = <E as StateEventContent>::StateKey>
674        + EventContentFromType,
675{
676    fn from(val: EventBuilder<E>) -> Self {
677        Raw::<StateEvent<E>>::from(val).deserialize().expect("expected state")
678    }
679}
680
681impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
682    for Raw<AnyStrippedStateEvent>
683{
684    fn from(val: EventBuilder<E>) -> Self {
685        val.format(EventFormat::StrippedState).into_raw()
686    }
687}
688
689impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
690    for AnyStrippedStateEvent
691{
692    fn from(val: EventBuilder<E>) -> Self {
693        Raw::<AnyStrippedStateEvent>::from(val).deserialize().expect("expected stripped state")
694    }
695}
696
697impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
698    for Raw<StrippedStateEvent<E::PossiblyRedacted>>
699where
700    E: StaticStateEventContent,
701{
702    fn from(val: EventBuilder<E>) -> Self {
703        val.format(EventFormat::StrippedState).into_raw()
704    }
705}
706
707impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
708    for StrippedStateEvent<E::PossiblyRedacted>
709where
710    E: StaticStateEventContent,
711    E::PossiblyRedacted: PossiblyRedactedStateEventContent + EventContentFromType,
712{
713    fn from(val: EventBuilder<E>) -> Self {
714        Raw::<StrippedStateEvent<E::PossiblyRedacted>>::from(val)
715            .deserialize()
716            .expect("expected stripped state")
717    }
718}
719
720impl<E: StaticEventContent<IsPrefix = False> + EphemeralRoomEventContent> From<EventBuilder<E>>
721    for Raw<AnySyncEphemeralRoomEvent>
722{
723    fn from(val: EventBuilder<E>) -> Self {
724        val.format(EventFormat::Ephemeral).into_raw()
725    }
726}
727
728impl<E: StaticEventContent<IsPrefix = False> + EphemeralRoomEventContent> From<EventBuilder<E>>
729    for AnySyncEphemeralRoomEvent
730{
731    fn from(val: EventBuilder<E>) -> Self {
732        Raw::<AnySyncEphemeralRoomEvent>::from(val).deserialize().expect("expected ephemeral")
733    }
734}
735
736impl<E: StaticEventContent<IsPrefix = False> + MessageLikeEventContent> From<EventBuilder<E>>
737    for Raw<AnySyncMessageLikeEvent>
738{
739    fn from(val: EventBuilder<E>) -> Self {
740        val.format(EventFormat::SyncTimeline).into_raw()
741    }
742}
743
744impl<E: StaticEventContent<IsPrefix = False> + MessageLikeEventContent> From<EventBuilder<E>>
745    for AnySyncMessageLikeEvent
746{
747    fn from(val: EventBuilder<E>) -> Self {
748        Raw::<AnySyncMessageLikeEvent>::from(val).deserialize().expect("expected sync message-like")
749    }
750}
751
752impl<E: StaticEventContent<IsPrefix = False> + MessageLikeEventContent> From<EventBuilder<E>>
753    for Raw<SyncMessageLikeEvent<E>>
754where
755    E: RedactContent,
756    E::Redacted: RedactedMessageLikeEventContent,
757{
758    fn from(val: EventBuilder<E>) -> Self {
759        val.format(EventFormat::SyncTimeline).into_raw()
760    }
761}
762
763impl<E: StaticEventContent<IsPrefix = False> + MessageLikeEventContent> From<EventBuilder<E>>
764    for SyncMessageLikeEvent<E>
765where
766    E: RedactContent + EventContentFromType,
767    E::Redacted: RedactedMessageLikeEventContent + EventContentFromType,
768{
769    fn from(val: EventBuilder<E>) -> Self {
770        Raw::<SyncMessageLikeEvent<E>>::from(val).deserialize().expect("expected sync message-like")
771    }
772}
773
774impl<E: StaticEventContent<IsPrefix = False> + MessageLikeEventContent> From<EventBuilder<E>>
775    for Raw<AnyMessageLikeEvent>
776{
777    fn from(val: EventBuilder<E>) -> Self {
778        val.into_raw()
779    }
780}
781
782impl<E: StaticEventContent<IsPrefix = False> + MessageLikeEventContent> From<EventBuilder<E>>
783    for AnyMessageLikeEvent
784{
785    fn from(val: EventBuilder<E>) -> Self {
786        Raw::<AnyMessageLikeEvent>::from(val).deserialize().expect("expected message-like")
787    }
788}
789
790impl<E: StaticEventContent<IsPrefix = False> + MessageLikeEventContent> From<EventBuilder<E>>
791    for Raw<MessageLikeEvent<E>>
792where
793    E: RedactContent,
794    E::Redacted: RedactedMessageLikeEventContent,
795{
796    fn from(val: EventBuilder<E>) -> Self {
797        val.into_raw()
798    }
799}
800
801impl<E: StaticEventContent<IsPrefix = False> + MessageLikeEventContent> From<EventBuilder<E>>
802    for MessageLikeEvent<E>
803where
804    E: RedactContent + EventContentFromType,
805    E::Redacted: RedactedMessageLikeEventContent + EventContentFromType,
806{
807    fn from(val: EventBuilder<E>) -> Self {
808        Raw::<MessageLikeEvent<E>>::from(val).deserialize().expect("expected message-like")
809    }
810}
811
812#[derive(Debug, Default)]
813pub struct EventFactory {
814    next_ts: AtomicU64,
815    sender: Option<OwnedUserId>,
816    room: Option<OwnedRoomId>,
817}
818
819impl EventFactory {
820    pub fn new() -> Self {
821        Self { next_ts: AtomicU64::new(0), sender: None, room: None }
822    }
823
824    pub fn room(mut self, room_id: &RoomId) -> Self {
825        self.room = Some(room_id.to_owned());
826        self
827    }
828
829    pub fn sender(mut self, sender: &UserId) -> Self {
830        self.sender = Some(sender.to_owned());
831        self
832    }
833
834    pub fn server_ts(self, ts: u64) -> Self {
835        self.next_ts.store(ts, SeqCst);
836        self
837    }
838
839    fn next_server_ts(&self) -> MilliSecondsSinceUnixEpoch {
840        MilliSecondsSinceUnixEpoch(
841            self.next_ts
842                .fetch_add(1, SeqCst)
843                .try_into()
844                .expect("server timestamp should fit in js_int::UInt"),
845        )
846    }
847
848    /// Create an event from any event content.
849    pub fn event<E: StaticEventContent<IsPrefix = False>>(&self, content: E) -> EventBuilder<E> {
850        EventBuilder {
851            format: EventFormat::Timeline,
852            sender: self.sender.clone(),
853            room: self.room.clone(),
854            server_ts: self.next_server_ts(),
855            event_id: None,
856            no_event_id: false,
857            redacts: None,
858            content,
859            unsigned: None,
860            state_key: None,
861        }
862    }
863
864    /// Create a new plain text `m.room.message`.
865    pub fn text_msg(&self, content: impl Into<String>) -> EventBuilder<RoomMessageEventContent> {
866        self.event(RoomMessageEventContent::text_plain(content.into()))
867    }
868
869    /// Create a new plain emote `m.room.message`.
870    pub fn emote(&self, content: impl Into<String>) -> EventBuilder<RoomMessageEventContent> {
871        self.event(RoomMessageEventContent::emote_plain(content.into()))
872    }
873
874    /// Create a new `m.room.member` event for the given member.
875    ///
876    /// The given member will be used as the `sender` as well as the `state_key`
877    /// of the `m.room.member` event, unless the `sender` was already using
878    /// [`EventFactory::sender()`], in that case only the state key will be
879    /// set to the given `member`.
880    ///
881    /// The `membership` field of the content is set to
882    /// [`MembershipState::Join`].
883    ///
884    /// ```
885    /// use matrix_sdk_test::event_factory::EventFactory;
886    /// use ruma::{
887    ///     events::{
888    ///         SyncStateEvent,
889    ///         room::member::{MembershipState, RoomMemberEventContent},
890    ///     },
891    ///     room_id,
892    ///     serde::Raw,
893    ///     user_id,
894    /// };
895    ///
896    /// let factory = EventFactory::new().room(room_id!("!test:localhost"));
897    ///
898    /// let event: Raw<SyncStateEvent<RoomMemberEventContent>> = factory
899    ///     .member(user_id!("@alice:localhost"))
900    ///     .display_name("Alice")
901    ///     .into_raw();
902    /// ```
903    pub fn member(&self, member: &UserId) -> EventBuilder<RoomMemberEventContent> {
904        let mut event = self.event(RoomMemberEventContent::new(MembershipState::Join));
905
906        if self.sender.is_some() {
907            event.sender = self.sender.clone();
908        } else {
909            event.sender = Some(member.to_owned());
910        }
911
912        event.state_key = Some(member.to_string());
913
914        event
915    }
916
917    /// Create a tombstone state event for the room.
918    pub fn room_tombstone(
919        &self,
920        body: impl Into<String>,
921        replacement: &RoomId,
922    ) -> EventBuilder<RoomTombstoneEventContent> {
923        let mut event =
924            self.event(RoomTombstoneEventContent::new(body.into(), replacement.to_owned()));
925        event.state_key = Some("".to_owned());
926        event
927    }
928
929    /// Create a state event for the topic.
930    pub fn room_topic(&self, topic: impl Into<String>) -> EventBuilder<RoomTopicEventContent> {
931        let mut event = self.event(RoomTopicEventContent::new(topic.into()));
932        // The state key is empty for a room topic state event.
933        event.state_key = Some("".to_owned());
934        event
935    }
936
937    /// Create a state event for the room name.
938    pub fn room_name(&self, name: impl Into<String>) -> EventBuilder<RoomNameEventContent> {
939        let mut event = self.event(RoomNameEventContent::new(name.into()));
940        // The state key is empty for a room name state event.
941        event.state_key = Some("".to_owned());
942        event
943    }
944
945    /// Create an empty state event for the room avatar.
946    pub fn room_avatar(&self) -> EventBuilder<RoomAvatarEventContent> {
947        let mut event = self.event(RoomAvatarEventContent::new());
948        // The state key is empty for a room avatar state event.
949        event.state_key = Some("".to_owned());
950        event
951    }
952
953    /// Create a new `m.member_hints` event with the given service members.
954    ///
955    /// ```
956    /// use std::collections::BTreeSet;
957    ///
958    /// use matrix_sdk_test::event_factory::EventFactory;
959    /// use ruma::{
960    ///     events::{SyncStateEvent, member_hints::MemberHintsEventContent},
961    ///     owned_user_id, room_id,
962    ///     serde::Raw,
963    ///     user_id,
964    /// };
965    ///
966    /// let factory = EventFactory::new().room(room_id!("!test:localhost"));
967    ///
968    /// let event: Raw<SyncStateEvent<MemberHintsEventContent>> = factory
969    ///     .member_hints(BTreeSet::from([owned_user_id!("@alice:localhost")]))
970    ///     .sender(user_id!("@alice:localhost"))
971    ///     .into_raw();
972    /// ```
973    pub fn member_hints(
974        &self,
975        service_members: BTreeSet<OwnedUserId>,
976    ) -> EventBuilder<MemberHintsEventContent> {
977        // The `m.member_hints` event always has an empty state key, so let's set it.
978        self.event(MemberHintsEventContent::new(service_members)).state_key("")
979    }
980
981    /// Create a new plain/html `m.room.message`.
982    pub fn text_html(
983        &self,
984        plain: impl Into<String>,
985        html: impl Into<String>,
986    ) -> EventBuilder<RoomMessageEventContent> {
987        self.event(RoomMessageEventContent::text_html(plain, html))
988    }
989
990    /// Create a new plain notice `m.room.message`.
991    pub fn notice(&self, content: impl Into<String>) -> EventBuilder<RoomMessageEventContent> {
992        self.event(RoomMessageEventContent::notice_plain(content))
993    }
994
995    /// Add a reaction to an event.
996    pub fn reaction(
997        &self,
998        event_id: &EventId,
999        annotation: impl Into<String>,
1000    ) -> EventBuilder<ReactionEventContent> {
1001        self.event(ReactionEventContent::new(Annotation::new(
1002            event_id.to_owned(),
1003            annotation.into(),
1004        )))
1005    }
1006
1007    /// Create a live redaction for the given event id.
1008    ///
1009    /// Note: this is not a redacted event, but a redaction event, that will
1010    /// cause another event to be redacted.
1011    pub fn redaction(&self, event_id: &EventId) -> EventBuilder<RoomRedactionEventContent> {
1012        let mut builder = self.event(RoomRedactionEventContent::new_v11(event_id.to_owned()));
1013        builder.redacts = Some(event_id.to_owned());
1014        builder
1015    }
1016
1017    /// Create a redacted event, with extra information in the unsigned section
1018    /// about the redaction itself.
1019    pub fn redacted<T: StaticEventContent<IsPrefix = False> + RedactedMessageLikeEventContent>(
1020        &self,
1021        redacter: &UserId,
1022        content: T,
1023    ) -> EventBuilder<T> {
1024        let mut builder = self.event(content);
1025
1026        let redacted_because = RedactedBecause {
1027            content: RoomRedactionEventContent::default(),
1028            event_id: EventId::new(server_name!("dummy.server")),
1029            sender: redacter.to_owned(),
1030            origin_server_ts: self.next_server_ts(),
1031        };
1032        builder.unsigned.get_or_insert_with(Default::default).redacted_because =
1033            Some(redacted_because);
1034
1035        builder
1036    }
1037
1038    /// Create a redacted state event, with extra information in the unsigned
1039    /// section about the redaction itself.
1040    pub fn redacted_state<T: StaticEventContent<IsPrefix = False> + RedactedStateEventContent>(
1041        &self,
1042        redacter: &UserId,
1043        state_key: impl Into<String>,
1044        content: T,
1045    ) -> EventBuilder<T> {
1046        let mut builder = self.event(content);
1047
1048        let redacted_because = RedactedBecause {
1049            content: RoomRedactionEventContent::default(),
1050            event_id: EventId::new(server_name!("dummy.server")),
1051            sender: redacter.to_owned(),
1052            origin_server_ts: self.next_server_ts(),
1053        };
1054        builder.unsigned.get_or_insert_with(Default::default).redacted_because =
1055            Some(redacted_because);
1056        builder.state_key = Some(state_key.into());
1057
1058        builder
1059    }
1060
1061    /// Create a poll start event given a text, the question and the possible
1062    /// answers.
1063    pub fn poll_start(
1064        &self,
1065        fallback_text: impl Into<String>,
1066        poll_question: impl Into<String>,
1067        answers: Vec<impl Into<String>>,
1068    ) -> EventBuilder<UnstablePollStartEventContent> {
1069        // PollAnswers 'constructor' is not public, so we need to deserialize them
1070        let answers: Vec<UnstablePollAnswer> = answers
1071            .into_iter()
1072            .enumerate()
1073            .map(|(idx, answer)| UnstablePollAnswer::new(idx.to_string(), answer))
1074            .collect();
1075        let poll_answers = answers.try_into().unwrap();
1076        let poll_start_content =
1077            UnstablePollStartEventContent::New(NewUnstablePollStartEventContent::plain_text(
1078                fallback_text,
1079                UnstablePollStartContentBlock::new(poll_question, poll_answers),
1080            ));
1081        self.event(poll_start_content)
1082    }
1083
1084    /// Create a poll edit event given the new question and possible answers.
1085    pub fn poll_edit(
1086        &self,
1087        edited_event_id: &EventId,
1088        poll_question: impl Into<String>,
1089        answers: Vec<impl Into<String>>,
1090    ) -> EventBuilder<ReplacementUnstablePollStartEventContent> {
1091        // PollAnswers 'constructor' is not public, so we need to deserialize them
1092        let answers: Vec<UnstablePollAnswer> = answers
1093            .into_iter()
1094            .enumerate()
1095            .map(|(idx, answer)| UnstablePollAnswer::new(idx.to_string(), answer))
1096            .collect();
1097        let poll_answers = answers.try_into().unwrap();
1098        let poll_start_content_block =
1099            UnstablePollStartContentBlock::new(poll_question, poll_answers);
1100        self.event(ReplacementUnstablePollStartEventContent::new(
1101            poll_start_content_block,
1102            edited_event_id.to_owned(),
1103        ))
1104    }
1105
1106    /// Create a poll response with the given answer id and the associated poll
1107    /// start event id.
1108    pub fn poll_response(
1109        &self,
1110        answers: Vec<impl Into<String>>,
1111        poll_start_id: &EventId,
1112    ) -> EventBuilder<UnstablePollResponseEventContent> {
1113        self.event(UnstablePollResponseEventContent::new(
1114            answers.into_iter().map(Into::into).collect(),
1115            poll_start_id.to_owned(),
1116        ))
1117    }
1118
1119    /// Create a poll response with the given text and the associated poll start
1120    /// event id.
1121    pub fn poll_end(
1122        &self,
1123        content: impl Into<String>,
1124        poll_start_id: &EventId,
1125    ) -> EventBuilder<UnstablePollEndEventContent> {
1126        self.event(UnstablePollEndEventContent::new(content.into(), poll_start_id.to_owned()))
1127    }
1128
1129    /// Creates a plain (unencrypted) image event content referencing the given
1130    /// MXC ID.
1131    pub fn image(
1132        &self,
1133        filename: String,
1134        url: OwnedMxcUri,
1135    ) -> EventBuilder<RoomMessageEventContent> {
1136        let image_event_content = ImageMessageEventContent::plain(filename, url);
1137        self.event(RoomMessageEventContent::new(MessageType::Image(image_event_content)))
1138    }
1139
1140    /// Create a gallery event containing a single plain (unencrypted) image
1141    /// referencing the given MXC ID.
1142    pub fn gallery(
1143        &self,
1144        body: String,
1145        filename: String,
1146        url: OwnedMxcUri,
1147    ) -> EventBuilder<RoomMessageEventContent> {
1148        let gallery_event_content = GalleryMessageEventContent::new(
1149            body,
1150            None,
1151            vec![GalleryItemType::Image(ImageMessageEventContent::plain(filename, url))],
1152        );
1153        self.event(RoomMessageEventContent::new(MessageType::Gallery(gallery_event_content)))
1154    }
1155
1156    /// Create a typing notification event.
1157    pub fn typing(&self, user_ids: Vec<&UserId>) -> EventBuilder<TypingEventContent> {
1158        self.event(TypingEventContent::new(user_ids.into_iter().map(ToOwned::to_owned).collect()))
1159            .format(EventFormat::Ephemeral)
1160    }
1161
1162    /// Create a read receipt event.
1163    pub fn read_receipts(&self) -> ReadReceiptBuilder<'_> {
1164        ReadReceiptBuilder { factory: self, content: ReceiptEventContent(Default::default()) }
1165    }
1166
1167    /// Create a new `m.room.create` event.
1168    pub fn create(
1169        &self,
1170        creator_user_id: &UserId,
1171        room_version: RoomVersionId,
1172    ) -> EventBuilder<RoomCreateEventContent> {
1173        let mut event = self.event(RoomCreateEventContent::new_v1(creator_user_id.to_owned()));
1174        event.content.room_version = room_version;
1175
1176        if self.sender.is_some() {
1177            event.sender = self.sender.clone();
1178        } else {
1179            event.sender = Some(creator_user_id.to_owned());
1180        }
1181
1182        event.state_key = Some("".to_owned());
1183
1184        event
1185    }
1186
1187    /// Create a new `m.room.power_levels` event.
1188    pub fn power_levels(
1189        &self,
1190        map: &mut BTreeMap<OwnedUserId, Int>,
1191    ) -> EventBuilder<RoomPowerLevelsEventContent> {
1192        let mut event = RoomPowerLevelsEventContent::new(&AuthorizationRules::V1);
1193        event.users.append(map);
1194        self.event(event)
1195    }
1196
1197    /// Create a new `m.room.server_acl` event.
1198    pub fn server_acl(
1199        &self,
1200        allow_ip_literals: bool,
1201        allow: Vec<String>,
1202        deny: Vec<String>,
1203    ) -> EventBuilder<RoomServerAclEventContent> {
1204        self.event(RoomServerAclEventContent::new(allow_ip_literals, allow, deny))
1205    }
1206
1207    /// Create a new `m.room.canonical_alias` event.
1208    pub fn canonical_alias(
1209        &self,
1210        alias: Option<OwnedRoomAliasId>,
1211        alt_aliases: Vec<OwnedRoomAliasId>,
1212    ) -> EventBuilder<RoomCanonicalAliasEventContent> {
1213        let mut event = RoomCanonicalAliasEventContent::new();
1214        event.alias = alias;
1215        event.alt_aliases = alt_aliases;
1216        self.event(event)
1217    }
1218
1219    /// Create a new `org.matrix.msc3672.beacon` event.
1220    ///
1221    /// ```
1222    /// use matrix_sdk_test::event_factory::EventFactory;
1223    /// use ruma::{
1224    ///     MilliSecondsSinceUnixEpoch,
1225    ///     events::{MessageLikeEvent, beacon::BeaconEventContent},
1226    ///     owned_event_id, room_id,
1227    ///     serde::Raw,
1228    ///     user_id,
1229    /// };
1230    ///
1231    /// let factory = EventFactory::new().room(room_id!("!test:localhost"));
1232    ///
1233    /// let event: Raw<MessageLikeEvent<BeaconEventContent>> = factory
1234    ///     .beacon(
1235    ///         owned_event_id!("$123456789abc:localhost"),
1236    ///         10.1,
1237    ///         15.2,
1238    ///         5,
1239    ///         Some(MilliSecondsSinceUnixEpoch(1000u32.into())),
1240    ///     )
1241    ///     .sender(user_id!("@alice:localhost"))
1242    ///     .into_raw();
1243    /// ```
1244    pub fn beacon(
1245        &self,
1246        beacon_info_event_id: OwnedEventId,
1247        latitude: f64,
1248        longitude: f64,
1249        uncertainty: u32,
1250        ts: Option<MilliSecondsSinceUnixEpoch>,
1251    ) -> EventBuilder<BeaconEventContent> {
1252        let geo_uri = format!("geo:{latitude},{longitude};u={uncertainty}");
1253        self.event(BeaconEventContent::new(beacon_info_event_id, geo_uri, ts))
1254    }
1255
1256    /// Create a new `m.sticker` event.
1257    pub fn sticker(
1258        &self,
1259        body: impl Into<String>,
1260        info: ImageInfo,
1261        url: OwnedMxcUri,
1262    ) -> EventBuilder<StickerEventContent> {
1263        self.event(StickerEventContent::new(body.into(), info, url))
1264    }
1265
1266    /// Create a new `m.call.invite` event.
1267    pub fn call_invite(
1268        &self,
1269        call_id: OwnedVoipId,
1270        lifetime: UInt,
1271        offer: SessionDescription,
1272        version: VoipVersionId,
1273    ) -> EventBuilder<CallInviteEventContent> {
1274        self.event(CallInviteEventContent::new(call_id, lifetime, offer, version))
1275    }
1276
1277    /// Create a new `m.rtc.notification` event.
1278    pub fn rtc_notification(
1279        &self,
1280        notification_type: NotificationType,
1281    ) -> EventBuilder<RtcNotificationEventContent> {
1282        self.event(RtcNotificationEventContent::new(
1283            MilliSecondsSinceUnixEpoch::now(),
1284            Duration::new(30, 0),
1285            notification_type,
1286        ))
1287    }
1288
1289    // Creates a new `org.matrix.msc4310.rtc.decline` event.
1290    pub fn call_decline(
1291        &self,
1292        notification_event_id: &EventId,
1293    ) -> EventBuilder<RtcDeclineEventContent> {
1294        self.event(RtcDeclineEventContent::new(notification_event_id))
1295    }
1296
1297    /// Create a new `m.direct` global account data event.
1298    pub fn direct(&self) -> EventBuilder<DirectEventContent> {
1299        self.global_account_data(DirectEventContent::default())
1300    }
1301
1302    /// Create a new `m.ignored_user_list` global account data event.
1303    pub fn ignored_user_list(
1304        &self,
1305        users: impl IntoIterator<Item = OwnedUserId>,
1306    ) -> EventBuilder<IgnoredUserListEventContent> {
1307        self.global_account_data(IgnoredUserListEventContent::users(users))
1308    }
1309
1310    /// Create a new `m.push_rules` global account data event.
1311    pub fn push_rules(&self, rules: Ruleset) -> EventBuilder<PushRulesEventContent> {
1312        self.global_account_data(PushRulesEventContent::new(rules))
1313    }
1314
1315    /// Create a new `m.space.child` state event.
1316    pub fn space_child(
1317        &self,
1318        parent: OwnedRoomId,
1319        child: OwnedRoomId,
1320    ) -> EventBuilder<SpaceChildEventContent> {
1321        let mut event = self.event(SpaceChildEventContent::new(vec![]));
1322        event.room = Some(parent);
1323        event.state_key = Some(child.to_string());
1324        event
1325    }
1326
1327    /// Create a new `m.space.parent` state event.
1328    pub fn space_parent(
1329        &self,
1330        parent: OwnedRoomId,
1331        child: OwnedRoomId,
1332    ) -> EventBuilder<SpaceParentEventContent> {
1333        let mut event = self.event(SpaceParentEventContent::new(vec![]));
1334        event.state_key = Some(parent.to_string());
1335        event.room = Some(child);
1336        event
1337    }
1338
1339    /// Create a new `rs.matrix-sdk.custom.test` custom event
1340    pub fn custom_message_like_event(&self) -> EventBuilder<CustomMessageLikeEventContent> {
1341        self.event(CustomMessageLikeEventContent)
1342    }
1343
1344    /// Set the next server timestamp.
1345    ///
1346    /// Timestamps will continue to increase by 1 (millisecond) from that value.
1347    pub fn set_next_ts(&self, value: u64) {
1348        self.next_ts.store(value, SeqCst);
1349    }
1350
1351    /// Create a new global account data event of the given `C` content type.
1352    pub fn global_account_data<C>(&self, content: C) -> EventBuilder<C>
1353    where
1354        C: GlobalAccountDataEventContent + StaticEventContent<IsPrefix = False>,
1355    {
1356        self.event(content).format(EventFormat::GlobalAccountData)
1357    }
1358}
1359
1360impl EventBuilder<DirectEventContent> {
1361    /// Add a user/room pair to the `m.direct` event.
1362    pub fn add_user(mut self, user_id: OwnedDirectUserIdentifier, room_id: &RoomId) -> Self {
1363        self.content.0.entry(user_id).or_default().push(room_id.to_owned());
1364        self
1365    }
1366}
1367
1368impl EventBuilder<RoomMemberEventContent> {
1369    /// Set the `membership` of the `m.room.member` event to the given
1370    /// [`MembershipState`].
1371    ///
1372    /// The default is [`MembershipState::Join`].
1373    pub fn membership(mut self, state: MembershipState) -> Self {
1374        self.content.membership = state;
1375        self
1376    }
1377
1378    /// Set that the sender of this event invited the user passed as a parameter
1379    /// here.
1380    pub fn invited(mut self, invited_user: &UserId) -> Self {
1381        assert_ne!(
1382            self.sender.as_deref().unwrap(),
1383            invited_user,
1384            "invited user and sender can't be the same person"
1385        );
1386        self.content.membership = MembershipState::Invite;
1387        self.state_key = Some(invited_user.to_string());
1388        self
1389    }
1390
1391    /// Set that the sender of this event kicked the user passed as a parameter
1392    /// here.
1393    pub fn kicked(mut self, kicked_user: &UserId) -> Self {
1394        assert_ne!(
1395            self.sender.as_deref().unwrap(),
1396            kicked_user,
1397            "kicked user and sender can't be the same person, otherwise it's just a Leave"
1398        );
1399        self.content.membership = MembershipState::Leave;
1400        self.state_key = Some(kicked_user.to_string());
1401        self
1402    }
1403
1404    /// Set that the sender of this event banned the user passed as a parameter
1405    /// here.
1406    pub fn banned(mut self, banned_user: &UserId) -> Self {
1407        assert_ne!(
1408            self.sender.as_deref().unwrap(),
1409            banned_user,
1410            "a user can't ban itself" // hopefully
1411        );
1412        self.content.membership = MembershipState::Ban;
1413        self.state_key = Some(banned_user.to_string());
1414        self
1415    }
1416
1417    /// Set the display name of the `m.room.member` event.
1418    pub fn display_name(mut self, display_name: impl Into<String>) -> Self {
1419        self.content.displayname = Some(display_name.into());
1420        self
1421    }
1422
1423    /// Set the avatar URL of the `m.room.member` event.
1424    pub fn avatar_url(mut self, url: &MxcUri) -> Self {
1425        self.content.avatar_url = Some(url.to_owned());
1426        self
1427    }
1428
1429    /// Set the reason field of the `m.room.member` event.
1430    pub fn reason(mut self, reason: impl Into<String>) -> Self {
1431        self.content.reason = Some(reason.into());
1432        self
1433    }
1434
1435    /// Set the previous membership state (in the unsigned section).
1436    pub fn previous(mut self, previous: impl Into<PreviousMembership>) -> Self {
1437        let previous = previous.into();
1438
1439        let mut prev_content = RoomMemberEventContent::new(previous.state);
1440        if let Some(avatar_url) = previous.avatar_url {
1441            prev_content.avatar_url = Some(avatar_url);
1442        }
1443        if let Some(display_name) = previous.display_name {
1444            prev_content.displayname = Some(display_name);
1445        }
1446
1447        self.unsigned.get_or_insert_with(Default::default).prev_content = Some(prev_content);
1448        self
1449    }
1450}
1451
1452impl EventBuilder<RoomAvatarEventContent> {
1453    /// Defines the URL for the room avatar.
1454    pub fn url(mut self, url: &MxcUri) -> Self {
1455        self.content.url = Some(url.to_owned());
1456        self
1457    }
1458
1459    /// Defines the image info for the avatar.
1460    pub fn info(mut self, image: avatar::ImageInfo) -> Self {
1461        self.content.info = Some(Box::new(image));
1462        self
1463    }
1464}
1465
1466impl EventBuilder<RtcNotificationEventContent> {
1467    pub fn mentions(mut self, users: impl IntoIterator<Item = OwnedUserId>) -> Self {
1468        self.content.mentions = Some(Mentions::with_user_ids(users));
1469        self
1470    }
1471
1472    pub fn relates_to_membership_state_event(mut self, event_id: OwnedEventId) -> Self {
1473        self.content.relates_to = Some(Reference::new(event_id));
1474        self
1475    }
1476
1477    pub fn lifetime(mut self, time_in_seconds: u64) -> Self {
1478        self.content.lifetime = Duration::from_secs(time_in_seconds);
1479        self
1480    }
1481}
1482
1483pub struct ReadReceiptBuilder<'a> {
1484    factory: &'a EventFactory,
1485    content: ReceiptEventContent,
1486}
1487
1488impl ReadReceiptBuilder<'_> {
1489    /// Add a single read receipt to the event.
1490    pub fn add(
1491        self,
1492        event_id: &EventId,
1493        user_id: &UserId,
1494        tyype: ReceiptType,
1495        thread: ReceiptThread,
1496    ) -> Self {
1497        let ts = self.factory.next_server_ts();
1498        self.add_with_timestamp(event_id, user_id, tyype, thread, Some(ts))
1499    }
1500
1501    /// Add a single read receipt to the event, with an optional timestamp.
1502    pub fn add_with_timestamp(
1503        mut self,
1504        event_id: &EventId,
1505        user_id: &UserId,
1506        tyype: ReceiptType,
1507        thread: ReceiptThread,
1508        ts: Option<MilliSecondsSinceUnixEpoch>,
1509    ) -> Self {
1510        let by_event = self.content.0.entry(event_id.to_owned()).or_default();
1511        let by_type = by_event.entry(tyype).or_default();
1512
1513        let mut receipt = Receipt::default();
1514        if let Some(ts) = ts {
1515            receipt.ts = Some(ts);
1516        }
1517        receipt.thread = thread;
1518
1519        by_type.insert(user_id.to_owned(), receipt);
1520        self
1521    }
1522
1523    /// Finalize the builder into the receipt event content.
1524    pub fn into_content(self) -> ReceiptEventContent {
1525        self.content
1526    }
1527
1528    /// Finalize the builder into an event builder.
1529    pub fn into_event(self) -> EventBuilder<ReceiptEventContent> {
1530        self.factory.event(self.into_content()).format(EventFormat::Ephemeral)
1531    }
1532}
1533
1534pub struct PreviousMembership {
1535    state: MembershipState,
1536    avatar_url: Option<OwnedMxcUri>,
1537    display_name: Option<String>,
1538}
1539
1540impl PreviousMembership {
1541    pub fn new(state: MembershipState) -> Self {
1542        Self { state, avatar_url: None, display_name: None }
1543    }
1544
1545    pub fn avatar_url(mut self, url: &MxcUri) -> Self {
1546        self.avatar_url = Some(url.to_owned());
1547        self
1548    }
1549
1550    pub fn display_name(mut self, name: impl Into<String>) -> Self {
1551        self.display_name = Some(name.into());
1552        self
1553    }
1554}
1555
1556impl From<MembershipState> for PreviousMembership {
1557    fn from(state: MembershipState) -> Self {
1558        Self::new(state)
1559    }
1560}
1561
1562#[derive(Clone, Default, Debug, Serialize, EventContent)]
1563#[ruma_event(type = "rs.matrix-sdk.custom.test", kind = MessageLike)]
1564pub struct CustomMessageLikeEventContent;