matrix_sdk_base/response_processors/
timeline.rs
1use 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 RoomVersionId, 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::new(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().unwrap_or(&RoomVersionId::V1);
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 context,
103 e2ee.clone(),
104 timeline_event.raw(),
105 room_id,
106 ))
107 .await?
108 {
109 timeline_event = decrypted_timeline_event;
110 }
111 }
112
113 _ => {
114 Box::pin(verification::process_if_relevant(
115 context,
116 &sync_timeline_event,
117 e2ee.clone(),
118 room_id,
119 ))
120 .await?;
121 }
122 }
123 }
124
125 #[cfg(not(feature = "e2e-encryption"))]
127 AnySyncTimelineEvent::MessageLike(_) => (),
128 }
129
130 if let Some(push_condition_room_ctx) = &mut push_condition_room_ctx {
131 update_push_room_context(
132 context,
133 push_condition_room_ctx,
134 room.own_user_id(),
135 room_info,
136 )
137 } else {
138 push_condition_room_ctx =
139 get_push_room_context(context, room, room_info, notification.state_store)
140 .await?;
141 }
142
143 if let Some(push_condition_room_ctx) = &push_condition_room_ctx {
144 let actions = notification.push_notification_from_event_if(
145 room_id,
146 push_condition_room_ctx,
147 timeline_event.raw(),
148 Action::should_notify,
149 );
150
151 timeline_event.push_actions = Some(actions.to_owned());
152 }
153 }
154 Err(error) => {
155 warn!("Error deserializing event: {error}");
156 }
157 }
158
159 timeline.events.push(timeline_event);
161 }
162
163 Ok(timeline)
164}
165
166pub mod builder {
169 use ruma::{
170 api::client::sync::sync_events::{v3, v5},
171 events::AnySyncTimelineEvent,
172 serde::Raw,
173 };
174
175 pub struct Timeline {
176 pub limited: bool,
177 pub raw_events: Vec<Raw<AnySyncTimelineEvent>>,
178 pub prev_batch: Option<String>,
179 }
180
181 impl From<v3::Timeline> for Timeline {
182 fn from(value: v3::Timeline) -> Self {
183 Self { limited: value.limited, raw_events: value.events, prev_batch: value.prev_batch }
184 }
185 }
186
187 impl From<&v5::response::Room> for Timeline {
188 fn from(value: &v5::response::Room) -> Self {
189 Self {
190 limited: value.limited,
191 raw_events: value.timeline.clone(),
192 prev_batch: value.prev_batch.clone(),
193 }
194 }
195 }
196}
197
198fn update_push_room_context(
202 context: &mut Context,
203 push_rules: &mut PushConditionRoomCtx,
204 user_id: &UserId,
205 room_info: &RoomInfo,
206) {
207 let room_id = &*room_info.room_id;
208
209 push_rules.member_count = UInt::new(room_info.active_members_count()).unwrap_or(UInt::MAX);
210
211 if let Some(AnySyncStateEvent::RoomMember(member)) =
213 context.state_changes.state.get(room_id).and_then(|events| {
214 events.get(&StateEventType::RoomMember)?.get(user_id.as_str())?.deserialize().ok()
215 })
216 {
217 push_rules.user_display_name = member
218 .as_original()
219 .and_then(|ev| ev.content.displayname.clone())
220 .unwrap_or_else(|| user_id.localpart().to_owned())
221 }
222
223 if let Some(AnySyncStateEvent::RoomPowerLevels(event)) =
224 context.state_changes.state.get(room_id).and_then(|types| {
225 types.get(&StateEventType::RoomPowerLevels)?.get("")?.deserialize().ok()
226 })
227 {
228 push_rules.power_levels = Some(event.power_levels().into());
229 }
230}
231
232pub async fn get_push_room_context(
240 context: &mut Context,
241 room: &Room,
242 room_info: &RoomInfo,
243 state_store: &BaseStateStore,
244) -> Result<Option<PushConditionRoomCtx>> {
245 let room_id = room.room_id();
246 let user_id = room.own_user_id();
247
248 let member_count = room_info.active_members_count();
249
250 let user_display_name = if let Some(AnySyncStateEvent::RoomMember(member)) =
252 context.state_changes.state.get(room_id).and_then(|events| {
253 events.get(&StateEventType::RoomMember)?.get(user_id.as_str())?.deserialize().ok()
254 }) {
255 member
256 .as_original()
257 .and_then(|ev| ev.content.displayname.clone())
258 .unwrap_or_else(|| user_id.localpart().to_owned())
259 } else if let Some(AnyStrippedStateEvent::RoomMember(member)) =
260 context.state_changes.stripped_state.get(room_id).and_then(|events| {
261 events.get(&StateEventType::RoomMember)?.get(user_id.as_str())?.deserialize().ok()
262 })
263 {
264 member.content.displayname.unwrap_or_else(|| user_id.localpart().to_owned())
265 } else if let Some(member) = Box::pin(room.get_member(user_id)).await? {
266 member.name().to_owned()
267 } else {
268 trace!("Couldn't get push context because of missing own member information");
269 return Ok(None);
270 };
271
272 let power_levels = if let Some(event) =
273 context.state_changes.state.get(room_id).and_then(|types| {
274 types
275 .get(&StateEventType::RoomPowerLevels)?
276 .get("")?
277 .deserialize_as::<RoomPowerLevelsEvent>()
278 .ok()
279 }) {
280 Some(event.power_levels().into())
281 } else if let Some(event) =
282 context.state_changes.stripped_state.get(room_id).and_then(|types| {
283 types
284 .get(&StateEventType::RoomPowerLevels)?
285 .get("")?
286 .deserialize_as::<StrippedRoomPowerLevelsEvent>()
287 .ok()
288 })
289 {
290 Some(event.power_levels().into())
291 } else {
292 state_store
293 .get_state_event_static::<RoomPowerLevelsEventContent>(room_id)
294 .await?
295 .and_then(|e| e.deserialize().ok())
296 .map(|event| event.power_levels().into())
297 };
298
299 Ok(Some(PushConditionRoomCtx {
300 user_id: user_id.to_owned(),
301 room_id: room_id.to_owned(),
302 member_count: UInt::new(member_count).unwrap_or(UInt::MAX),
303 user_display_name,
304 power_levels,
305 }))
306}