forked from MirrorHub/synapse
Implementation for MSC3664: Pushrules for relations (#11804)
This commit is contained in:
parent
c9dffd5b33
commit
2d0ba3f89a
10 changed files with 454 additions and 17 deletions
1
changelog.d/11804.feature
Normal file
1
changelog.d/11804.feature
Normal file
|
@ -0,0 +1 @@
|
|||
Implement [MSC3664](https://github.com/matrix-org/matrix-doc/pull/3664). Contributed by Nico.
|
|
@ -25,6 +25,7 @@ use crate::push::Action;
|
|||
use crate::push::Condition;
|
||||
use crate::push::EventMatchCondition;
|
||||
use crate::push::PushRule;
|
||||
use crate::push::RelatedEventMatchCondition;
|
||||
use crate::push::SetTweak;
|
||||
use crate::push::TweakValue;
|
||||
|
||||
|
@ -114,6 +115,22 @@ pub const BASE_APPEND_OVERRIDE_RULES: &[PushRule] = &[
|
|||
default: true,
|
||||
default_enabled: true,
|
||||
},
|
||||
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")),
|
||||
rel_type: Cow::Borrowed("m.in_reply_to"),
|
||||
include_fallbacks: None,
|
||||
},
|
||||
))]),
|
||||
actions: Cow::Borrowed(&[Action::Notify, HIGHLIGHT_ACTION, SOUND_ACTION]),
|
||||
default: true,
|
||||
default_enabled: true,
|
||||
},
|
||||
PushRule {
|
||||
rule_id: Cow::Borrowed("global/override/.m.rule.contains_display_name"),
|
||||
priority_class: 5,
|
||||
|
|
|
@ -23,6 +23,7 @@ use regex::Regex;
|
|||
use super::{
|
||||
utils::{get_glob_matcher, get_localpart_from_id, GlobMatchType},
|
||||
Action, Condition, EventMatchCondition, FilteredPushRules, KnownCondition,
|
||||
RelatedEventMatchCondition,
|
||||
};
|
||||
|
||||
lazy_static! {
|
||||
|
@ -49,6 +50,13 @@ pub struct PushRuleEvaluator {
|
|||
/// The power level of the sender of the event, or None if event is an
|
||||
/// outlier.
|
||||
sender_power_level: Option<i64>,
|
||||
|
||||
/// The related events, indexed by relation type. Flattened in the same manner as
|
||||
/// `flattened_keys`.
|
||||
related_events_flattened: BTreeMap<String, BTreeMap<String, String>>,
|
||||
|
||||
/// If msc3664, push rules for related events, is enabled.
|
||||
related_event_match_enabled: bool,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
|
@ -60,6 +68,8 @@ impl PushRuleEvaluator {
|
|||
room_member_count: u64,
|
||||
sender_power_level: Option<i64>,
|
||||
notification_power_levels: BTreeMap<String, i64>,
|
||||
related_events_flattened: BTreeMap<String, BTreeMap<String, String>>,
|
||||
related_event_match_enabled: bool,
|
||||
) -> Result<Self, Error> {
|
||||
let body = flattened_keys
|
||||
.get("content.body")
|
||||
|
@ -72,6 +82,8 @@ impl PushRuleEvaluator {
|
|||
room_member_count,
|
||||
notification_power_levels,
|
||||
sender_power_level,
|
||||
related_events_flattened,
|
||||
related_event_match_enabled,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -156,6 +168,9 @@ impl PushRuleEvaluator {
|
|||
KnownCondition::EventMatch(event_match) => {
|
||||
self.match_event_match(event_match, user_id)?
|
||||
}
|
||||
KnownCondition::RelatedEventMatch(event_match) => {
|
||||
self.match_related_event_match(event_match, user_id)?
|
||||
}
|
||||
KnownCondition::ContainsDisplayName => {
|
||||
if let Some(dn) = display_name {
|
||||
if !dn.is_empty() {
|
||||
|
@ -239,6 +254,79 @@ impl PushRuleEvaluator {
|
|||
compiled_pattern.is_match(haystack)
|
||||
}
|
||||
|
||||
/// Evaluates a `related_event_match` condition. (MSC3664)
|
||||
fn match_related_event_match(
|
||||
&self,
|
||||
event_match: &RelatedEventMatchCondition,
|
||||
user_id: Option<&str>,
|
||||
) -> Result<bool, Error> {
|
||||
// First check if related event matching is enabled...
|
||||
if !self.related_event_match_enabled {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
// get the related event, fail if there is none.
|
||||
let event = if let Some(event) = self.related_events_flattened.get(&*event_match.rel_type) {
|
||||
event
|
||||
} else {
|
||||
return Ok(false);
|
||||
};
|
||||
|
||||
// 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")
|
||||
{
|
||||
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);
|
||||
};
|
||||
|
||||
let haystack = if let Some(haystack) = event.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)
|
||||
}
|
||||
|
||||
/// Match the member count against an 'is' condition
|
||||
/// The `is` condition can be things like '>2', '==3' or even just '4'.
|
||||
fn match_member_count(&self, is: &str) -> Result<bool, Error> {
|
||||
|
@ -267,8 +355,15 @@ impl PushRuleEvaluator {
|
|||
fn push_rule_evaluator() {
|
||||
let mut flattened_keys = BTreeMap::new();
|
||||
flattened_keys.insert("content.body".to_string(), "foo bar bob hello".to_string());
|
||||
let evaluator =
|
||||
PushRuleEvaluator::py_new(flattened_keys, 10, Some(0), BTreeMap::new()).unwrap();
|
||||
let evaluator = PushRuleEvaluator::py_new(
|
||||
flattened_keys,
|
||||
10,
|
||||
Some(0),
|
||||
BTreeMap::new(),
|
||||
BTreeMap::new(),
|
||||
true,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let result = evaluator.run(&FilteredPushRules::default(), None, Some("bob"));
|
||||
assert_eq!(result.len(), 3);
|
||||
|
|
|
@ -267,6 +267,8 @@ pub enum Condition {
|
|||
#[serde(tag = "kind")]
|
||||
pub enum KnownCondition {
|
||||
EventMatch(EventMatchCondition),
|
||||
#[serde(rename = "im.nheko.msc3664.related_event_match")]
|
||||
RelatedEventMatch(RelatedEventMatchCondition),
|
||||
ContainsDisplayName,
|
||||
RoomMemberCount {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
|
@ -299,6 +301,20 @@ pub struct EventMatchCondition {
|
|||
pub pattern_type: Option<Cow<'static, str>>,
|
||||
}
|
||||
|
||||
/// The body of a [`Condition::RelatedEventMatch`]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct RelatedEventMatchCondition {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub key: Option<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 rel_type: Cow<'static, str>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub include_fallbacks: Option<bool>,
|
||||
}
|
||||
|
||||
/// The collection of push rules for a user.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[pyclass(frozen)]
|
||||
|
@ -391,15 +407,21 @@ impl PushRules {
|
|||
pub struct FilteredPushRules {
|
||||
push_rules: PushRules,
|
||||
enabled_map: BTreeMap<String, bool>,
|
||||
msc3664_enabled: bool,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl FilteredPushRules {
|
||||
#[new]
|
||||
pub fn py_new(push_rules: PushRules, enabled_map: BTreeMap<String, bool>) -> Self {
|
||||
pub fn py_new(
|
||||
push_rules: PushRules,
|
||||
enabled_map: BTreeMap<String, bool>,
|
||||
msc3664_enabled: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
push_rules,
|
||||
enabled_map,
|
||||
msc3664_enabled,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -414,13 +436,25 @@ impl FilteredPushRules {
|
|||
/// Iterates over all the rules and their enabled state, including base
|
||||
/// rules, in the order they should be executed in.
|
||||
fn iter(&self) -> impl Iterator<Item = (&PushRule, bool)> {
|
||||
self.push_rules.iter().map(|r| {
|
||||
let enabled = *self
|
||||
.enabled_map
|
||||
.get(&*r.rule_id)
|
||||
.unwrap_or(&r.default_enabled);
|
||||
(r, enabled)
|
||||
})
|
||||
self.push_rules
|
||||
.iter()
|
||||
.filter(|rule| {
|
||||
// Ignore disabled experimental push rules
|
||||
if !self.msc3664_enabled
|
||||
&& rule.rule_id == "global/override/.im.nheko.msc3664.reply"
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
})
|
||||
.map(|r| {
|
||||
let enabled = *self
|
||||
.enabled_map
|
||||
.get(&*r.rule_id)
|
||||
.unwrap_or(&r.default_enabled);
|
||||
(r, enabled)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -446,6 +480,17 @@ fn test_deserialize_condition() {
|
|||
let _: Condition = serde_json::from_str(json).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deserialize_unstable_msc3664_condition() {
|
||||
let json = r#"{"kind":"im.nheko.msc3664.related_event_match","key":"content.body","pattern":"coffee","rel_type":"m.in_reply_to"}"#;
|
||||
|
||||
let condition: Condition = serde_json::from_str(json).unwrap();
|
||||
assert!(matches!(
|
||||
condition,
|
||||
Condition::Known(KnownCondition::RelatedEventMatch(_))
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deserialize_custom_condition() {
|
||||
let json = r#"{"kind":"custom_tag"}"#;
|
||||
|
|
|
@ -25,7 +25,9 @@ class PushRules:
|
|||
def rules(self) -> Collection[PushRule]: ...
|
||||
|
||||
class FilteredPushRules:
|
||||
def __init__(self, push_rules: PushRules, enabled_map: Dict[str, bool]): ...
|
||||
def __init__(
|
||||
self, push_rules: PushRules, enabled_map: Dict[str, bool], msc3664_enabled: bool
|
||||
): ...
|
||||
def rules(self) -> Collection[Tuple[PushRule, bool]]: ...
|
||||
|
||||
def get_base_rule_ids() -> Collection[str]: ...
|
||||
|
@ -37,6 +39,8 @@ class PushRuleEvaluator:
|
|||
room_member_count: int,
|
||||
sender_power_level: Optional[int],
|
||||
notification_power_levels: Mapping[str, int],
|
||||
related_events_flattened: Mapping[str, Mapping[str, str]],
|
||||
related_event_match_enabled: bool,
|
||||
): ...
|
||||
def run(
|
||||
self,
|
||||
|
|
|
@ -98,6 +98,9 @@ class ExperimentalConfig(Config):
|
|||
# MSC3773: Thread notifications
|
||||
self.msc3773_enabled: bool = experimental.get("msc3773_enabled", False)
|
||||
|
||||
# MSC3664: Pushrules to match on related events
|
||||
self.msc3664_enabled: bool = experimental.get("msc3664_enabled", False)
|
||||
|
||||
# MSC3848: Introduce errcodes for specific event sending failures
|
||||
self.msc3848_enabled: bool = experimental.get("msc3848_enabled", False)
|
||||
|
||||
|
|
|
@ -45,7 +45,6 @@ if TYPE_CHECKING:
|
|||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
push_rules_invalidation_counter = Counter(
|
||||
"synapse_push_bulk_push_rule_evaluator_push_rules_invalidation_counter", ""
|
||||
)
|
||||
|
@ -107,6 +106,8 @@ class BulkPushRuleEvaluator:
|
|||
self.clock = hs.get_clock()
|
||||
self._event_auth_handler = hs.get_event_auth_handler()
|
||||
|
||||
self._related_event_match_enabled = self.hs.config.experimental.msc3664_enabled
|
||||
|
||||
self.room_push_rule_cache_metrics = register_cache(
|
||||
"cache",
|
||||
"room_push_rule_cache",
|
||||
|
@ -218,6 +219,48 @@ class BulkPushRuleEvaluator:
|
|||
|
||||
return pl_event.content if pl_event else {}, sender_level
|
||||
|
||||
async def _related_events(self, event: EventBase) -> Dict[str, Dict[str, str]]:
|
||||
"""Fetches the related events for 'event'. Sets the im.vector.is_falling_back key if the event is from a fallback relation
|
||||
|
||||
Returns:
|
||||
Mapping of relation type to flattened events.
|
||||
"""
|
||||
related_events: Dict[str, Dict[str, str]] = {}
|
||||
if self._related_event_match_enabled:
|
||||
related_event_id = event.content.get("m.relates_to", {}).get("event_id")
|
||||
relation_type = event.content.get("m.relates_to", {}).get("rel_type")
|
||||
if related_event_id is not None and relation_type is not None:
|
||||
related_event = await self.store.get_event(
|
||||
related_event_id, allow_none=True
|
||||
)
|
||||
if related_event is not None:
|
||||
related_events[relation_type] = _flatten_dict(related_event)
|
||||
|
||||
reply_event_id = (
|
||||
event.content.get("m.relates_to", {})
|
||||
.get("m.in_reply_to", {})
|
||||
.get("event_id")
|
||||
)
|
||||
|
||||
# convert replies to pseudo relations
|
||||
if reply_event_id is not None:
|
||||
related_event = await self.store.get_event(
|
||||
reply_event_id, allow_none=True
|
||||
)
|
||||
|
||||
if related_event is not None:
|
||||
related_events["m.in_reply_to"] = _flatten_dict(related_event)
|
||||
|
||||
# indicate that this is from a fallback relation.
|
||||
if relation_type == "m.thread" and event.content.get(
|
||||
"m.relates_to", {}
|
||||
).get("is_falling_back", False):
|
||||
related_events["m.in_reply_to"][
|
||||
"im.vector.is_falling_back"
|
||||
] = ""
|
||||
|
||||
return related_events
|
||||
|
||||
async def action_for_events_by_user(
|
||||
self, events_and_context: List[Tuple[EventBase, EventContext]]
|
||||
) -> None:
|
||||
|
@ -286,6 +329,8 @@ class BulkPushRuleEvaluator:
|
|||
# the parent is part of a thread.
|
||||
thread_id = await self.store.get_thread_id(relation.parent_id)
|
||||
|
||||
related_events = await self._related_events(event)
|
||||
|
||||
# It's possible that old room versions have non-integer power levels (floats or
|
||||
# strings). Workaround this by explicitly converting to int.
|
||||
notification_levels = power_levels.get("notifications", {})
|
||||
|
@ -298,6 +343,8 @@ class BulkPushRuleEvaluator:
|
|||
room_member_count,
|
||||
sender_power_level,
|
||||
notification_levels,
|
||||
related_events,
|
||||
self._related_event_match_enabled,
|
||||
)
|
||||
|
||||
users = rules_by_user.keys()
|
||||
|
|
|
@ -77,6 +77,11 @@ class CapabilitiesRestServlet(RestServlet):
|
|||
"enabled": True,
|
||||
}
|
||||
|
||||
if self.config.experimental.msc3664_enabled:
|
||||
response["capabilities"]["im.nheko.msc3664.related_event_match"] = {
|
||||
"enabled": self.config.experimental.msc3664_enabled,
|
||||
}
|
||||
|
||||
return HTTPStatus.OK, response
|
||||
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@ from typing import (
|
|||
)
|
||||
|
||||
from synapse.api.errors import StoreError
|
||||
from synapse.config.homeserver import ExperimentalConfig
|
||||
from synapse.replication.slave.storage._slaved_id_tracker import SlavedIdTracker
|
||||
from synapse.storage._base import SQLBaseStore
|
||||
from synapse.storage.database import (
|
||||
|
@ -62,7 +63,9 @@ logger = logging.getLogger(__name__)
|
|||
|
||||
|
||||
def _load_rules(
|
||||
rawrules: List[JsonDict], enabled_map: Dict[str, bool]
|
||||
rawrules: List[JsonDict],
|
||||
enabled_map: Dict[str, bool],
|
||||
experimental_config: ExperimentalConfig,
|
||||
) -> FilteredPushRules:
|
||||
"""Take the DB rows returned from the DB and convert them into a full
|
||||
`FilteredPushRules` object.
|
||||
|
@ -80,7 +83,9 @@ def _load_rules(
|
|||
|
||||
push_rules = PushRules(ruleslist)
|
||||
|
||||
filtered_rules = FilteredPushRules(push_rules, enabled_map)
|
||||
filtered_rules = FilteredPushRules(
|
||||
push_rules, enabled_map, msc3664_enabled=experimental_config.msc3664_enabled
|
||||
)
|
||||
|
||||
return filtered_rules
|
||||
|
||||
|
@ -160,7 +165,7 @@ class PushRulesWorkerStore(
|
|||
|
||||
enabled_map = await self.get_push_rules_enabled_for_user(user_id)
|
||||
|
||||
return _load_rules(rows, enabled_map)
|
||||
return _load_rules(rows, enabled_map, self.hs.config.experimental)
|
||||
|
||||
async def get_push_rules_enabled_for_user(self, user_id: str) -> Dict[str, bool]:
|
||||
results = await self.db_pool.simple_select_list(
|
||||
|
@ -219,7 +224,9 @@ class PushRulesWorkerStore(
|
|||
results: Dict[str, FilteredPushRules] = {}
|
||||
|
||||
for user_id, rules in raw_rules.items():
|
||||
results[user_id] = _load_rules(rules, enabled_map_by_user.get(user_id, {}))
|
||||
results[user_id] = _load_rules(
|
||||
rules, enabled_map_by_user.get(user_id, {}), self.hs.config.experimental
|
||||
)
|
||||
|
||||
return results
|
||||
|
||||
|
|
|
@ -38,7 +38,9 @@ from tests.test_utils.event_injection import create_event, inject_member_event
|
|||
|
||||
|
||||
class PushRuleEvaluatorTestCase(unittest.TestCase):
|
||||
def _get_evaluator(self, content: JsonDict) -> PushRuleEvaluator:
|
||||
def _get_evaluator(
|
||||
self, content: JsonDict, related_events=None
|
||||
) -> PushRuleEvaluator:
|
||||
event = FrozenEvent(
|
||||
{
|
||||
"event_id": "$event_id",
|
||||
|
@ -58,6 +60,8 @@ class PushRuleEvaluatorTestCase(unittest.TestCase):
|
|||
room_member_count,
|
||||
sender_power_level,
|
||||
power_levels.get("notifications", {}),
|
||||
{} if related_events is None else related_events,
|
||||
True,
|
||||
)
|
||||
|
||||
def test_display_name(self) -> None:
|
||||
|
@ -292,6 +296,215 @@ class PushRuleEvaluatorTestCase(unittest.TestCase):
|
|||
{"sound": "default", "highlight": True},
|
||||
)
|
||||
|
||||
def test_related_event_match(self):
|
||||
evaluator = self._get_evaluator(
|
||||
{
|
||||
"m.relates_to": {
|
||||
"event_id": "$parent_event_id",
|
||||
"key": "😀",
|
||||
"rel_type": "m.annotation",
|
||||
"m.in_reply_to": {
|
||||
"event_id": "$parent_event_id",
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
"m.in_reply_to": {
|
||||
"event_id": "$parent_event_id",
|
||||
"type": "m.room.message",
|
||||
"sender": "@other_user:test",
|
||||
"room_id": "!room:test",
|
||||
"content.msgtype": "m.text",
|
||||
"content.body": "Original message",
|
||||
},
|
||||
"m.annotation": {
|
||||
"event_id": "$parent_event_id",
|
||||
"type": "m.room.message",
|
||||
"sender": "@other_user:test",
|
||||
"room_id": "!room:test",
|
||||
"content.msgtype": "m.text",
|
||||
"content.body": "Original message",
|
||||
},
|
||||
},
|
||||
)
|
||||
self.assertTrue(
|
||||
evaluator.matches(
|
||||
{
|
||||
"kind": "im.nheko.msc3664.related_event_match",
|
||||
"key": "sender",
|
||||
"rel_type": "m.in_reply_to",
|
||||
"pattern": "@other_user:test",
|
||||
},
|
||||
"@user:test",
|
||||
"display_name",
|
||||
)
|
||||
)
|
||||
self.assertFalse(
|
||||
evaluator.matches(
|
||||
{
|
||||
"kind": "im.nheko.msc3664.related_event_match",
|
||||
"key": "sender",
|
||||
"rel_type": "m.in_reply_to",
|
||||
"pattern": "@user:test",
|
||||
},
|
||||
"@other_user:test",
|
||||
"display_name",
|
||||
)
|
||||
)
|
||||
self.assertTrue(
|
||||
evaluator.matches(
|
||||
{
|
||||
"kind": "im.nheko.msc3664.related_event_match",
|
||||
"key": "sender",
|
||||
"rel_type": "m.annotation",
|
||||
"pattern": "@other_user:test",
|
||||
},
|
||||
"@other_user:test",
|
||||
"display_name",
|
||||
)
|
||||
)
|
||||
self.assertFalse(
|
||||
evaluator.matches(
|
||||
{
|
||||
"kind": "im.nheko.msc3664.related_event_match",
|
||||
"key": "sender",
|
||||
"rel_type": "m.in_reply_to",
|
||||
},
|
||||
"@user:test",
|
||||
"display_name",
|
||||
)
|
||||
)
|
||||
self.assertTrue(
|
||||
evaluator.matches(
|
||||
{
|
||||
"kind": "im.nheko.msc3664.related_event_match",
|
||||
"rel_type": "m.in_reply_to",
|
||||
},
|
||||
"@user:test",
|
||||
"display_name",
|
||||
)
|
||||
)
|
||||
self.assertFalse(
|
||||
evaluator.matches(
|
||||
{
|
||||
"kind": "im.nheko.msc3664.related_event_match",
|
||||
"rel_type": "m.replace",
|
||||
},
|
||||
"@other_user:test",
|
||||
"display_name",
|
||||
)
|
||||
)
|
||||
|
||||
def test_related_event_match_with_fallback(self):
|
||||
evaluator = self._get_evaluator(
|
||||
{
|
||||
"m.relates_to": {
|
||||
"event_id": "$parent_event_id",
|
||||
"key": "😀",
|
||||
"rel_type": "m.thread",
|
||||
"is_falling_back": True,
|
||||
"m.in_reply_to": {
|
||||
"event_id": "$parent_event_id",
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
"m.in_reply_to": {
|
||||
"event_id": "$parent_event_id",
|
||||
"type": "m.room.message",
|
||||
"sender": "@other_user:test",
|
||||
"room_id": "!room:test",
|
||||
"content.msgtype": "m.text",
|
||||
"content.body": "Original message",
|
||||
"im.vector.is_falling_back": "",
|
||||
},
|
||||
"m.thread": {
|
||||
"event_id": "$parent_event_id",
|
||||
"type": "m.room.message",
|
||||
"sender": "@other_user:test",
|
||||
"room_id": "!room:test",
|
||||
"content.msgtype": "m.text",
|
||||
"content.body": "Original message",
|
||||
},
|
||||
},
|
||||
)
|
||||
self.assertTrue(
|
||||
evaluator.matches(
|
||||
{
|
||||
"kind": "im.nheko.msc3664.related_event_match",
|
||||
"key": "sender",
|
||||
"rel_type": "m.in_reply_to",
|
||||
"pattern": "@other_user:test",
|
||||
"include_fallbacks": True,
|
||||
},
|
||||
"@user:test",
|
||||
"display_name",
|
||||
)
|
||||
)
|
||||
self.assertFalse(
|
||||
evaluator.matches(
|
||||
{
|
||||
"kind": "im.nheko.msc3664.related_event_match",
|
||||
"key": "sender",
|
||||
"rel_type": "m.in_reply_to",
|
||||
"pattern": "@other_user:test",
|
||||
"include_fallbacks": False,
|
||||
},
|
||||
"@user:test",
|
||||
"display_name",
|
||||
)
|
||||
)
|
||||
self.assertFalse(
|
||||
evaluator.matches(
|
||||
{
|
||||
"kind": "im.nheko.msc3664.related_event_match",
|
||||
"key": "sender",
|
||||
"rel_type": "m.in_reply_to",
|
||||
"pattern": "@other_user:test",
|
||||
},
|
||||
"@user:test",
|
||||
"display_name",
|
||||
)
|
||||
)
|
||||
|
||||
def test_related_event_match_no_related_event(self):
|
||||
evaluator = self._get_evaluator(
|
||||
{"msgtype": "m.text", "body": "Message without related event"}
|
||||
)
|
||||
self.assertFalse(
|
||||
evaluator.matches(
|
||||
{
|
||||
"kind": "im.nheko.msc3664.related_event_match",
|
||||
"key": "sender",
|
||||
"rel_type": "m.in_reply_to",
|
||||
"pattern": "@other_user:test",
|
||||
},
|
||||
"@user:test",
|
||||
"display_name",
|
||||
)
|
||||
)
|
||||
self.assertFalse(
|
||||
evaluator.matches(
|
||||
{
|
||||
"kind": "im.nheko.msc3664.related_event_match",
|
||||
"key": "sender",
|
||||
"rel_type": "m.in_reply_to",
|
||||
},
|
||||
"@user:test",
|
||||
"display_name",
|
||||
)
|
||||
)
|
||||
self.assertFalse(
|
||||
evaluator.matches(
|
||||
{
|
||||
"kind": "im.nheko.msc3664.related_event_match",
|
||||
"rel_type": "m.in_reply_to",
|
||||
},
|
||||
"@user:test",
|
||||
"display_name",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class TestBulkPushRuleEvaluator(unittest.HomeserverTestCase):
|
||||
"""Tests for the bulk push rule evaluator"""
|
||||
|
|
Loading…
Reference in a new issue