@@ -23,6 +23,7 @@ use std::borrow::Cow;
2323use std:: collections:: BTreeMap ;
2424
2525use anyhow:: { Context , Error } ;
26+ use chrono:: Datelike ;
2627use lazy_static:: lazy_static;
2728use log:: warn;
2829use pyo3:: prelude:: * ;
@@ -31,7 +32,7 @@ use regex::Regex;
3132use super :: {
3233 utils:: { get_glob_matcher, get_localpart_from_id, GlobMatchType } ,
3334 Action , Condition , EventPropertyIsCondition , FilteredPushRules , KnownCondition ,
34- SimpleJsonValue ,
35+ SimpleJsonValue , TimeAndDayIntervals ,
3536} ;
3637use crate :: push:: { EventMatchPatternType , JsonValue } ;
3738
@@ -105,6 +106,9 @@ pub struct PushRuleEvaluator {
105106 /// If MSC3931 (room version feature flags) is enabled. Usually controlled by the same
106107 /// flag as MSC1767 (extensible events core).
107108 msc3931_enabled : bool ,
109+
110+ /// If MSC3767 (time based notification filtering push rule condition) is enabled
111+ msc3767_time_and_day : bool ,
108112}
109113
110114#[ pymethods]
@@ -122,6 +126,7 @@ impl PushRuleEvaluator {
122126 related_event_match_enabled,
123127 room_version_feature_flags,
124128 msc3931_enabled,
129+ msc3767_time_and_day,
125130 ) ) ]
126131 pub fn py_new (
127132 flattened_keys : BTreeMap < String , JsonValue > ,
@@ -133,6 +138,7 @@ impl PushRuleEvaluator {
133138 related_event_match_enabled : bool ,
134139 room_version_feature_flags : Vec < String > ,
135140 msc3931_enabled : bool ,
141+ msc3767_time_and_day : bool ,
136142 ) -> Result < Self , Error > {
137143 let body = match flattened_keys. get ( "content.body" ) {
138144 Some ( JsonValue :: Value ( SimpleJsonValue :: Str ( s) ) ) => s. clone ( ) . into_owned ( ) ,
@@ -150,6 +156,7 @@ impl PushRuleEvaluator {
150156 related_event_match_enabled,
151157 room_version_feature_flags,
152158 msc3931_enabled,
159+ msc3767_time_and_day,
153160 } )
154161 }
155162
@@ -384,6 +391,13 @@ impl PushRuleEvaluator {
384391 && self . room_version_feature_flags . contains ( & flag)
385392 }
386393 }
394+ KnownCondition :: TimeAndDay ( time_and_day) => {
395+ if !self . msc3767_time_and_day {
396+ false
397+ } else {
398+ self . match_time_and_day ( time_and_day. timezone . clone ( ) , & time_and_day. intervals ) ?
399+ }
400+ }
387401 } ;
388402
389403 Ok ( result)
@@ -507,6 +521,31 @@ impl PushRuleEvaluator {
507521
508522 Ok ( matches)
509523 }
524+
525+ ///
526+ fn match_time_and_day (
527+ & self ,
528+ _timezone : Option < Cow < str > > ,
529+ intervals : & [ TimeAndDayIntervals ] ,
530+ ) -> Result < bool , Error > {
531+ // Temp Notes from spec:
532+ // The timezone to use for time comparison. This format allows for automatic DST handling.
533+ // Intervals representing time periods in which the rule should match. Evaluated with an OR condition.
534+ //
535+ // time_of_day condition is met when the server's timezone-adjusted time is between the values of the tuple,
536+ // or when no time_of_day is set on the interval. Values are inclusive.
537+ // day_of_week condition is met when the server's timezone-adjusted day is included in the array.
538+ // next step -> consider timezone if given
539+ let now = chrono:: Utc :: now ( ) ;
540+ let today = now. weekday ( ) . num_days_from_sunday ( ) ;
541+ let current_time = now. time ( ) . format ( "%H:%M" ) . to_string ( ) ;
542+ let matches = intervals. iter ( ) . any ( |interval| {
543+ interval. day_of_week . contains ( & today)
544+ && interval. time_of_day . contains ( current_time. to_string ( ) )
545+ } ) ;
546+
547+ Ok ( matches)
548+ }
510549}
511550
512551#[ test]
@@ -526,6 +565,7 @@ fn push_rule_evaluator() {
526565 true ,
527566 vec ! [ ] ,
528567 true ,
568+ true ,
529569 )
530570 . unwrap ( ) ;
531571
@@ -555,6 +595,7 @@ fn test_requires_room_version_supports_condition() {
555595 false ,
556596 flags,
557597 true ,
598+ true ,
558599 )
559600 . unwrap ( ) ;
560601
@@ -588,3 +629,72 @@ fn test_requires_room_version_supports_condition() {
588629 ) ;
589630 assert_eq ! ( result. len( ) , 1 ) ;
590631}
632+
633+ #[ test]
634+ fn test_time_and_day_condition ( ) {
635+ use chrono:: Duration ;
636+
637+ use crate :: push:: { PushRule , PushRules , TimeAndDayCondition , TimeInterval } ;
638+
639+ let mut flattened_keys = BTreeMap :: new ( ) ;
640+ flattened_keys. insert (
641+ "content.body" . to_string ( ) ,
642+ JsonValue :: Value ( SimpleJsonValue :: Str ( Cow :: Borrowed ( "foo bar bob hello" ) ) ) ,
643+ ) ;
644+ let evaluator = PushRuleEvaluator :: py_new (
645+ flattened_keys,
646+ false ,
647+ 10 ,
648+ Some ( 0 ) ,
649+ BTreeMap :: new ( ) ,
650+ BTreeMap :: new ( ) ,
651+ true ,
652+ vec ! [ ] ,
653+ true ,
654+ true ,
655+ )
656+ . unwrap ( ) ;
657+
658+ // smoke test: notify is working with other conditions
659+ let result = evaluator. run (
660+ & FilteredPushRules :: default ( ) ,
661+ Some ( "@bob:example.org" ) ,
662+ None ,
663+ ) ;
664+ assert_eq ! ( result. len( ) , 3 ) ;
665+
666+ // for testing sakes, use current duration of two hours behind and forward of now as dnd
667+ let test_time = chrono:: Utc :: now ( ) ;
668+ let test_start_time = ( test_time - Duration :: hours ( 2 ) ) . format ( "%H:%M" ) . to_string ( ) ;
669+ let test_end_time = ( test_time + Duration :: hours ( 2 ) ) . format ( "%H:%M" ) . to_string ( ) ;
670+
671+ // time and day condition in push rule
672+ let custom_rule = PushRule {
673+ rule_id : Cow :: from ( ".m.rule.master" ) ,
674+ priority_class : 5 , // override
675+ conditions : Cow :: from ( vec ! [ Condition :: Known ( KnownCondition :: TimeAndDay (
676+ TimeAndDayCondition {
677+ timezone: None ,
678+ intervals: vec![ TimeAndDayIntervals {
679+ time_of_day: TimeInterval {
680+ start_time: Cow :: from( test_start_time) ,
681+ end_time: Cow :: from( test_end_time) ,
682+ } ,
683+ day_of_week: vec![ 6 ] ,
684+ } ] ,
685+ } ,
686+ ) ) ] ) ,
687+ actions : Cow :: from ( vec ! [ Action :: DontNotify ] ) ,
688+ default : true ,
689+ default_enabled : true ,
690+ } ;
691+ let rules = PushRules :: new ( vec ! [ custom_rule] ) ;
692+ let result = evaluator. run (
693+ & FilteredPushRules :: py_new ( rules, BTreeMap :: new ( ) , true , false , true , false ) ,
694+ None ,
695+ None ,
696+ ) ;
697+
698+ // dnd time, dont_notify
699+ assert_eq ! ( result. len( ) , 0 ) ;
700+ }
0 commit comments