1use std::future::Future;
16
17use eyeball::Subscriber;
18use indexmap::IndexMap;
19use matrix_sdk::{
20 Result, Room, SendOutsideWasm,
21 deserialized_responses::TimelineEvent,
22 paginators::{PaginableRoom, thread::PaginableThread},
23};
24use matrix_sdk_base::{
25 RoomInfo, crypto::types::events::CryptoContextInfo, latest_event::LatestEvent,
26};
27use ruma::{
28 EventId, OwnedEventId, OwnedTransactionId, OwnedUserId, UserId,
29 events::{
30 AnyMessageLikeEventContent,
31 fully_read::FullyReadEventContent,
32 receipt::{Receipt, ReceiptThread, ReceiptType},
33 },
34 room_version_rules::RoomVersionRules,
35};
36use tracing::error;
37
38use super::{EventTimelineItem, Profile, RedactError, TimelineBuilder};
39use crate::timeline::{
40 self, Timeline, latest_event::LatestEventValue, pinned_events_loader::PinnedEventsRoom,
41};
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 fn latest_event_item(
67 &self,
68 ) -> impl Future<Output = Option<EventTimelineItem>> + SendOutsideWasm;
69
70 fn new_latest_event(&self) -> impl Future<Output = LatestEventValue>;
72}
73
74impl RoomExt for Room {
75 async fn timeline(&self) -> Result<Timeline, timeline::Error> {
76 self.timeline_builder().build().await
77 }
78
79 fn timeline_builder(&self) -> TimelineBuilder {
80 TimelineBuilder::new(self).track_read_marker_and_receipts()
81 }
82
83 async fn latest_event_item(&self) -> Option<EventTimelineItem> {
84 if let Some(latest_event) = self.latest_event() {
85 EventTimelineItem::from_latest_event(self.client(), self.room_id(), latest_event).await
86 } else {
87 None
88 }
89 }
90
91 async fn new_latest_event(&self) -> LatestEventValue {
92 LatestEventValue::from_base_latest_event_value(
93 (**self).new_latest_event(),
94 self,
95 &self.client(),
96 )
97 .await
98 }
99}
100
101pub(super) trait RoomDataProvider:
102 Clone + PaginableRoom + PaginableThread + PinnedEventsRoom + 'static
103{
104 fn own_user_id(&self) -> &UserId;
105 fn room_version_rules(&self) -> RoomVersionRules;
106
107 fn crypto_context_info(&self)
108 -> impl Future<Output = CryptoContextInfo> + SendOutsideWasm + '_;
109
110 fn profile_from_user_id<'a>(
111 &'a self,
112 user_id: &'a UserId,
113 ) -> impl Future<Output = Option<Profile>> + SendOutsideWasm + 'a;
114 fn profile_from_latest_event(&self, latest_event: &LatestEvent) -> Option<Profile>;
115
116 fn load_user_receipt<'a>(
118 &'a self,
119 receipt_type: ReceiptType,
120 thread: ReceiptThread,
121 user_id: &'a UserId,
122 ) -> impl Future<Output = Option<(OwnedEventId, Receipt)>> + SendOutsideWasm + 'a;
123
124 fn load_event_receipts<'a>(
126 &'a self,
127 event_id: &'a EventId,
128 receipt_thread: ReceiptThread,
129 ) -> impl Future<Output = IndexMap<OwnedUserId, Receipt>> + SendOutsideWasm + 'a;
130
131 fn load_fully_read_marker(&self) -> impl Future<Output = Option<OwnedEventId>> + '_;
133
134 fn send(
136 &self,
137 content: AnyMessageLikeEventContent,
138 ) -> impl Future<Output = Result<(), super::Error>> + SendOutsideWasm + '_;
139
140 fn redact<'a>(
142 &'a self,
143 event_id: &'a EventId,
144 reason: Option<&'a str>,
145 transaction_id: Option<OwnedTransactionId>,
146 ) -> impl Future<Output = Result<(), super::Error>> + SendOutsideWasm + 'a;
147
148 fn room_info(&self) -> Subscriber<RoomInfo>;
149
150 fn load_event<'a>(
152 &'a self,
153 event_id: &'a EventId,
154 ) -> impl Future<Output = Result<TimelineEvent>> + SendOutsideWasm + 'a;
155}
156
157impl RoomDataProvider for Room {
158 fn own_user_id(&self) -> &UserId {
159 (**self).own_user_id()
160 }
161
162 fn room_version_rules(&self) -> RoomVersionRules {
163 (**self).clone_info().room_version_rules_or_default()
164 }
165
166 async fn crypto_context_info(&self) -> CryptoContextInfo {
167 self.crypto_context_info().await
168 }
169
170 async fn profile_from_user_id<'a>(&'a self, user_id: &'a UserId) -> Option<Profile> {
171 match self.get_member_no_sync(user_id).await {
172 Ok(Some(member)) => Some(Profile {
173 display_name: member.display_name().map(ToOwned::to_owned),
174 display_name_ambiguous: member.name_ambiguous(),
175 avatar_url: member.avatar_url().map(ToOwned::to_owned),
176 }),
177 Ok(None) if self.are_members_synced() => Some(Profile::default()),
178 Ok(None) => None,
179 Err(e) => {
180 error!(%user_id, "Failed to fetch room member information: {e}");
181 None
182 }
183 }
184 }
185
186 fn profile_from_latest_event(&self, latest_event: &LatestEvent) -> Option<Profile> {
187 if !latest_event.has_sender_profile() {
188 return None;
189 }
190
191 Some(Profile {
192 display_name: latest_event.sender_display_name().map(ToOwned::to_owned),
193 display_name_ambiguous: latest_event.sender_name_ambiguous().unwrap_or(false),
194 avatar_url: latest_event.sender_avatar_url().map(ToOwned::to_owned),
195 })
196 }
197
198 async fn load_user_receipt<'a>(
199 &'a self,
200 receipt_type: ReceiptType,
201 thread: ReceiptThread,
202 user_id: &'a UserId,
203 ) -> Option<(OwnedEventId, Receipt)> {
204 match self.load_user_receipt(receipt_type.clone(), thread.clone(), user_id).await {
205 Ok(receipt) => receipt,
206 Err(e) => {
207 error!(
208 ?receipt_type,
209 ?thread,
210 ?user_id,
211 "Failed to get read receipt for user: {e}"
212 );
213 None
214 }
215 }
216 }
217
218 async fn load_event_receipts<'a>(
219 &'a self,
220 event_id: &'a EventId,
221 receipt_thread: ReceiptThread,
222 ) -> IndexMap<OwnedUserId, Receipt> {
223 match self.load_event_receipts(ReceiptType::Read, receipt_thread.clone(), event_id).await {
224 Ok(receipts) => receipts.into_iter().collect(),
225 Err(e) => {
226 error!(?event_id, ?receipt_thread, "Failed to get read receipts for event: {e}");
227 IndexMap::new()
228 }
229 }
230 }
231
232 async fn load_fully_read_marker(&self) -> Option<OwnedEventId> {
233 match self.account_data_static::<FullyReadEventContent>().await {
234 Ok(Some(fully_read)) => match fully_read.deserialize() {
235 Ok(fully_read) => Some(fully_read.content.event_id),
236 Err(e) => {
237 error!("Failed to deserialize fully-read account data: {e}");
238 None
239 }
240 },
241 Err(e) => {
242 error!("Failed to get fully-read account data from the store: {e}");
243 None
244 }
245 _ => None,
246 }
247 }
248
249 async fn send(&self, content: AnyMessageLikeEventContent) -> Result<(), super::Error> {
250 let _ = self.send_queue().send(content).await?;
251 Ok(())
252 }
253
254 async fn redact<'a>(
255 &'a self,
256 event_id: &'a EventId,
257 reason: Option<&'a str>,
258 transaction_id: Option<OwnedTransactionId>,
259 ) -> Result<(), super::Error> {
260 let _ = self
261 .redact(event_id, reason, transaction_id)
262 .await
263 .map_err(RedactError::HttpError)
264 .map_err(super::Error::RedactError)?;
265 Ok(())
266 }
267
268 fn room_info(&self) -> Subscriber<RoomInfo> {
269 self.subscribe_info()
270 }
271
272 async fn load_event<'a>(&'a self, event_id: &'a EventId) -> Result<TimelineEvent> {
273 self.load_or_fetch_event(event_id, None).await
274 }
275}