1use ruma_macros::StringEnum;
6
7use super::{
8 Action::*, ConditionalPushRule, PatternedPushRule, PushCondition::*, RoomMemberCountIs,
9 RuleKind, Ruleset, Tweak,
10};
11use crate::{power_levels::NotificationPowerLevelsKey, PrivOwnedStr, UserId};
12
13impl Ruleset {
14 pub fn server_default(user_id: &UserId) -> Self {
23 Self {
24 content: [
25 #[allow(deprecated)]
26 PatternedPushRule::contains_user_name(user_id),
27 ]
28 .into(),
29 override_: [
30 ConditionalPushRule::master(),
31 ConditionalPushRule::suppress_notices(),
32 ConditionalPushRule::invite_for_me(user_id),
33 ConditionalPushRule::member_event(),
34 ConditionalPushRule::is_user_mention(user_id),
35 #[allow(deprecated)]
36 ConditionalPushRule::contains_display_name(),
37 ConditionalPushRule::is_room_mention(),
38 #[allow(deprecated)]
39 ConditionalPushRule::roomnotif(),
40 ConditionalPushRule::tombstone(),
41 ConditionalPushRule::reaction(),
42 ConditionalPushRule::server_acl(),
43 ConditionalPushRule::suppress_edits(),
44 #[cfg(feature = "unstable-msc3930")]
45 ConditionalPushRule::poll_response(),
46 ]
47 .into(),
48 underride: [
49 #[cfg(feature = "unstable-msc4306")]
50 ConditionalPushRule::unsubscribed_thread(),
51 #[cfg(feature = "unstable-msc4306")]
52 ConditionalPushRule::subscribed_thread(),
53 ConditionalPushRule::call(),
54 ConditionalPushRule::encrypted_room_one_to_one(),
55 ConditionalPushRule::room_one_to_one(),
56 ConditionalPushRule::message(),
57 ConditionalPushRule::encrypted(),
58 #[cfg(feature = "unstable-msc3930")]
59 ConditionalPushRule::poll_start_one_to_one(),
60 #[cfg(feature = "unstable-msc3930")]
61 ConditionalPushRule::poll_start(),
62 #[cfg(feature = "unstable-msc3930")]
63 ConditionalPushRule::poll_end_one_to_one(),
64 #[cfg(feature = "unstable-msc3930")]
65 ConditionalPushRule::poll_end(),
66 ]
67 .into(),
68 ..Default::default()
69 }
70 }
71
72 pub fn update_with_server_default(&mut self, mut new_server_default: Ruleset) {
84 macro_rules! copy_rules_state {
87 ($new_ruleset:ident, $old_ruleset:ident, @fields $($field_name:ident),+) => {
88 $(
89 $new_ruleset.$field_name = $new_ruleset
90 .$field_name
91 .into_iter()
92 .map(|mut new_rule| {
93 if let Some(old_rule) =
94 $old_ruleset.$field_name.shift_take(new_rule.rule_id.as_str())
95 {
96 new_rule.enabled = old_rule.enabled;
97 new_rule.actions = old_rule.actions;
98 }
99
100 new_rule
101 })
102 .collect();
103 )+
104 };
105 }
106 copy_rules_state!(new_server_default, self, @fields override_, content, room, sender, underride);
107
108 macro_rules! remove_remaining_default_rules {
110 ($ruleset:ident, @fields $($field_name:ident),+) => {
111 $(
112 $ruleset.$field_name.retain(|rule| !rule.default);
113 )+
114 };
115 }
116 remove_remaining_default_rules!(self, @fields override_, content, room, sender, underride);
117
118 if let Some(master_rule) =
121 new_server_default.override_.shift_take(PredefinedOverrideRuleId::Master.as_str())
122 {
123 let (pos, _) = self.override_.insert_full(master_rule);
124 self.override_.move_index(pos, 0);
125 }
126
127 macro_rules! merge_rules {
129 ($old_ruleset:ident, $new_ruleset:ident, @fields $($field_name:ident),+) => {
130 $(
131 $old_ruleset.$field_name.extend($new_ruleset.$field_name);
132 )+
133 };
134 }
135 merge_rules!(self, new_server_default, @fields override_, content, room, sender, underride);
136 }
137}
138
139impl ConditionalPushRule {
141 pub fn master() -> Self {
144 Self {
145 actions: vec![],
146 default: true,
147 enabled: false,
148 rule_id: PredefinedOverrideRuleId::Master.to_string(),
149 conditions: vec![],
150 }
151 }
152
153 pub fn suppress_notices() -> Self {
155 Self {
156 actions: vec![],
157 default: true,
158 enabled: true,
159 rule_id: PredefinedOverrideRuleId::SuppressNotices.to_string(),
160 conditions: vec![EventMatch {
161 key: "content.msgtype".into(),
162 pattern: "m.notice".into(),
163 }],
164 }
165 }
166
167 pub fn invite_for_me(user_id: &UserId) -> Self {
169 Self {
170 actions: vec![
171 Notify,
172 SetTweak(Tweak::Sound("default".into())),
173 SetTweak(Tweak::Highlight(false)),
174 ],
175 default: true,
176 enabled: true,
177 rule_id: PredefinedOverrideRuleId::InviteForMe.to_string(),
178 conditions: vec![
179 EventMatch { key: "type".into(), pattern: "m.room.member".into() },
180 EventMatch { key: "content.membership".into(), pattern: "invite".into() },
181 EventMatch { key: "state_key".into(), pattern: user_id.to_string() },
182 ],
183 }
184 }
185
186 pub fn member_event() -> Self {
188 Self {
189 actions: vec![],
190 default: true,
191 enabled: true,
192 rule_id: PredefinedOverrideRuleId::MemberEvent.to_string(),
193 conditions: vec![EventMatch { key: "type".into(), pattern: "m.room.member".into() }],
194 }
195 }
196
197 pub fn is_user_mention(user_id: &UserId) -> Self {
200 Self {
201 actions: vec![
202 Notify,
203 SetTweak(Tweak::Sound("default".to_owned())),
204 SetTweak(Tweak::Highlight(true)),
205 ],
206 default: true,
207 enabled: true,
208 rule_id: PredefinedOverrideRuleId::IsUserMention.to_string(),
209 conditions: vec![EventPropertyContains {
210 key: r"content.m\.mentions.user_ids".to_owned(),
211 value: user_id.as_str().into(),
212 }],
213 }
214 }
215
216 #[deprecated = "Since Matrix 1.7. Use the m.mentions property with ConditionalPushRule::is_user_mention() instead."]
222 pub fn contains_display_name() -> Self {
223 #[allow(deprecated)]
224 Self {
225 actions: vec![
226 Notify,
227 SetTweak(Tweak::Sound("default".into())),
228 SetTweak(Tweak::Highlight(true)),
229 ],
230 default: true,
231 enabled: true,
232 rule_id: PredefinedOverrideRuleId::ContainsDisplayName.to_string(),
233 conditions: vec![ContainsDisplayName],
234 }
235 }
236
237 pub fn tombstone() -> Self {
241 Self {
242 actions: vec![Notify, SetTweak(Tweak::Highlight(true))],
243 default: true,
244 enabled: true,
245 rule_id: PredefinedOverrideRuleId::Tombstone.to_string(),
246 conditions: vec![
247 EventMatch { key: "type".into(), pattern: "m.room.tombstone".into() },
248 EventMatch { key: "state_key".into(), pattern: "".into() },
249 ],
250 }
251 }
252
253 pub fn is_room_mention() -> Self {
256 Self {
257 actions: vec![Notify, SetTweak(Tweak::Highlight(true))],
258 default: true,
259 enabled: true,
260 rule_id: PredefinedOverrideRuleId::IsRoomMention.to_string(),
261 conditions: vec![
262 EventPropertyIs { key: r"content.m\.mentions.room".to_owned(), value: true.into() },
263 SenderNotificationPermission { key: NotificationPowerLevelsKey::Room },
264 ],
265 }
266 }
267
268 #[deprecated = "Since Matrix 1.7. Use the m.mentions property with ConditionalPushRule::is_room_mention() instead."]
274 pub fn roomnotif() -> Self {
275 #[allow(deprecated)]
276 Self {
277 actions: vec![Notify, SetTweak(Tweak::Highlight(true))],
278 default: true,
279 enabled: true,
280 rule_id: PredefinedOverrideRuleId::RoomNotif.to_string(),
281 conditions: vec![
282 EventMatch { key: "content.body".into(), pattern: "@room".into() },
283 SenderNotificationPermission { key: "room".into() },
284 ],
285 }
286 }
287
288 pub fn reaction() -> Self {
292 Self {
293 actions: vec![],
294 default: true,
295 enabled: true,
296 rule_id: PredefinedOverrideRuleId::Reaction.to_string(),
297 conditions: vec![EventMatch { key: "type".into(), pattern: "m.reaction".into() }],
298 }
299 }
300
301 pub fn server_acl() -> Self {
305 Self {
306 actions: vec![],
307 default: true,
308 enabled: true,
309 rule_id: PredefinedOverrideRuleId::RoomServerAcl.to_string(),
310 conditions: vec![
311 EventMatch { key: "type".into(), pattern: "m.room.server_acl".into() },
312 EventMatch { key: "state_key".into(), pattern: "".into() },
313 ],
314 }
315 }
316
317 pub fn suppress_edits() -> Self {
321 Self {
322 actions: vec![],
323 default: true,
324 enabled: true,
325 rule_id: PredefinedOverrideRuleId::SuppressEdits.to_string(),
326 conditions: vec![EventPropertyIs {
327 key: r"content.m\.relates_to.rel_type".to_owned(),
328 value: "m.replace".into(),
329 }],
330 }
331 }
332
333 #[cfg(feature = "unstable-msc3930")]
340 pub fn poll_response() -> Self {
341 Self {
342 rule_id: PredefinedOverrideRuleId::PollResponse.to_string(),
343 default: true,
344 enabled: true,
345 conditions: vec![EventPropertyIs {
346 key: "type".to_owned(),
347 value: "org.matrix.msc3381.poll.response".into(),
348 }],
349 actions: vec![],
350 }
351 }
352}
353
354impl PatternedPushRule {
356 #[deprecated = "Since Matrix 1.7. Use the m.mentions property with ConditionalPushRule::is_user_mention() instead."]
362 pub fn contains_user_name(user_id: &UserId) -> Self {
363 #[allow(deprecated)]
364 Self {
365 rule_id: PredefinedContentRuleId::ContainsUserName.to_string(),
366 enabled: true,
367 default: true,
368 pattern: user_id.localpart().into(),
369 actions: vec![
370 Notify,
371 SetTweak(Tweak::Sound("default".into())),
372 SetTweak(Tweak::Highlight(true)),
373 ],
374 }
375 }
376}
377
378impl ConditionalPushRule {
380 pub fn call() -> Self {
382 Self {
383 rule_id: PredefinedUnderrideRuleId::Call.to_string(),
384 default: true,
385 enabled: true,
386 conditions: vec![EventMatch { key: "type".into(), pattern: "m.call.invite".into() }],
387 actions: vec![
388 Notify,
389 SetTweak(Tweak::Sound("ring".into())),
390 SetTweak(Tweak::Highlight(false)),
391 ],
392 }
393 }
394
395 pub fn encrypted_room_one_to_one() -> Self {
401 Self {
402 rule_id: PredefinedUnderrideRuleId::EncryptedRoomOneToOne.to_string(),
403 default: true,
404 enabled: true,
405 conditions: vec![
406 RoomMemberCount { is: RoomMemberCountIs::from(js_int::uint!(2)) },
407 EventMatch { key: "type".into(), pattern: "m.room.encrypted".into() },
408 ],
409 actions: vec![
410 Notify,
411 SetTweak(Tweak::Sound("default".into())),
412 SetTweak(Tweak::Highlight(false)),
413 ],
414 }
415 }
416
417 pub fn room_one_to_one() -> Self {
419 Self {
420 rule_id: PredefinedUnderrideRuleId::RoomOneToOne.to_string(),
421 default: true,
422 enabled: true,
423 conditions: vec![
424 RoomMemberCount { is: RoomMemberCountIs::from(js_int::uint!(2)) },
425 EventMatch { key: "type".into(), pattern: "m.room.message".into() },
426 ],
427 actions: vec![
428 Notify,
429 SetTweak(Tweak::Sound("default".into())),
430 SetTweak(Tweak::Highlight(false)),
431 ],
432 }
433 }
434
435 pub fn message() -> Self {
437 Self {
438 rule_id: PredefinedUnderrideRuleId::Message.to_string(),
439 default: true,
440 enabled: true,
441 conditions: vec![EventMatch { key: "type".into(), pattern: "m.room.message".into() }],
442 actions: vec![Notify, SetTweak(Tweak::Highlight(false))],
443 }
444 }
445
446 pub fn encrypted() -> Self {
452 Self {
453 rule_id: PredefinedUnderrideRuleId::Encrypted.to_string(),
454 default: true,
455 enabled: true,
456 conditions: vec![EventMatch { key: "type".into(), pattern: "m.room.encrypted".into() }],
457 actions: vec![Notify, SetTweak(Tweak::Highlight(false))],
458 }
459 }
460
461 #[cfg(feature = "unstable-msc3930")]
468 pub fn poll_start_one_to_one() -> Self {
469 Self {
470 rule_id: PredefinedUnderrideRuleId::PollStartOneToOne.to_string(),
471 default: true,
472 enabled: true,
473 conditions: vec![
474 RoomMemberCount { is: RoomMemberCountIs::from(js_int::uint!(2)) },
475 EventPropertyIs {
476 key: "type".to_owned(),
477 value: "org.matrix.msc3381.poll.start".into(),
478 },
479 ],
480 actions: vec![Notify, SetTweak(Tweak::Sound("default".into()))],
481 }
482 }
483
484 #[cfg(feature = "unstable-msc3930")]
491 pub fn poll_start() -> Self {
492 Self {
493 rule_id: PredefinedUnderrideRuleId::PollStart.to_string(),
494 default: true,
495 enabled: true,
496 conditions: vec![EventPropertyIs {
497 key: "type".to_owned(),
498 value: "org.matrix.msc3381.poll.start".into(),
499 }],
500 actions: vec![Notify],
501 }
502 }
503
504 #[cfg(feature = "unstable-msc3930")]
511 pub fn poll_end_one_to_one() -> Self {
512 Self {
513 rule_id: PredefinedUnderrideRuleId::PollEndOneToOne.to_string(),
514 default: true,
515 enabled: true,
516 conditions: vec![
517 RoomMemberCount { is: RoomMemberCountIs::from(js_int::uint!(2)) },
518 EventPropertyIs {
519 key: "type".to_owned(),
520 value: "org.matrix.msc3381.poll.end".into(),
521 },
522 ],
523 actions: vec![Notify, SetTweak(Tweak::Sound("default".into()))],
524 }
525 }
526
527 #[cfg(feature = "unstable-msc3930")]
534 pub fn poll_end() -> Self {
535 Self {
536 rule_id: PredefinedUnderrideRuleId::PollEnd.to_string(),
537 default: true,
538 enabled: true,
539 conditions: vec![EventPropertyIs {
540 key: "type".to_owned(),
541 value: "org.matrix.msc3381.poll.end".into(),
542 }],
543 actions: vec![Notify],
544 }
545 }
546
547 #[cfg(feature = "unstable-msc4306")]
553 pub fn unsubscribed_thread() -> Self {
554 Self {
555 rule_id: PredefinedUnderrideRuleId::UnsubscribedThread.to_string(),
556 default: true,
557 enabled: true,
558 conditions: vec![ThreadSubscription { subscribed: false }],
559 actions: vec![],
560 }
561 }
562
563 #[cfg(feature = "unstable-msc4306")]
569 pub fn subscribed_thread() -> Self {
570 Self {
571 rule_id: PredefinedUnderrideRuleId::SubscribedThread.to_string(),
572 default: true,
573 enabled: true,
574 conditions: vec![ThreadSubscription { subscribed: true }],
575 actions: vec![Notify, SetTweak(Tweak::Sound("default".into()))],
576 }
577 }
578}
579
580#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
582#[non_exhaustive]
583pub enum PredefinedRuleId {
584 Override(PredefinedOverrideRuleId),
586
587 Underride(PredefinedUnderrideRuleId),
589
590 Content(PredefinedContentRuleId),
592}
593
594impl PredefinedRuleId {
595 pub fn as_str(&self) -> &str {
597 match self {
598 Self::Override(id) => id.as_str(),
599 Self::Underride(id) => id.as_str(),
600 Self::Content(id) => id.as_str(),
601 }
602 }
603
604 pub fn kind(&self) -> RuleKind {
606 match self {
607 Self::Override(id) => id.kind(),
608 Self::Underride(id) => id.kind(),
609 Self::Content(id) => id.kind(),
610 }
611 }
612}
613
614impl AsRef<str> for PredefinedRuleId {
615 fn as_ref(&self) -> &str {
616 self.as_str()
617 }
618}
619
620#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/string_enum.md"))]
622#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, StringEnum)]
623#[ruma_enum(rename_all = ".m.rule.snake_case")]
624#[non_exhaustive]
625pub enum PredefinedOverrideRuleId {
626 Master,
628
629 SuppressNotices,
631
632 InviteForMe,
634
635 MemberEvent,
637
638 IsUserMention,
640
641 #[deprecated = "Since Matrix 1.7. Use the m.mentions property with PredefinedOverrideRuleId::IsUserMention instead."]
643 ContainsDisplayName,
644
645 IsRoomMention,
647
648 #[ruma_enum(rename = ".m.rule.roomnotif")]
650 #[deprecated = "Since Matrix 1.7. Use the m.mentions property with PredefinedOverrideRuleId::IsRoomMention instead."]
651 RoomNotif,
652
653 Tombstone,
655
656 Reaction,
658
659 #[ruma_enum(rename = ".m.rule.room.server_acl")]
661 RoomServerAcl,
662
663 SuppressEdits,
665
666 #[cfg(feature = "unstable-msc3930")]
672 #[ruma_enum(rename = ".org.matrix.msc3930.rule.poll_response")]
673 PollResponse,
674
675 #[doc(hidden)]
676 _Custom(PrivOwnedStr),
677}
678
679impl PredefinedOverrideRuleId {
680 pub fn kind(&self) -> RuleKind {
682 RuleKind::Override
683 }
684}
685
686#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/string_enum.md"))]
688#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, StringEnum)]
689#[ruma_enum(rename_all = ".m.rule.snake_case")]
690#[non_exhaustive]
691pub enum PredefinedUnderrideRuleId {
692 Call,
694
695 EncryptedRoomOneToOne,
697
698 RoomOneToOne,
700
701 Message,
703
704 Encrypted,
706
707 #[cfg(feature = "unstable-msc3930")]
713 #[ruma_enum(rename = ".org.matrix.msc3930.rule.poll_start_one_to_one")]
714 PollStartOneToOne,
715
716 #[cfg(feature = "unstable-msc3930")]
722 #[ruma_enum(rename = ".org.matrix.msc3930.rule.poll_start")]
723 PollStart,
724
725 #[cfg(feature = "unstable-msc3930")]
731 #[ruma_enum(rename = ".org.matrix.msc3930.rule.poll_end_one_to_one")]
732 PollEndOneToOne,
733
734 #[cfg(feature = "unstable-msc3930")]
740 #[ruma_enum(rename = ".org.matrix.msc3930.rule.poll_end")]
741 PollEnd,
742
743 #[cfg(feature = "unstable-msc4306")]
749 #[ruma_enum(rename = ".io.element.msc4306.rule.unsubscribed_thread")]
750 UnsubscribedThread,
751
752 #[cfg(feature = "unstable-msc4306")]
758 #[ruma_enum(rename = ".io.element.msc4306.rule.subscribed_thread")]
759 SubscribedThread,
760
761 #[doc(hidden)]
762 _Custom(PrivOwnedStr),
763}
764
765impl PredefinedUnderrideRuleId {
766 pub fn kind(&self) -> RuleKind {
768 RuleKind::Underride
769 }
770}
771
772#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/string_enum.md"))]
774#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, StringEnum)]
775#[ruma_enum(rename_all = ".m.rule.snake_case")]
776#[non_exhaustive]
777pub enum PredefinedContentRuleId {
778 #[deprecated = "Since Matrix 1.7. Use the m.mentions property with PredefinedOverrideRuleId::IsUserMention instead."]
780 ContainsUserName,
781
782 #[doc(hidden)]
783 _Custom(PrivOwnedStr),
784}
785
786impl PredefinedContentRuleId {
787 pub fn kind(&self) -> RuleKind {
789 RuleKind::Content
790 }
791}
792
793#[cfg(test)]
794mod tests {
795 use assert_matches2::assert_matches;
796 use assign::assign;
797
798 use super::PredefinedOverrideRuleId;
799 use crate::{
800 push::{Action, ConditionalPushRule, ConditionalPushRuleInit, Ruleset},
801 user_id,
802 };
803
804 #[test]
805 fn update_with_server_default() {
806 let user_rule_id = "user_always_true";
807 let default_rule_id = ".default_always_true";
808
809 let override_ = [
810 assign!(ConditionalPushRule::master(), { enabled: true, actions: vec![Action::Notify]}),
812 ConditionalPushRuleInit {
814 actions: vec![],
815 default: false,
816 enabled: false,
817 rule_id: user_rule_id.to_owned(),
818 conditions: vec![],
819 }
820 .into(),
821 ConditionalPushRuleInit {
823 actions: vec![],
824 default: true,
825 enabled: true,
826 rule_id: default_rule_id.to_owned(),
827 conditions: vec![],
828 }
829 .into(),
830 ]
831 .into_iter()
832 .collect();
833 let mut ruleset = Ruleset { override_, ..Default::default() };
834
835 let new_server_default = Ruleset::server_default(user_id!("@user:localhost"));
836
837 ruleset.update_with_server_default(new_server_default);
838
839 let master_rule = &ruleset.override_[0];
841 assert_eq!(master_rule.rule_id, PredefinedOverrideRuleId::Master.as_str());
842
843 assert!(master_rule.enabled);
845 assert_eq!(master_rule.actions.len(), 1);
846 assert_matches!(&master_rule.actions[0], Action::Notify);
847
848 let user_rule = ruleset.override_.get(user_rule_id).unwrap();
850 assert!(!user_rule.enabled);
851 assert_eq!(user_rule.actions.len(), 0);
852
853 assert_matches!(ruleset.override_.get(default_rule_id), None);
855
856 let member_event_rule =
858 ruleset.override_.get(PredefinedOverrideRuleId::MemberEvent.as_str()).unwrap();
859 assert!(member_event_rule.enabled);
860 assert_eq!(member_event_rule.actions.len(), 0);
861 }
862}