diff --git a/rust/src/push/base_rules.rs b/rust/src/push/base_rules.rs
index e9af26dd4f..d7c73c1f25 100644
--- a/rust/src/push/base_rules.rs
+++ b/rust/src/push/base_rules.rs
@@ -21,13 +21,13 @@ use lazy_static::lazy_static;
use serde_json::Value;
use super::KnownCondition;
-use crate::push::Action;
-use crate::push::Condition;
-use crate::push::EventMatchCondition;
-use crate::push::PushRule;
-use crate::push::RelatedEventMatchCondition;
+use crate::push::RelatedEventMatchTypeCondition;
use crate::push::SetTweak;
use crate::push::TweakValue;
+use crate::push::{Action, EventPropertyIsCondition, SimpleJsonValue};
+use crate::push::{Condition, EventMatchTypeCondition};
+use crate::push::{EventMatchCondition, EventMatchPatternType};
+use crate::push::{EventPropertyIsTypeCondition, PushRule};
const HIGHLIGHT_ACTION: Action = Action::SetTweak(SetTweak {
set_tweak: Cow::Borrowed("highlight"),
@@ -71,12 +71,11 @@ pub const BASE_APPEND_OVERRIDE_RULES: &[PushRule] = &[
priority_class: 5,
conditions: Cow::Borrowed(&[Condition::Known(KnownCondition::EventMatch(
EventMatchCondition {
- key: Cow::Borrowed("content.m.relates_to.rel_type"),
- pattern: Some(Cow::Borrowed("m.replace")),
- pattern_type: None,
+ key: Cow::Borrowed("content.m\\.relates_to.rel_type"),
+ pattern: Cow::Borrowed("m.replace"),
},
))]),
- actions: Cow::Borrowed(&[Action::DontNotify]),
+ actions: Cow::Borrowed(&[]),
default: true,
default_enabled: true,
},
@@ -86,8 +85,7 @@ pub const BASE_APPEND_OVERRIDE_RULES: &[PushRule] = &[
conditions: Cow::Borrowed(&[Condition::Known(KnownCondition::EventMatch(
EventMatchCondition {
key: Cow::Borrowed("content.msgtype"),
- pattern: Some(Cow::Borrowed("m.notice")),
- pattern_type: None,
+ pattern: Cow::Borrowed("m.notice"),
},
))]),
actions: Cow::Borrowed(&[Action::DontNotify]),
@@ -100,18 +98,15 @@ pub const BASE_APPEND_OVERRIDE_RULES: &[PushRule] = &[
conditions: Cow::Borrowed(&[
Condition::Known(KnownCondition::EventMatch(EventMatchCondition {
key: Cow::Borrowed("type"),
- pattern: Some(Cow::Borrowed("m.room.member")),
- pattern_type: None,
+ pattern: Cow::Borrowed("m.room.member"),
})),
Condition::Known(KnownCondition::EventMatch(EventMatchCondition {
key: Cow::Borrowed("content.membership"),
- pattern: Some(Cow::Borrowed("invite")),
- pattern_type: None,
+ pattern: Cow::Borrowed("invite"),
})),
- Condition::Known(KnownCondition::EventMatch(EventMatchCondition {
+ Condition::Known(KnownCondition::EventMatchType(EventMatchTypeCondition {
key: Cow::Borrowed("state_key"),
- pattern: None,
- pattern_type: Some(Cow::Borrowed("user_id")),
+ pattern_type: Cow::Borrowed(&EventMatchPatternType::UserId),
})),
]),
actions: Cow::Borrowed(&[Action::Notify, HIGHLIGHT_FALSE_ACTION, SOUND_ACTION]),
@@ -124,8 +119,7 @@ pub const BASE_APPEND_OVERRIDE_RULES: &[PushRule] = &[
conditions: Cow::Borrowed(&[Condition::Known(KnownCondition::EventMatch(
EventMatchCondition {
key: Cow::Borrowed("type"),
- pattern: Some(Cow::Borrowed("m.room.member")),
- pattern_type: None,
+ pattern: Cow::Borrowed("m.room.member"),
},
))]),
actions: Cow::Borrowed(&[Action::DontNotify]),
@@ -135,11 +129,10 @@ pub const BASE_APPEND_OVERRIDE_RULES: &[PushRule] = &[
PushRule {
rule_id: Cow::Borrowed("global/override/.im.nheko.msc3664.reply"),
priority_class: 5,
- conditions: Cow::Borrowed(&[Condition::Known(KnownCondition::RelatedEventMatch(
- RelatedEventMatchCondition {
- key: Some(Cow::Borrowed("sender")),
- pattern: None,
- pattern_type: Some(Cow::Borrowed("user_id")),
+ conditions: Cow::Borrowed(&[Condition::Known(KnownCondition::RelatedEventMatchType(
+ RelatedEventMatchTypeCondition {
+ key: Cow::Borrowed("sender"),
+ pattern_type: Cow::Borrowed(&EventMatchPatternType::UserId),
rel_type: Cow::Borrowed("m.in_reply_to"),
include_fallbacks: None,
},
@@ -151,7 +144,12 @@ pub const BASE_APPEND_OVERRIDE_RULES: &[PushRule] = &[
PushRule {
rule_id: Cow::Borrowed(".org.matrix.msc3952.is_user_mention"),
priority_class: 5,
- conditions: Cow::Borrowed(&[Condition::Known(KnownCondition::IsUserMention)]),
+ conditions: Cow::Borrowed(&[Condition::Known(
+ KnownCondition::ExactEventPropertyContainsType(EventPropertyIsTypeCondition {
+ key: Cow::Borrowed("content.org\\.matrix\\.msc3952\\.mentions.user_ids"),
+ value_type: Cow::Borrowed(&EventMatchPatternType::UserId),
+ }),
+ )]),
actions: Cow::Borrowed(&[Action::Notify, HIGHLIGHT_ACTION, SOUND_ACTION]),
default: true,
default_enabled: true,
@@ -168,7 +166,10 @@ pub const BASE_APPEND_OVERRIDE_RULES: &[PushRule] = &[
rule_id: Cow::Borrowed(".org.matrix.msc3952.is_room_mention"),
priority_class: 5,
conditions: Cow::Borrowed(&[
- Condition::Known(KnownCondition::IsRoomMention),
+ Condition::Known(KnownCondition::EventPropertyIs(EventPropertyIsCondition {
+ key: Cow::Borrowed("content.org\\.matrix\\.msc3952\\.mentions.room"),
+ value: Cow::Borrowed(&SimpleJsonValue::Bool(true)),
+ })),
Condition::Known(KnownCondition::SenderNotificationPermission {
key: Cow::Borrowed("room"),
}),
@@ -186,8 +187,7 @@ pub const BASE_APPEND_OVERRIDE_RULES: &[PushRule] = &[
}),
Condition::Known(KnownCondition::EventMatch(EventMatchCondition {
key: Cow::Borrowed("content.body"),
- pattern: Some(Cow::Borrowed("@room")),
- pattern_type: None,
+ pattern: Cow::Borrowed("@room"),
})),
]),
actions: Cow::Borrowed(&[Action::Notify, HIGHLIGHT_ACTION]),
@@ -200,13 +200,11 @@ pub const BASE_APPEND_OVERRIDE_RULES: &[PushRule] = &[
conditions: Cow::Borrowed(&[
Condition::Known(KnownCondition::EventMatch(EventMatchCondition {
key: Cow::Borrowed("type"),
- pattern: Some(Cow::Borrowed("m.room.tombstone")),
- pattern_type: None,
+ pattern: Cow::Borrowed("m.room.tombstone"),
})),
Condition::Known(KnownCondition::EventMatch(EventMatchCondition {
key: Cow::Borrowed("state_key"),
- pattern: Some(Cow::Borrowed("")),
- pattern_type: None,
+ pattern: Cow::Borrowed(""),
})),
]),
actions: Cow::Borrowed(&[Action::Notify, HIGHLIGHT_ACTION]),
@@ -219,11 +217,10 @@ pub const BASE_APPEND_OVERRIDE_RULES: &[PushRule] = &[
conditions: Cow::Borrowed(&[Condition::Known(KnownCondition::EventMatch(
EventMatchCondition {
key: Cow::Borrowed("type"),
- pattern: Some(Cow::Borrowed("m.reaction")),
- pattern_type: None,
+ pattern: Cow::Borrowed("m.reaction"),
},
))]),
- actions: Cow::Borrowed(&[Action::DontNotify]),
+ actions: Cow::Borrowed(&[]),
default: true,
default_enabled: true,
},
@@ -233,13 +230,11 @@ pub const BASE_APPEND_OVERRIDE_RULES: &[PushRule] = &[
conditions: Cow::Borrowed(&[
Condition::Known(KnownCondition::EventMatch(EventMatchCondition {
key: Cow::Borrowed("type"),
- pattern: Some(Cow::Borrowed("m.room.server_acl")),
- pattern_type: None,
+ pattern: Cow::Borrowed("m.room.server_acl"),
})),
Condition::Known(KnownCondition::EventMatch(EventMatchCondition {
key: Cow::Borrowed("state_key"),
- pattern: Some(Cow::Borrowed("")),
- pattern_type: None,
+ pattern: Cow::Borrowed(""),
})),
]),
actions: Cow::Borrowed(&[]),
@@ -252,8 +247,7 @@ pub const BASE_APPEND_OVERRIDE_RULES: &[PushRule] = &[
conditions: Cow::Borrowed(&[Condition::Known(KnownCondition::EventMatch(
EventMatchCondition {
key: Cow::Borrowed("type"),
- pattern: Some(Cow::Borrowed("org.matrix.msc3381.poll.response")),
- pattern_type: None,
+ pattern: Cow::Borrowed("org.matrix.msc3381.poll.response"),
},
))]),
actions: Cow::Borrowed(&[]),
@@ -265,11 +259,10 @@ pub const BASE_APPEND_OVERRIDE_RULES: &[PushRule] = &[
pub const BASE_APPEND_CONTENT_RULES: &[PushRule] = &[PushRule {
rule_id: Cow::Borrowed("global/content/.m.rule.contains_user_name"),
priority_class: 4,
- conditions: Cow::Borrowed(&[Condition::Known(KnownCondition::EventMatch(
- EventMatchCondition {
+ conditions: Cow::Borrowed(&[Condition::Known(KnownCondition::EventMatchType(
+ EventMatchTypeCondition {
key: Cow::Borrowed("content.body"),
- pattern: None,
- pattern_type: Some(Cow::Borrowed("user_localpart")),
+ pattern_type: Cow::Borrowed(&EventMatchPatternType::UserLocalpart),
},
))]),
actions: Cow::Borrowed(&[Action::Notify, HIGHLIGHT_ACTION, SOUND_ACTION]),
@@ -284,8 +277,7 @@ pub const BASE_APPEND_UNDERRIDE_RULES: &[PushRule] = &[
conditions: Cow::Borrowed(&[Condition::Known(KnownCondition::EventMatch(
EventMatchCondition {
key: Cow::Borrowed("type"),
- pattern: Some(Cow::Borrowed("m.call.invite")),
- pattern_type: None,
+ pattern: Cow::Borrowed("m.call.invite"),
},
))]),
actions: Cow::Borrowed(&[Action::Notify, RING_ACTION, HIGHLIGHT_FALSE_ACTION]),
@@ -298,8 +290,7 @@ pub const BASE_APPEND_UNDERRIDE_RULES: &[PushRule] = &[
conditions: Cow::Borrowed(&[
Condition::Known(KnownCondition::EventMatch(EventMatchCondition {
key: Cow::Borrowed("type"),
- pattern: Some(Cow::Borrowed("m.room.message")),
- pattern_type: None,
+ pattern: Cow::Borrowed("m.room.message"),
})),
Condition::Known(KnownCondition::RoomMemberCount {
is: Some(Cow::Borrowed("2")),
@@ -315,8 +306,7 @@ pub const BASE_APPEND_UNDERRIDE_RULES: &[PushRule] = &[
conditions: Cow::Borrowed(&[
Condition::Known(KnownCondition::EventMatch(EventMatchCondition {
key: Cow::Borrowed("type"),
- pattern: Some(Cow::Borrowed("m.room.encrypted")),
- pattern_type: None,
+ pattern: Cow::Borrowed("m.room.encrypted"),
})),
Condition::Known(KnownCondition::RoomMemberCount {
is: Some(Cow::Borrowed("2")),
@@ -335,8 +325,7 @@ pub const BASE_APPEND_UNDERRIDE_RULES: &[PushRule] = &[
Condition::Known(KnownCondition::EventMatch(EventMatchCondition {
key: Cow::Borrowed("type"),
// MSC3933: Type changed from template rule - see MSC.
- pattern: Some(Cow::Borrowed("org.matrix.msc1767.encrypted")),
- pattern_type: None,
+ pattern: Cow::Borrowed("org.matrix.msc1767.encrypted"),
})),
Condition::Known(KnownCondition::RoomMemberCount {
is: Some(Cow::Borrowed("2")),
@@ -360,8 +349,7 @@ pub const BASE_APPEND_UNDERRIDE_RULES: &[PushRule] = &[
Condition::Known(KnownCondition::EventMatch(EventMatchCondition {
key: Cow::Borrowed("type"),
// MSC3933: Type changed from template rule - see MSC.
- pattern: Some(Cow::Borrowed("org.matrix.msc1767.message")),
- pattern_type: None,
+ pattern: Cow::Borrowed("org.matrix.msc1767.message"),
})),
Condition::Known(KnownCondition::RoomMemberCount {
is: Some(Cow::Borrowed("2")),
@@ -385,8 +373,7 @@ pub const BASE_APPEND_UNDERRIDE_RULES: &[PushRule] = &[
Condition::Known(KnownCondition::EventMatch(EventMatchCondition {
key: Cow::Borrowed("type"),
// MSC3933: Type changed from template rule - see MSC.
- pattern: Some(Cow::Borrowed("org.matrix.msc1767.file")),
- pattern_type: None,
+ pattern: Cow::Borrowed("org.matrix.msc1767.file"),
})),
Condition::Known(KnownCondition::RoomMemberCount {
is: Some(Cow::Borrowed("2")),
@@ -410,8 +397,7 @@ pub const BASE_APPEND_UNDERRIDE_RULES: &[PushRule] = &[
Condition::Known(KnownCondition::EventMatch(EventMatchCondition {
key: Cow::Borrowed("type"),
// MSC3933: Type changed from template rule - see MSC.
- pattern: Some(Cow::Borrowed("org.matrix.msc1767.image")),
- pattern_type: None,
+ pattern: Cow::Borrowed("org.matrix.msc1767.image"),
})),
Condition::Known(KnownCondition::RoomMemberCount {
is: Some(Cow::Borrowed("2")),
@@ -435,8 +421,7 @@ pub const BASE_APPEND_UNDERRIDE_RULES: &[PushRule] = &[
Condition::Known(KnownCondition::EventMatch(EventMatchCondition {
key: Cow::Borrowed("type"),
// MSC3933: Type changed from template rule - see MSC.
- pattern: Some(Cow::Borrowed("org.matrix.msc1767.video")),
- pattern_type: None,
+ pattern: Cow::Borrowed("org.matrix.msc1767.video"),
})),
Condition::Known(KnownCondition::RoomMemberCount {
is: Some(Cow::Borrowed("2")),
@@ -460,8 +445,7 @@ pub const BASE_APPEND_UNDERRIDE_RULES: &[PushRule] = &[
Condition::Known(KnownCondition::EventMatch(EventMatchCondition {
key: Cow::Borrowed("type"),
// MSC3933: Type changed from template rule - see MSC.
- pattern: Some(Cow::Borrowed("org.matrix.msc1767.audio")),
- pattern_type: None,
+ pattern: Cow::Borrowed("org.matrix.msc1767.audio"),
})),
Condition::Known(KnownCondition::RoomMemberCount {
is: Some(Cow::Borrowed("2")),
@@ -482,8 +466,7 @@ pub const BASE_APPEND_UNDERRIDE_RULES: &[PushRule] = &[
conditions: Cow::Borrowed(&[Condition::Known(KnownCondition::EventMatch(
EventMatchCondition {
key: Cow::Borrowed("type"),
- pattern: Some(Cow::Borrowed("m.room.message")),
- pattern_type: None,
+ pattern: Cow::Borrowed("m.room.message"),
},
))]),
actions: Cow::Borrowed(&[Action::Notify, HIGHLIGHT_FALSE_ACTION]),
@@ -496,8 +479,7 @@ pub const BASE_APPEND_UNDERRIDE_RULES: &[PushRule] = &[
conditions: Cow::Borrowed(&[Condition::Known(KnownCondition::EventMatch(
EventMatchCondition {
key: Cow::Borrowed("type"),
- pattern: Some(Cow::Borrowed("m.room.encrypted")),
- pattern_type: None,
+ pattern: Cow::Borrowed("m.room.encrypted"),
},
))]),
actions: Cow::Borrowed(&[Action::Notify, HIGHLIGHT_FALSE_ACTION]),
@@ -511,8 +493,7 @@ pub const BASE_APPEND_UNDERRIDE_RULES: &[PushRule] = &[
Condition::Known(KnownCondition::EventMatch(EventMatchCondition {
key: Cow::Borrowed("type"),
// MSC3933: Type changed from template rule - see MSC.
- pattern: Some(Cow::Borrowed("m.encrypted")),
- pattern_type: None,
+ pattern: Cow::Borrowed("m.encrypted"),
})),
// MSC3933: Add condition on top of template rule - see MSC.
Condition::Known(KnownCondition::RoomVersionSupports {
@@ -531,8 +512,7 @@ pub const BASE_APPEND_UNDERRIDE_RULES: &[PushRule] = &[
Condition::Known(KnownCondition::EventMatch(EventMatchCondition {
key: Cow::Borrowed("type"),
// MSC3933: Type changed from template rule - see MSC.
- pattern: Some(Cow::Borrowed("m.message")),
- pattern_type: None,
+ pattern: Cow::Borrowed("m.message"),
})),
// MSC3933: Add condition on top of template rule - see MSC.
Condition::Known(KnownCondition::RoomVersionSupports {
@@ -551,8 +531,7 @@ pub const BASE_APPEND_UNDERRIDE_RULES: &[PushRule] = &[
Condition::Known(KnownCondition::EventMatch(EventMatchCondition {
key: Cow::Borrowed("type"),
// MSC3933: Type changed from template rule - see MSC.
- pattern: Some(Cow::Borrowed("m.file")),
- pattern_type: None,
+ pattern: Cow::Borrowed("m.file"),
})),
// MSC3933: Add condition on top of template rule - see MSC.
Condition::Known(KnownCondition::RoomVersionSupports {
@@ -571,8 +550,7 @@ pub const BASE_APPEND_UNDERRIDE_RULES: &[PushRule] = &[
Condition::Known(KnownCondition::EventMatch(EventMatchCondition {
key: Cow::Borrowed("type"),
// MSC3933: Type changed from template rule - see MSC.
- pattern: Some(Cow::Borrowed("m.image")),
- pattern_type: None,
+ pattern: Cow::Borrowed("m.image"),
})),
// MSC3933: Add condition on top of template rule - see MSC.
Condition::Known(KnownCondition::RoomVersionSupports {
@@ -591,8 +569,7 @@ pub const BASE_APPEND_UNDERRIDE_RULES: &[PushRule] = &[
Condition::Known(KnownCondition::EventMatch(EventMatchCondition {
key: Cow::Borrowed("type"),
// MSC3933: Type changed from template rule - see MSC.
- pattern: Some(Cow::Borrowed("m.video")),
- pattern_type: None,
+ pattern: Cow::Borrowed("m.video"),
})),
// MSC3933: Add condition on top of template rule - see MSC.
Condition::Known(KnownCondition::RoomVersionSupports {
@@ -611,8 +588,7 @@ pub const BASE_APPEND_UNDERRIDE_RULES: &[PushRule] = &[
Condition::Known(KnownCondition::EventMatch(EventMatchCondition {
key: Cow::Borrowed("type"),
// MSC3933: Type changed from template rule - see MSC.
- pattern: Some(Cow::Borrowed("m.audio")),
- pattern_type: None,
+ pattern: Cow::Borrowed("m.audio"),
})),
// MSC3933: Add condition on top of template rule - see MSC.
Condition::Known(KnownCondition::RoomVersionSupports {
@@ -630,18 +606,15 @@ pub const BASE_APPEND_UNDERRIDE_RULES: &[PushRule] = &[
conditions: Cow::Borrowed(&[
Condition::Known(KnownCondition::EventMatch(EventMatchCondition {
key: Cow::Borrowed("type"),
- pattern: Some(Cow::Borrowed("im.vector.modular.widgets")),
- pattern_type: None,
+ pattern: Cow::Borrowed("im.vector.modular.widgets"),
})),
Condition::Known(KnownCondition::EventMatch(EventMatchCondition {
key: Cow::Borrowed("content.type"),
- pattern: Some(Cow::Borrowed("jitsi")),
- pattern_type: None,
+ pattern: Cow::Borrowed("jitsi"),
})),
Condition::Known(KnownCondition::EventMatch(EventMatchCondition {
key: Cow::Borrowed("state_key"),
- pattern: Some(Cow::Borrowed("*")),
- pattern_type: None,
+ pattern: Cow::Borrowed("*"),
})),
]),
actions: Cow::Borrowed(&[Action::Notify, HIGHLIGHT_FALSE_ACTION]),
@@ -657,8 +630,7 @@ pub const BASE_APPEND_UNDERRIDE_RULES: &[PushRule] = &[
}),
Condition::Known(KnownCondition::EventMatch(EventMatchCondition {
key: Cow::Borrowed("type"),
- pattern: Some(Cow::Borrowed("org.matrix.msc3381.poll.start")),
- pattern_type: None,
+ pattern: Cow::Borrowed("org.matrix.msc3381.poll.start"),
})),
]),
actions: Cow::Borrowed(&[Action::Notify, SOUND_ACTION]),
@@ -671,8 +643,7 @@ pub const BASE_APPEND_UNDERRIDE_RULES: &[PushRule] = &[
conditions: Cow::Borrowed(&[Condition::Known(KnownCondition::EventMatch(
EventMatchCondition {
key: Cow::Borrowed("type"),
- pattern: Some(Cow::Borrowed("org.matrix.msc3381.poll.start")),
- pattern_type: None,
+ pattern: Cow::Borrowed("org.matrix.msc3381.poll.start"),
},
))]),
actions: Cow::Borrowed(&[Action::Notify]),
@@ -688,8 +659,7 @@ pub const BASE_APPEND_UNDERRIDE_RULES: &[PushRule] = &[
}),
Condition::Known(KnownCondition::EventMatch(EventMatchCondition {
key: Cow::Borrowed("type"),
- pattern: Some(Cow::Borrowed("org.matrix.msc3381.poll.end")),
- pattern_type: None,
+ pattern: Cow::Borrowed("org.matrix.msc3381.poll.end"),
})),
]),
actions: Cow::Borrowed(&[Action::Notify, SOUND_ACTION]),
@@ -702,8 +672,7 @@ pub const BASE_APPEND_UNDERRIDE_RULES: &[PushRule] = &[
conditions: Cow::Borrowed(&[Condition::Known(KnownCondition::EventMatch(
EventMatchCondition {
key: Cow::Borrowed("type"),
- pattern: Some(Cow::Borrowed("org.matrix.msc3381.poll.end")),
- pattern_type: None,
+ pattern: Cow::Borrowed("org.matrix.msc3381.poll.end"),
},
))]),
actions: Cow::Borrowed(&[Action::Notify]),
diff --git a/rust/src/push/evaluator.rs b/rust/src/push/evaluator.rs
index ec7a8c4453..6941c61ea4 100644
--- a/rust/src/push/evaluator.rs
+++ b/rust/src/push/evaluator.rs
@@ -12,7 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-use std::collections::{BTreeMap, BTreeSet};
+use std::borrow::Cow;
+use std::collections::BTreeMap;
use anyhow::{Context, Error};
use lazy_static::lazy_static;
@@ -22,9 +23,10 @@ use regex::Regex;
use super::{
utils::{get_glob_matcher, get_localpart_from_id, GlobMatchType},
- Action, Condition, EventMatchCondition, FilteredPushRules, KnownCondition,
- RelatedEventMatchCondition,
+ Action, Condition, EventPropertyIsCondition, FilteredPushRules, KnownCondition,
+ SimpleJsonValue,
};
+use crate::push::{EventMatchPatternType, JsonValue};
lazy_static! {
/// Used to parse the `is` clause in the room member count condition.
@@ -61,19 +63,15 @@ impl RoomVersionFeatures {
/// Allows running a set of push rules against a particular event.
#[pyclass]
pub struct PushRuleEvaluator {
- /// A mapping of "flattened" keys to string values in the event, e.g.
+ /// A mapping of "flattened" keys to simple JSON values in the event, e.g.
/// includes things like "type" and "content.msgtype".
- flattened_keys: BTreeMap<String, String>,
+ flattened_keys: BTreeMap<String, JsonValue>,
/// The "content.body", if any.
body: String,
/// True if the event has a mentions property and MSC3952 support is enabled.
has_mentions: bool,
- /// The user mentions that were part of the message.
- user_mentions: BTreeSet<String>,
- /// True if the message is a room message.
- room_mention: bool,
/// The number of users in the room.
room_member_count: u64,
@@ -87,7 +85,7 @@ pub struct PushRuleEvaluator {
/// The related events, indexed by relation type. Flattened in the same manner as
/// `flattened_keys`.
- related_events_flattened: BTreeMap<String, BTreeMap<String, String>>,
+ related_events_flattened: BTreeMap<String, BTreeMap<String, JsonValue>>,
/// If msc3664, push rules for related events, is enabled.
related_event_match_enabled: bool,
@@ -106,29 +104,25 @@ impl PushRuleEvaluator {
#[allow(clippy::too_many_arguments)]
#[new]
pub fn py_new(
- flattened_keys: BTreeMap<String, String>,
+ flattened_keys: BTreeMap<String, JsonValue>,
has_mentions: bool,
- user_mentions: BTreeSet<String>,
- room_mention: bool,
room_member_count: u64,
sender_power_level: Option<i64>,
notification_power_levels: BTreeMap<String, i64>,
- related_events_flattened: BTreeMap<String, BTreeMap<String, String>>,
+ related_events_flattened: BTreeMap<String, BTreeMap<String, JsonValue>>,
related_event_match_enabled: bool,
room_version_feature_flags: Vec<String>,
msc3931_enabled: bool,
) -> Result<Self, Error> {
- let body = flattened_keys
- .get("content.body")
- .cloned()
- .unwrap_or_default();
+ let body = match flattened_keys.get("content.body") {
+ Some(JsonValue::Value(SimpleJsonValue::Str(s))) => s.clone(),
+ _ => String::new(),
+ };
Ok(PushRuleEvaluator {
flattened_keys,
body,
has_mentions,
- user_mentions,
- room_mention,
room_member_count,
notification_power_levels,
sender_power_level,
@@ -249,20 +243,84 @@ impl PushRuleEvaluator {
};
let result = match known_condition {
- KnownCondition::EventMatch(event_match) => {
- self.match_event_match(event_match, user_id)?
+ KnownCondition::EventMatch(event_match) => self.match_event_match(
+ &self.flattened_keys,
+ &event_match.key,
+ &event_match.pattern,
+ )?,
+ KnownCondition::EventMatchType(event_match) => {
+ // The `pattern_type` can either be "user_id" or "user_localpart",
+ // either way if we don't have a `user_id` then the condition can't
+ // match.
+ let user_id = if let Some(user_id) = user_id {
+ user_id
+ } else {
+ return Ok(false);
+ };
+
+ let pattern = match &*event_match.pattern_type {
+ EventMatchPatternType::UserId => user_id,
+ EventMatchPatternType::UserLocalpart => get_localpart_from_id(user_id)?,
+ };
+
+ self.match_event_match(&self.flattened_keys, &event_match.key, pattern)?
}
- KnownCondition::RelatedEventMatch(event_match) => {
- self.match_related_event_match(event_match, user_id)?
+ KnownCondition::EventPropertyIs(event_property_is) => {
+ self.match_event_property_is(event_property_is)?
}
- KnownCondition::IsUserMention => {
- if let Some(uid) = user_id {
- self.user_mentions.contains(uid)
+ KnownCondition::RelatedEventMatch(event_match) => self.match_related_event_match(
+ &event_match.rel_type.clone(),
+ event_match.include_fallbacks,
+ event_match.key.clone(),
+ event_match.pattern.clone(),
+ )?,
+ KnownCondition::RelatedEventMatchType(event_match) => {
+ // The `pattern_type` can either be "user_id" or "user_localpart",
+ // either way if we don't have a `user_id` then the condition can't
+ // match.
+ let user_id = if let Some(user_id) = user_id {
+ user_id
} else {
- false
- }
+ return Ok(false);
+ };
+
+ let pattern = match &*event_match.pattern_type {
+ EventMatchPatternType::UserId => user_id,
+ EventMatchPatternType::UserLocalpart => get_localpart_from_id(user_id)?,
+ };
+
+ self.match_related_event_match(
+ &event_match.rel_type.clone(),
+ event_match.include_fallbacks,
+ Some(event_match.key.clone()),
+ Some(Cow::Borrowed(pattern)),
+ )?
+ }
+ KnownCondition::EventPropertyContains(event_property_is) => self
+ .match_event_property_contains(
+ event_property_is.key.clone(),
+ event_property_is.value.clone(),
+ )?,
+ KnownCondition::ExactEventPropertyContainsType(exact_event_match) => {
+ // The `pattern_type` can either be "user_id" or "user_localpart",
+ // either way if we don't have a `user_id` then the condition can't
+ // match.
+ let user_id = if let Some(user_id) = user_id {
+ user_id
+ } else {
+ return Ok(false);
+ };
+
+ let pattern = match &*exact_event_match.value_type {
+ EventMatchPatternType::UserId => user_id,
+ EventMatchPatternType::UserLocalpart => get_localpart_from_id(user_id)?,
+ };
+
+ self.match_event_property_contains(
+ exact_event_match.key.clone(),
+ Cow::Borrowed(&SimpleJsonValue::Str(pattern.to_string())),
+ )?
}
- KnownCondition::IsRoomMention => self.room_mention,
KnownCondition::ContainsDisplayName => {
if let Some(dn) = display_name {
if !dn.is_empty() {
@@ -313,31 +371,13 @@ impl PushRuleEvaluator {
/// Evaluates a `event_match` condition.
fn match_event_match(
&self,
- event_match: &EventMatchCondition,
- user_id: Option<&str>,
+ flattened_event: &BTreeMap<String, JsonValue>,
+ key: &str,
+ pattern: &str,
) -> Result<bool, Error> {
- let pattern = if let Some(pattern) = &event_match.pattern {
- pattern
- } else if let Some(pattern_type) = &event_match.pattern_type {
- // The `pattern_type` can either be "user_id" or "user_localpart",
- // either way if we don't have a `user_id` then the condition can't
- // match.
- let user_id = if let Some(user_id) = user_id {
- user_id
- } else {
- return Ok(false);
- };
-
- match &**pattern_type {
- "user_id" => user_id,
- "user_localpart" => get_localpart_from_id(user_id)?,
- _ => return Ok(false),
- }
- } else {
- return Ok(false);
- };
-
- let haystack = if let Some(haystack) = self.flattened_keys.get(&*event_match.key) {
+ let haystack = if let Some(JsonValue::Value(SimpleJsonValue::Str(haystack))) =
+ flattened_event.get(key)
+ {
haystack
} else {
return Ok(false);
@@ -345,7 +385,7 @@ impl PushRuleEvaluator {
// For the content.body we match against "words", but for everything
// else we match against the entire value.
- let match_type = if event_match.key == "content.body" {
+ let match_type = if key == "content.body" {
GlobMatchType::Word
} else {
GlobMatchType::Whole
@@ -355,11 +395,31 @@ impl PushRuleEvaluator {
compiled_pattern.is_match(haystack)
}
+ /// Evaluates a `event_property_is` condition.
+ fn match_event_property_is(
+ &self,
+ event_property_is: &EventPropertyIsCondition,
+ ) -> Result<bool, Error> {
+ let value = &event_property_is.value;
+
+ let haystack = if let Some(JsonValue::Value(haystack)) =
+ self.flattened_keys.get(&*event_property_is.key)
+ {
+ haystack
+ } else {
+ return Ok(false);
+ };
+
+ Ok(haystack == &**value)
+ }
+
/// Evaluates a `related_event_match` condition. (MSC3664)
fn match_related_event_match(
&self,
- event_match: &RelatedEventMatchCondition,
- user_id: Option<&str>,
+ rel_type: &str,
+ include_fallbacks: Option<bool>,
+ key: Option<Cow<str>>,
+ pattern: Option<Cow<str>>,
) -> Result<bool, Error> {
// First check if related event matching is enabled...
if !self.related_event_match_enabled {
@@ -367,7 +427,7 @@ impl PushRuleEvaluator {
}
// get the related event, fail if there is none.
- let event = if let Some(event) = self.related_events_flattened.get(&*event_match.rel_type) {
+ let event = if let Some(event) = self.related_events_flattened.get(rel_type) {
event
} else {
return Ok(false);
@@ -375,57 +435,33 @@ impl PushRuleEvaluator {
// If we are not matching fallbacks, don't match if our special key indicating this is a
// fallback relation is not present.
- if !event_match.include_fallbacks.unwrap_or(false)
- && event.contains_key("im.vector.is_falling_back")
- {
+ if !include_fallbacks.unwrap_or(false) && event.contains_key("im.vector.is_falling_back") {
return Ok(false);
}
- // if we have no key, accept the event as matching, if it existed without matching any
- // fields.
- let key = if let Some(key) = &event_match.key {
- key
- } else {
- return Ok(true);
- };
-
- let pattern = if let Some(pattern) = &event_match.pattern {
- pattern
- } else if let Some(pattern_type) = &event_match.pattern_type {
- // The `pattern_type` can either be "user_id" or "user_localpart",
- // either way if we don't have a `user_id` then the condition can't
- // match.
- let user_id = if let Some(user_id) = user_id {
- user_id
- } else {
- return Ok(false);
- };
-
- match &**pattern_type {
- "user_id" => user_id,
- "user_localpart" => get_localpart_from_id(user_id)?,
- _ => return Ok(false),
- }
- } else {
- return Ok(false);
- };
+ match (key, pattern) {
+ // if we have no key, accept the event as matching.
+ (None, _) => Ok(true),
+ // There was a key, so we *must* have a pattern to go with it.
+ (Some(_), None) => Ok(false),
+ // If there is a key & pattern, check if they're in the flattened event (given by rel_type).
+ (Some(key), Some(pattern)) => self.match_event_match(event, &key, &pattern),
+ }
+ }
- let haystack = if let Some(haystack) = event.get(&**key) {
+ /// Evaluates a `event_property_contains` condition.
+ fn match_event_property_contains(
+ &self,
+ key: Cow<str>,
+ value: Cow<SimpleJsonValue>,
+ ) -> Result<bool, Error> {
+ let haystack = if let Some(JsonValue::Array(haystack)) = self.flattened_keys.get(&*key) {
haystack
} else {
return Ok(false);
};
- // For the content.body we match against "words", but for everything
- // else we match against the entire value.
- let match_type = if key == "content.body" {
- GlobMatchType::Word
- } else {
- GlobMatchType::Whole
- };
-
- let mut compiled_pattern = get_glob_matcher(pattern, match_type)?;
- compiled_pattern.is_match(haystack)
+ Ok(haystack.contains(&value))
}
/// Match the member count against an 'is' condition
@@ -455,12 +491,13 @@ impl PushRuleEvaluator {
#[test]
fn push_rule_evaluator() {
let mut flattened_keys = BTreeMap::new();
- flattened_keys.insert("content.body".to_string(), "foo bar bob hello".to_string());
+ flattened_keys.insert(
+ "content.body".to_string(),
+ JsonValue::Value(SimpleJsonValue::Str("foo bar bob hello".to_string())),
+ );
let evaluator = PushRuleEvaluator::py_new(
flattened_keys,
false,
- BTreeSet::new(),
- false,
10,
Some(0),
BTreeMap::new(),
@@ -482,13 +519,14 @@ fn test_requires_room_version_supports_condition() {
use crate::push::{PushRule, PushRules};
let mut flattened_keys = BTreeMap::new();
- flattened_keys.insert("content.body".to_string(), "foo bar bob hello".to_string());
+ flattened_keys.insert(
+ "content.body".to_string(),
+ JsonValue::Value(SimpleJsonValue::Str("foo bar bob hello".to_string())),
+ );
let flags = vec![RoomVersionFeatures::ExtensibleEvents.as_str().to_string()];
let evaluator = PushRuleEvaluator::py_new(
flattened_keys,
false,
- BTreeSet::new(),
- false,
10,
Some(0),
BTreeMap::new(),
diff --git a/rust/src/push/mod.rs b/rust/src/push/mod.rs
index 3c4f876cab..575a1c1e68 100644
--- a/rust/src/push/mod.rs
+++ b/rust/src/push/mod.rs
@@ -56,7 +56,9 @@ use std::collections::{BTreeMap, HashMap, HashSet};
use anyhow::{Context, Error};
use log::warn;
+use pyo3::exceptions::PyTypeError;
use pyo3::prelude::*;
+use pyo3::types::{PyBool, PyList, PyLong, PyString};
use pythonize::{depythonize, pythonize};
use serde::de::Error as _;
use serde::{Deserialize, Serialize};
@@ -248,6 +250,65 @@ impl<'de> Deserialize<'de> for Action {
}
}
+/// A simple JSON values (string, int, boolean, or null).
+#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
+#[serde(untagged)]
+pub enum SimpleJsonValue {
+ Str(String),
+ Int(i64),
+ Bool(bool),
+ Null,
+}
+
+impl<'source> FromPyObject<'source> for SimpleJsonValue {
+ fn extract(ob: &'source PyAny) -> PyResult<Self> {
+ if let Ok(s) = <PyString as pyo3::PyTryFrom>::try_from(ob) {
+ Ok(SimpleJsonValue::Str(s.to_string()))
+ // A bool *is* an int, ensure we try bool first.
+ } else if let Ok(b) = <PyBool as pyo3::PyTryFrom>::try_from(ob) {
+ Ok(SimpleJsonValue::Bool(b.extract()?))
+ } else if let Ok(i) = <PyLong as pyo3::PyTryFrom>::try_from(ob) {
+ Ok(SimpleJsonValue::Int(i.extract()?))
+ } else if ob.is_none() {
+ Ok(SimpleJsonValue::Null)
+ } else {
+ Err(PyTypeError::new_err(format!(
+ "Can't convert from {} to SimpleJsonValue",
+ ob.get_type().name()?
+ )))
+ }
+ }
+}
+
+/// A JSON values (list, string, int, boolean, or null).
+#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
+#[serde(untagged)]
+pub enum JsonValue {
+ Array(Vec<SimpleJsonValue>),
+ Value(SimpleJsonValue),
+}
+
+impl<'source> FromPyObject<'source> for JsonValue {
+ fn extract(ob: &'source PyAny) -> PyResult<Self> {
+ if let Ok(l) = <PyList as pyo3::PyTryFrom>::try_from(ob) {
+ match l.iter().map(SimpleJsonValue::extract).collect() {
+ Ok(a) => Ok(JsonValue::Array(a)),
+ Err(e) => Err(PyTypeError::new_err(format!(
+ "Can't convert to JsonValue::Array: {}",
+ e
+ ))),
+ }
+ } else if let Ok(v) = SimpleJsonValue::extract(ob) {
+ Ok(JsonValue::Value(v))
+ } else {
+ Err(PyTypeError::new_err(format!(
+ "Can't convert from {} to JsonValue",
+ ob.get_type().name()?
+ )))
+ }
+ }
+}
+
/// A condition used in push rules to match against an event.
///
/// We need this split as `serde` doesn't give us the ability to have a
@@ -267,12 +328,19 @@ pub enum Condition {
#[serde(tag = "kind")]
pub enum KnownCondition {
EventMatch(EventMatchCondition),
+ // Identical to event_match but gives predefined patterns. Cannot be added by users.
+ #[serde(skip_deserializing, rename = "event_match")]
+ EventMatchType(EventMatchTypeCondition),
+ EventPropertyIs(EventPropertyIsCondition),
#[serde(rename = "im.nheko.msc3664.related_event_match")]
RelatedEventMatch(RelatedEventMatchCondition),
- #[serde(rename = "org.matrix.msc3952.is_user_mention")]
- IsUserMention,
- #[serde(rename = "org.matrix.msc3952.is_room_mention")]
- IsRoomMention,
+ // Identical to related_event_match but gives predefined patterns. Cannot be added by users.
+ #[serde(skip_deserializing, rename = "im.nheko.msc3664.related_event_match")]
+ RelatedEventMatchType(RelatedEventMatchTypeCondition),
+ EventPropertyContains(EventPropertyIsCondition),
+ // Identical to exact_event_property_contains but gives predefined patterns. Cannot be added by users.
+ #[serde(skip_deserializing, rename = "event_property_contains")]
+ ExactEventPropertyContainsType(EventPropertyIsTypeCondition),
ContainsDisplayName,
RoomMemberCount {
#[serde(skip_serializing_if = "Option::is_none")]
@@ -299,14 +367,43 @@ impl<'source> FromPyObject<'source> for Condition {
}
}
-/// The body of a [`Condition::EventMatch`]
+/// The body of a [`Condition::EventMatch`] with a pattern.
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct EventMatchCondition {
pub key: Cow<'static, str>,
- #[serde(skip_serializing_if = "Option::is_none")]
- pub pattern: Option<Cow<'static, str>>,
- #[serde(skip_serializing_if = "Option::is_none")]
- pub pattern_type: Option<Cow<'static, str>>,
+ pub pattern: Cow<'static, str>,
+}
+
+#[derive(Serialize, Debug, Clone)]
+#[serde(rename_all = "snake_case")]
+pub enum EventMatchPatternType {
+ UserId,
+ UserLocalpart,
+}
+
+/// The body of a [`Condition::EventMatch`] that uses user_id or user_localpart as a pattern.
+#[derive(Serialize, Debug, Clone)]
+pub struct EventMatchTypeCondition {
+ pub key: Cow<'static, str>,
+ // During serialization, the pattern_type property gets replaced with a
+ // pattern property of the correct value in synapse.push.clientformat.format_push_rules_for_user.
+ pub pattern_type: Cow<'static, EventMatchPatternType>,
+}
+
+/// The body of a [`Condition::EventPropertyIs`]
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub struct EventPropertyIsCondition {
+ pub key: Cow<'static, str>,
+ pub value: Cow<'static, SimpleJsonValue>,
+}
+
+/// The body of a [`Condition::EventPropertyIs`] that uses user_id or user_localpart as a pattern.
+#[derive(Serialize, Debug, Clone)]
+pub struct EventPropertyIsTypeCondition {
+ pub key: Cow<'static, str>,
+ // During serialization, the pattern_type property gets replaced with a
+ // pattern property of the correct value in synapse.push.clientformat.format_push_rules_for_user.
+ pub value_type: Cow<'static, EventMatchPatternType>,
}
/// The body of a [`Condition::RelatedEventMatch`]
@@ -316,8 +413,18 @@ pub struct RelatedEventMatchCondition {
pub key: Option<Cow<'static, str>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub pattern: Option<Cow<'static, str>>,
+ pub rel_type: Cow<'static, str>,
#[serde(skip_serializing_if = "Option::is_none")]
- pub pattern_type: Option<Cow<'static, str>>,
+ pub include_fallbacks: Option<bool>,
+}
+
+/// The body of a [`Condition::RelatedEventMatch`] that uses user_id or user_localpart as a pattern.
+#[derive(Serialize, Debug, Clone)]
+pub struct RelatedEventMatchTypeCondition {
+ // This is only used if pattern_type exists (and thus key must exist), so is
+ // a bit simpler than RelatedEventMatchCondition.
+ pub key: Cow<'static, str>,
+ pub pattern_type: Cow<'static, EventMatchPatternType>,
pub rel_type: Cow<'static, str>,
#[serde(skip_serializing_if = "Option::is_none")]
pub include_fallbacks: Option<bool>,
@@ -501,8 +608,7 @@ impl FilteredPushRules {
fn test_serialize_condition() {
let condition = Condition::Known(KnownCondition::EventMatch(EventMatchCondition {
key: "content.body".into(),
- pattern: Some("coffee".into()),
- pattern_type: None,
+ pattern: "coffee".into(),
}));
let json = serde_json::to_string(&condition).unwrap();
@@ -516,7 +622,33 @@ fn test_serialize_condition() {
fn test_deserialize_condition() {
let json = r#"{"kind":"event_match","key":"content.body","pattern":"coffee"}"#;
- let _: Condition = serde_json::from_str(json).unwrap();
+ let condition: Condition = serde_json::from_str(json).unwrap();
+ assert!(matches!(
+ condition,
+ Condition::Known(KnownCondition::EventMatch(_))
+ ));
+}
+
+#[test]
+fn test_serialize_event_match_condition_with_pattern_type() {
+ let condition = Condition::Known(KnownCondition::EventMatchType(EventMatchTypeCondition {
+ key: "content.body".into(),
+ pattern_type: Cow::Owned(EventMatchPatternType::UserId),
+ }));
+
+ let json = serde_json::to_string(&condition).unwrap();
+ assert_eq!(
+ json,
+ r#"{"kind":"event_match","key":"content.body","pattern_type":"user_id"}"#
+ )
+}
+
+#[test]
+fn test_cannot_deserialize_event_match_condition_with_pattern_type() {
+ let json = r#"{"kind":"event_match","key":"content.body","pattern_type":"user_id"}"#;
+
+ let condition: Condition = serde_json::from_str(json).unwrap();
+ assert!(matches!(condition, Condition::Unknown(_)));
}
#[test]
@@ -531,6 +663,37 @@ fn test_deserialize_unstable_msc3664_condition() {
}
#[test]
+fn test_serialize_unstable_msc3664_condition_with_pattern_type() {
+ let condition = Condition::Known(KnownCondition::RelatedEventMatchType(
+ RelatedEventMatchTypeCondition {
+ key: "content.body".into(),
+ pattern_type: Cow::Owned(EventMatchPatternType::UserId),
+ rel_type: "m.in_reply_to".into(),
+ include_fallbacks: Some(true),
+ },
+ ));
+
+ let json = serde_json::to_string(&condition).unwrap();
+ assert_eq!(
+ json,
+ r#"{"kind":"im.nheko.msc3664.related_event_match","key":"content.body","pattern_type":"user_id","rel_type":"m.in_reply_to","include_fallbacks":true}"#
+ )
+}
+
+#[test]
+fn test_cannot_deserialize_unstable_msc3664_condition_with_pattern_type() {
+ let json = r#"{"kind":"im.nheko.msc3664.related_event_match","key":"content.body","pattern_type":"user_id","rel_type":"m.in_reply_to"}"#;
+
+ let condition: Condition = serde_json::from_str(json).unwrap();
+ // Since pattern is optional on RelatedEventMatch it deserializes it to that
+ // instead of RelatedEventMatchType.
+ assert!(matches!(
+ condition,
+ Condition::Known(KnownCondition::RelatedEventMatch(_))
+ ));
+}
+
+#[test]
fn test_deserialize_unstable_msc3931_condition() {
let json =
r#"{"kind":"org.matrix.msc3931.room_version_supports","feature":"org.example.feature"}"#;
@@ -543,24 +706,41 @@ fn test_deserialize_unstable_msc3931_condition() {
}
#[test]
-fn test_deserialize_unstable_msc3952_user_condition() {
- let json = r#"{"kind":"org.matrix.msc3952.is_user_mention"}"#;
+fn test_deserialize_event_property_is_condition() {
+ // A string condition should work.
+ let json = r#"{"kind":"event_property_is","key":"content.value","value":"foo"}"#;
let condition: Condition = serde_json::from_str(json).unwrap();
assert!(matches!(
condition,
- Condition::Known(KnownCondition::IsUserMention)
+ Condition::Known(KnownCondition::EventPropertyIs(_))
));
-}
-#[test]
-fn test_deserialize_unstable_msc3952_room_condition() {
- let json = r#"{"kind":"org.matrix.msc3952.is_room_mention"}"#;
+ // A boolean condition should work.
+ let json = r#"{"kind":"event_property_is","key":"content.value","value":true}"#;
+
+ let condition: Condition = serde_json::from_str(json).unwrap();
+ assert!(matches!(
+ condition,
+ Condition::Known(KnownCondition::EventPropertyIs(_))
+ ));
+
+ // An integer condition should work.
+ let json = r#"{"kind":"event_property_is","key":"content.value","value":1}"#;
+
+ let condition: Condition = serde_json::from_str(json).unwrap();
+ assert!(matches!(
+ condition,
+ Condition::Known(KnownCondition::EventPropertyIs(_))
+ ));
+
+ // A null condition should work
+ let json = r#"{"kind":"event_property_is","key":"content.value","value":null}"#;
let condition: Condition = serde_json::from_str(json).unwrap();
assert!(matches!(
condition,
- Condition::Known(KnownCondition::IsRoomMention)
+ Condition::Known(KnownCondition::EventPropertyIs(_))
));
}
|