1use std::{collections::HashSet, sync::Arc};
2
3use futures::{
4 FutureExt, StreamExt,
5 future::{join, join4},
6};
7use ruma::{
8 EventId, RoomId, UserId,
9 api::client::push::ProfileTag,
10 events::{
11 AnySyncTimelineEvent, TimelineEventType,
12 invite_permission_config::InvitePermission,
13 room::{member::MembershipState, power_levels::RoomPowerLevels},
14 },
15 push::{Action, Actions, HighlightTweakValue, Tweak},
16 serde::Raw,
17};
18use serde::{Deserialize, Serialize};
19use tracing::Level;
20use tuwunel_core::{
21 Result, implement,
22 matrix::{
23 event::Event,
24 pdu::{Count, Pdu, PduId, RawPduId},
25 },
26 trace,
27 utils::{
28 BoolExt, ReadyExt, future::TryExtExt, option::OptionExt, result::ErrLog, time::now_millis,
29 },
30};
31use tuwunel_database::{Deserialized, Json, Map};
32
33use super::{Evaluate, RelatedEvents};
34use crate::rooms::short::ShortRoomId;
35
36#[derive(Clone, Debug, Deserialize, Serialize)]
41pub struct Notified {
42 pub ts: u64,
44
45 pub sroomid: ShortRoomId,
47
48 #[serde(skip_serializing_if = "Option::is_none")]
50 pub tag: Option<ProfileTag>,
51
52 pub actions: Actions,
54}
55
56#[derive(Clone, Copy)]
61struct Appended<'a> {
62 pdu_id: &'a RawPduId,
63 pdu: &'a Pdu,
64 power_levels: Option<&'a RoomPowerLevels>,
65 serialized: &'a Raw<AnySyncTimelineEvent>,
66 thread_root: Option<&'a EventId>,
67 related_events: Option<&'a Arc<RelatedEvents>>,
68}
69
70#[implement(super::Service)]
72#[tracing::instrument(name = "append", level = "debug", skip_all)]
73pub(crate) async fn append_pdu(&self, pdu_id: RawPduId, pdu: &Pdu) -> Result {
74 let push_target = self
75 .services
76 .state_cache
77 .active_local_users_in_room(pdu.room_id())
78 .map(ToOwned::to_owned)
79 .ready_filter(|user| *user != pdu.sender())
80 .filter_map(async |recipient_user| {
81 self.services
82 .users
83 .user_is_ignored(pdu.sender(), &recipient_user)
84 .await
85 .is_false()
86 .then_some(recipient_user)
87 })
88 .collect::<HashSet<_>>();
89
90 let power_levels = self
91 .services
92 .state_accessor
93 .get_power_levels(pdu.room_id())
94 .ok();
95
96 let (mut push_target, power_levels) = join(push_target, power_levels).boxed().await;
97
98 if *pdu.kind() == TimelineEventType::RoomMember
99 && let Some(Ok(target_user_id)) = pdu.state_key().map(UserId::parse)
100 && self
101 .services
102 .users
103 .is_active_local(&target_user_id)
104 .await
105 && self.invite_notifiable(pdu, &target_user_id).await
106 {
107 push_target.insert(target_user_id);
108 }
109
110 if push_target.is_empty() {
111 return Ok(());
112 }
113
114 let serialized = pdu.to_format();
115 let (thread_root, related_events) =
116 join(self.services.threads.get_thread_id(pdu), self.related_events(pdu)).await;
117
118 let appended = Appended {
119 pdu_id: &pdu_id,
120 pdu,
121 power_levels: power_levels.as_ref(),
122 serialized: &serialized,
123 thread_root: thread_root.as_deref(),
124 related_events: related_events.as_ref(),
125 };
126
127 let _cork = self.db.db.cork();
128 for user in &push_target {
129 self.append_pdu_for_user(user, appended).await;
130 }
131
132 Ok(())
133}
134
135#[implement(super::Service)]
140async fn invite_notifiable(&self, pdu: &Pdu, user_id: &UserId) -> bool {
141 #[derive(Deserialize)]
142 struct Membership {
143 membership: MembershipState,
144 }
145
146 let is_invite = pdu
147 .get_content()
148 .is_ok_and(|content: Membership| content.membership == MembershipState::Invite);
149
150 !is_invite
151 || self
152 .services
153 .users
154 .invite_permission(pdu.sender(), user_id)
155 .await
156 .eq(&InvitePermission::Allow)
157}
158
159#[implement(super::Service)]
160async fn append_pdu_for_user(
161 &self,
162 user: &UserId,
163 Appended {
164 pdu_id,
165 pdu,
166 power_levels,
167 serialized,
168 thread_root,
169 related_events,
170 }: Appended<'_>,
171) {
172 let ruleset = self.ruleset(user).await;
173 let actions = self
174 .get_actions(Evaluate {
175 user,
176 ruleset: &ruleset,
177 power_levels,
178 pdu: serialized,
179 room_id: pdu.room_id(),
180 related_events,
181 })
182 .await;
183
184 let notify = actions.iter().any(Action::should_notify);
185
186 let highlight = actions.iter().any(|action| {
187 matches!(action, Action::SetTweak(Tweak::Highlight(HighlightTweakValue::Yes)))
188 });
189
190 trace!(
191 %user,
192 event_id = %pdu.event_id(),
193 actions = %actions.len(),
194 notify,
195 highlight,
196 "Push rules evaluated",
197 );
198
199 let main_notify = (notify && thread_root.is_none())
202 .then_async(|| self.increment_notificationcount(pdu.room_id(), user));
203
204 let main_highlight = (highlight && thread_root.is_none())
205 .then_async(|| self.increment_highlightcount(pdu.room_id(), user));
206
207 let thread_notify = thread_root
208 .filter(|_| notify)
209 .map_async(|root| self.increment_thread_notificationcount(pdu.room_id(), user, root));
210
211 let thread_highlight = thread_root
212 .filter(|_| highlight)
213 .map_async(|root| self.increment_thread_highlightcount(pdu.room_id(), user, root));
214
215 join4(main_notify, thread_notify, main_highlight, thread_highlight).await;
216
217 if notify || highlight {
218 let id: PduId = (*pdu_id).into();
219 let notified = Notified {
220 ts: now_millis(),
221 sroomid: id.shortroomid,
222 tag: None,
223 actions: actions.into(),
224 };
225
226 if matches!(id.count, Count::Normal(_)) {
227 self.db
228 .useridcount_notification
229 .put((user, id.count.into_unsigned()), Json(notified));
230 }
231 }
232
233 if notify || highlight || self.services.config.push_everything {
234 self.get_pushkeys(user)
235 .map(ToOwned::to_owned)
236 .ready_for_each(|push_key| {
237 self.services
238 .sending
239 .send_pdu_push(pdu_id, user, push_key)
240 .log_err(Level::TRACE)
241 .ok();
242 })
243 .await;
244 }
245}
246
247#[implement(super::Service)]
248async fn increment_notificationcount(&self, room_id: &RoomId, user_id: &UserId) {
249 let db = &self.db.userroomid_notificationcount;
250 let key = (room_id.to_owned(), user_id.to_owned());
251 let _lock = self.notification_increment_mutex.lock(&key).await;
252
253 increment(db, (user_id, room_id)).await;
254}
255
256#[implement(super::Service)]
257async fn increment_highlightcount(&self, room_id: &RoomId, user_id: &UserId) {
258 let db = &self.db.userroomid_highlightcount;
259 let key = (room_id.to_owned(), user_id.to_owned());
260 let _lock = self.highlight_increment_mutex.lock(&key).await;
261
262 increment(db, (user_id, room_id)).await;
263}
264
265#[implement(super::Service)]
266async fn increment_thread_notificationcount(
267 &self,
268 room_id: &RoomId,
269 user_id: &UserId,
270 thread_root: &EventId,
271) {
272 let db = &self.db.userroomid_notificationcount;
273 let key = (room_id.to_owned(), user_id.to_owned());
274 let _lock = self.notification_increment_mutex.lock(&key).await;
275
276 increment_thread(db, (user_id, room_id, thread_root)).await;
277}
278
279#[implement(super::Service)]
280async fn increment_thread_highlightcount(
281 &self,
282 room_id: &RoomId,
283 user_id: &UserId,
284 thread_root: &EventId,
285) {
286 let db = &self.db.userroomid_highlightcount;
287 let key = (room_id.to_owned(), user_id.to_owned());
288 let _lock = self.highlight_increment_mutex.lock(&key).await;
289
290 increment_thread(db, (user_id, room_id, thread_root)).await;
291}
292
293async fn increment(db: &Arc<Map>, key: (&UserId, &RoomId)) {
294 let old: u64 = db.qry(&key).await.deserialized().unwrap_or(0);
295 let new = old.saturating_add(1);
296 db.put(key, new);
297}
298
299async fn increment_thread(db: &Arc<Map>, key: (&UserId, &RoomId, &EventId)) {
300 let old: u64 = db.qry(&key).await.deserialized().unwrap_or(0);
301 let new = old.saturating_add(1);
302 db.put(key, new);
303}