Skip to main content

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