matrix_sdk_base/response_processors/
timeline.rs1use matrix_sdk_common::{deserialized_responses::TimelineEvent, timer};
16#[cfg(feature = "e2e-encryption")]
17use ruma::events::SyncMessageLikeEvent;
18use ruma::{
19 UInt, UserId, assign,
20 events::{AnySyncMessageLikeEvent, AnySyncTimelineEvent},
21 push::{Action, PushConditionRoomCtx},
22};
23use tracing::{instrument, trace, warn};
24
25use super::{Context, notification};
26#[cfg(feature = "e2e-encryption")]
27use super::{e2ee, verification};
28use crate::{Result, Room, RoomInfo, sync::Timeline};
29
30#[allow(clippy::extra_unused_lifetimes)]
38#[instrument(skip_all, fields(room_id = ?room_info.room_id))]
39pub async fn build<'notification, 'e2ee>(
40 context: &mut Context,
41 room: &Room,
42 room_info: &mut RoomInfo,
43 timeline_inputs: builder::Timeline,
44 mut notification: notification::Notification<'notification>,
45 #[cfg(feature = "e2e-encryption")] e2ee: e2ee::E2EE<'e2ee>,
46) -> Result<Timeline> {
47 let _timer = timer!(tracing::Level::TRACE, "build a timeline from sync");
48
49 let mut timeline = Timeline::new(timeline_inputs.limited, timeline_inputs.prev_batch);
50 let mut push_condition_room_ctx = get_push_room_context(context, room, room_info).await?;
51 let room_id = room.room_id();
52
53 for raw_event in timeline_inputs.raw_events {
54 let mut timeline_event = TimelineEvent::from_plaintext(raw_event);
57
58 match timeline_event.raw().deserialize() {
60 Ok(sync_timeline_event) => {
61 match &sync_timeline_event {
62 AnySyncTimelineEvent::State(_) => {
64 }
66
67 AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomRedaction(
69 redaction_event,
70 )) => {
71 let redaction_rules = room_info.room_version_rules_or_default().redaction;
72
73 if let Some(redacts) = redaction_event.redacts(&redaction_rules) {
74 room_info.handle_redaction(
75 redaction_event,
76 timeline_event.raw().cast_ref_unchecked(),
77 );
78
79 context.state_changes.add_redaction(
80 room_id,
81 redacts,
82 timeline_event.raw().clone().cast_unchecked(),
83 );
84 }
85 }
86
87 #[cfg(feature = "e2e-encryption")]
89 AnySyncTimelineEvent::MessageLike(sync_message_like_event) => {
90 match sync_message_like_event {
91 AnySyncMessageLikeEvent::RoomEncrypted(
92 SyncMessageLikeEvent::Original(_),
93 ) => {
94 if let Some(decrypted_timeline_event) =
95 Box::pin(e2ee::decrypt::sync_timeline_event(
96 e2ee.clone(),
97 timeline_event.raw(),
98 room_id,
99 ))
100 .await?
101 {
102 timeline_event = decrypted_timeline_event;
103 }
104 }
105
106 _ => {
107 Box::pin(verification::process_if_relevant(
108 &sync_timeline_event,
109 e2ee.clone(),
110 room_id,
111 ))
112 .await?;
113 }
114 }
115 }
116
117 #[cfg(not(feature = "e2e-encryption"))]
119 AnySyncTimelineEvent::MessageLike(_) => (),
120 }
121
122 if let Some(push_condition_room_ctx) = &mut push_condition_room_ctx {
123 update_push_room_context(
124 context,
125 push_condition_room_ctx,
126 room.own_user_id(),
127 room_info,
128 )
129 } else {
130 push_condition_room_ctx =
131 get_push_room_context(context, room, room_info).await?;
132 }
133
134 if let Some(push_condition_room_ctx) = &push_condition_room_ctx {
135 let actions = notification
136 .push_notification_from_event_if(
137 push_condition_room_ctx,
138 timeline_event.raw(),
139 Action::should_notify,
140 )
141 .await;
142
143 timeline_event.set_push_actions(actions.to_owned());
144 }
145 }
146 Err(error) => {
147 warn!("Error deserializing event: {error}");
148 }
149 }
150
151 timeline.events.push(timeline_event);
153 }
154
155 Ok(timeline)
156}
157
158pub mod builder {
161 use ruma::{
162 api::client::sync::sync_events::{v3, v5},
163 events::AnySyncTimelineEvent,
164 serde::Raw,
165 };
166
167 pub struct Timeline {
168 pub limited: bool,
169 pub raw_events: Vec<Raw<AnySyncTimelineEvent>>,
170 pub prev_batch: Option<String>,
171 }
172
173 impl From<v3::Timeline> for Timeline {
174 fn from(value: v3::Timeline) -> Self {
175 Self { limited: value.limited, raw_events: value.events, prev_batch: value.prev_batch }
176 }
177 }
178
179 impl From<&v5::response::Room> for Timeline {
180 fn from(value: &v5::response::Room) -> Self {
181 Self {
182 limited: value.limited,
183 raw_events: value.timeline.clone(),
184 prev_batch: value.prev_batch.clone(),
185 }
186 }
187 }
188}
189
190fn update_push_room_context(
194 context: &Context,
195 push_rules: &mut PushConditionRoomCtx,
196 user_id: &UserId,
197 room_info: &RoomInfo,
198) {
199 let room_id = &*room_info.room_id;
200
201 push_rules.member_count = UInt::new(room_info.active_members_count()).unwrap_or(UInt::MAX);
202
203 if let Some(member) = context.state_changes.member(room_id, user_id) {
205 push_rules.user_display_name =
206 member.content.displayname.unwrap_or_else(|| user_id.localpart().to_owned())
207 }
208
209 if let Some(power_levels) = context.state_changes.power_levels(room_id) {
210 push_rules.power_levels = Some(power_levels.into());
211 }
212}
213
214pub async fn get_push_room_context(
222 context: &Context,
223 room: &Room,
224 room_info: &RoomInfo,
225) -> Result<Option<PushConditionRoomCtx>> {
226 let room_id = room.room_id();
227 let user_id = room.own_user_id();
228
229 let member_count = room_info.active_members_count();
230
231 let user_display_name = if let Some(member) = context.state_changes.member(room_id, user_id) {
233 member.content.displayname.unwrap_or_else(|| user_id.localpart().to_owned())
234 } else if let Some(member) = Box::pin(room.get_member(user_id)).await? {
235 member.name().to_owned()
236 } else {
237 trace!("Couldn't get push context because of missing own member information");
238 return Ok(None);
239 };
240
241 let power_levels = if let Some(power_levels) = context.state_changes.power_levels(room_id) {
242 Some(power_levels)
243 } else {
244 room.power_levels().await.ok()
245 };
246
247 Ok(Some(assign!(
248 PushConditionRoomCtx::new(
249 room_id.to_owned(),
250 UInt::new(member_count).unwrap_or(UInt::MAX),
251 user_id.to_owned(),
252 user_display_name
253 ),
254 { power_levels: power_levels.map(Into::into) }
255 )))
256}