1use std::future::Future;
16
17use eyeball::Subscriber;
18use indexmap::IndexMap;
19#[cfg(test)]
20use matrix_sdk::crypto::{DecryptionSettings, RoomEventDecryptionResult, TrustRequirement};
21use matrix_sdk::{
22 crypto::types::events::CryptoContextInfo,
23 deserialized_responses::{EncryptionInfo, TimelineEvent},
24 event_cache::paginator::PaginableRoom,
25 AsyncTraitDeps, Result, Room, SendOutsideWasm,
26};
27use matrix_sdk_base::{latest_event::LatestEvent, RoomInfo};
28use ruma::{
29 events::{
30 fully_read::FullyReadEventContent,
31 receipt::{Receipt, ReceiptThread, ReceiptType},
32 AnyMessageLikeEventContent, AnySyncTimelineEvent,
33 },
34 push::{PushConditionRoomCtx, Ruleset},
35 serde::Raw,
36 EventId, OwnedEventId, OwnedTransactionId, OwnedUserId, RoomVersionId, UserId,
37};
38use tracing::{debug, error};
39
40use super::{Profile, RedactError, TimelineBuilder};
41use crate::timeline::{self, pinned_events_loader::PinnedEventsRoom, Timeline};
42
43pub trait RoomExt {
44 fn timeline(&self)
52 -> impl Future<Output = Result<Timeline, timeline::Error>> + SendOutsideWasm;
53
54 fn timeline_builder(&self) -> TimelineBuilder;
63}
64
65impl RoomExt for Room {
66 async fn timeline(&self) -> Result<Timeline, timeline::Error> {
67 self.timeline_builder().build().await
68 }
69
70 fn timeline_builder(&self) -> TimelineBuilder {
71 Timeline::builder(self).track_read_marker_and_receipts()
72 }
73}
74
75pub(super) trait RoomDataProvider:
76 Clone + PaginableRoom + PinnedEventsRoom + 'static
77{
78 fn own_user_id(&self) -> &UserId;
79 fn room_version(&self) -> RoomVersionId;
80
81 fn crypto_context_info(&self)
82 -> impl Future<Output = CryptoContextInfo> + SendOutsideWasm + '_;
83
84 fn profile_from_user_id<'a>(
85 &'a self,
86 user_id: &'a UserId,
87 ) -> impl Future<Output = Option<Profile>> + SendOutsideWasm + 'a;
88 fn profile_from_latest_event(&self, latest_event: &LatestEvent) -> Option<Profile>;
89
90 fn load_user_receipt<'a>(
92 &'a self,
93 receipt_type: ReceiptType,
94 thread: ReceiptThread,
95 user_id: &'a UserId,
96 ) -> impl Future<Output = Option<(OwnedEventId, Receipt)>> + SendOutsideWasm + 'a;
97
98 fn load_event_receipts<'a>(
100 &'a self,
101 event_id: &'a EventId,
102 ) -> impl Future<Output = IndexMap<OwnedUserId, Receipt>> + SendOutsideWasm + 'a;
103
104 fn load_fully_read_marker(&self) -> impl Future<Output = Option<OwnedEventId>> + '_;
106
107 fn push_rules_and_context(
108 &self,
109 ) -> impl Future<Output = Option<(Ruleset, PushConditionRoomCtx)>> + SendOutsideWasm + '_;
110
111 fn send(
113 &self,
114 content: AnyMessageLikeEventContent,
115 ) -> impl Future<Output = Result<(), super::Error>> + SendOutsideWasm + '_;
116
117 fn redact<'a>(
119 &'a self,
120 event_id: &'a EventId,
121 reason: Option<&'a str>,
122 transaction_id: Option<OwnedTransactionId>,
123 ) -> impl Future<Output = Result<(), super::Error>> + SendOutsideWasm + 'a;
124
125 fn room_info(&self) -> Subscriber<RoomInfo>;
126
127 fn get_encryption_info(
130 &self,
131 session_id: &str,
132 sender: &UserId,
133 ) -> impl Future<Output = Option<EncryptionInfo>> + SendOutsideWasm;
134}
135
136impl RoomDataProvider for Room {
137 fn own_user_id(&self) -> &UserId {
138 (**self).own_user_id()
139 }
140
141 fn room_version(&self) -> RoomVersionId {
142 (**self).clone_info().room_version_or_default()
143 }
144
145 async fn crypto_context_info(&self) -> CryptoContextInfo {
146 self.crypto_context_info().await
147 }
148
149 async fn profile_from_user_id<'a>(&'a self, user_id: &'a UserId) -> Option<Profile> {
150 match self.get_member_no_sync(user_id).await {
151 Ok(Some(member)) => Some(Profile {
152 display_name: member.display_name().map(ToOwned::to_owned),
153 display_name_ambiguous: member.name_ambiguous(),
154 avatar_url: member.avatar_url().map(ToOwned::to_owned),
155 }),
156 Ok(None) if self.are_members_synced() => Some(Profile::default()),
157 Ok(None) => None,
158 Err(e) => {
159 error!(%user_id, "Failed to fetch room member information: {e}");
160 None
161 }
162 }
163 }
164
165 fn profile_from_latest_event(&self, latest_event: &LatestEvent) -> Option<Profile> {
166 if !latest_event.has_sender_profile() {
167 return None;
168 }
169
170 Some(Profile {
171 display_name: latest_event.sender_display_name().map(ToOwned::to_owned),
172 display_name_ambiguous: latest_event.sender_name_ambiguous().unwrap_or(false),
173 avatar_url: latest_event.sender_avatar_url().map(ToOwned::to_owned),
174 })
175 }
176
177 async fn load_user_receipt<'a>(
178 &'a self,
179 receipt_type: ReceiptType,
180 thread: ReceiptThread,
181 user_id: &'a UserId,
182 ) -> Option<(OwnedEventId, Receipt)> {
183 match self.load_user_receipt(receipt_type.clone(), thread.clone(), user_id).await {
184 Ok(receipt) => receipt,
185 Err(e) => {
186 error!(
187 ?receipt_type,
188 ?thread,
189 ?user_id,
190 "Failed to get read receipt for user: {e}"
191 );
192 None
193 }
194 }
195 }
196
197 async fn load_event_receipts<'a>(
198 &'a self,
199 event_id: &'a EventId,
200 ) -> IndexMap<OwnedUserId, Receipt> {
201 let mut unthreaded_receipts = match self
202 .load_event_receipts(ReceiptType::Read, ReceiptThread::Unthreaded, event_id)
203 .await
204 {
205 Ok(receipts) => receipts.into_iter().collect(),
206 Err(e) => {
207 error!(?event_id, "Failed to get unthreaded read receipts for event: {e}");
208 IndexMap::new()
209 }
210 };
211
212 let main_thread_receipts = match self
213 .load_event_receipts(ReceiptType::Read, ReceiptThread::Main, event_id)
214 .await
215 {
216 Ok(receipts) => receipts,
217 Err(e) => {
218 error!(?event_id, "Failed to get main thread read receipts for event: {e}");
219 Vec::new()
220 }
221 };
222
223 unthreaded_receipts.extend(main_thread_receipts);
224 unthreaded_receipts
225 }
226
227 async fn push_rules_and_context(&self) -> Option<(Ruleset, PushConditionRoomCtx)> {
228 match self.push_context().await {
229 Ok(Some(push_context)) => match self.client().account().push_rules().await {
230 Ok(push_rules) => Some((push_rules, push_context)),
231 Err(e) => {
232 error!("Could not get push rules: {e}");
233 None
234 }
235 },
236 Ok(None) => {
237 debug!("Could not aggregate push context");
238 None
239 }
240 Err(e) => {
241 error!("Could not get push context: {e}");
242 None
243 }
244 }
245 }
246
247 async fn load_fully_read_marker(&self) -> Option<OwnedEventId> {
248 match self.account_data_static::<FullyReadEventContent>().await {
249 Ok(Some(fully_read)) => match fully_read.deserialize() {
250 Ok(fully_read) => Some(fully_read.content.event_id),
251 Err(e) => {
252 error!("Failed to deserialize fully-read account data: {e}");
253 None
254 }
255 },
256 Err(e) => {
257 error!("Failed to get fully-read account data from the store: {e}");
258 None
259 }
260 _ => None,
261 }
262 }
263
264 async fn send(&self, content: AnyMessageLikeEventContent) -> Result<(), super::Error> {
265 let _ = self.send_queue().send(content).await?;
266 Ok(())
267 }
268
269 async fn redact<'a>(
270 &'a self,
271 event_id: &'a EventId,
272 reason: Option<&'a str>,
273 transaction_id: Option<OwnedTransactionId>,
274 ) -> Result<(), super::Error> {
275 let _ = self
276 .redact(event_id, reason, transaction_id)
277 .await
278 .map_err(RedactError::HttpError)
279 .map_err(super::Error::RedactError)?;
280 Ok(())
281 }
282
283 fn room_info(&self) -> Subscriber<RoomInfo> {
284 self.subscribe_info()
285 }
286
287 async fn get_encryption_info(
288 &self,
289 session_id: &str,
290 sender: &UserId,
291 ) -> Option<EncryptionInfo> {
292 self.get_encryption_info(session_id, sender).await
294 }
295}
296
297pub(super) trait Decryptor: AsyncTraitDeps + Clone + 'static {
300 fn decrypt_event_impl(
301 &self,
302 raw: &Raw<AnySyncTimelineEvent>,
303 ) -> impl Future<Output = Result<TimelineEvent>> + SendOutsideWasm;
304}
305
306impl Decryptor for Room {
307 async fn decrypt_event_impl(&self, raw: &Raw<AnySyncTimelineEvent>) -> Result<TimelineEvent> {
308 self.decrypt_event(raw.cast_ref()).await
309 }
310}
311
312#[cfg(test)]
313impl Decryptor for (matrix_sdk_base::crypto::OlmMachine, ruma::OwnedRoomId) {
314 async fn decrypt_event_impl(&self, raw: &Raw<AnySyncTimelineEvent>) -> Result<TimelineEvent> {
315 let (olm_machine, room_id) = self;
316 let decryption_settings =
317 DecryptionSettings { sender_device_trust_requirement: TrustRequirement::Untrusted };
318 match olm_machine
319 .try_decrypt_room_event(raw.cast_ref(), room_id, &decryption_settings)
320 .await?
321 {
322 RoomEventDecryptionResult::Decrypted(decrypted) => Ok(decrypted.into()),
323 RoomEventDecryptionResult::UnableToDecrypt(utd_info) => {
324 Ok(TimelineEvent::new_utd_event(raw.clone(), utd_info))
325 }
326 }
327 }
328}