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