mirror of
https://mau.dev/maunium/synapse.git
synced 2024-12-15 07:33:50 +01:00
Use push settings!
This commit is contained in:
parent
fc7a05c443
commit
f21f9fa3c5
3 changed files with 117 additions and 26 deletions
|
@ -21,6 +21,8 @@ from synapse.types import StreamToken
|
|||
import synapse.util.async
|
||||
|
||||
import logging
|
||||
import fnmatch
|
||||
import json
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -29,6 +31,7 @@ class Pusher(object):
|
|||
INITIAL_BACKOFF = 1000
|
||||
MAX_BACKOFF = 60 * 60 * 1000
|
||||
GIVE_UP_AFTER = 24 * 60 * 60 * 1000
|
||||
DEFAULT_ACTIONS = ['notify']
|
||||
|
||||
def __init__(self, _hs, instance_handle, user_name, app_id,
|
||||
app_display_name, device_display_name, pushkey, pushkey_ts,
|
||||
|
@ -37,7 +40,7 @@ class Pusher(object):
|
|||
self.evStreamHandler = self.hs.get_handlers().event_stream_handler
|
||||
self.store = self.hs.get_datastore()
|
||||
self.clock = self.hs.get_clock()
|
||||
self.instance_handle = instance_handle,
|
||||
self.instance_handle = instance_handle
|
||||
self.user_name = user_name
|
||||
self.app_id = app_id
|
||||
self.app_display_name = app_display_name
|
||||
|
@ -51,7 +54,8 @@ class Pusher(object):
|
|||
self.failing_since = failing_since
|
||||
self.alive = True
|
||||
|
||||
def _should_notify_for_event(self, ev):
|
||||
@defer.inlineCallbacks
|
||||
def _actions_for_event(self, ev):
|
||||
"""
|
||||
This should take into account notification settings that the user
|
||||
has configured both globally and per-room when we have the ability
|
||||
|
@ -59,8 +63,47 @@ class Pusher(object):
|
|||
"""
|
||||
if ev['user_id'] == self.user_name:
|
||||
# let's assume you probably know about messages you sent yourself
|
||||
defer.returnValue(['dont_notify'])
|
||||
|
||||
rules = yield self.store.get_push_rules_for_user_name(self.user_name)
|
||||
|
||||
for r in rules:
|
||||
matches = True
|
||||
|
||||
conditions = json.loads(r['conditions'])
|
||||
actions = json.loads(r['actions'])
|
||||
|
||||
for c in conditions:
|
||||
matches &= self._event_fulfills_condition(ev, c)
|
||||
# ignore rules with no actions (we have an explict 'dont_notify'
|
||||
if len(actions) == 0:
|
||||
logger.warn(
|
||||
"Ignoring rule id %s with no actions for user %s" %
|
||||
(r['rule_id'], r['user_name'])
|
||||
)
|
||||
continue
|
||||
if matches:
|
||||
defer.returnValue(actions)
|
||||
|
||||
defer.returnValue(Pusher.DEFAULT_ACTIONS)
|
||||
|
||||
def _event_fulfills_condition(self, ev, condition):
|
||||
if condition['kind'] == 'event_match':
|
||||
if 'pattern' not in condition:
|
||||
logger.warn("event_match condition with no pattern")
|
||||
return False
|
||||
pat = condition['pattern']
|
||||
|
||||
val = _value_for_dotted_key(condition['key'], ev)
|
||||
if fnmatch.fnmatch(val, pat):
|
||||
return True
|
||||
return False
|
||||
return True
|
||||
elif condition['kind'] == 'device':
|
||||
if 'instance_handle' not in condition:
|
||||
return True
|
||||
return condition['instance_handle'] == self.instance_handle
|
||||
else:
|
||||
return True
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def get_context_for_event(self, ev):
|
||||
|
@ -113,8 +156,23 @@ class Pusher(object):
|
|||
continue
|
||||
|
||||
processed = False
|
||||
if self._should_notify_for_event(single_event):
|
||||
rejected = yield self.dispatch_push(single_event)
|
||||
actions = yield self._actions_for_event(single_event)
|
||||
tweaks = _tweaks_for_actions(actions)
|
||||
|
||||
if len(actions) == 0:
|
||||
logger.warn("Empty actions! Using default action.")
|
||||
actions = Pusher.DEFAULT_ACTIONS
|
||||
if 'notify' not in actions and 'dont_notify' not in actions:
|
||||
logger.warn("Neither notify nor dont_notify in actions: adding default")
|
||||
actions.extend(Pusher.DEFAULT_ACTIONS)
|
||||
if 'dont_notify' in actions:
|
||||
logger.debug(
|
||||
"%s for %s: dont_notify",
|
||||
single_event['event_id'], self.user_name
|
||||
)
|
||||
processed = True
|
||||
else:
|
||||
rejected = yield self.dispatch_push(single_event, tweaks)
|
||||
if not rejected is False:
|
||||
processed = True
|
||||
for pk in rejected:
|
||||
|
@ -133,8 +191,6 @@ class Pusher(object):
|
|||
yield self.hs.get_pusherpool().remove_pusher(
|
||||
self.app_id, pk
|
||||
)
|
||||
else:
|
||||
processed = True
|
||||
|
||||
if not self.alive:
|
||||
continue
|
||||
|
@ -202,7 +258,7 @@ class Pusher(object):
|
|||
def stop(self):
|
||||
self.alive = False
|
||||
|
||||
def dispatch_push(self, p):
|
||||
def dispatch_push(self, p, tweaks):
|
||||
"""
|
||||
Overridden by implementing classes to actually deliver the notification
|
||||
:param p: The event to notify for as a single event from the event stream
|
||||
|
@ -214,6 +270,25 @@ class Pusher(object):
|
|||
pass
|
||||
|
||||
|
||||
def _value_for_dotted_key(dotted_key, event):
|
||||
parts = dotted_key.split(".")
|
||||
val = event
|
||||
while len(parts) > 0:
|
||||
if parts[0] not in val:
|
||||
return None
|
||||
val = val[parts[0]]
|
||||
parts = parts[1:]
|
||||
return val
|
||||
|
||||
def _tweaks_for_actions(actions):
|
||||
tweaks = {}
|
||||
for a in actions:
|
||||
if not isinstance(a, dict):
|
||||
continue
|
||||
if 'set_sound' in a:
|
||||
tweaks['sound'] = a['set_sound']
|
||||
return tweaks
|
||||
|
||||
class PusherConfigException(Exception):
|
||||
def __init__(self, msg):
|
||||
super(PusherConfigException, self).__init__(msg)
|
|
@ -52,7 +52,7 @@ class HttpPusher(Pusher):
|
|||
del self.data_minus_url['url']
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def _build_notification_dict(self, event):
|
||||
def _build_notification_dict(self, event, tweaks):
|
||||
# we probably do not want to push for every presence update
|
||||
# (we may want to be able to set up notifications when specific
|
||||
# people sign in, but we'd want to only deliver the pertinent ones)
|
||||
|
@ -83,7 +83,8 @@ class HttpPusher(Pusher):
|
|||
'app_id': self.app_id,
|
||||
'pushkey': self.pushkey,
|
||||
'pushkey_ts': long(self.pushkey_ts / 1000),
|
||||
'data': self.data_minus_url
|
||||
'data': self.data_minus_url,
|
||||
'tweaks': tweaks
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -97,8 +98,8 @@ class HttpPusher(Pusher):
|
|||
defer.returnValue(d)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def dispatch_push(self, event):
|
||||
notification_dict = yield self._build_notification_dict(event)
|
||||
def dispatch_push(self, event, tweaks):
|
||||
notification_dict = yield self._build_notification_dict(event, tweaks)
|
||||
if not notification_dict:
|
||||
defer.returnValue([])
|
||||
try:
|
||||
|
|
|
@ -96,10 +96,15 @@ class PushRuleRestServlet(RestServlet):
|
|||
elif rule_template == 'content':
|
||||
if 'pattern' not in req_obj:
|
||||
raise InvalidRuleException("Content rule missing 'pattern'")
|
||||
pat = req_obj['pattern']
|
||||
if pat.strip("*?[]") == pat:
|
||||
# no special glob characters so we assume the user means
|
||||
# 'contains this string' rather than 'is this string'
|
||||
pat = "*%s*" % (pat)
|
||||
conditions = [{
|
||||
'kind': 'event_match',
|
||||
'key': 'content.body',
|
||||
'pattern': req_obj['pattern']
|
||||
'pattern': pat
|
||||
}]
|
||||
else:
|
||||
raise InvalidRuleException("Unknown rule template: %s" % (rule_template,))
|
||||
|
@ -115,7 +120,7 @@ class PushRuleRestServlet(RestServlet):
|
|||
actions = req_obj['actions']
|
||||
|
||||
for a in actions:
|
||||
if a in ['notify', 'dont-notify', 'coalesce']:
|
||||
if a in ['notify', 'dont_notify', 'coalesce']:
|
||||
pass
|
||||
elif isinstance(a, dict) and 'set_sound' in a:
|
||||
pass
|
||||
|
@ -124,21 +129,11 @@ class PushRuleRestServlet(RestServlet):
|
|||
|
||||
return conditions, actions
|
||||
|
||||
def priority_class_from_spec(self, spec):
|
||||
if spec['template'] not in PushRuleRestServlet.PRIORITY_CLASS_MAP.keys():
|
||||
raise InvalidRuleException("Unknown template: %s" % (spec['kind']))
|
||||
pc = PushRuleRestServlet.PRIORITY_CLASS_MAP[spec['template']]
|
||||
|
||||
if spec['scope'] == 'device':
|
||||
pc += 5
|
||||
|
||||
return pc
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def on_PUT(self, request):
|
||||
spec = self.rule_spec_from_path(request.postpath)
|
||||
try:
|
||||
priority_class = self.priority_class_from_spec(spec)
|
||||
priority_class = _priority_class_from_spec(spec)
|
||||
except InvalidRuleException as e:
|
||||
raise SynapseError(400, e.message)
|
||||
|
||||
|
@ -204,6 +199,7 @@ class PushRuleRestServlet(RestServlet):
|
|||
if r['priority_class'] > PushRuleRestServlet.PRIORITY_CLASS_MAP['override']:
|
||||
# per-device rule
|
||||
instance_handle = _instance_handle_from_conditions(r["conditions"])
|
||||
r = _strip_device_condition(r)
|
||||
if not instance_handle:
|
||||
continue
|
||||
if instance_handle not in rules['device']:
|
||||
|
@ -239,6 +235,7 @@ class PushRuleRestServlet(RestServlet):
|
|||
defer.returnValue((200, rules['device']))
|
||||
|
||||
instance_handle = path[0]
|
||||
path = path[1:]
|
||||
if instance_handle not in rules['device']:
|
||||
ret = {}
|
||||
ret = _add_empty_priority_class_arrays(ret)
|
||||
|
@ -290,10 +287,21 @@ def _filter_ruleset_with_path(ruleset, path):
|
|||
raise NotFoundError
|
||||
|
||||
|
||||
def _priority_class_from_spec(spec):
|
||||
if spec['template'] not in PushRuleRestServlet.PRIORITY_CLASS_MAP.keys():
|
||||
raise InvalidRuleException("Unknown template: %s" % (spec['kind']))
|
||||
pc = PushRuleRestServlet.PRIORITY_CLASS_MAP[spec['template']]
|
||||
|
||||
if spec['scope'] == 'device':
|
||||
pc += len(PushRuleRestServlet.PRIORITY_CLASS_MAP)
|
||||
|
||||
return pc
|
||||
|
||||
|
||||
def _priority_class_to_template_name(pc):
|
||||
if pc > PushRuleRestServlet.PRIORITY_CLASS_MAP['override']:
|
||||
# per-device
|
||||
prio_class_index = pc - PushRuleRestServlet.PRIORITY_CLASS_MAP['override']
|
||||
prio_class_index = pc - len(PushRuleRestServlet.PRIORITY_CLASS_MAP)
|
||||
return PushRuleRestServlet.PRIORITY_CLASS_INVERSE_MAP[prio_class_index]
|
||||
else:
|
||||
return PushRuleRestServlet.PRIORITY_CLASS_INVERSE_MAP[pc]
|
||||
|
@ -316,6 +324,13 @@ def _rule_to_template(rule):
|
|||
return ret
|
||||
|
||||
|
||||
def _strip_device_condition(rule):
|
||||
for i,c in enumerate(rule['conditions']):
|
||||
if c['kind'] == 'device':
|
||||
del rule['conditions'][i]
|
||||
return rule
|
||||
|
||||
|
||||
class InvalidRuleException(Exception):
|
||||
pass
|
||||
|
||||
|
|
Loading…
Reference in a new issue