atagen

macltok

public
Star0
Watch0

Commit 913832a327a8

tarball

atagen <boss@atagen.co>2026-10-25 09:33 UTC

6 files changed+48-14
timeline: carry the push rule highlight on every event

Malice had no way to tell which rows the push rules flag for the user, which the sdk already computes as is_highlighted. Context pages take it from the push actions the sdk attaches as it fetches, so the flag costs no extra request.
Modified file: src/engine/context.rs+40 additions−11 deletions
@@ -1,8 +1,10 @@
11 use std::{collections::HashMap, sync::Arc};
22
33 use futures_util::future::{join_all, try_join};
4-use matrix_sdk::{Room, room::MessagesOptions};
5-use ruma::{OwnedEventId, OwnedUserId, UInt};
4+use matrix_sdk::{
5+ Room, deserialized_responses::TimelineEvent as SdkTimelineEvent, room::MessagesOptions,
6+};
7+use ruma::{OwnedEventId, OwnedUserId, UInt, push::Action};
68 use serde_json::Value;
79
810 use crate::{
@@ -51,7 +53,14 @@ pub(crate) async fn execute(
5153 ));
5254 }
5355 let side = fetch_side(&room, Some(&token), limit, backwards).await?;
54- let mut page = Assembly::new(&room, current_user_id.as_deref(), &side.values, None).await;
56+ let mut page = Assembly::new(
57+ &room,
58+ current_user_id.as_deref(),
59+ &side.values,
60+ &side.highlights,
61+ None,
62+ )
63+ .await;
5564 let events = page.take(0..side.values.len());
5665 media.register(page.media).await;
5766 Ok(crate::CommandOutput::EventContextPage(EventContextPage {
@@ -84,10 +93,10 @@ pub(crate) async fn capture(
8493 error
8594 }
8695 })?;
87- let target = raw
96+ let (target, target_highlight) = raw
8897 .event
8998 .as_ref()
90- .and_then(|event| serde_json::from_str::<Value>(event.raw().json().get()).ok())
99+ .and_then(raw_event)
91100 .ok_or_else(|| Error::new(ErrorKind::EventNotFound, "event was not found"))?;
92101 let (before, after) = try_join(
93102 fetch_side(&room, raw.prev_batch_token.as_deref(), before_limit, true),
@@ -98,10 +107,14 @@ pub(crate) async fn capture(
98107 let mut values = before.values;
99108 values.push(target);
100109 values.extend(after.values);
110+ let mut highlights = before.highlights;
111+ highlights.push(target_highlight);
112+ highlights.extend(after.highlights);
101113 let mut page = Assembly::new(
102114 &room,
103115 current_user_id.as_deref(),
104116 &values,
117+ &highlights,
105118 Some(target_index),
106119 )
107120 .await;
@@ -127,9 +140,19 @@ pub(crate) async fn capture(
127140 struct Side {
128141 /// in timeline order
129142 values: Vec<Value>,
143+ /// beside `values`, from the push actions the sdk worked out as it fetched
144+ highlights: Vec<bool>,
130145 token: Option<String>,
131146 }
132147
148+fn raw_event(event: &SdkTimelineEvent) -> Option<(Value, bool)> {
149+ let value = serde_json::from_str(event.raw().json().get()).ok()?;
150+ let highlight = event
151+ .push_actions()
152+ .is_some_and(|actions| actions.iter().any(Action::is_highlight));
153+ Some((value, highlight))
154+}
155+
133156 async fn fetch_side(
134157 room: &Room,
135158 token: Option<&str>,
@@ -139,12 +162,14 @@ async fn fetch_side(
139162 let Some(token) = token else {
140163 return Ok(Side {
141164 values: Vec::new(),
165+ highlights: Vec::new(),
142166 token: None,
143167 });
144168 };
145169 if limit == 0 {
146170 return Ok(Side {
147171 values: Vec::new(),
172+ highlights: Vec::new(),
148173 token: Some(token.to_owned()),
149174 });
150175 }
@@ -159,16 +184,14 @@ async fn fetch_side(
159184 .messages(options)
160185 .await
161186 .map_err(|source| Error::from_sdk("fetching event context events failed", source))?;
162- let mut values: Vec<Value> = messages
163- .chunk
164- .iter()
165- .filter_map(|event| serde_json::from_str(event.raw().json().get()).ok())
166- .collect();
187+ let mut events: Vec<_> = messages.chunk.iter().filter_map(raw_event).collect();
167188 if backwards {
168- values.reverse();
189+ events.reverse();
169190 }
191+ let (values, highlights) = events.into_iter().unzip();
170192 Ok(Side {
171193 values,
194+ highlights,
172195 token: messages.end,
173196 })
174197 }
@@ -184,10 +207,16 @@ impl Assembly {
184207 room: &Room,
185208 current_user_id: Option<&ruma::UserId>,
186209 values: &[Value],
210+ highlights: &[bool],
187211 keep: Option<usize>,
188212 ) -> Self {
189213 let page = normalise_context(values, keep, current_user_id, room.room_id());
190214 let mut items = page.items;
215+ for (index, item) in &mut items {
216+ if let TimelineItem::Event(event) = item {
217+ event.highlight = highlights[*index];
218+ }
219+ }
191220 let mut media = page.media;
192221 let mut targets: HashMap<OwnedEventId, TimelineEvent> = HashMap::new();
193222 let mut missing = Vec::new();
Modified file: src/engine/mod.rs+3 additions−3 deletions
@@ -172,9 +172,9 @@ impl EngineError {
172172 kind: match self {
173173 Self::EventReceiverClosed => EngineErrorKind::EventReceiverClosed,
174174 Self::TaskPanicked { .. } => EngineErrorKind::Watcher,
175- Self::Unauthenticated
176- | Self::ZeroCapacity
177- | Self::SyncService(_) => EngineErrorKind::Other,
175+ Self::Unauthenticated | Self::ZeroCapacity | Self::SyncService(_) => {
176+ EngineErrorKind::Other
177+ }
178178 },
179179 message: self.to_string(),
180180 }
Modified file: src/mentions/state_tests.rs+1 additions
@@ -60,6 +60,7 @@ fn message_event(event_id: &str, timestamp_ms: u64, body: &str) -> TimelineItem
6060 reactions: Vec::new(),
6161 read_by: Vec::new(),
6262 editable: false,
63+ highlight: false,
6364 }))
6465 }
6566
Modified file: src/model.rs+2 additions
@@ -1629,6 +1629,8 @@ pub struct TimelineEvent {
16291629 pub reactions: Vec<Reaction>,
16301630 pub read_by: Vec<OwnedUserId>,
16311631 pub editable: bool,
1632+ /// the push rules ask for this one to stand out, always false for a local echo
1633+ pub highlight: bool,
16321634 }
16331635
16341636 impl TimelineEvent {
Modified file: src/normalise/observed.rs+1 additions
@@ -182,6 +182,7 @@ pub(crate) fn normalise_observed(
182182 reactions: Vec::new(),
183183 read_by: Vec::new(),
184184 editable: false,
185+ highlight: false,
185186 };
186187 Some((TimelineItem::Event(Box::new(event)), media))
187188 }
Modified file: src/normalise/timeline.rs+1 additions
@@ -145,6 +145,7 @@ impl Normaliser<'_> {
145145 reactions: reactions(event, self.current_user_id),
146146 read_by,
147147 editable: event.is_editable(),
148+ highlight: event.is_highlighted(),
148149 }
149150 }
150151 }