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 #[cfg(feature = "unstable-msc4306")]
49 postcontent: [
50 ConditionalPushRule::unsubscribed_thread(),
51 ConditionalPushRule::subscribed_thread(),
52 ]
53 .into(),
54 underride: [
55 ConditionalPushRule::call(),
56 ConditionalPushRule::encrypted_room_one_to_one(),
57 ConditionalPushRule::room_one_to_one(),
58 ConditionalPushRule::message(),
59 ConditionalPushRule::encrypted(),
60 #[cfg(feature = "unstable-msc3930")]
61 ConditionalPushRule::poll_start_one_to_one(),
62 #[cfg(feature = "unstable-msc3930")]
63 ConditionalPushRule::poll_start(),
64 #[cfg(feature = "unstable-msc3930")]
65 ConditionalPushRule::poll_end_one_to_one(),
66 #[cfg(feature = "unstable-msc3930")]
67 ConditionalPushRule::poll_end(),
68 ]
69 .into(),
70 ..Default::default()
71 }
72 }
73
74 pub fn update_with_server_default(&mut self, mut new_server_default: Ruleset) {
86 macro_rules! copy_rules_state {
89 ($new_ruleset:ident, $old_ruleset:ident, @fields $($field_name:ident),+) => {
90 $(
91 $new_ruleset.$field_name = $new_ruleset
92 .$field_name
93 .into_iter()
94 .map(|mut new_rule| {
95 if let Some(old_rule) =
96 $old_ruleset.$field_name.shift_take(new_rule.rule_id.as_str())
97 {
98 new_rule.enabled = old_rule.enabled;
99 new_rule.actions = old_rule.actions;
100 }
101
102 new_rule
103 })
104 .collect();
105 )+
106 };
107 }
108 copy_rules_state!(new_server_default, self, @fields override_, content, room, sender, underride);
109
110 macro_rules! remove_remaining_default_rules {
112 ($ruleset:ident, @fields $($field_name:ident),+) => {
113 $(
114 $ruleset.$field_name.retain(|rule| !rule.default);
115 )+
116 };
117 }
118 remove_remaining_default_rules!(self, @fields override_, content, room, sender, underride);
119
120 if let Some(master_rule) =
123 new_server_default.override_.shift_take(PredefinedOverrideRuleId::Master.as_str())
124 {
125 let (pos, _) = self.override_.insert_full(master_rule);
126 self.override_.move_index(pos, 0);
127 }
128
129 macro_rules! merge_rules {
131 ($old_ruleset:ident, $new_ruleset:ident, @fields $($field_name:ident),+) => {
132 $(
133 $old_ruleset.$field_name.extend($new_ruleset.$field_name);
134 )+
135 };
136 }
137 merge_rules!(self, new_server_default, @fields override_, content, room, sender, underride);
138 }
139}
140
141impl ConditionalPushRule {
143 pub fn master() -> Self {
146 Self {
147 actions: vec![],
148 default: true,
149 enabled: false,
150 rule_id: PredefinedOverrideRuleId::Master.to_string(),
151 conditions: vec![],
152 }
153 }
154
155 pub fn suppress_notices() -> Self {
157 Self {
158 actions: vec![],
159 default: true,
160 enabled: true,
161 rule_id: PredefinedOverrideRuleId::SuppressNotices.to_string(),
162 conditions: vec![EventMatch {
163 key: "content.msgtype".into(),
164 pattern: "m.notice".into(),
165 }],
166 }
167 }
168
169 pub fn invite_for_me(user_id: &UserId) -> Self {
171 Self {
172 actions: vec![
173 Notify,
174 SetTweak(Tweak::Sound("default".into())),
175 SetTweak(Tweak::Highlight(false)),
176 ],
177 default: true,
178 enabled: true,
179 rule_id: PredefinedOverrideRuleId::InviteForMe.to_string(),
180 conditions: vec![
181 EventMatch { key: "type".into(), pattern: "m.room.member".into() },
182 EventMatch { key: "content.membership".into(), pattern: "invite".into() },
183 EventMatch { key: "state_key".into(), pattern: user_id.to_string() },
184 ],
185 }
186 }
187
188 pub fn member_event() -> Self {
190 Self {
191 actions: vec![],
192 default: true,
193 enabled: true,
194 rule_id: PredefinedOverrideRuleId::MemberEvent.to_string(),
195 conditions: vec![EventMatch { key: "type".into(), pattern: "m.room.member".into() }],
196 }
197 }
198
199 pub fn is_user_mention(user_id: &UserId) -> Self {
202 Self {
203 actions: vec![
204 Notify,
205 SetTweak(Tweak::Sound("default".to_owned())),
206 SetTweak(Tweak::Highlight(true)),
207 ],
208 default: true,
209 enabled: true,
210 rule_id: PredefinedOverrideRuleId::IsUserMention.to_string(),
211 conditions: vec![EventPropertyContains {
212 key: r"content.m\.mentions.user_ids".to_owned(),
213 value: user_id.as_str().into(),
214 }],
215 }
216 }
217
218 #[deprecated = "Since Matrix 1.7. Use the m.mentions property with ConditionalPushRule::is_user_mention() instead."]
224 pub fn contains_display_name() -> Self {
225 #[allow(deprecated)]
226 Self {
227 actions: vec![
228 Notify,
229 SetTweak(Tweak::Sound("default".into())),
230 SetTweak(Tweak::Highlight(true)),
231 ],
232 default: true,
233 enabled: true,
234 rule_id: PredefinedOverrideRuleId::ContainsDisplayName.to_string(),
235 conditions: vec![ContainsDisplayName],
236 }
237 }
238
239 pub fn tombstone() -> Self {
243 Self {
244 actions: vec![Notify, SetTweak(Tweak::Highlight(true))],
245 default: true,
246 enabled: true,
247 rule_id: PredefinedOverrideRuleId::Tombstone.to_string(),
248 conditions: vec![
249 EventMatch { key: "type".into(), pattern: "m.room.tombstone".into() },
250 EventMatch { key: "state_key".into(), pattern: "".into() },
251 ],
252 }
253 }
254
255 pub fn is_room_mention() -> Self {
258 Self {
259 actions: vec![Notify, SetTweak(Tweak::Highlight(true))],
260 default: true,
261 enabled: true,
262 rule_id: PredefinedOverrideRuleId::IsRoomMention.to_string(),
263 conditions: vec![
264 EventPropertyIs { key: r"content.m\.mentions.room".to_owned(), value: true.into() },
265 SenderNotificationPermission { key: NotificationPowerLevelsKey::Room },
266 ],
267 }
268 }
269
270 #[deprecated = "Since Matrix 1.7. Use the m.mentions property with ConditionalPushRule::is_room_mention() instead."]
276 pub fn roomnotif() -> Self {
277 #[allow(deprecated)]
278 Self {
279 actions: vec![Notify, SetTweak(Tweak::Highlight(true))],
280 default: true,
281 enabled: true,
282 rule_id: PredefinedOverrideRuleId::RoomNotif.to_string(),
283 conditions: vec![
284 EventMatch { key: "content.body".into(), pattern: "@room".into() },
285 SenderNotificationPermission { key: "room".into() },
286 ],
287 }
288 }
289
290 pub fn reaction() -> Self {
294 Self {
295 actions: vec![],
296 default: true,
297 enabled: true,
298 rule_id: PredefinedOverrideRuleId::Reaction.to_string(),
299 conditions: vec![EventMatch { key: "type".into(), pattern: "m.reaction".into() }],
300 }
301 }
302
303 pub fn server_acl() -> Self {
307 Self {
308 actions: vec![],
309 default: true,
310 enabled: true,
311 rule_id: PredefinedOverrideRuleId::RoomServerAcl.to_string(),
312 conditions: vec![
313 EventMatch { key: "type".into(), pattern: "m.room.server_acl".into() },
314 EventMatch { key: "state_key".into(), pattern: "".into() },
315 ],
316 }
317 }
318
319 pub fn suppress_edits() -> Self {
323 Self {
324 actions: vec![],
325 default: true,
326 enabled: true,
327 rule_id: PredefinedOverrideRuleId::SuppressEdits.to_string(),
328 conditions: vec![EventPropertyIs {
329 key: r"content.m\.relates_to.rel_type".to_owned(),
330 value: "m.replace".into(),
331 }],
332 }
333 }
334
335 #[cfg(feature = "unstable-msc3930")]
342 pub fn poll_response() -> Self {
343 Self {
344 rule_id: PredefinedOverrideRuleId::PollResponse.to_string(),
345 default: true,
346 enabled: true,
347 conditions: vec![EventPropertyIs {
348 key: "type".to_owned(),
349 value: "org.matrix.msc3381.poll.response".into(),
350 }],
351 actions: vec![],
352 }
353 }
354}
355
356impl PatternedPushRule {
358 #[deprecated = "Since Matrix 1.7. Use the m.mentions property with ConditionalPushRule::is_user_mention() instead."]
364 pub fn contains_user_name(user_id: &UserId) -> Self {
365 #[allow(deprecated)]
366 Self {
367 rule_id: PredefinedContentRuleId::ContainsUserName.to_string(),
368 enabled: true,
369 default: true,
370 pattern: user_id.localpart().into(),
371 actions: vec![
372 Notify,
373 SetTweak(Tweak::Sound("default".into())),
374 SetTweak(Tweak::Highlight(true)),
375 ],
376 }
377 }
378}
379
380impl ConditionalPushRule {
382 pub fn call() -> Self {
384 Self {
385 rule_id: PredefinedUnderrideRuleId::Call.to_string(),
386 default: true,
387 enabled: true,
388 conditions: vec![EventMatch { key: "type".into(), pattern: "m.call.invite".into() }],
389 actions: vec![
390 Notify,
391 SetTweak(Tweak::Sound("ring".into())),
392 SetTweak(Tweak::Highlight(false)),
393 ],
394 }
395 }
396
397 pub fn encrypted_room_one_to_one() -> Self {
403 Self {
404 rule_id: PredefinedUnderrideRuleId::EncryptedRoomOneToOne.to_string(),
405 default: true,
406 enabled: true,
407 conditions: vec![
408 RoomMemberCount { is: RoomMemberCountIs::from(js_int::uint!(2)) },
409 EventMatch { key: "type".into(), pattern: "m.room.encrypted".into() },
410 ],
411 actions: vec![
412 Notify,
413 SetTweak(Tweak::Sound("default".into())),
414 SetTweak(Tweak::Highlight(false)),
415 ],
416 }
417 }
418
419 pub fn room_one_to_one() -> Self {
421 Self {
422 rule_id: PredefinedUnderrideRuleId::RoomOneToOne.to_string(),
423 default: true,
424 enabled: true,
425 conditions: vec![
426 RoomMemberCount { is: RoomMemberCountIs::from(js_int::uint!(2)) },
427 EventMatch { key: "type".into(), pattern: "m.room.message".into() },
428 ],
429 actions: vec![
430 Notify,
431 SetTweak(Tweak::Sound("default".into())),
432 SetTweak(Tweak::Highlight(false)),
433 ],
434 }
435 }
436
437 pub fn message() -> Self {
439 Self {
440 rule_id: PredefinedUnderrideRuleId::Message.to_string(),
441 default: true,
442 enabled: true,
443 conditions: vec![EventMatch { key: "type".into(), pattern: "m.room.message".into() }],
444 actions: vec![Notify, SetTweak(Tweak::Highlight(false))],
445 }
446 }
447
448 pub fn encrypted() -> Self {
454 Self {
455 rule_id: PredefinedUnderrideRuleId::Encrypted.to_string(),
456 default: true,
457 enabled: true,
458 conditions: vec![EventMatch { key: "type".into(), pattern: "m.room.encrypted".into() }],
459 actions: vec![Notify, SetTweak(Tweak::Highlight(false))],
460 }
461 }
462
463 #[cfg(feature = "unstable-msc3930")]
470 pub fn poll_start_one_to_one() -> Self {
471 Self {
472 rule_id: PredefinedUnderrideRuleId::PollStartOneToOne.to_string(),
473 default: true,
474 enabled: true,
475 conditions: vec![
476 RoomMemberCount { is: RoomMemberCountIs::from(js_int::uint!(2)) },
477 EventPropertyIs {
478 key: "type".to_owned(),
479 value: "org.matrix.msc3381.poll.start".into(),
480 },
481 ],
482 actions: vec![Notify, SetTweak(Tweak::Sound("default".into()))],
483 }
484 }
485
486 #[cfg(feature = "unstable-msc3930")]
493 pub fn poll_start() -> Self {
494 Self {
495 rule_id: PredefinedUnderrideRuleId::PollStart.to_string(),
496 default: true,
497 enabled: true,
498 conditions: vec![EventPropertyIs {
499 key: "type".to_owned(),
500 value: "org.matrix.msc3381.poll.start".into(),
501 }],
502 actions: vec![Notify],
503 }
504 }
505
506 #[cfg(feature = "unstable-msc3930")]
513 pub fn poll_end_one_to_one() -> Self {
514 Self {
515 rule_id: PredefinedUnderrideRuleId::PollEndOneToOne.to_string(),
516 default: true,
517 enabled: true,
518 conditions: vec![
519 RoomMemberCount { is: RoomMemberCountIs::from(js_int::uint!(2)) },
520 EventPropertyIs {
521 key: "type".to_owned(),
522 value: "org.matrix.msc3381.poll.end".into(),
523 },
524 ],
525 actions: vec![Notify, SetTweak(Tweak::Sound("default".into()))],
526 }
527 }
528
529 #[cfg(feature = "unstable-msc3930")]
536 pub fn poll_end() -> Self {
537 Self {
538 rule_id: PredefinedUnderrideRuleId::PollEnd.to_string(),
539 default: true,
540 enabled: true,
541 conditions: vec![EventPropertyIs {
542 key: "type".to_owned(),
543 value: "org.matrix.msc3381.poll.end".into(),
544 }],
545 actions: vec![Notify],
546 }
547 }
548
549 #[cfg(feature = "unstable-msc4306")]
555 pub fn unsubscribed_thread() -> Self {
556 Self {
557 rule_id: PredefinedUnderrideRuleId::UnsubscribedThread.to_string(),
558 default: true,
559 enabled: true,
560 conditions: vec![ThreadSubscription { subscribed: false }],
561 actions: vec![],
562 }
563 }
564
565 #[cfg(feature = "unstable-msc4306")]
571 pub fn subscribed_thread() -> Self {
572 Self {
573 rule_id: PredefinedUnderrideRuleId::SubscribedThread.to_string(),
574 default: true,
575 enabled: true,
576 conditions: vec![ThreadSubscription { subscribed: true }],
577 actions: vec![Notify, SetTweak(Tweak::Sound("default".into()))],
578 }
579 }
580}
581
582#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
584#[non_exhaustive]
585pub enum PredefinedRuleId {
586 Override(PredefinedOverrideRuleId),
588
589 Underride(PredefinedUnderrideRuleId),
591
592 Content(PredefinedContentRuleId),
594}
595
596impl PredefinedRuleId {
597 pub fn as_str(&self) -> &str {
599 match self {
600 Self::Override(id) => id.as_str(),
601 Self::Underride(id) => id.as_str(),
602 Self::Content(id) => id.as_str(),
603 }
604 }
605
606 pub fn kind(&self) -> RuleKind {
608 match self {
609 Self::Override(id) => id.kind(),
610 Self::Underride(id) => id.kind(),
611 Self::Content(id) => id.kind(),
612 }
613 }
614}
615
616impl AsRef<str> for PredefinedRuleId {
617 fn as_ref(&self) -> &str {
618 self.as_str()
619 }
620}
621
622#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/string_enum.md"))]
624#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, StringEnum)]
625#[ruma_enum(rename_all = ".m.rule.snake_case")]
626#[non_exhaustive]
627pub enum PredefinedOverrideRuleId {
628 Master,
630
631 SuppressNotices,
633
634 InviteForMe,
636
637 MemberEvent,
639
640 IsUserMention,
642
643 #[deprecated = "Since Matrix 1.7. Use the m.mentions property with PredefinedOverrideRuleId::IsUserMention instead."]
645 ContainsDisplayName,
646
647 IsRoomMention,
649
650 #[ruma_enum(rename = ".m.rule.roomnotif")]
652 #[deprecated = "Since Matrix 1.7. Use the m.mentions property with PredefinedOverrideRuleId::IsRoomMention instead."]
653 RoomNotif,
654
655 Tombstone,
657
658 Reaction,
660
661 #[ruma_enum(rename = ".m.rule.room.server_acl")]
663 RoomServerAcl,
664
665 SuppressEdits,
667
668 #[cfg(feature = "unstable-msc3930")]
674 #[ruma_enum(rename = ".org.matrix.msc3930.rule.poll_response")]
675 PollResponse,
676
677 #[doc(hidden)]
678 _Custom(PrivOwnedStr),
679}
680
681impl PredefinedOverrideRuleId {
682 pub fn kind(&self) -> RuleKind {
684 RuleKind::Override
685 }
686}
687
688#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/string_enum.md"))]
690#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, StringEnum)]
691#[ruma_enum(rename_all = ".m.rule.snake_case")]
692#[non_exhaustive]
693pub enum PredefinedUnderrideRuleId {
694 Call,
696
697 EncryptedRoomOneToOne,
699
700 RoomOneToOne,
702
703 Message,
705
706 Encrypted,
708
709 #[cfg(feature = "unstable-msc3930")]
715 #[ruma_enum(rename = ".org.matrix.msc3930.rule.poll_start_one_to_one")]
716 PollStartOneToOne,
717
718 #[cfg(feature = "unstable-msc3930")]
724 #[ruma_enum(rename = ".org.matrix.msc3930.rule.poll_start")]
725 PollStart,
726
727 #[cfg(feature = "unstable-msc3930")]
733 #[ruma_enum(rename = ".org.matrix.msc3930.rule.poll_end_one_to_one")]
734 PollEndOneToOne,
735
736 #[cfg(feature = "unstable-msc3930")]
742 #[ruma_enum(rename = ".org.matrix.msc3930.rule.poll_end")]
743 PollEnd,
744
745 #[cfg(feature = "unstable-msc4306")]
751 #[ruma_enum(rename = ".io.element.msc4306.rule.unsubscribed_thread")]
752 UnsubscribedThread,
753
754 #[cfg(feature = "unstable-msc4306")]
760 #[ruma_enum(rename = ".io.element.msc4306.rule.subscribed_thread")]
761 SubscribedThread,
762
763 #[doc(hidden)]
764 _Custom(PrivOwnedStr),
765}
766
767impl PredefinedUnderrideRuleId {
768 pub fn kind(&self) -> RuleKind {
770 RuleKind::Underride
771 }
772}
773
774#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/string_enum.md"))]
776#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, StringEnum)]
777#[ruma_enum(rename_all = ".m.rule.snake_case")]
778#[non_exhaustive]
779pub enum PredefinedContentRuleId {
780 #[deprecated = "Since Matrix 1.7. Use the m.mentions property with PredefinedOverrideRuleId::IsUserMention instead."]
782 ContainsUserName,
783
784 #[doc(hidden)]
785 _Custom(PrivOwnedStr),
786}
787
788impl PredefinedContentRuleId {
789 pub fn kind(&self) -> RuleKind {
791 RuleKind::Content
792 }
793}
794
795#[cfg(test)]
796mod tests {
797 use assert_matches2::assert_matches;
798 use assign::assign;
799
800 use super::PredefinedOverrideRuleId;
801 use crate::{
802 push::{Action, ConditionalPushRule, ConditionalPushRuleInit, Ruleset},
803 user_id,
804 };
805
806 #[test]
807 fn update_with_server_default() {
808 let user_rule_id = "user_always_true";
809 let default_rule_id = ".default_always_true";
810
811 let override_ = [
812 assign!(ConditionalPushRule::master(), { enabled: true, actions: vec![Action::Notify]}),
814 ConditionalPushRuleInit {
816 actions: vec![],
817 default: false,
818 enabled: false,
819 rule_id: user_rule_id.to_owned(),
820 conditions: vec![],
821 }
822 .into(),
823 ConditionalPushRuleInit {
825 actions: vec![],
826 default: true,
827 enabled: true,
828 rule_id: default_rule_id.to_owned(),
829 conditions: vec![],
830 }
831 .into(),
832 ]
833 .into_iter()
834 .collect();
835 let mut ruleset = Ruleset { override_, ..Default::default() };
836
837 let new_server_default = Ruleset::server_default(user_id!("@user:localhost"));
838
839 ruleset.update_with_server_default(new_server_default);
840
841 let master_rule = &ruleset.override_[0];
843 assert_eq!(master_rule.rule_id, PredefinedOverrideRuleId::Master.as_str());
844
845 assert!(master_rule.enabled);
847 assert_eq!(master_rule.actions.len(), 1);
848 assert_matches!(&master_rule.actions[0], Action::Notify);
849
850 let user_rule = ruleset.override_.get(user_rule_id).unwrap();
852 assert!(!user_rule.enabled);
853 assert_eq!(user_rule.actions.len(), 0);
854
855 assert_matches!(ruleset.override_.get(default_rule_id), None);
857
858 let member_event_rule =
860 ruleset.override_.get(PredefinedOverrideRuleId::MemberEvent.as_str()).unwrap();
861 assert!(member_event_rule.enabled);
862 assert_eq!(member_event_rule.actions.len(), 0);
863 }
864}