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,
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")]
407 #[instrument(skip_all, fields(room_id = ?self.inner.room_id()))]
408 async fn leave_impl(&self) -> (Result<()>, &Room) {
409 let state = self.state();
410 if state == RoomState::Left {
411 return (
412 Err(Error::WrongRoomState(Box::new(WrongRoomState::new(
413 "Joined or Invited",
414 state,
415 )))),
416 self,
417 );
418 }
419
420 let should_forget = matches!(self.state(), RoomState::Invited);
423
424 let request = leave_room::v3::Request::new(self.inner.room_id().to_owned());
425 let response = self.client.send(request).await;
426
427 if let Err(error) = response {
430 #[allow(clippy::collapsible_match)]
431 let ignore_error = if let Some(error) = error.client_api_error_kind() {
432 match error {
433 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<()> {
472 let mut rooms: Vec<Room> = vec![self.clone()];
473 let mut current_room = self;
474
475 while let Some(predecessor) = current_room.predecessor_room() {
476 let maybe_predecessor_room = current_room.client.get_room(&predecessor.room_id);
477
478 if let Some(predecessor_room) = maybe_predecessor_room {
479 rooms.push(predecessor_room.clone());
480 current_room = rooms.last().expect("Room just pushed so can't be empty");
481 } else {
482 warn!("Cannot find predecessor room");
483 break;
484 }
485 }
486
487 let batch_size = 5;
488
489 let rooms_futures: Vec<_> = rooms
490 .iter()
491 .filter_map(|room| match room.state() {
492 RoomState::Joined | RoomState::Invited | RoomState::Knocked => {
493 Some(room.leave_impl())
494 }
495 RoomState::Banned | RoomState::Left => None,
496 })
497 .collect();
498
499 let mut futures_stream = futures_stream::iter(rooms_futures).buffer_unordered(batch_size);
500
501 let mut maybe_this_room_failed_with: Option<Error> = None;
502
503 while let Some(result) = futures_stream.next().await {
504 if let (Err(e), room) = result {
505 if room.room_id() == self.room_id() {
506 maybe_this_room_failed_with = Some(e);
507 } else {
508 warn!("Failure while attempting to leave predecessor room: {e:?}");
509 }
510 }
511 }
512
513 maybe_this_room_failed_with.map_or(Ok(()), Err)
514 }
515
516 #[doc(alias = "accept_invitation")]
520 pub async fn join(&self) -> Result<()> {
521 let prev_room_state = self.inner.state();
522
523 if prev_room_state == RoomState::Joined {
524 return Err(Error::WrongRoomState(Box::new(WrongRoomState::new(
525 "Invited or Left",
526 prev_room_state,
527 ))));
528 }
529
530 self.client.join_room_by_id(self.room_id()).await?;
531
532 Ok(())
533 }
534
535 pub fn client(&self) -> Client {
539 self.client.clone()
540 }
541
542 pub fn is_synced(&self) -> bool {
545 self.inner.is_state_fully_synced()
546 }
547
548 pub async fn avatar(&self, format: MediaFormat) -> Result<Option<Vec<u8>>> {
578 let Some(url) = self.avatar_url() else { return Ok(None) };
579 let request = MediaRequestParameters { source: MediaSource::Plain(url.to_owned()), format };
580 Ok(Some(self.client.media().get_media_content(&request, true).await?))
581 }
582
583 #[instrument(skip_all, fields(room_id = ?self.inner.room_id(), ?options))]
612 pub async fn messages(&self, options: MessagesOptions) -> Result<Messages> {
613 let room_id = self.inner.room_id();
614 let request = options.into_request(room_id);
615 let http_response = self.client.send(request).await?;
616
617 let push_ctx = self.push_context().await?;
618 let chunk = join_all(
619 http_response.chunk.into_iter().map(|ev| self.try_decrypt_event(ev, push_ctx.as_ref())),
620 )
621 .await;
622
623 Ok(Messages {
624 start: http_response.start,
625 end: http_response.end,
626 chunk,
627 state: http_response.state,
628 })
629 }
630
631 pub fn add_event_handler<Ev, Ctx, H>(&self, handler: H) -> EventHandlerHandle
641 where
642 Ev: SyncEvent + DeserializeOwned + Send + 'static,
643 H: EventHandler<Ev, Ctx>,
644 {
645 self.client.add_room_event_handler(self.room_id(), handler)
646 }
647
648 pub fn subscribe_to_updates(&self) -> broadcast::Receiver<RoomUpdate> {
653 self.client.subscribe_to_room_updates(self.room_id())
654 }
655
656 pub fn subscribe_to_typing_notifications(
662 &self,
663 ) -> (EventHandlerDropGuard, broadcast::Receiver<Vec<OwnedUserId>>) {
664 let (sender, receiver) = broadcast::channel(16);
665 let typing_event_handler_handle = self.client.add_room_event_handler(self.room_id(), {
666 let own_user_id = self.own_user_id().to_owned();
667 move |event: SyncTypingEvent| async move {
668 let typing_user_ids = event
670 .content
671 .user_ids
672 .into_iter()
673 .filter(|user_id| *user_id != own_user_id)
674 .collect();
675 let _ = sender.send(typing_user_ids);
677 }
678 });
679 let drop_guard = self.client().event_handler_drop_guard(typing_event_handler_handle);
680 (drop_guard, receiver)
681 }
682
683 #[cfg(feature = "e2e-encryption")]
706 pub async fn subscribe_to_identity_status_changes(
707 &self,
708 ) -> Result<impl Stream<Item = Vec<IdentityStatusChange>> + use<>> {
709 IdentityStatusChanges::create_stream(self.clone()).await
710 }
711
712 pub async fn live_locations_observer(&self) -> LiveLocationsObserver {
720 LiveLocationsObserver::new(self.clone()).await
721 }
722
723 #[cfg(not(feature = "experimental-encrypted-state-events"))]
728 #[allow(clippy::unused_async)] async fn try_decrypt_event(
730 &self,
731 event: Raw<AnyTimelineEvent>,
732 push_ctx: Option<&PushContext>,
733 ) -> TimelineEvent {
734 #[cfg(feature = "e2e-encryption")]
735 if let Ok(AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomEncrypted(
736 SyncMessageLikeEvent::Original(_),
737 ))) = event.deserialize_as::<AnySyncTimelineEvent>()
738 && let Ok(event) = self.decrypt_event(event.cast_ref_unchecked(), push_ctx).await
739 {
740 return event;
741 }
742
743 let mut event = TimelineEvent::from_plaintext(event.cast());
744 if let Some(push_ctx) = push_ctx {
745 event.set_push_actions(push_ctx.for_event(event.raw()).await);
746 }
747
748 event
749 }
750
751 #[cfg(feature = "experimental-encrypted-state-events")]
756 #[allow(clippy::unused_async)] async fn try_decrypt_event(
758 &self,
759 event: Raw<AnyTimelineEvent>,
760 push_ctx: Option<&PushContext>,
761 ) -> TimelineEvent {
762 match event.deserialize_as::<AnySyncTimelineEvent>() {
764 Ok(AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomEncrypted(
765 SyncMessageLikeEvent::Original(_),
766 ))) => {
767 if let Ok(event) = self
768 .decrypt_event(
769 event.cast_ref_unchecked::<OriginalSyncRoomEncryptedEvent>(),
770 push_ctx,
771 )
772 .await
773 {
774 return event;
775 }
776 }
777 Ok(AnySyncTimelineEvent::State(AnySyncStateEvent::RoomEncrypted(
778 SyncStateEvent::Original(_),
779 ))) => {
780 if let Ok(event) = self
781 .decrypt_event(
782 event.cast_ref_unchecked::<OriginalSyncStateRoomEncryptedEvent>(),
783 push_ctx,
784 )
785 .await
786 {
787 return event;
788 }
789 }
790 _ => {}
791 }
792
793 let mut event = TimelineEvent::from_plaintext(event.cast());
794 if let Some(push_ctx) = push_ctx {
795 event.set_push_actions(push_ctx.for_event(event.raw()).await);
796 }
797
798 event
799 }
800
801 pub async fn event(
806 &self,
807 event_id: &EventId,
808 request_config: Option<RequestConfig>,
809 ) -> Result<TimelineEvent> {
810 let request =
811 get_room_event::v3::Request::new(self.room_id().to_owned(), event_id.to_owned());
812
813 let raw_event = self.client.send(request).with_request_config(request_config).await?.event;
814 let push_ctx = self.push_context().await?;
815 let event = self.try_decrypt_event(raw_event, push_ctx.as_ref()).await;
816
817 Ok(event)
818 }
819
820 pub async fn load_or_fetch_event(
827 &self,
828 event_id: &EventId,
829 request_config: Option<RequestConfig>,
830 ) -> Result<TimelineEvent> {
831 match self.event_cache().await {
832 Ok((event_cache, _drop_handles)) => {
833 if let Some(event) = event_cache.find_event(event_id).await? {
834 return Ok(event);
835 }
836 }
838 Err(err) => {
839 debug!("error when getting the event cache: {err}");
840 }
841 }
842 self.event(event_id, request_config).await
843 }
844
845 pub async fn load_or_fetch_event_with_relations(
863 &self,
864 event_id: &EventId,
865 filter: Option<Vec<RelationType>>,
866 request_config: Option<RequestConfig>,
867 ) -> Result<(TimelineEvent, Vec<TimelineEvent>)> {
868 let fetch_relations = async || {
869 let include_relations = if let Some(filter) = &filter
876 && filter.len() == 1
877 {
878 IncludeRelations::RelationsOfType(filter[0].clone())
879 } else {
880 IncludeRelations::AllRelations
881 };
882
883 let mut opts = RelationsOptions {
884 include_relations,
885 recurse: true,
886 limit: Some(uint!(256)),
887 ..Default::default()
888 };
889
890 let mut events = Vec::new();
891 loop {
892 match self.relations(event_id.to_owned(), opts.clone()).await {
893 Ok(relations) => {
894 if let Some(filter) = filter.as_ref() {
895 events.extend(relations.chunk.into_iter().filter_map(|ev| {
897 let (rel_type, _) = extract_relation(ev.raw())?;
898 filter
899 .iter()
900 .any(|ruma_filter| ruma_filter == &rel_type)
901 .then_some(ev)
902 }));
903 } else {
904 events.extend(relations.chunk);
906 }
907
908 if let Some(next_from) = relations.next_batch_token {
909 opts.from = Some(next_from);
910 } else {
911 break events;
912 }
913 }
914
915 Err(err) => {
916 warn!(%event_id, "error when loading relations of pinned event from server: {err}");
917 break events;
918 }
919 }
920 }
921 };
922
923 let event_cache = match self.event_cache().await {
926 Ok((event_cache, drop_handles)) => {
927 if let Some((event, mut relations)) =
928 event_cache.find_event_with_relations(event_id, filter.clone()).await?
929 {
930 if relations.is_empty() {
931 relations = fetch_relations().await;
934 }
935
936 return Ok((event, relations));
937 }
938
939 Some((event_cache, drop_handles))
941 }
942
943 Err(err) => {
944 debug!("error when getting the event cache: {err}");
945 None
947 }
948 };
949
950 let event = self.event(event_id, request_config).await?;
953
954 if let Some((event_cache, _drop_handles)) = event_cache
956 && let Some(relations) =
957 event_cache.find_event_relations(event_id, filter.clone()).await.ok()
958 && !relations.is_empty()
959 {
960 return Ok((event, relations));
961 }
962
963 Ok((event, fetch_relations().await))
966 }
967
968 pub async fn event_with_context(
971 &self,
972 event_id: &EventId,
973 lazy_load_members: bool,
974 context_size: UInt,
975 request_config: Option<RequestConfig>,
976 ) -> Result<EventWithContextResponse> {
977 let mut request =
978 context::get_context::v3::Request::new(self.room_id().to_owned(), event_id.to_owned());
979
980 request.limit = context_size;
981
982 if lazy_load_members {
983 request.filter.lazy_load_options =
984 LazyLoadOptions::Enabled { include_redundant_members: false };
985 }
986
987 let response = self.client.send(request).with_request_config(request_config).await?;
988
989 let push_ctx = self.push_context().await?;
990 let push_ctx = push_ctx.as_ref();
991 let target_event = if let Some(event) = response.event {
992 Some(self.try_decrypt_event(event, push_ctx).await)
993 } else {
994 None
995 };
996
997 let (events_before, events_after) = join!(
1001 join_all(
1002 response.events_before.into_iter().map(|ev| self.try_decrypt_event(ev, push_ctx)),
1003 ),
1004 join_all(
1005 response.events_after.into_iter().map(|ev| self.try_decrypt_event(ev, push_ctx)),
1006 ),
1007 );
1008
1009 Ok(EventWithContextResponse {
1010 event: target_event,
1011 events_before,
1012 events_after,
1013 state: response.state,
1014 prev_batch_token: response.start,
1015 next_batch_token: response.end,
1016 })
1017 }
1018
1019 pub(crate) async fn request_members(&self) -> Result<()> {
1020 self.client
1021 .locks()
1022 .members_request_deduplicated_handler
1023 .run(self.room_id().to_owned(), async move {
1024 let request = get_member_events::v3::Request::new(self.inner.room_id().to_owned());
1025 let response = self
1026 .client
1027 .send(request.clone())
1028 .with_request_config(
1029 RequestConfig::new().timeout(Duration::from_secs(60)).retry_limit(3),
1032 )
1033 .await?;
1034
1035 Box::pin(self.client.base_client().receive_all_members(
1037 self.room_id(),
1038 &request,
1039 &response,
1040 ))
1041 .await?;
1042
1043 Ok(())
1044 })
1045 .await
1046 }
1047
1048 pub async fn request_encryption_state(&self) -> Result<()> {
1053 if !self.inner.encryption_state().is_unknown() {
1054 return Ok(());
1055 }
1056
1057 self.client
1058 .locks()
1059 .encryption_state_deduplicated_handler
1060 .run(self.room_id().to_owned(), async move {
1061 let request = get_state_event_for_key::v3::Request::new(
1063 self.room_id().to_owned(),
1064 StateEventType::RoomEncryption,
1065 "".to_owned(),
1066 );
1067 let response = match self.client.send(request).await {
1068 Ok(response) => Some(
1069 response
1070 .into_content()
1071 .deserialize_as_unchecked::<PossiblyRedactedRoomEncryptionEventContent>(
1072 )?,
1073 ),
1074 Err(err) if err.client_api_error_kind() == Some(&ErrorKind::NotFound) => None,
1075 Err(err) => return Err(err.into()),
1076 };
1077
1078 self.update_and_save_room_info(|mut room_info| {
1081 room_info.mark_encryption_state_synced();
1082 room_info.set_encryption_event(response);
1083 (room_info, RoomInfoNotableUpdateReasons::empty())
1084 })
1085 .await?;
1086
1087 Ok(())
1088 })
1089 .await
1090 }
1091
1092 pub fn encryption_state(&self) -> EncryptionState {
1097 self.inner.encryption_state()
1098 }
1099
1100 pub async fn latest_encryption_state(&self) -> Result<EncryptionState> {
1106 self.request_encryption_state().await?;
1107
1108 Ok(self.encryption_state())
1109 }
1110
1111 #[cfg(feature = "e2e-encryption")]
1113 pub async fn crypto_context_info(&self) -> CryptoContextInfo {
1114 let encryption = self.client.encryption();
1115
1116 let this_device_is_verified = match encryption.get_own_device().await {
1117 Ok(Some(device)) => device.is_verified_with_cross_signing(),
1118
1119 _ => true,
1121 };
1122
1123 let backup_exists_on_server =
1124 encryption.backups().exists_on_server().await.unwrap_or(false);
1125
1126 CryptoContextInfo {
1127 device_creation_ts: encryption.device_creation_timestamp().await,
1128 this_device_is_verified,
1129 is_backup_configured: encryption.backups().state() == BackupState::Enabled,
1130 backup_exists_on_server,
1131 }
1132 }
1133
1134 fn are_events_visible(&self) -> bool {
1135 if let RoomState::Invited = self.inner.state() {
1136 return matches!(
1137 self.inner.history_visibility_or_default(),
1138 HistoryVisibility::WorldReadable | HistoryVisibility::Invited
1139 );
1140 }
1141
1142 true
1143 }
1144
1145 pub async fn sync_members(&self) -> Result<()> {
1151 if !self.are_events_visible() {
1152 return Ok(());
1153 }
1154
1155 if !self.are_members_synced() {
1156 self.request_members().await?;
1157
1158 self.update_active_service_members().await?;
1160
1161 Ok(())
1162 } else {
1163 Ok(())
1164 }
1165 }
1166
1167 pub async fn get_member(&self, user_id: &UserId) -> Result<Option<RoomMember>> {
1181 self.sync_members().await?;
1182 self.get_member_no_sync(user_id).await
1183 }
1184
1185 pub async fn get_member_no_sync(&self, user_id: &UserId) -> Result<Option<RoomMember>> {
1199 Ok(self
1200 .inner
1201 .get_member(user_id)
1202 .await?
1203 .map(|member| RoomMember::new(self.client.clone(), member)))
1204 }
1205
1206 pub async fn members(&self, memberships: RoomMemberships) -> Result<Vec<RoomMember>> {
1215 self.sync_members().await?;
1216 self.members_no_sync(memberships).await
1217 }
1218
1219 pub async fn members_no_sync(&self, memberships: RoomMemberships) -> Result<Vec<RoomMember>> {
1228 Ok(self
1229 .inner
1230 .members(memberships)
1231 .await?
1232 .into_iter()
1233 .map(|member| RoomMember::new(self.client.clone(), member))
1234 .collect())
1235 }
1236
1237 pub async fn human_member_ids(&self, memberships: RoomMemberships) -> Result<Vec<OwnedUserId>> {
1241 self.sync_members().await?;
1242 self.human_member_ids_no_sync(memberships).await
1243 }
1244
1245 pub async fn human_member_ids_no_sync(
1248 &self,
1249 memberships: RoomMemberships,
1250 ) -> Result<Vec<OwnedUserId>> {
1251 Ok(self.inner.human_member_ids(memberships).await?)
1252 }
1253
1254 pub async fn set_own_member_display_name(
1259 &self,
1260 display_name: Option<String>,
1261 ) -> Result<send_state_event::v3::Response> {
1262 let user_id = self.own_user_id();
1263 let member_event =
1264 self.get_state_event_static_for_key::<RoomMemberEventContent, _>(user_id).await?;
1265
1266 let Some(RawSyncOrStrippedState::Sync(raw_event)) = member_event else {
1267 return Err(Error::InsufficientData);
1268 };
1269
1270 let event = raw_event.deserialize()?;
1271
1272 let mut content = match event {
1273 SyncStateEvent::Original(original_event) => original_event.content,
1274 SyncStateEvent::Redacted(redacted_event) => {
1275 RoomMemberEventContent::new(redacted_event.content.membership)
1276 }
1277 };
1278
1279 content.displayname = display_name;
1280 self.send_state_event_for_key(user_id, content).await
1281 }
1282
1283 pub async fn get_state_events(
1285 &self,
1286 event_type: StateEventType,
1287 ) -> Result<Vec<RawAnySyncOrStrippedState>> {
1288 self.client
1289 .state_store()
1290 .get_state_events(self.room_id(), event_type)
1291 .await
1292 .map_err(Into::into)
1293 }
1294
1295 pub async fn get_state_events_static<C>(&self) -> Result<Vec<RawSyncOrStrippedState<C>>>
1312 where
1313 C: StaticEventContent<IsPrefix = ruma::events::False>
1314 + StaticStateEventContent
1315 + RedactContent,
1316 C::Redacted: RedactedStateEventContent,
1317 {
1318 Ok(self.client.state_store().get_state_events_static(self.room_id()).await?)
1319 }
1320
1321 pub async fn get_state_events_for_keys(
1324 &self,
1325 event_type: StateEventType,
1326 state_keys: &[&str],
1327 ) -> Result<Vec<RawAnySyncOrStrippedState>> {
1328 self.client
1329 .state_store()
1330 .get_state_events_for_keys(self.room_id(), event_type, state_keys)
1331 .await
1332 .map_err(Into::into)
1333 }
1334
1335 pub async fn get_state_events_for_keys_static<'a, C, K, I>(
1355 &self,
1356 state_keys: I,
1357 ) -> Result<Vec<RawSyncOrStrippedState<C>>>
1358 where
1359 C: StaticEventContent<IsPrefix = ruma::events::False>
1360 + StaticStateEventContent
1361 + RedactContent,
1362 C::StateKey: Borrow<K>,
1363 C::Redacted: RedactedStateEventContent,
1364 K: AsRef<str> + Sized + Sync + 'a,
1365 I: IntoIterator<Item = &'a K> + Send,
1366 I::IntoIter: Send,
1367 {
1368 Ok(self
1369 .client
1370 .state_store()
1371 .get_state_events_for_keys_static(self.room_id(), state_keys)
1372 .await?)
1373 }
1374
1375 pub async fn get_state_event(
1377 &self,
1378 event_type: StateEventType,
1379 state_key: &str,
1380 ) -> Result<Option<RawAnySyncOrStrippedState>> {
1381 self.client
1382 .state_store()
1383 .get_state_event(self.room_id(), event_type, state_key)
1384 .await
1385 .map_err(Into::into)
1386 }
1387
1388 pub async fn get_state_event_static<C>(&self) -> Result<Option<RawSyncOrStrippedState<C>>>
1407 where
1408 C: StaticEventContent<IsPrefix = ruma::events::False>
1409 + StaticStateEventContent<StateKey = EmptyStateKey>
1410 + RedactContent,
1411 C::Redacted: RedactedStateEventContent,
1412 {
1413 self.get_state_event_static_for_key(&EmptyStateKey).await
1414 }
1415
1416 pub async fn get_state_event_static_for_key<C, K>(
1436 &self,
1437 state_key: &K,
1438 ) -> Result<Option<RawSyncOrStrippedState<C>>>
1439 where
1440 C: StaticEventContent<IsPrefix = ruma::events::False>
1441 + StaticStateEventContent
1442 + RedactContent,
1443 C::StateKey: Borrow<K>,
1444 C::Redacted: RedactedStateEventContent,
1445 K: AsRef<str> + ?Sized + Sync,
1446 {
1447 Ok(self
1448 .client
1449 .state_store()
1450 .get_state_event_static_for_key(self.room_id(), state_key)
1451 .await?)
1452 }
1453
1454 pub async fn parent_spaces(&self) -> Result<impl Stream<Item = Result<ParentSpace>> + '_> {
1458 Ok(self
1463 .get_state_events_static::<SpaceParentEventContent>()
1464 .await?
1465 .into_iter()
1466 .filter_map(|parent_event| match parent_event.deserialize() {
1468 Ok(SyncOrStrippedState::Sync(SyncStateEvent::Original(e))) => {
1469 Some((e.state_key.to_owned(), e.sender))
1470 }
1471 Ok(SyncOrStrippedState::Sync(SyncStateEvent::Redacted(_))) => None,
1472 Ok(SyncOrStrippedState::Stripped(e)) => Some((e.state_key.to_owned(), e.sender)),
1473 Err(e) => {
1474 info!(room_id = ?self.room_id(), "Could not deserialize m.space.parent: {e}");
1475 None
1476 }
1477 })
1478 .map(|(state_key, sender): (OwnedRoomId, OwnedUserId)| async move {
1480 let Some(parent_room) = self.client.get_room(&state_key) else {
1481 return Ok(ParentSpace::Unverifiable(state_key));
1484 };
1485 if let Some(child_event) = parent_room
1488 .get_state_event_static_for_key::<SpaceChildEventContent, _>(self.room_id())
1489 .await?
1490 {
1491 match child_event.deserialize() {
1492 Ok(SyncOrStrippedState::Sync(SyncStateEvent::Original(_))) => {
1493 return Ok(ParentSpace::Reciprocal(parent_room));
1496 }
1497 Ok(SyncOrStrippedState::Sync(SyncStateEvent::Redacted(_))) => {}
1498 Ok(SyncOrStrippedState::Stripped(_)) => {}
1499 Err(e) => {
1500 info!(
1501 room_id = ?self.room_id(), parent_room_id = ?state_key,
1502 "Could not deserialize m.space.child: {e}"
1503 );
1504 }
1505 }
1506 }
1511
1512 let Some(member) = parent_room.get_member(&sender).await? else {
1515 return Ok(ParentSpace::Illegitimate(parent_room));
1517 };
1518
1519 if member.can_send_state(StateEventType::SpaceChild) {
1520 Ok(ParentSpace::WithPowerlevel(parent_room))
1522 } else {
1523 Ok(ParentSpace::Illegitimate(parent_room))
1524 }
1525 })
1526 .collect::<FuturesUnordered<_>>())
1527 }
1528
1529 pub async fn account_data(
1531 &self,
1532 data_type: RoomAccountDataEventType,
1533 ) -> Result<Option<Raw<AnyRoomAccountDataEvent>>> {
1534 self.client
1535 .state_store()
1536 .get_room_account_data_event(self.room_id(), data_type)
1537 .await
1538 .map_err(Into::into)
1539 }
1540
1541 pub async fn account_data_static<C>(&self) -> Result<Option<Raw<RoomAccountDataEvent<C>>>>
1560 where
1561 C: StaticEventContent<IsPrefix = ruma::events::False> + RoomAccountDataEventContent,
1562 {
1563 Ok(self.account_data(C::TYPE.into()).await?.map(Raw::cast_unchecked))
1564 }
1565
1566 #[cfg(feature = "e2e-encryption")]
1571 pub async fn contains_only_verified_devices(&self) -> Result<bool> {
1572 let user_ids = self
1573 .client
1574 .state_store()
1575 .get_user_ids(self.room_id(), RoomMemberships::empty())
1576 .await?;
1577
1578 for user_id in user_ids {
1579 let devices = self.client.encryption().get_user_devices(&user_id).await?;
1580 let any_unverified = devices.devices().any(|d| !d.is_verified());
1581
1582 if any_unverified {
1583 return Ok(false);
1584 }
1585 }
1586
1587 Ok(true)
1588 }
1589
1590 pub async fn set_account_data<T>(
1605 &self,
1606 content: T,
1607 ) -> Result<set_room_account_data::v3::Response>
1608 where
1609 T: RoomAccountDataEventContent,
1610 {
1611 let own_user = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
1612
1613 let request = set_room_account_data::v3::Request::new(
1614 own_user.to_owned(),
1615 self.room_id().to_owned(),
1616 &content,
1617 )?;
1618
1619 Ok(self.client.send(request).await?)
1620 }
1621
1622 pub async fn set_account_data_raw(
1647 &self,
1648 event_type: RoomAccountDataEventType,
1649 content: Raw<AnyRoomAccountDataEventContent>,
1650 ) -> Result<set_room_account_data::v3::Response> {
1651 let own_user = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
1652
1653 let request = set_room_account_data::v3::Request::new_raw(
1654 own_user.to_owned(),
1655 self.room_id().to_owned(),
1656 event_type,
1657 content,
1658 );
1659
1660 Ok(self.client.send(request).await?)
1661 }
1662
1663 pub async fn set_tag(
1694 &self,
1695 tag: TagName,
1696 tag_info: TagInfo,
1697 ) -> Result<create_tag::v3::Response> {
1698 let user_id = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
1699 let request = create_tag::v3::Request::new(
1700 user_id.to_owned(),
1701 self.inner.room_id().to_owned(),
1702 tag.to_string(),
1703 tag_info,
1704 );
1705 Ok(self.client.send(request).await?)
1706 }
1707
1708 pub async fn remove_tag(&self, tag: TagName) -> Result<delete_tag::v3::Response> {
1715 let user_id = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
1716 let request = delete_tag::v3::Request::new(
1717 user_id.to_owned(),
1718 self.inner.room_id().to_owned(),
1719 tag.to_string(),
1720 );
1721 Ok(self.client.send(request).await?)
1722 }
1723
1724 pub async fn set_is_favourite(&self, is_favourite: bool, tag_order: Option<f64>) -> Result<()> {
1734 if is_favourite {
1735 let tag_info = assign!(TagInfo::new(), { order: tag_order });
1736
1737 self.set_tag(TagName::Favorite, tag_info).await?;
1738
1739 if self.is_low_priority() {
1740 self.remove_tag(TagName::LowPriority).await?;
1741 }
1742 } else {
1743 self.remove_tag(TagName::Favorite).await?;
1744 }
1745 Ok(())
1746 }
1747
1748 pub async fn set_is_low_priority(
1758 &self,
1759 is_low_priority: bool,
1760 tag_order: Option<f64>,
1761 ) -> Result<()> {
1762 if is_low_priority {
1763 let tag_info = assign!(TagInfo::new(), { order: tag_order });
1764
1765 self.set_tag(TagName::LowPriority, tag_info).await?;
1766
1767 if self.is_favourite() {
1768 self.remove_tag(TagName::Favorite).await?;
1769 }
1770 } else {
1771 self.remove_tag(TagName::LowPriority).await?;
1772 }
1773 Ok(())
1774 }
1775
1776 pub async fn set_is_direct(&self, is_direct: bool) -> Result<()> {
1785 let user_id = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
1786
1787 let mut content = self
1788 .client
1789 .account()
1790 .account_data::<DirectEventContent>()
1791 .await?
1792 .map(|c| c.deserialize())
1793 .transpose()?
1794 .unwrap_or_default();
1795
1796 let this_room_id = self.inner.room_id();
1797
1798 if is_direct {
1799 let mut room_members = self.members(RoomMemberships::ACTIVE).await?;
1800 room_members.retain(|member| member.user_id() != self.own_user_id());
1801
1802 for member in room_members {
1803 let entry = content.entry(member.user_id().into()).or_default();
1804 if !entry.iter().any(|room_id| room_id == this_room_id) {
1805 entry.push(this_room_id.to_owned());
1806 }
1807 }
1808 } else {
1809 for list in content.values_mut() {
1810 list.retain(|room_id| *room_id != this_room_id);
1811 }
1812
1813 content.retain(|_, list| !list.is_empty());
1815 }
1816
1817 let request = set_global_account_data::v3::Request::new(user_id.to_owned(), &content)?;
1818
1819 self.client.send(request).await?;
1820 Ok(())
1821 }
1822
1823 #[cfg(feature = "e2e-encryption")]
1831 #[cfg(not(feature = "experimental-encrypted-state-events"))]
1832 pub async fn decrypt_event(
1833 &self,
1834 event: &Raw<OriginalSyncRoomEncryptedEvent>,
1835 push_ctx: Option<&PushContext>,
1836 ) -> Result<TimelineEvent> {
1837 let machine = self.client.olm_machine().await;
1838 let machine = machine.as_ref().ok_or(Error::NoOlmMachine)?;
1839
1840 match machine
1841 .try_decrypt_room_event(
1842 event.cast_ref(),
1843 self.inner.room_id(),
1844 self.client.decryption_settings(),
1845 )
1846 .await?
1847 {
1848 RoomEventDecryptionResult::Decrypted(decrypted) => {
1849 let push_actions = if let Some(push_ctx) = push_ctx {
1850 Some(push_ctx.for_event(&decrypted.event).await)
1851 } else {
1852 None
1853 };
1854 Ok(TimelineEvent::from_decrypted(decrypted, push_actions))
1855 }
1856 RoomEventDecryptionResult::UnableToDecrypt(utd_info) => {
1857 self.client
1858 .encryption()
1859 .backups()
1860 .maybe_download_room_key(self.room_id().to_owned(), event.clone());
1861 Ok(TimelineEvent::from_utd(event.clone().cast(), utd_info))
1862 }
1863 }
1864 }
1865
1866 #[cfg(feature = "experimental-encrypted-state-events")]
1874 pub async fn decrypt_event<T: JsonCastable<EncryptedEvent>>(
1875 &self,
1876 event: &Raw<T>,
1877 push_ctx: Option<&PushContext>,
1878 ) -> Result<TimelineEvent> {
1879 let machine = self.client.olm_machine().await;
1880 let machine = machine.as_ref().ok_or(Error::NoOlmMachine)?;
1881
1882 match machine
1883 .try_decrypt_room_event(
1884 event.cast_ref(),
1885 self.inner.room_id(),
1886 self.client.decryption_settings(),
1887 )
1888 .await?
1889 {
1890 RoomEventDecryptionResult::Decrypted(decrypted) => {
1891 let push_actions = if let Some(push_ctx) = push_ctx {
1892 Some(push_ctx.for_event(&decrypted.event).await)
1893 } else {
1894 None
1895 };
1896 Ok(TimelineEvent::from_decrypted(decrypted, push_actions))
1897 }
1898 RoomEventDecryptionResult::UnableToDecrypt(utd_info) => {
1899 self.client
1900 .encryption()
1901 .backups()
1902 .maybe_download_room_key(self.room_id().to_owned(), event.clone());
1903 Ok(TimelineEvent::from_utd(event.clone().cast_unchecked(), utd_info))
1906 }
1907 }
1908 }
1909
1910 #[cfg(feature = "e2e-encryption")]
1923 pub async fn get_encryption_info(
1924 &self,
1925 session_id: &str,
1926 sender: &UserId,
1927 ) -> Option<Arc<EncryptionInfo>> {
1928 let machine = self.client.olm_machine().await;
1929 let machine = machine.as_ref()?;
1930 machine.get_session_encryption_info(self.room_id(), session_id, sender).await.ok()
1931 }
1932
1933 #[cfg(feature = "e2e-encryption")]
1946 pub async fn discard_room_key(&self) -> Result<()> {
1947 let machine = self.client.olm_machine().await;
1948 if let Some(machine) = machine.as_ref() {
1949 machine.discard_room_key(self.inner.room_id()).await?;
1950 Ok(())
1951 } else {
1952 Err(Error::NoOlmMachine)
1953 }
1954 }
1955
1956 #[instrument(skip_all)]
1964 pub async fn ban_user(&self, user_id: &UserId, reason: Option<&str>) -> Result<()> {
1965 let request = assign!(
1966 ban_user::v3::Request::new(self.room_id().to_owned(), user_id.to_owned()),
1967 { reason: reason.map(ToOwned::to_owned) }
1968 );
1969 self.client.send(request).await?;
1970 Ok(())
1971 }
1972
1973 #[instrument(skip_all)]
1981 pub async fn unban_user(&self, user_id: &UserId, reason: Option<&str>) -> Result<()> {
1982 let request = assign!(
1983 unban_user::v3::Request::new(self.room_id().to_owned(), user_id.to_owned()),
1984 { reason: reason.map(ToOwned::to_owned) }
1985 );
1986 self.client.send(request).await?;
1987 Ok(())
1988 }
1989
1990 #[instrument(skip_all)]
1999 pub async fn kick_user(&self, user_id: &UserId, reason: Option<&str>) -> Result<()> {
2000 let request = assign!(
2001 kick_user::v3::Request::new(self.room_id().to_owned(), user_id.to_owned()),
2002 { reason: reason.map(ToOwned::to_owned) }
2003 );
2004 self.client.send(request).await?;
2005 Ok(())
2006 }
2007
2008 #[instrument(skip_all)]
2014 pub async fn invite_user_by_id(&self, user_id: &UserId) -> Result<()> {
2015 #[cfg(feature = "e2e-encryption")]
2016 if self.client.inner.enable_share_history_on_invite {
2017 shared_room_history::share_room_history(self, user_id.to_owned()).await?;
2018 }
2019
2020 let recipient = InvitationRecipient::UserId(InviteUserId::new(user_id.to_owned()));
2021 let request = invite_user::v3::Request::new(self.room_id().to_owned(), recipient);
2022 self.client.send(request).await?;
2023
2024 self.mark_members_missing();
2028
2029 Ok(())
2030 }
2031
2032 #[instrument(skip_all)]
2038 pub async fn invite_user_by_3pid(&self, invite_id: Invite3pid) -> Result<()> {
2039 let recipient = InvitationRecipient::ThirdPartyId(invite_id);
2040 let request = invite_user::v3::Request::new(self.room_id().to_owned(), recipient);
2041 self.client.send(request).await?;
2042
2043 self.mark_members_missing();
2047
2048 Ok(())
2049 }
2050
2051 pub async fn typing_notice(&self, typing: bool) -> Result<()> {
2086 self.ensure_room_joined()?;
2087
2088 let send = if let Some(typing_time) =
2091 self.client.inner.typing_notice_times.read().unwrap().get(self.room_id())
2092 {
2093 if typing_time.elapsed() > TYPING_NOTICE_RESEND_TIMEOUT {
2094 typing || typing_time.elapsed() <= TYPING_NOTICE_TIMEOUT
2098 } else {
2099 !typing
2101 }
2102 } else {
2103 typing
2106 };
2107
2108 if send {
2109 self.send_typing_notice(typing).await?;
2110 }
2111
2112 Ok(())
2113 }
2114
2115 #[instrument(name = "typing_notice", skip(self))]
2116 async fn send_typing_notice(&self, typing: bool) -> Result<()> {
2117 let typing = if typing {
2118 self.client
2119 .inner
2120 .typing_notice_times
2121 .write()
2122 .unwrap()
2123 .insert(self.room_id().to_owned(), Instant::now());
2124 Typing::Yes(TypingInfo::new(TYPING_NOTICE_TIMEOUT))
2125 } else {
2126 self.client.inner.typing_notice_times.write().unwrap().remove(self.room_id());
2127 Typing::No
2128 };
2129
2130 let request = create_typing_event::v3::Request::new(
2131 self.own_user_id().to_owned(),
2132 self.room_id().to_owned(),
2133 typing,
2134 );
2135
2136 self.client.send(request).await?;
2137
2138 Ok(())
2139 }
2140
2141 #[instrument(skip_all)]
2158 pub async fn send_single_receipt(
2159 &self,
2160 receipt_type: create_receipt::v3::ReceiptType,
2161 thread: ReceiptThread,
2162 event_id: OwnedEventId,
2163 ) -> Result<()> {
2164 let request_key = format!("{}|{}", receipt_type, thread.as_str().unwrap_or("<unthreaded>"));
2167
2168 self.client
2169 .inner
2170 .locks
2171 .read_receipt_deduplicated_handler
2172 .run((request_key, event_id.clone()), async {
2173 let is_unthreaded = thread == ReceiptThread::Unthreaded;
2175
2176 let mut request = create_receipt::v3::Request::new(
2177 self.room_id().to_owned(),
2178 receipt_type,
2179 event_id,
2180 );
2181 request.thread = thread;
2182
2183 self.client.send(request).await?;
2184
2185 if is_unthreaded {
2186 self.set_unread_flag(false).await?;
2187 }
2188
2189 Ok(())
2190 })
2191 .await
2192 }
2193
2194 #[instrument(skip_all)]
2204 pub async fn send_multiple_receipts(&self, receipts: Receipts) -> Result<()> {
2205 if receipts.is_empty() {
2206 return Ok(());
2207 }
2208
2209 let Receipts { fully_read, public_read_receipt, private_read_receipt } = receipts;
2210 let request = assign!(set_read_marker::v3::Request::new(self.room_id().to_owned()), {
2211 fully_read,
2212 read_receipt: public_read_receipt,
2213 private_read_receipt,
2214 });
2215
2216 self.client.send(request).await?;
2217
2218 self.set_unread_flag(false).await?;
2219
2220 Ok(())
2221 }
2222
2223 #[allow(unused_variables, unused_mut)]
2227 async fn enable_encryption_inner(&self, encrypted_state_events: bool) -> Result<()> {
2228 use ruma::{
2229 EventEncryptionAlgorithm, events::room::encryption::RoomEncryptionEventContent,
2230 };
2231 const SYNC_WAIT_TIME: Duration = Duration::from_secs(3);
2232
2233 if !self.latest_encryption_state().await?.is_encrypted() {
2234 let mut content =
2235 RoomEncryptionEventContent::new(EventEncryptionAlgorithm::MegolmV1AesSha2);
2236 #[cfg(feature = "experimental-encrypted-state-events")]
2237 if encrypted_state_events {
2238 content = content.with_encrypted_state();
2239 }
2240 self.send_state_event(content).await?;
2241
2242 let res = timeout(
2249 async {
2250 loop {
2251 self.client.inner.sync_beat.listen().await;
2253 let _state_store_lock =
2254 self.client.base_client().state_store_lock().lock().await;
2255
2256 if !self.inner.encryption_state().is_unknown() {
2257 break;
2258 }
2259 }
2260 },
2261 SYNC_WAIT_TIME,
2262 )
2263 .await;
2264
2265 let store_guard = self.client.base_client().state_store_lock().lock().await;
2266
2267 #[cfg(not(feature = "experimental-encrypted-state-events"))]
2269 if res.is_ok() && self.inner.encryption_state().is_encrypted() {
2270 debug!("room successfully marked as encrypted");
2271 return Ok(());
2272 }
2273
2274 #[cfg(feature = "experimental-encrypted-state-events")]
2276 if res.is_ok() && {
2277 if encrypted_state_events {
2278 self.inner.encryption_state().is_state_encrypted()
2279 } else {
2280 self.inner.encryption_state().is_encrypted()
2281 }
2282 } {
2283 debug!("room successfully marked as encrypted");
2284 return Ok(());
2285 }
2286
2287 debug!("still not marked as encrypted, marking encryption state as missing");
2292
2293 self.update_and_save_room_info_with_store_guard(&store_guard, |mut info| {
2294 info.mark_encryption_state_missing();
2295 (info, RoomInfoNotableUpdateReasons::empty())
2296 })
2297 .await?;
2298 }
2299
2300 Ok(())
2301 }
2302
2303 #[instrument(skip_all)]
2335 pub async fn enable_encryption(&self) -> Result<()> {
2336 self.enable_encryption_inner(false).await
2337 }
2338
2339 #[instrument(skip_all)]
2372 #[cfg(feature = "experimental-encrypted-state-events")]
2373 pub async fn enable_encryption_with_state_event_encryption(&self) -> Result<()> {
2374 self.enable_encryption_inner(true).await
2375 }
2376
2377 #[cfg(feature = "e2e-encryption")]
2386 #[instrument(skip_all, fields(room_id = ?self.room_id()))]
2387 async fn preshare_room_key(&self) -> Result<()> {
2388 self.ensure_room_joined()?;
2389
2390 let _guard = self.client.encryption().spin_lock_store(Some(60000)).await?;
2392
2393 self.client
2394 .locks()
2395 .group_session_deduplicated_handler
2396 .run(self.room_id().to_owned(), async move {
2397 {
2398 let members = self
2399 .client
2400 .state_store()
2401 .get_user_ids(self.room_id(), RoomMemberships::ACTIVE)
2402 .await?;
2403 self.client.claim_one_time_keys(members.iter().map(Deref::deref)).await?;
2404 };
2405
2406 let response = self.share_room_key().await;
2407
2408 if let Err(r) = response {
2412 let machine = self.client.olm_machine().await;
2413 if let Some(machine) = machine.as_ref() {
2414 machine.discard_room_key(self.room_id()).await?;
2415 }
2416 return Err(r);
2417 }
2418
2419 Ok(())
2420 })
2421 .await
2422 }
2423
2424 #[cfg(feature = "e2e-encryption")]
2430 #[instrument(skip_all)]
2431 async fn share_room_key(&self) -> Result<()> {
2432 self.ensure_room_joined()?;
2433
2434 let requests = self.client.base_client().share_room_key(self.room_id()).await?;
2435
2436 for request in requests {
2437 let response = self.client.send_to_device(&request).await?;
2438 self.client.mark_request_as_sent(&request.txn_id, &response).await?;
2439 }
2440
2441 Ok(())
2442 }
2443
2444 #[instrument(skip_all)]
2453 pub async fn sync_up(&self) {
2454 while !self.is_synced() && self.state() == RoomState::Joined {
2455 let wait_for_beat = self.client.inner.sync_beat.listen();
2456 let _ = timeout(wait_for_beat, Duration::from_millis(1000)).await;
2458 }
2459 }
2460
2461 pub fn send(&self, content: impl MessageLikeEventContent) -> SendMessageLikeEvent<'_> {
2532 SendMessageLikeEvent::new(self, content)
2533 }
2534
2535 #[cfg(feature = "e2e-encryption")]
2538 async fn query_keys_for_untracked_or_dirty_users(&self) -> Result<()> {
2539 let olm = self.client.olm_machine().await;
2540 let olm = olm.as_ref().expect("Olm machine wasn't started");
2541
2542 let members =
2543 self.client.state_store().get_user_ids(self.room_id(), RoomMemberships::ACTIVE).await?;
2544
2545 let tracked: HashMap<_, _> = olm
2546 .store()
2547 .load_tracked_users()
2548 .await?
2549 .into_iter()
2550 .map(|tracked| (tracked.user_id, tracked.dirty))
2551 .collect();
2552
2553 let members_with_unknown_devices =
2556 members.iter().filter(|member| tracked.get(*member).is_none_or(|dirty| *dirty));
2557
2558 let (req_id, request) =
2559 olm.query_keys_for_users(members_with_unknown_devices.map(|owned| owned.borrow()));
2560
2561 if !request.device_keys.is_empty() {
2562 self.client.keys_query(&req_id, request.device_keys).await?;
2563 }
2564
2565 Ok(())
2566 }
2567
2568 #[instrument(skip_all, fields(event_type, room_id = ?self.room_id(), transaction_id, is_room_encrypted, event_id))]
2612 pub fn send_raw<'a>(
2613 &'a self,
2614 event_type: &'a str,
2615 content: impl IntoRawMessageLikeEventContent,
2616 ) -> SendRawMessageLikeEvent<'a> {
2617 SendRawMessageLikeEvent::new(self, event_type, content)
2620 }
2621
2622 #[instrument(skip_all)]
2670 pub fn send_attachment<'a>(
2671 &'a self,
2672 filename: impl Into<String>,
2673 content_type: &'a Mime,
2674 data: Vec<u8>,
2675 config: AttachmentConfig,
2676 ) -> SendAttachment<'a> {
2677 SendAttachment::new(self, filename.into(), content_type, data, config)
2678 }
2679
2680 #[instrument(skip_all)]
2708 pub(super) async fn prepare_and_send_attachment<'a>(
2709 &'a self,
2710 filename: String,
2711 content_type: &'a Mime,
2712 data: Vec<u8>,
2713 mut config: AttachmentConfig,
2714 send_progress: SharedObservable<TransmissionProgress>,
2715 store_in_cache: bool,
2716 ) -> Result<send_message_event::v3::Response> {
2717 self.ensure_room_joined()?;
2718
2719 let txn_id = config.txn_id.take();
2720 let extra_content = config.extra_content.take();
2721 let mentions = config.mentions.take();
2722
2723 let thumbnail = config.thumbnail.take();
2724
2725 let thumbnail_cache_info = if store_in_cache {
2727 thumbnail
2728 .as_ref()
2729 .map(|thumbnail| (thumbnail.data.clone(), thumbnail.height, thumbnail.width))
2730 } else {
2731 None
2732 };
2733
2734 #[cfg(feature = "e2e-encryption")]
2735 let (media_source, thumbnail) = if self.latest_encryption_state().await?.is_encrypted() {
2736 self.client
2737 .upload_encrypted_media_and_thumbnail(&data, thumbnail, send_progress)
2738 .await?
2739 } else {
2740 self.client
2741 .media()
2742 .upload_plain_media_and_thumbnail(
2743 content_type,
2744 data.clone(),
2747 thumbnail,
2748 send_progress,
2749 )
2750 .await?
2751 };
2752
2753 #[cfg(not(feature = "e2e-encryption"))]
2754 let (media_source, thumbnail) = self
2755 .client
2756 .media()
2757 .upload_plain_media_and_thumbnail(content_type, data.clone(), thumbnail, send_progress)
2758 .await?;
2759
2760 if store_in_cache {
2761 let media_store_lock_guard = self.client.media_store().lock().await?;
2762
2763 debug!("caching the media");
2767 let request =
2768 MediaRequestParameters { source: media_source.clone(), format: MediaFormat::File };
2769
2770 if let Err(err) = media_store_lock_guard
2771 .add_media_content(&request, data, IgnoreMediaRetentionPolicy::No)
2772 .await
2773 {
2774 warn!("unable to cache the media after uploading it: {err}");
2775 }
2776
2777 if let Some(((data, height, width), source)) =
2778 thumbnail_cache_info.zip(thumbnail.as_ref().map(|tuple| &tuple.0))
2779 {
2780 debug!("caching the thumbnail");
2781
2782 let request = MediaRequestParameters {
2783 source: source.clone(),
2784 format: MediaFormat::Thumbnail(MediaThumbnailSettings::new(width, height)),
2785 };
2786
2787 if let Err(err) = media_store_lock_guard
2788 .add_media_content(&request, data, IgnoreMediaRetentionPolicy::No)
2789 .await
2790 {
2791 warn!("unable to cache the media after uploading it: {err}");
2792 }
2793 }
2794 }
2795
2796 let content = self
2797 .make_media_event(
2798 Room::make_attachment_type(
2799 content_type,
2800 filename,
2801 media_source,
2802 config.caption,
2803 config.info,
2804 thumbnail,
2805 ),
2806 mentions,
2807 config.reply,
2808 )
2809 .await?;
2810
2811 if let Some(extra_content) = extra_content.filter(|extra| !extra.is_empty()) {
2815 let serde_json::Value::Object(mut object) = serde_json::to_value(&content)
2816 .map_err(|error| Error::UnknownError(Box::new(error)))?
2817 else {
2818 unreachable!("a room message event content always serializes to a JSON object");
2819 };
2820 for (key, value) in extra_content {
2821 match object.entry(key) {
2822 serde_json::map::Entry::Occupied(entry) => {
2823 warn!(
2824 key = entry.key(),
2825 "extra content field shadowed by the event's own field"
2826 );
2827 }
2828 serde_json::map::Entry::Vacant(entry) => {
2829 entry.insert(value);
2830 }
2831 }
2832 }
2833
2834 let event_type = content.event_type().to_string();
2835 let mut fut = self.send_raw(&event_type, serde_json::Value::Object(object));
2836 if let Some(txn_id) = &txn_id {
2837 fut = fut.with_transaction_id(txn_id);
2838 }
2839 return fut.await.map(|result| result.response);
2840 }
2841
2842 let mut fut = self.send(content);
2843 if let Some(txn_id) = txn_id {
2844 fut = fut.with_transaction_id(txn_id);
2845 }
2846
2847 fut.await.map(|result| result.response)
2848 }
2849
2850 #[allow(clippy::too_many_arguments)]
2853 pub(crate) fn make_attachment_type(
2854 content_type: &Mime,
2855 filename: String,
2856 source: MediaSource,
2857 caption: Option<TextMessageEventContent>,
2858 info: Option<AttachmentInfo>,
2859 thumbnail: Option<(MediaSource, Box<ThumbnailInfo>)>,
2860 ) -> MessageType {
2861 make_media_type!(MessageType, content_type, filename, source, caption, info, thumbnail)
2862 }
2863
2864 pub(crate) async fn make_media_event(
2867 &self,
2868 msg_type: MessageType,
2869 mentions: Option<Mentions>,
2870 reply: Option<Reply>,
2871 ) -> Result<RoomMessageEventContent> {
2872 let mut content = RoomMessageEventContent::new(msg_type);
2873 if let Some(mentions) = mentions {
2874 content = content.add_mentions(mentions);
2875 }
2876 if let Some(reply) = reply {
2877 content = self.make_reply_event(content.into(), reply).await?;
2880 }
2881 Ok(content)
2882 }
2883
2884 #[cfg(feature = "unstable-msc4274")]
2887 #[allow(clippy::too_many_arguments)]
2888 pub(crate) fn make_gallery_item_type(
2889 content_type: &Mime,
2890 filename: String,
2891 source: MediaSource,
2892 caption: Option<TextMessageEventContent>,
2893 info: Option<AttachmentInfo>,
2894 thumbnail: Option<(MediaSource, Box<ThumbnailInfo>)>,
2895 ) -> GalleryItemType {
2896 make_media_type!(GalleryItemType, content_type, filename, source, caption, info, thumbnail)
2897 }
2898
2899 pub async fn update_power_levels(
2908 &self,
2909 updates: Vec<(&UserId, Int)>,
2910 ) -> Result<send_state_event::v3::Response> {
2911 let mut power_levels = self.power_levels().await?;
2912
2913 for (user_id, new_level) in updates {
2914 if new_level == power_levels.users_default {
2915 power_levels.users.remove(user_id);
2916 } else {
2917 power_levels.users.insert(user_id.to_owned(), new_level);
2918 }
2919 }
2920
2921 self.send_state_event(RoomPowerLevelsEventContent::try_from(power_levels)?).await
2922 }
2923
2924 pub async fn apply_power_level_changes(&self, changes: RoomPowerLevelChanges) -> Result<()> {
2929 let mut power_levels = self.power_levels().await?;
2930 power_levels.apply(changes)?;
2931 self.send_state_event(RoomPowerLevelsEventContent::try_from(power_levels)?).await?;
2932 Ok(())
2933 }
2934
2935 pub async fn reset_power_levels(&self) -> Result<RoomPowerLevels> {
2939 let creators = self.creators().unwrap_or_default();
2940 let rules = self.clone_info().room_version_rules_or_default();
2941
2942 let default_power_levels =
2943 RoomPowerLevels::new(RoomPowerLevelsSource::None, &rules.authorization, creators);
2944 let changes = RoomPowerLevelChanges::from(default_power_levels);
2945 self.apply_power_level_changes(changes).await?;
2946 Ok(self.power_levels().await?)
2947 }
2948
2949 pub async fn get_suggested_user_role(&self, user_id: &UserId) -> Result<RoomMemberRole> {
2954 let power_level = self.get_user_power_level(user_id).await?;
2955 Ok(RoomMemberRole::suggested_role_for_power_level(power_level))
2956 }
2957
2958 pub async fn get_user_power_level(&self, user_id: &UserId) -> Result<UserPowerLevel> {
2963 let event = self.power_levels().await?;
2964 Ok(event.for_user(user_id))
2965 }
2966
2967 pub async fn users_with_power_levels(&self) -> HashMap<OwnedUserId, i64> {
2970 let power_levels = self.power_levels().await.ok();
2971 let mut user_power_levels = HashMap::<OwnedUserId, i64>::new();
2972 if let Some(power_levels) = power_levels {
2973 for (id, level) in power_levels.users.into_iter() {
2974 user_power_levels.insert(id, level.into());
2975 }
2976 }
2977 user_power_levels
2978 }
2979
2980 pub async fn set_name(&self, name: String) -> Result<send_state_event::v3::Response> {
2982 self.send_state_event(RoomNameEventContent::new(name)).await
2983 }
2984
2985 pub async fn set_room_topic(&self, topic: &str) -> Result<send_state_event::v3::Response> {
2987 self.send_state_event(RoomTopicEventContent::new(topic.into())).await
2988 }
2989
2990 pub async fn set_avatar_url(
2996 &self,
2997 url: &MxcUri,
2998 info: Option<avatar::ImageInfo>,
2999 ) -> Result<send_state_event::v3::Response> {
3000 self.ensure_room_joined()?;
3001
3002 let mut room_avatar_event = RoomAvatarEventContent::new();
3003 room_avatar_event.url = Some(url.to_owned());
3004 room_avatar_event.info = info.map(Box::new);
3005
3006 self.send_state_event(room_avatar_event).await
3007 }
3008
3009 pub async fn remove_avatar(&self) -> Result<send_state_event::v3::Response> {
3011 self.send_state_event(RoomAvatarEventContent::new()).await
3012 }
3013
3014 pub async fn upload_avatar(
3022 &self,
3023 mime: &Mime,
3024 data: Vec<u8>,
3025 info: Option<avatar::ImageInfo>,
3026 ) -> Result<send_state_event::v3::Response> {
3027 self.ensure_room_joined()?;
3028
3029 let upload_response = self.client.media().upload(mime, data, None).await?;
3030 let mut info = info.unwrap_or_default();
3031 info.blurhash = upload_response.blurhash;
3032 info.mimetype = Some(mime.to_string());
3033
3034 self.set_avatar_url(&upload_response.content_uri, Some(info)).await
3035 }
3036
3037 #[cfg(not(feature = "experimental-encrypted-state-events"))]
3081 #[instrument(skip_all)]
3082 pub async fn send_state_event(
3083 &self,
3084 content: impl StateEventContent<StateKey = EmptyStateKey>,
3085 ) -> Result<send_state_event::v3::Response> {
3086 self.send_state_event_for_key(&EmptyStateKey, content).await
3087 }
3088
3089 #[cfg(feature = "experimental-encrypted-state-events")]
3140 #[instrument(skip_all)]
3141 pub fn send_state_event<'a>(
3142 &'a self,
3143 content: impl StateEventContent<StateKey = EmptyStateKey>,
3144 ) -> SendStateEvent<'a> {
3145 self.send_state_event_for_key(&EmptyStateKey, content)
3146 }
3147
3148 #[cfg(not(feature = "experimental-encrypted-state-events"))]
3189 pub async fn send_state_event_for_key<C, K>(
3190 &self,
3191 state_key: &K,
3192 content: C,
3193 ) -> Result<send_state_event::v3::Response>
3194 where
3195 C: StateEventContent,
3196 C::StateKey: Borrow<K>,
3197 K: AsRef<str> + ?Sized,
3198 {
3199 self.ensure_room_joined()?;
3200 let request =
3201 send_state_event::v3::Request::new(self.room_id().to_owned(), state_key, &content)?;
3202 let response = self.client.send(request).await?;
3203 Ok(response)
3204 }
3205
3206 #[cfg(feature = "experimental-encrypted-state-events")]
3255 pub fn send_state_event_for_key<'a, C, K>(
3256 &'a self,
3257 state_key: &K,
3258 content: C,
3259 ) -> SendStateEvent<'a>
3260 where
3261 C: StateEventContent,
3262 C::StateKey: Borrow<K>,
3263 K: AsRef<str> + ?Sized,
3264 {
3265 SendStateEvent::new(self, state_key, content)
3266 }
3267
3268 #[cfg(not(feature = "experimental-encrypted-state-events"))]
3303 #[instrument(skip_all)]
3304 pub async fn send_state_event_raw(
3305 &self,
3306 event_type: &str,
3307 state_key: &str,
3308 content: impl IntoRawStateEventContent,
3309 ) -> Result<send_state_event::v3::Response> {
3310 self.ensure_room_joined()?;
3311
3312 let request = send_state_event::v3::Request::new_raw(
3313 self.room_id().to_owned(),
3314 event_type.into(),
3315 state_key.to_owned(),
3316 content.into_raw_state_event_content(),
3317 );
3318
3319 Ok(self.client.send(request).await?)
3320 }
3321
3322 #[cfg(feature = "experimental-encrypted-state-events")]
3364 #[instrument(skip_all)]
3365 pub fn send_state_event_raw<'a>(
3366 &'a self,
3367 event_type: &'a str,
3368 state_key: &'a str,
3369 content: impl IntoRawStateEventContent,
3370 ) -> SendRawStateEvent<'a> {
3371 SendRawStateEvent::new(self, event_type, state_key, content)
3372 }
3373
3374 #[instrument(skip_all)]
3409 pub async fn redact(
3410 &self,
3411 event_id: &EventId,
3412 reason: Option<&str>,
3413 txn_id: Option<OwnedTransactionId>,
3414 ) -> HttpResult<redact_event::v3::Response> {
3415 let txn_id = txn_id.unwrap_or_else(TransactionId::new);
3416 let request = assign!(
3417 redact_event::v3::Request::new(self.room_id().to_owned(), event_id.to_owned(), txn_id),
3418 { reason: reason.map(ToOwned::to_owned) }
3419 );
3420
3421 self.client.send(request).await
3422 }
3423
3424 pub async fn route(&self) -> Result<Vec<OwnedServerName>> {
3433 let acl_ev = self
3434 .get_state_event_static::<RoomServerAclEventContent>()
3435 .await?
3436 .and_then(|ev| ev.deserialize().ok());
3437 let acl = acl_ev.as_ref().and_then(|ev| match ev {
3438 SyncOrStrippedState::Sync(ev) => ev.as_original().map(|ev| &ev.content),
3439 SyncOrStrippedState::Stripped(ev) => Some(&ev.content),
3440 });
3441
3442 let members: Vec<_> = self
3446 .members_no_sync(RoomMemberships::JOIN)
3447 .await?
3448 .into_iter()
3449 .filter(|member| {
3450 let server = member.user_id().server_name();
3451 acl.filter(|acl| !acl.is_allowed(server)).is_none() && !server.is_ip_literal()
3452 })
3453 .collect();
3454
3455 let max = members
3458 .iter()
3459 .max_by_key(|member| member.power_level())
3460 .filter(|max| max.power_level() >= int!(50))
3461 .map(|member| member.user_id().server_name());
3462
3463 let servers = members
3465 .iter()
3466 .map(|member| member.user_id().server_name())
3467 .filter(|server| max.filter(|max| max == server).is_none())
3468 .fold(BTreeMap::<_, u32>::new(), |mut servers, server| {
3469 *servers.entry(server).or_default() += 1;
3470 servers
3471 });
3472 let mut servers: Vec<_> = servers.into_iter().collect();
3473 servers.sort_unstable_by(|(_, count_a), (_, count_b)| count_b.cmp(count_a));
3474
3475 Ok(max
3476 .into_iter()
3477 .chain(servers.into_iter().map(|(name, _)| name))
3478 .take(3)
3479 .map(ToOwned::to_owned)
3480 .collect())
3481 }
3482
3483 pub async fn matrix_to_permalink(&self) -> Result<MatrixToUri> {
3490 if let Some(alias) = self.canonical_alias().or_else(|| self.alt_aliases().pop()) {
3491 return Ok(alias.matrix_to_uri());
3492 }
3493
3494 let via = self.route().await?;
3495 Ok(self.room_id().matrix_to_uri_via(via))
3496 }
3497
3498 pub async fn matrix_permalink(&self, join: bool) -> Result<MatrixUri> {
3509 if let Some(alias) = self.canonical_alias().or_else(|| self.alt_aliases().pop()) {
3510 return Ok(alias.matrix_uri(join));
3511 }
3512
3513 let via = self.route().await?;
3514 Ok(self.room_id().matrix_uri_via(via, join))
3515 }
3516
3517 pub async fn matrix_to_event_permalink(
3531 &self,
3532 event_id: impl Into<OwnedEventId>,
3533 ) -> Result<MatrixToUri> {
3534 let via = self.route().await?;
3537 Ok(self.room_id().matrix_to_event_uri_via(event_id, via))
3538 }
3539
3540 pub async fn matrix_event_permalink(
3554 &self,
3555 event_id: impl Into<OwnedEventId>,
3556 ) -> Result<MatrixUri> {
3557 let via = self.route().await?;
3560 Ok(self.room_id().matrix_event_uri_via(event_id, via))
3561 }
3562
3563 pub async fn load_user_receipt(
3576 &self,
3577 receipt_type: ReceiptType,
3578 receipt_thread: &ReceiptThread,
3579 user_id: &UserId,
3580 ) -> Result<Option<(OwnedEventId, Receipt)>> {
3581 self.inner
3582 .load_user_receipt(receipt_type, receipt_thread, user_id)
3583 .await
3584 .map_err(Into::into)
3585 }
3586
3587 pub async fn load_event_receipts(
3600 &self,
3601 receipt_type: ReceiptType,
3602 receipt_thread: &ReceiptThread,
3603 event_id: &EventId,
3604 ) -> Result<Vec<(OwnedUserId, Receipt)>> {
3605 self.inner
3606 .load_event_receipts(receipt_type, receipt_thread, event_id)
3607 .await
3608 .map_err(Into::into)
3609 }
3610
3611 pub async fn push_condition_room_ctx(&self) -> Result<Option<PushConditionRoomCtx>> {
3616 self.push_condition_room_ctx_internal(self.client.enabled_thread_subscriptions().await?)
3617 .await
3618 }
3619
3620 pub(crate) async fn push_condition_room_ctx_internal(
3627 &self,
3628 with_threads_subscriptions: bool,
3629 ) -> Result<Option<PushConditionRoomCtx>> {
3630 let room_id = self.room_id();
3631 let user_id = self.own_user_id();
3632 let room_info = self.clone_info();
3633 let member_count = room_info.active_members_count();
3634
3635 let user_display_name = if let Some(member) = self.get_member_no_sync(user_id).await? {
3636 member.name().to_owned()
3637 } else {
3638 return Ok(None);
3639 };
3640
3641 let power_levels = match self.power_levels().await {
3642 Ok(power_levels) => Some(power_levels.into()),
3643 Err(error) => {
3644 if matches!(room_info.state(), RoomState::Joined) {
3645 error!("Could not compute power levels for push conditions: {error}");
3648 }
3649 None
3650 }
3651 };
3652
3653 let mut ctx = assign!(PushConditionRoomCtx::new(
3654 room_id.to_owned(),
3655 UInt::new(member_count).unwrap_or(UInt::MAX),
3656 user_id.to_owned(),
3657 user_display_name,
3658 ),
3659 {
3660 power_levels,
3661 });
3662
3663 if with_threads_subscriptions {
3664 let this = self.clone();
3665 ctx = ctx.with_has_thread_subscription_fn(move |event_id: &EventId| {
3666 let room = this.clone();
3667 Box::pin(async move {
3668 if let Ok(maybe_sub) = room.load_or_fetch_thread_subscription(event_id).await {
3669 maybe_sub.is_some()
3670 } else {
3671 false
3672 }
3673 })
3674 });
3675 }
3676
3677 Ok(Some(ctx))
3678 }
3679
3680 pub async fn push_context(&self) -> Result<Option<PushContext>> {
3683 self.push_context_internal(self.client.enabled_thread_subscriptions().await?).await
3684 }
3685
3686 #[instrument(skip(self))]
3690 pub(crate) async fn push_context_internal(
3691 &self,
3692 with_threads_subscriptions: bool,
3693 ) -> Result<Option<PushContext>> {
3694 let Some(push_condition_room_ctx) =
3695 self.push_condition_room_ctx_internal(with_threads_subscriptions).await?
3696 else {
3697 debug!("Could not aggregate push context");
3698 return Ok(None);
3699 };
3700 let push_rules = self.client().account().push_rules().await?;
3701 Ok(Some(PushContext::new(push_condition_room_ctx, push_rules)))
3702 }
3703
3704 pub async fn event_push_actions<T>(&self, event: &Raw<T>) -> Result<Option<Vec<Action>>> {
3709 if let Some(ctx) = self.push_context().await? {
3710 Ok(Some(ctx.for_event(event).await))
3711 } else {
3712 Ok(None)
3713 }
3714 }
3715
3716 pub async fn invite_details(&self) -> Result<Invite> {
3719 let state = self.state();
3720
3721 if state != RoomState::Invited {
3722 return Err(Error::WrongRoomState(Box::new(WrongRoomState::new("Invited", state))));
3723 }
3724
3725 let invitee = self
3726 .get_member_no_sync(self.own_user_id())
3727 .await?
3728 .ok_or_else(|| Error::UnknownError(Box::new(InvitationError::EventMissing)))?;
3729 let event = invitee.event();
3730
3731 let inviter_id = event.sender().to_owned();
3732 let inviter = self.get_member_no_sync(&inviter_id).await?;
3733
3734 Ok(Invite { invitee, inviter_id, inviter })
3735 }
3736
3737 pub async fn member_with_sender_info(
3745 &self,
3746 user_id: &UserId,
3747 ) -> Result<RoomMemberWithSenderInfo> {
3748 let Some(member) = self.get_member_no_sync(user_id).await? else {
3749 return Err(Error::InsufficientData);
3750 };
3751
3752 let sender_member =
3753 if let Some(member) = self.get_member_no_sync(member.event().sender()).await? {
3754 Some(member)
3756 } else if self.are_members_synced() {
3757 None
3759 } else if self.sync_members().await.is_ok() {
3760 self.get_member_no_sync(member.event().sender()).await?
3762 } else {
3763 None
3764 };
3765
3766 Ok(RoomMemberWithSenderInfo { room_member: member, sender_info: sender_member })
3767 }
3768
3769 pub async fn forget(&self) -> Result<()> {
3775 let state = self.state();
3776 match state {
3777 RoomState::Joined | RoomState::Invited | RoomState::Knocked => {
3778 return Err(Error::WrongRoomState(Box::new(WrongRoomState::new(
3779 "Left / Banned",
3780 state,
3781 ))));
3782 }
3783 RoomState::Left | RoomState::Banned => {}
3784 }
3785
3786 let room_id = self.room_id();
3787
3788 let request = forget_room::v3::Request::new(room_id.to_owned());
3789 let _response = self.client.send(request).await?;
3790
3791 if self.inner.direct_targets_length() != 0
3793 && let Err(e) = self.set_is_direct(false).await
3794 {
3795 warn!(?room_id, "failed to remove room from m.direct account data: {e}");
3798 }
3799
3800 self.client.base_client().forget_room(room_id).await?;
3801 self.client.event_cache().forget_room(room_id).await?;
3802
3803 Ok(())
3804 }
3805
3806 fn ensure_room_joined(&self) -> Result<()> {
3807 let state = self.state();
3808 if state == RoomState::Joined {
3809 Ok(())
3810 } else {
3811 Err(Error::WrongRoomState(Box::new(WrongRoomState::new("Joined", state))))
3812 }
3813 }
3814
3815 pub async fn notification_mode(&self) -> Option<RoomNotificationMode> {
3817 if !matches!(self.state(), RoomState::Joined) {
3818 return None;
3819 }
3820
3821 let notification_settings = self.client().notification_settings().await;
3822
3823 let notification_mode =
3825 notification_settings.get_user_defined_room_notification_mode(self.room_id()).await;
3826
3827 if notification_mode.is_some() {
3828 notification_mode
3829 } else if let Ok(is_encrypted) =
3830 self.latest_encryption_state().await.map(|state| state.is_encrypted())
3831 {
3832 let is_one_to_one = IsOneToOne::from(self.active_members_count() == 2);
3837 let default_mode = notification_settings
3838 .get_default_room_notification_mode(IsEncrypted::from(is_encrypted), is_one_to_one)
3839 .await;
3840 Some(default_mode)
3841 } else {
3842 None
3843 }
3844 }
3845
3846 pub async fn user_defined_notification_mode(&self) -> Option<RoomNotificationMode> {
3857 if !matches!(self.state(), RoomState::Joined) {
3858 return None;
3859 }
3860
3861 let notification_settings = self.client().notification_settings().await;
3862
3863 let mode =
3865 notification_settings.get_user_defined_room_notification_mode(self.room_id()).await;
3866
3867 if let Some(mode) = mode {
3868 self.update_cached_user_defined_notification_mode(mode);
3869 }
3870
3871 mode
3872 }
3873
3874 pub async fn report_content(
3887 &self,
3888 event_id: OwnedEventId,
3889 reason: Option<String>,
3890 ) -> Result<report_content::v3::Response> {
3891 let state = self.state();
3892 if state != RoomState::Joined {
3893 return Err(Error::WrongRoomState(Box::new(WrongRoomState::new("Joined", state))));
3894 }
3895
3896 let request = assign!(
3897 report_content::v3::Request::new(
3898 self.inner.room_id().to_owned(),
3899 event_id,
3900 ), {
3901 reason: reason
3902 }
3903 );
3904 Ok(self.client.send(request).await?)
3905 }
3906
3907 pub async fn report_room(&self, reason: String) -> Result<report_room::v3::Response> {
3918 let request = report_room::v3::Request::new(self.inner.room_id().to_owned(), reason);
3919
3920 Ok(self.client.send(request).await?)
3921 }
3922
3923 pub async fn set_unread_flag(&self, unread: bool) -> Result<()> {
3929 if self.is_marked_unread() == unread {
3930 return Ok(());
3932 }
3933
3934 let user_id = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
3935
3936 let content = MarkedUnreadEventContent::new(unread);
3937
3938 let request = set_room_account_data::v3::Request::new(
3939 user_id.to_owned(),
3940 self.inner.room_id().to_owned(),
3941 &content,
3942 )?;
3943
3944 self.client.send(request).await?;
3945 Ok(())
3946 }
3947
3948 pub async fn event_cache(
3951 &self,
3952 ) -> event_cache::Result<(RoomEventCache, Arc<EventCacheDropHandles>)> {
3953 self.client.event_cache().room(self.room_id()).await
3954 }
3955
3956 pub(crate) async fn get_user_beacon_info(
3963 &self,
3964 user_id: &UserId,
3965 ) -> Result<OriginalSyncStateEvent<BeaconInfoEventContent>, BeaconError> {
3966 let raw_event = self
3967 .get_state_event_static_for_key::<BeaconInfoEventContent, _>(user_id)
3968 .await?
3969 .ok_or(BeaconError::NotFound)?;
3970
3971 match raw_event.deserialize()? {
3972 SyncOrStrippedState::Sync(SyncStateEvent::Original(beacon_info)) => Ok(beacon_info),
3973 SyncOrStrippedState::Sync(SyncStateEvent::Redacted(_)) => Err(BeaconError::Redacted),
3974 SyncOrStrippedState::Stripped(_) => Err(BeaconError::Stripped),
3975 }
3976 }
3977
3978 pub async fn start_live_location_share(
3991 &self,
3992 duration_millis: u64,
3993 description: Option<String>,
3994 ) -> Result<send_state_event::v3::Response> {
3995 self.ensure_room_joined()?;
3996
3997 self.send_state_event_for_key(
3998 self.own_user_id(),
3999 BeaconInfoEventContent::new(
4000 description,
4001 Duration::from_millis(duration_millis),
4002 true,
4003 None,
4004 ),
4005 )
4006 .await
4007 }
4008
4009 pub async fn stop_live_location_share(
4017 &self,
4018 ) -> Result<send_state_event::v3::Response, BeaconError> {
4019 self.ensure_room_joined()?;
4020
4021 let mut beacon_info_event = self.get_user_beacon_info(self.own_user_id()).await?;
4022
4023 if beacon_info_event.content.live {
4024 beacon_info_event.content.stop();
4025 Ok(self.send_state_event_for_key(self.own_user_id(), beacon_info_event.content).await?)
4026 } else {
4027 Err(BeaconError::NotLive)
4028 }
4029 }
4030
4031 pub async fn send_location_beacon(
4043 &self,
4044 geo_uri: String,
4045 ) -> Result<send_message_event::v3::Response, BeaconError> {
4046 self.ensure_room_joined()?;
4047
4048 let beacon_info_event = self.get_user_beacon_info(self.own_user_id()).await?;
4049
4050 if beacon_info_event.content.is_live() {
4051 let content = BeaconEventContent::new(beacon_info_event.event_id, geo_uri, None);
4052 Ok(self
4053 .send(content)
4054 .with_request_config(RequestConfig::new().retry_limit(6))
4055 .await?
4056 .response)
4057 } else {
4058 Err(BeaconError::NotLive)
4059 }
4060 }
4061
4062 pub async fn save_composer_draft(
4065 &self,
4066 draft: ComposerDraft,
4067 thread_root: Option<&EventId>,
4068 ) -> Result<()> {
4069 self.client
4070 .state_store()
4071 .set_kv_data(
4072 StateStoreDataKey::ComposerDraft(self.room_id(), thread_root),
4073 StateStoreDataValue::ComposerDraft(draft),
4074 )
4075 .await?;
4076 Ok(())
4077 }
4078
4079 pub async fn load_composer_draft(
4082 &self,
4083 thread_root: Option<&EventId>,
4084 ) -> Result<Option<ComposerDraft>> {
4085 let data = self
4086 .client
4087 .state_store()
4088 .get_kv_data(StateStoreDataKey::ComposerDraft(self.room_id(), thread_root))
4089 .await?;
4090 Ok(data.and_then(|d| d.into_composer_draft()))
4091 }
4092
4093 pub async fn clear_composer_draft(&self, thread_root: Option<&EventId>) -> Result<()> {
4096 self.client
4097 .state_store()
4098 .remove_kv_data(StateStoreDataKey::ComposerDraft(self.room_id(), thread_root))
4099 .await?;
4100 Ok(())
4101 }
4102
4103 pub async fn load_pinned_events(&self) -> Result<Option<Vec<OwnedEventId>>> {
4106 let response = self
4107 .client
4108 .send(get_state_event_for_key::v3::Request::new(
4109 self.room_id().to_owned(),
4110 StateEventType::RoomPinnedEvents,
4111 "".to_owned(),
4112 ))
4113 .await;
4114
4115 match response {
4116 Ok(response) => Ok(Some(
4117 response
4118 .into_content()
4119 .deserialize_as_unchecked::<RoomPinnedEventsEventContent>()?
4120 .pinned,
4121 )),
4122 Err(http_error) => match http_error.as_client_api_error() {
4123 Some(error) if error.status_code == StatusCode::NOT_FOUND => Ok(None),
4124 _ => Err(http_error.into()),
4125 },
4126 }
4127 }
4128
4129 pub async fn subscribe_to_knock_requests(
4143 &self,
4144 ) -> Result<(impl Stream<Item = Vec<KnockRequest>> + use<>, JoinHandle<()>)> {
4145 let this = Arc::new(self.clone());
4146
4147 let room_member_events_observer =
4148 self.client.observe_room_events::<SyncRoomMemberEvent, (Client, Room)>(this.room_id());
4149
4150 let current_seen_ids = self.get_seen_knock_request_ids().await?;
4151 let mut seen_request_ids_stream = self
4152 .seen_knock_request_ids_map
4153 .subscribe()
4154 .await
4155 .map(|values| values.unwrap_or_default());
4156
4157 let mut room_info_stream = self.subscribe_info();
4158
4159 let clear_seen_ids_handle = spawn({
4162 let this = self.clone();
4163 async move {
4164 let mut member_updates_stream = this.room_member_updates_sender.subscribe();
4165 while member_updates_stream.recv().await.is_ok() {
4166 if let Err(err) = this.remove_outdated_seen_knock_requests_ids().await {
4168 warn!("Failed to remove seen knock requests: {err}")
4169 }
4170 }
4171 }
4172 });
4173
4174 let combined_stream = stream! {
4175 match this.get_current_join_requests(¤t_seen_ids).await {
4177 Ok(initial_requests) => yield initial_requests,
4178 Err(err) => warn!("Failed to get initial requests to join: {err}")
4179 }
4180
4181 let mut requests_stream = room_member_events_observer.subscribe();
4182 let mut seen_ids = current_seen_ids.clone();
4183
4184 loop {
4185 tokio::select! {
4188 Some((event, _)) = requests_stream.next() => {
4189 if let Some(event) = event.as_original() {
4190 let emit = if event.prev_content().is_some() {
4192 matches!(event.membership_change(),
4193 MembershipChange::Banned |
4194 MembershipChange::Knocked |
4195 MembershipChange::KnockAccepted |
4196 MembershipChange::KnockDenied |
4197 MembershipChange::KnockRetracted
4198 )
4199 } else {
4200 true
4203 };
4204
4205 if emit {
4206 match this.get_current_join_requests(&seen_ids).await {
4207 Ok(requests) => yield requests,
4208 Err(err) => {
4209 warn!("Failed to get updated knock requests on new member event: {err}")
4210 }
4211 }
4212 }
4213 }
4214 }
4215
4216 Some(new_seen_ids) = seen_request_ids_stream.next() => {
4217 seen_ids = new_seen_ids;
4219
4220 match this.get_current_join_requests(&seen_ids).await {
4223 Ok(requests) => yield requests,
4224 Err(err) => {
4225 warn!("Failed to get updated knock requests on seen ids changed: {err}")
4226 }
4227 }
4228 }
4229
4230 Some(room_info) = room_info_stream.next() => {
4231 if !room_info.are_members_synced() {
4234 match this.get_current_join_requests(&seen_ids).await {
4235 Ok(requests) => yield requests,
4236 Err(err) => {
4237 warn!("Failed to get updated knock requests on gappy (limited) sync: {err}")
4238 }
4239 }
4240 }
4241 }
4242 else => break,
4244 }
4245 }
4246 };
4247
4248 Ok((combined_stream, clear_seen_ids_handle))
4249 }
4250
4251 async fn get_current_join_requests(
4252 &self,
4253 seen_request_ids: &BTreeMap<OwnedEventId, OwnedUserId>,
4254 ) -> Result<Vec<KnockRequest>> {
4255 Ok(self
4256 .members(RoomMemberships::KNOCK)
4257 .await?
4258 .into_iter()
4259 .filter_map(|member| {
4260 let event_id = member.event().event_id()?;
4261 Some(KnockRequest::new(
4262 self,
4263 event_id,
4264 member.event().timestamp(),
4265 KnockRequestMemberInfo::from_member(&member),
4266 seen_request_ids.contains_key(event_id),
4267 ))
4268 })
4269 .collect())
4270 }
4271
4272 pub async fn effective_retention(&self) -> Result<Option<RoomRetentionEventContent>> {
4284 let config = match self.client.get_retention_configuration().await {
4285 Ok(config) => config,
4286 Err(e) if e.is_endpoint_not_implemented() => {
4287 debug!(
4288 room_id = ?self.room_id(),
4289 "Homeserver doesn't implement the MSC1763 retention configuration \
4290 endpoint; treating this room as having no effective retention policy"
4291 );
4292 return Ok(None);
4293 }
4294 Err(e) => return Err(e.into()),
4295 };
4296 Ok(self.effective_retention_with_server_config(&config))
4297 }
4298
4299 pub fn effective_retention_with_server_config(
4315 &self,
4316 config: &get_retention_configuration::unstable::Response,
4317 ) -> Option<RoomRetentionEventContent> {
4318 use ruma::api::client::retention::{
4319 RoomIdOrAllRooms, get_retention_configuration::unstable::LifetimeLimits,
4320 };
4321
4322 fn clamp(value: Option<Duration>, limits: &Option<LifetimeLimits>) -> Option<Duration> {
4323 let Some(limits) = limits else { return value };
4326 let Some(value) = value else {
4327 return limits.min();
4330 };
4331 let min = limits.min().unwrap_or(Duration::ZERO);
4333 let max = limits.max().unwrap_or(Duration::MAX);
4334 Some(value.clamp(min, max))
4335 }
4336
4337 if let Some(policy) =
4339 config.policies.get(&RoomIdOrAllRooms::RoomId(self.room_id().to_owned()))
4340 {
4341 return Some(policy.clone());
4342 }
4343
4344 let Some(room_policy) = self.retention() else {
4346 return config.policies.get(&RoomIdOrAllRooms::AllRooms).cloned();
4347 };
4348
4349 let max_lifetime = clamp(room_policy.max_lifetime(), &config.limits.max_lifetime);
4351 let min_lifetime = clamp(room_policy.min_lifetime(), &config.limits.min_lifetime);
4352
4353 let min_lifetime = min_lifetime.map(|min| {
4360 let Some(max) = max_lifetime else { return min };
4361 if min > max {
4362 warn!(
4363 room_id = ?self.room_id(),
4364 ?min, ?max,
4365 "Retention min_lifetime exceeds max_lifetime after clamping to server limits; \
4366 capping min_lifetime to max_lifetime"
4367 );
4368 max
4369 } else {
4370 min
4371 }
4372 });
4373
4374 match (max_lifetime, min_lifetime) {
4375 (Some(max), Some(min)) => RoomRetentionEventContent::from_range(min..=max),
4376 (Some(max), None) => RoomRetentionEventContent::new().at_most(max),
4377 (None, Some(min)) => RoomRetentionEventContent::new().at_least(min),
4378 (None, None) => Some(RoomRetentionEventContent::new()),
4379 }
4380 }
4381
4382 pub fn privacy_settings(&self) -> RoomPrivacySettings<'_> {
4384 RoomPrivacySettings::new(&self.inner, &self.client)
4385 }
4386
4387 pub async fn list_threads(&self, opts: ListThreadsOptions) -> Result<ThreadRoots> {
4395 let request = opts.into_request(self.room_id());
4396
4397 let response = self.client.send(request).await?;
4398
4399 let push_ctx = self.push_context().await?;
4400 let chunk = join_all(
4401 response.chunk.into_iter().map(|ev| self.try_decrypt_event(ev, push_ctx.as_ref())),
4402 )
4403 .await;
4404
4405 Ok(ThreadRoots { chunk, prev_batch_token: response.next_batch })
4406 }
4407
4408 pub async fn relations(
4422 &self,
4423 event_id: OwnedEventId,
4424 opts: RelationsOptions,
4425 ) -> Result<Relations> {
4426 opts.send(self, event_id).await
4427 }
4428
4429 #[instrument(skip(self), fields(room_id = %self.room_id()))]
4451 pub async fn subscribe_thread(
4452 &self,
4453 thread_root: OwnedEventId,
4454 automatic: Option<OwnedEventId>,
4455 ) -> Result<()> {
4456 let is_automatic = automatic.is_some();
4457
4458 match self
4459 .client
4460 .send(subscribe_thread::unstable::Request::new(
4461 self.room_id().to_owned(),
4462 thread_root.clone(),
4463 automatic,
4464 ))
4465 .await
4466 {
4467 Ok(_response) => {
4468 trace!("Server acknowledged the thread subscription; saving in db");
4469
4470 self.client
4472 .state_store()
4473 .upsert_thread_subscriptions(vec![(
4474 self.room_id(),
4475 &thread_root,
4476 StoredThreadSubscription {
4477 status: ThreadSubscriptionStatus::Subscribed {
4478 automatic: is_automatic,
4479 },
4480 bump_stamp: None,
4481 },
4482 )])
4483 .await?;
4484
4485 Ok(())
4486 }
4487
4488 Err(err) => {
4489 if let Some(ErrorKind::ConflictingUnsubscription) = err.client_api_error_kind() {
4490 trace!("Thread subscription skipped: {err}");
4495 Ok(())
4496 } else {
4497 Err(err.into())
4499 }
4500 }
4501 }
4502 }
4503
4504 pub async fn subscribe_thread_if_needed(
4510 &self,
4511 thread_root: &EventId,
4512 automatic: Option<OwnedEventId>,
4513 ) -> Result<()> {
4514 if let Some(prev_sub) = self.load_or_fetch_thread_subscription(thread_root).await? {
4515 if !prev_sub.automatic || automatic.is_some() {
4518 return Ok(());
4521 }
4522 }
4523 self.subscribe_thread(thread_root.to_owned(), automatic).await
4524 }
4525
4526 #[instrument(skip(self), fields(room_id = %self.room_id()))]
4538 pub async fn unsubscribe_thread(&self, thread_root: OwnedEventId) -> Result<()> {
4539 self.client
4540 .send(unsubscribe_thread::unstable::Request::new(
4541 self.room_id().to_owned(),
4542 thread_root.clone(),
4543 ))
4544 .await?;
4545
4546 trace!("Server acknowledged the thread subscription removal; removed it from db too");
4547
4548 self.client
4550 .state_store()
4551 .upsert_thread_subscriptions(vec![(
4552 self.room_id(),
4553 &thread_root,
4554 StoredThreadSubscription {
4555 status: ThreadSubscriptionStatus::Unsubscribed,
4556 bump_stamp: None,
4557 },
4558 )])
4559 .await?;
4560
4561 Ok(())
4562 }
4563
4564 #[instrument(skip(self), fields(room_id = %self.room_id()))]
4581 pub async fn fetch_thread_subscription(
4582 &self,
4583 thread_root: OwnedEventId,
4584 ) -> Result<Option<ThreadSubscription>> {
4585 let result = self
4586 .client
4587 .send(get_thread_subscription::unstable::Request::new(
4588 self.room_id().to_owned(),
4589 thread_root.clone(),
4590 ))
4591 .await;
4592
4593 let subscription = match result {
4594 Ok(response) => Some(ThreadSubscription { automatic: response.automatic }),
4595 Err(http_error) => match http_error.as_client_api_error() {
4596 Some(error) if error.status_code == StatusCode::NOT_FOUND => None,
4597 _ => return Err(http_error.into()),
4598 },
4599 };
4600
4601 if let Some(sub) = &subscription {
4603 self.client
4604 .state_store()
4605 .upsert_thread_subscriptions(vec![(
4606 self.room_id(),
4607 &thread_root,
4608 StoredThreadSubscription {
4609 status: ThreadSubscriptionStatus::Subscribed { automatic: sub.automatic },
4610 bump_stamp: None,
4611 },
4612 )])
4613 .await?;
4614 } else {
4615 self.client
4617 .state_store()
4618 .remove_thread_subscription(self.room_id(), &thread_root)
4619 .await?;
4620 }
4621
4622 Ok(subscription)
4623 }
4624
4625 pub async fn load_or_fetch_thread_subscription(
4632 &self,
4633 thread_root: &EventId,
4634 ) -> Result<Option<ThreadSubscription>> {
4635 if self.client.thread_subscription_catchup().is_outdated() {
4637 return self.fetch_thread_subscription(thread_root.to_owned()).await;
4638 }
4639
4640 Ok(self
4642 .client
4643 .state_store()
4644 .load_thread_subscription(self.room_id(), thread_root)
4645 .await
4646 .map(|maybe_sub| {
4647 maybe_sub.and_then(|stored| match stored.status {
4648 ThreadSubscriptionStatus::Unsubscribed => None,
4649 ThreadSubscriptionStatus::Subscribed { automatic } => {
4650 Some(ThreadSubscription { automatic })
4651 }
4652 })
4653 })?)
4654 }
4655
4656 pub async fn pin_event(&self, event_id: &EventId) -> Result<bool> {
4666 let mut pinned_event_ids = if let Some(event_ids) = self.pinned_event_ids() {
4667 event_ids
4668 } else {
4669 self.load_pinned_events().await?.unwrap_or_default()
4670 };
4671 let event_id = event_id.to_owned();
4672 if pinned_event_ids.contains(&event_id) {
4673 Ok(false)
4674 } else {
4675 pinned_event_ids.push(event_id);
4676 let content = RoomPinnedEventsEventContent::new(pinned_event_ids);
4677 self.send_state_event(content).await?;
4678 Ok(true)
4679 }
4680 }
4681
4682 pub async fn unpin_event(&self, event_id: &EventId) -> Result<bool> {
4692 let mut pinned_event_ids = if let Some(event_ids) = self.pinned_event_ids() {
4693 event_ids
4694 } else {
4695 self.load_pinned_events().await?.unwrap_or_default()
4696 };
4697 let event_id = event_id.to_owned();
4698 if let Some(idx) = pinned_event_ids.iter().position(|e| *e == *event_id) {
4699 pinned_event_ids.remove(idx);
4700 let content = RoomPinnedEventsEventContent::new(pinned_event_ids);
4701 self.send_state_event(content).await?;
4702 Ok(true)
4703 } else {
4704 Ok(false)
4705 }
4706 }
4707
4708 pub async fn compute_is_dm(&self) -> Result<bool> {
4711 Ok(self.inner.compute_is_dm(self.client.dm_room_definition()).await?)
4712 }
4713
4714 pub fn is_dm(&self) -> bool {
4719 let is_direct = self.direct_targets_length() == 1;
4721 match self.client.dm_room_definition() {
4722 DmRoomDefinition::MatrixSpec => {
4723 is_direct
4725 }
4726 DmRoomDefinition::TwoMembers => {
4727 let active_service_member_count =
4730 self.active_service_members_count().unwrap_or_else(|| {
4731 self.service_members().map(|members| members.len()).unwrap_or_default()
4734 as u64
4735 });
4736 let has_at_most_two_active_members =
4737 self.active_members_count().saturating_sub(active_service_member_count) <= 2;
4738 is_direct && has_at_most_two_active_members
4739 }
4740 }
4741 }
4742}
4743
4744#[cfg(feature = "e2e-encryption")]
4745impl RoomIdentityProvider for Room {
4746 fn is_member<'a>(&'a self, user_id: &'a UserId) -> BoxFuture<'a, bool> {
4747 Box::pin(async { self.get_member(user_id).await.unwrap_or(None).is_some() })
4748 }
4749
4750 fn member_identities(&self) -> BoxFuture<'_, Vec<UserIdentity>> {
4751 Box::pin(async {
4752 let members = self
4753 .members(RoomMemberships::JOIN | RoomMemberships::INVITE)
4754 .await
4755 .unwrap_or_else(|_| Default::default());
4756
4757 let mut ret: Vec<UserIdentity> = Vec::new();
4758 for member in members {
4759 if let Some(i) = self.user_identity(member.user_id()).await {
4760 ret.push(i);
4761 }
4762 }
4763 ret
4764 })
4765 }
4766
4767 fn user_identity<'a>(&'a self, user_id: &'a UserId) -> BoxFuture<'a, Option<UserIdentity>> {
4768 Box::pin(async {
4769 self.client
4770 .encryption()
4771 .get_user_identity(user_id)
4772 .await
4773 .unwrap_or(None)
4774 .map(|u| u.underlying_identity())
4775 })
4776 }
4777}
4778
4779#[derive(Clone, Debug)]
4782pub(crate) struct WeakRoom {
4783 client: WeakClient,
4784 room_id: OwnedRoomId,
4785}
4786
4787impl WeakRoom {
4788 pub fn new(client: WeakClient, room_id: OwnedRoomId) -> Self {
4790 Self { client, room_id }
4791 }
4792
4793 pub fn get(&self) -> Option<Room> {
4795 self.client.get().and_then(|client| client.get_room(&self.room_id))
4796 }
4797
4798 pub fn room_id(&self) -> &RoomId {
4800 &self.room_id
4801 }
4802}
4803
4804#[derive(Debug, Clone)]
4806pub struct Invite {
4807 pub invitee: RoomMember,
4809
4810 pub inviter_id: OwnedUserId,
4814
4815 pub inviter: Option<RoomMember>,
4819}
4820
4821#[derive(Error, Debug)]
4822enum InvitationError {
4823 #[error("No membership event found")]
4824 EventMissing,
4825}
4826
4827#[derive(Debug, Clone, Default)]
4829#[non_exhaustive]
4830pub struct Receipts {
4831 pub fully_read: Option<OwnedEventId>,
4833 pub public_read_receipt: Option<OwnedEventId>,
4835 pub private_read_receipt: Option<OwnedEventId>,
4837}
4838
4839impl Receipts {
4840 pub fn new() -> Self {
4842 Self::default()
4843 }
4844
4845 pub fn fully_read_marker(mut self, event_id: impl Into<Option<OwnedEventId>>) -> Self {
4854 self.fully_read = event_id.into();
4855 self
4856 }
4857
4858 pub fn public_read_receipt(mut self, event_id: impl Into<Option<OwnedEventId>>) -> Self {
4864 self.public_read_receipt = event_id.into();
4865 self
4866 }
4867
4868 pub fn private_read_receipt(mut self, event_id: impl Into<Option<OwnedEventId>>) -> Self {
4872 self.private_read_receipt = event_id.into();
4873 self
4874 }
4875
4876 pub fn is_empty(&self) -> bool {
4878 self.fully_read.is_none()
4879 && self.public_read_receipt.is_none()
4880 && self.private_read_receipt.is_none()
4881 }
4882}
4883
4884#[derive(Debug)]
4887pub enum ParentSpace {
4888 Reciprocal(Room),
4891 WithPowerlevel(Room),
4896 Illegitimate(Room),
4899 Unverifiable(OwnedRoomId),
4902}
4903
4904trait EventSource {
4905 fn get_event(
4906 &self,
4907 event_id: &EventId,
4908 ) -> impl Future<Output = Result<TimelineEvent, Error>> + SendOutsideWasm;
4909}
4910
4911impl EventSource for &Room {
4912 async fn get_event(&self, event_id: &EventId) -> Result<TimelineEvent, Error> {
4913 self.load_or_fetch_event(event_id, None).await
4914 }
4915}
4916
4917#[derive(Debug)]
4920pub struct RoomMemberWithSenderInfo {
4921 pub room_member: RoomMember,
4923 pub sender_info: Option<RoomMember>,
4926}
4927
4928#[cfg(all(test, not(target_family = "wasm")))]
4929mod tests {
4930 use std::{collections::BTreeMap, time::Duration};
4931
4932 use matrix_sdk_base::{ComposerDraft, DraftAttachment, store::ComposerDraftType};
4933 use matrix_sdk_test::{
4934 JoinedRoomBuilder, SyncResponseBuilder, async_test, event_factory::EventFactory,
4935 };
4936 use ruma::{
4937 RoomVersionId, event_id,
4938 events::{
4939 relation::RelationType,
4940 room::{member::MembershipState, retention::RoomRetentionEventContent},
4941 },
4942 owned_event_id, room_id, user_id,
4943 };
4944 use wiremock::{
4945 Mock, MockServer, ResponseTemplate,
4946 matchers::{header, method, path, path_regex},
4947 };
4948
4949 use crate::{
4950 Client, Room,
4951 config::RequestConfig,
4952 room::messages::{IncludeRelations, ListThreadsOptions, RelationsOptions},
4953 test_utils::{
4954 client::mock_matrix_session,
4955 logged_in_client,
4956 mocks::{MatrixMockServer, RoomRelationsResponseTemplate},
4957 },
4958 };
4959
4960 #[cfg(all(feature = "sqlite", feature = "e2e-encryption"))]
4961 #[async_test]
4962 async fn test_cache_invalidation_while_encrypt() {
4963 use matrix_sdk_base::store::RoomLoadSettings;
4964 use matrix_sdk_test::{DEFAULT_TEST_ROOM_ID, message_like_event_content};
4965
4966 let tmp_dir = tempfile::tempdir().unwrap();
4967 let sqlite_path = tmp_dir.path().join("cache_invalidation_while_encrypt.db");
4968 let session = mock_matrix_session();
4969
4970 let client = Client::builder()
4971 .homeserver_url("http://localhost:1234")
4972 .request_config(RequestConfig::new().disable_retry())
4973 .sqlite_store(&sqlite_path, None)
4974 .build()
4975 .await
4976 .unwrap();
4977 client
4978 .matrix_auth()
4979 .restore_session(session.clone(), RoomLoadSettings::default())
4980 .await
4981 .unwrap();
4982
4983 client.encryption().enable_cross_process_store_lock("client1".to_owned()).await.unwrap();
4984
4985 let server = MockServer::start().await;
4987 {
4988 Mock::given(method("GET"))
4989 .and(path_regex(r"^/_matrix/client/r0/rooms/.*/state/m.*room.*encryption.?"))
4990 .and(header("authorization", "Bearer 1234"))
4991 .respond_with(
4992 ResponseTemplate::new(200)
4993 .set_body_json(EventFactory::new().room_encryption().into_content()),
4994 )
4995 .mount(&server)
4996 .await;
4997 let f = EventFactory::new().sender(user_id!("@example:localhost"));
4998 let response = SyncResponseBuilder::default()
4999 .add_joined_room(
5000 JoinedRoomBuilder::default()
5001 .add_state_event(
5002 f.member(user_id!("@example:localhost")).display_name("example"),
5003 )
5004 .add_state_event(f.default_power_levels())
5005 .add_state_event(f.room_encryption()),
5006 )
5007 .build_sync_response();
5008 client.base_client().receive_sync_response(response).await.unwrap();
5009 }
5010
5011 let room = client.get_room(&DEFAULT_TEST_ROOM_ID).expect("Room should exist");
5012
5013 room.preshare_room_key().await.unwrap();
5015
5016 {
5019 let client = Client::builder()
5020 .homeserver_url("http://localhost:1234")
5021 .request_config(RequestConfig::new().disable_retry())
5022 .sqlite_store(&sqlite_path, None)
5023 .build()
5024 .await
5025 .unwrap();
5026 client
5027 .matrix_auth()
5028 .restore_session(session.clone(), RoomLoadSettings::default())
5029 .await
5030 .unwrap();
5031 client
5032 .encryption()
5033 .enable_cross_process_store_lock("client2".to_owned())
5034 .await
5035 .unwrap();
5036
5037 let guard = client.encryption().spin_lock_store(None).await.unwrap();
5038 assert!(guard.is_some());
5039 }
5040
5041 let guard = client.encryption().spin_lock_store(None).await.unwrap();
5043 assert!(guard.is_some());
5044
5045 let olm = client.olm_machine().await;
5047 let olm = olm.as_ref().expect("Olm machine wasn't started");
5048
5049 let _encrypted_content = olm
5052 .encrypt_room_event_raw(room.room_id(), "test-event", &message_like_event_content!({}))
5053 .await
5054 .unwrap();
5055 }
5056
5057 #[async_test]
5058 async fn test_composer_draft() {
5059 use matrix_sdk_test::DEFAULT_TEST_ROOM_ID;
5060
5061 let client = logged_in_client(None).await;
5062
5063 let response = SyncResponseBuilder::default()
5064 .add_joined_room(JoinedRoomBuilder::default())
5065 .build_sync_response();
5066 client.base_client().receive_sync_response(response).await.unwrap();
5067 let room = client.get_room(&DEFAULT_TEST_ROOM_ID).expect("Room should exist");
5068
5069 assert_eq!(room.load_composer_draft(None).await.unwrap(), None);
5070
5071 let draft = ComposerDraft {
5074 plain_text: "Hello, world!".to_owned(),
5075 html_text: Some("<strong>Hello</strong>, world!".to_owned()),
5076 draft_type: ComposerDraftType::NewMessage,
5077 attachments: vec![DraftAttachment {
5078 filename: "cat.txt".to_owned(),
5079 content: matrix_sdk_base::DraftAttachmentContent::File {
5080 data: b"meow".to_vec(),
5081 mimetype: Some("text/plain".to_owned()),
5082 size: Some(5),
5083 },
5084 }],
5085 };
5086
5087 room.save_composer_draft(draft.clone(), None).await.unwrap();
5088
5089 let thread_root = owned_event_id!("$thread_root:b.c");
5090 let thread_draft = ComposerDraft {
5091 plain_text: "Hello, thread!".to_owned(),
5092 html_text: Some("<strong>Hello</strong>, thread!".to_owned()),
5093 draft_type: ComposerDraftType::NewMessage,
5094 attachments: vec![DraftAttachment {
5095 filename: "dog.txt".to_owned(),
5096 content: matrix_sdk_base::DraftAttachmentContent::File {
5097 data: b"wuv".to_vec(),
5098 mimetype: Some("text/plain".to_owned()),
5099 size: Some(4),
5100 },
5101 }],
5102 };
5103
5104 room.save_composer_draft(thread_draft.clone(), Some(&thread_root)).await.unwrap();
5105
5106 assert_eq!(room.load_composer_draft(None).await.unwrap(), Some(draft));
5108
5109 assert_eq!(
5111 room.load_composer_draft(Some(&thread_root)).await.unwrap(),
5112 Some(thread_draft.clone())
5113 );
5114
5115 room.clear_composer_draft(None).await.unwrap();
5117 assert_eq!(room.load_composer_draft(None).await.unwrap(), None);
5118
5119 assert_eq!(room.load_composer_draft(Some(&thread_root)).await.unwrap(), Some(thread_draft));
5121
5122 room.clear_composer_draft(Some(&thread_root)).await.unwrap();
5124 assert_eq!(room.load_composer_draft(Some(&thread_root)).await.unwrap(), None);
5125 }
5126
5127 #[async_test]
5128 async fn test_mark_join_requests_as_seen() {
5129 let server = MatrixMockServer::new().await;
5130 let client = server.client_builder().build().await;
5131 let event_id = event_id!("$a:b.c");
5132 let room_id = room_id!("!a:b.c");
5133 let user_id = user_id!("@alice:b.c");
5134
5135 let f = EventFactory::new().room(room_id);
5136 let joined_room_builder = JoinedRoomBuilder::new(room_id).add_state_bulk(vec![
5137 f.member(user_id).membership(MembershipState::Knock).event_id(event_id).into(),
5138 ]);
5139 let room = server.sync_room(&client, joined_room_builder).await;
5140
5141 let seen_ids =
5143 room.get_seen_knock_request_ids().await.expect("Couldn't load seen join request ids");
5144 assert!(seen_ids.is_empty());
5145
5146 room.mark_knock_requests_as_seen(&[user_id.to_owned()])
5148 .await
5149 .expect("Couldn't mark join request as seen");
5150
5151 let seen_ids =
5153 room.get_seen_knock_request_ids().await.expect("Couldn't load seen join request ids");
5154 assert_eq!(seen_ids.len(), 1);
5155 assert_eq!(
5156 seen_ids.into_iter().next().expect("No next value"),
5157 (event_id.to_owned(), user_id.to_owned())
5158 )
5159 }
5160
5161 #[async_test]
5162 async fn test_own_room_membership_with_no_own_member_event() {
5163 let server = MatrixMockServer::new().await;
5164 let client = server.client_builder().build().await;
5165 let room_id = room_id!("!a:b.c");
5166
5167 let room = server.sync_joined_room(&client, room_id).await;
5168
5169 let error = room.member_with_sender_info(client.user_id().unwrap()).await.err();
5172 assert!(error.is_some());
5173 }
5174
5175 #[async_test]
5176 async fn test_own_room_membership_with_own_member_event_but_unknown_sender() {
5177 let server = MatrixMockServer::new().await;
5178 let client = server.client_builder().build().await;
5179 let room_id = room_id!("!a:b.c");
5180 let user_id = user_id!("@example:localhost");
5181
5182 let f = EventFactory::new().room(room_id).sender(user_id!("@alice:b.c"));
5183 let joined_room_builder =
5184 JoinedRoomBuilder::new(room_id).add_state_bulk(vec![f.member(user_id).into()]);
5185 let room = server.sync_room(&client, joined_room_builder).await;
5186
5187 let ret = room
5189 .member_with_sender_info(client.user_id().unwrap())
5190 .await
5191 .expect("Room member info should be available");
5192
5193 assert_eq!(ret.room_member.event().user_id(), user_id);
5195
5196 assert!(ret.sender_info.is_none());
5198 }
5199
5200 #[async_test]
5201 async fn test_own_room_membership_with_own_member_event_and_own_sender() {
5202 let server = MatrixMockServer::new().await;
5203 let client = server.client_builder().build().await;
5204 let room_id = room_id!("!a:b.c");
5205 let user_id = user_id!("@example:localhost");
5206
5207 let f = EventFactory::new().room(room_id).sender(user_id);
5208 let joined_room_builder =
5209 JoinedRoomBuilder::new(room_id).add_state_bulk(vec![f.member(user_id).into()]);
5210 let room = server.sync_room(&client, joined_room_builder).await;
5211
5212 let ret = room
5214 .member_with_sender_info(client.user_id().unwrap())
5215 .await
5216 .expect("Room member info should be available");
5217
5218 assert_eq!(ret.room_member.event().user_id(), user_id);
5220
5221 assert!(ret.sender_info.is_some());
5223 assert_eq!(ret.sender_info.unwrap().event().user_id(), user_id);
5224 }
5225
5226 #[async_test]
5227 async fn test_own_room_membership_with_own_member_event_and_known_sender() {
5228 let server = MatrixMockServer::new().await;
5229 let client = server.client_builder().build().await;
5230 let room_id = room_id!("!a:b.c");
5231 let user_id = user_id!("@example:localhost");
5232 let sender_id = user_id!("@alice:b.c");
5233
5234 let f = EventFactory::new().room(room_id).sender(sender_id);
5235 let joined_room_builder = JoinedRoomBuilder::new(room_id).add_state_bulk(vec![
5236 f.member(user_id).into(),
5237 f.member(sender_id).into(),
5239 ]);
5240 let room = server.sync_room(&client, joined_room_builder).await;
5241
5242 let ret = room
5244 .member_with_sender_info(client.user_id().unwrap())
5245 .await
5246 .expect("Room member info should be available");
5247
5248 assert_eq!(ret.room_member.event().user_id(), user_id);
5250
5251 assert!(ret.sender_info.is_some());
5253 assert_eq!(ret.sender_info.unwrap().event().user_id(), sender_id);
5254 }
5255
5256 #[async_test]
5257 async fn test_own_room_membership_with_own_member_event_and_unknown_but_available_sender() {
5258 let server = MatrixMockServer::new().await;
5259 let client = server.client_builder().build().await;
5260 let room_id = room_id!("!a:b.c");
5261 let user_id = user_id!("@example:localhost");
5262 let sender_id = user_id!("@alice:b.c");
5263
5264 let f = EventFactory::new().room(room_id).sender(sender_id);
5265 let joined_room_builder =
5266 JoinedRoomBuilder::new(room_id).add_state_bulk(vec![f.member(user_id).into()]);
5267 let room = server.sync_room(&client, joined_room_builder).await;
5268
5269 server
5271 .mock_get_members()
5272 .ok(vec![f.member(sender_id).into_raw()])
5273 .mock_once()
5274 .mount()
5275 .await;
5276
5277 let ret = room
5279 .member_with_sender_info(client.user_id().unwrap())
5280 .await
5281 .expect("Room member info should be available");
5282
5283 assert_eq!(ret.room_member.event().user_id(), user_id);
5285
5286 assert!(ret.sender_info.is_some());
5288 assert_eq!(ret.sender_info.unwrap().event().user_id(), sender_id);
5289 }
5290
5291 #[async_test]
5292 async fn test_list_threads() {
5293 let server = MatrixMockServer::new().await;
5294 let client = server.client_builder().build().await;
5295
5296 let room_id = room_id!("!a:b.c");
5297 let sender_id = user_id!("@alice:b.c");
5298 let f = EventFactory::new().room(room_id).sender(sender_id);
5299
5300 let eid1 = event_id!("$1");
5301 let eid2 = event_id!("$2");
5302 let batch1 = vec![f.text_msg("Thread root 1").event_id(eid1).into_raw()];
5303 let batch2 = vec![f.text_msg("Thread root 2").event_id(eid2).into_raw()];
5304
5305 server
5306 .mock_room_threads()
5307 .ok(batch1.clone(), Some("prev_batch".to_owned()))
5308 .mock_once()
5309 .mount()
5310 .await;
5311 server
5312 .mock_room_threads()
5313 .match_from("prev_batch")
5314 .ok(batch2, None)
5315 .mock_once()
5316 .mount()
5317 .await;
5318
5319 let room = server.sync_joined_room(&client, room_id).await;
5320 let result =
5321 room.list_threads(ListThreadsOptions::default()).await.expect("Failed to list threads");
5322 assert_eq!(result.chunk.len(), 1);
5323 assert_eq!(result.chunk[0].event_id().unwrap(), eid1);
5324 assert!(result.prev_batch_token.is_some());
5325
5326 let opts = ListThreadsOptions { from: result.prev_batch_token, ..Default::default() };
5327 let result = room.list_threads(opts).await.expect("Failed to list threads");
5328 assert_eq!(result.chunk.len(), 1);
5329 assert_eq!(result.chunk[0].event_id().unwrap(), eid2);
5330 assert!(result.prev_batch_token.is_none());
5331 }
5332
5333 #[async_test]
5334 async fn test_relations() {
5335 let server = MatrixMockServer::new().await;
5336 let client = server.client_builder().build().await;
5337
5338 let room_id = room_id!("!a:b.c");
5339 let sender_id = user_id!("@alice:b.c");
5340 let f = EventFactory::new().room(room_id).sender(sender_id);
5341
5342 let target_event_id = owned_event_id!("$target");
5343 let eid1 = event_id!("$1");
5344 let eid2 = event_id!("$2");
5345 let batch1 = vec![f.text_msg("Related event 1").event_id(eid1).into_raw()];
5346 let batch2 = vec![f.text_msg("Related event 2").event_id(eid2).into_raw()];
5347
5348 server
5349 .mock_room_relations()
5350 .match_target_event(target_event_id.clone())
5351 .ok(RoomRelationsResponseTemplate::default().events(batch1).next_batch("next_batch"))
5352 .mock_once()
5353 .mount()
5354 .await;
5355
5356 server
5357 .mock_room_relations()
5358 .match_target_event(target_event_id.clone())
5359 .match_from("next_batch")
5360 .ok(RoomRelationsResponseTemplate::default().events(batch2))
5361 .mock_once()
5362 .mount()
5363 .await;
5364
5365 let room = server.sync_joined_room(&client, room_id).await;
5366
5367 let mut opts = RelationsOptions {
5369 include_relations: IncludeRelations::AllRelations,
5370 ..Default::default()
5371 };
5372 let result = room
5373 .relations(target_event_id.clone(), opts.clone())
5374 .await
5375 .expect("Failed to list relations the first time");
5376 assert_eq!(result.chunk.len(), 1);
5377 assert_eq!(result.chunk[0].event_id().unwrap(), eid1);
5378 assert!(result.prev_batch_token.is_none());
5379 assert!(result.next_batch_token.is_some());
5380 assert!(result.recursion_depth.is_none());
5381
5382 opts.from = result.next_batch_token;
5383 let result = room
5384 .relations(target_event_id, opts)
5385 .await
5386 .expect("Failed to list relations the second time");
5387 assert_eq!(result.chunk.len(), 1);
5388 assert_eq!(result.chunk[0].event_id().unwrap(), eid2);
5389 assert!(result.prev_batch_token.is_none());
5390 assert!(result.next_batch_token.is_none());
5391 assert!(result.recursion_depth.is_none());
5392 }
5393
5394 #[async_test]
5395 async fn test_relations_with_reltype() {
5396 let server = MatrixMockServer::new().await;
5397 let client = server.client_builder().build().await;
5398
5399 let room_id = room_id!("!a:b.c");
5400 let sender_id = user_id!("@alice:b.c");
5401 let f = EventFactory::new().room(room_id).sender(sender_id);
5402
5403 let target_event_id = owned_event_id!("$target");
5404 let eid1 = event_id!("$1");
5405 let eid2 = event_id!("$2");
5406 let batch1 = vec![f.text_msg("In-thread event 1").event_id(eid1).into_raw()];
5407 let batch2 = vec![f.text_msg("In-thread event 2").event_id(eid2).into_raw()];
5408
5409 server
5410 .mock_room_relations()
5411 .match_target_event(target_event_id.clone())
5412 .match_subrequest(IncludeRelations::RelationsOfType(RelationType::Thread))
5413 .ok(RoomRelationsResponseTemplate::default().events(batch1).next_batch("next_batch"))
5414 .mock_once()
5415 .mount()
5416 .await;
5417
5418 server
5419 .mock_room_relations()
5420 .match_target_event(target_event_id.clone())
5421 .match_from("next_batch")
5422 .match_subrequest(IncludeRelations::RelationsOfType(RelationType::Thread))
5423 .ok(RoomRelationsResponseTemplate::default().events(batch2))
5424 .mock_once()
5425 .mount()
5426 .await;
5427
5428 let room = server.sync_joined_room(&client, room_id).await;
5429
5430 let mut opts = RelationsOptions {
5432 include_relations: IncludeRelations::RelationsOfType(RelationType::Thread),
5433 ..Default::default()
5434 };
5435 let result = room
5436 .relations(target_event_id.clone(), opts.clone())
5437 .await
5438 .expect("Failed to list relations the first time");
5439 assert_eq!(result.chunk.len(), 1);
5440 assert_eq!(result.chunk[0].event_id().unwrap(), eid1);
5441 assert!(result.prev_batch_token.is_none());
5442 assert!(result.next_batch_token.is_some());
5443 assert!(result.recursion_depth.is_none());
5444
5445 opts.from = result.next_batch_token;
5446 let result = room
5447 .relations(target_event_id, opts)
5448 .await
5449 .expect("Failed to list relations the second time");
5450 assert_eq!(result.chunk.len(), 1);
5451 assert_eq!(result.chunk[0].event_id().unwrap(), eid2);
5452 assert!(result.prev_batch_token.is_none());
5453 assert!(result.next_batch_token.is_none());
5454 assert!(result.recursion_depth.is_none());
5455 }
5456
5457 #[async_test]
5458 async fn test_power_levels_computation() {
5459 let server = MatrixMockServer::new().await;
5460 let client = server.client_builder().build().await;
5461
5462 let room_id = room_id!("!a:b.c");
5463 let sender_id = client.user_id().expect("No session id");
5464 let f = EventFactory::new().room(room_id).sender(sender_id);
5465 let mut user_map = BTreeMap::from([(sender_id.into(), 50.into())]);
5466
5467 let room_create_event = f.create(sender_id, RoomVersionId::V1).state_key("").into();
5469 let power_levels_event = f.power_levels(&mut user_map).state_key("").into();
5470 let room_member_event = f.member(sender_id).into();
5471
5472 let room = server
5474 .sync_room(&client, JoinedRoomBuilder::new(room_id).add_state_bulk([room_member_event]))
5475 .await;
5476 let ctx = room
5477 .push_condition_room_ctx()
5478 .await
5479 .expect("Failed to get push condition context")
5480 .expect("Could not get push condition context");
5481
5482 assert!(ctx.power_levels.is_none());
5484
5485 let room = server
5487 .sync_room(&client, JoinedRoomBuilder::new(room_id).add_state_bulk([room_create_event]))
5488 .await;
5489 let ctx = room
5490 .push_condition_room_ctx()
5491 .await
5492 .expect("Failed to get push condition context")
5493 .expect("Could not get push condition context");
5494
5495 assert!(ctx.power_levels.is_none());
5497
5498 let room = server
5500 .sync_room(
5501 &client,
5502 JoinedRoomBuilder::new(room_id).add_state_bulk([power_levels_event]),
5503 )
5504 .await;
5505 let ctx = room
5506 .push_condition_room_ctx()
5507 .await
5508 .expect("Failed to get push condition context")
5509 .expect("Could not get push condition context");
5510
5511 assert!(ctx.power_levels.is_some());
5513 }
5514
5515 const ONE_DAY: Duration = Duration::from_secs(86_400);
5518 const ONE_WEEK: Duration = Duration::from_secs(86_400 * 7);
5519 const ONE_HOUR: Duration = Duration::from_secs(3_600);
5520
5521 fn retention_room_id() -> &'static ruma::RoomId {
5522 room_id!("!a:b.c")
5523 }
5524
5525 async fn mock_retention_config(
5526 server: &MatrixMockServer,
5527 body: serde_json::Value,
5528 ) -> wiremock::MockGuard {
5529 Mock::given(method("GET"))
5530 .and(path("/_matrix/client/unstable/org.matrix.msc1763/retention/configuration"))
5531 .respond_with(ResponseTemplate::new(200).set_body_json(body))
5532 .mount_as_scoped(server.server())
5533 .await
5534 }
5535
5536 async fn room_with_retention(
5537 server: &MatrixMockServer,
5538 client: &Client,
5539 content: RoomRetentionEventContent,
5540 ) -> Room {
5541 server
5542 .sync_room(
5543 client,
5544 JoinedRoomBuilder::new(retention_room_id()).add_state_event(
5545 EventFactory::new().sender(user_id!("@alice:b.c")).event(content).state_key(""),
5546 ),
5547 )
5548 .await
5549 }
5550
5551 #[async_test]
5552 async fn test_effective_retention_no_policy_anywhere() {
5553 let server = MatrixMockServer::new().await;
5554 let client = server.client_builder().build().await;
5555 let room = server.sync_joined_room(&client, retention_room_id()).await;
5556
5557 let _mock = mock_retention_config(
5558 &server,
5559 serde_json::json!({
5560 "policies": {},
5561 "limits": {},
5562 }),
5563 )
5564 .await;
5565
5566 let result = room.effective_retention().await.unwrap();
5567 assert!(result.is_none());
5568 }
5569
5570 #[async_test]
5571 async fn test_effective_retention_endpoint_not_implemented() {
5572 let server = MatrixMockServer::new().await;
5577 let client = server.client_builder().build().await;
5578 let room = room_with_retention(
5579 &server,
5580 &client,
5581 RoomRetentionEventContent::new().at_most(ONE_DAY).unwrap(),
5582 )
5583 .await;
5584
5585 Mock::given(method("GET"))
5586 .and(path("/_matrix/client/unstable/org.matrix.msc1763/retention/configuration"))
5587 .respond_with(ResponseTemplate::new(404).set_body_json(serde_json::json!({
5588 "errcode": "M_UNRECOGNIZED",
5589 "error": "Unrecognized request",
5590 })))
5591 .mount(server.server())
5592 .await;
5593
5594 let result = room.effective_retention().await.unwrap();
5595 assert!(result.is_none());
5596 }
5597
5598 #[async_test]
5599 async fn test_effective_retention_server_default_only() {
5600 let server = MatrixMockServer::new().await;
5601 let client = server.client_builder().build().await;
5602 let room = server.sync_joined_room(&client, retention_room_id()).await;
5603
5604 let _mock = mock_retention_config(
5605 &server,
5606 serde_json::json!({
5607 "policies": {
5608 "*": { "max_lifetime": ONE_WEEK.as_millis() as u64 }
5609 },
5610 "limits": {},
5611 }),
5612 )
5613 .await;
5614
5615 let result = room.effective_retention().await.unwrap().unwrap();
5616 assert_eq!(result.max_lifetime(), Some(ONE_WEEK));
5617 assert!(result.min_lifetime().is_none());
5618 }
5619
5620 #[async_test]
5621 async fn test_effective_retention_server_room_override() {
5622 let server = MatrixMockServer::new().await;
5624 let client = server.client_builder().build().await;
5625 let room = room_with_retention(
5626 &server,
5627 &client,
5628 RoomRetentionEventContent::new().at_most(ONE_DAY).unwrap(),
5629 )
5630 .await;
5631
5632 let _mock = mock_retention_config(
5633 &server,
5634 serde_json::json!({
5635 "policies": {
5636 "*": { "max_lifetime": ONE_WEEK.as_millis() as u64 },
5637 "!a:b.c": { "max_lifetime": ONE_HOUR.as_millis() as u64 }
5638 },
5639 "limits": {},
5640 }),
5641 )
5642 .await;
5643
5644 let result = room.effective_retention().await.unwrap().unwrap();
5645 assert_eq!(result.max_lifetime(), Some(ONE_HOUR));
5646 }
5647
5648 #[async_test]
5649 async fn test_effective_retention_room_policy_with_limits() {
5650 struct TestCase {
5651 description: &'static str,
5652 room_max: Option<Duration>,
5653 room_min: Option<Duration>,
5654 limits: serde_json::Value,
5655 expected_max: Option<Duration>,
5656 expected_min: Option<Duration>,
5657 }
5658
5659 let test_cases = vec![
5660 TestCase {
5661 description: "room sets `max_lifetime` only, no server limits configured",
5662 room_max: Some(ONE_DAY),
5663 room_min: None,
5664 limits: serde_json::json!({}),
5665 expected_max: Some(ONE_DAY),
5666 expected_min: None,
5667 },
5668 TestCase {
5669 description: "room sets `min_lifetime` only, no server limits configured",
5670 room_max: None,
5671 room_min: Some(ONE_HOUR),
5672 limits: serde_json::json!({}),
5673 expected_max: None,
5674 expected_min: Some(ONE_HOUR),
5675 },
5676 TestCase {
5677 description: "room sets `max_lifetime` and `min_lifetime`, no server limits configured",
5678 room_max: Some(ONE_DAY),
5679 room_min: Some(ONE_HOUR),
5680 limits: serde_json::json!({}),
5681 expected_max: Some(ONE_DAY),
5682 expected_min: Some(ONE_HOUR),
5683 },
5684 TestCase {
5685 description: "room `max_lifetime` above server `max_lifetime.max` → outcome uses server `max_lifetime.max`",
5686 room_max: Some(ONE_WEEK),
5687 room_min: None,
5688 limits: serde_json::json!({
5689 "max_lifetime": { "max": ONE_DAY.as_millis() as u64 }
5690 }),
5691 expected_max: Some(ONE_DAY),
5692 expected_min: None,
5693 },
5694 TestCase {
5695 description: "room `max_lifetime` below server `max_lifetime.min` → outcome uses server `max_lifetime.min`",
5696 room_max: Some(ONE_HOUR),
5697 room_min: None,
5698 limits: serde_json::json!({
5699 "max_lifetime": { "min": ONE_DAY.as_millis() as u64 }
5700 }),
5701 expected_max: Some(ONE_DAY),
5702 expected_min: None,
5703 },
5704 TestCase {
5705 description: "room `min_lifetime` above server `min_lifetime.max` → outcome uses server `min_lifetime.max`",
5706 room_max: None,
5707 room_min: Some(ONE_WEEK),
5708 limits: serde_json::json!({
5709 "min_lifetime": { "max": ONE_DAY.as_millis() as u64 }
5710 }),
5711 expected_max: None,
5712 expected_min: Some(ONE_DAY),
5713 },
5714 TestCase {
5715 description: "room `min_lifetime` below server `min_lifetime.min` → outcome uses server `min_lifetime.min`",
5716 room_max: None,
5717 room_min: Some(ONE_HOUR),
5718 limits: serde_json::json!({
5719 "min_lifetime": { "min": ONE_DAY.as_millis() as u64 }
5720 }),
5721 expected_max: None,
5722 expected_min: Some(ONE_DAY),
5723 },
5724 TestCase {
5725 description: "room `max_lifetime` and `min_lifetime` both within their respective server limits → unchanged",
5726 room_max: Some(ONE_DAY),
5727 room_min: Some(ONE_HOUR),
5728 limits: serde_json::json!({
5729 "max_lifetime": {
5730 "min": ONE_HOUR.as_millis() as u64,
5731 "max": ONE_WEEK.as_millis() as u64,
5732 },
5733 "min_lifetime": {
5734 "min": Duration::from_secs(60).as_millis() as u64,
5735 "max": ONE_DAY.as_millis() as u64,
5736 },
5737 }),
5738 expected_max: Some(ONE_DAY),
5739 expected_min: Some(ONE_HOUR),
5740 },
5741 TestCase {
5742 description: "room `max_lifetime` omitted, server `max_lifetime.min` is used",
5743 room_max: None,
5744 room_min: None,
5745 limits: serde_json::json!({
5746 "max_lifetime": { "min": ONE_DAY.as_millis() as u64 }
5747 }),
5748 expected_max: Some(ONE_DAY),
5749 expected_min: None,
5750 },
5751 TestCase {
5752 description: "room `min_lifetime` omitted, server `min_lifetime.min` is used",
5753 room_max: None,
5754 room_min: None,
5755 limits: serde_json::json!({
5756 "min_lifetime": { "min": ONE_HOUR.as_millis() as u64 }
5757 }),
5758 expected_max: None,
5759 expected_min: Some(ONE_HOUR),
5760 },
5761 TestCase {
5762 description: "independently-clamped `max_lifetime`/`min_lifetime` cross → `min_lifetime` capped down to `max_lifetime`",
5763 room_max: Some(ONE_DAY),
5764 room_min: Some(ONE_HOUR),
5765 limits: serde_json::json!({
5766 "max_lifetime": { "max": ONE_HOUR.as_millis() as u64 },
5767 "min_lifetime": { "min": ONE_WEEK.as_millis() as u64 },
5768 }),
5769 expected_max: Some(ONE_HOUR),
5770 expected_min: Some(ONE_HOUR),
5771 },
5772 TestCase {
5773 description: "room `max_lifetime` omitted, server sets `max_lifetime.max` but no `.min` → stays unbounded (documents MSC1763 gap)",
5774 room_max: None,
5775 room_min: None,
5776 limits: serde_json::json!({
5777 "max_lifetime": { "max": ONE_WEEK.as_millis() as u64 }
5778 }),
5779 expected_max: None,
5780 expected_min: None,
5781 },
5782 ];
5783
5784 for case in test_cases {
5785 let server = MatrixMockServer::new().await;
5786 let client = server.client_builder().build().await;
5787
5788 let mut content = RoomRetentionEventContent::new();
5789 if let Some(max) = case.room_max {
5790 content = content.at_most(max).unwrap();
5791 }
5792 if let Some(min) = case.room_min {
5793 content = content.at_least(min).unwrap();
5794 }
5795
5796 let room = room_with_retention(&server, &client, content).await;
5797
5798 let _mock = mock_retention_config(
5799 &server,
5800 serde_json::json!({
5801 "policies": {},
5802 "limits": case.limits,
5803 }),
5804 )
5805 .await;
5806
5807 let result = room
5808 .effective_retention()
5809 .await
5810 .unwrap_or_else(|e| panic!("{}: effective_retention failed: {e}", case.description))
5811 .unwrap_or_else(|| panic!("{}: expected Some, got None", case.description));
5812
5813 assert_eq!(result.max_lifetime(), case.expected_max, "{}", case.description);
5814 assert_eq!(result.min_lifetime(), case.expected_min, "{}", case.description);
5815 }
5816 }
5817}