matrix_sdk/room/
mod.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//! High-level room API
16
17use std::{
18    borrow::Borrow,
19    collections::{BTreeMap, HashMap},
20    future::Future,
21    ops::Deref,
22    sync::Arc,
23    time::Duration,
24};
25
26use async_stream::stream;
27use eyeball::SharedObservable;
28use futures_core::Stream;
29use futures_util::{
30    future::join_all, stream as futures_stream, stream::FuturesUnordered, StreamExt,
31};
32use http::StatusCode;
33#[cfg(feature = "e2e-encryption")]
34pub use identity_status_changes::IdentityStatusChanges;
35#[cfg(feature = "e2e-encryption")]
36use matrix_sdk_base::crypto::{IdentityStatusChange, RoomIdentityProvider, UserIdentity};
37pub use matrix_sdk_base::store::ThreadSubscription;
38#[cfg(feature = "e2e-encryption")]
39use matrix_sdk_base::{crypto::RoomEventDecryptionResult, deserialized_responses::EncryptionInfo};
40use matrix_sdk_base::{
41    deserialized_responses::{
42        RawAnySyncOrStrippedState, RawSyncOrStrippedState, SyncOrStrippedState,
43    },
44    event_cache::store::media::IgnoreMediaRetentionPolicy,
45    media::MediaThumbnailSettings,
46    store::StateStoreExt,
47    ComposerDraft, EncryptionState, RoomInfoNotableUpdateReasons, RoomMemberships, SendOutsideWasm,
48    StateChanges, StateStoreDataKey, StateStoreDataValue,
49};
50#[cfg(feature = "e2e-encryption")]
51use matrix_sdk_common::BoxFuture;
52use matrix_sdk_common::{
53    deserialized_responses::TimelineEvent,
54    executor::{spawn, JoinHandle},
55    timeout::timeout,
56};
57use mime::Mime;
58use reply::Reply;
59#[cfg(feature = "unstable-msc4274")]
60use ruma::events::room::message::GalleryItemType;
61#[cfg(feature = "e2e-encryption")]
62use ruma::events::{
63    room::encrypted::OriginalSyncRoomEncryptedEvent, AnySyncMessageLikeEvent, AnySyncTimelineEvent,
64    SyncMessageLikeEvent,
65};
66use ruma::{
67    api::client::{
68        config::{set_global_account_data, set_room_account_data},
69        context,
70        error::ErrorKind,
71        filter::LazyLoadOptions,
72        membership::{
73            ban_user, forget_room, get_member_events,
74            invite_user::{self, v3::InvitationRecipient},
75            kick_user, leave_room, unban_user, Invite3pid,
76        },
77        message::send_message_event,
78        read_marker::set_read_marker,
79        receipt::create_receipt,
80        redact::redact_event,
81        room::{get_room_event, report_content, report_room},
82        state::{get_state_event_for_key, send_state_event},
83        tag::{create_tag, delete_tag},
84        threads::{get_thread_subscription, subscribe_thread, unsubscribe_thread},
85        typing::create_typing_event::{self, v3::Typing},
86    },
87    assign,
88    events::{
89        beacon::BeaconEventContent,
90        beacon_info::BeaconInfoEventContent,
91        direct::DirectEventContent,
92        marked_unread::MarkedUnreadEventContent,
93        receipt::{Receipt, ReceiptThread, ReceiptType},
94        room::{
95            avatar::{self, RoomAvatarEventContent},
96            encryption::RoomEncryptionEventContent,
97            history_visibility::HistoryVisibility,
98            member::{MembershipChange, SyncRoomMemberEvent},
99            message::{
100                AudioInfo, AudioMessageEventContent, FileInfo, FileMessageEventContent,
101                FormattedBody, ImageMessageEventContent, MessageType, RoomMessageEventContent,
102                UnstableAudioDetailsContentBlock, UnstableVoiceContentBlock, VideoInfo,
103                VideoMessageEventContent,
104            },
105            name::RoomNameEventContent,
106            pinned_events::RoomPinnedEventsEventContent,
107            power_levels::{
108                RoomPowerLevels, RoomPowerLevelsEventContent, RoomPowerLevelsSource, UserPowerLevel,
109            },
110            server_acl::RoomServerAclEventContent,
111            topic::RoomTopicEventContent,
112            ImageInfo, MediaSource, ThumbnailInfo,
113        },
114        space::{child::SpaceChildEventContent, parent::SpaceParentEventContent},
115        tag::{TagInfo, TagName},
116        typing::SyncTypingEvent,
117        AnyRoomAccountDataEvent, AnyRoomAccountDataEventContent, AnyTimelineEvent, EmptyStateKey,
118        Mentions, MessageLikeEventContent, OriginalSyncStateEvent, RedactContent,
119        RedactedStateEventContent, RoomAccountDataEvent, RoomAccountDataEventContent,
120        RoomAccountDataEventType, StateEventContent, StateEventType, StaticEventContent,
121        StaticStateEventContent, SyncStateEvent,
122    },
123    int,
124    push::{Action, PushConditionRoomCtx, Ruleset},
125    serde::Raw,
126    time::Instant,
127    EventId, Int, MatrixToUri, MatrixUri, MxcUri, OwnedEventId, OwnedRoomId, OwnedServerName,
128    OwnedTransactionId, OwnedUserId, RoomId, TransactionId, UInt, UserId,
129};
130use serde::de::DeserializeOwned;
131use thiserror::Error;
132use tokio::{join, sync::broadcast};
133use tracing::{debug, error, info, instrument, trace, warn};
134
135use self::futures::{SendAttachment, SendMessageLikeEvent, SendRawMessageLikeEvent};
136pub use self::{
137    member::{RoomMember, RoomMemberRole},
138    messages::{
139        EventWithContextResponse, IncludeRelations, ListThreadsOptions, Messages, MessagesOptions,
140        Relations, RelationsOptions, ThreadRoots,
141    },
142};
143#[cfg(doc)]
144use crate::event_cache::EventCache;
145use crate::{
146    attachment::{AttachmentConfig, AttachmentInfo},
147    client::WeakClient,
148    config::RequestConfig,
149    error::{BeaconError, WrongRoomState},
150    event_cache::{self, EventCacheDropHandles, RoomEventCache},
151    event_handler::{EventHandler, EventHandlerDropGuard, EventHandlerHandle, SyncEvent},
152    live_location_share::ObservableLiveLocation,
153    media::{MediaFormat, MediaRequestParameters},
154    notification_settings::{IsEncrypted, IsOneToOne, RoomNotificationMode},
155    room::{
156        knock_requests::{KnockRequest, KnockRequestMemberInfo},
157        power_levels::{RoomPowerLevelChanges, RoomPowerLevelsExt},
158        privacy_settings::RoomPrivacySettings,
159    },
160    sync::RoomUpdate,
161    utils::{IntoRawMessageLikeEventContent, IntoRawStateEventContent},
162    BaseRoom, Client, Error, HttpResult, Result, RoomState, TransmissionProgress,
163};
164#[cfg(feature = "e2e-encryption")]
165use crate::{crypto::types::events::CryptoContextInfo, encryption::backups::BackupState};
166
167pub mod edit;
168pub mod futures;
169pub mod identity_status_changes;
170/// Contains code related to requests to join a room.
171pub mod knock_requests;
172mod member;
173mod messages;
174pub mod power_levels;
175pub mod reply;
176
177/// Contains all the functionality for modifying the privacy settings in a room.
178pub mod privacy_settings;
179
180#[cfg(feature = "e2e-encryption")]
181pub(crate) mod shared_room_history;
182
183/// A struct containing methods that are common for Joined, Invited and Left
184/// Rooms
185#[derive(Debug, Clone)]
186pub struct Room {
187    inner: BaseRoom,
188    pub(crate) client: Client,
189}
190
191impl Deref for Room {
192    type Target = BaseRoom;
193
194    fn deref(&self) -> &Self::Target {
195        &self.inner
196    }
197}
198
199const TYPING_NOTICE_TIMEOUT: Duration = Duration::from_secs(4);
200const TYPING_NOTICE_RESEND_TIMEOUT: Duration = Duration::from_secs(3);
201
202/// Context allowing to compute the push actions for a given event.
203#[derive(Debug)]
204pub struct PushContext {
205    /// The Ruma context used to compute the push actions.
206    push_condition_room_ctx: PushConditionRoomCtx,
207
208    /// Push rules for this room, based on the push rules state event, or the
209    /// global server default as defined by [`Ruleset::server_default`].
210    push_rules: Ruleset,
211}
212
213impl PushContext {
214    /// Create a new [`PushContext`] from its inner components.
215    pub fn new(push_condition_room_ctx: PushConditionRoomCtx, push_rules: Ruleset) -> Self {
216        Self { push_condition_room_ctx, push_rules }
217    }
218
219    /// Compute the push rules for a given event.
220    pub async fn for_event<T>(&self, event: &Raw<T>) -> Vec<Action> {
221        self.push_rules.get_actions(event, &self.push_condition_room_ctx).await.to_owned()
222    }
223}
224
225macro_rules! make_media_type {
226    ($t:ty, $content_type: ident, $filename: ident, $source: ident, $caption: ident, $formatted_caption: ident, $info: ident, $thumbnail: ident) => {{
227        // If caption is set, use it as body, and filename as the file name; otherwise,
228        // body is the filename, and the filename is not set.
229        // https://github.com/matrix-org/matrix-spec-proposals/blob/main/proposals/2530-body-as-caption.md
230        let (body, filename) = match $caption {
231            Some(caption) => (caption, Some($filename)),
232            None => ($filename, None),
233        };
234
235        let (thumbnail_source, thumbnail_info) = $thumbnail.unzip();
236
237        match $content_type.type_() {
238            mime::IMAGE => {
239                let info = assign!($info.map(ImageInfo::from).unwrap_or_default(), {
240                    mimetype: Some($content_type.as_ref().to_owned()),
241                    thumbnail_source,
242                    thumbnail_info
243                });
244                let content = assign!(ImageMessageEventContent::new(body, $source), {
245                    info: Some(Box::new(info)),
246                    formatted: $formatted_caption,
247                    filename
248                });
249                <$t>::Image(content)
250            }
251
252            mime::AUDIO => {
253                let mut content = assign!(AudioMessageEventContent::new(body, $source), {
254                    formatted: $formatted_caption,
255                    filename
256                });
257
258                if let Some(AttachmentInfo::Voice { audio_info, waveform: Some(waveform_vec) }) =
259                    &$info
260                {
261                    if let Some(duration) = audio_info.duration {
262                        let waveform = waveform_vec.iter().map(|v| (*v).into()).collect();
263                        content.audio =
264                            Some(UnstableAudioDetailsContentBlock::new(duration, waveform));
265                    }
266                    content.voice = Some(UnstableVoiceContentBlock::new());
267                }
268
269                let mut audio_info = $info.map(AudioInfo::from).unwrap_or_default();
270                audio_info.mimetype = Some($content_type.as_ref().to_owned());
271                let content = content.info(Box::new(audio_info));
272
273                <$t>::Audio(content)
274            }
275
276            mime::VIDEO => {
277                let info = assign!($info.map(VideoInfo::from).unwrap_or_default(), {
278                    mimetype: Some($content_type.as_ref().to_owned()),
279                    thumbnail_source,
280                    thumbnail_info
281                });
282                let content = assign!(VideoMessageEventContent::new(body, $source), {
283                    info: Some(Box::new(info)),
284                    formatted: $formatted_caption,
285                    filename
286                });
287                <$t>::Video(content)
288            }
289
290            _ => {
291                let info = assign!($info.map(FileInfo::from).unwrap_or_default(), {
292                    mimetype: Some($content_type.as_ref().to_owned()),
293                    thumbnail_source,
294                    thumbnail_info
295                });
296                let content = assign!(FileMessageEventContent::new(body, $source), {
297                    info: Some(Box::new(info)),
298                    formatted: $formatted_caption,
299                    filename,
300                });
301                <$t>::File(content)
302            }
303        }
304    }};
305}
306
307impl Room {
308    /// Create a new `Room`
309    ///
310    /// # Arguments
311    /// * `client` - The client used to make requests.
312    ///
313    /// * `room` - The underlying room.
314    pub(crate) fn new(client: Client, room: BaseRoom) -> Self {
315        Self { inner: room, client }
316    }
317
318    /// Leave this room.
319    /// If the room was in [`RoomState::Invited`] state, it'll also be forgotten
320    /// automatically.
321    ///
322    /// Only invited and joined rooms can be left.
323    #[doc(alias = "reject_invitation")]
324    #[instrument(skip_all, fields(room_id = ?self.inner.room_id()))]
325    async fn leave_impl(&self) -> (Result<()>, &Room) {
326        let state = self.state();
327        if state == RoomState::Left {
328            return (
329                Err(Error::WrongRoomState(Box::new(WrongRoomState::new(
330                    "Joined or Invited",
331                    state,
332                )))),
333                self,
334            );
335        }
336
337        // If the room was in Invited state we should also forget it when declining the
338        // invite.
339        let should_forget = matches!(self.state(), RoomState::Invited);
340
341        let request = leave_room::v3::Request::new(self.inner.room_id().to_owned());
342        let response = self.client.send(request).await;
343
344        // The server can return with an error that is acceptable to ignore. Let's find
345        // which one.
346        if let Err(error) = response {
347            #[allow(clippy::collapsible_match)]
348            let ignore_error = if let Some(error) = error.client_api_error_kind() {
349                match error {
350                    // The user is trying to leave a room but doesn't have permissions to do so.
351                    // Let's consider the user has left the room.
352                    ErrorKind::Forbidden { .. } => true,
353                    _ => false,
354                }
355            } else {
356                false
357            };
358
359            error!(?error, ignore_error, should_forget, "Failed to leave the room");
360
361            if !ignore_error {
362                return (Err(error.into()), self);
363            }
364        }
365
366        if let Err(e) = self.client.base_client().room_left(self.room_id()).await {
367            return (Err(e.into()), self);
368        }
369
370        if should_forget {
371            trace!("Trying to forget the room");
372
373            if let Err(error) = self.forget().await {
374                error!(?error, "Failed to forget the room");
375            }
376        }
377
378        (Ok(()), self)
379    }
380
381    /// Leave this room and all predecessors.
382    /// If any room was in [`RoomState::Invited`] state, it'll also be forgotten
383    /// automatically.
384    ///
385    /// Only invited and joined rooms can be left.
386    /// Will return an error if the current room fails to leave but
387    /// will only warn if a predecessor fails to leave.
388    pub async fn leave(&self) -> Result<()> {
389        let mut rooms: Vec<Room> = vec![self.clone()];
390        let mut current_room = self;
391
392        while let Some(predecessor) = current_room.predecessor_room() {
393            let maybe_predecessor_room = current_room.client.get_room(&predecessor.room_id);
394
395            if let Some(predecessor_room) = maybe_predecessor_room {
396                rooms.push(predecessor_room.clone());
397                current_room = rooms.last().expect("Room just pushed so can't be empty");
398            } else {
399                warn!("Cannot find predecessor room");
400                break;
401            }
402        }
403
404        let batch_size = 5;
405
406        let rooms_futures: Vec<_> = rooms
407            .iter()
408            .filter_map(|room| match room.state() {
409                RoomState::Joined | RoomState::Invited | RoomState::Knocked => {
410                    Some(room.leave_impl())
411                }
412                RoomState::Banned | RoomState::Left => None,
413            })
414            .collect();
415
416        let mut futures_stream = futures_stream::iter(rooms_futures).buffer_unordered(batch_size);
417
418        let mut maybe_this_room_failed_with: Option<Error> = None;
419
420        while let Some(result) = futures_stream.next().await {
421            if let (Err(e), room) = result {
422                if room.room_id() == self.room_id() {
423                    maybe_this_room_failed_with = Some(e);
424                } else {
425                    warn!("Failure while attempting to leave predecessor room: {e:?}");
426                }
427            }
428        }
429
430        maybe_this_room_failed_with.map_or(Ok(()), Err)
431    }
432
433    /// Join this room.
434    ///
435    /// Only invited and left rooms can be joined via this method.
436    #[doc(alias = "accept_invitation")]
437    pub async fn join(&self) -> Result<()> {
438        let prev_room_state = self.inner.state();
439
440        if prev_room_state == RoomState::Joined {
441            return Err(Error::WrongRoomState(Box::new(WrongRoomState::new(
442                "Invited or Left",
443                prev_room_state,
444            ))));
445        }
446
447        self.client.join_room_by_id(self.room_id()).await?;
448
449        Ok(())
450    }
451
452    /// Get the inner client saved in this room instance.
453    ///
454    /// Returns the client this room is part of.
455    pub fn client(&self) -> Client {
456        self.client.clone()
457    }
458
459    /// Get the sync state of this room, i.e. whether it was fully synced with
460    /// the server.
461    pub fn is_synced(&self) -> bool {
462        self.inner.is_state_fully_synced()
463    }
464
465    /// Gets the avatar of this room, if set.
466    ///
467    /// Returns the avatar.
468    /// If a thumbnail is requested no guarantee on the size of the image is
469    /// given.
470    ///
471    /// # Arguments
472    ///
473    /// * `format` - The desired format of the avatar.
474    ///
475    /// # Examples
476    ///
477    /// ```no_run
478    /// # use matrix_sdk::Client;
479    /// # use matrix_sdk::ruma::room_id;
480    /// # use matrix_sdk::media::MediaFormat;
481    /// # use url::Url;
482    /// # let homeserver = Url::parse("http://example.com").unwrap();
483    /// # async {
484    /// # let user = "example";
485    /// let client = Client::new(homeserver).await.unwrap();
486    /// client.matrix_auth().login_username(user, "password").send().await.unwrap();
487    /// let room_id = room_id!("!roomid:example.com");
488    /// let room = client.get_room(&room_id).unwrap();
489    /// if let Some(avatar) = room.avatar(MediaFormat::File).await.unwrap() {
490    ///     std::fs::write("avatar.png", avatar);
491    /// }
492    /// # };
493    /// ```
494    pub async fn avatar(&self, format: MediaFormat) -> Result<Option<Vec<u8>>> {
495        let Some(url) = self.avatar_url() else { return Ok(None) };
496        let request = MediaRequestParameters { source: MediaSource::Plain(url.to_owned()), format };
497        Ok(Some(self.client.media().get_media_content(&request, true).await?))
498    }
499
500    /// Sends a request to `/_matrix/client/r0/rooms/{room_id}/messages` and
501    /// returns a `Messages` struct that contains a chunk of room and state
502    /// events (`RoomEvent` and `AnyStateEvent`).
503    ///
504    /// With the encryption feature, messages are decrypted if possible. If
505    /// decryption fails for an individual message, that message is returned
506    /// undecrypted.
507    ///
508    /// # Examples
509    ///
510    /// ```no_run
511    /// use matrix_sdk::{room::MessagesOptions, Client};
512    /// # use matrix_sdk::ruma::{
513    /// #     api::client::filter::RoomEventFilter,
514    /// #     room_id,
515    /// # };
516    /// # use url::Url;
517    ///
518    /// # let homeserver = Url::parse("http://example.com").unwrap();
519    /// # async {
520    /// let options =
521    ///     MessagesOptions::backward().from("t47429-4392820_219380_26003_2265");
522    ///
523    /// let mut client = Client::new(homeserver).await.unwrap();
524    /// let room = client.get_room(room_id!("!roomid:example.com")).unwrap();
525    /// assert!(room.messages(options).await.is_ok());
526    /// # };
527    /// ```
528    #[instrument(skip_all, fields(room_id = ?self.inner.room_id(), ?options))]
529    pub async fn messages(&self, options: MessagesOptions) -> Result<Messages> {
530        let room_id = self.inner.room_id();
531        let request = options.into_request(room_id);
532        let http_response = self.client.send(request).await?;
533
534        let push_ctx = self.push_context().await?;
535        let chunk = join_all(
536            http_response.chunk.into_iter().map(|ev| self.try_decrypt_event(ev, push_ctx.as_ref())),
537        )
538        .await;
539
540        Ok(Messages {
541            start: http_response.start,
542            end: http_response.end,
543            chunk,
544            state: http_response.state,
545        })
546    }
547
548    /// Register a handler for events of a specific type, within this room.
549    ///
550    /// This method works the same way as [`Client::add_event_handler`], except
551    /// that the handler will only be called for events within this room. See
552    /// that method for more details on event handler functions.
553    ///
554    /// `room.add_event_handler(hdl)` is equivalent to
555    /// `client.add_room_event_handler(room_id, hdl)`. Use whichever one is more
556    /// convenient in your use case.
557    pub fn add_event_handler<Ev, Ctx, H>(&self, handler: H) -> EventHandlerHandle
558    where
559        Ev: SyncEvent + DeserializeOwned + Send + 'static,
560        H: EventHandler<Ev, Ctx>,
561    {
562        self.client.add_room_event_handler(self.room_id(), handler)
563    }
564
565    /// Subscribe to all updates for this room.
566    ///
567    /// The returned receiver will receive a new message for each sync response
568    /// that contains updates for this room.
569    pub fn subscribe_to_updates(&self) -> broadcast::Receiver<RoomUpdate> {
570        self.client.subscribe_to_room_updates(self.room_id())
571    }
572
573    /// Subscribe to typing notifications for this room.
574    ///
575    /// The returned receiver will receive a new vector of user IDs for each
576    /// sync response that contains 'm.typing' event. The current user ID will
577    /// be filtered out.
578    pub fn subscribe_to_typing_notifications(
579        &self,
580    ) -> (EventHandlerDropGuard, broadcast::Receiver<Vec<OwnedUserId>>) {
581        let (sender, receiver) = broadcast::channel(16);
582        let typing_event_handler_handle = self.client.add_room_event_handler(self.room_id(), {
583            let own_user_id = self.own_user_id().to_owned();
584            move |event: SyncTypingEvent| async move {
585                // Ignore typing notifications from own user.
586                let typing_user_ids = event
587                    .content
588                    .user_ids
589                    .into_iter()
590                    .filter(|user_id| *user_id != own_user_id)
591                    .collect();
592                // Ignore the result. It can only fail if there are no listeners.
593                let _ = sender.send(typing_user_ids);
594            }
595        });
596        let drop_guard = self.client().event_handler_drop_guard(typing_event_handler_handle);
597        (drop_guard, receiver)
598    }
599
600    /// Subscribe to updates about users who are in "pin violation" i.e. their
601    /// identity has changed and the user has not yet acknowledged this.
602    ///
603    /// The returned receiver will receive a new vector of
604    /// [`IdentityStatusChange`] each time a /keys/query response shows a
605    /// changed identity for a member of this room, or a sync shows a change
606    /// to the membership of an affected user. (Changes to the current user are
607    /// not directly included, but some changes to the current user's identity
608    /// can trigger changes to how we see other users' identities, which
609    /// will be included.)
610    ///
611    /// The first item in the stream provides the current state of the room:
612    /// each member of the room who is not in "pinned" or "verified" state will
613    /// be included (except the current user).
614    ///
615    /// If the `changed_to` property of an [`IdentityStatusChange`] is set to
616    /// `PinViolation` then a warning should be displayed to the user. If it is
617    /// set to `Pinned` then no warning should be displayed.
618    ///
619    /// Note that if a user who is in pin violation leaves the room, a `Pinned`
620    /// update is sent, to indicate that the warning should be removed, even
621    /// though the user's identity is not necessarily pinned.
622    #[cfg(feature = "e2e-encryption")]
623    pub async fn subscribe_to_identity_status_changes(
624        &self,
625    ) -> Result<impl Stream<Item = Vec<IdentityStatusChange>>> {
626        IdentityStatusChanges::create_stream(self.clone()).await
627    }
628
629    /// Returns a wrapping `TimelineEvent` for the input `AnyTimelineEvent`,
630    /// decrypted if needs be.
631    ///
632    /// Only logs from the crypto crate will indicate a failure to decrypt.
633    #[allow(clippy::unused_async)] // Used only in e2e-encryption.
634    async fn try_decrypt_event(
635        &self,
636        event: Raw<AnyTimelineEvent>,
637        push_ctx: Option<&PushContext>,
638    ) -> TimelineEvent {
639        #[cfg(feature = "e2e-encryption")]
640        if let Ok(AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomEncrypted(
641            SyncMessageLikeEvent::Original(_),
642        ))) = event.deserialize_as::<AnySyncTimelineEvent>()
643        {
644            if let Ok(event) = self.decrypt_event(event.cast_ref_unchecked(), push_ctx).await {
645                return event;
646            }
647        }
648
649        let mut event = TimelineEvent::from_plaintext(event.cast());
650        if let Some(push_ctx) = push_ctx {
651            event.set_push_actions(push_ctx.for_event(event.raw()).await);
652        }
653
654        event
655    }
656
657    /// Fetch the event with the given `EventId` in this room.
658    ///
659    /// It uses the given [`RequestConfig`] if provided, or the client's default
660    /// one otherwise.
661    pub async fn event(
662        &self,
663        event_id: &EventId,
664        request_config: Option<RequestConfig>,
665    ) -> Result<TimelineEvent> {
666        let request =
667            get_room_event::v3::Request::new(self.room_id().to_owned(), event_id.to_owned());
668
669        let raw_event = self.client.send(request).with_request_config(request_config).await?.event;
670        let push_ctx = self.push_context().await?;
671        let event = self.try_decrypt_event(raw_event, push_ctx.as_ref()).await;
672
673        // Save the event into the event cache, if it's set up.
674        if let Ok((cache, _handles)) = self.event_cache().await {
675            cache.save_events([event.clone()]).await;
676        }
677
678        Ok(event)
679    }
680
681    /// Try to load the event from the [`EventCache`][crate::event_cache], if
682    /// it's enabled, or fetch it from the homeserver.
683    ///
684    /// When running the request against the homeserver, it uses the given
685    /// [`RequestConfig`] if provided, or the client's default one
686    /// otherwise.
687    pub async fn load_or_fetch_event(
688        &self,
689        event_id: &EventId,
690        request_config: Option<RequestConfig>,
691    ) -> Result<TimelineEvent> {
692        match self.event_cache().await {
693            Ok((event_cache, _drop_handles)) => {
694                if let Some(event) = event_cache.find_event(event_id).await {
695                    return Ok(event);
696                }
697                // Fallthrough: try with a request.
698            }
699            Err(err) => {
700                debug!("error when getting the event cache: {err}");
701            }
702        }
703        self.event(event_id, request_config).await
704    }
705
706    /// Fetch the event with the given `EventId` in this room, using the
707    /// `/context` endpoint to get more information.
708    pub async fn event_with_context(
709        &self,
710        event_id: &EventId,
711        lazy_load_members: bool,
712        context_size: UInt,
713        request_config: Option<RequestConfig>,
714    ) -> Result<EventWithContextResponse> {
715        let mut request =
716            context::get_context::v3::Request::new(self.room_id().to_owned(), event_id.to_owned());
717
718        request.limit = context_size;
719
720        if lazy_load_members {
721            request.filter.lazy_load_options =
722                LazyLoadOptions::Enabled { include_redundant_members: false };
723        }
724
725        let response = self.client.send(request).with_request_config(request_config).await?;
726
727        let push_ctx = self.push_context().await?;
728        let push_ctx = push_ctx.as_ref();
729        let target_event = if let Some(event) = response.event {
730            Some(self.try_decrypt_event(event, push_ctx).await)
731        } else {
732            None
733        };
734
735        // Note: the joined future will fail if any future failed, but
736        // [`Self::try_decrypt_event`] doesn't hard-fail when there's a
737        // decryption error, so we should prevent against most bad cases here.
738        let (events_before, events_after) = join!(
739            join_all(
740                response.events_before.into_iter().map(|ev| self.try_decrypt_event(ev, push_ctx)),
741            ),
742            join_all(
743                response.events_after.into_iter().map(|ev| self.try_decrypt_event(ev, push_ctx)),
744            ),
745        );
746
747        // Save the loaded events into the event cache, if it's set up.
748        if let Ok((cache, _handles)) = self.event_cache().await {
749            let mut events_to_save: Vec<TimelineEvent> = Vec::new();
750            if let Some(event) = &target_event {
751                events_to_save.push(event.clone());
752            }
753
754            for event in &events_before {
755                events_to_save.push(event.clone());
756            }
757
758            for event in &events_after {
759                events_to_save.push(event.clone());
760            }
761
762            cache.save_events(events_to_save).await;
763        }
764
765        Ok(EventWithContextResponse {
766            event: target_event,
767            events_before,
768            events_after,
769            state: response.state,
770            prev_batch_token: response.start,
771            next_batch_token: response.end,
772        })
773    }
774
775    pub(crate) async fn request_members(&self) -> Result<()> {
776        self.client
777            .locks()
778            .members_request_deduplicated_handler
779            .run(self.room_id().to_owned(), async move {
780                let request = get_member_events::v3::Request::new(self.inner.room_id().to_owned());
781                let response = self
782                    .client
783                    .send(request.clone())
784                    .with_request_config(
785                        // In some cases it can take longer than 30s to load:
786                        // https://github.com/element-hq/synapse/issues/16872
787                        RequestConfig::new().timeout(Duration::from_secs(60)).retry_limit(3),
788                    )
789                    .await?;
790
791                // That's a large `Future`. Let's `Box::pin` to reduce its size on the stack.
792                Box::pin(self.client.base_client().receive_all_members(
793                    self.room_id(),
794                    &request,
795                    &response,
796                ))
797                .await?;
798
799                Ok(())
800            })
801            .await
802    }
803
804    /// Request to update the encryption state for this room.
805    ///
806    /// It does nothing if the encryption state is already
807    /// [`EncryptionState::Encrypted`] or [`EncryptionState::NotEncrypted`].
808    pub async fn request_encryption_state(&self) -> Result<()> {
809        if !self.inner.encryption_state().is_unknown() {
810            return Ok(());
811        }
812
813        self.client
814            .locks()
815            .encryption_state_deduplicated_handler
816            .run(self.room_id().to_owned(), async move {
817                // Request the event from the server.
818                let request = get_state_event_for_key::v3::Request::new(
819                    self.room_id().to_owned(),
820                    StateEventType::RoomEncryption,
821                    "".to_owned(),
822                );
823                let response = match self.client.send(request).await {
824                    Ok(response) => Some(
825                        response
826                            .into_content()
827                            .deserialize_as_unchecked::<RoomEncryptionEventContent>()?,
828                    ),
829                    Err(err) if err.client_api_error_kind() == Some(&ErrorKind::NotFound) => None,
830                    Err(err) => return Err(err.into()),
831                };
832
833                let _sync_lock = self.client.base_client().sync_lock().lock().await;
834
835                // Persist the event and the fact that we requested it from the server in
836                // `RoomInfo`.
837                let mut room_info = self.clone_info();
838                room_info.mark_encryption_state_synced();
839                room_info.set_encryption_event(response.clone());
840                let mut changes = StateChanges::default();
841                changes.add_room(room_info.clone());
842
843                self.client.state_store().save_changes(&changes).await?;
844                self.set_room_info(room_info, RoomInfoNotableUpdateReasons::empty());
845
846                Ok(())
847            })
848            .await
849    }
850
851    /// Check the encryption state of this room.
852    ///
853    /// If the result is [`EncryptionState::Unknown`], one might want to call
854    /// [`Room::request_encryption_state`].
855    pub fn encryption_state(&self) -> EncryptionState {
856        self.inner.encryption_state()
857    }
858
859    /// Force to update the encryption state by calling
860    /// [`Room::request_encryption_state`], and then calling
861    /// [`Room::encryption_state`].
862    ///
863    /// This method is useful to ensure the encryption state is up-to-date.
864    pub async fn latest_encryption_state(&self) -> Result<EncryptionState> {
865        self.request_encryption_state().await?;
866
867        Ok(self.encryption_state())
868    }
869
870    /// Gets additional context info about the client crypto.
871    #[cfg(feature = "e2e-encryption")]
872    pub async fn crypto_context_info(&self) -> CryptoContextInfo {
873        let encryption = self.client.encryption();
874
875        let this_device_is_verified = match encryption.get_own_device().await {
876            Ok(Some(device)) => device.is_verified_with_cross_signing(),
877
878            // Should not happen, there will always be an own device
879            _ => true,
880        };
881
882        let backup_exists_on_server =
883            encryption.backups().exists_on_server().await.unwrap_or(false);
884
885        CryptoContextInfo {
886            device_creation_ts: encryption.device_creation_timestamp().await,
887            this_device_is_verified,
888            is_backup_configured: encryption.backups().state() == BackupState::Enabled,
889            backup_exists_on_server,
890        }
891    }
892
893    fn are_events_visible(&self) -> bool {
894        if let RoomState::Invited = self.inner.state() {
895            return matches!(
896                self.inner.history_visibility_or_default(),
897                HistoryVisibility::WorldReadable | HistoryVisibility::Invited
898            );
899        }
900
901        true
902    }
903
904    /// Sync the member list with the server.
905    ///
906    /// This method will de-duplicate requests if it is called multiple times in
907    /// quick succession, in that case the return value will be `None`. This
908    /// method does nothing if the members are already synced.
909    pub async fn sync_members(&self) -> Result<()> {
910        if !self.are_events_visible() {
911            return Ok(());
912        }
913
914        if !self.are_members_synced() {
915            self.request_members().await
916        } else {
917            Ok(())
918        }
919    }
920
921    /// Get a specific member of this room.
922    ///
923    /// *Note*: This method will fetch the members from the homeserver if the
924    /// member list isn't synchronized due to member lazy loading. Because of
925    /// that it might panic if it isn't run on a tokio thread.
926    ///
927    /// Use [get_member_no_sync()](#method.get_member_no_sync) if you want a
928    /// method that doesn't do any requests.
929    ///
930    /// # Arguments
931    ///
932    /// * `user_id` - The ID of the user that should be fetched out of the
933    ///   store.
934    pub async fn get_member(&self, user_id: &UserId) -> Result<Option<RoomMember>> {
935        self.sync_members().await?;
936        self.get_member_no_sync(user_id).await
937    }
938
939    /// Get a specific member of this room.
940    ///
941    /// *Note*: This method will not fetch the members from the homeserver if
942    /// the member list isn't synchronized due to member lazy loading. Thus,
943    /// members could be missing.
944    ///
945    /// Use [get_member()](#method.get_member) if you want to ensure to always
946    /// have the full member list to chose from.
947    ///
948    /// # Arguments
949    ///
950    /// * `user_id` - The ID of the user that should be fetched out of the
951    ///   store.
952    pub async fn get_member_no_sync(&self, user_id: &UserId) -> Result<Option<RoomMember>> {
953        Ok(self
954            .inner
955            .get_member(user_id)
956            .await?
957            .map(|member| RoomMember::new(self.client.clone(), member)))
958    }
959
960    /// Get members for this room, with the given memberships.
961    ///
962    /// *Note*: This method will fetch the members from the homeserver if the
963    /// member list isn't synchronized due to member lazy loading. Because of
964    /// that it might panic if it isn't run on a tokio thread.
965    ///
966    /// Use [members_no_sync()](#method.members_no_sync) if you want a
967    /// method that doesn't do any requests.
968    pub async fn members(&self, memberships: RoomMemberships) -> Result<Vec<RoomMember>> {
969        self.sync_members().await?;
970        self.members_no_sync(memberships).await
971    }
972
973    /// Get members for this room, with the given memberships.
974    ///
975    /// *Note*: This method will not fetch the members from the homeserver if
976    /// the member list isn't synchronized due to member lazy loading. Thus,
977    /// members could be missing.
978    ///
979    /// Use [members()](#method.members) if you want to ensure to always get
980    /// the full member list.
981    pub async fn members_no_sync(&self, memberships: RoomMemberships) -> Result<Vec<RoomMember>> {
982        Ok(self
983            .inner
984            .members(memberships)
985            .await?
986            .into_iter()
987            .map(|member| RoomMember::new(self.client.clone(), member))
988            .collect())
989    }
990
991    /// Get all state events of a given type in this room.
992    pub async fn get_state_events(
993        &self,
994        event_type: StateEventType,
995    ) -> Result<Vec<RawAnySyncOrStrippedState>> {
996        self.client
997            .state_store()
998            .get_state_events(self.room_id(), event_type)
999            .await
1000            .map_err(Into::into)
1001    }
1002
1003    /// Get all state events of a given statically-known type in this room.
1004    ///
1005    /// # Examples
1006    ///
1007    /// ```no_run
1008    /// # async {
1009    /// # let room: matrix_sdk::Room = todo!();
1010    /// use matrix_sdk::ruma::{
1011    ///     events::room::member::RoomMemberEventContent, serde::Raw,
1012    /// };
1013    ///
1014    /// let room_members =
1015    ///     room.get_state_events_static::<RoomMemberEventContent>().await?;
1016    /// # anyhow::Ok(())
1017    /// # };
1018    /// ```
1019    pub async fn get_state_events_static<C>(&self) -> Result<Vec<RawSyncOrStrippedState<C>>>
1020    where
1021        C: StaticEventContent<IsPrefix = ruma::events::False>
1022            + StaticStateEventContent
1023            + RedactContent,
1024        C::Redacted: RedactedStateEventContent,
1025    {
1026        Ok(self.client.state_store().get_state_events_static(self.room_id()).await?)
1027    }
1028
1029    /// Get the state events of a given type with the given state keys in this
1030    /// room.
1031    pub async fn get_state_events_for_keys(
1032        &self,
1033        event_type: StateEventType,
1034        state_keys: &[&str],
1035    ) -> Result<Vec<RawAnySyncOrStrippedState>> {
1036        self.client
1037            .state_store()
1038            .get_state_events_for_keys(self.room_id(), event_type, state_keys)
1039            .await
1040            .map_err(Into::into)
1041    }
1042
1043    /// Get the state events of a given statically-known type with the given
1044    /// state keys in this room.
1045    ///
1046    /// # Examples
1047    ///
1048    /// ```no_run
1049    /// # async {
1050    /// # let room: matrix_sdk::Room = todo!();
1051    /// # let user_ids: &[matrix_sdk::ruma::OwnedUserId] = &[];
1052    /// use matrix_sdk::ruma::events::room::member::RoomMemberEventContent;
1053    ///
1054    /// let room_members = room
1055    ///     .get_state_events_for_keys_static::<RoomMemberEventContent, _, _>(
1056    ///         user_ids,
1057    ///     )
1058    ///     .await?;
1059    /// # anyhow::Ok(())
1060    /// # };
1061    /// ```
1062    pub async fn get_state_events_for_keys_static<'a, C, K, I>(
1063        &self,
1064        state_keys: I,
1065    ) -> Result<Vec<RawSyncOrStrippedState<C>>>
1066    where
1067        C: StaticEventContent<IsPrefix = ruma::events::False>
1068            + StaticStateEventContent
1069            + RedactContent,
1070        C::StateKey: Borrow<K>,
1071        C::Redacted: RedactedStateEventContent,
1072        K: AsRef<str> + Sized + Sync + 'a,
1073        I: IntoIterator<Item = &'a K> + Send,
1074        I::IntoIter: Send,
1075    {
1076        Ok(self
1077            .client
1078            .state_store()
1079            .get_state_events_for_keys_static(self.room_id(), state_keys)
1080            .await?)
1081    }
1082
1083    /// Get a specific state event in this room.
1084    pub async fn get_state_event(
1085        &self,
1086        event_type: StateEventType,
1087        state_key: &str,
1088    ) -> Result<Option<RawAnySyncOrStrippedState>> {
1089        self.client
1090            .state_store()
1091            .get_state_event(self.room_id(), event_type, state_key)
1092            .await
1093            .map_err(Into::into)
1094    }
1095
1096    /// Get a specific state event of statically-known type with an empty state
1097    /// key in this room.
1098    ///
1099    /// # Examples
1100    ///
1101    /// ```no_run
1102    /// # async {
1103    /// # let room: matrix_sdk::Room = todo!();
1104    /// use matrix_sdk::ruma::events::room::power_levels::RoomPowerLevelsEventContent;
1105    ///
1106    /// let power_levels = room
1107    ///     .get_state_event_static::<RoomPowerLevelsEventContent>()
1108    ///     .await?
1109    ///     .expect("every room has a power_levels event")
1110    ///     .deserialize()?;
1111    /// # anyhow::Ok(())
1112    /// # };
1113    /// ```
1114    pub async fn get_state_event_static<C>(&self) -> Result<Option<RawSyncOrStrippedState<C>>>
1115    where
1116        C: StaticEventContent<IsPrefix = ruma::events::False>
1117            + StaticStateEventContent<StateKey = EmptyStateKey>
1118            + RedactContent,
1119        C::Redacted: RedactedStateEventContent,
1120    {
1121        self.get_state_event_static_for_key(&EmptyStateKey).await
1122    }
1123
1124    /// Get a specific state event of statically-known type in this room.
1125    ///
1126    /// # Examples
1127    ///
1128    /// ```no_run
1129    /// # async {
1130    /// # let room: matrix_sdk::Room = todo!();
1131    /// use matrix_sdk::ruma::{
1132    ///     events::room::member::RoomMemberEventContent, serde::Raw, user_id,
1133    /// };
1134    ///
1135    /// let member_event = room
1136    ///     .get_state_event_static_for_key::<RoomMemberEventContent, _>(user_id!(
1137    ///         "@alice:example.org"
1138    ///     ))
1139    ///     .await?;
1140    /// # anyhow::Ok(())
1141    /// # };
1142    /// ```
1143    pub async fn get_state_event_static_for_key<C, K>(
1144        &self,
1145        state_key: &K,
1146    ) -> Result<Option<RawSyncOrStrippedState<C>>>
1147    where
1148        C: StaticEventContent<IsPrefix = ruma::events::False>
1149            + StaticStateEventContent
1150            + RedactContent,
1151        C::StateKey: Borrow<K>,
1152        C::Redacted: RedactedStateEventContent,
1153        K: AsRef<str> + ?Sized + Sync,
1154    {
1155        Ok(self
1156            .client
1157            .state_store()
1158            .get_state_event_static_for_key(self.room_id(), state_key)
1159            .await?)
1160    }
1161
1162    /// Returns the parents this room advertises as its parents.
1163    ///
1164    /// Results are in no particular order.
1165    pub async fn parent_spaces(&self) -> Result<impl Stream<Item = Result<ParentSpace>> + '_> {
1166        // Implements this algorithm:
1167        // https://spec.matrix.org/v1.8/client-server-api/#mspaceparent-relationships
1168
1169        // Get all m.room.parent events for this room
1170        Ok(self
1171            .get_state_events_static::<SpaceParentEventContent>()
1172            .await?
1173            .into_iter()
1174            // Extract state key (ie. the parent's id) and sender
1175            .flat_map(|parent_event| match parent_event.deserialize() {
1176                Ok(SyncOrStrippedState::Sync(SyncStateEvent::Original(e))) => {
1177                    Some((e.state_key.to_owned(), e.sender))
1178                }
1179                Ok(SyncOrStrippedState::Sync(SyncStateEvent::Redacted(_))) => None,
1180                Ok(SyncOrStrippedState::Stripped(e)) => Some((e.state_key.to_owned(), e.sender)),
1181                Err(e) => {
1182                    info!(room_id = ?self.room_id(), "Could not deserialize m.room.parent: {e}");
1183                    None
1184                }
1185            })
1186            // Check whether the parent recognizes this room as its child
1187            .map(|(state_key, sender): (OwnedRoomId, OwnedUserId)| async move {
1188                let Some(parent_room) = self.client.get_room(&state_key) else {
1189                    // We are not in the room, cannot check if the relationship is reciprocal
1190                    // TODO: try peeking into the room
1191                    return Ok(ParentSpace::Unverifiable(state_key));
1192                };
1193                // Get the m.room.child state of the parent with this room's id
1194                // as state key.
1195                if let Some(child_event) = parent_room
1196                    .get_state_event_static_for_key::<SpaceChildEventContent, _>(self.room_id())
1197                    .await?
1198                {
1199                    match child_event.deserialize() {
1200                        Ok(SyncOrStrippedState::Sync(SyncStateEvent::Original(_))) => {
1201                            // There is a valid m.room.child in the parent pointing to
1202                            // this room
1203                            return Ok(ParentSpace::Reciprocal(parent_room));
1204                        }
1205                        Ok(SyncOrStrippedState::Sync(SyncStateEvent::Redacted(_))) => {}
1206                        Ok(SyncOrStrippedState::Stripped(_)) => {}
1207                        Err(e) => {
1208                            info!(
1209                                room_id = ?self.room_id(), parent_room_id = ?state_key,
1210                                "Could not deserialize m.room.child: {e}"
1211                            );
1212                        }
1213                    }
1214                    // Otherwise the event is either invalid or redacted. If
1215                    // redacted it would be missing the
1216                    // `via` key, thereby invalidating that end of the
1217                    // relationship: https://spec.matrix.org/v1.8/client-server-api/#mspacechild
1218                }
1219
1220                // No reciprocal m.room.child found, let's check if the sender has the
1221                // power to set it
1222                let Some(member) = parent_room.get_member(&sender).await? else {
1223                    // Sender is not even in the parent room
1224                    return Ok(ParentSpace::Illegitimate(parent_room));
1225                };
1226
1227                if member.can_send_state(StateEventType::SpaceChild) {
1228                    // Sender does have the power to set m.room.child
1229                    Ok(ParentSpace::WithPowerlevel(parent_room))
1230                } else {
1231                    Ok(ParentSpace::Illegitimate(parent_room))
1232                }
1233            })
1234            .collect::<FuturesUnordered<_>>())
1235    }
1236
1237    /// Read account data in this room, from storage.
1238    pub async fn account_data(
1239        &self,
1240        data_type: RoomAccountDataEventType,
1241    ) -> Result<Option<Raw<AnyRoomAccountDataEvent>>> {
1242        self.client
1243            .state_store()
1244            .get_room_account_data_event(self.room_id(), data_type)
1245            .await
1246            .map_err(Into::into)
1247    }
1248
1249    /// Get account data of a statically-known type in this room, from storage.
1250    ///
1251    /// # Examples
1252    ///
1253    /// ```no_run
1254    /// # async {
1255    /// # let room: matrix_sdk::Room = todo!();
1256    /// use matrix_sdk::ruma::events::fully_read::FullyReadEventContent;
1257    ///
1258    /// match room.account_data_static::<FullyReadEventContent>().await? {
1259    ///     Some(fully_read) => {
1260    ///         println!("Found read marker: {:?}", fully_read.deserialize()?)
1261    ///     }
1262    ///     None => println!("No read marker for this room"),
1263    /// }
1264    /// # anyhow::Ok(())
1265    /// # };
1266    /// ```
1267    pub async fn account_data_static<C>(&self) -> Result<Option<Raw<RoomAccountDataEvent<C>>>>
1268    where
1269        C: StaticEventContent<IsPrefix = ruma::events::False> + RoomAccountDataEventContent,
1270    {
1271        Ok(self.account_data(C::TYPE.into()).await?.map(Raw::cast_unchecked))
1272    }
1273
1274    /// Check if all members of this room are verified and all their devices are
1275    /// verified.
1276    ///
1277    /// Returns true if all devices in the room are verified, otherwise false.
1278    #[cfg(feature = "e2e-encryption")]
1279    pub async fn contains_only_verified_devices(&self) -> Result<bool> {
1280        let user_ids = self
1281            .client
1282            .state_store()
1283            .get_user_ids(self.room_id(), RoomMemberships::empty())
1284            .await?;
1285
1286        for user_id in user_ids {
1287            let devices = self.client.encryption().get_user_devices(&user_id).await?;
1288            let any_unverified = devices.devices().any(|d| !d.is_verified());
1289
1290            if any_unverified {
1291                return Ok(false);
1292            }
1293        }
1294
1295        Ok(true)
1296    }
1297
1298    /// Set the given account data event for this room.
1299    ///
1300    /// # Example
1301    /// ```
1302    /// # async {
1303    /// # let room: matrix_sdk::Room = todo!();
1304    /// # let event_id: ruma::OwnedEventId = todo!();
1305    /// use matrix_sdk::ruma::events::fully_read::FullyReadEventContent;
1306    /// let content = FullyReadEventContent::new(event_id);
1307    ///
1308    /// room.set_account_data(content).await?;
1309    /// # anyhow::Ok(())
1310    /// # };
1311    /// ```
1312    pub async fn set_account_data<T>(
1313        &self,
1314        content: T,
1315    ) -> Result<set_room_account_data::v3::Response>
1316    where
1317        T: RoomAccountDataEventContent,
1318    {
1319        let own_user = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
1320
1321        let request = set_room_account_data::v3::Request::new(
1322            own_user.to_owned(),
1323            self.room_id().to_owned(),
1324            &content,
1325        )?;
1326
1327        Ok(self.client.send(request).await?)
1328    }
1329
1330    /// Set the given raw account data event in this room.
1331    ///
1332    /// # Example
1333    /// ```
1334    /// # async {
1335    /// # let room: matrix_sdk::Room = todo!();
1336    /// use matrix_sdk::ruma::{
1337    ///     events::{
1338    ///         marked_unread::MarkedUnreadEventContent,
1339    ///         AnyRoomAccountDataEventContent, RoomAccountDataEventContent,
1340    ///     },
1341    ///     serde::Raw,
1342    /// };
1343    /// let marked_unread_content = MarkedUnreadEventContent::new(true);
1344    /// let full_event: AnyRoomAccountDataEventContent =
1345    ///     marked_unread_content.clone().into();
1346    /// room.set_account_data_raw(
1347    ///     marked_unread_content.event_type(),
1348    ///     Raw::new(&full_event).unwrap(),
1349    /// )
1350    /// .await?;
1351    /// # anyhow::Ok(())
1352    /// # };
1353    /// ```
1354    pub async fn set_account_data_raw(
1355        &self,
1356        event_type: RoomAccountDataEventType,
1357        content: Raw<AnyRoomAccountDataEventContent>,
1358    ) -> Result<set_room_account_data::v3::Response> {
1359        let own_user = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
1360
1361        let request = set_room_account_data::v3::Request::new_raw(
1362            own_user.to_owned(),
1363            self.room_id().to_owned(),
1364            event_type,
1365            content,
1366        );
1367
1368        Ok(self.client.send(request).await?)
1369    }
1370
1371    /// Adds a tag to the room, or updates it if it already exists.
1372    ///
1373    /// Returns the [`create_tag::v3::Response`] from the server.
1374    ///
1375    /// # Arguments
1376    /// * `tag` - The tag to add or update.
1377    ///
1378    /// * `tag_info` - Information about the tag, generally containing the
1379    ///   `order` parameter.
1380    ///
1381    /// # Examples
1382    ///
1383    /// ```no_run
1384    /// # use std::str::FromStr;
1385    /// # use ruma::events::tag::{TagInfo, TagName, UserTagName};
1386    /// # async {
1387    /// # let homeserver = url::Url::parse("http://localhost:8080")?;
1388    /// # let mut client = matrix_sdk::Client::new(homeserver).await?;
1389    /// # let room_id = matrix_sdk::ruma::room_id!("!test:localhost");
1390    /// use matrix_sdk::ruma::events::tag::TagInfo;
1391    ///
1392    /// if let Some(room) = client.get_room(&room_id) {
1393    ///     let mut tag_info = TagInfo::new();
1394    ///     tag_info.order = Some(0.9);
1395    ///     let user_tag = UserTagName::from_str("u.work")?;
1396    ///
1397    ///     room.set_tag(TagName::User(user_tag), tag_info).await?;
1398    /// }
1399    /// # anyhow::Ok(()) };
1400    /// ```
1401    pub async fn set_tag(
1402        &self,
1403        tag: TagName,
1404        tag_info: TagInfo,
1405    ) -> Result<create_tag::v3::Response> {
1406        let user_id = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
1407        let request = create_tag::v3::Request::new(
1408            user_id.to_owned(),
1409            self.inner.room_id().to_owned(),
1410            tag.to_string(),
1411            tag_info,
1412        );
1413        Ok(self.client.send(request).await?)
1414    }
1415
1416    /// Removes a tag from the room.
1417    ///
1418    /// Returns the [`delete_tag::v3::Response`] from the server.
1419    ///
1420    /// # Arguments
1421    /// * `tag` - The tag to remove.
1422    pub async fn remove_tag(&self, tag: TagName) -> Result<delete_tag::v3::Response> {
1423        let user_id = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
1424        let request = delete_tag::v3::Request::new(
1425            user_id.to_owned(),
1426            self.inner.room_id().to_owned(),
1427            tag.to_string(),
1428        );
1429        Ok(self.client.send(request).await?)
1430    }
1431
1432    /// Add or remove the `m.favourite` flag for this room.
1433    ///
1434    /// If `is_favourite` is `true`, and the `m.low_priority` tag is set on the
1435    /// room, the tag will be removed too.
1436    ///
1437    /// # Arguments
1438    ///
1439    /// * `is_favourite` - Whether to mark this room as favourite.
1440    /// * `tag_order` - The order of the tag if any.
1441    pub async fn set_is_favourite(&self, is_favourite: bool, tag_order: Option<f64>) -> Result<()> {
1442        if is_favourite {
1443            let tag_info = assign!(TagInfo::new(), { order: tag_order });
1444
1445            self.set_tag(TagName::Favorite, tag_info).await?;
1446
1447            if self.is_low_priority() {
1448                self.remove_tag(TagName::LowPriority).await?;
1449            }
1450        } else {
1451            self.remove_tag(TagName::Favorite).await?;
1452        }
1453        Ok(())
1454    }
1455
1456    /// Add or remove the `m.lowpriority` flag for this room.
1457    ///
1458    /// If `is_low_priority` is `true`, and the `m.favourite` tag is set on the
1459    /// room, the tag will be removed too.
1460    ///
1461    /// # Arguments
1462    ///
1463    /// * `is_low_priority` - Whether to mark this room as low_priority or not.
1464    /// * `tag_order` - The order of the tag if any.
1465    pub async fn set_is_low_priority(
1466        &self,
1467        is_low_priority: bool,
1468        tag_order: Option<f64>,
1469    ) -> Result<()> {
1470        if is_low_priority {
1471            let tag_info = assign!(TagInfo::new(), { order: tag_order });
1472
1473            self.set_tag(TagName::LowPriority, tag_info).await?;
1474
1475            if self.is_favourite() {
1476                self.remove_tag(TagName::Favorite).await?;
1477            }
1478        } else {
1479            self.remove_tag(TagName::LowPriority).await?;
1480        }
1481        Ok(())
1482    }
1483
1484    /// Sets whether this room is a DM.
1485    ///
1486    /// When setting this room as DM, it will be marked as DM for all active
1487    /// members of the room. When unsetting this room as DM, it will be
1488    /// unmarked as DM for all users, not just the members.
1489    ///
1490    /// # Arguments
1491    /// * `is_direct` - Whether to mark this room as direct.
1492    pub async fn set_is_direct(&self, is_direct: bool) -> Result<()> {
1493        let user_id = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
1494
1495        let mut content = self
1496            .client
1497            .account()
1498            .account_data::<DirectEventContent>()
1499            .await?
1500            .map(|c| c.deserialize())
1501            .transpose()?
1502            .unwrap_or_default();
1503
1504        let this_room_id = self.inner.room_id();
1505
1506        if is_direct {
1507            let mut room_members = self.members(RoomMemberships::ACTIVE).await?;
1508            room_members.retain(|member| member.user_id() != self.own_user_id());
1509
1510            for member in room_members {
1511                let entry = content.entry(member.user_id().into()).or_default();
1512                if !entry.iter().any(|room_id| room_id == this_room_id) {
1513                    entry.push(this_room_id.to_owned());
1514                }
1515            }
1516        } else {
1517            for (_, list) in content.iter_mut() {
1518                list.retain(|room_id| *room_id != this_room_id);
1519            }
1520
1521            // Remove user ids that don't have any room marked as DM
1522            content.retain(|_, list| !list.is_empty());
1523        }
1524
1525        let request = set_global_account_data::v3::Request::new(user_id.to_owned(), &content)?;
1526
1527        self.client.send(request).await?;
1528        Ok(())
1529    }
1530
1531    /// Tries to decrypt a room event.
1532    ///
1533    /// # Arguments
1534    /// * `event` - The room event to be decrypted.
1535    ///
1536    /// Returns the decrypted event. In the case of a decryption error, returns
1537    /// a `TimelineEvent` representing the decryption error.
1538    #[cfg(feature = "e2e-encryption")]
1539    pub async fn decrypt_event(
1540        &self,
1541        event: &Raw<OriginalSyncRoomEncryptedEvent>,
1542        push_ctx: Option<&PushContext>,
1543    ) -> Result<TimelineEvent> {
1544        let machine = self.client.olm_machine().await;
1545        let machine = machine.as_ref().ok_or(Error::NoOlmMachine)?;
1546
1547        match machine
1548            .try_decrypt_room_event(
1549                event.cast_ref(),
1550                self.inner.room_id(),
1551                self.client.decryption_settings(),
1552            )
1553            .await?
1554        {
1555            RoomEventDecryptionResult::Decrypted(decrypted) => {
1556                let push_actions = if let Some(push_ctx) = push_ctx {
1557                    Some(push_ctx.for_event(&decrypted.event).await)
1558                } else {
1559                    None
1560                };
1561                Ok(TimelineEvent::from_decrypted(decrypted, push_actions))
1562            }
1563            RoomEventDecryptionResult::UnableToDecrypt(utd_info) => {
1564                self.client
1565                    .encryption()
1566                    .backups()
1567                    .maybe_download_room_key(self.room_id().to_owned(), event.clone());
1568                Ok(TimelineEvent::from_utd(event.clone().cast(), utd_info))
1569            }
1570        }
1571    }
1572
1573    /// Fetches the [`EncryptionInfo`] for an event decrypted with the supplied
1574    /// session_id.
1575    ///
1576    /// This may be used when we receive an update for a session, and we want to
1577    /// reflect the changes in messages we have received that were encrypted
1578    /// with that session, e.g. to remove a warning shield because a device is
1579    /// now verified.
1580    ///
1581    /// # Arguments
1582    /// * `session_id` - The ID of the Megolm session to get information for.
1583    /// * `sender` - The (claimed) sender of the event where the session was
1584    ///   used.
1585    #[cfg(feature = "e2e-encryption")]
1586    pub async fn get_encryption_info(
1587        &self,
1588        session_id: &str,
1589        sender: &UserId,
1590    ) -> Option<Arc<EncryptionInfo>> {
1591        let machine = self.client.olm_machine().await;
1592        let machine = machine.as_ref()?;
1593        machine.get_session_encryption_info(self.room_id(), session_id, sender).await.ok()
1594    }
1595
1596    /// Forces the currently active room key, which is used to encrypt messages,
1597    /// to be rotated.
1598    ///
1599    /// A new room key will be crated and shared with all the room members the
1600    /// next time a message will be sent. You don't have to call this method,
1601    /// room keys will be rotated automatically when necessary. This method is
1602    /// still useful for debugging purposes.
1603    ///
1604    /// For more info please take a look a the [`encryption`] module
1605    /// documentation.
1606    ///
1607    /// [`encryption`]: crate::encryption
1608    #[cfg(feature = "e2e-encryption")]
1609    pub async fn discard_room_key(&self) -> Result<()> {
1610        let machine = self.client.olm_machine().await;
1611        if let Some(machine) = machine.as_ref() {
1612            machine.discard_room_key(self.inner.room_id()).await?;
1613            Ok(())
1614        } else {
1615            Err(Error::NoOlmMachine)
1616        }
1617    }
1618
1619    /// Ban the user with `UserId` from this room.
1620    ///
1621    /// # Arguments
1622    ///
1623    /// * `user_id` - The user to ban with `UserId`.
1624    ///
1625    /// * `reason` - The reason for banning this user.
1626    #[instrument(skip_all)]
1627    pub async fn ban_user(&self, user_id: &UserId, reason: Option<&str>) -> Result<()> {
1628        let request = assign!(
1629            ban_user::v3::Request::new(self.room_id().to_owned(), user_id.to_owned()),
1630            { reason: reason.map(ToOwned::to_owned) }
1631        );
1632        self.client.send(request).await?;
1633        Ok(())
1634    }
1635
1636    /// Unban the user with `UserId` from this room.
1637    ///
1638    /// # Arguments
1639    ///
1640    /// * `user_id` - The user to unban with `UserId`.
1641    ///
1642    /// * `reason` - The reason for unbanning this user.
1643    #[instrument(skip_all)]
1644    pub async fn unban_user(&self, user_id: &UserId, reason: Option<&str>) -> Result<()> {
1645        let request = assign!(
1646            unban_user::v3::Request::new(self.room_id().to_owned(), user_id.to_owned()),
1647            { reason: reason.map(ToOwned::to_owned) }
1648        );
1649        self.client.send(request).await?;
1650        Ok(())
1651    }
1652
1653    /// Kick a user out of this room.
1654    ///
1655    /// # Arguments
1656    ///
1657    /// * `user_id` - The `UserId` of the user that should be kicked out of the
1658    ///   room.
1659    ///
1660    /// * `reason` - Optional reason why the room member is being kicked out.
1661    #[instrument(skip_all)]
1662    pub async fn kick_user(&self, user_id: &UserId, reason: Option<&str>) -> Result<()> {
1663        let request = assign!(
1664            kick_user::v3::Request::new(self.room_id().to_owned(), user_id.to_owned()),
1665            { reason: reason.map(ToOwned::to_owned) }
1666        );
1667        self.client.send(request).await?;
1668        Ok(())
1669    }
1670
1671    /// Invite the specified user by `UserId` to this room.
1672    ///
1673    /// # Arguments
1674    ///
1675    /// * `user_id` - The `UserId` of the user to invite to the room.
1676    #[instrument(skip_all)]
1677    pub async fn invite_user_by_id(&self, user_id: &UserId) -> Result<()> {
1678        #[cfg(feature = "e2e-encryption")]
1679        if self.client.inner.enable_share_history_on_invite {
1680            shared_room_history::share_room_history(self, user_id.to_owned()).await?;
1681        }
1682
1683        let recipient = InvitationRecipient::UserId { user_id: user_id.to_owned() };
1684        let request = invite_user::v3::Request::new(self.room_id().to_owned(), recipient);
1685        self.client.send(request).await?;
1686
1687        // Force a future room members reload before sending any event to prevent UTDs
1688        // that can happen when some event is sent after a room member has been invited
1689        // but before the /sync request could fetch the membership change event.
1690        self.mark_members_missing();
1691
1692        Ok(())
1693    }
1694
1695    /// Invite the specified user by third party id to this room.
1696    ///
1697    /// # Arguments
1698    ///
1699    /// * `invite_id` - A third party id of a user to invite to the room.
1700    #[instrument(skip_all)]
1701    pub async fn invite_user_by_3pid(&self, invite_id: Invite3pid) -> Result<()> {
1702        let recipient = InvitationRecipient::ThirdPartyId(invite_id);
1703        let request = invite_user::v3::Request::new(self.room_id().to_owned(), recipient);
1704        self.client.send(request).await?;
1705
1706        // Force a future room members reload before sending any event to prevent UTDs
1707        // that can happen when some event is sent after a room member has been invited
1708        // but before the /sync request could fetch the membership change event.
1709        self.mark_members_missing();
1710
1711        Ok(())
1712    }
1713
1714    /// Activate typing notice for this room.
1715    ///
1716    /// The typing notice remains active for 4s. It can be deactivate at any
1717    /// point by setting typing to `false`. If this method is called while
1718    /// the typing notice is active nothing will happen. This method can be
1719    /// called on every key stroke, since it will do nothing while typing is
1720    /// active.
1721    ///
1722    /// # Arguments
1723    ///
1724    /// * `typing` - Whether the user is typing or has stopped typing.
1725    ///
1726    /// # Examples
1727    ///
1728    /// ```no_run
1729    /// use std::time::Duration;
1730    ///
1731    /// use matrix_sdk::ruma::api::client::typing::create_typing_event::v3::Typing;
1732    /// # use matrix_sdk::{
1733    /// #     Client, config::SyncSettings,
1734    /// #     ruma::room_id,
1735    /// # };
1736    /// # use url::Url;
1737    ///
1738    /// # async {
1739    /// # let homeserver = Url::parse("http://localhost:8080")?;
1740    /// # let client = Client::new(homeserver).await?;
1741    /// let room_id = room_id!("!SVkFJHzfwvuaIEawgC:localhost");
1742    ///
1743    /// if let Some(room) = client.get_room(&room_id) {
1744    ///     room.typing_notice(true).await?
1745    /// }
1746    /// # anyhow::Ok(()) };
1747    /// ```
1748    pub async fn typing_notice(&self, typing: bool) -> Result<()> {
1749        self.ensure_room_joined()?;
1750
1751        // Only send a request to the homeserver if the old timeout has elapsed
1752        // or the typing notice changed state within the `TYPING_NOTICE_TIMEOUT`
1753        let send = if let Some(typing_time) =
1754            self.client.inner.typing_notice_times.read().unwrap().get(self.room_id())
1755        {
1756            if typing_time.elapsed() > TYPING_NOTICE_RESEND_TIMEOUT {
1757                // We always reactivate the typing notice if typing is true or
1758                // we may need to deactivate it if it's
1759                // currently active if typing is false
1760                typing || typing_time.elapsed() <= TYPING_NOTICE_TIMEOUT
1761            } else {
1762                // Only send a request when we need to deactivate typing
1763                !typing
1764            }
1765        } else {
1766            // Typing notice is currently deactivated, therefore, send a request
1767            // only when it's about to be activated
1768            typing
1769        };
1770
1771        if send {
1772            self.send_typing_notice(typing).await?;
1773        }
1774
1775        Ok(())
1776    }
1777
1778    #[instrument(name = "typing_notice", skip(self))]
1779    async fn send_typing_notice(&self, typing: bool) -> Result<()> {
1780        let typing = if typing {
1781            self.client
1782                .inner
1783                .typing_notice_times
1784                .write()
1785                .unwrap()
1786                .insert(self.room_id().to_owned(), Instant::now());
1787            Typing::Yes(TYPING_NOTICE_TIMEOUT)
1788        } else {
1789            self.client.inner.typing_notice_times.write().unwrap().remove(self.room_id());
1790            Typing::No
1791        };
1792
1793        let request = create_typing_event::v3::Request::new(
1794            self.own_user_id().to_owned(),
1795            self.room_id().to_owned(),
1796            typing,
1797        );
1798
1799        self.client.send(request).await?;
1800
1801        Ok(())
1802    }
1803
1804    /// Send a request to set a single receipt.
1805    ///
1806    /// If an unthreaded receipt is sent, this will also unset the unread flag
1807    /// of the room if necessary.
1808    ///
1809    /// # Arguments
1810    ///
1811    /// * `receipt_type` - The type of the receipt to set. Note that it is
1812    ///   possible to set the fully-read marker although it is technically not a
1813    ///   receipt.
1814    ///
1815    /// * `thread` - The thread where this receipt should apply, if any. Note
1816    ///   that this must be [`ReceiptThread::Unthreaded`] when sending a
1817    ///   [`ReceiptType::FullyRead`][create_receipt::v3::ReceiptType::FullyRead].
1818    ///
1819    /// * `event_id` - The `EventId` of the event to set the receipt on.
1820    #[instrument(skip_all)]
1821    pub async fn send_single_receipt(
1822        &self,
1823        receipt_type: create_receipt::v3::ReceiptType,
1824        thread: ReceiptThread,
1825        event_id: OwnedEventId,
1826    ) -> Result<()> {
1827        // Since the receipt type and the thread aren't Hash/Ord, flatten then as a
1828        // string key.
1829        let request_key = format!("{}|{}", receipt_type, thread.as_str().unwrap_or("<unthreaded>"));
1830
1831        self.client
1832            .inner
1833            .locks
1834            .read_receipt_deduplicated_handler
1835            .run((request_key, event_id.clone()), async {
1836                // We will unset the unread flag if we send an unthreaded receipt.
1837                let is_unthreaded = thread == ReceiptThread::Unthreaded;
1838
1839                let mut request = create_receipt::v3::Request::new(
1840                    self.room_id().to_owned(),
1841                    receipt_type,
1842                    event_id,
1843                );
1844                request.thread = thread;
1845
1846                self.client.send(request).await?;
1847
1848                if is_unthreaded {
1849                    self.set_unread_flag(false).await?;
1850                }
1851
1852                Ok(())
1853            })
1854            .await
1855    }
1856
1857    /// Send a request to set multiple receipts at once.
1858    ///
1859    /// This will also unset the unread flag of the room if necessary.
1860    ///
1861    /// # Arguments
1862    ///
1863    /// * `receipts` - The `Receipts` to send.
1864    ///
1865    /// If `receipts` is empty, this is a no-op.
1866    #[instrument(skip_all)]
1867    pub async fn send_multiple_receipts(&self, receipts: Receipts) -> Result<()> {
1868        if receipts.is_empty() {
1869            return Ok(());
1870        }
1871
1872        let Receipts { fully_read, public_read_receipt, private_read_receipt } = receipts;
1873        let request = assign!(set_read_marker::v3::Request::new(self.room_id().to_owned()), {
1874            fully_read,
1875            read_receipt: public_read_receipt,
1876            private_read_receipt,
1877        });
1878
1879        self.client.send(request).await?;
1880
1881        self.set_unread_flag(false).await?;
1882
1883        Ok(())
1884    }
1885
1886    /// Enable End-to-end encryption in this room.
1887    ///
1888    /// This method will be a noop if encryption is already enabled, otherwise
1889    /// sends a `m.room.encryption` state event to the room. This might fail if
1890    /// you don't have the appropriate power level to enable end-to-end
1891    /// encryption.
1892    ///
1893    /// A sync needs to be received to update the local room state. This method
1894    /// will wait for a sync to be received, this might time out if no
1895    /// sync loop is running or if the server is slow.
1896    ///
1897    /// # Examples
1898    ///
1899    /// ```no_run
1900    /// # use matrix_sdk::{
1901    /// #     Client, config::SyncSettings,
1902    /// #     ruma::room_id,
1903    /// # };
1904    /// # use url::Url;
1905    /// #
1906    /// # async {
1907    /// # let homeserver = Url::parse("http://localhost:8080")?;
1908    /// # let client = Client::new(homeserver).await?;
1909    /// # let room_id = room_id!("!test:localhost");
1910    /// let room_id = room_id!("!SVkFJHzfwvuaIEawgC:localhost");
1911    ///
1912    /// if let Some(room) = client.get_room(&room_id) {
1913    ///     room.enable_encryption().await?
1914    /// }
1915    /// # anyhow::Ok(()) };
1916    /// ```
1917    #[instrument(skip_all)]
1918    pub async fn enable_encryption(&self) -> Result<()> {
1919        use ruma::{
1920            events::room::encryption::RoomEncryptionEventContent, EventEncryptionAlgorithm,
1921        };
1922        const SYNC_WAIT_TIME: Duration = Duration::from_secs(3);
1923
1924        if !self.latest_encryption_state().await?.is_encrypted() {
1925            let content =
1926                RoomEncryptionEventContent::new(EventEncryptionAlgorithm::MegolmV1AesSha2);
1927            self.send_state_event(content).await?;
1928
1929            // TODO do we want to return an error here if we time out? This
1930            // could be quite useful if someone wants to enable encryption and
1931            // send a message right after it's enabled.
1932            _ = timeout(self.client.inner.sync_beat.listen(), SYNC_WAIT_TIME).await;
1933
1934            // If after waiting for a sync, we don't have the encryption state we expect,
1935            // assume the local encryption state is incorrect; this will cause
1936            // the SDK to re-request it later for confirmation, instead of
1937            // assuming it's sync'd and correct (and not encrypted).
1938            let _sync_lock = self.client.base_client().sync_lock().lock().await;
1939            if !self.inner.encryption_state().is_encrypted() {
1940                debug!("still not marked as encrypted, marking encryption state as missing");
1941
1942                let mut room_info = self.clone_info();
1943                room_info.mark_encryption_state_missing();
1944                let mut changes = StateChanges::default();
1945                changes.add_room(room_info.clone());
1946
1947                self.client.state_store().save_changes(&changes).await?;
1948                self.set_room_info(room_info, RoomInfoNotableUpdateReasons::empty());
1949            } else {
1950                debug!("room successfully marked as encrypted");
1951            }
1952        }
1953
1954        Ok(())
1955    }
1956
1957    /// Share a room key with users in the given room.
1958    ///
1959    /// This will create Olm sessions with all the users/device pairs in the
1960    /// room if necessary and share a room key that can be shared with them.
1961    ///
1962    /// Does nothing if no room key needs to be shared.
1963    // TODO: expose this publicly so people can pre-share a group session if
1964    // e.g. a user starts to type a message for a room.
1965    #[cfg(feature = "e2e-encryption")]
1966    #[instrument(skip_all, fields(room_id = ?self.room_id(), store_generation))]
1967    async fn preshare_room_key(&self) -> Result<()> {
1968        self.ensure_room_joined()?;
1969
1970        // Take and release the lock on the store, if needs be.
1971        let guard = self.client.encryption().spin_lock_store(Some(60000)).await?;
1972        tracing::Span::current().record("store_generation", guard.map(|guard| guard.generation()));
1973
1974        self.client
1975            .locks()
1976            .group_session_deduplicated_handler
1977            .run(self.room_id().to_owned(), async move {
1978                {
1979                    let members = self
1980                        .client
1981                        .state_store()
1982                        .get_user_ids(self.room_id(), RoomMemberships::ACTIVE)
1983                        .await?;
1984                    self.client.claim_one_time_keys(members.iter().map(Deref::deref)).await?;
1985                };
1986
1987                let response = self.share_room_key().await;
1988
1989                // If one of the responses failed invalidate the group
1990                // session as using it would end up in undecryptable
1991                // messages.
1992                if let Err(r) = response {
1993                    let machine = self.client.olm_machine().await;
1994                    if let Some(machine) = machine.as_ref() {
1995                        machine.discard_room_key(self.room_id()).await?;
1996                    }
1997                    return Err(r);
1998                }
1999
2000                Ok(())
2001            })
2002            .await
2003    }
2004
2005    /// Share a group session for a room.
2006    ///
2007    /// # Panics
2008    ///
2009    /// Panics if the client isn't logged in.
2010    #[cfg(feature = "e2e-encryption")]
2011    #[instrument(skip_all)]
2012    async fn share_room_key(&self) -> Result<()> {
2013        self.ensure_room_joined()?;
2014
2015        let requests = self.client.base_client().share_room_key(self.room_id()).await?;
2016
2017        for request in requests {
2018            let response = self.client.send_to_device(&request).await?;
2019            self.client.mark_request_as_sent(&request.txn_id, &response).await?;
2020        }
2021
2022        Ok(())
2023    }
2024
2025    /// Wait for the room to be fully synced.
2026    ///
2027    /// This method makes sure the room that was returned when joining a room
2028    /// has been echoed back in the sync.
2029    ///
2030    /// Warning: This waits until a sync happens and does not return if no sync
2031    /// is happening. It can also return early when the room is not a joined
2032    /// room anymore.
2033    #[instrument(skip_all)]
2034    pub async fn sync_up(&self) {
2035        while !self.is_synced() && self.state() == RoomState::Joined {
2036            let wait_for_beat = self.client.inner.sync_beat.listen();
2037            // We don't care whether it's a timeout or a sync beat.
2038            let _ = timeout(wait_for_beat, Duration::from_millis(1000)).await;
2039        }
2040    }
2041
2042    /// Send a message-like event to this room.
2043    ///
2044    /// Returns the parsed response from the server.
2045    ///
2046    /// If the encryption feature is enabled this method will transparently
2047    /// encrypt the event if this room is encrypted (except for `m.reaction`
2048    /// events, which are never encrypted).
2049    ///
2050    /// **Note**: If you just want to send an event with custom JSON content to
2051    /// a room, you can use the [`send_raw()`][Self::send_raw] method for that.
2052    ///
2053    /// If you want to set a transaction ID for the event, use
2054    /// [`.with_transaction_id()`][SendMessageLikeEvent::with_transaction_id]
2055    /// on the returned value before `.await`ing it.
2056    ///
2057    /// # Arguments
2058    ///
2059    /// * `content` - The content of the message event.
2060    ///
2061    /// # Examples
2062    ///
2063    /// ```no_run
2064    /// # use std::sync::{Arc, RwLock};
2065    /// # use matrix_sdk::{Client, config::SyncSettings};
2066    /// # use url::Url;
2067    /// # use matrix_sdk::ruma::room_id;
2068    /// # use serde::{Deserialize, Serialize};
2069    /// use matrix_sdk::ruma::{
2070    ///     events::{
2071    ///         macros::EventContent,
2072    ///         room::message::{RoomMessageEventContent, TextMessageEventContent},
2073    ///     },
2074    ///     uint, MilliSecondsSinceUnixEpoch, TransactionId,
2075    /// };
2076    ///
2077    /// # async {
2078    /// # let homeserver = Url::parse("http://localhost:8080")?;
2079    /// # let mut client = Client::new(homeserver).await?;
2080    /// # let room_id = room_id!("!test:localhost");
2081    /// let content = RoomMessageEventContent::text_plain("Hello world");
2082    /// let txn_id = TransactionId::new();
2083    ///
2084    /// if let Some(room) = client.get_room(&room_id) {
2085    ///     room.send(content).with_transaction_id(txn_id).await?;
2086    /// }
2087    ///
2088    /// // Custom events work too:
2089    /// #[derive(Clone, Debug, Deserialize, Serialize, EventContent)]
2090    /// #[ruma_event(type = "org.shiny_new_2fa.token", kind = MessageLike)]
2091    /// struct TokenEventContent {
2092    ///     token: String,
2093    ///     #[serde(rename = "exp")]
2094    ///     expires_at: MilliSecondsSinceUnixEpoch,
2095    /// }
2096    ///
2097    /// # fn generate_token() -> String { todo!() }
2098    /// let content = TokenEventContent {
2099    ///     token: generate_token(),
2100    ///     expires_at: {
2101    ///         let now = MilliSecondsSinceUnixEpoch::now();
2102    ///         MilliSecondsSinceUnixEpoch(now.0 + uint!(30_000))
2103    ///     },
2104    /// };
2105    ///
2106    /// if let Some(room) = client.get_room(&room_id) {
2107    ///     room.send(content).await?;
2108    /// }
2109    /// # anyhow::Ok(()) };
2110    /// ```
2111    pub fn send(&self, content: impl MessageLikeEventContent) -> SendMessageLikeEvent<'_> {
2112        SendMessageLikeEvent::new(self, content)
2113    }
2114
2115    /// Run /keys/query requests for all the non-tracked users, and for users
2116    /// with an out-of-date device list.
2117    #[cfg(feature = "e2e-encryption")]
2118    async fn query_keys_for_untracked_or_dirty_users(&self) -> Result<()> {
2119        let olm = self.client.olm_machine().await;
2120        let olm = olm.as_ref().expect("Olm machine wasn't started");
2121
2122        let members =
2123            self.client.state_store().get_user_ids(self.room_id(), RoomMemberships::ACTIVE).await?;
2124
2125        let tracked: HashMap<_, _> = olm
2126            .store()
2127            .load_tracked_users()
2128            .await?
2129            .into_iter()
2130            .map(|tracked| (tracked.user_id, tracked.dirty))
2131            .collect();
2132
2133        // A member has no unknown devices iff it was tracked *and* the tracking is
2134        // not considered dirty.
2135        let members_with_unknown_devices =
2136            members.iter().filter(|member| tracked.get(*member).is_none_or(|dirty| *dirty));
2137
2138        let (req_id, request) =
2139            olm.query_keys_for_users(members_with_unknown_devices.map(|owned| owned.borrow()));
2140
2141        if !request.device_keys.is_empty() {
2142            self.client.keys_query(&req_id, request.device_keys).await?;
2143        }
2144
2145        Ok(())
2146    }
2147
2148    /// Send a message-like event with custom JSON content to this room.
2149    ///
2150    /// Returns the parsed response from the server.
2151    ///
2152    /// If the encryption feature is enabled this method will transparently
2153    /// encrypt the event if this room is encrypted (except for `m.reaction`
2154    /// events, which are never encrypted).
2155    ///
2156    /// This method is equivalent to the [`send()`][Self::send] method but
2157    /// allows sending custom JSON payloads, e.g. constructed using the
2158    /// [`serde_json::json!()`] macro.
2159    ///
2160    /// If you want to set a transaction ID for the event, use
2161    /// [`.with_transaction_id()`][SendRawMessageLikeEvent::with_transaction_id]
2162    /// on the returned value before `.await`ing it.
2163    ///
2164    /// # Arguments
2165    ///
2166    /// * `event_type` - The type of the event.
2167    ///
2168    /// * `content` - The content of the event as a raw JSON value. The argument
2169    ///   type can be `serde_json::Value`, but also other raw JSON types; for
2170    ///   the full list check the documentation of
2171    ///   [`IntoRawMessageLikeEventContent`].
2172    ///
2173    /// # Examples
2174    ///
2175    /// ```no_run
2176    /// # use std::sync::{Arc, RwLock};
2177    /// # use matrix_sdk::{Client, config::SyncSettings};
2178    /// # use url::Url;
2179    /// # use matrix_sdk::ruma::room_id;
2180    /// # async {
2181    /// # let homeserver = Url::parse("http://localhost:8080")?;
2182    /// # let mut client = Client::new(homeserver).await?;
2183    /// # let room_id = room_id!("!test:localhost");
2184    /// use serde_json::json;
2185    ///
2186    /// if let Some(room) = client.get_room(&room_id) {
2187    ///     room.send_raw("m.room.message", json!({ "body": "Hello world" })).await?;
2188    /// }
2189    /// # anyhow::Ok(()) };
2190    /// ```
2191    #[instrument(skip_all, fields(event_type, room_id = ?self.room_id(), transaction_id, is_room_encrypted, event_id))]
2192    pub fn send_raw<'a>(
2193        &'a self,
2194        event_type: &'a str,
2195        content: impl IntoRawMessageLikeEventContent,
2196    ) -> SendRawMessageLikeEvent<'a> {
2197        // Note: the recorded instrument fields are saved in
2198        // `SendRawMessageLikeEvent::into_future`.
2199        SendRawMessageLikeEvent::new(self, event_type, content)
2200    }
2201
2202    /// Send an attachment to this room.
2203    ///
2204    /// This will upload the given data that the reader produces using the
2205    /// [`upload()`] method and post an event to the given room.
2206    /// If the room is encrypted and the encryption feature is enabled the
2207    /// upload will be encrypted.
2208    ///
2209    /// This is a convenience method that calls the
2210    /// [`upload()`] and afterwards the [`send()`].
2211    ///
2212    /// # Arguments
2213    /// * `filename` - The file name.
2214    ///
2215    /// * `content_type` - The type of the media, this will be used as the
2216    /// content-type header.
2217    ///
2218    /// * `reader` - A `Reader` that will be used to fetch the raw bytes of the
2219    /// media.
2220    ///
2221    /// * `config` - Metadata and configuration for the attachment.
2222    ///
2223    /// # Examples
2224    ///
2225    /// ```no_run
2226    /// # use std::fs;
2227    /// # use matrix_sdk::{Client, ruma::room_id, attachment::AttachmentConfig};
2228    /// # use url::Url;
2229    /// # use mime;
2230    /// # async {
2231    /// # let homeserver = Url::parse("http://localhost:8080")?;
2232    /// # let mut client = Client::new(homeserver).await?;
2233    /// # let room_id = room_id!("!test:localhost");
2234    /// let mut image = fs::read("/home/example/my-cat.jpg")?;
2235    ///
2236    /// if let Some(room) = client.get_room(&room_id) {
2237    ///     room.send_attachment(
2238    ///         "my_favorite_cat.jpg",
2239    ///         &mime::IMAGE_JPEG,
2240    ///         image,
2241    ///         AttachmentConfig::new(),
2242    ///     ).await?;
2243    /// }
2244    /// # anyhow::Ok(()) };
2245    /// ```
2246    ///
2247    /// [`upload()`]: crate::Media::upload
2248    /// [`send()`]: Self::send
2249    #[instrument(skip_all)]
2250    pub fn send_attachment<'a>(
2251        &'a self,
2252        filename: impl Into<String>,
2253        content_type: &'a Mime,
2254        data: Vec<u8>,
2255        config: AttachmentConfig,
2256    ) -> SendAttachment<'a> {
2257        SendAttachment::new(self, filename.into(), content_type, data, config)
2258    }
2259
2260    /// Prepare and send an attachment to this room.
2261    ///
2262    /// This will upload the given data that the reader produces using the
2263    /// [`upload()`](#method.upload) method and post an event to the given room.
2264    /// If the room is encrypted and the encryption feature is enabled the
2265    /// upload will be encrypted.
2266    ///
2267    /// This is a convenience method that calls the
2268    /// [`Client::upload()`](#Client::method.upload) and afterwards the
2269    /// [`send()`](#method.send).
2270    ///
2271    /// # Arguments
2272    /// * `filename` - The file name.
2273    ///
2274    /// * `content_type` - The type of the media, this will be used as the
2275    ///   content-type header.
2276    ///
2277    /// * `reader` - A `Reader` that will be used to fetch the raw bytes of the
2278    ///   media.
2279    ///
2280    /// * `config` - Metadata and configuration for the attachment.
2281    ///
2282    /// * `send_progress` - An observable to transmit forward progress about the
2283    ///   upload.
2284    ///
2285    /// * `store_in_cache` - A boolean defining whether the uploaded media will
2286    ///   be stored in the cache immediately after a successful upload.
2287    #[instrument(skip_all)]
2288    pub(super) async fn prepare_and_send_attachment<'a>(
2289        &'a self,
2290        filename: String,
2291        content_type: &'a Mime,
2292        data: Vec<u8>,
2293        mut config: AttachmentConfig,
2294        send_progress: SharedObservable<TransmissionProgress>,
2295        store_in_cache: bool,
2296    ) -> Result<send_message_event::v3::Response> {
2297        self.ensure_room_joined()?;
2298
2299        let txn_id = config.txn_id.take();
2300        let mentions = config.mentions.take();
2301
2302        let thumbnail = config.thumbnail.take();
2303
2304        // If necessary, store caching data for the thumbnail ahead of time.
2305        let thumbnail_cache_info = if store_in_cache {
2306            thumbnail
2307                .as_ref()
2308                .map(|thumbnail| (thumbnail.data.clone(), thumbnail.height, thumbnail.width))
2309        } else {
2310            None
2311        };
2312
2313        #[cfg(feature = "e2e-encryption")]
2314        let (media_source, thumbnail) = if self.latest_encryption_state().await?.is_encrypted() {
2315            self.client
2316                .upload_encrypted_media_and_thumbnail(&data, thumbnail, send_progress)
2317                .await?
2318        } else {
2319            self.client
2320                .media()
2321                .upload_plain_media_and_thumbnail(
2322                    content_type,
2323                    // TODO: get rid of this clone; wait for Ruma to use `Bytes` or something
2324                    // similar.
2325                    data.clone(),
2326                    thumbnail,
2327                    send_progress,
2328                )
2329                .await?
2330        };
2331
2332        #[cfg(not(feature = "e2e-encryption"))]
2333        let (media_source, thumbnail) = self
2334            .client
2335            .media()
2336            .upload_plain_media_and_thumbnail(content_type, data.clone(), thumbnail, send_progress)
2337            .await?;
2338
2339        if store_in_cache {
2340            let cache_store_lock_guard = self.client.event_cache_store().lock().await?;
2341
2342            // A failure to cache shouldn't prevent the whole upload from finishing
2343            // properly, so only log errors during caching.
2344
2345            debug!("caching the media");
2346            let request =
2347                MediaRequestParameters { source: media_source.clone(), format: MediaFormat::File };
2348
2349            if let Err(err) = cache_store_lock_guard
2350                .add_media_content(&request, data, IgnoreMediaRetentionPolicy::No)
2351                .await
2352            {
2353                warn!("unable to cache the media after uploading it: {err}");
2354            }
2355
2356            if let Some(((data, height, width), source)) =
2357                thumbnail_cache_info.zip(thumbnail.as_ref().map(|tuple| &tuple.0))
2358            {
2359                debug!("caching the thumbnail");
2360
2361                let request = MediaRequestParameters {
2362                    source: source.clone(),
2363                    format: MediaFormat::Thumbnail(MediaThumbnailSettings::new(width, height)),
2364                };
2365
2366                if let Err(err) = cache_store_lock_guard
2367                    .add_media_content(&request, data, IgnoreMediaRetentionPolicy::No)
2368                    .await
2369                {
2370                    warn!("unable to cache the media after uploading it: {err}");
2371                }
2372            }
2373        }
2374
2375        let content = self
2376            .make_media_event(
2377                Room::make_attachment_type(
2378                    content_type,
2379                    filename,
2380                    media_source,
2381                    config.caption,
2382                    config.formatted_caption,
2383                    config.info,
2384                    thumbnail,
2385                ),
2386                mentions,
2387                config.reply,
2388            )
2389            .await?;
2390
2391        let mut fut = self.send(content);
2392        if let Some(txn_id) = txn_id {
2393            fut = fut.with_transaction_id(txn_id);
2394        }
2395        fut.await
2396    }
2397
2398    /// Creates the inner [`MessageType`] for an already-uploaded media file
2399    /// provided by its source.
2400    #[allow(clippy::too_many_arguments)]
2401    pub(crate) fn make_attachment_type(
2402        content_type: &Mime,
2403        filename: String,
2404        source: MediaSource,
2405        caption: Option<String>,
2406        formatted_caption: Option<FormattedBody>,
2407        info: Option<AttachmentInfo>,
2408        thumbnail: Option<(MediaSource, Box<ThumbnailInfo>)>,
2409    ) -> MessageType {
2410        make_media_type!(
2411            MessageType,
2412            content_type,
2413            filename,
2414            source,
2415            caption,
2416            formatted_caption,
2417            info,
2418            thumbnail
2419        )
2420    }
2421
2422    /// Creates the [`RoomMessageEventContent`] based on the message type,
2423    /// mentions and reply information.
2424    pub(crate) async fn make_media_event(
2425        &self,
2426        msg_type: MessageType,
2427        mentions: Option<Mentions>,
2428        reply: Option<Reply>,
2429    ) -> Result<RoomMessageEventContent> {
2430        let mut content = RoomMessageEventContent::new(msg_type);
2431        if let Some(mentions) = mentions {
2432            content = content.add_mentions(mentions);
2433        }
2434        if let Some(reply) = reply {
2435            // Since we just created the event, there is no relation attached to it. Thus,
2436            // it is safe to add the reply relation without overriding anything.
2437            content = self.make_reply_event(content.into(), reply).await?;
2438        }
2439        Ok(content)
2440    }
2441
2442    /// Creates the inner [`GalleryItemType`] for an already-uploaded media file
2443    /// provided by its source.
2444    #[cfg(feature = "unstable-msc4274")]
2445    #[allow(clippy::too_many_arguments)]
2446    pub(crate) fn make_gallery_item_type(
2447        content_type: &Mime,
2448        filename: String,
2449        source: MediaSource,
2450        caption: Option<String>,
2451        formatted_caption: Option<FormattedBody>,
2452        info: Option<AttachmentInfo>,
2453        thumbnail: Option<(MediaSource, Box<ThumbnailInfo>)>,
2454    ) -> GalleryItemType {
2455        make_media_type!(
2456            GalleryItemType,
2457            content_type,
2458            filename,
2459            source,
2460            caption,
2461            formatted_caption,
2462            info,
2463            thumbnail
2464        )
2465    }
2466
2467    /// Update the power levels of a select set of users of this room.
2468    ///
2469    /// Issue a `power_levels` state event request to the server, changing the
2470    /// given UserId -> Int levels. May fail if the `power_levels` aren't
2471    /// locally known yet or the server rejects the state event update, e.g.
2472    /// because of insufficient permissions. Neither permissions to update
2473    /// nor whether the data might be stale is checked prior to issuing the
2474    /// request.
2475    pub async fn update_power_levels(
2476        &self,
2477        updates: Vec<(&UserId, Int)>,
2478    ) -> Result<send_state_event::v3::Response> {
2479        let mut power_levels = self.power_levels().await?;
2480
2481        for (user_id, new_level) in updates {
2482            if new_level == power_levels.users_default {
2483                power_levels.users.remove(user_id);
2484            } else {
2485                power_levels.users.insert(user_id.to_owned(), new_level);
2486            }
2487        }
2488
2489        self.send_state_event(RoomPowerLevelsEventContent::try_from(power_levels)?).await
2490    }
2491
2492    /// Applies a set of power level changes to this room.
2493    ///
2494    /// Any values that are `None` in the given `RoomPowerLevelChanges` will
2495    /// remain unchanged.
2496    pub async fn apply_power_level_changes(&self, changes: RoomPowerLevelChanges) -> Result<()> {
2497        let mut power_levels = self.power_levels().await?;
2498        power_levels.apply(changes)?;
2499        self.send_state_event(RoomPowerLevelsEventContent::try_from(power_levels)?).await?;
2500        Ok(())
2501    }
2502
2503    /// Resets the room's power levels to the default values
2504    ///
2505    /// [spec]: https://spec.matrix.org/v1.9/client-server-api/#mroompower_levels
2506    pub async fn reset_power_levels(&self) -> Result<RoomPowerLevels> {
2507        let creators = self.creators().unwrap_or_default();
2508        let rules = self.clone_info().room_version_rules_or_default();
2509
2510        let default_power_levels =
2511            RoomPowerLevels::new(RoomPowerLevelsSource::None, &rules.authorization, creators);
2512        let changes = RoomPowerLevelChanges::from(default_power_levels);
2513        self.apply_power_level_changes(changes).await?;
2514        Ok(self.power_levels().await?)
2515    }
2516
2517    /// Gets the suggested role for the user with the provided `user_id`.
2518    ///
2519    /// This method checks the `RoomPowerLevels` events instead of loading the
2520    /// member list and looking for the member.
2521    pub async fn get_suggested_user_role(&self, user_id: &UserId) -> Result<RoomMemberRole> {
2522        let power_level = self.get_user_power_level(user_id).await?;
2523        Ok(RoomMemberRole::suggested_role_for_power_level(power_level))
2524    }
2525
2526    /// Gets the power level the user with the provided `user_id`.
2527    ///
2528    /// This method checks the `RoomPowerLevels` events instead of loading the
2529    /// member list and looking for the member.
2530    pub async fn get_user_power_level(&self, user_id: &UserId) -> Result<UserPowerLevel> {
2531        let event = self.power_levels().await?;
2532        Ok(event.for_user(user_id))
2533    }
2534
2535    /// Gets a map with the `UserId` of users with power levels other than `0`
2536    /// and this power level.
2537    pub async fn users_with_power_levels(&self) -> HashMap<OwnedUserId, i64> {
2538        let power_levels = self.power_levels().await.ok();
2539        let mut user_power_levels = HashMap::<OwnedUserId, i64>::new();
2540        if let Some(power_levels) = power_levels {
2541            for (id, level) in power_levels.users.into_iter() {
2542                user_power_levels.insert(id, level.into());
2543            }
2544        }
2545        user_power_levels
2546    }
2547
2548    /// Sets the name of this room.
2549    pub async fn set_name(&self, name: String) -> Result<send_state_event::v3::Response> {
2550        self.send_state_event(RoomNameEventContent::new(name)).await
2551    }
2552
2553    /// Sets a new topic for this room.
2554    pub async fn set_room_topic(&self, topic: &str) -> Result<send_state_event::v3::Response> {
2555        self.send_state_event(RoomTopicEventContent::new(topic.into())).await
2556    }
2557
2558    /// Sets the new avatar url for this room.
2559    ///
2560    /// # Arguments
2561    /// * `avatar_url` - The owned Matrix uri that represents the avatar
2562    /// * `info` - The optional image info that can be provided for the avatar
2563    pub async fn set_avatar_url(
2564        &self,
2565        url: &MxcUri,
2566        info: Option<avatar::ImageInfo>,
2567    ) -> Result<send_state_event::v3::Response> {
2568        self.ensure_room_joined()?;
2569
2570        let mut room_avatar_event = RoomAvatarEventContent::new();
2571        room_avatar_event.url = Some(url.to_owned());
2572        room_avatar_event.info = info.map(Box::new);
2573
2574        self.send_state_event(room_avatar_event).await
2575    }
2576
2577    /// Removes the avatar from the room
2578    pub async fn remove_avatar(&self) -> Result<send_state_event::v3::Response> {
2579        self.send_state_event(RoomAvatarEventContent::new()).await
2580    }
2581
2582    /// Uploads a new avatar for this room.
2583    ///
2584    /// # Arguments
2585    /// * `mime` - The mime type describing the data
2586    /// * `data` - The data representation of the avatar
2587    /// * `info` - The optional image info provided for the avatar, the blurhash
2588    ///   and the mimetype will always be updated
2589    pub async fn upload_avatar(
2590        &self,
2591        mime: &Mime,
2592        data: Vec<u8>,
2593        info: Option<avatar::ImageInfo>,
2594    ) -> Result<send_state_event::v3::Response> {
2595        self.ensure_room_joined()?;
2596
2597        let upload_response = self.client.media().upload(mime, data, None).await?;
2598        let mut info = info.unwrap_or_default();
2599        info.blurhash = upload_response.blurhash;
2600        info.mimetype = Some(mime.to_string());
2601
2602        self.set_avatar_url(&upload_response.content_uri, Some(info)).await
2603    }
2604
2605    /// Send a state event with an empty state key to the homeserver.
2606    ///
2607    /// For state events with a non-empty state key, see
2608    /// [`send_state_event_for_key`][Self::send_state_event_for_key].
2609    ///
2610    /// Returns the parsed response from the server.
2611    ///
2612    /// # Arguments
2613    ///
2614    /// * `content` - The content of the state event.
2615    ///
2616    /// # Examples
2617    ///
2618    /// ```no_run
2619    /// # use serde::{Deserialize, Serialize};
2620    /// # async {
2621    /// # let joined_room: matrix_sdk::Room = todo!();
2622    /// use matrix_sdk::ruma::{
2623    ///     events::{
2624    ///         macros::EventContent, room::encryption::RoomEncryptionEventContent,
2625    ///         EmptyStateKey,
2626    ///     },
2627    ///     EventEncryptionAlgorithm,
2628    /// };
2629    ///
2630    /// let encryption_event_content = RoomEncryptionEventContent::new(
2631    ///     EventEncryptionAlgorithm::MegolmV1AesSha2,
2632    /// );
2633    /// joined_room.send_state_event(encryption_event_content).await?;
2634    ///
2635    /// // Custom event:
2636    /// #[derive(Clone, Debug, Deserialize, Serialize, EventContent)]
2637    /// #[ruma_event(
2638    ///     type = "org.matrix.msc_9000.xxx",
2639    ///     kind = State,
2640    ///     state_key_type = EmptyStateKey,
2641    /// )]
2642    /// struct XxxStateEventContent {/* fields... */}
2643    ///
2644    /// let content: XxxStateEventContent = todo!();
2645    /// joined_room.send_state_event(content).await?;
2646    /// # anyhow::Ok(()) };
2647    /// ```
2648    #[instrument(skip_all)]
2649    pub async fn send_state_event(
2650        &self,
2651        content: impl StateEventContent<StateKey = EmptyStateKey>,
2652    ) -> Result<send_state_event::v3::Response> {
2653        self.send_state_event_for_key(&EmptyStateKey, content).await
2654    }
2655
2656    /// Send a state event to the homeserver.
2657    ///
2658    /// Returns the parsed response from the server.
2659    ///
2660    /// # Arguments
2661    ///
2662    /// * `content` - The content of the state event.
2663    ///
2664    /// * `state_key` - A unique key which defines the overwriting semantics for
2665    ///   this piece of room state.
2666    ///
2667    /// # Examples
2668    ///
2669    /// ```no_run
2670    /// # use serde::{Deserialize, Serialize};
2671    /// # async {
2672    /// # let joined_room: matrix_sdk::Room = todo!();
2673    /// use matrix_sdk::ruma::{
2674    ///     events::{
2675    ///         macros::EventContent,
2676    ///         room::member::{RoomMemberEventContent, MembershipState},
2677    ///     },
2678    ///     mxc_uri,
2679    /// };
2680    ///
2681    /// let avatar_url = mxc_uri!("mxc://example.org/avatar").to_owned();
2682    /// let mut content = RoomMemberEventContent::new(MembershipState::Join);
2683    /// content.avatar_url = Some(avatar_url);
2684    ///
2685    /// joined_room.send_state_event_for_key(ruma::user_id!("@foo:bar.com"), content).await?;
2686    ///
2687    /// // Custom event:
2688    /// #[derive(Clone, Debug, Deserialize, Serialize, EventContent)]
2689    /// #[ruma_event(type = "org.matrix.msc_9000.xxx", kind = State, state_key_type = String)]
2690    /// struct XxxStateEventContent { /* fields... */ }
2691    ///
2692    /// let content: XxxStateEventContent = todo!();
2693    /// joined_room.send_state_event_for_key("foo", content).await?;
2694    /// # anyhow::Ok(()) };
2695    /// ```
2696    pub async fn send_state_event_for_key<C, K>(
2697        &self,
2698        state_key: &K,
2699        content: C,
2700    ) -> Result<send_state_event::v3::Response>
2701    where
2702        C: StateEventContent,
2703        C::StateKey: Borrow<K>,
2704        K: AsRef<str> + ?Sized,
2705    {
2706        self.ensure_room_joined()?;
2707        let request =
2708            send_state_event::v3::Request::new(self.room_id().to_owned(), state_key, &content)?;
2709        let response = self.client.send(request).await?;
2710        Ok(response)
2711    }
2712
2713    /// Send a raw room state event to the homeserver.
2714    ///
2715    /// Returns the parsed response from the server.
2716    ///
2717    /// # Arguments
2718    ///
2719    /// * `event_type` - The type of the event that we're sending out.
2720    ///
2721    /// * `state_key` - A unique key which defines the overwriting semantics for
2722    /// this piece of room state. This value is often a zero-length string.
2723    ///
2724    /// * `content` - The content of the event as a raw JSON value. The argument
2725    ///   type can be `serde_json::Value`, but also other raw JSON types; for
2726    ///   the full list check the documentation of [`IntoRawStateEventContent`].
2727    ///
2728    /// # Examples
2729    ///
2730    /// ```no_run
2731    /// use serde_json::json;
2732    ///
2733    /// # async {
2734    /// # let homeserver = url::Url::parse("http://localhost:8080")?;
2735    /// # let mut client = matrix_sdk::Client::new(homeserver).await?;
2736    /// # let room_id = matrix_sdk::ruma::room_id!("!test:localhost");
2737    ///
2738    /// if let Some(room) = client.get_room(&room_id) {
2739    ///     room.send_state_event_raw("m.room.member", "", json!({
2740    ///         "avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
2741    ///         "displayname": "Alice Margatroid",
2742    ///         "membership": "join",
2743    ///     })).await?;
2744    /// }
2745    /// # anyhow::Ok(()) };
2746    /// ```
2747    #[instrument(skip_all)]
2748    pub async fn send_state_event_raw(
2749        &self,
2750        event_type: &str,
2751        state_key: &str,
2752        content: impl IntoRawStateEventContent,
2753    ) -> Result<send_state_event::v3::Response> {
2754        self.ensure_room_joined()?;
2755
2756        let request = send_state_event::v3::Request::new_raw(
2757            self.room_id().to_owned(),
2758            event_type.into(),
2759            state_key.to_owned(),
2760            content.into_raw_state_event_content(),
2761        );
2762
2763        Ok(self.client.send(request).await?)
2764    }
2765
2766    /// Strips all information out of an event of the room.
2767    ///
2768    /// Returns the [`redact_event::v3::Response`] from the server.
2769    ///
2770    /// This cannot be undone. Users may redact their own events, and any user
2771    /// with a power level greater than or equal to the redact power level of
2772    /// the room may redact events there.
2773    ///
2774    /// # Arguments
2775    ///
2776    /// * `event_id` - The ID of the event to redact
2777    ///
2778    /// * `reason` - The reason for the event being redacted.
2779    ///
2780    /// * `txn_id` - A unique ID that can be attached to this event as
2781    /// its transaction ID. If not given one is created for the message.
2782    ///
2783    /// # Examples
2784    ///
2785    /// ```no_run
2786    /// use matrix_sdk::ruma::event_id;
2787    ///
2788    /// # async {
2789    /// # let homeserver = url::Url::parse("http://localhost:8080")?;
2790    /// # let mut client = matrix_sdk::Client::new(homeserver).await?;
2791    /// # let room_id = matrix_sdk::ruma::room_id!("!test:localhost");
2792    /// #
2793    /// if let Some(room) = client.get_room(&room_id) {
2794    ///     let event_id = event_id!("$xxxxxx:example.org");
2795    ///     let reason = Some("Indecent material");
2796    ///     room.redact(&event_id, reason, None).await?;
2797    /// }
2798    /// # anyhow::Ok(()) };
2799    /// ```
2800    #[instrument(skip_all)]
2801    pub async fn redact(
2802        &self,
2803        event_id: &EventId,
2804        reason: Option<&str>,
2805        txn_id: Option<OwnedTransactionId>,
2806    ) -> HttpResult<redact_event::v3::Response> {
2807        let txn_id = txn_id.unwrap_or_else(TransactionId::new);
2808        let request = assign!(
2809            redact_event::v3::Request::new(self.room_id().to_owned(), event_id.to_owned(), txn_id),
2810            { reason: reason.map(ToOwned::to_owned) }
2811        );
2812
2813        self.client.send(request).await
2814    }
2815
2816    /// Get a list of servers that should know this room.
2817    ///
2818    /// Uses the synced members of the room and the suggested [routing
2819    /// algorithm] from the Matrix spec.
2820    ///
2821    /// Returns at most three servers.
2822    ///
2823    /// [routing algorithm]: https://spec.matrix.org/v1.3/appendices/#routing
2824    pub async fn route(&self) -> Result<Vec<OwnedServerName>> {
2825        let acl_ev = self
2826            .get_state_event_static::<RoomServerAclEventContent>()
2827            .await?
2828            .and_then(|ev| ev.deserialize().ok());
2829        let acl = acl_ev.as_ref().and_then(|ev| match ev {
2830            SyncOrStrippedState::Sync(ev) => ev.as_original().map(|ev| &ev.content),
2831            SyncOrStrippedState::Stripped(ev) => Some(&ev.content),
2832        });
2833
2834        // Filter out server names that:
2835        // - Are blocked due to server ACLs
2836        // - Are IP addresses
2837        let members: Vec<_> = self
2838            .members_no_sync(RoomMemberships::JOIN)
2839            .await?
2840            .into_iter()
2841            .filter(|member| {
2842                let server = member.user_id().server_name();
2843                acl.filter(|acl| !acl.is_allowed(server)).is_none() && !server.is_ip_literal()
2844            })
2845            .collect();
2846
2847        // Get the server of the highest power level user in the room, provided
2848        // they are at least power level 50.
2849        let max = members
2850            .iter()
2851            .max_by_key(|member| member.power_level())
2852            .filter(|max| max.power_level() >= int!(50))
2853            .map(|member| member.user_id().server_name());
2854
2855        // Sort the servers by population.
2856        let servers = members
2857            .iter()
2858            .map(|member| member.user_id().server_name())
2859            .filter(|server| max.filter(|max| max == server).is_none())
2860            .fold(BTreeMap::<_, u32>::new(), |mut servers, server| {
2861                *servers.entry(server).or_default() += 1;
2862                servers
2863            });
2864        let mut servers: Vec<_> = servers.into_iter().collect();
2865        servers.sort_unstable_by(|(_, count_a), (_, count_b)| count_b.cmp(count_a));
2866
2867        Ok(max
2868            .into_iter()
2869            .chain(servers.into_iter().map(|(name, _)| name))
2870            .take(3)
2871            .map(ToOwned::to_owned)
2872            .collect())
2873    }
2874
2875    /// Get a `matrix.to` permalink to this room.
2876    ///
2877    /// If this room has an alias, we use it. Otherwise, we try to use the
2878    /// synced members in the room for [routing] the room ID.
2879    ///
2880    /// [routing]: https://spec.matrix.org/v1.3/appendices/#routing
2881    pub async fn matrix_to_permalink(&self) -> Result<MatrixToUri> {
2882        if let Some(alias) = self.canonical_alias().or_else(|| self.alt_aliases().pop()) {
2883            return Ok(alias.matrix_to_uri());
2884        }
2885
2886        let via = self.route().await?;
2887        Ok(self.room_id().matrix_to_uri_via(via))
2888    }
2889
2890    /// Get a `matrix:` permalink to this room.
2891    ///
2892    /// If this room has an alias, we use it. Otherwise, we try to use the
2893    /// synced members in the room for [routing] the room ID.
2894    ///
2895    /// # Arguments
2896    ///
2897    /// * `join` - Whether the user should join the room.
2898    ///
2899    /// [routing]: https://spec.matrix.org/v1.3/appendices/#routing
2900    pub async fn matrix_permalink(&self, join: bool) -> Result<MatrixUri> {
2901        if let Some(alias) = self.canonical_alias().or_else(|| self.alt_aliases().pop()) {
2902            return Ok(alias.matrix_uri(join));
2903        }
2904
2905        let via = self.route().await?;
2906        Ok(self.room_id().matrix_uri_via(via, join))
2907    }
2908
2909    /// Get a `matrix.to` permalink to an event in this room.
2910    ///
2911    /// We try to use the synced members in the room for [routing] the room ID.
2912    ///
2913    /// *Note*: This method does not check if the given event ID is actually
2914    /// part of this room. It needs to be checked before calling this method
2915    /// otherwise the permalink won't work.
2916    ///
2917    /// # Arguments
2918    ///
2919    /// * `event_id` - The ID of the event.
2920    ///
2921    /// [routing]: https://spec.matrix.org/v1.3/appendices/#routing
2922    pub async fn matrix_to_event_permalink(
2923        &self,
2924        event_id: impl Into<OwnedEventId>,
2925    ) -> Result<MatrixToUri> {
2926        // Don't use the alias because an event is tied to a room ID, but an
2927        // alias might point to another room, e.g. after a room upgrade.
2928        let via = self.route().await?;
2929        Ok(self.room_id().matrix_to_event_uri_via(event_id, via))
2930    }
2931
2932    /// Get a `matrix:` permalink to an event in this room.
2933    ///
2934    /// We try to use the synced members in the room for [routing] the room ID.
2935    ///
2936    /// *Note*: This method does not check if the given event ID is actually
2937    /// part of this room. It needs to be checked before calling this method
2938    /// otherwise the permalink won't work.
2939    ///
2940    /// # Arguments
2941    ///
2942    /// * `event_id` - The ID of the event.
2943    ///
2944    /// [routing]: https://spec.matrix.org/v1.3/appendices/#routing
2945    pub async fn matrix_event_permalink(
2946        &self,
2947        event_id: impl Into<OwnedEventId>,
2948    ) -> Result<MatrixUri> {
2949        // Don't use the alias because an event is tied to a room ID, but an
2950        // alias might point to another room, e.g. after a room upgrade.
2951        let via = self.route().await?;
2952        Ok(self.room_id().matrix_event_uri_via(event_id, via))
2953    }
2954
2955    /// Get the latest receipt of a user in this room.
2956    ///
2957    /// # Arguments
2958    ///
2959    /// * `receipt_type` - The type of receipt to get.
2960    ///
2961    /// * `thread` - The thread containing the event of the receipt, if any.
2962    ///
2963    /// * `user_id` - The ID of the user.
2964    ///
2965    /// Returns the ID of the event on which the receipt applies and the
2966    /// receipt.
2967    pub async fn load_user_receipt(
2968        &self,
2969        receipt_type: ReceiptType,
2970        thread: ReceiptThread,
2971        user_id: &UserId,
2972    ) -> Result<Option<(OwnedEventId, Receipt)>> {
2973        self.inner.load_user_receipt(receipt_type, thread, user_id).await.map_err(Into::into)
2974    }
2975
2976    /// Load the receipts for an event in this room from storage.
2977    ///
2978    /// # Arguments
2979    ///
2980    /// * `receipt_type` - The type of receipt to get.
2981    ///
2982    /// * `thread` - The thread containing the event of the receipt, if any.
2983    ///
2984    /// * `event_id` - The ID of the event.
2985    ///
2986    /// Returns a list of IDs of users who have sent a receipt for the event and
2987    /// the corresponding receipts.
2988    pub async fn load_event_receipts(
2989        &self,
2990        receipt_type: ReceiptType,
2991        thread: ReceiptThread,
2992        event_id: &EventId,
2993    ) -> Result<Vec<(OwnedUserId, Receipt)>> {
2994        self.inner.load_event_receipts(receipt_type, thread, event_id).await.map_err(Into::into)
2995    }
2996
2997    /// Get the push context for this room.
2998    ///
2999    /// Returns `None` if some data couldn't be found. This should only happen
3000    /// in brand new rooms, while we process its state.
3001    pub async fn push_condition_room_ctx(&self) -> Result<Option<PushConditionRoomCtx>> {
3002        let room_id = self.room_id();
3003        let user_id = self.own_user_id();
3004        let room_info = self.clone_info();
3005        let member_count = room_info.active_members_count();
3006
3007        let user_display_name = if let Some(member) = self.get_member_no_sync(user_id).await? {
3008            member.name().to_owned()
3009        } else {
3010            return Ok(None);
3011        };
3012
3013        let power_levels = match self.power_levels().await {
3014            Ok(power_levels) => Some(power_levels.into()),
3015            Err(error) => {
3016                if matches!(room_info.state(), RoomState::Joined) {
3017                    // It's normal to not have the power levels in a non-joined room, so don't log
3018                    // the error if the room is not joined
3019                    error!("Could not compute power levels for push conditions: {error}");
3020                }
3021                None
3022            }
3023        };
3024
3025        let this = self.clone();
3026        let mut ctx = assign!(PushConditionRoomCtx::new(
3027            room_id.to_owned(),
3028            UInt::new(member_count).unwrap_or(UInt::MAX),
3029            user_id.to_owned(),
3030            user_display_name,
3031        ),
3032        {
3033            power_levels,
3034        });
3035
3036        if self.client.enabled_thread_subscriptions() {
3037            ctx = ctx.with_has_thread_subscription_fn(move |event_id: &EventId| {
3038                let room = this.clone();
3039                Box::pin(async move {
3040                    if let Ok(maybe_sub) = room.fetch_thread_subscription(event_id.to_owned()).await
3041                    {
3042                        maybe_sub.is_some()
3043                    } else {
3044                        false
3045                    }
3046                })
3047            });
3048        }
3049
3050        Ok(Some(ctx))
3051    }
3052
3053    /// Retrieves a [`PushContext`] that can be used to compute the push
3054    /// actions for events.
3055    pub async fn push_context(&self) -> Result<Option<PushContext>> {
3056        let Some(push_condition_room_ctx) = self.push_condition_room_ctx().await? else {
3057            debug!("Could not aggregate push context");
3058            return Ok(None);
3059        };
3060        let push_rules = self.client().account().push_rules().await?;
3061        Ok(Some(PushContext::new(push_condition_room_ctx, push_rules)))
3062    }
3063
3064    /// Get the push actions for the given event with the current room state.
3065    ///
3066    /// Note that it is possible that no push action is returned because the
3067    /// current room state does not have all the required state events.
3068    pub async fn event_push_actions<T>(&self, event: &Raw<T>) -> Result<Option<Vec<Action>>> {
3069        if let Some(ctx) = self.push_context().await? {
3070            Ok(Some(ctx.for_event(event).await))
3071        } else {
3072            Ok(None)
3073        }
3074    }
3075
3076    /// The membership details of the (latest) invite for the logged-in user in
3077    /// this room.
3078    pub async fn invite_details(&self) -> Result<Invite> {
3079        let state = self.state();
3080
3081        if state != RoomState::Invited {
3082            return Err(Error::WrongRoomState(Box::new(WrongRoomState::new("Invited", state))));
3083        }
3084
3085        let invitee = self
3086            .get_member_no_sync(self.own_user_id())
3087            .await?
3088            .ok_or_else(|| Error::UnknownError(Box::new(InvitationError::EventMissing)))?;
3089        let event = invitee.event();
3090        let inviter_id = event.sender();
3091        let inviter = self.get_member_no_sync(inviter_id).await?;
3092        Ok(Invite { invitee, inviter })
3093    }
3094
3095    /// Get the membership details for the current user.
3096    ///
3097    /// Returns:
3098    ///     - If the user was present in the room, a
3099    ///       [`RoomMemberWithSenderInfo`] containing both the user info and the
3100    ///       member info of the sender of the `m.room.member` event.
3101    ///     - If the current user is not present, an error.
3102    pub async fn member_with_sender_info(
3103        &self,
3104        user_id: &UserId,
3105    ) -> Result<RoomMemberWithSenderInfo> {
3106        let Some(member) = self.get_member_no_sync(user_id).await? else {
3107            return Err(Error::InsufficientData);
3108        };
3109
3110        let sender_member =
3111            if let Some(member) = self.get_member_no_sync(member.event().sender()).await? {
3112                // If the sender room member info is already available, return it
3113                Some(member)
3114            } else if self.are_members_synced() {
3115                // The room members are synced and we couldn't find the sender info
3116                None
3117            } else if self.sync_members().await.is_ok() {
3118                // Try getting the sender room member info again after syncing
3119                self.get_member_no_sync(member.event().sender()).await?
3120            } else {
3121                None
3122            };
3123
3124        Ok(RoomMemberWithSenderInfo { room_member: member, sender_info: sender_member })
3125    }
3126
3127    /// Forget this room.
3128    ///
3129    /// This communicates to the homeserver that it should forget the room.
3130    ///
3131    /// Only left or banned-from rooms can be forgotten.
3132    pub async fn forget(&self) -> Result<()> {
3133        let state = self.state();
3134        match state {
3135            RoomState::Joined | RoomState::Invited | RoomState::Knocked => {
3136                return Err(Error::WrongRoomState(Box::new(WrongRoomState::new(
3137                    "Left / Banned",
3138                    state,
3139                ))));
3140            }
3141            RoomState::Left | RoomState::Banned => {}
3142        }
3143
3144        let request = forget_room::v3::Request::new(self.inner.room_id().to_owned());
3145        let _response = self.client.send(request).await?;
3146
3147        // If it was a DM, remove the room from the `m.direct` global account data.
3148        if self.inner.direct_targets_length() != 0 {
3149            if let Err(e) = self.set_is_direct(false).await {
3150                // It is not important whether we managed to remove the room, it will not have
3151                // any consequences, so just log the error.
3152                warn!(room_id = ?self.room_id(), "failed to remove room from m.direct account data: {e}");
3153            }
3154        }
3155
3156        self.client.base_client().forget_room(self.inner.room_id()).await?;
3157
3158        Ok(())
3159    }
3160
3161    fn ensure_room_joined(&self) -> Result<()> {
3162        let state = self.state();
3163        if state == RoomState::Joined {
3164            Ok(())
3165        } else {
3166            Err(Error::WrongRoomState(Box::new(WrongRoomState::new("Joined", state))))
3167        }
3168    }
3169
3170    /// Get the notification mode.
3171    pub async fn notification_mode(&self) -> Option<RoomNotificationMode> {
3172        if !matches!(self.state(), RoomState::Joined) {
3173            return None;
3174        }
3175
3176        let notification_settings = self.client().notification_settings().await;
3177
3178        // Get the user-defined mode if available
3179        let notification_mode =
3180            notification_settings.get_user_defined_room_notification_mode(self.room_id()).await;
3181
3182        if notification_mode.is_some() {
3183            notification_mode
3184        } else if let Ok(is_encrypted) =
3185            self.latest_encryption_state().await.map(|state| state.is_encrypted())
3186        {
3187            // Otherwise, if encrypted status is available, get the default mode for this
3188            // type of room.
3189            // From the point of view of notification settings, a `one-to-one` room is one
3190            // that involves exactly two people.
3191            let is_one_to_one = IsOneToOne::from(self.active_members_count() == 2);
3192            let default_mode = notification_settings
3193                .get_default_room_notification_mode(IsEncrypted::from(is_encrypted), is_one_to_one)
3194                .await;
3195            Some(default_mode)
3196        } else {
3197            None
3198        }
3199    }
3200
3201    /// Get the user-defined notification mode.
3202    ///
3203    /// The result is cached for fast and non-async call. To read the cached
3204    /// result, use
3205    /// [`matrix_sdk_base::Room::cached_user_defined_notification_mode`].
3206    //
3207    // Note for maintainers:
3208    //
3209    // The fact the result is cached is an important property. If you change that in
3210    // the future, please review all calls to this method.
3211    pub async fn user_defined_notification_mode(&self) -> Option<RoomNotificationMode> {
3212        if !matches!(self.state(), RoomState::Joined) {
3213            return None;
3214        }
3215
3216        let notification_settings = self.client().notification_settings().await;
3217
3218        // Get the user-defined mode if available
3219        let mode =
3220            notification_settings.get_user_defined_room_notification_mode(self.room_id()).await;
3221
3222        if let Some(mode) = mode {
3223            self.update_cached_user_defined_notification_mode(mode);
3224        }
3225
3226        mode
3227    }
3228
3229    /// Report an event as inappropriate to the homeserver's administrator.
3230    ///
3231    /// # Arguments
3232    ///
3233    /// * `event_id` - The ID of the event to report.
3234    /// * `score` - The score to rate this content.
3235    /// * `reason` - The reason the content is being reported.
3236    ///
3237    /// # Errors
3238    ///
3239    /// Returns an error if the room is not joined or if an error occurs with
3240    /// the request.
3241    pub async fn report_content(
3242        &self,
3243        event_id: OwnedEventId,
3244        score: Option<ReportedContentScore>,
3245        reason: Option<String>,
3246    ) -> Result<report_content::v3::Response> {
3247        let state = self.state();
3248        if state != RoomState::Joined {
3249            return Err(Error::WrongRoomState(Box::new(WrongRoomState::new("Joined", state))));
3250        }
3251
3252        let request = report_content::v3::Request::new(
3253            self.inner.room_id().to_owned(),
3254            event_id,
3255            score.map(Into::into),
3256            reason,
3257        );
3258        Ok(self.client.send(request).await?)
3259    }
3260
3261    /// Reports a room as inappropriate to the server.
3262    /// The caller is not required to be joined to the room to report it.
3263    ///
3264    /// # Arguments
3265    ///
3266    /// * `reason` - The reason the room is being reported.
3267    ///
3268    /// # Errors
3269    ///
3270    /// Returns an error if the room is not found or on rate limit
3271    pub async fn report_room(&self, reason: String) -> Result<report_room::v3::Response> {
3272        let request = report_room::v3::Request::new(self.inner.room_id().to_owned(), reason);
3273
3274        Ok(self.client.send(request).await?)
3275    }
3276
3277    /// Set a flag on the room to indicate that the user has explicitly marked
3278    /// it as (un)read.
3279    ///
3280    /// This is a no-op if [`BaseRoom::is_marked_unread()`] returns the same
3281    /// value as `unread`.
3282    pub async fn set_unread_flag(&self, unread: bool) -> Result<()> {
3283        if self.is_marked_unread() == unread {
3284            // The request is not necessary.
3285            return Ok(());
3286        }
3287
3288        let user_id = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
3289
3290        let content = MarkedUnreadEventContent::new(unread);
3291
3292        let request = set_room_account_data::v3::Request::new(
3293            user_id.to_owned(),
3294            self.inner.room_id().to_owned(),
3295            &content,
3296        )?;
3297
3298        self.client.send(request).await?;
3299        Ok(())
3300    }
3301
3302    /// Returns the [`RoomEventCache`] associated to this room, assuming the
3303    /// global [`EventCache`] has been enabled for subscription.
3304    pub async fn event_cache(
3305        &self,
3306    ) -> event_cache::Result<(RoomEventCache, Arc<EventCacheDropHandles>)> {
3307        self.client.event_cache().for_room(self.room_id()).await
3308    }
3309
3310    /// Get the beacon information event in the room for the `user_id`.
3311    ///
3312    /// # Errors
3313    ///
3314    /// Returns an error if the event is redacted, stripped, not found or could
3315    /// not be deserialized.
3316    pub(crate) async fn get_user_beacon_info(
3317        &self,
3318        user_id: &UserId,
3319    ) -> Result<OriginalSyncStateEvent<BeaconInfoEventContent>, BeaconError> {
3320        let raw_event = self
3321            .get_state_event_static_for_key::<BeaconInfoEventContent, _>(user_id)
3322            .await?
3323            .ok_or(BeaconError::NotFound)?;
3324
3325        match raw_event.deserialize()? {
3326            SyncOrStrippedState::Sync(SyncStateEvent::Original(beacon_info)) => Ok(beacon_info),
3327            SyncOrStrippedState::Sync(SyncStateEvent::Redacted(_)) => Err(BeaconError::Redacted),
3328            SyncOrStrippedState::Stripped(_) => Err(BeaconError::Stripped),
3329        }
3330    }
3331
3332    /// Start sharing live location in the room.
3333    ///
3334    /// # Arguments
3335    ///
3336    /// * `duration_millis` - The duration for which the live location is
3337    ///   shared, in milliseconds.
3338    /// * `description` - An optional description for the live location share.
3339    ///
3340    /// # Errors
3341    ///
3342    /// Returns an error if the room is not joined or if the state event could
3343    /// not be sent.
3344    pub async fn start_live_location_share(
3345        &self,
3346        duration_millis: u64,
3347        description: Option<String>,
3348    ) -> Result<send_state_event::v3::Response> {
3349        self.ensure_room_joined()?;
3350
3351        self.send_state_event_for_key(
3352            self.own_user_id(),
3353            BeaconInfoEventContent::new(
3354                description,
3355                Duration::from_millis(duration_millis),
3356                true,
3357                None,
3358            ),
3359        )
3360        .await
3361    }
3362
3363    /// Stop sharing live location in the room.
3364    ///
3365    /// # Errors
3366    ///
3367    /// Returns an error if the room is not joined, if the beacon information
3368    /// is redacted or stripped, or if the state event is not found.
3369    pub async fn stop_live_location_share(
3370        &self,
3371    ) -> Result<send_state_event::v3::Response, BeaconError> {
3372        self.ensure_room_joined()?;
3373
3374        let mut beacon_info_event = self.get_user_beacon_info(self.own_user_id()).await?;
3375        beacon_info_event.content.stop();
3376        Ok(self.send_state_event_for_key(self.own_user_id(), beacon_info_event.content).await?)
3377    }
3378
3379    /// Send a location beacon event in the current room.
3380    ///
3381    /// # Arguments
3382    ///
3383    /// * `geo_uri` - The geo URI of the location beacon.
3384    ///
3385    /// # Errors
3386    ///
3387    /// Returns an error if the room is not joined, if the beacon information
3388    /// is redacted or stripped, if the location share is no longer live,
3389    /// or if the state event is not found.
3390    pub async fn send_location_beacon(
3391        &self,
3392        geo_uri: String,
3393    ) -> Result<send_message_event::v3::Response, BeaconError> {
3394        self.ensure_room_joined()?;
3395
3396        let beacon_info_event = self.get_user_beacon_info(self.own_user_id()).await?;
3397
3398        if beacon_info_event.content.is_live() {
3399            let content = BeaconEventContent::new(beacon_info_event.event_id, geo_uri, None);
3400            Ok(self.send(content).await?)
3401        } else {
3402            Err(BeaconError::NotLive)
3403        }
3404    }
3405
3406    /// Store the given `ComposerDraft` in the state store using the current
3407    /// room id and optional thread root id as identifier.
3408    pub async fn save_composer_draft(
3409        &self,
3410        draft: ComposerDraft,
3411        thread_root: Option<&EventId>,
3412    ) -> Result<()> {
3413        self.client
3414            .state_store()
3415            .set_kv_data(
3416                StateStoreDataKey::ComposerDraft(self.room_id(), thread_root),
3417                StateStoreDataValue::ComposerDraft(draft),
3418            )
3419            .await?;
3420        Ok(())
3421    }
3422
3423    /// Retrieve the `ComposerDraft` stored in the state store for this room
3424    /// and given thread, if any.
3425    pub async fn load_composer_draft(
3426        &self,
3427        thread_root: Option<&EventId>,
3428    ) -> Result<Option<ComposerDraft>> {
3429        let data = self
3430            .client
3431            .state_store()
3432            .get_kv_data(StateStoreDataKey::ComposerDraft(self.room_id(), thread_root))
3433            .await?;
3434        Ok(data.and_then(|d| d.into_composer_draft()))
3435    }
3436
3437    /// Remove the `ComposerDraft` stored in the state store for this room
3438    /// and given thread, if any.
3439    pub async fn clear_composer_draft(&self, thread_root: Option<&EventId>) -> Result<()> {
3440        self.client
3441            .state_store()
3442            .remove_kv_data(StateStoreDataKey::ComposerDraft(self.room_id(), thread_root))
3443            .await?;
3444        Ok(())
3445    }
3446
3447    /// Load pinned state events for a room from the `/state` endpoint in the
3448    /// home server.
3449    pub async fn load_pinned_events(&self) -> Result<Option<Vec<OwnedEventId>>> {
3450        let response = self
3451            .client
3452            .send(get_state_event_for_key::v3::Request::new(
3453                self.room_id().to_owned(),
3454                StateEventType::RoomPinnedEvents,
3455                "".to_owned(),
3456            ))
3457            .await;
3458
3459        match response {
3460            Ok(response) => Ok(Some(
3461                response
3462                    .into_content()
3463                    .deserialize_as_unchecked::<RoomPinnedEventsEventContent>()?
3464                    .pinned,
3465            )),
3466            Err(http_error) => match http_error.as_client_api_error() {
3467                Some(error) if error.status_code == StatusCode::NOT_FOUND => Ok(None),
3468                _ => Err(http_error.into()),
3469            },
3470        }
3471    }
3472
3473    /// Observe live location sharing events for this room.
3474    ///
3475    /// The returned observable will receive the newest event for each sync
3476    /// response that contains an `m.beacon` event.
3477    ///
3478    /// Returns a stream of [`ObservableLiveLocation`] events from other users
3479    /// in the room, excluding the live location events of the room's own user.
3480    pub fn observe_live_location_shares(&self) -> ObservableLiveLocation {
3481        ObservableLiveLocation::new(&self.client, self.room_id())
3482    }
3483
3484    /// Subscribe to knock requests in this `Room`.
3485    ///
3486    /// The current requests to join the room will be emitted immediately
3487    /// when subscribing.
3488    ///
3489    /// A new set of knock requests will be emitted whenever:
3490    /// - A new member event is received.
3491    /// - A knock request is marked as seen.
3492    /// - A sync is gappy (limited), so room membership information may be
3493    ///   outdated.
3494    ///
3495    /// Returns both a stream of knock requests and a handle for a task that
3496    /// will clean up the seen knock request ids when possible.
3497    pub async fn subscribe_to_knock_requests(
3498        &self,
3499    ) -> Result<(impl Stream<Item = Vec<KnockRequest>>, JoinHandle<()>)> {
3500        let this = Arc::new(self.clone());
3501
3502        let room_member_events_observer =
3503            self.client.observe_room_events::<SyncRoomMemberEvent, (Client, Room)>(this.room_id());
3504
3505        let current_seen_ids = self.get_seen_knock_request_ids().await?;
3506        let mut seen_request_ids_stream = self
3507            .seen_knock_request_ids_map
3508            .subscribe()
3509            .await
3510            .map(|values| values.unwrap_or_default());
3511
3512        let mut room_info_stream = self.subscribe_info();
3513
3514        // Spawn a task that will clean up the seen knock request ids when updated room
3515        // members are received
3516        let clear_seen_ids_handle = spawn({
3517            let this = self.clone();
3518            async move {
3519                let mut member_updates_stream = this.room_member_updates_sender.subscribe();
3520                while member_updates_stream.recv().await.is_ok() {
3521                    // If room members were updated, try to remove outdated seen knock request ids
3522                    if let Err(err) = this.remove_outdated_seen_knock_requests_ids().await {
3523                        warn!("Failed to remove seen knock requests: {err}")
3524                    }
3525                }
3526            }
3527        });
3528
3529        let combined_stream = stream! {
3530            // Emit current requests to join
3531            match this.get_current_join_requests(&current_seen_ids).await {
3532                Ok(initial_requests) => yield initial_requests,
3533                Err(err) => warn!("Failed to get initial requests to join: {err}")
3534            }
3535
3536            let mut requests_stream = room_member_events_observer.subscribe();
3537            let mut seen_ids = current_seen_ids.clone();
3538
3539            loop {
3540                // This is equivalent to a combine stream operation, triggering a new emission
3541                // when any of the branches changes
3542                tokio::select! {
3543                    Some((event, _)) = requests_stream.next() => {
3544                        if let Some(event) = event.as_original() {
3545                            // If we can calculate the membership change, try to emit only when needed
3546                            let emit = if event.prev_content().is_some() {
3547                                matches!(event.membership_change(),
3548                                    MembershipChange::Banned |
3549                                    MembershipChange::Knocked |
3550                                    MembershipChange::KnockAccepted |
3551                                    MembershipChange::KnockDenied |
3552                                    MembershipChange::KnockRetracted
3553                                )
3554                            } else {
3555                                // If we can't calculate the membership change, assume we need to
3556                                // emit updated values
3557                                true
3558                            };
3559
3560                            if emit {
3561                                match this.get_current_join_requests(&seen_ids).await {
3562                                    Ok(requests) => yield requests,
3563                                    Err(err) => {
3564                                        warn!("Failed to get updated knock requests on new member event: {err}")
3565                                    }
3566                                }
3567                            }
3568                        }
3569                    }
3570
3571                    Some(new_seen_ids) = seen_request_ids_stream.next() => {
3572                        // Update the current seen ids
3573                        seen_ids = new_seen_ids;
3574
3575                        // If seen requests have changed we need to recalculate
3576                        // all the knock requests
3577                        match this.get_current_join_requests(&seen_ids).await {
3578                            Ok(requests) => yield requests,
3579                            Err(err) => {
3580                                warn!("Failed to get updated knock requests on seen ids changed: {err}")
3581                            }
3582                        }
3583                    }
3584
3585                    Some(room_info) = room_info_stream.next() => {
3586                        // We need to emit new items when we may have missing room members:
3587                        // this usually happens after a gappy (limited) sync
3588                        if !room_info.are_members_synced() {
3589                            match this.get_current_join_requests(&seen_ids).await {
3590                                Ok(requests) => yield requests,
3591                                Err(err) => {
3592                                    warn!("Failed to get updated knock requests on gappy (limited) sync: {err}")
3593                                }
3594                            }
3595                        }
3596                    }
3597                    // If the streams in all branches are closed, stop the loop
3598                    else => break,
3599                }
3600            }
3601        };
3602
3603        Ok((combined_stream, clear_seen_ids_handle))
3604    }
3605
3606    async fn get_current_join_requests(
3607        &self,
3608        seen_request_ids: &BTreeMap<OwnedEventId, OwnedUserId>,
3609    ) -> Result<Vec<KnockRequest>> {
3610        Ok(self
3611            .members(RoomMemberships::KNOCK)
3612            .await?
3613            .into_iter()
3614            .filter_map(|member| {
3615                let event_id = member.event().event_id()?;
3616                Some(KnockRequest::new(
3617                    self,
3618                    event_id,
3619                    member.event().timestamp(),
3620                    KnockRequestMemberInfo::from_member(&member),
3621                    seen_request_ids.contains_key(event_id),
3622                ))
3623            })
3624            .collect())
3625    }
3626
3627    /// Access the room settings related to privacy and visibility.
3628    pub fn privacy_settings(&self) -> RoomPrivacySettings<'_> {
3629        RoomPrivacySettings::new(&self.inner, &self.client)
3630    }
3631
3632    /// Retrieve a list of all the threads for the current room.
3633    ///
3634    /// Since this client-server API is paginated, the return type may include a
3635    /// token used to resuming back-pagination into the list of results, in
3636    /// [`ThreadRoots::prev_batch_token`]. This token can be fed back into
3637    /// [`ListThreadsOptions::from`] to continue the pagination
3638    /// from the previous position.
3639    pub async fn list_threads(&self, opts: ListThreadsOptions) -> Result<ThreadRoots> {
3640        let request = opts.into_request(self.room_id());
3641
3642        let response = self.client.send(request).await?;
3643
3644        let push_ctx = self.push_context().await?;
3645        let chunk = join_all(
3646            response.chunk.into_iter().map(|ev| self.try_decrypt_event(ev, push_ctx.as_ref())),
3647        )
3648        .await;
3649
3650        Ok(ThreadRoots { chunk, prev_batch_token: response.next_batch })
3651    }
3652
3653    /// Retrieve a list of relations for the given event, according to the given
3654    /// options.
3655    ///
3656    /// Since this client-server API is paginated, the return type may include a
3657    /// token used to resuming back-pagination into the list of results, in
3658    /// [`Relations::prev_batch_token`]. This token can be fed back into
3659    /// [`RelationsOptions::from`] to continue the pagination from the previous
3660    /// position.
3661    ///
3662    /// **Note**: if [`RelationsOptions::from`] is set for a subsequent request,
3663    /// then it must be used with the same
3664    /// [`RelationsOptions::include_relations`] value as the request that
3665    /// returns the `from` token, otherwise the server behavior is undefined.
3666    pub async fn relations(
3667        &self,
3668        event_id: OwnedEventId,
3669        opts: RelationsOptions,
3670    ) -> Result<Relations> {
3671        opts.send(self, event_id).await
3672    }
3673
3674    /// Subscribe to a given thread in this room.
3675    ///
3676    /// This will subscribe the user to the thread, so that they will receive
3677    /// notifications for that thread specifically.
3678    ///
3679    /// # Arguments
3680    ///
3681    /// - `thread_root`: The ID of the thread root event to subscribe to.
3682    /// - `automatic`: Whether the subscription was made automatically by a
3683    ///   client, not by manual user choice. If set, must include the latest
3684    ///   event ID that's known in the thread and that is causing the automatic
3685    ///   subscription. If unset (i.e. we're now subscribing manually) and there
3686    ///   was a previous automatic subscription, the subscription will be
3687    ///   overridden to a manual one instead.
3688    ///
3689    /// # Returns
3690    ///
3691    /// - A 404 error if the event isn't known, or isn't a thread root.
3692    /// - An `Ok` result if the subscription was successful, or if the server
3693    ///   skipped an automatic subscription (as the user unsubscribed from the
3694    ///   thread after the event causing the automatic subscription).
3695    #[instrument(skip(self), fields(room_id = %self.room_id()))]
3696    pub async fn subscribe_thread(
3697        &self,
3698        thread_root: OwnedEventId,
3699        automatic: Option<OwnedEventId>,
3700    ) -> Result<()> {
3701        let is_automatic = automatic.is_some();
3702
3703        match self
3704            .client
3705            .send(subscribe_thread::unstable::Request::new(
3706                self.room_id().to_owned(),
3707                thread_root.clone(),
3708                automatic,
3709            ))
3710            .await
3711        {
3712            Ok(_response) => {
3713                trace!("Server acknowledged the thread subscription; saving in db");
3714                // Immediately save the result into the database.
3715                self.client
3716                    .state_store()
3717                    .upsert_thread_subscription(
3718                        self.room_id(),
3719                        &thread_root,
3720                        ThreadSubscription { automatic: is_automatic },
3721                    )
3722                    .await?;
3723
3724                Ok(())
3725            }
3726
3727            Err(err) => {
3728                if let Some(ErrorKind::ConflictingUnsubscription) = err.client_api_error_kind() {
3729                    // In this case: the server indicates that the user unsubscribed *after* the
3730                    // event ID we've used in an automatic subscription; don't
3731                    // save the subscription state in the database, as the
3732                    // previous one should be more correct.
3733                    trace!("Thread subscription skipped: {err}");
3734                    Ok(())
3735                } else {
3736                    // Forward the error to the caller.
3737                    Err(err.into())
3738                }
3739            }
3740        }
3741    }
3742
3743    /// Unsubscribe from a given thread in this room.
3744    ///
3745    /// # Arguments
3746    ///
3747    /// - `thread_root`: The ID of the thread root event to unsubscribe to.
3748    ///
3749    /// # Returns
3750    ///
3751    /// - An `Ok` result if the unsubscription was successful, or the thread was
3752    ///   already unsubscribed.
3753    /// - A 404 error if the event isn't known, or isn't a thread root.
3754    #[instrument(skip(self), fields(room_id = %self.room_id()))]
3755    pub async fn unsubscribe_thread(&self, thread_root: OwnedEventId) -> Result<()> {
3756        self.client
3757            .send(unsubscribe_thread::unstable::Request::new(
3758                self.room_id().to_owned(),
3759                thread_root.clone(),
3760            ))
3761            .await?;
3762
3763        trace!("Server acknowledged the thread subscription removal; removed it from db too");
3764
3765        // Immediately save the result into the database.
3766        self.client.state_store().remove_thread_subscription(self.room_id(), &thread_root).await?;
3767
3768        Ok(())
3769    }
3770
3771    /// Return the current thread subscription for the given thread root in this
3772    /// room.
3773    ///
3774    /// # Arguments
3775    ///
3776    /// - `thread_root`: The ID of the thread root event to get the subscription
3777    ///   for.
3778    ///
3779    /// # Returns
3780    ///
3781    /// - An `Ok` result with `Some(ThreadSubscription)` if we have some
3782    ///   subscription information.
3783    /// - An `Ok` result with `None` if the subscription does not exist, or the
3784    ///   event couldn't be found, or the event isn't a thread.
3785    /// - An error if the request fails for any other reason, such as a network
3786    ///   error.
3787    #[instrument(skip(self), fields(room_id = %self.room_id()))]
3788    pub async fn fetch_thread_subscription(
3789        &self,
3790        thread_root: OwnedEventId,
3791    ) -> Result<Option<ThreadSubscription>> {
3792        let result = self
3793            .client
3794            .send(get_thread_subscription::unstable::Request::new(
3795                self.room_id().to_owned(),
3796                thread_root.clone(),
3797            ))
3798            .await;
3799
3800        let subscription = match result {
3801            Ok(response) => Some(ThreadSubscription { automatic: response.automatic }),
3802            Err(http_error) => match http_error.as_client_api_error() {
3803                Some(error) if error.status_code == StatusCode::NOT_FOUND => None,
3804                _ => return Err(http_error.into()),
3805            },
3806        };
3807
3808        // Keep the database in sync.
3809        if let Some(sub) = &subscription {
3810            self.client
3811                .state_store()
3812                .upsert_thread_subscription(self.room_id(), &thread_root, *sub)
3813                .await?;
3814        } else {
3815            // If the subscription was not found, remove it from the database.
3816            self.client
3817                .state_store()
3818                .remove_thread_subscription(self.room_id(), &thread_root)
3819                .await?;
3820        }
3821
3822        Ok(subscription)
3823    }
3824}
3825
3826#[cfg(feature = "e2e-encryption")]
3827impl RoomIdentityProvider for Room {
3828    fn is_member<'a>(&'a self, user_id: &'a UserId) -> BoxFuture<'a, bool> {
3829        Box::pin(async { self.get_member(user_id).await.unwrap_or(None).is_some() })
3830    }
3831
3832    fn member_identities(&self) -> BoxFuture<'_, Vec<UserIdentity>> {
3833        Box::pin(async {
3834            let members = self
3835                .members(RoomMemberships::JOIN | RoomMemberships::INVITE)
3836                .await
3837                .unwrap_or_else(|_| Default::default());
3838
3839            let mut ret: Vec<UserIdentity> = Vec::new();
3840            for member in members {
3841                if let Some(i) = self.user_identity(member.user_id()).await {
3842                    ret.push(i);
3843                }
3844            }
3845            ret
3846        })
3847    }
3848
3849    fn user_identity<'a>(&'a self, user_id: &'a UserId) -> BoxFuture<'a, Option<UserIdentity>> {
3850        Box::pin(async {
3851            self.client
3852                .encryption()
3853                .get_user_identity(user_id)
3854                .await
3855                .unwrap_or(None)
3856                .map(|u| u.underlying_identity())
3857        })
3858    }
3859}
3860
3861/// A wrapper for a weak client and a room id that allows to lazily retrieve a
3862/// room, only when needed.
3863#[derive(Clone, Debug)]
3864pub(crate) struct WeakRoom {
3865    client: WeakClient,
3866    room_id: OwnedRoomId,
3867}
3868
3869impl WeakRoom {
3870    /// Create a new `WeakRoom` given its weak components.
3871    pub fn new(client: WeakClient, room_id: OwnedRoomId) -> Self {
3872        Self { client, room_id }
3873    }
3874
3875    /// Attempts to reconstruct the room.
3876    pub fn get(&self) -> Option<Room> {
3877        self.client.get().and_then(|client| client.get_room(&self.room_id))
3878    }
3879
3880    /// The room id for that room.
3881    pub fn room_id(&self) -> &RoomId {
3882        &self.room_id
3883    }
3884}
3885
3886/// Details of the (latest) invite.
3887#[derive(Debug, Clone)]
3888pub struct Invite {
3889    /// Who has been invited.
3890    pub invitee: RoomMember,
3891    /// Who sent the invite.
3892    pub inviter: Option<RoomMember>,
3893}
3894
3895#[derive(Error, Debug)]
3896enum InvitationError {
3897    #[error("No membership event found")]
3898    EventMissing,
3899}
3900
3901/// Receipts to send all at once.
3902#[derive(Debug, Clone, Default)]
3903#[non_exhaustive]
3904pub struct Receipts {
3905    /// Fully-read marker (room account data).
3906    pub fully_read: Option<OwnedEventId>,
3907    /// Read receipt (public ephemeral room event).
3908    pub public_read_receipt: Option<OwnedEventId>,
3909    /// Read receipt (private ephemeral room event).
3910    pub private_read_receipt: Option<OwnedEventId>,
3911}
3912
3913impl Receipts {
3914    /// Create an empty `Receipts`.
3915    pub fn new() -> Self {
3916        Self::default()
3917    }
3918
3919    /// Set the last event the user has read.
3920    ///
3921    /// It means that the user has read all the events before this event.
3922    ///
3923    /// This is a private marker only visible by the user.
3924    ///
3925    /// Note that this is technically not a receipt as it is persisted in the
3926    /// room account data.
3927    pub fn fully_read_marker(mut self, event_id: impl Into<Option<OwnedEventId>>) -> Self {
3928        self.fully_read = event_id.into();
3929        self
3930    }
3931
3932    /// Set the last event presented to the user and forward it to the other
3933    /// users in the room.
3934    ///
3935    /// This is used to reset the unread messages/notification count and
3936    /// advertise to other users the last event that the user has likely seen.
3937    pub fn public_read_receipt(mut self, event_id: impl Into<Option<OwnedEventId>>) -> Self {
3938        self.public_read_receipt = event_id.into();
3939        self
3940    }
3941
3942    /// Set the last event presented to the user and don't forward it.
3943    ///
3944    /// This is used to reset the unread messages/notification count.
3945    pub fn private_read_receipt(mut self, event_id: impl Into<Option<OwnedEventId>>) -> Self {
3946        self.private_read_receipt = event_id.into();
3947        self
3948    }
3949
3950    /// Whether this `Receipts` is empty.
3951    pub fn is_empty(&self) -> bool {
3952        self.fully_read.is_none()
3953            && self.public_read_receipt.is_none()
3954            && self.private_read_receipt.is_none()
3955    }
3956}
3957
3958/// [Parent space](https://spec.matrix.org/v1.8/client-server-api/#mspaceparent-relationships)
3959/// listed by a room, possibly validated by checking the space's state.
3960#[derive(Debug)]
3961pub enum ParentSpace {
3962    /// The room recognizes the given room as its parent, and the parent
3963    /// recognizes it as its child.
3964    Reciprocal(Room),
3965    /// The room recognizes the given room as its parent, but the parent does
3966    /// not recognizes it as its child. However, the author of the
3967    /// `m.room.parent` event in the room has a sufficient power level in the
3968    /// parent to create the child event.
3969    WithPowerlevel(Room),
3970    /// The room recognizes the given room as its parent, but the parent does
3971    /// not recognizes it as its child.
3972    Illegitimate(Room),
3973    /// The room recognizes the given id as its parent room, but we cannot check
3974    /// whether the parent recognizes it as its child.
3975    Unverifiable(OwnedRoomId),
3976}
3977
3978/// The score to rate an inappropriate content.
3979///
3980/// Must be a value between `0`, inoffensive, and `-100`, very offensive.
3981#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
3982pub struct ReportedContentScore(i8);
3983
3984impl ReportedContentScore {
3985    /// The smallest value that can be represented by this type.
3986    ///
3987    /// This is for very offensive content.
3988    pub const MIN: Self = Self(-100);
3989
3990    /// The largest value that can be represented by this type.
3991    ///
3992    /// This is for inoffensive content.
3993    pub const MAX: Self = Self(0);
3994
3995    /// Try to create a `ReportedContentScore` from the provided `i8`.
3996    ///
3997    /// Returns `None` if it is smaller than [`ReportedContentScore::MIN`] or
3998    /// larger than [`ReportedContentScore::MAX`] .
3999    ///
4000    /// This is the same as the `TryFrom<i8>` implementation for
4001    /// `ReportedContentScore`, except that it returns an `Option` instead
4002    /// of a `Result`.
4003    pub fn new(value: i8) -> Option<Self> {
4004        value.try_into().ok()
4005    }
4006
4007    /// Create a `ReportedContentScore` from the provided `i8` clamped to the
4008    /// acceptable interval.
4009    ///
4010    /// The given value gets clamped into the closed interval between
4011    /// [`ReportedContentScore::MIN`] and [`ReportedContentScore::MAX`].
4012    pub fn new_saturating(value: i8) -> Self {
4013        if value > Self::MAX {
4014            Self::MAX
4015        } else if value < Self::MIN {
4016            Self::MIN
4017        } else {
4018            Self(value)
4019        }
4020    }
4021
4022    /// The value of this score.
4023    pub fn value(&self) -> i8 {
4024        self.0
4025    }
4026}
4027
4028impl PartialEq<i8> for ReportedContentScore {
4029    fn eq(&self, other: &i8) -> bool {
4030        self.0.eq(other)
4031    }
4032}
4033
4034impl PartialEq<ReportedContentScore> for i8 {
4035    fn eq(&self, other: &ReportedContentScore) -> bool {
4036        self.eq(&other.0)
4037    }
4038}
4039
4040impl PartialOrd<i8> for ReportedContentScore {
4041    fn partial_cmp(&self, other: &i8) -> Option<std::cmp::Ordering> {
4042        self.0.partial_cmp(other)
4043    }
4044}
4045
4046impl PartialOrd<ReportedContentScore> for i8 {
4047    fn partial_cmp(&self, other: &ReportedContentScore) -> Option<std::cmp::Ordering> {
4048        self.partial_cmp(&other.0)
4049    }
4050}
4051
4052impl From<ReportedContentScore> for Int {
4053    fn from(value: ReportedContentScore) -> Self {
4054        value.0.into()
4055    }
4056}
4057
4058impl TryFrom<i8> for ReportedContentScore {
4059    type Error = TryFromReportedContentScoreError;
4060
4061    fn try_from(value: i8) -> std::prelude::v1::Result<Self, Self::Error> {
4062        if value > Self::MAX || value < Self::MIN {
4063            Err(TryFromReportedContentScoreError(()))
4064        } else {
4065            Ok(Self(value))
4066        }
4067    }
4068}
4069
4070impl TryFrom<i16> for ReportedContentScore {
4071    type Error = TryFromReportedContentScoreError;
4072
4073    fn try_from(value: i16) -> std::prelude::v1::Result<Self, Self::Error> {
4074        let value = i8::try_from(value).map_err(|_| TryFromReportedContentScoreError(()))?;
4075        value.try_into()
4076    }
4077}
4078
4079impl TryFrom<i32> for ReportedContentScore {
4080    type Error = TryFromReportedContentScoreError;
4081
4082    fn try_from(value: i32) -> std::prelude::v1::Result<Self, Self::Error> {
4083        let value = i8::try_from(value).map_err(|_| TryFromReportedContentScoreError(()))?;
4084        value.try_into()
4085    }
4086}
4087
4088impl TryFrom<i64> for ReportedContentScore {
4089    type Error = TryFromReportedContentScoreError;
4090
4091    fn try_from(value: i64) -> std::prelude::v1::Result<Self, Self::Error> {
4092        let value = i8::try_from(value).map_err(|_| TryFromReportedContentScoreError(()))?;
4093        value.try_into()
4094    }
4095}
4096
4097impl TryFrom<Int> for ReportedContentScore {
4098    type Error = TryFromReportedContentScoreError;
4099
4100    fn try_from(value: Int) -> std::prelude::v1::Result<Self, Self::Error> {
4101        let value = i8::try_from(value).map_err(|_| TryFromReportedContentScoreError(()))?;
4102        value.try_into()
4103    }
4104}
4105
4106trait EventSource {
4107    fn get_event(
4108        &self,
4109        event_id: &EventId,
4110    ) -> impl Future<Output = Result<TimelineEvent, Error>> + SendOutsideWasm;
4111}
4112
4113impl EventSource for &Room {
4114    async fn get_event(&self, event_id: &EventId) -> Result<TimelineEvent, Error> {
4115        self.load_or_fetch_event(event_id, None).await
4116    }
4117}
4118
4119/// The error type returned when a checked `ReportedContentScore` conversion
4120/// fails.
4121#[derive(Debug, Clone, Error)]
4122#[error("out of range conversion attempted")]
4123pub struct TryFromReportedContentScoreError(());
4124
4125/// Contains the current user's room member info and the optional room member
4126/// info of the sender of the `m.room.member` event that this info represents.
4127#[derive(Debug)]
4128pub struct RoomMemberWithSenderInfo {
4129    /// The actual room member.
4130    pub room_member: RoomMember,
4131    /// The info of the sender of the event `room_member` is based on, if
4132    /// available.
4133    pub sender_info: Option<RoomMember>,
4134}
4135
4136#[cfg(all(test, not(target_family = "wasm")))]
4137mod tests {
4138    use std::collections::BTreeMap;
4139
4140    use matrix_sdk_base::{store::ComposerDraftType, ComposerDraft};
4141    use matrix_sdk_test::{
4142        async_test, event_factory::EventFactory, test_json, JoinedRoomBuilder, StateTestEvent,
4143        SyncResponseBuilder,
4144    };
4145    use ruma::{
4146        event_id,
4147        events::{relation::RelationType, room::member::MembershipState},
4148        int, owned_event_id, room_id, user_id, RoomVersionId,
4149    };
4150    use wiremock::{
4151        matchers::{header, method, path_regex},
4152        Mock, MockServer, ResponseTemplate,
4153    };
4154
4155    use super::ReportedContentScore;
4156    use crate::{
4157        config::RequestConfig,
4158        room::messages::{IncludeRelations, ListThreadsOptions, RelationsOptions},
4159        test_utils::{
4160            client::mock_matrix_session,
4161            logged_in_client,
4162            mocks::{MatrixMockServer, RoomRelationsResponseTemplate},
4163        },
4164        Client,
4165    };
4166
4167    #[cfg(all(feature = "sqlite", feature = "e2e-encryption"))]
4168    #[async_test]
4169    async fn test_cache_invalidation_while_encrypt() {
4170        use matrix_sdk_base::store::RoomLoadSettings;
4171        use matrix_sdk_test::{message_like_event_content, DEFAULT_TEST_ROOM_ID};
4172
4173        let sqlite_path = std::env::temp_dir().join("cache_invalidation_while_encrypt.db");
4174        let session = mock_matrix_session();
4175
4176        let client = Client::builder()
4177            .homeserver_url("http://localhost:1234")
4178            .request_config(RequestConfig::new().disable_retry())
4179            .sqlite_store(&sqlite_path, None)
4180            .build()
4181            .await
4182            .unwrap();
4183        client
4184            .matrix_auth()
4185            .restore_session(session.clone(), RoomLoadSettings::default())
4186            .await
4187            .unwrap();
4188
4189        client.encryption().enable_cross_process_store_lock("client1".to_owned()).await.unwrap();
4190
4191        // Mock receiving an event to create an internal room.
4192        let server = MockServer::start().await;
4193        {
4194            Mock::given(method("GET"))
4195                .and(path_regex(r"^/_matrix/client/r0/rooms/.*/state/m.*room.*encryption.?"))
4196                .and(header("authorization", "Bearer 1234"))
4197                .respond_with(
4198                    ResponseTemplate::new(200)
4199                        .set_body_json(&*test_json::sync_events::ENCRYPTION_CONTENT),
4200                )
4201                .mount(&server)
4202                .await;
4203            let response = SyncResponseBuilder::default()
4204                .add_joined_room(
4205                    JoinedRoomBuilder::default()
4206                        .add_state_event(StateTestEvent::Member)
4207                        .add_state_event(StateTestEvent::PowerLevels)
4208                        .add_state_event(StateTestEvent::Encryption),
4209                )
4210                .build_sync_response();
4211            client.base_client().receive_sync_response(response).await.unwrap();
4212        }
4213
4214        let room = client.get_room(&DEFAULT_TEST_ROOM_ID).expect("Room should exist");
4215
4216        // Step 1, preshare the room keys.
4217        room.preshare_room_key().await.unwrap();
4218
4219        // Step 2, force lock invalidation by pretending another client obtained the
4220        // lock.
4221        {
4222            let client = Client::builder()
4223                .homeserver_url("http://localhost:1234")
4224                .request_config(RequestConfig::new().disable_retry())
4225                .sqlite_store(&sqlite_path, None)
4226                .build()
4227                .await
4228                .unwrap();
4229            client
4230                .matrix_auth()
4231                .restore_session(session.clone(), RoomLoadSettings::default())
4232                .await
4233                .unwrap();
4234            client
4235                .encryption()
4236                .enable_cross_process_store_lock("client2".to_owned())
4237                .await
4238                .unwrap();
4239
4240            let guard = client.encryption().spin_lock_store(None).await.unwrap();
4241            assert!(guard.is_some());
4242        }
4243
4244        // Step 3, take the crypto-store lock.
4245        let guard = client.encryption().spin_lock_store(None).await.unwrap();
4246        assert!(guard.is_some());
4247
4248        // Step 4, try to encrypt a message.
4249        let olm = client.olm_machine().await;
4250        let olm = olm.as_ref().expect("Olm machine wasn't started");
4251
4252        // Now pretend we're encrypting an event; the olm machine shouldn't rely on
4253        // caching the outgoing session before.
4254        let _encrypted_content = olm
4255            .encrypt_room_event_raw(room.room_id(), "test-event", &message_like_event_content!({}))
4256            .await
4257            .unwrap();
4258    }
4259
4260    #[test]
4261    fn reported_content_score() {
4262        // i8
4263        let score = ReportedContentScore::new(0).unwrap();
4264        assert_eq!(score.value(), 0);
4265        let score = ReportedContentScore::new(-50).unwrap();
4266        assert_eq!(score.value(), -50);
4267        let score = ReportedContentScore::new(-100).unwrap();
4268        assert_eq!(score.value(), -100);
4269        assert_eq!(ReportedContentScore::new(10), None);
4270        assert_eq!(ReportedContentScore::new(-110), None);
4271
4272        let score = ReportedContentScore::new_saturating(0);
4273        assert_eq!(score.value(), 0);
4274        let score = ReportedContentScore::new_saturating(-50);
4275        assert_eq!(score.value(), -50);
4276        let score = ReportedContentScore::new_saturating(-100);
4277        assert_eq!(score.value(), -100);
4278        let score = ReportedContentScore::new_saturating(10);
4279        assert_eq!(score, ReportedContentScore::MAX);
4280        let score = ReportedContentScore::new_saturating(-110);
4281        assert_eq!(score, ReportedContentScore::MIN);
4282
4283        // i16
4284        let score = ReportedContentScore::try_from(0i16).unwrap();
4285        assert_eq!(score.value(), 0);
4286        let score = ReportedContentScore::try_from(-100i16).unwrap();
4287        assert_eq!(score.value(), -100);
4288        ReportedContentScore::try_from(10i16).unwrap_err();
4289        ReportedContentScore::try_from(-110i16).unwrap_err();
4290
4291        // i32
4292        let score = ReportedContentScore::try_from(0i32).unwrap();
4293        assert_eq!(score.value(), 0);
4294        let score = ReportedContentScore::try_from(-100i32).unwrap();
4295        assert_eq!(score.value(), -100);
4296        ReportedContentScore::try_from(10i32).unwrap_err();
4297        ReportedContentScore::try_from(-110i32).unwrap_err();
4298
4299        // i64
4300        let score = ReportedContentScore::try_from(0i64).unwrap();
4301        assert_eq!(score.value(), 0);
4302        let score = ReportedContentScore::try_from(-100i64).unwrap();
4303        assert_eq!(score.value(), -100);
4304        ReportedContentScore::try_from(10i64).unwrap_err();
4305        ReportedContentScore::try_from(-110i64).unwrap_err();
4306
4307        // Int
4308        let score = ReportedContentScore::try_from(int!(0)).unwrap();
4309        assert_eq!(score.value(), 0);
4310        let score = ReportedContentScore::try_from(int!(-100)).unwrap();
4311        assert_eq!(score.value(), -100);
4312        ReportedContentScore::try_from(int!(10)).unwrap_err();
4313        ReportedContentScore::try_from(int!(-110)).unwrap_err();
4314    }
4315
4316    #[async_test]
4317    async fn test_composer_draft() {
4318        use matrix_sdk_test::DEFAULT_TEST_ROOM_ID;
4319
4320        let client = logged_in_client(None).await;
4321
4322        let response = SyncResponseBuilder::default()
4323            .add_joined_room(JoinedRoomBuilder::default())
4324            .build_sync_response();
4325        client.base_client().receive_sync_response(response).await.unwrap();
4326        let room = client.get_room(&DEFAULT_TEST_ROOM_ID).expect("Room should exist");
4327
4328        assert_eq!(room.load_composer_draft(None).await.unwrap(), None);
4329
4330        // Save 2 drafts, one for the room and one for a thread.
4331
4332        let draft = ComposerDraft {
4333            plain_text: "Hello, world!".to_owned(),
4334            html_text: Some("<strong>Hello</strong>, world!".to_owned()),
4335            draft_type: ComposerDraftType::NewMessage,
4336        };
4337
4338        room.save_composer_draft(draft.clone(), None).await.unwrap();
4339
4340        let thread_root = owned_event_id!("$thread_root:b.c");
4341        let thread_draft = ComposerDraft {
4342            plain_text: "Hello, thread!".to_owned(),
4343            html_text: Some("<strong>Hello</strong>, thread!".to_owned()),
4344            draft_type: ComposerDraftType::NewMessage,
4345        };
4346
4347        room.save_composer_draft(thread_draft.clone(), Some(&thread_root)).await.unwrap();
4348
4349        // Check that the room draft was saved correctly
4350        assert_eq!(room.load_composer_draft(None).await.unwrap(), Some(draft));
4351
4352        // Check that the thread draft was saved correctly
4353        assert_eq!(
4354            room.load_composer_draft(Some(&thread_root)).await.unwrap(),
4355            Some(thread_draft.clone())
4356        );
4357
4358        // Clear the room draft
4359        room.clear_composer_draft(None).await.unwrap();
4360        assert_eq!(room.load_composer_draft(None).await.unwrap(), None);
4361
4362        // Check that the thread one is still there
4363        assert_eq!(room.load_composer_draft(Some(&thread_root)).await.unwrap(), Some(thread_draft));
4364
4365        // Clear the thread draft as well
4366        room.clear_composer_draft(Some(&thread_root)).await.unwrap();
4367        assert_eq!(room.load_composer_draft(Some(&thread_root)).await.unwrap(), None);
4368    }
4369
4370    #[async_test]
4371    async fn test_mark_join_requests_as_seen() {
4372        let server = MatrixMockServer::new().await;
4373        let client = server.client_builder().build().await;
4374        let event_id = event_id!("$a:b.c");
4375        let room_id = room_id!("!a:b.c");
4376        let user_id = user_id!("@alice:b.c");
4377
4378        let f = EventFactory::new().room(room_id);
4379        let joined_room_builder = JoinedRoomBuilder::new(room_id).add_state_bulk(vec![f
4380            .member(user_id)
4381            .membership(MembershipState::Knock)
4382            .event_id(event_id)
4383            .into_raw()]);
4384        let room = server.sync_room(&client, joined_room_builder).await;
4385
4386        // When loading the initial seen ids, there are none
4387        let seen_ids =
4388            room.get_seen_knock_request_ids().await.expect("Couldn't load seen join request ids");
4389        assert!(seen_ids.is_empty());
4390
4391        // We mark a random event id as seen
4392        room.mark_knock_requests_as_seen(&[user_id.to_owned()])
4393            .await
4394            .expect("Couldn't mark join request as seen");
4395
4396        // Then we can check it was successfully marked as seen
4397        let seen_ids =
4398            room.get_seen_knock_request_ids().await.expect("Couldn't load seen join request ids");
4399        assert_eq!(seen_ids.len(), 1);
4400        assert_eq!(
4401            seen_ids.into_iter().next().expect("No next value"),
4402            (event_id.to_owned(), user_id.to_owned())
4403        )
4404    }
4405
4406    #[async_test]
4407    async fn test_own_room_membership_with_no_own_member_event() {
4408        let server = MatrixMockServer::new().await;
4409        let client = server.client_builder().build().await;
4410        let room_id = room_id!("!a:b.c");
4411
4412        let room = server.sync_joined_room(&client, room_id).await;
4413
4414        // Since there is no member event for the own user, the method fails.
4415        // This should never happen in an actual room.
4416        let error = room.member_with_sender_info(client.user_id().unwrap()).await.err();
4417        assert!(error.is_some());
4418    }
4419
4420    #[async_test]
4421    async fn test_own_room_membership_with_own_member_event_but_unknown_sender() {
4422        let server = MatrixMockServer::new().await;
4423        let client = server.client_builder().build().await;
4424        let room_id = room_id!("!a:b.c");
4425        let user_id = user_id!("@example:localhost");
4426
4427        let f = EventFactory::new().room(room_id).sender(user_id!("@alice:b.c"));
4428        let joined_room_builder =
4429            JoinedRoomBuilder::new(room_id).add_state_bulk(vec![f.member(user_id).into_raw()]);
4430        let room = server.sync_room(&client, joined_room_builder).await;
4431
4432        // When we load the membership details
4433        let ret = room
4434            .member_with_sender_info(client.user_id().unwrap())
4435            .await
4436            .expect("Room member info should be available");
4437
4438        // We get the member info for the current user
4439        assert_eq!(ret.room_member.event().user_id(), user_id);
4440
4441        // But there is no info for the sender
4442        assert!(ret.sender_info.is_none());
4443    }
4444
4445    #[async_test]
4446    async fn test_own_room_membership_with_own_member_event_and_own_sender() {
4447        let server = MatrixMockServer::new().await;
4448        let client = server.client_builder().build().await;
4449        let room_id = room_id!("!a:b.c");
4450        let user_id = user_id!("@example:localhost");
4451
4452        let f = EventFactory::new().room(room_id).sender(user_id);
4453        let joined_room_builder =
4454            JoinedRoomBuilder::new(room_id).add_state_bulk(vec![f.member(user_id).into_raw()]);
4455        let room = server.sync_room(&client, joined_room_builder).await;
4456
4457        // When we load the membership details
4458        let ret = room
4459            .member_with_sender_info(client.user_id().unwrap())
4460            .await
4461            .expect("Room member info should be available");
4462
4463        // We get the current user's member info
4464        assert_eq!(ret.room_member.event().user_id(), user_id);
4465
4466        // And the sender has the same info, since it's also the current user
4467        assert!(ret.sender_info.is_some());
4468        assert_eq!(ret.sender_info.unwrap().event().user_id(), user_id);
4469    }
4470
4471    #[async_test]
4472    async fn test_own_room_membership_with_own_member_event_and_known_sender() {
4473        let server = MatrixMockServer::new().await;
4474        let client = server.client_builder().build().await;
4475        let room_id = room_id!("!a:b.c");
4476        let user_id = user_id!("@example:localhost");
4477        let sender_id = user_id!("@alice:b.c");
4478
4479        let f = EventFactory::new().room(room_id).sender(sender_id);
4480        let joined_room_builder = JoinedRoomBuilder::new(room_id).add_state_bulk(vec![
4481            f.member(user_id).into_raw(),
4482            // The sender info comes from the sync
4483            f.member(sender_id).into_raw(),
4484        ]);
4485        let room = server.sync_room(&client, joined_room_builder).await;
4486
4487        // When we load the membership details
4488        let ret = room
4489            .member_with_sender_info(client.user_id().unwrap())
4490            .await
4491            .expect("Room member info should be available");
4492
4493        // We get the current user's member info
4494        assert_eq!(ret.room_member.event().user_id(), user_id);
4495
4496        // And also the sender info from the events received in the sync
4497        assert!(ret.sender_info.is_some());
4498        assert_eq!(ret.sender_info.unwrap().event().user_id(), sender_id);
4499    }
4500
4501    #[async_test]
4502    async fn test_own_room_membership_with_own_member_event_and_unknown_but_available_sender() {
4503        let server = MatrixMockServer::new().await;
4504        let client = server.client_builder().build().await;
4505        let room_id = room_id!("!a:b.c");
4506        let user_id = user_id!("@example:localhost");
4507        let sender_id = user_id!("@alice:b.c");
4508
4509        let f = EventFactory::new().room(room_id).sender(sender_id);
4510        let joined_room_builder =
4511            JoinedRoomBuilder::new(room_id).add_state_bulk(vec![f.member(user_id).into_raw()]);
4512        let room = server.sync_room(&client, joined_room_builder).await;
4513
4514        // We'll receive the member info through the /members endpoint
4515        server
4516            .mock_get_members()
4517            .ok(vec![f.member(sender_id).into_raw()])
4518            .mock_once()
4519            .mount()
4520            .await;
4521
4522        // We get the current user's member info
4523        let ret = room
4524            .member_with_sender_info(client.user_id().unwrap())
4525            .await
4526            .expect("Room member info should be available");
4527
4528        // We get the current user's member info
4529        assert_eq!(ret.room_member.event().user_id(), user_id);
4530
4531        // And also the sender info from the /members endpoint
4532        assert!(ret.sender_info.is_some());
4533        assert_eq!(ret.sender_info.unwrap().event().user_id(), sender_id);
4534    }
4535
4536    #[async_test]
4537    async fn test_list_threads() {
4538        let server = MatrixMockServer::new().await;
4539        let client = server.client_builder().build().await;
4540
4541        let room_id = room_id!("!a:b.c");
4542        let sender_id = user_id!("@alice:b.c");
4543        let f = EventFactory::new().room(room_id).sender(sender_id);
4544
4545        let eid1 = event_id!("$1");
4546        let eid2 = event_id!("$2");
4547        let batch1 = vec![f.text_msg("Thread root 1").event_id(eid1).into_raw()];
4548        let batch2 = vec![f.text_msg("Thread root 2").event_id(eid2).into_raw()];
4549
4550        server
4551            .mock_room_threads()
4552            .ok(batch1.clone(), Some("prev_batch".to_owned()))
4553            .mock_once()
4554            .mount()
4555            .await;
4556        server
4557            .mock_room_threads()
4558            .match_from("prev_batch")
4559            .ok(batch2, None)
4560            .mock_once()
4561            .mount()
4562            .await;
4563
4564        let room = server.sync_joined_room(&client, room_id).await;
4565        let result =
4566            room.list_threads(ListThreadsOptions::default()).await.expect("Failed to list threads");
4567        assert_eq!(result.chunk.len(), 1);
4568        assert_eq!(result.chunk[0].event_id().unwrap(), eid1);
4569        assert!(result.prev_batch_token.is_some());
4570
4571        let opts = ListThreadsOptions { from: result.prev_batch_token, ..Default::default() };
4572        let result = room.list_threads(opts).await.expect("Failed to list threads");
4573        assert_eq!(result.chunk.len(), 1);
4574        assert_eq!(result.chunk[0].event_id().unwrap(), eid2);
4575        assert!(result.prev_batch_token.is_none());
4576    }
4577
4578    #[async_test]
4579    async fn test_relations() {
4580        let server = MatrixMockServer::new().await;
4581        let client = server.client_builder().build().await;
4582
4583        let room_id = room_id!("!a:b.c");
4584        let sender_id = user_id!("@alice:b.c");
4585        let f = EventFactory::new().room(room_id).sender(sender_id);
4586
4587        let target_event_id = owned_event_id!("$target");
4588        let eid1 = event_id!("$1");
4589        let eid2 = event_id!("$2");
4590        let batch1 = vec![f.text_msg("Related event 1").event_id(eid1).into_raw()];
4591        let batch2 = vec![f.text_msg("Related event 2").event_id(eid2).into_raw()];
4592
4593        server
4594            .mock_room_relations()
4595            .match_target_event(target_event_id.clone())
4596            .ok(RoomRelationsResponseTemplate::default().events(batch1).next_batch("next_batch"))
4597            .mock_once()
4598            .mount()
4599            .await;
4600
4601        server
4602            .mock_room_relations()
4603            .match_target_event(target_event_id.clone())
4604            .match_from("next_batch")
4605            .ok(RoomRelationsResponseTemplate::default().events(batch2))
4606            .mock_once()
4607            .mount()
4608            .await;
4609
4610        let room = server.sync_joined_room(&client, room_id).await;
4611
4612        // Main endpoint: no relation type filtered out.
4613        let mut opts = RelationsOptions {
4614            include_relations: IncludeRelations::AllRelations,
4615            ..Default::default()
4616        };
4617        let result = room
4618            .relations(target_event_id.clone(), opts.clone())
4619            .await
4620            .expect("Failed to list relations the first time");
4621        assert_eq!(result.chunk.len(), 1);
4622        assert_eq!(result.chunk[0].event_id().unwrap(), eid1);
4623        assert!(result.prev_batch_token.is_none());
4624        assert!(result.next_batch_token.is_some());
4625        assert!(result.recursion_depth.is_none());
4626
4627        opts.from = result.next_batch_token;
4628        let result = room
4629            .relations(target_event_id, opts)
4630            .await
4631            .expect("Failed to list relations the second time");
4632        assert_eq!(result.chunk.len(), 1);
4633        assert_eq!(result.chunk[0].event_id().unwrap(), eid2);
4634        assert!(result.prev_batch_token.is_none());
4635        assert!(result.next_batch_token.is_none());
4636        assert!(result.recursion_depth.is_none());
4637    }
4638
4639    #[async_test]
4640    async fn test_relations_with_reltype() {
4641        let server = MatrixMockServer::new().await;
4642        let client = server.client_builder().build().await;
4643
4644        let room_id = room_id!("!a:b.c");
4645        let sender_id = user_id!("@alice:b.c");
4646        let f = EventFactory::new().room(room_id).sender(sender_id);
4647
4648        let target_event_id = owned_event_id!("$target");
4649        let eid1 = event_id!("$1");
4650        let eid2 = event_id!("$2");
4651        let batch1 = vec![f.text_msg("In-thread event 1").event_id(eid1).into_raw()];
4652        let batch2 = vec![f.text_msg("In-thread event 2").event_id(eid2).into_raw()];
4653
4654        server
4655            .mock_room_relations()
4656            .match_target_event(target_event_id.clone())
4657            .match_subrequest(IncludeRelations::RelationsOfType(RelationType::Thread))
4658            .ok(RoomRelationsResponseTemplate::default().events(batch1).next_batch("next_batch"))
4659            .mock_once()
4660            .mount()
4661            .await;
4662
4663        server
4664            .mock_room_relations()
4665            .match_target_event(target_event_id.clone())
4666            .match_from("next_batch")
4667            .match_subrequest(IncludeRelations::RelationsOfType(RelationType::Thread))
4668            .ok(RoomRelationsResponseTemplate::default().events(batch2))
4669            .mock_once()
4670            .mount()
4671            .await;
4672
4673        let room = server.sync_joined_room(&client, room_id).await;
4674
4675        // Reltype-filtered endpoint, for threads \o/
4676        let mut opts = RelationsOptions {
4677            include_relations: IncludeRelations::RelationsOfType(RelationType::Thread),
4678            ..Default::default()
4679        };
4680        let result = room
4681            .relations(target_event_id.clone(), opts.clone())
4682            .await
4683            .expect("Failed to list relations the first time");
4684        assert_eq!(result.chunk.len(), 1);
4685        assert_eq!(result.chunk[0].event_id().unwrap(), eid1);
4686        assert!(result.prev_batch_token.is_none());
4687        assert!(result.next_batch_token.is_some());
4688        assert!(result.recursion_depth.is_none());
4689
4690        opts.from = result.next_batch_token;
4691        let result = room
4692            .relations(target_event_id, opts)
4693            .await
4694            .expect("Failed to list relations the second time");
4695        assert_eq!(result.chunk.len(), 1);
4696        assert_eq!(result.chunk[0].event_id().unwrap(), eid2);
4697        assert!(result.prev_batch_token.is_none());
4698        assert!(result.next_batch_token.is_none());
4699        assert!(result.recursion_depth.is_none());
4700    }
4701
4702    #[async_test]
4703    async fn test_power_levels_computation() {
4704        let server = MatrixMockServer::new().await;
4705        let client = server.client_builder().build().await;
4706
4707        let room_id = room_id!("!a:b.c");
4708        let sender_id = client.user_id().expect("No session id");
4709        let f = EventFactory::new().room(room_id).sender(sender_id);
4710        let mut user_map = BTreeMap::from([(sender_id.into(), 50.into())]);
4711
4712        // Computing the power levels will need these 3 state events:
4713        let room_create_event = f.create(sender_id, RoomVersionId::V1).state_key("").into_raw();
4714        let power_levels_event = f.power_levels(&mut user_map).state_key("").into_raw();
4715        let room_member_event = f.member(sender_id).into_raw();
4716
4717        // With only the room member event
4718        let room = server
4719            .sync_room(
4720                &client,
4721                JoinedRoomBuilder::new(room_id).add_state_bulk([room_member_event.clone()]),
4722            )
4723            .await;
4724        let ctx = room
4725            .push_condition_room_ctx()
4726            .await
4727            .expect("Failed to get push condition context")
4728            .expect("Could not get push condition context");
4729
4730        // The internal power levels couldn't be computed
4731        assert!(ctx.power_levels.is_none());
4732
4733        // Adding the room creation event
4734        let room = server
4735            .sync_room(
4736                &client,
4737                JoinedRoomBuilder::new(room_id).add_state_bulk([room_create_event.clone()]),
4738            )
4739            .await;
4740        let ctx = room
4741            .push_condition_room_ctx()
4742            .await
4743            .expect("Failed to get push condition context")
4744            .expect("Could not get push condition context");
4745
4746        // The internal power levels still couldn't be computed
4747        assert!(ctx.power_levels.is_none());
4748
4749        // With the room member, room creation and the power levels events
4750        let room = server
4751            .sync_room(
4752                &client,
4753                JoinedRoomBuilder::new(room_id).add_state_bulk([power_levels_event]),
4754            )
4755            .await;
4756        let ctx = room
4757            .push_condition_room_ctx()
4758            .await
4759            .expect("Failed to get push condition context")
4760            .expect("Could not get push condition context");
4761
4762        // The internal power levels can finally be computed
4763        assert!(ctx.power_levels.is_some());
4764    }
4765}