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