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