Skip to main content

matrix_sdk/room/
mod.rs

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