Skip to main content

matrix_sdk_test/
event_factory.rs

1// Copyright 2024 The Matrix.org Foundation C.I.C.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#![allow(missing_docs)]
16
17use std::{
18    collections::{BTreeMap, BTreeSet},
19    sync::atomic::{AtomicU64, Ordering::SeqCst},
20    time::Duration,
21};
22
23use as_variant::as_variant;
24use matrix_sdk_common::deserialized_responses::{
25    TimelineEvent, UnableToDecryptInfo, UnableToDecryptReason,
26};
27use ruma::{
28    EventId, Int, MilliSecondsSinceUnixEpoch, MxcUri, OwnedDeviceId, OwnedEventId, OwnedMxcUri,
29    OwnedRoomAliasId, OwnedRoomId, OwnedTransactionId, OwnedUserId, OwnedVoipId, RoomId,
30    RoomVersionId, TransactionId, UInt, UserId, VoipVersionId,
31    events::{
32        AnyGlobalAccountDataEvent, AnyMessageLikeEvent, AnyRoomAccountDataEvent, AnyStateEvent,
33        AnyStrippedStateEvent, AnySyncEphemeralRoomEvent, AnySyncMessageLikeEvent,
34        AnySyncStateEvent, AnySyncTimelineEvent, AnyTimelineEvent, BundledMessageLikeRelations,
35        EphemeralRoomEventContent, EventContentFromType, False, GlobalAccountDataEventContent,
36        Mentions, MessageLikeEvent, MessageLikeEventContent, PossiblyRedactedStateEventContent,
37        RedactContent, RedactedMessageLikeEventContent, RedactedStateEventContent,
38        RoomAccountDataEventContent, StateEvent, StateEventContent, StaticEventContent,
39        StaticStateEventContent, StrippedStateEvent, SyncMessageLikeEvent, SyncStateEvent,
40        beacon::BeaconEventContent,
41        beacon_info::BeaconInfoEventContent,
42        call::{
43            SessionDescription,
44            invite::CallInviteEventContent,
45            member::{
46                ActiveFocus, ActiveLivekitFocus, Application, CallApplicationContent,
47                CallMemberEventContent, CallMemberStateKey, CallScope, Focus, LivekitFocus,
48            },
49        },
50        direct::{DirectEventContent, OwnedDirectUserIdentifier},
51        fully_read::FullyReadEventContent,
52        ignored_user_list::IgnoredUserListEventContent,
53        macros::EventContent,
54        marked_unread::MarkedUnreadEventContent,
55        member_hints::MemberHintsEventContent,
56        poll::{
57            unstable_end::UnstablePollEndEventContent,
58            unstable_response::UnstablePollResponseEventContent,
59            unstable_start::{
60                NewUnstablePollStartEventContent, ReplacementUnstablePollStartEventContent,
61                UnstablePollAnswer, UnstablePollStartContentBlock, UnstablePollStartEventContent,
62            },
63        },
64        presence::{PresenceEvent, PresenceEventContent},
65        push_rules::PushRulesEventContent,
66        reaction::ReactionEventContent,
67        receipt::{Receipt, ReceiptEventContent, ReceiptThread, ReceiptType},
68        relation::{Annotation, BundledThread, Reference, Replacement, Reply, Thread},
69        room::{
70            ImageInfo,
71            avatar::{self, RoomAvatarEventContent},
72            canonical_alias::RoomCanonicalAliasEventContent,
73            create::{PreviousRoom, RoomCreateEventContent},
74            encrypted::{
75                EncryptedEventScheme, MegolmV1AesSha2ContentInit, RoomEncryptedEventContent,
76            },
77            encryption::RoomEncryptionEventContent,
78            history_visibility::{HistoryVisibility, RoomHistoryVisibilityEventContent},
79            join_rules::{JoinRule, RoomJoinRulesEventContent},
80            member::{MembershipState, RoomMemberEventContent},
81            message::{
82                FormattedBody, GalleryItemType, GalleryMessageEventContent,
83                ImageMessageEventContent, MessageType, OriginalSyncRoomMessageEvent, Relation,
84                RelationWithoutReplacement, RoomMessageEventContent,
85                RoomMessageEventContentWithoutRelation,
86            },
87            name::RoomNameEventContent,
88            pinned_events::RoomPinnedEventsEventContent,
89            power_levels::RoomPowerLevelsEventContent,
90            redaction::RoomRedactionEventContent,
91            server_acl::RoomServerAclEventContent,
92            tombstone::RoomTombstoneEventContent,
93            topic::RoomTopicEventContent,
94        },
95        rtc::{
96            decline::RtcDeclineEventContent,
97            notification::{CallIntent, NotificationType, RtcNotificationEventContent},
98        },
99        space::{child::SpaceChildEventContent, parent::SpaceParentEventContent},
100        space_order::SpaceOrderEventContent,
101        sticker::StickerEventContent,
102        tag::{TagEventContent, Tags},
103        typing::TypingEventContent,
104    },
105    owned_device_id,
106    presence::PresenceState,
107    push::Ruleset,
108    room::RoomType,
109    room_version_rules::AuthorizationRules,
110    serde::Raw,
111    server_name,
112};
113use serde::{Deserialize, Serialize, de::DeserializeOwned};
114use serde_json::json;
115
116use crate::base64_sha256_hash;
117
118pub trait TimestampArg {
119    fn to_milliseconds_since_unix_epoch(self) -> MilliSecondsSinceUnixEpoch;
120}
121
122impl TimestampArg for MilliSecondsSinceUnixEpoch {
123    fn to_milliseconds_since_unix_epoch(self) -> MilliSecondsSinceUnixEpoch {
124        self
125    }
126}
127
128impl TimestampArg for u64 {
129    fn to_milliseconds_since_unix_epoch(self) -> MilliSecondsSinceUnixEpoch {
130        MilliSecondsSinceUnixEpoch(UInt::try_from(self).unwrap())
131    }
132}
133
134/// A thin copy of [`ruma::events::UnsignedRoomRedactionEvent`].
135#[derive(Debug, Serialize)]
136struct RedactedBecause {
137    /// Data specific to the event type.
138    content: RoomRedactionEventContent,
139
140    /// The globally unique event identifier for the user who sent the event.
141    event_id: OwnedEventId,
142
143    /// The fully-qualified ID of the user who sent this event.
144    sender: OwnedUserId,
145
146    /// Timestamp in milliseconds on originating homeserver when this event was
147    /// sent.
148    origin_server_ts: MilliSecondsSinceUnixEpoch,
149}
150
151#[derive(Debug, Serialize)]
152struct Unsigned<C: StaticEventContent> {
153    #[serde(skip_serializing_if = "Option::is_none")]
154    prev_content: Option<C>,
155
156    #[serde(skip_serializing_if = "Option::is_none")]
157    transaction_id: Option<OwnedTransactionId>,
158
159    #[serde(rename = "m.relations", skip_serializing_if = "Option::is_none")]
160    relations: Option<BundledMessageLikeRelations<Raw<AnySyncTimelineEvent>>>,
161
162    #[serde(skip_serializing_if = "Option::is_none")]
163    redacted_because: Option<RedactedBecause>,
164
165    #[serde(skip_serializing_if = "Option::is_none")]
166    age: Option<Int>,
167
168    #[serde(skip_serializing_if = "Option::is_none")]
169    invite_room_state: Option<Vec<Raw<AnyStrippedStateEvent>>>,
170}
171
172// rustc can't derive Default because C isn't marked as `Default` 🤔 oh well.
173impl<C: StaticEventContent> Default for Unsigned<C> {
174    fn default() -> Self {
175        Self {
176            prev_content: None,
177            transaction_id: None,
178            relations: None,
179            redacted_because: None,
180            age: None,
181            invite_room_state: None,
182        }
183    }
184}
185
186#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
187enum EventFormat {
188    /// An event that can be received in the timeline, via `/messages` for
189    /// example.
190    #[default]
191    Timeline,
192    /// An event that can be received in the timeline, via `/sync`.
193    SyncTimeline,
194    /// An event that is received in stripped state.
195    StrippedState,
196    /// An ephemeral event, like a read receipt.
197    Ephemeral,
198    /// A global account data.
199    GlobalAccountData,
200    /// A room account data.
201    RoomAccountData,
202}
203
204impl EventFormat {
205    /// Whether this format has a `sender` field.
206    fn has_sender(self) -> bool {
207        matches!(self, Self::Timeline | Self::SyncTimeline | Self::StrippedState)
208    }
209
210    /// Whether this format has an `event_id` field.
211    fn has_event_id(self) -> bool {
212        matches!(self, Self::Timeline | Self::SyncTimeline)
213    }
214
215    /// Whether this format has an `room_id` field.
216    fn has_room_id(self) -> bool {
217        matches!(self, Self::Timeline)
218    }
219}
220
221#[derive(Debug)]
222pub struct EventBuilder<C: StaticEventContent<IsPrefix = False>> {
223    /// The format of the event.
224    ///
225    /// It will decide which fields are added to the JSON.
226    format: EventFormat,
227    sender: Option<OwnedUserId>,
228    room: Option<OwnedRoomId>,
229    event_id: Option<OwnedEventId>,
230    /// Whether the event should *not* have an event id. False by default.
231    no_event_id: bool,
232    redacts: Option<OwnedEventId>,
233    content: C,
234    server_ts: MilliSecondsSinceUnixEpoch,
235    unsigned: Option<Unsigned<C>>,
236    state_key: Option<String>,
237}
238
239impl<E: StaticEventContent<IsPrefix = False>> EventBuilder<E> {
240    fn format(mut self, format: EventFormat) -> Self {
241        self.format = format;
242        self
243    }
244
245    pub fn room(mut self, room_id: &RoomId) -> Self {
246        self.room = Some(room_id.to_owned());
247        self
248    }
249
250    pub fn sender(mut self, sender: impl Into<OwnedUserId>) -> Self {
251        self.sender = Some(sender.into());
252        self
253    }
254
255    pub fn event_id(mut self, event_id: &EventId) -> Self {
256        self.event_id = Some(event_id.to_owned());
257        self.no_event_id = false;
258        self
259    }
260
261    pub fn no_event_id(mut self) -> Self {
262        self.event_id = None;
263        self.no_event_id = true;
264        self
265    }
266
267    pub fn server_ts(mut self, ts: impl TimestampArg) -> Self {
268        self.server_ts = ts.to_milliseconds_since_unix_epoch();
269        self
270    }
271
272    pub fn unsigned_transaction_id(mut self, transaction_id: &TransactionId) -> Self {
273        self.unsigned.get_or_insert_with(Default::default).transaction_id =
274            Some(transaction_id.to_owned());
275        self
276    }
277
278    /// Add age to unsigned data in this event.
279    pub fn age(mut self, age: impl Into<Int>) -> Self {
280        self.unsigned.get_or_insert_with(Default::default).age = Some(age.into());
281        self
282    }
283
284    /// Set the previous content for this state event (in the unsigned section).
285    pub fn prev_content(mut self, prev: E) -> Self {
286        self.unsigned.get_or_insert_with(Default::default).prev_content = Some(prev);
287        self
288    }
289
290    /// Create a bundled thread summary in the unsigned bundled relations of
291    /// this event.
292    pub fn with_bundled_thread_summary(
293        mut self,
294        latest_event: Raw<AnySyncMessageLikeEvent>,
295        count: usize,
296        current_user_participated: bool,
297    ) -> Self {
298        let relations = self
299            .unsigned
300            .get_or_insert_with(Default::default)
301            .relations
302            .get_or_insert_with(BundledMessageLikeRelations::new);
303        relations.thread = Some(Box::new(BundledThread::new(
304            latest_event,
305            UInt::try_from(count).unwrap(),
306            current_user_participated,
307        )));
308        self
309    }
310
311    /// Create a bundled edit in the unsigned bundled relations of this event.
312    pub fn with_bundled_edit(mut self, replacement: impl Into<Raw<AnySyncTimelineEvent>>) -> Self {
313        let relations = self
314            .unsigned
315            .get_or_insert_with(Default::default)
316            .relations
317            .get_or_insert_with(BundledMessageLikeRelations::new);
318        relations.replace = Some(Box::new(replacement.into()));
319        self
320    }
321
322    /// For state events manually created, define the state key.
323    ///
324    /// For other state events created in the [`EventFactory`], this is
325    /// automatically filled upon creation or update of the events.
326    pub fn state_key(mut self, state_key: impl Into<String>) -> Self {
327        self.state_key = Some(state_key.into());
328        self
329    }
330}
331
332impl<E> EventBuilder<E>
333where
334    E: StaticEventContent<IsPrefix = False> + Serialize,
335{
336    #[inline(always)]
337    fn construct_json(self) -> serde_json::Value {
338        let mut json = json!({
339            "type": E::TYPE,
340            "content": self.content,
341            "origin_server_ts": self.server_ts,
342        });
343
344        let map = json.as_object_mut().unwrap();
345
346        if self.format.has_sender() {
347            // Use the `sender` preferably, or resort to the `redacted_because`
348            // sender if none has been set.
349            let sender = self
350                .sender
351                .or_else(|| Some(self.unsigned.as_ref()?.redacted_because.as_ref()?.sender.clone())).expect("the sender must be known when building the JSON for a non read-receipt or global event");
352            map.insert("sender".to_owned(), json!(sender));
353        }
354
355        if self.format.has_room_id() {
356            let room_id = self.room.expect("TimelineEvent requires a room id");
357            map.insert("room_id".to_owned(), json!(room_id));
358        }
359
360        if let Some(redacts) = self.redacts {
361            map.insert("redacts".to_owned(), json!(redacts));
362        }
363
364        if let Some(state_key) = self.state_key {
365            map.insert("state_key".to_owned(), json!(state_key));
366        }
367
368        if self.format.has_event_id() && !self.no_event_id {
369            let event_id = self.event_id.unwrap_or_else(|| {
370                // Compute a hash of the event to use it as the event ID,
371                // similar to how a server would. This is a
372                // little bit different since a server would redact the
373                // event before hashing, but at least the event ID construction
374                // will be deterministic and have the same
375                // format as in recent room versions.
376                let bytes = serde_json::to_vec(&map).unwrap();
377                EventId::new_v2_or_v3(&base64_sha256_hash(&bytes)).unwrap()
378            });
379
380            map.insert("event_id".to_owned(), json!(event_id));
381        }
382
383        if let Some(unsigned) = self.unsigned {
384            map.insert("unsigned".to_owned(), json!(unsigned));
385        }
386
387        json
388    }
389
390    /// Build an event from the [`EventBuilder`] and convert it into a
391    /// serialized and [`Raw`] event.
392    ///
393    /// The generic argument `T` allows you to automatically cast the [`Raw`]
394    /// event into any desired type.
395    pub fn into_raw<T>(self) -> Raw<T> {
396        Raw::new(&self.construct_json()).unwrap().cast_unchecked()
397    }
398
399    pub fn into_raw_timeline(self) -> Raw<AnyTimelineEvent> {
400        self.into_raw()
401    }
402
403    pub fn into_any_sync_message_like_event(self) -> AnySyncMessageLikeEvent {
404        self.format(EventFormat::SyncTimeline)
405            .into_raw()
406            .deserialize()
407            .expect("expected message like event")
408    }
409
410    pub fn into_original_sync_room_message_event(self) -> OriginalSyncRoomMessageEvent {
411        self.format(EventFormat::SyncTimeline)
412            .into_raw()
413            .deserialize()
414            .expect("expected original sync room message event")
415    }
416
417    pub fn into_raw_sync(self) -> Raw<AnySyncTimelineEvent> {
418        self.format(EventFormat::SyncTimeline).into_raw()
419    }
420
421    pub fn into_raw_sync_state(self) -> Raw<AnySyncStateEvent> {
422        self.format(EventFormat::SyncTimeline).into_raw()
423    }
424
425    pub fn into_event(self) -> TimelineEvent {
426        TimelineEvent::from_plaintext(self.into_raw_sync())
427    }
428
429    /// Returns just the event content as a JSON value.
430    ///
431    /// This is useful when you need only the content portion of an event,
432    /// for example when mocking HTTP responses that return event content.
433    pub fn into_content(self) -> serde_json::Value {
434        json!(self.content)
435    }
436}
437
438impl EventBuilder<RoomEncryptedEventContent> {
439    /// Turn this event into a [`TimelineEvent`] representing a decryption
440    /// failure
441    pub fn into_utd_sync_timeline_event(self) -> TimelineEvent {
442        let session_id = as_variant!(&self.content.scheme, EncryptedEventScheme::MegolmV1AesSha2)
443            .map(|content| content.session_id.clone());
444
445        TimelineEvent::from_utd(
446            self.into(),
447            UnableToDecryptInfo {
448                session_id,
449                reason: UnableToDecryptReason::MissingMegolmSession { withheld_code: None },
450            },
451        )
452    }
453}
454
455impl EventBuilder<RoomMessageEventContent> {
456    /// Adds a reply relation to the current event.
457    pub fn reply_to(mut self, event_id: &EventId) -> Self {
458        self.content.relates_to = Some(Relation::Reply(Reply::with_event_id(event_id.to_owned())));
459        self
460    }
461
462    /// Adds a thread relation to the root event, setting the reply fallback to
463    /// the latest in-thread event.
464    pub fn in_thread(mut self, root: &EventId, latest_thread_event: &EventId) -> Self {
465        self.content.relates_to =
466            Some(Relation::Thread(Thread::plain(root.to_owned(), latest_thread_event.to_owned())));
467        self
468    }
469
470    /// Adds a thread relation to the root event, that's a non-fallback reply to
471    /// another thread event.
472    pub fn in_thread_reply(mut self, root: &EventId, replied_to: &EventId) -> Self {
473        self.content.relates_to =
474            Some(Relation::Thread(Thread::reply(root.to_owned(), replied_to.to_owned())));
475        self
476    }
477
478    /// Adds the given mentions to the current event.
479    pub fn mentions(mut self, mentions: Mentions) -> Self {
480        self.content.mentions = Some(mentions);
481        self
482    }
483
484    /// Adds a replacement relation to the current event, with the new content
485    /// passed.
486    pub fn edit(
487        mut self,
488        edited_event_id: &EventId,
489        new_content: RoomMessageEventContentWithoutRelation,
490    ) -> Self {
491        self.content.relates_to =
492            Some(Relation::Replacement(Replacement::new(edited_event_id.to_owned(), new_content)));
493        self
494    }
495
496    /// Adds a caption to a media event.
497    ///
498    /// Will crash if the event isn't a media room message.
499    pub fn caption(
500        mut self,
501        caption: Option<String>,
502        formatted_caption: Option<FormattedBody>,
503    ) -> Self {
504        match &mut self.content.msgtype {
505            MessageType::Image(image) => {
506                let filename = image.filename().to_owned();
507                if let Some(caption) = caption {
508                    image.body = caption;
509                    image.filename = Some(filename);
510                } else {
511                    image.body = filename;
512                    image.filename = None;
513                }
514                image.formatted = formatted_caption;
515            }
516
517            MessageType::Audio(_) | MessageType::Video(_) | MessageType::File(_) => {
518                unimplemented!();
519            }
520
521            _ => panic!("unexpected event type for a caption"),
522        }
523
524        self
525    }
526}
527
528impl EventBuilder<UnstablePollStartEventContent> {
529    /// Adds a reply relation to the current event.
530    pub fn reply_to(mut self, event_id: &EventId) -> Self {
531        if let UnstablePollStartEventContent::New(content) = &mut self.content {
532            content.relates_to =
533                Some(RelationWithoutReplacement::Reply(Reply::with_event_id(event_id.to_owned())));
534        }
535        self
536    }
537
538    /// Adds a thread relation to the root event, setting the reply to
539    /// event id as well.
540    pub fn in_thread(mut self, root: &EventId, reply_to_event_id: &EventId) -> Self {
541        let thread = Thread::reply(root.to_owned(), reply_to_event_id.to_owned());
542
543        if let UnstablePollStartEventContent::New(content) = &mut self.content {
544            content.relates_to = Some(RelationWithoutReplacement::Thread(thread));
545        }
546        self
547    }
548}
549
550impl EventBuilder<RoomCreateEventContent> {
551    /// Define the predecessor fields.
552    pub fn predecessor(mut self, room_id: &RoomId) -> Self {
553        self.content.predecessor = Some(PreviousRoom::new(room_id.to_owned()));
554        self
555    }
556
557    /// Erase the predecessor if any.
558    pub fn no_predecessor(mut self) -> Self {
559        self.content.predecessor = None;
560        self
561    }
562
563    /// Sets the `m.room.create` `type` field to `m.space`.
564    pub fn with_space_type(mut self) -> Self {
565        self.content.room_type = Some(RoomType::Space);
566        self
567    }
568}
569
570impl EventBuilder<StickerEventContent> {
571    /// Add reply [`Thread`] relation to root event and set replied-to event id.
572    pub fn reply_thread(mut self, root: &EventId, reply_to_event: &EventId) -> Self {
573        self.content.relates_to =
574            Some(Relation::Thread(Thread::reply(root.to_owned(), reply_to_event.to_owned())));
575        self
576    }
577}
578
579impl<E: StaticEventContent<IsPrefix = False>> From<EventBuilder<E>> for Raw<AnySyncTimelineEvent>
580where
581    E: Serialize,
582{
583    fn from(val: EventBuilder<E>) -> Self {
584        val.into_raw_sync()
585    }
586}
587
588impl<E: StaticEventContent<IsPrefix = False>> From<EventBuilder<E>> for AnySyncTimelineEvent
589where
590    E: Serialize,
591{
592    fn from(val: EventBuilder<E>) -> Self {
593        Raw::<AnySyncTimelineEvent>::from(val).deserialize().expect("expected sync timeline event")
594    }
595}
596
597impl<E: StaticEventContent<IsPrefix = False>> From<EventBuilder<E>> for Raw<AnyTimelineEvent>
598where
599    E: Serialize,
600{
601    fn from(val: EventBuilder<E>) -> Self {
602        val.into_raw_timeline()
603    }
604}
605
606impl<E: StaticEventContent<IsPrefix = False>> From<EventBuilder<E>> for AnyTimelineEvent
607where
608    E: Serialize,
609{
610    fn from(val: EventBuilder<E>) -> Self {
611        Raw::<AnyTimelineEvent>::from(val).deserialize().expect("expected timeline event")
612    }
613}
614
615impl<E: StaticEventContent<IsPrefix = False>> From<EventBuilder<E>>
616    for Raw<AnyGlobalAccountDataEvent>
617where
618    E: Serialize,
619{
620    fn from(val: EventBuilder<E>) -> Self {
621        val.format(EventFormat::GlobalAccountData).into_raw()
622    }
623}
624
625impl<E: StaticEventContent<IsPrefix = False>> From<EventBuilder<E>> for AnyGlobalAccountDataEvent
626where
627    E: Serialize,
628{
629    fn from(val: EventBuilder<E>) -> Self {
630        Raw::<AnyGlobalAccountDataEvent>::from(val)
631            .deserialize()
632            .expect("expected global account data")
633    }
634}
635
636impl<E: StaticEventContent<IsPrefix = False>> From<EventBuilder<E>> for Raw<AnyRoomAccountDataEvent>
637where
638    E: Serialize,
639{
640    fn from(val: EventBuilder<E>) -> Self {
641        val.format(EventFormat::RoomAccountData).into_raw()
642    }
643}
644
645impl<E: StaticEventContent<IsPrefix = False>> From<EventBuilder<E>> for AnyRoomAccountDataEvent
646where
647    E: Serialize,
648{
649    fn from(val: EventBuilder<E>) -> Self {
650        Raw::<AnyRoomAccountDataEvent>::from(val).deserialize().expect("expected room account data")
651    }
652}
653
654impl<E: StaticEventContent<IsPrefix = False>> From<EventBuilder<E>> for TimelineEvent
655where
656    E: Serialize,
657{
658    fn from(val: EventBuilder<E>) -> Self {
659        val.into_event()
660    }
661}
662
663impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
664    for Raw<AnySyncStateEvent>
665{
666    fn from(val: EventBuilder<E>) -> Self {
667        val.format(EventFormat::SyncTimeline).into_raw()
668    }
669}
670
671impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
672    for AnySyncStateEvent
673{
674    fn from(val: EventBuilder<E>) -> Self {
675        Raw::<AnySyncStateEvent>::from(val).deserialize().expect("expected sync state")
676    }
677}
678
679impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
680    for Raw<SyncStateEvent<E>>
681where
682    E: StaticStateEventContent + RedactContent,
683    E::Redacted: RedactedStateEventContent,
684{
685    fn from(val: EventBuilder<E>) -> Self {
686        val.format(EventFormat::SyncTimeline).into_raw()
687    }
688}
689
690impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
691    for SyncStateEvent<E>
692where
693    E: StaticStateEventContent + RedactContent + EventContentFromType,
694    E::Redacted: RedactedStateEventContent<StateKey = <E as StateEventContent>::StateKey>
695        + EventContentFromType,
696    E::Unsigned: DeserializeOwned,
697{
698    fn from(val: EventBuilder<E>) -> Self {
699        Raw::<SyncStateEvent<E>>::from(val).deserialize().expect("expected sync state")
700    }
701}
702
703impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
704    for Raw<AnyStateEvent>
705{
706    fn from(val: EventBuilder<E>) -> Self {
707        val.into_raw()
708    }
709}
710
711impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
712    for AnyStateEvent
713{
714    fn from(val: EventBuilder<E>) -> Self {
715        Raw::<AnyStateEvent>::from(val).deserialize().expect("expected state")
716    }
717}
718
719impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
720    for Raw<StateEvent<E>>
721where
722    E: StaticStateEventContent + RedactContent,
723    E::Redacted: RedactedStateEventContent,
724{
725    fn from(val: EventBuilder<E>) -> Self {
726        val.into_raw()
727    }
728}
729
730impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
731    for StateEvent<E>
732where
733    E: StaticStateEventContent + RedactContent + EventContentFromType,
734    E::Redacted: RedactedStateEventContent<StateKey = <E as StateEventContent>::StateKey>
735        + EventContentFromType,
736    E::Unsigned: DeserializeOwned,
737{
738    fn from(val: EventBuilder<E>) -> Self {
739        Raw::<StateEvent<E>>::from(val).deserialize().expect("expected state")
740    }
741}
742
743impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
744    for Raw<AnyStrippedStateEvent>
745{
746    fn from(val: EventBuilder<E>) -> Self {
747        val.format(EventFormat::StrippedState).into_raw()
748    }
749}
750
751impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
752    for AnyStrippedStateEvent
753{
754    fn from(val: EventBuilder<E>) -> Self {
755        Raw::<AnyStrippedStateEvent>::from(val).deserialize().expect("expected stripped state")
756    }
757}
758
759impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
760    for Raw<StrippedStateEvent<E::PossiblyRedacted>>
761where
762    E: StaticStateEventContent,
763{
764    fn from(val: EventBuilder<E>) -> Self {
765        val.format(EventFormat::StrippedState).into_raw()
766    }
767}
768
769impl<E: StaticEventContent<IsPrefix = False> + StateEventContent> From<EventBuilder<E>>
770    for StrippedStateEvent<E::PossiblyRedacted>
771where
772    E: StaticStateEventContent,
773    E::PossiblyRedacted: PossiblyRedactedStateEventContent + EventContentFromType,
774{
775    fn from(val: EventBuilder<E>) -> Self {
776        Raw::<StrippedStateEvent<E::PossiblyRedacted>>::from(val)
777            .deserialize()
778            .expect("expected stripped state")
779    }
780}
781
782impl<E: StaticEventContent<IsPrefix = False> + EphemeralRoomEventContent> From<EventBuilder<E>>
783    for Raw<AnySyncEphemeralRoomEvent>
784{
785    fn from(val: EventBuilder<E>) -> Self {
786        val.format(EventFormat::Ephemeral).into_raw()
787    }
788}
789
790impl<E: StaticEventContent<IsPrefix = False> + EphemeralRoomEventContent> From<EventBuilder<E>>
791    for AnySyncEphemeralRoomEvent
792{
793    fn from(val: EventBuilder<E>) -> Self {
794        Raw::<AnySyncEphemeralRoomEvent>::from(val).deserialize().expect("expected ephemeral")
795    }
796}
797
798impl<E: StaticEventContent<IsPrefix = False> + MessageLikeEventContent> From<EventBuilder<E>>
799    for Raw<AnySyncMessageLikeEvent>
800{
801    fn from(val: EventBuilder<E>) -> Self {
802        val.format(EventFormat::SyncTimeline).into_raw()
803    }
804}
805
806impl<E: StaticEventContent<IsPrefix = False> + MessageLikeEventContent> From<EventBuilder<E>>
807    for AnySyncMessageLikeEvent
808{
809    fn from(val: EventBuilder<E>) -> Self {
810        Raw::<AnySyncMessageLikeEvent>::from(val).deserialize().expect("expected sync message-like")
811    }
812}
813
814impl<E: StaticEventContent<IsPrefix = False> + MessageLikeEventContent> From<EventBuilder<E>>
815    for Raw<SyncMessageLikeEvent<E>>
816where
817    E: RedactContent,
818    E::Redacted: RedactedMessageLikeEventContent,
819{
820    fn from(val: EventBuilder<E>) -> Self {
821        val.format(EventFormat::SyncTimeline).into_raw()
822    }
823}
824
825impl<E: StaticEventContent<IsPrefix = False> + MessageLikeEventContent> From<EventBuilder<E>>
826    for SyncMessageLikeEvent<E>
827where
828    E: RedactContent + EventContentFromType,
829    E::Redacted: RedactedMessageLikeEventContent + EventContentFromType,
830{
831    fn from(val: EventBuilder<E>) -> Self {
832        Raw::<SyncMessageLikeEvent<E>>::from(val).deserialize().expect("expected sync message-like")
833    }
834}
835
836impl<E: StaticEventContent<IsPrefix = False> + MessageLikeEventContent> From<EventBuilder<E>>
837    for Raw<AnyMessageLikeEvent>
838{
839    fn from(val: EventBuilder<E>) -> Self {
840        val.into_raw()
841    }
842}
843
844impl<E: StaticEventContent<IsPrefix = False> + MessageLikeEventContent> From<EventBuilder<E>>
845    for AnyMessageLikeEvent
846{
847    fn from(val: EventBuilder<E>) -> Self {
848        Raw::<AnyMessageLikeEvent>::from(val).deserialize().expect("expected message-like")
849    }
850}
851
852impl<E: StaticEventContent<IsPrefix = False> + MessageLikeEventContent> From<EventBuilder<E>>
853    for Raw<MessageLikeEvent<E>>
854where
855    E: RedactContent,
856    E::Redacted: RedactedMessageLikeEventContent,
857{
858    fn from(val: EventBuilder<E>) -> Self {
859        val.into_raw()
860    }
861}
862
863impl<E: StaticEventContent<IsPrefix = False> + MessageLikeEventContent> From<EventBuilder<E>>
864    for MessageLikeEvent<E>
865where
866    E: RedactContent + EventContentFromType,
867    E::Redacted: RedactedMessageLikeEventContent + EventContentFromType,
868{
869    fn from(val: EventBuilder<E>) -> Self {
870        Raw::<MessageLikeEvent<E>>::from(val).deserialize().expect("expected message-like")
871    }
872}
873
874#[derive(Debug, Default)]
875pub struct EventFactory {
876    next_ts: AtomicU64,
877    sender: Option<OwnedUserId>,
878    room: Option<OwnedRoomId>,
879}
880
881impl EventFactory {
882    pub fn new() -> Self {
883        Self { next_ts: AtomicU64::new(0), sender: None, room: None }
884    }
885
886    pub fn room(mut self, room_id: &RoomId) -> Self {
887        self.room = Some(room_id.to_owned());
888        self
889    }
890
891    pub fn sender(mut self, sender: impl Into<OwnedUserId>) -> Self {
892        self.sender = Some(sender.into());
893        self
894    }
895
896    pub fn server_ts(self, ts: u64) -> Self {
897        self.next_ts.store(ts, SeqCst);
898        self
899    }
900
901    fn next_server_ts(&self) -> MilliSecondsSinceUnixEpoch {
902        MilliSecondsSinceUnixEpoch(
903            self.next_ts
904                .fetch_add(1, SeqCst)
905                .try_into()
906                .expect("server timestamp should fit in js_int::UInt"),
907        )
908    }
909
910    /// Create an event from any event content.
911    pub fn event<E: StaticEventContent<IsPrefix = False>>(&self, content: E) -> EventBuilder<E> {
912        EventBuilder {
913            format: EventFormat::Timeline,
914            sender: self.sender.clone(),
915            room: self.room.clone(),
916            server_ts: self.next_server_ts(),
917            event_id: None,
918            no_event_id: false,
919            redacts: None,
920            content,
921            unsigned: None,
922            state_key: None,
923        }
924    }
925
926    /// Create a new plain text `m.room.message`.
927    pub fn text_msg(&self, content: impl Into<String>) -> EventBuilder<RoomMessageEventContent> {
928        self.event(RoomMessageEventContent::text_plain(content.into()))
929    }
930
931    /// Create a new plain emote `m.room.message`.
932    pub fn emote(&self, content: impl Into<String>) -> EventBuilder<RoomMessageEventContent> {
933        self.event(RoomMessageEventContent::emote_plain(content.into()))
934    }
935
936    /// Create a new `m.room.encrypted` event using the `m.megolm.v1.aes-sha2`
937    /// algorithm.
938    pub fn encrypted(
939        &self,
940        ciphertext: impl Into<String>,
941        sender_key: impl Into<String>,
942        device_id: impl Into<OwnedDeviceId>,
943        session_id: impl Into<String>,
944    ) -> EventBuilder<RoomEncryptedEventContent> {
945        self.event(RoomEncryptedEventContent::new(
946            EncryptedEventScheme::MegolmV1AesSha2(
947                MegolmV1AesSha2ContentInit {
948                    ciphertext: ciphertext.into(),
949                    sender_key: sender_key.into(),
950                    device_id: device_id.into(),
951                    session_id: session_id.into(),
952                }
953                .into(),
954            ),
955            None,
956        ))
957    }
958
959    /// Create a new `m.room.member` event for the given member.
960    ///
961    /// The given member will be used as the `sender` as well as the `state_key`
962    /// of the `m.room.member` event, unless the `sender` was already using
963    /// [`EventFactory::sender()`], in that case only the state key will be
964    /// set to the given `member`.
965    ///
966    /// The `membership` field of the content is set to
967    /// [`MembershipState::Join`].
968    ///
969    /// ```
970    /// use matrix_sdk_test::event_factory::EventFactory;
971    /// use ruma::{
972    ///     events::{
973    ///         SyncStateEvent,
974    ///         room::member::{MembershipState, RoomMemberEventContent},
975    ///     },
976    ///     room_id,
977    ///     serde::Raw,
978    ///     user_id,
979    /// };
980    ///
981    /// let factory = EventFactory::new().room(room_id!("!test:localhost"));
982    ///
983    /// let event: Raw<SyncStateEvent<RoomMemberEventContent>> = factory
984    ///     .member(user_id!("@alice:localhost"))
985    ///     .display_name("Alice")
986    ///     .into_raw();
987    /// ```
988    pub fn member(&self, member: impl Into<OwnedUserId>) -> EventBuilder<RoomMemberEventContent> {
989        let member = member.into();
990        let mut event = self.event(RoomMemberEventContent::new(MembershipState::Join));
991
992        if self.sender.is_some() {
993            event.sender = self.sender.clone();
994        } else {
995            event.sender = Some(member.clone());
996        }
997
998        event.state_key = Some(member.into());
999
1000        event
1001    }
1002
1003    /// Create a tombstone state event for the room.
1004    pub fn room_tombstone(
1005        &self,
1006        body: impl Into<String>,
1007        replacement: &RoomId,
1008    ) -> EventBuilder<RoomTombstoneEventContent> {
1009        let mut event =
1010            self.event(RoomTombstoneEventContent::new(body.into(), replacement.to_owned()));
1011        event.state_key = Some("".to_owned());
1012        event
1013    }
1014
1015    /// Create a state event for the topic.
1016    pub fn room_topic(&self, topic: impl Into<String>) -> EventBuilder<RoomTopicEventContent> {
1017        let mut event = self.event(RoomTopicEventContent::new(topic.into()));
1018        // The state key is empty for a room topic state event.
1019        event.state_key = Some("".to_owned());
1020        event
1021    }
1022
1023    /// Create a state event for the room name.
1024    pub fn room_name(&self, name: impl Into<String>) -> EventBuilder<RoomNameEventContent> {
1025        let mut event = self.event(RoomNameEventContent::new(name.into()));
1026        // The state key is empty for a room name state event.
1027        event.state_key = Some("".to_owned());
1028        event
1029    }
1030
1031    /// Create an empty state event for the room avatar.
1032    pub fn room_avatar(&self) -> EventBuilder<RoomAvatarEventContent> {
1033        let mut event = self.event(RoomAvatarEventContent::new());
1034        // The state key is empty for a room avatar state event.
1035        event.state_key = Some("".to_owned());
1036        event
1037    }
1038
1039    /// Create a state event for room encryption with recommended defaults.
1040    ///
1041    /// This creates an `m.room.encryption` event with the
1042    /// `m.megolm.v1.aes-sha2` algorithm and recommended rotation settings.
1043    pub fn room_encryption(&self) -> EventBuilder<RoomEncryptionEventContent> {
1044        let mut event = self.event(RoomEncryptionEventContent::with_recommended_defaults());
1045        // The state key is empty for a room encryption state event.
1046        event.state_key = Some("".to_owned());
1047        event
1048    }
1049
1050    /// Create a state event for room encryption with state event encryption
1051    /// enabled.
1052    #[cfg(feature = "experimental-encrypted-state-events")]
1053    pub fn room_encryption_with_state_encryption(
1054        &self,
1055    ) -> EventBuilder<RoomEncryptionEventContent> {
1056        let mut content = RoomEncryptionEventContent::with_recommended_defaults();
1057        content.encrypt_state_events = true;
1058        let mut event = self.event(content);
1059        event.state_key = Some("".to_owned());
1060        event
1061    }
1062
1063    /// Create a room history visibility state event.
1064    ///
1065    /// This creates an `m.room.history_visibility` event with the given
1066    /// visibility setting.
1067    pub fn room_history_visibility(
1068        &self,
1069        visibility: HistoryVisibility,
1070    ) -> EventBuilder<RoomHistoryVisibilityEventContent> {
1071        let mut event = self.event(RoomHistoryVisibilityEventContent::new(visibility));
1072        event.state_key = Some("".to_owned());
1073        event
1074    }
1075
1076    /// Create a room join rules state event.
1077    ///
1078    /// This creates an `m.room.join_rules` event with the given join rule.
1079    pub fn room_join_rules(&self, join_rule: JoinRule) -> EventBuilder<RoomJoinRulesEventContent> {
1080        let mut event = self.event(RoomJoinRulesEventContent::new(join_rule));
1081        event.state_key = Some("".to_owned());
1082        event
1083    }
1084
1085    /// Create a state event for the room's pinned events.
1086    pub fn room_pinned_events(
1087        &self,
1088        pinned: Vec<OwnedEventId>,
1089    ) -> EventBuilder<RoomPinnedEventsEventContent> {
1090        self.event(RoomPinnedEventsEventContent::new(pinned)).state_key("")
1091    }
1092
1093    /// Create a new `m.member_hints` event with the given service members.
1094    ///
1095    /// ```
1096    /// use std::collections::BTreeSet;
1097    ///
1098    /// use matrix_sdk_test::event_factory::EventFactory;
1099    /// use ruma::{
1100    ///     events::{SyncStateEvent, member_hints::MemberHintsEventContent},
1101    ///     owned_user_id, room_id,
1102    ///     serde::Raw,
1103    ///     user_id,
1104    /// };
1105    ///
1106    /// let factory = EventFactory::new().room(room_id!("!test:localhost"));
1107    ///
1108    /// let event: Raw<SyncStateEvent<MemberHintsEventContent>> = factory
1109    ///     .member_hints(BTreeSet::from([owned_user_id!("@alice:localhost")]))
1110    ///     .sender(user_id!("@alice:localhost"))
1111    ///     .into_raw();
1112    /// ```
1113    pub fn member_hints(
1114        &self,
1115        service_members: BTreeSet<OwnedUserId>,
1116    ) -> EventBuilder<MemberHintsEventContent> {
1117        // The `m.member_hints` event always has an empty state key, so let's
1118        // set it.
1119        self.event(MemberHintsEventContent::new(service_members)).state_key("")
1120    }
1121
1122    /// Create a new plain/html `m.room.message`.
1123    pub fn text_html(
1124        &self,
1125        plain: impl Into<String>,
1126        html: impl Into<String>,
1127    ) -> EventBuilder<RoomMessageEventContent> {
1128        self.event(RoomMessageEventContent::text_html(plain, html))
1129    }
1130
1131    /// Create a new plain notice `m.room.message`.
1132    pub fn notice(&self, content: impl Into<String>) -> EventBuilder<RoomMessageEventContent> {
1133        self.event(RoomMessageEventContent::notice_plain(content))
1134    }
1135
1136    /// Add a reaction to an event.
1137    pub fn reaction(
1138        &self,
1139        event_id: &EventId,
1140        annotation: impl Into<String>,
1141    ) -> EventBuilder<ReactionEventContent> {
1142        self.event(ReactionEventContent::new(Annotation::new(
1143            event_id.to_owned(),
1144            annotation.into(),
1145        )))
1146    }
1147
1148    /// Create a live redaction for the given event id.
1149    ///
1150    /// Note: this is not a redacted event, but a redaction event, that will
1151    /// cause another event to be redacted.
1152    pub fn redaction(&self, event_id: &EventId) -> EventBuilder<RoomRedactionEventContent> {
1153        let mut builder = self.event(RoomRedactionEventContent::new_v11(event_id.to_owned()));
1154        builder.redacts = Some(event_id.to_owned());
1155        builder
1156    }
1157
1158    /// Create a redacted event, with extra information in the unsigned section
1159    /// about the redaction itself.
1160    pub fn redacted<T: StaticEventContent<IsPrefix = False> + RedactedMessageLikeEventContent>(
1161        &self,
1162        redacter: impl Into<OwnedUserId>,
1163        content: T,
1164    ) -> EventBuilder<T> {
1165        let mut builder = self.event(content);
1166
1167        let redacted_because = RedactedBecause {
1168            content: RoomRedactionEventContent::default(),
1169            event_id: EventId::new_v1(server_name!("dummy.server")),
1170            sender: redacter.into(),
1171            origin_server_ts: self.next_server_ts(),
1172        };
1173        builder.unsigned.get_or_insert_with(Default::default).redacted_because =
1174            Some(redacted_because);
1175
1176        builder
1177    }
1178
1179    /// Create a redacted state event, with extra information in the unsigned
1180    /// section about the redaction itself.
1181    pub fn redacted_state<T: StaticEventContent<IsPrefix = False> + RedactedStateEventContent>(
1182        &self,
1183        redacter: impl Into<OwnedUserId>,
1184        state_key: impl Into<String>,
1185        content: T,
1186    ) -> EventBuilder<T> {
1187        let mut builder = self.event(content);
1188
1189        let redacted_because = RedactedBecause {
1190            content: RoomRedactionEventContent::default(),
1191            event_id: EventId::new_v1(server_name!("dummy.server")),
1192            sender: redacter.into(),
1193            origin_server_ts: self.next_server_ts(),
1194        };
1195        builder.unsigned.get_or_insert_with(Default::default).redacted_because =
1196            Some(redacted_because);
1197        builder.state_key = Some(state_key.into());
1198
1199        builder
1200    }
1201
1202    /// Create a poll start event given a text, the question and the possible
1203    /// answers.
1204    pub fn poll_start(
1205        &self,
1206        fallback_text: impl Into<String>,
1207        poll_question: impl Into<String>,
1208        answers: Vec<impl Into<String>>,
1209    ) -> EventBuilder<UnstablePollStartEventContent> {
1210        // PollAnswers 'constructor' is not public, so we need to deserialize
1211        // them
1212        let answers: Vec<UnstablePollAnswer> = answers
1213            .into_iter()
1214            .enumerate()
1215            .map(|(idx, answer)| UnstablePollAnswer::new(idx.to_string(), answer))
1216            .collect();
1217        let poll_answers = answers.try_into().unwrap();
1218        let poll_start_content =
1219            UnstablePollStartEventContent::New(NewUnstablePollStartEventContent::plain_text(
1220                fallback_text,
1221                UnstablePollStartContentBlock::new(poll_question, poll_answers),
1222            ));
1223        self.event(poll_start_content)
1224    }
1225
1226    /// Create a poll edit event given the new question and possible answers.
1227    pub fn poll_edit(
1228        &self,
1229        edited_event_id: &EventId,
1230        poll_question: impl Into<String>,
1231        answers: Vec<impl Into<String>>,
1232    ) -> EventBuilder<ReplacementUnstablePollStartEventContent> {
1233        // PollAnswers 'constructor' is not public, so we need to deserialize
1234        // them
1235        let answers: Vec<UnstablePollAnswer> = answers
1236            .into_iter()
1237            .enumerate()
1238            .map(|(idx, answer)| UnstablePollAnswer::new(idx.to_string(), answer))
1239            .collect();
1240        let poll_answers = answers.try_into().unwrap();
1241        let poll_start_content_block =
1242            UnstablePollStartContentBlock::new(poll_question, poll_answers);
1243        self.event(ReplacementUnstablePollStartEventContent::new(
1244            poll_start_content_block,
1245            edited_event_id.to_owned(),
1246        ))
1247    }
1248
1249    /// Create a poll response with the given answer id and the associated poll
1250    /// start event id.
1251    pub fn poll_response(
1252        &self,
1253        answers: Vec<impl Into<String>>,
1254        poll_start_id: &EventId,
1255    ) -> EventBuilder<UnstablePollResponseEventContent> {
1256        self.event(UnstablePollResponseEventContent::new(
1257            answers.into_iter().map(Into::into).collect(),
1258            poll_start_id.to_owned(),
1259        ))
1260    }
1261
1262    /// Create a poll response with the given text and the associated poll start
1263    /// event id.
1264    pub fn poll_end(
1265        &self,
1266        content: impl Into<String>,
1267        poll_start_id: &EventId,
1268    ) -> EventBuilder<UnstablePollEndEventContent> {
1269        self.event(UnstablePollEndEventContent::new(content.into(), poll_start_id.to_owned()))
1270    }
1271
1272    /// Creates a plain (unencrypted) image event content referencing the given
1273    /// MXC ID.
1274    pub fn image(
1275        &self,
1276        filename: String,
1277        url: OwnedMxcUri,
1278    ) -> EventBuilder<RoomMessageEventContent> {
1279        let image_event_content = ImageMessageEventContent::plain(filename, url);
1280        self.event(RoomMessageEventContent::new(MessageType::Image(image_event_content)))
1281    }
1282
1283    /// Create a gallery event containing a single plain (unencrypted) image
1284    /// referencing the given MXC ID.
1285    pub fn gallery(
1286        &self,
1287        body: String,
1288        filename: String,
1289        url: OwnedMxcUri,
1290    ) -> EventBuilder<RoomMessageEventContent> {
1291        let gallery_event_content = GalleryMessageEventContent::new(
1292            body,
1293            None,
1294            vec![GalleryItemType::Image(ImageMessageEventContent::plain(filename, url))],
1295        );
1296        self.event(RoomMessageEventContent::new(MessageType::Gallery(gallery_event_content)))
1297    }
1298
1299    /// Create a typing notification event.
1300    pub fn typing(&self, user_ids: Vec<&UserId>) -> EventBuilder<TypingEventContent> {
1301        self.event(TypingEventContent::new(user_ids.into_iter().map(ToOwned::to_owned).collect()))
1302            .format(EventFormat::Ephemeral)
1303    }
1304
1305    /// Create a read receipt event.
1306    pub fn read_receipts(&self) -> ReadReceiptBuilder<'_> {
1307        ReadReceiptBuilder { factory: self, content: ReceiptEventContent(Default::default()) }
1308    }
1309
1310    /// Create a new `m.room.create` event.
1311    pub fn create(
1312        &self,
1313        creator_user_id: &UserId,
1314        room_version: RoomVersionId,
1315    ) -> EventBuilder<RoomCreateEventContent> {
1316        let mut event = self.event(RoomCreateEventContent::new_v1(creator_user_id.to_owned()));
1317        event.content.room_version = room_version;
1318
1319        if self.sender.is_some() {
1320            event.sender = self.sender.clone();
1321        } else {
1322            event.sender = Some(creator_user_id.to_owned());
1323        }
1324
1325        event.state_key = Some("".to_owned());
1326
1327        event
1328    }
1329
1330    /// Create a new `m.room.power_levels` event.
1331    pub fn power_levels(
1332        &self,
1333        map: &mut BTreeMap<OwnedUserId, Int>,
1334    ) -> EventBuilder<RoomPowerLevelsEventContent> {
1335        let mut content = RoomPowerLevelsEventContent::new(&AuthorizationRules::V1);
1336        content.users.append(map);
1337        let mut event = self.event(content);
1338        event.state_key = Some("".to_owned());
1339        event
1340    }
1341
1342    /// Create a new `m.room.power_levels` event with default values.
1343    pub fn default_power_levels(&self) -> EventBuilder<RoomPowerLevelsEventContent> {
1344        self.power_levels(&mut BTreeMap::new())
1345    }
1346
1347    /// Create a new `m.room.server_acl` event.
1348    pub fn server_acl(
1349        &self,
1350        allow_ip_literals: bool,
1351        allow: Vec<String>,
1352        deny: Vec<String>,
1353    ) -> EventBuilder<RoomServerAclEventContent> {
1354        self.event(RoomServerAclEventContent::new(allow_ip_literals, allow, deny))
1355    }
1356
1357    /// Create a new `m.room.canonical_alias` event.
1358    pub fn canonical_alias(
1359        &self,
1360        alias: Option<OwnedRoomAliasId>,
1361        alt_aliases: Vec<OwnedRoomAliasId>,
1362    ) -> EventBuilder<RoomCanonicalAliasEventContent> {
1363        let mut content = RoomCanonicalAliasEventContent::new();
1364        content.alias = alias;
1365        content.alt_aliases = alt_aliases;
1366        let mut event = self.event(content);
1367        // The state key is empty for a canonical alias state event.
1368        event.state_key = Some("".to_owned());
1369        event
1370    }
1371
1372    /// Create a new `org.matrix.msc3672.beacon` event.
1373    ///
1374    /// ```
1375    /// use matrix_sdk_test::event_factory::EventFactory;
1376    /// use ruma::{
1377    ///     MilliSecondsSinceUnixEpoch,
1378    ///     events::{MessageLikeEvent, beacon::BeaconEventContent},
1379    ///     owned_event_id, room_id,
1380    ///     serde::Raw,
1381    ///     user_id,
1382    /// };
1383    ///
1384    /// let factory = EventFactory::new().room(room_id!("!test:localhost"));
1385    ///
1386    /// let event: Raw<MessageLikeEvent<BeaconEventContent>> = factory
1387    ///     .beacon(
1388    ///         owned_event_id!("$123456789abc:localhost"),
1389    ///         10.1,
1390    ///         15.2,
1391    ///         5,
1392    ///         Some(MilliSecondsSinceUnixEpoch(1000u32.into())),
1393    ///     )
1394    ///     .sender(user_id!("@alice:localhost"))
1395    ///     .into_raw();
1396    /// ```
1397    pub fn beacon(
1398        &self,
1399        beacon_info_event_id: OwnedEventId,
1400        latitude: f64,
1401        longitude: f64,
1402        uncertainty: u32,
1403        ts: Option<MilliSecondsSinceUnixEpoch>,
1404    ) -> EventBuilder<BeaconEventContent> {
1405        let geo_uri = format!("geo:{latitude},{longitude};u={uncertainty}");
1406        self.event(BeaconEventContent::new(beacon_info_event_id, geo_uri, ts))
1407    }
1408
1409    /// Create a new `org.matrix.msc3672.beacon_info` state event.
1410    ///
1411    /// # Arguments
1412    ///
1413    /// * `description` - An optional human-readable label for the sharing
1414    ///   session.
1415    /// * `duration` - How long the location share is active.
1416    /// * `live` - Whether the sharing session is active. Pass `true` to start
1417    ///   and `false` to stop.
1418    /// * `ts` - The start timestamp; if `None` the current time is used.
1419    ///
1420    /// # Example
1421    ///
1422    /// ```rust
1423    /// use std::time::Duration;
1424    ///
1425    /// use matrix_sdk_test::event_factory::EventFactory;
1426    /// use ruma::{room_id, user_id};
1427    ///
1428    /// let factory = EventFactory::new().room(room_id!("!test:localhost"));
1429    ///
1430    /// let event = factory
1431    ///     .beacon_info(None, Duration::from_secs(60), true, None)
1432    ///     .sender(user_id!("@alice:localhost"))
1433    ///     .state_key(user_id!("@alice:localhost"));
1434    /// ```
1435    pub fn beacon_info(
1436        &self,
1437        description: Option<String>,
1438        duration: Duration,
1439        live: bool,
1440        ts: Option<MilliSecondsSinceUnixEpoch>,
1441    ) -> EventBuilder<BeaconInfoEventContent> {
1442        self.event(BeaconInfoEventContent::new(description, duration, live, ts))
1443    }
1444
1445    /// Create a new `m.sticker` event.
1446    pub fn sticker(
1447        &self,
1448        body: impl Into<String>,
1449        info: ImageInfo,
1450        url: OwnedMxcUri,
1451    ) -> EventBuilder<StickerEventContent> {
1452        self.event(StickerEventContent::new(body.into(), info, url))
1453    }
1454
1455    /// Create a new `m.call.invite` event.
1456    pub fn call_invite(
1457        &self,
1458        call_id: OwnedVoipId,
1459        lifetime: UInt,
1460        offer: SessionDescription,
1461        version: VoipVersionId,
1462    ) -> EventBuilder<CallInviteEventContent> {
1463        self.event(CallInviteEventContent::new(call_id, lifetime, offer, version))
1464    }
1465
1466    /// Create a new `m.rtc.notification` event.
1467    pub fn rtc_notification(
1468        &self,
1469        notification_type: NotificationType,
1470    ) -> EventBuilder<RtcNotificationEventContent> {
1471        self.event(RtcNotificationEventContent::new(
1472            MilliSecondsSinceUnixEpoch::now(),
1473            Duration::new(30, 0),
1474            notification_type,
1475        ))
1476    }
1477
1478    // Creates a new `org.matrix.msc4310.rtc.decline` event.
1479    pub fn call_decline(
1480        &self,
1481        notification_event_id: &EventId,
1482    ) -> EventBuilder<RtcDeclineEventContent> {
1483        self.event(RtcDeclineEventContent::new(notification_event_id))
1484    }
1485
1486    /// Creates a rtc membership state event.
1487    ///
1488    /// ```
1489    /// use matrix_sdk_test::event_factory::EventFactory;
1490    /// use ruma::{
1491    ///     events::{SyncStateEvent, call::member::CallMemberEventContent},
1492    ///     owned_user_id, room_id,
1493    ///     serde::Raw,
1494    /// };
1495    ///
1496    /// let factory = EventFactory::new().room(room_id!("!test:localhost"));
1497    ///
1498    /// let event: Raw<SyncStateEvent<CallMemberEventContent>> = factory
1499    ///     .call_membership_state(
1500    ///         owned_user_id!("@alice:localhost"),
1501    ///         "ABCDEF".to_owned(),
1502    ///     )
1503    ///     .lk_focus("alias".to_owned(), "https://livekit2.com".to_owned())
1504    ///     .into_raw();
1505    /// ```
1506    pub fn call_membership_state(
1507        &self,
1508        user_id: OwnedUserId,
1509        device_id: String,
1510    ) -> EventBuilder<CallMemberEventContent> {
1511        let event = self.event(CallMemberEventContent::new(
1512            Application::Call(CallApplicationContent::new("".to_owned(), CallScope::Room)),
1513            owned_device_id!(device_id.clone()),
1514            ActiveFocus::Livekit(ActiveLivekitFocus::new()),
1515            vec![],
1516            Some(MilliSecondsSinceUnixEpoch::now()),
1517            Some(Duration::from_secs(3600)),
1518        ));
1519        event
1520            .sender(&user_id)
1521            .state_key(CallMemberStateKey::new(user_id, device_id.into(), true).as_ref())
1522    }
1523
1524    /// Create a new `m.direct` global account data event.
1525    pub fn direct(&self) -> EventBuilder<DirectEventContent> {
1526        self.global_account_data(DirectEventContent::default())
1527    }
1528
1529    /// Create a new `m.ignored_user_list` global account data event.
1530    pub fn ignored_user_list(
1531        &self,
1532        users: impl IntoIterator<Item = OwnedUserId>,
1533    ) -> EventBuilder<IgnoredUserListEventContent> {
1534        self.global_account_data(IgnoredUserListEventContent::users(users))
1535    }
1536
1537    /// Create a new `m.push_rules` global account data event.
1538    pub fn push_rules(&self, rules: Ruleset) -> EventBuilder<PushRulesEventContent> {
1539        self.global_account_data(PushRulesEventContent::new(rules))
1540    }
1541
1542    /// Create a new `m.space.child` state event.
1543    pub fn space_child(
1544        &self,
1545        parent: OwnedRoomId,
1546        child: OwnedRoomId,
1547    ) -> EventBuilder<SpaceChildEventContent> {
1548        let mut event = self.event(SpaceChildEventContent::new(vec![]));
1549        event.room = Some(parent);
1550        event.state_key = Some(child.to_string());
1551        event
1552    }
1553
1554    /// Create a new `m.space.parent` state event.
1555    pub fn space_parent(
1556        &self,
1557        parent: OwnedRoomId,
1558        child: OwnedRoomId,
1559    ) -> EventBuilder<SpaceParentEventContent> {
1560        let mut event = self.event(SpaceParentEventContent::new(vec![]));
1561        event.state_key = Some(parent.to_string());
1562        event.room = Some(child);
1563        event
1564    }
1565
1566    /// Create a new `rs.matrix-sdk.custom.test` custom event
1567    pub fn custom_message_like_event(&self) -> EventBuilder<CustomMessageLikeEventContent> {
1568        self.event(CustomMessageLikeEventContent)
1569    }
1570
1571    /// Create a new `rs.matrix-sdk.custom.test` custom event
1572    pub fn custom_state_event(&self) -> EventBuilder<CustomStateEventContent> {
1573        self.event(CustomStateEventContent).state_key("test")
1574    }
1575
1576    /// Set the next server timestamp.
1577    ///
1578    /// Timestamps will continue to increase by 1 (millisecond) from that value.
1579    pub fn set_next_ts(&self, value: u64) {
1580        self.next_ts.store(value, SeqCst);
1581    }
1582
1583    /// Create a new global account data event of the given `C` content type.
1584    pub fn global_account_data<C>(&self, content: C) -> EventBuilder<C>
1585    where
1586        C: GlobalAccountDataEventContent + StaticEventContent<IsPrefix = False>,
1587    {
1588        self.event(content).format(EventFormat::GlobalAccountData)
1589    }
1590
1591    /// Create a new room account data event of the given `C` content type.
1592    pub fn room_account_data<C>(&self, content: C) -> EventBuilder<C>
1593    where
1594        C: RoomAccountDataEventContent + StaticEventContent<IsPrefix = False>,
1595    {
1596        self.event(content).format(EventFormat::RoomAccountData)
1597    }
1598
1599    /// Create a new `m.fully_read` room account data event.
1600    pub fn fully_read(&self, event_id: &EventId) -> EventBuilder<FullyReadEventContent> {
1601        self.room_account_data(FullyReadEventContent::new(event_id.to_owned()))
1602    }
1603
1604    /// Create a new `m.marked_unread` room account data event.
1605    pub fn marked_unread(&self, unread: bool) -> EventBuilder<MarkedUnreadEventContent> {
1606        self.room_account_data(MarkedUnreadEventContent::new(unread))
1607    }
1608
1609    /// Create a new `m.tag` room account data event with the given tags.
1610    pub fn tag(&self, tags: Tags) -> EventBuilder<TagEventContent> {
1611        self.room_account_data(tags.into())
1612    }
1613
1614    /// Create a new `m.space_order` room account data event with the given
1615    /// order.
1616    pub fn space_order(&self, order: &str) -> EventBuilder<SpaceOrderEventContent> {
1617        let order = ruma::SpaceChildOrder::parse(order).expect("order should be valid");
1618        self.room_account_data(SpaceOrderEventContent::new(order))
1619    }
1620
1621    /// Create a new `m.presence` event.
1622    ///
1623    /// This is a special event type that has its own structure different from
1624    /// regular Matrix events.
1625    pub fn presence(&self, state: PresenceState) -> PresenceBuilder {
1626        PresenceBuilder { sender: self.sender.clone(), content: PresenceEventContent::new(state) }
1627    }
1628}
1629
1630/// Builder for presence events.
1631#[derive(Debug)]
1632pub struct PresenceBuilder {
1633    sender: Option<OwnedUserId>,
1634    content: PresenceEventContent,
1635}
1636
1637impl PresenceBuilder {
1638    /// Set the sender of the presence event.
1639    pub fn sender(mut self, sender: &UserId) -> Self {
1640        self.sender = Some(sender.to_owned());
1641        self
1642    }
1643
1644    /// Set the avatar URL.
1645    pub fn avatar_url(mut self, url: &MxcUri) -> Self {
1646        self.content.avatar_url = Some(url.to_owned());
1647        self
1648    }
1649
1650    /// Set whether the user is currently active.
1651    pub fn currently_active(mut self, active: bool) -> Self {
1652        self.content.currently_active = Some(active);
1653        self
1654    }
1655
1656    /// Set the last active time in milliseconds.
1657    pub fn last_active_ago(mut self, ms: u64) -> Self {
1658        self.content.last_active_ago = Some(UInt::try_from(ms).unwrap());
1659        self
1660    }
1661
1662    /// Set the status message.
1663    pub fn status_msg(mut self, msg: impl Into<String>) -> Self {
1664        self.content.status_msg = Some(msg.into());
1665        self
1666    }
1667
1668    /// Set the display name.
1669    pub fn display_name(mut self, name: impl Into<String>) -> Self {
1670        self.content.displayname = Some(name.into());
1671        self
1672    }
1673}
1674
1675impl From<PresenceBuilder> for Raw<PresenceEvent> {
1676    fn from(builder: PresenceBuilder) -> Self {
1677        let sender = builder.sender.expect("sender must be set for presence events");
1678        let event = PresenceEvent { content: builder.content, sender };
1679        Raw::new(&event).unwrap().cast_unchecked()
1680    }
1681}
1682
1683impl EventBuilder<DirectEventContent> {
1684    /// Add a user/room pair to the `m.direct` event.
1685    pub fn add_user(
1686        mut self,
1687        user_id: impl Into<OwnedDirectUserIdentifier>,
1688        room_id: &RoomId,
1689    ) -> Self {
1690        self.content.0.entry(user_id.into()).or_default().push(room_id.to_owned());
1691        self
1692    }
1693}
1694
1695impl EventBuilder<RoomMemberEventContent> {
1696    /// Set the `membership` of the `m.room.member` event to the given
1697    /// [`MembershipState`].
1698    ///
1699    /// The default is [`MembershipState::Join`].
1700    pub fn membership(mut self, state: MembershipState) -> Self {
1701        self.content.membership = state;
1702        self
1703    }
1704
1705    /// Set that the sender of this event invited the user passed as a parameter
1706    /// here.
1707    pub fn invited(mut self, invited_user: impl Into<OwnedUserId> + Into<String>) -> Self {
1708        let invited_user: String = invited_user.into();
1709        assert_ne!(
1710            self.sender.as_deref().unwrap(),
1711            invited_user.as_str(),
1712            "invited user and sender can't be the same person"
1713        );
1714        self.content.membership = MembershipState::Invite;
1715        self.state_key = Some(invited_user);
1716        self
1717    }
1718
1719    /// Set that the sender of this event left the room (self-leave).
1720    ///
1721    /// This sets the membership to Leave and uses the sender as the state_key.
1722    pub fn leave(mut self) -> Self {
1723        self.content.membership = MembershipState::Leave;
1724        self.state_key = Some(self.sender.as_ref().expect("sender must be set").to_string());
1725        self
1726    }
1727
1728    /// Set that the sender of this event kicked the user passed as a parameter
1729    /// here.
1730    pub fn kicked(mut self, kicked_user: impl Into<OwnedUserId> + Into<String>) -> Self {
1731        let kicked_user: String = kicked_user.into();
1732        assert_ne!(
1733            self.sender.as_deref().unwrap(),
1734            kicked_user.as_str(),
1735            "kicked user and sender can't be the same person, otherwise it's just a Leave"
1736        );
1737        self.content.membership = MembershipState::Leave;
1738        self.state_key = Some(kicked_user);
1739        self
1740    }
1741
1742    /// Set that the sender of this event banned the user passed as a parameter
1743    /// here.
1744    pub fn banned(mut self, banned_user: impl Into<OwnedUserId> + Into<String>) -> Self {
1745        let banned_user: String = banned_user.into();
1746        assert_ne!(
1747            self.sender.as_deref().unwrap(),
1748            banned_user.as_str(),
1749            "a user can't ban itself" // hopefully
1750        );
1751        self.content.membership = MembershipState::Ban;
1752        self.state_key = Some(banned_user);
1753        self
1754    }
1755
1756    /// Set the display name of the `m.room.member` event.
1757    pub fn display_name(mut self, display_name: impl Into<String>) -> Self {
1758        self.content.displayname = Some(display_name.into());
1759        self
1760    }
1761
1762    /// Set the avatar URL of the `m.room.member` event.
1763    pub fn avatar_url(mut self, url: &MxcUri) -> Self {
1764        self.content.avatar_url = Some(url.to_owned());
1765        self
1766    }
1767
1768    /// Set the reason field of the `m.room.member` event.
1769    pub fn reason(mut self, reason: impl Into<String>) -> Self {
1770        self.content.reason = Some(reason.into());
1771        self
1772    }
1773
1774    /// Set the previous membership state (in the unsigned section).
1775    pub fn previous(mut self, previous: impl Into<PreviousMembership>) -> Self {
1776        let previous = previous.into();
1777
1778        let mut prev_content = RoomMemberEventContent::new(previous.state);
1779        if let Some(avatar_url) = previous.avatar_url {
1780            prev_content.avatar_url = Some(avatar_url);
1781        }
1782        if let Some(display_name) = previous.display_name {
1783            prev_content.displayname = Some(display_name);
1784        }
1785
1786        self.unsigned.get_or_insert_with(Default::default).prev_content = Some(prev_content);
1787        self
1788    }
1789
1790    /// Set the invite room state (in the unsigned section).
1791    ///
1792    /// This is used to provide context about the room when a user is invited,
1793    /// such as the room name and join rules.
1794    pub fn invite_room_state<I, E>(mut self, events: I) -> Self
1795    where
1796        I: IntoIterator<Item = E>,
1797        E: Into<Raw<AnyStrippedStateEvent>>,
1798    {
1799        self.unsigned.get_or_insert_with(Default::default).invite_room_state =
1800            Some(events.into_iter().map(Into::into).collect());
1801        self
1802    }
1803}
1804
1805impl EventBuilder<RoomAvatarEventContent> {
1806    /// Defines the URL for the room avatar.
1807    pub fn url(mut self, url: &MxcUri) -> Self {
1808        self.content.url = Some(url.to_owned());
1809        self
1810    }
1811
1812    /// Defines the image info for the avatar.
1813    pub fn info(mut self, image: avatar::ImageInfo) -> Self {
1814        self.content.info = Some(Box::new(image));
1815        self
1816    }
1817}
1818
1819impl EventBuilder<RtcNotificationEventContent> {
1820    pub fn mentions(mut self, users: impl IntoIterator<Item = OwnedUserId>) -> Self {
1821        self.content.mentions = Some(Mentions::with_user_ids(users));
1822        self
1823    }
1824
1825    pub fn call_intent(mut self, call_intent: CallIntent) -> Self {
1826        self.content.call_intent = Some(call_intent);
1827        self
1828    }
1829
1830    pub fn relates_to_membership_state_event(mut self, event_id: OwnedEventId) -> Self {
1831        self.content.relates_to = Some(Reference::new(event_id));
1832        self
1833    }
1834
1835    pub fn lifetime(mut self, time_in_seconds: u64) -> Self {
1836        self.content.lifetime = Duration::from_secs(time_in_seconds);
1837        self
1838    }
1839}
1840
1841impl EventBuilder<CallMemberEventContent> {
1842    /// Sets the livekit focus to the call membership event
1843    pub fn set_livekit_focus(mut self, alias: String, service_url: String) -> Self {
1844        if let CallMemberEventContent::SessionContent(session_data) = &mut self.content {
1845            session_data.foci_preferred =
1846                vec![Focus::Livekit(LivekitFocus::new(alias, service_url))];
1847        } else {
1848            panic!("focus() called on a non-SessionContent call member event");
1849        }
1850        self
1851    }
1852
1853    /// Sets the application for the call membership event.
1854    pub fn application(mut self, call_id: String, scope: CallScope) -> Self {
1855        if let CallMemberEventContent::SessionContent(session_data) = &mut self.content {
1856            session_data.application =
1857                Application::Call(CallApplicationContent::new(call_id, scope));
1858        } else {
1859            panic!("application() called on a non-SessionContent call member event");
1860        }
1861        self
1862    }
1863
1864    /// Make the membership event as a membership event.
1865    pub fn leave(mut self) -> Self {
1866        self.content = CallMemberEventContent::new_empty(None);
1867        self
1868    }
1869}
1870pub struct ReadReceiptBuilder<'a> {
1871    factory: &'a EventFactory,
1872    content: ReceiptEventContent,
1873}
1874
1875impl ReadReceiptBuilder<'_> {
1876    /// Add a single read receipt to the event.
1877    pub fn add(
1878        self,
1879        event_id: &EventId,
1880        user_id: impl Into<OwnedUserId>,
1881        tyype: ReceiptType,
1882        thread: ReceiptThread,
1883    ) -> Self {
1884        let ts = self.factory.next_server_ts();
1885        self.add_with_timestamp(event_id, user_id, tyype, thread, Some(ts))
1886    }
1887
1888    /// Add a single read receipt to the event, with an optional timestamp.
1889    pub fn add_with_timestamp(
1890        mut self,
1891        event_id: &EventId,
1892        user_id: impl Into<OwnedUserId>,
1893        tyype: ReceiptType,
1894        thread: ReceiptThread,
1895        ts: Option<MilliSecondsSinceUnixEpoch>,
1896    ) -> Self {
1897        let by_event = self.content.0.entry(event_id.to_owned()).or_default();
1898        let by_type = by_event.entry(tyype).or_default();
1899
1900        let mut receipt = Receipt::default();
1901        if let Some(ts) = ts {
1902            receipt.ts = Some(ts);
1903        }
1904        receipt.thread = thread;
1905
1906        by_type.insert(user_id.into(), receipt);
1907        self
1908    }
1909
1910    /// Finalize the builder into the receipt event content.
1911    pub fn into_content(self) -> ReceiptEventContent {
1912        self.content
1913    }
1914
1915    /// Finalize the builder into an event builder.
1916    pub fn into_event(self) -> EventBuilder<ReceiptEventContent> {
1917        self.factory.event(self.into_content()).format(EventFormat::Ephemeral)
1918    }
1919}
1920
1921pub struct PreviousMembership {
1922    state: MembershipState,
1923    avatar_url: Option<OwnedMxcUri>,
1924    display_name: Option<String>,
1925}
1926
1927impl PreviousMembership {
1928    pub fn new(state: MembershipState) -> Self {
1929        Self { state, avatar_url: None, display_name: None }
1930    }
1931
1932    pub fn avatar_url(mut self, url: &MxcUri) -> Self {
1933        self.avatar_url = Some(url.to_owned());
1934        self
1935    }
1936
1937    pub fn display_name(mut self, name: impl Into<String>) -> Self {
1938        self.display_name = Some(name.into());
1939        self
1940    }
1941}
1942
1943impl From<MembershipState> for PreviousMembership {
1944    fn from(state: MembershipState) -> Self {
1945        Self::new(state)
1946    }
1947}
1948
1949#[derive(Clone, Default, Debug, Serialize, EventContent)]
1950#[ruma_event(type = "rs.matrix-sdk.custom.test", kind = MessageLike)]
1951pub struct CustomMessageLikeEventContent;
1952
1953#[derive(Clone, Default, Debug, Serialize, Deserialize, EventContent)]
1954#[ruma_event(type = "rs.matrix-sdk.custom-state.test", kind = State, state_key_type = String)]
1955pub struct CustomStateEventContent;