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, GlobalAccountDataEventType, TimelineEventType,
12 push_rules::PushRulesEvent, room::power_levels::RoomPowerLevels,
13 },
14 push::{Action, Actions, HighlightTweakValue, Ruleset, Tweak},
15 serde::Raw,
16};
17use serde::{Deserialize, Serialize};
18use tuwunel_core::{
19 Result, implement,
20 matrix::{
21 event::Event,
22 pdu::{Count, Pdu, PduId, RawPduId},
23 },
24 utils::{BoolExt, ReadyExt, future::TryExtExt, option::OptionExt, time::now_millis},
25};
26use tuwunel_database::{Deserialized, Json, Map};
27
28use super::{Evaluate, RelatedEvents};
29use crate::rooms::short::ShortRoomId;
30
31#[derive(Clone, Debug, Deserialize, Serialize)]
36pub struct Notified {
37 pub ts: u64,
39
40 pub sroomid: ShortRoomId,
42
43 #[serde(skip_serializing_if = "Option::is_none")]
45 pub tag: Option<ProfileTag>,
46
47 pub actions: Actions,
49}
50
51#[derive(Clone, Copy)]
56struct Appended<'a> {
57 pdu_id: &'a RawPduId,
58 pdu: &'a Pdu,
59 power_levels: Option<&'a RoomPowerLevels>,
60 serialized: &'a Raw<AnySyncTimelineEvent>,
61 thread_root: Option<&'a EventId>,
62 related_events: Option<&'a Arc<RelatedEvents>>,
63}
64
65#[implement(super::Service)]
67#[tracing::instrument(name = "append", level = "debug", skip_all)]
68pub(crate) async fn append_pdu(&self, pdu_id: RawPduId, pdu: &Pdu) -> Result {
69 let push_target = self
70 .services
71 .state_cache
72 .active_local_users_in_room(pdu.room_id())
73 .map(ToOwned::to_owned)
74 .ready_filter(|user| *user != pdu.sender())
75 .filter_map(async |recipient_user| {
76 self.services
77 .users
78 .user_is_ignored(pdu.sender(), &recipient_user)
79 .await
80 .is_false()
81 .then_some(recipient_user)
82 })
83 .collect::<HashSet<_>>();
84
85 let power_levels = self
86 .services
87 .state_accessor
88 .get_power_levels(pdu.room_id())
89 .ok();
90
91 let (mut push_target, power_levels) = join(push_target, power_levels).boxed().await;
92
93 if *pdu.kind() == TimelineEventType::RoomMember
94 && let Some(Ok(target_user_id)) = pdu.state_key().map(UserId::parse)
95 && self
96 .services
97 .users
98 .is_active_local(&target_user_id)
99 .await
100 {
101 push_target.insert(target_user_id);
102 }
103
104 if push_target.is_empty() {
105 return Ok(());
106 }
107
108 let serialized = pdu.to_format();
109 let (thread_root, related_events) =
110 join(self.services.threads.get_thread_id(pdu), self.related_events(pdu)).await;
111
112 let appended = Appended {
113 pdu_id: &pdu_id,
114 pdu,
115 power_levels: power_levels.as_ref(),
116 serialized: &serialized,
117 thread_root: thread_root.as_deref(),
118 related_events: related_events.as_ref(),
119 };
120
121 let _cork = self.db.db.cork();
122 for user in &push_target {
123 self.append_pdu_for_user(user, appended).await;
124 }
125
126 Ok(())
127}
128
129#[implement(super::Service)]
130async fn append_pdu_for_user(
131 &self,
132 user: &UserId,
133 Appended {
134 pdu_id,
135 pdu,
136 power_levels,
137 serialized,
138 thread_root,
139 related_events,
140 }: Appended<'_>,
141) {
142 let rules_for_user = self
143 .services
144 .account_data
145 .get_global(user, GlobalAccountDataEventType::PushRules)
146 .await
147 .map_or_else(|_| Ruleset::server_default(user), |ev: PushRulesEvent| ev.content.global);
148
149 let actions = self
150 .get_actions(Evaluate {
151 user,
152 ruleset: &rules_for_user,
153 power_levels,
154 pdu: serialized,
155 room_id: pdu.room_id(),
156 related_events,
157 })
158 .await;
159
160 let notify = actions
161 .iter()
162 .any(|action| matches!(action, Action::Notify));
163
164 let highlight = actions.iter().any(|action| {
165 matches!(action, Action::SetTweak(Tweak::Highlight(HighlightTweakValue::Yes)))
166 });
167
168 let main_notify = (notify && thread_root.is_none())
171 .then_async(|| self.increment_notificationcount(pdu.room_id(), user));
172
173 let main_highlight = (highlight && thread_root.is_none())
174 .then_async(|| self.increment_highlightcount(pdu.room_id(), user));
175
176 let thread_notify = thread_root
177 .filter(|_| notify)
178 .map_async(|root| self.increment_thread_notificationcount(pdu.room_id(), user, root));
179
180 let thread_highlight = thread_root
181 .filter(|_| highlight)
182 .map_async(|root| self.increment_thread_highlightcount(pdu.room_id(), user, root));
183
184 join4(main_notify, thread_notify, main_highlight, thread_highlight).await;
185
186 if notify || highlight {
187 let id: PduId = (*pdu_id).into();
188 let notified = Notified {
189 ts: now_millis(),
190 sroomid: id.shortroomid,
191 tag: None,
192 actions: actions.into(),
193 };
194
195 if matches!(id.count, Count::Normal(_)) {
196 self.db
197 .useridcount_notification
198 .put((user, id.count.into_unsigned()), Json(notified));
199 }
200 }
201
202 if notify || highlight || self.services.config.push_everything {
203 self.get_pushkeys(user)
204 .map(ToOwned::to_owned)
205 .ready_for_each(|push_key| {
206 self.services
207 .sending
208 .send_pdu_push(pdu_id, user, push_key)
209 .expect("TODO: replace with future");
210 })
211 .await;
212 }
213}
214
215#[implement(super::Service)]
216async fn increment_notificationcount(&self, room_id: &RoomId, user_id: &UserId) {
217 let db = &self.db.userroomid_notificationcount;
218 let key = (room_id.to_owned(), user_id.to_owned());
219 let _lock = self.notification_increment_mutex.lock(&key).await;
220
221 increment(db, (user_id, room_id)).await;
222}
223
224#[implement(super::Service)]
225async fn increment_highlightcount(&self, room_id: &RoomId, user_id: &UserId) {
226 let db = &self.db.userroomid_highlightcount;
227 let key = (room_id.to_owned(), user_id.to_owned());
228 let _lock = self.highlight_increment_mutex.lock(&key).await;
229
230 increment(db, (user_id, room_id)).await;
231}
232
233#[implement(super::Service)]
234async fn increment_thread_notificationcount(
235 &self,
236 room_id: &RoomId,
237 user_id: &UserId,
238 thread_root: &EventId,
239) {
240 let db = &self.db.userroomid_notificationcount;
241 let key = (room_id.to_owned(), user_id.to_owned());
242 let _lock = self.notification_increment_mutex.lock(&key).await;
243
244 increment_thread(db, (user_id, room_id, thread_root)).await;
245}
246
247#[implement(super::Service)]
248async fn increment_thread_highlightcount(
249 &self,
250 room_id: &RoomId,
251 user_id: &UserId,
252 thread_root: &EventId,
253) {
254 let db = &self.db.userroomid_highlightcount;
255 let key = (room_id.to_owned(), user_id.to_owned());
256 let _lock = self.highlight_increment_mutex.lock(&key).await;
257
258 increment_thread(db, (user_id, room_id, thread_root)).await;
259}
260
261async fn increment(db: &Arc<Map>, key: (&UserId, &RoomId)) {
262 let old: u64 = db.qry(&key).await.deserialized().unwrap_or(0);
263 let new = old.saturating_add(1);
264 db.put(key, new);
265}
266
267async fn increment_thread(db: &Arc<Map>, key: (&UserId, &RoomId, &EventId)) {
268 let old: u64 = db.qry(&key).await.deserialized().unwrap_or(0);
269 let new = old.saturating_add(1);
270 db.put(key, new);
271}