1use ruma_macros::StringEnum;
6
7use super::{
8 Action::*, ConditionalPushRule, EventMatchConditionData, EventPropertyContainsConditionData,
9 EventPropertyIsConditionData, HighlightTweakValue, PushCondition::*,
10 RoomMemberCountConditionData, RoomMemberCountIs, RuleKind, Ruleset,
11 SenderNotificationPermissionConditionData, SoundTweakValue, Tweak,
12};
13use crate::{PrivOwnedStr, UserId, power_levels::NotificationPowerLevelsKey};
14
15impl Ruleset {
16 pub fn server_default(user_id: &UserId) -> Self {
25 Self {
26 override_: [
27 ConditionalPushRule::master(),
28 ConditionalPushRule::suppress_notices(),
29 ConditionalPushRule::invite_for_me(user_id),
30 ConditionalPushRule::member_event(),
31 ConditionalPushRule::is_user_mention(user_id),
32 ConditionalPushRule::is_room_mention(),
33 ConditionalPushRule::tombstone(),
34 ConditionalPushRule::reaction(),
35 ConditionalPushRule::server_acl(),
36 ConditionalPushRule::suppress_edits(),
37 #[cfg(feature = "unstable-msc3930")]
38 ConditionalPushRule::poll_response(),
39 ]
40 .into(),
41 #[cfg(feature = "unstable-msc4306")]
42 postcontent: [
43 ConditionalPushRule::unsubscribed_thread(),
44 ConditionalPushRule::subscribed_thread(),
45 ]
46 .into(),
47 underride: [
48 ConditionalPushRule::call(),
49 ConditionalPushRule::encrypted_room_one_to_one(),
50 ConditionalPushRule::room_one_to_one(),
51 ConditionalPushRule::message(),
52 ConditionalPushRule::encrypted(),
53 #[cfg(feature = "unstable-msc3930")]
54 ConditionalPushRule::poll_start_one_to_one(),
55 #[cfg(feature = "unstable-msc3930")]
56 ConditionalPushRule::poll_start(),
57 #[cfg(feature = "unstable-msc3930")]
58 ConditionalPushRule::poll_end_one_to_one(),
59 #[cfg(feature = "unstable-msc3930")]
60 ConditionalPushRule::poll_end(),
61 ]
62 .into(),
63 ..Default::default()
64 }
65 }
66
67 pub fn update_with_server_default(&mut self, mut new_server_default: Ruleset) {
79 macro_rules! copy_rules_state {
82 ($new_ruleset:ident, $old_ruleset:ident, @fields $($field_name:ident),+) => {
83 $(
84 $new_ruleset.$field_name = $new_ruleset
85 .$field_name
86 .into_iter()
87 .map(|mut new_rule| {
88 if let Some(old_rule) =
89 $old_ruleset.$field_name.shift_take(new_rule.rule_id.as_str())
90 {
91 new_rule.enabled = old_rule.enabled;
92 new_rule.actions = old_rule.actions;
93 }
94
95 new_rule
96 })
97 .collect();
98 )+
99 };
100 }
101 copy_rules_state!(new_server_default, self, @fields override_, content, room, sender, underride);
102
103 macro_rules! remove_remaining_default_rules {
105 ($ruleset:ident, @fields $($field_name:ident),+) => {
106 $(
107 $ruleset.$field_name.retain(|rule| !rule.default);
108 )+
109 };
110 }
111 remove_remaining_default_rules!(self, @fields override_, content, room, sender, underride);
112
113 if let Some(master_rule) =
116 new_server_default.override_.shift_take(PredefinedOverrideRuleId::Master.as_str())
117 {
118 let (pos, _) = self.override_.insert_full(master_rule);
119 self.override_.move_index(pos, 0);
120 }
121
122 macro_rules! merge_rules {
124 ($old_ruleset:ident, $new_ruleset:ident, @fields $($field_name:ident),+) => {
125 $(
126 $old_ruleset.$field_name.extend($new_ruleset.$field_name);
127 )+
128 };
129 }
130 merge_rules!(self, new_server_default, @fields override_, content, room, sender, underride);
131 }
132}
133
134impl ConditionalPushRule {
136 pub fn master() -> Self {
139 Self {
140 actions: vec![],
141 default: true,
142 enabled: false,
143 rule_id: PredefinedOverrideRuleId::Master.to_string(),
144 conditions: vec![],
145 }
146 }
147
148 pub fn suppress_notices() -> Self {
150 Self {
151 actions: vec![],
152 default: true,
153 enabled: true,
154 rule_id: PredefinedOverrideRuleId::SuppressNotices.to_string(),
155 conditions: vec![EventMatch(EventMatchConditionData::new(
156 "content.msgtype".into(),
157 "m.notice".into(),
158 ))],
159 }
160 }
161
162 pub fn invite_for_me(user_id: &UserId) -> Self {
164 Self {
165 actions: vec![Notify, SetTweak(Tweak::Sound(SoundTweakValue::Default))],
166 default: true,
167 enabled: true,
168 rule_id: PredefinedOverrideRuleId::InviteForMe.to_string(),
169 conditions: vec![
170 EventMatch(EventMatchConditionData::new("type".into(), "m.room.member".into())),
171 EventMatch(EventMatchConditionData::new(
172 "content.membership".into(),
173 "invite".into(),
174 )),
175 EventMatch(EventMatchConditionData::new("state_key".into(), user_id.to_string())),
176 ],
177 }
178 }
179
180 pub fn member_event() -> Self {
182 Self {
183 actions: vec![],
184 default: true,
185 enabled: true,
186 rule_id: PredefinedOverrideRuleId::MemberEvent.to_string(),
187 conditions: vec![EventMatch(EventMatchConditionData::new(
188 "type".into(),
189 "m.room.member".into(),
190 ))],
191 }
192 }
193
194 pub fn is_user_mention(user_id: &UserId) -> Self {
197 Self {
198 actions: vec![
199 Notify,
200 SetTweak(Tweak::Sound(SoundTweakValue::Default)),
201 SetTweak(Tweak::Highlight(HighlightTweakValue::Yes)),
202 ],
203 default: true,
204 enabled: true,
205 rule_id: PredefinedOverrideRuleId::IsUserMention.to_string(),
206 conditions: vec![EventPropertyContains(EventPropertyContainsConditionData::new(
207 r"content.m\.mentions.user_ids".to_owned(),
208 user_id.as_str().into(),
209 ))],
210 }
211 }
212
213 pub fn tombstone() -> Self {
217 Self {
218 actions: vec![Notify, SetTweak(Tweak::Highlight(HighlightTweakValue::Yes))],
219 default: true,
220 enabled: true,
221 rule_id: PredefinedOverrideRuleId::Tombstone.to_string(),
222 conditions: vec![
223 EventMatch(EventMatchConditionData::new("type".into(), "m.room.tombstone".into())),
224 EventMatch(EventMatchConditionData::new("state_key".into(), "".into())),
225 ],
226 }
227 }
228
229 pub fn is_room_mention() -> Self {
232 Self {
233 actions: vec![Notify, SetTweak(Tweak::Highlight(HighlightTweakValue::Yes))],
234 default: true,
235 enabled: true,
236 rule_id: PredefinedOverrideRuleId::IsRoomMention.to_string(),
237 conditions: vec![
238 EventPropertyIs(EventPropertyIsConditionData::new(
239 r"content.m\.mentions.room".to_owned(),
240 true.into(),
241 )),
242 SenderNotificationPermission(SenderNotificationPermissionConditionData::new(
243 NotificationPowerLevelsKey::Room,
244 )),
245 ],
246 }
247 }
248
249 pub fn reaction() -> Self {
253 Self {
254 actions: vec![],
255 default: true,
256 enabled: true,
257 rule_id: PredefinedOverrideRuleId::Reaction.to_string(),
258 conditions: vec![EventMatch(EventMatchConditionData::new(
259 "type".into(),
260 "m.reaction".into(),
261 ))],
262 }
263 }
264
265 pub fn server_acl() -> Self {
269 Self {
270 actions: vec![],
271 default: true,
272 enabled: true,
273 rule_id: PredefinedOverrideRuleId::RoomServerAcl.to_string(),
274 conditions: vec![
275 EventMatch(EventMatchConditionData::new("type".into(), "m.room.server_acl".into())),
276 EventMatch(EventMatchConditionData::new("state_key".into(), "".into())),
277 ],
278 }
279 }
280
281 pub fn suppress_edits() -> Self {
285 Self {
286 actions: vec![],
287 default: true,
288 enabled: true,
289 rule_id: PredefinedOverrideRuleId::SuppressEdits.to_string(),
290 conditions: vec![EventPropertyIs(EventPropertyIsConditionData::new(
291 r"content.m\.relates_to.rel_type".to_owned(),
292 "m.replace".into(),
293 ))],
294 }
295 }
296
297 #[cfg(feature = "unstable-msc3930")]
304 pub fn poll_response() -> Self {
305 Self {
306 rule_id: PredefinedOverrideRuleId::PollResponse.to_string(),
307 default: true,
308 enabled: true,
309 conditions: vec![EventPropertyIs(EventPropertyIsConditionData::new(
310 "type".to_owned(),
311 "org.matrix.msc3381.poll.response".into(),
312 ))],
313 actions: vec![],
314 }
315 }
316}
317
318impl ConditionalPushRule {
320 pub fn call() -> Self {
322 Self {
323 rule_id: PredefinedUnderrideRuleId::Call.to_string(),
324 default: true,
325 enabled: true,
326 conditions: vec![EventMatch(EventMatchConditionData::new(
327 "type".into(),
328 "m.call.invite".into(),
329 ))],
330 actions: vec![Notify, SetTweak(Tweak::Sound("ring".into()))],
331 }
332 }
333
334 pub fn encrypted_room_one_to_one() -> Self {
340 Self {
341 rule_id: PredefinedUnderrideRuleId::EncryptedRoomOneToOne.to_string(),
342 default: true,
343 enabled: true,
344 conditions: vec![
345 RoomMemberCount(RoomMemberCountConditionData::new(RoomMemberCountIs::from(
346 js_int::uint!(2),
347 ))),
348 EventMatch(EventMatchConditionData::new("type".into(), "m.room.encrypted".into())),
349 ],
350 actions: vec![Notify, SetTweak(Tweak::Sound(SoundTweakValue::Default))],
351 }
352 }
353
354 pub fn room_one_to_one() -> Self {
356 Self {
357 rule_id: PredefinedUnderrideRuleId::RoomOneToOne.to_string(),
358 default: true,
359 enabled: true,
360 conditions: vec![
361 RoomMemberCount(RoomMemberCountConditionData::new(js_int::uint!(2).into())),
362 EventMatch(EventMatchConditionData::new("type".into(), "m.room.message".into())),
363 ],
364 actions: vec![Notify, SetTweak(Tweak::Sound(SoundTweakValue::Default))],
365 }
366 }
367
368 pub fn message() -> Self {
370 Self {
371 rule_id: PredefinedUnderrideRuleId::Message.to_string(),
372 default: true,
373 enabled: true,
374 conditions: vec![EventMatch(EventMatchConditionData::new(
375 "type".into(),
376 "m.room.message".into(),
377 ))],
378 actions: vec![Notify],
379 }
380 }
381
382 pub fn encrypted() -> Self {
388 Self {
389 rule_id: PredefinedUnderrideRuleId::Encrypted.to_string(),
390 default: true,
391 enabled: true,
392 conditions: vec![EventMatch(EventMatchConditionData::new(
393 "type".into(),
394 "m.room.encrypted".into(),
395 ))],
396 actions: vec![Notify],
397 }
398 }
399
400 #[cfg(feature = "unstable-msc3930")]
407 pub fn poll_start_one_to_one() -> Self {
408 Self {
409 rule_id: PredefinedUnderrideRuleId::PollStartOneToOne.to_string(),
410 default: true,
411 enabled: true,
412 conditions: vec![
413 RoomMemberCount(RoomMemberCountConditionData::new(RoomMemberCountIs::from(
414 js_int::uint!(2),
415 ))),
416 EventPropertyIs(EventPropertyIsConditionData::new(
417 "type".to_owned(),
418 "org.matrix.msc3381.poll.start".into(),
419 )),
420 ],
421 actions: vec![Notify, SetTweak(Tweak::Sound(SoundTweakValue::Default))],
422 }
423 }
424
425 #[cfg(feature = "unstable-msc3930")]
432 pub fn poll_start() -> Self {
433 Self {
434 rule_id: PredefinedUnderrideRuleId::PollStart.to_string(),
435 default: true,
436 enabled: true,
437 conditions: vec![EventPropertyIs(EventPropertyIsConditionData::new(
438 "type".to_owned(),
439 "org.matrix.msc3381.poll.start".into(),
440 ))],
441 actions: vec![Notify],
442 }
443 }
444
445 #[cfg(feature = "unstable-msc3930")]
452 pub fn poll_end_one_to_one() -> Self {
453 Self {
454 rule_id: PredefinedUnderrideRuleId::PollEndOneToOne.to_string(),
455 default: true,
456 enabled: true,
457 conditions: vec![
458 RoomMemberCount(RoomMemberCountConditionData::new(RoomMemberCountIs::from(
459 js_int::uint!(2),
460 ))),
461 EventPropertyIs(EventPropertyIsConditionData::new(
462 "type".to_owned(),
463 "org.matrix.msc3381.poll.end".into(),
464 )),
465 ],
466 actions: vec![Notify, SetTweak(Tweak::Sound(SoundTweakValue::Default))],
467 }
468 }
469
470 #[cfg(feature = "unstable-msc3930")]
477 pub fn poll_end() -> Self {
478 Self {
479 rule_id: PredefinedUnderrideRuleId::PollEnd.to_string(),
480 default: true,
481 enabled: true,
482 conditions: vec![EventPropertyIs(EventPropertyIsConditionData::new(
483 "type".to_owned(),
484 "org.matrix.msc3381.poll.end".into(),
485 ))],
486 actions: vec![Notify],
487 }
488 }
489
490 #[cfg(feature = "unstable-msc4306")]
496 pub fn unsubscribed_thread() -> Self {
497 Self {
498 rule_id: PredefinedUnderrideRuleId::UnsubscribedThread.to_string(),
499 default: true,
500 enabled: true,
501 conditions: vec![ThreadSubscription(super::ThreadSubscriptionConditionData::new(
502 false,
503 ))],
504 actions: vec![],
505 }
506 }
507
508 #[cfg(feature = "unstable-msc4306")]
514 pub fn subscribed_thread() -> Self {
515 Self {
516 rule_id: PredefinedUnderrideRuleId::SubscribedThread.to_string(),
517 default: true,
518 enabled: true,
519 conditions: vec![ThreadSubscription(super::ThreadSubscriptionConditionData::new(true))],
520 actions: vec![Notify, SetTweak(Tweak::Sound(SoundTweakValue::Default))],
521 }
522 }
523}
524
525#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
527#[non_exhaustive]
528pub enum PredefinedRuleId {
529 Override(PredefinedOverrideRuleId),
531
532 Underride(PredefinedUnderrideRuleId),
534
535 Content(PredefinedContentRuleId),
537}
538
539impl PredefinedRuleId {
540 pub fn as_str(&self) -> &str {
542 match self {
543 Self::Override(id) => id.as_str(),
544 Self::Underride(id) => id.as_str(),
545 Self::Content(id) => id.as_str(),
546 }
547 }
548
549 pub fn kind(&self) -> RuleKind {
551 match self {
552 Self::Override(id) => id.kind(),
553 Self::Underride(id) => id.kind(),
554 Self::Content(id) => id.kind(),
555 }
556 }
557}
558
559impl AsRef<str> for PredefinedRuleId {
560 fn as_ref(&self) -> &str {
561 self.as_str()
562 }
563}
564
565#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/string_enum.md"))]
567#[derive(Clone, StringEnum)]
568#[ruma_enum(rename_all(prefix = ".m.rule.", rule = "snake_case"))]
569#[non_exhaustive]
570pub enum PredefinedOverrideRuleId {
571 Master,
573
574 SuppressNotices,
576
577 InviteForMe,
579
580 MemberEvent,
582
583 IsUserMention,
585
586 #[deprecated = "Since Matrix 1.7. Use the m.mentions property with PredefinedOverrideRuleId::IsUserMention instead."]
588 ContainsDisplayName,
589
590 IsRoomMention,
592
593 #[ruma_enum(rename = ".m.rule.roomnotif")]
595 #[deprecated = "Since Matrix 1.7. Use the m.mentions property with PredefinedOverrideRuleId::IsRoomMention instead."]
596 RoomNotif,
597
598 Tombstone,
600
601 Reaction,
603
604 #[ruma_enum(rename = ".m.rule.room.server_acl")]
606 RoomServerAcl,
607
608 SuppressEdits,
610
611 #[cfg(feature = "unstable-msc3930")]
617 #[ruma_enum(rename = ".org.matrix.msc3930.rule.poll_response")]
618 PollResponse,
619
620 #[doc(hidden)]
621 _Custom(PrivOwnedStr),
622}
623
624impl PredefinedOverrideRuleId {
625 pub fn kind(&self) -> RuleKind {
627 RuleKind::Override
628 }
629}
630
631#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/string_enum.md"))]
633#[derive(Clone, StringEnum)]
634#[ruma_enum(rename_all(prefix = ".m.rule.", rule = "snake_case"))]
635#[non_exhaustive]
636pub enum PredefinedUnderrideRuleId {
637 Call,
639
640 EncryptedRoomOneToOne,
642
643 RoomOneToOne,
645
646 Message,
648
649 Encrypted,
651
652 #[cfg(feature = "unstable-msc3930")]
658 #[ruma_enum(rename = ".org.matrix.msc3930.rule.poll_start_one_to_one")]
659 PollStartOneToOne,
660
661 #[cfg(feature = "unstable-msc3930")]
667 #[ruma_enum(rename = ".org.matrix.msc3930.rule.poll_start")]
668 PollStart,
669
670 #[cfg(feature = "unstable-msc3930")]
676 #[ruma_enum(rename = ".org.matrix.msc3930.rule.poll_end_one_to_one")]
677 PollEndOneToOne,
678
679 #[cfg(feature = "unstable-msc3930")]
685 #[ruma_enum(rename = ".org.matrix.msc3930.rule.poll_end")]
686 PollEnd,
687
688 #[cfg(feature = "unstable-msc4306")]
694 #[ruma_enum(rename = ".io.element.msc4306.rule.unsubscribed_thread")]
695 UnsubscribedThread,
696
697 #[cfg(feature = "unstable-msc4306")]
703 #[ruma_enum(rename = ".io.element.msc4306.rule.subscribed_thread")]
704 SubscribedThread,
705
706 #[doc(hidden)]
707 _Custom(PrivOwnedStr),
708}
709
710impl PredefinedUnderrideRuleId {
711 pub fn kind(&self) -> RuleKind {
713 RuleKind::Underride
714 }
715}
716
717#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/string_enum.md"))]
719#[derive(Clone, StringEnum)]
720#[ruma_enum(rename_all(prefix = ".m.rule.", rule = "snake_case"))]
721#[non_exhaustive]
722pub enum PredefinedContentRuleId {
723 #[deprecated = "Since Matrix 1.7. Use the m.mentions property with PredefinedOverrideRuleId::IsUserMention instead."]
725 ContainsUserName,
726
727 #[doc(hidden)]
728 _Custom(PrivOwnedStr),
729}
730
731impl PredefinedContentRuleId {
732 pub fn kind(&self) -> RuleKind {
734 RuleKind::Content
735 }
736}
737
738#[cfg(test)]
739mod tests {
740 use assert_matches2::assert_matches;
741 use assign::assign;
742
743 use super::PredefinedOverrideRuleId;
744 use crate::{
745 push::{Action, ConditionalPushRule, ConditionalPushRuleInit, Ruleset},
746 user_id,
747 };
748
749 #[test]
750 fn update_with_server_default() {
751 let user_rule_id = "user_always_true";
752 let default_rule_id = ".default_always_true";
753
754 let override_ = [
755 assign!(ConditionalPushRule::master(), { enabled: true, actions: vec![Action::Notify]}),
757 ConditionalPushRuleInit {
759 actions: vec![],
760 default: false,
761 enabled: false,
762 rule_id: user_rule_id.to_owned(),
763 conditions: vec![],
764 }
765 .into(),
766 ConditionalPushRuleInit {
768 actions: vec![],
769 default: true,
770 enabled: true,
771 rule_id: default_rule_id.to_owned(),
772 conditions: vec![],
773 }
774 .into(),
775 ]
776 .into_iter()
777 .collect();
778 let mut ruleset = Ruleset { override_, ..Default::default() };
779
780 let new_server_default = Ruleset::server_default(user_id!("@user:localhost"));
781
782 ruleset.update_with_server_default(new_server_default);
783
784 let master_rule = &ruleset.override_[0];
786 assert_eq!(master_rule.rule_id, PredefinedOverrideRuleId::Master.as_str());
787
788 assert!(master_rule.enabled);
790 assert_eq!(master_rule.actions.len(), 1);
791 assert_matches!(&master_rule.actions[0], Action::Notify);
792
793 let user_rule = ruleset.override_.get(user_rule_id).unwrap();
795 assert!(!user_rule.enabled);
796 assert_eq!(user_rule.actions.len(), 0);
797
798 assert_matches!(ruleset.override_.get(default_rule_id), None);
800
801 let member_event_rule =
803 ruleset.override_.get(PredefinedOverrideRuleId::MemberEvent.as_str()).unwrap();
804 assert!(member_event_rule.enabled);
805 assert_eq!(member_event_rule.actions.len(), 0);
806 }
807}