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