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