mirror of
https://github.com/Tautulli/Tautulli.git
synced 2025-07-10 15:32:38 -07:00
Add parsing and evaluating custom notification conditions
This commit is contained in:
parent
d536a10d20
commit
12c94ee79e
2 changed files with 195 additions and 9 deletions
|
@ -798,3 +798,72 @@ def humanFileSize(bytes, si=False):
|
||||||
u += 1
|
u += 1
|
||||||
|
|
||||||
return "{0:.1f} {1}".format(bytes, units[u])
|
return "{0:.1f} {1}".format(bytes, units[u])
|
||||||
|
|
||||||
|
def parse_condition_logic_string(s, num_cond=0):
|
||||||
|
""" Parse a logic string into a nested list
|
||||||
|
Based on http://stackoverflow.com/a/23185606
|
||||||
|
"""
|
||||||
|
valid_tokens = re.compile(r'(\(|\)|and|or)')
|
||||||
|
conditions_pattern = re.compile(r'{\d+}')
|
||||||
|
|
||||||
|
tokens = [x.strip() for x in re.split(valid_tokens, s.lower()) if x.strip()]
|
||||||
|
|
||||||
|
stack = [[]]
|
||||||
|
|
||||||
|
cond_next = True
|
||||||
|
bool_next = False
|
||||||
|
open_bracket_next = True
|
||||||
|
close_bracket_next = False
|
||||||
|
|
||||||
|
for i, x in enumerate(tokens):
|
||||||
|
if open_bracket_next and x == '(':
|
||||||
|
stack[-1].append([])
|
||||||
|
stack.append(stack[-1][-1])
|
||||||
|
cond_next = True
|
||||||
|
bool_next = False
|
||||||
|
open_bracket_next = True
|
||||||
|
close_bracket_next = False
|
||||||
|
|
||||||
|
elif close_bracket_next and x == ')':
|
||||||
|
stack.pop()
|
||||||
|
if not stack:
|
||||||
|
raise ValueError('opening bracket is missing')
|
||||||
|
cond_next = False
|
||||||
|
bool_next = True
|
||||||
|
open_bracket_next = False
|
||||||
|
close_bracket_next = True
|
||||||
|
|
||||||
|
elif cond_next and re.match(conditions_pattern, x):
|
||||||
|
try:
|
||||||
|
num = int(x[1:-1])
|
||||||
|
except:
|
||||||
|
raise ValueError('invalid condition logic')
|
||||||
|
if not 0 < num <= num_cond:
|
||||||
|
raise ValueError('invalid condition number in condition logic')
|
||||||
|
stack[-1].append(x)
|
||||||
|
cond_next = False
|
||||||
|
bool_next = True
|
||||||
|
open_bracket_next = False
|
||||||
|
close_bracket_next = True
|
||||||
|
|
||||||
|
elif bool_next and x in ('and', 'or') and i < len(tokens)-1:
|
||||||
|
stack[-1].append(x)
|
||||||
|
cond_next = True
|
||||||
|
bool_next = False
|
||||||
|
open_bracket_next = True
|
||||||
|
close_bracket_next = False
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise ValueError('invalid condition logic')
|
||||||
|
|
||||||
|
if len(stack) > 1:
|
||||||
|
raise ValueError('closing bracket is missing')
|
||||||
|
|
||||||
|
return stack.pop()
|
||||||
|
|
||||||
|
def nested_list_to_string(l):
|
||||||
|
for i, x in enumerate(l):
|
||||||
|
if isinstance(x, list):
|
||||||
|
l[i] = nested_list_to_string(x)
|
||||||
|
s = '(' + ' '.join(l) + ')'
|
||||||
|
return s
|
|
@ -105,15 +105,19 @@ def add_notifier_each(notify_action=None, stream_data=None, timeline_data=None,
|
||||||
logger.error(u"PlexPy NotificationHandler :: Failed to build notification parameters.")
|
logger.error(u"PlexPy NotificationHandler :: Failed to build notification parameters.")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Add each notifier to the queue
|
|
||||||
for notifier in notifiers_enabled:
|
for notifier in notifiers_enabled:
|
||||||
data = {'notifier_id': notifier['id'],
|
# Check custom user conditions
|
||||||
'notify_action': notify_action,
|
if notify_custom_conditions(notifier_id=notifier['id'], parameters=parameters):
|
||||||
'stream_data': stream_data,
|
# Add each notifier to the queue
|
||||||
'timeline_data': timeline_data,
|
data = {'notifier_id': notifier['id'],
|
||||||
'parameters': parameters}
|
'notify_action': notify_action,
|
||||||
data.update(kwargs)
|
'stream_data': stream_data,
|
||||||
plexpy.NOTIFY_QUEUE.put(data)
|
'timeline_data': timeline_data,
|
||||||
|
'parameters': parameters}
|
||||||
|
data.update(kwargs)
|
||||||
|
plexpy.NOTIFY_QUEUE.put(data)
|
||||||
|
else:
|
||||||
|
logger.debug(u"PlexPy NotificationHandler :: Custom notification conditions not satisfied, skipping notifier_id %s." % notifier['id'])
|
||||||
|
|
||||||
# Add on_concurrent and on_newdevice to queue if action is on_play
|
# Add on_concurrent and on_newdevice to queue if action is on_play
|
||||||
if notify_action == 'on_play':
|
if notify_action == 'on_play':
|
||||||
|
@ -121,7 +125,7 @@ def add_notifier_each(notify_action=None, stream_data=None, timeline_data=None,
|
||||||
plexpy.NOTIFY_QUEUE.put({'stream_data': stream_data, 'notify_action': 'on_newdevice'})
|
plexpy.NOTIFY_QUEUE.put({'stream_data': stream_data, 'notify_action': 'on_newdevice'})
|
||||||
|
|
||||||
|
|
||||||
def notify_conditions(notifier=None, notify_action=None, stream_data=None, timeline_data=None):
|
def notify_conditions(notify_action=None, stream_data=None, timeline_data=None):
|
||||||
# Activity notifications
|
# Activity notifications
|
||||||
if stream_data:
|
if stream_data:
|
||||||
|
|
||||||
|
@ -188,7 +192,120 @@ def notify_conditions(notifier=None, notify_action=None, stream_data=None, timel
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def notify_custom_conditions(notifier_id=None, parameters=None):
|
||||||
|
notifier_config = notifiers.get_notifier_config(notifier_id=notifier_id)
|
||||||
|
|
||||||
|
custom_conditions_logic = notifier_config['custom_conditions_logic']
|
||||||
|
|
||||||
|
if custom_conditions_logic:
|
||||||
|
logger.debug(u"PlexPy NotificationHandler :: Checking custom notification conditions for notifier_id %s." % notifier_id)
|
||||||
|
|
||||||
|
custom_conditions = json.loads(notifier_config['custom_conditions'])
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Parse and validate the custom conditions logic
|
||||||
|
logic_groups = helpers.parse_condition_logic_string(custom_conditions_logic, len(custom_conditions))
|
||||||
|
logic_string = helpers.nested_list_to_string(logic_groups)
|
||||||
|
except ValueError as e:
|
||||||
|
logger.error(u"PlexPy NotificationHandler :: Unable to parse custom condition logic: %s." % e)
|
||||||
|
return False
|
||||||
|
|
||||||
|
param_types = {param['value']: param['type']
|
||||||
|
for category in common.NOTIFICATION_PARAMETERS for param in category['parameters']}
|
||||||
|
|
||||||
|
evaluated_conditions = [None] # Set condition {0} to None
|
||||||
|
|
||||||
|
for condition in custom_conditions:
|
||||||
|
parameter = condition['parameter']
|
||||||
|
operator = condition['operator']
|
||||||
|
values = condition['value']
|
||||||
|
|
||||||
|
# Set blank conditions to None
|
||||||
|
if not values:
|
||||||
|
evaluated_conditions.append(None)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Make sure the condition values is in a list
|
||||||
|
if not isinstance(values, list):
|
||||||
|
values = [values]
|
||||||
|
|
||||||
|
parameter_type = param_types[parameter]
|
||||||
|
|
||||||
|
# Cast the condition values to the correct type
|
||||||
|
try:
|
||||||
|
if parameter_type == 'str':
|
||||||
|
values = [unicode(v).lower() for v in values]
|
||||||
|
|
||||||
|
elif parameter_type == 'int':
|
||||||
|
values = [int(v) for v in values]
|
||||||
|
|
||||||
|
elif parameter_type == 'float':
|
||||||
|
values = [float(v) for v in values]
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(u"PlexPy NotificationHandler :: Unable to cast condition '%s' to type '%s'."
|
||||||
|
% (parameter, parameter_type))
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Cast the parameter value to the correct type
|
||||||
|
try:
|
||||||
|
if parameter_type == 'str':
|
||||||
|
parameter_value = unicode(parameters[parameter]).lower()
|
||||||
|
|
||||||
|
elif parameter_type == 'int':
|
||||||
|
parameter_value = int(parameters[parameter])
|
||||||
|
|
||||||
|
elif parameter_type == 'float':
|
||||||
|
parameter_value = float(parameters[parameter])
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(u"PlexPy NotificationHandler :: Unable to cast parameter '%s' to type '%s'."
|
||||||
|
% (parameter, parameter_type))
|
||||||
|
return False
|
||||||
|
|
||||||
|
condition_value = values[0]
|
||||||
|
|
||||||
|
# Check each condition
|
||||||
|
if operator == 'contains':
|
||||||
|
evaluated_conditions.append(condition_value in parameter_value)
|
||||||
|
|
||||||
|
elif operator == 'does not contain':
|
||||||
|
evaluated_conditions.append(condition_value not in parameter_value)
|
||||||
|
|
||||||
|
elif operator == 'is':
|
||||||
|
evaluated_conditions.append(parameter_value == condition_value)
|
||||||
|
|
||||||
|
elif operator == 'is not':
|
||||||
|
evaluated_conditions.append(parameter_value != condition_value)
|
||||||
|
|
||||||
|
elif operator == 'begins with':
|
||||||
|
evaluated_conditions.append(parameter_value.startswith(condition_value))
|
||||||
|
|
||||||
|
elif operator == 'ends with':
|
||||||
|
evaluated_conditions.append(parameter_value.endswith(condition_value))
|
||||||
|
|
||||||
|
elif operator == 'greater than':
|
||||||
|
evaluated_conditions.append(parameter_value > condition_value)
|
||||||
|
|
||||||
|
elif operator == 'less than':
|
||||||
|
evaluated_conditions.append(parameter_value < condition_value)
|
||||||
|
|
||||||
|
# Format and evaluate the logic string
|
||||||
|
try:
|
||||||
|
evaluated_logic = bool(eval(logic_string.format(*evaluated_conditions)))
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(u"PlexPy NotificationHandler :: Unable to evaluate custom condition logic: %s." % e)
|
||||||
|
return False
|
||||||
|
|
||||||
|
logger.debug(u"PlexPy NotificationHandler :: Custom condition evaluated to '%s'." % str(evaluated_logic))
|
||||||
|
return evaluated_logic
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def notify(notifier_id=None, notify_action=None, stream_data=None, timeline_data=None, parameters=None, **kwargs):
|
def notify(notifier_id=None, notify_action=None, stream_data=None, timeline_data=None, parameters=None, **kwargs):
|
||||||
|
logger.debug(u"PlexPy NotificationHandler :: Preparing notifications for notifier_id %s." % notifier_id)
|
||||||
|
|
||||||
notifier_config = notifiers.get_notifier_config(notifier_id=notifier_id)
|
notifier_config = notifiers.get_notifier_config(notifier_id=notifier_id)
|
||||||
|
|
||||||
if not notifier_config:
|
if not notifier_config:
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue