matrix_sdk_base/response_processors/
timeline.rs1use matrix_sdk_common::deserialized_responses::TimelineEvent;
16#[cfg(feature = "e2e-encryption")]
17use ruma::events::SyncMessageLikeEvent;
18use ruma::{
19 events::{
20 room::power_levels::{
21 RoomPowerLevelsEvent, RoomPowerLevelsEventContent, StrippedRoomPowerLevelsEvent,
22 },
23 AnyStrippedStateEvent, AnySyncMessageLikeEvent, AnySyncStateEvent, AnySyncTimelineEvent,
24 StateEventType,
25 },
26 push::{Action, PushConditionRoomCtx},
27 UInt, UserId,
28};
29use tracing::{instrument, trace, warn};
30
31#[cfg(feature = "e2e-encryption")]
32use super::{e2ee, verification};
33use super::{notification, Context};
34use crate::{
35 store::{BaseStateStore, StateStoreExt as _},
36 sync::Timeline,
37 Result, Room, RoomInfo,
38};
39
40#[instrument(skip_all, fields(room_id = ?room_info.room_id))]
48pub async fn build<'notification, 'e2ee>(
49 context: &mut Context,
50 room: &Room,
51 room_info: &mut RoomInfo,
52 timeline_inputs: builder::Timeline,
53 mut notification: notification::Notification<'notification>,
54 #[cfg(feature = "e2e-encryption")] e2ee: e2ee::E2EE<'e2ee>,
55) -> Result<Timeline> {
56 let mut timeline = Timeline::new(timeline_inputs.limited, timeline_inputs.prev_batch);
57 let mut push_condition_room_ctx =
58 get_push_room_context(context, room, room_info, notification.state_store).await?;
59 let room_id = room.room_id();
60
61 for raw_event in timeline_inputs.raw_events {
62 let mut timeline_event = TimelineEvent::from_plaintext(raw_event);
65
66 match timeline_event.raw().deserialize() {
68 Ok(sync_timeline_event) => {
69 match &sync_timeline_event {
70 AnySyncTimelineEvent::State(_) => {
72 }
74
75 AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomRedaction(
77 redaction_event,
78 )) => {
79 let room_version = room_info.room_version_or_default();
80
81 if let Some(redacts) = redaction_event.redacts(&room_version) {
82 room_info
83 .handle_redaction(redaction_event, timeline_event.raw().cast_ref());
84
85 context.state_changes.add_redaction(
86 room_id,
87 redacts,
88 timeline_event.raw().clone().cast(),
89 );
90 }
91 }
92
93 #[cfg(feature = "e2e-encryption")]
95 AnySyncTimelineEvent::MessageLike(sync_message_like_event) => {
96 match sync_message_like_event {
97 AnySyncMessageLikeEvent::RoomEncrypted(
98 SyncMessageLikeEvent::Original(_),
99 ) => {
100 if let Some(decrypted_timeline_event) =
101 Box::pin(e2ee::decrypt::sync_timeline_event(
102 e2ee.clone(),
103 timeline_event.raw(),
104 room_id,
105 ))
106 .await?
107 {
108 timeline_event = decrypted_timeline_event;
109 }
110 }
111
112 _ => {
113 Box::pin(verification::process_if_relevant(
114 &sync_timeline_event,
115 e2ee.clone(),
116 room_id,
117 ))
118 .await?;
119 }
120 }
121 }
122
123 #[cfg(not(feature = "e2e-encryption"))]
125 AnySyncTimelineEvent::MessageLike(_) => (),
126 }
127
128 if let Some(push_condition_room_ctx) = &mut push_condition_room_ctx {
129 update_push_room_context(
130 context,
131 push_condition_room_ctx,
132 room.own_user_id(),
133 room_info,
134 )
135 } else {
136 push_condition_room_ctx =
137 get_push_room_context(context, room, room_info, notification.state_store)
138 .await?;
139 }
140
141 if let Some(push_condition_room_ctx) = &push_condition_room_ctx {
142 let actions = notification.push_notification_from_event_if(
143 room_id,
144 push_condition_room_ctx,
145 timeline_event.raw(),
146 Action::should_notify,
147 );
148
149 timeline_event.set_push_actions(actions.to_owned());
150 }
151 }
152 Err(error) => {
153 warn!("Error deserializing event: {error}");
154 }
155 }
156
157 timeline.events.push(timeline_event);
159 }
160
161 Ok(timeline)
162}
163
164pub mod builder {
167 use ruma::{
168 api::client::sync::sync_events::{v3, v5},
169 events::AnySyncTimelineEvent,
170 serde::Raw,
171 };
172
173 pub struct Timeline {
174 pub limited: bool,
175 pub raw_events: Vec<Raw<AnySyncTimelineEvent>>,
176 pub prev_batch: Option<String>,
177 }
178
179 impl From<v3::Timeline> for Timeline {
180 fn from(value: v3::Timeline) -> Self {
181 Self { limited: value.limited, raw_events: value.events, prev_batch: value.prev_batch }
182 }
183 }
184
185 impl From<&v5::response::Room> for Timeline {
186 fn from(value: &v5::response::Room) -> Self {
187 Self {
188 limited: value.limited,
189 raw_events: value.timeline.clone(),
190 prev_batch: value.prev_batch.clone(),
191 }
192 }
193 }
194}
195
196fn update_push_room_context(
200 context: &Context,
201 push_rules: &mut PushConditionRoomCtx,
202 user_id: &UserId,
203 room_info: &RoomInfo,
204) {
205 let room_id = &*room_info.room_id;
206
207 push_rules.member_count = UInt::new(room_info.active_members_count()).unwrap_or(UInt::MAX);
208
209 if let Some(AnySyncStateEvent::RoomMember(member)) =
211 context.state_changes.state.get(room_id).and_then(|events| {
212 events.get(&StateEventType::RoomMember)?.get(user_id.as_str())?.deserialize().ok()
213 })
214 {
215 push_rules.user_display_name = member
216 .as_original()
217 .and_then(|ev| ev.content.displayname.clone())
218 .unwrap_or_else(|| user_id.localpart().to_owned())
219 }
220
221 if let Some(AnySyncStateEvent::RoomPowerLevels(event)) =
222 context.state_changes.state.get(room_id).and_then(|types| {
223 types.get(&StateEventType::RoomPowerLevels)?.get("")?.deserialize().ok()
224 })
225 {
226 push_rules.power_levels = Some(event.power_levels().into());
227 }
228}
229
230pub async fn get_push_room_context(
238 context: &Context,
239 room: &Room,
240 room_info: &RoomInfo,
241 state_store: &BaseStateStore,
242) -> Result<Option<PushConditionRoomCtx>> {
243 let room_id = room.room_id();
244 let user_id = room.own_user_id();
245
246 let member_count = room_info.active_members_count();
247
248 let user_display_name = if let Some(AnySyncStateEvent::RoomMember(member)) =
250 context.state_changes.state.get(room_id).and_then(|events| {
251 events.get(&StateEventType::RoomMember)?.get(user_id.as_str())?.deserialize().ok()
252 }) {
253 member
254 .as_original()
255 .and_then(|ev| ev.content.displayname.clone())
256 .unwrap_or_else(|| user_id.localpart().to_owned())
257 } else if let Some(AnyStrippedStateEvent::RoomMember(member)) =
258 context.state_changes.stripped_state.get(room_id).and_then(|events| {
259 events.get(&StateEventType::RoomMember)?.get(user_id.as_str())?.deserialize().ok()
260 })
261 {
262 member.content.displayname.unwrap_or_else(|| user_id.localpart().to_owned())
263 } else if let Some(member) = Box::pin(room.get_member(user_id)).await? {
264 member.name().to_owned()
265 } else {
266 trace!("Couldn't get push context because of missing own member information");
267 return Ok(None);
268 };
269
270 let power_levels = if let Some(event) =
271 context.state_changes.state.get(room_id).and_then(|types| {
272 types
273 .get(&StateEventType::RoomPowerLevels)?
274 .get("")?
275 .deserialize_as::<RoomPowerLevelsEvent>()
276 .ok()
277 }) {
278 Some(event.power_levels().into())
279 } else if let Some(event) =
280 context.state_changes.stripped_state.get(room_id).and_then(|types| {
281 types
282 .get(&StateEventType::RoomPowerLevels)?
283 .get("")?
284 .deserialize_as::<StrippedRoomPowerLevelsEvent>()
285 .ok()
286 })
287 {
288 Some(event.power_levels().into())
289 } else {
290 state_store
291 .get_state_event_static::<RoomPowerLevelsEventContent>(room_id)
292 .await?
293 .and_then(|e| e.deserialize().ok())
294 .map(|event| event.power_levels().into())
295 };
296
297 Ok(Some(PushConditionRoomCtx {
298 user_id: user_id.to_owned(),
299 room_id: room_id.to_owned(),
300 member_count: UInt::new(member_count).unwrap_or(UInt::MAX),
301 user_display_name,
302 power_levels,
303 }))
304}