1use 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 = "unstable-msc4536")]
64use ruma::api::FeatureFlag;
65#[cfg(feature = "e2e-encryption")]
66use ruma::events::AnySyncMessageLikeEvent;
67#[cfg(feature = "experimental-encrypted-state-events")]
68use ruma::events::AnySyncStateEvent;
69#[cfg(feature = "unstable-msc4274")]
70use ruma::events::room::message::GalleryItemType;
71#[cfg(feature = "e2e-encryption")]
72use ruma::events::{
73 AnySyncTimelineEvent, SyncMessageLikeEvent, room::encrypted::OriginalSyncRoomEncryptedEvent,
74};
75use ruma::{
76 EventId, Int, MatrixToUri, MatrixUri, MxcUri, OwnedEventId, OwnedRoomId, OwnedServerName,
77 OwnedTransactionId, OwnedUserId, RoomId, TransactionId, UInt, UserId,
78 api::{
79 client::{
80 config::{set_global_account_data, set_room_account_data},
81 context,
82 filter::LazyLoadOptions,
83 membership::{
84 Invite3pid, ban_user, forget_room, get_member_events,
85 invite_user::{
86 self,
87 v3::{InvitationRecipient, InviteUserId},
88 },
89 kick_user, leave_room, unban_user,
90 },
91 message::send_message_event,
92 read_marker::set_read_marker,
93 receipt::create_receipt,
94 redact::redact_event,
95 retention::get_retention_configuration,
96 room::{get_room_event, report_content, report_room},
97 state::{get_state_event_for_key, send_state_event},
98 tag::{create_tag, delete_tag},
99 threads::{get_thread_subscription, subscribe_thread, unsubscribe_thread},
100 typing::create_typing_event::{
101 self,
102 v3::{Typing, TypingInfo},
103 },
104 },
105 error::ErrorKind,
106 },
107 assign,
108 events::{
109 AnyRoomAccountDataEvent, AnyRoomAccountDataEventContent, AnyTimelineEvent, EmptyStateKey,
110 Mentions, MessageLikeEventContent, OriginalSyncStateEvent, RedactContent,
111 RedactedStateEventContent, RoomAccountDataEvent, RoomAccountDataEventContent,
112 RoomAccountDataEventType, StateEventContent, StateEventType, StaticEventContent,
113 StaticStateEventContent, SyncStateEvent,
114 beacon::BeaconEventContent,
115 beacon_info::BeaconInfoEventContent,
116 direct::DirectEventContent,
117 marked_unread::MarkedUnreadEventContent,
118 receipt::{Receipt, ReceiptThread, ReceiptType},
119 relation::RelationType,
120 room::{
121 ImageInfo, MediaSource, ThumbnailInfo,
122 avatar::{self, RoomAvatarEventContent},
123 encryption::PossiblyRedactedRoomEncryptionEventContent,
124 history_visibility::HistoryVisibility,
125 member::{MembershipChange, RoomMemberEventContent, SyncRoomMemberEvent},
126 message::{
127 AudioInfo, AudioMessageEventContent, FileInfo, FileMessageEventContent,
128 ImageMessageEventContent, MessageType, RoomMessageEventContent,
129 TextMessageEventContent, UnstableAmplitude, UnstableAudioDetailsContentBlock,
130 UnstableVoiceContentBlock, VideoInfo, VideoMessageEventContent,
131 },
132 name::RoomNameEventContent,
133 pinned_events::RoomPinnedEventsEventContent,
134 power_levels::{
135 RoomPowerLevels, RoomPowerLevelsEventContent, RoomPowerLevelsSource, UserPowerLevel,
136 },
137 retention::RoomRetentionEventContent,
138 server_acl::RoomServerAclEventContent,
139 topic::RoomTopicEventContent,
140 },
141 space::{child::SpaceChildEventContent, parent::SpaceParentEventContent},
142 tag::{TagInfo, TagName},
143 typing::SyncTypingEvent,
144 },
145 int,
146 push::{Action, AnyPushRuleRef, PushConditionRoomCtx, Ruleset},
147 serde::Raw,
148 time::Instant,
149 uint,
150};
151#[cfg(feature = "experimental-encrypted-state-events")]
152use ruma::{
153 events::room::encrypted::unstable_state::OriginalSyncStateRoomEncryptedEvent,
154 serde::JsonCastable,
155};
156use serde::de::DeserializeOwned;
157use thiserror::Error;
158use tokio::{join, sync::broadcast};
159use tracing::{debug, error, info, instrument, trace, warn};
160
161use self::futures::{
162 SendAttachment, SendMessageLikeEvent, SendRawMessageLikeEvent, SendRawStateEvent,
163};
164pub use self::{
165 member::{RoomMember, RoomMemberRole},
166 messages::{
167 EventWithContextResponse, IncludeRelations, ListThreadsOptions, Messages, MessagesOptions,
168 Relations, RelationsOptions, ThreadRoots,
169 },
170};
171#[cfg(feature = "e2e-encryption")]
172use crate::encryption::backups::BackupState;
173#[cfg(doc)]
174use crate::event_cache::EventCache;
175#[cfg(feature = "experimental-encrypted-state-events")]
176use crate::room::futures::SendStateEvent;
177use crate::{
178 BaseRoom, Client, Error, HttpResult, Result, RoomState, TransmissionProgress,
179 attachment::{AttachmentConfig, AttachmentInfo},
180 client::WeakClient,
181 config::RequestConfig,
182 error::{BeaconError, WrongRoomState},
183 event_handler::{EventHandler, EventHandlerDropGuard, EventHandlerHandle, SyncEvent},
184 live_locations_observer::LiveLocationsObserver,
185 media::{MediaFormat, MediaRequestParameters},
186 notification_settings::{IsEncrypted, IsOneToOne, RoomNotificationMode},
187 room::{
188 knock_requests::{KnockRequest, KnockRequestMemberInfo},
189 power_levels::{RoomPowerLevelChanges, RoomPowerLevelsExt},
190 privacy_settings::RoomPrivacySettings,
191 },
192 sync::{RoomUpdate, State},
193 utils::{IntoRawMessageLikeEventContent, IntoRawStateEventContent},
194};
195
196pub mod edit;
197pub mod futures;
198pub mod identity_status_changes;
199pub mod knock_requests;
201mod member;
202mod messages;
203pub mod power_levels;
204pub mod reply;
205
206pub mod calls;
207
208pub mod privacy_settings;
210
211#[cfg(feature = "e2e-encryption")]
212pub(crate) mod shared_room_history;
213
214#[derive(Debug, Clone)]
217pub struct Room {
218 inner: BaseRoom,
219 pub(crate) client: Client,
220}
221
222impl Deref for Room {
223 type Target = BaseRoom;
224
225 fn deref(&self) -> &Self::Target {
226 &self.inner
227 }
228}
229
230const TYPING_NOTICE_TIMEOUT: Duration = Duration::from_secs(4);
231const TYPING_NOTICE_RESEND_TIMEOUT: Duration = Duration::from_secs(3);
232
233#[derive(Debug, Clone, Copy, PartialEq, Eq)]
235pub struct ThreadSubscription {
236 pub automatic: bool,
239}
240
241#[derive(Debug)]
243pub struct PushContext {
244 push_condition_room_ctx: PushConditionRoomCtx,
246
247 push_rules: Ruleset,
250}
251
252impl PushContext {
253 pub fn new(push_condition_room_ctx: PushConditionRoomCtx, push_rules: Ruleset) -> Self {
255 Self { push_condition_room_ctx, push_rules }
256 }
257
258 pub async fn for_event<T>(&self, event: &Raw<T>) -> Vec<Action> {
260 self.push_rules.get_actions(event, &self.push_condition_room_ctx).await.to_owned()
261 }
262
263 #[doc(hidden)]
266 #[instrument(skip_all)]
267 pub async fn traced_for_event<T>(&self, event: &Raw<T>) -> Vec<Action> {
268 let rules = self
269 .push_rules
270 .iter()
271 .filter_map(|r| {
272 if !r.enabled() {
273 return None;
274 }
275
276 let simplified_action = if r.actions().is_empty() { "inhibit" } else { "notify" };
277
278 let conditions = match r {
279 AnyPushRuleRef::Override(r) => {
280 format!("{:?}", r.conditions)
281 }
282 AnyPushRuleRef::Content(r) => format!("content-body-match:{}", r.pattern),
283 AnyPushRuleRef::Room(r) => format!("room-match:{}", r.rule_id),
284 AnyPushRuleRef::Sender(r) => format!("sender-match:{}", r.rule_id),
285 AnyPushRuleRef::Underride(r) => format!("{:?}", r.conditions),
286 _ => "<unknown push rule kind>".to_owned(),
287 };
288
289 Some(format!("- {}: {conditions} => {simplified_action}", r.rule_id(),))
290 })
291 .collect::<Vec<_>>()
292 .join("\n");
293 trace!("rules:\n\n{rules}\n\n");
294
295 let found = self.push_rules.get_match(event, &self.push_condition_room_ctx).await;
296
297 if let Some(found) = found {
298 trace!("rule {} matched", found.rule_id());
299 found.actions().to_owned()
300 } else {
301 trace!("no match");
302 Vec::new()
303 }
304 }
305}
306
307macro_rules! make_media_type {
308 ($t:ty, $content_type: ident, $filename: ident, $source: ident, $caption: ident, $info: ident, $thumbnail: ident) => {{
309 let (body, formatted, filename) = match $caption {
313 Some(TextMessageEventContent { body, formatted, .. }) => (body, formatted, Some($filename)),
314 None => ($filename, None, None),
315 };
316
317 let (thumbnail_source, thumbnail_info) = $thumbnail.unzip();
318
319 match $content_type.type_() {
320 mime::IMAGE => {
321 let info = assign!($info.map(ImageInfo::from).unwrap_or_default(), {
322 mimetype: Some($content_type.as_ref().to_owned()),
323 thumbnail_source,
324 thumbnail_info
325 });
326 let content = assign!(ImageMessageEventContent::new(body, $source), {
327 info: Some(Box::new(info)),
328 formatted,
329 filename
330 });
331 <$t>::Image(content)
332 }
333
334 mime::AUDIO => {
335 let mut content = assign!(AudioMessageEventContent::new(body, $source), {
336 formatted,
337 filename
338 });
339
340 if let Some(AttachmentInfo::Audio(audio_info) | AttachmentInfo::Voice(audio_info)) = &$info &&
341 let Some(duration) = audio_info.duration && let Some(waveform_vec) = &audio_info.waveform {
342 let waveform = waveform_vec
343 .iter()
344 .map(|v| ((*v).clamp(0.0, 1.0) * UnstableAmplitude::MAX as f32) as u16)
345 .map(Into::into)
346 .collect();
347 content.audio =
348 Some(UnstableAudioDetailsContentBlock::new(duration, waveform));
349 }
350
351 if matches!($info, Some(AttachmentInfo::Voice(_))) {
352 content.voice = Some(UnstableVoiceContentBlock::new());
353 }
354
355 let mut audio_info = $info.map(AudioInfo::from).unwrap_or_default();
356 audio_info.mimetype = Some($content_type.as_ref().to_owned());
357 let content = content.info(Box::new(audio_info));
358
359 <$t>::Audio(content)
360 }
361
362 mime::VIDEO => {
363 #[cfg(feature = "unstable-msc4546")]
364 let circle = match &$info {
365 Some(AttachmentInfo::Video(video_info)) => video_info.circle,
366 _ => false,
367 };
368 let info = assign!($info.map(VideoInfo::from).unwrap_or_default(), {
369 mimetype: Some($content_type.as_ref().to_owned()),
370 thumbnail_source,
371 thumbnail_info
372 });
373 let content = assign!(VideoMessageEventContent::new(body, $source), {
374 info: Some(Box::new(info)),
375 formatted,
376 filename,
377 });
378 #[cfg(feature = "unstable-msc4546")]
379 let content = content.circle(circle);
380 <$t>::Video(content)
381 }
382
383 _ => {
384 let info = assign!($info.map(FileInfo::from).unwrap_or_default(), {
385 mimetype: Some($content_type.as_ref().to_owned()),
386 thumbnail_source,
387 thumbnail_info
388 });
389 let content = assign!(FileMessageEventContent::new(body, $source), {
390 info: Some(Box::new(info)),
391 formatted,
392 filename,
393 });
394 <$t>::File(content)
395 }
396 }
397 }};
398}
399
400impl Room {
401 pub(crate) fn new(client: Client, room: BaseRoom) -> Self {
408 Self { inner: room, client }
409 }
410
411 #[doc(alias = "reject_invitation")]
416 #[instrument(skip_all, fields(room_id = ?self.inner.room_id()))]
417 async fn leave_impl(&self) -> (Result<()>, &Room) {
418 let state = self.state();
419 if state == RoomState::Left {
420 return (
421 Err(Error::WrongRoomState(Box::new(WrongRoomState::new(
422 "Joined or Invited",
423 state,
424 )))),
425 self,
426 );
427 }
428
429 let should_forget = matches!(self.state(), RoomState::Invited);
432
433 let request = leave_room::v3::Request::new(self.inner.room_id().to_owned());
434 let response = self.client.send(request).await;
435
436 if let Err(error) = response {
439 #[allow(clippy::collapsible_match)]
440 let ignore_error = if let Some(error) = error.client_api_error_kind() {
441 match error {
442 ErrorKind::Forbidden => true,
446 _ => false,
447 }
448 } else {
449 false
450 };
451
452 error!(?error, ignore_error, should_forget, "Failed to leave the room");
453
454 if !ignore_error {
455 return (Err(error.into()), self);
456 }
457 }
458
459 if let Err(e) = self.client.base_client().room_left(self.room_id()).await {
460 return (Err(e.into()), self);
461 }
462
463 if should_forget {
464 trace!("Trying to forget the room");
465
466 if let Err(error) = self.forget().await {
467 error!(?error, "Failed to forget the room");
468 }
469 }
470
471 (Ok(()), self)
472 }
473
474 pub async fn leave(&self) -> Result<()> {
481 let mut rooms: Vec<Room> = vec![self.clone()];
482 let mut current_room = self;
483
484 while let Some(predecessor) = current_room.predecessor_room() {
485 let maybe_predecessor_room = current_room.client.get_room(&predecessor.room_id);
486
487 if let Some(predecessor_room) = maybe_predecessor_room {
488 rooms.push(predecessor_room);
489 current_room = rooms.last().expect("Room just pushed so can't be empty");
490 } else {
491 warn!("Cannot find predecessor room");
492 break;
493 }
494 }
495
496 let batch_size = 5;
497
498 let rooms_futures: Vec<_> = rooms
499 .iter()
500 .filter_map(|room| match room.state() {
501 RoomState::Joined | RoomState::Invited | RoomState::Knocked => {
502 Some(room.leave_impl())
503 }
504 RoomState::Banned | RoomState::Left => None,
505 })
506 .collect();
507
508 let mut futures_stream = futures_stream::iter(rooms_futures).buffer_unordered(batch_size);
509
510 let mut maybe_this_room_failed_with: Option<Error> = None;
511
512 while let Some(result) = futures_stream.next().await {
513 if let (Err(e), room) = result {
514 if room.room_id() == self.room_id() {
515 maybe_this_room_failed_with = Some(e);
516 } else {
517 warn!("Failure while attempting to leave predecessor room: {e:?}");
518 }
519 }
520 }
521
522 maybe_this_room_failed_with.map_or(Ok(()), Err)
523 }
524
525 #[doc(alias = "accept_invitation")]
529 pub async fn join(&self) -> Result<()> {
530 let prev_room_state = self.inner.state();
531
532 if prev_room_state == RoomState::Joined {
533 return Err(Error::WrongRoomState(Box::new(WrongRoomState::new(
534 "Invited or Left",
535 prev_room_state,
536 ))));
537 }
538
539 self.client.join_room_by_id(self.room_id()).await?;
540
541 Ok(())
542 }
543
544 pub fn client(&self) -> Client {
548 self.client.clone()
549 }
550
551 pub fn is_synced(&self) -> bool {
554 self.inner.is_state_fully_synced()
555 }
556
557 pub async fn avatar(&self, format: MediaFormat) -> Result<Option<Vec<u8>>> {
586 let Some(url) = self.avatar_url() else { return Ok(None) };
587 let request = MediaRequestParameters { source: MediaSource::Plain(url.to_owned()), format };
588 Ok(Some(self.client.media().get_media_content(&request, true).await?))
589 }
590
591 #[instrument(skip_all, fields(room_id = ?self.inner.room_id(), ?options))]
620 pub async fn messages(&self, options: MessagesOptions) -> Result<Messages> {
621 let room_id = self.inner.room_id();
622 let request = options.into_request(room_id);
623 let http_response = self.client.send(request).await?;
624
625 let push_ctx = self.push_context().await?;
626 let chunk = join_all(
627 http_response.chunk.into_iter().map(|ev| self.try_decrypt_event(ev, push_ctx.as_ref())),
628 )
629 .await;
630
631 Ok(Messages {
632 start: http_response.start,
633 end: http_response.end,
634 chunk,
635 state: http_response.state,
636 })
637 }
638
639 pub fn add_event_handler<Ev, Ctx, H>(&self, handler: H) -> EventHandlerHandle
649 where
650 Ev: SyncEvent + DeserializeOwned + Send + 'static,
651 H: EventHandler<Ev, Ctx>,
652 {
653 self.client.add_room_event_handler(self.room_id(), handler)
654 }
655
656 pub fn subscribe_to_updates(&self) -> broadcast::Receiver<RoomUpdate> {
661 self.client.subscribe_to_room_updates(self.room_id())
662 }
663
664 pub fn subscribe_to_typing_notifications(
670 &self,
671 ) -> (EventHandlerDropGuard, broadcast::Receiver<Vec<OwnedUserId>>) {
672 let (sender, receiver) = broadcast::channel(16);
673 let typing_event_handler_handle = self.client.add_room_event_handler(self.room_id(), {
674 let own_user_id = self.own_user_id().to_owned();
675 move |event: SyncTypingEvent| async move {
676 let typing_user_ids = event
678 .content
679 .user_ids
680 .into_iter()
681 .filter(|user_id| *user_id != own_user_id)
682 .collect();
683 let _ = sender.send(typing_user_ids);
686 }
687 });
688 let drop_guard = self.client().event_handler_drop_guard(typing_event_handler_handle);
689 (drop_guard, receiver)
690 }
691
692 pub fn subscribe_to_state_events(
703 &self,
704 event_type: StateEventType,
705 ) -> impl Stream<Item = Result<Vec<RawAnySyncOrStrippedState>>> + use<> {
706 let room = self.clone();
707 let mut room_updates = self.subscribe_to_updates();
708
709 stream! {
710 yield room.get_state_events(event_type.clone()).await;
715
716 loop {
717 match room_updates.recv().await {
718 Ok(update) => {
719 if !has_state_events_of_type(&update, &event_type) {
720 continue;
721 }
722 }
723 Err(broadcast::error::RecvError::Lagged(_)) => {}
727 Err(broadcast::error::RecvError::Closed) => break,
728 }
729
730 yield room.get_state_events(event_type.clone()).await;
731 }
732 }
733 }
734
735 #[cfg(feature = "e2e-encryption")]
758 pub async fn subscribe_to_identity_status_changes(
759 &self,
760 ) -> Result<impl Stream<Item = Vec<IdentityStatusChange>> + use<>> {
761 IdentityStatusChanges::create_stream(self.clone()).await
762 }
763
764 pub async fn live_locations_observer(&self) -> LiveLocationsObserver {
772 LiveLocationsObserver::new(self.clone()).await
773 }
774
775 #[cfg(not(feature = "experimental-encrypted-state-events"))]
780 #[allow(clippy::unused_async)] async fn try_decrypt_event(
782 &self,
783 event: Raw<AnyTimelineEvent>,
784 push_ctx: Option<&PushContext>,
785 ) -> TimelineEvent {
786 #[cfg(feature = "e2e-encryption")]
787 if let Ok(AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomEncrypted(
788 SyncMessageLikeEvent::Original(_),
789 ))) = event.deserialize_as::<AnySyncTimelineEvent>()
790 && let Ok(event) = self.decrypt_event(event.cast_ref_unchecked(), push_ctx).await
791 {
792 return event;
793 }
794
795 let mut event = TimelineEvent::from_plaintext(event.cast());
796 if let Some(push_ctx) = push_ctx {
797 event.set_push_actions(push_ctx.for_event(event.raw()).await);
798 }
799
800 event
801 }
802
803 #[cfg(feature = "experimental-encrypted-state-events")]
808 #[allow(clippy::unused_async)] async fn try_decrypt_event(
810 &self,
811 event: Raw<AnyTimelineEvent>,
812 push_ctx: Option<&PushContext>,
813 ) -> TimelineEvent {
814 match event.deserialize_as::<AnySyncTimelineEvent>() {
817 Ok(AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomEncrypted(
818 SyncMessageLikeEvent::Original(_),
819 ))) => {
820 if let Ok(event) = self
821 .decrypt_event(
822 event.cast_ref_unchecked::<OriginalSyncRoomEncryptedEvent>(),
823 push_ctx,
824 )
825 .await
826 {
827 return event;
828 }
829 }
830 Ok(AnySyncTimelineEvent::State(AnySyncStateEvent::RoomEncrypted(
831 SyncStateEvent::Original(_),
832 ))) => {
833 if let Ok(event) = self
834 .decrypt_event(
835 event.cast_ref_unchecked::<OriginalSyncStateRoomEncryptedEvent>(),
836 push_ctx,
837 )
838 .await
839 {
840 return event;
841 }
842 }
843 _ => {}
844 }
845
846 let mut event = TimelineEvent::from_plaintext(event.cast());
847 if let Some(push_ctx) = push_ctx {
848 event.set_push_actions(push_ctx.for_event(event.raw()).await);
849 }
850
851 event
852 }
853
854 pub async fn event(
859 &self,
860 event_id: &EventId,
861 request_config: Option<RequestConfig>,
862 ) -> Result<TimelineEvent> {
863 let request =
864 get_room_event::v3::Request::new(self.room_id().to_owned(), event_id.to_owned());
865
866 let raw_event = self.client.send(request).with_request_config(request_config).await?.event;
867 let push_ctx = self.push_context().await?;
868 let event = self.try_decrypt_event(raw_event, push_ctx.as_ref()).await;
869
870 Ok(event)
871 }
872
873 pub async fn load_or_fetch_event(
879 &self,
880 event_id: &EventId,
881 request_config: Option<RequestConfig>,
882 ) -> Result<TimelineEvent> {
883 if let Some(event_cache) = self.client.event_cache() {
884 match event_cache.room(self.room_id()).await {
885 Ok((room_event_cache, _drop_handles)) => {
886 if let Some(event) = room_event_cache.find_event(event_id).await? {
887 return Ok(event);
888 }
889 }
891 Err(err) => {
892 debug!("error when getting the event cache: {err}");
893 }
894 }
895 }
896
897 self.event(event_id, request_config).await
898
899 }
913
914 pub async fn load_or_fetch_event_with_relations(
931 &self,
932 event_id: &EventId,
933 filter: Option<Vec<RelationType>>,
934 request_config: Option<RequestConfig>,
935 ) -> Result<(TimelineEvent, Vec<TimelineEvent>)> {
936 let fetch_relations = async || {
937 let include_relations = if let Some(filter) = &filter
946 && filter.len() == 1
947 {
948 IncludeRelations::RelationsOfType(filter[0].clone())
949 } else {
950 IncludeRelations::AllRelations
951 };
952
953 let mut opts = RelationsOptions {
954 include_relations,
955 recurse: true,
956 limit: Some(uint!(256)),
957 ..Default::default()
958 };
959
960 let mut events = Vec::new();
961 loop {
962 match self.relations(event_id.to_owned(), opts.clone()).await {
963 Ok(relations) => {
964 if let Some(filter) = filter.as_ref() {
965 events.extend(relations.chunk.into_iter().filter_map(|ev| {
968 let (rel_type, _) = extract_relation(ev.raw())?;
969 filter
970 .iter()
971 .any(|ruma_filter| ruma_filter == &rel_type)
972 .then_some(ev)
973 }));
974 } else {
975 events.extend(relations.chunk);
977 }
978
979 if let Some(next_from) = relations.next_batch_token {
980 opts.from = Some(next_from);
981 } else {
982 break events;
983 }
984 }
985
986 Err(err) => {
987 warn!(%event_id, "error when loading relations of pinned event from server: {err}");
988 break events;
989 }
990 }
991 }
992 };
993
994 let room_event_cache = match self.client.event_cache() {
997 Some(event_cache) => {
998 match event_cache.room(self.room_id()).await {
999 Ok((event_cache, drop_handles)) => {
1000 if let Some((event, mut relations)) =
1001 event_cache.find_event_with_relations(event_id, filter.clone()).await?
1002 {
1003 if relations.is_empty() {
1004 relations = fetch_relations().await;
1009 }
1010
1011 return Ok((event, relations));
1012 }
1013
1014 Some((event_cache, drop_handles))
1016 }
1017
1018 Err(err) => {
1019 debug!("error when getting the event cache: {err}");
1020 None
1022 }
1023 }
1024 }
1025
1026 None => None,
1027 };
1028
1029 let event = self.event(event_id, request_config).await?;
1032
1033 if let Some((event_cache, _drop_handles)) = room_event_cache
1039 && let Some(relations) =
1040 event_cache.find_event_relations(event_id, filter.clone()).await.ok()
1041 && !relations.is_empty()
1042 {
1043 return Ok((event, relations));
1044 }
1045
1046 Ok((event, fetch_relations().await))
1049 }
1050
1051 pub async fn event_with_context(
1054 &self,
1055 event_id: &EventId,
1056 lazy_load_members: bool,
1057 context_size: UInt,
1058 request_config: Option<RequestConfig>,
1059 ) -> Result<EventWithContextResponse> {
1060 let mut request =
1061 context::get_context::v3::Request::new(self.room_id().to_owned(), event_id.to_owned());
1062
1063 request.limit = context_size;
1064
1065 if lazy_load_members {
1066 request.filter.lazy_load_options =
1067 LazyLoadOptions::Enabled { include_redundant_members: false };
1068 }
1069
1070 let response = self.client.send(request).with_request_config(request_config).await?;
1071
1072 let push_ctx = self.push_context().await?;
1073 let push_ctx = push_ctx.as_ref();
1074 let target_event = if let Some(event) = response.event {
1075 Some(self.try_decrypt_event(event, push_ctx).await)
1076 } else {
1077 None
1078 };
1079
1080 let (events_before, events_after) = join!(
1084 join_all(
1085 response.events_before.into_iter().map(|ev| self.try_decrypt_event(ev, push_ctx)),
1086 ),
1087 join_all(
1088 response.events_after.into_iter().map(|ev| self.try_decrypt_event(ev, push_ctx)),
1089 ),
1090 );
1091
1092 Ok(EventWithContextResponse {
1093 event: target_event,
1094 events_before,
1095 events_after,
1096 state: response.state,
1097 prev_batch_token: response.start,
1098 next_batch_token: response.end,
1099 })
1100 }
1101
1102 pub(crate) async fn request_members(&self) -> Result<()> {
1103 self.client
1104 .locks()
1105 .members_request_deduplicated_handler
1106 .run(self.room_id().to_owned(), async move {
1107 let request = get_member_events::v3::Request::new(self.inner.room_id().to_owned());
1108 let response = self
1109 .client
1110 .send(request.clone())
1111 .with_request_config(
1112 RequestConfig::new().timeout(Duration::from_secs(60)).retry_limit(3),
1115 )
1116 .await?;
1117
1118 Box::pin(self.client.base_client().receive_all_members(
1121 self.room_id(),
1122 &request,
1123 &response,
1124 ))
1125 .await?;
1126
1127 Ok(())
1128 })
1129 .await
1130 }
1131
1132 pub async fn request_encryption_state(&self) -> Result<()> {
1137 if !self.inner.encryption_state().is_unknown() {
1138 return Ok(());
1139 }
1140
1141 self.client
1142 .locks()
1143 .encryption_state_deduplicated_handler
1144 .run(self.room_id().to_owned(), async move {
1145 let request = get_state_event_for_key::v3::Request::new(
1147 self.room_id().to_owned(),
1148 StateEventType::RoomEncryption,
1149 "".to_owned(),
1150 );
1151 let response = match self.client.send(request).await {
1152 Ok(response) => Some(
1153 response
1154 .into_content()
1155 .deserialize_as_unchecked::<PossiblyRedactedRoomEncryptionEventContent>(
1156 )?,
1157 ),
1158 Err(err) if err.client_api_error_kind() == Some(&ErrorKind::NotFound) => None,
1159 Err(err) => return Err(err.into()),
1160 };
1161
1162 self.update_and_save_room_info(|mut room_info| {
1165 room_info.mark_encryption_state_synced();
1166 room_info.set_encryption_event(response);
1167 (room_info, RoomInfoNotableUpdateReasons::empty())
1168 })
1169 .await?;
1170
1171 Ok(())
1172 })
1173 .await
1174 }
1175
1176 pub fn encryption_state(&self) -> EncryptionState {
1181 self.inner.encryption_state()
1182 }
1183
1184 pub async fn latest_encryption_state(&self) -> Result<EncryptionState> {
1190 self.request_encryption_state().await?;
1191
1192 Ok(self.encryption_state())
1193 }
1194
1195 #[cfg(feature = "e2e-encryption")]
1197 pub async fn crypto_context_info(&self) -> CryptoContextInfo {
1198 let encryption = self.client.encryption();
1199
1200 let this_device_is_verified = match encryption.get_own_device().await {
1201 Ok(Some(device)) => device.is_verified_with_cross_signing(),
1202
1203 _ => true,
1205 };
1206
1207 let backup_exists_on_server =
1208 encryption.backups().exists_on_server().await.unwrap_or(false);
1209
1210 CryptoContextInfo {
1211 device_creation_ts: encryption.device_creation_timestamp().await,
1212 this_device_is_verified,
1213 is_backup_configured: encryption.backups().state() == BackupState::Enabled,
1214 backup_exists_on_server,
1215 }
1216 }
1217
1218 fn are_events_visible(&self) -> bool {
1219 if let RoomState::Invited = self.inner.state() {
1220 return matches!(
1221 self.inner.history_visibility_or_default(),
1222 HistoryVisibility::WorldReadable | HistoryVisibility::Invited
1223 );
1224 }
1225
1226 true
1227 }
1228
1229 pub async fn sync_members(&self) -> Result<()> {
1235 if !self.are_events_visible() {
1236 return Ok(());
1237 }
1238
1239 if !self.are_members_synced() {
1240 self.request_members().await?;
1241
1242 self.update_active_service_members().await?;
1244
1245 Ok(())
1246 } else {
1247 Ok(())
1248 }
1249 }
1250
1251 pub async fn get_member(&self, user_id: &UserId) -> Result<Option<RoomMember>> {
1265 self.sync_members().await?;
1266 self.get_member_no_sync(user_id).await
1267 }
1268
1269 pub async fn get_member_no_sync(&self, user_id: &UserId) -> Result<Option<RoomMember>> {
1283 Ok(self
1284 .inner
1285 .get_member(user_id)
1286 .await?
1287 .map(|member| RoomMember::new(self.client.clone(), member)))
1288 }
1289
1290 pub async fn members(&self, memberships: RoomMemberships) -> Result<Vec<RoomMember>> {
1299 self.sync_members().await?;
1300 self.members_no_sync(memberships).await
1301 }
1302
1303 pub async fn members_no_sync(&self, memberships: RoomMemberships) -> Result<Vec<RoomMember>> {
1312 Ok(self
1313 .inner
1314 .members(memberships)
1315 .await?
1316 .into_iter()
1317 .map(|member| RoomMember::new(self.client.clone(), member))
1318 .collect())
1319 }
1320
1321 #[cfg(feature = "unstable-msc4536")]
1336 pub async fn fetch_missing_global_profiles_in_background(&self) -> Result<()> {
1337 if self.state() != RoomState::Joined {
1338 return Ok(());
1339 }
1340
1341 if !self.client.is_global_profile_sync_enabled().await? {
1342 return Ok(());
1343 }
1344
1345 if !self.client.unstable_features().await?.contains(&FeatureFlag::Msc4536) {
1346 debug!("The homeserver doesn't support fetching multiple profiles");
1347 return Ok(());
1348 }
1349
1350 let user_ids = self.inner.joined_user_ids().await?;
1351 self.client.global_profile_fetcher().enqueue(user_ids);
1352
1353 Ok(())
1354 }
1355
1356 pub async fn human_member_ids(&self, memberships: RoomMemberships) -> Result<Vec<OwnedUserId>> {
1360 self.sync_members().await?;
1361 self.human_member_ids_no_sync(memberships).await
1362 }
1363
1364 pub async fn human_member_ids_no_sync(
1367 &self,
1368 memberships: RoomMemberships,
1369 ) -> Result<Vec<OwnedUserId>> {
1370 Ok(self.inner.human_member_ids(memberships).await?)
1371 }
1372
1373 pub async fn set_own_member_display_name(
1378 &self,
1379 display_name: Option<String>,
1380 ) -> Result<send_state_event::v3::Response> {
1381 let user_id = self.own_user_id();
1382 let member_event =
1383 self.get_state_event_static_for_key::<RoomMemberEventContent, _>(user_id).await?;
1384
1385 let Some(RawSyncOrStrippedState::Sync(raw_event)) = member_event else {
1386 return Err(Error::InsufficientData);
1387 };
1388
1389 let event = raw_event.deserialize()?;
1390
1391 let mut content = match event {
1392 SyncStateEvent::Original(original_event) => original_event.content,
1393 SyncStateEvent::Redacted(redacted_event) => {
1394 RoomMemberEventContent::new(redacted_event.content.membership)
1395 }
1396 };
1397
1398 content.displayname = display_name;
1399 self.send_state_event_for_key(user_id, content).await
1400 }
1401
1402 pub async fn get_state_events(
1404 &self,
1405 event_type: StateEventType,
1406 ) -> Result<Vec<RawAnySyncOrStrippedState>> {
1407 self.client
1408 .state_store()
1409 .get_state_events(self.room_id(), event_type)
1410 .await
1411 .map_err(Into::into)
1412 }
1413
1414 pub async fn get_state_events_static<C>(&self) -> Result<Vec<RawSyncOrStrippedState<C>>>
1431 where
1432 C: StaticEventContent<IsPrefix = ruma::events::False>
1433 + StaticStateEventContent
1434 + RedactContent,
1435 C::Redacted: RedactedStateEventContent,
1436 {
1437 Ok(self.client.state_store().get_state_events_static(self.room_id()).await?)
1438 }
1439
1440 pub async fn get_state_events_for_keys(
1443 &self,
1444 event_type: StateEventType,
1445 state_keys: &[&str],
1446 ) -> Result<Vec<RawAnySyncOrStrippedState>> {
1447 self.client
1448 .state_store()
1449 .get_state_events_for_keys(self.room_id(), event_type, state_keys)
1450 .await
1451 .map_err(Into::into)
1452 }
1453
1454 pub async fn get_state_events_for_keys_static<'a, C, K, I>(
1474 &self,
1475 state_keys: I,
1476 ) -> Result<Vec<RawSyncOrStrippedState<C>>>
1477 where
1478 C: StaticEventContent<IsPrefix = ruma::events::False>
1479 + StaticStateEventContent
1480 + RedactContent,
1481 C::StateKey: Borrow<K>,
1482 C::Redacted: RedactedStateEventContent,
1483 K: AsRef<str> + Sized + Sync + 'a,
1484 I: IntoIterator<Item = &'a K> + Send,
1485 I::IntoIter: Send,
1486 {
1487 Ok(self
1488 .client
1489 .state_store()
1490 .get_state_events_for_keys_static(self.room_id(), state_keys)
1491 .await?)
1492 }
1493
1494 pub async fn get_state_event(
1496 &self,
1497 event_type: StateEventType,
1498 state_key: &str,
1499 ) -> Result<Option<RawAnySyncOrStrippedState>> {
1500 self.client
1501 .state_store()
1502 .get_state_event(self.room_id(), event_type, state_key)
1503 .await
1504 .map_err(Into::into)
1505 }
1506
1507 pub async fn get_state_event_static<C>(&self) -> Result<Option<RawSyncOrStrippedState<C>>>
1526 where
1527 C: StaticEventContent<IsPrefix = ruma::events::False>
1528 + StaticStateEventContent<StateKey = EmptyStateKey>
1529 + RedactContent,
1530 C::Redacted: RedactedStateEventContent,
1531 {
1532 self.get_state_event_static_for_key(&EmptyStateKey).await
1533 }
1534
1535 pub async fn get_state_event_static_for_key<C, K>(
1555 &self,
1556 state_key: &K,
1557 ) -> Result<Option<RawSyncOrStrippedState<C>>>
1558 where
1559 C: StaticEventContent<IsPrefix = ruma::events::False>
1560 + StaticStateEventContent
1561 + RedactContent,
1562 C::StateKey: Borrow<K>,
1563 C::Redacted: RedactedStateEventContent,
1564 K: AsRef<str> + ?Sized + Sync,
1565 {
1566 Ok(self
1567 .client
1568 .state_store()
1569 .get_state_event_static_for_key(self.room_id(), state_key)
1570 .await?)
1571 }
1572
1573 pub async fn parent_spaces(&self) -> Result<impl Stream<Item = Result<ParentSpace>> + '_> {
1577 Ok(self
1582 .get_state_events_static::<SpaceParentEventContent>()
1583 .await?
1584 .into_iter()
1585 .filter_map(|parent_event| match parent_event.deserialize() {
1587 Ok(SyncOrStrippedState::Sync(SyncStateEvent::Original(e))) => {
1588 Some((e.state_key, e.sender))
1589 }
1590 Ok(SyncOrStrippedState::Sync(SyncStateEvent::Redacted(_))) => None,
1591 Ok(SyncOrStrippedState::Stripped(e)) => Some((e.state_key, e.sender)),
1592 Err(e) => {
1593 info!(room_id = ?self.room_id(), "Could not deserialize m.space.parent: {e}");
1594 None
1595 }
1596 })
1597 .map(|(state_key, sender): (OwnedRoomId, OwnedUserId)| async move {
1599 let Some(parent_room) = self.client.get_room(&state_key) else {
1600 return Ok(ParentSpace::Unverifiable(state_key));
1603 };
1604 if let Some(child_event) = parent_room
1607 .get_state_event_static_for_key::<SpaceChildEventContent, _>(self.room_id())
1608 .await?
1609 {
1610 match child_event.deserialize() {
1611 Ok(SyncOrStrippedState::Sync(SyncStateEvent::Original(_))) => {
1612 return Ok(ParentSpace::Reciprocal(parent_room));
1615 }
1616 Ok(SyncOrStrippedState::Sync(SyncStateEvent::Redacted(_))) => {}
1617 Ok(SyncOrStrippedState::Stripped(_)) => {}
1618 Err(e) => {
1619 info!(
1620 room_id = ?self.room_id(), parent_room_id = ?state_key,
1621 "Could not deserialize m.space.child: {e}"
1622 );
1623 }
1624 }
1625 }
1630
1631 let Some(member) = parent_room.get_member(&sender).await? else {
1634 return Ok(ParentSpace::Illegitimate(parent_room));
1636 };
1637
1638 if member.can_send_state(StateEventType::SpaceChild) {
1639 Ok(ParentSpace::WithPowerlevel(parent_room))
1641 } else {
1642 Ok(ParentSpace::Illegitimate(parent_room))
1643 }
1644 })
1645 .collect::<FuturesUnordered<_>>())
1646 }
1647
1648 pub async fn account_data(
1650 &self,
1651 data_type: RoomAccountDataEventType,
1652 ) -> Result<Option<Raw<AnyRoomAccountDataEvent>>> {
1653 self.client
1654 .state_store()
1655 .get_room_account_data_event(self.room_id(), data_type)
1656 .await
1657 .map_err(Into::into)
1658 }
1659
1660 pub async fn account_data_static<C>(&self) -> Result<Option<Raw<RoomAccountDataEvent<C>>>>
1679 where
1680 C: StaticEventContent<IsPrefix = ruma::events::False> + RoomAccountDataEventContent,
1681 {
1682 Ok(self.account_data(C::TYPE.into()).await?.map(Raw::cast_unchecked))
1683 }
1684
1685 #[cfg(feature = "e2e-encryption")]
1690 pub async fn contains_only_verified_devices(&self) -> Result<bool> {
1691 let user_ids = self
1692 .client
1693 .state_store()
1694 .get_user_ids(self.room_id(), RoomMemberships::empty())
1695 .await?;
1696
1697 for user_id in user_ids {
1698 let devices = self.client.encryption().get_user_devices(&user_id).await?;
1699 let any_unverified = devices.devices().any(|d| !d.is_verified());
1700
1701 if any_unverified {
1702 return Ok(false);
1703 }
1704 }
1705
1706 Ok(true)
1707 }
1708
1709 pub async fn set_account_data<T>(
1725 &self,
1726 content: T,
1727 ) -> Result<set_room_account_data::v3::Response>
1728 where
1729 T: RoomAccountDataEventContent,
1730 {
1731 let own_user = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
1732
1733 let request = set_room_account_data::v3::Request::new(
1734 own_user.to_owned(),
1735 self.room_id().to_owned(),
1736 &content,
1737 )?;
1738
1739 Ok(self.client.send(request).await?)
1740 }
1741
1742 pub async fn set_account_data_raw(
1768 &self,
1769 event_type: RoomAccountDataEventType,
1770 content: Raw<AnyRoomAccountDataEventContent>,
1771 ) -> Result<set_room_account_data::v3::Response> {
1772 let own_user = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
1773
1774 let request = set_room_account_data::v3::Request::new_raw(
1775 own_user.to_owned(),
1776 self.room_id().to_owned(),
1777 event_type,
1778 content,
1779 );
1780
1781 Ok(self.client.send(request).await?)
1782 }
1783
1784 pub async fn set_tag(
1815 &self,
1816 tag: TagName,
1817 tag_info: TagInfo,
1818 ) -> Result<create_tag::v3::Response> {
1819 let user_id = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
1820 let request = create_tag::v3::Request::new(
1821 user_id.to_owned(),
1822 self.inner.room_id().to_owned(),
1823 tag.to_string(),
1824 tag_info,
1825 );
1826 Ok(self.client.send(request).await?)
1827 }
1828
1829 pub async fn remove_tag(&self, tag: TagName) -> Result<delete_tag::v3::Response> {
1837 let user_id = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
1838 let request = delete_tag::v3::Request::new(
1839 user_id.to_owned(),
1840 self.inner.room_id().to_owned(),
1841 tag.to_string(),
1842 );
1843 Ok(self.client.send(request).await?)
1844 }
1845
1846 pub async fn set_is_favourite(&self, is_favourite: bool, tag_order: Option<f64>) -> Result<()> {
1856 if is_favourite {
1857 let tag_info = assign!(TagInfo::new(), { order: tag_order });
1858
1859 self.set_tag(TagName::Favorite, tag_info).await?;
1860
1861 if self.is_low_priority() {
1862 self.remove_tag(TagName::LowPriority).await?;
1863 }
1864 } else {
1865 self.remove_tag(TagName::Favorite).await?;
1866 }
1867 Ok(())
1868 }
1869
1870 pub async fn set_is_low_priority(
1880 &self,
1881 is_low_priority: bool,
1882 tag_order: Option<f64>,
1883 ) -> Result<()> {
1884 if is_low_priority {
1885 let tag_info = assign!(TagInfo::new(), { order: tag_order });
1886
1887 self.set_tag(TagName::LowPriority, tag_info).await?;
1888
1889 if self.is_favourite() {
1890 self.remove_tag(TagName::Favorite).await?;
1891 }
1892 } else {
1893 self.remove_tag(TagName::LowPriority).await?;
1894 }
1895 Ok(())
1896 }
1897
1898 pub async fn set_is_direct(&self, is_direct: bool) -> Result<()> {
1908 let user_id = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
1909
1910 let mut content = self
1911 .client
1912 .account()
1913 .account_data::<DirectEventContent>()
1914 .await?
1915 .map(|c| c.deserialize())
1916 .transpose()?
1917 .unwrap_or_default();
1918
1919 let this_room_id = self.inner.room_id();
1920
1921 if is_direct {
1922 let mut room_members = self.members(RoomMemberships::ACTIVE).await?;
1923 room_members.retain(|member| member.user_id() != self.own_user_id());
1924
1925 for member in room_members {
1926 let entry = content.entry(member.user_id().into()).or_default();
1927 if !entry.iter().any(|room_id| room_id == this_room_id) {
1928 entry.push(this_room_id.to_owned());
1929 }
1930 }
1931 } else {
1932 for list in content.values_mut() {
1933 list.retain(|room_id| *room_id != this_room_id);
1934 }
1935
1936 content.retain(|_, list| !list.is_empty());
1938 }
1939
1940 let request = set_global_account_data::v3::Request::new(user_id.to_owned(), &content)?;
1941
1942 self.client.send(request).await?;
1943 Ok(())
1944 }
1945
1946 #[cfg(feature = "e2e-encryption")]
1955 #[cfg(not(feature = "experimental-encrypted-state-events"))]
1956 pub async fn decrypt_event(
1957 &self,
1958 event: &Raw<OriginalSyncRoomEncryptedEvent>,
1959 push_ctx: Option<&PushContext>,
1960 ) -> Result<TimelineEvent> {
1961 let machine = self.client.olm_machine().await;
1962 let machine = machine.as_ref().ok_or(Error::NoOlmMachine)?;
1963
1964 match machine
1965 .try_decrypt_room_event(
1966 event.cast_ref(),
1967 self.inner.room_id(),
1968 self.client.decryption_settings(),
1969 )
1970 .await?
1971 {
1972 RoomEventDecryptionResult::Decrypted(decrypted) => {
1973 let push_actions = if let Some(push_ctx) = push_ctx {
1974 Some(push_ctx.for_event(&decrypted.event).await)
1975 } else {
1976 None
1977 };
1978 Ok(TimelineEvent::from_decrypted(decrypted, push_actions))
1979 }
1980 RoomEventDecryptionResult::UnableToDecrypt(utd_info) => {
1981 self.client
1982 .encryption()
1983 .backups()
1984 .maybe_download_room_key(self.room_id().to_owned(), event.clone());
1985 Ok(TimelineEvent::from_utd(event.clone().cast(), utd_info))
1986 }
1987 }
1988 }
1989
1990 #[cfg(feature = "experimental-encrypted-state-events")]
1999 pub async fn decrypt_event<T: JsonCastable<EncryptedEvent>>(
2000 &self,
2001 event: &Raw<T>,
2002 push_ctx: Option<&PushContext>,
2003 ) -> Result<TimelineEvent> {
2004 let machine = self.client.olm_machine().await;
2005 let machine = machine.as_ref().ok_or(Error::NoOlmMachine)?;
2006
2007 match machine
2008 .try_decrypt_room_event(
2009 event.cast_ref(),
2010 self.inner.room_id(),
2011 self.client.decryption_settings(),
2012 )
2013 .await?
2014 {
2015 RoomEventDecryptionResult::Decrypted(decrypted) => {
2016 let push_actions = if let Some(push_ctx) = push_ctx {
2017 Some(push_ctx.for_event(&decrypted.event).await)
2018 } else {
2019 None
2020 };
2021 Ok(TimelineEvent::from_decrypted(decrypted, push_actions))
2022 }
2023 RoomEventDecryptionResult::UnableToDecrypt(utd_info) => {
2024 self.client
2025 .encryption()
2026 .backups()
2027 .maybe_download_room_key(self.room_id().to_owned(), event.clone());
2028 Ok(TimelineEvent::from_utd(event.clone().cast_unchecked(), utd_info))
2031 }
2032 }
2033 }
2034
2035 #[cfg(feature = "e2e-encryption")]
2049 pub async fn get_encryption_info(
2050 &self,
2051 session_id: &str,
2052 sender: &UserId,
2053 ) -> Option<Arc<EncryptionInfo>> {
2054 let machine = self.client.olm_machine().await;
2055 let machine = machine.as_ref()?;
2056 machine.get_session_encryption_info(self.room_id(), session_id, sender).await.ok()
2057 }
2058
2059 #[cfg(feature = "e2e-encryption")]
2072 pub async fn discard_room_key(&self) -> Result<()> {
2073 let machine = self.client.olm_machine().await;
2074 if let Some(machine) = machine.as_ref() {
2075 machine.discard_room_key(self.inner.room_id()).await?;
2076 Ok(())
2077 } else {
2078 Err(Error::NoOlmMachine)
2079 }
2080 }
2081
2082 #[instrument(skip_all)]
2090 pub async fn ban_user(&self, user_id: &UserId, reason: Option<&str>) -> Result<()> {
2091 let request = assign!(
2092 ban_user::v3::Request::new(self.room_id().to_owned(), user_id.to_owned()),
2093 { reason: reason.map(ToOwned::to_owned) }
2094 );
2095 self.client.send(request).await?;
2096 Ok(())
2097 }
2098
2099 #[instrument(skip_all)]
2107 pub async fn unban_user(&self, user_id: &UserId, reason: Option<&str>) -> Result<()> {
2108 let request = assign!(
2109 unban_user::v3::Request::new(self.room_id().to_owned(), user_id.to_owned()),
2110 { reason: reason.map(ToOwned::to_owned) }
2111 );
2112 self.client.send(request).await?;
2113 Ok(())
2114 }
2115
2116 #[instrument(skip_all)]
2125 pub async fn kick_user(&self, user_id: &UserId, reason: Option<&str>) -> Result<()> {
2126 let request = assign!(
2127 kick_user::v3::Request::new(self.room_id().to_owned(), user_id.to_owned()),
2128 { reason: reason.map(ToOwned::to_owned) }
2129 );
2130 self.client.send(request).await?;
2131 Ok(())
2132 }
2133
2134 #[instrument(skip_all)]
2140 pub async fn invite_user_by_id(&self, user_id: &UserId) -> Result<()> {
2141 #[cfg(feature = "e2e-encryption")]
2142 if self.client.inner.enable_share_history_on_invite {
2143 shared_room_history::share_room_history(self, user_id.to_owned()).await?;
2144 }
2145
2146 let recipient = InvitationRecipient::UserId(InviteUserId::new(user_id.to_owned()));
2147 let request = invite_user::v3::Request::new(self.room_id().to_owned(), recipient);
2148 self.client.send(request).await?;
2149
2150 self.mark_members_missing();
2155
2156 Ok(())
2157 }
2158
2159 #[instrument(skip_all)]
2165 pub async fn invite_user_by_3pid(&self, invite_id: Invite3pid) -> Result<()> {
2166 let recipient = InvitationRecipient::ThirdPartyId(invite_id);
2167 let request = invite_user::v3::Request::new(self.room_id().to_owned(), recipient);
2168 self.client.send(request).await?;
2169
2170 self.mark_members_missing();
2175
2176 Ok(())
2177 }
2178
2179 pub async fn typing_notice(&self, typing: bool) -> Result<()> {
2213 self.ensure_room_joined()?;
2214
2215 let send = if let Some(typing_time) =
2218 self.client.inner.typing_notice_times.read().unwrap().get(self.room_id())
2219 {
2220 if typing_time.elapsed() > TYPING_NOTICE_RESEND_TIMEOUT {
2221 typing || typing_time.elapsed() <= TYPING_NOTICE_TIMEOUT
2225 } else {
2226 !typing
2228 }
2229 } else {
2230 typing
2233 };
2234
2235 if send {
2236 self.send_typing_notice(typing).await?;
2237 }
2238
2239 Ok(())
2240 }
2241
2242 #[instrument(name = "typing_notice", skip(self))]
2243 async fn send_typing_notice(&self, typing: bool) -> Result<()> {
2244 let typing = if typing {
2245 self.client
2246 .inner
2247 .typing_notice_times
2248 .write()
2249 .unwrap()
2250 .insert(self.room_id().to_owned(), Instant::now());
2251 Typing::Yes(TypingInfo::new(TYPING_NOTICE_TIMEOUT))
2252 } else {
2253 self.client.inner.typing_notice_times.write().unwrap().remove(self.room_id());
2254 Typing::No
2255 };
2256
2257 let request = create_typing_event::v3::Request::new(
2258 self.own_user_id().to_owned(),
2259 self.room_id().to_owned(),
2260 typing,
2261 );
2262
2263 self.client.send(request).await?;
2264
2265 Ok(())
2266 }
2267
2268 #[instrument(skip_all)]
2285 pub async fn send_single_receipt(
2286 &self,
2287 receipt_type: create_receipt::v3::ReceiptType,
2288 thread: ReceiptThread,
2289 event_id: OwnedEventId,
2290 ) -> Result<()> {
2291 let request_key = format!("{}|{}", receipt_type, thread.as_str().unwrap_or("<unthreaded>"));
2294
2295 self.client
2296 .inner
2297 .locks
2298 .read_receipt_deduplicated_handler
2299 .run((request_key, event_id.clone()), async {
2300 let is_unthreaded = thread == ReceiptThread::Unthreaded;
2303
2304 let mut request = create_receipt::v3::Request::new(
2305 self.room_id().to_owned(),
2306 receipt_type,
2307 event_id,
2308 );
2309 request.thread = thread;
2310
2311 self.client.send(request).await?;
2312
2313 if is_unthreaded {
2314 self.set_unread_flag(false).await?;
2315 }
2316
2317 Ok(())
2318 })
2319 .await
2320 }
2321
2322 #[instrument(skip_all)]
2332 pub async fn send_multiple_receipts(&self, receipts: Receipts) -> Result<()> {
2333 if receipts.is_empty() {
2334 return Ok(());
2335 }
2336
2337 let Receipts { fully_read, public_read_receipt, private_read_receipt } = receipts;
2338 let request = assign!(set_read_marker::v3::Request::new(self.room_id().to_owned()), {
2339 fully_read,
2340 read_receipt: public_read_receipt,
2341 private_read_receipt,
2342 });
2343
2344 self.client.send(request).await?;
2345
2346 self.set_unread_flag(false).await?;
2347
2348 Ok(())
2349 }
2350
2351 #[allow(unused_variables, unused_mut)]
2355 async fn enable_encryption_inner(&self, encrypted_state_events: bool) -> Result<()> {
2356 use ruma::{
2357 EventEncryptionAlgorithm, events::room::encryption::RoomEncryptionEventContent,
2358 };
2359 const SYNC_WAIT_TIME: Duration = Duration::from_secs(3);
2360
2361 if !self.latest_encryption_state().await?.is_encrypted() {
2362 let mut content =
2363 RoomEncryptionEventContent::new(EventEncryptionAlgorithm::MegolmV1AesSha2);
2364 #[cfg(feature = "experimental-encrypted-state-events")]
2365 if encrypted_state_events {
2366 content = content.with_encrypted_state();
2367 }
2368 self.send_state_event(content).await?;
2369
2370 let res = timeout(
2377 async {
2378 loop {
2379 self.client.inner.sync_beat.listen().await;
2382 let _state_store_lock =
2383 self.client.base_client().state_store_lock().lock().await;
2384
2385 if !self.inner.encryption_state().is_unknown() {
2386 break;
2387 }
2388 }
2389 },
2390 SYNC_WAIT_TIME,
2391 )
2392 .await;
2393
2394 let store_guard = self.client.base_client().state_store_lock().lock().await;
2395
2396 #[cfg(not(feature = "experimental-encrypted-state-events"))]
2398 if res.is_ok() && self.inner.encryption_state().is_encrypted() {
2399 debug!("room successfully marked as encrypted");
2400 return Ok(());
2401 }
2402
2403 #[cfg(feature = "experimental-encrypted-state-events")]
2405 if res.is_ok() && {
2406 if encrypted_state_events {
2407 self.inner.encryption_state().is_state_encrypted()
2408 } else {
2409 self.inner.encryption_state().is_encrypted()
2410 }
2411 } {
2412 debug!("room successfully marked as encrypted");
2413 return Ok(());
2414 }
2415
2416 debug!("still not marked as encrypted, marking encryption state as missing");
2421
2422 self.update_and_save_room_info_with_store_guard(&store_guard, |mut info| {
2423 info.mark_encryption_state_missing();
2424 (info, RoomInfoNotableUpdateReasons::empty())
2425 })
2426 .await?;
2427 }
2428
2429 Ok(())
2430 }
2431
2432 #[instrument(skip_all)]
2464 pub async fn enable_encryption(&self) -> Result<()> {
2465 self.enable_encryption_inner(false).await
2466 }
2467
2468 #[instrument(skip_all)]
2501 #[cfg(feature = "experimental-encrypted-state-events")]
2502 pub async fn enable_encryption_with_state_event_encryption(&self) -> Result<()> {
2503 self.enable_encryption_inner(true).await
2504 }
2505
2506 #[cfg(feature = "e2e-encryption")]
2515 #[instrument(skip_all, fields(room_id = ?self.room_id()))]
2516 async fn preshare_room_key(&self) -> Result<()> {
2517 self.ensure_room_joined()?;
2518
2519 let _guard = self.client.encryption().spin_lock_store(Some(60000)).await?;
2521
2522 self.client
2523 .locks()
2524 .group_session_deduplicated_handler
2525 .run(self.room_id().to_owned(), async move {
2526 {
2527 let members = self
2528 .client
2529 .state_store()
2530 .get_user_ids(self.room_id(), RoomMemberships::ACTIVE)
2531 .await?;
2532 self.client.claim_one_time_keys(members.iter().map(Deref::deref)).await?;
2533 };
2534
2535 let response = self.share_room_key().await;
2536
2537 if let Err(r) = response {
2540 let machine = self.client.olm_machine().await;
2541 if let Some(machine) = machine.as_ref() {
2542 machine.discard_room_key(self.room_id()).await?;
2543 }
2544 return Err(r);
2545 }
2546
2547 Ok(())
2548 })
2549 .await
2550 }
2551
2552 #[cfg(feature = "e2e-encryption")]
2558 #[instrument(skip_all)]
2559 async fn share_room_key(&self) -> Result<()> {
2560 self.ensure_room_joined()?;
2561
2562 let requests = self.client.base_client().share_room_key(self.room_id()).await?;
2563
2564 for request in requests {
2565 let response = self.client.send_to_device(&request).await?;
2566 self.client.mark_request_as_sent(&request.txn_id, &response).await?;
2567 }
2568
2569 Ok(())
2570 }
2571
2572 #[instrument(skip_all)]
2581 pub async fn sync_up(&self) {
2582 while !self.is_synced() && self.state() == RoomState::Joined {
2583 let wait_for_beat = self.client.inner.sync_beat.listen();
2584 let _ = timeout(wait_for_beat, Duration::from_millis(1000)).await;
2586 }
2587 }
2588
2589 pub fn send(&self, content: impl MessageLikeEventContent) -> SendMessageLikeEvent<'_> {
2660 SendMessageLikeEvent::new(self, content)
2661 }
2662
2663 #[cfg(feature = "e2e-encryption")]
2666 async fn query_keys_for_untracked_or_dirty_users(&self) -> Result<()> {
2667 let olm = self.client.olm_machine().await;
2668 let olm = olm.as_ref().expect("Olm machine wasn't started");
2669
2670 let members =
2671 self.client.state_store().get_user_ids(self.room_id(), RoomMemberships::ACTIVE).await?;
2672
2673 let tracked: HashMap<_, _> = olm
2674 .store()
2675 .load_tracked_users()
2676 .await?
2677 .into_iter()
2678 .map(|tracked| (tracked.user_id, tracked.dirty))
2679 .collect();
2680
2681 let members_with_unknown_devices =
2684 members.iter().filter(|member| tracked.get(*member).is_none_or(|dirty| *dirty));
2685
2686 let (req_id, request) =
2687 olm.query_keys_for_users(members_with_unknown_devices.map(|owned| owned.borrow()));
2688
2689 if !request.device_keys.is_empty() {
2690 self.client.keys_query(&req_id, request.device_keys).await?;
2691 }
2692
2693 Ok(())
2694 }
2695
2696 #[instrument(skip_all, fields(event_type, room_id = ?self.room_id(), transaction_id, is_room_encrypted, event_id))]
2743 pub fn send_raw<'a>(
2744 &'a self,
2745 event_type: &'a str,
2746 content: impl IntoRawMessageLikeEventContent,
2747 ) -> SendRawMessageLikeEvent<'a> {
2748 SendRawMessageLikeEvent::new(self, event_type, content)
2751 }
2752
2753 #[instrument(skip_all)]
2799 pub fn send_attachment<'a>(
2800 &'a self,
2801 filename: impl Into<String>,
2802 content_type: &'a Mime,
2803 data: Vec<u8>,
2804 config: AttachmentConfig,
2805 ) -> SendAttachment<'a> {
2806 SendAttachment::new(self, filename.into(), content_type, data, config)
2807 }
2808
2809 #[instrument(skip_all)]
2836 pub(super) async fn prepare_and_send_attachment<'a>(
2837 &'a self,
2838 filename: String,
2839 content_type: &'a Mime,
2840 data: Vec<u8>,
2841 mut config: AttachmentConfig,
2842 send_progress: SharedObservable<TransmissionProgress>,
2843 store_in_cache: bool,
2844 ) -> Result<send_message_event::v3::Response> {
2845 self.ensure_room_joined()?;
2846
2847 let txn_id = config.txn_id.take();
2848 let extra_content = config.extra_content.take();
2849 let mentions = config.mentions.take();
2850
2851 let thumbnail = config.thumbnail.take();
2852
2853 let thumbnail_cache_info = if store_in_cache {
2855 thumbnail
2856 .as_ref()
2857 .map(|thumbnail| (thumbnail.data.clone(), thumbnail.height, thumbnail.width))
2858 } else {
2859 None
2860 };
2861
2862 #[cfg(feature = "e2e-encryption")]
2863 let (media_source, thumbnail) = if self.latest_encryption_state().await?.is_encrypted() {
2864 self.client
2865 .upload_encrypted_media_and_thumbnail(&data, thumbnail, send_progress)
2866 .await?
2867 } else {
2868 self.client
2869 .media()
2870 .upload_plain_media_and_thumbnail(
2871 content_type,
2872 data.clone(),
2875 thumbnail,
2876 send_progress,
2877 )
2878 .await?
2879 };
2880
2881 #[cfg(not(feature = "e2e-encryption"))]
2882 let (media_source, thumbnail) = self
2883 .client
2884 .media()
2885 .upload_plain_media_and_thumbnail(content_type, data.clone(), thumbnail, send_progress)
2886 .await?;
2887
2888 if store_in_cache {
2889 let media_store_lock_guard = self.client.media_store().lock().await?;
2890
2891 debug!("caching the media");
2895 let request =
2896 MediaRequestParameters { source: media_source.clone(), format: MediaFormat::File };
2897
2898 if let Err(err) = media_store_lock_guard
2899 .add_media_content(&request, data, IgnoreMediaRetentionPolicy::No)
2900 .await
2901 {
2902 warn!("unable to cache the media after uploading it: {err}");
2903 }
2904
2905 if let Some(((data, height, width), source)) =
2906 thumbnail_cache_info.zip(thumbnail.as_ref().map(|tuple| &tuple.0))
2907 {
2908 debug!("caching the thumbnail");
2909
2910 let request = MediaRequestParameters {
2911 source: source.clone(),
2912 format: MediaFormat::Thumbnail(MediaThumbnailSettings::new(width, height)),
2913 };
2914
2915 if let Err(err) = media_store_lock_guard
2916 .add_media_content(&request, data, IgnoreMediaRetentionPolicy::No)
2917 .await
2918 {
2919 warn!("unable to cache the media after uploading it: {err}");
2920 }
2921 }
2922 }
2923
2924 let content = self
2925 .make_media_event(
2926 Room::make_attachment_type(
2927 content_type,
2928 filename,
2929 media_source,
2930 config.caption,
2931 config.info,
2932 thumbnail,
2933 ),
2934 mentions,
2935 config.reply,
2936 )
2937 .await?;
2938
2939 if let Some(extra_content) = extra_content.filter(|extra| !extra.is_empty()) {
2943 let serde_json::Value::Object(mut object) = serde_json::to_value(&content)
2944 .map_err(|error| Error::UnknownError(Box::new(error)))?
2945 else {
2946 unreachable!("a room message event content always serializes to a JSON object");
2947 };
2948 for (key, value) in extra_content {
2949 match object.entry(key) {
2950 serde_json::map::Entry::Occupied(entry) => {
2951 warn!(
2952 key = entry.key(),
2953 "extra content field shadowed by the event's own field"
2954 );
2955 }
2956 serde_json::map::Entry::Vacant(entry) => {
2957 entry.insert(value);
2958 }
2959 }
2960 }
2961
2962 let event_type = content.event_type().to_string();
2963 let mut fut = self.send_raw(&event_type, serde_json::Value::Object(object));
2964 if let Some(txn_id) = &txn_id {
2965 fut = fut.with_transaction_id(txn_id);
2966 }
2967 return fut.await.map(|result| result.response);
2968 }
2969
2970 let mut fut = self.send(content);
2971 if let Some(txn_id) = txn_id {
2972 fut = fut.with_transaction_id(txn_id);
2973 }
2974
2975 fut.await.map(|result| result.response)
2976 }
2977
2978 #[allow(clippy::too_many_arguments)]
2981 pub(crate) fn make_attachment_type(
2982 content_type: &Mime,
2983 filename: String,
2984 source: MediaSource,
2985 caption: Option<TextMessageEventContent>,
2986 info: Option<AttachmentInfo>,
2987 thumbnail: Option<(MediaSource, Box<ThumbnailInfo>)>,
2988 ) -> MessageType {
2989 make_media_type!(MessageType, content_type, filename, source, caption, info, thumbnail)
2990 }
2991
2992 pub(crate) async fn make_media_event(
2995 &self,
2996 msg_type: MessageType,
2997 mentions: Option<Mentions>,
2998 reply: Option<Reply>,
2999 ) -> Result<RoomMessageEventContent> {
3000 let mut content = RoomMessageEventContent::new(msg_type);
3001 if let Some(mentions) = mentions {
3002 content = content.add_mentions(mentions);
3003 }
3004 if let Some(reply) = reply {
3005 content = self.make_reply_event(content.into(), reply).await?;
3009 }
3010 Ok(content)
3011 }
3012
3013 #[cfg(feature = "unstable-msc4274")]
3016 #[allow(clippy::too_many_arguments)]
3017 pub(crate) fn make_gallery_item_type(
3018 content_type: &Mime,
3019 filename: String,
3020 source: MediaSource,
3021 caption: Option<TextMessageEventContent>,
3022 info: Option<AttachmentInfo>,
3023 thumbnail: Option<(MediaSource, Box<ThumbnailInfo>)>,
3024 ) -> GalleryItemType {
3025 make_media_type!(GalleryItemType, content_type, filename, source, caption, info, thumbnail)
3026 }
3027
3028 pub async fn update_power_levels(
3036 &self,
3037 updates: Vec<(&UserId, Int)>,
3038 ) -> Result<send_state_event::v3::Response> {
3039 let mut power_levels = self.power_levels().await?;
3040
3041 for (user_id, new_level) in updates {
3042 if new_level == power_levels.users_default {
3043 power_levels.users.remove(user_id);
3044 } else {
3045 power_levels.users.insert(user_id.to_owned(), new_level);
3046 }
3047 }
3048
3049 self.send_state_event(RoomPowerLevelsEventContent::try_from(power_levels)?).await
3050 }
3051
3052 pub async fn apply_power_level_changes(&self, changes: RoomPowerLevelChanges) -> Result<()> {
3057 let mut power_levels = self.power_levels().await?;
3058 power_levels.apply(changes)?;
3059 self.send_state_event(RoomPowerLevelsEventContent::try_from(power_levels)?).await?;
3060 Ok(())
3061 }
3062
3063 pub async fn reset_power_levels(&self) -> Result<RoomPowerLevels> {
3067 let creators = self.creators().unwrap_or_default();
3068 let rules = self.clone_info().room_version_rules_or_default();
3069
3070 let default_power_levels =
3071 RoomPowerLevels::new(RoomPowerLevelsSource::None, &rules.authorization, creators);
3072 let changes = RoomPowerLevelChanges::from(default_power_levels);
3073 self.apply_power_level_changes(changes).await?;
3074 Ok(self.power_levels().await?)
3075 }
3076
3077 pub async fn get_suggested_user_role(&self, user_id: &UserId) -> Result<RoomMemberRole> {
3082 let power_level = self.get_user_power_level(user_id).await?;
3083 Ok(RoomMemberRole::suggested_role_for_power_level(power_level))
3084 }
3085
3086 pub async fn get_user_power_level(&self, user_id: &UserId) -> Result<UserPowerLevel> {
3091 let event = self.power_levels().await?;
3092 Ok(event.for_user(user_id))
3093 }
3094
3095 pub async fn users_with_power_levels(&self) -> HashMap<OwnedUserId, i64> {
3098 let power_levels = self.power_levels().await.ok();
3099 let mut user_power_levels = HashMap::<OwnedUserId, i64>::new();
3100 if let Some(power_levels) = power_levels {
3101 for (id, level) in power_levels.users.into_iter() {
3102 user_power_levels.insert(id, level.into());
3103 }
3104 }
3105 user_power_levels
3106 }
3107
3108 pub async fn set_name(&self, name: String) -> Result<send_state_event::v3::Response> {
3110 self.send_state_event(RoomNameEventContent::new(name)).await
3111 }
3112
3113 pub async fn set_room_topic(&self, topic: &str) -> Result<send_state_event::v3::Response> {
3115 self.send_state_event(RoomTopicEventContent::new(topic.into())).await
3116 }
3117
3118 pub async fn set_avatar_url(
3125 &self,
3126 url: &MxcUri,
3127 info: Option<avatar::ImageInfo>,
3128 ) -> Result<send_state_event::v3::Response> {
3129 self.ensure_room_joined()?;
3130
3131 let mut room_avatar_event = RoomAvatarEventContent::new();
3132 room_avatar_event.url = Some(url.to_owned());
3133 room_avatar_event.info = info.map(Box::new);
3134
3135 self.send_state_event(room_avatar_event).await
3136 }
3137
3138 pub async fn remove_avatar(&self) -> Result<send_state_event::v3::Response> {
3140 self.send_state_event(RoomAvatarEventContent::new()).await
3141 }
3142
3143 pub async fn upload_avatar(
3152 &self,
3153 mime: &Mime,
3154 data: Vec<u8>,
3155 info: Option<avatar::ImageInfo>,
3156 ) -> Result<send_state_event::v3::Response> {
3157 self.ensure_room_joined()?;
3158
3159 let upload_response = self.client.media().upload(mime, data, None).await?;
3160 let mut info = info.unwrap_or_default();
3161 info.blurhash = upload_response.blurhash;
3162 info.mimetype = Some(mime.to_string());
3163
3164 self.set_avatar_url(&upload_response.content_uri, Some(info)).await
3165 }
3166
3167 #[cfg(not(feature = "experimental-encrypted-state-events"))]
3211 #[instrument(skip_all)]
3212 pub async fn send_state_event(
3213 &self,
3214 content: impl StateEventContent<StateKey = EmptyStateKey>,
3215 ) -> Result<send_state_event::v3::Response> {
3216 self.send_state_event_for_key(&EmptyStateKey, content).await
3217 }
3218
3219 #[cfg(feature = "experimental-encrypted-state-events")]
3270 #[instrument(skip_all)]
3271 pub fn send_state_event<'a>(
3272 &'a self,
3273 content: impl StateEventContent<StateKey = EmptyStateKey>,
3274 ) -> SendStateEvent<'a> {
3275 self.send_state_event_for_key(&EmptyStateKey, content)
3276 }
3277
3278 #[cfg(not(feature = "experimental-encrypted-state-events"))]
3318 pub async fn send_state_event_for_key<C, K>(
3319 &self,
3320 state_key: &K,
3321 content: C,
3322 ) -> Result<send_state_event::v3::Response>
3323 where
3324 C: StateEventContent,
3325 C::StateKey: Borrow<K>,
3326 K: AsRef<str> + ?Sized,
3327 {
3328 self.ensure_room_joined()?;
3329 let request =
3330 send_state_event::v3::Request::new(self.room_id().to_owned(), state_key, &content)?;
3331 let response = self.client.send(request).await?;
3332 Ok(response)
3333 }
3334
3335 #[cfg(feature = "experimental-encrypted-state-events")]
3383 pub fn send_state_event_for_key<'a, C, K>(
3384 &'a self,
3385 state_key: &K,
3386 content: C,
3387 ) -> SendStateEvent<'a>
3388 where
3389 C: StateEventContent,
3390 C::StateKey: Borrow<K>,
3391 K: AsRef<str> + ?Sized,
3392 {
3393 SendStateEvent::new(self, state_key, content)
3394 }
3395
3396 #[instrument(skip_all)]
3441 pub fn send_state_event_raw<'a>(
3442 &'a self,
3443 event_type: &'a str,
3444 state_key: &'a str,
3445 content: impl IntoRawStateEventContent,
3446 ) -> SendRawStateEvent<'a> {
3447 SendRawStateEvent::new(self, event_type, state_key, content)
3448 }
3449
3450 #[instrument(skip_all)]
3483 pub async fn redact(
3484 &self,
3485 event_id: &EventId,
3486 reason: Option<&str>,
3487 txn_id: Option<OwnedTransactionId>,
3488 ) -> HttpResult<redact_event::v3::Response> {
3489 let txn_id = txn_id.unwrap_or_else(TransactionId::new);
3490 let request = assign!(
3491 redact_event::v3::Request::new(self.room_id().to_owned(), event_id.to_owned(), txn_id),
3492 { reason: reason.map(ToOwned::to_owned) }
3493 );
3494
3495 self.client.send(request).await
3496 }
3497
3498 pub async fn route(&self) -> Result<Vec<OwnedServerName>> {
3507 let acl_ev = self
3508 .get_state_event_static::<RoomServerAclEventContent>()
3509 .await?
3510 .and_then(|ev| ev.deserialize().ok());
3511 let acl = acl_ev.as_ref().and_then(|ev| match ev {
3512 SyncOrStrippedState::Sync(ev) => ev.as_original().map(|ev| &ev.content),
3513 SyncOrStrippedState::Stripped(ev) => Some(&ev.content),
3514 });
3515
3516 let members: Vec<_> = self
3521 .members_no_sync(RoomMemberships::JOIN)
3522 .await?
3523 .into_iter()
3524 .filter(|member| {
3525 let server = member.user_id().server_name();
3526 acl.filter(|acl| !acl.is_allowed(server)).is_none() && !server.is_ip_literal()
3527 })
3528 .collect();
3529
3530 let max = members
3533 .iter()
3534 .max_by_key(|member| member.power_level())
3535 .filter(|max| max.power_level() >= int!(50))
3536 .map(|member| member.user_id().server_name());
3537
3538 let servers = members
3540 .iter()
3541 .map(|member| member.user_id().server_name())
3542 .filter(|server| max.filter(|max| max == server).is_none())
3543 .fold(BTreeMap::<_, u32>::new(), |mut servers, server| {
3544 *servers.entry(server).or_default() += 1;
3545 servers
3546 });
3547 let mut servers: Vec<_> = servers.into_iter().collect();
3548 servers.sort_unstable_by(|(_, count_a), (_, count_b)| count_b.cmp(count_a));
3549
3550 Ok(max
3551 .into_iter()
3552 .chain(servers.into_iter().map(|(name, _)| name))
3553 .take(3)
3554 .map(ToOwned::to_owned)
3555 .collect())
3556 }
3557
3558 pub async fn matrix_to_permalink(&self) -> Result<MatrixToUri> {
3565 if let Some(alias) = self.canonical_alias().or_else(|| self.alt_aliases().pop()) {
3566 return Ok(alias.matrix_to_uri());
3567 }
3568
3569 let via = self.route().await?;
3570 Ok(self.room_id().matrix_to_uri_via(via))
3571 }
3572
3573 pub async fn matrix_permalink(&self, join: bool) -> Result<MatrixUri> {
3584 if let Some(alias) = self.canonical_alias().or_else(|| self.alt_aliases().pop()) {
3585 return Ok(alias.matrix_uri(join));
3586 }
3587
3588 let via = self.route().await?;
3589 Ok(self.room_id().matrix_uri_via(via, join))
3590 }
3591
3592 pub async fn matrix_to_event_permalink(
3606 &self,
3607 event_id: impl Into<OwnedEventId>,
3608 ) -> Result<MatrixToUri> {
3609 let via = self.route().await?;
3612 Ok(self.room_id().matrix_to_event_uri_via(event_id, via))
3613 }
3614
3615 pub async fn matrix_event_permalink(
3629 &self,
3630 event_id: impl Into<OwnedEventId>,
3631 ) -> Result<MatrixUri> {
3632 let via = self.route().await?;
3635 Ok(self.room_id().matrix_event_uri_via(event_id, via))
3636 }
3637
3638 pub async fn load_user_receipt(
3649 &self,
3650 receipt_type: ReceiptType,
3651 receipt_thread: &ReceiptThread,
3652 user_id: &UserId,
3653 ) -> Result<Option<(OwnedEventId, Receipt)>> {
3654 self.inner
3655 .load_user_receipt(receipt_type, receipt_thread, user_id)
3656 .await
3657 .map_err(Into::into)
3658 }
3659
3660 pub async fn load_event_receipts(
3671 &self,
3672 receipt_type: ReceiptType,
3673 receipt_thread: &ReceiptThread,
3674 event_id: &EventId,
3675 ) -> Result<Vec<(OwnedUserId, Receipt)>> {
3676 self.inner
3677 .load_event_receipts(receipt_type, receipt_thread, event_id)
3678 .await
3679 .map_err(Into::into)
3680 }
3681
3682 pub async fn push_condition_room_ctx(&self) -> Result<Option<PushConditionRoomCtx>> {
3687 self.push_condition_room_ctx_internal(self.client.enabled_thread_subscriptions().await?)
3688 .await
3689 }
3690
3691 pub(crate) async fn push_condition_room_ctx_internal(
3698 &self,
3699 with_threads_subscriptions: bool,
3700 ) -> Result<Option<PushConditionRoomCtx>> {
3701 let room_id = self.room_id();
3702 let user_id = self.own_user_id();
3703 let room_info = self.clone_info();
3704 let member_count = room_info.active_members_count();
3705
3706 let user_display_name = if let Some(member) = self.get_member_no_sync(user_id).await? {
3707 member.name().to_owned()
3708 } else {
3709 return Ok(None);
3710 };
3711
3712 let power_levels = match self.power_levels().await {
3713 Ok(power_levels) => Some(power_levels.into()),
3714 Err(error) => {
3715 if matches!(room_info.state(), RoomState::Joined) {
3716 error!("Could not compute power levels for push conditions: {error}");
3719 }
3720 None
3721 }
3722 };
3723
3724 let mut ctx = assign!(PushConditionRoomCtx::new(
3725 room_id.to_owned(),
3726 UInt::new(member_count).unwrap_or(UInt::MAX),
3727 user_id.to_owned(),
3728 user_display_name,
3729 ),
3730 {
3731 power_levels,
3732 });
3733
3734 if with_threads_subscriptions {
3735 let this = self.clone();
3736 ctx = ctx.with_has_thread_subscription_fn(move |event_id: &EventId| {
3737 let room = this.clone();
3738 Box::pin(async move {
3739 if let Ok(maybe_sub) = room.load_or_fetch_thread_subscription(event_id).await {
3740 maybe_sub.is_some()
3741 } else {
3742 false
3743 }
3744 })
3745 });
3746 }
3747
3748 Ok(Some(ctx))
3749 }
3750
3751 pub async fn push_context(&self) -> Result<Option<PushContext>> {
3754 self.push_context_internal(self.client.enabled_thread_subscriptions().await?).await
3755 }
3756
3757 #[instrument(skip(self))]
3761 pub(crate) async fn push_context_internal(
3762 &self,
3763 with_threads_subscriptions: bool,
3764 ) -> Result<Option<PushContext>> {
3765 let Some(push_condition_room_ctx) =
3766 self.push_condition_room_ctx_internal(with_threads_subscriptions).await?
3767 else {
3768 debug!("Could not aggregate push context");
3769 return Ok(None);
3770 };
3771 let push_rules = self.client().account().push_rules().await?;
3772 Ok(Some(PushContext::new(push_condition_room_ctx, push_rules)))
3773 }
3774
3775 pub async fn event_push_actions<T>(&self, event: &Raw<T>) -> Result<Option<Vec<Action>>> {
3780 if let Some(ctx) = self.push_context().await? {
3781 Ok(Some(ctx.for_event(event).await))
3782 } else {
3783 Ok(None)
3784 }
3785 }
3786
3787 pub async fn invite_details(&self) -> Result<Invite> {
3790 let state = self.state();
3791
3792 if state != RoomState::Invited {
3793 return Err(Error::WrongRoomState(Box::new(WrongRoomState::new("Invited", state))));
3794 }
3795
3796 let invitee = self
3797 .get_member_no_sync(self.own_user_id())
3798 .await?
3799 .ok_or_else(|| Error::UnknownError(Box::new(InvitationError::EventMissing)))?;
3800 let event = invitee.event();
3801
3802 let inviter_id = event.sender().to_owned();
3803 let inviter = self.get_member_no_sync(&inviter_id).await?;
3804
3805 Ok(Invite { invitee, inviter_id, inviter })
3806 }
3807
3808 pub async fn member_with_sender_info(
3816 &self,
3817 user_id: &UserId,
3818 ) -> Result<RoomMemberWithSenderInfo> {
3819 let Some(member) = self.get_member_no_sync(user_id).await? else {
3820 return Err(Error::InsufficientData);
3821 };
3822
3823 let sender_member = if let Some(member) =
3824 self.get_member_no_sync(member.event().sender()).await?
3825 {
3826 Some(member)
3828 } else if self.are_members_synced() {
3829 None
3831 } else if self.sync_members().await.is_ok() {
3832 self.get_member_no_sync(member.event().sender()).await?
3834 } else {
3835 None
3836 };
3837
3838 Ok(RoomMemberWithSenderInfo { room_member: member, sender_info: sender_member })
3839 }
3840
3841 pub async fn forget(&self) -> Result<()> {
3847 let state = self.state();
3848 match state {
3849 RoomState::Joined | RoomState::Invited | RoomState::Knocked => {
3850 return Err(Error::WrongRoomState(Box::new(WrongRoomState::new(
3851 "Left / Banned",
3852 state,
3853 ))));
3854 }
3855 RoomState::Left | RoomState::Banned => {}
3856 }
3857
3858 let room_id = self.room_id();
3859
3860 let request = forget_room::v3::Request::new(room_id.to_owned());
3861 let _response = self.client.send(request).await?;
3862
3863 if self.inner.direct_targets_length() != 0
3866 && let Err(e) = self.set_is_direct(false).await
3867 {
3868 warn!(?room_id, "failed to remove room from m.direct account data: {e}");
3871 }
3872
3873 self.client.base_client().forget_room(room_id).await?;
3874
3875 if let Some(event_cache) = self.client.event_cache() {
3876 event_cache.forget_room(room_id).await?;
3877 }
3878
3879 Ok(())
3880 }
3881
3882 fn ensure_room_joined(&self) -> Result<()> {
3883 let state = self.state();
3884 if state == RoomState::Joined {
3885 Ok(())
3886 } else {
3887 Err(Error::WrongRoomState(Box::new(WrongRoomState::new("Joined", state))))
3888 }
3889 }
3890
3891 pub async fn notification_mode(&self) -> Option<RoomNotificationMode> {
3893 if !matches!(self.state(), RoomState::Joined) {
3894 return None;
3895 }
3896
3897 let notification_settings = self.client().notification_settings().await;
3898
3899 let notification_mode =
3901 notification_settings.get_user_defined_room_notification_mode(self.room_id()).await;
3902
3903 if notification_mode.is_some() {
3904 notification_mode
3905 } else if let Ok(is_encrypted) =
3906 self.latest_encryption_state().await.map(|state| state.is_encrypted())
3907 {
3908 let is_one_to_one = IsOneToOne::from(self.active_members_count() == 2);
3913 let default_mode = notification_settings
3914 .get_default_room_notification_mode(IsEncrypted::from(is_encrypted), is_one_to_one)
3915 .await;
3916 Some(default_mode)
3917 } else {
3918 None
3919 }
3920 }
3921
3922 pub async fn user_defined_notification_mode(&self) -> Option<RoomNotificationMode> {
3932 if !matches!(self.state(), RoomState::Joined) {
3933 return None;
3934 }
3935
3936 let notification_settings = self.client().notification_settings().await;
3937
3938 let mode =
3940 notification_settings.get_user_defined_room_notification_mode(self.room_id()).await;
3941
3942 if let Some(mode) = mode {
3943 self.update_cached_user_defined_notification_mode(mode);
3944 }
3945
3946 mode
3947 }
3948
3949 pub async fn report_content(
3962 &self,
3963 event_id: OwnedEventId,
3964 reason: Option<String>,
3965 ) -> Result<report_content::v3::Response> {
3966 let state = self.state();
3967 if state != RoomState::Joined {
3968 return Err(Error::WrongRoomState(Box::new(WrongRoomState::new("Joined", state))));
3969 }
3970
3971 let request = assign!(
3972 report_content::v3::Request::new(
3973 self.inner.room_id().to_owned(),
3974 event_id,
3975 ), {
3976 reason: reason
3977 }
3978 );
3979 Ok(self.client.send(request).await?)
3980 }
3981
3982 pub async fn report_room(&self, reason: String) -> Result<report_room::v3::Response> {
3993 let request = report_room::v3::Request::new(self.inner.room_id().to_owned(), reason);
3994
3995 Ok(self.client.send(request).await?)
3996 }
3997
3998 pub async fn set_unread_flag(&self, unread: bool) -> Result<()> {
4004 if self.is_marked_unread() == unread {
4005 return Ok(());
4007 }
4008
4009 let user_id = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
4010
4011 let content = MarkedUnreadEventContent::new(unread);
4012
4013 let request = set_room_account_data::v3::Request::new(
4014 user_id.to_owned(),
4015 self.inner.room_id().to_owned(),
4016 &content,
4017 )?;
4018
4019 self.client.send(request).await?;
4020 Ok(())
4021 }
4022
4023 pub(crate) async fn get_user_beacon_info(
4030 &self,
4031 user_id: &UserId,
4032 ) -> Result<OriginalSyncStateEvent<BeaconInfoEventContent>, BeaconError> {
4033 let raw_event = self
4034 .get_state_event_static_for_key::<BeaconInfoEventContent, _>(user_id)
4035 .await?
4036 .ok_or(BeaconError::NotFound)?;
4037
4038 match raw_event.deserialize()? {
4039 SyncOrStrippedState::Sync(SyncStateEvent::Original(beacon_info)) => Ok(beacon_info),
4040 SyncOrStrippedState::Sync(SyncStateEvent::Redacted(_)) => Err(BeaconError::Redacted),
4041 SyncOrStrippedState::Stripped(_) => Err(BeaconError::Stripped),
4042 }
4043 }
4044
4045 pub async fn start_live_location_share(
4058 &self,
4059 duration_millis: u64,
4060 description: Option<String>,
4061 ) -> Result<send_state_event::v3::Response> {
4062 self.ensure_room_joined()?;
4063
4064 self.send_state_event_for_key(
4065 self.own_user_id(),
4066 BeaconInfoEventContent::new(
4067 description,
4068 Duration::from_millis(duration_millis),
4069 true,
4070 None,
4071 ),
4072 )
4073 .await
4074 }
4075
4076 pub async fn stop_live_location_share(
4084 &self,
4085 ) -> Result<send_state_event::v3::Response, BeaconError> {
4086 self.ensure_room_joined()?;
4087
4088 let mut beacon_info_event = self.get_user_beacon_info(self.own_user_id()).await?;
4089
4090 if beacon_info_event.content.live {
4091 beacon_info_event.content.stop();
4092 Ok(self.send_state_event_for_key(self.own_user_id(), beacon_info_event.content).await?)
4093 } else {
4094 Err(BeaconError::NotLive)
4095 }
4096 }
4097
4098 pub async fn send_location_beacon(
4110 &self,
4111 geo_uri: String,
4112 ) -> Result<send_message_event::v3::Response, BeaconError> {
4113 self.ensure_room_joined()?;
4114
4115 let beacon_info_event = self.get_user_beacon_info(self.own_user_id()).await?;
4116
4117 if beacon_info_event.content.is_live() {
4118 let content = BeaconEventContent::new(beacon_info_event.event_id, geo_uri, None);
4119 Ok(self
4120 .send(content)
4121 .with_request_config(RequestConfig::new().retry_limit(6))
4122 .await?
4123 .response)
4124 } else {
4125 Err(BeaconError::NotLive)
4126 }
4127 }
4128
4129 pub async fn save_composer_draft(
4132 &self,
4133 draft: ComposerDraft,
4134 thread_root: Option<&EventId>,
4135 ) -> Result<()> {
4136 self.client
4137 .state_store()
4138 .set_kv_data(
4139 StateStoreDataKey::ComposerDraft(self.room_id(), thread_root),
4140 StateStoreDataValue::ComposerDraft(draft),
4141 )
4142 .await?;
4143 Ok(())
4144 }
4145
4146 pub async fn load_composer_draft(
4149 &self,
4150 thread_root: Option<&EventId>,
4151 ) -> Result<Option<ComposerDraft>> {
4152 let data = self
4153 .client
4154 .state_store()
4155 .get_kv_data(StateStoreDataKey::ComposerDraft(self.room_id(), thread_root))
4156 .await?;
4157 Ok(data.and_then(|d| d.into_composer_draft()))
4158 }
4159
4160 pub async fn clear_composer_draft(&self, thread_root: Option<&EventId>) -> Result<()> {
4163 self.client
4164 .state_store()
4165 .remove_kv_data(StateStoreDataKey::ComposerDraft(self.room_id(), thread_root))
4166 .await?;
4167 Ok(())
4168 }
4169
4170 pub async fn load_pinned_events(&self) -> Result<Option<Vec<OwnedEventId>>> {
4173 let response = self
4174 .client
4175 .send(get_state_event_for_key::v3::Request::new(
4176 self.room_id().to_owned(),
4177 StateEventType::RoomPinnedEvents,
4178 "".to_owned(),
4179 ))
4180 .await;
4181
4182 match response {
4183 Ok(response) => Ok(Some(
4184 response
4185 .into_content()
4186 .deserialize_as_unchecked::<RoomPinnedEventsEventContent>()?
4187 .pinned,
4188 )),
4189 Err(http_error) => match http_error.as_client_api_error() {
4190 Some(error) if error.status_code == StatusCode::NOT_FOUND => Ok(None),
4191 _ => Err(http_error.into()),
4192 },
4193 }
4194 }
4195
4196 pub async fn subscribe_to_knock_requests(
4211 &self,
4212 ) -> Result<(impl Stream<Item = Vec<KnockRequest>> + use<>, JoinHandle<()>)> {
4213 let this = Arc::new(self.clone());
4214
4215 let room_member_events_observer =
4216 self.client.observe_room_events::<SyncRoomMemberEvent, (Client, Room)>(this.room_id());
4217
4218 let current_seen_ids = self.get_seen_knock_request_ids().await?;
4219 let mut seen_request_ids_stream = self
4220 .seen_knock_request_ids_map
4221 .subscribe()
4222 .await
4223 .map(|values| values.unwrap_or_default());
4224
4225 let mut room_info_stream = self.subscribe_info();
4226
4227 let clear_seen_ids_handle = spawn({
4230 let this = self.clone();
4231 async move {
4232 let mut member_updates_stream = this.room_member_updates_sender.subscribe();
4233 while member_updates_stream.recv().await.is_ok() {
4234 if let Err(err) = this.remove_outdated_seen_knock_requests_ids().await {
4237 warn!("Failed to remove seen knock requests: {err}")
4238 }
4239 }
4240 }
4241 });
4242
4243 let combined_stream = stream! {
4244 match this.get_current_join_requests(¤t_seen_ids).await {
4246 Ok(initial_requests) => yield initial_requests,
4247 Err(err) => warn!("Failed to get initial requests to join: {err}")
4248 }
4249
4250 let mut requests_stream = room_member_events_observer.subscribe();
4251 let mut seen_ids = current_seen_ids.clone();
4252
4253 loop {
4254 tokio::select! {
4257 Some((event, _)) = requests_stream.next() => {
4258 if let Some(event) = event.as_original() {
4259 let emit = if event.prev_content().is_some() {
4261 matches!(event.membership_change(),
4262 MembershipChange::Banned |
4263 MembershipChange::Knocked |
4264 MembershipChange::KnockAccepted |
4265 MembershipChange::KnockDenied |
4266 MembershipChange::KnockRetracted
4267 )
4268 } else {
4269 true
4272 };
4273
4274 if emit {
4275 match this.get_current_join_requests(&seen_ids).await {
4276 Ok(requests) => yield requests,
4277 Err(err) => {
4278 warn!("Failed to get updated knock requests on new member event: {err}")
4279 }
4280 }
4281 }
4282 }
4283 }
4284
4285 Some(new_seen_ids) = seen_request_ids_stream.next() => {
4286 seen_ids = new_seen_ids;
4288
4289 match this.get_current_join_requests(&seen_ids).await {
4292 Ok(requests) => yield requests,
4293 Err(err) => {
4294 warn!("Failed to get updated knock requests on seen ids changed: {err}")
4295 }
4296 }
4297 }
4298
4299 Some(room_info) = room_info_stream.next() => {
4300 if !room_info.are_members_synced() {
4304 match this.get_current_join_requests(&seen_ids).await {
4305 Ok(requests) => yield requests,
4306 Err(err) => {
4307 warn!("Failed to get updated knock requests on gappy (limited) sync: {err}")
4308 }
4309 }
4310 }
4311 }
4312 else => break,
4314 }
4315 }
4316 };
4317
4318 Ok((combined_stream, clear_seen_ids_handle))
4319 }
4320
4321 async fn get_current_join_requests(
4322 &self,
4323 seen_request_ids: &BTreeMap<OwnedEventId, OwnedUserId>,
4324 ) -> Result<Vec<KnockRequest>> {
4325 Ok(self
4326 .members(RoomMemberships::KNOCK)
4327 .await?
4328 .into_iter()
4329 .filter_map(|member| {
4330 let event_id = member.event().event_id()?;
4331 Some(KnockRequest::new(
4332 self,
4333 event_id,
4334 member.event().timestamp(),
4335 KnockRequestMemberInfo::from_member(&member),
4336 seen_request_ids.contains_key(event_id),
4337 ))
4338 })
4339 .collect())
4340 }
4341
4342 pub async fn effective_retention(&self) -> Result<Option<RoomRetentionEventContent>> {
4356 let config = match self.client.get_retention_configuration().await {
4357 Ok(config) => config,
4358 Err(e) if e.is_endpoint_not_implemented() => {
4359 debug!(
4360 room_id = ?self.room_id(),
4361 "Homeserver doesn't implement the MSC1763 retention configuration \
4362 endpoint; treating this room as having no effective retention policy"
4363 );
4364 return Ok(None);
4365 }
4366 Err(e) => return Err(e.into()),
4367 };
4368 Ok(self.effective_retention_with_server_config(&config))
4369 }
4370
4371 pub fn effective_retention_with_server_config(
4389 &self,
4390 config: &get_retention_configuration::msc1763::Response,
4391 ) -> Option<RoomRetentionEventContent> {
4392 use ruma::api::client::retention::{
4393 RoomIdOrAllRooms, get_retention_configuration::msc1763::LifetimeLimits,
4394 };
4395
4396 fn clamp(value: Option<Duration>, limits: &Option<LifetimeLimits>) -> Option<Duration> {
4397 let Some(limits) = limits else { return value };
4400 let Some(value) = value else {
4401 return limits.min();
4404 };
4405 let min = limits.min().unwrap_or(Duration::ZERO);
4407 let max = limits.max().unwrap_or(Duration::MAX);
4408 Some(value.clamp(min, max))
4409 }
4410
4411 if let Some(policy) =
4413 config.policies.get(&RoomIdOrAllRooms::RoomId(self.room_id().to_owned()))
4414 {
4415 return Some(policy.clone());
4416 }
4417
4418 let Some(room_policy) = self.retention() else {
4420 return config.policies.get(&RoomIdOrAllRooms::AllRooms).cloned();
4421 };
4422
4423 let max_lifetime = clamp(room_policy.max_lifetime(), &config.limits.max_lifetime);
4425 let min_lifetime = clamp(room_policy.min_lifetime(), &config.limits.min_lifetime);
4426
4427 let min_lifetime = min_lifetime.map(|min| {
4434 let Some(max) = max_lifetime else { return min };
4435 if min > max {
4436 warn!(
4437 room_id = ?self.room_id(),
4438 ?min, ?max,
4439 "Retention min_lifetime exceeds max_lifetime after clamping to server limits; \
4440 capping min_lifetime to max_lifetime"
4441 );
4442 max
4443 } else {
4444 min
4445 }
4446 });
4447
4448 match (max_lifetime, min_lifetime) {
4449 (Some(max), Some(min)) => RoomRetentionEventContent::from_range(min..=max),
4450 (Some(max), None) => RoomRetentionEventContent::new().at_most(max),
4451 (None, Some(min)) => RoomRetentionEventContent::new().at_least(min),
4452 (None, None) => Some(RoomRetentionEventContent::new()),
4453 }
4454 }
4455
4456 pub fn privacy_settings(&self) -> RoomPrivacySettings<'_> {
4458 RoomPrivacySettings::new(&self.inner, &self.client)
4459 }
4460
4461 pub async fn list_threads(&self, opts: ListThreadsOptions) -> Result<ThreadRoots> {
4469 let request = opts.into_request(self.room_id());
4470
4471 let response = self.client.send(request).await?;
4472
4473 let push_ctx = self.push_context().await?;
4474 let chunk = join_all(
4475 response.chunk.into_iter().map(|ev| self.try_decrypt_event(ev, push_ctx.as_ref())),
4476 )
4477 .await;
4478
4479 Ok(ThreadRoots { chunk, prev_batch_token: response.next_batch })
4480 }
4481
4482 pub async fn relations(
4496 &self,
4497 event_id: OwnedEventId,
4498 opts: RelationsOptions,
4499 ) -> Result<Relations> {
4500 opts.send(self, event_id).await
4501 }
4502
4503 #[instrument(skip(self), fields(room_id = %self.room_id()))]
4525 pub async fn subscribe_thread(
4526 &self,
4527 thread_root: OwnedEventId,
4528 automatic: Option<OwnedEventId>,
4529 ) -> Result<()> {
4530 let is_automatic = automatic.is_some();
4531
4532 match self
4533 .client
4534 .send(subscribe_thread::msc4306::Request::new(
4535 self.room_id().to_owned(),
4536 thread_root.clone(),
4537 automatic,
4538 ))
4539 .await
4540 {
4541 Ok(_response) => {
4542 trace!("Server acknowledged the thread subscription; saving in db");
4543
4544 self.client
4546 .state_store()
4547 .upsert_thread_subscriptions(vec![(
4548 self.room_id(),
4549 &thread_root,
4550 StoredThreadSubscription {
4551 status: ThreadSubscriptionStatus::Subscribed {
4552 automatic: is_automatic,
4553 },
4554 bump_stamp: None,
4555 },
4556 )])
4557 .await?;
4558
4559 Ok(())
4560 }
4561
4562 Err(err) => {
4563 if let Some(ErrorKind::ConflictingUnsubscription) = err.client_api_error_kind() {
4564 trace!("Thread subscription skipped: {err}");
4570 Ok(())
4571 } else {
4572 Err(err.into())
4574 }
4575 }
4576 }
4577 }
4578
4579 pub async fn subscribe_thread_if_needed(
4585 &self,
4586 thread_root: &EventId,
4587 automatic: Option<OwnedEventId>,
4588 ) -> Result<()> {
4589 if let Some(prev_sub) = self.load_or_fetch_thread_subscription(thread_root).await? {
4590 if !prev_sub.automatic || automatic.is_some() {
4593 return Ok(());
4597 }
4598 }
4599 self.subscribe_thread(thread_root.to_owned(), automatic).await
4600 }
4601
4602 #[instrument(skip(self), fields(room_id = %self.room_id()))]
4614 pub async fn unsubscribe_thread(&self, thread_root: OwnedEventId) -> Result<()> {
4615 self.client
4616 .send(unsubscribe_thread::msc4306::Request::new(
4617 self.room_id().to_owned(),
4618 thread_root.clone(),
4619 ))
4620 .await?;
4621
4622 trace!("Server acknowledged the thread subscription removal; removed it from db too");
4623
4624 self.client
4626 .state_store()
4627 .upsert_thread_subscriptions(vec![(
4628 self.room_id(),
4629 &thread_root,
4630 StoredThreadSubscription {
4631 status: ThreadSubscriptionStatus::Unsubscribed,
4632 bump_stamp: None,
4633 },
4634 )])
4635 .await?;
4636
4637 Ok(())
4638 }
4639
4640 #[instrument(skip(self), fields(room_id = %self.room_id()))]
4657 pub async fn fetch_thread_subscription(
4658 &self,
4659 thread_root: OwnedEventId,
4660 ) -> Result<Option<ThreadSubscription>> {
4661 let result = self
4662 .client
4663 .send(get_thread_subscription::msc4306::Request::new(
4664 self.room_id().to_owned(),
4665 thread_root.clone(),
4666 ))
4667 .await;
4668
4669 let subscription = match result {
4670 Ok(response) => Some(ThreadSubscription { automatic: response.automatic }),
4671 Err(http_error) => match http_error.as_client_api_error() {
4672 Some(error) if error.status_code == StatusCode::NOT_FOUND => None,
4673 _ => return Err(http_error.into()),
4674 },
4675 };
4676
4677 if let Some(sub) = &subscription {
4679 self.client
4680 .state_store()
4681 .upsert_thread_subscriptions(vec![(
4682 self.room_id(),
4683 &thread_root,
4684 StoredThreadSubscription {
4685 status: ThreadSubscriptionStatus::Subscribed { automatic: sub.automatic },
4686 bump_stamp: None,
4687 },
4688 )])
4689 .await?;
4690 } else {
4691 self.client
4693 .state_store()
4694 .remove_thread_subscription(self.room_id(), &thread_root)
4695 .await?;
4696 }
4697
4698 Ok(subscription)
4699 }
4700
4701 pub async fn load_or_fetch_thread_subscription(
4708 &self,
4709 thread_root: &EventId,
4710 ) -> Result<Option<ThreadSubscription>> {
4711 if self.client.thread_subscription_catchup().is_outdated() {
4713 return self.fetch_thread_subscription(thread_root.to_owned()).await;
4714 }
4715
4716 Ok(self
4718 .client
4719 .state_store()
4720 .load_thread_subscription(self.room_id(), thread_root)
4721 .await
4722 .map(|maybe_sub| {
4723 maybe_sub.and_then(|stored| match stored.status {
4724 ThreadSubscriptionStatus::Unsubscribed => None,
4725 ThreadSubscriptionStatus::Subscribed { automatic } => {
4726 Some(ThreadSubscription { automatic })
4727 }
4728 })
4729 })?)
4730 }
4731
4732 pub async fn pin_event(&self, event_id: &EventId) -> Result<bool> {
4742 let mut pinned_event_ids = if let Some(event_ids) = self.pinned_event_ids() {
4743 event_ids
4744 } else {
4745 self.load_pinned_events().await?.unwrap_or_default()
4746 };
4747 let event_id = event_id.to_owned();
4748 if pinned_event_ids.contains(&event_id) {
4749 Ok(false)
4750 } else {
4751 pinned_event_ids.push(event_id);
4752 let content = RoomPinnedEventsEventContent::new(pinned_event_ids);
4753 self.send_state_event(content).await?;
4754 Ok(true)
4755 }
4756 }
4757
4758 pub async fn unpin_event(&self, event_id: &EventId) -> Result<bool> {
4768 let mut pinned_event_ids = if let Some(event_ids) = self.pinned_event_ids() {
4769 event_ids
4770 } else {
4771 self.load_pinned_events().await?.unwrap_or_default()
4772 };
4773 let event_id = event_id.to_owned();
4774 if let Some(idx) = pinned_event_ids.iter().position(|e| *e == *event_id) {
4775 pinned_event_ids.remove(idx);
4776 let content = RoomPinnedEventsEventContent::new(pinned_event_ids);
4777 self.send_state_event(content).await?;
4778 Ok(true)
4779 } else {
4780 Ok(false)
4781 }
4782 }
4783
4784 pub async fn compute_is_dm(&self) -> Result<bool> {
4787 Ok(self.inner.compute_is_dm(self.client.dm_room_definition()).await?)
4788 }
4789
4790 pub fn is_dm(&self) -> bool {
4795 let is_direct = self.direct_targets_length() == 1;
4797 match self.client.dm_room_definition() {
4798 DmRoomDefinition::MatrixSpec => {
4799 is_direct
4801 }
4802 DmRoomDefinition::TwoMembers => {
4803 let active_service_member_count =
4807 self.active_service_members_count().unwrap_or_else(|| {
4808 self.service_members().map(|members| members.len()).unwrap_or_default()
4811 as u64
4812 });
4813 let has_at_most_two_active_members =
4814 self.active_members_count().saturating_sub(active_service_member_count) <= 2;
4815 is_direct && has_at_most_two_active_members
4816 }
4817 }
4818 }
4819}
4820
4821#[cfg(feature = "e2e-encryption")]
4822impl RoomIdentityProvider for Room {
4823 fn is_member<'a>(&'a self, user_id: &'a UserId) -> BoxFuture<'a, bool> {
4824 Box::pin(async { self.get_member(user_id).await.unwrap_or(None).is_some() })
4825 }
4826
4827 fn member_identities(&self) -> BoxFuture<'_, Vec<UserIdentity>> {
4828 Box::pin(async {
4829 let members = self
4830 .members(RoomMemberships::JOIN | RoomMemberships::INVITE)
4831 .await
4832 .unwrap_or_else(|_| Default::default());
4833
4834 let mut ret: Vec<UserIdentity> = Vec::new();
4835 for member in members {
4836 if let Some(i) = self.user_identity(member.user_id()).await {
4837 ret.push(i);
4838 }
4839 }
4840 ret
4841 })
4842 }
4843
4844 fn user_identity<'a>(&'a self, user_id: &'a UserId) -> BoxFuture<'a, Option<UserIdentity>> {
4845 Box::pin(async {
4846 self.client
4847 .encryption()
4848 .get_user_identity(user_id)
4849 .await
4850 .unwrap_or(None)
4851 .map(|u| u.underlying_identity())
4852 })
4853 }
4854}
4855
4856#[derive(Clone, Debug)]
4859pub(crate) struct WeakRoom {
4860 client: WeakClient,
4861 room_id: OwnedRoomId,
4862}
4863
4864impl WeakRoom {
4865 pub fn new(client: WeakClient, room_id: OwnedRoomId) -> Self {
4867 Self { client, room_id }
4868 }
4869
4870 pub fn get(&self) -> Option<Room> {
4872 self.client.get().and_then(|client| client.get_room(&self.room_id))
4873 }
4874
4875 pub fn room_id(&self) -> &RoomId {
4877 &self.room_id
4878 }
4879}
4880
4881#[derive(Debug, Clone)]
4883pub struct Invite {
4884 pub invitee: RoomMember,
4886
4887 pub inviter_id: OwnedUserId,
4891
4892 pub inviter: Option<RoomMember>,
4896}
4897
4898#[derive(Error, Debug)]
4899enum InvitationError {
4900 #[error("No membership event found")]
4901 EventMissing,
4902}
4903
4904#[derive(Debug, Clone, Default)]
4906#[non_exhaustive]
4907pub struct Receipts {
4908 pub fully_read: Option<OwnedEventId>,
4910 pub public_read_receipt: Option<OwnedEventId>,
4912 pub private_read_receipt: Option<OwnedEventId>,
4914}
4915
4916impl Receipts {
4917 pub fn new() -> Self {
4919 Self::default()
4920 }
4921
4922 pub fn fully_read_marker(mut self, event_id: impl Into<Option<OwnedEventId>>) -> Self {
4931 self.fully_read = event_id.into();
4932 self
4933 }
4934
4935 pub fn public_read_receipt(mut self, event_id: impl Into<Option<OwnedEventId>>) -> Self {
4941 self.public_read_receipt = event_id.into();
4942 self
4943 }
4944
4945 pub fn private_read_receipt(mut self, event_id: impl Into<Option<OwnedEventId>>) -> Self {
4949 self.private_read_receipt = event_id.into();
4950 self
4951 }
4952
4953 pub fn is_empty(&self) -> bool {
4955 self.fully_read.is_none()
4956 && self.public_read_receipt.is_none()
4957 && self.private_read_receipt.is_none()
4958 }
4959}
4960
4961#[derive(Debug)]
4964pub enum ParentSpace {
4965 Reciprocal(Room),
4968 WithPowerlevel(Room),
4973 Illegitimate(Room),
4976 Unverifiable(OwnedRoomId),
4979}
4980
4981pub(crate) trait EventSource {
4982 fn get_event(
4983 &self,
4984 event_id: &EventId,
4985 ) -> impl Future<Output = Result<TimelineEvent, Error>> + SendOutsideWasm;
4986}
4987
4988impl EventSource for &Room {
4989 async fn get_event(&self, event_id: &EventId) -> Result<TimelineEvent, Error> {
4990 self.load_or_fetch_event(event_id, None).await
4991 }
4992}
4993
4994#[derive(Debug)]
4997pub struct RoomMemberWithSenderInfo {
4998 pub room_member: RoomMember,
5000 pub sender_info: Option<RoomMember>,
5003}
5004
5005fn has_state_events_of_type(update: &RoomUpdate, event_type: &StateEventType) -> bool {
5009 let RoomUpdate::Joined { updates, .. } = update else {
5011 return false;
5012 };
5013
5014 let (State::Before(state_events) | State::After(state_events)) = &updates.state;
5015
5016 state_events.iter().any(|raw| {
5017 raw.get_field::<StateEventType>("type")
5018 .ok()
5019 .flatten()
5020 .is_some_and(|received_type| received_type == *event_type)
5021 })
5022}
5023
5024#[cfg(all(test, not(target_family = "wasm")))]
5025mod tests {
5026 use std::time::Duration;
5027
5028 use matrix_sdk_base::{ComposerDraft, DraftAttachment, store::ComposerDraftType};
5029 use matrix_sdk_test::{
5030 JoinedRoomBuilder, SyncResponseBuilder, TestRoom, async_test, event_factory::EventFactory,
5031 };
5032 use ruma::{
5033 RoomVersionId, event_id,
5034 events::{
5035 relation::RelationType,
5036 room::{member::MembershipState, retention::RoomRetentionEventContent},
5037 },
5038 owned_event_id, room_id, user_id,
5039 };
5040 use wiremock::{
5041 Mock, MockServer, ResponseTemplate,
5042 matchers::{header, method, path, path_regex},
5043 };
5044
5045 use crate::{
5046 Client, Room,
5047 config::RequestConfig,
5048 room::messages::{IncludeRelations, ListThreadsOptions, RelationsOptions},
5049 test_utils::{
5050 client::mock_matrix_session,
5051 logged_in_client,
5052 mocks::{MatrixMockServer, RoomRelationsResponseTemplate},
5053 },
5054 };
5055
5056 #[cfg(all(feature = "sqlite", feature = "e2e-encryption"))]
5057 #[async_test]
5058 async fn test_cache_invalidation_while_encrypt() {
5059 use matrix_sdk_base::store::RoomLoadSettings;
5060 use matrix_sdk_test::{TestRoom, message_like_event_content};
5061
5062 let tmp_dir = tempfile::tempdir().unwrap();
5063 let sqlite_path = tmp_dir.path().join("cache_invalidation_while_encrypt.db");
5064 let session = mock_matrix_session();
5065
5066 let client = Client::builder()
5067 .homeserver_url("http://localhost:1234")
5068 .request_config(RequestConfig::new().disable_retry())
5069 .sqlite_store(&sqlite_path, None)
5070 .build()
5071 .await
5072 .unwrap();
5073 client
5074 .matrix_auth()
5075 .restore_session(session.clone(), RoomLoadSettings::default())
5076 .await
5077 .unwrap();
5078
5079 client.encryption().enable_cross_process_store_lock("client1".to_owned()).await.unwrap();
5080
5081 let server = MockServer::start().await;
5083 {
5084 Mock::given(method("GET"))
5085 .and(path_regex(r"^/_matrix/client/r0/rooms/.*/state/m.*room.*encryption.?"))
5086 .and(header("authorization", "Bearer 1234"))
5087 .respond_with(
5088 ResponseTemplate::new(200)
5089 .set_body_json(EventFactory::new().room_encryption().into_content()),
5090 )
5091 .mount(&server)
5092 .await;
5093 let f = EventFactory::new().sender(user_id!("@example:localhost"));
5094 let response = SyncResponseBuilder::default()
5095 .add_joined_room(
5096 JoinedRoomBuilder::default()
5097 .add_state_event(
5098 f.member(user_id!("@example:localhost")).display_name("example"),
5099 )
5100 .add_state_event(f.power_levels())
5101 .add_state_event(f.room_encryption()),
5102 )
5103 .build_sync_response();
5104 client.base_client().receive_sync_response(response).await.unwrap();
5105 }
5106
5107 let room = client.get_room(TestRoom::Default.into()).expect("Room should exist");
5108
5109 room.preshare_room_key().await.unwrap();
5111
5112 {
5115 let client = Client::builder()
5116 .homeserver_url("http://localhost:1234")
5117 .request_config(RequestConfig::new().disable_retry())
5118 .sqlite_store(&sqlite_path, None)
5119 .build()
5120 .await
5121 .unwrap();
5122 client
5123 .matrix_auth()
5124 .restore_session(session.clone(), RoomLoadSettings::default())
5125 .await
5126 .unwrap();
5127 client
5128 .encryption()
5129 .enable_cross_process_store_lock("client2".to_owned())
5130 .await
5131 .unwrap();
5132
5133 let guard = client.encryption().spin_lock_store(None).await.unwrap();
5134 assert!(guard.is_some());
5135 }
5136
5137 let guard = client.encryption().spin_lock_store(None).await.unwrap();
5139 assert!(guard.is_some());
5140
5141 let olm = client.olm_machine().await;
5143 let olm = olm.as_ref().expect("Olm machine wasn't started");
5144
5145 let _encrypted_content = olm
5148 .encrypt_room_event_raw(room.room_id(), "test-event", &message_like_event_content!({}))
5149 .await
5150 .unwrap();
5151 }
5152
5153 #[async_test]
5154 async fn test_composer_draft() {
5155 let client = logged_in_client(None).await;
5156
5157 let response = SyncResponseBuilder::default()
5158 .add_joined_room(JoinedRoomBuilder::default())
5159 .build_sync_response();
5160 client.base_client().receive_sync_response(response).await.unwrap();
5161 let room = client.get_room(TestRoom::Default.into()).expect("Room should exist");
5162
5163 assert_eq!(room.load_composer_draft(None).await.unwrap(), None);
5164
5165 let draft = ComposerDraft {
5168 plain_text: "Hello, world!".to_owned(),
5169 html_text: Some("<strong>Hello</strong>, world!".to_owned()),
5170 draft_type: ComposerDraftType::NewMessage,
5171 attachments: vec![DraftAttachment {
5172 filename: "cat.txt".to_owned(),
5173 content: matrix_sdk_base::DraftAttachmentContent::File {
5174 data: b"meow".to_vec(),
5175 mimetype: Some("text/plain".to_owned()),
5176 size: Some(5),
5177 },
5178 }],
5179 };
5180
5181 room.save_composer_draft(draft.clone(), None).await.unwrap();
5182
5183 let thread_root = owned_event_id!("$thread_root:b.c");
5184 let thread_draft = ComposerDraft {
5185 plain_text: "Hello, thread!".to_owned(),
5186 html_text: Some("<strong>Hello</strong>, thread!".to_owned()),
5187 draft_type: ComposerDraftType::NewMessage,
5188 attachments: vec![DraftAttachment {
5189 filename: "dog.txt".to_owned(),
5190 content: matrix_sdk_base::DraftAttachmentContent::File {
5191 data: b"wuv".to_vec(),
5192 mimetype: Some("text/plain".to_owned()),
5193 size: Some(4),
5194 },
5195 }],
5196 };
5197
5198 room.save_composer_draft(thread_draft.clone(), Some(&thread_root)).await.unwrap();
5199
5200 assert_eq!(room.load_composer_draft(None).await.unwrap(), Some(draft));
5202
5203 assert_eq!(
5205 room.load_composer_draft(Some(&thread_root)).await.unwrap(),
5206 Some(thread_draft.clone())
5207 );
5208
5209 room.clear_composer_draft(None).await.unwrap();
5211 assert_eq!(room.load_composer_draft(None).await.unwrap(), None);
5212
5213 assert_eq!(room.load_composer_draft(Some(&thread_root)).await.unwrap(), Some(thread_draft));
5215
5216 room.clear_composer_draft(Some(&thread_root)).await.unwrap();
5218 assert_eq!(room.load_composer_draft(Some(&thread_root)).await.unwrap(), None);
5219 }
5220
5221 #[async_test]
5222 async fn test_mark_join_requests_as_seen() {
5223 let server = MatrixMockServer::new().await;
5224 let client = server.client_builder().build().await;
5225 let event_id = event_id!("$a:b.c");
5226 let room_id = room_id!("!a:b.c");
5227 let user_id = user_id!("@alice:b.c");
5228
5229 let f = EventFactory::new().room(room_id);
5230 let joined_room_builder = JoinedRoomBuilder::new(room_id).add_state_bulk(vec![
5231 f.member(user_id).membership(MembershipState::Knock).event_id(event_id).into(),
5232 ]);
5233 let room = server.sync_room(&client, joined_room_builder).await;
5234
5235 let seen_ids =
5237 room.get_seen_knock_request_ids().await.expect("Couldn't load seen join request ids");
5238 assert!(seen_ids.is_empty());
5239
5240 room.mark_knock_requests_as_seen(&[user_id.to_owned()])
5242 .await
5243 .expect("Couldn't mark join request as seen");
5244
5245 let seen_ids =
5247 room.get_seen_knock_request_ids().await.expect("Couldn't load seen join request ids");
5248 assert_eq!(seen_ids.len(), 1);
5249 assert_eq!(
5250 seen_ids.into_iter().next().expect("No next value"),
5251 (event_id.to_owned(), user_id.to_owned())
5252 )
5253 }
5254
5255 #[async_test]
5256 async fn test_own_room_membership_with_no_own_member_event() {
5257 let server = MatrixMockServer::new().await;
5258 let client = server.client_builder().build().await;
5259 let room_id = room_id!("!a:b.c");
5260
5261 let room = server.sync_joined_room(&client, room_id).await;
5262
5263 let error = room.member_with_sender_info(client.user_id().unwrap()).await.err();
5266 assert!(error.is_some());
5267 }
5268
5269 #[async_test]
5270 async fn test_own_room_membership_with_own_member_event_but_unknown_sender() {
5271 let server = MatrixMockServer::new().await;
5272 let client = server.client_builder().build().await;
5273 let room_id = room_id!("!a:b.c");
5274 let user_id = user_id!("@example:localhost");
5275
5276 let f = EventFactory::new().room(room_id).sender(user_id!("@alice:b.c"));
5277 let joined_room_builder =
5278 JoinedRoomBuilder::new(room_id).add_state_bulk(vec![f.member(user_id).into()]);
5279 let room = server.sync_room(&client, joined_room_builder).await;
5280
5281 let ret = room
5283 .member_with_sender_info(client.user_id().unwrap())
5284 .await
5285 .expect("Room member info should be available");
5286
5287 assert_eq!(ret.room_member.event().user_id(), user_id);
5289
5290 assert!(ret.sender_info.is_none());
5292 }
5293
5294 #[async_test]
5295 async fn test_own_room_membership_with_own_member_event_and_own_sender() {
5296 let server = MatrixMockServer::new().await;
5297 let client = server.client_builder().build().await;
5298 let room_id = room_id!("!a:b.c");
5299 let user_id = user_id!("@example:localhost");
5300
5301 let f = EventFactory::new().room(room_id).sender(user_id);
5302 let joined_room_builder =
5303 JoinedRoomBuilder::new(room_id).add_state_bulk(vec![f.member(user_id).into()]);
5304 let room = server.sync_room(&client, joined_room_builder).await;
5305
5306 let ret = room
5308 .member_with_sender_info(client.user_id().unwrap())
5309 .await
5310 .expect("Room member info should be available");
5311
5312 assert_eq!(ret.room_member.event().user_id(), user_id);
5314
5315 assert!(ret.sender_info.is_some());
5317 assert_eq!(ret.sender_info.unwrap().event().user_id(), user_id);
5318 }
5319
5320 #[async_test]
5321 async fn test_own_room_membership_with_own_member_event_and_known_sender() {
5322 let server = MatrixMockServer::new().await;
5323 let client = server.client_builder().build().await;
5324 let room_id = room_id!("!a:b.c");
5325 let user_id = user_id!("@example:localhost");
5326 let sender_id = user_id!("@alice:b.c");
5327
5328 let f = EventFactory::new().room(room_id).sender(sender_id);
5329 let joined_room_builder = JoinedRoomBuilder::new(room_id).add_state_bulk(vec![
5330 f.member(user_id).into(),
5331 f.member(sender_id).into(),
5333 ]);
5334 let room = server.sync_room(&client, joined_room_builder).await;
5335
5336 let ret = room
5338 .member_with_sender_info(client.user_id().unwrap())
5339 .await
5340 .expect("Room member info should be available");
5341
5342 assert_eq!(ret.room_member.event().user_id(), user_id);
5344
5345 assert!(ret.sender_info.is_some());
5347 assert_eq!(ret.sender_info.unwrap().event().user_id(), sender_id);
5348 }
5349
5350 #[async_test]
5351 async fn test_own_room_membership_with_own_member_event_and_unknown_but_available_sender() {
5352 let server = MatrixMockServer::new().await;
5353 let client = server.client_builder().build().await;
5354 let room_id = room_id!("!a:b.c");
5355 let user_id = user_id!("@example:localhost");
5356 let sender_id = user_id!("@alice:b.c");
5357
5358 let f = EventFactory::new().room(room_id).sender(sender_id);
5359 let joined_room_builder =
5360 JoinedRoomBuilder::new(room_id).add_state_bulk(vec![f.member(user_id).into()]);
5361 let room = server.sync_room(&client, joined_room_builder).await;
5362
5363 server
5365 .mock_get_members()
5366 .ok(vec![f.member(sender_id).into_raw()])
5367 .mock_once()
5368 .mount()
5369 .await;
5370
5371 let ret = room
5373 .member_with_sender_info(client.user_id().unwrap())
5374 .await
5375 .expect("Room member info should be available");
5376
5377 assert_eq!(ret.room_member.event().user_id(), user_id);
5379
5380 assert!(ret.sender_info.is_some());
5382 assert_eq!(ret.sender_info.unwrap().event().user_id(), sender_id);
5383 }
5384
5385 #[async_test]
5386 async fn test_list_threads() {
5387 let server = MatrixMockServer::new().await;
5388 let client = server.client_builder().build().await;
5389
5390 let room_id = room_id!("!a:b.c");
5391 let sender_id = user_id!("@alice:b.c");
5392 let f = EventFactory::new().room(room_id).sender(sender_id);
5393
5394 let eid1 = event_id!("$1");
5395 let eid2 = event_id!("$2");
5396 let batch1 = vec![f.text_msg("Thread root 1").event_id(eid1).into_raw()];
5397 let batch2 = vec![f.text_msg("Thread root 2").event_id(eid2).into_raw()];
5398
5399 server
5400 .mock_room_threads()
5401 .ok(batch1.clone(), Some("prev_batch".to_owned()))
5402 .mock_once()
5403 .mount()
5404 .await;
5405 server
5406 .mock_room_threads()
5407 .match_from("prev_batch")
5408 .ok(batch2, None)
5409 .mock_once()
5410 .mount()
5411 .await;
5412
5413 let room = server.sync_joined_room(&client, room_id).await;
5414 let result =
5415 room.list_threads(ListThreadsOptions::default()).await.expect("Failed to list threads");
5416 assert_eq!(result.chunk.len(), 1);
5417 assert_eq!(result.chunk[0].event_id().unwrap(), eid1);
5418 assert!(result.prev_batch_token.is_some());
5419
5420 let opts = ListThreadsOptions { from: result.prev_batch_token, ..Default::default() };
5421 let result = room.list_threads(opts).await.expect("Failed to list threads");
5422 assert_eq!(result.chunk.len(), 1);
5423 assert_eq!(result.chunk[0].event_id().unwrap(), eid2);
5424 assert!(result.prev_batch_token.is_none());
5425 }
5426
5427 #[async_test]
5428 async fn test_relations() {
5429 let server = MatrixMockServer::new().await;
5430 let client = server.client_builder().build().await;
5431
5432 let room_id = room_id!("!a:b.c");
5433 let sender_id = user_id!("@alice:b.c");
5434 let f = EventFactory::new().room(room_id).sender(sender_id);
5435
5436 let target_event_id = owned_event_id!("$target");
5437 let eid1 = event_id!("$1");
5438 let eid2 = event_id!("$2");
5439 let batch1 = vec![f.text_msg("Related event 1").event_id(eid1).into_raw()];
5440 let batch2 = vec![f.text_msg("Related event 2").event_id(eid2).into_raw()];
5441
5442 server
5443 .mock_room_relations()
5444 .match_target_event(target_event_id.clone())
5445 .ok(RoomRelationsResponseTemplate::default().events(batch1).next_batch("next_batch"))
5446 .mock_once()
5447 .mount()
5448 .await;
5449
5450 server
5451 .mock_room_relations()
5452 .match_target_event(target_event_id.clone())
5453 .match_from("next_batch")
5454 .ok(RoomRelationsResponseTemplate::default().events(batch2))
5455 .mock_once()
5456 .mount()
5457 .await;
5458
5459 let room = server.sync_joined_room(&client, room_id).await;
5460
5461 let mut opts = RelationsOptions {
5463 include_relations: IncludeRelations::AllRelations,
5464 ..Default::default()
5465 };
5466 let result = room
5467 .relations(target_event_id.clone(), opts.clone())
5468 .await
5469 .expect("Failed to list relations the first time");
5470 assert_eq!(result.chunk.len(), 1);
5471 assert_eq!(result.chunk[0].event_id().unwrap(), eid1);
5472 assert!(result.prev_batch_token.is_none());
5473 assert!(result.next_batch_token.is_some());
5474 assert!(result.recursion_depth.is_none());
5475
5476 opts.from = result.next_batch_token;
5477 let result = room
5478 .relations(target_event_id, opts)
5479 .await
5480 .expect("Failed to list relations the second time");
5481 assert_eq!(result.chunk.len(), 1);
5482 assert_eq!(result.chunk[0].event_id().unwrap(), eid2);
5483 assert!(result.prev_batch_token.is_none());
5484 assert!(result.next_batch_token.is_none());
5485 assert!(result.recursion_depth.is_none());
5486 }
5487
5488 #[async_test]
5489 async fn test_relations_with_reltype() {
5490 let server = MatrixMockServer::new().await;
5491 let client = server.client_builder().build().await;
5492
5493 let room_id = room_id!("!a:b.c");
5494 let sender_id = user_id!("@alice:b.c");
5495 let f = EventFactory::new().room(room_id).sender(sender_id);
5496
5497 let target_event_id = owned_event_id!("$target");
5498 let eid1 = event_id!("$1");
5499 let eid2 = event_id!("$2");
5500 let batch1 = vec![f.text_msg("In-thread event 1").event_id(eid1).into_raw()];
5501 let batch2 = vec![f.text_msg("In-thread event 2").event_id(eid2).into_raw()];
5502
5503 server
5504 .mock_room_relations()
5505 .match_target_event(target_event_id.clone())
5506 .match_subrequest(IncludeRelations::RelationsOfType(RelationType::Thread))
5507 .ok(RoomRelationsResponseTemplate::default().events(batch1).next_batch("next_batch"))
5508 .mock_once()
5509 .mount()
5510 .await;
5511
5512 server
5513 .mock_room_relations()
5514 .match_target_event(target_event_id.clone())
5515 .match_from("next_batch")
5516 .match_subrequest(IncludeRelations::RelationsOfType(RelationType::Thread))
5517 .ok(RoomRelationsResponseTemplate::default().events(batch2))
5518 .mock_once()
5519 .mount()
5520 .await;
5521
5522 let room = server.sync_joined_room(&client, room_id).await;
5523
5524 let mut opts = RelationsOptions {
5526 include_relations: IncludeRelations::RelationsOfType(RelationType::Thread),
5527 ..Default::default()
5528 };
5529 let result = room
5530 .relations(target_event_id.clone(), opts.clone())
5531 .await
5532 .expect("Failed to list relations the first time");
5533 assert_eq!(result.chunk.len(), 1);
5534 assert_eq!(result.chunk[0].event_id().unwrap(), eid1);
5535 assert!(result.prev_batch_token.is_none());
5536 assert!(result.next_batch_token.is_some());
5537 assert!(result.recursion_depth.is_none());
5538
5539 opts.from = result.next_batch_token;
5540 let result = room
5541 .relations(target_event_id, opts)
5542 .await
5543 .expect("Failed to list relations the second time");
5544 assert_eq!(result.chunk.len(), 1);
5545 assert_eq!(result.chunk[0].event_id().unwrap(), eid2);
5546 assert!(result.prev_batch_token.is_none());
5547 assert!(result.next_batch_token.is_none());
5548 assert!(result.recursion_depth.is_none());
5549 }
5550
5551 #[async_test]
5552 async fn test_power_levels_computation() {
5553 let server = MatrixMockServer::new().await;
5554 let client = server.client_builder().build().await;
5555
5556 let room_id = room_id!("!a:b.c");
5557 let sender_id = client.user_id().expect("No session id");
5558 let f = EventFactory::new().room(room_id).sender(sender_id);
5559
5560 let room_create_event = f.create(sender_id, RoomVersionId::V1).into();
5562 let power_levels_event = f.power_levels().set_users([(sender_id, 50)]).into();
5563 let room_member_event = f.member(sender_id).into();
5564
5565 let room = server
5567 .sync_room(&client, JoinedRoomBuilder::new(room_id).add_state_bulk([room_member_event]))
5568 .await;
5569 let ctx = room
5570 .push_condition_room_ctx()
5571 .await
5572 .expect("Failed to get push condition context")
5573 .expect("Could not get push condition context");
5574
5575 assert!(ctx.power_levels.is_none());
5577
5578 let room = server
5580 .sync_room(&client, JoinedRoomBuilder::new(room_id).add_state_bulk([room_create_event]))
5581 .await;
5582 let ctx = room
5583 .push_condition_room_ctx()
5584 .await
5585 .expect("Failed to get push condition context")
5586 .expect("Could not get push condition context");
5587
5588 assert!(ctx.power_levels.is_none());
5590
5591 let room = server
5593 .sync_room(
5594 &client,
5595 JoinedRoomBuilder::new(room_id).add_state_bulk([power_levels_event]),
5596 )
5597 .await;
5598 let ctx = room
5599 .push_condition_room_ctx()
5600 .await
5601 .expect("Failed to get push condition context")
5602 .expect("Could not get push condition context");
5603
5604 assert!(ctx.power_levels.is_some());
5606 }
5607
5608 const ONE_DAY: Duration = Duration::from_secs(86_400);
5611 const ONE_WEEK: Duration = Duration::from_secs(86_400 * 7);
5612 const ONE_HOUR: Duration = Duration::from_secs(3_600);
5613
5614 fn retention_room_id() -> &'static ruma::RoomId {
5615 room_id!("!a:b.c")
5616 }
5617
5618 async fn mock_retention_config(
5619 server: &MatrixMockServer,
5620 body: serde_json::Value,
5621 ) -> wiremock::MockGuard {
5622 Mock::given(method("GET"))
5623 .and(path("/_matrix/client/unstable/org.matrix.msc1763/retention/configuration"))
5624 .respond_with(ResponseTemplate::new(200).set_body_json(body))
5625 .mount_as_scoped(server.server())
5626 .await
5627 }
5628
5629 async fn room_with_retention(
5630 server: &MatrixMockServer,
5631 client: &Client,
5632 content: RoomRetentionEventContent,
5633 ) -> Room {
5634 server
5635 .sync_room(
5636 client,
5637 JoinedRoomBuilder::new(retention_room_id()).add_state_event(
5638 EventFactory::new().sender(user_id!("@alice:b.c")).event(content).state_key(""),
5639 ),
5640 )
5641 .await
5642 }
5643
5644 #[async_test]
5645 async fn test_effective_retention_no_policy_anywhere() {
5646 let server = MatrixMockServer::new().await;
5647 let client = server.client_builder().build().await;
5648 let room = server.sync_joined_room(&client, retention_room_id()).await;
5649
5650 let _mock = mock_retention_config(
5651 &server,
5652 serde_json::json!({
5653 "policies": {},
5654 "limits": {},
5655 }),
5656 )
5657 .await;
5658
5659 let result = room.effective_retention().await.unwrap();
5660 assert!(result.is_none());
5661 }
5662
5663 #[async_test]
5664 async fn test_effective_retention_endpoint_not_implemented() {
5665 let server = MatrixMockServer::new().await;
5670 let client = server.client_builder().build().await;
5671 let room = room_with_retention(
5672 &server,
5673 &client,
5674 RoomRetentionEventContent::new().at_most(ONE_DAY).unwrap(),
5675 )
5676 .await;
5677
5678 Mock::given(method("GET"))
5679 .and(path("/_matrix/client/unstable/org.matrix.msc1763/retention/configuration"))
5680 .respond_with(ResponseTemplate::new(404).set_body_json(serde_json::json!({
5681 "errcode": "M_UNRECOGNIZED",
5682 "error": "Unrecognized request",
5683 })))
5684 .mount(server.server())
5685 .await;
5686
5687 let result = room.effective_retention().await.unwrap();
5688 assert!(result.is_none());
5689 }
5690
5691 #[async_test]
5692 async fn test_effective_retention_server_default_only() {
5693 let server = MatrixMockServer::new().await;
5694 let client = server.client_builder().build().await;
5695 let room = server.sync_joined_room(&client, retention_room_id()).await;
5696
5697 let _mock = mock_retention_config(
5698 &server,
5699 serde_json::json!({
5700 "policies": {
5701 "*": { "max_lifetime": ONE_WEEK.as_millis() as u64 }
5702 },
5703 "limits": {},
5704 }),
5705 )
5706 .await;
5707
5708 let result = room.effective_retention().await.unwrap().unwrap();
5709 assert_eq!(result.max_lifetime(), Some(ONE_WEEK));
5710 assert!(result.min_lifetime().is_none());
5711 }
5712
5713 #[async_test]
5714 async fn test_effective_retention_server_room_override() {
5715 let server = MatrixMockServer::new().await;
5718 let client = server.client_builder().build().await;
5719 let room = room_with_retention(
5720 &server,
5721 &client,
5722 RoomRetentionEventContent::new().at_most(ONE_DAY).unwrap(),
5723 )
5724 .await;
5725
5726 let _mock = mock_retention_config(
5727 &server,
5728 serde_json::json!({
5729 "policies": {
5730 "*": { "max_lifetime": ONE_WEEK.as_millis() as u64 },
5731 "!a:b.c": { "max_lifetime": ONE_HOUR.as_millis() as u64 }
5732 },
5733 "limits": {},
5734 }),
5735 )
5736 .await;
5737
5738 let result = room.effective_retention().await.unwrap().unwrap();
5739 assert_eq!(result.max_lifetime(), Some(ONE_HOUR));
5740 }
5741
5742 #[async_test]
5743 async fn test_effective_retention_room_policy_with_limits() {
5744 struct TestCase {
5745 description: &'static str,
5746 room_max: Option<Duration>,
5747 room_min: Option<Duration>,
5748 limits: serde_json::Value,
5749 expected_max: Option<Duration>,
5750 expected_min: Option<Duration>,
5751 }
5752
5753 let test_cases = vec![
5754 TestCase {
5755 description: "room sets `max_lifetime` only, no server limits configured",
5756 room_max: Some(ONE_DAY),
5757 room_min: None,
5758 limits: serde_json::json!({}),
5759 expected_max: Some(ONE_DAY),
5760 expected_min: None,
5761 },
5762 TestCase {
5763 description: "room sets `min_lifetime` only, no server limits configured",
5764 room_max: None,
5765 room_min: Some(ONE_HOUR),
5766 limits: serde_json::json!({}),
5767 expected_max: None,
5768 expected_min: Some(ONE_HOUR),
5769 },
5770 TestCase {
5771 description: "room sets `max_lifetime` and `min_lifetime`, no server limits configured",
5772 room_max: Some(ONE_DAY),
5773 room_min: Some(ONE_HOUR),
5774 limits: serde_json::json!({}),
5775 expected_max: Some(ONE_DAY),
5776 expected_min: Some(ONE_HOUR),
5777 },
5778 TestCase {
5779 description: "room `max_lifetime` above server `max_lifetime.max` → outcome uses server `max_lifetime.max`",
5780 room_max: Some(ONE_WEEK),
5781 room_min: None,
5782 limits: serde_json::json!({
5783 "max_lifetime": { "max": ONE_DAY.as_millis() as u64 }
5784 }),
5785 expected_max: Some(ONE_DAY),
5786 expected_min: None,
5787 },
5788 TestCase {
5789 description: "room `max_lifetime` below server `max_lifetime.min` → outcome uses server `max_lifetime.min`",
5790 room_max: Some(ONE_HOUR),
5791 room_min: None,
5792 limits: serde_json::json!({
5793 "max_lifetime": { "min": ONE_DAY.as_millis() as u64 }
5794 }),
5795 expected_max: Some(ONE_DAY),
5796 expected_min: None,
5797 },
5798 TestCase {
5799 description: "room `min_lifetime` above server `min_lifetime.max` → outcome uses server `min_lifetime.max`",
5800 room_max: None,
5801 room_min: Some(ONE_WEEK),
5802 limits: serde_json::json!({
5803 "min_lifetime": { "max": ONE_DAY.as_millis() as u64 }
5804 }),
5805 expected_max: None,
5806 expected_min: Some(ONE_DAY),
5807 },
5808 TestCase {
5809 description: "room `min_lifetime` below server `min_lifetime.min` → outcome uses server `min_lifetime.min`",
5810 room_max: None,
5811 room_min: Some(ONE_HOUR),
5812 limits: serde_json::json!({
5813 "min_lifetime": { "min": ONE_DAY.as_millis() as u64 }
5814 }),
5815 expected_max: None,
5816 expected_min: Some(ONE_DAY),
5817 },
5818 TestCase {
5819 description: "room `max_lifetime` and `min_lifetime` both within their respective server limits → unchanged",
5820 room_max: Some(ONE_DAY),
5821 room_min: Some(ONE_HOUR),
5822 limits: serde_json::json!({
5823 "max_lifetime": {
5824 "min": ONE_HOUR.as_millis() as u64,
5825 "max": ONE_WEEK.as_millis() as u64,
5826 },
5827 "min_lifetime": {
5828 "min": Duration::from_secs(60).as_millis() as u64,
5829 "max": ONE_DAY.as_millis() as u64,
5830 },
5831 }),
5832 expected_max: Some(ONE_DAY),
5833 expected_min: Some(ONE_HOUR),
5834 },
5835 TestCase {
5836 description: "room `max_lifetime` omitted, server `max_lifetime.min` is used",
5837 room_max: None,
5838 room_min: None,
5839 limits: serde_json::json!({
5840 "max_lifetime": { "min": ONE_DAY.as_millis() as u64 }
5841 }),
5842 expected_max: Some(ONE_DAY),
5843 expected_min: None,
5844 },
5845 TestCase {
5846 description: "room `min_lifetime` omitted, server `min_lifetime.min` is used",
5847 room_max: None,
5848 room_min: None,
5849 limits: serde_json::json!({
5850 "min_lifetime": { "min": ONE_HOUR.as_millis() as u64 }
5851 }),
5852 expected_max: None,
5853 expected_min: Some(ONE_HOUR),
5854 },
5855 TestCase {
5856 description: "independently-clamped `max_lifetime`/`min_lifetime` cross → `min_lifetime` capped down to `max_lifetime`",
5857 room_max: Some(ONE_DAY),
5858 room_min: Some(ONE_HOUR),
5859 limits: serde_json::json!({
5860 "max_lifetime": { "max": ONE_HOUR.as_millis() as u64 },
5861 "min_lifetime": { "min": ONE_WEEK.as_millis() as u64 },
5862 }),
5863 expected_max: Some(ONE_HOUR),
5864 expected_min: Some(ONE_HOUR),
5865 },
5866 TestCase {
5867 description: "room `max_lifetime` omitted, server sets `max_lifetime.max` but no `.min` → stays unbounded (documents MSC1763 gap)",
5868 room_max: None,
5869 room_min: None,
5870 limits: serde_json::json!({
5871 "max_lifetime": { "max": ONE_WEEK.as_millis() as u64 }
5872 }),
5873 expected_max: None,
5874 expected_min: None,
5875 },
5876 ];
5877
5878 for case in test_cases {
5879 let server = MatrixMockServer::new().await;
5880 let client = server.client_builder().build().await;
5881
5882 let mut content = RoomRetentionEventContent::new();
5883 if let Some(max) = case.room_max {
5884 content = content.at_most(max).unwrap();
5885 }
5886 if let Some(min) = case.room_min {
5887 content = content.at_least(min).unwrap();
5888 }
5889
5890 let room = room_with_retention(&server, &client, content).await;
5891
5892 let _mock = mock_retention_config(
5893 &server,
5894 serde_json::json!({
5895 "policies": {},
5896 "limits": case.limits,
5897 }),
5898 )
5899 .await;
5900
5901 let result = room
5902 .effective_retention()
5903 .await
5904 .unwrap_or_else(|e| panic!("{}: effective_retention failed: {e}", case.description))
5905 .unwrap_or_else(|| panic!("{}: expected Some, got None", case.description));
5906
5907 assert_eq!(result.max_lifetime(), case.expected_max, "{}", case.description);
5908 assert_eq!(result.min_lifetime(), case.expected_min, "{}", case.description);
5909 }
5910 }
5911
5912 #[async_test]
5913 async fn test_send_state_event_raw_with_request_config() {
5914 let server = MatrixMockServer::new().await;
5915 let client = server
5917 .client_builder()
5918 .on_builder(|builder| builder.request_config(RequestConfig::new().retry_limit(3)))
5919 .build()
5920 .await;
5921 let room = server.sync_joined_room(&client, room_id!("!a:b.c")).await;
5922
5923 server.mock_room_send_state().error500().expect(1).mount().await;
5924
5925 room.send_state_event_raw("org.example.custom", "", serde_json::json!({}))
5926 .with_request_config(RequestConfig::new().disable_retry())
5927 .await
5928 .unwrap_err();
5929 }
5930}