forked from MirrorHub/synapse
Make push rules use proper structures. (#13522)
This improves load times for push rules: | Version | Time per user | Time for 1k users | | -------------------- | ------------- | ----------------- | | Before | 138 µs | 138ms | | Now (with custom) | 2.11 µs | 2.11ms | | Now (without custom) | 49.7 ns | 0.05 ms | This therefore has a large impact on send times for rooms with large numbers of local users in the room.
This commit is contained in:
parent
d642ce4b32
commit
5442891cbc
8 changed files with 495 additions and 334 deletions
1
changelog.d/13522.misc
Normal file
1
changelog.d/13522.misc
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Improve performance of sending messages in rooms with thousands of local users.
|
|
@ -14,128 +14,224 @@
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import copy
|
|
||||||
from typing import Any, Dict, List
|
|
||||||
|
|
||||||
from synapse.push.rulekinds import PRIORITY_CLASS_INVERSE_MAP, PRIORITY_CLASS_MAP
|
|
||||||
|
|
||||||
|
|
||||||
def list_with_base_rules(rawrules: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
|
||||||
"""Combine the list of rules set by the user with the default push rules
|
|
||||||
|
|
||||||
Args:
|
|
||||||
rawrules: The rules the user has modified or set.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
A new list with the rules set by the user combined with the defaults.
|
|
||||||
"""
|
"""
|
||||||
ruleslist = []
|
Push rules is the system used to determine which events trigger a push (and a
|
||||||
|
bump in notification counts).
|
||||||
|
|
||||||
# Grab the base rules that the user has modified.
|
This consists of a list of "push rules" for each user, where a push rule is a
|
||||||
# The modified base rules have a priority_class of -1.
|
pair of "conditions" and "actions". When a user receives an event Synapse
|
||||||
modified_base_rules = {r["rule_id"]: r for r in rawrules if r["priority_class"] < 0}
|
iterates over the list of push rules until it finds one where all the conditions
|
||||||
|
match the event, at which point "actions" describe the outcome (e.g. notify,
|
||||||
|
highlight, etc).
|
||||||
|
|
||||||
# Remove the modified base rules from the list, They'll be added back
|
Push rules are split up into 5 different "kinds" (aka "priority classes"), which
|
||||||
# in the default positions in the list.
|
are run in order:
|
||||||
rawrules = [r for r in rawrules if r["priority_class"] >= 0]
|
1. Override — highest priority rules, e.g. always ignore notices
|
||||||
|
2. Content — content specific rules, e.g. @ notifications
|
||||||
|
3. Room — per room rules, e.g. enable/disable notifications for all messages
|
||||||
|
in a room
|
||||||
|
4. Sender — per sender rules, e.g. never notify for messages from a given
|
||||||
|
user
|
||||||
|
5. Underride — the lowest priority "default" rules, e.g. notify for every
|
||||||
|
message.
|
||||||
|
|
||||||
# shove the server default rules for each kind onto the end of each
|
The set of "base rules" are the list of rules that every user has by default. A
|
||||||
current_prio_class = list(PRIORITY_CLASS_INVERSE_MAP)[-1]
|
user can modify their copy of the push rules in one of three ways:
|
||||||
|
|
||||||
ruleslist.extend(
|
1. Adding a new push rule of a certain kind
|
||||||
make_base_prepend_rules(
|
2. Changing the actions of a base rule
|
||||||
PRIORITY_CLASS_INVERSE_MAP[current_prio_class], modified_base_rules
|
3. Enabling/disabling a base rule.
|
||||||
)
|
|
||||||
|
The base rules are split into whether they come before or after a particular
|
||||||
|
kind, so the order of push rule evaluation would be: base rules for before
|
||||||
|
"override" kind, user defined "override" rules, base rules after "override"
|
||||||
|
kind, etc, etc.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import itertools
|
||||||
|
from typing import Dict, Iterator, List, Mapping, Sequence, Tuple, Union
|
||||||
|
|
||||||
|
import attr
|
||||||
|
|
||||||
|
from synapse.config.experimental import ExperimentalConfig
|
||||||
|
from synapse.push.rulekinds import PRIORITY_CLASS_MAP
|
||||||
|
|
||||||
|
|
||||||
|
@attr.s(auto_attribs=True, slots=True, frozen=True)
|
||||||
|
class PushRule:
|
||||||
|
"""A push rule
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
rule_id: a unique ID for this rule
|
||||||
|
priority_class: what "kind" of push rule this is (see
|
||||||
|
`PRIORITY_CLASS_MAP` for mapping between int and kind)
|
||||||
|
conditions: the sequence of conditions that all need to match
|
||||||
|
actions: the actions to apply if all conditions are met
|
||||||
|
default: is this a base rule?
|
||||||
|
default_enabled: is this enabled by default?
|
||||||
|
"""
|
||||||
|
|
||||||
|
rule_id: str
|
||||||
|
priority_class: int
|
||||||
|
conditions: Sequence[Mapping[str, str]]
|
||||||
|
actions: Sequence[Union[str, Mapping]]
|
||||||
|
default: bool = False
|
||||||
|
default_enabled: bool = True
|
||||||
|
|
||||||
|
|
||||||
|
@attr.s(auto_attribs=True, slots=True, frozen=True, weakref_slot=False)
|
||||||
|
class PushRules:
|
||||||
|
"""A collection of push rules for an account.
|
||||||
|
|
||||||
|
Can be iterated over, producing push rules in priority order.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# A mapping from rule ID to push rule that overrides a base rule. These will
|
||||||
|
# be returned instead of the base rule.
|
||||||
|
overriden_base_rules: Dict[str, PushRule] = attr.Factory(dict)
|
||||||
|
|
||||||
|
# The following stores the custom push rules at each priority class.
|
||||||
|
#
|
||||||
|
# We keep these separate (rather than combining into one big list) to avoid
|
||||||
|
# copying the base rules around all the time.
|
||||||
|
override: List[PushRule] = attr.Factory(list)
|
||||||
|
content: List[PushRule] = attr.Factory(list)
|
||||||
|
room: List[PushRule] = attr.Factory(list)
|
||||||
|
sender: List[PushRule] = attr.Factory(list)
|
||||||
|
underride: List[PushRule] = attr.Factory(list)
|
||||||
|
|
||||||
|
def __iter__(self) -> Iterator[PushRule]:
|
||||||
|
# When iterating over the push rules we need to return the base rules
|
||||||
|
# interspersed at the correct spots.
|
||||||
|
for rule in itertools.chain(
|
||||||
|
BASE_PREPEND_OVERRIDE_RULES,
|
||||||
|
self.override,
|
||||||
|
BASE_APPEND_OVERRIDE_RULES,
|
||||||
|
self.content,
|
||||||
|
BASE_APPEND_CONTENT_RULES,
|
||||||
|
self.room,
|
||||||
|
self.sender,
|
||||||
|
self.underride,
|
||||||
|
BASE_APPEND_UNDERRIDE_RULES,
|
||||||
|
):
|
||||||
|
# Check if a base rule has been overriden by a custom rule. If so
|
||||||
|
# return that instead.
|
||||||
|
override_rule = self.overriden_base_rules.get(rule.rule_id)
|
||||||
|
if override_rule:
|
||||||
|
yield override_rule
|
||||||
|
else:
|
||||||
|
yield rule
|
||||||
|
|
||||||
|
def __len__(self) -> int:
|
||||||
|
# The length is mostly used by caches to get a sense of "size" / amount
|
||||||
|
# of memory this object is using, so we only count the number of custom
|
||||||
|
# rules.
|
||||||
|
return (
|
||||||
|
len(self.overriden_base_rules)
|
||||||
|
+ len(self.override)
|
||||||
|
+ len(self.content)
|
||||||
|
+ len(self.room)
|
||||||
|
+ len(self.sender)
|
||||||
|
+ len(self.underride)
|
||||||
)
|
)
|
||||||
|
|
||||||
for r in rawrules:
|
|
||||||
if r["priority_class"] < current_prio_class:
|
|
||||||
while r["priority_class"] < current_prio_class:
|
|
||||||
ruleslist.extend(
|
|
||||||
make_base_append_rules(
|
|
||||||
PRIORITY_CLASS_INVERSE_MAP[current_prio_class],
|
|
||||||
modified_base_rules,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
current_prio_class -= 1
|
|
||||||
if current_prio_class > 0:
|
|
||||||
ruleslist.extend(
|
|
||||||
make_base_prepend_rules(
|
|
||||||
PRIORITY_CLASS_INVERSE_MAP[current_prio_class],
|
|
||||||
modified_base_rules,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
ruleslist.append(r)
|
@attr.s(auto_attribs=True, slots=True, frozen=True, weakref_slot=False)
|
||||||
|
class FilteredPushRules:
|
||||||
|
"""A wrapper around `PushRules` that filters out disabled experimental push
|
||||||
|
rules, and includes the "enabled" state for each rule when iterated over.
|
||||||
|
"""
|
||||||
|
|
||||||
while current_prio_class > 0:
|
push_rules: PushRules
|
||||||
ruleslist.extend(
|
enabled_map: Dict[str, bool]
|
||||||
make_base_append_rules(
|
experimental_config: ExperimentalConfig
|
||||||
PRIORITY_CLASS_INVERSE_MAP[current_prio_class], modified_base_rules
|
|
||||||
)
|
|
||||||
)
|
|
||||||
current_prio_class -= 1
|
|
||||||
if current_prio_class > 0:
|
|
||||||
ruleslist.extend(
|
|
||||||
make_base_prepend_rules(
|
|
||||||
PRIORITY_CLASS_INVERSE_MAP[current_prio_class], modified_base_rules
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
return ruleslist
|
def __iter__(self) -> Iterator[Tuple[PushRule, bool]]:
|
||||||
|
for rule in self.push_rules:
|
||||||
|
if not _is_experimental_rule_enabled(
|
||||||
|
rule.rule_id, self.experimental_config
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
|
||||||
|
enabled = self.enabled_map.get(rule.rule_id, rule.default_enabled)
|
||||||
|
|
||||||
|
yield rule, enabled
|
||||||
|
|
||||||
|
def __len__(self) -> int:
|
||||||
|
return len(self.push_rules)
|
||||||
|
|
||||||
|
|
||||||
def make_base_append_rules(
|
DEFAULT_EMPTY_PUSH_RULES = PushRules()
|
||||||
kind: str, modified_base_rules: Dict[str, Dict[str, Any]]
|
|
||||||
) -> List[Dict[str, Any]]:
|
|
||||||
rules = []
|
|
||||||
|
|
||||||
if kind == "override":
|
|
||||||
rules = BASE_APPEND_OVERRIDE_RULES
|
|
||||||
elif kind == "underride":
|
|
||||||
rules = BASE_APPEND_UNDERRIDE_RULES
|
|
||||||
elif kind == "content":
|
|
||||||
rules = BASE_APPEND_CONTENT_RULES
|
|
||||||
|
|
||||||
# Copy the rules before modifying them
|
def compile_push_rules(rawrules: List[PushRule]) -> PushRules:
|
||||||
rules = copy.deepcopy(rules)
|
"""Given a set of custom push rules return a `PushRules` instance (which
|
||||||
for r in rules:
|
includes the base rules).
|
||||||
# Only modify the actions, keep the conditions the same.
|
"""
|
||||||
assert isinstance(r["rule_id"], str)
|
|
||||||
modified = modified_base_rules.get(r["rule_id"])
|
if not rawrules:
|
||||||
if modified:
|
# Fast path to avoid allocating empty lists when there are no custom
|
||||||
r["actions"] = modified["actions"]
|
# rules for the user.
|
||||||
|
return DEFAULT_EMPTY_PUSH_RULES
|
||||||
|
|
||||||
|
rules = PushRules()
|
||||||
|
|
||||||
|
for rule in rawrules:
|
||||||
|
# We need to decide which bucket each custom push rule goes into.
|
||||||
|
|
||||||
|
# If it has the same ID as a base rule then it overrides that...
|
||||||
|
overriden_base_rule = BASE_RULES_BY_ID.get(rule.rule_id)
|
||||||
|
if overriden_base_rule:
|
||||||
|
rules.overriden_base_rules[rule.rule_id] = attr.evolve(
|
||||||
|
overriden_base_rule, actions=rule.actions
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# ... otherwise it gets added to the appropriate priority class bucket
|
||||||
|
collection: List[PushRule]
|
||||||
|
if rule.priority_class == 5:
|
||||||
|
collection = rules.override
|
||||||
|
elif rule.priority_class == 4:
|
||||||
|
collection = rules.content
|
||||||
|
elif rule.priority_class == 3:
|
||||||
|
collection = rules.room
|
||||||
|
elif rule.priority_class == 2:
|
||||||
|
collection = rules.sender
|
||||||
|
elif rule.priority_class == 1:
|
||||||
|
collection = rules.underride
|
||||||
|
else:
|
||||||
|
raise Exception(f"Unknown priority class: {rule.priority_class}")
|
||||||
|
|
||||||
|
collection.append(rule)
|
||||||
|
|
||||||
return rules
|
return rules
|
||||||
|
|
||||||
|
|
||||||
def make_base_prepend_rules(
|
def _is_experimental_rule_enabled(
|
||||||
kind: str,
|
rule_id: str, experimental_config: ExperimentalConfig
|
||||||
modified_base_rules: Dict[str, Dict[str, Any]],
|
) -> bool:
|
||||||
) -> List[Dict[str, Any]]:
|
"""Used by `FilteredPushRules` to filter out experimental rules when they
|
||||||
rules = []
|
have not been enabled.
|
||||||
|
"""
|
||||||
if kind == "override":
|
if (
|
||||||
rules = BASE_PREPEND_OVERRIDE_RULES
|
rule_id == "global/override/.org.matrix.msc3786.rule.room.server_acl"
|
||||||
|
and not experimental_config.msc3786_enabled
|
||||||
# Copy the rules before modifying them
|
):
|
||||||
rules = copy.deepcopy(rules)
|
return False
|
||||||
for r in rules:
|
if (
|
||||||
# Only modify the actions, keep the conditions the same.
|
rule_id == "global/underride/.org.matrix.msc3772.thread_reply"
|
||||||
assert isinstance(r["rule_id"], str)
|
and not experimental_config.msc3772_enabled
|
||||||
modified = modified_base_rules.get(r["rule_id"])
|
):
|
||||||
if modified:
|
return False
|
||||||
r["actions"] = modified["actions"]
|
return True
|
||||||
|
|
||||||
return rules
|
|
||||||
|
|
||||||
|
|
||||||
# We have to annotate these types, otherwise mypy infers them as
|
BASE_APPEND_CONTENT_RULES = [
|
||||||
# `List[Dict[str, Sequence[Collection[str]]]]`.
|
PushRule(
|
||||||
BASE_APPEND_CONTENT_RULES: List[Dict[str, Any]] = [
|
default=True,
|
||||||
{
|
priority_class=PRIORITY_CLASS_MAP["content"],
|
||||||
"rule_id": "global/content/.m.rule.contains_user_name",
|
rule_id="global/content/.m.rule.contains_user_name",
|
||||||
"conditions": [
|
conditions=[
|
||||||
{
|
{
|
||||||
"kind": "event_match",
|
"kind": "event_match",
|
||||||
"key": "content.body",
|
"key": "content.body",
|
||||||
|
@ -143,29 +239,33 @@ BASE_APPEND_CONTENT_RULES: List[Dict[str, Any]] = [
|
||||||
"pattern_type": "user_localpart",
|
"pattern_type": "user_localpart",
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"actions": [
|
actions=[
|
||||||
"notify",
|
"notify",
|
||||||
{"set_tweak": "sound", "value": "default"},
|
{"set_tweak": "sound", "value": "default"},
|
||||||
{"set_tweak": "highlight"},
|
{"set_tweak": "highlight"},
|
||||||
],
|
],
|
||||||
}
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
BASE_PREPEND_OVERRIDE_RULES: List[Dict[str, Any]] = [
|
BASE_PREPEND_OVERRIDE_RULES = [
|
||||||
{
|
PushRule(
|
||||||
"rule_id": "global/override/.m.rule.master",
|
default=True,
|
||||||
"enabled": False,
|
priority_class=PRIORITY_CLASS_MAP["override"],
|
||||||
"conditions": [],
|
rule_id="global/override/.m.rule.master",
|
||||||
"actions": ["dont_notify"],
|
default_enabled=False,
|
||||||
}
|
conditions=[],
|
||||||
|
actions=["dont_notify"],
|
||||||
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
BASE_APPEND_OVERRIDE_RULES: List[Dict[str, Any]] = [
|
BASE_APPEND_OVERRIDE_RULES = [
|
||||||
{
|
PushRule(
|
||||||
"rule_id": "global/override/.m.rule.suppress_notices",
|
default=True,
|
||||||
"conditions": [
|
priority_class=PRIORITY_CLASS_MAP["override"],
|
||||||
|
rule_id="global/override/.m.rule.suppress_notices",
|
||||||
|
conditions=[
|
||||||
{
|
{
|
||||||
"kind": "event_match",
|
"kind": "event_match",
|
||||||
"key": "content.msgtype",
|
"key": "content.msgtype",
|
||||||
|
@ -173,13 +273,15 @@ BASE_APPEND_OVERRIDE_RULES: List[Dict[str, Any]] = [
|
||||||
"_cache_key": "_suppress_notices",
|
"_cache_key": "_suppress_notices",
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"actions": ["dont_notify"],
|
actions=["dont_notify"],
|
||||||
},
|
),
|
||||||
# NB. .m.rule.invite_for_me must be higher prio than .m.rule.member_event
|
# NB. .m.rule.invite_for_me must be higher prio than .m.rule.member_event
|
||||||
# otherwise invites will be matched by .m.rule.member_event
|
# otherwise invites will be matched by .m.rule.member_event
|
||||||
{
|
PushRule(
|
||||||
"rule_id": "global/override/.m.rule.invite_for_me",
|
default=True,
|
||||||
"conditions": [
|
priority_class=PRIORITY_CLASS_MAP["override"],
|
||||||
|
rule_id="global/override/.m.rule.invite_for_me",
|
||||||
|
conditions=[
|
||||||
{
|
{
|
||||||
"kind": "event_match",
|
"kind": "event_match",
|
||||||
"key": "type",
|
"key": "type",
|
||||||
|
@ -195,21 +297,23 @@ BASE_APPEND_OVERRIDE_RULES: List[Dict[str, Any]] = [
|
||||||
# Match the requester's MXID.
|
# Match the requester's MXID.
|
||||||
{"kind": "event_match", "key": "state_key", "pattern_type": "user_id"},
|
{"kind": "event_match", "key": "state_key", "pattern_type": "user_id"},
|
||||||
],
|
],
|
||||||
"actions": [
|
actions=[
|
||||||
"notify",
|
"notify",
|
||||||
{"set_tweak": "sound", "value": "default"},
|
{"set_tweak": "sound", "value": "default"},
|
||||||
{"set_tweak": "highlight", "value": False},
|
{"set_tweak": "highlight", "value": False},
|
||||||
],
|
],
|
||||||
},
|
),
|
||||||
# Will we sometimes want to know about people joining and leaving?
|
# Will we sometimes want to know about people joining and leaving?
|
||||||
# Perhaps: if so, this could be expanded upon. Seems the most usual case
|
# Perhaps: if so, this could be expanded upon. Seems the most usual case
|
||||||
# is that we don't though. We add this override rule so that even if
|
# is that we don't though. We add this override rule so that even if
|
||||||
# the room rule is set to notify, we don't get notifications about
|
# the room rule is set to notify, we don't get notifications about
|
||||||
# join/leave/avatar/displayname events.
|
# join/leave/avatar/displayname events.
|
||||||
# See also: https://matrix.org/jira/browse/SYN-607
|
# See also: https://matrix.org/jira/browse/SYN-607
|
||||||
{
|
PushRule(
|
||||||
"rule_id": "global/override/.m.rule.member_event",
|
default=True,
|
||||||
"conditions": [
|
priority_class=PRIORITY_CLASS_MAP["override"],
|
||||||
|
rule_id="global/override/.m.rule.member_event",
|
||||||
|
conditions=[
|
||||||
{
|
{
|
||||||
"kind": "event_match",
|
"kind": "event_match",
|
||||||
"key": "type",
|
"key": "type",
|
||||||
|
@ -217,24 +321,28 @@ BASE_APPEND_OVERRIDE_RULES: List[Dict[str, Any]] = [
|
||||||
"_cache_key": "_member",
|
"_cache_key": "_member",
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"actions": ["dont_notify"],
|
actions=["dont_notify"],
|
||||||
},
|
),
|
||||||
# This was changed from underride to override so it's closer in priority
|
# This was changed from underride to override so it's closer in priority
|
||||||
# to the content rules where the user name highlight rule lives. This
|
# to the content rules where the user name highlight rule lives. This
|
||||||
# way a room rule is lower priority than both but a custom override rule
|
# way a room rule is lower priority than both but a custom override rule
|
||||||
# is higher priority than both.
|
# is higher priority than both.
|
||||||
{
|
PushRule(
|
||||||
"rule_id": "global/override/.m.rule.contains_display_name",
|
default=True,
|
||||||
"conditions": [{"kind": "contains_display_name"}],
|
priority_class=PRIORITY_CLASS_MAP["override"],
|
||||||
"actions": [
|
rule_id="global/override/.m.rule.contains_display_name",
|
||||||
|
conditions=[{"kind": "contains_display_name"}],
|
||||||
|
actions=[
|
||||||
"notify",
|
"notify",
|
||||||
{"set_tweak": "sound", "value": "default"},
|
{"set_tweak": "sound", "value": "default"},
|
||||||
{"set_tweak": "highlight"},
|
{"set_tweak": "highlight"},
|
||||||
],
|
],
|
||||||
},
|
),
|
||||||
{
|
PushRule(
|
||||||
"rule_id": "global/override/.m.rule.roomnotif",
|
default=True,
|
||||||
"conditions": [
|
priority_class=PRIORITY_CLASS_MAP["override"],
|
||||||
|
rule_id="global/override/.m.rule.roomnotif",
|
||||||
|
conditions=[
|
||||||
{
|
{
|
||||||
"kind": "event_match",
|
"kind": "event_match",
|
||||||
"key": "content.body",
|
"key": "content.body",
|
||||||
|
@ -247,11 +355,13 @@ BASE_APPEND_OVERRIDE_RULES: List[Dict[str, Any]] = [
|
||||||
"_cache_key": "_roomnotif_pl",
|
"_cache_key": "_roomnotif_pl",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"actions": ["notify", {"set_tweak": "highlight", "value": True}],
|
actions=["notify", {"set_tweak": "highlight", "value": True}],
|
||||||
},
|
),
|
||||||
{
|
PushRule(
|
||||||
"rule_id": "global/override/.m.rule.tombstone",
|
default=True,
|
||||||
"conditions": [
|
priority_class=PRIORITY_CLASS_MAP["override"],
|
||||||
|
rule_id="global/override/.m.rule.tombstone",
|
||||||
|
conditions=[
|
||||||
{
|
{
|
||||||
"kind": "event_match",
|
"kind": "event_match",
|
||||||
"key": "type",
|
"key": "type",
|
||||||
|
@ -265,11 +375,13 @@ BASE_APPEND_OVERRIDE_RULES: List[Dict[str, Any]] = [
|
||||||
"_cache_key": "_tombstone_statekey",
|
"_cache_key": "_tombstone_statekey",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"actions": ["notify", {"set_tweak": "highlight", "value": True}],
|
actions=["notify", {"set_tweak": "highlight", "value": True}],
|
||||||
},
|
),
|
||||||
{
|
PushRule(
|
||||||
"rule_id": "global/override/.m.rule.reaction",
|
default=True,
|
||||||
"conditions": [
|
priority_class=PRIORITY_CLASS_MAP["override"],
|
||||||
|
rule_id="global/override/.m.rule.reaction",
|
||||||
|
conditions=[
|
||||||
{
|
{
|
||||||
"kind": "event_match",
|
"kind": "event_match",
|
||||||
"key": "type",
|
"key": "type",
|
||||||
|
@ -277,14 +389,16 @@ BASE_APPEND_OVERRIDE_RULES: List[Dict[str, Any]] = [
|
||||||
"_cache_key": "_reaction",
|
"_cache_key": "_reaction",
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"actions": ["dont_notify"],
|
actions=["dont_notify"],
|
||||||
},
|
),
|
||||||
# XXX: This is an experimental rule that is only enabled if msc3786_enabled
|
# XXX: This is an experimental rule that is only enabled if msc3786_enabled
|
||||||
# is enabled, if it is not the rule gets filtered out in _load_rules() in
|
# is enabled, if it is not the rule gets filtered out in _load_rules() in
|
||||||
# PushRulesWorkerStore
|
# PushRulesWorkerStore
|
||||||
{
|
PushRule(
|
||||||
"rule_id": "global/override/.org.matrix.msc3786.rule.room.server_acl",
|
default=True,
|
||||||
"conditions": [
|
priority_class=PRIORITY_CLASS_MAP["override"],
|
||||||
|
rule_id="global/override/.org.matrix.msc3786.rule.room.server_acl",
|
||||||
|
conditions=[
|
||||||
{
|
{
|
||||||
"kind": "event_match",
|
"kind": "event_match",
|
||||||
"key": "type",
|
"key": "type",
|
||||||
|
@ -298,15 +412,17 @@ BASE_APPEND_OVERRIDE_RULES: List[Dict[str, Any]] = [
|
||||||
"_cache_key": "_room_server_acl_state_key",
|
"_cache_key": "_room_server_acl_state_key",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"actions": [],
|
actions=[],
|
||||||
},
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
BASE_APPEND_UNDERRIDE_RULES: List[Dict[str, Any]] = [
|
BASE_APPEND_UNDERRIDE_RULES = [
|
||||||
{
|
PushRule(
|
||||||
"rule_id": "global/underride/.m.rule.call",
|
default=True,
|
||||||
"conditions": [
|
priority_class=PRIORITY_CLASS_MAP["underride"],
|
||||||
|
rule_id="global/underride/.m.rule.call",
|
||||||
|
conditions=[
|
||||||
{
|
{
|
||||||
"kind": "event_match",
|
"kind": "event_match",
|
||||||
"key": "type",
|
"key": "type",
|
||||||
|
@ -314,17 +430,19 @@ BASE_APPEND_UNDERRIDE_RULES: List[Dict[str, Any]] = [
|
||||||
"_cache_key": "_call",
|
"_cache_key": "_call",
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"actions": [
|
actions=[
|
||||||
"notify",
|
"notify",
|
||||||
{"set_tweak": "sound", "value": "ring"},
|
{"set_tweak": "sound", "value": "ring"},
|
||||||
{"set_tweak": "highlight", "value": False},
|
{"set_tweak": "highlight", "value": False},
|
||||||
],
|
],
|
||||||
},
|
),
|
||||||
# XXX: once m.direct is standardised everywhere, we should use it to detect
|
# XXX: once m.direct is standardised everywhere, we should use it to detect
|
||||||
# a DM from the user's perspective rather than this heuristic.
|
# a DM from the user's perspective rather than this heuristic.
|
||||||
{
|
PushRule(
|
||||||
"rule_id": "global/underride/.m.rule.room_one_to_one",
|
default=True,
|
||||||
"conditions": [
|
priority_class=PRIORITY_CLASS_MAP["underride"],
|
||||||
|
rule_id="global/underride/.m.rule.room_one_to_one",
|
||||||
|
conditions=[
|
||||||
{"kind": "room_member_count", "is": "2", "_cache_key": "member_count"},
|
{"kind": "room_member_count", "is": "2", "_cache_key": "member_count"},
|
||||||
{
|
{
|
||||||
"kind": "event_match",
|
"kind": "event_match",
|
||||||
|
@ -333,17 +451,19 @@ BASE_APPEND_UNDERRIDE_RULES: List[Dict[str, Any]] = [
|
||||||
"_cache_key": "_message",
|
"_cache_key": "_message",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"actions": [
|
actions=[
|
||||||
"notify",
|
"notify",
|
||||||
{"set_tweak": "sound", "value": "default"},
|
{"set_tweak": "sound", "value": "default"},
|
||||||
{"set_tweak": "highlight", "value": False},
|
{"set_tweak": "highlight", "value": False},
|
||||||
],
|
],
|
||||||
},
|
),
|
||||||
# XXX: this is going to fire for events which aren't m.room.messages
|
# XXX: this is going to fire for events which aren't m.room.messages
|
||||||
# but are encrypted (e.g. m.call.*)...
|
# but are encrypted (e.g. m.call.*)...
|
||||||
{
|
PushRule(
|
||||||
"rule_id": "global/underride/.m.rule.encrypted_room_one_to_one",
|
default=True,
|
||||||
"conditions": [
|
priority_class=PRIORITY_CLASS_MAP["underride"],
|
||||||
|
rule_id="global/underride/.m.rule.encrypted_room_one_to_one",
|
||||||
|
conditions=[
|
||||||
{"kind": "room_member_count", "is": "2", "_cache_key": "member_count"},
|
{"kind": "room_member_count", "is": "2", "_cache_key": "member_count"},
|
||||||
{
|
{
|
||||||
"kind": "event_match",
|
"kind": "event_match",
|
||||||
|
@ -352,15 +472,17 @@ BASE_APPEND_UNDERRIDE_RULES: List[Dict[str, Any]] = [
|
||||||
"_cache_key": "_encrypted",
|
"_cache_key": "_encrypted",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"actions": [
|
actions=[
|
||||||
"notify",
|
"notify",
|
||||||
{"set_tweak": "sound", "value": "default"},
|
{"set_tweak": "sound", "value": "default"},
|
||||||
{"set_tweak": "highlight", "value": False},
|
{"set_tweak": "highlight", "value": False},
|
||||||
],
|
],
|
||||||
},
|
),
|
||||||
{
|
PushRule(
|
||||||
"rule_id": "global/underride/.org.matrix.msc3772.thread_reply",
|
default=True,
|
||||||
"conditions": [
|
priority_class=PRIORITY_CLASS_MAP["underride"],
|
||||||
|
rule_id="global/underride/.org.matrix.msc3772.thread_reply",
|
||||||
|
conditions=[
|
||||||
{
|
{
|
||||||
"kind": "org.matrix.msc3772.relation_match",
|
"kind": "org.matrix.msc3772.relation_match",
|
||||||
"rel_type": "m.thread",
|
"rel_type": "m.thread",
|
||||||
|
@ -368,11 +490,13 @@ BASE_APPEND_UNDERRIDE_RULES: List[Dict[str, Any]] = [
|
||||||
"sender_type": "user_id",
|
"sender_type": "user_id",
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"actions": ["notify", {"set_tweak": "highlight", "value": False}],
|
actions=["notify", {"set_tweak": "highlight", "value": False}],
|
||||||
},
|
),
|
||||||
{
|
PushRule(
|
||||||
"rule_id": "global/underride/.m.rule.message",
|
default=True,
|
||||||
"conditions": [
|
priority_class=PRIORITY_CLASS_MAP["underride"],
|
||||||
|
rule_id="global/underride/.m.rule.message",
|
||||||
|
conditions=[
|
||||||
{
|
{
|
||||||
"kind": "event_match",
|
"kind": "event_match",
|
||||||
"key": "type",
|
"key": "type",
|
||||||
|
@ -380,13 +504,15 @@ BASE_APPEND_UNDERRIDE_RULES: List[Dict[str, Any]] = [
|
||||||
"_cache_key": "_message",
|
"_cache_key": "_message",
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"actions": ["notify", {"set_tweak": "highlight", "value": False}],
|
actions=["notify", {"set_tweak": "highlight", "value": False}],
|
||||||
},
|
),
|
||||||
# XXX: this is going to fire for events which aren't m.room.messages
|
# XXX: this is going to fire for events which aren't m.room.messages
|
||||||
# but are encrypted (e.g. m.call.*)...
|
# but are encrypted (e.g. m.call.*)...
|
||||||
{
|
PushRule(
|
||||||
"rule_id": "global/underride/.m.rule.encrypted",
|
default=True,
|
||||||
"conditions": [
|
priority_class=PRIORITY_CLASS_MAP["underride"],
|
||||||
|
rule_id="global/underride/.m.rule.encrypted",
|
||||||
|
conditions=[
|
||||||
{
|
{
|
||||||
"kind": "event_match",
|
"kind": "event_match",
|
||||||
"key": "type",
|
"key": "type",
|
||||||
|
@ -394,11 +520,13 @@ BASE_APPEND_UNDERRIDE_RULES: List[Dict[str, Any]] = [
|
||||||
"_cache_key": "_encrypted",
|
"_cache_key": "_encrypted",
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"actions": ["notify", {"set_tweak": "highlight", "value": False}],
|
actions=["notify", {"set_tweak": "highlight", "value": False}],
|
||||||
},
|
),
|
||||||
{
|
PushRule(
|
||||||
"rule_id": "global/underride/.im.vector.jitsi",
|
default=True,
|
||||||
"conditions": [
|
priority_class=PRIORITY_CLASS_MAP["underride"],
|
||||||
|
rule_id="global/underride/.im.vector.jitsi",
|
||||||
|
conditions=[
|
||||||
{
|
{
|
||||||
"kind": "event_match",
|
"kind": "event_match",
|
||||||
"key": "type",
|
"key": "type",
|
||||||
|
@ -418,29 +546,27 @@ BASE_APPEND_UNDERRIDE_RULES: List[Dict[str, Any]] = [
|
||||||
"_cache_key": "_is_state_event",
|
"_cache_key": "_is_state_event",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"actions": ["notify", {"set_tweak": "highlight", "value": False}],
|
actions=["notify", {"set_tweak": "highlight", "value": False}],
|
||||||
},
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
BASE_RULE_IDS = set()
|
BASE_RULE_IDS = set()
|
||||||
|
|
||||||
|
BASE_RULES_BY_ID: Dict[str, PushRule] = {}
|
||||||
|
|
||||||
for r in BASE_APPEND_CONTENT_RULES:
|
for r in BASE_APPEND_CONTENT_RULES:
|
||||||
r["priority_class"] = PRIORITY_CLASS_MAP["content"]
|
BASE_RULE_IDS.add(r.rule_id)
|
||||||
r["default"] = True
|
BASE_RULES_BY_ID[r.rule_id] = r
|
||||||
BASE_RULE_IDS.add(r["rule_id"])
|
|
||||||
|
|
||||||
for r in BASE_PREPEND_OVERRIDE_RULES:
|
for r in BASE_PREPEND_OVERRIDE_RULES:
|
||||||
r["priority_class"] = PRIORITY_CLASS_MAP["override"]
|
BASE_RULE_IDS.add(r.rule_id)
|
||||||
r["default"] = True
|
BASE_RULES_BY_ID[r.rule_id] = r
|
||||||
BASE_RULE_IDS.add(r["rule_id"])
|
|
||||||
|
|
||||||
for r in BASE_APPEND_OVERRIDE_RULES:
|
for r in BASE_APPEND_OVERRIDE_RULES:
|
||||||
r["priority_class"] = PRIORITY_CLASS_MAP["override"]
|
BASE_RULE_IDS.add(r.rule_id)
|
||||||
r["default"] = True
|
BASE_RULES_BY_ID[r.rule_id] = r
|
||||||
BASE_RULE_IDS.add(r["rule_id"])
|
|
||||||
|
|
||||||
for r in BASE_APPEND_UNDERRIDE_RULES:
|
for r in BASE_APPEND_UNDERRIDE_RULES:
|
||||||
r["priority_class"] = PRIORITY_CLASS_MAP["underride"]
|
BASE_RULE_IDS.add(r.rule_id)
|
||||||
r["default"] = True
|
BASE_RULES_BY_ID[r.rule_id] = r
|
||||||
BASE_RULE_IDS.add(r["rule_id"])
|
|
||||||
|
|
|
@ -15,7 +15,18 @@
|
||||||
|
|
||||||
import itertools
|
import itertools
|
||||||
import logging
|
import logging
|
||||||
from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Optional, Set, Tuple, Union
|
from typing import (
|
||||||
|
TYPE_CHECKING,
|
||||||
|
Collection,
|
||||||
|
Dict,
|
||||||
|
Iterable,
|
||||||
|
List,
|
||||||
|
Mapping,
|
||||||
|
Optional,
|
||||||
|
Set,
|
||||||
|
Tuple,
|
||||||
|
Union,
|
||||||
|
)
|
||||||
|
|
||||||
from prometheus_client import Counter
|
from prometheus_client import Counter
|
||||||
|
|
||||||
|
@ -30,6 +41,7 @@ from synapse.util.caches import register_cache
|
||||||
from synapse.util.metrics import measure_func
|
from synapse.util.metrics import measure_func
|
||||||
from synapse.visibility import filter_event_for_clients_with_state
|
from synapse.visibility import filter_event_for_clients_with_state
|
||||||
|
|
||||||
|
from .baserules import FilteredPushRules, PushRule
|
||||||
from .push_rule_evaluator import PushRuleEvaluatorForEvent
|
from .push_rule_evaluator import PushRuleEvaluatorForEvent
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
@ -112,7 +124,7 @@ class BulkPushRuleEvaluator:
|
||||||
async def _get_rules_for_event(
|
async def _get_rules_for_event(
|
||||||
self,
|
self,
|
||||||
event: EventBase,
|
event: EventBase,
|
||||||
) -> Dict[str, List[Dict[str, Any]]]:
|
) -> Dict[str, FilteredPushRules]:
|
||||||
"""Get the push rules for all users who may need to be notified about
|
"""Get the push rules for all users who may need to be notified about
|
||||||
the event.
|
the event.
|
||||||
|
|
||||||
|
@ -186,7 +198,7 @@ class BulkPushRuleEvaluator:
|
||||||
return pl_event.content if pl_event else {}, sender_level
|
return pl_event.content if pl_event else {}, sender_level
|
||||||
|
|
||||||
async def _get_mutual_relations(
|
async def _get_mutual_relations(
|
||||||
self, event: EventBase, rules: Iterable[Dict[str, Any]]
|
self, event: EventBase, rules: Iterable[Tuple[PushRule, bool]]
|
||||||
) -> Dict[str, Set[Tuple[str, str]]]:
|
) -> Dict[str, Set[Tuple[str, str]]]:
|
||||||
"""
|
"""
|
||||||
Fetch event metadata for events which related to the same event as the given event.
|
Fetch event metadata for events which related to the same event as the given event.
|
||||||
|
@ -216,12 +228,11 @@ class BulkPushRuleEvaluator:
|
||||||
|
|
||||||
# Pre-filter to figure out which relation types are interesting.
|
# Pre-filter to figure out which relation types are interesting.
|
||||||
rel_types = set()
|
rel_types = set()
|
||||||
for rule in rules:
|
for rule, enabled in rules:
|
||||||
# Skip disabled rules.
|
if not enabled:
|
||||||
if "enabled" in rule and not rule["enabled"]:
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
for condition in rule["conditions"]:
|
for condition in rule.conditions:
|
||||||
if condition["kind"] != "org.matrix.msc3772.relation_match":
|
if condition["kind"] != "org.matrix.msc3772.relation_match":
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
@ -254,7 +265,7 @@ class BulkPushRuleEvaluator:
|
||||||
count_as_unread = _should_count_as_unread(event, context)
|
count_as_unread = _should_count_as_unread(event, context)
|
||||||
|
|
||||||
rules_by_user = await self._get_rules_for_event(event)
|
rules_by_user = await self._get_rules_for_event(event)
|
||||||
actions_by_user: Dict[str, List[Union[dict, str]]] = {}
|
actions_by_user: Dict[str, Collection[Union[Mapping, str]]] = {}
|
||||||
|
|
||||||
room_member_count = await self.store.get_number_joined_users_in_room(
|
room_member_count = await self.store.get_number_joined_users_in_room(
|
||||||
event.room_id
|
event.room_id
|
||||||
|
@ -317,15 +328,13 @@ class BulkPushRuleEvaluator:
|
||||||
# current user, it'll be added to the dict later.
|
# current user, it'll be added to the dict later.
|
||||||
actions_by_user[uid] = []
|
actions_by_user[uid] = []
|
||||||
|
|
||||||
for rule in rules:
|
for rule, enabled in rules:
|
||||||
if "enabled" in rule and not rule["enabled"]:
|
if not enabled:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
matches = evaluator.check_conditions(
|
matches = evaluator.check_conditions(rule.conditions, uid, display_name)
|
||||||
rule["conditions"], uid, display_name
|
|
||||||
)
|
|
||||||
if matches:
|
if matches:
|
||||||
actions = [x for x in rule["actions"] if x != "dont_notify"]
|
actions = [x for x in rule.actions if x != "dont_notify"]
|
||||||
if actions and "notify" in actions:
|
if actions and "notify" in actions:
|
||||||
# Push rules say we should notify the user of this event
|
# Push rules say we should notify the user of this event
|
||||||
actions_by_user[uid] = actions
|
actions_by_user[uid] = actions
|
||||||
|
|
|
@ -18,16 +18,15 @@ from typing import Any, Dict, List, Optional
|
||||||
from synapse.push.rulekinds import PRIORITY_CLASS_INVERSE_MAP, PRIORITY_CLASS_MAP
|
from synapse.push.rulekinds import PRIORITY_CLASS_INVERSE_MAP, PRIORITY_CLASS_MAP
|
||||||
from synapse.types import UserID
|
from synapse.types import UserID
|
||||||
|
|
||||||
|
from .baserules import FilteredPushRules, PushRule
|
||||||
|
|
||||||
|
|
||||||
def format_push_rules_for_user(
|
def format_push_rules_for_user(
|
||||||
user: UserID, ruleslist: List
|
user: UserID, ruleslist: FilteredPushRules
|
||||||
) -> Dict[str, Dict[str, list]]:
|
) -> Dict[str, Dict[str, list]]:
|
||||||
"""Converts a list of rawrules and a enabled map into nested dictionaries
|
"""Converts a list of rawrules and a enabled map into nested dictionaries
|
||||||
to match the Matrix client-server format for push rules"""
|
to match the Matrix client-server format for push rules"""
|
||||||
|
|
||||||
# We're going to be mutating this a lot, so do a deep copy
|
|
||||||
ruleslist = copy.deepcopy(ruleslist)
|
|
||||||
|
|
||||||
rules: Dict[str, Dict[str, List[Dict[str, Any]]]] = {
|
rules: Dict[str, Dict[str, List[Dict[str, Any]]]] = {
|
||||||
"global": {},
|
"global": {},
|
||||||
"device": {},
|
"device": {},
|
||||||
|
@ -35,11 +34,30 @@ def format_push_rules_for_user(
|
||||||
|
|
||||||
rules["global"] = _add_empty_priority_class_arrays(rules["global"])
|
rules["global"] = _add_empty_priority_class_arrays(rules["global"])
|
||||||
|
|
||||||
for r in ruleslist:
|
for r, enabled in ruleslist:
|
||||||
template_name = _priority_class_to_template_name(r["priority_class"])
|
template_name = _priority_class_to_template_name(r.priority_class)
|
||||||
|
|
||||||
|
rulearray = rules["global"][template_name]
|
||||||
|
|
||||||
|
template_rule = _rule_to_template(r)
|
||||||
|
if not template_rule:
|
||||||
|
continue
|
||||||
|
|
||||||
|
rulearray.append(template_rule)
|
||||||
|
|
||||||
|
template_rule["enabled"] = enabled
|
||||||
|
|
||||||
|
if "conditions" not in template_rule:
|
||||||
|
# Not all formatted rules have explicit conditions, e.g. "room"
|
||||||
|
# rules omit them as they can be derived from the kind and rule ID.
|
||||||
|
#
|
||||||
|
# If the formatted rule has no conditions then we can skip the
|
||||||
|
# formatting of conditions.
|
||||||
|
continue
|
||||||
|
|
||||||
# Remove internal stuff.
|
# Remove internal stuff.
|
||||||
for c in r["conditions"]:
|
template_rule["conditions"] = copy.deepcopy(template_rule["conditions"])
|
||||||
|
for c in template_rule["conditions"]:
|
||||||
c.pop("_cache_key", None)
|
c.pop("_cache_key", None)
|
||||||
|
|
||||||
pattern_type = c.pop("pattern_type", None)
|
pattern_type = c.pop("pattern_type", None)
|
||||||
|
@ -52,16 +70,6 @@ def format_push_rules_for_user(
|
||||||
if sender_type == "user_id":
|
if sender_type == "user_id":
|
||||||
c["sender"] = user.to_string()
|
c["sender"] = user.to_string()
|
||||||
|
|
||||||
rulearray = rules["global"][template_name]
|
|
||||||
|
|
||||||
template_rule = _rule_to_template(r)
|
|
||||||
if template_rule:
|
|
||||||
if "enabled" in r:
|
|
||||||
template_rule["enabled"] = r["enabled"]
|
|
||||||
else:
|
|
||||||
template_rule["enabled"] = True
|
|
||||||
rulearray.append(template_rule)
|
|
||||||
|
|
||||||
return rules
|
return rules
|
||||||
|
|
||||||
|
|
||||||
|
@ -71,24 +79,24 @@ def _add_empty_priority_class_arrays(d: Dict[str, list]) -> Dict[str, list]:
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
|
||||||
def _rule_to_template(rule: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
def _rule_to_template(rule: PushRule) -> Optional[Dict[str, Any]]:
|
||||||
unscoped_rule_id = None
|
templaterule: Dict[str, Any]
|
||||||
if "rule_id" in rule:
|
|
||||||
unscoped_rule_id = _rule_id_from_namespaced(rule["rule_id"])
|
|
||||||
|
|
||||||
template_name = _priority_class_to_template_name(rule["priority_class"])
|
unscoped_rule_id = _rule_id_from_namespaced(rule.rule_id)
|
||||||
|
|
||||||
|
template_name = _priority_class_to_template_name(rule.priority_class)
|
||||||
if template_name in ["override", "underride"]:
|
if template_name in ["override", "underride"]:
|
||||||
templaterule = {k: rule[k] for k in ["conditions", "actions"]}
|
templaterule = {"conditions": rule.conditions, "actions": rule.actions}
|
||||||
elif template_name in ["sender", "room"]:
|
elif template_name in ["sender", "room"]:
|
||||||
templaterule = {"actions": rule["actions"]}
|
templaterule = {"actions": rule.actions}
|
||||||
unscoped_rule_id = rule["conditions"][0]["pattern"]
|
unscoped_rule_id = rule.conditions[0]["pattern"]
|
||||||
elif template_name == "content":
|
elif template_name == "content":
|
||||||
if len(rule["conditions"]) != 1:
|
if len(rule.conditions) != 1:
|
||||||
return None
|
return None
|
||||||
thecond = rule["conditions"][0]
|
thecond = rule.conditions[0]
|
||||||
if "pattern" not in thecond:
|
if "pattern" not in thecond:
|
||||||
return None
|
return None
|
||||||
templaterule = {"actions": rule["actions"]}
|
templaterule = {"actions": rule.actions}
|
||||||
templaterule["pattern"] = thecond["pattern"]
|
templaterule["pattern"] = thecond["pattern"]
|
||||||
else:
|
else:
|
||||||
# This should not be reached unless this function is not kept in sync
|
# This should not be reached unless this function is not kept in sync
|
||||||
|
@ -97,8 +105,8 @@ def _rule_to_template(rule: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
||||||
|
|
||||||
if unscoped_rule_id:
|
if unscoped_rule_id:
|
||||||
templaterule["rule_id"] = unscoped_rule_id
|
templaterule["rule_id"] = unscoped_rule_id
|
||||||
if "default" in rule:
|
if rule.default:
|
||||||
templaterule["default"] = rule["default"]
|
templaterule["default"] = True
|
||||||
return templaterule
|
return templaterule
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,18 @@
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
from typing import Any, Dict, List, Mapping, Optional, Pattern, Set, Tuple, Union
|
from typing import (
|
||||||
|
Any,
|
||||||
|
Dict,
|
||||||
|
List,
|
||||||
|
Mapping,
|
||||||
|
Optional,
|
||||||
|
Pattern,
|
||||||
|
Sequence,
|
||||||
|
Set,
|
||||||
|
Tuple,
|
||||||
|
Union,
|
||||||
|
)
|
||||||
|
|
||||||
from matrix_common.regex import glob_to_regex, to_word_pattern
|
from matrix_common.regex import glob_to_regex, to_word_pattern
|
||||||
|
|
||||||
|
@ -32,14 +43,14 @@ INEQUALITY_EXPR = re.compile("^([=<>]*)([0-9]*)$")
|
||||||
|
|
||||||
|
|
||||||
def _room_member_count(
|
def _room_member_count(
|
||||||
ev: EventBase, condition: Dict[str, Any], room_member_count: int
|
ev: EventBase, condition: Mapping[str, Any], room_member_count: int
|
||||||
) -> bool:
|
) -> bool:
|
||||||
return _test_ineq_condition(condition, room_member_count)
|
return _test_ineq_condition(condition, room_member_count)
|
||||||
|
|
||||||
|
|
||||||
def _sender_notification_permission(
|
def _sender_notification_permission(
|
||||||
ev: EventBase,
|
ev: EventBase,
|
||||||
condition: Dict[str, Any],
|
condition: Mapping[str, Any],
|
||||||
sender_power_level: int,
|
sender_power_level: int,
|
||||||
power_levels: Dict[str, Union[int, Dict[str, int]]],
|
power_levels: Dict[str, Union[int, Dict[str, int]]],
|
||||||
) -> bool:
|
) -> bool:
|
||||||
|
@ -54,7 +65,7 @@ def _sender_notification_permission(
|
||||||
return sender_power_level >= room_notif_level
|
return sender_power_level >= room_notif_level
|
||||||
|
|
||||||
|
|
||||||
def _test_ineq_condition(condition: Dict[str, Any], number: int) -> bool:
|
def _test_ineq_condition(condition: Mapping[str, Any], number: int) -> bool:
|
||||||
if "is" not in condition:
|
if "is" not in condition:
|
||||||
return False
|
return False
|
||||||
m = INEQUALITY_EXPR.match(condition["is"])
|
m = INEQUALITY_EXPR.match(condition["is"])
|
||||||
|
@ -137,7 +148,7 @@ class PushRuleEvaluatorForEvent:
|
||||||
self._condition_cache: Dict[str, bool] = {}
|
self._condition_cache: Dict[str, bool] = {}
|
||||||
|
|
||||||
def check_conditions(
|
def check_conditions(
|
||||||
self, conditions: List[dict], uid: str, display_name: Optional[str]
|
self, conditions: Sequence[Mapping], uid: str, display_name: Optional[str]
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""
|
"""
|
||||||
Returns true if a user's conditions/user ID/display name match the event.
|
Returns true if a user's conditions/user ID/display name match the event.
|
||||||
|
@ -169,7 +180,7 @@ class PushRuleEvaluatorForEvent:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def matches(
|
def matches(
|
||||||
self, condition: Dict[str, Any], user_id: str, display_name: Optional[str]
|
self, condition: Mapping[str, Any], user_id: str, display_name: Optional[str]
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""
|
"""
|
||||||
Returns true if a user's condition/user ID/display name match the event.
|
Returns true if a user's condition/user ID/display name match the event.
|
||||||
|
@ -204,7 +215,7 @@ class PushRuleEvaluatorForEvent:
|
||||||
# endpoint with an unknown kind, see _rule_tuple_from_request_object.
|
# endpoint with an unknown kind, see _rule_tuple_from_request_object.
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _event_match(self, condition: dict, user_id: str) -> bool:
|
def _event_match(self, condition: Mapping, user_id: str) -> bool:
|
||||||
"""
|
"""
|
||||||
Check an "event_match" push rule condition.
|
Check an "event_match" push rule condition.
|
||||||
|
|
||||||
|
@ -269,7 +280,7 @@ class PushRuleEvaluatorForEvent:
|
||||||
|
|
||||||
return bool(r.search(body))
|
return bool(r.search(body))
|
||||||
|
|
||||||
def _relation_match(self, condition: dict, user_id: str) -> bool:
|
def _relation_match(self, condition: Mapping, user_id: str) -> bool:
|
||||||
"""
|
"""
|
||||||
Check an "relation_match" push rule condition.
|
Check an "relation_match" push rule condition.
|
||||||
|
|
||||||
|
|
|
@ -74,7 +74,17 @@ receipt.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Union, cast
|
from typing import (
|
||||||
|
TYPE_CHECKING,
|
||||||
|
Collection,
|
||||||
|
Dict,
|
||||||
|
List,
|
||||||
|
Mapping,
|
||||||
|
Optional,
|
||||||
|
Tuple,
|
||||||
|
Union,
|
||||||
|
cast,
|
||||||
|
)
|
||||||
|
|
||||||
import attr
|
import attr
|
||||||
|
|
||||||
|
@ -154,7 +164,9 @@ class NotifCounts:
|
||||||
highlight_count: int = 0
|
highlight_count: int = 0
|
||||||
|
|
||||||
|
|
||||||
def _serialize_action(actions: List[Union[dict, str]], is_highlight: bool) -> str:
|
def _serialize_action(
|
||||||
|
actions: Collection[Union[Mapping, str]], is_highlight: bool
|
||||||
|
) -> str:
|
||||||
"""Custom serializer for actions. This allows us to "compress" common actions.
|
"""Custom serializer for actions. This allows us to "compress" common actions.
|
||||||
|
|
||||||
We use the fact that most users have the same actions for notifs (and for
|
We use the fact that most users have the same actions for notifs (and for
|
||||||
|
@ -750,7 +762,7 @@ class EventPushActionsWorkerStore(ReceiptsWorkerStore, StreamWorkerStore, SQLBas
|
||||||
async def add_push_actions_to_staging(
|
async def add_push_actions_to_staging(
|
||||||
self,
|
self,
|
||||||
event_id: str,
|
event_id: str,
|
||||||
user_id_actions: Dict[str, List[Union[dict, str]]],
|
user_id_actions: Dict[str, Collection[Union[Mapping, str]]],
|
||||||
count_as_unread: bool,
|
count_as_unread: bool,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Add the push actions for the event to the push action staging area.
|
"""Add the push actions for the event to the push action staging area.
|
||||||
|
@ -767,7 +779,7 @@ class EventPushActionsWorkerStore(ReceiptsWorkerStore, StreamWorkerStore, SQLBas
|
||||||
# This is a helper function for generating the necessary tuple that
|
# This is a helper function for generating the necessary tuple that
|
||||||
# can be used to insert into the `event_push_actions_staging` table.
|
# can be used to insert into the `event_push_actions_staging` table.
|
||||||
def _gen_entry(
|
def _gen_entry(
|
||||||
user_id: str, actions: List[Union[dict, str]]
|
user_id: str, actions: Collection[Union[Mapping, str]]
|
||||||
) -> Tuple[str, str, str, int, int, int]:
|
) -> Tuple[str, str, str, int, int, int]:
|
||||||
is_highlight = 1 if _action_has_highlight(actions) else 0
|
is_highlight = 1 if _action_has_highlight(actions) else 0
|
||||||
notif = 1 if "notify" in actions else 0
|
notif = 1 if "notify" in actions else 0
|
||||||
|
@ -1410,7 +1422,7 @@ class EventPushActionsStore(EventPushActionsWorkerStore):
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def _action_has_highlight(actions: List[Union[dict, str]]) -> bool:
|
def _action_has_highlight(actions: Collection[Union[Mapping, str]]) -> bool:
|
||||||
for action in actions:
|
for action in actions:
|
||||||
if not isinstance(action, dict):
|
if not isinstance(action, dict):
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -14,11 +14,23 @@
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
import abc
|
import abc
|
||||||
import logging
|
import logging
|
||||||
from typing import TYPE_CHECKING, Collection, Dict, List, Optional, Tuple, Union, cast
|
from typing import (
|
||||||
|
TYPE_CHECKING,
|
||||||
|
Any,
|
||||||
|
Collection,
|
||||||
|
Dict,
|
||||||
|
List,
|
||||||
|
Mapping,
|
||||||
|
Optional,
|
||||||
|
Sequence,
|
||||||
|
Tuple,
|
||||||
|
Union,
|
||||||
|
cast,
|
||||||
|
)
|
||||||
|
|
||||||
from synapse.api.errors import StoreError
|
from synapse.api.errors import StoreError
|
||||||
from synapse.config.homeserver import ExperimentalConfig
|
from synapse.config.homeserver import ExperimentalConfig
|
||||||
from synapse.push.baserules import list_with_base_rules
|
from synapse.push.baserules import FilteredPushRules, PushRule, compile_push_rules
|
||||||
from synapse.replication.slave.storage._slaved_id_tracker import SlavedIdTracker
|
from synapse.replication.slave.storage._slaved_id_tracker import SlavedIdTracker
|
||||||
from synapse.storage._base import SQLBaseStore, db_to_json
|
from synapse.storage._base import SQLBaseStore, db_to_json
|
||||||
from synapse.storage.database import (
|
from synapse.storage.database import (
|
||||||
|
@ -50,60 +62,30 @@ if TYPE_CHECKING:
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def _is_experimental_rule_enabled(
|
|
||||||
rule_id: str, experimental_config: ExperimentalConfig
|
|
||||||
) -> bool:
|
|
||||||
"""Used by `_load_rules` to filter out experimental rules when they
|
|
||||||
have not been enabled.
|
|
||||||
"""
|
|
||||||
if (
|
|
||||||
rule_id == "global/override/.org.matrix.msc3786.rule.room.server_acl"
|
|
||||||
and not experimental_config.msc3786_enabled
|
|
||||||
):
|
|
||||||
return False
|
|
||||||
if (
|
|
||||||
rule_id == "global/underride/.org.matrix.msc3772.thread_reply"
|
|
||||||
and not experimental_config.msc3772_enabled
|
|
||||||
):
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def _load_rules(
|
def _load_rules(
|
||||||
rawrules: List[JsonDict],
|
rawrules: List[JsonDict],
|
||||||
enabled_map: Dict[str, bool],
|
enabled_map: Dict[str, bool],
|
||||||
experimental_config: ExperimentalConfig,
|
experimental_config: ExperimentalConfig,
|
||||||
) -> List[JsonDict]:
|
) -> FilteredPushRules:
|
||||||
ruleslist = []
|
"""Take the DB rows returned from the DB and convert them into a full
|
||||||
for rawrule in rawrules:
|
`FilteredPushRules` object.
|
||||||
rule = dict(rawrule)
|
"""
|
||||||
rule["conditions"] = db_to_json(rawrule["conditions"])
|
|
||||||
rule["actions"] = db_to_json(rawrule["actions"])
|
|
||||||
rule["default"] = False
|
|
||||||
ruleslist.append(rule)
|
|
||||||
|
|
||||||
# We're going to be mutating this a lot, so copy it. We also filter out
|
ruleslist = [
|
||||||
# any experimental default push rules that aren't enabled.
|
PushRule(
|
||||||
rules = [
|
rule_id=rawrule["rule_id"],
|
||||||
rule
|
priority_class=rawrule["priority_class"],
|
||||||
for rule in list_with_base_rules(ruleslist)
|
conditions=db_to_json(rawrule["conditions"]),
|
||||||
if _is_experimental_rule_enabled(rule["rule_id"], experimental_config)
|
actions=db_to_json(rawrule["actions"]),
|
||||||
|
)
|
||||||
|
for rawrule in rawrules
|
||||||
]
|
]
|
||||||
|
|
||||||
for i, rule in enumerate(rules):
|
push_rules = compile_push_rules(ruleslist)
|
||||||
rule_id = rule["rule_id"]
|
|
||||||
|
|
||||||
if rule_id not in enabled_map:
|
filtered_rules = FilteredPushRules(push_rules, enabled_map, experimental_config)
|
||||||
continue
|
|
||||||
if rule.get("enabled", True) == bool(enabled_map[rule_id]):
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Rules are cached across users.
|
return filtered_rules
|
||||||
rule = dict(rule)
|
|
||||||
rule["enabled"] = bool(enabled_map[rule_id])
|
|
||||||
rules[i] = rule
|
|
||||||
|
|
||||||
return rules
|
|
||||||
|
|
||||||
|
|
||||||
# The ABCMeta metaclass ensures that it cannot be instantiated without
|
# The ABCMeta metaclass ensures that it cannot be instantiated without
|
||||||
|
@ -162,7 +144,7 @@ class PushRulesWorkerStore(
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
@cached(max_entries=5000)
|
@cached(max_entries=5000)
|
||||||
async def get_push_rules_for_user(self, user_id: str) -> List[JsonDict]:
|
async def get_push_rules_for_user(self, user_id: str) -> FilteredPushRules:
|
||||||
rows = await self.db_pool.simple_select_list(
|
rows = await self.db_pool.simple_select_list(
|
||||||
table="push_rules",
|
table="push_rules",
|
||||||
keyvalues={"user_name": user_id},
|
keyvalues={"user_name": user_id},
|
||||||
|
@ -216,11 +198,11 @@ class PushRulesWorkerStore(
|
||||||
@cachedList(cached_method_name="get_push_rules_for_user", list_name="user_ids")
|
@cachedList(cached_method_name="get_push_rules_for_user", list_name="user_ids")
|
||||||
async def bulk_get_push_rules(
|
async def bulk_get_push_rules(
|
||||||
self, user_ids: Collection[str]
|
self, user_ids: Collection[str]
|
||||||
) -> Dict[str, List[JsonDict]]:
|
) -> Dict[str, FilteredPushRules]:
|
||||||
if not user_ids:
|
if not user_ids:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
results: Dict[str, List[JsonDict]] = {user_id: [] for user_id in user_ids}
|
raw_rules: Dict[str, List[JsonDict]] = {user_id: [] for user_id in user_ids}
|
||||||
|
|
||||||
rows = await self.db_pool.simple_select_many_batch(
|
rows = await self.db_pool.simple_select_many_batch(
|
||||||
table="push_rules",
|
table="push_rules",
|
||||||
|
@ -234,11 +216,13 @@ class PushRulesWorkerStore(
|
||||||
rows.sort(key=lambda row: (-int(row["priority_class"]), -int(row["priority"])))
|
rows.sort(key=lambda row: (-int(row["priority_class"]), -int(row["priority"])))
|
||||||
|
|
||||||
for row in rows:
|
for row in rows:
|
||||||
results.setdefault(row["user_name"], []).append(row)
|
raw_rules.setdefault(row["user_name"], []).append(row)
|
||||||
|
|
||||||
enabled_map_by_user = await self.bulk_get_push_rules_enabled(user_ids)
|
enabled_map_by_user = await self.bulk_get_push_rules_enabled(user_ids)
|
||||||
|
|
||||||
for user_id, rules in results.items():
|
results: Dict[str, FilteredPushRules] = {}
|
||||||
|
|
||||||
|
for user_id, rules in raw_rules.items():
|
||||||
results[user_id] = _load_rules(
|
results[user_id] = _load_rules(
|
||||||
rules, enabled_map_by_user.get(user_id, {}), self.hs.config.experimental
|
rules, enabled_map_by_user.get(user_id, {}), self.hs.config.experimental
|
||||||
)
|
)
|
||||||
|
@ -345,8 +329,8 @@ class PushRuleStore(PushRulesWorkerStore):
|
||||||
user_id: str,
|
user_id: str,
|
||||||
rule_id: str,
|
rule_id: str,
|
||||||
priority_class: int,
|
priority_class: int,
|
||||||
conditions: List[Dict[str, str]],
|
conditions: Sequence[Mapping[str, str]],
|
||||||
actions: List[Union[JsonDict, str]],
|
actions: Sequence[Union[Mapping[str, Any], str]],
|
||||||
before: Optional[str] = None,
|
before: Optional[str] = None,
|
||||||
after: Optional[str] = None,
|
after: Optional[str] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@ -817,7 +801,7 @@ class PushRuleStore(PushRulesWorkerStore):
|
||||||
return self._push_rules_stream_id_gen.get_current_token()
|
return self._push_rules_stream_id_gen.get_current_token()
|
||||||
|
|
||||||
async def copy_push_rule_from_room_to_room(
|
async def copy_push_rule_from_room_to_room(
|
||||||
self, new_room_id: str, user_id: str, rule: dict
|
self, new_room_id: str, user_id: str, rule: PushRule
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Copy a single push rule from one room to another for a specific user.
|
"""Copy a single push rule from one room to another for a specific user.
|
||||||
|
|
||||||
|
@ -827,21 +811,27 @@ class PushRuleStore(PushRulesWorkerStore):
|
||||||
rule: A push rule.
|
rule: A push rule.
|
||||||
"""
|
"""
|
||||||
# Create new rule id
|
# Create new rule id
|
||||||
rule_id_scope = "/".join(rule["rule_id"].split("/")[:-1])
|
rule_id_scope = "/".join(rule.rule_id.split("/")[:-1])
|
||||||
new_rule_id = rule_id_scope + "/" + new_room_id
|
new_rule_id = rule_id_scope + "/" + new_room_id
|
||||||
|
|
||||||
|
new_conditions = []
|
||||||
|
|
||||||
# Change room id in each condition
|
# Change room id in each condition
|
||||||
for condition in rule.get("conditions", []):
|
for condition in rule.conditions:
|
||||||
|
new_condition = condition
|
||||||
if condition.get("key") == "room_id":
|
if condition.get("key") == "room_id":
|
||||||
condition["pattern"] = new_room_id
|
new_condition = dict(condition)
|
||||||
|
new_condition["pattern"] = new_room_id
|
||||||
|
|
||||||
|
new_conditions.append(new_condition)
|
||||||
|
|
||||||
# Add the rule for the new room
|
# Add the rule for the new room
|
||||||
await self.add_push_rule(
|
await self.add_push_rule(
|
||||||
user_id=user_id,
|
user_id=user_id,
|
||||||
rule_id=new_rule_id,
|
rule_id=new_rule_id,
|
||||||
priority_class=rule["priority_class"],
|
priority_class=rule.priority_class,
|
||||||
conditions=rule["conditions"],
|
conditions=new_conditions,
|
||||||
actions=rule["actions"],
|
actions=rule.actions,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def copy_push_rules_from_room_to_room_for_user(
|
async def copy_push_rules_from_room_to_room_for_user(
|
||||||
|
@ -859,8 +849,11 @@ class PushRuleStore(PushRulesWorkerStore):
|
||||||
user_push_rules = await self.get_push_rules_for_user(user_id)
|
user_push_rules = await self.get_push_rules_for_user(user_id)
|
||||||
|
|
||||||
# Get rules relating to the old room and copy them to the new room
|
# Get rules relating to the old room and copy them to the new room
|
||||||
for rule in user_push_rules:
|
for rule, enabled in user_push_rules:
|
||||||
conditions = rule.get("conditions", [])
|
if not enabled:
|
||||||
|
continue
|
||||||
|
|
||||||
|
conditions = rule.conditions
|
||||||
if any(
|
if any(
|
||||||
(c.get("key") == "room_id" and c.get("pattern") == old_room_id)
|
(c.get("key") == "room_id" and c.get("pattern") == old_room_id)
|
||||||
for c in conditions
|
for c in conditions
|
||||||
|
|
|
@ -11,11 +11,11 @@
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
from typing import Any, Dict
|
|
||||||
|
|
||||||
from twisted.test.proto_helpers import MemoryReactor
|
from twisted.test.proto_helpers import MemoryReactor
|
||||||
|
|
||||||
from synapse.api.constants import AccountDataTypes
|
from synapse.api.constants import AccountDataTypes
|
||||||
|
from synapse.push.baserules import PushRule
|
||||||
from synapse.push.rulekinds import PRIORITY_CLASS_MAP
|
from synapse.push.rulekinds import PRIORITY_CLASS_MAP
|
||||||
from synapse.rest import admin
|
from synapse.rest import admin
|
||||||
from synapse.rest.client import account, login
|
from synapse.rest.client import account, login
|
||||||
|
@ -130,12 +130,12 @@ class DeactivateAccountTestCase(HomeserverTestCase):
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
def _is_custom_rule(self, push_rule: Dict[str, Any]) -> bool:
|
def _is_custom_rule(self, push_rule: PushRule) -> bool:
|
||||||
"""
|
"""
|
||||||
Default rules start with a dot: such as .m.rule and .im.vector.
|
Default rules start with a dot: such as .m.rule and .im.vector.
|
||||||
This function returns true iff a rule is custom (not default).
|
This function returns true iff a rule is custom (not default).
|
||||||
"""
|
"""
|
||||||
return "/." not in push_rule["rule_id"]
|
return "/." not in push_rule.rule_id
|
||||||
|
|
||||||
def test_push_rules_deleted_upon_account_deactivation(self) -> None:
|
def test_push_rules_deleted_upon_account_deactivation(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -157,22 +157,21 @@ class DeactivateAccountTestCase(HomeserverTestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test the rule exists
|
# Test the rule exists
|
||||||
push_rules = self.get_success(self._store.get_push_rules_for_user(self.user))
|
filtered_push_rules = self.get_success(
|
||||||
|
self._store.get_push_rules_for_user(self.user)
|
||||||
|
)
|
||||||
# Filter out default rules; we don't care
|
# Filter out default rules; we don't care
|
||||||
push_rules = list(filter(self._is_custom_rule, push_rules))
|
push_rules = [r for r, _ in filtered_push_rules if self._is_custom_rule(r)]
|
||||||
# Check our rule made it
|
# Check our rule made it
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
push_rules,
|
push_rules,
|
||||||
[
|
[
|
||||||
{
|
PushRule(
|
||||||
"user_name": "@user:test",
|
rule_id="personal.override.rule1",
|
||||||
"rule_id": "personal.override.rule1",
|
priority_class=5,
|
||||||
"priority_class": 5,
|
conditions=[],
|
||||||
"priority": 0,
|
actions=[],
|
||||||
"conditions": [],
|
)
|
||||||
"actions": [],
|
|
||||||
"default": False,
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
push_rules,
|
push_rules,
|
||||||
)
|
)
|
||||||
|
@ -180,9 +179,11 @@ class DeactivateAccountTestCase(HomeserverTestCase):
|
||||||
# Request the deactivation of our account
|
# Request the deactivation of our account
|
||||||
self._deactivate_my_account()
|
self._deactivate_my_account()
|
||||||
|
|
||||||
push_rules = self.get_success(self._store.get_push_rules_for_user(self.user))
|
filtered_push_rules = self.get_success(
|
||||||
|
self._store.get_push_rules_for_user(self.user)
|
||||||
|
)
|
||||||
# Filter out default rules; we don't care
|
# Filter out default rules; we don't care
|
||||||
push_rules = list(filter(self._is_custom_rule, push_rules))
|
push_rules = [r for r, _ in filtered_push_rules if self._is_custom_rule(r)]
|
||||||
# Check our rule no longer exists
|
# Check our rule no longer exists
|
||||||
self.assertEqual(push_rules, [], push_rules)
|
self.assertEqual(push_rules, [], push_rules)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue