1use ruma_macros::StringEnum;
6
7use super::{
8 Action::*, ConditionalPushRule, PushCondition::*, RoomMemberCountIs, RuleKind, Ruleset, Tweak,
9};
10use crate::{power_levels::NotificationPowerLevelsKey, PrivOwnedStr, UserId};
11
12impl Ruleset {
13 pub fn server_default(user_id: &UserId) -> Self {
22 Self {
23 override_: [
24 ConditionalPushRule::master(),
25 ConditionalPushRule::suppress_notices(),
26 ConditionalPushRule::invite_for_me(user_id),
27 ConditionalPushRule::member_event(),
28 ConditionalPushRule::is_user_mention(user_id),
29 ConditionalPushRule::is_room_mention(),
30 ConditionalPushRule::tombstone(),
31 ConditionalPushRule::reaction(),
32 ConditionalPushRule::server_acl(),
33 ConditionalPushRule::suppress_edits(),
34 #[cfg(feature = "unstable-msc3930")]
35 ConditionalPushRule::poll_response(),
36 ]
37 .into(),
38 #[cfg(feature = "unstable-msc4306")]
39 postcontent: [
40 ConditionalPushRule::unsubscribed_thread(),
41 ConditionalPushRule::subscribed_thread(),
42 ]
43 .into(),
44 underride: [
45 ConditionalPushRule::call(),
46 ConditionalPushRule::encrypted_room_one_to_one(),
47 ConditionalPushRule::room_one_to_one(),
48 ConditionalPushRule::message(),
49 ConditionalPushRule::encrypted(),
50 #[cfg(feature = "unstable-msc3930")]
51 ConditionalPushRule::poll_start_one_to_one(),
52 #[cfg(feature = "unstable-msc3930")]
53 ConditionalPushRule::poll_start(),
54 #[cfg(feature = "unstable-msc3930")]
55 ConditionalPushRule::poll_end_one_to_one(),
56 #[cfg(feature = "unstable-msc3930")]
57 ConditionalPushRule::poll_end(),
58 ]
59 .into(),
60 ..Default::default()
61 }
62 }
63
64 pub fn update_with_server_default(&mut self, mut new_server_default: Ruleset) {
76 macro_rules! copy_rules_state {
79 ($new_ruleset:ident, $old_ruleset:ident, @fields $($field_name:ident),+) => {
80 $(
81 $new_ruleset.$field_name = $new_ruleset
82 .$field_name
83 .into_iter()
84 .map(|mut new_rule| {
85 if let Some(old_rule) =
86 $old_ruleset.$field_name.shift_take(new_rule.rule_id.as_str())
87 {
88 new_rule.enabled = old_rule.enabled;
89 new_rule.actions = old_rule.actions;
90 }
91
92 new_rule
93 })
94 .collect();
95 )+
96 };
97 }
98 copy_rules_state!(new_server_default, self, @fields override_, content, room, sender, underride);
99
100 macro_rules! remove_remaining_default_rules {
102 ($ruleset:ident, @fields $($field_name:ident),+) => {
103 $(
104 $ruleset.$field_name.retain(|rule| !rule.default);
105 )+
106 };
107 }
108 remove_remaining_default_rules!(self, @fields override_, content, room, sender, underride);
109
110 if let Some(master_rule) =
113 new_server_default.override_.shift_take(PredefinedOverrideRuleId::Master.as_str())
114 {
115 let (pos, _) = self.override_.insert_full(master_rule);
116 self.override_.move_index(pos, 0);
117 }
118
119 macro_rules! merge_rules {
121 ($old_ruleset:ident, $new_ruleset:ident, @fields $($field_name:ident),+) => {
122 $(
123 $old_ruleset.$field_name.extend($new_ruleset.$field_name);
124 )+
125 };
126 }
127 merge_rules!(self, new_server_default, @fields override_, content, room, sender, underride);
128 }
129}
130
131impl ConditionalPushRule {
133 pub fn master() -> Self {
136 Self {
137 actions: vec![],
138 default: true,
139 enabled: false,
140 rule_id: PredefinedOverrideRuleId::Master.to_string(),
141 conditions: vec![],
142 }
143 }
144
145 pub fn suppress_notices() -> Self {
147 Self {
148 actions: vec![],
149 default: true,
150 enabled: true,
151 rule_id: PredefinedOverrideRuleId::SuppressNotices.to_string(),
152 conditions: vec![EventMatch {
153 key: "content.msgtype".into(),
154 pattern: "m.notice".into(),
155 }],
156 }
157 }
158
159 pub fn invite_for_me(user_id: &UserId) -> Self {
161 Self {
162 actions: vec![
163 Notify,
164 SetTweak(Tweak::Sound("default".into())),
165 SetTweak(Tweak::Highlight(false)),
166 ],
167 default: true,
168 enabled: true,
169 rule_id: PredefinedOverrideRuleId::InviteForMe.to_string(),
170 conditions: vec![
171 EventMatch { key: "type".into(), pattern: "m.room.member".into() },
172 EventMatch { key: "content.membership".into(), pattern: "invite".into() },
173 EventMatch { key: "state_key".into(), pattern: user_id.to_string() },
174 ],
175 }
176 }
177
178 pub fn member_event() -> Self {
180 Self {
181 actions: vec![],
182 default: true,
183 enabled: true,
184 rule_id: PredefinedOverrideRuleId::MemberEvent.to_string(),
185 conditions: vec![EventMatch { key: "type".into(), pattern: "m.room.member".into() }],
186 }
187 }
188
189 pub fn is_user_mention(user_id: &UserId) -> Self {
192 Self {
193 actions: vec![
194 Notify,
195 SetTweak(Tweak::Sound("default".to_owned())),
196 SetTweak(Tweak::Highlight(true)),
197 ],
198 default: true,
199 enabled: true,
200 rule_id: PredefinedOverrideRuleId::IsUserMention.to_string(),
201 conditions: vec![EventPropertyContains {
202 key: r"content.m\.mentions.user_ids".to_owned(),
203 value: user_id.as_str().into(),
204 }],
205 }
206 }
207
208 pub fn tombstone() -> Self {
212 Self {
213 actions: vec![Notify, SetTweak(Tweak::Highlight(true))],
214 default: true,
215 enabled: true,
216 rule_id: PredefinedOverrideRuleId::Tombstone.to_string(),
217 conditions: vec![
218 EventMatch { key: "type".into(), pattern: "m.room.tombstone".into() },
219 EventMatch { key: "state_key".into(), pattern: "".into() },
220 ],
221 }
222 }
223
224 pub fn is_room_mention() -> Self {
227 Self {
228 actions: vec![Notify, SetTweak(Tweak::Highlight(true))],
229 default: true,
230 enabled: true,
231 rule_id: PredefinedOverrideRuleId::IsRoomMention.to_string(),
232 conditions: vec![
233 EventPropertyIs { key: r"content.m\.mentions.room".to_owned(), value: true.into() },
234 SenderNotificationPermission { key: NotificationPowerLevelsKey::Room },
235 ],
236 }
237 }
238
239 pub fn reaction() -> Self {
243 Self {
244 actions: vec![],
245 default: true,
246 enabled: true,
247 rule_id: PredefinedOverrideRuleId::Reaction.to_string(),
248 conditions: vec![EventMatch { key: "type".into(), pattern: "m.reaction".into() }],
249 }
250 }
251
252 pub fn server_acl() -> Self {
256 Self {
257 actions: vec![],
258 default: true,
259 enabled: true,
260 rule_id: PredefinedOverrideRuleId::RoomServerAcl.to_string(),
261 conditions: vec![
262 EventMatch { key: "type".into(), pattern: "m.room.server_acl".into() },
263 EventMatch { key: "state_key".into(), pattern: "".into() },
264 ],
265 }
266 }
267
268 pub fn suppress_edits() -> Self {
272 Self {
273 actions: vec![],
274 default: true,
275 enabled: true,
276 rule_id: PredefinedOverrideRuleId::SuppressEdits.to_string(),
277 conditions: vec![EventPropertyIs {
278 key: r"content.m\.relates_to.rel_type".to_owned(),
279 value: "m.replace".into(),
280 }],
281 }
282 }
283
284 #[cfg(feature = "unstable-msc3930")]
291 pub fn poll_response() -> Self {
292 Self {
293 rule_id: PredefinedOverrideRuleId::PollResponse.to_string(),
294 default: true,
295 enabled: true,
296 conditions: vec![EventPropertyIs {
297 key: "type".to_owned(),
298 value: "org.matrix.msc3381.poll.response".into(),
299 }],
300 actions: vec![],
301 }
302 }
303}
304
305impl ConditionalPushRule {
307 pub fn call() -> Self {
309 Self {
310 rule_id: PredefinedUnderrideRuleId::Call.to_string(),
311 default: true,
312 enabled: true,
313 conditions: vec![EventMatch { key: "type".into(), pattern: "m.call.invite".into() }],
314 actions: vec![
315 Notify,
316 SetTweak(Tweak::Sound("ring".into())),
317 SetTweak(Tweak::Highlight(false)),
318 ],
319 }
320 }
321
322 pub fn encrypted_room_one_to_one() -> Self {
328 Self {
329 rule_id: PredefinedUnderrideRuleId::EncryptedRoomOneToOne.to_string(),
330 default: true,
331 enabled: true,
332 conditions: vec![
333 RoomMemberCount { is: RoomMemberCountIs::from(js_int::uint!(2)) },
334 EventMatch { key: "type".into(), pattern: "m.room.encrypted".into() },
335 ],
336 actions: vec![
337 Notify,
338 SetTweak(Tweak::Sound("default".into())),
339 SetTweak(Tweak::Highlight(false)),
340 ],
341 }
342 }
343
344 pub fn room_one_to_one() -> Self {
346 Self {
347 rule_id: PredefinedUnderrideRuleId::RoomOneToOne.to_string(),
348 default: true,
349 enabled: true,
350 conditions: vec![
351 RoomMemberCount { is: RoomMemberCountIs::from(js_int::uint!(2)) },
352 EventMatch { key: "type".into(), pattern: "m.room.message".into() },
353 ],
354 actions: vec![
355 Notify,
356 SetTweak(Tweak::Sound("default".into())),
357 SetTweak(Tweak::Highlight(false)),
358 ],
359 }
360 }
361
362 pub fn message() -> Self {
364 Self {
365 rule_id: PredefinedUnderrideRuleId::Message.to_string(),
366 default: true,
367 enabled: true,
368 conditions: vec![EventMatch { key: "type".into(), pattern: "m.room.message".into() }],
369 actions: vec![Notify, SetTweak(Tweak::Highlight(false))],
370 }
371 }
372
373 pub fn encrypted() -> Self {
379 Self {
380 rule_id: PredefinedUnderrideRuleId::Encrypted.to_string(),
381 default: true,
382 enabled: true,
383 conditions: vec![EventMatch { key: "type".into(), pattern: "m.room.encrypted".into() }],
384 actions: vec![Notify, SetTweak(Tweak::Highlight(false))],
385 }
386 }
387
388 #[cfg(feature = "unstable-msc3930")]
395 pub fn poll_start_one_to_one() -> Self {
396 Self {
397 rule_id: PredefinedUnderrideRuleId::PollStartOneToOne.to_string(),
398 default: true,
399 enabled: true,
400 conditions: vec![
401 RoomMemberCount { is: RoomMemberCountIs::from(js_int::uint!(2)) },
402 EventPropertyIs {
403 key: "type".to_owned(),
404 value: "org.matrix.msc3381.poll.start".into(),
405 },
406 ],
407 actions: vec![Notify, SetTweak(Tweak::Sound("default".into()))],
408 }
409 }
410
411 #[cfg(feature = "unstable-msc3930")]
418 pub fn poll_start() -> Self {
419 Self {
420 rule_id: PredefinedUnderrideRuleId::PollStart.to_string(),
421 default: true,
422 enabled: true,
423 conditions: vec![EventPropertyIs {
424 key: "type".to_owned(),
425 value: "org.matrix.msc3381.poll.start".into(),
426 }],
427 actions: vec![Notify],
428 }
429 }
430
431 #[cfg(feature = "unstable-msc3930")]
438 pub fn poll_end_one_to_one() -> Self {
439 Self {
440 rule_id: PredefinedUnderrideRuleId::PollEndOneToOne.to_string(),
441 default: true,
442 enabled: true,
443 conditions: vec![
444 RoomMemberCount { is: RoomMemberCountIs::from(js_int::uint!(2)) },
445 EventPropertyIs {
446 key: "type".to_owned(),
447 value: "org.matrix.msc3381.poll.end".into(),
448 },
449 ],
450 actions: vec![Notify, SetTweak(Tweak::Sound("default".into()))],
451 }
452 }
453
454 #[cfg(feature = "unstable-msc3930")]
461 pub fn poll_end() -> Self {
462 Self {
463 rule_id: PredefinedUnderrideRuleId::PollEnd.to_string(),
464 default: true,
465 enabled: true,
466 conditions: vec![EventPropertyIs {
467 key: "type".to_owned(),
468 value: "org.matrix.msc3381.poll.end".into(),
469 }],
470 actions: vec![Notify],
471 }
472 }
473
474 #[cfg(feature = "unstable-msc4306")]
480 pub fn unsubscribed_thread() -> Self {
481 Self {
482 rule_id: PredefinedUnderrideRuleId::UnsubscribedThread.to_string(),
483 default: true,
484 enabled: true,
485 conditions: vec![ThreadSubscription { subscribed: false }],
486 actions: vec![],
487 }
488 }
489
490 #[cfg(feature = "unstable-msc4306")]
496 pub fn subscribed_thread() -> Self {
497 Self {
498 rule_id: PredefinedUnderrideRuleId::SubscribedThread.to_string(),
499 default: true,
500 enabled: true,
501 conditions: vec![ThreadSubscription { subscribed: true }],
502 actions: vec![Notify, SetTweak(Tweak::Sound("default".into()))],
503 }
504 }
505}
506
507#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
509#[non_exhaustive]
510pub enum PredefinedRuleId {
511 Override(PredefinedOverrideRuleId),
513
514 Underride(PredefinedUnderrideRuleId),
516
517 Content(PredefinedContentRuleId),
519}
520
521impl PredefinedRuleId {
522 pub fn as_str(&self) -> &str {
524 match self {
525 Self::Override(id) => id.as_str(),
526 Self::Underride(id) => id.as_str(),
527 Self::Content(id) => id.as_str(),
528 }
529 }
530
531 pub fn kind(&self) -> RuleKind {
533 match self {
534 Self::Override(id) => id.kind(),
535 Self::Underride(id) => id.kind(),
536 Self::Content(id) => id.kind(),
537 }
538 }
539}
540
541impl AsRef<str> for PredefinedRuleId {
542 fn as_ref(&self) -> &str {
543 self.as_str()
544 }
545}
546
547#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/string_enum.md"))]
549#[derive(Clone, StringEnum)]
550#[ruma_enum(rename_all = ".m.rule.snake_case")]
551#[non_exhaustive]
552pub enum PredefinedOverrideRuleId {
553 Master,
555
556 SuppressNotices,
558
559 InviteForMe,
561
562 MemberEvent,
564
565 IsUserMention,
567
568 #[deprecated = "Since Matrix 1.7. Use the m.mentions property with PredefinedOverrideRuleId::IsUserMention instead."]
570 ContainsDisplayName,
571
572 IsRoomMention,
574
575 #[ruma_enum(rename = ".m.rule.roomnotif")]
577 #[deprecated = "Since Matrix 1.7. Use the m.mentions property with PredefinedOverrideRuleId::IsRoomMention instead."]
578 RoomNotif,
579
580 Tombstone,
582
583 Reaction,
585
586 #[ruma_enum(rename = ".m.rule.room.server_acl")]
588 RoomServerAcl,
589
590 SuppressEdits,
592
593 #[cfg(feature = "unstable-msc3930")]
599 #[ruma_enum(rename = ".org.matrix.msc3930.rule.poll_response")]
600 PollResponse,
601
602 #[doc(hidden)]
603 _Custom(PrivOwnedStr),
604}
605
606impl PredefinedOverrideRuleId {
607 pub fn kind(&self) -> RuleKind {
609 RuleKind::Override
610 }
611}
612
613#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/string_enum.md"))]
615#[derive(Clone, StringEnum)]
616#[ruma_enum(rename_all = ".m.rule.snake_case")]
617#[non_exhaustive]
618pub enum PredefinedUnderrideRuleId {
619 Call,
621
622 EncryptedRoomOneToOne,
624
625 RoomOneToOne,
627
628 Message,
630
631 Encrypted,
633
634 #[cfg(feature = "unstable-msc3930")]
640 #[ruma_enum(rename = ".org.matrix.msc3930.rule.poll_start_one_to_one")]
641 PollStartOneToOne,
642
643 #[cfg(feature = "unstable-msc3930")]
649 #[ruma_enum(rename = ".org.matrix.msc3930.rule.poll_start")]
650 PollStart,
651
652 #[cfg(feature = "unstable-msc3930")]
658 #[ruma_enum(rename = ".org.matrix.msc3930.rule.poll_end_one_to_one")]
659 PollEndOneToOne,
660
661 #[cfg(feature = "unstable-msc3930")]
667 #[ruma_enum(rename = ".org.matrix.msc3930.rule.poll_end")]
668 PollEnd,
669
670 #[cfg(feature = "unstable-msc4306")]
676 #[ruma_enum(rename = ".io.element.msc4306.rule.unsubscribed_thread")]
677 UnsubscribedThread,
678
679 #[cfg(feature = "unstable-msc4306")]
685 #[ruma_enum(rename = ".io.element.msc4306.rule.subscribed_thread")]
686 SubscribedThread,
687
688 #[doc(hidden)]
689 _Custom(PrivOwnedStr),
690}
691
692impl PredefinedUnderrideRuleId {
693 pub fn kind(&self) -> RuleKind {
695 RuleKind::Underride
696 }
697}
698
699#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/string_enum.md"))]
701#[derive(Clone, StringEnum)]
702#[ruma_enum(rename_all = ".m.rule.snake_case")]
703#[non_exhaustive]
704pub enum PredefinedContentRuleId {
705 #[deprecated = "Since Matrix 1.7. Use the m.mentions property with PredefinedOverrideRuleId::IsUserMention instead."]
707 ContainsUserName,
708
709 #[doc(hidden)]
710 _Custom(PrivOwnedStr),
711}
712
713impl PredefinedContentRuleId {
714 pub fn kind(&self) -> RuleKind {
716 RuleKind::Content
717 }
718}
719
720#[cfg(test)]
721mod tests {
722 use assert_matches2::assert_matches;
723 use assign::assign;
724
725 use super::PredefinedOverrideRuleId;
726 use crate::{
727 push::{Action, ConditionalPushRule, ConditionalPushRuleInit, Ruleset},
728 user_id,
729 };
730
731 #[test]
732 fn update_with_server_default() {
733 let user_rule_id = "user_always_true";
734 let default_rule_id = ".default_always_true";
735
736 let override_ = [
737 assign!(ConditionalPushRule::master(), { enabled: true, actions: vec![Action::Notify]}),
739 ConditionalPushRuleInit {
741 actions: vec![],
742 default: false,
743 enabled: false,
744 rule_id: user_rule_id.to_owned(),
745 conditions: vec![],
746 }
747 .into(),
748 ConditionalPushRuleInit {
750 actions: vec![],
751 default: true,
752 enabled: true,
753 rule_id: default_rule_id.to_owned(),
754 conditions: vec![],
755 }
756 .into(),
757 ]
758 .into_iter()
759 .collect();
760 let mut ruleset = Ruleset { override_, ..Default::default() };
761
762 let new_server_default = Ruleset::server_default(user_id!("@user:localhost"));
763
764 ruleset.update_with_server_default(new_server_default);
765
766 let master_rule = &ruleset.override_[0];
768 assert_eq!(master_rule.rule_id, PredefinedOverrideRuleId::Master.as_str());
769
770 assert!(master_rule.enabled);
772 assert_eq!(master_rule.actions.len(), 1);
773 assert_matches!(&master_rule.actions[0], Action::Notify);
774
775 let user_rule = ruleset.override_.get(user_rule_id).unwrap();
777 assert!(!user_rule.enabled);
778 assert_eq!(user_rule.actions.len(), 0);
779
780 assert_matches!(ruleset.override_.get(default_rule_id), None);
782
783 let member_event_rule =
785 ruleset.override_.get(PredefinedOverrideRuleId::MemberEvent.as_str()).unwrap();
786 assert!(member_event_rule.enabled);
787 assert_eq!(member_event_rule.actions.len(), 0);
788 }
789}