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 future::join_all, stream as futures_stream, stream::FuturesUnordered, StreamExt,
31};
32use http::StatusCode;
33#[cfg(feature = "e2e-encryption")]
34pub use identity_status_changes::IdentityStatusChanges;
35#[cfg(feature = "e2e-encryption")]
36use matrix_sdk_base::crypto::{IdentityStatusChange, RoomIdentityProvider, UserIdentity};
37pub use matrix_sdk_base::store::ThreadSubscription;
38#[cfg(feature = "e2e-encryption")]
39use matrix_sdk_base::{crypto::RoomEventDecryptionResult, deserialized_responses::EncryptionInfo};
40use matrix_sdk_base::{
41 deserialized_responses::{
42 RawAnySyncOrStrippedState, RawSyncOrStrippedState, SyncOrStrippedState,
43 },
44 event_cache::store::media::IgnoreMediaRetentionPolicy,
45 media::MediaThumbnailSettings,
46 store::StateStoreExt,
47 ComposerDraft, EncryptionState, RoomInfoNotableUpdateReasons, RoomMemberships, SendOutsideWasm,
48 StateChanges, StateStoreDataKey, StateStoreDataValue,
49};
50#[cfg(feature = "e2e-encryption")]
51use matrix_sdk_common::BoxFuture;
52use matrix_sdk_common::{
53 deserialized_responses::TimelineEvent,
54 executor::{spawn, JoinHandle},
55 timeout::timeout,
56};
57use mime::Mime;
58use reply::Reply;
59#[cfg(feature = "unstable-msc4274")]
60use ruma::events::room::message::GalleryItemType;
61#[cfg(feature = "e2e-encryption")]
62use ruma::events::{
63 room::encrypted::OriginalSyncRoomEncryptedEvent, AnySyncMessageLikeEvent, AnySyncTimelineEvent,
64 SyncMessageLikeEvent,
65};
66use ruma::{
67 api::client::{
68 config::{set_global_account_data, set_room_account_data},
69 context,
70 error::ErrorKind,
71 filter::LazyLoadOptions,
72 membership::{
73 ban_user, forget_room, get_member_events,
74 invite_user::{self, v3::InvitationRecipient},
75 kick_user, leave_room, unban_user, Invite3pid,
76 },
77 message::send_message_event,
78 read_marker::set_read_marker,
79 receipt::create_receipt,
80 redact::redact_event,
81 room::{get_room_event, report_content, report_room},
82 state::{get_state_event_for_key, send_state_event},
83 tag::{create_tag, delete_tag},
84 threads::{get_thread_subscription, subscribe_thread, unsubscribe_thread},
85 typing::create_typing_event::{self, v3::Typing},
86 },
87 assign,
88 events::{
89 beacon::BeaconEventContent,
90 beacon_info::BeaconInfoEventContent,
91 direct::DirectEventContent,
92 marked_unread::MarkedUnreadEventContent,
93 receipt::{Receipt, ReceiptThread, ReceiptType},
94 room::{
95 avatar::{self, RoomAvatarEventContent},
96 encryption::RoomEncryptionEventContent,
97 history_visibility::HistoryVisibility,
98 member::{MembershipChange, SyncRoomMemberEvent},
99 message::{
100 AudioInfo, AudioMessageEventContent, FileInfo, FileMessageEventContent,
101 FormattedBody, ImageMessageEventContent, MessageType, RoomMessageEventContent,
102 UnstableAudioDetailsContentBlock, UnstableVoiceContentBlock, VideoInfo,
103 VideoMessageEventContent,
104 },
105 name::RoomNameEventContent,
106 pinned_events::RoomPinnedEventsEventContent,
107 power_levels::{
108 RoomPowerLevels, RoomPowerLevelsEventContent, RoomPowerLevelsSource, UserPowerLevel,
109 },
110 server_acl::RoomServerAclEventContent,
111 topic::RoomTopicEventContent,
112 ImageInfo, MediaSource, ThumbnailInfo,
113 },
114 space::{child::SpaceChildEventContent, parent::SpaceParentEventContent},
115 tag::{TagInfo, TagName},
116 typing::SyncTypingEvent,
117 AnyRoomAccountDataEvent, AnyRoomAccountDataEventContent, AnyTimelineEvent, EmptyStateKey,
118 Mentions, MessageLikeEventContent, OriginalSyncStateEvent, RedactContent,
119 RedactedStateEventContent, RoomAccountDataEvent, RoomAccountDataEventContent,
120 RoomAccountDataEventType, StateEventContent, StateEventType, StaticEventContent,
121 StaticStateEventContent, SyncStateEvent,
122 },
123 int,
124 push::{Action, PushConditionRoomCtx, Ruleset},
125 serde::Raw,
126 time::Instant,
127 EventId, Int, MatrixToUri, MatrixUri, MxcUri, OwnedEventId, OwnedRoomId, OwnedServerName,
128 OwnedTransactionId, OwnedUserId, RoomId, TransactionId, UInt, UserId,
129};
130use serde::de::DeserializeOwned;
131use thiserror::Error;
132use tokio::{join, sync::broadcast};
133use tracing::{debug, error, info, instrument, trace, warn};
134
135use self::futures::{SendAttachment, SendMessageLikeEvent, SendRawMessageLikeEvent};
136pub use self::{
137 member::{RoomMember, RoomMemberRole},
138 messages::{
139 EventWithContextResponse, IncludeRelations, ListThreadsOptions, Messages, MessagesOptions,
140 Relations, RelationsOptions, ThreadRoots,
141 },
142};
143#[cfg(doc)]
144use crate::event_cache::EventCache;
145use crate::{
146 attachment::{AttachmentConfig, AttachmentInfo},
147 client::WeakClient,
148 config::RequestConfig,
149 error::{BeaconError, WrongRoomState},
150 event_cache::{self, EventCacheDropHandles, RoomEventCache},
151 event_handler::{EventHandler, EventHandlerDropGuard, EventHandlerHandle, SyncEvent},
152 live_location_share::ObservableLiveLocation,
153 media::{MediaFormat, MediaRequestParameters},
154 notification_settings::{IsEncrypted, IsOneToOne, RoomNotificationMode},
155 room::{
156 knock_requests::{KnockRequest, KnockRequestMemberInfo},
157 power_levels::{RoomPowerLevelChanges, RoomPowerLevelsExt},
158 privacy_settings::RoomPrivacySettings,
159 },
160 sync::RoomUpdate,
161 utils::{IntoRawMessageLikeEventContent, IntoRawStateEventContent},
162 BaseRoom, Client, Error, HttpResult, Result, RoomState, TransmissionProgress,
163};
164#[cfg(feature = "e2e-encryption")]
165use crate::{crypto::types::events::CryptoContextInfo, encryption::backups::BackupState};
166
167pub mod edit;
168pub mod futures;
169pub mod identity_status_changes;
170pub mod knock_requests;
172mod member;
173mod messages;
174pub mod power_levels;
175pub mod reply;
176
177pub mod privacy_settings;
179
180#[cfg(feature = "e2e-encryption")]
181pub(crate) mod shared_room_history;
182
183#[derive(Debug, Clone)]
186pub struct Room {
187 inner: BaseRoom,
188 pub(crate) client: Client,
189}
190
191impl Deref for Room {
192 type Target = BaseRoom;
193
194 fn deref(&self) -> &Self::Target {
195 &self.inner
196 }
197}
198
199const TYPING_NOTICE_TIMEOUT: Duration = Duration::from_secs(4);
200const TYPING_NOTICE_RESEND_TIMEOUT: Duration = Duration::from_secs(3);
201
202#[derive(Debug)]
204pub struct PushContext {
205 push_condition_room_ctx: PushConditionRoomCtx,
207
208 push_rules: Ruleset,
211}
212
213impl PushContext {
214 pub fn new(push_condition_room_ctx: PushConditionRoomCtx, push_rules: Ruleset) -> Self {
216 Self { push_condition_room_ctx, push_rules }
217 }
218
219 pub async fn for_event<T>(&self, event: &Raw<T>) -> Vec<Action> {
221 self.push_rules.get_actions(event, &self.push_condition_room_ctx).await.to_owned()
222 }
223}
224
225macro_rules! make_media_type {
226 ($t:ty, $content_type: ident, $filename: ident, $source: ident, $caption: ident, $formatted_caption: ident, $info: ident, $thumbnail: ident) => {{
227 let (body, filename) = match $caption {
231 Some(caption) => (caption, Some($filename)),
232 None => ($filename, None),
233 };
234
235 let (thumbnail_source, thumbnail_info) = $thumbnail.unzip();
236
237 match $content_type.type_() {
238 mime::IMAGE => {
239 let info = assign!($info.map(ImageInfo::from).unwrap_or_default(), {
240 mimetype: Some($content_type.as_ref().to_owned()),
241 thumbnail_source,
242 thumbnail_info
243 });
244 let content = assign!(ImageMessageEventContent::new(body, $source), {
245 info: Some(Box::new(info)),
246 formatted: $formatted_caption,
247 filename
248 });
249 <$t>::Image(content)
250 }
251
252 mime::AUDIO => {
253 let mut content = assign!(AudioMessageEventContent::new(body, $source), {
254 formatted: $formatted_caption,
255 filename
256 });
257
258 if let Some(AttachmentInfo::Voice { audio_info, waveform: Some(waveform_vec) }) =
259 &$info
260 {
261 if let Some(duration) = audio_info.duration {
262 let waveform = waveform_vec.iter().map(|v| (*v).into()).collect();
263 content.audio =
264 Some(UnstableAudioDetailsContentBlock::new(duration, waveform));
265 }
266 content.voice = Some(UnstableVoiceContentBlock::new());
267 }
268
269 let mut audio_info = $info.map(AudioInfo::from).unwrap_or_default();
270 audio_info.mimetype = Some($content_type.as_ref().to_owned());
271 let content = content.info(Box::new(audio_info));
272
273 <$t>::Audio(content)
274 }
275
276 mime::VIDEO => {
277 let info = assign!($info.map(VideoInfo::from).unwrap_or_default(), {
278 mimetype: Some($content_type.as_ref().to_owned()),
279 thumbnail_source,
280 thumbnail_info
281 });
282 let content = assign!(VideoMessageEventContent::new(body, $source), {
283 info: Some(Box::new(info)),
284 formatted: $formatted_caption,
285 filename
286 });
287 <$t>::Video(content)
288 }
289
290 _ => {
291 let info = assign!($info.map(FileInfo::from).unwrap_or_default(), {
292 mimetype: Some($content_type.as_ref().to_owned()),
293 thumbnail_source,
294 thumbnail_info
295 });
296 let content = assign!(FileMessageEventContent::new(body, $source), {
297 info: Some(Box::new(info)),
298 formatted: $formatted_caption,
299 filename,
300 });
301 <$t>::File(content)
302 }
303 }
304 }};
305}
306
307impl Room {
308 pub(crate) fn new(client: Client, room: BaseRoom) -> Self {
315 Self { inner: room, client }
316 }
317
318 #[doc(alias = "reject_invitation")]
324 #[instrument(skip_all, fields(room_id = ?self.inner.room_id()))]
325 async fn leave_impl(&self) -> (Result<()>, &Room) {
326 let state = self.state();
327 if state == RoomState::Left {
328 return (
329 Err(Error::WrongRoomState(Box::new(WrongRoomState::new(
330 "Joined or Invited",
331 state,
332 )))),
333 self,
334 );
335 }
336
337 let should_forget = matches!(self.state(), RoomState::Invited);
340
341 let request = leave_room::v3::Request::new(self.inner.room_id().to_owned());
342 let response = self.client.send(request).await;
343
344 if let Err(error) = response {
347 #[allow(clippy::collapsible_match)]
348 let ignore_error = if let Some(error) = error.client_api_error_kind() {
349 match error {
350 ErrorKind::Forbidden { .. } => true,
353 _ => false,
354 }
355 } else {
356 false
357 };
358
359 error!(?error, ignore_error, should_forget, "Failed to leave the room");
360
361 if !ignore_error {
362 return (Err(error.into()), self);
363 }
364 }
365
366 if let Err(e) = self.client.base_client().room_left(self.room_id()).await {
367 return (Err(e.into()), self);
368 }
369
370 if should_forget {
371 trace!("Trying to forget the room");
372
373 if let Err(error) = self.forget().await {
374 error!(?error, "Failed to forget the room");
375 }
376 }
377
378 (Ok(()), self)
379 }
380
381 pub async fn leave(&self) -> Result<()> {
389 let mut rooms: Vec<Room> = vec![self.clone()];
390 let mut current_room = self;
391
392 while let Some(predecessor) = current_room.predecessor_room() {
393 let maybe_predecessor_room = current_room.client.get_room(&predecessor.room_id);
394
395 if let Some(predecessor_room) = maybe_predecessor_room {
396 rooms.push(predecessor_room.clone());
397 current_room = rooms.last().expect("Room just pushed so can't be empty");
398 } else {
399 warn!("Cannot find predecessor room");
400 break;
401 }
402 }
403
404 let batch_size = 5;
405
406 let rooms_futures: Vec<_> = rooms
407 .iter()
408 .filter_map(|room| match room.state() {
409 RoomState::Joined | RoomState::Invited | RoomState::Knocked => {
410 Some(room.leave_impl())
411 }
412 RoomState::Banned | RoomState::Left => None,
413 })
414 .collect();
415
416 let mut futures_stream = futures_stream::iter(rooms_futures).buffer_unordered(batch_size);
417
418 let mut maybe_this_room_failed_with: Option<Error> = None;
419
420 while let Some(result) = futures_stream.next().await {
421 if let (Err(e), room) = result {
422 if room.room_id() == self.room_id() {
423 maybe_this_room_failed_with = Some(e);
424 } else {
425 warn!("Failure while attempting to leave predecessor room: {e:?}");
426 }
427 }
428 }
429
430 maybe_this_room_failed_with.map_or(Ok(()), Err)
431 }
432
433 #[doc(alias = "accept_invitation")]
437 pub async fn join(&self) -> Result<()> {
438 let prev_room_state = self.inner.state();
439
440 if prev_room_state == RoomState::Joined {
441 return Err(Error::WrongRoomState(Box::new(WrongRoomState::new(
442 "Invited or Left",
443 prev_room_state,
444 ))));
445 }
446
447 self.client.join_room_by_id(self.room_id()).await?;
448
449 Ok(())
450 }
451
452 pub fn client(&self) -> Client {
456 self.client.clone()
457 }
458
459 pub fn is_synced(&self) -> bool {
462 self.inner.is_state_fully_synced()
463 }
464
465 pub async fn avatar(&self, format: MediaFormat) -> Result<Option<Vec<u8>>> {
495 let Some(url) = self.avatar_url() else { return Ok(None) };
496 let request = MediaRequestParameters { source: MediaSource::Plain(url.to_owned()), format };
497 Ok(Some(self.client.media().get_media_content(&request, true).await?))
498 }
499
500 #[instrument(skip_all, fields(room_id = ?self.inner.room_id(), ?options))]
529 pub async fn messages(&self, options: MessagesOptions) -> Result<Messages> {
530 let room_id = self.inner.room_id();
531 let request = options.into_request(room_id);
532 let http_response = self.client.send(request).await?;
533
534 let push_ctx = self.push_context().await?;
535 let chunk = join_all(
536 http_response.chunk.into_iter().map(|ev| self.try_decrypt_event(ev, push_ctx.as_ref())),
537 )
538 .await;
539
540 Ok(Messages {
541 start: http_response.start,
542 end: http_response.end,
543 chunk,
544 state: http_response.state,
545 })
546 }
547
548 pub fn add_event_handler<Ev, Ctx, H>(&self, handler: H) -> EventHandlerHandle
558 where
559 Ev: SyncEvent + DeserializeOwned + Send + 'static,
560 H: EventHandler<Ev, Ctx>,
561 {
562 self.client.add_room_event_handler(self.room_id(), handler)
563 }
564
565 pub fn subscribe_to_updates(&self) -> broadcast::Receiver<RoomUpdate> {
570 self.client.subscribe_to_room_updates(self.room_id())
571 }
572
573 pub fn subscribe_to_typing_notifications(
579 &self,
580 ) -> (EventHandlerDropGuard, broadcast::Receiver<Vec<OwnedUserId>>) {
581 let (sender, receiver) = broadcast::channel(16);
582 let typing_event_handler_handle = self.client.add_room_event_handler(self.room_id(), {
583 let own_user_id = self.own_user_id().to_owned();
584 move |event: SyncTypingEvent| async move {
585 let typing_user_ids = event
587 .content
588 .user_ids
589 .into_iter()
590 .filter(|user_id| *user_id != own_user_id)
591 .collect();
592 let _ = sender.send(typing_user_ids);
594 }
595 });
596 let drop_guard = self.client().event_handler_drop_guard(typing_event_handler_handle);
597 (drop_guard, receiver)
598 }
599
600 #[cfg(feature = "e2e-encryption")]
623 pub async fn subscribe_to_identity_status_changes(
624 &self,
625 ) -> Result<impl Stream<Item = Vec<IdentityStatusChange>>> {
626 IdentityStatusChanges::create_stream(self.clone()).await
627 }
628
629 #[allow(clippy::unused_async)] async fn try_decrypt_event(
635 &self,
636 event: Raw<AnyTimelineEvent>,
637 push_ctx: Option<&PushContext>,
638 ) -> TimelineEvent {
639 #[cfg(feature = "e2e-encryption")]
640 if let Ok(AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomEncrypted(
641 SyncMessageLikeEvent::Original(_),
642 ))) = event.deserialize_as::<AnySyncTimelineEvent>()
643 {
644 if let Ok(event) = self.decrypt_event(event.cast_ref_unchecked(), push_ctx).await {
645 return event;
646 }
647 }
648
649 let mut event = TimelineEvent::from_plaintext(event.cast());
650 if let Some(push_ctx) = push_ctx {
651 event.set_push_actions(push_ctx.for_event(event.raw()).await);
652 }
653
654 event
655 }
656
657 pub async fn event(
662 &self,
663 event_id: &EventId,
664 request_config: Option<RequestConfig>,
665 ) -> Result<TimelineEvent> {
666 let request =
667 get_room_event::v3::Request::new(self.room_id().to_owned(), event_id.to_owned());
668
669 let raw_event = self.client.send(request).with_request_config(request_config).await?.event;
670 let push_ctx = self.push_context().await?;
671 let event = self.try_decrypt_event(raw_event, push_ctx.as_ref()).await;
672
673 if let Ok((cache, _handles)) = self.event_cache().await {
675 cache.save_events([event.clone()]).await;
676 }
677
678 Ok(event)
679 }
680
681 pub async fn load_or_fetch_event(
688 &self,
689 event_id: &EventId,
690 request_config: Option<RequestConfig>,
691 ) -> Result<TimelineEvent> {
692 match self.event_cache().await {
693 Ok((event_cache, _drop_handles)) => {
694 if let Some(event) = event_cache.find_event(event_id).await {
695 return Ok(event);
696 }
697 }
699 Err(err) => {
700 debug!("error when getting the event cache: {err}");
701 }
702 }
703 self.event(event_id, request_config).await
704 }
705
706 pub async fn event_with_context(
709 &self,
710 event_id: &EventId,
711 lazy_load_members: bool,
712 context_size: UInt,
713 request_config: Option<RequestConfig>,
714 ) -> Result<EventWithContextResponse> {
715 let mut request =
716 context::get_context::v3::Request::new(self.room_id().to_owned(), event_id.to_owned());
717
718 request.limit = context_size;
719
720 if lazy_load_members {
721 request.filter.lazy_load_options =
722 LazyLoadOptions::Enabled { include_redundant_members: false };
723 }
724
725 let response = self.client.send(request).with_request_config(request_config).await?;
726
727 let push_ctx = self.push_context().await?;
728 let push_ctx = push_ctx.as_ref();
729 let target_event = if let Some(event) = response.event {
730 Some(self.try_decrypt_event(event, push_ctx).await)
731 } else {
732 None
733 };
734
735 let (events_before, events_after) = join!(
739 join_all(
740 response.events_before.into_iter().map(|ev| self.try_decrypt_event(ev, push_ctx)),
741 ),
742 join_all(
743 response.events_after.into_iter().map(|ev| self.try_decrypt_event(ev, push_ctx)),
744 ),
745 );
746
747 if let Ok((cache, _handles)) = self.event_cache().await {
749 let mut events_to_save: Vec<TimelineEvent> = Vec::new();
750 if let Some(event) = &target_event {
751 events_to_save.push(event.clone());
752 }
753
754 for event in &events_before {
755 events_to_save.push(event.clone());
756 }
757
758 for event in &events_after {
759 events_to_save.push(event.clone());
760 }
761
762 cache.save_events(events_to_save).await;
763 }
764
765 Ok(EventWithContextResponse {
766 event: target_event,
767 events_before,
768 events_after,
769 state: response.state,
770 prev_batch_token: response.start,
771 next_batch_token: response.end,
772 })
773 }
774
775 pub(crate) async fn request_members(&self) -> Result<()> {
776 self.client
777 .locks()
778 .members_request_deduplicated_handler
779 .run(self.room_id().to_owned(), async move {
780 let request = get_member_events::v3::Request::new(self.inner.room_id().to_owned());
781 let response = self
782 .client
783 .send(request.clone())
784 .with_request_config(
785 RequestConfig::new().timeout(Duration::from_secs(60)).retry_limit(3),
788 )
789 .await?;
790
791 Box::pin(self.client.base_client().receive_all_members(
793 self.room_id(),
794 &request,
795 &response,
796 ))
797 .await?;
798
799 Ok(())
800 })
801 .await
802 }
803
804 pub async fn request_encryption_state(&self) -> Result<()> {
809 if !self.inner.encryption_state().is_unknown() {
810 return Ok(());
811 }
812
813 self.client
814 .locks()
815 .encryption_state_deduplicated_handler
816 .run(self.room_id().to_owned(), async move {
817 let request = get_state_event_for_key::v3::Request::new(
819 self.room_id().to_owned(),
820 StateEventType::RoomEncryption,
821 "".to_owned(),
822 );
823 let response = match self.client.send(request).await {
824 Ok(response) => Some(
825 response
826 .into_content()
827 .deserialize_as_unchecked::<RoomEncryptionEventContent>()?,
828 ),
829 Err(err) if err.client_api_error_kind() == Some(&ErrorKind::NotFound) => None,
830 Err(err) => return Err(err.into()),
831 };
832
833 let _sync_lock = self.client.base_client().sync_lock().lock().await;
834
835 let mut room_info = self.clone_info();
838 room_info.mark_encryption_state_synced();
839 room_info.set_encryption_event(response.clone());
840 let mut changes = StateChanges::default();
841 changes.add_room(room_info.clone());
842
843 self.client.state_store().save_changes(&changes).await?;
844 self.set_room_info(room_info, RoomInfoNotableUpdateReasons::empty());
845
846 Ok(())
847 })
848 .await
849 }
850
851 pub fn encryption_state(&self) -> EncryptionState {
856 self.inner.encryption_state()
857 }
858
859 pub async fn latest_encryption_state(&self) -> Result<EncryptionState> {
865 self.request_encryption_state().await?;
866
867 Ok(self.encryption_state())
868 }
869
870 #[cfg(feature = "e2e-encryption")]
872 pub async fn crypto_context_info(&self) -> CryptoContextInfo {
873 let encryption = self.client.encryption();
874
875 let this_device_is_verified = match encryption.get_own_device().await {
876 Ok(Some(device)) => device.is_verified_with_cross_signing(),
877
878 _ => true,
880 };
881
882 let backup_exists_on_server =
883 encryption.backups().exists_on_server().await.unwrap_or(false);
884
885 CryptoContextInfo {
886 device_creation_ts: encryption.device_creation_timestamp().await,
887 this_device_is_verified,
888 is_backup_configured: encryption.backups().state() == BackupState::Enabled,
889 backup_exists_on_server,
890 }
891 }
892
893 fn are_events_visible(&self) -> bool {
894 if let RoomState::Invited = self.inner.state() {
895 return matches!(
896 self.inner.history_visibility_or_default(),
897 HistoryVisibility::WorldReadable | HistoryVisibility::Invited
898 );
899 }
900
901 true
902 }
903
904 pub async fn sync_members(&self) -> Result<()> {
910 if !self.are_events_visible() {
911 return Ok(());
912 }
913
914 if !self.are_members_synced() {
915 self.request_members().await
916 } else {
917 Ok(())
918 }
919 }
920
921 pub async fn get_member(&self, user_id: &UserId) -> Result<Option<RoomMember>> {
935 self.sync_members().await?;
936 self.get_member_no_sync(user_id).await
937 }
938
939 pub async fn get_member_no_sync(&self, user_id: &UserId) -> Result<Option<RoomMember>> {
953 Ok(self
954 .inner
955 .get_member(user_id)
956 .await?
957 .map(|member| RoomMember::new(self.client.clone(), member)))
958 }
959
960 pub async fn members(&self, memberships: RoomMemberships) -> Result<Vec<RoomMember>> {
969 self.sync_members().await?;
970 self.members_no_sync(memberships).await
971 }
972
973 pub async fn members_no_sync(&self, memberships: RoomMemberships) -> Result<Vec<RoomMember>> {
982 Ok(self
983 .inner
984 .members(memberships)
985 .await?
986 .into_iter()
987 .map(|member| RoomMember::new(self.client.clone(), member))
988 .collect())
989 }
990
991 pub async fn get_state_events(
993 &self,
994 event_type: StateEventType,
995 ) -> Result<Vec<RawAnySyncOrStrippedState>> {
996 self.client
997 .state_store()
998 .get_state_events(self.room_id(), event_type)
999 .await
1000 .map_err(Into::into)
1001 }
1002
1003 pub async fn get_state_events_static<C>(&self) -> Result<Vec<RawSyncOrStrippedState<C>>>
1020 where
1021 C: StaticEventContent<IsPrefix = ruma::events::False>
1022 + StaticStateEventContent
1023 + RedactContent,
1024 C::Redacted: RedactedStateEventContent,
1025 {
1026 Ok(self.client.state_store().get_state_events_static(self.room_id()).await?)
1027 }
1028
1029 pub async fn get_state_events_for_keys(
1032 &self,
1033 event_type: StateEventType,
1034 state_keys: &[&str],
1035 ) -> Result<Vec<RawAnySyncOrStrippedState>> {
1036 self.client
1037 .state_store()
1038 .get_state_events_for_keys(self.room_id(), event_type, state_keys)
1039 .await
1040 .map_err(Into::into)
1041 }
1042
1043 pub async fn get_state_events_for_keys_static<'a, C, K, I>(
1063 &self,
1064 state_keys: I,
1065 ) -> Result<Vec<RawSyncOrStrippedState<C>>>
1066 where
1067 C: StaticEventContent<IsPrefix = ruma::events::False>
1068 + StaticStateEventContent
1069 + RedactContent,
1070 C::StateKey: Borrow<K>,
1071 C::Redacted: RedactedStateEventContent,
1072 K: AsRef<str> + Sized + Sync + 'a,
1073 I: IntoIterator<Item = &'a K> + Send,
1074 I::IntoIter: Send,
1075 {
1076 Ok(self
1077 .client
1078 .state_store()
1079 .get_state_events_for_keys_static(self.room_id(), state_keys)
1080 .await?)
1081 }
1082
1083 pub async fn get_state_event(
1085 &self,
1086 event_type: StateEventType,
1087 state_key: &str,
1088 ) -> Result<Option<RawAnySyncOrStrippedState>> {
1089 self.client
1090 .state_store()
1091 .get_state_event(self.room_id(), event_type, state_key)
1092 .await
1093 .map_err(Into::into)
1094 }
1095
1096 pub async fn get_state_event_static<C>(&self) -> Result<Option<RawSyncOrStrippedState<C>>>
1115 where
1116 C: StaticEventContent<IsPrefix = ruma::events::False>
1117 + StaticStateEventContent<StateKey = EmptyStateKey>
1118 + RedactContent,
1119 C::Redacted: RedactedStateEventContent,
1120 {
1121 self.get_state_event_static_for_key(&EmptyStateKey).await
1122 }
1123
1124 pub async fn get_state_event_static_for_key<C, K>(
1144 &self,
1145 state_key: &K,
1146 ) -> Result<Option<RawSyncOrStrippedState<C>>>
1147 where
1148 C: StaticEventContent<IsPrefix = ruma::events::False>
1149 + StaticStateEventContent
1150 + RedactContent,
1151 C::StateKey: Borrow<K>,
1152 C::Redacted: RedactedStateEventContent,
1153 K: AsRef<str> + ?Sized + Sync,
1154 {
1155 Ok(self
1156 .client
1157 .state_store()
1158 .get_state_event_static_for_key(self.room_id(), state_key)
1159 .await?)
1160 }
1161
1162 pub async fn parent_spaces(&self) -> Result<impl Stream<Item = Result<ParentSpace>> + '_> {
1166 Ok(self
1171 .get_state_events_static::<SpaceParentEventContent>()
1172 .await?
1173 .into_iter()
1174 .flat_map(|parent_event| match parent_event.deserialize() {
1176 Ok(SyncOrStrippedState::Sync(SyncStateEvent::Original(e))) => {
1177 Some((e.state_key.to_owned(), e.sender))
1178 }
1179 Ok(SyncOrStrippedState::Sync(SyncStateEvent::Redacted(_))) => None,
1180 Ok(SyncOrStrippedState::Stripped(e)) => Some((e.state_key.to_owned(), e.sender)),
1181 Err(e) => {
1182 info!(room_id = ?self.room_id(), "Could not deserialize m.room.parent: {e}");
1183 None
1184 }
1185 })
1186 .map(|(state_key, sender): (OwnedRoomId, OwnedUserId)| async move {
1188 let Some(parent_room) = self.client.get_room(&state_key) else {
1189 return Ok(ParentSpace::Unverifiable(state_key));
1192 };
1193 if let Some(child_event) = parent_room
1196 .get_state_event_static_for_key::<SpaceChildEventContent, _>(self.room_id())
1197 .await?
1198 {
1199 match child_event.deserialize() {
1200 Ok(SyncOrStrippedState::Sync(SyncStateEvent::Original(_))) => {
1201 return Ok(ParentSpace::Reciprocal(parent_room));
1204 }
1205 Ok(SyncOrStrippedState::Sync(SyncStateEvent::Redacted(_))) => {}
1206 Ok(SyncOrStrippedState::Stripped(_)) => {}
1207 Err(e) => {
1208 info!(
1209 room_id = ?self.room_id(), parent_room_id = ?state_key,
1210 "Could not deserialize m.room.child: {e}"
1211 );
1212 }
1213 }
1214 }
1219
1220 let Some(member) = parent_room.get_member(&sender).await? else {
1223 return Ok(ParentSpace::Illegitimate(parent_room));
1225 };
1226
1227 if member.can_send_state(StateEventType::SpaceChild) {
1228 Ok(ParentSpace::WithPowerlevel(parent_room))
1230 } else {
1231 Ok(ParentSpace::Illegitimate(parent_room))
1232 }
1233 })
1234 .collect::<FuturesUnordered<_>>())
1235 }
1236
1237 pub async fn account_data(
1239 &self,
1240 data_type: RoomAccountDataEventType,
1241 ) -> Result<Option<Raw<AnyRoomAccountDataEvent>>> {
1242 self.client
1243 .state_store()
1244 .get_room_account_data_event(self.room_id(), data_type)
1245 .await
1246 .map_err(Into::into)
1247 }
1248
1249 pub async fn account_data_static<C>(&self) -> Result<Option<Raw<RoomAccountDataEvent<C>>>>
1268 where
1269 C: StaticEventContent<IsPrefix = ruma::events::False> + RoomAccountDataEventContent,
1270 {
1271 Ok(self.account_data(C::TYPE.into()).await?.map(Raw::cast_unchecked))
1272 }
1273
1274 #[cfg(feature = "e2e-encryption")]
1279 pub async fn contains_only_verified_devices(&self) -> Result<bool> {
1280 let user_ids = self
1281 .client
1282 .state_store()
1283 .get_user_ids(self.room_id(), RoomMemberships::empty())
1284 .await?;
1285
1286 for user_id in user_ids {
1287 let devices = self.client.encryption().get_user_devices(&user_id).await?;
1288 let any_unverified = devices.devices().any(|d| !d.is_verified());
1289
1290 if any_unverified {
1291 return Ok(false);
1292 }
1293 }
1294
1295 Ok(true)
1296 }
1297
1298 pub async fn set_account_data<T>(
1313 &self,
1314 content: T,
1315 ) -> Result<set_room_account_data::v3::Response>
1316 where
1317 T: RoomAccountDataEventContent,
1318 {
1319 let own_user = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
1320
1321 let request = set_room_account_data::v3::Request::new(
1322 own_user.to_owned(),
1323 self.room_id().to_owned(),
1324 &content,
1325 )?;
1326
1327 Ok(self.client.send(request).await?)
1328 }
1329
1330 pub async fn set_account_data_raw(
1355 &self,
1356 event_type: RoomAccountDataEventType,
1357 content: Raw<AnyRoomAccountDataEventContent>,
1358 ) -> Result<set_room_account_data::v3::Response> {
1359 let own_user = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
1360
1361 let request = set_room_account_data::v3::Request::new_raw(
1362 own_user.to_owned(),
1363 self.room_id().to_owned(),
1364 event_type,
1365 content,
1366 );
1367
1368 Ok(self.client.send(request).await?)
1369 }
1370
1371 pub async fn set_tag(
1402 &self,
1403 tag: TagName,
1404 tag_info: TagInfo,
1405 ) -> Result<create_tag::v3::Response> {
1406 let user_id = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
1407 let request = create_tag::v3::Request::new(
1408 user_id.to_owned(),
1409 self.inner.room_id().to_owned(),
1410 tag.to_string(),
1411 tag_info,
1412 );
1413 Ok(self.client.send(request).await?)
1414 }
1415
1416 pub async fn remove_tag(&self, tag: TagName) -> Result<delete_tag::v3::Response> {
1423 let user_id = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
1424 let request = delete_tag::v3::Request::new(
1425 user_id.to_owned(),
1426 self.inner.room_id().to_owned(),
1427 tag.to_string(),
1428 );
1429 Ok(self.client.send(request).await?)
1430 }
1431
1432 pub async fn set_is_favourite(&self, is_favourite: bool, tag_order: Option<f64>) -> Result<()> {
1442 if is_favourite {
1443 let tag_info = assign!(TagInfo::new(), { order: tag_order });
1444
1445 self.set_tag(TagName::Favorite, tag_info).await?;
1446
1447 if self.is_low_priority() {
1448 self.remove_tag(TagName::LowPriority).await?;
1449 }
1450 } else {
1451 self.remove_tag(TagName::Favorite).await?;
1452 }
1453 Ok(())
1454 }
1455
1456 pub async fn set_is_low_priority(
1466 &self,
1467 is_low_priority: bool,
1468 tag_order: Option<f64>,
1469 ) -> Result<()> {
1470 if is_low_priority {
1471 let tag_info = assign!(TagInfo::new(), { order: tag_order });
1472
1473 self.set_tag(TagName::LowPriority, tag_info).await?;
1474
1475 if self.is_favourite() {
1476 self.remove_tag(TagName::Favorite).await?;
1477 }
1478 } else {
1479 self.remove_tag(TagName::LowPriority).await?;
1480 }
1481 Ok(())
1482 }
1483
1484 pub async fn set_is_direct(&self, is_direct: bool) -> Result<()> {
1493 let user_id = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
1494
1495 let mut content = self
1496 .client
1497 .account()
1498 .account_data::<DirectEventContent>()
1499 .await?
1500 .map(|c| c.deserialize())
1501 .transpose()?
1502 .unwrap_or_default();
1503
1504 let this_room_id = self.inner.room_id();
1505
1506 if is_direct {
1507 let mut room_members = self.members(RoomMemberships::ACTIVE).await?;
1508 room_members.retain(|member| member.user_id() != self.own_user_id());
1509
1510 for member in room_members {
1511 let entry = content.entry(member.user_id().into()).or_default();
1512 if !entry.iter().any(|room_id| room_id == this_room_id) {
1513 entry.push(this_room_id.to_owned());
1514 }
1515 }
1516 } else {
1517 for (_, list) in content.iter_mut() {
1518 list.retain(|room_id| *room_id != this_room_id);
1519 }
1520
1521 content.retain(|_, list| !list.is_empty());
1523 }
1524
1525 let request = set_global_account_data::v3::Request::new(user_id.to_owned(), &content)?;
1526
1527 self.client.send(request).await?;
1528 Ok(())
1529 }
1530
1531 #[cfg(feature = "e2e-encryption")]
1539 pub async fn decrypt_event(
1540 &self,
1541 event: &Raw<OriginalSyncRoomEncryptedEvent>,
1542 push_ctx: Option<&PushContext>,
1543 ) -> Result<TimelineEvent> {
1544 let machine = self.client.olm_machine().await;
1545 let machine = machine.as_ref().ok_or(Error::NoOlmMachine)?;
1546
1547 match machine
1548 .try_decrypt_room_event(
1549 event.cast_ref(),
1550 self.inner.room_id(),
1551 self.client.decryption_settings(),
1552 )
1553 .await?
1554 {
1555 RoomEventDecryptionResult::Decrypted(decrypted) => {
1556 let push_actions = if let Some(push_ctx) = push_ctx {
1557 Some(push_ctx.for_event(&decrypted.event).await)
1558 } else {
1559 None
1560 };
1561 Ok(TimelineEvent::from_decrypted(decrypted, push_actions))
1562 }
1563 RoomEventDecryptionResult::UnableToDecrypt(utd_info) => {
1564 self.client
1565 .encryption()
1566 .backups()
1567 .maybe_download_room_key(self.room_id().to_owned(), event.clone());
1568 Ok(TimelineEvent::from_utd(event.clone().cast(), utd_info))
1569 }
1570 }
1571 }
1572
1573 #[cfg(feature = "e2e-encryption")]
1586 pub async fn get_encryption_info(
1587 &self,
1588 session_id: &str,
1589 sender: &UserId,
1590 ) -> Option<Arc<EncryptionInfo>> {
1591 let machine = self.client.olm_machine().await;
1592 let machine = machine.as_ref()?;
1593 machine.get_session_encryption_info(self.room_id(), session_id, sender).await.ok()
1594 }
1595
1596 #[cfg(feature = "e2e-encryption")]
1609 pub async fn discard_room_key(&self) -> Result<()> {
1610 let machine = self.client.olm_machine().await;
1611 if let Some(machine) = machine.as_ref() {
1612 machine.discard_room_key(self.inner.room_id()).await?;
1613 Ok(())
1614 } else {
1615 Err(Error::NoOlmMachine)
1616 }
1617 }
1618
1619 #[instrument(skip_all)]
1627 pub async fn ban_user(&self, user_id: &UserId, reason: Option<&str>) -> Result<()> {
1628 let request = assign!(
1629 ban_user::v3::Request::new(self.room_id().to_owned(), user_id.to_owned()),
1630 { reason: reason.map(ToOwned::to_owned) }
1631 );
1632 self.client.send(request).await?;
1633 Ok(())
1634 }
1635
1636 #[instrument(skip_all)]
1644 pub async fn unban_user(&self, user_id: &UserId, reason: Option<&str>) -> Result<()> {
1645 let request = assign!(
1646 unban_user::v3::Request::new(self.room_id().to_owned(), user_id.to_owned()),
1647 { reason: reason.map(ToOwned::to_owned) }
1648 );
1649 self.client.send(request).await?;
1650 Ok(())
1651 }
1652
1653 #[instrument(skip_all)]
1662 pub async fn kick_user(&self, user_id: &UserId, reason: Option<&str>) -> Result<()> {
1663 let request = assign!(
1664 kick_user::v3::Request::new(self.room_id().to_owned(), user_id.to_owned()),
1665 { reason: reason.map(ToOwned::to_owned) }
1666 );
1667 self.client.send(request).await?;
1668 Ok(())
1669 }
1670
1671 #[instrument(skip_all)]
1677 pub async fn invite_user_by_id(&self, user_id: &UserId) -> Result<()> {
1678 #[cfg(feature = "e2e-encryption")]
1679 if self.client.inner.enable_share_history_on_invite {
1680 shared_room_history::share_room_history(self, user_id.to_owned()).await?;
1681 }
1682
1683 let recipient = InvitationRecipient::UserId { user_id: user_id.to_owned() };
1684 let request = invite_user::v3::Request::new(self.room_id().to_owned(), recipient);
1685 self.client.send(request).await?;
1686
1687 self.mark_members_missing();
1691
1692 Ok(())
1693 }
1694
1695 #[instrument(skip_all)]
1701 pub async fn invite_user_by_3pid(&self, invite_id: Invite3pid) -> Result<()> {
1702 let recipient = InvitationRecipient::ThirdPartyId(invite_id);
1703 let request = invite_user::v3::Request::new(self.room_id().to_owned(), recipient);
1704 self.client.send(request).await?;
1705
1706 self.mark_members_missing();
1710
1711 Ok(())
1712 }
1713
1714 pub async fn typing_notice(&self, typing: bool) -> Result<()> {
1749 self.ensure_room_joined()?;
1750
1751 let send = if let Some(typing_time) =
1754 self.client.inner.typing_notice_times.read().unwrap().get(self.room_id())
1755 {
1756 if typing_time.elapsed() > TYPING_NOTICE_RESEND_TIMEOUT {
1757 typing || typing_time.elapsed() <= TYPING_NOTICE_TIMEOUT
1761 } else {
1762 !typing
1764 }
1765 } else {
1766 typing
1769 };
1770
1771 if send {
1772 self.send_typing_notice(typing).await?;
1773 }
1774
1775 Ok(())
1776 }
1777
1778 #[instrument(name = "typing_notice", skip(self))]
1779 async fn send_typing_notice(&self, typing: bool) -> Result<()> {
1780 let typing = if typing {
1781 self.client
1782 .inner
1783 .typing_notice_times
1784 .write()
1785 .unwrap()
1786 .insert(self.room_id().to_owned(), Instant::now());
1787 Typing::Yes(TYPING_NOTICE_TIMEOUT)
1788 } else {
1789 self.client.inner.typing_notice_times.write().unwrap().remove(self.room_id());
1790 Typing::No
1791 };
1792
1793 let request = create_typing_event::v3::Request::new(
1794 self.own_user_id().to_owned(),
1795 self.room_id().to_owned(),
1796 typing,
1797 );
1798
1799 self.client.send(request).await?;
1800
1801 Ok(())
1802 }
1803
1804 #[instrument(skip_all)]
1821 pub async fn send_single_receipt(
1822 &self,
1823 receipt_type: create_receipt::v3::ReceiptType,
1824 thread: ReceiptThread,
1825 event_id: OwnedEventId,
1826 ) -> Result<()> {
1827 let request_key = format!("{}|{}", receipt_type, thread.as_str().unwrap_or("<unthreaded>"));
1830
1831 self.client
1832 .inner
1833 .locks
1834 .read_receipt_deduplicated_handler
1835 .run((request_key, event_id.clone()), async {
1836 let is_unthreaded = thread == ReceiptThread::Unthreaded;
1838
1839 let mut request = create_receipt::v3::Request::new(
1840 self.room_id().to_owned(),
1841 receipt_type,
1842 event_id,
1843 );
1844 request.thread = thread;
1845
1846 self.client.send(request).await?;
1847
1848 if is_unthreaded {
1849 self.set_unread_flag(false).await?;
1850 }
1851
1852 Ok(())
1853 })
1854 .await
1855 }
1856
1857 #[instrument(skip_all)]
1867 pub async fn send_multiple_receipts(&self, receipts: Receipts) -> Result<()> {
1868 if receipts.is_empty() {
1869 return Ok(());
1870 }
1871
1872 let Receipts { fully_read, public_read_receipt, private_read_receipt } = receipts;
1873 let request = assign!(set_read_marker::v3::Request::new(self.room_id().to_owned()), {
1874 fully_read,
1875 read_receipt: public_read_receipt,
1876 private_read_receipt,
1877 });
1878
1879 self.client.send(request).await?;
1880
1881 self.set_unread_flag(false).await?;
1882
1883 Ok(())
1884 }
1885
1886 #[instrument(skip_all)]
1918 pub async fn enable_encryption(&self) -> Result<()> {
1919 use ruma::{
1920 events::room::encryption::RoomEncryptionEventContent, EventEncryptionAlgorithm,
1921 };
1922 const SYNC_WAIT_TIME: Duration = Duration::from_secs(3);
1923
1924 if !self.latest_encryption_state().await?.is_encrypted() {
1925 let content =
1926 RoomEncryptionEventContent::new(EventEncryptionAlgorithm::MegolmV1AesSha2);
1927 self.send_state_event(content).await?;
1928
1929 _ = timeout(self.client.inner.sync_beat.listen(), SYNC_WAIT_TIME).await;
1933
1934 let _sync_lock = self.client.base_client().sync_lock().lock().await;
1939 if !self.inner.encryption_state().is_encrypted() {
1940 debug!("still not marked as encrypted, marking encryption state as missing");
1941
1942 let mut room_info = self.clone_info();
1943 room_info.mark_encryption_state_missing();
1944 let mut changes = StateChanges::default();
1945 changes.add_room(room_info.clone());
1946
1947 self.client.state_store().save_changes(&changes).await?;
1948 self.set_room_info(room_info, RoomInfoNotableUpdateReasons::empty());
1949 } else {
1950 debug!("room successfully marked as encrypted");
1951 }
1952 }
1953
1954 Ok(())
1955 }
1956
1957 #[cfg(feature = "e2e-encryption")]
1966 #[instrument(skip_all, fields(room_id = ?self.room_id(), store_generation))]
1967 async fn preshare_room_key(&self) -> Result<()> {
1968 self.ensure_room_joined()?;
1969
1970 let guard = self.client.encryption().spin_lock_store(Some(60000)).await?;
1972 tracing::Span::current().record("store_generation", guard.map(|guard| guard.generation()));
1973
1974 self.client
1975 .locks()
1976 .group_session_deduplicated_handler
1977 .run(self.room_id().to_owned(), async move {
1978 {
1979 let members = self
1980 .client
1981 .state_store()
1982 .get_user_ids(self.room_id(), RoomMemberships::ACTIVE)
1983 .await?;
1984 self.client.claim_one_time_keys(members.iter().map(Deref::deref)).await?;
1985 };
1986
1987 let response = self.share_room_key().await;
1988
1989 if let Err(r) = response {
1993 let machine = self.client.olm_machine().await;
1994 if let Some(machine) = machine.as_ref() {
1995 machine.discard_room_key(self.room_id()).await?;
1996 }
1997 return Err(r);
1998 }
1999
2000 Ok(())
2001 })
2002 .await
2003 }
2004
2005 #[cfg(feature = "e2e-encryption")]
2011 #[instrument(skip_all)]
2012 async fn share_room_key(&self) -> Result<()> {
2013 self.ensure_room_joined()?;
2014
2015 let requests = self.client.base_client().share_room_key(self.room_id()).await?;
2016
2017 for request in requests {
2018 let response = self.client.send_to_device(&request).await?;
2019 self.client.mark_request_as_sent(&request.txn_id, &response).await?;
2020 }
2021
2022 Ok(())
2023 }
2024
2025 #[instrument(skip_all)]
2034 pub async fn sync_up(&self) {
2035 while !self.is_synced() && self.state() == RoomState::Joined {
2036 let wait_for_beat = self.client.inner.sync_beat.listen();
2037 let _ = timeout(wait_for_beat, Duration::from_millis(1000)).await;
2039 }
2040 }
2041
2042 pub fn send(&self, content: impl MessageLikeEventContent) -> SendMessageLikeEvent<'_> {
2112 SendMessageLikeEvent::new(self, content)
2113 }
2114
2115 #[cfg(feature = "e2e-encryption")]
2118 async fn query_keys_for_untracked_or_dirty_users(&self) -> Result<()> {
2119 let olm = self.client.olm_machine().await;
2120 let olm = olm.as_ref().expect("Olm machine wasn't started");
2121
2122 let members =
2123 self.client.state_store().get_user_ids(self.room_id(), RoomMemberships::ACTIVE).await?;
2124
2125 let tracked: HashMap<_, _> = olm
2126 .store()
2127 .load_tracked_users()
2128 .await?
2129 .into_iter()
2130 .map(|tracked| (tracked.user_id, tracked.dirty))
2131 .collect();
2132
2133 let members_with_unknown_devices =
2136 members.iter().filter(|member| tracked.get(*member).is_none_or(|dirty| *dirty));
2137
2138 let (req_id, request) =
2139 olm.query_keys_for_users(members_with_unknown_devices.map(|owned| owned.borrow()));
2140
2141 if !request.device_keys.is_empty() {
2142 self.client.keys_query(&req_id, request.device_keys).await?;
2143 }
2144
2145 Ok(())
2146 }
2147
2148 #[instrument(skip_all, fields(event_type, room_id = ?self.room_id(), transaction_id, is_room_encrypted, event_id))]
2192 pub fn send_raw<'a>(
2193 &'a self,
2194 event_type: &'a str,
2195 content: impl IntoRawMessageLikeEventContent,
2196 ) -> SendRawMessageLikeEvent<'a> {
2197 SendRawMessageLikeEvent::new(self, event_type, content)
2200 }
2201
2202 #[instrument(skip_all)]
2250 pub fn send_attachment<'a>(
2251 &'a self,
2252 filename: impl Into<String>,
2253 content_type: &'a Mime,
2254 data: Vec<u8>,
2255 config: AttachmentConfig,
2256 ) -> SendAttachment<'a> {
2257 SendAttachment::new(self, filename.into(), content_type, data, config)
2258 }
2259
2260 #[instrument(skip_all)]
2288 pub(super) async fn prepare_and_send_attachment<'a>(
2289 &'a self,
2290 filename: String,
2291 content_type: &'a Mime,
2292 data: Vec<u8>,
2293 mut config: AttachmentConfig,
2294 send_progress: SharedObservable<TransmissionProgress>,
2295 store_in_cache: bool,
2296 ) -> Result<send_message_event::v3::Response> {
2297 self.ensure_room_joined()?;
2298
2299 let txn_id = config.txn_id.take();
2300 let mentions = config.mentions.take();
2301
2302 let thumbnail = config.thumbnail.take();
2303
2304 let thumbnail_cache_info = if store_in_cache {
2306 thumbnail
2307 .as_ref()
2308 .map(|thumbnail| (thumbnail.data.clone(), thumbnail.height, thumbnail.width))
2309 } else {
2310 None
2311 };
2312
2313 #[cfg(feature = "e2e-encryption")]
2314 let (media_source, thumbnail) = if self.latest_encryption_state().await?.is_encrypted() {
2315 self.client
2316 .upload_encrypted_media_and_thumbnail(&data, thumbnail, send_progress)
2317 .await?
2318 } else {
2319 self.client
2320 .media()
2321 .upload_plain_media_and_thumbnail(
2322 content_type,
2323 data.clone(),
2326 thumbnail,
2327 send_progress,
2328 )
2329 .await?
2330 };
2331
2332 #[cfg(not(feature = "e2e-encryption"))]
2333 let (media_source, thumbnail) = self
2334 .client
2335 .media()
2336 .upload_plain_media_and_thumbnail(content_type, data.clone(), thumbnail, send_progress)
2337 .await?;
2338
2339 if store_in_cache {
2340 let cache_store_lock_guard = self.client.event_cache_store().lock().await?;
2341
2342 debug!("caching the media");
2346 let request =
2347 MediaRequestParameters { source: media_source.clone(), format: MediaFormat::File };
2348
2349 if let Err(err) = cache_store_lock_guard
2350 .add_media_content(&request, data, IgnoreMediaRetentionPolicy::No)
2351 .await
2352 {
2353 warn!("unable to cache the media after uploading it: {err}");
2354 }
2355
2356 if let Some(((data, height, width), source)) =
2357 thumbnail_cache_info.zip(thumbnail.as_ref().map(|tuple| &tuple.0))
2358 {
2359 debug!("caching the thumbnail");
2360
2361 let request = MediaRequestParameters {
2362 source: source.clone(),
2363 format: MediaFormat::Thumbnail(MediaThumbnailSettings::new(width, height)),
2364 };
2365
2366 if let Err(err) = cache_store_lock_guard
2367 .add_media_content(&request, data, IgnoreMediaRetentionPolicy::No)
2368 .await
2369 {
2370 warn!("unable to cache the media after uploading it: {err}");
2371 }
2372 }
2373 }
2374
2375 let content = self
2376 .make_media_event(
2377 Room::make_attachment_type(
2378 content_type,
2379 filename,
2380 media_source,
2381 config.caption,
2382 config.formatted_caption,
2383 config.info,
2384 thumbnail,
2385 ),
2386 mentions,
2387 config.reply,
2388 )
2389 .await?;
2390
2391 let mut fut = self.send(content);
2392 if let Some(txn_id) = txn_id {
2393 fut = fut.with_transaction_id(txn_id);
2394 }
2395 fut.await
2396 }
2397
2398 #[allow(clippy::too_many_arguments)]
2401 pub(crate) fn make_attachment_type(
2402 content_type: &Mime,
2403 filename: String,
2404 source: MediaSource,
2405 caption: Option<String>,
2406 formatted_caption: Option<FormattedBody>,
2407 info: Option<AttachmentInfo>,
2408 thumbnail: Option<(MediaSource, Box<ThumbnailInfo>)>,
2409 ) -> MessageType {
2410 make_media_type!(
2411 MessageType,
2412 content_type,
2413 filename,
2414 source,
2415 caption,
2416 formatted_caption,
2417 info,
2418 thumbnail
2419 )
2420 }
2421
2422 pub(crate) async fn make_media_event(
2425 &self,
2426 msg_type: MessageType,
2427 mentions: Option<Mentions>,
2428 reply: Option<Reply>,
2429 ) -> Result<RoomMessageEventContent> {
2430 let mut content = RoomMessageEventContent::new(msg_type);
2431 if let Some(mentions) = mentions {
2432 content = content.add_mentions(mentions);
2433 }
2434 if let Some(reply) = reply {
2435 content = self.make_reply_event(content.into(), reply).await?;
2438 }
2439 Ok(content)
2440 }
2441
2442 #[cfg(feature = "unstable-msc4274")]
2445 #[allow(clippy::too_many_arguments)]
2446 pub(crate) fn make_gallery_item_type(
2447 content_type: &Mime,
2448 filename: String,
2449 source: MediaSource,
2450 caption: Option<String>,
2451 formatted_caption: Option<FormattedBody>,
2452 info: Option<AttachmentInfo>,
2453 thumbnail: Option<(MediaSource, Box<ThumbnailInfo>)>,
2454 ) -> GalleryItemType {
2455 make_media_type!(
2456 GalleryItemType,
2457 content_type,
2458 filename,
2459 source,
2460 caption,
2461 formatted_caption,
2462 info,
2463 thumbnail
2464 )
2465 }
2466
2467 pub async fn update_power_levels(
2476 &self,
2477 updates: Vec<(&UserId, Int)>,
2478 ) -> Result<send_state_event::v3::Response> {
2479 let mut power_levels = self.power_levels().await?;
2480
2481 for (user_id, new_level) in updates {
2482 if new_level == power_levels.users_default {
2483 power_levels.users.remove(user_id);
2484 } else {
2485 power_levels.users.insert(user_id.to_owned(), new_level);
2486 }
2487 }
2488
2489 self.send_state_event(RoomPowerLevelsEventContent::try_from(power_levels)?).await
2490 }
2491
2492 pub async fn apply_power_level_changes(&self, changes: RoomPowerLevelChanges) -> Result<()> {
2497 let mut power_levels = self.power_levels().await?;
2498 power_levels.apply(changes)?;
2499 self.send_state_event(RoomPowerLevelsEventContent::try_from(power_levels)?).await?;
2500 Ok(())
2501 }
2502
2503 pub async fn reset_power_levels(&self) -> Result<RoomPowerLevels> {
2507 let creators = self.creators().unwrap_or_default();
2508 let rules = self.clone_info().room_version_rules_or_default();
2509
2510 let default_power_levels =
2511 RoomPowerLevels::new(RoomPowerLevelsSource::None, &rules.authorization, creators);
2512 let changes = RoomPowerLevelChanges::from(default_power_levels);
2513 self.apply_power_level_changes(changes).await?;
2514 Ok(self.power_levels().await?)
2515 }
2516
2517 pub async fn get_suggested_user_role(&self, user_id: &UserId) -> Result<RoomMemberRole> {
2522 let power_level = self.get_user_power_level(user_id).await?;
2523 Ok(RoomMemberRole::suggested_role_for_power_level(power_level))
2524 }
2525
2526 pub async fn get_user_power_level(&self, user_id: &UserId) -> Result<UserPowerLevel> {
2531 let event = self.power_levels().await?;
2532 Ok(event.for_user(user_id))
2533 }
2534
2535 pub async fn users_with_power_levels(&self) -> HashMap<OwnedUserId, i64> {
2538 let power_levels = self.power_levels().await.ok();
2539 let mut user_power_levels = HashMap::<OwnedUserId, i64>::new();
2540 if let Some(power_levels) = power_levels {
2541 for (id, level) in power_levels.users.into_iter() {
2542 user_power_levels.insert(id, level.into());
2543 }
2544 }
2545 user_power_levels
2546 }
2547
2548 pub async fn set_name(&self, name: String) -> Result<send_state_event::v3::Response> {
2550 self.send_state_event(RoomNameEventContent::new(name)).await
2551 }
2552
2553 pub async fn set_room_topic(&self, topic: &str) -> Result<send_state_event::v3::Response> {
2555 self.send_state_event(RoomTopicEventContent::new(topic.into())).await
2556 }
2557
2558 pub async fn set_avatar_url(
2564 &self,
2565 url: &MxcUri,
2566 info: Option<avatar::ImageInfo>,
2567 ) -> Result<send_state_event::v3::Response> {
2568 self.ensure_room_joined()?;
2569
2570 let mut room_avatar_event = RoomAvatarEventContent::new();
2571 room_avatar_event.url = Some(url.to_owned());
2572 room_avatar_event.info = info.map(Box::new);
2573
2574 self.send_state_event(room_avatar_event).await
2575 }
2576
2577 pub async fn remove_avatar(&self) -> Result<send_state_event::v3::Response> {
2579 self.send_state_event(RoomAvatarEventContent::new()).await
2580 }
2581
2582 pub async fn upload_avatar(
2590 &self,
2591 mime: &Mime,
2592 data: Vec<u8>,
2593 info: Option<avatar::ImageInfo>,
2594 ) -> Result<send_state_event::v3::Response> {
2595 self.ensure_room_joined()?;
2596
2597 let upload_response = self.client.media().upload(mime, data, None).await?;
2598 let mut info = info.unwrap_or_default();
2599 info.blurhash = upload_response.blurhash;
2600 info.mimetype = Some(mime.to_string());
2601
2602 self.set_avatar_url(&upload_response.content_uri, Some(info)).await
2603 }
2604
2605 #[instrument(skip_all)]
2649 pub async fn send_state_event(
2650 &self,
2651 content: impl StateEventContent<StateKey = EmptyStateKey>,
2652 ) -> Result<send_state_event::v3::Response> {
2653 self.send_state_event_for_key(&EmptyStateKey, content).await
2654 }
2655
2656 pub async fn send_state_event_for_key<C, K>(
2697 &self,
2698 state_key: &K,
2699 content: C,
2700 ) -> Result<send_state_event::v3::Response>
2701 where
2702 C: StateEventContent,
2703 C::StateKey: Borrow<K>,
2704 K: AsRef<str> + ?Sized,
2705 {
2706 self.ensure_room_joined()?;
2707 let request =
2708 send_state_event::v3::Request::new(self.room_id().to_owned(), state_key, &content)?;
2709 let response = self.client.send(request).await?;
2710 Ok(response)
2711 }
2712
2713 #[instrument(skip_all)]
2748 pub async fn send_state_event_raw(
2749 &self,
2750 event_type: &str,
2751 state_key: &str,
2752 content: impl IntoRawStateEventContent,
2753 ) -> Result<send_state_event::v3::Response> {
2754 self.ensure_room_joined()?;
2755
2756 let request = send_state_event::v3::Request::new_raw(
2757 self.room_id().to_owned(),
2758 event_type.into(),
2759 state_key.to_owned(),
2760 content.into_raw_state_event_content(),
2761 );
2762
2763 Ok(self.client.send(request).await?)
2764 }
2765
2766 #[instrument(skip_all)]
2801 pub async fn redact(
2802 &self,
2803 event_id: &EventId,
2804 reason: Option<&str>,
2805 txn_id: Option<OwnedTransactionId>,
2806 ) -> HttpResult<redact_event::v3::Response> {
2807 let txn_id = txn_id.unwrap_or_else(TransactionId::new);
2808 let request = assign!(
2809 redact_event::v3::Request::new(self.room_id().to_owned(), event_id.to_owned(), txn_id),
2810 { reason: reason.map(ToOwned::to_owned) }
2811 );
2812
2813 self.client.send(request).await
2814 }
2815
2816 pub async fn route(&self) -> Result<Vec<OwnedServerName>> {
2825 let acl_ev = self
2826 .get_state_event_static::<RoomServerAclEventContent>()
2827 .await?
2828 .and_then(|ev| ev.deserialize().ok());
2829 let acl = acl_ev.as_ref().and_then(|ev| match ev {
2830 SyncOrStrippedState::Sync(ev) => ev.as_original().map(|ev| &ev.content),
2831 SyncOrStrippedState::Stripped(ev) => Some(&ev.content),
2832 });
2833
2834 let members: Vec<_> = self
2838 .members_no_sync(RoomMemberships::JOIN)
2839 .await?
2840 .into_iter()
2841 .filter(|member| {
2842 let server = member.user_id().server_name();
2843 acl.filter(|acl| !acl.is_allowed(server)).is_none() && !server.is_ip_literal()
2844 })
2845 .collect();
2846
2847 let max = members
2850 .iter()
2851 .max_by_key(|member| member.power_level())
2852 .filter(|max| max.power_level() >= int!(50))
2853 .map(|member| member.user_id().server_name());
2854
2855 let servers = members
2857 .iter()
2858 .map(|member| member.user_id().server_name())
2859 .filter(|server| max.filter(|max| max == server).is_none())
2860 .fold(BTreeMap::<_, u32>::new(), |mut servers, server| {
2861 *servers.entry(server).or_default() += 1;
2862 servers
2863 });
2864 let mut servers: Vec<_> = servers.into_iter().collect();
2865 servers.sort_unstable_by(|(_, count_a), (_, count_b)| count_b.cmp(count_a));
2866
2867 Ok(max
2868 .into_iter()
2869 .chain(servers.into_iter().map(|(name, _)| name))
2870 .take(3)
2871 .map(ToOwned::to_owned)
2872 .collect())
2873 }
2874
2875 pub async fn matrix_to_permalink(&self) -> Result<MatrixToUri> {
2882 if let Some(alias) = self.canonical_alias().or_else(|| self.alt_aliases().pop()) {
2883 return Ok(alias.matrix_to_uri());
2884 }
2885
2886 let via = self.route().await?;
2887 Ok(self.room_id().matrix_to_uri_via(via))
2888 }
2889
2890 pub async fn matrix_permalink(&self, join: bool) -> Result<MatrixUri> {
2901 if let Some(alias) = self.canonical_alias().or_else(|| self.alt_aliases().pop()) {
2902 return Ok(alias.matrix_uri(join));
2903 }
2904
2905 let via = self.route().await?;
2906 Ok(self.room_id().matrix_uri_via(via, join))
2907 }
2908
2909 pub async fn matrix_to_event_permalink(
2923 &self,
2924 event_id: impl Into<OwnedEventId>,
2925 ) -> Result<MatrixToUri> {
2926 let via = self.route().await?;
2929 Ok(self.room_id().matrix_to_event_uri_via(event_id, via))
2930 }
2931
2932 pub async fn matrix_event_permalink(
2946 &self,
2947 event_id: impl Into<OwnedEventId>,
2948 ) -> Result<MatrixUri> {
2949 let via = self.route().await?;
2952 Ok(self.room_id().matrix_event_uri_via(event_id, via))
2953 }
2954
2955 pub async fn load_user_receipt(
2968 &self,
2969 receipt_type: ReceiptType,
2970 thread: ReceiptThread,
2971 user_id: &UserId,
2972 ) -> Result<Option<(OwnedEventId, Receipt)>> {
2973 self.inner.load_user_receipt(receipt_type, thread, user_id).await.map_err(Into::into)
2974 }
2975
2976 pub async fn load_event_receipts(
2989 &self,
2990 receipt_type: ReceiptType,
2991 thread: ReceiptThread,
2992 event_id: &EventId,
2993 ) -> Result<Vec<(OwnedUserId, Receipt)>> {
2994 self.inner.load_event_receipts(receipt_type, thread, event_id).await.map_err(Into::into)
2995 }
2996
2997 pub async fn push_condition_room_ctx(&self) -> Result<Option<PushConditionRoomCtx>> {
3002 let room_id = self.room_id();
3003 let user_id = self.own_user_id();
3004 let room_info = self.clone_info();
3005 let member_count = room_info.active_members_count();
3006
3007 let user_display_name = if let Some(member) = self.get_member_no_sync(user_id).await? {
3008 member.name().to_owned()
3009 } else {
3010 return Ok(None);
3011 };
3012
3013 let power_levels = match self.power_levels().await {
3014 Ok(power_levels) => Some(power_levels.into()),
3015 Err(error) => {
3016 if matches!(room_info.state(), RoomState::Joined) {
3017 error!("Could not compute power levels for push conditions: {error}");
3020 }
3021 None
3022 }
3023 };
3024
3025 let this = self.clone();
3026 let mut ctx = assign!(PushConditionRoomCtx::new(
3027 room_id.to_owned(),
3028 UInt::new(member_count).unwrap_or(UInt::MAX),
3029 user_id.to_owned(),
3030 user_display_name,
3031 ),
3032 {
3033 power_levels,
3034 });
3035
3036 if self.client.enabled_thread_subscriptions() {
3037 ctx = ctx.with_has_thread_subscription_fn(move |event_id: &EventId| {
3038 let room = this.clone();
3039 Box::pin(async move {
3040 if let Ok(maybe_sub) = room.fetch_thread_subscription(event_id.to_owned()).await
3041 {
3042 maybe_sub.is_some()
3043 } else {
3044 false
3045 }
3046 })
3047 });
3048 }
3049
3050 Ok(Some(ctx))
3051 }
3052
3053 pub async fn push_context(&self) -> Result<Option<PushContext>> {
3056 let Some(push_condition_room_ctx) = self.push_condition_room_ctx().await? else {
3057 debug!("Could not aggregate push context");
3058 return Ok(None);
3059 };
3060 let push_rules = self.client().account().push_rules().await?;
3061 Ok(Some(PushContext::new(push_condition_room_ctx, push_rules)))
3062 }
3063
3064 pub async fn event_push_actions<T>(&self, event: &Raw<T>) -> Result<Option<Vec<Action>>> {
3069 if let Some(ctx) = self.push_context().await? {
3070 Ok(Some(ctx.for_event(event).await))
3071 } else {
3072 Ok(None)
3073 }
3074 }
3075
3076 pub async fn invite_details(&self) -> Result<Invite> {
3079 let state = self.state();
3080
3081 if state != RoomState::Invited {
3082 return Err(Error::WrongRoomState(Box::new(WrongRoomState::new("Invited", state))));
3083 }
3084
3085 let invitee = self
3086 .get_member_no_sync(self.own_user_id())
3087 .await?
3088 .ok_or_else(|| Error::UnknownError(Box::new(InvitationError::EventMissing)))?;
3089 let event = invitee.event();
3090 let inviter_id = event.sender();
3091 let inviter = self.get_member_no_sync(inviter_id).await?;
3092 Ok(Invite { invitee, inviter })
3093 }
3094
3095 pub async fn member_with_sender_info(
3103 &self,
3104 user_id: &UserId,
3105 ) -> Result<RoomMemberWithSenderInfo> {
3106 let Some(member) = self.get_member_no_sync(user_id).await? else {
3107 return Err(Error::InsufficientData);
3108 };
3109
3110 let sender_member =
3111 if let Some(member) = self.get_member_no_sync(member.event().sender()).await? {
3112 Some(member)
3114 } else if self.are_members_synced() {
3115 None
3117 } else if self.sync_members().await.is_ok() {
3118 self.get_member_no_sync(member.event().sender()).await?
3120 } else {
3121 None
3122 };
3123
3124 Ok(RoomMemberWithSenderInfo { room_member: member, sender_info: sender_member })
3125 }
3126
3127 pub async fn forget(&self) -> Result<()> {
3133 let state = self.state();
3134 match state {
3135 RoomState::Joined | RoomState::Invited | RoomState::Knocked => {
3136 return Err(Error::WrongRoomState(Box::new(WrongRoomState::new(
3137 "Left / Banned",
3138 state,
3139 ))));
3140 }
3141 RoomState::Left | RoomState::Banned => {}
3142 }
3143
3144 let request = forget_room::v3::Request::new(self.inner.room_id().to_owned());
3145 let _response = self.client.send(request).await?;
3146
3147 if self.inner.direct_targets_length() != 0 {
3149 if let Err(e) = self.set_is_direct(false).await {
3150 warn!(room_id = ?self.room_id(), "failed to remove room from m.direct account data: {e}");
3153 }
3154 }
3155
3156 self.client.base_client().forget_room(self.inner.room_id()).await?;
3157
3158 Ok(())
3159 }
3160
3161 fn ensure_room_joined(&self) -> Result<()> {
3162 let state = self.state();
3163 if state == RoomState::Joined {
3164 Ok(())
3165 } else {
3166 Err(Error::WrongRoomState(Box::new(WrongRoomState::new("Joined", state))))
3167 }
3168 }
3169
3170 pub async fn notification_mode(&self) -> Option<RoomNotificationMode> {
3172 if !matches!(self.state(), RoomState::Joined) {
3173 return None;
3174 }
3175
3176 let notification_settings = self.client().notification_settings().await;
3177
3178 let notification_mode =
3180 notification_settings.get_user_defined_room_notification_mode(self.room_id()).await;
3181
3182 if notification_mode.is_some() {
3183 notification_mode
3184 } else if let Ok(is_encrypted) =
3185 self.latest_encryption_state().await.map(|state| state.is_encrypted())
3186 {
3187 let is_one_to_one = IsOneToOne::from(self.active_members_count() == 2);
3192 let default_mode = notification_settings
3193 .get_default_room_notification_mode(IsEncrypted::from(is_encrypted), is_one_to_one)
3194 .await;
3195 Some(default_mode)
3196 } else {
3197 None
3198 }
3199 }
3200
3201 pub async fn user_defined_notification_mode(&self) -> Option<RoomNotificationMode> {
3212 if !matches!(self.state(), RoomState::Joined) {
3213 return None;
3214 }
3215
3216 let notification_settings = self.client().notification_settings().await;
3217
3218 let mode =
3220 notification_settings.get_user_defined_room_notification_mode(self.room_id()).await;
3221
3222 if let Some(mode) = mode {
3223 self.update_cached_user_defined_notification_mode(mode);
3224 }
3225
3226 mode
3227 }
3228
3229 pub async fn report_content(
3242 &self,
3243 event_id: OwnedEventId,
3244 score: Option<ReportedContentScore>,
3245 reason: Option<String>,
3246 ) -> Result<report_content::v3::Response> {
3247 let state = self.state();
3248 if state != RoomState::Joined {
3249 return Err(Error::WrongRoomState(Box::new(WrongRoomState::new("Joined", state))));
3250 }
3251
3252 let request = report_content::v3::Request::new(
3253 self.inner.room_id().to_owned(),
3254 event_id,
3255 score.map(Into::into),
3256 reason,
3257 );
3258 Ok(self.client.send(request).await?)
3259 }
3260
3261 pub async fn report_room(&self, reason: String) -> Result<report_room::v3::Response> {
3272 let request = report_room::v3::Request::new(self.inner.room_id().to_owned(), reason);
3273
3274 Ok(self.client.send(request).await?)
3275 }
3276
3277 pub async fn set_unread_flag(&self, unread: bool) -> Result<()> {
3283 if self.is_marked_unread() == unread {
3284 return Ok(());
3286 }
3287
3288 let user_id = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
3289
3290 let content = MarkedUnreadEventContent::new(unread);
3291
3292 let request = set_room_account_data::v3::Request::new(
3293 user_id.to_owned(),
3294 self.inner.room_id().to_owned(),
3295 &content,
3296 )?;
3297
3298 self.client.send(request).await?;
3299 Ok(())
3300 }
3301
3302 pub async fn event_cache(
3305 &self,
3306 ) -> event_cache::Result<(RoomEventCache, Arc<EventCacheDropHandles>)> {
3307 self.client.event_cache().for_room(self.room_id()).await
3308 }
3309
3310 pub(crate) async fn get_user_beacon_info(
3317 &self,
3318 user_id: &UserId,
3319 ) -> Result<OriginalSyncStateEvent<BeaconInfoEventContent>, BeaconError> {
3320 let raw_event = self
3321 .get_state_event_static_for_key::<BeaconInfoEventContent, _>(user_id)
3322 .await?
3323 .ok_or(BeaconError::NotFound)?;
3324
3325 match raw_event.deserialize()? {
3326 SyncOrStrippedState::Sync(SyncStateEvent::Original(beacon_info)) => Ok(beacon_info),
3327 SyncOrStrippedState::Sync(SyncStateEvent::Redacted(_)) => Err(BeaconError::Redacted),
3328 SyncOrStrippedState::Stripped(_) => Err(BeaconError::Stripped),
3329 }
3330 }
3331
3332 pub async fn start_live_location_share(
3345 &self,
3346 duration_millis: u64,
3347 description: Option<String>,
3348 ) -> Result<send_state_event::v3::Response> {
3349 self.ensure_room_joined()?;
3350
3351 self.send_state_event_for_key(
3352 self.own_user_id(),
3353 BeaconInfoEventContent::new(
3354 description,
3355 Duration::from_millis(duration_millis),
3356 true,
3357 None,
3358 ),
3359 )
3360 .await
3361 }
3362
3363 pub async fn stop_live_location_share(
3370 &self,
3371 ) -> Result<send_state_event::v3::Response, BeaconError> {
3372 self.ensure_room_joined()?;
3373
3374 let mut beacon_info_event = self.get_user_beacon_info(self.own_user_id()).await?;
3375 beacon_info_event.content.stop();
3376 Ok(self.send_state_event_for_key(self.own_user_id(), beacon_info_event.content).await?)
3377 }
3378
3379 pub async fn send_location_beacon(
3391 &self,
3392 geo_uri: String,
3393 ) -> Result<send_message_event::v3::Response, BeaconError> {
3394 self.ensure_room_joined()?;
3395
3396 let beacon_info_event = self.get_user_beacon_info(self.own_user_id()).await?;
3397
3398 if beacon_info_event.content.is_live() {
3399 let content = BeaconEventContent::new(beacon_info_event.event_id, geo_uri, None);
3400 Ok(self.send(content).await?)
3401 } else {
3402 Err(BeaconError::NotLive)
3403 }
3404 }
3405
3406 pub async fn save_composer_draft(
3409 &self,
3410 draft: ComposerDraft,
3411 thread_root: Option<&EventId>,
3412 ) -> Result<()> {
3413 self.client
3414 .state_store()
3415 .set_kv_data(
3416 StateStoreDataKey::ComposerDraft(self.room_id(), thread_root),
3417 StateStoreDataValue::ComposerDraft(draft),
3418 )
3419 .await?;
3420 Ok(())
3421 }
3422
3423 pub async fn load_composer_draft(
3426 &self,
3427 thread_root: Option<&EventId>,
3428 ) -> Result<Option<ComposerDraft>> {
3429 let data = self
3430 .client
3431 .state_store()
3432 .get_kv_data(StateStoreDataKey::ComposerDraft(self.room_id(), thread_root))
3433 .await?;
3434 Ok(data.and_then(|d| d.into_composer_draft()))
3435 }
3436
3437 pub async fn clear_composer_draft(&self, thread_root: Option<&EventId>) -> Result<()> {
3440 self.client
3441 .state_store()
3442 .remove_kv_data(StateStoreDataKey::ComposerDraft(self.room_id(), thread_root))
3443 .await?;
3444 Ok(())
3445 }
3446
3447 pub async fn load_pinned_events(&self) -> Result<Option<Vec<OwnedEventId>>> {
3450 let response = self
3451 .client
3452 .send(get_state_event_for_key::v3::Request::new(
3453 self.room_id().to_owned(),
3454 StateEventType::RoomPinnedEvents,
3455 "".to_owned(),
3456 ))
3457 .await;
3458
3459 match response {
3460 Ok(response) => Ok(Some(
3461 response
3462 .into_content()
3463 .deserialize_as_unchecked::<RoomPinnedEventsEventContent>()?
3464 .pinned,
3465 )),
3466 Err(http_error) => match http_error.as_client_api_error() {
3467 Some(error) if error.status_code == StatusCode::NOT_FOUND => Ok(None),
3468 _ => Err(http_error.into()),
3469 },
3470 }
3471 }
3472
3473 pub fn observe_live_location_shares(&self) -> ObservableLiveLocation {
3481 ObservableLiveLocation::new(&self.client, self.room_id())
3482 }
3483
3484 pub async fn subscribe_to_knock_requests(
3498 &self,
3499 ) -> Result<(impl Stream<Item = Vec<KnockRequest>>, JoinHandle<()>)> {
3500 let this = Arc::new(self.clone());
3501
3502 let room_member_events_observer =
3503 self.client.observe_room_events::<SyncRoomMemberEvent, (Client, Room)>(this.room_id());
3504
3505 let current_seen_ids = self.get_seen_knock_request_ids().await?;
3506 let mut seen_request_ids_stream = self
3507 .seen_knock_request_ids_map
3508 .subscribe()
3509 .await
3510 .map(|values| values.unwrap_or_default());
3511
3512 let mut room_info_stream = self.subscribe_info();
3513
3514 let clear_seen_ids_handle = spawn({
3517 let this = self.clone();
3518 async move {
3519 let mut member_updates_stream = this.room_member_updates_sender.subscribe();
3520 while member_updates_stream.recv().await.is_ok() {
3521 if let Err(err) = this.remove_outdated_seen_knock_requests_ids().await {
3523 warn!("Failed to remove seen knock requests: {err}")
3524 }
3525 }
3526 }
3527 });
3528
3529 let combined_stream = stream! {
3530 match this.get_current_join_requests(¤t_seen_ids).await {
3532 Ok(initial_requests) => yield initial_requests,
3533 Err(err) => warn!("Failed to get initial requests to join: {err}")
3534 }
3535
3536 let mut requests_stream = room_member_events_observer.subscribe();
3537 let mut seen_ids = current_seen_ids.clone();
3538
3539 loop {
3540 tokio::select! {
3543 Some((event, _)) = requests_stream.next() => {
3544 if let Some(event) = event.as_original() {
3545 let emit = if event.prev_content().is_some() {
3547 matches!(event.membership_change(),
3548 MembershipChange::Banned |
3549 MembershipChange::Knocked |
3550 MembershipChange::KnockAccepted |
3551 MembershipChange::KnockDenied |
3552 MembershipChange::KnockRetracted
3553 )
3554 } else {
3555 true
3558 };
3559
3560 if emit {
3561 match this.get_current_join_requests(&seen_ids).await {
3562 Ok(requests) => yield requests,
3563 Err(err) => {
3564 warn!("Failed to get updated knock requests on new member event: {err}")
3565 }
3566 }
3567 }
3568 }
3569 }
3570
3571 Some(new_seen_ids) = seen_request_ids_stream.next() => {
3572 seen_ids = new_seen_ids;
3574
3575 match this.get_current_join_requests(&seen_ids).await {
3578 Ok(requests) => yield requests,
3579 Err(err) => {
3580 warn!("Failed to get updated knock requests on seen ids changed: {err}")
3581 }
3582 }
3583 }
3584
3585 Some(room_info) = room_info_stream.next() => {
3586 if !room_info.are_members_synced() {
3589 match this.get_current_join_requests(&seen_ids).await {
3590 Ok(requests) => yield requests,
3591 Err(err) => {
3592 warn!("Failed to get updated knock requests on gappy (limited) sync: {err}")
3593 }
3594 }
3595 }
3596 }
3597 else => break,
3599 }
3600 }
3601 };
3602
3603 Ok((combined_stream, clear_seen_ids_handle))
3604 }
3605
3606 async fn get_current_join_requests(
3607 &self,
3608 seen_request_ids: &BTreeMap<OwnedEventId, OwnedUserId>,
3609 ) -> Result<Vec<KnockRequest>> {
3610 Ok(self
3611 .members(RoomMemberships::KNOCK)
3612 .await?
3613 .into_iter()
3614 .filter_map(|member| {
3615 let event_id = member.event().event_id()?;
3616 Some(KnockRequest::new(
3617 self,
3618 event_id,
3619 member.event().timestamp(),
3620 KnockRequestMemberInfo::from_member(&member),
3621 seen_request_ids.contains_key(event_id),
3622 ))
3623 })
3624 .collect())
3625 }
3626
3627 pub fn privacy_settings(&self) -> RoomPrivacySettings<'_> {
3629 RoomPrivacySettings::new(&self.inner, &self.client)
3630 }
3631
3632 pub async fn list_threads(&self, opts: ListThreadsOptions) -> Result<ThreadRoots> {
3640 let request = opts.into_request(self.room_id());
3641
3642 let response = self.client.send(request).await?;
3643
3644 let push_ctx = self.push_context().await?;
3645 let chunk = join_all(
3646 response.chunk.into_iter().map(|ev| self.try_decrypt_event(ev, push_ctx.as_ref())),
3647 )
3648 .await;
3649
3650 Ok(ThreadRoots { chunk, prev_batch_token: response.next_batch })
3651 }
3652
3653 pub async fn relations(
3667 &self,
3668 event_id: OwnedEventId,
3669 opts: RelationsOptions,
3670 ) -> Result<Relations> {
3671 opts.send(self, event_id).await
3672 }
3673
3674 #[instrument(skip(self), fields(room_id = %self.room_id()))]
3696 pub async fn subscribe_thread(
3697 &self,
3698 thread_root: OwnedEventId,
3699 automatic: Option<OwnedEventId>,
3700 ) -> Result<()> {
3701 let is_automatic = automatic.is_some();
3702
3703 match self
3704 .client
3705 .send(subscribe_thread::unstable::Request::new(
3706 self.room_id().to_owned(),
3707 thread_root.clone(),
3708 automatic,
3709 ))
3710 .await
3711 {
3712 Ok(_response) => {
3713 trace!("Server acknowledged the thread subscription; saving in db");
3714 self.client
3716 .state_store()
3717 .upsert_thread_subscription(
3718 self.room_id(),
3719 &thread_root,
3720 ThreadSubscription { automatic: is_automatic },
3721 )
3722 .await?;
3723
3724 Ok(())
3725 }
3726
3727 Err(err) => {
3728 if let Some(ErrorKind::ConflictingUnsubscription) = err.client_api_error_kind() {
3729 trace!("Thread subscription skipped: {err}");
3734 Ok(())
3735 } else {
3736 Err(err.into())
3738 }
3739 }
3740 }
3741 }
3742
3743 #[instrument(skip(self), fields(room_id = %self.room_id()))]
3755 pub async fn unsubscribe_thread(&self, thread_root: OwnedEventId) -> Result<()> {
3756 self.client
3757 .send(unsubscribe_thread::unstable::Request::new(
3758 self.room_id().to_owned(),
3759 thread_root.clone(),
3760 ))
3761 .await?;
3762
3763 trace!("Server acknowledged the thread subscription removal; removed it from db too");
3764
3765 self.client.state_store().remove_thread_subscription(self.room_id(), &thread_root).await?;
3767
3768 Ok(())
3769 }
3770
3771 #[instrument(skip(self), fields(room_id = %self.room_id()))]
3788 pub async fn fetch_thread_subscription(
3789 &self,
3790 thread_root: OwnedEventId,
3791 ) -> Result<Option<ThreadSubscription>> {
3792 let result = self
3793 .client
3794 .send(get_thread_subscription::unstable::Request::new(
3795 self.room_id().to_owned(),
3796 thread_root.clone(),
3797 ))
3798 .await;
3799
3800 let subscription = match result {
3801 Ok(response) => Some(ThreadSubscription { automatic: response.automatic }),
3802 Err(http_error) => match http_error.as_client_api_error() {
3803 Some(error) if error.status_code == StatusCode::NOT_FOUND => None,
3804 _ => return Err(http_error.into()),
3805 },
3806 };
3807
3808 if let Some(sub) = &subscription {
3810 self.client
3811 .state_store()
3812 .upsert_thread_subscription(self.room_id(), &thread_root, *sub)
3813 .await?;
3814 } else {
3815 self.client
3817 .state_store()
3818 .remove_thread_subscription(self.room_id(), &thread_root)
3819 .await?;
3820 }
3821
3822 Ok(subscription)
3823 }
3824}
3825
3826#[cfg(feature = "e2e-encryption")]
3827impl RoomIdentityProvider for Room {
3828 fn is_member<'a>(&'a self, user_id: &'a UserId) -> BoxFuture<'a, bool> {
3829 Box::pin(async { self.get_member(user_id).await.unwrap_or(None).is_some() })
3830 }
3831
3832 fn member_identities(&self) -> BoxFuture<'_, Vec<UserIdentity>> {
3833 Box::pin(async {
3834 let members = self
3835 .members(RoomMemberships::JOIN | RoomMemberships::INVITE)
3836 .await
3837 .unwrap_or_else(|_| Default::default());
3838
3839 let mut ret: Vec<UserIdentity> = Vec::new();
3840 for member in members {
3841 if let Some(i) = self.user_identity(member.user_id()).await {
3842 ret.push(i);
3843 }
3844 }
3845 ret
3846 })
3847 }
3848
3849 fn user_identity<'a>(&'a self, user_id: &'a UserId) -> BoxFuture<'a, Option<UserIdentity>> {
3850 Box::pin(async {
3851 self.client
3852 .encryption()
3853 .get_user_identity(user_id)
3854 .await
3855 .unwrap_or(None)
3856 .map(|u| u.underlying_identity())
3857 })
3858 }
3859}
3860
3861#[derive(Clone, Debug)]
3864pub(crate) struct WeakRoom {
3865 client: WeakClient,
3866 room_id: OwnedRoomId,
3867}
3868
3869impl WeakRoom {
3870 pub fn new(client: WeakClient, room_id: OwnedRoomId) -> Self {
3872 Self { client, room_id }
3873 }
3874
3875 pub fn get(&self) -> Option<Room> {
3877 self.client.get().and_then(|client| client.get_room(&self.room_id))
3878 }
3879
3880 pub fn room_id(&self) -> &RoomId {
3882 &self.room_id
3883 }
3884}
3885
3886#[derive(Debug, Clone)]
3888pub struct Invite {
3889 pub invitee: RoomMember,
3891 pub inviter: Option<RoomMember>,
3893}
3894
3895#[derive(Error, Debug)]
3896enum InvitationError {
3897 #[error("No membership event found")]
3898 EventMissing,
3899}
3900
3901#[derive(Debug, Clone, Default)]
3903#[non_exhaustive]
3904pub struct Receipts {
3905 pub fully_read: Option<OwnedEventId>,
3907 pub public_read_receipt: Option<OwnedEventId>,
3909 pub private_read_receipt: Option<OwnedEventId>,
3911}
3912
3913impl Receipts {
3914 pub fn new() -> Self {
3916 Self::default()
3917 }
3918
3919 pub fn fully_read_marker(mut self, event_id: impl Into<Option<OwnedEventId>>) -> Self {
3928 self.fully_read = event_id.into();
3929 self
3930 }
3931
3932 pub fn public_read_receipt(mut self, event_id: impl Into<Option<OwnedEventId>>) -> Self {
3938 self.public_read_receipt = event_id.into();
3939 self
3940 }
3941
3942 pub fn private_read_receipt(mut self, event_id: impl Into<Option<OwnedEventId>>) -> Self {
3946 self.private_read_receipt = event_id.into();
3947 self
3948 }
3949
3950 pub fn is_empty(&self) -> bool {
3952 self.fully_read.is_none()
3953 && self.public_read_receipt.is_none()
3954 && self.private_read_receipt.is_none()
3955 }
3956}
3957
3958#[derive(Debug)]
3961pub enum ParentSpace {
3962 Reciprocal(Room),
3965 WithPowerlevel(Room),
3970 Illegitimate(Room),
3973 Unverifiable(OwnedRoomId),
3976}
3977
3978#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
3982pub struct ReportedContentScore(i8);
3983
3984impl ReportedContentScore {
3985 pub const MIN: Self = Self(-100);
3989
3990 pub const MAX: Self = Self(0);
3994
3995 pub fn new(value: i8) -> Option<Self> {
4004 value.try_into().ok()
4005 }
4006
4007 pub fn new_saturating(value: i8) -> Self {
4013 if value > Self::MAX {
4014 Self::MAX
4015 } else if value < Self::MIN {
4016 Self::MIN
4017 } else {
4018 Self(value)
4019 }
4020 }
4021
4022 pub fn value(&self) -> i8 {
4024 self.0
4025 }
4026}
4027
4028impl PartialEq<i8> for ReportedContentScore {
4029 fn eq(&self, other: &i8) -> bool {
4030 self.0.eq(other)
4031 }
4032}
4033
4034impl PartialEq<ReportedContentScore> for i8 {
4035 fn eq(&self, other: &ReportedContentScore) -> bool {
4036 self.eq(&other.0)
4037 }
4038}
4039
4040impl PartialOrd<i8> for ReportedContentScore {
4041 fn partial_cmp(&self, other: &i8) -> Option<std::cmp::Ordering> {
4042 self.0.partial_cmp(other)
4043 }
4044}
4045
4046impl PartialOrd<ReportedContentScore> for i8 {
4047 fn partial_cmp(&self, other: &ReportedContentScore) -> Option<std::cmp::Ordering> {
4048 self.partial_cmp(&other.0)
4049 }
4050}
4051
4052impl From<ReportedContentScore> for Int {
4053 fn from(value: ReportedContentScore) -> Self {
4054 value.0.into()
4055 }
4056}
4057
4058impl TryFrom<i8> for ReportedContentScore {
4059 type Error = TryFromReportedContentScoreError;
4060
4061 fn try_from(value: i8) -> std::prelude::v1::Result<Self, Self::Error> {
4062 if value > Self::MAX || value < Self::MIN {
4063 Err(TryFromReportedContentScoreError(()))
4064 } else {
4065 Ok(Self(value))
4066 }
4067 }
4068}
4069
4070impl TryFrom<i16> for ReportedContentScore {
4071 type Error = TryFromReportedContentScoreError;
4072
4073 fn try_from(value: i16) -> std::prelude::v1::Result<Self, Self::Error> {
4074 let value = i8::try_from(value).map_err(|_| TryFromReportedContentScoreError(()))?;
4075 value.try_into()
4076 }
4077}
4078
4079impl TryFrom<i32> for ReportedContentScore {
4080 type Error = TryFromReportedContentScoreError;
4081
4082 fn try_from(value: i32) -> std::prelude::v1::Result<Self, Self::Error> {
4083 let value = i8::try_from(value).map_err(|_| TryFromReportedContentScoreError(()))?;
4084 value.try_into()
4085 }
4086}
4087
4088impl TryFrom<i64> for ReportedContentScore {
4089 type Error = TryFromReportedContentScoreError;
4090
4091 fn try_from(value: i64) -> std::prelude::v1::Result<Self, Self::Error> {
4092 let value = i8::try_from(value).map_err(|_| TryFromReportedContentScoreError(()))?;
4093 value.try_into()
4094 }
4095}
4096
4097impl TryFrom<Int> for ReportedContentScore {
4098 type Error = TryFromReportedContentScoreError;
4099
4100 fn try_from(value: Int) -> std::prelude::v1::Result<Self, Self::Error> {
4101 let value = i8::try_from(value).map_err(|_| TryFromReportedContentScoreError(()))?;
4102 value.try_into()
4103 }
4104}
4105
4106trait EventSource {
4107 fn get_event(
4108 &self,
4109 event_id: &EventId,
4110 ) -> impl Future<Output = Result<TimelineEvent, Error>> + SendOutsideWasm;
4111}
4112
4113impl EventSource for &Room {
4114 async fn get_event(&self, event_id: &EventId) -> Result<TimelineEvent, Error> {
4115 self.load_or_fetch_event(event_id, None).await
4116 }
4117}
4118
4119#[derive(Debug, Clone, Error)]
4122#[error("out of range conversion attempted")]
4123pub struct TryFromReportedContentScoreError(());
4124
4125#[derive(Debug)]
4128pub struct RoomMemberWithSenderInfo {
4129 pub room_member: RoomMember,
4131 pub sender_info: Option<RoomMember>,
4134}
4135
4136#[cfg(all(test, not(target_family = "wasm")))]
4137mod tests {
4138 use std::collections::BTreeMap;
4139
4140 use matrix_sdk_base::{store::ComposerDraftType, ComposerDraft};
4141 use matrix_sdk_test::{
4142 async_test, event_factory::EventFactory, test_json, JoinedRoomBuilder, StateTestEvent,
4143 SyncResponseBuilder,
4144 };
4145 use ruma::{
4146 event_id,
4147 events::{relation::RelationType, room::member::MembershipState},
4148 int, owned_event_id, room_id, user_id, RoomVersionId,
4149 };
4150 use wiremock::{
4151 matchers::{header, method, path_regex},
4152 Mock, MockServer, ResponseTemplate,
4153 };
4154
4155 use super::ReportedContentScore;
4156 use crate::{
4157 config::RequestConfig,
4158 room::messages::{IncludeRelations, ListThreadsOptions, RelationsOptions},
4159 test_utils::{
4160 client::mock_matrix_session,
4161 logged_in_client,
4162 mocks::{MatrixMockServer, RoomRelationsResponseTemplate},
4163 },
4164 Client,
4165 };
4166
4167 #[cfg(all(feature = "sqlite", feature = "e2e-encryption"))]
4168 #[async_test]
4169 async fn test_cache_invalidation_while_encrypt() {
4170 use matrix_sdk_base::store::RoomLoadSettings;
4171 use matrix_sdk_test::{message_like_event_content, DEFAULT_TEST_ROOM_ID};
4172
4173 let sqlite_path = std::env::temp_dir().join("cache_invalidation_while_encrypt.db");
4174 let session = mock_matrix_session();
4175
4176 let client = Client::builder()
4177 .homeserver_url("http://localhost:1234")
4178 .request_config(RequestConfig::new().disable_retry())
4179 .sqlite_store(&sqlite_path, None)
4180 .build()
4181 .await
4182 .unwrap();
4183 client
4184 .matrix_auth()
4185 .restore_session(session.clone(), RoomLoadSettings::default())
4186 .await
4187 .unwrap();
4188
4189 client.encryption().enable_cross_process_store_lock("client1".to_owned()).await.unwrap();
4190
4191 let server = MockServer::start().await;
4193 {
4194 Mock::given(method("GET"))
4195 .and(path_regex(r"^/_matrix/client/r0/rooms/.*/state/m.*room.*encryption.?"))
4196 .and(header("authorization", "Bearer 1234"))
4197 .respond_with(
4198 ResponseTemplate::new(200)
4199 .set_body_json(&*test_json::sync_events::ENCRYPTION_CONTENT),
4200 )
4201 .mount(&server)
4202 .await;
4203 let response = SyncResponseBuilder::default()
4204 .add_joined_room(
4205 JoinedRoomBuilder::default()
4206 .add_state_event(StateTestEvent::Member)
4207 .add_state_event(StateTestEvent::PowerLevels)
4208 .add_state_event(StateTestEvent::Encryption),
4209 )
4210 .build_sync_response();
4211 client.base_client().receive_sync_response(response).await.unwrap();
4212 }
4213
4214 let room = client.get_room(&DEFAULT_TEST_ROOM_ID).expect("Room should exist");
4215
4216 room.preshare_room_key().await.unwrap();
4218
4219 {
4222 let client = Client::builder()
4223 .homeserver_url("http://localhost:1234")
4224 .request_config(RequestConfig::new().disable_retry())
4225 .sqlite_store(&sqlite_path, None)
4226 .build()
4227 .await
4228 .unwrap();
4229 client
4230 .matrix_auth()
4231 .restore_session(session.clone(), RoomLoadSettings::default())
4232 .await
4233 .unwrap();
4234 client
4235 .encryption()
4236 .enable_cross_process_store_lock("client2".to_owned())
4237 .await
4238 .unwrap();
4239
4240 let guard = client.encryption().spin_lock_store(None).await.unwrap();
4241 assert!(guard.is_some());
4242 }
4243
4244 let guard = client.encryption().spin_lock_store(None).await.unwrap();
4246 assert!(guard.is_some());
4247
4248 let olm = client.olm_machine().await;
4250 let olm = olm.as_ref().expect("Olm machine wasn't started");
4251
4252 let _encrypted_content = olm
4255 .encrypt_room_event_raw(room.room_id(), "test-event", &message_like_event_content!({}))
4256 .await
4257 .unwrap();
4258 }
4259
4260 #[test]
4261 fn reported_content_score() {
4262 let score = ReportedContentScore::new(0).unwrap();
4264 assert_eq!(score.value(), 0);
4265 let score = ReportedContentScore::new(-50).unwrap();
4266 assert_eq!(score.value(), -50);
4267 let score = ReportedContentScore::new(-100).unwrap();
4268 assert_eq!(score.value(), -100);
4269 assert_eq!(ReportedContentScore::new(10), None);
4270 assert_eq!(ReportedContentScore::new(-110), None);
4271
4272 let score = ReportedContentScore::new_saturating(0);
4273 assert_eq!(score.value(), 0);
4274 let score = ReportedContentScore::new_saturating(-50);
4275 assert_eq!(score.value(), -50);
4276 let score = ReportedContentScore::new_saturating(-100);
4277 assert_eq!(score.value(), -100);
4278 let score = ReportedContentScore::new_saturating(10);
4279 assert_eq!(score, ReportedContentScore::MAX);
4280 let score = ReportedContentScore::new_saturating(-110);
4281 assert_eq!(score, ReportedContentScore::MIN);
4282
4283 let score = ReportedContentScore::try_from(0i16).unwrap();
4285 assert_eq!(score.value(), 0);
4286 let score = ReportedContentScore::try_from(-100i16).unwrap();
4287 assert_eq!(score.value(), -100);
4288 ReportedContentScore::try_from(10i16).unwrap_err();
4289 ReportedContentScore::try_from(-110i16).unwrap_err();
4290
4291 let score = ReportedContentScore::try_from(0i32).unwrap();
4293 assert_eq!(score.value(), 0);
4294 let score = ReportedContentScore::try_from(-100i32).unwrap();
4295 assert_eq!(score.value(), -100);
4296 ReportedContentScore::try_from(10i32).unwrap_err();
4297 ReportedContentScore::try_from(-110i32).unwrap_err();
4298
4299 let score = ReportedContentScore::try_from(0i64).unwrap();
4301 assert_eq!(score.value(), 0);
4302 let score = ReportedContentScore::try_from(-100i64).unwrap();
4303 assert_eq!(score.value(), -100);
4304 ReportedContentScore::try_from(10i64).unwrap_err();
4305 ReportedContentScore::try_from(-110i64).unwrap_err();
4306
4307 let score = ReportedContentScore::try_from(int!(0)).unwrap();
4309 assert_eq!(score.value(), 0);
4310 let score = ReportedContentScore::try_from(int!(-100)).unwrap();
4311 assert_eq!(score.value(), -100);
4312 ReportedContentScore::try_from(int!(10)).unwrap_err();
4313 ReportedContentScore::try_from(int!(-110)).unwrap_err();
4314 }
4315
4316 #[async_test]
4317 async fn test_composer_draft() {
4318 use matrix_sdk_test::DEFAULT_TEST_ROOM_ID;
4319
4320 let client = logged_in_client(None).await;
4321
4322 let response = SyncResponseBuilder::default()
4323 .add_joined_room(JoinedRoomBuilder::default())
4324 .build_sync_response();
4325 client.base_client().receive_sync_response(response).await.unwrap();
4326 let room = client.get_room(&DEFAULT_TEST_ROOM_ID).expect("Room should exist");
4327
4328 assert_eq!(room.load_composer_draft(None).await.unwrap(), None);
4329
4330 let draft = ComposerDraft {
4333 plain_text: "Hello, world!".to_owned(),
4334 html_text: Some("<strong>Hello</strong>, world!".to_owned()),
4335 draft_type: ComposerDraftType::NewMessage,
4336 };
4337
4338 room.save_composer_draft(draft.clone(), None).await.unwrap();
4339
4340 let thread_root = owned_event_id!("$thread_root:b.c");
4341 let thread_draft = ComposerDraft {
4342 plain_text: "Hello, thread!".to_owned(),
4343 html_text: Some("<strong>Hello</strong>, thread!".to_owned()),
4344 draft_type: ComposerDraftType::NewMessage,
4345 };
4346
4347 room.save_composer_draft(thread_draft.clone(), Some(&thread_root)).await.unwrap();
4348
4349 assert_eq!(room.load_composer_draft(None).await.unwrap(), Some(draft));
4351
4352 assert_eq!(
4354 room.load_composer_draft(Some(&thread_root)).await.unwrap(),
4355 Some(thread_draft.clone())
4356 );
4357
4358 room.clear_composer_draft(None).await.unwrap();
4360 assert_eq!(room.load_composer_draft(None).await.unwrap(), None);
4361
4362 assert_eq!(room.load_composer_draft(Some(&thread_root)).await.unwrap(), Some(thread_draft));
4364
4365 room.clear_composer_draft(Some(&thread_root)).await.unwrap();
4367 assert_eq!(room.load_composer_draft(Some(&thread_root)).await.unwrap(), None);
4368 }
4369
4370 #[async_test]
4371 async fn test_mark_join_requests_as_seen() {
4372 let server = MatrixMockServer::new().await;
4373 let client = server.client_builder().build().await;
4374 let event_id = event_id!("$a:b.c");
4375 let room_id = room_id!("!a:b.c");
4376 let user_id = user_id!("@alice:b.c");
4377
4378 let f = EventFactory::new().room(room_id);
4379 let joined_room_builder = JoinedRoomBuilder::new(room_id).add_state_bulk(vec![f
4380 .member(user_id)
4381 .membership(MembershipState::Knock)
4382 .event_id(event_id)
4383 .into_raw()]);
4384 let room = server.sync_room(&client, joined_room_builder).await;
4385
4386 let seen_ids =
4388 room.get_seen_knock_request_ids().await.expect("Couldn't load seen join request ids");
4389 assert!(seen_ids.is_empty());
4390
4391 room.mark_knock_requests_as_seen(&[user_id.to_owned()])
4393 .await
4394 .expect("Couldn't mark join request as seen");
4395
4396 let seen_ids =
4398 room.get_seen_knock_request_ids().await.expect("Couldn't load seen join request ids");
4399 assert_eq!(seen_ids.len(), 1);
4400 assert_eq!(
4401 seen_ids.into_iter().next().expect("No next value"),
4402 (event_id.to_owned(), user_id.to_owned())
4403 )
4404 }
4405
4406 #[async_test]
4407 async fn test_own_room_membership_with_no_own_member_event() {
4408 let server = MatrixMockServer::new().await;
4409 let client = server.client_builder().build().await;
4410 let room_id = room_id!("!a:b.c");
4411
4412 let room = server.sync_joined_room(&client, room_id).await;
4413
4414 let error = room.member_with_sender_info(client.user_id().unwrap()).await.err();
4417 assert!(error.is_some());
4418 }
4419
4420 #[async_test]
4421 async fn test_own_room_membership_with_own_member_event_but_unknown_sender() {
4422 let server = MatrixMockServer::new().await;
4423 let client = server.client_builder().build().await;
4424 let room_id = room_id!("!a:b.c");
4425 let user_id = user_id!("@example:localhost");
4426
4427 let f = EventFactory::new().room(room_id).sender(user_id!("@alice:b.c"));
4428 let joined_room_builder =
4429 JoinedRoomBuilder::new(room_id).add_state_bulk(vec![f.member(user_id).into_raw()]);
4430 let room = server.sync_room(&client, joined_room_builder).await;
4431
4432 let ret = room
4434 .member_with_sender_info(client.user_id().unwrap())
4435 .await
4436 .expect("Room member info should be available");
4437
4438 assert_eq!(ret.room_member.event().user_id(), user_id);
4440
4441 assert!(ret.sender_info.is_none());
4443 }
4444
4445 #[async_test]
4446 async fn test_own_room_membership_with_own_member_event_and_own_sender() {
4447 let server = MatrixMockServer::new().await;
4448 let client = server.client_builder().build().await;
4449 let room_id = room_id!("!a:b.c");
4450 let user_id = user_id!("@example:localhost");
4451
4452 let f = EventFactory::new().room(room_id).sender(user_id);
4453 let joined_room_builder =
4454 JoinedRoomBuilder::new(room_id).add_state_bulk(vec![f.member(user_id).into_raw()]);
4455 let room = server.sync_room(&client, joined_room_builder).await;
4456
4457 let ret = room
4459 .member_with_sender_info(client.user_id().unwrap())
4460 .await
4461 .expect("Room member info should be available");
4462
4463 assert_eq!(ret.room_member.event().user_id(), user_id);
4465
4466 assert!(ret.sender_info.is_some());
4468 assert_eq!(ret.sender_info.unwrap().event().user_id(), user_id);
4469 }
4470
4471 #[async_test]
4472 async fn test_own_room_membership_with_own_member_event_and_known_sender() {
4473 let server = MatrixMockServer::new().await;
4474 let client = server.client_builder().build().await;
4475 let room_id = room_id!("!a:b.c");
4476 let user_id = user_id!("@example:localhost");
4477 let sender_id = user_id!("@alice:b.c");
4478
4479 let f = EventFactory::new().room(room_id).sender(sender_id);
4480 let joined_room_builder = JoinedRoomBuilder::new(room_id).add_state_bulk(vec![
4481 f.member(user_id).into_raw(),
4482 f.member(sender_id).into_raw(),
4484 ]);
4485 let room = server.sync_room(&client, joined_room_builder).await;
4486
4487 let ret = room
4489 .member_with_sender_info(client.user_id().unwrap())
4490 .await
4491 .expect("Room member info should be available");
4492
4493 assert_eq!(ret.room_member.event().user_id(), user_id);
4495
4496 assert!(ret.sender_info.is_some());
4498 assert_eq!(ret.sender_info.unwrap().event().user_id(), sender_id);
4499 }
4500
4501 #[async_test]
4502 async fn test_own_room_membership_with_own_member_event_and_unknown_but_available_sender() {
4503 let server = MatrixMockServer::new().await;
4504 let client = server.client_builder().build().await;
4505 let room_id = room_id!("!a:b.c");
4506 let user_id = user_id!("@example:localhost");
4507 let sender_id = user_id!("@alice:b.c");
4508
4509 let f = EventFactory::new().room(room_id).sender(sender_id);
4510 let joined_room_builder =
4511 JoinedRoomBuilder::new(room_id).add_state_bulk(vec![f.member(user_id).into_raw()]);
4512 let room = server.sync_room(&client, joined_room_builder).await;
4513
4514 server
4516 .mock_get_members()
4517 .ok(vec![f.member(sender_id).into_raw()])
4518 .mock_once()
4519 .mount()
4520 .await;
4521
4522 let ret = room
4524 .member_with_sender_info(client.user_id().unwrap())
4525 .await
4526 .expect("Room member info should be available");
4527
4528 assert_eq!(ret.room_member.event().user_id(), user_id);
4530
4531 assert!(ret.sender_info.is_some());
4533 assert_eq!(ret.sender_info.unwrap().event().user_id(), sender_id);
4534 }
4535
4536 #[async_test]
4537 async fn test_list_threads() {
4538 let server = MatrixMockServer::new().await;
4539 let client = server.client_builder().build().await;
4540
4541 let room_id = room_id!("!a:b.c");
4542 let sender_id = user_id!("@alice:b.c");
4543 let f = EventFactory::new().room(room_id).sender(sender_id);
4544
4545 let eid1 = event_id!("$1");
4546 let eid2 = event_id!("$2");
4547 let batch1 = vec![f.text_msg("Thread root 1").event_id(eid1).into_raw()];
4548 let batch2 = vec![f.text_msg("Thread root 2").event_id(eid2).into_raw()];
4549
4550 server
4551 .mock_room_threads()
4552 .ok(batch1.clone(), Some("prev_batch".to_owned()))
4553 .mock_once()
4554 .mount()
4555 .await;
4556 server
4557 .mock_room_threads()
4558 .match_from("prev_batch")
4559 .ok(batch2, None)
4560 .mock_once()
4561 .mount()
4562 .await;
4563
4564 let room = server.sync_joined_room(&client, room_id).await;
4565 let result =
4566 room.list_threads(ListThreadsOptions::default()).await.expect("Failed to list threads");
4567 assert_eq!(result.chunk.len(), 1);
4568 assert_eq!(result.chunk[0].event_id().unwrap(), eid1);
4569 assert!(result.prev_batch_token.is_some());
4570
4571 let opts = ListThreadsOptions { from: result.prev_batch_token, ..Default::default() };
4572 let result = room.list_threads(opts).await.expect("Failed to list threads");
4573 assert_eq!(result.chunk.len(), 1);
4574 assert_eq!(result.chunk[0].event_id().unwrap(), eid2);
4575 assert!(result.prev_batch_token.is_none());
4576 }
4577
4578 #[async_test]
4579 async fn test_relations() {
4580 let server = MatrixMockServer::new().await;
4581 let client = server.client_builder().build().await;
4582
4583 let room_id = room_id!("!a:b.c");
4584 let sender_id = user_id!("@alice:b.c");
4585 let f = EventFactory::new().room(room_id).sender(sender_id);
4586
4587 let target_event_id = owned_event_id!("$target");
4588 let eid1 = event_id!("$1");
4589 let eid2 = event_id!("$2");
4590 let batch1 = vec![f.text_msg("Related event 1").event_id(eid1).into_raw()];
4591 let batch2 = vec![f.text_msg("Related event 2").event_id(eid2).into_raw()];
4592
4593 server
4594 .mock_room_relations()
4595 .match_target_event(target_event_id.clone())
4596 .ok(RoomRelationsResponseTemplate::default().events(batch1).next_batch("next_batch"))
4597 .mock_once()
4598 .mount()
4599 .await;
4600
4601 server
4602 .mock_room_relations()
4603 .match_target_event(target_event_id.clone())
4604 .match_from("next_batch")
4605 .ok(RoomRelationsResponseTemplate::default().events(batch2))
4606 .mock_once()
4607 .mount()
4608 .await;
4609
4610 let room = server.sync_joined_room(&client, room_id).await;
4611
4612 let mut opts = RelationsOptions {
4614 include_relations: IncludeRelations::AllRelations,
4615 ..Default::default()
4616 };
4617 let result = room
4618 .relations(target_event_id.clone(), opts.clone())
4619 .await
4620 .expect("Failed to list relations the first time");
4621 assert_eq!(result.chunk.len(), 1);
4622 assert_eq!(result.chunk[0].event_id().unwrap(), eid1);
4623 assert!(result.prev_batch_token.is_none());
4624 assert!(result.next_batch_token.is_some());
4625 assert!(result.recursion_depth.is_none());
4626
4627 opts.from = result.next_batch_token;
4628 let result = room
4629 .relations(target_event_id, opts)
4630 .await
4631 .expect("Failed to list relations the second time");
4632 assert_eq!(result.chunk.len(), 1);
4633 assert_eq!(result.chunk[0].event_id().unwrap(), eid2);
4634 assert!(result.prev_batch_token.is_none());
4635 assert!(result.next_batch_token.is_none());
4636 assert!(result.recursion_depth.is_none());
4637 }
4638
4639 #[async_test]
4640 async fn test_relations_with_reltype() {
4641 let server = MatrixMockServer::new().await;
4642 let client = server.client_builder().build().await;
4643
4644 let room_id = room_id!("!a:b.c");
4645 let sender_id = user_id!("@alice:b.c");
4646 let f = EventFactory::new().room(room_id).sender(sender_id);
4647
4648 let target_event_id = owned_event_id!("$target");
4649 let eid1 = event_id!("$1");
4650 let eid2 = event_id!("$2");
4651 let batch1 = vec![f.text_msg("In-thread event 1").event_id(eid1).into_raw()];
4652 let batch2 = vec![f.text_msg("In-thread event 2").event_id(eid2).into_raw()];
4653
4654 server
4655 .mock_room_relations()
4656 .match_target_event(target_event_id.clone())
4657 .match_subrequest(IncludeRelations::RelationsOfType(RelationType::Thread))
4658 .ok(RoomRelationsResponseTemplate::default().events(batch1).next_batch("next_batch"))
4659 .mock_once()
4660 .mount()
4661 .await;
4662
4663 server
4664 .mock_room_relations()
4665 .match_target_event(target_event_id.clone())
4666 .match_from("next_batch")
4667 .match_subrequest(IncludeRelations::RelationsOfType(RelationType::Thread))
4668 .ok(RoomRelationsResponseTemplate::default().events(batch2))
4669 .mock_once()
4670 .mount()
4671 .await;
4672
4673 let room = server.sync_joined_room(&client, room_id).await;
4674
4675 let mut opts = RelationsOptions {
4677 include_relations: IncludeRelations::RelationsOfType(RelationType::Thread),
4678 ..Default::default()
4679 };
4680 let result = room
4681 .relations(target_event_id.clone(), opts.clone())
4682 .await
4683 .expect("Failed to list relations the first time");
4684 assert_eq!(result.chunk.len(), 1);
4685 assert_eq!(result.chunk[0].event_id().unwrap(), eid1);
4686 assert!(result.prev_batch_token.is_none());
4687 assert!(result.next_batch_token.is_some());
4688 assert!(result.recursion_depth.is_none());
4689
4690 opts.from = result.next_batch_token;
4691 let result = room
4692 .relations(target_event_id, opts)
4693 .await
4694 .expect("Failed to list relations the second time");
4695 assert_eq!(result.chunk.len(), 1);
4696 assert_eq!(result.chunk[0].event_id().unwrap(), eid2);
4697 assert!(result.prev_batch_token.is_none());
4698 assert!(result.next_batch_token.is_none());
4699 assert!(result.recursion_depth.is_none());
4700 }
4701
4702 #[async_test]
4703 async fn test_power_levels_computation() {
4704 let server = MatrixMockServer::new().await;
4705 let client = server.client_builder().build().await;
4706
4707 let room_id = room_id!("!a:b.c");
4708 let sender_id = client.user_id().expect("No session id");
4709 let f = EventFactory::new().room(room_id).sender(sender_id);
4710 let mut user_map = BTreeMap::from([(sender_id.into(), 50.into())]);
4711
4712 let room_create_event = f.create(sender_id, RoomVersionId::V1).state_key("").into_raw();
4714 let power_levels_event = f.power_levels(&mut user_map).state_key("").into_raw();
4715 let room_member_event = f.member(sender_id).into_raw();
4716
4717 let room = server
4719 .sync_room(
4720 &client,
4721 JoinedRoomBuilder::new(room_id).add_state_bulk([room_member_event.clone()]),
4722 )
4723 .await;
4724 let ctx = room
4725 .push_condition_room_ctx()
4726 .await
4727 .expect("Failed to get push condition context")
4728 .expect("Could not get push condition context");
4729
4730 assert!(ctx.power_levels.is_none());
4732
4733 let room = server
4735 .sync_room(
4736 &client,
4737 JoinedRoomBuilder::new(room_id).add_state_bulk([room_create_event.clone()]),
4738 )
4739 .await;
4740 let ctx = room
4741 .push_condition_room_ctx()
4742 .await
4743 .expect("Failed to get push condition context")
4744 .expect("Could not get push condition context");
4745
4746 assert!(ctx.power_levels.is_none());
4748
4749 let room = server
4751 .sync_room(
4752 &client,
4753 JoinedRoomBuilder::new(room_id).add_state_bulk([power_levels_event]),
4754 )
4755 .await;
4756 let ctx = room
4757 .push_condition_room_ctx()
4758 .await
4759 .expect("Failed to get push condition context")
4760 .expect("Could not get push condition context");
4761
4762 assert!(ctx.power_levels.is_some());
4764 }
4765}