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