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::{
160 SendAttachment, SendMessageLikeEvent, SendRawMessageLikeEvent, SendRawStateEvent,
161};
162pub use self::{
163 member::{RoomMember, RoomMemberRole},
164 messages::{
165 EventWithContextResponse, IncludeRelations, ListThreadsOptions, Messages, MessagesOptions,
166 Relations, RelationsOptions, ThreadRoots,
167 },
168};
169#[cfg(feature = "e2e-encryption")]
170use crate::encryption::backups::BackupState;
171#[cfg(doc)]
172use crate::event_cache::EventCache;
173#[cfg(feature = "experimental-encrypted-state-events")]
174use crate::room::futures::SendStateEvent;
175use crate::{
176 BaseRoom, Client, Error, HttpResult, Result, RoomState, TransmissionProgress,
177 attachment::{AttachmentConfig, AttachmentInfo},
178 client::WeakClient,
179 config::RequestConfig,
180 error::{BeaconError, WrongRoomState},
181 event_handler::{EventHandler, EventHandlerDropGuard, EventHandlerHandle, SyncEvent},
182 live_locations_observer::LiveLocationsObserver,
183 media::{MediaFormat, MediaRequestParameters},
184 notification_settings::{IsEncrypted, IsOneToOne, RoomNotificationMode},
185 room::{
186 knock_requests::{KnockRequest, KnockRequestMemberInfo},
187 power_levels::{RoomPowerLevelChanges, RoomPowerLevelsExt},
188 privacy_settings::RoomPrivacySettings,
189 },
190 sync::{RoomUpdate, State},
191 utils::{IntoRawMessageLikeEventContent, IntoRawStateEventContent},
192};
193
194pub mod edit;
195pub mod futures;
196pub mod identity_status_changes;
197pub mod knock_requests;
199mod member;
200mod messages;
201pub mod power_levels;
202pub mod reply;
203
204pub mod calls;
205
206pub mod privacy_settings;
208
209#[cfg(feature = "e2e-encryption")]
210pub(crate) mod shared_room_history;
211
212#[derive(Debug, Clone)]
215pub struct Room {
216 inner: BaseRoom,
217 pub(crate) client: Client,
218}
219
220impl Deref for Room {
221 type Target = BaseRoom;
222
223 fn deref(&self) -> &Self::Target {
224 &self.inner
225 }
226}
227
228const TYPING_NOTICE_TIMEOUT: Duration = Duration::from_secs(4);
229const TYPING_NOTICE_RESEND_TIMEOUT: Duration = Duration::from_secs(3);
230
231#[derive(Debug, Clone, Copy, PartialEq, Eq)]
233pub struct ThreadSubscription {
234 pub automatic: bool,
237}
238
239#[derive(Debug)]
241pub struct PushContext {
242 push_condition_room_ctx: PushConditionRoomCtx,
244
245 push_rules: Ruleset,
248}
249
250impl PushContext {
251 pub fn new(push_condition_room_ctx: PushConditionRoomCtx, push_rules: Ruleset) -> Self {
253 Self { push_condition_room_ctx, push_rules }
254 }
255
256 pub async fn for_event<T>(&self, event: &Raw<T>) -> Vec<Action> {
258 self.push_rules.get_actions(event, &self.push_condition_room_ctx).await.to_owned()
259 }
260
261 #[doc(hidden)]
264 #[instrument(skip_all)]
265 pub async fn traced_for_event<T>(&self, event: &Raw<T>) -> Vec<Action> {
266 let rules = self
267 .push_rules
268 .iter()
269 .filter_map(|r| {
270 if !r.enabled() {
271 return None;
272 }
273
274 let simplified_action = if r.actions().is_empty() { "inhibit" } else { "notify" };
275
276 let conditions = match r {
277 AnyPushRuleRef::Override(r) => {
278 format!("{:?}", r.conditions)
279 }
280 AnyPushRuleRef::Content(r) => format!("content-body-match:{}", r.pattern),
281 AnyPushRuleRef::Room(r) => format!("room-match:{}", r.rule_id),
282 AnyPushRuleRef::Sender(r) => format!("sender-match:{}", r.rule_id),
283 AnyPushRuleRef::Underride(r) => format!("{:?}", r.conditions),
284 _ => "<unknown push rule kind>".to_owned(),
285 };
286
287 Some(format!("- {}: {conditions} => {simplified_action}", r.rule_id(),))
288 })
289 .collect::<Vec<_>>()
290 .join("\n");
291 trace!("rules:\n\n{rules}\n\n");
292
293 let found = self.push_rules.get_match(event, &self.push_condition_room_ctx).await;
294
295 if let Some(found) = found {
296 trace!("rule {} matched", found.rule_id());
297 found.actions().to_owned()
298 } else {
299 trace!("no match");
300 Vec::new()
301 }
302 }
303}
304
305macro_rules! make_media_type {
306 ($t:ty, $content_type: ident, $filename: ident, $source: ident, $caption: ident, $info: ident, $thumbnail: ident) => {{
307 let (body, formatted, filename) = match $caption {
311 Some(TextMessageEventContent { body, formatted, .. }) => (body, formatted, Some($filename)),
312 None => ($filename, None, None),
313 };
314
315 let (thumbnail_source, thumbnail_info) = $thumbnail.unzip();
316
317 match $content_type.type_() {
318 mime::IMAGE => {
319 let info = assign!($info.map(ImageInfo::from).unwrap_or_default(), {
320 mimetype: Some($content_type.as_ref().to_owned()),
321 thumbnail_source,
322 thumbnail_info
323 });
324 let content = assign!(ImageMessageEventContent::new(body, $source), {
325 info: Some(Box::new(info)),
326 formatted,
327 filename
328 });
329 <$t>::Image(content)
330 }
331
332 mime::AUDIO => {
333 let mut content = assign!(AudioMessageEventContent::new(body, $source), {
334 formatted,
335 filename
336 });
337
338 if let Some(AttachmentInfo::Audio(audio_info) | AttachmentInfo::Voice(audio_info)) = &$info &&
339 let Some(duration) = audio_info.duration && let Some(waveform_vec) = &audio_info.waveform {
340 let waveform = waveform_vec
341 .iter()
342 .map(|v| ((*v).clamp(0.0, 1.0) * UnstableAmplitude::MAX as f32) as u16)
343 .map(Into::into)
344 .collect();
345 content.audio =
346 Some(UnstableAudioDetailsContentBlock::new(duration, waveform));
347 }
348
349 if matches!($info, Some(AttachmentInfo::Voice(_))) {
350 content.voice = Some(UnstableVoiceContentBlock::new());
351 }
352
353 let mut audio_info = $info.map(AudioInfo::from).unwrap_or_default();
354 audio_info.mimetype = Some($content_type.as_ref().to_owned());
355 let content = content.info(Box::new(audio_info));
356
357 <$t>::Audio(content)
358 }
359
360 mime::VIDEO => {
361 let info = assign!($info.map(VideoInfo::from).unwrap_or_default(), {
362 mimetype: Some($content_type.as_ref().to_owned()),
363 thumbnail_source,
364 thumbnail_info
365 });
366 let content = assign!(VideoMessageEventContent::new(body, $source), {
367 info: Some(Box::new(info)),
368 formatted,
369 filename
370 });
371 <$t>::Video(content)
372 }
373
374 _ => {
375 let info = assign!($info.map(FileInfo::from).unwrap_or_default(), {
376 mimetype: Some($content_type.as_ref().to_owned()),
377 thumbnail_source,
378 thumbnail_info
379 });
380 let content = assign!(FileMessageEventContent::new(body, $source), {
381 info: Some(Box::new(info)),
382 formatted,
383 filename,
384 });
385 <$t>::File(content)
386 }
387 }
388 }};
389}
390
391impl Room {
392 pub(crate) fn new(client: Client, room: BaseRoom) -> Self {
399 Self { inner: room, client }
400 }
401
402 #[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,
437 _ => false,
438 }
439 } else {
440 false
441 };
442
443 error!(?error, ignore_error, should_forget, "Failed to leave the room");
444
445 if !ignore_error {
446 return (Err(error.into()), self);
447 }
448 }
449
450 if let Err(e) = self.client.base_client().room_left(self.room_id()).await {
451 return (Err(e.into()), self);
452 }
453
454 if should_forget {
455 trace!("Trying to forget the room");
456
457 if let Err(error) = self.forget().await {
458 error!(?error, "Failed to forget the room");
459 }
460 }
461
462 (Ok(()), self)
463 }
464
465 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);
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>>> {
577 let Some(url) = self.avatar_url() else { return Ok(None) };
578 let request = MediaRequestParameters { source: MediaSource::Plain(url.to_owned()), format };
579 Ok(Some(self.client.media().get_media_content(&request, true).await?))
580 }
581
582 #[instrument(skip_all, fields(room_id = ?self.inner.room_id(), ?options))]
611 pub async fn messages(&self, options: MessagesOptions) -> Result<Messages> {
612 let room_id = self.inner.room_id();
613 let request = options.into_request(room_id);
614 let http_response = self.client.send(request).await?;
615
616 let push_ctx = self.push_context().await?;
617 let chunk = join_all(
618 http_response.chunk.into_iter().map(|ev| self.try_decrypt_event(ev, push_ctx.as_ref())),
619 )
620 .await;
621
622 Ok(Messages {
623 start: http_response.start,
624 end: http_response.end,
625 chunk,
626 state: http_response.state,
627 })
628 }
629
630 pub fn add_event_handler<Ev, Ctx, H>(&self, handler: H) -> EventHandlerHandle
640 where
641 Ev: SyncEvent + DeserializeOwned + Send + 'static,
642 H: EventHandler<Ev, Ctx>,
643 {
644 self.client.add_room_event_handler(self.room_id(), handler)
645 }
646
647 pub fn subscribe_to_updates(&self) -> broadcast::Receiver<RoomUpdate> {
652 self.client.subscribe_to_room_updates(self.room_id())
653 }
654
655 pub fn subscribe_to_typing_notifications(
661 &self,
662 ) -> (EventHandlerDropGuard, broadcast::Receiver<Vec<OwnedUserId>>) {
663 let (sender, receiver) = broadcast::channel(16);
664 let typing_event_handler_handle = self.client.add_room_event_handler(self.room_id(), {
665 let own_user_id = self.own_user_id().to_owned();
666 move |event: SyncTypingEvent| async move {
667 let typing_user_ids = event
669 .content
670 .user_ids
671 .into_iter()
672 .filter(|user_id| *user_id != own_user_id)
673 .collect();
674 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 pub fn subscribe_to_state_events(
694 &self,
695 event_type: StateEventType,
696 ) -> impl Stream<Item = Result<Vec<RawAnySyncOrStrippedState>>> + use<> {
697 let room = self.clone();
698 let mut room_updates = self.subscribe_to_updates();
699
700 stream! {
701 yield room.get_state_events(event_type.clone()).await;
706
707 loop {
708 match room_updates.recv().await {
709 Ok(update) => {
710 if !has_state_events_of_type(&update, &event_type) {
711 continue;
712 }
713 }
714 Err(broadcast::error::RecvError::Lagged(_)) => {}
718 Err(broadcast::error::RecvError::Closed) => break,
719 }
720
721 yield room.get_state_events(event_type.clone()).await;
722 }
723 }
724 }
725
726 #[cfg(feature = "e2e-encryption")]
749 pub async fn subscribe_to_identity_status_changes(
750 &self,
751 ) -> Result<impl Stream<Item = Vec<IdentityStatusChange>> + use<>> {
752 IdentityStatusChanges::create_stream(self.clone()).await
753 }
754
755 pub async fn live_locations_observer(&self) -> LiveLocationsObserver {
763 LiveLocationsObserver::new(self.clone()).await
764 }
765
766 #[cfg(not(feature = "experimental-encrypted-state-events"))]
771 #[allow(clippy::unused_async)] async fn try_decrypt_event(
773 &self,
774 event: Raw<AnyTimelineEvent>,
775 push_ctx: Option<&PushContext>,
776 ) -> TimelineEvent {
777 #[cfg(feature = "e2e-encryption")]
778 if let Ok(AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomEncrypted(
779 SyncMessageLikeEvent::Original(_),
780 ))) = event.deserialize_as::<AnySyncTimelineEvent>()
781 && let Ok(event) = self.decrypt_event(event.cast_ref_unchecked(), push_ctx).await
782 {
783 return event;
784 }
785
786 let mut event = TimelineEvent::from_plaintext(event.cast());
787 if let Some(push_ctx) = push_ctx {
788 event.set_push_actions(push_ctx.for_event(event.raw()).await);
789 }
790
791 event
792 }
793
794 #[cfg(feature = "experimental-encrypted-state-events")]
799 #[allow(clippy::unused_async)] async fn try_decrypt_event(
801 &self,
802 event: Raw<AnyTimelineEvent>,
803 push_ctx: Option<&PushContext>,
804 ) -> TimelineEvent {
805 match event.deserialize_as::<AnySyncTimelineEvent>() {
808 Ok(AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomEncrypted(
809 SyncMessageLikeEvent::Original(_),
810 ))) => {
811 if let Ok(event) = self
812 .decrypt_event(
813 event.cast_ref_unchecked::<OriginalSyncRoomEncryptedEvent>(),
814 push_ctx,
815 )
816 .await
817 {
818 return event;
819 }
820 }
821 Ok(AnySyncTimelineEvent::State(AnySyncStateEvent::RoomEncrypted(
822 SyncStateEvent::Original(_),
823 ))) => {
824 if let Ok(event) = self
825 .decrypt_event(
826 event.cast_ref_unchecked::<OriginalSyncStateRoomEncryptedEvent>(),
827 push_ctx,
828 )
829 .await
830 {
831 return event;
832 }
833 }
834 _ => {}
835 }
836
837 let mut event = TimelineEvent::from_plaintext(event.cast());
838 if let Some(push_ctx) = push_ctx {
839 event.set_push_actions(push_ctx.for_event(event.raw()).await);
840 }
841
842 event
843 }
844
845 pub async fn event(
850 &self,
851 event_id: &EventId,
852 request_config: Option<RequestConfig>,
853 ) -> Result<TimelineEvent> {
854 let request =
855 get_room_event::v3::Request::new(self.room_id().to_owned(), event_id.to_owned());
856
857 let raw_event = self.client.send(request).with_request_config(request_config).await?.event;
858 let push_ctx = self.push_context().await?;
859 let event = self.try_decrypt_event(raw_event, push_ctx.as_ref()).await;
860
861 Ok(event)
862 }
863
864 pub async fn load_or_fetch_event(
870 &self,
871 event_id: &EventId,
872 request_config: Option<RequestConfig>,
873 ) -> Result<TimelineEvent> {
874 if let Some(event_cache) = self.client.event_cache() {
875 match event_cache.room(self.room_id()).await {
876 Ok((room_event_cache, _drop_handles)) => {
877 if let Some(event) = room_event_cache.find_event(event_id).await? {
878 return Ok(event);
879 }
880 }
882 Err(err) => {
883 debug!("error when getting the event cache: {err}");
884 }
885 }
886 }
887
888 self.event(event_id, request_config).await
889
890 }
904
905 pub async fn load_or_fetch_event_with_relations(
922 &self,
923 event_id: &EventId,
924 filter: Option<Vec<RelationType>>,
925 request_config: Option<RequestConfig>,
926 ) -> Result<(TimelineEvent, Vec<TimelineEvent>)> {
927 let fetch_relations = async || {
928 let include_relations = if let Some(filter) = &filter
937 && filter.len() == 1
938 {
939 IncludeRelations::RelationsOfType(filter[0].clone())
940 } else {
941 IncludeRelations::AllRelations
942 };
943
944 let mut opts = RelationsOptions {
945 include_relations,
946 recurse: true,
947 limit: Some(uint!(256)),
948 ..Default::default()
949 };
950
951 let mut events = Vec::new();
952 loop {
953 match self.relations(event_id.to_owned(), opts.clone()).await {
954 Ok(relations) => {
955 if let Some(filter) = filter.as_ref() {
956 events.extend(relations.chunk.into_iter().filter_map(|ev| {
959 let (rel_type, _) = extract_relation(ev.raw())?;
960 filter
961 .iter()
962 .any(|ruma_filter| ruma_filter == &rel_type)
963 .then_some(ev)
964 }));
965 } else {
966 events.extend(relations.chunk);
968 }
969
970 if let Some(next_from) = relations.next_batch_token {
971 opts.from = Some(next_from);
972 } else {
973 break events;
974 }
975 }
976
977 Err(err) => {
978 warn!(%event_id, "error when loading relations of pinned event from server: {err}");
979 break events;
980 }
981 }
982 }
983 };
984
985 let room_event_cache = match self.client.event_cache() {
988 Some(event_cache) => {
989 match event_cache.room(self.room_id()).await {
990 Ok((event_cache, drop_handles)) => {
991 if let Some((event, mut relations)) =
992 event_cache.find_event_with_relations(event_id, filter.clone()).await?
993 {
994 if relations.is_empty() {
995 relations = fetch_relations().await;
1000 }
1001
1002 return Ok((event, relations));
1003 }
1004
1005 Some((event_cache, drop_handles))
1007 }
1008
1009 Err(err) => {
1010 debug!("error when getting the event cache: {err}");
1011 None
1013 }
1014 }
1015 }
1016
1017 None => None,
1018 };
1019
1020 let event = self.event(event_id, request_config).await?;
1023
1024 if let Some((event_cache, _drop_handles)) = room_event_cache
1030 && let Some(relations) =
1031 event_cache.find_event_relations(event_id, filter.clone()).await.ok()
1032 && !relations.is_empty()
1033 {
1034 return Ok((event, relations));
1035 }
1036
1037 Ok((event, fetch_relations().await))
1040 }
1041
1042 pub async fn event_with_context(
1045 &self,
1046 event_id: &EventId,
1047 lazy_load_members: bool,
1048 context_size: UInt,
1049 request_config: Option<RequestConfig>,
1050 ) -> Result<EventWithContextResponse> {
1051 let mut request =
1052 context::get_context::v3::Request::new(self.room_id().to_owned(), event_id.to_owned());
1053
1054 request.limit = context_size;
1055
1056 if lazy_load_members {
1057 request.filter.lazy_load_options =
1058 LazyLoadOptions::Enabled { include_redundant_members: false };
1059 }
1060
1061 let response = self.client.send(request).with_request_config(request_config).await?;
1062
1063 let push_ctx = self.push_context().await?;
1064 let push_ctx = push_ctx.as_ref();
1065 let target_event = if let Some(event) = response.event {
1066 Some(self.try_decrypt_event(event, push_ctx).await)
1067 } else {
1068 None
1069 };
1070
1071 let (events_before, events_after) = join!(
1075 join_all(
1076 response.events_before.into_iter().map(|ev| self.try_decrypt_event(ev, push_ctx)),
1077 ),
1078 join_all(
1079 response.events_after.into_iter().map(|ev| self.try_decrypt_event(ev, push_ctx)),
1080 ),
1081 );
1082
1083 Ok(EventWithContextResponse {
1084 event: target_event,
1085 events_before,
1086 events_after,
1087 state: response.state,
1088 prev_batch_token: response.start,
1089 next_batch_token: response.end,
1090 })
1091 }
1092
1093 pub(crate) async fn request_members(&self) -> Result<()> {
1094 self.client
1095 .locks()
1096 .members_request_deduplicated_handler
1097 .run(self.room_id().to_owned(), async move {
1098 let request = get_member_events::v3::Request::new(self.inner.room_id().to_owned());
1099 let response = self
1100 .client
1101 .send(request.clone())
1102 .with_request_config(
1103 RequestConfig::new().timeout(Duration::from_secs(60)).retry_limit(3),
1106 )
1107 .await?;
1108
1109 Box::pin(self.client.base_client().receive_all_members(
1112 self.room_id(),
1113 &request,
1114 &response,
1115 ))
1116 .await?;
1117
1118 Ok(())
1119 })
1120 .await
1121 }
1122
1123 pub async fn request_encryption_state(&self) -> Result<()> {
1128 if !self.inner.encryption_state().is_unknown() {
1129 return Ok(());
1130 }
1131
1132 self.client
1133 .locks()
1134 .encryption_state_deduplicated_handler
1135 .run(self.room_id().to_owned(), async move {
1136 let request = get_state_event_for_key::v3::Request::new(
1138 self.room_id().to_owned(),
1139 StateEventType::RoomEncryption,
1140 "".to_owned(),
1141 );
1142 let response = match self.client.send(request).await {
1143 Ok(response) => Some(
1144 response
1145 .into_content()
1146 .deserialize_as_unchecked::<PossiblyRedactedRoomEncryptionEventContent>(
1147 )?,
1148 ),
1149 Err(err) if err.client_api_error_kind() == Some(&ErrorKind::NotFound) => None,
1150 Err(err) => return Err(err.into()),
1151 };
1152
1153 self.update_and_save_room_info(|mut room_info| {
1156 room_info.mark_encryption_state_synced();
1157 room_info.set_encryption_event(response);
1158 (room_info, RoomInfoNotableUpdateReasons::empty())
1159 })
1160 .await?;
1161
1162 Ok(())
1163 })
1164 .await
1165 }
1166
1167 pub fn encryption_state(&self) -> EncryptionState {
1172 self.inner.encryption_state()
1173 }
1174
1175 pub async fn latest_encryption_state(&self) -> Result<EncryptionState> {
1181 self.request_encryption_state().await?;
1182
1183 Ok(self.encryption_state())
1184 }
1185
1186 #[cfg(feature = "e2e-encryption")]
1188 pub async fn crypto_context_info(&self) -> CryptoContextInfo {
1189 let encryption = self.client.encryption();
1190
1191 let this_device_is_verified = match encryption.get_own_device().await {
1192 Ok(Some(device)) => device.is_verified_with_cross_signing(),
1193
1194 _ => true,
1196 };
1197
1198 let backup_exists_on_server =
1199 encryption.backups().exists_on_server().await.unwrap_or(false);
1200
1201 CryptoContextInfo {
1202 device_creation_ts: encryption.device_creation_timestamp().await,
1203 this_device_is_verified,
1204 is_backup_configured: encryption.backups().state() == BackupState::Enabled,
1205 backup_exists_on_server,
1206 }
1207 }
1208
1209 fn are_events_visible(&self) -> bool {
1210 if let RoomState::Invited = self.inner.state() {
1211 return matches!(
1212 self.inner.history_visibility_or_default(),
1213 HistoryVisibility::WorldReadable | HistoryVisibility::Invited
1214 );
1215 }
1216
1217 true
1218 }
1219
1220 pub async fn sync_members(&self) -> Result<()> {
1226 if !self.are_events_visible() {
1227 return Ok(());
1228 }
1229
1230 if !self.are_members_synced() {
1231 self.request_members().await?;
1232
1233 self.update_active_service_members().await?;
1235
1236 Ok(())
1237 } else {
1238 Ok(())
1239 }
1240 }
1241
1242 pub async fn get_member(&self, user_id: &UserId) -> Result<Option<RoomMember>> {
1256 self.sync_members().await?;
1257 self.get_member_no_sync(user_id).await
1258 }
1259
1260 pub async fn get_member_no_sync(&self, user_id: &UserId) -> Result<Option<RoomMember>> {
1274 Ok(self
1275 .inner
1276 .get_member(user_id)
1277 .await?
1278 .map(|member| RoomMember::new(self.client.clone(), member)))
1279 }
1280
1281 pub async fn members(&self, memberships: RoomMemberships) -> Result<Vec<RoomMember>> {
1290 self.sync_members().await?;
1291 self.members_no_sync(memberships).await
1292 }
1293
1294 pub async fn members_no_sync(&self, memberships: RoomMemberships) -> Result<Vec<RoomMember>> {
1303 Ok(self
1304 .inner
1305 .members(memberships)
1306 .await?
1307 .into_iter()
1308 .map(|member| RoomMember::new(self.client.clone(), member))
1309 .collect())
1310 }
1311
1312 pub async fn human_member_ids(&self, memberships: RoomMemberships) -> Result<Vec<OwnedUserId>> {
1316 self.sync_members().await?;
1317 self.human_member_ids_no_sync(memberships).await
1318 }
1319
1320 pub async fn human_member_ids_no_sync(
1323 &self,
1324 memberships: RoomMemberships,
1325 ) -> Result<Vec<OwnedUserId>> {
1326 Ok(self.inner.human_member_ids(memberships).await?)
1327 }
1328
1329 pub async fn set_own_member_display_name(
1334 &self,
1335 display_name: Option<String>,
1336 ) -> Result<send_state_event::v3::Response> {
1337 let user_id = self.own_user_id();
1338 let member_event =
1339 self.get_state_event_static_for_key::<RoomMemberEventContent, _>(user_id).await?;
1340
1341 let Some(RawSyncOrStrippedState::Sync(raw_event)) = member_event else {
1342 return Err(Error::InsufficientData);
1343 };
1344
1345 let event = raw_event.deserialize()?;
1346
1347 let mut content = match event {
1348 SyncStateEvent::Original(original_event) => original_event.content,
1349 SyncStateEvent::Redacted(redacted_event) => {
1350 RoomMemberEventContent::new(redacted_event.content.membership)
1351 }
1352 };
1353
1354 content.displayname = display_name;
1355 self.send_state_event_for_key(user_id, content).await
1356 }
1357
1358 pub async fn get_state_events(
1360 &self,
1361 event_type: StateEventType,
1362 ) -> Result<Vec<RawAnySyncOrStrippedState>> {
1363 self.client
1364 .state_store()
1365 .get_state_events(self.room_id(), event_type)
1366 .await
1367 .map_err(Into::into)
1368 }
1369
1370 pub async fn get_state_events_static<C>(&self) -> Result<Vec<RawSyncOrStrippedState<C>>>
1387 where
1388 C: StaticEventContent<IsPrefix = ruma::events::False>
1389 + StaticStateEventContent
1390 + RedactContent,
1391 C::Redacted: RedactedStateEventContent,
1392 {
1393 Ok(self.client.state_store().get_state_events_static(self.room_id()).await?)
1394 }
1395
1396 pub async fn get_state_events_for_keys(
1399 &self,
1400 event_type: StateEventType,
1401 state_keys: &[&str],
1402 ) -> Result<Vec<RawAnySyncOrStrippedState>> {
1403 self.client
1404 .state_store()
1405 .get_state_events_for_keys(self.room_id(), event_type, state_keys)
1406 .await
1407 .map_err(Into::into)
1408 }
1409
1410 pub async fn get_state_events_for_keys_static<'a, C, K, I>(
1430 &self,
1431 state_keys: I,
1432 ) -> Result<Vec<RawSyncOrStrippedState<C>>>
1433 where
1434 C: StaticEventContent<IsPrefix = ruma::events::False>
1435 + StaticStateEventContent
1436 + RedactContent,
1437 C::StateKey: Borrow<K>,
1438 C::Redacted: RedactedStateEventContent,
1439 K: AsRef<str> + Sized + Sync + 'a,
1440 I: IntoIterator<Item = &'a K> + Send,
1441 I::IntoIter: Send,
1442 {
1443 Ok(self
1444 .client
1445 .state_store()
1446 .get_state_events_for_keys_static(self.room_id(), state_keys)
1447 .await?)
1448 }
1449
1450 pub async fn get_state_event(
1452 &self,
1453 event_type: StateEventType,
1454 state_key: &str,
1455 ) -> Result<Option<RawAnySyncOrStrippedState>> {
1456 self.client
1457 .state_store()
1458 .get_state_event(self.room_id(), event_type, state_key)
1459 .await
1460 .map_err(Into::into)
1461 }
1462
1463 pub async fn get_state_event_static<C>(&self) -> Result<Option<RawSyncOrStrippedState<C>>>
1482 where
1483 C: StaticEventContent<IsPrefix = ruma::events::False>
1484 + StaticStateEventContent<StateKey = EmptyStateKey>
1485 + RedactContent,
1486 C::Redacted: RedactedStateEventContent,
1487 {
1488 self.get_state_event_static_for_key(&EmptyStateKey).await
1489 }
1490
1491 pub async fn get_state_event_static_for_key<C, K>(
1511 &self,
1512 state_key: &K,
1513 ) -> Result<Option<RawSyncOrStrippedState<C>>>
1514 where
1515 C: StaticEventContent<IsPrefix = ruma::events::False>
1516 + StaticStateEventContent
1517 + RedactContent,
1518 C::StateKey: Borrow<K>,
1519 C::Redacted: RedactedStateEventContent,
1520 K: AsRef<str> + ?Sized + Sync,
1521 {
1522 Ok(self
1523 .client
1524 .state_store()
1525 .get_state_event_static_for_key(self.room_id(), state_key)
1526 .await?)
1527 }
1528
1529 pub async fn parent_spaces(&self) -> Result<impl Stream<Item = Result<ParentSpace>> + '_> {
1533 Ok(self
1538 .get_state_events_static::<SpaceParentEventContent>()
1539 .await?
1540 .into_iter()
1541 .filter_map(|parent_event| match parent_event.deserialize() {
1543 Ok(SyncOrStrippedState::Sync(SyncStateEvent::Original(e))) => {
1544 Some((e.state_key, e.sender))
1545 }
1546 Ok(SyncOrStrippedState::Sync(SyncStateEvent::Redacted(_))) => None,
1547 Ok(SyncOrStrippedState::Stripped(e)) => Some((e.state_key, e.sender)),
1548 Err(e) => {
1549 info!(room_id = ?self.room_id(), "Could not deserialize m.space.parent: {e}");
1550 None
1551 }
1552 })
1553 .map(|(state_key, sender): (OwnedRoomId, OwnedUserId)| async move {
1555 let Some(parent_room) = self.client.get_room(&state_key) else {
1556 return Ok(ParentSpace::Unverifiable(state_key));
1559 };
1560 if let Some(child_event) = parent_room
1563 .get_state_event_static_for_key::<SpaceChildEventContent, _>(self.room_id())
1564 .await?
1565 {
1566 match child_event.deserialize() {
1567 Ok(SyncOrStrippedState::Sync(SyncStateEvent::Original(_))) => {
1568 return Ok(ParentSpace::Reciprocal(parent_room));
1571 }
1572 Ok(SyncOrStrippedState::Sync(SyncStateEvent::Redacted(_))) => {}
1573 Ok(SyncOrStrippedState::Stripped(_)) => {}
1574 Err(e) => {
1575 info!(
1576 room_id = ?self.room_id(), parent_room_id = ?state_key,
1577 "Could not deserialize m.space.child: {e}"
1578 );
1579 }
1580 }
1581 }
1586
1587 let Some(member) = parent_room.get_member(&sender).await? else {
1590 return Ok(ParentSpace::Illegitimate(parent_room));
1592 };
1593
1594 if member.can_send_state(StateEventType::SpaceChild) {
1595 Ok(ParentSpace::WithPowerlevel(parent_room))
1597 } else {
1598 Ok(ParentSpace::Illegitimate(parent_room))
1599 }
1600 })
1601 .collect::<FuturesUnordered<_>>())
1602 }
1603
1604 pub async fn account_data(
1606 &self,
1607 data_type: RoomAccountDataEventType,
1608 ) -> Result<Option<Raw<AnyRoomAccountDataEvent>>> {
1609 self.client
1610 .state_store()
1611 .get_room_account_data_event(self.room_id(), data_type)
1612 .await
1613 .map_err(Into::into)
1614 }
1615
1616 pub async fn account_data_static<C>(&self) -> Result<Option<Raw<RoomAccountDataEvent<C>>>>
1635 where
1636 C: StaticEventContent<IsPrefix = ruma::events::False> + RoomAccountDataEventContent,
1637 {
1638 Ok(self.account_data(C::TYPE.into()).await?.map(Raw::cast_unchecked))
1639 }
1640
1641 #[cfg(feature = "e2e-encryption")]
1646 pub async fn contains_only_verified_devices(&self) -> Result<bool> {
1647 let user_ids = self
1648 .client
1649 .state_store()
1650 .get_user_ids(self.room_id(), RoomMemberships::empty())
1651 .await?;
1652
1653 for user_id in user_ids {
1654 let devices = self.client.encryption().get_user_devices(&user_id).await?;
1655 let any_unverified = devices.devices().any(|d| !d.is_verified());
1656
1657 if any_unverified {
1658 return Ok(false);
1659 }
1660 }
1661
1662 Ok(true)
1663 }
1664
1665 pub async fn set_account_data<T>(
1681 &self,
1682 content: T,
1683 ) -> Result<set_room_account_data::v3::Response>
1684 where
1685 T: RoomAccountDataEventContent,
1686 {
1687 let own_user = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
1688
1689 let request = set_room_account_data::v3::Request::new(
1690 own_user.to_owned(),
1691 self.room_id().to_owned(),
1692 &content,
1693 )?;
1694
1695 Ok(self.client.send(request).await?)
1696 }
1697
1698 pub async fn set_account_data_raw(
1724 &self,
1725 event_type: RoomAccountDataEventType,
1726 content: Raw<AnyRoomAccountDataEventContent>,
1727 ) -> Result<set_room_account_data::v3::Response> {
1728 let own_user = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
1729
1730 let request = set_room_account_data::v3::Request::new_raw(
1731 own_user.to_owned(),
1732 self.room_id().to_owned(),
1733 event_type,
1734 content,
1735 );
1736
1737 Ok(self.client.send(request).await?)
1738 }
1739
1740 pub async fn set_tag(
1771 &self,
1772 tag: TagName,
1773 tag_info: TagInfo,
1774 ) -> Result<create_tag::v3::Response> {
1775 let user_id = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
1776 let request = create_tag::v3::Request::new(
1777 user_id.to_owned(),
1778 self.inner.room_id().to_owned(),
1779 tag.to_string(),
1780 tag_info,
1781 );
1782 Ok(self.client.send(request).await?)
1783 }
1784
1785 pub async fn remove_tag(&self, tag: TagName) -> Result<delete_tag::v3::Response> {
1793 let user_id = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
1794 let request = delete_tag::v3::Request::new(
1795 user_id.to_owned(),
1796 self.inner.room_id().to_owned(),
1797 tag.to_string(),
1798 );
1799 Ok(self.client.send(request).await?)
1800 }
1801
1802 pub async fn set_is_favourite(&self, is_favourite: bool, tag_order: Option<f64>) -> Result<()> {
1812 if is_favourite {
1813 let tag_info = assign!(TagInfo::new(), { order: tag_order });
1814
1815 self.set_tag(TagName::Favorite, tag_info).await?;
1816
1817 if self.is_low_priority() {
1818 self.remove_tag(TagName::LowPriority).await?;
1819 }
1820 } else {
1821 self.remove_tag(TagName::Favorite).await?;
1822 }
1823 Ok(())
1824 }
1825
1826 pub async fn set_is_low_priority(
1836 &self,
1837 is_low_priority: bool,
1838 tag_order: Option<f64>,
1839 ) -> Result<()> {
1840 if is_low_priority {
1841 let tag_info = assign!(TagInfo::new(), { order: tag_order });
1842
1843 self.set_tag(TagName::LowPriority, tag_info).await?;
1844
1845 if self.is_favourite() {
1846 self.remove_tag(TagName::Favorite).await?;
1847 }
1848 } else {
1849 self.remove_tag(TagName::LowPriority).await?;
1850 }
1851 Ok(())
1852 }
1853
1854 pub async fn set_is_direct(&self, is_direct: bool) -> Result<()> {
1864 let user_id = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
1865
1866 let mut content = self
1867 .client
1868 .account()
1869 .account_data::<DirectEventContent>()
1870 .await?
1871 .map(|c| c.deserialize())
1872 .transpose()?
1873 .unwrap_or_default();
1874
1875 let this_room_id = self.inner.room_id();
1876
1877 if is_direct {
1878 let mut room_members = self.members(RoomMemberships::ACTIVE).await?;
1879 room_members.retain(|member| member.user_id() != self.own_user_id());
1880
1881 for member in room_members {
1882 let entry = content.entry(member.user_id().into()).or_default();
1883 if !entry.iter().any(|room_id| room_id == this_room_id) {
1884 entry.push(this_room_id.to_owned());
1885 }
1886 }
1887 } else {
1888 for list in content.values_mut() {
1889 list.retain(|room_id| *room_id != this_room_id);
1890 }
1891
1892 content.retain(|_, list| !list.is_empty());
1894 }
1895
1896 let request = set_global_account_data::v3::Request::new(user_id.to_owned(), &content)?;
1897
1898 self.client.send(request).await?;
1899 Ok(())
1900 }
1901
1902 #[cfg(feature = "e2e-encryption")]
1911 #[cfg(not(feature = "experimental-encrypted-state-events"))]
1912 pub async fn decrypt_event(
1913 &self,
1914 event: &Raw<OriginalSyncRoomEncryptedEvent>,
1915 push_ctx: Option<&PushContext>,
1916 ) -> Result<TimelineEvent> {
1917 let machine = self.client.olm_machine().await;
1918 let machine = machine.as_ref().ok_or(Error::NoOlmMachine)?;
1919
1920 match machine
1921 .try_decrypt_room_event(
1922 event.cast_ref(),
1923 self.inner.room_id(),
1924 self.client.decryption_settings(),
1925 )
1926 .await?
1927 {
1928 RoomEventDecryptionResult::Decrypted(decrypted) => {
1929 let push_actions = if let Some(push_ctx) = push_ctx {
1930 Some(push_ctx.for_event(&decrypted.event).await)
1931 } else {
1932 None
1933 };
1934 Ok(TimelineEvent::from_decrypted(decrypted, push_actions))
1935 }
1936 RoomEventDecryptionResult::UnableToDecrypt(utd_info) => {
1937 self.client
1938 .encryption()
1939 .backups()
1940 .maybe_download_room_key(self.room_id().to_owned(), event.clone());
1941 Ok(TimelineEvent::from_utd(event.clone().cast(), utd_info))
1942 }
1943 }
1944 }
1945
1946 #[cfg(feature = "experimental-encrypted-state-events")]
1955 pub async fn decrypt_event<T: JsonCastable<EncryptedEvent>>(
1956 &self,
1957 event: &Raw<T>,
1958 push_ctx: Option<&PushContext>,
1959 ) -> Result<TimelineEvent> {
1960 let machine = self.client.olm_machine().await;
1961 let machine = machine.as_ref().ok_or(Error::NoOlmMachine)?;
1962
1963 match machine
1964 .try_decrypt_room_event(
1965 event.cast_ref(),
1966 self.inner.room_id(),
1967 self.client.decryption_settings(),
1968 )
1969 .await?
1970 {
1971 RoomEventDecryptionResult::Decrypted(decrypted) => {
1972 let push_actions = if let Some(push_ctx) = push_ctx {
1973 Some(push_ctx.for_event(&decrypted.event).await)
1974 } else {
1975 None
1976 };
1977 Ok(TimelineEvent::from_decrypted(decrypted, push_actions))
1978 }
1979 RoomEventDecryptionResult::UnableToDecrypt(utd_info) => {
1980 self.client
1981 .encryption()
1982 .backups()
1983 .maybe_download_room_key(self.room_id().to_owned(), event.clone());
1984 Ok(TimelineEvent::from_utd(event.clone().cast_unchecked(), utd_info))
1987 }
1988 }
1989 }
1990
1991 #[cfg(feature = "e2e-encryption")]
2005 pub async fn get_encryption_info(
2006 &self,
2007 session_id: &str,
2008 sender: &UserId,
2009 ) -> Option<Arc<EncryptionInfo>> {
2010 let machine = self.client.olm_machine().await;
2011 let machine = machine.as_ref()?;
2012 machine.get_session_encryption_info(self.room_id(), session_id, sender).await.ok()
2013 }
2014
2015 #[cfg(feature = "e2e-encryption")]
2028 pub async fn discard_room_key(&self) -> Result<()> {
2029 let machine = self.client.olm_machine().await;
2030 if let Some(machine) = machine.as_ref() {
2031 machine.discard_room_key(self.inner.room_id()).await?;
2032 Ok(())
2033 } else {
2034 Err(Error::NoOlmMachine)
2035 }
2036 }
2037
2038 #[instrument(skip_all)]
2046 pub async fn ban_user(&self, user_id: &UserId, reason: Option<&str>) -> Result<()> {
2047 let request = assign!(
2048 ban_user::v3::Request::new(self.room_id().to_owned(), user_id.to_owned()),
2049 { reason: reason.map(ToOwned::to_owned) }
2050 );
2051 self.client.send(request).await?;
2052 Ok(())
2053 }
2054
2055 #[instrument(skip_all)]
2063 pub async fn unban_user(&self, user_id: &UserId, reason: Option<&str>) -> Result<()> {
2064 let request = assign!(
2065 unban_user::v3::Request::new(self.room_id().to_owned(), user_id.to_owned()),
2066 { reason: reason.map(ToOwned::to_owned) }
2067 );
2068 self.client.send(request).await?;
2069 Ok(())
2070 }
2071
2072 #[instrument(skip_all)]
2081 pub async fn kick_user(&self, user_id: &UserId, reason: Option<&str>) -> Result<()> {
2082 let request = assign!(
2083 kick_user::v3::Request::new(self.room_id().to_owned(), user_id.to_owned()),
2084 { reason: reason.map(ToOwned::to_owned) }
2085 );
2086 self.client.send(request).await?;
2087 Ok(())
2088 }
2089
2090 #[instrument(skip_all)]
2096 pub async fn invite_user_by_id(&self, user_id: &UserId) -> Result<()> {
2097 #[cfg(feature = "e2e-encryption")]
2098 if self.client.inner.enable_share_history_on_invite {
2099 shared_room_history::share_room_history(self, user_id.to_owned()).await?;
2100 }
2101
2102 let recipient = InvitationRecipient::UserId(InviteUserId::new(user_id.to_owned()));
2103 let request = invite_user::v3::Request::new(self.room_id().to_owned(), recipient);
2104 self.client.send(request).await?;
2105
2106 self.mark_members_missing();
2111
2112 Ok(())
2113 }
2114
2115 #[instrument(skip_all)]
2121 pub async fn invite_user_by_3pid(&self, invite_id: Invite3pid) -> Result<()> {
2122 let recipient = InvitationRecipient::ThirdPartyId(invite_id);
2123 let request = invite_user::v3::Request::new(self.room_id().to_owned(), recipient);
2124 self.client.send(request).await?;
2125
2126 self.mark_members_missing();
2131
2132 Ok(())
2133 }
2134
2135 pub async fn typing_notice(&self, typing: bool) -> Result<()> {
2169 self.ensure_room_joined()?;
2170
2171 let send = if let Some(typing_time) =
2174 self.client.inner.typing_notice_times.read().unwrap().get(self.room_id())
2175 {
2176 if typing_time.elapsed() > TYPING_NOTICE_RESEND_TIMEOUT {
2177 typing || typing_time.elapsed() <= TYPING_NOTICE_TIMEOUT
2181 } else {
2182 !typing
2184 }
2185 } else {
2186 typing
2189 };
2190
2191 if send {
2192 self.send_typing_notice(typing).await?;
2193 }
2194
2195 Ok(())
2196 }
2197
2198 #[instrument(name = "typing_notice", skip(self))]
2199 async fn send_typing_notice(&self, typing: bool) -> Result<()> {
2200 let typing = if typing {
2201 self.client
2202 .inner
2203 .typing_notice_times
2204 .write()
2205 .unwrap()
2206 .insert(self.room_id().to_owned(), Instant::now());
2207 Typing::Yes(TypingInfo::new(TYPING_NOTICE_TIMEOUT))
2208 } else {
2209 self.client.inner.typing_notice_times.write().unwrap().remove(self.room_id());
2210 Typing::No
2211 };
2212
2213 let request = create_typing_event::v3::Request::new(
2214 self.own_user_id().to_owned(),
2215 self.room_id().to_owned(),
2216 typing,
2217 );
2218
2219 self.client.send(request).await?;
2220
2221 Ok(())
2222 }
2223
2224 #[instrument(skip_all)]
2241 pub async fn send_single_receipt(
2242 &self,
2243 receipt_type: create_receipt::v3::ReceiptType,
2244 thread: ReceiptThread,
2245 event_id: OwnedEventId,
2246 ) -> Result<()> {
2247 let request_key = format!("{}|{}", receipt_type, thread.as_str().unwrap_or("<unthreaded>"));
2250
2251 self.client
2252 .inner
2253 .locks
2254 .read_receipt_deduplicated_handler
2255 .run((request_key, event_id.clone()), async {
2256 let is_unthreaded = thread == ReceiptThread::Unthreaded;
2259
2260 let mut request = create_receipt::v3::Request::new(
2261 self.room_id().to_owned(),
2262 receipt_type,
2263 event_id,
2264 );
2265 request.thread = thread;
2266
2267 self.client.send(request).await?;
2268
2269 if is_unthreaded {
2270 self.set_unread_flag(false).await?;
2271 }
2272
2273 Ok(())
2274 })
2275 .await
2276 }
2277
2278 #[instrument(skip_all)]
2288 pub async fn send_multiple_receipts(&self, receipts: Receipts) -> Result<()> {
2289 if receipts.is_empty() {
2290 return Ok(());
2291 }
2292
2293 let Receipts { fully_read, public_read_receipt, private_read_receipt } = receipts;
2294 let request = assign!(set_read_marker::v3::Request::new(self.room_id().to_owned()), {
2295 fully_read,
2296 read_receipt: public_read_receipt,
2297 private_read_receipt,
2298 });
2299
2300 self.client.send(request).await?;
2301
2302 self.set_unread_flag(false).await?;
2303
2304 Ok(())
2305 }
2306
2307 #[allow(unused_variables, unused_mut)]
2311 async fn enable_encryption_inner(&self, encrypted_state_events: bool) -> Result<()> {
2312 use ruma::{
2313 EventEncryptionAlgorithm, events::room::encryption::RoomEncryptionEventContent,
2314 };
2315 const SYNC_WAIT_TIME: Duration = Duration::from_secs(3);
2316
2317 if !self.latest_encryption_state().await?.is_encrypted() {
2318 let mut content =
2319 RoomEncryptionEventContent::new(EventEncryptionAlgorithm::MegolmV1AesSha2);
2320 #[cfg(feature = "experimental-encrypted-state-events")]
2321 if encrypted_state_events {
2322 content = content.with_encrypted_state();
2323 }
2324 self.send_state_event(content).await?;
2325
2326 let res = timeout(
2333 async {
2334 loop {
2335 self.client.inner.sync_beat.listen().await;
2338 let _state_store_lock =
2339 self.client.base_client().state_store_lock().lock().await;
2340
2341 if !self.inner.encryption_state().is_unknown() {
2342 break;
2343 }
2344 }
2345 },
2346 SYNC_WAIT_TIME,
2347 )
2348 .await;
2349
2350 let store_guard = self.client.base_client().state_store_lock().lock().await;
2351
2352 #[cfg(not(feature = "experimental-encrypted-state-events"))]
2354 if res.is_ok() && self.inner.encryption_state().is_encrypted() {
2355 debug!("room successfully marked as encrypted");
2356 return Ok(());
2357 }
2358
2359 #[cfg(feature = "experimental-encrypted-state-events")]
2361 if res.is_ok() && {
2362 if encrypted_state_events {
2363 self.inner.encryption_state().is_state_encrypted()
2364 } else {
2365 self.inner.encryption_state().is_encrypted()
2366 }
2367 } {
2368 debug!("room successfully marked as encrypted");
2369 return Ok(());
2370 }
2371
2372 debug!("still not marked as encrypted, marking encryption state as missing");
2377
2378 self.update_and_save_room_info_with_store_guard(&store_guard, |mut info| {
2379 info.mark_encryption_state_missing();
2380 (info, RoomInfoNotableUpdateReasons::empty())
2381 })
2382 .await?;
2383 }
2384
2385 Ok(())
2386 }
2387
2388 #[instrument(skip_all)]
2420 pub async fn enable_encryption(&self) -> Result<()> {
2421 self.enable_encryption_inner(false).await
2422 }
2423
2424 #[instrument(skip_all)]
2457 #[cfg(feature = "experimental-encrypted-state-events")]
2458 pub async fn enable_encryption_with_state_event_encryption(&self) -> Result<()> {
2459 self.enable_encryption_inner(true).await
2460 }
2461
2462 #[cfg(feature = "e2e-encryption")]
2471 #[instrument(skip_all, fields(room_id = ?self.room_id()))]
2472 async fn preshare_room_key(&self) -> Result<()> {
2473 self.ensure_room_joined()?;
2474
2475 let _guard = self.client.encryption().spin_lock_store(Some(60000)).await?;
2477
2478 self.client
2479 .locks()
2480 .group_session_deduplicated_handler
2481 .run(self.room_id().to_owned(), async move {
2482 {
2483 let members = self
2484 .client
2485 .state_store()
2486 .get_user_ids(self.room_id(), RoomMemberships::ACTIVE)
2487 .await?;
2488 self.client.claim_one_time_keys(members.iter().map(Deref::deref)).await?;
2489 };
2490
2491 let response = self.share_room_key().await;
2492
2493 if let Err(r) = response {
2496 let machine = self.client.olm_machine().await;
2497 if let Some(machine) = machine.as_ref() {
2498 machine.discard_room_key(self.room_id()).await?;
2499 }
2500 return Err(r);
2501 }
2502
2503 Ok(())
2504 })
2505 .await
2506 }
2507
2508 #[cfg(feature = "e2e-encryption")]
2514 #[instrument(skip_all)]
2515 async fn share_room_key(&self) -> Result<()> {
2516 self.ensure_room_joined()?;
2517
2518 let requests = self.client.base_client().share_room_key(self.room_id()).await?;
2519
2520 for request in requests {
2521 let response = self.client.send_to_device(&request).await?;
2522 self.client.mark_request_as_sent(&request.txn_id, &response).await?;
2523 }
2524
2525 Ok(())
2526 }
2527
2528 #[instrument(skip_all)]
2537 pub async fn sync_up(&self) {
2538 while !self.is_synced() && self.state() == RoomState::Joined {
2539 let wait_for_beat = self.client.inner.sync_beat.listen();
2540 let _ = timeout(wait_for_beat, Duration::from_millis(1000)).await;
2542 }
2543 }
2544
2545 pub fn send(&self, content: impl MessageLikeEventContent) -> SendMessageLikeEvent<'_> {
2616 SendMessageLikeEvent::new(self, content)
2617 }
2618
2619 #[cfg(feature = "e2e-encryption")]
2622 async fn query_keys_for_untracked_or_dirty_users(&self) -> Result<()> {
2623 let olm = self.client.olm_machine().await;
2624 let olm = olm.as_ref().expect("Olm machine wasn't started");
2625
2626 let members =
2627 self.client.state_store().get_user_ids(self.room_id(), RoomMemberships::ACTIVE).await?;
2628
2629 let tracked: HashMap<_, _> = olm
2630 .store()
2631 .load_tracked_users()
2632 .await?
2633 .into_iter()
2634 .map(|tracked| (tracked.user_id, tracked.dirty))
2635 .collect();
2636
2637 let members_with_unknown_devices =
2640 members.iter().filter(|member| tracked.get(*member).is_none_or(|dirty| *dirty));
2641
2642 let (req_id, request) =
2643 olm.query_keys_for_users(members_with_unknown_devices.map(|owned| owned.borrow()));
2644
2645 if !request.device_keys.is_empty() {
2646 self.client.keys_query(&req_id, request.device_keys).await?;
2647 }
2648
2649 Ok(())
2650 }
2651
2652 #[instrument(skip_all, fields(event_type, room_id = ?self.room_id(), transaction_id, is_room_encrypted, event_id))]
2695 pub fn send_raw<'a>(
2696 &'a self,
2697 event_type: &'a str,
2698 content: impl IntoRawMessageLikeEventContent,
2699 ) -> SendRawMessageLikeEvent<'a> {
2700 SendRawMessageLikeEvent::new(self, event_type, content)
2703 }
2704
2705 #[instrument(skip_all)]
2751 pub fn send_attachment<'a>(
2752 &'a self,
2753 filename: impl Into<String>,
2754 content_type: &'a Mime,
2755 data: Vec<u8>,
2756 config: AttachmentConfig,
2757 ) -> SendAttachment<'a> {
2758 SendAttachment::new(self, filename.into(), content_type, data, config)
2759 }
2760
2761 #[instrument(skip_all)]
2788 pub(super) async fn prepare_and_send_attachment<'a>(
2789 &'a self,
2790 filename: String,
2791 content_type: &'a Mime,
2792 data: Vec<u8>,
2793 mut config: AttachmentConfig,
2794 send_progress: SharedObservable<TransmissionProgress>,
2795 store_in_cache: bool,
2796 ) -> Result<send_message_event::v3::Response> {
2797 self.ensure_room_joined()?;
2798
2799 let txn_id = config.txn_id.take();
2800 let extra_content = config.extra_content.take();
2801 let mentions = config.mentions.take();
2802
2803 let thumbnail = config.thumbnail.take();
2804
2805 let thumbnail_cache_info = if store_in_cache {
2807 thumbnail
2808 .as_ref()
2809 .map(|thumbnail| (thumbnail.data.clone(), thumbnail.height, thumbnail.width))
2810 } else {
2811 None
2812 };
2813
2814 #[cfg(feature = "e2e-encryption")]
2815 let (media_source, thumbnail) = if self.latest_encryption_state().await?.is_encrypted() {
2816 self.client
2817 .upload_encrypted_media_and_thumbnail(&data, thumbnail, send_progress)
2818 .await?
2819 } else {
2820 self.client
2821 .media()
2822 .upload_plain_media_and_thumbnail(
2823 content_type,
2824 data.clone(),
2827 thumbnail,
2828 send_progress,
2829 )
2830 .await?
2831 };
2832
2833 #[cfg(not(feature = "e2e-encryption"))]
2834 let (media_source, thumbnail) = self
2835 .client
2836 .media()
2837 .upload_plain_media_and_thumbnail(content_type, data.clone(), thumbnail, send_progress)
2838 .await?;
2839
2840 if store_in_cache {
2841 let media_store_lock_guard = self.client.media_store().lock().await?;
2842
2843 debug!("caching the media");
2847 let request =
2848 MediaRequestParameters { source: media_source.clone(), format: MediaFormat::File };
2849
2850 if let Err(err) = media_store_lock_guard
2851 .add_media_content(&request, data, IgnoreMediaRetentionPolicy::No)
2852 .await
2853 {
2854 warn!("unable to cache the media after uploading it: {err}");
2855 }
2856
2857 if let Some(((data, height, width), source)) =
2858 thumbnail_cache_info.zip(thumbnail.as_ref().map(|tuple| &tuple.0))
2859 {
2860 debug!("caching the thumbnail");
2861
2862 let request = MediaRequestParameters {
2863 source: source.clone(),
2864 format: MediaFormat::Thumbnail(MediaThumbnailSettings::new(width, height)),
2865 };
2866
2867 if let Err(err) = media_store_lock_guard
2868 .add_media_content(&request, data, IgnoreMediaRetentionPolicy::No)
2869 .await
2870 {
2871 warn!("unable to cache the media after uploading it: {err}");
2872 }
2873 }
2874 }
2875
2876 let content = self
2877 .make_media_event(
2878 Room::make_attachment_type(
2879 content_type,
2880 filename,
2881 media_source,
2882 config.caption,
2883 config.info,
2884 thumbnail,
2885 ),
2886 mentions,
2887 config.reply,
2888 )
2889 .await?;
2890
2891 if let Some(extra_content) = extra_content.filter(|extra| !extra.is_empty()) {
2895 let serde_json::Value::Object(mut object) = serde_json::to_value(&content)
2896 .map_err(|error| Error::UnknownError(Box::new(error)))?
2897 else {
2898 unreachable!("a room message event content always serializes to a JSON object");
2899 };
2900 for (key, value) in extra_content {
2901 match object.entry(key) {
2902 serde_json::map::Entry::Occupied(entry) => {
2903 warn!(
2904 key = entry.key(),
2905 "extra content field shadowed by the event's own field"
2906 );
2907 }
2908 serde_json::map::Entry::Vacant(entry) => {
2909 entry.insert(value);
2910 }
2911 }
2912 }
2913
2914 let event_type = content.event_type().to_string();
2915 let mut fut = self.send_raw(&event_type, serde_json::Value::Object(object));
2916 if let Some(txn_id) = &txn_id {
2917 fut = fut.with_transaction_id(txn_id);
2918 }
2919 return fut.await.map(|result| result.response);
2920 }
2921
2922 let mut fut = self.send(content);
2923 if let Some(txn_id) = txn_id {
2924 fut = fut.with_transaction_id(txn_id);
2925 }
2926
2927 fut.await.map(|result| result.response)
2928 }
2929
2930 #[allow(clippy::too_many_arguments)]
2933 pub(crate) fn make_attachment_type(
2934 content_type: &Mime,
2935 filename: String,
2936 source: MediaSource,
2937 caption: Option<TextMessageEventContent>,
2938 info: Option<AttachmentInfo>,
2939 thumbnail: Option<(MediaSource, Box<ThumbnailInfo>)>,
2940 ) -> MessageType {
2941 make_media_type!(MessageType, content_type, filename, source, caption, info, thumbnail)
2942 }
2943
2944 pub(crate) async fn make_media_event(
2947 &self,
2948 msg_type: MessageType,
2949 mentions: Option<Mentions>,
2950 reply: Option<Reply>,
2951 ) -> Result<RoomMessageEventContent> {
2952 let mut content = RoomMessageEventContent::new(msg_type);
2953 if let Some(mentions) = mentions {
2954 content = content.add_mentions(mentions);
2955 }
2956 if let Some(reply) = reply {
2957 content = self.make_reply_event(content.into(), reply).await?;
2961 }
2962 Ok(content)
2963 }
2964
2965 #[cfg(feature = "unstable-msc4274")]
2968 #[allow(clippy::too_many_arguments)]
2969 pub(crate) fn make_gallery_item_type(
2970 content_type: &Mime,
2971 filename: String,
2972 source: MediaSource,
2973 caption: Option<TextMessageEventContent>,
2974 info: Option<AttachmentInfo>,
2975 thumbnail: Option<(MediaSource, Box<ThumbnailInfo>)>,
2976 ) -> GalleryItemType {
2977 make_media_type!(GalleryItemType, content_type, filename, source, caption, info, thumbnail)
2978 }
2979
2980 pub async fn update_power_levels(
2988 &self,
2989 updates: Vec<(&UserId, Int)>,
2990 ) -> Result<send_state_event::v3::Response> {
2991 let mut power_levels = self.power_levels().await?;
2992
2993 for (user_id, new_level) in updates {
2994 if new_level == power_levels.users_default {
2995 power_levels.users.remove(user_id);
2996 } else {
2997 power_levels.users.insert(user_id.to_owned(), new_level);
2998 }
2999 }
3000
3001 self.send_state_event(RoomPowerLevelsEventContent::try_from(power_levels)?).await
3002 }
3003
3004 pub async fn apply_power_level_changes(&self, changes: RoomPowerLevelChanges) -> Result<()> {
3009 let mut power_levels = self.power_levels().await?;
3010 power_levels.apply(changes)?;
3011 self.send_state_event(RoomPowerLevelsEventContent::try_from(power_levels)?).await?;
3012 Ok(())
3013 }
3014
3015 pub async fn reset_power_levels(&self) -> Result<RoomPowerLevels> {
3019 let creators = self.creators().unwrap_or_default();
3020 let rules = self.clone_info().room_version_rules_or_default();
3021
3022 let default_power_levels =
3023 RoomPowerLevels::new(RoomPowerLevelsSource::None, &rules.authorization, creators);
3024 let changes = RoomPowerLevelChanges::from(default_power_levels);
3025 self.apply_power_level_changes(changes).await?;
3026 Ok(self.power_levels().await?)
3027 }
3028
3029 pub async fn get_suggested_user_role(&self, user_id: &UserId) -> Result<RoomMemberRole> {
3034 let power_level = self.get_user_power_level(user_id).await?;
3035 Ok(RoomMemberRole::suggested_role_for_power_level(power_level))
3036 }
3037
3038 pub async fn get_user_power_level(&self, user_id: &UserId) -> Result<UserPowerLevel> {
3043 let event = self.power_levels().await?;
3044 Ok(event.for_user(user_id))
3045 }
3046
3047 pub async fn users_with_power_levels(&self) -> HashMap<OwnedUserId, i64> {
3050 let power_levels = self.power_levels().await.ok();
3051 let mut user_power_levels = HashMap::<OwnedUserId, i64>::new();
3052 if let Some(power_levels) = power_levels {
3053 for (id, level) in power_levels.users.into_iter() {
3054 user_power_levels.insert(id, level.into());
3055 }
3056 }
3057 user_power_levels
3058 }
3059
3060 pub async fn set_name(&self, name: String) -> Result<send_state_event::v3::Response> {
3062 self.send_state_event(RoomNameEventContent::new(name)).await
3063 }
3064
3065 pub async fn set_room_topic(&self, topic: &str) -> Result<send_state_event::v3::Response> {
3067 self.send_state_event(RoomTopicEventContent::new(topic.into())).await
3068 }
3069
3070 pub async fn set_avatar_url(
3077 &self,
3078 url: &MxcUri,
3079 info: Option<avatar::ImageInfo>,
3080 ) -> Result<send_state_event::v3::Response> {
3081 self.ensure_room_joined()?;
3082
3083 let mut room_avatar_event = RoomAvatarEventContent::new();
3084 room_avatar_event.url = Some(url.to_owned());
3085 room_avatar_event.info = info.map(Box::new);
3086
3087 self.send_state_event(room_avatar_event).await
3088 }
3089
3090 pub async fn remove_avatar(&self) -> Result<send_state_event::v3::Response> {
3092 self.send_state_event(RoomAvatarEventContent::new()).await
3093 }
3094
3095 pub async fn upload_avatar(
3104 &self,
3105 mime: &Mime,
3106 data: Vec<u8>,
3107 info: Option<avatar::ImageInfo>,
3108 ) -> Result<send_state_event::v3::Response> {
3109 self.ensure_room_joined()?;
3110
3111 let upload_response = self.client.media().upload(mime, data, None).await?;
3112 let mut info = info.unwrap_or_default();
3113 info.blurhash = upload_response.blurhash;
3114 info.mimetype = Some(mime.to_string());
3115
3116 self.set_avatar_url(&upload_response.content_uri, Some(info)).await
3117 }
3118
3119 #[cfg(not(feature = "experimental-encrypted-state-events"))]
3163 #[instrument(skip_all)]
3164 pub async fn send_state_event(
3165 &self,
3166 content: impl StateEventContent<StateKey = EmptyStateKey>,
3167 ) -> Result<send_state_event::v3::Response> {
3168 self.send_state_event_for_key(&EmptyStateKey, content).await
3169 }
3170
3171 #[cfg(feature = "experimental-encrypted-state-events")]
3222 #[instrument(skip_all)]
3223 pub fn send_state_event<'a>(
3224 &'a self,
3225 content: impl StateEventContent<StateKey = EmptyStateKey>,
3226 ) -> SendStateEvent<'a> {
3227 self.send_state_event_for_key(&EmptyStateKey, content)
3228 }
3229
3230 #[cfg(not(feature = "experimental-encrypted-state-events"))]
3270 pub async fn send_state_event_for_key<C, K>(
3271 &self,
3272 state_key: &K,
3273 content: C,
3274 ) -> Result<send_state_event::v3::Response>
3275 where
3276 C: StateEventContent,
3277 C::StateKey: Borrow<K>,
3278 K: AsRef<str> + ?Sized,
3279 {
3280 self.ensure_room_joined()?;
3281 let request =
3282 send_state_event::v3::Request::new(self.room_id().to_owned(), state_key, &content)?;
3283 let response = self.client.send(request).await?;
3284 Ok(response)
3285 }
3286
3287 #[cfg(feature = "experimental-encrypted-state-events")]
3335 pub fn send_state_event_for_key<'a, C, K>(
3336 &'a self,
3337 state_key: &K,
3338 content: C,
3339 ) -> SendStateEvent<'a>
3340 where
3341 C: StateEventContent,
3342 C::StateKey: Borrow<K>,
3343 K: AsRef<str> + ?Sized,
3344 {
3345 SendStateEvent::new(self, state_key, content)
3346 }
3347
3348 #[instrument(skip_all)]
3388 pub fn send_state_event_raw<'a>(
3389 &'a self,
3390 event_type: &'a str,
3391 state_key: &'a str,
3392 content: impl IntoRawStateEventContent,
3393 ) -> SendRawStateEvent<'a> {
3394 SendRawStateEvent::new(self, event_type, state_key, content)
3395 }
3396
3397 #[instrument(skip_all)]
3430 pub async fn redact(
3431 &self,
3432 event_id: &EventId,
3433 reason: Option<&str>,
3434 txn_id: Option<OwnedTransactionId>,
3435 ) -> HttpResult<redact_event::v3::Response> {
3436 let txn_id = txn_id.unwrap_or_else(TransactionId::new);
3437 let request = assign!(
3438 redact_event::v3::Request::new(self.room_id().to_owned(), event_id.to_owned(), txn_id),
3439 { reason: reason.map(ToOwned::to_owned) }
3440 );
3441
3442 self.client.send(request).await
3443 }
3444
3445 pub async fn route(&self) -> Result<Vec<OwnedServerName>> {
3454 let acl_ev = self
3455 .get_state_event_static::<RoomServerAclEventContent>()
3456 .await?
3457 .and_then(|ev| ev.deserialize().ok());
3458 let acl = acl_ev.as_ref().and_then(|ev| match ev {
3459 SyncOrStrippedState::Sync(ev) => ev.as_original().map(|ev| &ev.content),
3460 SyncOrStrippedState::Stripped(ev) => Some(&ev.content),
3461 });
3462
3463 let members: Vec<_> = self
3468 .members_no_sync(RoomMemberships::JOIN)
3469 .await?
3470 .into_iter()
3471 .filter(|member| {
3472 let server = member.user_id().server_name();
3473 acl.filter(|acl| !acl.is_allowed(server)).is_none() && !server.is_ip_literal()
3474 })
3475 .collect();
3476
3477 let max = members
3480 .iter()
3481 .max_by_key(|member| member.power_level())
3482 .filter(|max| max.power_level() >= int!(50))
3483 .map(|member| member.user_id().server_name());
3484
3485 let servers = members
3487 .iter()
3488 .map(|member| member.user_id().server_name())
3489 .filter(|server| max.filter(|max| max == server).is_none())
3490 .fold(BTreeMap::<_, u32>::new(), |mut servers, server| {
3491 *servers.entry(server).or_default() += 1;
3492 servers
3493 });
3494 let mut servers: Vec<_> = servers.into_iter().collect();
3495 servers.sort_unstable_by(|(_, count_a), (_, count_b)| count_b.cmp(count_a));
3496
3497 Ok(max
3498 .into_iter()
3499 .chain(servers.into_iter().map(|(name, _)| name))
3500 .take(3)
3501 .map(ToOwned::to_owned)
3502 .collect())
3503 }
3504
3505 pub async fn matrix_to_permalink(&self) -> Result<MatrixToUri> {
3512 if let Some(alias) = self.canonical_alias().or_else(|| self.alt_aliases().pop()) {
3513 return Ok(alias.matrix_to_uri());
3514 }
3515
3516 let via = self.route().await?;
3517 Ok(self.room_id().matrix_to_uri_via(via))
3518 }
3519
3520 pub async fn matrix_permalink(&self, join: bool) -> Result<MatrixUri> {
3531 if let Some(alias) = self.canonical_alias().or_else(|| self.alt_aliases().pop()) {
3532 return Ok(alias.matrix_uri(join));
3533 }
3534
3535 let via = self.route().await?;
3536 Ok(self.room_id().matrix_uri_via(via, join))
3537 }
3538
3539 pub async fn matrix_to_event_permalink(
3553 &self,
3554 event_id: impl Into<OwnedEventId>,
3555 ) -> Result<MatrixToUri> {
3556 let via = self.route().await?;
3559 Ok(self.room_id().matrix_to_event_uri_via(event_id, via))
3560 }
3561
3562 pub async fn matrix_event_permalink(
3576 &self,
3577 event_id: impl Into<OwnedEventId>,
3578 ) -> Result<MatrixUri> {
3579 let via = self.route().await?;
3582 Ok(self.room_id().matrix_event_uri_via(event_id, via))
3583 }
3584
3585 pub async fn load_user_receipt(
3596 &self,
3597 receipt_type: ReceiptType,
3598 receipt_thread: &ReceiptThread,
3599 user_id: &UserId,
3600 ) -> Result<Option<(OwnedEventId, Receipt)>> {
3601 self.inner
3602 .load_user_receipt(receipt_type, receipt_thread, user_id)
3603 .await
3604 .map_err(Into::into)
3605 }
3606
3607 pub async fn load_event_receipts(
3618 &self,
3619 receipt_type: ReceiptType,
3620 receipt_thread: &ReceiptThread,
3621 event_id: &EventId,
3622 ) -> Result<Vec<(OwnedUserId, Receipt)>> {
3623 self.inner
3624 .load_event_receipts(receipt_type, receipt_thread, event_id)
3625 .await
3626 .map_err(Into::into)
3627 }
3628
3629 pub async fn push_condition_room_ctx(&self) -> Result<Option<PushConditionRoomCtx>> {
3634 self.push_condition_room_ctx_internal(self.client.enabled_thread_subscriptions().await?)
3635 .await
3636 }
3637
3638 pub(crate) async fn push_condition_room_ctx_internal(
3645 &self,
3646 with_threads_subscriptions: bool,
3647 ) -> Result<Option<PushConditionRoomCtx>> {
3648 let room_id = self.room_id();
3649 let user_id = self.own_user_id();
3650 let room_info = self.clone_info();
3651 let member_count = room_info.active_members_count();
3652
3653 let user_display_name = if let Some(member) = self.get_member_no_sync(user_id).await? {
3654 member.name().to_owned()
3655 } else {
3656 return Ok(None);
3657 };
3658
3659 let power_levels = match self.power_levels().await {
3660 Ok(power_levels) => Some(power_levels.into()),
3661 Err(error) => {
3662 if matches!(room_info.state(), RoomState::Joined) {
3663 error!("Could not compute power levels for push conditions: {error}");
3666 }
3667 None
3668 }
3669 };
3670
3671 let mut ctx = assign!(PushConditionRoomCtx::new(
3672 room_id.to_owned(),
3673 UInt::new(member_count).unwrap_or(UInt::MAX),
3674 user_id.to_owned(),
3675 user_display_name,
3676 ),
3677 {
3678 power_levels,
3679 });
3680
3681 if with_threads_subscriptions {
3682 let this = self.clone();
3683 ctx = ctx.with_has_thread_subscription_fn(move |event_id: &EventId| {
3684 let room = this.clone();
3685 Box::pin(async move {
3686 if let Ok(maybe_sub) = room.load_or_fetch_thread_subscription(event_id).await {
3687 maybe_sub.is_some()
3688 } else {
3689 false
3690 }
3691 })
3692 });
3693 }
3694
3695 Ok(Some(ctx))
3696 }
3697
3698 pub async fn push_context(&self) -> Result<Option<PushContext>> {
3701 self.push_context_internal(self.client.enabled_thread_subscriptions().await?).await
3702 }
3703
3704 #[instrument(skip(self))]
3708 pub(crate) async fn push_context_internal(
3709 &self,
3710 with_threads_subscriptions: bool,
3711 ) -> Result<Option<PushContext>> {
3712 let Some(push_condition_room_ctx) =
3713 self.push_condition_room_ctx_internal(with_threads_subscriptions).await?
3714 else {
3715 debug!("Could not aggregate push context");
3716 return Ok(None);
3717 };
3718 let push_rules = self.client().account().push_rules().await?;
3719 Ok(Some(PushContext::new(push_condition_room_ctx, push_rules)))
3720 }
3721
3722 pub async fn event_push_actions<T>(&self, event: &Raw<T>) -> Result<Option<Vec<Action>>> {
3727 if let Some(ctx) = self.push_context().await? {
3728 Ok(Some(ctx.for_event(event).await))
3729 } else {
3730 Ok(None)
3731 }
3732 }
3733
3734 pub async fn invite_details(&self) -> Result<Invite> {
3737 let state = self.state();
3738
3739 if state != RoomState::Invited {
3740 return Err(Error::WrongRoomState(Box::new(WrongRoomState::new("Invited", state))));
3741 }
3742
3743 let invitee = self
3744 .get_member_no_sync(self.own_user_id())
3745 .await?
3746 .ok_or_else(|| Error::UnknownError(Box::new(InvitationError::EventMissing)))?;
3747 let event = invitee.event();
3748
3749 let inviter_id = event.sender().to_owned();
3750 let inviter = self.get_member_no_sync(&inviter_id).await?;
3751
3752 Ok(Invite { invitee, inviter_id, inviter })
3753 }
3754
3755 pub async fn member_with_sender_info(
3763 &self,
3764 user_id: &UserId,
3765 ) -> Result<RoomMemberWithSenderInfo> {
3766 let Some(member) = self.get_member_no_sync(user_id).await? else {
3767 return Err(Error::InsufficientData);
3768 };
3769
3770 let sender_member = if let Some(member) =
3771 self.get_member_no_sync(member.event().sender()).await?
3772 {
3773 Some(member)
3775 } else if self.are_members_synced() {
3776 None
3778 } else if self.sync_members().await.is_ok() {
3779 self.get_member_no_sync(member.event().sender()).await?
3781 } else {
3782 None
3783 };
3784
3785 Ok(RoomMemberWithSenderInfo { room_member: member, sender_info: sender_member })
3786 }
3787
3788 pub async fn forget(&self) -> Result<()> {
3794 let state = self.state();
3795 match state {
3796 RoomState::Joined | RoomState::Invited | RoomState::Knocked => {
3797 return Err(Error::WrongRoomState(Box::new(WrongRoomState::new(
3798 "Left / Banned",
3799 state,
3800 ))));
3801 }
3802 RoomState::Left | RoomState::Banned => {}
3803 }
3804
3805 let room_id = self.room_id();
3806
3807 let request = forget_room::v3::Request::new(room_id.to_owned());
3808 let _response = self.client.send(request).await?;
3809
3810 if self.inner.direct_targets_length() != 0
3813 && let Err(e) = self.set_is_direct(false).await
3814 {
3815 warn!(?room_id, "failed to remove room from m.direct account data: {e}");
3818 }
3819
3820 self.client.base_client().forget_room(room_id).await?;
3821
3822 if let Some(event_cache) = self.client.event_cache() {
3823 event_cache.forget_room(room_id).await?;
3824 }
3825
3826 Ok(())
3827 }
3828
3829 fn ensure_room_joined(&self) -> Result<()> {
3830 let state = self.state();
3831 if state == RoomState::Joined {
3832 Ok(())
3833 } else {
3834 Err(Error::WrongRoomState(Box::new(WrongRoomState::new("Joined", state))))
3835 }
3836 }
3837
3838 pub async fn notification_mode(&self) -> Option<RoomNotificationMode> {
3840 if !matches!(self.state(), RoomState::Joined) {
3841 return None;
3842 }
3843
3844 let notification_settings = self.client().notification_settings().await;
3845
3846 let notification_mode =
3848 notification_settings.get_user_defined_room_notification_mode(self.room_id()).await;
3849
3850 if notification_mode.is_some() {
3851 notification_mode
3852 } else if let Ok(is_encrypted) =
3853 self.latest_encryption_state().await.map(|state| state.is_encrypted())
3854 {
3855 let is_one_to_one = IsOneToOne::from(self.active_members_count() == 2);
3860 let default_mode = notification_settings
3861 .get_default_room_notification_mode(IsEncrypted::from(is_encrypted), is_one_to_one)
3862 .await;
3863 Some(default_mode)
3864 } else {
3865 None
3866 }
3867 }
3868
3869 pub async fn user_defined_notification_mode(&self) -> Option<RoomNotificationMode> {
3879 if !matches!(self.state(), RoomState::Joined) {
3880 return None;
3881 }
3882
3883 let notification_settings = self.client().notification_settings().await;
3884
3885 let mode =
3887 notification_settings.get_user_defined_room_notification_mode(self.room_id()).await;
3888
3889 if let Some(mode) = mode {
3890 self.update_cached_user_defined_notification_mode(mode);
3891 }
3892
3893 mode
3894 }
3895
3896 pub async fn report_content(
3909 &self,
3910 event_id: OwnedEventId,
3911 reason: Option<String>,
3912 ) -> Result<report_content::v3::Response> {
3913 let state = self.state();
3914 if state != RoomState::Joined {
3915 return Err(Error::WrongRoomState(Box::new(WrongRoomState::new("Joined", state))));
3916 }
3917
3918 let request = assign!(
3919 report_content::v3::Request::new(
3920 self.inner.room_id().to_owned(),
3921 event_id,
3922 ), {
3923 reason: reason
3924 }
3925 );
3926 Ok(self.client.send(request).await?)
3927 }
3928
3929 pub async fn report_room(&self, reason: String) -> Result<report_room::v3::Response> {
3940 let request = report_room::v3::Request::new(self.inner.room_id().to_owned(), reason);
3941
3942 Ok(self.client.send(request).await?)
3943 }
3944
3945 pub async fn set_unread_flag(&self, unread: bool) -> Result<()> {
3951 if self.is_marked_unread() == unread {
3952 return Ok(());
3954 }
3955
3956 let user_id = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
3957
3958 let content = MarkedUnreadEventContent::new(unread);
3959
3960 let request = set_room_account_data::v3::Request::new(
3961 user_id.to_owned(),
3962 self.inner.room_id().to_owned(),
3963 &content,
3964 )?;
3965
3966 self.client.send(request).await?;
3967 Ok(())
3968 }
3969
3970 pub(crate) async fn get_user_beacon_info(
3977 &self,
3978 user_id: &UserId,
3979 ) -> Result<OriginalSyncStateEvent<BeaconInfoEventContent>, BeaconError> {
3980 let raw_event = self
3981 .get_state_event_static_for_key::<BeaconInfoEventContent, _>(user_id)
3982 .await?
3983 .ok_or(BeaconError::NotFound)?;
3984
3985 match raw_event.deserialize()? {
3986 SyncOrStrippedState::Sync(SyncStateEvent::Original(beacon_info)) => Ok(beacon_info),
3987 SyncOrStrippedState::Sync(SyncStateEvent::Redacted(_)) => Err(BeaconError::Redacted),
3988 SyncOrStrippedState::Stripped(_) => Err(BeaconError::Stripped),
3989 }
3990 }
3991
3992 pub async fn start_live_location_share(
4005 &self,
4006 duration_millis: u64,
4007 description: Option<String>,
4008 ) -> Result<send_state_event::v3::Response> {
4009 self.ensure_room_joined()?;
4010
4011 self.send_state_event_for_key(
4012 self.own_user_id(),
4013 BeaconInfoEventContent::new(
4014 description,
4015 Duration::from_millis(duration_millis),
4016 true,
4017 None,
4018 ),
4019 )
4020 .await
4021 }
4022
4023 pub async fn stop_live_location_share(
4031 &self,
4032 ) -> Result<send_state_event::v3::Response, BeaconError> {
4033 self.ensure_room_joined()?;
4034
4035 let mut beacon_info_event = self.get_user_beacon_info(self.own_user_id()).await?;
4036
4037 if beacon_info_event.content.live {
4038 beacon_info_event.content.stop();
4039 Ok(self.send_state_event_for_key(self.own_user_id(), beacon_info_event.content).await?)
4040 } else {
4041 Err(BeaconError::NotLive)
4042 }
4043 }
4044
4045 pub async fn send_location_beacon(
4057 &self,
4058 geo_uri: String,
4059 ) -> Result<send_message_event::v3::Response, BeaconError> {
4060 self.ensure_room_joined()?;
4061
4062 let beacon_info_event = self.get_user_beacon_info(self.own_user_id()).await?;
4063
4064 if beacon_info_event.content.is_live() {
4065 let content = BeaconEventContent::new(beacon_info_event.event_id, geo_uri, None);
4066 Ok(self
4067 .send(content)
4068 .with_request_config(RequestConfig::new().retry_limit(6))
4069 .await?
4070 .response)
4071 } else {
4072 Err(BeaconError::NotLive)
4073 }
4074 }
4075
4076 pub async fn save_composer_draft(
4079 &self,
4080 draft: ComposerDraft,
4081 thread_root: Option<&EventId>,
4082 ) -> Result<()> {
4083 self.client
4084 .state_store()
4085 .set_kv_data(
4086 StateStoreDataKey::ComposerDraft(self.room_id(), thread_root),
4087 StateStoreDataValue::ComposerDraft(draft),
4088 )
4089 .await?;
4090 Ok(())
4091 }
4092
4093 pub async fn load_composer_draft(
4096 &self,
4097 thread_root: Option<&EventId>,
4098 ) -> Result<Option<ComposerDraft>> {
4099 let data = self
4100 .client
4101 .state_store()
4102 .get_kv_data(StateStoreDataKey::ComposerDraft(self.room_id(), thread_root))
4103 .await?;
4104 Ok(data.and_then(|d| d.into_composer_draft()))
4105 }
4106
4107 pub async fn clear_composer_draft(&self, thread_root: Option<&EventId>) -> Result<()> {
4110 self.client
4111 .state_store()
4112 .remove_kv_data(StateStoreDataKey::ComposerDraft(self.room_id(), thread_root))
4113 .await?;
4114 Ok(())
4115 }
4116
4117 pub async fn load_pinned_events(&self) -> Result<Option<Vec<OwnedEventId>>> {
4120 let response = self
4121 .client
4122 .send(get_state_event_for_key::v3::Request::new(
4123 self.room_id().to_owned(),
4124 StateEventType::RoomPinnedEvents,
4125 "".to_owned(),
4126 ))
4127 .await;
4128
4129 match response {
4130 Ok(response) => Ok(Some(
4131 response
4132 .into_content()
4133 .deserialize_as_unchecked::<RoomPinnedEventsEventContent>()?
4134 .pinned,
4135 )),
4136 Err(http_error) => match http_error.as_client_api_error() {
4137 Some(error) if error.status_code == StatusCode::NOT_FOUND => Ok(None),
4138 _ => Err(http_error.into()),
4139 },
4140 }
4141 }
4142
4143 pub async fn subscribe_to_knock_requests(
4158 &self,
4159 ) -> Result<(impl Stream<Item = Vec<KnockRequest>> + use<>, JoinHandle<()>)> {
4160 let this = Arc::new(self.clone());
4161
4162 let room_member_events_observer =
4163 self.client.observe_room_events::<SyncRoomMemberEvent, (Client, Room)>(this.room_id());
4164
4165 let current_seen_ids = self.get_seen_knock_request_ids().await?;
4166 let mut seen_request_ids_stream = self
4167 .seen_knock_request_ids_map
4168 .subscribe()
4169 .await
4170 .map(|values| values.unwrap_or_default());
4171
4172 let mut room_info_stream = self.subscribe_info();
4173
4174 let clear_seen_ids_handle = spawn({
4177 let this = self.clone();
4178 async move {
4179 let mut member_updates_stream = this.room_member_updates_sender.subscribe();
4180 while member_updates_stream.recv().await.is_ok() {
4181 if let Err(err) = this.remove_outdated_seen_knock_requests_ids().await {
4184 warn!("Failed to remove seen knock requests: {err}")
4185 }
4186 }
4187 }
4188 });
4189
4190 let combined_stream = stream! {
4191 match this.get_current_join_requests(¤t_seen_ids).await {
4193 Ok(initial_requests) => yield initial_requests,
4194 Err(err) => warn!("Failed to get initial requests to join: {err}")
4195 }
4196
4197 let mut requests_stream = room_member_events_observer.subscribe();
4198 let mut seen_ids = current_seen_ids.clone();
4199
4200 loop {
4201 tokio::select! {
4204 Some((event, _)) = requests_stream.next() => {
4205 if let Some(event) = event.as_original() {
4206 let emit = if event.prev_content().is_some() {
4208 matches!(event.membership_change(),
4209 MembershipChange::Banned |
4210 MembershipChange::Knocked |
4211 MembershipChange::KnockAccepted |
4212 MembershipChange::KnockDenied |
4213 MembershipChange::KnockRetracted
4214 )
4215 } else {
4216 true
4219 };
4220
4221 if emit {
4222 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 new member event: {err}")
4226 }
4227 }
4228 }
4229 }
4230 }
4231
4232 Some(new_seen_ids) = seen_request_ids_stream.next() => {
4233 seen_ids = new_seen_ids;
4235
4236 match this.get_current_join_requests(&seen_ids).await {
4239 Ok(requests) => yield requests,
4240 Err(err) => {
4241 warn!("Failed to get updated knock requests on seen ids changed: {err}")
4242 }
4243 }
4244 }
4245
4246 Some(room_info) = room_info_stream.next() => {
4247 if !room_info.are_members_synced() {
4251 match this.get_current_join_requests(&seen_ids).await {
4252 Ok(requests) => yield requests,
4253 Err(err) => {
4254 warn!("Failed to get updated knock requests on gappy (limited) sync: {err}")
4255 }
4256 }
4257 }
4258 }
4259 else => break,
4261 }
4262 }
4263 };
4264
4265 Ok((combined_stream, clear_seen_ids_handle))
4266 }
4267
4268 async fn get_current_join_requests(
4269 &self,
4270 seen_request_ids: &BTreeMap<OwnedEventId, OwnedUserId>,
4271 ) -> Result<Vec<KnockRequest>> {
4272 Ok(self
4273 .members(RoomMemberships::KNOCK)
4274 .await?
4275 .into_iter()
4276 .filter_map(|member| {
4277 let event_id = member.event().event_id()?;
4278 Some(KnockRequest::new(
4279 self,
4280 event_id,
4281 member.event().timestamp(),
4282 KnockRequestMemberInfo::from_member(&member),
4283 seen_request_ids.contains_key(event_id),
4284 ))
4285 })
4286 .collect())
4287 }
4288
4289 pub async fn effective_retention(&self) -> Result<Option<RoomRetentionEventContent>> {
4303 let config = match self.client.get_retention_configuration().await {
4304 Ok(config) => config,
4305 Err(e) if e.is_endpoint_not_implemented() => {
4306 debug!(
4307 room_id = ?self.room_id(),
4308 "Homeserver doesn't implement the MSC1763 retention configuration \
4309 endpoint; treating this room as having no effective retention policy"
4310 );
4311 return Ok(None);
4312 }
4313 Err(e) => return Err(e.into()),
4314 };
4315 Ok(self.effective_retention_with_server_config(&config))
4316 }
4317
4318 pub fn effective_retention_with_server_config(
4336 &self,
4337 config: &get_retention_configuration::unstable::Response,
4338 ) -> Option<RoomRetentionEventContent> {
4339 use ruma::api::client::retention::{
4340 RoomIdOrAllRooms, get_retention_configuration::unstable::LifetimeLimits,
4341 };
4342
4343 fn clamp(value: Option<Duration>, limits: &Option<LifetimeLimits>) -> Option<Duration> {
4344 let Some(limits) = limits else { return value };
4347 let Some(value) = value else {
4348 return limits.min();
4351 };
4352 let min = limits.min().unwrap_or(Duration::ZERO);
4354 let max = limits.max().unwrap_or(Duration::MAX);
4355 Some(value.clamp(min, max))
4356 }
4357
4358 if let Some(policy) =
4360 config.policies.get(&RoomIdOrAllRooms::RoomId(self.room_id().to_owned()))
4361 {
4362 return Some(policy.clone());
4363 }
4364
4365 let Some(room_policy) = self.retention() else {
4367 return config.policies.get(&RoomIdOrAllRooms::AllRooms).cloned();
4368 };
4369
4370 let max_lifetime = clamp(room_policy.max_lifetime(), &config.limits.max_lifetime);
4372 let min_lifetime = clamp(room_policy.min_lifetime(), &config.limits.min_lifetime);
4373
4374 let min_lifetime = min_lifetime.map(|min| {
4381 let Some(max) = max_lifetime else { return min };
4382 if min > max {
4383 warn!(
4384 room_id = ?self.room_id(),
4385 ?min, ?max,
4386 "Retention min_lifetime exceeds max_lifetime after clamping to server limits; \
4387 capping min_lifetime to max_lifetime"
4388 );
4389 max
4390 } else {
4391 min
4392 }
4393 });
4394
4395 match (max_lifetime, min_lifetime) {
4396 (Some(max), Some(min)) => RoomRetentionEventContent::from_range(min..=max),
4397 (Some(max), None) => RoomRetentionEventContent::new().at_most(max),
4398 (None, Some(min)) => RoomRetentionEventContent::new().at_least(min),
4399 (None, None) => Some(RoomRetentionEventContent::new()),
4400 }
4401 }
4402
4403 pub fn privacy_settings(&self) -> RoomPrivacySettings<'_> {
4405 RoomPrivacySettings::new(&self.inner, &self.client)
4406 }
4407
4408 pub async fn list_threads(&self, opts: ListThreadsOptions) -> Result<ThreadRoots> {
4416 let request = opts.into_request(self.room_id());
4417
4418 let response = self.client.send(request).await?;
4419
4420 let push_ctx = self.push_context().await?;
4421 let chunk = join_all(
4422 response.chunk.into_iter().map(|ev| self.try_decrypt_event(ev, push_ctx.as_ref())),
4423 )
4424 .await;
4425
4426 Ok(ThreadRoots { chunk, prev_batch_token: response.next_batch })
4427 }
4428
4429 pub async fn relations(
4443 &self,
4444 event_id: OwnedEventId,
4445 opts: RelationsOptions,
4446 ) -> Result<Relations> {
4447 opts.send(self, event_id).await
4448 }
4449
4450 #[instrument(skip(self), fields(room_id = %self.room_id()))]
4472 pub async fn subscribe_thread(
4473 &self,
4474 thread_root: OwnedEventId,
4475 automatic: Option<OwnedEventId>,
4476 ) -> Result<()> {
4477 let is_automatic = automatic.is_some();
4478
4479 match self
4480 .client
4481 .send(subscribe_thread::unstable::Request::new(
4482 self.room_id().to_owned(),
4483 thread_root.clone(),
4484 automatic,
4485 ))
4486 .await
4487 {
4488 Ok(_response) => {
4489 trace!("Server acknowledged the thread subscription; saving in db");
4490
4491 self.client
4493 .state_store()
4494 .upsert_thread_subscriptions(vec![(
4495 self.room_id(),
4496 &thread_root,
4497 StoredThreadSubscription {
4498 status: ThreadSubscriptionStatus::Subscribed {
4499 automatic: is_automatic,
4500 },
4501 bump_stamp: None,
4502 },
4503 )])
4504 .await?;
4505
4506 Ok(())
4507 }
4508
4509 Err(err) => {
4510 if let Some(ErrorKind::ConflictingUnsubscription) = err.client_api_error_kind() {
4511 trace!("Thread subscription skipped: {err}");
4517 Ok(())
4518 } else {
4519 Err(err.into())
4521 }
4522 }
4523 }
4524 }
4525
4526 pub async fn subscribe_thread_if_needed(
4532 &self,
4533 thread_root: &EventId,
4534 automatic: Option<OwnedEventId>,
4535 ) -> Result<()> {
4536 if let Some(prev_sub) = self.load_or_fetch_thread_subscription(thread_root).await? {
4537 if !prev_sub.automatic || automatic.is_some() {
4540 return Ok(());
4544 }
4545 }
4546 self.subscribe_thread(thread_root.to_owned(), automatic).await
4547 }
4548
4549 #[instrument(skip(self), fields(room_id = %self.room_id()))]
4561 pub async fn unsubscribe_thread(&self, thread_root: OwnedEventId) -> Result<()> {
4562 self.client
4563 .send(unsubscribe_thread::unstable::Request::new(
4564 self.room_id().to_owned(),
4565 thread_root.clone(),
4566 ))
4567 .await?;
4568
4569 trace!("Server acknowledged the thread subscription removal; removed it from db too");
4570
4571 self.client
4573 .state_store()
4574 .upsert_thread_subscriptions(vec![(
4575 self.room_id(),
4576 &thread_root,
4577 StoredThreadSubscription {
4578 status: ThreadSubscriptionStatus::Unsubscribed,
4579 bump_stamp: None,
4580 },
4581 )])
4582 .await?;
4583
4584 Ok(())
4585 }
4586
4587 #[instrument(skip(self), fields(room_id = %self.room_id()))]
4604 pub async fn fetch_thread_subscription(
4605 &self,
4606 thread_root: OwnedEventId,
4607 ) -> Result<Option<ThreadSubscription>> {
4608 let result = self
4609 .client
4610 .send(get_thread_subscription::unstable::Request::new(
4611 self.room_id().to_owned(),
4612 thread_root.clone(),
4613 ))
4614 .await;
4615
4616 let subscription = match result {
4617 Ok(response) => Some(ThreadSubscription { automatic: response.automatic }),
4618 Err(http_error) => match http_error.as_client_api_error() {
4619 Some(error) if error.status_code == StatusCode::NOT_FOUND => None,
4620 _ => return Err(http_error.into()),
4621 },
4622 };
4623
4624 if let Some(sub) = &subscription {
4626 self.client
4627 .state_store()
4628 .upsert_thread_subscriptions(vec![(
4629 self.room_id(),
4630 &thread_root,
4631 StoredThreadSubscription {
4632 status: ThreadSubscriptionStatus::Subscribed { automatic: sub.automatic },
4633 bump_stamp: None,
4634 },
4635 )])
4636 .await?;
4637 } else {
4638 self.client
4640 .state_store()
4641 .remove_thread_subscription(self.room_id(), &thread_root)
4642 .await?;
4643 }
4644
4645 Ok(subscription)
4646 }
4647
4648 pub async fn load_or_fetch_thread_subscription(
4655 &self,
4656 thread_root: &EventId,
4657 ) -> Result<Option<ThreadSubscription>> {
4658 if self.client.thread_subscription_catchup().is_outdated() {
4660 return self.fetch_thread_subscription(thread_root.to_owned()).await;
4661 }
4662
4663 Ok(self
4665 .client
4666 .state_store()
4667 .load_thread_subscription(self.room_id(), thread_root)
4668 .await
4669 .map(|maybe_sub| {
4670 maybe_sub.and_then(|stored| match stored.status {
4671 ThreadSubscriptionStatus::Unsubscribed => None,
4672 ThreadSubscriptionStatus::Subscribed { automatic } => {
4673 Some(ThreadSubscription { automatic })
4674 }
4675 })
4676 })?)
4677 }
4678
4679 pub async fn pin_event(&self, event_id: &EventId) -> Result<bool> {
4689 let mut pinned_event_ids = if let Some(event_ids) = self.pinned_event_ids() {
4690 event_ids
4691 } else {
4692 self.load_pinned_events().await?.unwrap_or_default()
4693 };
4694 let event_id = event_id.to_owned();
4695 if pinned_event_ids.contains(&event_id) {
4696 Ok(false)
4697 } else {
4698 pinned_event_ids.push(event_id);
4699 let content = RoomPinnedEventsEventContent::new(pinned_event_ids);
4700 self.send_state_event(content).await?;
4701 Ok(true)
4702 }
4703 }
4704
4705 pub async fn unpin_event(&self, event_id: &EventId) -> Result<bool> {
4715 let mut pinned_event_ids = if let Some(event_ids) = self.pinned_event_ids() {
4716 event_ids
4717 } else {
4718 self.load_pinned_events().await?.unwrap_or_default()
4719 };
4720 let event_id = event_id.to_owned();
4721 if let Some(idx) = pinned_event_ids.iter().position(|e| *e == *event_id) {
4722 pinned_event_ids.remove(idx);
4723 let content = RoomPinnedEventsEventContent::new(pinned_event_ids);
4724 self.send_state_event(content).await?;
4725 Ok(true)
4726 } else {
4727 Ok(false)
4728 }
4729 }
4730
4731 pub async fn compute_is_dm(&self) -> Result<bool> {
4734 Ok(self.inner.compute_is_dm(self.client.dm_room_definition()).await?)
4735 }
4736
4737 pub fn is_dm(&self) -> bool {
4742 let is_direct = self.direct_targets_length() == 1;
4744 match self.client.dm_room_definition() {
4745 DmRoomDefinition::MatrixSpec => {
4746 is_direct
4748 }
4749 DmRoomDefinition::TwoMembers => {
4750 let active_service_member_count =
4754 self.active_service_members_count().unwrap_or_else(|| {
4755 self.service_members().map(|members| members.len()).unwrap_or_default()
4758 as u64
4759 });
4760 let has_at_most_two_active_members =
4761 self.active_members_count().saturating_sub(active_service_member_count) <= 2;
4762 is_direct && has_at_most_two_active_members
4763 }
4764 }
4765 }
4766}
4767
4768#[cfg(feature = "e2e-encryption")]
4769impl RoomIdentityProvider for Room {
4770 fn is_member<'a>(&'a self, user_id: &'a UserId) -> BoxFuture<'a, bool> {
4771 Box::pin(async { self.get_member(user_id).await.unwrap_or(None).is_some() })
4772 }
4773
4774 fn member_identities(&self) -> BoxFuture<'_, Vec<UserIdentity>> {
4775 Box::pin(async {
4776 let members = self
4777 .members(RoomMemberships::JOIN | RoomMemberships::INVITE)
4778 .await
4779 .unwrap_or_else(|_| Default::default());
4780
4781 let mut ret: Vec<UserIdentity> = Vec::new();
4782 for member in members {
4783 if let Some(i) = self.user_identity(member.user_id()).await {
4784 ret.push(i);
4785 }
4786 }
4787 ret
4788 })
4789 }
4790
4791 fn user_identity<'a>(&'a self, user_id: &'a UserId) -> BoxFuture<'a, Option<UserIdentity>> {
4792 Box::pin(async {
4793 self.client
4794 .encryption()
4795 .get_user_identity(user_id)
4796 .await
4797 .unwrap_or(None)
4798 .map(|u| u.underlying_identity())
4799 })
4800 }
4801}
4802
4803#[derive(Clone, Debug)]
4806pub(crate) struct WeakRoom {
4807 client: WeakClient,
4808 room_id: OwnedRoomId,
4809}
4810
4811impl WeakRoom {
4812 pub fn new(client: WeakClient, room_id: OwnedRoomId) -> Self {
4814 Self { client, room_id }
4815 }
4816
4817 pub fn get(&self) -> Option<Room> {
4819 self.client.get().and_then(|client| client.get_room(&self.room_id))
4820 }
4821
4822 pub fn room_id(&self) -> &RoomId {
4824 &self.room_id
4825 }
4826}
4827
4828#[derive(Debug, Clone)]
4830pub struct Invite {
4831 pub invitee: RoomMember,
4833
4834 pub inviter_id: OwnedUserId,
4838
4839 pub inviter: Option<RoomMember>,
4843}
4844
4845#[derive(Error, Debug)]
4846enum InvitationError {
4847 #[error("No membership event found")]
4848 EventMissing,
4849}
4850
4851#[derive(Debug, Clone, Default)]
4853#[non_exhaustive]
4854pub struct Receipts {
4855 pub fully_read: Option<OwnedEventId>,
4857 pub public_read_receipt: Option<OwnedEventId>,
4859 pub private_read_receipt: Option<OwnedEventId>,
4861}
4862
4863impl Receipts {
4864 pub fn new() -> Self {
4866 Self::default()
4867 }
4868
4869 pub fn fully_read_marker(mut self, event_id: impl Into<Option<OwnedEventId>>) -> Self {
4878 self.fully_read = event_id.into();
4879 self
4880 }
4881
4882 pub fn public_read_receipt(mut self, event_id: impl Into<Option<OwnedEventId>>) -> Self {
4888 self.public_read_receipt = event_id.into();
4889 self
4890 }
4891
4892 pub fn private_read_receipt(mut self, event_id: impl Into<Option<OwnedEventId>>) -> Self {
4896 self.private_read_receipt = event_id.into();
4897 self
4898 }
4899
4900 pub fn is_empty(&self) -> bool {
4902 self.fully_read.is_none()
4903 && self.public_read_receipt.is_none()
4904 && self.private_read_receipt.is_none()
4905 }
4906}
4907
4908#[derive(Debug)]
4911pub enum ParentSpace {
4912 Reciprocal(Room),
4915 WithPowerlevel(Room),
4920 Illegitimate(Room),
4923 Unverifiable(OwnedRoomId),
4926}
4927
4928pub(crate) trait EventSource {
4929 fn get_event(
4930 &self,
4931 event_id: &EventId,
4932 ) -> impl Future<Output = Result<TimelineEvent, Error>> + SendOutsideWasm;
4933}
4934
4935impl EventSource for &Room {
4936 async fn get_event(&self, event_id: &EventId) -> Result<TimelineEvent, Error> {
4937 self.load_or_fetch_event(event_id, None).await
4938 }
4939}
4940
4941#[derive(Debug)]
4944pub struct RoomMemberWithSenderInfo {
4945 pub room_member: RoomMember,
4947 pub sender_info: Option<RoomMember>,
4950}
4951
4952fn has_state_events_of_type(update: &RoomUpdate, event_type: &StateEventType) -> bool {
4956 let RoomUpdate::Joined { updates, .. } = update else {
4958 return false;
4959 };
4960
4961 let (State::Before(state_events) | State::After(state_events)) = &updates.state;
4962
4963 state_events.iter().any(|raw| {
4964 raw.get_field::<StateEventType>("type")
4965 .ok()
4966 .flatten()
4967 .is_some_and(|received_type| received_type == *event_type)
4968 })
4969}
4970
4971#[cfg(all(test, not(target_family = "wasm")))]
4972mod tests {
4973 use std::{collections::BTreeMap, time::Duration};
4974
4975 use matrix_sdk_base::{ComposerDraft, DraftAttachment, store::ComposerDraftType};
4976 use matrix_sdk_test::{
4977 JoinedRoomBuilder, SyncResponseBuilder, async_test, event_factory::EventFactory,
4978 };
4979 use ruma::{
4980 RoomVersionId, event_id,
4981 events::{
4982 relation::RelationType,
4983 room::{member::MembershipState, retention::RoomRetentionEventContent},
4984 },
4985 owned_event_id, room_id, user_id,
4986 };
4987 use wiremock::{
4988 Mock, MockServer, ResponseTemplate,
4989 matchers::{header, method, path, path_regex},
4990 };
4991
4992 use crate::{
4993 Client, Room,
4994 config::RequestConfig,
4995 room::messages::{IncludeRelations, ListThreadsOptions, RelationsOptions},
4996 test_utils::{
4997 client::mock_matrix_session,
4998 logged_in_client,
4999 mocks::{MatrixMockServer, RoomRelationsResponseTemplate},
5000 },
5001 };
5002
5003 #[cfg(all(feature = "sqlite", feature = "e2e-encryption"))]
5004 #[async_test]
5005 async fn test_cache_invalidation_while_encrypt() {
5006 use matrix_sdk_base::store::RoomLoadSettings;
5007 use matrix_sdk_test::{DEFAULT_TEST_ROOM_ID, message_like_event_content};
5008
5009 let tmp_dir = tempfile::tempdir().unwrap();
5010 let sqlite_path = tmp_dir.path().join("cache_invalidation_while_encrypt.db");
5011 let session = mock_matrix_session();
5012
5013 let client = Client::builder()
5014 .homeserver_url("http://localhost:1234")
5015 .request_config(RequestConfig::new().disable_retry())
5016 .sqlite_store(&sqlite_path, None)
5017 .build()
5018 .await
5019 .unwrap();
5020 client
5021 .matrix_auth()
5022 .restore_session(session.clone(), RoomLoadSettings::default())
5023 .await
5024 .unwrap();
5025
5026 client.encryption().enable_cross_process_store_lock("client1".to_owned()).await.unwrap();
5027
5028 let server = MockServer::start().await;
5030 {
5031 Mock::given(method("GET"))
5032 .and(path_regex(r"^/_matrix/client/r0/rooms/.*/state/m.*room.*encryption.?"))
5033 .and(header("authorization", "Bearer 1234"))
5034 .respond_with(
5035 ResponseTemplate::new(200)
5036 .set_body_json(EventFactory::new().room_encryption().into_content()),
5037 )
5038 .mount(&server)
5039 .await;
5040 let f = EventFactory::new().sender(user_id!("@example:localhost"));
5041 let response = SyncResponseBuilder::default()
5042 .add_joined_room(
5043 JoinedRoomBuilder::default()
5044 .add_state_event(
5045 f.member(user_id!("@example:localhost")).display_name("example"),
5046 )
5047 .add_state_event(f.default_power_levels())
5048 .add_state_event(f.room_encryption()),
5049 )
5050 .build_sync_response();
5051 client.base_client().receive_sync_response(response).await.unwrap();
5052 }
5053
5054 let room = client.get_room(&DEFAULT_TEST_ROOM_ID).expect("Room should exist");
5055
5056 room.preshare_room_key().await.unwrap();
5058
5059 {
5062 let client = Client::builder()
5063 .homeserver_url("http://localhost:1234")
5064 .request_config(RequestConfig::new().disable_retry())
5065 .sqlite_store(&sqlite_path, None)
5066 .build()
5067 .await
5068 .unwrap();
5069 client
5070 .matrix_auth()
5071 .restore_session(session.clone(), RoomLoadSettings::default())
5072 .await
5073 .unwrap();
5074 client
5075 .encryption()
5076 .enable_cross_process_store_lock("client2".to_owned())
5077 .await
5078 .unwrap();
5079
5080 let guard = client.encryption().spin_lock_store(None).await.unwrap();
5081 assert!(guard.is_some());
5082 }
5083
5084 let guard = client.encryption().spin_lock_store(None).await.unwrap();
5086 assert!(guard.is_some());
5087
5088 let olm = client.olm_machine().await;
5090 let olm = olm.as_ref().expect("Olm machine wasn't started");
5091
5092 let _encrypted_content = olm
5095 .encrypt_room_event_raw(room.room_id(), "test-event", &message_like_event_content!({}))
5096 .await
5097 .unwrap();
5098 }
5099
5100 #[async_test]
5101 async fn test_composer_draft() {
5102 use matrix_sdk_test::DEFAULT_TEST_ROOM_ID;
5103
5104 let client = logged_in_client(None).await;
5105
5106 let response = SyncResponseBuilder::default()
5107 .add_joined_room(JoinedRoomBuilder::default())
5108 .build_sync_response();
5109 client.base_client().receive_sync_response(response).await.unwrap();
5110 let room = client.get_room(&DEFAULT_TEST_ROOM_ID).expect("Room should exist");
5111
5112 assert_eq!(room.load_composer_draft(None).await.unwrap(), None);
5113
5114 let draft = ComposerDraft {
5117 plain_text: "Hello, world!".to_owned(),
5118 html_text: Some("<strong>Hello</strong>, world!".to_owned()),
5119 draft_type: ComposerDraftType::NewMessage,
5120 attachments: vec![DraftAttachment {
5121 filename: "cat.txt".to_owned(),
5122 content: matrix_sdk_base::DraftAttachmentContent::File {
5123 data: b"meow".to_vec(),
5124 mimetype: Some("text/plain".to_owned()),
5125 size: Some(5),
5126 },
5127 }],
5128 };
5129
5130 room.save_composer_draft(draft.clone(), None).await.unwrap();
5131
5132 let thread_root = owned_event_id!("$thread_root:b.c");
5133 let thread_draft = ComposerDraft {
5134 plain_text: "Hello, thread!".to_owned(),
5135 html_text: Some("<strong>Hello</strong>, thread!".to_owned()),
5136 draft_type: ComposerDraftType::NewMessage,
5137 attachments: vec![DraftAttachment {
5138 filename: "dog.txt".to_owned(),
5139 content: matrix_sdk_base::DraftAttachmentContent::File {
5140 data: b"wuv".to_vec(),
5141 mimetype: Some("text/plain".to_owned()),
5142 size: Some(4),
5143 },
5144 }],
5145 };
5146
5147 room.save_composer_draft(thread_draft.clone(), Some(&thread_root)).await.unwrap();
5148
5149 assert_eq!(room.load_composer_draft(None).await.unwrap(), Some(draft));
5151
5152 assert_eq!(
5154 room.load_composer_draft(Some(&thread_root)).await.unwrap(),
5155 Some(thread_draft.clone())
5156 );
5157
5158 room.clear_composer_draft(None).await.unwrap();
5160 assert_eq!(room.load_composer_draft(None).await.unwrap(), None);
5161
5162 assert_eq!(room.load_composer_draft(Some(&thread_root)).await.unwrap(), Some(thread_draft));
5164
5165 room.clear_composer_draft(Some(&thread_root)).await.unwrap();
5167 assert_eq!(room.load_composer_draft(Some(&thread_root)).await.unwrap(), None);
5168 }
5169
5170 #[async_test]
5171 async fn test_mark_join_requests_as_seen() {
5172 let server = MatrixMockServer::new().await;
5173 let client = server.client_builder().build().await;
5174 let event_id = event_id!("$a:b.c");
5175 let room_id = room_id!("!a:b.c");
5176 let user_id = user_id!("@alice:b.c");
5177
5178 let f = EventFactory::new().room(room_id);
5179 let joined_room_builder = JoinedRoomBuilder::new(room_id).add_state_bulk(vec![
5180 f.member(user_id).membership(MembershipState::Knock).event_id(event_id).into(),
5181 ]);
5182 let room = server.sync_room(&client, joined_room_builder).await;
5183
5184 let seen_ids =
5186 room.get_seen_knock_request_ids().await.expect("Couldn't load seen join request ids");
5187 assert!(seen_ids.is_empty());
5188
5189 room.mark_knock_requests_as_seen(&[user_id.to_owned()])
5191 .await
5192 .expect("Couldn't mark join request as seen");
5193
5194 let seen_ids =
5196 room.get_seen_knock_request_ids().await.expect("Couldn't load seen join request ids");
5197 assert_eq!(seen_ids.len(), 1);
5198 assert_eq!(
5199 seen_ids.into_iter().next().expect("No next value"),
5200 (event_id.to_owned(), user_id.to_owned())
5201 )
5202 }
5203
5204 #[async_test]
5205 async fn test_own_room_membership_with_no_own_member_event() {
5206 let server = MatrixMockServer::new().await;
5207 let client = server.client_builder().build().await;
5208 let room_id = room_id!("!a:b.c");
5209
5210 let room = server.sync_joined_room(&client, room_id).await;
5211
5212 let error = room.member_with_sender_info(client.user_id().unwrap()).await.err();
5215 assert!(error.is_some());
5216 }
5217
5218 #[async_test]
5219 async fn test_own_room_membership_with_own_member_event_but_unknown_sender() {
5220 let server = MatrixMockServer::new().await;
5221 let client = server.client_builder().build().await;
5222 let room_id = room_id!("!a:b.c");
5223 let user_id = user_id!("@example:localhost");
5224
5225 let f = EventFactory::new().room(room_id).sender(user_id!("@alice:b.c"));
5226 let joined_room_builder =
5227 JoinedRoomBuilder::new(room_id).add_state_bulk(vec![f.member(user_id).into()]);
5228 let room = server.sync_room(&client, joined_room_builder).await;
5229
5230 let ret = room
5232 .member_with_sender_info(client.user_id().unwrap())
5233 .await
5234 .expect("Room member info should be available");
5235
5236 assert_eq!(ret.room_member.event().user_id(), user_id);
5238
5239 assert!(ret.sender_info.is_none());
5241 }
5242
5243 #[async_test]
5244 async fn test_own_room_membership_with_own_member_event_and_own_sender() {
5245 let server = MatrixMockServer::new().await;
5246 let client = server.client_builder().build().await;
5247 let room_id = room_id!("!a:b.c");
5248 let user_id = user_id!("@example:localhost");
5249
5250 let f = EventFactory::new().room(room_id).sender(user_id);
5251 let joined_room_builder =
5252 JoinedRoomBuilder::new(room_id).add_state_bulk(vec![f.member(user_id).into()]);
5253 let room = server.sync_room(&client, joined_room_builder).await;
5254
5255 let ret = room
5257 .member_with_sender_info(client.user_id().unwrap())
5258 .await
5259 .expect("Room member info should be available");
5260
5261 assert_eq!(ret.room_member.event().user_id(), user_id);
5263
5264 assert!(ret.sender_info.is_some());
5266 assert_eq!(ret.sender_info.unwrap().event().user_id(), user_id);
5267 }
5268
5269 #[async_test]
5270 async fn test_own_room_membership_with_own_member_event_and_known_sender() {
5271 let server = MatrixMockServer::new().await;
5272 let client = server.client_builder().build().await;
5273 let room_id = room_id!("!a:b.c");
5274 let user_id = user_id!("@example:localhost");
5275 let sender_id = user_id!("@alice:b.c");
5276
5277 let f = EventFactory::new().room(room_id).sender(sender_id);
5278 let joined_room_builder = JoinedRoomBuilder::new(room_id).add_state_bulk(vec![
5279 f.member(user_id).into(),
5280 f.member(sender_id).into(),
5282 ]);
5283 let room = server.sync_room(&client, joined_room_builder).await;
5284
5285 let ret = room
5287 .member_with_sender_info(client.user_id().unwrap())
5288 .await
5289 .expect("Room member info should be available");
5290
5291 assert_eq!(ret.room_member.event().user_id(), user_id);
5293
5294 assert!(ret.sender_info.is_some());
5296 assert_eq!(ret.sender_info.unwrap().event().user_id(), sender_id);
5297 }
5298
5299 #[async_test]
5300 async fn test_own_room_membership_with_own_member_event_and_unknown_but_available_sender() {
5301 let server = MatrixMockServer::new().await;
5302 let client = server.client_builder().build().await;
5303 let room_id = room_id!("!a:b.c");
5304 let user_id = user_id!("@example:localhost");
5305 let sender_id = user_id!("@alice:b.c");
5306
5307 let f = EventFactory::new().room(room_id).sender(sender_id);
5308 let joined_room_builder =
5309 JoinedRoomBuilder::new(room_id).add_state_bulk(vec![f.member(user_id).into()]);
5310 let room = server.sync_room(&client, joined_room_builder).await;
5311
5312 server
5314 .mock_get_members()
5315 .ok(vec![f.member(sender_id).into_raw()])
5316 .mock_once()
5317 .mount()
5318 .await;
5319
5320 let ret = room
5322 .member_with_sender_info(client.user_id().unwrap())
5323 .await
5324 .expect("Room member info should be available");
5325
5326 assert_eq!(ret.room_member.event().user_id(), user_id);
5328
5329 assert!(ret.sender_info.is_some());
5331 assert_eq!(ret.sender_info.unwrap().event().user_id(), sender_id);
5332 }
5333
5334 #[async_test]
5335 async fn test_list_threads() {
5336 let server = MatrixMockServer::new().await;
5337 let client = server.client_builder().build().await;
5338
5339 let room_id = room_id!("!a:b.c");
5340 let sender_id = user_id!("@alice:b.c");
5341 let f = EventFactory::new().room(room_id).sender(sender_id);
5342
5343 let eid1 = event_id!("$1");
5344 let eid2 = event_id!("$2");
5345 let batch1 = vec![f.text_msg("Thread root 1").event_id(eid1).into_raw()];
5346 let batch2 = vec![f.text_msg("Thread root 2").event_id(eid2).into_raw()];
5347
5348 server
5349 .mock_room_threads()
5350 .ok(batch1.clone(), Some("prev_batch".to_owned()))
5351 .mock_once()
5352 .mount()
5353 .await;
5354 server
5355 .mock_room_threads()
5356 .match_from("prev_batch")
5357 .ok(batch2, None)
5358 .mock_once()
5359 .mount()
5360 .await;
5361
5362 let room = server.sync_joined_room(&client, room_id).await;
5363 let result =
5364 room.list_threads(ListThreadsOptions::default()).await.expect("Failed to list threads");
5365 assert_eq!(result.chunk.len(), 1);
5366 assert_eq!(result.chunk[0].event_id().unwrap(), eid1);
5367 assert!(result.prev_batch_token.is_some());
5368
5369 let opts = ListThreadsOptions { from: result.prev_batch_token, ..Default::default() };
5370 let result = room.list_threads(opts).await.expect("Failed to list threads");
5371 assert_eq!(result.chunk.len(), 1);
5372 assert_eq!(result.chunk[0].event_id().unwrap(), eid2);
5373 assert!(result.prev_batch_token.is_none());
5374 }
5375
5376 #[async_test]
5377 async fn test_relations() {
5378 let server = MatrixMockServer::new().await;
5379 let client = server.client_builder().build().await;
5380
5381 let room_id = room_id!("!a:b.c");
5382 let sender_id = user_id!("@alice:b.c");
5383 let f = EventFactory::new().room(room_id).sender(sender_id);
5384
5385 let target_event_id = owned_event_id!("$target");
5386 let eid1 = event_id!("$1");
5387 let eid2 = event_id!("$2");
5388 let batch1 = vec![f.text_msg("Related event 1").event_id(eid1).into_raw()];
5389 let batch2 = vec![f.text_msg("Related event 2").event_id(eid2).into_raw()];
5390
5391 server
5392 .mock_room_relations()
5393 .match_target_event(target_event_id.clone())
5394 .ok(RoomRelationsResponseTemplate::default().events(batch1).next_batch("next_batch"))
5395 .mock_once()
5396 .mount()
5397 .await;
5398
5399 server
5400 .mock_room_relations()
5401 .match_target_event(target_event_id.clone())
5402 .match_from("next_batch")
5403 .ok(RoomRelationsResponseTemplate::default().events(batch2))
5404 .mock_once()
5405 .mount()
5406 .await;
5407
5408 let room = server.sync_joined_room(&client, room_id).await;
5409
5410 let mut opts = RelationsOptions {
5412 include_relations: IncludeRelations::AllRelations,
5413 ..Default::default()
5414 };
5415 let result = room
5416 .relations(target_event_id.clone(), opts.clone())
5417 .await
5418 .expect("Failed to list relations the first time");
5419 assert_eq!(result.chunk.len(), 1);
5420 assert_eq!(result.chunk[0].event_id().unwrap(), eid1);
5421 assert!(result.prev_batch_token.is_none());
5422 assert!(result.next_batch_token.is_some());
5423 assert!(result.recursion_depth.is_none());
5424
5425 opts.from = result.next_batch_token;
5426 let result = room
5427 .relations(target_event_id, opts)
5428 .await
5429 .expect("Failed to list relations the second time");
5430 assert_eq!(result.chunk.len(), 1);
5431 assert_eq!(result.chunk[0].event_id().unwrap(), eid2);
5432 assert!(result.prev_batch_token.is_none());
5433 assert!(result.next_batch_token.is_none());
5434 assert!(result.recursion_depth.is_none());
5435 }
5436
5437 #[async_test]
5438 async fn test_relations_with_reltype() {
5439 let server = MatrixMockServer::new().await;
5440 let client = server.client_builder().build().await;
5441
5442 let room_id = room_id!("!a:b.c");
5443 let sender_id = user_id!("@alice:b.c");
5444 let f = EventFactory::new().room(room_id).sender(sender_id);
5445
5446 let target_event_id = owned_event_id!("$target");
5447 let eid1 = event_id!("$1");
5448 let eid2 = event_id!("$2");
5449 let batch1 = vec![f.text_msg("In-thread event 1").event_id(eid1).into_raw()];
5450 let batch2 = vec![f.text_msg("In-thread event 2").event_id(eid2).into_raw()];
5451
5452 server
5453 .mock_room_relations()
5454 .match_target_event(target_event_id.clone())
5455 .match_subrequest(IncludeRelations::RelationsOfType(RelationType::Thread))
5456 .ok(RoomRelationsResponseTemplate::default().events(batch1).next_batch("next_batch"))
5457 .mock_once()
5458 .mount()
5459 .await;
5460
5461 server
5462 .mock_room_relations()
5463 .match_target_event(target_event_id.clone())
5464 .match_from("next_batch")
5465 .match_subrequest(IncludeRelations::RelationsOfType(RelationType::Thread))
5466 .ok(RoomRelationsResponseTemplate::default().events(batch2))
5467 .mock_once()
5468 .mount()
5469 .await;
5470
5471 let room = server.sync_joined_room(&client, room_id).await;
5472
5473 let mut opts = RelationsOptions {
5475 include_relations: IncludeRelations::RelationsOfType(RelationType::Thread),
5476 ..Default::default()
5477 };
5478 let result = room
5479 .relations(target_event_id.clone(), opts.clone())
5480 .await
5481 .expect("Failed to list relations the first time");
5482 assert_eq!(result.chunk.len(), 1);
5483 assert_eq!(result.chunk[0].event_id().unwrap(), eid1);
5484 assert!(result.prev_batch_token.is_none());
5485 assert!(result.next_batch_token.is_some());
5486 assert!(result.recursion_depth.is_none());
5487
5488 opts.from = result.next_batch_token;
5489 let result = room
5490 .relations(target_event_id, opts)
5491 .await
5492 .expect("Failed to list relations the second time");
5493 assert_eq!(result.chunk.len(), 1);
5494 assert_eq!(result.chunk[0].event_id().unwrap(), eid2);
5495 assert!(result.prev_batch_token.is_none());
5496 assert!(result.next_batch_token.is_none());
5497 assert!(result.recursion_depth.is_none());
5498 }
5499
5500 #[async_test]
5501 async fn test_power_levels_computation() {
5502 let server = MatrixMockServer::new().await;
5503 let client = server.client_builder().build().await;
5504
5505 let room_id = room_id!("!a:b.c");
5506 let sender_id = client.user_id().expect("No session id");
5507 let f = EventFactory::new().room(room_id).sender(sender_id);
5508 let mut user_map = BTreeMap::from([(sender_id.into(), 50.into())]);
5509
5510 let room_create_event = f.create(sender_id, RoomVersionId::V1).state_key("").into();
5512 let power_levels_event = f.power_levels(&mut user_map).state_key("").into();
5513 let room_member_event = f.member(sender_id).into();
5514
5515 let room = server
5517 .sync_room(&client, JoinedRoomBuilder::new(room_id).add_state_bulk([room_member_event]))
5518 .await;
5519 let ctx = room
5520 .push_condition_room_ctx()
5521 .await
5522 .expect("Failed to get push condition context")
5523 .expect("Could not get push condition context");
5524
5525 assert!(ctx.power_levels.is_none());
5527
5528 let room = server
5530 .sync_room(&client, JoinedRoomBuilder::new(room_id).add_state_bulk([room_create_event]))
5531 .await;
5532 let ctx = room
5533 .push_condition_room_ctx()
5534 .await
5535 .expect("Failed to get push condition context")
5536 .expect("Could not get push condition context");
5537
5538 assert!(ctx.power_levels.is_none());
5540
5541 let room = server
5543 .sync_room(
5544 &client,
5545 JoinedRoomBuilder::new(room_id).add_state_bulk([power_levels_event]),
5546 )
5547 .await;
5548 let ctx = room
5549 .push_condition_room_ctx()
5550 .await
5551 .expect("Failed to get push condition context")
5552 .expect("Could not get push condition context");
5553
5554 assert!(ctx.power_levels.is_some());
5556 }
5557
5558 const ONE_DAY: Duration = Duration::from_secs(86_400);
5561 const ONE_WEEK: Duration = Duration::from_secs(86_400 * 7);
5562 const ONE_HOUR: Duration = Duration::from_secs(3_600);
5563
5564 fn retention_room_id() -> &'static ruma::RoomId {
5565 room_id!("!a:b.c")
5566 }
5567
5568 async fn mock_retention_config(
5569 server: &MatrixMockServer,
5570 body: serde_json::Value,
5571 ) -> wiremock::MockGuard {
5572 Mock::given(method("GET"))
5573 .and(path("/_matrix/client/unstable/org.matrix.msc1763/retention/configuration"))
5574 .respond_with(ResponseTemplate::new(200).set_body_json(body))
5575 .mount_as_scoped(server.server())
5576 .await
5577 }
5578
5579 async fn room_with_retention(
5580 server: &MatrixMockServer,
5581 client: &Client,
5582 content: RoomRetentionEventContent,
5583 ) -> Room {
5584 server
5585 .sync_room(
5586 client,
5587 JoinedRoomBuilder::new(retention_room_id()).add_state_event(
5588 EventFactory::new().sender(user_id!("@alice:b.c")).event(content).state_key(""),
5589 ),
5590 )
5591 .await
5592 }
5593
5594 #[async_test]
5595 async fn test_effective_retention_no_policy_anywhere() {
5596 let server = MatrixMockServer::new().await;
5597 let client = server.client_builder().build().await;
5598 let room = server.sync_joined_room(&client, retention_room_id()).await;
5599
5600 let _mock = mock_retention_config(
5601 &server,
5602 serde_json::json!({
5603 "policies": {},
5604 "limits": {},
5605 }),
5606 )
5607 .await;
5608
5609 let result = room.effective_retention().await.unwrap();
5610 assert!(result.is_none());
5611 }
5612
5613 #[async_test]
5614 async fn test_effective_retention_endpoint_not_implemented() {
5615 let server = MatrixMockServer::new().await;
5620 let client = server.client_builder().build().await;
5621 let room = room_with_retention(
5622 &server,
5623 &client,
5624 RoomRetentionEventContent::new().at_most(ONE_DAY).unwrap(),
5625 )
5626 .await;
5627
5628 Mock::given(method("GET"))
5629 .and(path("/_matrix/client/unstable/org.matrix.msc1763/retention/configuration"))
5630 .respond_with(ResponseTemplate::new(404).set_body_json(serde_json::json!({
5631 "errcode": "M_UNRECOGNIZED",
5632 "error": "Unrecognized request",
5633 })))
5634 .mount(server.server())
5635 .await;
5636
5637 let result = room.effective_retention().await.unwrap();
5638 assert!(result.is_none());
5639 }
5640
5641 #[async_test]
5642 async fn test_effective_retention_server_default_only() {
5643 let server = MatrixMockServer::new().await;
5644 let client = server.client_builder().build().await;
5645 let room = server.sync_joined_room(&client, retention_room_id()).await;
5646
5647 let _mock = mock_retention_config(
5648 &server,
5649 serde_json::json!({
5650 "policies": {
5651 "*": { "max_lifetime": ONE_WEEK.as_millis() as u64 }
5652 },
5653 "limits": {},
5654 }),
5655 )
5656 .await;
5657
5658 let result = room.effective_retention().await.unwrap().unwrap();
5659 assert_eq!(result.max_lifetime(), Some(ONE_WEEK));
5660 assert!(result.min_lifetime().is_none());
5661 }
5662
5663 #[async_test]
5664 async fn test_effective_retention_server_room_override() {
5665 let server = MatrixMockServer::new().await;
5668 let client = server.client_builder().build().await;
5669 let room = room_with_retention(
5670 &server,
5671 &client,
5672 RoomRetentionEventContent::new().at_most(ONE_DAY).unwrap(),
5673 )
5674 .await;
5675
5676 let _mock = mock_retention_config(
5677 &server,
5678 serde_json::json!({
5679 "policies": {
5680 "*": { "max_lifetime": ONE_WEEK.as_millis() as u64 },
5681 "!a:b.c": { "max_lifetime": ONE_HOUR.as_millis() as u64 }
5682 },
5683 "limits": {},
5684 }),
5685 )
5686 .await;
5687
5688 let result = room.effective_retention().await.unwrap().unwrap();
5689 assert_eq!(result.max_lifetime(), Some(ONE_HOUR));
5690 }
5691
5692 #[async_test]
5693 async fn test_effective_retention_room_policy_with_limits() {
5694 struct TestCase {
5695 description: &'static str,
5696 room_max: Option<Duration>,
5697 room_min: Option<Duration>,
5698 limits: serde_json::Value,
5699 expected_max: Option<Duration>,
5700 expected_min: Option<Duration>,
5701 }
5702
5703 let test_cases = vec![
5704 TestCase {
5705 description: "room sets `max_lifetime` only, no server limits configured",
5706 room_max: Some(ONE_DAY),
5707 room_min: None,
5708 limits: serde_json::json!({}),
5709 expected_max: Some(ONE_DAY),
5710 expected_min: None,
5711 },
5712 TestCase {
5713 description: "room sets `min_lifetime` only, no server limits configured",
5714 room_max: None,
5715 room_min: Some(ONE_HOUR),
5716 limits: serde_json::json!({}),
5717 expected_max: None,
5718 expected_min: Some(ONE_HOUR),
5719 },
5720 TestCase {
5721 description: "room sets `max_lifetime` and `min_lifetime`, no server limits configured",
5722 room_max: Some(ONE_DAY),
5723 room_min: Some(ONE_HOUR),
5724 limits: serde_json::json!({}),
5725 expected_max: Some(ONE_DAY),
5726 expected_min: Some(ONE_HOUR),
5727 },
5728 TestCase {
5729 description: "room `max_lifetime` above server `max_lifetime.max` → outcome uses server `max_lifetime.max`",
5730 room_max: Some(ONE_WEEK),
5731 room_min: None,
5732 limits: serde_json::json!({
5733 "max_lifetime": { "max": ONE_DAY.as_millis() as u64 }
5734 }),
5735 expected_max: Some(ONE_DAY),
5736 expected_min: None,
5737 },
5738 TestCase {
5739 description: "room `max_lifetime` below server `max_lifetime.min` → outcome uses server `max_lifetime.min`",
5740 room_max: Some(ONE_HOUR),
5741 room_min: None,
5742 limits: serde_json::json!({
5743 "max_lifetime": { "min": ONE_DAY.as_millis() as u64 }
5744 }),
5745 expected_max: Some(ONE_DAY),
5746 expected_min: None,
5747 },
5748 TestCase {
5749 description: "room `min_lifetime` above server `min_lifetime.max` → outcome uses server `min_lifetime.max`",
5750 room_max: None,
5751 room_min: Some(ONE_WEEK),
5752 limits: serde_json::json!({
5753 "min_lifetime": { "max": ONE_DAY.as_millis() as u64 }
5754 }),
5755 expected_max: None,
5756 expected_min: Some(ONE_DAY),
5757 },
5758 TestCase {
5759 description: "room `min_lifetime` below server `min_lifetime.min` → outcome uses server `min_lifetime.min`",
5760 room_max: None,
5761 room_min: Some(ONE_HOUR),
5762 limits: serde_json::json!({
5763 "min_lifetime": { "min": ONE_DAY.as_millis() as u64 }
5764 }),
5765 expected_max: None,
5766 expected_min: Some(ONE_DAY),
5767 },
5768 TestCase {
5769 description: "room `max_lifetime` and `min_lifetime` both within their respective server limits → unchanged",
5770 room_max: Some(ONE_DAY),
5771 room_min: Some(ONE_HOUR),
5772 limits: serde_json::json!({
5773 "max_lifetime": {
5774 "min": ONE_HOUR.as_millis() as u64,
5775 "max": ONE_WEEK.as_millis() as u64,
5776 },
5777 "min_lifetime": {
5778 "min": Duration::from_secs(60).as_millis() as u64,
5779 "max": ONE_DAY.as_millis() as u64,
5780 },
5781 }),
5782 expected_max: Some(ONE_DAY),
5783 expected_min: Some(ONE_HOUR),
5784 },
5785 TestCase {
5786 description: "room `max_lifetime` omitted, server `max_lifetime.min` is used",
5787 room_max: None,
5788 room_min: None,
5789 limits: serde_json::json!({
5790 "max_lifetime": { "min": ONE_DAY.as_millis() as u64 }
5791 }),
5792 expected_max: Some(ONE_DAY),
5793 expected_min: None,
5794 },
5795 TestCase {
5796 description: "room `min_lifetime` omitted, server `min_lifetime.min` is used",
5797 room_max: None,
5798 room_min: None,
5799 limits: serde_json::json!({
5800 "min_lifetime": { "min": ONE_HOUR.as_millis() as u64 }
5801 }),
5802 expected_max: None,
5803 expected_min: Some(ONE_HOUR),
5804 },
5805 TestCase {
5806 description: "independently-clamped `max_lifetime`/`min_lifetime` cross → `min_lifetime` capped down to `max_lifetime`",
5807 room_max: Some(ONE_DAY),
5808 room_min: Some(ONE_HOUR),
5809 limits: serde_json::json!({
5810 "max_lifetime": { "max": ONE_HOUR.as_millis() as u64 },
5811 "min_lifetime": { "min": ONE_WEEK.as_millis() as u64 },
5812 }),
5813 expected_max: Some(ONE_HOUR),
5814 expected_min: Some(ONE_HOUR),
5815 },
5816 TestCase {
5817 description: "room `max_lifetime` omitted, server sets `max_lifetime.max` but no `.min` → stays unbounded (documents MSC1763 gap)",
5818 room_max: None,
5819 room_min: None,
5820 limits: serde_json::json!({
5821 "max_lifetime": { "max": ONE_WEEK.as_millis() as u64 }
5822 }),
5823 expected_max: None,
5824 expected_min: None,
5825 },
5826 ];
5827
5828 for case in test_cases {
5829 let server = MatrixMockServer::new().await;
5830 let client = server.client_builder().build().await;
5831
5832 let mut content = RoomRetentionEventContent::new();
5833 if let Some(max) = case.room_max {
5834 content = content.at_most(max).unwrap();
5835 }
5836 if let Some(min) = case.room_min {
5837 content = content.at_least(min).unwrap();
5838 }
5839
5840 let room = room_with_retention(&server, &client, content).await;
5841
5842 let _mock = mock_retention_config(
5843 &server,
5844 serde_json::json!({
5845 "policies": {},
5846 "limits": case.limits,
5847 }),
5848 )
5849 .await;
5850
5851 let result = room
5852 .effective_retention()
5853 .await
5854 .unwrap_or_else(|e| panic!("{}: effective_retention failed: {e}", case.description))
5855 .unwrap_or_else(|| panic!("{}: expected Some, got None", case.description));
5856
5857 assert_eq!(result.max_lifetime(), case.expected_max, "{}", case.description);
5858 assert_eq!(result.min_lifetime(), case.expected_min, "{}", case.description);
5859 }
5860 }
5861
5862 #[async_test]
5863 async fn test_send_state_event_raw_with_request_config() {
5864 let server = MatrixMockServer::new().await;
5865 let client = server
5867 .client_builder()
5868 .on_builder(|builder| builder.request_config(RequestConfig::new().retry_limit(3)))
5869 .build()
5870 .await;
5871 let room = server.sync_joined_room(&client, room_id!("!a:b.c")).await;
5872
5873 server.mock_room_send_state().error500().expect(1).mount().await;
5874
5875 room.send_state_event_raw("org.example.custom", "", serde_json::json!({}))
5876 .with_request_config(RequestConfig::new().disable_retry())
5877 .await
5878 .unwrap_err();
5879 }
5880}