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