Skip to content

Commit 6514381

Browse files
Implement the push rules for experimental MSC4306: Thread Subscriptions. (#18762)
Follows: #18756 Implements: MSC4306 --------- Signed-off-by: Olivier 'reivilibre <oliverw@matrix.org> Co-authored-by: Andrew Morgan <1342360+anoadragon453@users.noreply.github.com>
1 parent 8306cee commit 6514381

File tree

12 files changed

+404
-28
lines changed

12 files changed

+404
-28
lines changed

changelog.d/18762.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Implement the push rules for experimental [MSC4306: Thread Subscriptions](https://github.com/matrix-org/matrix-doc/issues/4306).

rust/benches/evaluator.rs

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ fn bench_match_exact(b: &mut Bencher) {
6161
vec![],
6262
false,
6363
false,
64+
false,
6465
)
6566
.unwrap();
6667

@@ -71,10 +72,10 @@ fn bench_match_exact(b: &mut Bencher) {
7172
},
7273
));
7374

74-
let matched = eval.match_condition(&condition, None, None).unwrap();
75+
let matched = eval.match_condition(&condition, None, None, None).unwrap();
7576
assert!(matched, "Didn't match");
7677

77-
b.iter(|| eval.match_condition(&condition, None, None).unwrap());
78+
b.iter(|| eval.match_condition(&condition, None, None, None).unwrap());
7879
}
7980

8081
#[bench]
@@ -107,6 +108,7 @@ fn bench_match_word(b: &mut Bencher) {
107108
vec![],
108109
false,
109110
false,
111+
false,
110112
)
111113
.unwrap();
112114

@@ -117,10 +119,10 @@ fn bench_match_word(b: &mut Bencher) {
117119
},
118120
));
119121

120-
let matched = eval.match_condition(&condition, None, None).unwrap();
122+
let matched = eval.match_condition(&condition, None, None, None).unwrap();
121123
assert!(matched, "Didn't match");
122124

123-
b.iter(|| eval.match_condition(&condition, None, None).unwrap());
125+
b.iter(|| eval.match_condition(&condition, None, None, None).unwrap());
124126
}
125127

126128
#[bench]
@@ -153,6 +155,7 @@ fn bench_match_word_miss(b: &mut Bencher) {
153155
vec![],
154156
false,
155157
false,
158+
false,
156159
)
157160
.unwrap();
158161

@@ -163,10 +166,10 @@ fn bench_match_word_miss(b: &mut Bencher) {
163166
},
164167
));
165168

166-
let matched = eval.match_condition(&condition, None, None).unwrap();
169+
let matched = eval.match_condition(&condition, None, None, None).unwrap();
167170
assert!(!matched, "Didn't match");
168171

169-
b.iter(|| eval.match_condition(&condition, None, None).unwrap());
172+
b.iter(|| eval.match_condition(&condition, None, None, None).unwrap());
170173
}
171174

172175
#[bench]
@@ -199,6 +202,7 @@ fn bench_eval_message(b: &mut Bencher) {
199202
vec![],
200203
false,
201204
false,
205+
false,
202206
)
203207
.unwrap();
204208

@@ -210,7 +214,8 @@ fn bench_eval_message(b: &mut Bencher) {
210214
false,
211215
false,
212216
false,
217+
false,
213218
);
214219

215-
b.iter(|| eval.run(&rules, Some("bob"), Some("person")));
220+
b.iter(|| eval.run(&rules, Some("bob"), Some("person"), None));
216221
}

rust/src/push/base_rules.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,26 @@ pub const BASE_APPEND_CONTENT_RULES: &[PushRule] = &[PushRule {
290290
}];
291291

292292
pub const BASE_APPEND_UNDERRIDE_RULES: &[PushRule] = &[
293+
PushRule {
294+
rule_id: Cow::Borrowed("global/content/.io.element.msc4306.rule.unsubscribed_thread"),
295+
priority_class: 1,
296+
conditions: Cow::Borrowed(&[Condition::Known(
297+
KnownCondition::Msc4306ThreadSubscription { subscribed: false },
298+
)]),
299+
actions: Cow::Borrowed(&[]),
300+
default: true,
301+
default_enabled: true,
302+
},
303+
PushRule {
304+
rule_id: Cow::Borrowed("global/content/.io.element.msc4306.rule.subscribed_thread"),
305+
priority_class: 1,
306+
conditions: Cow::Borrowed(&[Condition::Known(
307+
KnownCondition::Msc4306ThreadSubscription { subscribed: true },
308+
)]),
309+
actions: Cow::Borrowed(&[Action::Notify, SOUND_ACTION]),
310+
default: true,
311+
default_enabled: true,
312+
},
293313
PushRule {
294314
rule_id: Cow::Borrowed("global/underride/.m.rule.call"),
295315
priority_class: 1,

rust/src/push/evaluator.rs

Lines changed: 52 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,11 @@ pub struct PushRuleEvaluator {
106106
/// flag as MSC1767 (extensible events core).
107107
msc3931_enabled: bool,
108108

109-
// If MSC4210 (remove legacy mentions) is enabled.
109+
/// If MSC4210 (remove legacy mentions) is enabled.
110110
msc4210_enabled: bool,
111+
112+
/// If MSC4306 (thread subscriptions) is enabled.
113+
msc4306_enabled: bool,
111114
}
112115

113116
#[pymethods]
@@ -126,6 +129,7 @@ impl PushRuleEvaluator {
126129
room_version_feature_flags,
127130
msc3931_enabled,
128131
msc4210_enabled,
132+
msc4306_enabled,
129133
))]
130134
pub fn py_new(
131135
flattened_keys: BTreeMap<String, JsonValue>,
@@ -138,6 +142,7 @@ impl PushRuleEvaluator {
138142
room_version_feature_flags: Vec<String>,
139143
msc3931_enabled: bool,
140144
msc4210_enabled: bool,
145+
msc4306_enabled: bool,
141146
) -> Result<Self, Error> {
142147
let body = match flattened_keys.get("content.body") {
143148
Some(JsonValue::Value(SimpleJsonValue::Str(s))) => s.clone().into_owned(),
@@ -156,6 +161,7 @@ impl PushRuleEvaluator {
156161
room_version_feature_flags,
157162
msc3931_enabled,
158163
msc4210_enabled,
164+
msc4306_enabled,
159165
})
160166
}
161167

@@ -167,12 +173,19 @@ impl PushRuleEvaluator {
167173
///
168174
/// Returns the set of actions, if any, that match (filtering out any
169175
/// `dont_notify` and `coalesce` actions).
170-
#[pyo3(signature = (push_rules, user_id=None, display_name=None))]
176+
///
177+
/// msc4306_thread_subscription_state: (Only populated if MSC4306 is enabled)
178+
/// The thread subscription state corresponding to the thread containing this event.
179+
/// - `None` if the event is not in a thread, or if MSC4306 is disabled.
180+
/// - `Some(true)` if the event is in a thread and the user has a subscription for that thread
181+
/// - `Some(false)` if the event is in a thread and the user does NOT have a subscription for that thread
182+
#[pyo3(signature = (push_rules, user_id=None, display_name=None, msc4306_thread_subscription_state=None))]
171183
pub fn run(
172184
&self,
173185
push_rules: &FilteredPushRules,
174186
user_id: Option<&str>,
175187
display_name: Option<&str>,
188+
msc4306_thread_subscription_state: Option<bool>,
176189
) -> Vec<Action> {
177190
'outer: for (push_rule, enabled) in push_rules.iter() {
178191
if !enabled {
@@ -204,7 +217,12 @@ impl PushRuleEvaluator {
204217
Condition::Known(KnownCondition::RoomVersionSupports { feature: _ }),
205218
);
206219

207-
match self.match_condition(condition, user_id, display_name) {
220+
match self.match_condition(
221+
condition,
222+
user_id,
223+
display_name,
224+
msc4306_thread_subscription_state,
225+
) {
208226
Ok(true) => {}
209227
Ok(false) => continue 'outer,
210228
Err(err) => {
@@ -237,14 +255,20 @@ impl PushRuleEvaluator {
237255
}
238256

239257
/// Check if the given condition matches.
240-
#[pyo3(signature = (condition, user_id=None, display_name=None))]
258+
#[pyo3(signature = (condition, user_id=None, display_name=None, msc4306_thread_subscription_state=None))]
241259
fn matches(
242260
&self,
243261
condition: Condition,
244262
user_id: Option<&str>,
245263
display_name: Option<&str>,
264+
msc4306_thread_subscription_state: Option<bool>,
246265
) -> bool {
247-
match self.match_condition(&condition, user_id, display_name) {
266+
match self.match_condition(
267+
&condition,
268+
user_id,
269+
display_name,
270+
msc4306_thread_subscription_state,
271+
) {
248272
Ok(true) => true,
249273
Ok(false) => false,
250274
Err(err) => {
@@ -262,6 +286,7 @@ impl PushRuleEvaluator {
262286
condition: &Condition,
263287
user_id: Option<&str>,
264288
display_name: Option<&str>,
289+
msc4306_thread_subscription_state: Option<bool>,
265290
) -> Result<bool, Error> {
266291
let known_condition = match condition {
267292
Condition::Known(known) => known,
@@ -393,6 +418,13 @@ impl PushRuleEvaluator {
393418
&& self.room_version_feature_flags.contains(&flag)
394419
}
395420
}
421+
KnownCondition::Msc4306ThreadSubscription { subscribed } => {
422+
if !self.msc4306_enabled {
423+
false
424+
} else {
425+
msc4306_thread_subscription_state == Some(*subscribed)
426+
}
427+
}
396428
};
397429

398430
Ok(result)
@@ -536,10 +568,11 @@ fn push_rule_evaluator() {
536568
vec![],
537569
true,
538570
false,
571+
false,
539572
)
540573
.unwrap();
541574

542-
let result = evaluator.run(&FilteredPushRules::default(), None, Some("bob"));
575+
let result = evaluator.run(&FilteredPushRules::default(), None, Some("bob"), None);
543576
assert_eq!(result.len(), 3);
544577
}
545578

@@ -566,6 +599,7 @@ fn test_requires_room_version_supports_condition() {
566599
flags,
567600
true,
568601
false,
602+
false,
569603
)
570604
.unwrap();
571605

@@ -575,6 +609,7 @@ fn test_requires_room_version_supports_condition() {
575609
&FilteredPushRules::default(),
576610
Some("@bob:example.org"),
577611
None,
612+
None,
578613
);
579614
assert_eq!(result.len(), 3);
580615

@@ -593,7 +628,17 @@ fn test_requires_room_version_supports_condition() {
593628
};
594629
let rules = PushRules::new(vec![custom_rule]);
595630
result = evaluator.run(
596-
&FilteredPushRules::py_new(rules, BTreeMap::new(), true, false, true, false, false),
631+
&FilteredPushRules::py_new(
632+
rules,
633+
BTreeMap::new(),
634+
true,
635+
false,
636+
true,
637+
false,
638+
false,
639+
false,
640+
),
641+
None,
597642
None,
598643
None,
599644
);

rust/src/push/mod.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,10 @@ pub enum KnownCondition {
369369
RoomVersionSupports {
370370
feature: Cow<'static, str>,
371371
},
372+
#[serde(rename = "io.element.msc4306.thread_subscription")]
373+
Msc4306ThreadSubscription {
374+
subscribed: bool,
375+
},
372376
}
373377

374378
impl<'source> IntoPyObject<'source> for Condition {
@@ -547,11 +551,13 @@ pub struct FilteredPushRules {
547551
msc3664_enabled: bool,
548552
msc4028_push_encrypted_events: bool,
549553
msc4210_enabled: bool,
554+
msc4306_enabled: bool,
550555
}
551556

552557
#[pymethods]
553558
impl FilteredPushRules {
554559
#[new]
560+
#[allow(clippy::too_many_arguments)]
555561
pub fn py_new(
556562
push_rules: PushRules,
557563
enabled_map: BTreeMap<String, bool>,
@@ -560,6 +566,7 @@ impl FilteredPushRules {
560566
msc3664_enabled: bool,
561567
msc4028_push_encrypted_events: bool,
562568
msc4210_enabled: bool,
569+
msc4306_enabled: bool,
563570
) -> Self {
564571
Self {
565572
push_rules,
@@ -569,6 +576,7 @@ impl FilteredPushRules {
569576
msc3664_enabled,
570577
msc4028_push_encrypted_events,
571578
msc4210_enabled,
579+
msc4306_enabled,
572580
}
573581
}
574582

@@ -619,6 +627,10 @@ impl FilteredPushRules {
619627
return false;
620628
}
621629

630+
if !self.msc4306_enabled && rule.rule_id.contains("/.io.element.msc4306.rule.") {
631+
return false;
632+
}
633+
622634
true
623635
})
624636
.map(|r| {

synapse/push/bulk_push_rule_evaluator.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
Any,
2626
Collection,
2727
Dict,
28+
FrozenSet,
2829
List,
2930
Mapping,
3031
Optional,
@@ -477,8 +478,18 @@ async def _action_for_event_by_user(
477478
event.room_version.msc3931_push_features,
478479
self.hs.config.experimental.msc1767_enabled, # MSC3931 flag
479480
self.hs.config.experimental.msc4210_enabled,
481+
self.hs.config.experimental.msc4306_enabled,
480482
)
481483

484+
msc4306_thread_subscribers: Optional[FrozenSet[str]] = None
485+
if self.hs.config.experimental.msc4306_enabled and thread_id != MAIN_TIMELINE:
486+
# pull out, in batch, all local subscribers to this thread
487+
# (in the common case, they will all be getting processed for push
488+
# rules right now)
489+
msc4306_thread_subscribers = await self.store.get_subscribers_to_thread(
490+
event.room_id, thread_id
491+
)
492+
482493
for uid, rules in rules_by_user.items():
483494
if event.sender == uid:
484495
continue
@@ -503,7 +514,13 @@ async def _action_for_event_by_user(
503514
# current user, it'll be added to the dict later.
504515
actions_by_user[uid] = []
505516

506-
actions = evaluator.run(rules, uid, display_name)
517+
msc4306_thread_subscription_state: Optional[bool] = None
518+
if msc4306_thread_subscribers is not None:
519+
msc4306_thread_subscription_state = uid in msc4306_thread_subscribers
520+
521+
actions = evaluator.run(
522+
rules, uid, display_name, msc4306_thread_subscription_state
523+
)
507524
if "notify" in actions:
508525
# Push rules say we should notify the user of this event
509526
actions_by_user[uid] = actions

synapse/storage/databases/main/push_rule.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ def _load_rules(
110110
msc3381_polls_enabled=experimental_config.msc3381_polls_enabled,
111111
msc4028_push_encrypted_events=experimental_config.msc4028_push_encrypted_events,
112112
msc4210_enabled=experimental_config.msc4210_enabled,
113+
msc4306_enabled=experimental_config.msc4306_enabled,
113114
)
114115

115116
return filtered_rules

0 commit comments

Comments
 (0)