1use ruma_macros::StringEnum;
6
7use super::{
8 Action::*, ConditionalPushRule, PatternedPushRule, PushCondition::*, RoomMemberCountIs,
9 RuleKind, Ruleset, Tweak,
10};
11use crate::{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 ConditionalPushRule::call(),
50 ConditionalPushRule::encrypted_room_one_to_one(),
51 ConditionalPushRule::room_one_to_one(),
52 ConditionalPushRule::message(),
53 ConditionalPushRule::encrypted(),
54 #[cfg(feature = "unstable-msc3930")]
55 ConditionalPushRule::poll_start_one_to_one(),
56 #[cfg(feature = "unstable-msc3930")]
57 ConditionalPushRule::poll_start(),
58 #[cfg(feature = "unstable-msc3930")]
59 ConditionalPushRule::poll_end_one_to_one(),
60 #[cfg(feature = "unstable-msc3930")]
61 ConditionalPushRule::poll_end(),
62 ]
63 .into(),
64 ..Default::default()
65 }
66 }
67
68 pub fn update_with_server_default(&mut self, mut new_server_default: Ruleset) {
80 macro_rules! copy_rules_state {
83 ($new_ruleset:ident, $old_ruleset:ident, @fields $($field_name:ident),+) => {
84 $(
85 $new_ruleset.$field_name = $new_ruleset
86 .$field_name
87 .into_iter()
88 .map(|mut new_rule| {
89 if let Some(old_rule) =
90 $old_ruleset.$field_name.shift_take(new_rule.rule_id.as_str())
91 {
92 new_rule.enabled = old_rule.enabled;
93 new_rule.actions = old_rule.actions;
94 }
95
96 new_rule
97 })
98 .collect();
99 )+
100 };
101 }
102 copy_rules_state!(new_server_default, self, @fields override_, content, room, sender, underride);
103
104 macro_rules! remove_remaining_default_rules {
106 ($ruleset:ident, @fields $($field_name:ident),+) => {
107 $(
108 $ruleset.$field_name.retain(|rule| !rule.default);
109 )+
110 };
111 }
112 remove_remaining_default_rules!(self, @fields override_, content, room, sender, underride);
113
114 if let Some(master_rule) =
117 new_server_default.override_.shift_take(PredefinedOverrideRuleId::Master.as_str())
118 {
119 let (pos, _) = self.override_.insert_full(master_rule);
120 self.override_.move_index(pos, 0);
121 }
122
123 macro_rules! merge_rules {
125 ($old_ruleset:ident, $new_ruleset:ident, @fields $($field_name:ident),+) => {
126 $(
127 $old_ruleset.$field_name.extend($new_ruleset.$field_name);
128 )+
129 };
130 }
131 merge_rules!(self, new_server_default, @fields override_, content, room, sender, underride);
132 }
133}
134
135impl ConditionalPushRule {
137 pub fn master() -> Self {
140 Self {
141 actions: vec![],
142 default: true,
143 enabled: false,
144 rule_id: PredefinedOverrideRuleId::Master.to_string(),
145 conditions: vec![],
146 }
147 }
148
149 pub fn suppress_notices() -> Self {
151 Self {
152 actions: vec![],
153 default: true,
154 enabled: true,
155 rule_id: PredefinedOverrideRuleId::SuppressNotices.to_string(),
156 conditions: vec![EventMatch {
157 key: "content.msgtype".into(),
158 pattern: "m.notice".into(),
159 }],
160 }
161 }
162
163 pub fn invite_for_me(user_id: &UserId) -> Self {
165 Self {
166 actions: vec![
167 Notify,
168 SetTweak(Tweak::Sound("default".into())),
169 SetTweak(Tweak::Highlight(false)),
170 ],
171 default: true,
172 enabled: true,
173 rule_id: PredefinedOverrideRuleId::InviteForMe.to_string(),
174 conditions: vec![
175 EventMatch { key: "type".into(), pattern: "m.room.member".into() },
176 EventMatch { key: "content.membership".into(), pattern: "invite".into() },
177 EventMatch { key: "state_key".into(), pattern: user_id.to_string() },
178 ],
179 }
180 }
181
182 pub fn member_event() -> Self {
184 Self {
185 actions: vec![],
186 default: true,
187 enabled: true,
188 rule_id: PredefinedOverrideRuleId::MemberEvent.to_string(),
189 conditions: vec![EventMatch { key: "type".into(), pattern: "m.room.member".into() }],
190 }
191 }
192
193 pub fn is_user_mention(user_id: &UserId) -> Self {
196 Self {
197 actions: vec![
198 Notify,
199 SetTweak(Tweak::Sound("default".to_owned())),
200 SetTweak(Tweak::Highlight(true)),
201 ],
202 default: true,
203 enabled: true,
204 rule_id: PredefinedOverrideRuleId::IsUserMention.to_string(),
205 conditions: vec![EventPropertyContains {
206 key: r"content.m\.mentions.user_ids".to_owned(),
207 value: user_id.as_str().into(),
208 }],
209 }
210 }
211
212 #[deprecated = "Since Matrix 1.7. Use the m.mentions property with ConditionalPushRule::is_user_mention() instead."]
218 pub fn contains_display_name() -> Self {
219 #[allow(deprecated)]
220 Self {
221 actions: vec![
222 Notify,
223 SetTweak(Tweak::Sound("default".into())),
224 SetTweak(Tweak::Highlight(true)),
225 ],
226 default: true,
227 enabled: true,
228 rule_id: PredefinedOverrideRuleId::ContainsDisplayName.to_string(),
229 conditions: vec![ContainsDisplayName],
230 }
231 }
232
233 pub fn tombstone() -> Self {
237 Self {
238 actions: vec![Notify, SetTweak(Tweak::Highlight(true))],
239 default: true,
240 enabled: true,
241 rule_id: PredefinedOverrideRuleId::Tombstone.to_string(),
242 conditions: vec![
243 EventMatch { key: "type".into(), pattern: "m.room.tombstone".into() },
244 EventMatch { key: "state_key".into(), pattern: "".into() },
245 ],
246 }
247 }
248
249 pub fn is_room_mention() -> Self {
252 Self {
253 actions: vec![Notify, SetTweak(Tweak::Highlight(true))],
254 default: true,
255 enabled: true,
256 rule_id: PredefinedOverrideRuleId::IsRoomMention.to_string(),
257 conditions: vec![
258 EventPropertyIs { key: r"content.m\.mentions.room".to_owned(), value: true.into() },
259 SenderNotificationPermission { key: "room".to_owned() },
260 ],
261 }
262 }
263
264 #[deprecated = "Since Matrix 1.7. Use the m.mentions property with ConditionalPushRule::is_room_mention() instead."]
270 pub fn roomnotif() -> Self {
271 #[allow(deprecated)]
272 Self {
273 actions: vec![Notify, SetTweak(Tweak::Highlight(true))],
274 default: true,
275 enabled: true,
276 rule_id: PredefinedOverrideRuleId::RoomNotif.to_string(),
277 conditions: vec![
278 EventMatch { key: "content.body".into(), pattern: "@room".into() },
279 SenderNotificationPermission { key: "room".into() },
280 ],
281 }
282 }
283
284 pub fn reaction() -> Self {
288 Self {
289 actions: vec![],
290 default: true,
291 enabled: true,
292 rule_id: PredefinedOverrideRuleId::Reaction.to_string(),
293 conditions: vec![EventMatch { key: "type".into(), pattern: "m.reaction".into() }],
294 }
295 }
296
297 pub fn server_acl() -> Self {
301 Self {
302 actions: vec![],
303 default: true,
304 enabled: true,
305 rule_id: PredefinedOverrideRuleId::RoomServerAcl.to_string(),
306 conditions: vec![
307 EventMatch { key: "type".into(), pattern: "m.room.server_acl".into() },
308 EventMatch { key: "state_key".into(), pattern: "".into() },
309 ],
310 }
311 }
312
313 pub fn suppress_edits() -> Self {
317 Self {
318 actions: vec![],
319 default: true,
320 enabled: true,
321 rule_id: PredefinedOverrideRuleId::SuppressEdits.to_string(),
322 conditions: vec![EventPropertyIs {
323 key: r"content.m\.relates_to.rel_type".to_owned(),
324 value: "m.replace".into(),
325 }],
326 }
327 }
328
329 #[cfg(feature = "unstable-msc3930")]
336 pub fn poll_response() -> Self {
337 Self {
338 rule_id: PredefinedOverrideRuleId::PollResponse.to_string(),
339 default: true,
340 enabled: true,
341 conditions: vec![EventPropertyIs {
342 key: "type".to_owned(),
343 value: "org.matrix.msc3381.poll.response".into(),
344 }],
345 actions: vec![],
346 }
347 }
348}
349
350impl PatternedPushRule {
352 #[deprecated = "Since Matrix 1.7. Use the m.mentions property with ConditionalPushRule::is_user_mention() instead."]
358 pub fn contains_user_name(user_id: &UserId) -> Self {
359 #[allow(deprecated)]
360 Self {
361 rule_id: PredefinedContentRuleId::ContainsUserName.to_string(),
362 enabled: true,
363 default: true,
364 pattern: user_id.localpart().into(),
365 actions: vec![
366 Notify,
367 SetTweak(Tweak::Sound("default".into())),
368 SetTweak(Tweak::Highlight(true)),
369 ],
370 }
371 }
372}
373
374impl ConditionalPushRule {
376 pub fn call() -> Self {
378 Self {
379 rule_id: PredefinedUnderrideRuleId::Call.to_string(),
380 default: true,
381 enabled: true,
382 conditions: vec![EventMatch { key: "type".into(), pattern: "m.call.invite".into() }],
383 actions: vec![
384 Notify,
385 SetTweak(Tweak::Sound("ring".into())),
386 SetTweak(Tweak::Highlight(false)),
387 ],
388 }
389 }
390
391 pub fn encrypted_room_one_to_one() -> Self {
397 Self {
398 rule_id: PredefinedUnderrideRuleId::EncryptedRoomOneToOne.to_string(),
399 default: true,
400 enabled: true,
401 conditions: vec![
402 RoomMemberCount { is: RoomMemberCountIs::from(js_int::uint!(2)) },
403 EventMatch { key: "type".into(), pattern: "m.room.encrypted".into() },
404 ],
405 actions: vec![
406 Notify,
407 SetTweak(Tweak::Sound("default".into())),
408 SetTweak(Tweak::Highlight(false)),
409 ],
410 }
411 }
412
413 pub fn room_one_to_one() -> Self {
415 Self {
416 rule_id: PredefinedUnderrideRuleId::RoomOneToOne.to_string(),
417 default: true,
418 enabled: true,
419 conditions: vec![
420 RoomMemberCount { is: RoomMemberCountIs::from(js_int::uint!(2)) },
421 EventMatch { key: "type".into(), pattern: "m.room.message".into() },
422 ],
423 actions: vec![
424 Notify,
425 SetTweak(Tweak::Sound("default".into())),
426 SetTweak(Tweak::Highlight(false)),
427 ],
428 }
429 }
430
431 pub fn message() -> Self {
433 Self {
434 rule_id: PredefinedUnderrideRuleId::Message.to_string(),
435 default: true,
436 enabled: true,
437 conditions: vec![EventMatch { key: "type".into(), pattern: "m.room.message".into() }],
438 actions: vec![Notify, SetTweak(Tweak::Highlight(false))],
439 }
440 }
441
442 pub fn encrypted() -> Self {
448 Self {
449 rule_id: PredefinedUnderrideRuleId::Encrypted.to_string(),
450 default: true,
451 enabled: true,
452 conditions: vec![EventMatch { key: "type".into(), pattern: "m.room.encrypted".into() }],
453 actions: vec![Notify, SetTweak(Tweak::Highlight(false))],
454 }
455 }
456
457 #[cfg(feature = "unstable-msc3930")]
464 pub fn poll_start_one_to_one() -> Self {
465 Self {
466 rule_id: PredefinedUnderrideRuleId::PollStartOneToOne.to_string(),
467 default: true,
468 enabled: true,
469 conditions: vec![
470 RoomMemberCount { is: RoomMemberCountIs::from(js_int::uint!(2)) },
471 EventPropertyIs {
472 key: "type".to_owned(),
473 value: "org.matrix.msc3381.poll.start".into(),
474 },
475 ],
476 actions: vec![Notify, SetTweak(Tweak::Sound("default".into()))],
477 }
478 }
479
480 #[cfg(feature = "unstable-msc3930")]
487 pub fn poll_start() -> Self {
488 Self {
489 rule_id: PredefinedUnderrideRuleId::PollStart.to_string(),
490 default: true,
491 enabled: true,
492 conditions: vec![EventPropertyIs {
493 key: "type".to_owned(),
494 value: "org.matrix.msc3381.poll.start".into(),
495 }],
496 actions: vec![Notify],
497 }
498 }
499
500 #[cfg(feature = "unstable-msc3930")]
507 pub fn poll_end_one_to_one() -> Self {
508 Self {
509 rule_id: PredefinedUnderrideRuleId::PollEndOneToOne.to_string(),
510 default: true,
511 enabled: true,
512 conditions: vec![
513 RoomMemberCount { is: RoomMemberCountIs::from(js_int::uint!(2)) },
514 EventPropertyIs {
515 key: "type".to_owned(),
516 value: "org.matrix.msc3381.poll.end".into(),
517 },
518 ],
519 actions: vec![Notify, SetTweak(Tweak::Sound("default".into()))],
520 }
521 }
522
523 #[cfg(feature = "unstable-msc3930")]
530 pub fn poll_end() -> Self {
531 Self {
532 rule_id: PredefinedUnderrideRuleId::PollEnd.to_string(),
533 default: true,
534 enabled: true,
535 conditions: vec![EventPropertyIs {
536 key: "type".to_owned(),
537 value: "org.matrix.msc3381.poll.end".into(),
538 }],
539 actions: vec![Notify],
540 }
541 }
542}
543
544#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
546#[non_exhaustive]
547pub enum PredefinedRuleId {
548 Override(PredefinedOverrideRuleId),
550
551 Underride(PredefinedUnderrideRuleId),
553
554 Content(PredefinedContentRuleId),
556}
557
558impl PredefinedRuleId {
559 pub fn as_str(&self) -> &str {
561 match self {
562 Self::Override(id) => id.as_str(),
563 Self::Underride(id) => id.as_str(),
564 Self::Content(id) => id.as_str(),
565 }
566 }
567
568 pub fn kind(&self) -> RuleKind {
570 match self {
571 Self::Override(id) => id.kind(),
572 Self::Underride(id) => id.kind(),
573 Self::Content(id) => id.kind(),
574 }
575 }
576}
577
578impl AsRef<str> for PredefinedRuleId {
579 fn as_ref(&self) -> &str {
580 self.as_str()
581 }
582}
583
584#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/string_enum.md"))]
586#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, StringEnum)]
587#[ruma_enum(rename_all = ".m.rule.snake_case")]
588#[non_exhaustive]
589pub enum PredefinedOverrideRuleId {
590 Master,
592
593 SuppressNotices,
595
596 InviteForMe,
598
599 MemberEvent,
601
602 IsUserMention,
604
605 #[deprecated = "Since Matrix 1.7. Use the m.mentions property with PredefinedOverrideRuleId::IsUserMention instead."]
607 ContainsDisplayName,
608
609 IsRoomMention,
611
612 #[ruma_enum(rename = ".m.rule.roomnotif")]
614 #[deprecated = "Since Matrix 1.7. Use the m.mentions property with PredefinedOverrideRuleId::IsRoomMention instead."]
615 RoomNotif,
616
617 Tombstone,
619
620 Reaction,
622
623 #[ruma_enum(rename = ".m.rule.room.server_acl")]
625 RoomServerAcl,
626
627 SuppressEdits,
629
630 #[cfg(feature = "unstable-msc3930")]
636 #[ruma_enum(rename = ".org.matrix.msc3930.rule.poll_response")]
637 PollResponse,
638
639 #[doc(hidden)]
640 _Custom(PrivOwnedStr),
641}
642
643impl PredefinedOverrideRuleId {
644 pub fn kind(&self) -> RuleKind {
646 RuleKind::Override
647 }
648}
649
650#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/string_enum.md"))]
652#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, StringEnum)]
653#[ruma_enum(rename_all = ".m.rule.snake_case")]
654#[non_exhaustive]
655pub enum PredefinedUnderrideRuleId {
656 Call,
658
659 EncryptedRoomOneToOne,
661
662 RoomOneToOne,
664
665 Message,
667
668 Encrypted,
670
671 #[cfg(feature = "unstable-msc3930")]
677 #[ruma_enum(rename = ".org.matrix.msc3930.rule.poll_start_one_to_one")]
678 PollStartOneToOne,
679
680 #[cfg(feature = "unstable-msc3930")]
686 #[ruma_enum(rename = ".org.matrix.msc3930.rule.poll_start")]
687 PollStart,
688
689 #[cfg(feature = "unstable-msc3930")]
695 #[ruma_enum(rename = ".org.matrix.msc3930.rule.poll_end_one_to_one")]
696 PollEndOneToOne,
697
698 #[cfg(feature = "unstable-msc3930")]
704 #[ruma_enum(rename = ".org.matrix.msc3930.rule.poll_end")]
705 PollEnd,
706
707 #[doc(hidden)]
708 _Custom(PrivOwnedStr),
709}
710
711impl PredefinedUnderrideRuleId {
712 pub fn kind(&self) -> RuleKind {
714 RuleKind::Underride
715 }
716}
717
718#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/string_enum.md"))]
720#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, StringEnum)]
721#[ruma_enum(rename_all = ".m.rule.snake_case")]
722#[non_exhaustive]
723pub enum PredefinedContentRuleId {
724 #[deprecated = "Since Matrix 1.7. Use the m.mentions property with PredefinedOverrideRuleId::IsUserMention instead."]
726 ContainsUserName,
727
728 #[doc(hidden)]
729 _Custom(PrivOwnedStr),
730}
731
732impl PredefinedContentRuleId {
733 pub fn kind(&self) -> RuleKind {
735 RuleKind::Content
736 }
737}
738
739#[cfg(test)]
740mod tests {
741 use assert_matches2::assert_matches;
742 use assign::assign;
743
744 use super::PredefinedOverrideRuleId;
745 use crate::{
746 push::{Action, ConditionalPushRule, ConditionalPushRuleInit, Ruleset},
747 user_id,
748 };
749
750 #[test]
751 fn update_with_server_default() {
752 let user_rule_id = "user_always_true";
753 let default_rule_id = ".default_always_true";
754
755 let override_ = [
756 assign!(ConditionalPushRule::master(), { enabled: true, actions: vec![Action::Notify]}),
758 ConditionalPushRuleInit {
760 actions: vec![],
761 default: false,
762 enabled: false,
763 rule_id: user_rule_id.to_owned(),
764 conditions: vec![],
765 }
766 .into(),
767 ConditionalPushRuleInit {
769 actions: vec![],
770 default: true,
771 enabled: true,
772 rule_id: default_rule_id.to_owned(),
773 conditions: vec![],
774 }
775 .into(),
776 ]
777 .into_iter()
778 .collect();
779 let mut ruleset = Ruleset { override_, ..Default::default() };
780
781 let new_server_default = Ruleset::server_default(user_id!("@user:localhost"));
782
783 ruleset.update_with_server_default(new_server_default);
784
785 let master_rule = &ruleset.override_[0];
787 assert_eq!(master_rule.rule_id, PredefinedOverrideRuleId::Master.as_str());
788
789 assert!(master_rule.enabled);
791 assert_eq!(master_rule.actions.len(), 1);
792 assert_matches!(&master_rule.actions[0], Action::Notify);
793
794 let user_rule = ruleset.override_.get(user_rule_id).unwrap();
796 assert!(!user_rule.enabled);
797 assert_eq!(user_rule.actions.len(), 0);
798
799 assert_matches!(ruleset.override_.get(default_rule_id), None);
801
802 let member_event_rule =
804 ruleset.override_.get(PredefinedOverrideRuleId::MemberEvent.as_str()).unwrap();
805 assert!(member_event_rule.enabled);
806 assert_eq!(member_event_rule.actions.len(), 0);
807 }
808}