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