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 = "e2e-encryption")]
64use ruma::events::AnySyncMessageLikeEvent;
65#[cfg(feature = "experimental-encrypted-state-events")]
66use ruma::events::AnySyncStateEvent;
67#[cfg(feature = "unstable-msc4274")]
68use ruma::events::room::message::GalleryItemType;
69#[cfg(feature = "e2e-encryption")]
70use ruma::events::{
71 AnySyncTimelineEvent, SyncMessageLikeEvent, room::encrypted::OriginalSyncRoomEncryptedEvent,
72};
73use ruma::{
74 EventId, Int, MatrixToUri, MatrixUri, MxcUri, OwnedEventId, OwnedRoomId, OwnedServerName,
75 OwnedTransactionId, OwnedUserId, RoomId, TransactionId, UInt, UserId,
76 api::{
77 client::{
78 config::{set_global_account_data, set_room_account_data},
79 context,
80 filter::LazyLoadOptions,
81 membership::{
82 Invite3pid, ban_user, forget_room, get_member_events,
83 invite_user::{
84 self,
85 v3::{InvitationRecipient, InviteUserId},
86 },
87 kick_user, leave_room, unban_user,
88 },
89 message::send_message_event,
90 read_marker::set_read_marker,
91 receipt::create_receipt,
92 redact::redact_event,
93 retention::get_retention_configuration,
94 room::{get_room_event, report_content, report_room},
95 state::{get_state_event_for_key, send_state_event},
96 tag::{create_tag, delete_tag},
97 threads::{get_thread_subscription, subscribe_thread, unsubscribe_thread},
98 typing::create_typing_event::{
99 self,
100 v3::{Typing, TypingInfo},
101 },
102 },
103 error::ErrorKind,
104 },
105 assign,
106 events::{
107 AnyRoomAccountDataEvent, AnyRoomAccountDataEventContent, AnyTimelineEvent, EmptyStateKey,
108 Mentions, MessageLikeEventContent, OriginalSyncStateEvent, RedactContent,
109 RedactedStateEventContent, RoomAccountDataEvent, RoomAccountDataEventContent,
110 RoomAccountDataEventType, StateEventContent, StateEventType, StaticEventContent,
111 StaticStateEventContent, SyncStateEvent,
112 beacon::BeaconEventContent,
113 beacon_info::BeaconInfoEventContent,
114 direct::DirectEventContent,
115 marked_unread::MarkedUnreadEventContent,
116 receipt::{Receipt, ReceiptThread, ReceiptType},
117 relation::RelationType,
118 room::{
119 ImageInfo, MediaSource, ThumbnailInfo,
120 avatar::{self, RoomAvatarEventContent},
121 encryption::PossiblyRedactedRoomEncryptionEventContent,
122 history_visibility::HistoryVisibility,
123 member::{MembershipChange, RoomMemberEventContent, SyncRoomMemberEvent},
124 message::{
125 AudioInfo, AudioMessageEventContent, FileInfo, FileMessageEventContent,
126 ImageMessageEventContent, MessageType, RoomMessageEventContent,
127 TextMessageEventContent, UnstableAmplitude, UnstableAudioDetailsContentBlock,
128 UnstableVoiceContentBlock, VideoInfo, VideoMessageEventContent,
129 },
130 name::RoomNameEventContent,
131 pinned_events::RoomPinnedEventsEventContent,
132 power_levels::{
133 RoomPowerLevels, RoomPowerLevelsEventContent, RoomPowerLevelsSource, UserPowerLevel,
134 },
135 retention::RoomRetentionEventContent,
136 server_acl::RoomServerAclEventContent,
137 topic::RoomTopicEventContent,
138 },
139 space::{child::SpaceChildEventContent, parent::SpaceParentEventContent},
140 tag::{TagInfo, TagName},
141 typing::SyncTypingEvent,
142 },
143 int,
144 push::{Action, AnyPushRuleRef, PushConditionRoomCtx, Ruleset},
145 serde::Raw,
146 time::Instant,
147 uint,
148};
149#[cfg(feature = "experimental-encrypted-state-events")]
150use ruma::{
151 events::room::encrypted::unstable_state::OriginalSyncStateRoomEncryptedEvent,
152 serde::JsonCastable,
153};
154use serde::de::DeserializeOwned;
155use thiserror::Error;
156use tokio::{join, sync::broadcast};
157use tracing::{debug, error, info, instrument, trace, warn};
158
159use self::futures::{SendAttachment, SendMessageLikeEvent, SendRawMessageLikeEvent};
160pub use self::{
161 member::{RoomMember, RoomMemberRole},
162 messages::{
163 EventWithContextResponse, IncludeRelations, ListThreadsOptions, Messages, MessagesOptions,
164 Relations, RelationsOptions, ThreadRoots,
165 },
166};
167#[cfg(feature = "e2e-encryption")]
168use crate::encryption::backups::BackupState;
169#[cfg(doc)]
170use crate::event_cache::EventCache;
171#[cfg(feature = "experimental-encrypted-state-events")]
172use crate::room::futures::{SendRawStateEvent, SendStateEvent};
173use crate::{
174 BaseRoom, Client, Error, HttpResult, Result, RoomState, TransmissionProgress,
175 attachment::{AttachmentConfig, AttachmentInfo},
176 client::WeakClient,
177 config::RequestConfig,
178 error::{BeaconError, WrongRoomState},
179 event_cache::{self, EventCacheDropHandles, RoomEventCache},
180 event_handler::{EventHandler, EventHandlerDropGuard, EventHandlerHandle, SyncEvent},
181 live_locations_observer::LiveLocationsObserver,
182 media::{MediaFormat, MediaRequestParameters},
183 notification_settings::{IsEncrypted, IsOneToOne, RoomNotificationMode},
184 room::{
185 knock_requests::{KnockRequest, KnockRequestMemberInfo},
186 power_levels::{RoomPowerLevelChanges, RoomPowerLevelsExt},
187 privacy_settings::RoomPrivacySettings,
188 },
189 sync::{RoomUpdate, State},
190 utils::{IntoRawMessageLikeEventContent, IntoRawStateEventContent},
191};
192
193pub mod edit;
194pub mod futures;
195pub mod identity_status_changes;
196pub mod knock_requests;
198mod member;
199mod messages;
200pub mod power_levels;
201pub mod reply;
202
203pub mod calls;
204
205pub mod privacy_settings;
207
208#[cfg(feature = "e2e-encryption")]
209pub(crate) mod shared_room_history;
210
211#[derive(Debug, Clone)]
214pub struct Room {
215 inner: BaseRoom,
216 pub(crate) client: Client,
217}
218
219impl Deref for Room {
220 type Target = BaseRoom;
221
222 fn deref(&self) -> &Self::Target {
223 &self.inner
224 }
225}
226
227const TYPING_NOTICE_TIMEOUT: Duration = Duration::from_secs(4);
228const TYPING_NOTICE_RESEND_TIMEOUT: Duration = Duration::from_secs(3);
229
230#[derive(Debug, Clone, Copy, PartialEq, Eq)]
232pub struct ThreadSubscription {
233 pub automatic: bool,
236}
237
238#[derive(Debug)]
240pub struct PushContext {
241 push_condition_room_ctx: PushConditionRoomCtx,
243
244 push_rules: Ruleset,
247}
248
249impl PushContext {
250 pub fn new(push_condition_room_ctx: PushConditionRoomCtx, push_rules: Ruleset) -> Self {
252 Self { push_condition_room_ctx, push_rules }
253 }
254
255 pub async fn for_event<T>(&self, event: &Raw<T>) -> Vec<Action> {
257 self.push_rules.get_actions(event, &self.push_condition_room_ctx).await.to_owned()
258 }
259
260 #[doc(hidden)]
263 #[instrument(skip_all)]
264 pub async fn traced_for_event<T>(&self, event: &Raw<T>) -> Vec<Action> {
265 let rules = self
266 .push_rules
267 .iter()
268 .filter_map(|r| {
269 if !r.enabled() {
270 return None;
271 }
272
273 let simplified_action = if r.actions().is_empty() { "inhibit" } else { "notify" };
274
275 let conditions = match r {
276 AnyPushRuleRef::Override(r) => {
277 format!("{:?}", r.conditions)
278 }
279 AnyPushRuleRef::Content(r) => format!("content-body-match:{}", r.pattern),
280 AnyPushRuleRef::Room(r) => format!("room-match:{}", r.rule_id),
281 AnyPushRuleRef::Sender(r) => format!("sender-match:{}", r.rule_id),
282 AnyPushRuleRef::Underride(r) => format!("{:?}", r.conditions),
283 _ => "<unknown push rule kind>".to_owned(),
284 };
285
286 Some(format!("- {}: {conditions} => {simplified_action}", r.rule_id(),))
287 })
288 .collect::<Vec<_>>()
289 .join("\n");
290 trace!("rules:\n\n{rules}\n\n");
291
292 let found = self.push_rules.get_match(event, &self.push_condition_room_ctx).await;
293
294 if let Some(found) = found {
295 trace!("rule {} matched", found.rule_id());
296 found.actions().to_owned()
297 } else {
298 trace!("no match");
299 Vec::new()
300 }
301 }
302}
303
304macro_rules! make_media_type {
305 ($t:ty, $content_type: ident, $filename: ident, $source: ident, $caption: ident, $info: ident, $thumbnail: ident) => {{
306 let (body, formatted, filename) = match $caption {
310 Some(TextMessageEventContent { body, formatted, .. }) => (body, formatted, Some($filename)),
311 None => ($filename, None, None),
312 };
313
314 let (thumbnail_source, thumbnail_info) = $thumbnail.unzip();
315
316 match $content_type.type_() {
317 mime::IMAGE => {
318 let info = assign!($info.map(ImageInfo::from).unwrap_or_default(), {
319 mimetype: Some($content_type.as_ref().to_owned()),
320 thumbnail_source,
321 thumbnail_info
322 });
323 let content = assign!(ImageMessageEventContent::new(body, $source), {
324 info: Some(Box::new(info)),
325 formatted,
326 filename
327 });
328 <$t>::Image(content)
329 }
330
331 mime::AUDIO => {
332 let mut content = assign!(AudioMessageEventContent::new(body, $source), {
333 formatted,
334 filename
335 });
336
337 if let Some(AttachmentInfo::Audio(audio_info) | AttachmentInfo::Voice(audio_info)) = &$info &&
338 let Some(duration) = audio_info.duration && let Some(waveform_vec) = &audio_info.waveform {
339 let waveform = waveform_vec
340 .iter()
341 .map(|v| ((*v).clamp(0.0, 1.0) * UnstableAmplitude::MAX as f32) as u16)
342 .map(Into::into)
343 .collect();
344 content.audio =
345 Some(UnstableAudioDetailsContentBlock::new(duration, waveform));
346 }
347
348 if matches!($info, Some(AttachmentInfo::Voice(_))) {
349 content.voice = Some(UnstableVoiceContentBlock::new());
350 }
351
352 let mut audio_info = $info.map(AudioInfo::from).unwrap_or_default();
353 audio_info.mimetype = Some($content_type.as_ref().to_owned());
354 let content = content.info(Box::new(audio_info));
355
356 <$t>::Audio(content)
357 }
358
359 mime::VIDEO => {
360 let info = assign!($info.map(VideoInfo::from).unwrap_or_default(), {
361 mimetype: Some($content_type.as_ref().to_owned()),
362 thumbnail_source,
363 thumbnail_info
364 });
365 let content = assign!(VideoMessageEventContent::new(body, $source), {
366 info: Some(Box::new(info)),
367 formatted,
368 filename
369 });
370 <$t>::Video(content)
371 }
372
373 _ => {
374 let info = assign!($info.map(FileInfo::from).unwrap_or_default(), {
375 mimetype: Some($content_type.as_ref().to_owned()),
376 thumbnail_source,
377 thumbnail_info
378 });
379 let content = assign!(FileMessageEventContent::new(body, $source), {
380 info: Some(Box::new(info)),
381 formatted,
382 filename,
383 });
384 <$t>::File(content)
385 }
386 }
387 }};
388}
389
390impl Room {
391 pub(crate) fn new(client: Client, room: BaseRoom) -> Self {
398 Self { inner: room, client }
399 }
400
401 #[doc(alias = "reject_invitation")]
406 #[instrument(skip_all, fields(room_id = ?self.inner.room_id()))]
407 async fn leave_impl(&self) -> (Result<()>, &Room) {
408 let state = self.state();
409 if state == RoomState::Left {
410 return (
411 Err(Error::WrongRoomState(Box::new(WrongRoomState::new(
412 "Joined or Invited",
413 state,
414 )))),
415 self,
416 );
417 }
418
419 let should_forget = matches!(self.state(), RoomState::Invited);
422
423 let request = leave_room::v3::Request::new(self.inner.room_id().to_owned());
424 let response = self.client.send(request).await;
425
426 if let Err(error) = response {
429 #[allow(clippy::collapsible_match)]
430 let ignore_error = if let Some(error) = error.client_api_error_kind() {
431 match error {
432 ErrorKind::Forbidden => true,
436 _ => false,
437 }
438 } else {
439 false
440 };
441
442 error!(?error, ignore_error, should_forget, "Failed to leave the room");
443
444 if !ignore_error {
445 return (Err(error.into()), self);
446 }
447 }
448
449 if let Err(e) = self.client.base_client().room_left(self.room_id()).await {
450 return (Err(e.into()), self);
451 }
452
453 if should_forget {
454 trace!("Trying to forget the room");
455
456 if let Err(error) = self.forget().await {
457 error!(?error, "Failed to forget the room");
458 }
459 }
460
461 (Ok(()), self)
462 }
463
464 pub async fn leave(&self) -> Result<()> {
471 let mut rooms: Vec<Room> = vec![self.clone()];
472 let mut current_room = self;
473
474 while let Some(predecessor) = current_room.predecessor_room() {
475 let maybe_predecessor_room = current_room.client.get_room(&predecessor.room_id);
476
477 if let Some(predecessor_room) = maybe_predecessor_room {
478 rooms.push(predecessor_room);
479 current_room = rooms.last().expect("Room just pushed so can't be empty");
480 } else {
481 warn!("Cannot find predecessor room");
482 break;
483 }
484 }
485
486 let batch_size = 5;
487
488 let rooms_futures: Vec<_> = rooms
489 .iter()
490 .filter_map(|room| match room.state() {
491 RoomState::Joined | RoomState::Invited | RoomState::Knocked => {
492 Some(room.leave_impl())
493 }
494 RoomState::Banned | RoomState::Left => None,
495 })
496 .collect();
497
498 let mut futures_stream = futures_stream::iter(rooms_futures).buffer_unordered(batch_size);
499
500 let mut maybe_this_room_failed_with: Option<Error> = None;
501
502 while let Some(result) = futures_stream.next().await {
503 if let (Err(e), room) = result {
504 if room.room_id() == self.room_id() {
505 maybe_this_room_failed_with = Some(e);
506 } else {
507 warn!("Failure while attempting to leave predecessor room: {e:?}");
508 }
509 }
510 }
511
512 maybe_this_room_failed_with.map_or(Ok(()), Err)
513 }
514
515 #[doc(alias = "accept_invitation")]
519 pub async fn join(&self) -> Result<()> {
520 let prev_room_state = self.inner.state();
521
522 if prev_room_state == RoomState::Joined {
523 return Err(Error::WrongRoomState(Box::new(WrongRoomState::new(
524 "Invited or Left",
525 prev_room_state,
526 ))));
527 }
528
529 self.client.join_room_by_id(self.room_id()).await?;
530
531 Ok(())
532 }
533
534 pub fn client(&self) -> Client {
538 self.client.clone()
539 }
540
541 pub fn is_synced(&self) -> bool {
544 self.inner.is_state_fully_synced()
545 }
546
547 pub async fn avatar(&self, format: MediaFormat) -> Result<Option<Vec<u8>>> {
576 let Some(url) = self.avatar_url() else { return Ok(None) };
577 let request = MediaRequestParameters { source: MediaSource::Plain(url.to_owned()), format };
578 Ok(Some(self.client.media().get_media_content(&request, true).await?))
579 }
580
581 #[instrument(skip_all, fields(room_id = ?self.inner.room_id(), ?options))]
610 pub async fn messages(&self, options: MessagesOptions) -> Result<Messages> {
611 let room_id = self.inner.room_id();
612 let request = options.into_request(room_id);
613 let http_response = self.client.send(request).await?;
614
615 let push_ctx = self.push_context().await?;
616 let chunk = join_all(
617 http_response.chunk.into_iter().map(|ev| self.try_decrypt_event(ev, push_ctx.as_ref())),
618 )
619 .await;
620
621 Ok(Messages {
622 start: http_response.start,
623 end: http_response.end,
624 chunk,
625 state: http_response.state,
626 })
627 }
628
629 pub fn add_event_handler<Ev, Ctx, H>(&self, handler: H) -> EventHandlerHandle
639 where
640 Ev: SyncEvent + DeserializeOwned + Send + 'static,
641 H: EventHandler<Ev, Ctx>,
642 {
643 self.client.add_room_event_handler(self.room_id(), handler)
644 }
645
646 pub fn subscribe_to_updates(&self) -> broadcast::Receiver<RoomUpdate> {
651 self.client.subscribe_to_room_updates(self.room_id())
652 }
653
654 pub fn subscribe_to_typing_notifications(
660 &self,
661 ) -> (EventHandlerDropGuard, broadcast::Receiver<Vec<OwnedUserId>>) {
662 let (sender, receiver) = broadcast::channel(16);
663 let typing_event_handler_handle = self.client.add_room_event_handler(self.room_id(), {
664 let own_user_id = self.own_user_id().to_owned();
665 move |event: SyncTypingEvent| async move {
666 let typing_user_ids = event
668 .content
669 .user_ids
670 .into_iter()
671 .filter(|user_id| *user_id != own_user_id)
672 .collect();
673 let _ = sender.send(typing_user_ids);
676 }
677 });
678 let drop_guard = self.client().event_handler_drop_guard(typing_event_handler_handle);
679 (drop_guard, receiver)
680 }
681
682 pub fn subscribe_to_state_events(
693 &self,
694 event_type: StateEventType,
695 ) -> impl Stream<Item = Result<Vec<RawAnySyncOrStrippedState>>> + use<> {
696 let room = self.clone();
697 let mut room_updates = self.subscribe_to_updates();
698
699 stream! {
700 yield room.get_state_events(event_type.clone()).await;
705
706 loop {
707 match room_updates.recv().await {
708 Ok(update) => {
709 if !has_state_events_of_type(&update, &event_type) {
710 continue;
711 }
712 }
713 Err(broadcast::error::RecvError::Lagged(_)) => {}
717 Err(broadcast::error::RecvError::Closed) => break,
718 }
719
720 yield room.get_state_events(event_type.clone()).await;
721 }
722 }
723 }
724
725 #[cfg(feature = "e2e-encryption")]
748 pub async fn subscribe_to_identity_status_changes(
749 &self,
750 ) -> Result<impl Stream<Item = Vec<IdentityStatusChange>> + use<>> {
751 IdentityStatusChanges::create_stream(self.clone()).await
752 }
753
754 pub async fn live_locations_observer(&self) -> LiveLocationsObserver {
762 LiveLocationsObserver::new(self.clone()).await
763 }
764
765 #[cfg(not(feature = "experimental-encrypted-state-events"))]
770 #[allow(clippy::unused_async)] async fn try_decrypt_event(
772 &self,
773 event: Raw<AnyTimelineEvent>,
774 push_ctx: Option<&PushContext>,
775 ) -> TimelineEvent {
776 #[cfg(feature = "e2e-encryption")]
777 if let Ok(AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomEncrypted(
778 SyncMessageLikeEvent::Original(_),
779 ))) = event.deserialize_as::<AnySyncTimelineEvent>()
780 && let Ok(event) = self.decrypt_event(event.cast_ref_unchecked(), push_ctx).await
781 {
782 return event;
783 }
784
785 let mut event = TimelineEvent::from_plaintext(event.cast());
786 if let Some(push_ctx) = push_ctx {
787 event.set_push_actions(push_ctx.for_event(event.raw()).await);
788 }
789
790 event
791 }
792
793 #[cfg(feature = "experimental-encrypted-state-events")]
798 #[allow(clippy::unused_async)] async fn try_decrypt_event(
800 &self,
801 event: Raw<AnyTimelineEvent>,
802 push_ctx: Option<&PushContext>,
803 ) -> TimelineEvent {
804 match event.deserialize_as::<AnySyncTimelineEvent>() {
807 Ok(AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomEncrypted(
808 SyncMessageLikeEvent::Original(_),
809 ))) => {
810 if let Ok(event) = self
811 .decrypt_event(
812 event.cast_ref_unchecked::<OriginalSyncRoomEncryptedEvent>(),
813 push_ctx,
814 )
815 .await
816 {
817 return event;
818 }
819 }
820 Ok(AnySyncTimelineEvent::State(AnySyncStateEvent::RoomEncrypted(
821 SyncStateEvent::Original(_),
822 ))) => {
823 if let Ok(event) = self
824 .decrypt_event(
825 event.cast_ref_unchecked::<OriginalSyncStateRoomEncryptedEvent>(),
826 push_ctx,
827 )
828 .await
829 {
830 return event;
831 }
832 }
833 _ => {}
834 }
835
836 let mut event = TimelineEvent::from_plaintext(event.cast());
837 if let Some(push_ctx) = push_ctx {
838 event.set_push_actions(push_ctx.for_event(event.raw()).await);
839 }
840
841 event
842 }
843
844 pub async fn event(
849 &self,
850 event_id: &EventId,
851 request_config: Option<RequestConfig>,
852 ) -> Result<TimelineEvent> {
853 let request =
854 get_room_event::v3::Request::new(self.room_id().to_owned(), event_id.to_owned());
855
856 let raw_event = self.client.send(request).with_request_config(request_config).await?.event;
857 let push_ctx = self.push_context().await?;
858 let event = self.try_decrypt_event(raw_event, push_ctx.as_ref()).await;
859
860 Ok(event)
861 }
862
863 pub async fn load_or_fetch_event(
869 &self,
870 event_id: &EventId,
871 request_config: Option<RequestConfig>,
872 ) -> Result<TimelineEvent> {
873 match self.event_cache().await {
874 Ok((event_cache, _drop_handles)) => {
875 if let Some(event) = event_cache.find_event(event_id).await? {
876 return Ok(event);
877 }
878 }
880 Err(err) => {
881 debug!("error when getting the event cache: {err}");
882 }
883 }
884
885 self.event(event_id, request_config).await
886
887 }
901
902 pub async fn load_or_fetch_event_with_relations(
919 &self,
920 event_id: &EventId,
921 filter: Option<Vec<RelationType>>,
922 request_config: Option<RequestConfig>,
923 ) -> Result<(TimelineEvent, Vec<TimelineEvent>)> {
924 let fetch_relations = async || {
925 let include_relations = if let Some(filter) = &filter
933 && filter.len() == 1
934 {
935 IncludeRelations::RelationsOfType(filter[0].clone())
936 } else {
937 IncludeRelations::AllRelations
938 };
939
940 let mut opts = RelationsOptions {
941 include_relations,
942 recurse: true,
943 limit: Some(uint!(256)),
944 ..Default::default()
945 };
946
947 let mut events = Vec::new();
948 loop {
949 match self.relations(event_id.to_owned(), opts.clone()).await {
950 Ok(relations) => {
951 if let Some(filter) = filter.as_ref() {
952 events.extend(relations.chunk.into_iter().filter_map(|ev| {
955 let (rel_type, _) = extract_relation(ev.raw())?;
956 filter
957 .iter()
958 .any(|ruma_filter| ruma_filter == &rel_type)
959 .then_some(ev)
960 }));
961 } else {
962 events.extend(relations.chunk);
964 }
965
966 if let Some(next_from) = relations.next_batch_token {
967 opts.from = Some(next_from);
968 } else {
969 break events;
970 }
971 }
972
973 Err(err) => {
974 warn!(%event_id, "error when loading relations of pinned event from server: {err}");
975 break events;
976 }
977 }
978 }
979 };
980
981 let event_cache = match self.event_cache().await {
984 Ok((event_cache, drop_handles)) => {
985 if let Some((event, mut relations)) =
986 event_cache.find_event_with_relations(event_id, filter.clone()).await?
987 {
988 if relations.is_empty() {
989 relations = fetch_relations().await;
992 }
993
994 return Ok((event, relations));
995 }
996
997 Some((event_cache, drop_handles))
999 }
1000
1001 Err(err) => {
1002 debug!("error when getting the event cache: {err}");
1003 None
1005 }
1006 };
1007
1008 let event = self.event(event_id, request_config).await?;
1011
1012 if let Some((event_cache, _drop_handles)) = event_cache
1018 && let Some(relations) =
1019 event_cache.find_event_relations(event_id, filter.clone()).await.ok()
1020 && !relations.is_empty()
1021 {
1022 return Ok((event, relations));
1023 }
1024
1025 Ok((event, fetch_relations().await))
1028 }
1029
1030 pub async fn event_with_context(
1033 &self,
1034 event_id: &EventId,
1035 lazy_load_members: bool,
1036 context_size: UInt,
1037 request_config: Option<RequestConfig>,
1038 ) -> Result<EventWithContextResponse> {
1039 let mut request =
1040 context::get_context::v3::Request::new(self.room_id().to_owned(), event_id.to_owned());
1041
1042 request.limit = context_size;
1043
1044 if lazy_load_members {
1045 request.filter.lazy_load_options =
1046 LazyLoadOptions::Enabled { include_redundant_members: false };
1047 }
1048
1049 let response = self.client.send(request).with_request_config(request_config).await?;
1050
1051 let push_ctx = self.push_context().await?;
1052 let push_ctx = push_ctx.as_ref();
1053 let target_event = if let Some(event) = response.event {
1054 Some(self.try_decrypt_event(event, push_ctx).await)
1055 } else {
1056 None
1057 };
1058
1059 let (events_before, events_after) = join!(
1063 join_all(
1064 response.events_before.into_iter().map(|ev| self.try_decrypt_event(ev, push_ctx)),
1065 ),
1066 join_all(
1067 response.events_after.into_iter().map(|ev| self.try_decrypt_event(ev, push_ctx)),
1068 ),
1069 );
1070
1071 Ok(EventWithContextResponse {
1072 event: target_event,
1073 events_before,
1074 events_after,
1075 state: response.state,
1076 prev_batch_token: response.start,
1077 next_batch_token: response.end,
1078 })
1079 }
1080
1081 pub(crate) async fn request_members(&self) -> Result<()> {
1082 self.client
1083 .locks()
1084 .members_request_deduplicated_handler
1085 .run(self.room_id().to_owned(), async move {
1086 let request = get_member_events::v3::Request::new(self.inner.room_id().to_owned());
1087 let response = self
1088 .client
1089 .send(request.clone())
1090 .with_request_config(
1091 RequestConfig::new().timeout(Duration::from_secs(60)).retry_limit(3),
1094 )
1095 .await?;
1096
1097 Box::pin(self.client.base_client().receive_all_members(
1100 self.room_id(),
1101 &request,
1102 &response,
1103 ))
1104 .await?;
1105
1106 Ok(())
1107 })
1108 .await
1109 }
1110
1111 pub async fn request_encryption_state(&self) -> Result<()> {
1116 if !self.inner.encryption_state().is_unknown() {
1117 return Ok(());
1118 }
1119
1120 self.client
1121 .locks()
1122 .encryption_state_deduplicated_handler
1123 .run(self.room_id().to_owned(), async move {
1124 let request = get_state_event_for_key::v3::Request::new(
1126 self.room_id().to_owned(),
1127 StateEventType::RoomEncryption,
1128 "".to_owned(),
1129 );
1130 let response = match self.client.send(request).await {
1131 Ok(response) => Some(
1132 response
1133 .into_content()
1134 .deserialize_as_unchecked::<PossiblyRedactedRoomEncryptionEventContent>(
1135 )?,
1136 ),
1137 Err(err) if err.client_api_error_kind() == Some(&ErrorKind::NotFound) => None,
1138 Err(err) => return Err(err.into()),
1139 };
1140
1141 self.update_and_save_room_info(|mut room_info| {
1144 room_info.mark_encryption_state_synced();
1145 room_info.set_encryption_event(response);
1146 (room_info, RoomInfoNotableUpdateReasons::empty())
1147 })
1148 .await?;
1149
1150 Ok(())
1151 })
1152 .await
1153 }
1154
1155 pub fn encryption_state(&self) -> EncryptionState {
1160 self.inner.encryption_state()
1161 }
1162
1163 pub async fn latest_encryption_state(&self) -> Result<EncryptionState> {
1169 self.request_encryption_state().await?;
1170
1171 Ok(self.encryption_state())
1172 }
1173
1174 #[cfg(feature = "e2e-encryption")]
1176 pub async fn crypto_context_info(&self) -> CryptoContextInfo {
1177 let encryption = self.client.encryption();
1178
1179 let this_device_is_verified = match encryption.get_own_device().await {
1180 Ok(Some(device)) => device.is_verified_with_cross_signing(),
1181
1182 _ => true,
1184 };
1185
1186 let backup_exists_on_server =
1187 encryption.backups().exists_on_server().await.unwrap_or(false);
1188
1189 CryptoContextInfo {
1190 device_creation_ts: encryption.device_creation_timestamp().await,
1191 this_device_is_verified,
1192 is_backup_configured: encryption.backups().state() == BackupState::Enabled,
1193 backup_exists_on_server,
1194 }
1195 }
1196
1197 fn are_events_visible(&self) -> bool {
1198 if let RoomState::Invited = self.inner.state() {
1199 return matches!(
1200 self.inner.history_visibility_or_default(),
1201 HistoryVisibility::WorldReadable | HistoryVisibility::Invited
1202 );
1203 }
1204
1205 true
1206 }
1207
1208 pub async fn sync_members(&self) -> Result<()> {
1214 if !self.are_events_visible() {
1215 return Ok(());
1216 }
1217
1218 if !self.are_members_synced() {
1219 self.request_members().await?;
1220
1221 self.update_active_service_members().await?;
1223
1224 Ok(())
1225 } else {
1226 Ok(())
1227 }
1228 }
1229
1230 pub async fn get_member(&self, user_id: &UserId) -> Result<Option<RoomMember>> {
1244 self.sync_members().await?;
1245 self.get_member_no_sync(user_id).await
1246 }
1247
1248 pub async fn get_member_no_sync(&self, user_id: &UserId) -> Result<Option<RoomMember>> {
1262 Ok(self
1263 .inner
1264 .get_member(user_id)
1265 .await?
1266 .map(|member| RoomMember::new(self.client.clone(), member)))
1267 }
1268
1269 pub async fn members(&self, memberships: RoomMemberships) -> Result<Vec<RoomMember>> {
1278 self.sync_members().await?;
1279 self.members_no_sync(memberships).await
1280 }
1281
1282 pub async fn members_no_sync(&self, memberships: RoomMemberships) -> Result<Vec<RoomMember>> {
1291 Ok(self
1292 .inner
1293 .members(memberships)
1294 .await?
1295 .into_iter()
1296 .map(|member| RoomMember::new(self.client.clone(), member))
1297 .collect())
1298 }
1299
1300 pub async fn human_member_ids(&self, memberships: RoomMemberships) -> Result<Vec<OwnedUserId>> {
1304 self.sync_members().await?;
1305 self.human_member_ids_no_sync(memberships).await
1306 }
1307
1308 pub async fn human_member_ids_no_sync(
1311 &self,
1312 memberships: RoomMemberships,
1313 ) -> Result<Vec<OwnedUserId>> {
1314 Ok(self.inner.human_member_ids(memberships).await?)
1315 }
1316
1317 pub async fn set_own_member_display_name(
1322 &self,
1323 display_name: Option<String>,
1324 ) -> Result<send_state_event::v3::Response> {
1325 let user_id = self.own_user_id();
1326 let member_event =
1327 self.get_state_event_static_for_key::<RoomMemberEventContent, _>(user_id).await?;
1328
1329 let Some(RawSyncOrStrippedState::Sync(raw_event)) = member_event else {
1330 return Err(Error::InsufficientData);
1331 };
1332
1333 let event = raw_event.deserialize()?;
1334
1335 let mut content = match event {
1336 SyncStateEvent::Original(original_event) => original_event.content,
1337 SyncStateEvent::Redacted(redacted_event) => {
1338 RoomMemberEventContent::new(redacted_event.content.membership)
1339 }
1340 };
1341
1342 content.displayname = display_name;
1343 self.send_state_event_for_key(user_id, content).await
1344 }
1345
1346 pub async fn get_state_events(
1348 &self,
1349 event_type: StateEventType,
1350 ) -> Result<Vec<RawAnySyncOrStrippedState>> {
1351 self.client
1352 .state_store()
1353 .get_state_events(self.room_id(), event_type)
1354 .await
1355 .map_err(Into::into)
1356 }
1357
1358 pub async fn get_state_events_static<C>(&self) -> Result<Vec<RawSyncOrStrippedState<C>>>
1375 where
1376 C: StaticEventContent<IsPrefix = ruma::events::False>
1377 + StaticStateEventContent
1378 + RedactContent,
1379 C::Redacted: RedactedStateEventContent,
1380 {
1381 Ok(self.client.state_store().get_state_events_static(self.room_id()).await?)
1382 }
1383
1384 pub async fn get_state_events_for_keys(
1387 &self,
1388 event_type: StateEventType,
1389 state_keys: &[&str],
1390 ) -> Result<Vec<RawAnySyncOrStrippedState>> {
1391 self.client
1392 .state_store()
1393 .get_state_events_for_keys(self.room_id(), event_type, state_keys)
1394 .await
1395 .map_err(Into::into)
1396 }
1397
1398 pub async fn get_state_events_for_keys_static<'a, C, K, I>(
1418 &self,
1419 state_keys: I,
1420 ) -> Result<Vec<RawSyncOrStrippedState<C>>>
1421 where
1422 C: StaticEventContent<IsPrefix = ruma::events::False>
1423 + StaticStateEventContent
1424 + RedactContent,
1425 C::StateKey: Borrow<K>,
1426 C::Redacted: RedactedStateEventContent,
1427 K: AsRef<str> + Sized + Sync + 'a,
1428 I: IntoIterator<Item = &'a K> + Send,
1429 I::IntoIter: Send,
1430 {
1431 Ok(self
1432 .client
1433 .state_store()
1434 .get_state_events_for_keys_static(self.room_id(), state_keys)
1435 .await?)
1436 }
1437
1438 pub async fn get_state_event(
1440 &self,
1441 event_type: StateEventType,
1442 state_key: &str,
1443 ) -> Result<Option<RawAnySyncOrStrippedState>> {
1444 self.client
1445 .state_store()
1446 .get_state_event(self.room_id(), event_type, state_key)
1447 .await
1448 .map_err(Into::into)
1449 }
1450
1451 pub async fn get_state_event_static<C>(&self) -> Result<Option<RawSyncOrStrippedState<C>>>
1470 where
1471 C: StaticEventContent<IsPrefix = ruma::events::False>
1472 + StaticStateEventContent<StateKey = EmptyStateKey>
1473 + RedactContent,
1474 C::Redacted: RedactedStateEventContent,
1475 {
1476 self.get_state_event_static_for_key(&EmptyStateKey).await
1477 }
1478
1479 pub async fn get_state_event_static_for_key<C, K>(
1499 &self,
1500 state_key: &K,
1501 ) -> Result<Option<RawSyncOrStrippedState<C>>>
1502 where
1503 C: StaticEventContent<IsPrefix = ruma::events::False>
1504 + StaticStateEventContent
1505 + RedactContent,
1506 C::StateKey: Borrow<K>,
1507 C::Redacted: RedactedStateEventContent,
1508 K: AsRef<str> + ?Sized + Sync,
1509 {
1510 Ok(self
1511 .client
1512 .state_store()
1513 .get_state_event_static_for_key(self.room_id(), state_key)
1514 .await?)
1515 }
1516
1517 pub async fn parent_spaces(&self) -> Result<impl Stream<Item = Result<ParentSpace>> + '_> {
1521 Ok(self
1526 .get_state_events_static::<SpaceParentEventContent>()
1527 .await?
1528 .into_iter()
1529 .filter_map(|parent_event| match parent_event.deserialize() {
1531 Ok(SyncOrStrippedState::Sync(SyncStateEvent::Original(e))) => {
1532 Some((e.state_key, e.sender))
1533 }
1534 Ok(SyncOrStrippedState::Sync(SyncStateEvent::Redacted(_))) => None,
1535 Ok(SyncOrStrippedState::Stripped(e)) => Some((e.state_key, e.sender)),
1536 Err(e) => {
1537 info!(room_id = ?self.room_id(), "Could not deserialize m.space.parent: {e}");
1538 None
1539 }
1540 })
1541 .map(|(state_key, sender): (OwnedRoomId, OwnedUserId)| async move {
1543 let Some(parent_room) = self.client.get_room(&state_key) else {
1544 return Ok(ParentSpace::Unverifiable(state_key));
1547 };
1548 if let Some(child_event) = parent_room
1551 .get_state_event_static_for_key::<SpaceChildEventContent, _>(self.room_id())
1552 .await?
1553 {
1554 match child_event.deserialize() {
1555 Ok(SyncOrStrippedState::Sync(SyncStateEvent::Original(_))) => {
1556 return Ok(ParentSpace::Reciprocal(parent_room));
1559 }
1560 Ok(SyncOrStrippedState::Sync(SyncStateEvent::Redacted(_))) => {}
1561 Ok(SyncOrStrippedState::Stripped(_)) => {}
1562 Err(e) => {
1563 info!(
1564 room_id = ?self.room_id(), parent_room_id = ?state_key,
1565 "Could not deserialize m.space.child: {e}"
1566 );
1567 }
1568 }
1569 }
1574
1575 let Some(member) = parent_room.get_member(&sender).await? else {
1578 return Ok(ParentSpace::Illegitimate(parent_room));
1580 };
1581
1582 if member.can_send_state(StateEventType::SpaceChild) {
1583 Ok(ParentSpace::WithPowerlevel(parent_room))
1585 } else {
1586 Ok(ParentSpace::Illegitimate(parent_room))
1587 }
1588 })
1589 .collect::<FuturesUnordered<_>>())
1590 }
1591
1592 pub async fn account_data(
1594 &self,
1595 data_type: RoomAccountDataEventType,
1596 ) -> Result<Option<Raw<AnyRoomAccountDataEvent>>> {
1597 self.client
1598 .state_store()
1599 .get_room_account_data_event(self.room_id(), data_type)
1600 .await
1601 .map_err(Into::into)
1602 }
1603
1604 pub async fn account_data_static<C>(&self) -> Result<Option<Raw<RoomAccountDataEvent<C>>>>
1623 where
1624 C: StaticEventContent<IsPrefix = ruma::events::False> + RoomAccountDataEventContent,
1625 {
1626 Ok(self.account_data(C::TYPE.into()).await?.map(Raw::cast_unchecked))
1627 }
1628
1629 #[cfg(feature = "e2e-encryption")]
1634 pub async fn contains_only_verified_devices(&self) -> Result<bool> {
1635 let user_ids = self
1636 .client
1637 .state_store()
1638 .get_user_ids(self.room_id(), RoomMemberships::empty())
1639 .await?;
1640
1641 for user_id in user_ids {
1642 let devices = self.client.encryption().get_user_devices(&user_id).await?;
1643 let any_unverified = devices.devices().any(|d| !d.is_verified());
1644
1645 if any_unverified {
1646 return Ok(false);
1647 }
1648 }
1649
1650 Ok(true)
1651 }
1652
1653 pub async fn set_account_data<T>(
1669 &self,
1670 content: T,
1671 ) -> Result<set_room_account_data::v3::Response>
1672 where
1673 T: RoomAccountDataEventContent,
1674 {
1675 let own_user = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
1676
1677 let request = set_room_account_data::v3::Request::new(
1678 own_user.to_owned(),
1679 self.room_id().to_owned(),
1680 &content,
1681 )?;
1682
1683 Ok(self.client.send(request).await?)
1684 }
1685
1686 pub async fn set_account_data_raw(
1712 &self,
1713 event_type: RoomAccountDataEventType,
1714 content: Raw<AnyRoomAccountDataEventContent>,
1715 ) -> Result<set_room_account_data::v3::Response> {
1716 let own_user = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
1717
1718 let request = set_room_account_data::v3::Request::new_raw(
1719 own_user.to_owned(),
1720 self.room_id().to_owned(),
1721 event_type,
1722 content,
1723 );
1724
1725 Ok(self.client.send(request).await?)
1726 }
1727
1728 pub async fn set_tag(
1759 &self,
1760 tag: TagName,
1761 tag_info: TagInfo,
1762 ) -> Result<create_tag::v3::Response> {
1763 let user_id = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
1764 let request = create_tag::v3::Request::new(
1765 user_id.to_owned(),
1766 self.inner.room_id().to_owned(),
1767 tag.to_string(),
1768 tag_info,
1769 );
1770 Ok(self.client.send(request).await?)
1771 }
1772
1773 pub async fn remove_tag(&self, tag: TagName) -> Result<delete_tag::v3::Response> {
1781 let user_id = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
1782 let request = delete_tag::v3::Request::new(
1783 user_id.to_owned(),
1784 self.inner.room_id().to_owned(),
1785 tag.to_string(),
1786 );
1787 Ok(self.client.send(request).await?)
1788 }
1789
1790 pub async fn set_is_favourite(&self, is_favourite: bool, tag_order: Option<f64>) -> Result<()> {
1800 if is_favourite {
1801 let tag_info = assign!(TagInfo::new(), { order: tag_order });
1802
1803 self.set_tag(TagName::Favorite, tag_info).await?;
1804
1805 if self.is_low_priority() {
1806 self.remove_tag(TagName::LowPriority).await?;
1807 }
1808 } else {
1809 self.remove_tag(TagName::Favorite).await?;
1810 }
1811 Ok(())
1812 }
1813
1814 pub async fn set_is_low_priority(
1824 &self,
1825 is_low_priority: bool,
1826 tag_order: Option<f64>,
1827 ) -> Result<()> {
1828 if is_low_priority {
1829 let tag_info = assign!(TagInfo::new(), { order: tag_order });
1830
1831 self.set_tag(TagName::LowPriority, tag_info).await?;
1832
1833 if self.is_favourite() {
1834 self.remove_tag(TagName::Favorite).await?;
1835 }
1836 } else {
1837 self.remove_tag(TagName::LowPriority).await?;
1838 }
1839 Ok(())
1840 }
1841
1842 pub async fn set_is_direct(&self, is_direct: bool) -> Result<()> {
1852 let user_id = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
1853
1854 let mut content = self
1855 .client
1856 .account()
1857 .account_data::<DirectEventContent>()
1858 .await?
1859 .map(|c| c.deserialize())
1860 .transpose()?
1861 .unwrap_or_default();
1862
1863 let this_room_id = self.inner.room_id();
1864
1865 if is_direct {
1866 let mut room_members = self.members(RoomMemberships::ACTIVE).await?;
1867 room_members.retain(|member| member.user_id() != self.own_user_id());
1868
1869 for member in room_members {
1870 let entry = content.entry(member.user_id().into()).or_default();
1871 if !entry.iter().any(|room_id| room_id == this_room_id) {
1872 entry.push(this_room_id.to_owned());
1873 }
1874 }
1875 } else {
1876 for list in content.values_mut() {
1877 list.retain(|room_id| *room_id != this_room_id);
1878 }
1879
1880 content.retain(|_, list| !list.is_empty());
1882 }
1883
1884 let request = set_global_account_data::v3::Request::new(user_id.to_owned(), &content)?;
1885
1886 self.client.send(request).await?;
1887 Ok(())
1888 }
1889
1890 #[cfg(feature = "e2e-encryption")]
1899 #[cfg(not(feature = "experimental-encrypted-state-events"))]
1900 pub async fn decrypt_event(
1901 &self,
1902 event: &Raw<OriginalSyncRoomEncryptedEvent>,
1903 push_ctx: Option<&PushContext>,
1904 ) -> Result<TimelineEvent> {
1905 let machine = self.client.olm_machine().await;
1906 let machine = machine.as_ref().ok_or(Error::NoOlmMachine)?;
1907
1908 match machine
1909 .try_decrypt_room_event(
1910 event.cast_ref(),
1911 self.inner.room_id(),
1912 self.client.decryption_settings(),
1913 )
1914 .await?
1915 {
1916 RoomEventDecryptionResult::Decrypted(decrypted) => {
1917 let push_actions = if let Some(push_ctx) = push_ctx {
1918 Some(push_ctx.for_event(&decrypted.event).await)
1919 } else {
1920 None
1921 };
1922 Ok(TimelineEvent::from_decrypted(decrypted, push_actions))
1923 }
1924 RoomEventDecryptionResult::UnableToDecrypt(utd_info) => {
1925 self.client
1926 .encryption()
1927 .backups()
1928 .maybe_download_room_key(self.room_id().to_owned(), event.clone());
1929 Ok(TimelineEvent::from_utd(event.clone().cast(), utd_info))
1930 }
1931 }
1932 }
1933
1934 #[cfg(feature = "experimental-encrypted-state-events")]
1943 pub async fn decrypt_event<T: JsonCastable<EncryptedEvent>>(
1944 &self,
1945 event: &Raw<T>,
1946 push_ctx: Option<&PushContext>,
1947 ) -> Result<TimelineEvent> {
1948 let machine = self.client.olm_machine().await;
1949 let machine = machine.as_ref().ok_or(Error::NoOlmMachine)?;
1950
1951 match machine
1952 .try_decrypt_room_event(
1953 event.cast_ref(),
1954 self.inner.room_id(),
1955 self.client.decryption_settings(),
1956 )
1957 .await?
1958 {
1959 RoomEventDecryptionResult::Decrypted(decrypted) => {
1960 let push_actions = if let Some(push_ctx) = push_ctx {
1961 Some(push_ctx.for_event(&decrypted.event).await)
1962 } else {
1963 None
1964 };
1965 Ok(TimelineEvent::from_decrypted(decrypted, push_actions))
1966 }
1967 RoomEventDecryptionResult::UnableToDecrypt(utd_info) => {
1968 self.client
1969 .encryption()
1970 .backups()
1971 .maybe_download_room_key(self.room_id().to_owned(), event.clone());
1972 Ok(TimelineEvent::from_utd(event.clone().cast_unchecked(), utd_info))
1975 }
1976 }
1977 }
1978
1979 #[cfg(feature = "e2e-encryption")]
1993 pub async fn get_encryption_info(
1994 &self,
1995 session_id: &str,
1996 sender: &UserId,
1997 ) -> Option<Arc<EncryptionInfo>> {
1998 let machine = self.client.olm_machine().await;
1999 let machine = machine.as_ref()?;
2000 machine.get_session_encryption_info(self.room_id(), session_id, sender).await.ok()
2001 }
2002
2003 #[cfg(feature = "e2e-encryption")]
2016 pub async fn discard_room_key(&self) -> Result<()> {
2017 let machine = self.client.olm_machine().await;
2018 if let Some(machine) = machine.as_ref() {
2019 machine.discard_room_key(self.inner.room_id()).await?;
2020 Ok(())
2021 } else {
2022 Err(Error::NoOlmMachine)
2023 }
2024 }
2025
2026 #[instrument(skip_all)]
2034 pub async fn ban_user(&self, user_id: &UserId, reason: Option<&str>) -> Result<()> {
2035 let request = assign!(
2036 ban_user::v3::Request::new(self.room_id().to_owned(), user_id.to_owned()),
2037 { reason: reason.map(ToOwned::to_owned) }
2038 );
2039 self.client.send(request).await?;
2040 Ok(())
2041 }
2042
2043 #[instrument(skip_all)]
2051 pub async fn unban_user(&self, user_id: &UserId, reason: Option<&str>) -> Result<()> {
2052 let request = assign!(
2053 unban_user::v3::Request::new(self.room_id().to_owned(), user_id.to_owned()),
2054 { reason: reason.map(ToOwned::to_owned) }
2055 );
2056 self.client.send(request).await?;
2057 Ok(())
2058 }
2059
2060 #[instrument(skip_all)]
2069 pub async fn kick_user(&self, user_id: &UserId, reason: Option<&str>) -> Result<()> {
2070 let request = assign!(
2071 kick_user::v3::Request::new(self.room_id().to_owned(), user_id.to_owned()),
2072 { reason: reason.map(ToOwned::to_owned) }
2073 );
2074 self.client.send(request).await?;
2075 Ok(())
2076 }
2077
2078 #[instrument(skip_all)]
2084 pub async fn invite_user_by_id(&self, user_id: &UserId) -> Result<()> {
2085 #[cfg(feature = "e2e-encryption")]
2086 if self.client.inner.enable_share_history_on_invite {
2087 shared_room_history::share_room_history(self, user_id.to_owned()).await?;
2088 }
2089
2090 let recipient = InvitationRecipient::UserId(InviteUserId::new(user_id.to_owned()));
2091 let request = invite_user::v3::Request::new(self.room_id().to_owned(), recipient);
2092 self.client.send(request).await?;
2093
2094 self.mark_members_missing();
2099
2100 Ok(())
2101 }
2102
2103 #[instrument(skip_all)]
2109 pub async fn invite_user_by_3pid(&self, invite_id: Invite3pid) -> Result<()> {
2110 let recipient = InvitationRecipient::ThirdPartyId(invite_id);
2111 let request = invite_user::v3::Request::new(self.room_id().to_owned(), recipient);
2112 self.client.send(request).await?;
2113
2114 self.mark_members_missing();
2119
2120 Ok(())
2121 }
2122
2123 pub async fn typing_notice(&self, typing: bool) -> Result<()> {
2157 self.ensure_room_joined()?;
2158
2159 let send = if let Some(typing_time) =
2162 self.client.inner.typing_notice_times.read().unwrap().get(self.room_id())
2163 {
2164 if typing_time.elapsed() > TYPING_NOTICE_RESEND_TIMEOUT {
2165 typing || typing_time.elapsed() <= TYPING_NOTICE_TIMEOUT
2169 } else {
2170 !typing
2172 }
2173 } else {
2174 typing
2177 };
2178
2179 if send {
2180 self.send_typing_notice(typing).await?;
2181 }
2182
2183 Ok(())
2184 }
2185
2186 #[instrument(name = "typing_notice", skip(self))]
2187 async fn send_typing_notice(&self, typing: bool) -> Result<()> {
2188 let typing = if typing {
2189 self.client
2190 .inner
2191 .typing_notice_times
2192 .write()
2193 .unwrap()
2194 .insert(self.room_id().to_owned(), Instant::now());
2195 Typing::Yes(TypingInfo::new(TYPING_NOTICE_TIMEOUT))
2196 } else {
2197 self.client.inner.typing_notice_times.write().unwrap().remove(self.room_id());
2198 Typing::No
2199 };
2200
2201 let request = create_typing_event::v3::Request::new(
2202 self.own_user_id().to_owned(),
2203 self.room_id().to_owned(),
2204 typing,
2205 );
2206
2207 self.client.send(request).await?;
2208
2209 Ok(())
2210 }
2211
2212 #[instrument(skip_all)]
2229 pub async fn send_single_receipt(
2230 &self,
2231 receipt_type: create_receipt::v3::ReceiptType,
2232 thread: ReceiptThread,
2233 event_id: OwnedEventId,
2234 ) -> Result<()> {
2235 let request_key = format!("{}|{}", receipt_type, thread.as_str().unwrap_or("<unthreaded>"));
2238
2239 self.client
2240 .inner
2241 .locks
2242 .read_receipt_deduplicated_handler
2243 .run((request_key, event_id.clone()), async {
2244 let is_unthreaded = thread == ReceiptThread::Unthreaded;
2247
2248 let mut request = create_receipt::v3::Request::new(
2249 self.room_id().to_owned(),
2250 receipt_type,
2251 event_id,
2252 );
2253 request.thread = thread;
2254
2255 self.client.send(request).await?;
2256
2257 if is_unthreaded {
2258 self.set_unread_flag(false).await?;
2259 }
2260
2261 Ok(())
2262 })
2263 .await
2264 }
2265
2266 #[instrument(skip_all)]
2276 pub async fn send_multiple_receipts(&self, receipts: Receipts) -> Result<()> {
2277 if receipts.is_empty() {
2278 return Ok(());
2279 }
2280
2281 let Receipts { fully_read, public_read_receipt, private_read_receipt } = receipts;
2282 let request = assign!(set_read_marker::v3::Request::new(self.room_id().to_owned()), {
2283 fully_read,
2284 read_receipt: public_read_receipt,
2285 private_read_receipt,
2286 });
2287
2288 self.client.send(request).await?;
2289
2290 self.set_unread_flag(false).await?;
2291
2292 Ok(())
2293 }
2294
2295 #[allow(unused_variables, unused_mut)]
2299 async fn enable_encryption_inner(&self, encrypted_state_events: bool) -> Result<()> {
2300 use ruma::{
2301 EventEncryptionAlgorithm, events::room::encryption::RoomEncryptionEventContent,
2302 };
2303 const SYNC_WAIT_TIME: Duration = Duration::from_secs(3);
2304
2305 if !self.latest_encryption_state().await?.is_encrypted() {
2306 let mut content =
2307 RoomEncryptionEventContent::new(EventEncryptionAlgorithm::MegolmV1AesSha2);
2308 #[cfg(feature = "experimental-encrypted-state-events")]
2309 if encrypted_state_events {
2310 content = content.with_encrypted_state();
2311 }
2312 self.send_state_event(content).await?;
2313
2314 let res = timeout(
2321 async {
2322 loop {
2323 self.client.inner.sync_beat.listen().await;
2326 let _state_store_lock =
2327 self.client.base_client().state_store_lock().lock().await;
2328
2329 if !self.inner.encryption_state().is_unknown() {
2330 break;
2331 }
2332 }
2333 },
2334 SYNC_WAIT_TIME,
2335 )
2336 .await;
2337
2338 let store_guard = self.client.base_client().state_store_lock().lock().await;
2339
2340 #[cfg(not(feature = "experimental-encrypted-state-events"))]
2342 if res.is_ok() && self.inner.encryption_state().is_encrypted() {
2343 debug!("room successfully marked as encrypted");
2344 return Ok(());
2345 }
2346
2347 #[cfg(feature = "experimental-encrypted-state-events")]
2349 if res.is_ok() && {
2350 if encrypted_state_events {
2351 self.inner.encryption_state().is_state_encrypted()
2352 } else {
2353 self.inner.encryption_state().is_encrypted()
2354 }
2355 } {
2356 debug!("room successfully marked as encrypted");
2357 return Ok(());
2358 }
2359
2360 debug!("still not marked as encrypted, marking encryption state as missing");
2365
2366 self.update_and_save_room_info_with_store_guard(&store_guard, |mut info| {
2367 info.mark_encryption_state_missing();
2368 (info, RoomInfoNotableUpdateReasons::empty())
2369 })
2370 .await?;
2371 }
2372
2373 Ok(())
2374 }
2375
2376 #[instrument(skip_all)]
2408 pub async fn enable_encryption(&self) -> Result<()> {
2409 self.enable_encryption_inner(false).await
2410 }
2411
2412 #[instrument(skip_all)]
2445 #[cfg(feature = "experimental-encrypted-state-events")]
2446 pub async fn enable_encryption_with_state_event_encryption(&self) -> Result<()> {
2447 self.enable_encryption_inner(true).await
2448 }
2449
2450 #[cfg(feature = "e2e-encryption")]
2459 #[instrument(skip_all, fields(room_id = ?self.room_id()))]
2460 async fn preshare_room_key(&self) -> Result<()> {
2461 self.ensure_room_joined()?;
2462
2463 let _guard = self.client.encryption().spin_lock_store(Some(60000)).await?;
2465
2466 self.client
2467 .locks()
2468 .group_session_deduplicated_handler
2469 .run(self.room_id().to_owned(), async move {
2470 {
2471 let members = self
2472 .client
2473 .state_store()
2474 .get_user_ids(self.room_id(), RoomMemberships::ACTIVE)
2475 .await?;
2476 self.client.claim_one_time_keys(members.iter().map(Deref::deref)).await?;
2477 };
2478
2479 let response = self.share_room_key().await;
2480
2481 if let Err(r) = response {
2484 let machine = self.client.olm_machine().await;
2485 if let Some(machine) = machine.as_ref() {
2486 machine.discard_room_key(self.room_id()).await?;
2487 }
2488 return Err(r);
2489 }
2490
2491 Ok(())
2492 })
2493 .await
2494 }
2495
2496 #[cfg(feature = "e2e-encryption")]
2502 #[instrument(skip_all)]
2503 async fn share_room_key(&self) -> Result<()> {
2504 self.ensure_room_joined()?;
2505
2506 let requests = self.client.base_client().share_room_key(self.room_id()).await?;
2507
2508 for request in requests {
2509 let response = self.client.send_to_device(&request).await?;
2510 self.client.mark_request_as_sent(&request.txn_id, &response).await?;
2511 }
2512
2513 Ok(())
2514 }
2515
2516 #[instrument(skip_all)]
2525 pub async fn sync_up(&self) {
2526 while !self.is_synced() && self.state() == RoomState::Joined {
2527 let wait_for_beat = self.client.inner.sync_beat.listen();
2528 let _ = timeout(wait_for_beat, Duration::from_millis(1000)).await;
2530 }
2531 }
2532
2533 pub fn send(&self, content: impl MessageLikeEventContent) -> SendMessageLikeEvent<'_> {
2604 SendMessageLikeEvent::new(self, content)
2605 }
2606
2607 #[cfg(feature = "e2e-encryption")]
2610 async fn query_keys_for_untracked_or_dirty_users(&self) -> Result<()> {
2611 let olm = self.client.olm_machine().await;
2612 let olm = olm.as_ref().expect("Olm machine wasn't started");
2613
2614 let members =
2615 self.client.state_store().get_user_ids(self.room_id(), RoomMemberships::ACTIVE).await?;
2616
2617 let tracked: HashMap<_, _> = olm
2618 .store()
2619 .load_tracked_users()
2620 .await?
2621 .into_iter()
2622 .map(|tracked| (tracked.user_id, tracked.dirty))
2623 .collect();
2624
2625 let members_with_unknown_devices =
2628 members.iter().filter(|member| tracked.get(*member).is_none_or(|dirty| *dirty));
2629
2630 let (req_id, request) =
2631 olm.query_keys_for_users(members_with_unknown_devices.map(|owned| owned.borrow()));
2632
2633 if !request.device_keys.is_empty() {
2634 self.client.keys_query(&req_id, request.device_keys).await?;
2635 }
2636
2637 Ok(())
2638 }
2639
2640 #[instrument(skip_all, fields(event_type, room_id = ?self.room_id(), transaction_id, is_room_encrypted, event_id))]
2683 pub fn send_raw<'a>(
2684 &'a self,
2685 event_type: &'a str,
2686 content: impl IntoRawMessageLikeEventContent,
2687 ) -> SendRawMessageLikeEvent<'a> {
2688 SendRawMessageLikeEvent::new(self, event_type, content)
2691 }
2692
2693 #[instrument(skip_all)]
2739 pub fn send_attachment<'a>(
2740 &'a self,
2741 filename: impl Into<String>,
2742 content_type: &'a Mime,
2743 data: Vec<u8>,
2744 config: AttachmentConfig,
2745 ) -> SendAttachment<'a> {
2746 SendAttachment::new(self, filename.into(), content_type, data, config)
2747 }
2748
2749 #[instrument(skip_all)]
2776 pub(super) async fn prepare_and_send_attachment<'a>(
2777 &'a self,
2778 filename: String,
2779 content_type: &'a Mime,
2780 data: Vec<u8>,
2781 mut config: AttachmentConfig,
2782 send_progress: SharedObservable<TransmissionProgress>,
2783 store_in_cache: bool,
2784 ) -> Result<send_message_event::v3::Response> {
2785 self.ensure_room_joined()?;
2786
2787 let txn_id = config.txn_id.take();
2788 let extra_content = config.extra_content.take();
2789 let mentions = config.mentions.take();
2790
2791 let thumbnail = config.thumbnail.take();
2792
2793 let thumbnail_cache_info = if store_in_cache {
2795 thumbnail
2796 .as_ref()
2797 .map(|thumbnail| (thumbnail.data.clone(), thumbnail.height, thumbnail.width))
2798 } else {
2799 None
2800 };
2801
2802 #[cfg(feature = "e2e-encryption")]
2803 let (media_source, thumbnail) = if self.latest_encryption_state().await?.is_encrypted() {
2804 self.client
2805 .upload_encrypted_media_and_thumbnail(&data, thumbnail, send_progress)
2806 .await?
2807 } else {
2808 self.client
2809 .media()
2810 .upload_plain_media_and_thumbnail(
2811 content_type,
2812 data.clone(),
2815 thumbnail,
2816 send_progress,
2817 )
2818 .await?
2819 };
2820
2821 #[cfg(not(feature = "e2e-encryption"))]
2822 let (media_source, thumbnail) = self
2823 .client
2824 .media()
2825 .upload_plain_media_and_thumbnail(content_type, data.clone(), thumbnail, send_progress)
2826 .await?;
2827
2828 if store_in_cache {
2829 let media_store_lock_guard = self.client.media_store().lock().await?;
2830
2831 debug!("caching the media");
2835 let request =
2836 MediaRequestParameters { source: media_source.clone(), format: MediaFormat::File };
2837
2838 if let Err(err) = media_store_lock_guard
2839 .add_media_content(&request, data, IgnoreMediaRetentionPolicy::No)
2840 .await
2841 {
2842 warn!("unable to cache the media after uploading it: {err}");
2843 }
2844
2845 if let Some(((data, height, width), source)) =
2846 thumbnail_cache_info.zip(thumbnail.as_ref().map(|tuple| &tuple.0))
2847 {
2848 debug!("caching the thumbnail");
2849
2850 let request = MediaRequestParameters {
2851 source: source.clone(),
2852 format: MediaFormat::Thumbnail(MediaThumbnailSettings::new(width, height)),
2853 };
2854
2855 if let Err(err) = media_store_lock_guard
2856 .add_media_content(&request, data, IgnoreMediaRetentionPolicy::No)
2857 .await
2858 {
2859 warn!("unable to cache the media after uploading it: {err}");
2860 }
2861 }
2862 }
2863
2864 let content = self
2865 .make_media_event(
2866 Room::make_attachment_type(
2867 content_type,
2868 filename,
2869 media_source,
2870 config.caption,
2871 config.info,
2872 thumbnail,
2873 ),
2874 mentions,
2875 config.reply,
2876 )
2877 .await?;
2878
2879 if let Some(extra_content) = extra_content.filter(|extra| !extra.is_empty()) {
2883 let serde_json::Value::Object(mut object) = serde_json::to_value(&content)
2884 .map_err(|error| Error::UnknownError(Box::new(error)))?
2885 else {
2886 unreachable!("a room message event content always serializes to a JSON object");
2887 };
2888 for (key, value) in extra_content {
2889 match object.entry(key) {
2890 serde_json::map::Entry::Occupied(entry) => {
2891 warn!(
2892 key = entry.key(),
2893 "extra content field shadowed by the event's own field"
2894 );
2895 }
2896 serde_json::map::Entry::Vacant(entry) => {
2897 entry.insert(value);
2898 }
2899 }
2900 }
2901
2902 let event_type = content.event_type().to_string();
2903 let mut fut = self.send_raw(&event_type, serde_json::Value::Object(object));
2904 if let Some(txn_id) = &txn_id {
2905 fut = fut.with_transaction_id(txn_id);
2906 }
2907 return fut.await.map(|result| result.response);
2908 }
2909
2910 let mut fut = self.send(content);
2911 if let Some(txn_id) = txn_id {
2912 fut = fut.with_transaction_id(txn_id);
2913 }
2914
2915 fut.await.map(|result| result.response)
2916 }
2917
2918 #[allow(clippy::too_many_arguments)]
2921 pub(crate) fn make_attachment_type(
2922 content_type: &Mime,
2923 filename: String,
2924 source: MediaSource,
2925 caption: Option<TextMessageEventContent>,
2926 info: Option<AttachmentInfo>,
2927 thumbnail: Option<(MediaSource, Box<ThumbnailInfo>)>,
2928 ) -> MessageType {
2929 make_media_type!(MessageType, content_type, filename, source, caption, info, thumbnail)
2930 }
2931
2932 pub(crate) async fn make_media_event(
2935 &self,
2936 msg_type: MessageType,
2937 mentions: Option<Mentions>,
2938 reply: Option<Reply>,
2939 ) -> Result<RoomMessageEventContent> {
2940 let mut content = RoomMessageEventContent::new(msg_type);
2941 if let Some(mentions) = mentions {
2942 content = content.add_mentions(mentions);
2943 }
2944 if let Some(reply) = reply {
2945 content = self.make_reply_event(content.into(), reply).await?;
2949 }
2950 Ok(content)
2951 }
2952
2953 #[cfg(feature = "unstable-msc4274")]
2956 #[allow(clippy::too_many_arguments)]
2957 pub(crate) fn make_gallery_item_type(
2958 content_type: &Mime,
2959 filename: String,
2960 source: MediaSource,
2961 caption: Option<TextMessageEventContent>,
2962 info: Option<AttachmentInfo>,
2963 thumbnail: Option<(MediaSource, Box<ThumbnailInfo>)>,
2964 ) -> GalleryItemType {
2965 make_media_type!(GalleryItemType, content_type, filename, source, caption, info, thumbnail)
2966 }
2967
2968 pub async fn update_power_levels(
2976 &self,
2977 updates: Vec<(&UserId, Int)>,
2978 ) -> Result<send_state_event::v3::Response> {
2979 let mut power_levels = self.power_levels().await?;
2980
2981 for (user_id, new_level) in updates {
2982 if new_level == power_levels.users_default {
2983 power_levels.users.remove(user_id);
2984 } else {
2985 power_levels.users.insert(user_id.to_owned(), new_level);
2986 }
2987 }
2988
2989 self.send_state_event(RoomPowerLevelsEventContent::try_from(power_levels)?).await
2990 }
2991
2992 pub async fn apply_power_level_changes(&self, changes: RoomPowerLevelChanges) -> Result<()> {
2997 let mut power_levels = self.power_levels().await?;
2998 power_levels.apply(changes)?;
2999 self.send_state_event(RoomPowerLevelsEventContent::try_from(power_levels)?).await?;
3000 Ok(())
3001 }
3002
3003 pub async fn reset_power_levels(&self) -> Result<RoomPowerLevels> {
3007 let creators = self.creators().unwrap_or_default();
3008 let rules = self.clone_info().room_version_rules_or_default();
3009
3010 let default_power_levels =
3011 RoomPowerLevels::new(RoomPowerLevelsSource::None, &rules.authorization, creators);
3012 let changes = RoomPowerLevelChanges::from(default_power_levels);
3013 self.apply_power_level_changes(changes).await?;
3014 Ok(self.power_levels().await?)
3015 }
3016
3017 pub async fn get_suggested_user_role(&self, user_id: &UserId) -> Result<RoomMemberRole> {
3022 let power_level = self.get_user_power_level(user_id).await?;
3023 Ok(RoomMemberRole::suggested_role_for_power_level(power_level))
3024 }
3025
3026 pub async fn get_user_power_level(&self, user_id: &UserId) -> Result<UserPowerLevel> {
3031 let event = self.power_levels().await?;
3032 Ok(event.for_user(user_id))
3033 }
3034
3035 pub async fn users_with_power_levels(&self) -> HashMap<OwnedUserId, i64> {
3038 let power_levels = self.power_levels().await.ok();
3039 let mut user_power_levels = HashMap::<OwnedUserId, i64>::new();
3040 if let Some(power_levels) = power_levels {
3041 for (id, level) in power_levels.users.into_iter() {
3042 user_power_levels.insert(id, level.into());
3043 }
3044 }
3045 user_power_levels
3046 }
3047
3048 pub async fn set_name(&self, name: String) -> Result<send_state_event::v3::Response> {
3050 self.send_state_event(RoomNameEventContent::new(name)).await
3051 }
3052
3053 pub async fn set_room_topic(&self, topic: &str) -> Result<send_state_event::v3::Response> {
3055 self.send_state_event(RoomTopicEventContent::new(topic.into())).await
3056 }
3057
3058 pub async fn set_avatar_url(
3065 &self,
3066 url: &MxcUri,
3067 info: Option<avatar::ImageInfo>,
3068 ) -> Result<send_state_event::v3::Response> {
3069 self.ensure_room_joined()?;
3070
3071 let mut room_avatar_event = RoomAvatarEventContent::new();
3072 room_avatar_event.url = Some(url.to_owned());
3073 room_avatar_event.info = info.map(Box::new);
3074
3075 self.send_state_event(room_avatar_event).await
3076 }
3077
3078 pub async fn remove_avatar(&self) -> Result<send_state_event::v3::Response> {
3080 self.send_state_event(RoomAvatarEventContent::new()).await
3081 }
3082
3083 pub async fn upload_avatar(
3092 &self,
3093 mime: &Mime,
3094 data: Vec<u8>,
3095 info: Option<avatar::ImageInfo>,
3096 ) -> Result<send_state_event::v3::Response> {
3097 self.ensure_room_joined()?;
3098
3099 let upload_response = self.client.media().upload(mime, data, None).await?;
3100 let mut info = info.unwrap_or_default();
3101 info.blurhash = upload_response.blurhash;
3102 info.mimetype = Some(mime.to_string());
3103
3104 self.set_avatar_url(&upload_response.content_uri, Some(info)).await
3105 }
3106
3107 #[cfg(not(feature = "experimental-encrypted-state-events"))]
3151 #[instrument(skip_all)]
3152 pub async fn send_state_event(
3153 &self,
3154 content: impl StateEventContent<StateKey = EmptyStateKey>,
3155 ) -> Result<send_state_event::v3::Response> {
3156 self.send_state_event_for_key(&EmptyStateKey, content).await
3157 }
3158
3159 #[cfg(feature = "experimental-encrypted-state-events")]
3210 #[instrument(skip_all)]
3211 pub fn send_state_event<'a>(
3212 &'a self,
3213 content: impl StateEventContent<StateKey = EmptyStateKey>,
3214 ) -> SendStateEvent<'a> {
3215 self.send_state_event_for_key(&EmptyStateKey, content)
3216 }
3217
3218 #[cfg(not(feature = "experimental-encrypted-state-events"))]
3258 pub async fn send_state_event_for_key<C, K>(
3259 &self,
3260 state_key: &K,
3261 content: C,
3262 ) -> Result<send_state_event::v3::Response>
3263 where
3264 C: StateEventContent,
3265 C::StateKey: Borrow<K>,
3266 K: AsRef<str> + ?Sized,
3267 {
3268 self.ensure_room_joined()?;
3269 let request =
3270 send_state_event::v3::Request::new(self.room_id().to_owned(), state_key, &content)?;
3271 let response = self.client.send(request).await?;
3272 Ok(response)
3273 }
3274
3275 #[cfg(feature = "experimental-encrypted-state-events")]
3323 pub fn send_state_event_for_key<'a, C, K>(
3324 &'a self,
3325 state_key: &K,
3326 content: C,
3327 ) -> SendStateEvent<'a>
3328 where
3329 C: StateEventContent,
3330 C::StateKey: Borrow<K>,
3331 K: AsRef<str> + ?Sized,
3332 {
3333 SendStateEvent::new(self, state_key, content)
3334 }
3335
3336 #[cfg(not(feature = "experimental-encrypted-state-events"))]
3369 #[instrument(skip_all)]
3370 pub async fn send_state_event_raw(
3371 &self,
3372 event_type: &str,
3373 state_key: &str,
3374 content: impl IntoRawStateEventContent,
3375 ) -> Result<send_state_event::v3::Response> {
3376 self.ensure_room_joined()?;
3377
3378 let request = send_state_event::v3::Request::new_raw(
3379 self.room_id().to_owned(),
3380 event_type.into(),
3381 state_key.to_owned(),
3382 content.into_raw_state_event_content(),
3383 );
3384
3385 Ok(self.client.send(request).await?)
3386 }
3387
3388 #[cfg(feature = "experimental-encrypted-state-events")]
3428 #[instrument(skip_all)]
3429 pub fn send_state_event_raw<'a>(
3430 &'a self,
3431 event_type: &'a str,
3432 state_key: &'a str,
3433 content: impl IntoRawStateEventContent,
3434 ) -> SendRawStateEvent<'a> {
3435 SendRawStateEvent::new(self, event_type, state_key, content)
3436 }
3437
3438 #[instrument(skip_all)]
3471 pub async fn redact(
3472 &self,
3473 event_id: &EventId,
3474 reason: Option<&str>,
3475 txn_id: Option<OwnedTransactionId>,
3476 ) -> HttpResult<redact_event::v3::Response> {
3477 let txn_id = txn_id.unwrap_or_else(TransactionId::new);
3478 let request = assign!(
3479 redact_event::v3::Request::new(self.room_id().to_owned(), event_id.to_owned(), txn_id),
3480 { reason: reason.map(ToOwned::to_owned) }
3481 );
3482
3483 self.client.send(request).await
3484 }
3485
3486 pub async fn route(&self) -> Result<Vec<OwnedServerName>> {
3495 let acl_ev = self
3496 .get_state_event_static::<RoomServerAclEventContent>()
3497 .await?
3498 .and_then(|ev| ev.deserialize().ok());
3499 let acl = acl_ev.as_ref().and_then(|ev| match ev {
3500 SyncOrStrippedState::Sync(ev) => ev.as_original().map(|ev| &ev.content),
3501 SyncOrStrippedState::Stripped(ev) => Some(&ev.content),
3502 });
3503
3504 let members: Vec<_> = self
3509 .members_no_sync(RoomMemberships::JOIN)
3510 .await?
3511 .into_iter()
3512 .filter(|member| {
3513 let server = member.user_id().server_name();
3514 acl.filter(|acl| !acl.is_allowed(server)).is_none() && !server.is_ip_literal()
3515 })
3516 .collect();
3517
3518 let max = members
3521 .iter()
3522 .max_by_key(|member| member.power_level())
3523 .filter(|max| max.power_level() >= int!(50))
3524 .map(|member| member.user_id().server_name());
3525
3526 let servers = members
3528 .iter()
3529 .map(|member| member.user_id().server_name())
3530 .filter(|server| max.filter(|max| max == server).is_none())
3531 .fold(BTreeMap::<_, u32>::new(), |mut servers, server| {
3532 *servers.entry(server).or_default() += 1;
3533 servers
3534 });
3535 let mut servers: Vec<_> = servers.into_iter().collect();
3536 servers.sort_unstable_by(|(_, count_a), (_, count_b)| count_b.cmp(count_a));
3537
3538 Ok(max
3539 .into_iter()
3540 .chain(servers.into_iter().map(|(name, _)| name))
3541 .take(3)
3542 .map(ToOwned::to_owned)
3543 .collect())
3544 }
3545
3546 pub async fn matrix_to_permalink(&self) -> Result<MatrixToUri> {
3553 if let Some(alias) = self.canonical_alias().or_else(|| self.alt_aliases().pop()) {
3554 return Ok(alias.matrix_to_uri());
3555 }
3556
3557 let via = self.route().await?;
3558 Ok(self.room_id().matrix_to_uri_via(via))
3559 }
3560
3561 pub async fn matrix_permalink(&self, join: bool) -> Result<MatrixUri> {
3572 if let Some(alias) = self.canonical_alias().or_else(|| self.alt_aliases().pop()) {
3573 return Ok(alias.matrix_uri(join));
3574 }
3575
3576 let via = self.route().await?;
3577 Ok(self.room_id().matrix_uri_via(via, join))
3578 }
3579
3580 pub async fn matrix_to_event_permalink(
3594 &self,
3595 event_id: impl Into<OwnedEventId>,
3596 ) -> Result<MatrixToUri> {
3597 let via = self.route().await?;
3600 Ok(self.room_id().matrix_to_event_uri_via(event_id, via))
3601 }
3602
3603 pub async fn matrix_event_permalink(
3617 &self,
3618 event_id: impl Into<OwnedEventId>,
3619 ) -> Result<MatrixUri> {
3620 let via = self.route().await?;
3623 Ok(self.room_id().matrix_event_uri_via(event_id, via))
3624 }
3625
3626 pub async fn load_user_receipt(
3637 &self,
3638 receipt_type: ReceiptType,
3639 receipt_thread: &ReceiptThread,
3640 user_id: &UserId,
3641 ) -> Result<Option<(OwnedEventId, Receipt)>> {
3642 self.inner
3643 .load_user_receipt(receipt_type, receipt_thread, user_id)
3644 .await
3645 .map_err(Into::into)
3646 }
3647
3648 pub async fn load_event_receipts(
3659 &self,
3660 receipt_type: ReceiptType,
3661 receipt_thread: &ReceiptThread,
3662 event_id: &EventId,
3663 ) -> Result<Vec<(OwnedUserId, Receipt)>> {
3664 self.inner
3665 .load_event_receipts(receipt_type, receipt_thread, event_id)
3666 .await
3667 .map_err(Into::into)
3668 }
3669
3670 pub async fn push_condition_room_ctx(&self) -> Result<Option<PushConditionRoomCtx>> {
3675 self.push_condition_room_ctx_internal(self.client.enabled_thread_subscriptions().await?)
3676 .await
3677 }
3678
3679 pub(crate) async fn push_condition_room_ctx_internal(
3686 &self,
3687 with_threads_subscriptions: bool,
3688 ) -> Result<Option<PushConditionRoomCtx>> {
3689 let room_id = self.room_id();
3690 let user_id = self.own_user_id();
3691 let room_info = self.clone_info();
3692 let member_count = room_info.active_members_count();
3693
3694 let user_display_name = if let Some(member) = self.get_member_no_sync(user_id).await? {
3695 member.name().to_owned()
3696 } else {
3697 return Ok(None);
3698 };
3699
3700 let power_levels = match self.power_levels().await {
3701 Ok(power_levels) => Some(power_levels.into()),
3702 Err(error) => {
3703 if matches!(room_info.state(), RoomState::Joined) {
3704 error!("Could not compute power levels for push conditions: {error}");
3707 }
3708 None
3709 }
3710 };
3711
3712 let mut ctx = assign!(PushConditionRoomCtx::new(
3713 room_id.to_owned(),
3714 UInt::new(member_count).unwrap_or(UInt::MAX),
3715 user_id.to_owned(),
3716 user_display_name,
3717 ),
3718 {
3719 power_levels,
3720 });
3721
3722 if with_threads_subscriptions {
3723 let this = self.clone();
3724 ctx = ctx.with_has_thread_subscription_fn(move |event_id: &EventId| {
3725 let room = this.clone();
3726 Box::pin(async move {
3727 if let Ok(maybe_sub) = room.load_or_fetch_thread_subscription(event_id).await {
3728 maybe_sub.is_some()
3729 } else {
3730 false
3731 }
3732 })
3733 });
3734 }
3735
3736 Ok(Some(ctx))
3737 }
3738
3739 pub async fn push_context(&self) -> Result<Option<PushContext>> {
3742 self.push_context_internal(self.client.enabled_thread_subscriptions().await?).await
3743 }
3744
3745 #[instrument(skip(self))]
3749 pub(crate) async fn push_context_internal(
3750 &self,
3751 with_threads_subscriptions: bool,
3752 ) -> Result<Option<PushContext>> {
3753 let Some(push_condition_room_ctx) =
3754 self.push_condition_room_ctx_internal(with_threads_subscriptions).await?
3755 else {
3756 debug!("Could not aggregate push context");
3757 return Ok(None);
3758 };
3759 let push_rules = self.client().account().push_rules().await?;
3760 Ok(Some(PushContext::new(push_condition_room_ctx, push_rules)))
3761 }
3762
3763 pub async fn event_push_actions<T>(&self, event: &Raw<T>) -> Result<Option<Vec<Action>>> {
3768 if let Some(ctx) = self.push_context().await? {
3769 Ok(Some(ctx.for_event(event).await))
3770 } else {
3771 Ok(None)
3772 }
3773 }
3774
3775 pub async fn invite_details(&self) -> Result<Invite> {
3778 let state = self.state();
3779
3780 if state != RoomState::Invited {
3781 return Err(Error::WrongRoomState(Box::new(WrongRoomState::new("Invited", state))));
3782 }
3783
3784 let invitee = self
3785 .get_member_no_sync(self.own_user_id())
3786 .await?
3787 .ok_or_else(|| Error::UnknownError(Box::new(InvitationError::EventMissing)))?;
3788 let event = invitee.event();
3789
3790 let inviter_id = event.sender().to_owned();
3791 let inviter = self.get_member_no_sync(&inviter_id).await?;
3792
3793 Ok(Invite { invitee, inviter_id, inviter })
3794 }
3795
3796 pub async fn member_with_sender_info(
3804 &self,
3805 user_id: &UserId,
3806 ) -> Result<RoomMemberWithSenderInfo> {
3807 let Some(member) = self.get_member_no_sync(user_id).await? else {
3808 return Err(Error::InsufficientData);
3809 };
3810
3811 let sender_member =
3812 if let Some(member) = self.get_member_no_sync(member.event().sender()).await? {
3813 Some(member)
3815 } else if self.are_members_synced() {
3816 None
3818 } else if self.sync_members().await.is_ok() {
3819 self.get_member_no_sync(member.event().sender()).await?
3821 } else {
3822 None
3823 };
3824
3825 Ok(RoomMemberWithSenderInfo { room_member: member, sender_info: sender_member })
3826 }
3827
3828 pub async fn forget(&self) -> Result<()> {
3834 let state = self.state();
3835 match state {
3836 RoomState::Joined | RoomState::Invited | RoomState::Knocked => {
3837 return Err(Error::WrongRoomState(Box::new(WrongRoomState::new(
3838 "Left / Banned",
3839 state,
3840 ))));
3841 }
3842 RoomState::Left | RoomState::Banned => {}
3843 }
3844
3845 let room_id = self.room_id();
3846
3847 let request = forget_room::v3::Request::new(room_id.to_owned());
3848 let _response = self.client.send(request).await?;
3849
3850 if self.inner.direct_targets_length() != 0
3853 && let Err(e) = self.set_is_direct(false).await
3854 {
3855 warn!(?room_id, "failed to remove room from m.direct account data: {e}");
3858 }
3859
3860 self.client.base_client().forget_room(room_id).await?;
3861 self.client.event_cache().forget_room(room_id).await?;
3862
3863 Ok(())
3864 }
3865
3866 fn ensure_room_joined(&self) -> Result<()> {
3867 let state = self.state();
3868 if state == RoomState::Joined {
3869 Ok(())
3870 } else {
3871 Err(Error::WrongRoomState(Box::new(WrongRoomState::new("Joined", state))))
3872 }
3873 }
3874
3875 pub async fn notification_mode(&self) -> Option<RoomNotificationMode> {
3877 if !matches!(self.state(), RoomState::Joined) {
3878 return None;
3879 }
3880
3881 let notification_settings = self.client().notification_settings().await;
3882
3883 let notification_mode =
3885 notification_settings.get_user_defined_room_notification_mode(self.room_id()).await;
3886
3887 if notification_mode.is_some() {
3888 notification_mode
3889 } else if let Ok(is_encrypted) =
3890 self.latest_encryption_state().await.map(|state| state.is_encrypted())
3891 {
3892 let is_one_to_one = IsOneToOne::from(self.active_members_count() == 2);
3897 let default_mode = notification_settings
3898 .get_default_room_notification_mode(IsEncrypted::from(is_encrypted), is_one_to_one)
3899 .await;
3900 Some(default_mode)
3901 } else {
3902 None
3903 }
3904 }
3905
3906 pub async fn user_defined_notification_mode(&self) -> Option<RoomNotificationMode> {
3916 if !matches!(self.state(), RoomState::Joined) {
3917 return None;
3918 }
3919
3920 let notification_settings = self.client().notification_settings().await;
3921
3922 let mode =
3924 notification_settings.get_user_defined_room_notification_mode(self.room_id()).await;
3925
3926 if let Some(mode) = mode {
3927 self.update_cached_user_defined_notification_mode(mode);
3928 }
3929
3930 mode
3931 }
3932
3933 pub async fn report_content(
3946 &self,
3947 event_id: OwnedEventId,
3948 reason: Option<String>,
3949 ) -> Result<report_content::v3::Response> {
3950 let state = self.state();
3951 if state != RoomState::Joined {
3952 return Err(Error::WrongRoomState(Box::new(WrongRoomState::new("Joined", state))));
3953 }
3954
3955 let request = assign!(
3956 report_content::v3::Request::new(
3957 self.inner.room_id().to_owned(),
3958 event_id,
3959 ), {
3960 reason: reason
3961 }
3962 );
3963 Ok(self.client.send(request).await?)
3964 }
3965
3966 pub async fn report_room(&self, reason: String) -> Result<report_room::v3::Response> {
3977 let request = report_room::v3::Request::new(self.inner.room_id().to_owned(), reason);
3978
3979 Ok(self.client.send(request).await?)
3980 }
3981
3982 pub async fn set_unread_flag(&self, unread: bool) -> Result<()> {
3988 if self.is_marked_unread() == unread {
3989 return Ok(());
3991 }
3992
3993 let user_id = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
3994
3995 let content = MarkedUnreadEventContent::new(unread);
3996
3997 let request = set_room_account_data::v3::Request::new(
3998 user_id.to_owned(),
3999 self.inner.room_id().to_owned(),
4000 &content,
4001 )?;
4002
4003 self.client.send(request).await?;
4004 Ok(())
4005 }
4006
4007 pub async fn event_cache(
4010 &self,
4011 ) -> event_cache::Result<(RoomEventCache, Arc<EventCacheDropHandles>)> {
4012 self.client.event_cache().room(self.room_id()).await
4013 }
4014
4015 pub(crate) async fn get_user_beacon_info(
4022 &self,
4023 user_id: &UserId,
4024 ) -> Result<OriginalSyncStateEvent<BeaconInfoEventContent>, BeaconError> {
4025 let raw_event = self
4026 .get_state_event_static_for_key::<BeaconInfoEventContent, _>(user_id)
4027 .await?
4028 .ok_or(BeaconError::NotFound)?;
4029
4030 match raw_event.deserialize()? {
4031 SyncOrStrippedState::Sync(SyncStateEvent::Original(beacon_info)) => Ok(beacon_info),
4032 SyncOrStrippedState::Sync(SyncStateEvent::Redacted(_)) => Err(BeaconError::Redacted),
4033 SyncOrStrippedState::Stripped(_) => Err(BeaconError::Stripped),
4034 }
4035 }
4036
4037 pub async fn start_live_location_share(
4050 &self,
4051 duration_millis: u64,
4052 description: Option<String>,
4053 ) -> Result<send_state_event::v3::Response> {
4054 self.ensure_room_joined()?;
4055
4056 self.send_state_event_for_key(
4057 self.own_user_id(),
4058 BeaconInfoEventContent::new(
4059 description,
4060 Duration::from_millis(duration_millis),
4061 true,
4062 None,
4063 ),
4064 )
4065 .await
4066 }
4067
4068 pub async fn stop_live_location_share(
4076 &self,
4077 ) -> Result<send_state_event::v3::Response, BeaconError> {
4078 self.ensure_room_joined()?;
4079
4080 let mut beacon_info_event = self.get_user_beacon_info(self.own_user_id()).await?;
4081
4082 if beacon_info_event.content.live {
4083 beacon_info_event.content.stop();
4084 Ok(self.send_state_event_for_key(self.own_user_id(), beacon_info_event.content).await?)
4085 } else {
4086 Err(BeaconError::NotLive)
4087 }
4088 }
4089
4090 pub async fn send_location_beacon(
4102 &self,
4103 geo_uri: String,
4104 ) -> Result<send_message_event::v3::Response, BeaconError> {
4105 self.ensure_room_joined()?;
4106
4107 let beacon_info_event = self.get_user_beacon_info(self.own_user_id()).await?;
4108
4109 if beacon_info_event.content.is_live() {
4110 let content = BeaconEventContent::new(beacon_info_event.event_id, geo_uri, None);
4111 Ok(self
4112 .send(content)
4113 .with_request_config(RequestConfig::new().retry_limit(6))
4114 .await?
4115 .response)
4116 } else {
4117 Err(BeaconError::NotLive)
4118 }
4119 }
4120
4121 pub async fn save_composer_draft(
4124 &self,
4125 draft: ComposerDraft,
4126 thread_root: Option<&EventId>,
4127 ) -> Result<()> {
4128 self.client
4129 .state_store()
4130 .set_kv_data(
4131 StateStoreDataKey::ComposerDraft(self.room_id(), thread_root),
4132 StateStoreDataValue::ComposerDraft(draft),
4133 )
4134 .await?;
4135 Ok(())
4136 }
4137
4138 pub async fn load_composer_draft(
4141 &self,
4142 thread_root: Option<&EventId>,
4143 ) -> Result<Option<ComposerDraft>> {
4144 let data = self
4145 .client
4146 .state_store()
4147 .get_kv_data(StateStoreDataKey::ComposerDraft(self.room_id(), thread_root))
4148 .await?;
4149 Ok(data.and_then(|d| d.into_composer_draft()))
4150 }
4151
4152 pub async fn clear_composer_draft(&self, thread_root: Option<&EventId>) -> Result<()> {
4155 self.client
4156 .state_store()
4157 .remove_kv_data(StateStoreDataKey::ComposerDraft(self.room_id(), thread_root))
4158 .await?;
4159 Ok(())
4160 }
4161
4162 pub async fn load_pinned_events(&self) -> Result<Option<Vec<OwnedEventId>>> {
4165 let response = self
4166 .client
4167 .send(get_state_event_for_key::v3::Request::new(
4168 self.room_id().to_owned(),
4169 StateEventType::RoomPinnedEvents,
4170 "".to_owned(),
4171 ))
4172 .await;
4173
4174 match response {
4175 Ok(response) => Ok(Some(
4176 response
4177 .into_content()
4178 .deserialize_as_unchecked::<RoomPinnedEventsEventContent>()?
4179 .pinned,
4180 )),
4181 Err(http_error) => match http_error.as_client_api_error() {
4182 Some(error) if error.status_code == StatusCode::NOT_FOUND => Ok(None),
4183 _ => Err(http_error.into()),
4184 },
4185 }
4186 }
4187
4188 pub async fn subscribe_to_knock_requests(
4203 &self,
4204 ) -> Result<(impl Stream<Item = Vec<KnockRequest>> + use<>, JoinHandle<()>)> {
4205 let this = Arc::new(self.clone());
4206
4207 let room_member_events_observer =
4208 self.client.observe_room_events::<SyncRoomMemberEvent, (Client, Room)>(this.room_id());
4209
4210 let current_seen_ids = self.get_seen_knock_request_ids().await?;
4211 let mut seen_request_ids_stream = self
4212 .seen_knock_request_ids_map
4213 .subscribe()
4214 .await
4215 .map(|values| values.unwrap_or_default());
4216
4217 let mut room_info_stream = self.subscribe_info();
4218
4219 let clear_seen_ids_handle = spawn({
4222 let this = self.clone();
4223 async move {
4224 let mut member_updates_stream = this.room_member_updates_sender.subscribe();
4225 while member_updates_stream.recv().await.is_ok() {
4226 if let Err(err) = this.remove_outdated_seen_knock_requests_ids().await {
4229 warn!("Failed to remove seen knock requests: {err}")
4230 }
4231 }
4232 }
4233 });
4234
4235 let combined_stream = stream! {
4236 match this.get_current_join_requests(¤t_seen_ids).await {
4238 Ok(initial_requests) => yield initial_requests,
4239 Err(err) => warn!("Failed to get initial requests to join: {err}")
4240 }
4241
4242 let mut requests_stream = room_member_events_observer.subscribe();
4243 let mut seen_ids = current_seen_ids.clone();
4244
4245 loop {
4246 tokio::select! {
4249 Some((event, _)) = requests_stream.next() => {
4250 if let Some(event) = event.as_original() {
4251 let emit = if event.prev_content().is_some() {
4253 matches!(event.membership_change(),
4254 MembershipChange::Banned |
4255 MembershipChange::Knocked |
4256 MembershipChange::KnockAccepted |
4257 MembershipChange::KnockDenied |
4258 MembershipChange::KnockRetracted
4259 )
4260 } else {
4261 true
4264 };
4265
4266 if emit {
4267 match this.get_current_join_requests(&seen_ids).await {
4268 Ok(requests) => yield requests,
4269 Err(err) => {
4270 warn!("Failed to get updated knock requests on new member event: {err}")
4271 }
4272 }
4273 }
4274 }
4275 }
4276
4277 Some(new_seen_ids) = seen_request_ids_stream.next() => {
4278 seen_ids = new_seen_ids;
4280
4281 match this.get_current_join_requests(&seen_ids).await {
4284 Ok(requests) => yield requests,
4285 Err(err) => {
4286 warn!("Failed to get updated knock requests on seen ids changed: {err}")
4287 }
4288 }
4289 }
4290
4291 Some(room_info) = room_info_stream.next() => {
4292 if !room_info.are_members_synced() {
4296 match this.get_current_join_requests(&seen_ids).await {
4297 Ok(requests) => yield requests,
4298 Err(err) => {
4299 warn!("Failed to get updated knock requests on gappy (limited) sync: {err}")
4300 }
4301 }
4302 }
4303 }
4304 else => break,
4306 }
4307 }
4308 };
4309
4310 Ok((combined_stream, clear_seen_ids_handle))
4311 }
4312
4313 async fn get_current_join_requests(
4314 &self,
4315 seen_request_ids: &BTreeMap<OwnedEventId, OwnedUserId>,
4316 ) -> Result<Vec<KnockRequest>> {
4317 Ok(self
4318 .members(RoomMemberships::KNOCK)
4319 .await?
4320 .into_iter()
4321 .filter_map(|member| {
4322 let event_id = member.event().event_id()?;
4323 Some(KnockRequest::new(
4324 self,
4325 event_id,
4326 member.event().timestamp(),
4327 KnockRequestMemberInfo::from_member(&member),
4328 seen_request_ids.contains_key(event_id),
4329 ))
4330 })
4331 .collect())
4332 }
4333
4334 pub async fn effective_retention(&self) -> Result<Option<RoomRetentionEventContent>> {
4348 let config = match self.client.get_retention_configuration().await {
4349 Ok(config) => config,
4350 Err(e) if e.is_endpoint_not_implemented() => {
4351 debug!(
4352 room_id = ?self.room_id(),
4353 "Homeserver doesn't implement the MSC1763 retention configuration \
4354 endpoint; treating this room as having no effective retention policy"
4355 );
4356 return Ok(None);
4357 }
4358 Err(e) => return Err(e.into()),
4359 };
4360 Ok(self.effective_retention_with_server_config(&config))
4361 }
4362
4363 pub fn effective_retention_with_server_config(
4381 &self,
4382 config: &get_retention_configuration::unstable::Response,
4383 ) -> Option<RoomRetentionEventContent> {
4384 use ruma::api::client::retention::{
4385 RoomIdOrAllRooms, get_retention_configuration::unstable::LifetimeLimits,
4386 };
4387
4388 fn clamp(value: Option<Duration>, limits: &Option<LifetimeLimits>) -> Option<Duration> {
4389 let Some(limits) = limits else { return value };
4392 let Some(value) = value else {
4393 return limits.min();
4396 };
4397 let min = limits.min().unwrap_or(Duration::ZERO);
4399 let max = limits.max().unwrap_or(Duration::MAX);
4400 Some(value.clamp(min, max))
4401 }
4402
4403 if let Some(policy) =
4405 config.policies.get(&RoomIdOrAllRooms::RoomId(self.room_id().to_owned()))
4406 {
4407 return Some(policy.clone());
4408 }
4409
4410 let Some(room_policy) = self.retention() else {
4412 return config.policies.get(&RoomIdOrAllRooms::AllRooms).cloned();
4413 };
4414
4415 let max_lifetime = clamp(room_policy.max_lifetime(), &config.limits.max_lifetime);
4417 let min_lifetime = clamp(room_policy.min_lifetime(), &config.limits.min_lifetime);
4418
4419 let min_lifetime = min_lifetime.map(|min| {
4426 let Some(max) = max_lifetime else { return min };
4427 if min > max {
4428 warn!(
4429 room_id = ?self.room_id(),
4430 ?min, ?max,
4431 "Retention min_lifetime exceeds max_lifetime after clamping to server limits; \
4432 capping min_lifetime to max_lifetime"
4433 );
4434 max
4435 } else {
4436 min
4437 }
4438 });
4439
4440 match (max_lifetime, min_lifetime) {
4441 (Some(max), Some(min)) => RoomRetentionEventContent::from_range(min..=max),
4442 (Some(max), None) => RoomRetentionEventContent::new().at_most(max),
4443 (None, Some(min)) => RoomRetentionEventContent::new().at_least(min),
4444 (None, None) => Some(RoomRetentionEventContent::new()),
4445 }
4446 }
4447
4448 pub fn privacy_settings(&self) -> RoomPrivacySettings<'_> {
4450 RoomPrivacySettings::new(&self.inner, &self.client)
4451 }
4452
4453 pub async fn list_threads(&self, opts: ListThreadsOptions) -> Result<ThreadRoots> {
4461 let request = opts.into_request(self.room_id());
4462
4463 let response = self.client.send(request).await?;
4464
4465 let push_ctx = self.push_context().await?;
4466 let chunk = join_all(
4467 response.chunk.into_iter().map(|ev| self.try_decrypt_event(ev, push_ctx.as_ref())),
4468 )
4469 .await;
4470
4471 Ok(ThreadRoots { chunk, prev_batch_token: response.next_batch })
4472 }
4473
4474 pub async fn relations(
4488 &self,
4489 event_id: OwnedEventId,
4490 opts: RelationsOptions,
4491 ) -> Result<Relations> {
4492 opts.send(self, event_id).await
4493 }
4494
4495 #[instrument(skip(self), fields(room_id = %self.room_id()))]
4517 pub async fn subscribe_thread(
4518 &self,
4519 thread_root: OwnedEventId,
4520 automatic: Option<OwnedEventId>,
4521 ) -> Result<()> {
4522 let is_automatic = automatic.is_some();
4523
4524 match self
4525 .client
4526 .send(subscribe_thread::unstable::Request::new(
4527 self.room_id().to_owned(),
4528 thread_root.clone(),
4529 automatic,
4530 ))
4531 .await
4532 {
4533 Ok(_response) => {
4534 trace!("Server acknowledged the thread subscription; saving in db");
4535
4536 self.client
4538 .state_store()
4539 .upsert_thread_subscriptions(vec![(
4540 self.room_id(),
4541 &thread_root,
4542 StoredThreadSubscription {
4543 status: ThreadSubscriptionStatus::Subscribed {
4544 automatic: is_automatic,
4545 },
4546 bump_stamp: None,
4547 },
4548 )])
4549 .await?;
4550
4551 Ok(())
4552 }
4553
4554 Err(err) => {
4555 if let Some(ErrorKind::ConflictingUnsubscription) = err.client_api_error_kind() {
4556 trace!("Thread subscription skipped: {err}");
4562 Ok(())
4563 } else {
4564 Err(err.into())
4566 }
4567 }
4568 }
4569 }
4570
4571 pub async fn subscribe_thread_if_needed(
4577 &self,
4578 thread_root: &EventId,
4579 automatic: Option<OwnedEventId>,
4580 ) -> Result<()> {
4581 if let Some(prev_sub) = self.load_or_fetch_thread_subscription(thread_root).await? {
4582 if !prev_sub.automatic || automatic.is_some() {
4585 return Ok(());
4589 }
4590 }
4591 self.subscribe_thread(thread_root.to_owned(), automatic).await
4592 }
4593
4594 #[instrument(skip(self), fields(room_id = %self.room_id()))]
4606 pub async fn unsubscribe_thread(&self, thread_root: OwnedEventId) -> Result<()> {
4607 self.client
4608 .send(unsubscribe_thread::unstable::Request::new(
4609 self.room_id().to_owned(),
4610 thread_root.clone(),
4611 ))
4612 .await?;
4613
4614 trace!("Server acknowledged the thread subscription removal; removed it from db too");
4615
4616 self.client
4618 .state_store()
4619 .upsert_thread_subscriptions(vec![(
4620 self.room_id(),
4621 &thread_root,
4622 StoredThreadSubscription {
4623 status: ThreadSubscriptionStatus::Unsubscribed,
4624 bump_stamp: None,
4625 },
4626 )])
4627 .await?;
4628
4629 Ok(())
4630 }
4631
4632 #[instrument(skip(self), fields(room_id = %self.room_id()))]
4649 pub async fn fetch_thread_subscription(
4650 &self,
4651 thread_root: OwnedEventId,
4652 ) -> Result<Option<ThreadSubscription>> {
4653 let result = self
4654 .client
4655 .send(get_thread_subscription::unstable::Request::new(
4656 self.room_id().to_owned(),
4657 thread_root.clone(),
4658 ))
4659 .await;
4660
4661 let subscription = match result {
4662 Ok(response) => Some(ThreadSubscription { automatic: response.automatic }),
4663 Err(http_error) => match http_error.as_client_api_error() {
4664 Some(error) if error.status_code == StatusCode::NOT_FOUND => None,
4665 _ => return Err(http_error.into()),
4666 },
4667 };
4668
4669 if let Some(sub) = &subscription {
4671 self.client
4672 .state_store()
4673 .upsert_thread_subscriptions(vec![(
4674 self.room_id(),
4675 &thread_root,
4676 StoredThreadSubscription {
4677 status: ThreadSubscriptionStatus::Subscribed { automatic: sub.automatic },
4678 bump_stamp: None,
4679 },
4680 )])
4681 .await?;
4682 } else {
4683 self.client
4685 .state_store()
4686 .remove_thread_subscription(self.room_id(), &thread_root)
4687 .await?;
4688 }
4689
4690 Ok(subscription)
4691 }
4692
4693 pub async fn load_or_fetch_thread_subscription(
4700 &self,
4701 thread_root: &EventId,
4702 ) -> Result<Option<ThreadSubscription>> {
4703 if self.client.thread_subscription_catchup().is_outdated() {
4705 return self.fetch_thread_subscription(thread_root.to_owned()).await;
4706 }
4707
4708 Ok(self
4710 .client
4711 .state_store()
4712 .load_thread_subscription(self.room_id(), thread_root)
4713 .await
4714 .map(|maybe_sub| {
4715 maybe_sub.and_then(|stored| match stored.status {
4716 ThreadSubscriptionStatus::Unsubscribed => None,
4717 ThreadSubscriptionStatus::Subscribed { automatic } => {
4718 Some(ThreadSubscription { automatic })
4719 }
4720 })
4721 })?)
4722 }
4723
4724 pub async fn pin_event(&self, event_id: &EventId) -> Result<bool> {
4734 let mut pinned_event_ids = if let Some(event_ids) = self.pinned_event_ids() {
4735 event_ids
4736 } else {
4737 self.load_pinned_events().await?.unwrap_or_default()
4738 };
4739 let event_id = event_id.to_owned();
4740 if pinned_event_ids.contains(&event_id) {
4741 Ok(false)
4742 } else {
4743 pinned_event_ids.push(event_id);
4744 let content = RoomPinnedEventsEventContent::new(pinned_event_ids);
4745 self.send_state_event(content).await?;
4746 Ok(true)
4747 }
4748 }
4749
4750 pub async fn unpin_event(&self, event_id: &EventId) -> Result<bool> {
4760 let mut pinned_event_ids = if let Some(event_ids) = self.pinned_event_ids() {
4761 event_ids
4762 } else {
4763 self.load_pinned_events().await?.unwrap_or_default()
4764 };
4765 let event_id = event_id.to_owned();
4766 if let Some(idx) = pinned_event_ids.iter().position(|e| *e == *event_id) {
4767 pinned_event_ids.remove(idx);
4768 let content = RoomPinnedEventsEventContent::new(pinned_event_ids);
4769 self.send_state_event(content).await?;
4770 Ok(true)
4771 } else {
4772 Ok(false)
4773 }
4774 }
4775
4776 pub async fn compute_is_dm(&self) -> Result<bool> {
4779 Ok(self.inner.compute_is_dm(self.client.dm_room_definition()).await?)
4780 }
4781
4782 pub fn is_dm(&self) -> bool {
4787 let is_direct = self.direct_targets_length() == 1;
4789 match self.client.dm_room_definition() {
4790 DmRoomDefinition::MatrixSpec => {
4791 is_direct
4793 }
4794 DmRoomDefinition::TwoMembers => {
4795 let active_service_member_count =
4799 self.active_service_members_count().unwrap_or_else(|| {
4800 self.service_members().map(|members| members.len()).unwrap_or_default()
4803 as u64
4804 });
4805 let has_at_most_two_active_members =
4806 self.active_members_count().saturating_sub(active_service_member_count) <= 2;
4807 is_direct && has_at_most_two_active_members
4808 }
4809 }
4810 }
4811}
4812
4813#[cfg(feature = "e2e-encryption")]
4814impl RoomIdentityProvider for Room {
4815 fn is_member<'a>(&'a self, user_id: &'a UserId) -> BoxFuture<'a, bool> {
4816 Box::pin(async { self.get_member(user_id).await.unwrap_or(None).is_some() })
4817 }
4818
4819 fn member_identities(&self) -> BoxFuture<'_, Vec<UserIdentity>> {
4820 Box::pin(async {
4821 let members = self
4822 .members(RoomMemberships::JOIN | RoomMemberships::INVITE)
4823 .await
4824 .unwrap_or_else(|_| Default::default());
4825
4826 let mut ret: Vec<UserIdentity> = Vec::new();
4827 for member in members {
4828 if let Some(i) = self.user_identity(member.user_id()).await {
4829 ret.push(i);
4830 }
4831 }
4832 ret
4833 })
4834 }
4835
4836 fn user_identity<'a>(&'a self, user_id: &'a UserId) -> BoxFuture<'a, Option<UserIdentity>> {
4837 Box::pin(async {
4838 self.client
4839 .encryption()
4840 .get_user_identity(user_id)
4841 .await
4842 .unwrap_or(None)
4843 .map(|u| u.underlying_identity())
4844 })
4845 }
4846}
4847
4848#[derive(Clone, Debug)]
4851pub(crate) struct WeakRoom {
4852 client: WeakClient,
4853 room_id: OwnedRoomId,
4854}
4855
4856impl WeakRoom {
4857 pub fn new(client: WeakClient, room_id: OwnedRoomId) -> Self {
4859 Self { client, room_id }
4860 }
4861
4862 pub fn get(&self) -> Option<Room> {
4864 self.client.get().and_then(|client| client.get_room(&self.room_id))
4865 }
4866
4867 pub fn room_id(&self) -> &RoomId {
4869 &self.room_id
4870 }
4871}
4872
4873#[derive(Debug, Clone)]
4875pub struct Invite {
4876 pub invitee: RoomMember,
4878
4879 pub inviter_id: OwnedUserId,
4883
4884 pub inviter: Option<RoomMember>,
4888}
4889
4890#[derive(Error, Debug)]
4891enum InvitationError {
4892 #[error("No membership event found")]
4893 EventMissing,
4894}
4895
4896#[derive(Debug, Clone, Default)]
4898#[non_exhaustive]
4899pub struct Receipts {
4900 pub fully_read: Option<OwnedEventId>,
4902 pub public_read_receipt: Option<OwnedEventId>,
4904 pub private_read_receipt: Option<OwnedEventId>,
4906}
4907
4908impl Receipts {
4909 pub fn new() -> Self {
4911 Self::default()
4912 }
4913
4914 pub fn fully_read_marker(mut self, event_id: impl Into<Option<OwnedEventId>>) -> Self {
4923 self.fully_read = event_id.into();
4924 self
4925 }
4926
4927 pub fn public_read_receipt(mut self, event_id: impl Into<Option<OwnedEventId>>) -> Self {
4933 self.public_read_receipt = event_id.into();
4934 self
4935 }
4936
4937 pub fn private_read_receipt(mut self, event_id: impl Into<Option<OwnedEventId>>) -> Self {
4941 self.private_read_receipt = event_id.into();
4942 self
4943 }
4944
4945 pub fn is_empty(&self) -> bool {
4947 self.fully_read.is_none()
4948 && self.public_read_receipt.is_none()
4949 && self.private_read_receipt.is_none()
4950 }
4951}
4952
4953#[derive(Debug)]
4956pub enum ParentSpace {
4957 Reciprocal(Room),
4960 WithPowerlevel(Room),
4965 Illegitimate(Room),
4968 Unverifiable(OwnedRoomId),
4971}
4972
4973pub(crate) trait EventSource {
4974 fn get_event(
4975 &self,
4976 event_id: &EventId,
4977 ) -> impl Future<Output = Result<TimelineEvent, Error>> + SendOutsideWasm;
4978}
4979
4980impl EventSource for &Room {
4981 async fn get_event(&self, event_id: &EventId) -> Result<TimelineEvent, Error> {
4982 self.load_or_fetch_event(event_id, None).await
4983 }
4984}
4985
4986#[derive(Debug)]
4989pub struct RoomMemberWithSenderInfo {
4990 pub room_member: RoomMember,
4992 pub sender_info: Option<RoomMember>,
4995}
4996
4997fn has_state_events_of_type(update: &RoomUpdate, event_type: &StateEventType) -> bool {
5001 let RoomUpdate::Joined { updates, .. } = update else {
5003 return false;
5004 };
5005
5006 let (State::Before(state_events) | State::After(state_events)) = &updates.state;
5007
5008 state_events.iter().any(|raw| {
5009 raw.get_field::<StateEventType>("type")
5010 .ok()
5011 .flatten()
5012 .is_some_and(|received_type| received_type == *event_type)
5013 })
5014}
5015
5016#[cfg(all(test, not(target_family = "wasm")))]
5017mod tests {
5018 use std::{collections::BTreeMap, time::Duration};
5019
5020 use matrix_sdk_base::{ComposerDraft, DraftAttachment, store::ComposerDraftType};
5021 use matrix_sdk_test::{
5022 JoinedRoomBuilder, SyncResponseBuilder, async_test, event_factory::EventFactory,
5023 };
5024 use ruma::{
5025 RoomVersionId, event_id,
5026 events::{
5027 relation::RelationType,
5028 room::{member::MembershipState, retention::RoomRetentionEventContent},
5029 },
5030 owned_event_id, room_id, user_id,
5031 };
5032 use wiremock::{
5033 Mock, MockServer, ResponseTemplate,
5034 matchers::{header, method, path, path_regex},
5035 };
5036
5037 use crate::{
5038 Client, Room,
5039 config::RequestConfig,
5040 room::messages::{IncludeRelations, ListThreadsOptions, RelationsOptions},
5041 test_utils::{
5042 client::mock_matrix_session,
5043 logged_in_client,
5044 mocks::{MatrixMockServer, RoomRelationsResponseTemplate},
5045 },
5046 };
5047
5048 #[cfg(all(feature = "sqlite", feature = "e2e-encryption"))]
5049 #[async_test]
5050 async fn test_cache_invalidation_while_encrypt() {
5051 use matrix_sdk_base::store::RoomLoadSettings;
5052 use matrix_sdk_test::{DEFAULT_TEST_ROOM_ID, message_like_event_content};
5053
5054 let tmp_dir = tempfile::tempdir().unwrap();
5055 let sqlite_path = tmp_dir.path().join("cache_invalidation_while_encrypt.db");
5056 let session = mock_matrix_session();
5057
5058 let client = Client::builder()
5059 .homeserver_url("http://localhost:1234")
5060 .request_config(RequestConfig::new().disable_retry())
5061 .sqlite_store(&sqlite_path, None)
5062 .build()
5063 .await
5064 .unwrap();
5065 client
5066 .matrix_auth()
5067 .restore_session(session.clone(), RoomLoadSettings::default())
5068 .await
5069 .unwrap();
5070
5071 client.encryption().enable_cross_process_store_lock("client1".to_owned()).await.unwrap();
5072
5073 let server = MockServer::start().await;
5075 {
5076 Mock::given(method("GET"))
5077 .and(path_regex(r"^/_matrix/client/r0/rooms/.*/state/m.*room.*encryption.?"))
5078 .and(header("authorization", "Bearer 1234"))
5079 .respond_with(
5080 ResponseTemplate::new(200)
5081 .set_body_json(EventFactory::new().room_encryption().into_content()),
5082 )
5083 .mount(&server)
5084 .await;
5085 let f = EventFactory::new().sender(user_id!("@example:localhost"));
5086 let response = SyncResponseBuilder::default()
5087 .add_joined_room(
5088 JoinedRoomBuilder::default()
5089 .add_state_event(
5090 f.member(user_id!("@example:localhost")).display_name("example"),
5091 )
5092 .add_state_event(f.default_power_levels())
5093 .add_state_event(f.room_encryption()),
5094 )
5095 .build_sync_response();
5096 client.base_client().receive_sync_response(response).await.unwrap();
5097 }
5098
5099 let room = client.get_room(&DEFAULT_TEST_ROOM_ID).expect("Room should exist");
5100
5101 room.preshare_room_key().await.unwrap();
5103
5104 {
5107 let client = Client::builder()
5108 .homeserver_url("http://localhost:1234")
5109 .request_config(RequestConfig::new().disable_retry())
5110 .sqlite_store(&sqlite_path, None)
5111 .build()
5112 .await
5113 .unwrap();
5114 client
5115 .matrix_auth()
5116 .restore_session(session.clone(), RoomLoadSettings::default())
5117 .await
5118 .unwrap();
5119 client
5120 .encryption()
5121 .enable_cross_process_store_lock("client2".to_owned())
5122 .await
5123 .unwrap();
5124
5125 let guard = client.encryption().spin_lock_store(None).await.unwrap();
5126 assert!(guard.is_some());
5127 }
5128
5129 let guard = client.encryption().spin_lock_store(None).await.unwrap();
5131 assert!(guard.is_some());
5132
5133 let olm = client.olm_machine().await;
5135 let olm = olm.as_ref().expect("Olm machine wasn't started");
5136
5137 let _encrypted_content = olm
5140 .encrypt_room_event_raw(room.room_id(), "test-event", &message_like_event_content!({}))
5141 .await
5142 .unwrap();
5143 }
5144
5145 #[async_test]
5146 async fn test_composer_draft() {
5147 use matrix_sdk_test::DEFAULT_TEST_ROOM_ID;
5148
5149 let client = logged_in_client(None).await;
5150
5151 let response = SyncResponseBuilder::default()
5152 .add_joined_room(JoinedRoomBuilder::default())
5153 .build_sync_response();
5154 client.base_client().receive_sync_response(response).await.unwrap();
5155 let room = client.get_room(&DEFAULT_TEST_ROOM_ID).expect("Room should exist");
5156
5157 assert_eq!(room.load_composer_draft(None).await.unwrap(), None);
5158
5159 let draft = ComposerDraft {
5162 plain_text: "Hello, world!".to_owned(),
5163 html_text: Some("<strong>Hello</strong>, world!".to_owned()),
5164 draft_type: ComposerDraftType::NewMessage,
5165 attachments: vec![DraftAttachment {
5166 filename: "cat.txt".to_owned(),
5167 content: matrix_sdk_base::DraftAttachmentContent::File {
5168 data: b"meow".to_vec(),
5169 mimetype: Some("text/plain".to_owned()),
5170 size: Some(5),
5171 },
5172 }],
5173 };
5174
5175 room.save_composer_draft(draft.clone(), None).await.unwrap();
5176
5177 let thread_root = owned_event_id!("$thread_root:b.c");
5178 let thread_draft = ComposerDraft {
5179 plain_text: "Hello, thread!".to_owned(),
5180 html_text: Some("<strong>Hello</strong>, thread!".to_owned()),
5181 draft_type: ComposerDraftType::NewMessage,
5182 attachments: vec![DraftAttachment {
5183 filename: "dog.txt".to_owned(),
5184 content: matrix_sdk_base::DraftAttachmentContent::File {
5185 data: b"wuv".to_vec(),
5186 mimetype: Some("text/plain".to_owned()),
5187 size: Some(4),
5188 },
5189 }],
5190 };
5191
5192 room.save_composer_draft(thread_draft.clone(), Some(&thread_root)).await.unwrap();
5193
5194 assert_eq!(room.load_composer_draft(None).await.unwrap(), Some(draft));
5196
5197 assert_eq!(
5199 room.load_composer_draft(Some(&thread_root)).await.unwrap(),
5200 Some(thread_draft.clone())
5201 );
5202
5203 room.clear_composer_draft(None).await.unwrap();
5205 assert_eq!(room.load_composer_draft(None).await.unwrap(), None);
5206
5207 assert_eq!(room.load_composer_draft(Some(&thread_root)).await.unwrap(), Some(thread_draft));
5209
5210 room.clear_composer_draft(Some(&thread_root)).await.unwrap();
5212 assert_eq!(room.load_composer_draft(Some(&thread_root)).await.unwrap(), None);
5213 }
5214
5215 #[async_test]
5216 async fn test_mark_join_requests_as_seen() {
5217 let server = MatrixMockServer::new().await;
5218 let client = server.client_builder().build().await;
5219 let event_id = event_id!("$a:b.c");
5220 let room_id = room_id!("!a:b.c");
5221 let user_id = user_id!("@alice:b.c");
5222
5223 let f = EventFactory::new().room(room_id);
5224 let joined_room_builder = JoinedRoomBuilder::new(room_id).add_state_bulk(vec![
5225 f.member(user_id).membership(MembershipState::Knock).event_id(event_id).into(),
5226 ]);
5227 let room = server.sync_room(&client, joined_room_builder).await;
5228
5229 let seen_ids =
5231 room.get_seen_knock_request_ids().await.expect("Couldn't load seen join request ids");
5232 assert!(seen_ids.is_empty());
5233
5234 room.mark_knock_requests_as_seen(&[user_id.to_owned()])
5236 .await
5237 .expect("Couldn't mark join request as seen");
5238
5239 let seen_ids =
5241 room.get_seen_knock_request_ids().await.expect("Couldn't load seen join request ids");
5242 assert_eq!(seen_ids.len(), 1);
5243 assert_eq!(
5244 seen_ids.into_iter().next().expect("No next value"),
5245 (event_id.to_owned(), user_id.to_owned())
5246 )
5247 }
5248
5249 #[async_test]
5250 async fn test_own_room_membership_with_no_own_member_event() {
5251 let server = MatrixMockServer::new().await;
5252 let client = server.client_builder().build().await;
5253 let room_id = room_id!("!a:b.c");
5254
5255 let room = server.sync_joined_room(&client, room_id).await;
5256
5257 let error = room.member_with_sender_info(client.user_id().unwrap()).await.err();
5260 assert!(error.is_some());
5261 }
5262
5263 #[async_test]
5264 async fn test_own_room_membership_with_own_member_event_but_unknown_sender() {
5265 let server = MatrixMockServer::new().await;
5266 let client = server.client_builder().build().await;
5267 let room_id = room_id!("!a:b.c");
5268 let user_id = user_id!("@example:localhost");
5269
5270 let f = EventFactory::new().room(room_id).sender(user_id!("@alice:b.c"));
5271 let joined_room_builder =
5272 JoinedRoomBuilder::new(room_id).add_state_bulk(vec![f.member(user_id).into()]);
5273 let room = server.sync_room(&client, joined_room_builder).await;
5274
5275 let ret = room
5277 .member_with_sender_info(client.user_id().unwrap())
5278 .await
5279 .expect("Room member info should be available");
5280
5281 assert_eq!(ret.room_member.event().user_id(), user_id);
5283
5284 assert!(ret.sender_info.is_none());
5286 }
5287
5288 #[async_test]
5289 async fn test_own_room_membership_with_own_member_event_and_own_sender() {
5290 let server = MatrixMockServer::new().await;
5291 let client = server.client_builder().build().await;
5292 let room_id = room_id!("!a:b.c");
5293 let user_id = user_id!("@example:localhost");
5294
5295 let f = EventFactory::new().room(room_id).sender(user_id);
5296 let joined_room_builder =
5297 JoinedRoomBuilder::new(room_id).add_state_bulk(vec![f.member(user_id).into()]);
5298 let room = server.sync_room(&client, joined_room_builder).await;
5299
5300 let ret = room
5302 .member_with_sender_info(client.user_id().unwrap())
5303 .await
5304 .expect("Room member info should be available");
5305
5306 assert_eq!(ret.room_member.event().user_id(), user_id);
5308
5309 assert!(ret.sender_info.is_some());
5311 assert_eq!(ret.sender_info.unwrap().event().user_id(), user_id);
5312 }
5313
5314 #[async_test]
5315 async fn test_own_room_membership_with_own_member_event_and_known_sender() {
5316 let server = MatrixMockServer::new().await;
5317 let client = server.client_builder().build().await;
5318 let room_id = room_id!("!a:b.c");
5319 let user_id = user_id!("@example:localhost");
5320 let sender_id = user_id!("@alice:b.c");
5321
5322 let f = EventFactory::new().room(room_id).sender(sender_id);
5323 let joined_room_builder = JoinedRoomBuilder::new(room_id).add_state_bulk(vec![
5324 f.member(user_id).into(),
5325 f.member(sender_id).into(),
5327 ]);
5328 let room = server.sync_room(&client, joined_room_builder).await;
5329
5330 let ret = room
5332 .member_with_sender_info(client.user_id().unwrap())
5333 .await
5334 .expect("Room member info should be available");
5335
5336 assert_eq!(ret.room_member.event().user_id(), user_id);
5338
5339 assert!(ret.sender_info.is_some());
5341 assert_eq!(ret.sender_info.unwrap().event().user_id(), sender_id);
5342 }
5343
5344 #[async_test]
5345 async fn test_own_room_membership_with_own_member_event_and_unknown_but_available_sender() {
5346 let server = MatrixMockServer::new().await;
5347 let client = server.client_builder().build().await;
5348 let room_id = room_id!("!a:b.c");
5349 let user_id = user_id!("@example:localhost");
5350 let sender_id = user_id!("@alice:b.c");
5351
5352 let f = EventFactory::new().room(room_id).sender(sender_id);
5353 let joined_room_builder =
5354 JoinedRoomBuilder::new(room_id).add_state_bulk(vec![f.member(user_id).into()]);
5355 let room = server.sync_room(&client, joined_room_builder).await;
5356
5357 server
5359 .mock_get_members()
5360 .ok(vec![f.member(sender_id).into_raw()])
5361 .mock_once()
5362 .mount()
5363 .await;
5364
5365 let ret = room
5367 .member_with_sender_info(client.user_id().unwrap())
5368 .await
5369 .expect("Room member info should be available");
5370
5371 assert_eq!(ret.room_member.event().user_id(), user_id);
5373
5374 assert!(ret.sender_info.is_some());
5376 assert_eq!(ret.sender_info.unwrap().event().user_id(), sender_id);
5377 }
5378
5379 #[async_test]
5380 async fn test_list_threads() {
5381 let server = MatrixMockServer::new().await;
5382 let client = server.client_builder().build().await;
5383
5384 let room_id = room_id!("!a:b.c");
5385 let sender_id = user_id!("@alice:b.c");
5386 let f = EventFactory::new().room(room_id).sender(sender_id);
5387
5388 let eid1 = event_id!("$1");
5389 let eid2 = event_id!("$2");
5390 let batch1 = vec![f.text_msg("Thread root 1").event_id(eid1).into_raw()];
5391 let batch2 = vec![f.text_msg("Thread root 2").event_id(eid2).into_raw()];
5392
5393 server
5394 .mock_room_threads()
5395 .ok(batch1.clone(), Some("prev_batch".to_owned()))
5396 .mock_once()
5397 .mount()
5398 .await;
5399 server
5400 .mock_room_threads()
5401 .match_from("prev_batch")
5402 .ok(batch2, None)
5403 .mock_once()
5404 .mount()
5405 .await;
5406
5407 let room = server.sync_joined_room(&client, room_id).await;
5408 let result =
5409 room.list_threads(ListThreadsOptions::default()).await.expect("Failed to list threads");
5410 assert_eq!(result.chunk.len(), 1);
5411 assert_eq!(result.chunk[0].event_id().unwrap(), eid1);
5412 assert!(result.prev_batch_token.is_some());
5413
5414 let opts = ListThreadsOptions { from: result.prev_batch_token, ..Default::default() };
5415 let result = room.list_threads(opts).await.expect("Failed to list threads");
5416 assert_eq!(result.chunk.len(), 1);
5417 assert_eq!(result.chunk[0].event_id().unwrap(), eid2);
5418 assert!(result.prev_batch_token.is_none());
5419 }
5420
5421 #[async_test]
5422 async fn test_relations() {
5423 let server = MatrixMockServer::new().await;
5424 let client = server.client_builder().build().await;
5425
5426 let room_id = room_id!("!a:b.c");
5427 let sender_id = user_id!("@alice:b.c");
5428 let f = EventFactory::new().room(room_id).sender(sender_id);
5429
5430 let target_event_id = owned_event_id!("$target");
5431 let eid1 = event_id!("$1");
5432 let eid2 = event_id!("$2");
5433 let batch1 = vec![f.text_msg("Related event 1").event_id(eid1).into_raw()];
5434 let batch2 = vec![f.text_msg("Related event 2").event_id(eid2).into_raw()];
5435
5436 server
5437 .mock_room_relations()
5438 .match_target_event(target_event_id.clone())
5439 .ok(RoomRelationsResponseTemplate::default().events(batch1).next_batch("next_batch"))
5440 .mock_once()
5441 .mount()
5442 .await;
5443
5444 server
5445 .mock_room_relations()
5446 .match_target_event(target_event_id.clone())
5447 .match_from("next_batch")
5448 .ok(RoomRelationsResponseTemplate::default().events(batch2))
5449 .mock_once()
5450 .mount()
5451 .await;
5452
5453 let room = server.sync_joined_room(&client, room_id).await;
5454
5455 let mut opts = RelationsOptions {
5457 include_relations: IncludeRelations::AllRelations,
5458 ..Default::default()
5459 };
5460 let result = room
5461 .relations(target_event_id.clone(), opts.clone())
5462 .await
5463 .expect("Failed to list relations the first time");
5464 assert_eq!(result.chunk.len(), 1);
5465 assert_eq!(result.chunk[0].event_id().unwrap(), eid1);
5466 assert!(result.prev_batch_token.is_none());
5467 assert!(result.next_batch_token.is_some());
5468 assert!(result.recursion_depth.is_none());
5469
5470 opts.from = result.next_batch_token;
5471 let result = room
5472 .relations(target_event_id, opts)
5473 .await
5474 .expect("Failed to list relations the second time");
5475 assert_eq!(result.chunk.len(), 1);
5476 assert_eq!(result.chunk[0].event_id().unwrap(), eid2);
5477 assert!(result.prev_batch_token.is_none());
5478 assert!(result.next_batch_token.is_none());
5479 assert!(result.recursion_depth.is_none());
5480 }
5481
5482 #[async_test]
5483 async fn test_relations_with_reltype() {
5484 let server = MatrixMockServer::new().await;
5485 let client = server.client_builder().build().await;
5486
5487 let room_id = room_id!("!a:b.c");
5488 let sender_id = user_id!("@alice:b.c");
5489 let f = EventFactory::new().room(room_id).sender(sender_id);
5490
5491 let target_event_id = owned_event_id!("$target");
5492 let eid1 = event_id!("$1");
5493 let eid2 = event_id!("$2");
5494 let batch1 = vec![f.text_msg("In-thread event 1").event_id(eid1).into_raw()];
5495 let batch2 = vec![f.text_msg("In-thread event 2").event_id(eid2).into_raw()];
5496
5497 server
5498 .mock_room_relations()
5499 .match_target_event(target_event_id.clone())
5500 .match_subrequest(IncludeRelations::RelationsOfType(RelationType::Thread))
5501 .ok(RoomRelationsResponseTemplate::default().events(batch1).next_batch("next_batch"))
5502 .mock_once()
5503 .mount()
5504 .await;
5505
5506 server
5507 .mock_room_relations()
5508 .match_target_event(target_event_id.clone())
5509 .match_from("next_batch")
5510 .match_subrequest(IncludeRelations::RelationsOfType(RelationType::Thread))
5511 .ok(RoomRelationsResponseTemplate::default().events(batch2))
5512 .mock_once()
5513 .mount()
5514 .await;
5515
5516 let room = server.sync_joined_room(&client, room_id).await;
5517
5518 let mut opts = RelationsOptions {
5520 include_relations: IncludeRelations::RelationsOfType(RelationType::Thread),
5521 ..Default::default()
5522 };
5523 let result = room
5524 .relations(target_event_id.clone(), opts.clone())
5525 .await
5526 .expect("Failed to list relations the first time");
5527 assert_eq!(result.chunk.len(), 1);
5528 assert_eq!(result.chunk[0].event_id().unwrap(), eid1);
5529 assert!(result.prev_batch_token.is_none());
5530 assert!(result.next_batch_token.is_some());
5531 assert!(result.recursion_depth.is_none());
5532
5533 opts.from = result.next_batch_token;
5534 let result = room
5535 .relations(target_event_id, opts)
5536 .await
5537 .expect("Failed to list relations the second time");
5538 assert_eq!(result.chunk.len(), 1);
5539 assert_eq!(result.chunk[0].event_id().unwrap(), eid2);
5540 assert!(result.prev_batch_token.is_none());
5541 assert!(result.next_batch_token.is_none());
5542 assert!(result.recursion_depth.is_none());
5543 }
5544
5545 #[async_test]
5546 async fn test_power_levels_computation() {
5547 let server = MatrixMockServer::new().await;
5548 let client = server.client_builder().build().await;
5549
5550 let room_id = room_id!("!a:b.c");
5551 let sender_id = client.user_id().expect("No session id");
5552 let f = EventFactory::new().room(room_id).sender(sender_id);
5553 let mut user_map = BTreeMap::from([(sender_id.into(), 50.into())]);
5554
5555 let room_create_event = f.create(sender_id, RoomVersionId::V1).state_key("").into();
5557 let power_levels_event = f.power_levels(&mut user_map).state_key("").into();
5558 let room_member_event = f.member(sender_id).into();
5559
5560 let room = server
5562 .sync_room(&client, JoinedRoomBuilder::new(room_id).add_state_bulk([room_member_event]))
5563 .await;
5564 let ctx = room
5565 .push_condition_room_ctx()
5566 .await
5567 .expect("Failed to get push condition context")
5568 .expect("Could not get push condition context");
5569
5570 assert!(ctx.power_levels.is_none());
5572
5573 let room = server
5575 .sync_room(&client, JoinedRoomBuilder::new(room_id).add_state_bulk([room_create_event]))
5576 .await;
5577 let ctx = room
5578 .push_condition_room_ctx()
5579 .await
5580 .expect("Failed to get push condition context")
5581 .expect("Could not get push condition context");
5582
5583 assert!(ctx.power_levels.is_none());
5585
5586 let room = server
5588 .sync_room(
5589 &client,
5590 JoinedRoomBuilder::new(room_id).add_state_bulk([power_levels_event]),
5591 )
5592 .await;
5593 let ctx = room
5594 .push_condition_room_ctx()
5595 .await
5596 .expect("Failed to get push condition context")
5597 .expect("Could not get push condition context");
5598
5599 assert!(ctx.power_levels.is_some());
5601 }
5602
5603 const ONE_DAY: Duration = Duration::from_secs(86_400);
5606 const ONE_WEEK: Duration = Duration::from_secs(86_400 * 7);
5607 const ONE_HOUR: Duration = Duration::from_secs(3_600);
5608
5609 fn retention_room_id() -> &'static ruma::RoomId {
5610 room_id!("!a:b.c")
5611 }
5612
5613 async fn mock_retention_config(
5614 server: &MatrixMockServer,
5615 body: serde_json::Value,
5616 ) -> wiremock::MockGuard {
5617 Mock::given(method("GET"))
5618 .and(path("/_matrix/client/unstable/org.matrix.msc1763/retention/configuration"))
5619 .respond_with(ResponseTemplate::new(200).set_body_json(body))
5620 .mount_as_scoped(server.server())
5621 .await
5622 }
5623
5624 async fn room_with_retention(
5625 server: &MatrixMockServer,
5626 client: &Client,
5627 content: RoomRetentionEventContent,
5628 ) -> Room {
5629 server
5630 .sync_room(
5631 client,
5632 JoinedRoomBuilder::new(retention_room_id()).add_state_event(
5633 EventFactory::new().sender(user_id!("@alice:b.c")).event(content).state_key(""),
5634 ),
5635 )
5636 .await
5637 }
5638
5639 #[async_test]
5640 async fn test_effective_retention_no_policy_anywhere() {
5641 let server = MatrixMockServer::new().await;
5642 let client = server.client_builder().build().await;
5643 let room = server.sync_joined_room(&client, retention_room_id()).await;
5644
5645 let _mock = mock_retention_config(
5646 &server,
5647 serde_json::json!({
5648 "policies": {},
5649 "limits": {},
5650 }),
5651 )
5652 .await;
5653
5654 let result = room.effective_retention().await.unwrap();
5655 assert!(result.is_none());
5656 }
5657
5658 #[async_test]
5659 async fn test_effective_retention_endpoint_not_implemented() {
5660 let server = MatrixMockServer::new().await;
5665 let client = server.client_builder().build().await;
5666 let room = room_with_retention(
5667 &server,
5668 &client,
5669 RoomRetentionEventContent::new().at_most(ONE_DAY).unwrap(),
5670 )
5671 .await;
5672
5673 Mock::given(method("GET"))
5674 .and(path("/_matrix/client/unstable/org.matrix.msc1763/retention/configuration"))
5675 .respond_with(ResponseTemplate::new(404).set_body_json(serde_json::json!({
5676 "errcode": "M_UNRECOGNIZED",
5677 "error": "Unrecognized request",
5678 })))
5679 .mount(server.server())
5680 .await;
5681
5682 let result = room.effective_retention().await.unwrap();
5683 assert!(result.is_none());
5684 }
5685
5686 #[async_test]
5687 async fn test_effective_retention_server_default_only() {
5688 let server = MatrixMockServer::new().await;
5689 let client = server.client_builder().build().await;
5690 let room = server.sync_joined_room(&client, retention_room_id()).await;
5691
5692 let _mock = mock_retention_config(
5693 &server,
5694 serde_json::json!({
5695 "policies": {
5696 "*": { "max_lifetime": ONE_WEEK.as_millis() as u64 }
5697 },
5698 "limits": {},
5699 }),
5700 )
5701 .await;
5702
5703 let result = room.effective_retention().await.unwrap().unwrap();
5704 assert_eq!(result.max_lifetime(), Some(ONE_WEEK));
5705 assert!(result.min_lifetime().is_none());
5706 }
5707
5708 #[async_test]
5709 async fn test_effective_retention_server_room_override() {
5710 let server = MatrixMockServer::new().await;
5713 let client = server.client_builder().build().await;
5714 let room = room_with_retention(
5715 &server,
5716 &client,
5717 RoomRetentionEventContent::new().at_most(ONE_DAY).unwrap(),
5718 )
5719 .await;
5720
5721 let _mock = mock_retention_config(
5722 &server,
5723 serde_json::json!({
5724 "policies": {
5725 "*": { "max_lifetime": ONE_WEEK.as_millis() as u64 },
5726 "!a:b.c": { "max_lifetime": ONE_HOUR.as_millis() as u64 }
5727 },
5728 "limits": {},
5729 }),
5730 )
5731 .await;
5732
5733 let result = room.effective_retention().await.unwrap().unwrap();
5734 assert_eq!(result.max_lifetime(), Some(ONE_HOUR));
5735 }
5736
5737 #[async_test]
5738 async fn test_effective_retention_room_policy_with_limits() {
5739 struct TestCase {
5740 description: &'static str,
5741 room_max: Option<Duration>,
5742 room_min: Option<Duration>,
5743 limits: serde_json::Value,
5744 expected_max: Option<Duration>,
5745 expected_min: Option<Duration>,
5746 }
5747
5748 let test_cases = vec![
5749 TestCase {
5750 description: "room sets `max_lifetime` only, no server limits configured",
5751 room_max: Some(ONE_DAY),
5752 room_min: None,
5753 limits: serde_json::json!({}),
5754 expected_max: Some(ONE_DAY),
5755 expected_min: None,
5756 },
5757 TestCase {
5758 description: "room sets `min_lifetime` only, no server limits configured",
5759 room_max: None,
5760 room_min: Some(ONE_HOUR),
5761 limits: serde_json::json!({}),
5762 expected_max: None,
5763 expected_min: Some(ONE_HOUR),
5764 },
5765 TestCase {
5766 description: "room sets `max_lifetime` and `min_lifetime`, no server limits configured",
5767 room_max: Some(ONE_DAY),
5768 room_min: Some(ONE_HOUR),
5769 limits: serde_json::json!({}),
5770 expected_max: Some(ONE_DAY),
5771 expected_min: Some(ONE_HOUR),
5772 },
5773 TestCase {
5774 description: "room `max_lifetime` above server `max_lifetime.max` → outcome uses server `max_lifetime.max`",
5775 room_max: Some(ONE_WEEK),
5776 room_min: None,
5777 limits: serde_json::json!({
5778 "max_lifetime": { "max": ONE_DAY.as_millis() as u64 }
5779 }),
5780 expected_max: Some(ONE_DAY),
5781 expected_min: None,
5782 },
5783 TestCase {
5784 description: "room `max_lifetime` below server `max_lifetime.min` → outcome uses server `max_lifetime.min`",
5785 room_max: Some(ONE_HOUR),
5786 room_min: None,
5787 limits: serde_json::json!({
5788 "max_lifetime": { "min": ONE_DAY.as_millis() as u64 }
5789 }),
5790 expected_max: Some(ONE_DAY),
5791 expected_min: None,
5792 },
5793 TestCase {
5794 description: "room `min_lifetime` above server `min_lifetime.max` → outcome uses server `min_lifetime.max`",
5795 room_max: None,
5796 room_min: Some(ONE_WEEK),
5797 limits: serde_json::json!({
5798 "min_lifetime": { "max": ONE_DAY.as_millis() as u64 }
5799 }),
5800 expected_max: None,
5801 expected_min: Some(ONE_DAY),
5802 },
5803 TestCase {
5804 description: "room `min_lifetime` below server `min_lifetime.min` → outcome uses server `min_lifetime.min`",
5805 room_max: None,
5806 room_min: Some(ONE_HOUR),
5807 limits: serde_json::json!({
5808 "min_lifetime": { "min": ONE_DAY.as_millis() as u64 }
5809 }),
5810 expected_max: None,
5811 expected_min: Some(ONE_DAY),
5812 },
5813 TestCase {
5814 description: "room `max_lifetime` and `min_lifetime` both within their respective server limits → unchanged",
5815 room_max: Some(ONE_DAY),
5816 room_min: Some(ONE_HOUR),
5817 limits: serde_json::json!({
5818 "max_lifetime": {
5819 "min": ONE_HOUR.as_millis() as u64,
5820 "max": ONE_WEEK.as_millis() as u64,
5821 },
5822 "min_lifetime": {
5823 "min": Duration::from_secs(60).as_millis() as u64,
5824 "max": ONE_DAY.as_millis() as u64,
5825 },
5826 }),
5827 expected_max: Some(ONE_DAY),
5828 expected_min: Some(ONE_HOUR),
5829 },
5830 TestCase {
5831 description: "room `max_lifetime` omitted, server `max_lifetime.min` is used",
5832 room_max: None,
5833 room_min: None,
5834 limits: serde_json::json!({
5835 "max_lifetime": { "min": ONE_DAY.as_millis() as u64 }
5836 }),
5837 expected_max: Some(ONE_DAY),
5838 expected_min: None,
5839 },
5840 TestCase {
5841 description: "room `min_lifetime` omitted, server `min_lifetime.min` is used",
5842 room_max: None,
5843 room_min: None,
5844 limits: serde_json::json!({
5845 "min_lifetime": { "min": ONE_HOUR.as_millis() as u64 }
5846 }),
5847 expected_max: None,
5848 expected_min: Some(ONE_HOUR),
5849 },
5850 TestCase {
5851 description: "independently-clamped `max_lifetime`/`min_lifetime` cross → `min_lifetime` capped down to `max_lifetime`",
5852 room_max: Some(ONE_DAY),
5853 room_min: Some(ONE_HOUR),
5854 limits: serde_json::json!({
5855 "max_lifetime": { "max": ONE_HOUR.as_millis() as u64 },
5856 "min_lifetime": { "min": ONE_WEEK.as_millis() as u64 },
5857 }),
5858 expected_max: Some(ONE_HOUR),
5859 expected_min: Some(ONE_HOUR),
5860 },
5861 TestCase {
5862 description: "room `max_lifetime` omitted, server sets `max_lifetime.max` but no `.min` → stays unbounded (documents MSC1763 gap)",
5863 room_max: None,
5864 room_min: None,
5865 limits: serde_json::json!({
5866 "max_lifetime": { "max": ONE_WEEK.as_millis() as u64 }
5867 }),
5868 expected_max: None,
5869 expected_min: None,
5870 },
5871 ];
5872
5873 for case in test_cases {
5874 let server = MatrixMockServer::new().await;
5875 let client = server.client_builder().build().await;
5876
5877 let mut content = RoomRetentionEventContent::new();
5878 if let Some(max) = case.room_max {
5879 content = content.at_most(max).unwrap();
5880 }
5881 if let Some(min) = case.room_min {
5882 content = content.at_least(min).unwrap();
5883 }
5884
5885 let room = room_with_retention(&server, &client, content).await;
5886
5887 let _mock = mock_retention_config(
5888 &server,
5889 serde_json::json!({
5890 "policies": {},
5891 "limits": case.limits,
5892 }),
5893 )
5894 .await;
5895
5896 let result = room
5897 .effective_retention()
5898 .await
5899 .unwrap_or_else(|e| panic!("{}: effective_retention failed: {e}", case.description))
5900 .unwrap_or_else(|| panic!("{}: expected Some, got None", case.description));
5901
5902 assert_eq!(result.max_lifetime(), case.expected_max, "{}", case.description);
5903 assert_eq!(result.min_lifetime(), case.expected_min, "{}", case.description);
5904 }
5905 }
5906}