push: Make power levels optional in PushConditionRoomCtx

This commit is contained in:
Kévin Commaille 2023-12-03 12:01:19 +01:00 committed by Kévin Commaille
parent 4efca6fba5
commit 90d3605b87
3 changed files with 99 additions and 41 deletions

View File

@ -1,5 +1,11 @@
# [unreleased]
Breaking changes:
- The power levels fields in `PushConditionRoomCtx` are grouped in an optional `power_levels` field.
If the field is missing, push rules that depend on it will never match. However, this allows to
match the `.m.rule.invite_for_me` push rule because usually the `invite_state` doesn't include
`m.room.power_levels`.
Improvements:
- Stabilize support for `.m.rule.suppress_edits` push rule (MSC3958 / Matrix 1.9)

View File

@ -38,8 +38,9 @@ pub use self::condition::RoomVersionFeature;
pub use self::{
action::{Action, Tweak},
condition::{
ComparisonOperator, FlattenedJson, FlattenedJsonValue, PushCondition, PushConditionRoomCtx,
RoomMemberCountIs, ScalarJsonValue, _CustomPushCondition,
ComparisonOperator, FlattenedJson, FlattenedJsonValue, PushCondition,
PushConditionPowerLevelsCtx, PushConditionRoomCtx, RoomMemberCountIs, ScalarJsonValue,
_CustomPushCondition,
},
iter::{AnyPushRule, AnyPushRuleRef, RulesetIntoIter, RulesetIter},
predefined::{
@ -988,7 +989,9 @@ mod tests {
use super::{
action::{Action, Tweak},
condition::{PushCondition, PushConditionRoomCtx, RoomMemberCountIs},
condition::{
PushCondition, PushConditionPowerLevelsCtx, PushConditionRoomCtx, RoomMemberCountIs,
},
AnyPushRule, ConditionalPushRule, PatternedPushRule, Ruleset, SimplePushRule,
};
use crate::{
@ -1016,6 +1019,14 @@ mod tests {
set
}
fn power_levels() -> PushConditionPowerLevelsCtx {
PushConditionPowerLevelsCtx {
users: BTreeMap::new(),
users_default: int!(50),
notifications: NotificationPowerLevels { room: int!(50) },
}
}
#[test]
fn iter() {
let mut set = example_ruleset();
@ -1431,9 +1442,7 @@ mod tests {
member_count: uint!(2),
user_id: owned_user_id!("@jj:server.name"),
user_display_name: "Jolly Jumper".into(),
users_power_levels: BTreeMap::new(),
default_power_level: int!(50),
notification_power_levels: NotificationPowerLevels { room: int!(50) },
power_levels: Some(power_levels()),
#[cfg(feature = "unstable-msc3931")]
supported_features: Default::default(),
};
@ -1443,9 +1452,7 @@ mod tests {
member_count: uint!(100),
user_id: owned_user_id!("@jj:server.name"),
user_display_name: "Jolly Jumper".into(),
users_power_levels: BTreeMap::new(),
default_power_level: int!(50),
notification_power_levels: NotificationPowerLevels { room: int!(50) },
power_levels: Some(power_levels()),
#[cfg(feature = "unstable-msc3931")]
supported_features: Default::default(),
};
@ -1536,9 +1543,7 @@ mod tests {
member_count: uint!(2),
user_id: owned_user_id!("@jj:server.name"),
user_display_name: "Jolly Jumper".into(),
users_power_levels: BTreeMap::new(),
default_power_level: int!(50),
notification_power_levels: NotificationPowerLevels { room: int!(50) },
power_levels: Some(power_levels()),
#[cfg(feature = "unstable-msc3931")]
supported_features: Default::default(),
};
@ -1677,9 +1682,7 @@ mod tests {
member_count: uint!(100),
user_id: owned_user_id!("@jj:server.name"),
user_display_name: "Jolly Jumper".into(),
users_power_levels: BTreeMap::new(),
default_power_level: int!(50),
notification_power_levels: NotificationPowerLevels { room: int!(50) },
power_levels: Some(power_levels()),
#[cfg(feature = "unstable-msc3931")]
supported_features: Default::default(),
};
@ -1789,9 +1792,7 @@ mod tests {
member_count: uint!(100),
user_id: owned_user_id!("@jj:server.name"),
user_display_name: "Jolly Jumper".into(),
users_power_levels: BTreeMap::new(),
default_power_level: int!(50),
notification_power_levels: NotificationPowerLevels { room: int!(50) },
power_levels: Some(power_levels()),
#[cfg(feature = "unstable-msc3931")]
supported_features: Default::default(),
};
@ -1834,4 +1835,37 @@ mod tests {
PredefinedOverrideRuleId::IsRoomMention.as_ref()
);
}
#[test]
fn invite_for_me_applies() {
let set = Ruleset::server_default(user_id!("@jolly_jumper:server.name"));
let context = &PushConditionRoomCtx {
room_id: owned_room_id!("!far_west:server.name"),
member_count: uint!(100),
user_id: owned_user_id!("@jj:server.name"),
user_display_name: "Jolly Jumper".into(),
// `invite_state` usually doesn't include the power levels.
power_levels: None,
#[cfg(feature = "unstable-msc3931")]
supported_features: Default::default(),
};
let message = serde_json::from_str::<Raw<JsonValue>>(
r#"{
"content": {
"membership": "invite"
},
"state_key": "@jolly_jumper:server.name",
"sender": "@admin:server.name",
"type": "m.room.member"
}"#,
)
.unwrap();
assert_eq!(
set.get_match(&message, context).unwrap().rule_id(),
PredefinedOverrideRuleId::InviteForMe.as_ref()
);
}
}

View File

@ -155,7 +155,8 @@ impl PushCondition {
/// # Arguments
///
/// * `event` - The flattened JSON representation of a room message event.
/// * `context` - The context of the room at the time of the event.
/// * `context` - The context of the room at the time of the event. If the power levels context
/// is missing from it, conditions that depend on it will never apply.
pub fn applies(&self, event: &FlattenedJson, context: &PushConditionRoomCtx) -> bool {
if event.get_str("sender").is_some_and(|sender| sender == context.user_id) {
return false;
@ -173,6 +174,10 @@ impl PushCondition {
}
Self::RoomMemberCount { is } => is.contains(&context.member_count),
Self::SenderNotificationPermission { key } => {
let Some(power_levels) = &context.power_levels else {
return false;
};
let sender_id = match event.get_str("sender") {
Some(v) => match <&UserId>::try_from(v) {
Ok(u) => u,
@ -181,12 +186,10 @@ impl PushCondition {
None => return false,
};
let sender_level = context
.users_power_levels
.get(sender_id)
.unwrap_or(&context.default_power_level);
let sender_level =
power_levels.users.get(sender_id).unwrap_or(&power_levels.users_default);
match context.notification_power_levels.get(key) {
match power_levels.notifications.get(key) {
Some(l) => sender_level >= l,
None => false,
}
@ -231,24 +234,34 @@ pub struct PushConditionRoomCtx {
/// The number of members in the room.
pub member_count: UInt,
/// The users matrix ID.
/// The user's matrix ID.
pub user_id: OwnedUserId,
/// The display name of the current user in the room.
pub user_display_name: String,
/// The room power levels context for the room.
///
/// If this is missing, push rules that require this will never match.
pub power_levels: Option<PushConditionPowerLevelsCtx>,
/// The list of features this room's version or the room itself supports.
#[cfg(feature = "unstable-msc3931")]
pub supported_features: Vec<RoomVersionFeature>,
}
/// The room power levels context to be able to test the corresponding push conditions.
#[derive(Clone, Debug)]
#[allow(clippy::exhaustive_structs)]
pub struct PushConditionPowerLevelsCtx {
/// The power levels of the users of the room.
pub users_power_levels: BTreeMap<OwnedUserId, Int>,
pub users: BTreeMap<OwnedUserId, Int>,
/// The default power level of the users of the room.
pub default_power_level: Int,
pub users_default: Int,
/// The notification power levels of the room.
pub notification_power_levels: NotificationPowerLevels,
#[cfg(feature = "unstable-msc3931")]
/// The list of features this room's version or the room itself supports.
pub supported_features: Vec<RoomVersionFeature>,
pub notifications: NotificationPowerLevels,
}
/// Additional functions for character matching.
@ -451,7 +464,10 @@ mod tests {
from_value as from_json_value, json, to_value as to_json_value, Value as JsonValue,
};
use super::{FlattenedJson, PushCondition, PushConditionRoomCtx, RoomMemberCountIs, StrExt};
use super::{
FlattenedJson, PushCondition, PushConditionPowerLevelsCtx, PushConditionRoomCtx,
RoomMemberCountIs, StrExt,
};
use crate::{
owned_room_id, owned_user_id, power_levels::NotificationPowerLevels, serde::Raw,
OwnedUserId,
@ -647,17 +663,21 @@ mod tests {
}
fn push_context() -> PushConditionRoomCtx {
let mut users_power_levels = BTreeMap::new();
users_power_levels.insert(sender(), int!(25));
let mut users = BTreeMap::new();
users.insert(sender(), int!(25));
let power_levels = PushConditionPowerLevelsCtx {
users,
users_default: int!(50),
notifications: NotificationPowerLevels { room: int!(50) },
};
PushConditionRoomCtx {
room_id: owned_room_id!("!room:server.name"),
member_count: uint!(3),
user_id: owned_user_id!("@gorilla:server.name"),
user_display_name: "Groovy Gorilla".into(),
users_power_levels,
default_power_level: int!(50),
notification_power_levels: NotificationPowerLevels { room: int!(50) },
power_levels: Some(power_levels),
#[cfg(feature = "unstable-msc3931")]
supported_features: Default::default(),
}
@ -776,9 +796,7 @@ mod tests {
member_count: uint!(3),
user_id: owned_user_id!("@gorilla:server.name"),
user_display_name: "Groovy Gorilla".into(),
users_power_levels: context_not_matching.users_power_levels.clone(),
default_power_level: int!(50),
notification_power_levels: NotificationPowerLevels { room: int!(50) },
power_levels: context_not_matching.power_levels.clone(),
supported_features: vec![super::RoomVersionFeature::ExtensibleEvents],
};