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