Skip to main content

matrix_sdk/room/
mod.rs

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