push: Add support for intentional mentions push rules
According to MSC3952
This commit is contained in:
parent
f8ed83aa53
commit
766fba75f9
@ -42,6 +42,7 @@ Improvements:
|
||||
- Add `MessageType::sanitize` behind the `unstable-sanitize` feature
|
||||
- Add `MatrixVersion::V1_7`
|
||||
- Stabilize support for annotations and reactions (MSC2677 / Matrix 1.7)
|
||||
- Add support for intentional mentions push rules (MSC3952 / Matrix 1.7)
|
||||
|
||||
# 0.11.3
|
||||
|
||||
|
@ -484,6 +484,7 @@ impl ConditionalPushRule {
|
||||
#[cfg(feature = "unstable-msc3932")]
|
||||
{
|
||||
// These 3 rules always apply.
|
||||
#[allow(deprecated)]
|
||||
if self.rule_id != PredefinedOverrideRuleId::Master.as_ref()
|
||||
&& self.rule_id != PredefinedOverrideRuleId::RoomNotif.as_ref()
|
||||
&& self.rule_id != PredefinedOverrideRuleId::ContainsDisplayName.as_ref()
|
||||
@ -503,6 +504,15 @@ impl ConditionalPushRule {
|
||||
}
|
||||
}
|
||||
|
||||
// The old mention rules are disabled when an m.mentions field is present.
|
||||
#[allow(deprecated)]
|
||||
if (self.rule_id == PredefinedOverrideRuleId::RoomNotif.as_ref()
|
||||
|| self.rule_id == PredefinedOverrideRuleId::ContainsDisplayName.as_ref())
|
||||
&& event.contains_mentions()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
self.conditions.iter().all(|cond| cond.applies(event, context))
|
||||
}
|
||||
}
|
||||
@ -601,6 +611,14 @@ impl PatternedPushRule {
|
||||
event: &FlattenedJson,
|
||||
context: &PushConditionRoomCtx,
|
||||
) -> bool {
|
||||
// The old mention rules are disabled when an m.mentions field is present.
|
||||
#[allow(deprecated)]
|
||||
if self.rule_id == PredefinedContentRuleId::ContainsUserName.as_ref()
|
||||
&& event.contains_mentions()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if event.get_str("sender").map_or(false, |sender| sender == context.user_id) {
|
||||
return false;
|
||||
}
|
||||
@ -971,7 +989,13 @@ mod tests {
|
||||
condition::{PushCondition, PushConditionRoomCtx, RoomMemberCountIs},
|
||||
AnyPushRule, ConditionalPushRule, PatternedPushRule, Ruleset, SimplePushRule,
|
||||
};
|
||||
use crate::{power_levels::NotificationPowerLevels, room_id, serde::Raw, user_id};
|
||||
use crate::{
|
||||
power_levels::NotificationPowerLevels,
|
||||
push::{PredefinedContentRuleId, PredefinedOverrideRuleId},
|
||||
room_id,
|
||||
serde::Raw,
|
||||
user_id,
|
||||
};
|
||||
|
||||
fn example_ruleset() -> Ruleset {
|
||||
let mut set = Ruleset::new();
|
||||
@ -1652,4 +1676,172 @@ mod tests {
|
||||
);
|
||||
assert_eq!(sound, "three");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(deprecated)]
|
||||
fn old_mentions_apply() {
|
||||
let set = Ruleset::server_default(user_id!("@jolly_jumper:server.name"));
|
||||
|
||||
let context = &PushConditionRoomCtx {
|
||||
room_id: room_id!("!far_west:server.name").to_owned(),
|
||||
member_count: uint!(100),
|
||||
user_id: user_id!("@jj:server.name").to_owned(),
|
||||
user_display_name: "Jolly Jumper".into(),
|
||||
users_power_levels: BTreeMap::new(),
|
||||
default_power_level: int!(50),
|
||||
notification_power_levels: NotificationPowerLevels { room: int!(50) },
|
||||
#[cfg(feature = "unstable-msc3931")]
|
||||
supported_features: Default::default(),
|
||||
};
|
||||
|
||||
let message = serde_json::from_str::<Raw<JsonValue>>(
|
||||
r#"{
|
||||
"content": {
|
||||
"body": "jolly_jumper"
|
||||
},
|
||||
"type": "m.room.message"
|
||||
}"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
set.get_match(&message, context).unwrap().rule_id(),
|
||||
PredefinedContentRuleId::ContainsUserName.as_ref()
|
||||
);
|
||||
|
||||
let message = serde_json::from_str::<Raw<JsonValue>>(
|
||||
r#"{
|
||||
"content": {
|
||||
"body": "jolly_jumper",
|
||||
"m.mentions": {}
|
||||
},
|
||||
"type": "m.room.message"
|
||||
}"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_ne!(
|
||||
set.get_match(&message, context).unwrap().rule_id(),
|
||||
PredefinedContentRuleId::ContainsUserName.as_ref()
|
||||
);
|
||||
|
||||
let message = serde_json::from_str::<Raw<JsonValue>>(
|
||||
r#"{
|
||||
"content": {
|
||||
"body": "Jolly Jumper"
|
||||
},
|
||||
"type": "m.room.message"
|
||||
}"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
set.get_match(&message, context).unwrap().rule_id(),
|
||||
PredefinedOverrideRuleId::ContainsDisplayName.as_ref()
|
||||
);
|
||||
|
||||
let message = serde_json::from_str::<Raw<JsonValue>>(
|
||||
r#"{
|
||||
"content": {
|
||||
"body": "Jolly Jumper",
|
||||
"m.mentions": {}
|
||||
},
|
||||
"type": "m.room.message"
|
||||
}"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_ne!(
|
||||
set.get_match(&message, context).unwrap().rule_id(),
|
||||
PredefinedOverrideRuleId::ContainsDisplayName.as_ref()
|
||||
);
|
||||
|
||||
let message = serde_json::from_str::<Raw<JsonValue>>(
|
||||
r#"{
|
||||
"content": {
|
||||
"body": "@room"
|
||||
},
|
||||
"sender": "@admin:server.name",
|
||||
"type": "m.room.message"
|
||||
}"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
set.get_match(&message, context).unwrap().rule_id(),
|
||||
PredefinedOverrideRuleId::RoomNotif.as_ref()
|
||||
);
|
||||
|
||||
let message = serde_json::from_str::<Raw<JsonValue>>(
|
||||
r#"{
|
||||
"content": {
|
||||
"body": "@room",
|
||||
"m.mentions": {}
|
||||
},
|
||||
"sender": "@admin:server.name",
|
||||
"type": "m.room.message"
|
||||
}"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_ne!(
|
||||
set.get_match(&message, context).unwrap().rule_id(),
|
||||
PredefinedOverrideRuleId::RoomNotif.as_ref()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn intentional_mentions_apply() {
|
||||
let set = Ruleset::server_default(user_id!("@jolly_jumper:server.name"));
|
||||
|
||||
let context = &PushConditionRoomCtx {
|
||||
room_id: room_id!("!far_west:server.name").to_owned(),
|
||||
member_count: uint!(100),
|
||||
user_id: user_id!("@jj:server.name").to_owned(),
|
||||
user_display_name: "Jolly Jumper".into(),
|
||||
users_power_levels: BTreeMap::new(),
|
||||
default_power_level: int!(50),
|
||||
notification_power_levels: NotificationPowerLevels { room: int!(50) },
|
||||
#[cfg(feature = "unstable-msc3931")]
|
||||
supported_features: Default::default(),
|
||||
};
|
||||
|
||||
let message = serde_json::from_str::<Raw<JsonValue>>(
|
||||
r#"{
|
||||
"content": {
|
||||
"body": "Hey jolly_jumper!",
|
||||
"m.mentions": {
|
||||
"user_ids": ["@jolly_jumper:server.name"]
|
||||
}
|
||||
},
|
||||
"sender": "@admin:server.name",
|
||||
"type": "m.room.message"
|
||||
}"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
set.get_match(&message, context).unwrap().rule_id(),
|
||||
PredefinedOverrideRuleId::IsUserMention.as_ref()
|
||||
);
|
||||
|
||||
let message = serde_json::from_str::<Raw<JsonValue>>(
|
||||
r#"{
|
||||
"content": {
|
||||
"body": "Listen room!",
|
||||
"m.mentions": {
|
||||
"room": true
|
||||
}
|
||||
},
|
||||
"sender": "@admin:server.name",
|
||||
"type": "m.room.message"
|
||||
}"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
set.get_match(&message, context).unwrap().rule_id(),
|
||||
PredefinedOverrideRuleId::IsRoomMention.as_ref()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -58,6 +58,13 @@ impl FlattenedJson {
|
||||
pub fn get_str(&self, path: &str) -> Option<&str> {
|
||||
self.map.get(path).and_then(|v| v.as_str())
|
||||
}
|
||||
|
||||
/// Whether this flattened JSON contains an `m.mentions` property under the `content` property.
|
||||
pub fn contains_mentions(&self) -> bool {
|
||||
self.map
|
||||
.keys()
|
||||
.any(|s| s == r"content.m\.mentions" || s.starts_with(r"content.m\.mentions."))
|
||||
}
|
||||
}
|
||||
|
||||
/// Escape a key for path matching.
|
||||
@ -382,4 +389,48 @@ mod tests {
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn contains_mentions() {
|
||||
let raw = serde_json::from_str::<Raw<JsonValue>>(
|
||||
r#"{
|
||||
"m.mentions": {},
|
||||
"content": {
|
||||
"body": "Text"
|
||||
}
|
||||
}"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let flattened = FlattenedJson::from_raw(&raw);
|
||||
assert!(!flattened.contains_mentions());
|
||||
|
||||
let raw = serde_json::from_str::<Raw<JsonValue>>(
|
||||
r#"{
|
||||
"content": {
|
||||
"body": "Text",
|
||||
"m.mentions": {}
|
||||
}
|
||||
}"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let flattened = FlattenedJson::from_raw(&raw);
|
||||
assert!(flattened.contains_mentions());
|
||||
|
||||
let raw = serde_json::from_str::<Raw<JsonValue>>(
|
||||
r#"{
|
||||
"content": {
|
||||
"body": "Text",
|
||||
"m.mentions": {
|
||||
"room": true
|
||||
}
|
||||
}
|
||||
}"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let flattened = FlattenedJson::from_raw(&raw);
|
||||
assert!(flattened.contains_mentions());
|
||||
}
|
||||
}
|
||||
|
@ -21,13 +21,21 @@ impl Ruleset {
|
||||
/// [predefined push rules]: https://spec.matrix.org/latest/client-server-api/#predefined-rules
|
||||
pub fn server_default(user_id: &UserId) -> Self {
|
||||
Self {
|
||||
content: [PatternedPushRule::contains_user_name(user_id)].into(),
|
||||
content: [
|
||||
#[allow(deprecated)]
|
||||
PatternedPushRule::contains_user_name(user_id),
|
||||
]
|
||||
.into(),
|
||||
override_: [
|
||||
ConditionalPushRule::master(),
|
||||
ConditionalPushRule::suppress_notices(),
|
||||
ConditionalPushRule::invite_for_me(user_id),
|
||||
ConditionalPushRule::member_event(),
|
||||
ConditionalPushRule::is_user_mention(user_id),
|
||||
#[allow(deprecated)]
|
||||
ConditionalPushRule::contains_display_name(),
|
||||
ConditionalPushRule::is_room_mention(),
|
||||
#[allow(deprecated)]
|
||||
ConditionalPushRule::roomnotif(),
|
||||
ConditionalPushRule::tombstone(),
|
||||
ConditionalPushRule::reaction(),
|
||||
@ -179,9 +187,33 @@ impl ConditionalPushRule {
|
||||
}
|
||||
}
|
||||
|
||||
/// Matches any message which contains the user’s Matrix ID in the list of `user_ids` under the
|
||||
/// `m.mentions` property.
|
||||
pub fn is_user_mention(user_id: &UserId) -> Self {
|
||||
Self {
|
||||
actions: vec![
|
||||
Notify,
|
||||
SetTweak(Tweak::Sound("default".to_owned())),
|
||||
SetTweak(Tweak::Highlight(true)),
|
||||
],
|
||||
default: true,
|
||||
enabled: true,
|
||||
rule_id: PredefinedOverrideRuleId::IsUserMention.to_string(),
|
||||
conditions: vec![EventPropertyContains {
|
||||
key: r"content.m\.mentions.user_ids".to_owned(),
|
||||
value: user_id.as_str().into(),
|
||||
}],
|
||||
}
|
||||
}
|
||||
|
||||
/// Matches any message whose content is unencrypted and contains the user's current display
|
||||
/// name in the room in which it was sent.
|
||||
///
|
||||
/// Since Matrix 1.7, this rule only matches if the event's content does not contain an
|
||||
/// `m.mentions` property.
|
||||
#[deprecated = "Since Matrix 1.7. Use the m.mentions property with ConditionalPushRule::is_user_mention() instead."]
|
||||
pub fn contains_display_name() -> Self {
|
||||
#[allow(deprecated)]
|
||||
Self {
|
||||
actions: vec![
|
||||
Notify,
|
||||
@ -211,9 +243,29 @@ impl ConditionalPushRule {
|
||||
}
|
||||
}
|
||||
|
||||
/// Matches any message from a sender with the proper power level with the `room` property of
|
||||
/// the `m.mentions` property set to `true`.
|
||||
pub fn is_room_mention() -> Self {
|
||||
Self {
|
||||
actions: vec![Notify, SetTweak(Tweak::Highlight(true))],
|
||||
default: true,
|
||||
enabled: true,
|
||||
rule_id: PredefinedOverrideRuleId::IsRoomMention.to_string(),
|
||||
conditions: vec![
|
||||
EventPropertyIs { key: r"content.m\.mentions.room".to_owned(), value: true.into() },
|
||||
SenderNotificationPermission { key: "room".to_owned() },
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
/// Matches any message whose content is unencrypted and contains the text `@room`, signifying
|
||||
/// the whole room should be notified of the event.
|
||||
///
|
||||
/// Since Matrix 1.7, this rule only matches if the event's content does not contain an
|
||||
/// `m.mentions` property.
|
||||
#[deprecated = "Since Matrix 1.7. Use the m.mentions property with ConditionalPushRule::is_room_mention() instead."]
|
||||
pub fn roomnotif() -> Self {
|
||||
#[allow(deprecated)]
|
||||
Self {
|
||||
actions: vec![Notify, SetTweak(Tweak::Highlight(true))],
|
||||
default: true,
|
||||
@ -260,7 +312,12 @@ impl ConditionalPushRule {
|
||||
impl PatternedPushRule {
|
||||
/// Matches any message whose content is unencrypted and contains the local part of the user's
|
||||
/// Matrix ID, separated by word boundaries.
|
||||
///
|
||||
/// Since Matrix 1.7, this rule only matches if the event's content does not contain an
|
||||
/// `m.mentions` property.
|
||||
#[deprecated = "Since Matrix 1.7. Use the m.mentions property with ConditionalPushRule::is_user_mention() instead."]
|
||||
pub fn contains_user_name(user_id: &UserId) -> Self {
|
||||
#[allow(deprecated)]
|
||||
Self {
|
||||
rule_id: PredefinedContentRuleId::ContainsUserName.to_string(),
|
||||
enabled: true,
|
||||
@ -453,11 +510,19 @@ pub enum PredefinedOverrideRuleId {
|
||||
/// `.m.rule.member_event`
|
||||
MemberEvent,
|
||||
|
||||
/// `.m.rule.is_user_mention`
|
||||
IsUserMention,
|
||||
|
||||
/// `.m.rule.contains_display_name`
|
||||
#[deprecated = "Since Matrix 1.7. Use the m.mentions property with PredefinedOverrideRuleId::IsUserMention instead."]
|
||||
ContainsDisplayName,
|
||||
|
||||
/// `.m.rule.is_room_mention`
|
||||
IsRoomMention,
|
||||
|
||||
/// `.m.rule.roomnotif`
|
||||
#[ruma_enum(rename = ".m.rule.roomnotif")]
|
||||
#[deprecated = "Since Matrix 1.7. Use the m.mentions property with PredefinedOverrideRuleId::IsRoomMention instead."]
|
||||
RoomNotif,
|
||||
|
||||
/// `.m.rule.tombstone`
|
||||
@ -522,6 +587,7 @@ pub enum PredefinedUnderrideRuleId {
|
||||
#[non_exhaustive]
|
||||
pub enum PredefinedContentRuleId {
|
||||
/// `.m.rule.contains_user_name`
|
||||
#[deprecated = "Since Matrix 1.7. Use the m.mentions property with PredefinedOverrideRuleId::IsUserMention instead."]
|
||||
ContainsUserName,
|
||||
|
||||
#[doc(hidden)]
|
||||
|
Loading…
x
Reference in New Issue
Block a user