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};
21
22use as_variant::as_variant;
23use matrix_sdk_common::deserialized_responses::{
24    TimelineEvent, UnableToDecryptInfo, UnableToDecryptReason,
25};
26use ruma::{
27    EventId, Int, MilliSecondsSinceUnixEpoch, MxcUri, OwnedEventId, OwnedMxcUri, OwnedRoomAliasId,
28    OwnedRoomId, OwnedTransactionId, OwnedUserId, OwnedVoipId, RoomId, RoomVersionId,
29    TransactionId, UInt, UserId, VoipVersionId,
30    events::{
31        AnyGlobalAccountDataEvent, AnyStateEvent, AnySyncMessageLikeEvent, AnySyncStateEvent,
32        AnySyncTimelineEvent, AnyTimelineEvent, BundledMessageLikeRelations, False, Mentions,
33        RedactedMessageLikeEventContent, RedactedStateEventContent, StateEventContent,
34        StaticEventContent,
35        beacon::BeaconEventContent,
36        call::{
37            SessionDescription,
38            invite::CallInviteEventContent,
39            notify::{ApplicationType, CallNotifyEventContent, NotifyType},
40        },
41        direct::{DirectEventContent, OwnedDirectUserIdentifier},
42        ignored_user_list::IgnoredUserListEventContent,
43        member_hints::MemberHintsEventContent,
44        poll::{
45            unstable_end::UnstablePollEndEventContent,
46            unstable_response::UnstablePollResponseEventContent,
47            unstable_start::{
48                NewUnstablePollStartEventContent, ReplacementUnstablePollStartEventContent,
49                UnstablePollAnswer, UnstablePollStartContentBlock, UnstablePollStartEventContent,
50            },
51        },
52        push_rules::PushRulesEventContent,
53        reaction::ReactionEventContent,
54        receipt::{Receipt, ReceiptEventContent, ReceiptThread, ReceiptType},
55        relation::{Annotation, BundledThread, InReplyTo, Replacement, Thread},
56        room::{
57            ImageInfo,
58            avatar::{self, RoomAvatarEventContent},
59            canonical_alias::RoomCanonicalAliasEventContent,
60            create::{PreviousRoom, RoomCreateEventContent},
61            encrypted::{EncryptedEventScheme, RoomEncryptedEventContent},
62            member::{MembershipState, RoomMemberEventContent},
63            message::{
64                FormattedBody, GalleryItemType, GalleryMessageEventContent,
65                ImageMessageEventContent, MessageType, OriginalSyncRoomMessageEvent, Relation,
66                RelationWithoutReplacement, RoomMessageEventContent,
67                RoomMessageEventContentWithoutRelation,
68            },
69            name::RoomNameEventContent,
70            power_levels::RoomPowerLevelsEventContent,
71            redaction::RoomRedactionEventContent,
72            server_acl::RoomServerAclEventContent,
73            tombstone::RoomTombstoneEventContent,
74            topic::RoomTopicEventContent,
75        },
76        space::{child::SpaceChildEventContent, parent::SpaceParentEventContent},
77        sticker::StickerEventContent,
78        typing::TypingEventContent,
79    },
80    push::Ruleset,
81    room::RoomType,
82    room_version_rules::AuthorizationRules,
83    serde::Raw,
84    server_name,
85};
86use serde::Serialize;
87use serde_json::json;
88
89pub trait TimestampArg {
90    fn to_milliseconds_since_unix_epoch(self) -> MilliSecondsSinceUnixEpoch;
91}
92
93impl TimestampArg for MilliSecondsSinceUnixEpoch {
94    fn to_milliseconds_since_unix_epoch(self) -> MilliSecondsSinceUnixEpoch {
95        self
96    }
97}
98
99impl TimestampArg for u64 {
100    fn to_milliseconds_since_unix_epoch(self) -> MilliSecondsSinceUnixEpoch {
101        MilliSecondsSinceUnixEpoch(UInt::try_from(self).unwrap())
102    }
103}
104
105/// A thin copy of [`ruma::events::UnsignedRoomRedactionEvent`].
106#[derive(Debug, Serialize)]
107struct RedactedBecause {
108    /// Data specific to the event type.
109    content: RoomRedactionEventContent,
110
111    /// The globally unique event identifier for the user who sent the event.
112    event_id: OwnedEventId,
113
114    /// The fully-qualified ID of the user who sent this event.
115    sender: OwnedUserId,
116
117    /// Timestamp in milliseconds on originating homeserver when this event was
118    /// sent.
119    origin_server_ts: MilliSecondsSinceUnixEpoch,
120}
121
122#[derive(Debug, Serialize)]
123struct Unsigned<C: StaticEventContent> {
124    #[serde(skip_serializing_if = "Option::is_none")]
125    prev_content: Option<C>,
126
127    #[serde(skip_serializing_if = "Option::is_none")]
128    transaction_id: Option<OwnedTransactionId>,
129
130    #[serde(rename = "m.relations", skip_serializing_if = "Option::is_none")]
131    relations: Option<BundledMessageLikeRelations<Raw<AnySyncTimelineEvent>>>,
132
133    #[serde(skip_serializing_if = "Option::is_none")]
134    redacted_because: Option<RedactedBecause>,
135
136    #[serde(skip_serializing_if = "Option::is_none")]
137    age: Option<Int>,
138}
139
140// rustc can't derive Default because C isn't marked as `Default` 🤔 oh well.
141impl<C: StaticEventContent> Default for Unsigned<C> {
142    fn default() -> Self {
143        Self {
144            prev_content: None,
145            transaction_id: None,
146            relations: None,
147            redacted_because: None,
148            age: None,
149        }
150    }
151}
152
153#[derive(Debug)]
154pub struct EventBuilder<C: StaticEventContent<IsPrefix = False>> {
155    sender: Option<OwnedUserId>,
156    /// Whether the event is an ephemeral one. As such, it doesn't require a
157    /// room id or a sender.
158    is_ephemeral: bool,
159    /// Whether the event is global account data. As such, it doesn't require a
160    /// room id.
161    is_global: bool,
162    room: Option<OwnedRoomId>,
163    event_id: Option<OwnedEventId>,
164    /// Whether the event should *not* have an event id. False by default.
165    no_event_id: bool,
166    redacts: Option<OwnedEventId>,
167    content: C,
168    server_ts: MilliSecondsSinceUnixEpoch,
169    unsigned: Option<Unsigned<C>>,
170    state_key: Option<String>,
171}
172
173impl<E: StaticEventContent<IsPrefix = False>> EventBuilder<E> {
174    pub fn room(mut self, room_id: &RoomId) -> Self {
175        self.room = Some(room_id.to_owned());
176        self
177    }
178
179    pub fn sender(mut self, sender: &UserId) -> Self {
180        self.sender = Some(sender.to_owned());
181        self
182    }
183
184    pub fn event_id(mut self, event_id: &EventId) -> Self {
185        self.event_id = Some(event_id.to_owned());
186        self.no_event_id = false;
187        self
188    }
189
190    pub fn no_event_id(mut self) -> Self {
191        self.event_id = None;
192        self.no_event_id = true;
193        self
194    }
195
196    pub fn server_ts(mut self, ts: impl TimestampArg) -> Self {
197        self.server_ts = ts.to_milliseconds_since_unix_epoch();
198        self
199    }
200
201    pub fn unsigned_transaction_id(mut self, transaction_id: &TransactionId) -> Self {
202        self.unsigned.get_or_insert_with(Default::default).transaction_id =
203            Some(transaction_id.to_owned());
204        self
205    }
206
207    /// Add age to unsigned data in this event.
208    pub fn age(mut self, age: impl Into<Int>) -> Self {
209        self.unsigned.get_or_insert_with(Default::default).age = Some(age.into());
210        self
211    }
212
213    /// Create a bundled thread summary in the unsigned bundled relations of
214    /// this event.
215    pub fn with_bundled_thread_summary(
216        mut self,
217        latest_event: Raw<AnySyncMessageLikeEvent>,
218        count: usize,
219        current_user_participated: bool,
220    ) -> Self {
221        let relations = self
222            .unsigned
223            .get_or_insert_with(Default::default)
224            .relations
225            .get_or_insert_with(BundledMessageLikeRelations::new);
226        relations.thread = Some(Box::new(BundledThread::new(
227            latest_event,
228            UInt::try_from(count).unwrap(),
229            current_user_participated,
230        )));
231        self
232    }
233
234    /// Create a bundled edit in the unsigned bundled relations of this event.
235    pub fn with_bundled_edit(mut self, replacement: impl Into<Raw<AnySyncTimelineEvent>>) -> Self {
236        let relations = self
237            .unsigned
238            .get_or_insert_with(Default::default)
239            .relations
240            .get_or_insert_with(BundledMessageLikeRelations::new);
241        relations.replace = Some(Box::new(replacement.into()));
242        self
243    }
244
245    /// For state events manually created, define the state key.
246    ///
247    /// For other state events created in the [`EventFactory`], this is
248    /// automatically filled upon creation or update of the events.
249    pub fn state_key(mut self, state_key: impl Into<String>) -> Self {
250        self.state_key = Some(state_key.into());
251        self
252    }
253}
254
255impl<E> EventBuilder<E>
256where
257    E: StaticEventContent<IsPrefix = False> + Serialize,
258{
259    #[inline(always)]
260    fn construct_json(self, requires_room: bool) -> serde_json::Value {
261        // Use the `sender` preferably, or resort to the `redacted_because` sender if
262        // none has been set.
263        let sender = self
264            .sender
265            .or_else(|| Some(self.unsigned.as_ref()?.redacted_because.as_ref()?.sender.clone()));
266
267        let mut json = json!({
268            "type": E::TYPE,
269            "content": self.content,
270            "origin_server_ts": self.server_ts,
271        });
272
273        let map = json.as_object_mut().unwrap();
274
275        if let Some(sender) = sender {
276            if !self.is_ephemeral && !self.is_global {
277                map.insert("sender".to_owned(), json!(sender));
278            }
279        } else {
280            assert!(
281                self.is_ephemeral || self.is_global,
282                "the sender must be known when building the JSON for a non read-receipt or global event"
283            );
284        }
285
286        let event_id = self
287            .event_id
288            .or_else(|| {
289                self.room.as_ref().map(|room_id| EventId::new(room_id.server_name().unwrap()))
290            })
291            .or_else(|| (!self.no_event_id).then(|| EventId::new(server_name!("dummy.org"))));
292        if let Some(event_id) = event_id {
293            map.insert("event_id".to_owned(), json!(event_id));
294        }
295
296        if requires_room && !self.is_ephemeral && !self.is_global {
297            let room_id = self.room.expect("TimelineEvent requires a room id");
298            map.insert("room_id".to_owned(), json!(room_id));
299        }
300
301        if let Some(redacts) = self.redacts {
302            map.insert("redacts".to_owned(), json!(redacts));
303        }
304
305        if let Some(unsigned) = self.unsigned {
306            map.insert("unsigned".to_owned(), json!(unsigned));
307        }
308
309        if let Some(state_key) = self.state_key {
310            map.insert("state_key".to_owned(), json!(state_key));
311        }
312
313        json
314    }
315
316    /// Build an event from the [`EventBuilder`] and convert it into a
317    /// serialized and [`Raw`] event.
318    ///
319    /// The generic argument `T` allows you to automatically cast the [`Raw`]
320    /// event into any desired type.
321    pub fn into_raw<T>(self) -> Raw<T> {
322        Raw::new(&self.construct_json(true)).unwrap().cast_unchecked()
323    }
324
325    pub fn into_raw_timeline(self) -> Raw<AnyTimelineEvent> {
326        self.into_raw()
327    }
328
329    pub fn into_any_sync_message_like_event(self) -> AnySyncMessageLikeEvent {
330        self.into_raw().deserialize().expect("expected message like event")
331    }
332
333    pub fn into_original_sync_room_message_event(self) -> OriginalSyncRoomMessageEvent {
334        self.into_raw().deserialize().expect("expected original sync room message event")
335    }
336
337    pub fn into_raw_sync(self) -> Raw<AnySyncTimelineEvent> {
338        Raw::new(&self.construct_json(false)).unwrap().cast_unchecked()
339    }
340
341    pub fn into_event(self) -> TimelineEvent {
342        TimelineEvent::from_plaintext(self.into_raw_sync())
343    }
344}
345
346impl EventBuilder<RoomEncryptedEventContent> {
347    /// Turn this event into a [`TimelineEvent`] representing a decryption
348    /// failure
349    pub fn into_utd_sync_timeline_event(self) -> TimelineEvent {
350        let session_id = as_variant!(&self.content.scheme, EncryptedEventScheme::MegolmV1AesSha2)
351            .map(|content| content.session_id.clone());
352
353        TimelineEvent::from_utd(
354            self.into(),
355            UnableToDecryptInfo {
356                session_id,
357                reason: UnableToDecryptReason::MissingMegolmSession { withheld_code: None },
358            },
359        )
360    }
361}
362
363impl EventBuilder<RoomMessageEventContent> {
364    /// Adds a reply relation to the current event.
365    pub fn reply_to(mut self, event_id: &EventId) -> Self {
366        self.content.relates_to =
367            Some(Relation::Reply { in_reply_to: InReplyTo::new(event_id.to_owned()) });
368        self
369    }
370
371    /// Adds a thread relation to the root event, setting the reply fallback to
372    /// the latest in-thread event.
373    pub fn in_thread(mut self, root: &EventId, latest_thread_event: &EventId) -> Self {
374        self.content.relates_to =
375            Some(Relation::Thread(Thread::plain(root.to_owned(), latest_thread_event.to_owned())));
376        self
377    }
378
379    /// Adds a thread relation to the root event, that's a non-fallback reply to
380    /// another thread event.
381    pub fn in_thread_reply(mut self, root: &EventId, replied_to: &EventId) -> Self {
382        self.content.relates_to =
383            Some(Relation::Thread(Thread::reply(root.to_owned(), replied_to.to_owned())));
384        self
385    }
386
387    /// Adds the given mentions to the current event.
388    pub fn mentions(mut self, mentions: Mentions) -> Self {
389        self.content.mentions = Some(mentions);
390        self
391    }
392
393    /// Adds a replacement relation to the current event, with the new content
394    /// passed.
395    pub fn edit(
396        mut self,
397        edited_event_id: &EventId,
398        new_content: RoomMessageEventContentWithoutRelation,
399    ) -> Self {
400        self.content.relates_to =
401            Some(Relation::Replacement(Replacement::new(edited_event_id.to_owned(), new_content)));
402        self
403    }
404
405    /// Adds a caption to a media event.
406    ///
407    /// Will crash if the event isn't a media room message.
408    pub fn caption(
409        mut self,
410        caption: Option<String>,
411        formatted_caption: Option<FormattedBody>,
412    ) -> Self {
413        match &mut self.content.msgtype {
414            MessageType::Image(image) => {
415                let filename = image.filename().to_owned();
416                if let Some(caption) = caption {
417                    image.body = caption;
418                    image.filename = Some(filename);
419                } else {
420                    image.body = filename;
421                    image.filename = None;
422                }
423                image.formatted = formatted_caption;
424            }
425
426            MessageType::Audio(_) | MessageType::Video(_) | MessageType::File(_) => {
427                unimplemented!();
428            }
429
430            _ => panic!("unexpected event type for a caption"),
431        }
432
433        self
434    }
435}
436
437impl EventBuilder<UnstablePollStartEventContent> {
438    /// Adds a reply relation to the current event.
439    pub fn reply_to(mut self, event_id: &EventId) -> Self {
440        if let UnstablePollStartEventContent::New(content) = &mut self.content {
441            content.relates_to = Some(RelationWithoutReplacement::Reply {
442                in_reply_to: InReplyTo::new(event_id.to_owned()),
443            });
444        }
445        self
446    }
447
448    /// Adds a thread relation to the root event, setting the reply to
449    /// event id as well.
450    pub fn in_thread(mut self, root: &EventId, reply_to_event_id: &EventId) -> Self {
451        let thread = Thread::reply(root.to_owned(), reply_to_event_id.to_owned());
452
453        if let UnstablePollStartEventContent::New(content) = &mut self.content {
454            content.relates_to = Some(RelationWithoutReplacement::Thread(thread));
455        }
456        self
457    }
458}
459
460impl EventBuilder<RoomCreateEventContent> {
461    /// Define the predecessor fields.
462    pub fn predecessor(mut self, room_id: &RoomId) -> Self {
463        self.content.predecessor = Some(PreviousRoom::new(room_id.to_owned()));
464        self
465    }
466
467    /// Erase the predecessor if any.
468    pub fn no_predecessor(mut self) -> Self {
469        self.content.predecessor = None;
470        self
471    }
472
473    /// Sets the `m.room.create` `type` field to `m.space`.
474    pub fn with_space_type(mut self) -> Self {
475        self.content.room_type = Some(RoomType::Space);
476        self
477    }
478}
479
480impl EventBuilder<StickerEventContent> {
481    /// Add reply [`Thread`] relation to root event and set replied-to event id.
482    pub fn reply_thread(mut self, root: &EventId, reply_to_event: &EventId) -> Self {
483        self.content.relates_to =
484            Some(Relation::Thread(Thread::reply(root.to_owned(), reply_to_event.to_owned())));
485        self
486    }
487}
488
489impl<E: StaticEventContent<IsPrefix = False>> From<EventBuilder<E>> for Raw<AnySyncTimelineEvent>
490where
491    E: Serialize,
492{
493    fn from(val: EventBuilder<E>) -> Self {
494        val.into_raw_sync()
495    }
496}
497
498impl<E: StaticEventContent<IsPrefix = False>> From<EventBuilder<E>> for Raw<AnyTimelineEvent>
499where
500    E: Serialize,
501{
502    fn from(val: EventBuilder<E>) -> Self {
503        val.into_raw_timeline()
504    }
505}
506
507impl<E: StaticEventContent<IsPrefix = False>> From<EventBuilder<E>>
508    for Raw<AnyGlobalAccountDataEvent>
509where
510    E: Serialize,
511{
512    fn from(val: EventBuilder<E>) -> Self {
513        val.into_raw()
514    }
515}
516
517impl<E: StaticEventContent<IsPrefix = False>> From<EventBuilder<E>> for TimelineEvent
518where
519    E: Serialize,
520{
521    fn from(val: EventBuilder<E>) -> Self {
522        val.into_event()
523    }
524}
525
526impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
527    for Raw<AnySyncStateEvent>
528{
529    fn from(val: EventBuilder<E>) -> Self {
530        Raw::new(&val.construct_json(false)).unwrap().cast_unchecked()
531    }
532}
533
534impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
535    for Raw<AnyStateEvent>
536{
537    fn from(val: EventBuilder<E>) -> Self {
538        Raw::new(&val.construct_json(true)).unwrap().cast_unchecked()
539    }
540}
541
542#[derive(Debug, Default)]
543pub struct EventFactory {
544    next_ts: AtomicU64,
545    sender: Option<OwnedUserId>,
546    room: Option<OwnedRoomId>,
547}
548
549impl EventFactory {
550    pub fn new() -> Self {
551        Self { next_ts: AtomicU64::new(0), sender: None, room: None }
552    }
553
554    pub fn room(mut self, room_id: &RoomId) -> Self {
555        self.room = Some(room_id.to_owned());
556        self
557    }
558
559    pub fn sender(mut self, sender: &UserId) -> Self {
560        self.sender = Some(sender.to_owned());
561        self
562    }
563
564    fn next_server_ts(&self) -> MilliSecondsSinceUnixEpoch {
565        MilliSecondsSinceUnixEpoch(
566            self.next_ts
567                .fetch_add(1, SeqCst)
568                .try_into()
569                .expect("server timestamp should fit in js_int::UInt"),
570        )
571    }
572
573    /// Create an event from any event content.
574    pub fn event<E: StaticEventContent<IsPrefix = False>>(&self, content: E) -> EventBuilder<E> {
575        EventBuilder {
576            sender: self.sender.clone(),
577            is_ephemeral: false,
578            is_global: false,
579            room: self.room.clone(),
580            server_ts: self.next_server_ts(),
581            event_id: None,
582            no_event_id: false,
583            redacts: None,
584            content,
585            unsigned: None,
586            state_key: None,
587        }
588    }
589
590    /// Create a new plain text `m.room.message`.
591    pub fn text_msg(&self, content: impl Into<String>) -> EventBuilder<RoomMessageEventContent> {
592        self.event(RoomMessageEventContent::text_plain(content.into()))
593    }
594
595    /// Create a new plain emote `m.room.message`.
596    pub fn emote(&self, content: impl Into<String>) -> EventBuilder<RoomMessageEventContent> {
597        self.event(RoomMessageEventContent::emote_plain(content.into()))
598    }
599
600    /// Create a new `m.room.member` event for the given member.
601    ///
602    /// The given member will be used as the `sender` as well as the `state_key`
603    /// of the `m.room.member` event, unless the `sender` was already using
604    /// [`EventFactory::sender()`], in that case only the state key will be
605    /// set to the given `member`.
606    ///
607    /// The `membership` field of the content is set to
608    /// [`MembershipState::Join`].
609    ///
610    /// ```
611    /// use matrix_sdk_test::event_factory::EventFactory;
612    /// use ruma::{
613    ///     events::{
614    ///         SyncStateEvent,
615    ///         room::member::{MembershipState, RoomMemberEventContent},
616    ///     },
617    ///     room_id,
618    ///     serde::Raw,
619    ///     user_id,
620    /// };
621    ///
622    /// let factory = EventFactory::new().room(room_id!("!test:localhost"));
623    ///
624    /// let event: Raw<SyncStateEvent<RoomMemberEventContent>> = factory
625    ///     .member(user_id!("@alice:localhost"))
626    ///     .display_name("Alice")
627    ///     .into_raw();
628    /// ```
629    pub fn member(&self, member: &UserId) -> EventBuilder<RoomMemberEventContent> {
630        let mut event = self.event(RoomMemberEventContent::new(MembershipState::Join));
631
632        if self.sender.is_some() {
633            event.sender = self.sender.clone();
634        } else {
635            event.sender = Some(member.to_owned());
636        }
637
638        event.state_key = Some(member.to_string());
639
640        event
641    }
642
643    /// Create a tombstone state event for the room.
644    pub fn room_tombstone(
645        &self,
646        body: impl Into<String>,
647        replacement: &RoomId,
648    ) -> EventBuilder<RoomTombstoneEventContent> {
649        let mut event =
650            self.event(RoomTombstoneEventContent::new(body.into(), replacement.to_owned()));
651        event.state_key = Some("".to_owned());
652        event
653    }
654
655    /// Create a state event for the topic.
656    pub fn room_topic(&self, topic: impl Into<String>) -> EventBuilder<RoomTopicEventContent> {
657        let mut event = self.event(RoomTopicEventContent::new(topic.into()));
658        // The state key is empty for a room topic state event.
659        event.state_key = Some("".to_owned());
660        event
661    }
662
663    /// Create a state event for the room name.
664    pub fn room_name(&self, name: impl Into<String>) -> EventBuilder<RoomNameEventContent> {
665        let mut event = self.event(RoomNameEventContent::new(name.into()));
666        // The state key is empty for a room name state event.
667        event.state_key = Some("".to_owned());
668        event
669    }
670
671    /// Create an empty state event for the room avatar.
672    pub fn room_avatar(&self) -> EventBuilder<RoomAvatarEventContent> {
673        let mut event = self.event(RoomAvatarEventContent::new());
674        // The state key is empty for a room avatar state event.
675        event.state_key = Some("".to_owned());
676        event
677    }
678
679    /// Create a new `m.member_hints` event with the given service members.
680    ///
681    /// ```
682    /// use std::collections::BTreeSet;
683    ///
684    /// use matrix_sdk_test::event_factory::EventFactory;
685    /// use ruma::{
686    ///     events::{SyncStateEvent, member_hints::MemberHintsEventContent},
687    ///     owned_user_id, room_id,
688    ///     serde::Raw,
689    ///     user_id,
690    /// };
691    ///
692    /// let factory = EventFactory::new().room(room_id!("!test:localhost"));
693    ///
694    /// let event: Raw<SyncStateEvent<MemberHintsEventContent>> = factory
695    ///     .member_hints(BTreeSet::from([owned_user_id!("@alice:localhost")]))
696    ///     .sender(user_id!("@alice:localhost"))
697    ///     .into_raw();
698    /// ```
699    pub fn member_hints(
700        &self,
701        service_members: BTreeSet<OwnedUserId>,
702    ) -> EventBuilder<MemberHintsEventContent> {
703        // The `m.member_hints` event always has an empty state key, so let's set it.
704        self.event(MemberHintsEventContent::new(service_members)).state_key("")
705    }
706
707    /// Create a new plain/html `m.room.message`.
708    pub fn text_html(
709        &self,
710        plain: impl Into<String>,
711        html: impl Into<String>,
712    ) -> EventBuilder<RoomMessageEventContent> {
713        self.event(RoomMessageEventContent::text_html(plain, html))
714    }
715
716    /// Create a new plain notice `m.room.message`.
717    pub fn notice(&self, content: impl Into<String>) -> EventBuilder<RoomMessageEventContent> {
718        self.event(RoomMessageEventContent::notice_plain(content))
719    }
720
721    /// Add a reaction to an event.
722    pub fn reaction(
723        &self,
724        event_id: &EventId,
725        annotation: impl Into<String>,
726    ) -> EventBuilder<ReactionEventContent> {
727        self.event(ReactionEventContent::new(Annotation::new(
728            event_id.to_owned(),
729            annotation.into(),
730        )))
731    }
732
733    /// Create a live redaction for the given event id.
734    ///
735    /// Note: this is not a redacted event, but a redaction event, that will
736    /// cause another event to be redacted.
737    pub fn redaction(&self, event_id: &EventId) -> EventBuilder<RoomRedactionEventContent> {
738        let mut builder = self.event(RoomRedactionEventContent::new_v11(event_id.to_owned()));
739        builder.redacts = Some(event_id.to_owned());
740        builder
741    }
742
743    /// Create a redacted event, with extra information in the unsigned section
744    /// about the redaction itself.
745    pub fn redacted<T: StaticEventContent<IsPrefix = False> + RedactedMessageLikeEventContent>(
746        &self,
747        redacter: &UserId,
748        content: T,
749    ) -> EventBuilder<T> {
750        let mut builder = self.event(content);
751
752        let redacted_because = RedactedBecause {
753            content: RoomRedactionEventContent::default(),
754            event_id: EventId::new(server_name!("dummy.server")),
755            sender: redacter.to_owned(),
756            origin_server_ts: self.next_server_ts(),
757        };
758        builder.unsigned.get_or_insert_with(Default::default).redacted_because =
759            Some(redacted_because);
760
761        builder
762    }
763
764    /// Create a redacted state event, with extra information in the unsigned
765    /// section about the redaction itself.
766    pub fn redacted_state<T: StaticEventContent<IsPrefix = False> + RedactedStateEventContent>(
767        &self,
768        redacter: &UserId,
769        state_key: impl Into<String>,
770        content: T,
771    ) -> EventBuilder<T> {
772        let mut builder = self.event(content);
773
774        let redacted_because = RedactedBecause {
775            content: RoomRedactionEventContent::default(),
776            event_id: EventId::new(server_name!("dummy.server")),
777            sender: redacter.to_owned(),
778            origin_server_ts: self.next_server_ts(),
779        };
780        builder.unsigned.get_or_insert_with(Default::default).redacted_because =
781            Some(redacted_because);
782        builder.state_key = Some(state_key.into());
783
784        builder
785    }
786
787    /// Create a poll start event given a text, the question and the possible
788    /// answers.
789    pub fn poll_start(
790        &self,
791        content: impl Into<String>,
792        poll_question: impl Into<String>,
793        answers: Vec<impl Into<String>>,
794    ) -> EventBuilder<UnstablePollStartEventContent> {
795        // PollAnswers 'constructor' is not public, so we need to deserialize them
796        let answers: Vec<UnstablePollAnswer> = answers
797            .into_iter()
798            .enumerate()
799            .map(|(idx, answer)| UnstablePollAnswer::new(idx.to_string(), answer))
800            .collect();
801        let poll_answers = answers.try_into().unwrap();
802        let poll_start_content =
803            UnstablePollStartEventContent::New(NewUnstablePollStartEventContent::plain_text(
804                content,
805                UnstablePollStartContentBlock::new(poll_question, poll_answers),
806            ));
807        self.event(poll_start_content)
808    }
809
810    /// Create a poll edit event given the new question and possible answers.
811    pub fn poll_edit(
812        &self,
813        edited_event_id: &EventId,
814        poll_question: impl Into<String>,
815        answers: Vec<impl Into<String>>,
816    ) -> EventBuilder<ReplacementUnstablePollStartEventContent> {
817        // PollAnswers 'constructor' is not public, so we need to deserialize them
818        let answers: Vec<UnstablePollAnswer> = answers
819            .into_iter()
820            .enumerate()
821            .map(|(idx, answer)| UnstablePollAnswer::new(idx.to_string(), answer))
822            .collect();
823        let poll_answers = answers.try_into().unwrap();
824        let poll_start_content_block =
825            UnstablePollStartContentBlock::new(poll_question, poll_answers);
826        self.event(ReplacementUnstablePollStartEventContent::new(
827            poll_start_content_block,
828            edited_event_id.to_owned(),
829        ))
830    }
831
832    /// Create a poll response with the given answer id and the associated poll
833    /// start event id.
834    pub fn poll_response(
835        &self,
836        answers: Vec<impl Into<String>>,
837        poll_start_id: &EventId,
838    ) -> EventBuilder<UnstablePollResponseEventContent> {
839        self.event(UnstablePollResponseEventContent::new(
840            answers.into_iter().map(Into::into).collect(),
841            poll_start_id.to_owned(),
842        ))
843    }
844
845    /// Create a poll response with the given text and the associated poll start
846    /// event id.
847    pub fn poll_end(
848        &self,
849        content: impl Into<String>,
850        poll_start_id: &EventId,
851    ) -> EventBuilder<UnstablePollEndEventContent> {
852        self.event(UnstablePollEndEventContent::new(content.into(), poll_start_id.to_owned()))
853    }
854
855    /// Creates a plain (unencrypted) image event content referencing the given
856    /// MXC ID.
857    pub fn image(
858        &self,
859        filename: String,
860        url: OwnedMxcUri,
861    ) -> EventBuilder<RoomMessageEventContent> {
862        let image_event_content = ImageMessageEventContent::plain(filename, url);
863        self.event(RoomMessageEventContent::new(MessageType::Image(image_event_content)))
864    }
865
866    /// Create a gallery event containing a single plain (unencrypted) image
867    /// referencing the given MXC ID.
868    pub fn gallery(
869        &self,
870        body: String,
871        filename: String,
872        url: OwnedMxcUri,
873    ) -> EventBuilder<RoomMessageEventContent> {
874        let gallery_event_content = GalleryMessageEventContent::new(
875            body,
876            None,
877            vec![GalleryItemType::Image(ImageMessageEventContent::plain(filename, url))],
878        );
879        self.event(RoomMessageEventContent::new(MessageType::Gallery(gallery_event_content)))
880    }
881
882    /// Create a typing notification event.
883    pub fn typing(&self, user_ids: Vec<&UserId>) -> EventBuilder<TypingEventContent> {
884        let mut builder = self
885            .event(TypingEventContent::new(user_ids.into_iter().map(ToOwned::to_owned).collect()));
886        builder.is_ephemeral = true;
887        builder
888    }
889
890    /// Create a read receipt event.
891    pub fn read_receipts(&self) -> ReadReceiptBuilder<'_> {
892        ReadReceiptBuilder { factory: self, content: ReceiptEventContent(Default::default()) }
893    }
894
895    /// Create a new `m.room.create` event.
896    pub fn create(
897        &self,
898        creator_user_id: &UserId,
899        room_version: RoomVersionId,
900    ) -> EventBuilder<RoomCreateEventContent> {
901        let mut event = self.event(RoomCreateEventContent::new_v1(creator_user_id.to_owned()));
902        event.content.room_version = room_version;
903
904        if self.sender.is_some() {
905            event.sender = self.sender.clone();
906        } else {
907            event.sender = Some(creator_user_id.to_owned());
908        }
909
910        event.state_key = Some("".to_owned());
911
912        event
913    }
914
915    /// Create a new `m.room.power_levels` event.
916    pub fn power_levels(
917        &self,
918        map: &mut BTreeMap<OwnedUserId, Int>,
919    ) -> EventBuilder<RoomPowerLevelsEventContent> {
920        let mut event = RoomPowerLevelsEventContent::new(&AuthorizationRules::V1);
921        event.users.append(map);
922        self.event(event)
923    }
924
925    /// Create a new `m.room.server_acl` event.
926    pub fn server_acl(
927        &self,
928        allow_ip_literals: bool,
929        allow: Vec<String>,
930        deny: Vec<String>,
931    ) -> EventBuilder<RoomServerAclEventContent> {
932        self.event(RoomServerAclEventContent::new(allow_ip_literals, allow, deny))
933    }
934
935    /// Create a new `m.room.canonical_alias` event.
936    pub fn canonical_alias(
937        &self,
938        alias: Option<OwnedRoomAliasId>,
939        alt_aliases: Vec<OwnedRoomAliasId>,
940    ) -> EventBuilder<RoomCanonicalAliasEventContent> {
941        let mut event = RoomCanonicalAliasEventContent::new();
942        event.alias = alias;
943        event.alt_aliases = alt_aliases;
944        self.event(event)
945    }
946
947    /// Create a new `org.matrix.msc3672.beacon` event.
948    ///
949    /// ```
950    /// use matrix_sdk_test::event_factory::EventFactory;
951    /// use ruma::{
952    ///     MilliSecondsSinceUnixEpoch,
953    ///     events::{MessageLikeEvent, beacon::BeaconEventContent},
954    ///     owned_event_id, room_id,
955    ///     serde::Raw,
956    ///     user_id,
957    /// };
958    ///
959    /// let factory = EventFactory::new().room(room_id!("!test:localhost"));
960    ///
961    /// let event: Raw<MessageLikeEvent<BeaconEventContent>> = factory
962    ///     .beacon(
963    ///         owned_event_id!("$123456789abc:localhost"),
964    ///         10.1,
965    ///         15.2,
966    ///         5,
967    ///         Some(MilliSecondsSinceUnixEpoch(1000u32.into())),
968    ///     )
969    ///     .sender(user_id!("@alice:localhost"))
970    ///     .into_raw();
971    /// ```
972    pub fn beacon(
973        &self,
974        beacon_info_event_id: OwnedEventId,
975        latitude: f64,
976        longitude: f64,
977        uncertainty: u32,
978        ts: Option<MilliSecondsSinceUnixEpoch>,
979    ) -> EventBuilder<BeaconEventContent> {
980        let geo_uri = format!("geo:{latitude},{longitude};u={uncertainty}");
981        self.event(BeaconEventContent::new(beacon_info_event_id, geo_uri, ts))
982    }
983
984    /// Create a new `m.sticker` event.
985    pub fn sticker(
986        &self,
987        body: impl Into<String>,
988        info: ImageInfo,
989        url: OwnedMxcUri,
990    ) -> EventBuilder<StickerEventContent> {
991        self.event(StickerEventContent::new(body.into(), info, url))
992    }
993
994    /// Create a new `m.call.invite` event.
995    pub fn call_invite(
996        &self,
997        call_id: OwnedVoipId,
998        lifetime: UInt,
999        offer: SessionDescription,
1000        version: VoipVersionId,
1001    ) -> EventBuilder<CallInviteEventContent> {
1002        self.event(CallInviteEventContent::new(call_id, lifetime, offer, version))
1003    }
1004
1005    /// Create a new `m.call.notify` event.
1006    pub fn call_notify(
1007        &self,
1008        call_id: String,
1009        application: ApplicationType,
1010        notify_type: NotifyType,
1011        mentions: Mentions,
1012    ) -> EventBuilder<CallNotifyEventContent> {
1013        self.event(CallNotifyEventContent::new(call_id, application, notify_type, mentions))
1014    }
1015
1016    /// Create a new `m.direct` global account data event.
1017    pub fn direct(&self) -> EventBuilder<DirectEventContent> {
1018        let mut builder = self.event(DirectEventContent::default());
1019        builder.is_global = true;
1020        builder
1021    }
1022
1023    /// Create a new `m.ignored_user_list` global account data event.
1024    pub fn ignored_user_list(
1025        &self,
1026        users: impl IntoIterator<Item = OwnedUserId>,
1027    ) -> EventBuilder<IgnoredUserListEventContent> {
1028        let mut builder = self.event(IgnoredUserListEventContent::users(users));
1029        builder.is_global = true;
1030        builder
1031    }
1032
1033    /// Create a new `m.push_rules` global account data event.
1034    pub fn push_rules(&self, rules: Ruleset) -> EventBuilder<PushRulesEventContent> {
1035        let mut builder = self.event(PushRulesEventContent::new(rules));
1036        builder.is_global = true;
1037        builder
1038    }
1039
1040    /// Create a new `m.space.child` state event.
1041    pub fn space_child(
1042        &self,
1043        parent: OwnedRoomId,
1044        child: OwnedRoomId,
1045    ) -> EventBuilder<SpaceChildEventContent> {
1046        let mut event = self.event(SpaceChildEventContent::new(vec![]));
1047        event.room = Some(parent);
1048        event.state_key = Some(child.to_string());
1049        event
1050    }
1051
1052    /// Create a new `m.space.parent` state event.
1053    pub fn space_parent(
1054        &self,
1055        parent: OwnedRoomId,
1056        child: OwnedRoomId,
1057    ) -> EventBuilder<SpaceParentEventContent> {
1058        let mut event = self.event(SpaceParentEventContent::new(vec![]));
1059        event.state_key = Some(parent.to_string());
1060        event.room = Some(child);
1061        event
1062    }
1063
1064    /// Set the next server timestamp.
1065    ///
1066    /// Timestamps will continue to increase by 1 (millisecond) from that value.
1067    pub fn set_next_ts(&self, value: u64) {
1068        self.next_ts.store(value, SeqCst);
1069    }
1070}
1071
1072impl EventBuilder<DirectEventContent> {
1073    /// Add a user/room pair to the `m.direct` event.
1074    pub fn add_user(mut self, user_id: OwnedDirectUserIdentifier, room_id: &RoomId) -> Self {
1075        self.content.0.entry(user_id).or_default().push(room_id.to_owned());
1076        self
1077    }
1078}
1079
1080impl EventBuilder<RoomMemberEventContent> {
1081    /// Set the `membership` of the `m.room.member` event to the given
1082    /// [`MembershipState`].
1083    ///
1084    /// The default is [`MembershipState::Join`].
1085    pub fn membership(mut self, state: MembershipState) -> Self {
1086        self.content.membership = state;
1087        self
1088    }
1089
1090    /// Set that the sender of this event invited the user passed as a parameter
1091    /// here.
1092    pub fn invited(mut self, invited_user: &UserId) -> Self {
1093        assert_ne!(
1094            self.sender.as_deref().unwrap(),
1095            invited_user,
1096            "invited user and sender can't be the same person"
1097        );
1098        self.content.membership = MembershipState::Invite;
1099        self.state_key = Some(invited_user.to_string());
1100        self
1101    }
1102
1103    /// Set that the sender of this event kicked the user passed as a parameter
1104    /// here.
1105    pub fn kicked(mut self, kicked_user: &UserId) -> Self {
1106        assert_ne!(
1107            self.sender.as_deref().unwrap(),
1108            kicked_user,
1109            "kicked user and sender can't be the same person, otherwise it's just a Leave"
1110        );
1111        self.content.membership = MembershipState::Leave;
1112        self.state_key = Some(kicked_user.to_string());
1113        self
1114    }
1115
1116    /// Set that the sender of this event banned the user passed as a parameter
1117    /// here.
1118    pub fn banned(mut self, banned_user: &UserId) -> Self {
1119        assert_ne!(
1120            self.sender.as_deref().unwrap(),
1121            banned_user,
1122            "a user can't ban itself" // hopefully
1123        );
1124        self.content.membership = MembershipState::Ban;
1125        self.state_key = Some(banned_user.to_string());
1126        self
1127    }
1128
1129    /// Set the display name of the `m.room.member` event.
1130    pub fn display_name(mut self, display_name: impl Into<String>) -> Self {
1131        self.content.displayname = Some(display_name.into());
1132        self
1133    }
1134
1135    /// Set the avatar URL of the `m.room.member` event.
1136    pub fn avatar_url(mut self, url: &MxcUri) -> Self {
1137        self.content.avatar_url = Some(url.to_owned());
1138        self
1139    }
1140
1141    /// Set the reason field of the `m.room.member` event.
1142    pub fn reason(mut self, reason: impl Into<String>) -> Self {
1143        self.content.reason = Some(reason.into());
1144        self
1145    }
1146
1147    /// Set the previous membership state (in the unsigned section).
1148    pub fn previous(mut self, previous: impl Into<PreviousMembership>) -> Self {
1149        let previous = previous.into();
1150
1151        let mut prev_content = RoomMemberEventContent::new(previous.state);
1152        if let Some(avatar_url) = previous.avatar_url {
1153            prev_content.avatar_url = Some(avatar_url);
1154        }
1155        if let Some(display_name) = previous.display_name {
1156            prev_content.displayname = Some(display_name);
1157        }
1158
1159        self.unsigned.get_or_insert_with(Default::default).prev_content = Some(prev_content);
1160        self
1161    }
1162}
1163
1164impl EventBuilder<RoomAvatarEventContent> {
1165    /// Defines the URL for the room avatar.
1166    pub fn url(mut self, url: &MxcUri) -> Self {
1167        self.content.url = Some(url.to_owned());
1168        self
1169    }
1170
1171    /// Defines the image info for the avatar.
1172    pub fn info(mut self, image: avatar::ImageInfo) -> Self {
1173        self.content.info = Some(Box::new(image));
1174        self
1175    }
1176}
1177
1178pub struct ReadReceiptBuilder<'a> {
1179    factory: &'a EventFactory,
1180    content: ReceiptEventContent,
1181}
1182
1183impl ReadReceiptBuilder<'_> {
1184    /// Add a single read receipt to the event.
1185    pub fn add(
1186        self,
1187        event_id: &EventId,
1188        user_id: &UserId,
1189        tyype: ReceiptType,
1190        thread: ReceiptThread,
1191    ) -> Self {
1192        let ts = self.factory.next_server_ts();
1193        self.add_with_timestamp(event_id, user_id, tyype, thread, Some(ts))
1194    }
1195
1196    /// Add a single read receipt to the event, with an optional timestamp.
1197    pub fn add_with_timestamp(
1198        mut self,
1199        event_id: &EventId,
1200        user_id: &UserId,
1201        tyype: ReceiptType,
1202        thread: ReceiptThread,
1203        ts: Option<MilliSecondsSinceUnixEpoch>,
1204    ) -> Self {
1205        let by_event = self.content.0.entry(event_id.to_owned()).or_default();
1206        let by_type = by_event.entry(tyype).or_default();
1207
1208        let mut receipt = Receipt::default();
1209        if let Some(ts) = ts {
1210            receipt.ts = Some(ts);
1211        }
1212        receipt.thread = thread;
1213
1214        by_type.insert(user_id.to_owned(), receipt);
1215        self
1216    }
1217
1218    /// Finalize the builder into the receipt event content.
1219    pub fn into_content(self) -> ReceiptEventContent {
1220        self.content
1221    }
1222
1223    /// Finalize the builder into an event builder.
1224    pub fn into_event(self) -> EventBuilder<ReceiptEventContent> {
1225        let mut builder = self.factory.event(self.into_content());
1226        builder.is_ephemeral = true;
1227        builder
1228    }
1229}
1230
1231pub struct PreviousMembership {
1232    state: MembershipState,
1233    avatar_url: Option<OwnedMxcUri>,
1234    display_name: Option<String>,
1235}
1236
1237impl PreviousMembership {
1238    pub fn new(state: MembershipState) -> Self {
1239        Self { state, avatar_url: None, display_name: None }
1240    }
1241
1242    pub fn avatar_url(mut self, url: &MxcUri) -> Self {
1243        self.avatar_url = Some(url.to_owned());
1244        self
1245    }
1246
1247    pub fn display_name(mut self, name: impl Into<String>) -> Self {
1248        self.display_name = Some(name.into());
1249        self
1250    }
1251}
1252
1253impl From<MembershipState> for PreviousMembership {
1254    fn from(state: MembershipState) -> Self {
1255        Self::new(state)
1256    }
1257}