mirror of
https://github.com/caronc/apprise.git
synced 2025-02-21 20:51:28 +01:00
normalize plugin classes mass code cleanup
This commit is contained in:
parent
c9b957c434
commit
842de28191
@ -106,7 +106,7 @@ class URLBase(object):
|
|||||||
# Secure Mode
|
# Secure Mode
|
||||||
self.secure = kwargs.get('secure', False)
|
self.secure = kwargs.get('secure', False)
|
||||||
|
|
||||||
self.host = kwargs.get('host', '')
|
self.host = URLBase.unquote(kwargs.get('host'))
|
||||||
self.port = kwargs.get('port')
|
self.port = kwargs.get('port')
|
||||||
if self.port:
|
if self.port:
|
||||||
try:
|
try:
|
||||||
@ -116,13 +116,20 @@ class URLBase(object):
|
|||||||
self.port = None
|
self.port = None
|
||||||
|
|
||||||
self.user = kwargs.get('user')
|
self.user = kwargs.get('user')
|
||||||
|
if self.user:
|
||||||
|
# Always unquote user if it exists
|
||||||
|
self.user = URLBase.unquote(self.user)
|
||||||
|
|
||||||
self.password = kwargs.get('password')
|
self.password = kwargs.get('password')
|
||||||
|
if self.password:
|
||||||
|
# Always unquote the pssword if it exists
|
||||||
|
self.password = URLBase.unquote(self.password)
|
||||||
|
|
||||||
if 'tag' in kwargs:
|
if 'tag' in kwargs:
|
||||||
# We want to associate some tags with our notification service.
|
# We want to associate some tags with our notification service.
|
||||||
# the code below gets the 'tag' argument if defined, otherwise
|
# the code below gets the 'tag' argument if defined, otherwise
|
||||||
# it just falls back to whatever was already defined globally
|
# it just falls back to whatever was already defined globally
|
||||||
self.tags = set(parse_list(kwargs.get('tag', self.tags)))
|
self.tags = set(parse_list(kwargs.get('tag'), self.tags))
|
||||||
|
|
||||||
# Tracks the time any i/o was made to the remote server. This value
|
# Tracks the time any i/o was made to the remote server. This value
|
||||||
# is automatically set and controlled through the throttle() call.
|
# is automatically set and controlled through the throttle() call.
|
||||||
@ -161,7 +168,7 @@ class URLBase(object):
|
|||||||
elapsed = (reference - self._last_io_datetime).total_seconds()
|
elapsed = (reference - self._last_io_datetime).total_seconds()
|
||||||
|
|
||||||
if wait is not None:
|
if wait is not None:
|
||||||
self.logger.debug('Throttling for {}s...'.format(wait))
|
self.logger.debug('Throttling forced for {}s...'.format(wait))
|
||||||
sleep(wait)
|
sleep(wait)
|
||||||
|
|
||||||
elif elapsed < self.request_rate_per_sec:
|
elif elapsed < self.request_rate_per_sec:
|
||||||
@ -348,10 +355,42 @@ class URLBase(object):
|
|||||||
list: A list containing all of the elements in the path
|
list: A list containing all of the elements in the path
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
paths = PATHSPLIT_LIST_DELIM.split(path.lstrip('/'))
|
||||||
|
if unquote:
|
||||||
|
paths = \
|
||||||
|
[URLBase.unquote(x) for x in filter(bool, paths)]
|
||||||
|
|
||||||
|
except AttributeError:
|
||||||
|
# path is not useable, we still want to gracefully return an
|
||||||
|
# empty list
|
||||||
|
paths = []
|
||||||
|
|
||||||
|
return paths
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def parse_list(content, unquote=True):
|
||||||
|
"""A wrapper to utils.parse_list() with unquoting support
|
||||||
|
|
||||||
|
Parses a specified set of data and breaks it into a list.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
content (str): The path to split up into a list. If a list is
|
||||||
|
provided, then it's individual entries are processed.
|
||||||
|
|
||||||
|
unquote (:obj:`bool`, optional): call unquote on each element
|
||||||
|
added to the returned list.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list: A unique list containing all of the elements in the path
|
||||||
|
"""
|
||||||
|
|
||||||
|
content = parse_list(content)
|
||||||
if unquote:
|
if unquote:
|
||||||
return PATHSPLIT_LIST_DELIM.split(
|
content = \
|
||||||
URLBase.unquote(path).lstrip('/'))
|
[URLBase.unquote(x) for x in filter(bool, content)]
|
||||||
return PATHSPLIT_LIST_DELIM.split(path.lstrip('/'))
|
|
||||||
|
return content
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def app_id(self):
|
def app_id(self):
|
||||||
|
@ -33,9 +33,6 @@ from ..common import NOTIFY_FORMATS
|
|||||||
from ..common import OverflowMode
|
from ..common import OverflowMode
|
||||||
from ..common import OVERFLOW_MODES
|
from ..common import OVERFLOW_MODES
|
||||||
|
|
||||||
# HTML New Line Delimiter
|
|
||||||
NOTIFY_NEWLINE = '\r\n'
|
|
||||||
|
|
||||||
|
|
||||||
class NotifyBase(URLBase):
|
class NotifyBase(URLBase):
|
||||||
"""
|
"""
|
||||||
@ -94,12 +91,10 @@ class NotifyBase(URLBase):
|
|||||||
# Store the specified format if specified
|
# Store the specified format if specified
|
||||||
notify_format = kwargs.get('format', '')
|
notify_format = kwargs.get('format', '')
|
||||||
if notify_format.lower() not in NOTIFY_FORMATS:
|
if notify_format.lower() not in NOTIFY_FORMATS:
|
||||||
self.logger.error(
|
msg = 'Invalid notification format %s'.format(notify_format)
|
||||||
'Invalid notification format %s' % notify_format,
|
self.logger.error(msg)
|
||||||
)
|
raise TypeError(msg)
|
||||||
raise TypeError(
|
|
||||||
'Invalid notification format %s' % notify_format,
|
|
||||||
)
|
|
||||||
# Provide override
|
# Provide override
|
||||||
self.notify_format = notify_format
|
self.notify_format = notify_format
|
||||||
|
|
||||||
@ -107,12 +102,10 @@ class NotifyBase(URLBase):
|
|||||||
# Store the specified format if specified
|
# Store the specified format if specified
|
||||||
overflow = kwargs.get('overflow', '')
|
overflow = kwargs.get('overflow', '')
|
||||||
if overflow.lower() not in OVERFLOW_MODES:
|
if overflow.lower() not in OVERFLOW_MODES:
|
||||||
self.logger.error(
|
msg = 'Invalid overflow method {}'.format(overflow)
|
||||||
'Invalid overflow method %s' % overflow,
|
self.logger.error(msg)
|
||||||
)
|
raise TypeError(msg)
|
||||||
raise TypeError(
|
|
||||||
'Invalid overflow method %s' % overflow,
|
|
||||||
)
|
|
||||||
# Provide override
|
# Provide override
|
||||||
self.overflow_mode = overflow
|
self.overflow_mode = overflow
|
||||||
|
|
||||||
|
@ -38,6 +38,7 @@ except ImportError:
|
|||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
from .NotifyBase import NotifyBase
|
from .NotifyBase import NotifyBase
|
||||||
|
from ..utils import parse_bool
|
||||||
from ..common import NotifyType
|
from ..common import NotifyType
|
||||||
from ..common import NotifyImageSize
|
from ..common import NotifyImageSize
|
||||||
|
|
||||||
@ -51,8 +52,8 @@ DEFAULT_TAG = '@all'
|
|||||||
IS_TAG = re.compile(r'^[@](?P<name>[A-Z0-9]{1,63})$', re.I)
|
IS_TAG = re.compile(r'^[@](?P<name>[A-Z0-9]{1,63})$', re.I)
|
||||||
|
|
||||||
# Device tokens are only referenced when developing.
|
# Device tokens are only referenced when developing.
|
||||||
# it's not likely you'll send a message directly to a device, but
|
# It's not likely you'll send a message directly to a device, but if you do;
|
||||||
# if you do; this plugin supports it.
|
# this plugin supports it.
|
||||||
IS_DEVICETOKEN = re.compile(r'^[A-Z0-9]{64}$', re.I)
|
IS_DEVICETOKEN = re.compile(r'^[A-Z0-9]{64}$', re.I)
|
||||||
|
|
||||||
# Both an access key and seret key are created and assigned to each project
|
# Both an access key and seret key are created and assigned to each project
|
||||||
@ -60,8 +61,8 @@ IS_DEVICETOKEN = re.compile(r'^[A-Z0-9]{64}$', re.I)
|
|||||||
VALIDATE_ACCESS = re.compile(r'[A-Z0-9_-]{64}', re.I)
|
VALIDATE_ACCESS = re.compile(r'[A-Z0-9_-]{64}', re.I)
|
||||||
VALIDATE_SECRET = re.compile(r'[A-Z0-9_-]{64}', re.I)
|
VALIDATE_SECRET = re.compile(r'[A-Z0-9_-]{64}', re.I)
|
||||||
|
|
||||||
# Used to break apart list of potential tags by their delimiter
|
# Used to break apart list of potential tags by their delimiter into a useable
|
||||||
# into a usable list.
|
# list.
|
||||||
TAGS_LIST_DELIM = re.compile(r'[ \t\r\n,\\/]+')
|
TAGS_LIST_DELIM = re.compile(r'[ \t\r\n,\\/]+')
|
||||||
|
|
||||||
|
|
||||||
@ -91,7 +92,8 @@ class NotifyBoxcar(NotifyBase):
|
|||||||
# The maximum allowable characters allowed in the body per message
|
# The maximum allowable characters allowed in the body per message
|
||||||
body_maxlen = 10000
|
body_maxlen = 10000
|
||||||
|
|
||||||
def __init__(self, access, secret, recipients=None, **kwargs):
|
def __init__(self, access, secret, targets=None, include_image=True,
|
||||||
|
**kwargs):
|
||||||
"""
|
"""
|
||||||
Initialize Boxcar Object
|
Initialize Boxcar Object
|
||||||
"""
|
"""
|
||||||
@ -108,66 +110,62 @@ class NotifyBoxcar(NotifyBase):
|
|||||||
self.access = access.strip()
|
self.access = access.strip()
|
||||||
|
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
self.logger.warning(
|
msg = 'The specified access key is invalid.'
|
||||||
'The specified access key specified is invalid.',
|
self.logger.warning(msg)
|
||||||
)
|
raise TypeError(msg)
|
||||||
raise TypeError(
|
|
||||||
'The specified access key specified is invalid.',
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Secret Key (associated with project)
|
# Secret Key (associated with project)
|
||||||
self.secret = secret.strip()
|
self.secret = secret.strip()
|
||||||
|
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
self.logger.warning(
|
msg = 'The specified secret key is invalid.'
|
||||||
'The specified secret key specified is invalid.',
|
self.logger.warning(msg)
|
||||||
)
|
raise TypeError(msg)
|
||||||
raise TypeError(
|
|
||||||
'The specified secret key specified is invalid.',
|
|
||||||
)
|
|
||||||
|
|
||||||
if not VALIDATE_ACCESS.match(self.access):
|
if not VALIDATE_ACCESS.match(self.access):
|
||||||
self.logger.warning(
|
msg = 'The access key specified ({}) is invalid.'\
|
||||||
'The access key specified (%s) is invalid.' % self.access,
|
.format(self.access)
|
||||||
)
|
self.logger.warning(msg)
|
||||||
raise TypeError(
|
raise TypeError(msg)
|
||||||
'The access key specified (%s) is invalid.' % self.access,
|
|
||||||
)
|
|
||||||
|
|
||||||
if not VALIDATE_SECRET.match(self.secret):
|
if not VALIDATE_SECRET.match(self.secret):
|
||||||
self.logger.warning(
|
msg = 'The secret key specified ({}) is invalid.'\
|
||||||
'The secret key specified (%s) is invalid.' % self.secret,
|
.format(self.secret)
|
||||||
)
|
self.logger.warning(msg)
|
||||||
raise TypeError(
|
raise TypeError(msg)
|
||||||
'The secret key specified (%s) is invalid.' % self.secret,
|
|
||||||
)
|
|
||||||
|
|
||||||
if not recipients:
|
if not targets:
|
||||||
self.tags.append(DEFAULT_TAG)
|
self.tags.append(DEFAULT_TAG)
|
||||||
recipients = []
|
targets = []
|
||||||
|
|
||||||
elif isinstance(recipients, six.string_types):
|
elif isinstance(targets, six.string_types):
|
||||||
recipients = [x for x in filter(bool, TAGS_LIST_DELIM.split(
|
targets = [x for x in filter(bool, TAGS_LIST_DELIM.split(
|
||||||
recipients,
|
targets,
|
||||||
))]
|
))]
|
||||||
|
|
||||||
# Validate recipients and drop bad ones:
|
# Validate targets and drop bad ones:
|
||||||
for recipient in recipients:
|
for target in targets:
|
||||||
if IS_TAG.match(recipient):
|
if IS_TAG.match(target):
|
||||||
# store valid tag/alias
|
# store valid tag/alias
|
||||||
self.tags.append(IS_TAG.match(recipient).group('name'))
|
self.tags.append(IS_TAG.match(target).group('name'))
|
||||||
|
|
||||||
elif IS_DEVICETOKEN.match(recipient):
|
elif IS_DEVICETOKEN.match(target):
|
||||||
# store valid device
|
# store valid device
|
||||||
self.device_tokens.append(recipient)
|
self.device_tokens.append(target)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
'Dropped invalid tag/alias/device_token '
|
'Dropped invalid tag/alias/device_token '
|
||||||
'(%s) specified.' % recipient,
|
'({}) specified.'.format(target),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Track whether or not we want to send an image with our notification
|
||||||
|
# or not.
|
||||||
|
self.include_image = include_image
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
||||||
"""
|
"""
|
||||||
Perform Boxcar Notification
|
Perform Boxcar Notification
|
||||||
@ -200,7 +198,9 @@ class NotifyBoxcar(NotifyBase):
|
|||||||
payload['device_tokens'] = self.device_tokens
|
payload['device_tokens'] = self.device_tokens
|
||||||
|
|
||||||
# Source picture should be <= 450 DP wide, ~2:1 aspect.
|
# Source picture should be <= 450 DP wide, ~2:1 aspect.
|
||||||
image_url = self.image_url(notify_type)
|
image_url = None if not self.include_image \
|
||||||
|
else self.image_url(notify_type)
|
||||||
|
|
||||||
if image_url:
|
if image_url:
|
||||||
# Set our image
|
# Set our image
|
||||||
payload['@img'] = image_url
|
payload['@img'] = image_url
|
||||||
@ -218,7 +218,7 @@ class NotifyBoxcar(NotifyBase):
|
|||||||
sha1,
|
sha1,
|
||||||
)
|
)
|
||||||
|
|
||||||
params = self.urlencode({
|
params = NotifyBoxcar.urlencode({
|
||||||
"publishkey": self.access,
|
"publishkey": self.access,
|
||||||
"signature": h.hexdigest(),
|
"signature": h.hexdigest(),
|
||||||
})
|
})
|
||||||
@ -244,7 +244,7 @@ class NotifyBoxcar(NotifyBase):
|
|||||||
if r.status_code != requests.codes.created:
|
if r.status_code != requests.codes.created:
|
||||||
# We had a problem
|
# We had a problem
|
||||||
status_str = \
|
status_str = \
|
||||||
NotifyBase.http_response_code_lookup(r.status_code)
|
NotifyBoxcar.http_response_code_lookup(r.status_code)
|
||||||
|
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
'Failed to send Boxcar notification: '
|
'Failed to send Boxcar notification: '
|
||||||
@ -282,16 +282,17 @@ class NotifyBoxcar(NotifyBase):
|
|||||||
args = {
|
args = {
|
||||||
'format': self.notify_format,
|
'format': self.notify_format,
|
||||||
'overflow': self.overflow_mode,
|
'overflow': self.overflow_mode,
|
||||||
|
'image': 'yes' if self.include_image else 'no',
|
||||||
}
|
}
|
||||||
|
|
||||||
return '{schema}://{access}/{secret}/{recipients}/?{args}'.format(
|
return '{schema}://{access}/{secret}/{targets}/?{args}'.format(
|
||||||
schema=self.secure_protocol,
|
schema=self.secure_protocol,
|
||||||
access=self.quote(self.access),
|
access=NotifyBoxcar.quote(self.access, safe=''),
|
||||||
secret=self.quote(self.secret),
|
secret=NotifyBoxcar.quote(self.secret, safe=''),
|
||||||
recipients='/'.join([
|
targets='/'.join([
|
||||||
self.quote(x) for x in chain(
|
NotifyBoxcar.quote(x, safe='') for x in chain(
|
||||||
self.tags, self.device_tokens) if x != DEFAULT_TAG]),
|
self.tags, self.device_tokens) if x != DEFAULT_TAG]),
|
||||||
args=self.urlencode(args),
|
args=NotifyBoxcar.urlencode(args),
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -307,23 +308,30 @@ class NotifyBoxcar(NotifyBase):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
# The first token is stored in the hostname
|
# The first token is stored in the hostname
|
||||||
access = results['host']
|
results['access'] = NotifyBoxcar.unquote(results['host'])
|
||||||
|
|
||||||
# Now fetch the remaining tokens
|
# Get our entries; split_path() looks after unquoting content for us
|
||||||
secret = NotifyBase.split_path(results['fullpath'])[0]
|
# by default
|
||||||
|
entries = NotifyBoxcar.split_path(results['fullpath'])
|
||||||
|
|
||||||
# Our recipients
|
try:
|
||||||
recipients = ','.join(
|
# Now fetch the remaining tokens
|
||||||
NotifyBase.split_path(results['fullpath'])[1:])
|
results['secret'] = entries.pop(0)
|
||||||
|
|
||||||
if not (access and secret):
|
except IndexError:
|
||||||
# If we did not recive an access and/or secret code
|
# secret wasn't specified
|
||||||
# then we're done
|
results['secret'] = None
|
||||||
return None
|
|
||||||
|
|
||||||
# Store our required content
|
# Our recipients make up the remaining entries of our array
|
||||||
results['recipients'] = recipients if recipients else None
|
results['targets'] = entries
|
||||||
results['access'] = access
|
|
||||||
results['secret'] = secret
|
# The 'to' makes it easier to use yaml configuration
|
||||||
|
if 'to' in results['qsd'] and len(results['qsd']['to']):
|
||||||
|
results['targets'] += \
|
||||||
|
NotifyBoxcar.parse_list(results['qsd'].get('to'))
|
||||||
|
|
||||||
|
# Include images with our message
|
||||||
|
results['include_image'] = \
|
||||||
|
parse_bool(results['qsd'].get('image', True))
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
@ -30,6 +30,7 @@ from .NotifyBase import NotifyBase
|
|||||||
from ..common import NotifyImageSize
|
from ..common import NotifyImageSize
|
||||||
from ..common import NotifyType
|
from ..common import NotifyType
|
||||||
from ..utils import GET_SCHEMA_RE
|
from ..utils import GET_SCHEMA_RE
|
||||||
|
from ..utils import parse_bool
|
||||||
|
|
||||||
# Default our global support flag
|
# Default our global support flag
|
||||||
NOTIFY_DBUS_SUPPORT_ENABLED = False
|
NOTIFY_DBUS_SUPPORT_ENABLED = False
|
||||||
@ -170,7 +171,8 @@ class NotifyDBus(NotifyBase):
|
|||||||
# let me know! :)
|
# let me know! :)
|
||||||
_enabled = NOTIFY_DBUS_SUPPORT_ENABLED
|
_enabled = NOTIFY_DBUS_SUPPORT_ENABLED
|
||||||
|
|
||||||
def __init__(self, urgency=None, x_axis=None, y_axis=None, **kwargs):
|
def __init__(self, urgency=None, x_axis=None, y_axis=None,
|
||||||
|
include_image=True, **kwargs):
|
||||||
"""
|
"""
|
||||||
Initialize DBus Object
|
Initialize DBus Object
|
||||||
"""
|
"""
|
||||||
@ -184,13 +186,10 @@ class NotifyDBus(NotifyBase):
|
|||||||
self.schema = kwargs.get('schema', 'dbus')
|
self.schema = kwargs.get('schema', 'dbus')
|
||||||
|
|
||||||
if self.schema not in MAINLOOP_MAP:
|
if self.schema not in MAINLOOP_MAP:
|
||||||
# Unsupported Schema
|
msg = 'The schema specified ({}) is not supported.' \
|
||||||
self.logger.warning(
|
.format(self.schema)
|
||||||
'The schema specified ({}) is not supported.'
|
self.logger.warning(msg)
|
||||||
.format(self.schema))
|
raise TypeError(msg)
|
||||||
raise TypeError(
|
|
||||||
'The schema specified ({}) is not supported.'
|
|
||||||
.format(self.schema))
|
|
||||||
|
|
||||||
# The urgency of the message
|
# The urgency of the message
|
||||||
if urgency not in DBUS_URGENCIES:
|
if urgency not in DBUS_URGENCIES:
|
||||||
@ -200,8 +199,12 @@ class NotifyDBus(NotifyBase):
|
|||||||
self.urgency = urgency
|
self.urgency = urgency
|
||||||
|
|
||||||
# Our x/y axis settings
|
# Our x/y axis settings
|
||||||
self.x_axis = x_axis
|
self.x_axis = x_axis if isinstance(x_axis, int) else None
|
||||||
self.y_axis = y_axis
|
self.y_axis = y_axis if isinstance(y_axis, int) else None
|
||||||
|
|
||||||
|
# Track whether or not we want to send an image with our notification
|
||||||
|
# or not.
|
||||||
|
self.include_image = include_image
|
||||||
|
|
||||||
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
||||||
"""
|
"""
|
||||||
@ -229,7 +232,8 @@ class NotifyDBus(NotifyBase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# image path
|
# image path
|
||||||
icon_path = self.image_path(notify_type, extension='.ico')
|
icon_path = None if not self.include_image \
|
||||||
|
else self.image_path(notify_type, extension='.ico')
|
||||||
|
|
||||||
# Our meta payload
|
# Our meta payload
|
||||||
meta_payload = {
|
meta_payload = {
|
||||||
@ -241,7 +245,7 @@ class NotifyDBus(NotifyBase):
|
|||||||
meta_payload['x'] = self.x_axis
|
meta_payload['x'] = self.x_axis
|
||||||
meta_payload['y'] = self.y_axis
|
meta_payload['y'] = self.y_axis
|
||||||
|
|
||||||
if NOTIFY_DBUS_IMAGE_SUPPORT is True:
|
if NOTIFY_DBUS_IMAGE_SUPPORT and icon_path:
|
||||||
try:
|
try:
|
||||||
# Use Pixbuf to create the proper image type
|
# Use Pixbuf to create the proper image type
|
||||||
image = GdkPixbuf.Pixbuf.new_from_file(icon_path)
|
image = GdkPixbuf.Pixbuf.new_from_file(icon_path)
|
||||||
@ -299,7 +303,33 @@ class NotifyDBus(NotifyBase):
|
|||||||
Returns the URL built dynamically based on specified arguments.
|
Returns the URL built dynamically based on specified arguments.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return '{schema}://'.format(schema=self.schema)
|
_map = {
|
||||||
|
DBusUrgency.LOW: 'low',
|
||||||
|
DBusUrgency.NORMAL: 'normal',
|
||||||
|
DBusUrgency.HIGH: 'high',
|
||||||
|
}
|
||||||
|
|
||||||
|
# Define any arguments set
|
||||||
|
args = {
|
||||||
|
'format': self.notify_format,
|
||||||
|
'overflow': self.overflow_mode,
|
||||||
|
'image': 'yes' if self.include_image else 'no',
|
||||||
|
'urgency': 'normal' if self.urgency not in _map
|
||||||
|
else _map[self.urgency]
|
||||||
|
}
|
||||||
|
|
||||||
|
# x in (x,y) screen coordinates
|
||||||
|
if self.x_axis:
|
||||||
|
args['x'] = str(self.x_axis)
|
||||||
|
|
||||||
|
# y in (x,y) screen coordinates
|
||||||
|
if self.y_axis:
|
||||||
|
args['y'] = str(self.y_axis)
|
||||||
|
|
||||||
|
return '{schema}://_/?{args}'.format(
|
||||||
|
schema=self.protocol,
|
||||||
|
args=NotifyDBus.urlencode(args),
|
||||||
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_url(url):
|
def parse_url(url):
|
||||||
@ -314,23 +344,58 @@ class NotifyDBus(NotifyBase):
|
|||||||
# Content is simply not parseable
|
# Content is simply not parseable
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# return a very basic set of requirements
|
results = NotifyBase.parse_url(url)
|
||||||
return {
|
if not results:
|
||||||
'schema': schema.group('schema').lower(),
|
results = {
|
||||||
'user': None,
|
'schema': schema.group('schema').lower(),
|
||||||
'password': None,
|
'user': None,
|
||||||
'port': None,
|
'password': None,
|
||||||
'host': 'localhost',
|
'port': None,
|
||||||
'fullpath': None,
|
'host': '_',
|
||||||
'path': None,
|
'fullpath': None,
|
||||||
'url': url,
|
'path': None,
|
||||||
'qsd': {},
|
'url': url,
|
||||||
# screen lat/lon (in pixels) where x=0 and y=0 if you want to put
|
'qsd': {},
|
||||||
# the notification in the top left hand side. Accept defaults if
|
}
|
||||||
# set to None
|
|
||||||
'x_axis': None,
|
# Include images with our message
|
||||||
'y_axis': None,
|
results['include_image'] = \
|
||||||
# Set the urgency to None so that we fall back to the default
|
parse_bool(results['qsd'].get('image', True))
|
||||||
# value.
|
|
||||||
'urgency': None,
|
# DBus supports urgency, but we we also support the keyword priority
|
||||||
}
|
# so that it is consistent with some of the other plugins
|
||||||
|
urgency = results['qsd'].get('urgency', results['qsd'].get('priority'))
|
||||||
|
if urgency and len(urgency):
|
||||||
|
_map = {
|
||||||
|
'0': DBusUrgency.LOW,
|
||||||
|
'l': DBusUrgency.LOW,
|
||||||
|
'n': DBusUrgency.NORMAL,
|
||||||
|
'1': DBusUrgency.NORMAL,
|
||||||
|
'h': DBusUrgency.HIGH,
|
||||||
|
'2': DBusUrgency.HIGH,
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Attempt to index/retrieve our urgency
|
||||||
|
results['urgency'] = _map[urgency[0].lower()]
|
||||||
|
|
||||||
|
except KeyError:
|
||||||
|
# No priority was set
|
||||||
|
pass
|
||||||
|
|
||||||
|
# handle x,y coordinates
|
||||||
|
try:
|
||||||
|
results['x_axis'] = int(results['qsd'].get('x'))
|
||||||
|
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
# No x was set
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
results['y_axis'] = int(results['qsd'].get('y'))
|
||||||
|
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
# No y was set
|
||||||
|
pass
|
||||||
|
|
||||||
|
return results
|
||||||
|
@ -78,7 +78,7 @@ class NotifyDiscord(NotifyBase):
|
|||||||
body_maxlen = 2000
|
body_maxlen = 2000
|
||||||
|
|
||||||
def __init__(self, webhook_id, webhook_token, tts=False, avatar=True,
|
def __init__(self, webhook_id, webhook_token, tts=False, avatar=True,
|
||||||
footer=False, thumbnail=True, **kwargs):
|
footer=False, footer_logo=True, include_image=True, **kwargs):
|
||||||
"""
|
"""
|
||||||
Initialize Discord Object
|
Initialize Discord Object
|
||||||
|
|
||||||
@ -86,14 +86,14 @@ class NotifyDiscord(NotifyBase):
|
|||||||
super(NotifyDiscord, self).__init__(**kwargs)
|
super(NotifyDiscord, self).__init__(**kwargs)
|
||||||
|
|
||||||
if not webhook_id:
|
if not webhook_id:
|
||||||
raise TypeError(
|
msg = 'An invalid Client ID was specified.'
|
||||||
'An invalid Client ID was specified.'
|
self.logger.warning(msg)
|
||||||
)
|
raise TypeError(msg)
|
||||||
|
|
||||||
if not webhook_token:
|
if not webhook_token:
|
||||||
raise TypeError(
|
msg = 'An invalid Webhook Token was specified.'
|
||||||
'An invalid Webhook Token was specified.'
|
self.logger.warning(msg)
|
||||||
)
|
raise TypeError(msg)
|
||||||
|
|
||||||
# Store our data
|
# Store our data
|
||||||
self.webhook_id = webhook_id
|
self.webhook_id = webhook_id
|
||||||
@ -105,11 +105,14 @@ class NotifyDiscord(NotifyBase):
|
|||||||
# Over-ride Avatar Icon
|
# Over-ride Avatar Icon
|
||||||
self.avatar = avatar
|
self.avatar = avatar
|
||||||
|
|
||||||
# Place a footer icon
|
# Place a footer
|
||||||
self.footer = footer
|
self.footer = footer
|
||||||
|
|
||||||
|
# include a footer_logo in footer
|
||||||
|
self.footer_logo = footer_logo
|
||||||
|
|
||||||
# Place a thumbnail image inline with the message body
|
# Place a thumbnail image inline with the message body
|
||||||
self.thumbnail = thumbnail
|
self.include_image = include_image
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -163,15 +166,18 @@ class NotifyDiscord(NotifyBase):
|
|||||||
payload['embeds'][0]['fields'] = fields[1:]
|
payload['embeds'][0]['fields'] = fields[1:]
|
||||||
|
|
||||||
if self.footer:
|
if self.footer:
|
||||||
|
# Acquire logo URL
|
||||||
logo_url = self.image_url(notify_type, logo=True)
|
logo_url = self.image_url(notify_type, logo=True)
|
||||||
|
|
||||||
|
# Set Footer text to our app description
|
||||||
payload['embeds'][0]['footer'] = {
|
payload['embeds'][0]['footer'] = {
|
||||||
'text': self.app_desc,
|
'text': self.app_desc,
|
||||||
}
|
}
|
||||||
|
|
||||||
if logo_url:
|
if self.footer_logo and logo_url:
|
||||||
payload['embeds'][0]['footer']['icon_url'] = logo_url
|
payload['embeds'][0]['footer']['icon_url'] = logo_url
|
||||||
|
|
||||||
if self.thumbnail and image_url:
|
if self.include_image and image_url:
|
||||||
payload['embeds'][0]['thumbnail'] = {
|
payload['embeds'][0]['thumbnail'] = {
|
||||||
'url': image_url,
|
'url': image_url,
|
||||||
'height': 256,
|
'height': 256,
|
||||||
@ -256,14 +262,15 @@ class NotifyDiscord(NotifyBase):
|
|||||||
'tts': 'yes' if self.tts else 'no',
|
'tts': 'yes' if self.tts else 'no',
|
||||||
'avatar': 'yes' if self.avatar else 'no',
|
'avatar': 'yes' if self.avatar else 'no',
|
||||||
'footer': 'yes' if self.footer else 'no',
|
'footer': 'yes' if self.footer else 'no',
|
||||||
'thumbnail': 'yes' if self.thumbnail else 'no',
|
'footer_logo': 'yes' if self.footer_logo else 'no',
|
||||||
|
'image': 'yes' if self.include_image else 'no',
|
||||||
}
|
}
|
||||||
|
|
||||||
return '{schema}://{webhook_id}/{webhook_token}/?{args}'.format(
|
return '{schema}://{webhook_id}/{webhook_token}/?{args}'.format(
|
||||||
schema=self.secure_protocol,
|
schema=self.secure_protocol,
|
||||||
webhook_id=self.quote(self.webhook_id),
|
webhook_id=NotifyDiscord.quote(self.webhook_id, safe=''),
|
||||||
webhook_token=self.quote(self.webhook_token),
|
webhook_token=NotifyDiscord.quote(self.webhook_token, safe=''),
|
||||||
args=self.urlencode(args),
|
args=NotifyDiscord.urlencode(args),
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -283,14 +290,14 @@ class NotifyDiscord(NotifyBase):
|
|||||||
return results
|
return results
|
||||||
|
|
||||||
# Store our webhook ID
|
# Store our webhook ID
|
||||||
webhook_id = results['host']
|
webhook_id = NotifyDiscord.unquote(results['host'])
|
||||||
|
|
||||||
# Now fetch our tokens
|
# Now fetch our tokens
|
||||||
try:
|
try:
|
||||||
webhook_token = [x for x in filter(bool, NotifyBase.split_path(
|
webhook_token = \
|
||||||
results['fullpath']))][0]
|
NotifyDiscord.split_path(results['fullpath'])[0]
|
||||||
|
|
||||||
except (ValueError, AttributeError, IndexError):
|
except IndexError:
|
||||||
# Force some bad values that will get caught
|
# Force some bad values that will get caught
|
||||||
# in parsing later
|
# in parsing later
|
||||||
webhook_token = None
|
webhook_token = None
|
||||||
@ -304,12 +311,27 @@ class NotifyDiscord(NotifyBase):
|
|||||||
# Use Footer
|
# Use Footer
|
||||||
results['footer'] = parse_bool(results['qsd'].get('footer', False))
|
results['footer'] = parse_bool(results['qsd'].get('footer', False))
|
||||||
|
|
||||||
|
# Use Footer Logo
|
||||||
|
results['footer_logo'] = \
|
||||||
|
parse_bool(results['qsd'].get('footer_logo', True))
|
||||||
|
|
||||||
# Update Avatar Icon
|
# Update Avatar Icon
|
||||||
results['avatar'] = parse_bool(results['qsd'].get('avatar', True))
|
results['avatar'] = parse_bool(results['qsd'].get('avatar', True))
|
||||||
|
|
||||||
# Use Thumbnail
|
# Use Thumbnail
|
||||||
results['thumbnail'] = \
|
if 'thumbnail' in results['qsd']:
|
||||||
parse_bool(results['qsd'].get('thumbnail', False))
|
# Deprication Notice issued for v0.7.5
|
||||||
|
NotifyDiscord.logger.warning(
|
||||||
|
'DEPRICATION NOTICE - The Discord URL contains the parameter '
|
||||||
|
'"thumbnail=" which will be depricated in an upcoming '
|
||||||
|
'release. Please use "image=" instead.'
|
||||||
|
)
|
||||||
|
|
||||||
|
# use image= for consistency with the other plugins but we also
|
||||||
|
# support thumbnail= for backwards compatibility.
|
||||||
|
results['include_image'] = \
|
||||||
|
parse_bool(results['qsd'].get(
|
||||||
|
'image', results['qsd'].get('thumbnail', False)))
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
|
||||||
|
@ -450,13 +450,13 @@ class NotifyEmail(NotifyBase):
|
|||||||
auth = ''
|
auth = ''
|
||||||
if self.user and self.password:
|
if self.user and self.password:
|
||||||
auth = '{user}:{password}@'.format(
|
auth = '{user}:{password}@'.format(
|
||||||
user=self.quote(user, safe=''),
|
user=NotifyEmail.quote(user, safe=''),
|
||||||
password=self.quote(self.password, safe=''),
|
password=NotifyEmail.quote(self.password, safe=''),
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# user url
|
# user url
|
||||||
auth = '{user}@'.format(
|
auth = '{user}@'.format(
|
||||||
user=self.quote(user, safe=''),
|
user=NotifyEmail.quote(user, safe=''),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Default Port setup
|
# Default Port setup
|
||||||
@ -466,10 +466,10 @@ class NotifyEmail(NotifyBase):
|
|||||||
return '{schema}://{auth}{hostname}{port}/?{args}'.format(
|
return '{schema}://{auth}{hostname}{port}/?{args}'.format(
|
||||||
schema=self.secure_protocol if self.secure else self.protocol,
|
schema=self.secure_protocol if self.secure else self.protocol,
|
||||||
auth=auth,
|
auth=auth,
|
||||||
hostname=self.host,
|
hostname=NotifyEmail.quote(self.host, safe=''),
|
||||||
port='' if self.port is None or self.port == default_port
|
port='' if self.port is None or self.port == default_port
|
||||||
else ':{}'.format(self.port),
|
else ':{}'.format(self.port),
|
||||||
args=self.urlencode(args),
|
args=NotifyEmail.urlencode(args),
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -485,21 +485,28 @@ class NotifyEmail(NotifyBase):
|
|||||||
# We're done early as we couldn't load the results
|
# We're done early as we couldn't load the results
|
||||||
return results
|
return results
|
||||||
|
|
||||||
# Apply our settings now
|
# The To: address is pre-determined if to= is not otherwise
|
||||||
|
# specified.
|
||||||
to_addr = ''
|
to_addr = ''
|
||||||
|
|
||||||
|
# The From address is a must; either through the use of templates
|
||||||
|
# from= entry and/or merging the user and hostname together, this
|
||||||
|
# must be calculated or parse_url will fail. The to_addr will
|
||||||
|
# become the from_addr if it can't be calculated
|
||||||
from_addr = ''
|
from_addr = ''
|
||||||
|
|
||||||
|
# The server we connect to to send our mail to
|
||||||
smtp_host = ''
|
smtp_host = ''
|
||||||
|
|
||||||
# Attempt to detect 'from' email address
|
# Attempt to detect 'from' email address
|
||||||
if 'from' in results['qsd'] and len(results['qsd']['from']):
|
if 'from' in results['qsd'] and len(results['qsd']['from']):
|
||||||
from_addr = NotifyBase.unquote(results['qsd']['from'])
|
from_addr = NotifyEmail.unquote(results['qsd']['from'])
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# get 'To' email address
|
# get 'To' email address
|
||||||
from_addr = '%s@%s' % (
|
from_addr = '%s@%s' % (
|
||||||
re.split(
|
re.split(
|
||||||
r'[\s@]+', NotifyBase.unquote(results['user']))[0],
|
r'[\s@]+', NotifyEmail.unquote(results['user']))[0],
|
||||||
results.get('host', '')
|
results.get('host', '')
|
||||||
)
|
)
|
||||||
# Lets be clever and attempt to make the from
|
# Lets be clever and attempt to make the from
|
||||||
@ -511,7 +518,7 @@ class NotifyEmail(NotifyBase):
|
|||||||
|
|
||||||
# Attempt to detect 'to' email address
|
# Attempt to detect 'to' email address
|
||||||
if 'to' in results['qsd'] and len(results['qsd']['to']):
|
if 'to' in results['qsd'] and len(results['qsd']['to']):
|
||||||
to_addr = NotifyBase.unquote(results['qsd']['to']).strip()
|
to_addr = NotifyEmail.unquote(results['qsd']['to']).strip()
|
||||||
|
|
||||||
if not to_addr:
|
if not to_addr:
|
||||||
# Send to ourselves if not otherwise specified to do so
|
# Send to ourselves if not otherwise specified to do so
|
||||||
@ -519,7 +526,7 @@ class NotifyEmail(NotifyBase):
|
|||||||
|
|
||||||
if 'name' in results['qsd'] and len(results['qsd']['name']):
|
if 'name' in results['qsd'] and len(results['qsd']['name']):
|
||||||
# Extract from name to associate with from address
|
# Extract from name to associate with from address
|
||||||
results['name'] = NotifyBase.unquote(results['qsd']['name'])
|
results['name'] = NotifyEmail.unquote(results['qsd']['name'])
|
||||||
|
|
||||||
if 'timeout' in results['qsd'] and len(results['qsd']['timeout']):
|
if 'timeout' in results['qsd'] and len(results['qsd']['timeout']):
|
||||||
# Extract the timeout to associate with smtp server
|
# Extract the timeout to associate with smtp server
|
||||||
@ -528,7 +535,7 @@ class NotifyEmail(NotifyBase):
|
|||||||
# Store SMTP Host if specified
|
# Store SMTP Host if specified
|
||||||
if 'smtp' in results['qsd'] and len(results['qsd']['smtp']):
|
if 'smtp' in results['qsd'] and len(results['qsd']['smtp']):
|
||||||
# Extract the smtp server
|
# Extract the smtp server
|
||||||
smtp_host = NotifyBase.unquote(results['qsd']['smtp'])
|
smtp_host = NotifyEmail.unquote(results['qsd']['smtp'])
|
||||||
|
|
||||||
if 'mode' in results['qsd'] and len(results['qsd']['mode']):
|
if 'mode' in results['qsd'] and len(results['qsd']['mode']):
|
||||||
# Extract the secure mode to over-ride the default
|
# Extract the secure mode to over-ride the default
|
||||||
|
@ -96,9 +96,10 @@ class NotifyEmby(NotifyBase):
|
|||||||
self.modal = modal
|
self.modal = modal
|
||||||
|
|
||||||
if not self.user:
|
if not self.user:
|
||||||
# Token was None
|
# User was not specified
|
||||||
self.logger.warning('No Username was specified.')
|
msg = 'No Username was specified.'
|
||||||
raise TypeError('No Username was specified.')
|
self.logger.warning(msg)
|
||||||
|
raise TypeError(msg)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -169,7 +170,7 @@ class NotifyEmby(NotifyBase):
|
|||||||
if r.status_code != requests.codes.ok:
|
if r.status_code != requests.codes.ok:
|
||||||
# We had a problem
|
# We had a problem
|
||||||
status_str = \
|
status_str = \
|
||||||
NotifyBase.http_response_code_lookup(r.status_code)
|
NotifyEmby.http_response_code_lookup(r.status_code)
|
||||||
|
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
'Failed to authenticate Emby user {} details: '
|
'Failed to authenticate Emby user {} details: '
|
||||||
@ -329,7 +330,7 @@ class NotifyEmby(NotifyBase):
|
|||||||
if r.status_code != requests.codes.ok:
|
if r.status_code != requests.codes.ok:
|
||||||
# We had a problem
|
# We had a problem
|
||||||
status_str = \
|
status_str = \
|
||||||
NotifyBase.http_response_code_lookup(r.status_code)
|
NotifyEmby.http_response_code_lookup(r.status_code)
|
||||||
|
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
'Failed to acquire Emby session for user {}: '
|
'Failed to acquire Emby session for user {}: '
|
||||||
@ -412,7 +413,7 @@ class NotifyEmby(NotifyBase):
|
|||||||
|
|
||||||
# We had a problem
|
# We had a problem
|
||||||
status_str = \
|
status_str = \
|
||||||
NotifyBase.http_response_code_lookup(r.status_code)
|
NotifyEmby.http_response_code_lookup(r.status_code)
|
||||||
|
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
'Failed to logoff Emby user {}: '
|
'Failed to logoff Emby user {}: '
|
||||||
@ -508,7 +509,7 @@ class NotifyEmby(NotifyBase):
|
|||||||
requests.codes.no_content):
|
requests.codes.no_content):
|
||||||
# We had a problem
|
# We had a problem
|
||||||
status_str = \
|
status_str = \
|
||||||
NotifyBase.http_response_code_lookup(r.status_code)
|
NotifyEmby.http_response_code_lookup(r.status_code)
|
||||||
|
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
'Failed to send Emby notification: '
|
'Failed to send Emby notification: '
|
||||||
@ -555,21 +556,21 @@ class NotifyEmby(NotifyBase):
|
|||||||
auth = ''
|
auth = ''
|
||||||
if self.user and self.password:
|
if self.user and self.password:
|
||||||
auth = '{user}:{password}@'.format(
|
auth = '{user}:{password}@'.format(
|
||||||
user=self.quote(self.user, safe=''),
|
user=NotifyEmby.quote(self.user, safe=''),
|
||||||
password=self.quote(self.password, safe=''),
|
password=NotifyEmby.quote(self.password, safe=''),
|
||||||
)
|
)
|
||||||
else: # self.user is set
|
else: # self.user is set
|
||||||
auth = '{user}@'.format(
|
auth = '{user}@'.format(
|
||||||
user=self.quote(self.user, safe=''),
|
user=NotifyEmby.quote(self.user, safe=''),
|
||||||
)
|
)
|
||||||
|
|
||||||
return '{schema}://{auth}{hostname}{port}/?{args}'.format(
|
return '{schema}://{auth}{hostname}{port}/?{args}'.format(
|
||||||
schema=self.secure_protocol if self.secure else self.protocol,
|
schema=self.secure_protocol if self.secure else self.protocol,
|
||||||
auth=auth,
|
auth=auth,
|
||||||
hostname=self.host,
|
hostname=NotifyEmby.quote(self.host, safe=''),
|
||||||
port='' if self.port is None or self.port == self.emby_default_port
|
port='' if self.port is None or self.port == self.emby_default_port
|
||||||
else ':{}'.format(self.port),
|
else ':{}'.format(self.port),
|
||||||
args=self.urlencode(args),
|
args=NotifyEmby.urlencode(args),
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -27,6 +27,7 @@ import requests
|
|||||||
from .NotifyBase import NotifyBase
|
from .NotifyBase import NotifyBase
|
||||||
from ..common import NotifyImageSize
|
from ..common import NotifyImageSize
|
||||||
from ..common import NotifyType
|
from ..common import NotifyType
|
||||||
|
from ..utils import parse_bool
|
||||||
|
|
||||||
|
|
||||||
class NotifyFaast(NotifyBase):
|
class NotifyFaast(NotifyBase):
|
||||||
@ -52,14 +53,18 @@ class NotifyFaast(NotifyBase):
|
|||||||
# Allows the user to specify the NotifyImageSize object
|
# Allows the user to specify the NotifyImageSize object
|
||||||
image_size = NotifyImageSize.XY_72
|
image_size = NotifyImageSize.XY_72
|
||||||
|
|
||||||
def __init__(self, authtoken, **kwargs):
|
def __init__(self, authtoken, include_image=True, **kwargs):
|
||||||
"""
|
"""
|
||||||
Initialize Faast Object
|
Initialize Faast Object
|
||||||
"""
|
"""
|
||||||
super(NotifyFaast, self).__init__(**kwargs)
|
super(NotifyFaast, self).__init__(**kwargs)
|
||||||
|
|
||||||
|
# Store the Authentication Token
|
||||||
self.authtoken = authtoken
|
self.authtoken = authtoken
|
||||||
|
|
||||||
|
# Associate an image with our post
|
||||||
|
self.include_image = include_image
|
||||||
|
|
||||||
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
||||||
"""
|
"""
|
||||||
Perform Faast Notification
|
Perform Faast Notification
|
||||||
@ -77,7 +82,10 @@ class NotifyFaast(NotifyBase):
|
|||||||
'message': body,
|
'message': body,
|
||||||
}
|
}
|
||||||
|
|
||||||
image_url = self.image_url(notify_type)
|
# Acquire our image if we're configured to do so
|
||||||
|
image_url = None if not self.include_image \
|
||||||
|
else self.image_url(notify_type)
|
||||||
|
|
||||||
if image_url:
|
if image_url:
|
||||||
payload['icon_url'] = image_url
|
payload['icon_url'] = image_url
|
||||||
|
|
||||||
@ -99,7 +107,7 @@ class NotifyFaast(NotifyBase):
|
|||||||
if r.status_code != requests.codes.ok:
|
if r.status_code != requests.codes.ok:
|
||||||
# We had a problem
|
# We had a problem
|
||||||
status_str = \
|
status_str = \
|
||||||
NotifyBase.http_response_code_lookup(r.status_code)
|
NotifyFaast.http_response_code_lookup(r.status_code)
|
||||||
|
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
'Failed to send Faast notification:'
|
'Failed to send Faast notification:'
|
||||||
@ -136,12 +144,13 @@ class NotifyFaast(NotifyBase):
|
|||||||
args = {
|
args = {
|
||||||
'format': self.notify_format,
|
'format': self.notify_format,
|
||||||
'overflow': self.overflow_mode,
|
'overflow': self.overflow_mode,
|
||||||
|
'image': 'yes' if self.include_image else 'no',
|
||||||
}
|
}
|
||||||
|
|
||||||
return '{schema}://{authtoken}/?{args}'.format(
|
return '{schema}://{authtoken}/?{args}'.format(
|
||||||
schema=self.protocol,
|
schema=self.protocol,
|
||||||
authtoken=self.quote(self.authtoken, safe=''),
|
authtoken=NotifyFaast.quote(self.authtoken, safe=''),
|
||||||
args=self.urlencode(args),
|
args=NotifyFaast.urlencode(args),
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -157,9 +166,11 @@ class NotifyFaast(NotifyBase):
|
|||||||
# We're done early as we couldn't load the results
|
# We're done early as we couldn't load the results
|
||||||
return results
|
return results
|
||||||
|
|
||||||
# Apply our settings now
|
|
||||||
|
|
||||||
# Store our authtoken using the host
|
# Store our authtoken using the host
|
||||||
results['authtoken'] = results['host']
|
results['authtoken'] = NotifyFaast.unquote(results['host'])
|
||||||
|
|
||||||
|
# Include image with our post
|
||||||
|
results['include_image'] = \
|
||||||
|
parse_bool(results['qsd'].get('image', True))
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
@ -25,15 +25,17 @@
|
|||||||
|
|
||||||
# To use this plugin, you need to first access https://dev.flock.com/webhooks
|
# To use this plugin, you need to first access https://dev.flock.com/webhooks
|
||||||
# Specifically https://dev.flock.com/webhooks/incoming
|
# Specifically https://dev.flock.com/webhooks/incoming
|
||||||
# to create a new incoming webhook for your account. You'll need to
|
#
|
||||||
|
# To create a new incoming webhook for your account. You'll need to
|
||||||
# follow the wizard to pre-determine the channel(s) you want your
|
# follow the wizard to pre-determine the channel(s) you want your
|
||||||
# message to broadcast to, and when you're complete, you will
|
# message to broadcast to. When you've completed this, you will
|
||||||
# recieve a URL that looks something like this:
|
# recieve a URL that looks something like this:
|
||||||
# https://api.flock.com/hooks/sendMessage/134b8gh0-eba0-4fa9-ab9c-257ced0e8221
|
# https://api.flock.com/hooks/sendMessage/134b8gh0-eba0-4fa9-ab9c-257ced0e8221
|
||||||
# ^
|
# ^
|
||||||
# |
|
# |
|
||||||
# This is important <----------------------------------------^
|
# This is important <----------------------------------------^
|
||||||
#
|
#
|
||||||
|
# It becomes your 'token' that you will pass into this class
|
||||||
#
|
#
|
||||||
import re
|
import re
|
||||||
import requests
|
import requests
|
||||||
@ -44,6 +46,7 @@ from ..common import NotifyType
|
|||||||
from ..common import NotifyFormat
|
from ..common import NotifyFormat
|
||||||
from ..common import NotifyImageSize
|
from ..common import NotifyImageSize
|
||||||
from ..utils import parse_list
|
from ..utils import parse_list
|
||||||
|
from ..utils import parse_bool
|
||||||
|
|
||||||
|
|
||||||
# Extend HTTP Error Messages
|
# Extend HTTP Error Messages
|
||||||
@ -89,7 +92,7 @@ class NotifyFlock(NotifyBase):
|
|||||||
# Allows the user to specify the NotifyImageSize object
|
# Allows the user to specify the NotifyImageSize object
|
||||||
image_size = NotifyImageSize.XY_72
|
image_size = NotifyImageSize.XY_72
|
||||||
|
|
||||||
def __init__(self, token, targets=None, **kwargs):
|
def __init__(self, token, targets=None, include_image=True, **kwargs):
|
||||||
"""
|
"""
|
||||||
Initialize Flock Object
|
Initialize Flock Object
|
||||||
"""
|
"""
|
||||||
@ -134,6 +137,10 @@ class NotifyFlock(NotifyBase):
|
|||||||
self.logger.warning(msg)
|
self.logger.warning(msg)
|
||||||
raise TypeError(msg)
|
raise TypeError(msg)
|
||||||
|
|
||||||
|
# Track whether or not we want to send an image with our notification
|
||||||
|
# or not.
|
||||||
|
self.include_image = include_image
|
||||||
|
|
||||||
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
||||||
"""
|
"""
|
||||||
Perform Flock Notification
|
Perform Flock Notification
|
||||||
@ -151,8 +158,8 @@ class NotifyFlock(NotifyBase):
|
|||||||
body = '<flockml>{}</flockml>'.format(body)
|
body = '<flockml>{}</flockml>'.format(body)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
title = NotifyBase.escape_html(title, whitespace=False)
|
title = NotifyFlock.escape_html(title, whitespace=False)
|
||||||
body = NotifyBase.escape_html(body, whitespace=False)
|
body = NotifyFlock.escape_html(body, whitespace=False)
|
||||||
|
|
||||||
body = '<flockml>{}{}</flockml>'.format(
|
body = '<flockml>{}{}</flockml>'.format(
|
||||||
'' if not title else '<b>{}</b><br/>'.format(title), body)
|
'' if not title else '<b>{}</b><br/>'.format(title), body)
|
||||||
@ -162,7 +169,10 @@ class NotifyFlock(NotifyBase):
|
|||||||
'flockml': body,
|
'flockml': body,
|
||||||
'sendAs': {
|
'sendAs': {
|
||||||
'name': FLOCK_DEFAULT_USER if not self.user else self.user,
|
'name': FLOCK_DEFAULT_USER if not self.user else self.user,
|
||||||
'profileImage': self.image_url(notify_type),
|
# A Profile Image is only configured if we're configured to
|
||||||
|
# allow it
|
||||||
|
'profileImage': None if not self.include_image
|
||||||
|
else self.image_url(notify_type),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -213,7 +223,7 @@ class NotifyFlock(NotifyBase):
|
|||||||
if r.status_code != requests.codes.ok:
|
if r.status_code != requests.codes.ok:
|
||||||
# We had a problem
|
# We had a problem
|
||||||
status_str = \
|
status_str = \
|
||||||
NotifyBase.http_response_code_lookup(
|
NotifyFlock.http_response_code_lookup(
|
||||||
r.status_code, FLOCK_HTTP_ERROR_MAP)
|
r.status_code, FLOCK_HTTP_ERROR_MAP)
|
||||||
|
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
@ -251,15 +261,17 @@ class NotifyFlock(NotifyBase):
|
|||||||
args = {
|
args = {
|
||||||
'format': self.notify_format,
|
'format': self.notify_format,
|
||||||
'overflow': self.overflow_mode,
|
'overflow': self.overflow_mode,
|
||||||
|
'image': 'yes' if self.include_image else 'no',
|
||||||
}
|
}
|
||||||
|
|
||||||
return '{schema}://{token}/{targets}?{args}'\
|
return '{schema}://{token}/{targets}?{args}'\
|
||||||
.format(
|
.format(
|
||||||
schema=self.secure_protocol,
|
schema=self.secure_protocol,
|
||||||
token=self.quote(self.token, safe=''),
|
token=NotifyFlock.quote(self.token, safe=''),
|
||||||
targets='/'.join(
|
targets='/'.join(
|
||||||
[self.quote(target, safe='') for target in self.targets]),
|
[NotifyFlock.quote(target, safe='')
|
||||||
args=self.urlencode(args),
|
for target in self.targets]),
|
||||||
|
args=NotifyFlock.urlencode(args),
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -274,12 +286,19 @@ class NotifyFlock(NotifyBase):
|
|||||||
# We're done early as we couldn't load the results
|
# We're done early as we couldn't load the results
|
||||||
return results
|
return results
|
||||||
|
|
||||||
# Apply our settings now
|
# Get our entries; split_path() looks after unquoting content for us
|
||||||
|
# by default
|
||||||
|
results['targets'] = NotifyFlock.split_path(results['fullpath'])
|
||||||
|
|
||||||
results['targets'] = [x for x in filter(
|
# The 'to' makes it easier to use yaml configuration
|
||||||
bool, NotifyBase.split_path(results['fullpath']))]
|
if 'to' in results['qsd'] and len(results['qsd']['to']):
|
||||||
|
results['targets'] += NotifyFlock.parse_list(results['qsd']['to'])
|
||||||
|
|
||||||
# The first token is stored in the hostname
|
# The first token is stored in the hostname
|
||||||
results['token'] = results['host']
|
results['token'] = NotifyFlock.unquote(results['host'])
|
||||||
|
|
||||||
|
# Include images with our message
|
||||||
|
results['include_image'] = \
|
||||||
|
parse_bool(results['qsd'].get('image', True))
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
@ -55,7 +55,7 @@ from ..utils import parse_bool
|
|||||||
# API Gitter URL
|
# API Gitter URL
|
||||||
GITTER_API_URL = 'https://api.gitter.im/v1'
|
GITTER_API_URL = 'https://api.gitter.im/v1'
|
||||||
|
|
||||||
# Used to validate API Key
|
# Used to validate your personal access token
|
||||||
VALIDATE_TOKEN = re.compile(r'^[a-z0-9]{40}$', re.I)
|
VALIDATE_TOKEN = re.compile(r'^[a-z0-9]{40}$', re.I)
|
||||||
|
|
||||||
# Used to break path apart into list of targets
|
# Used to break path apart into list of targets
|
||||||
@ -95,9 +95,11 @@ class NotifyGitter(NotifyBase):
|
|||||||
|
|
||||||
# For Tracking Purposes
|
# For Tracking Purposes
|
||||||
ratelimit_reset = datetime.utcnow()
|
ratelimit_reset = datetime.utcnow()
|
||||||
|
|
||||||
# Default to 1
|
# Default to 1
|
||||||
ratelimit_remaining = 1
|
ratelimit_remaining = 1
|
||||||
|
|
||||||
|
# Default Notification Format
|
||||||
notify_format = NotifyFormat.MARKDOWN
|
notify_format = NotifyFormat.MARKDOWN
|
||||||
|
|
||||||
def __init__(self, token, targets, include_image=True, **kwargs):
|
def __init__(self, token, targets, include_image=True, **kwargs):
|
||||||
@ -107,7 +109,7 @@ class NotifyGitter(NotifyBase):
|
|||||||
super(NotifyGitter, self).__init__(**kwargs)
|
super(NotifyGitter, self).__init__(**kwargs)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# The token associated with the account
|
# The personal access token associated with the account
|
||||||
self.token = token.strip()
|
self.token = token.strip()
|
||||||
|
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
@ -117,7 +119,8 @@ class NotifyGitter(NotifyBase):
|
|||||||
raise TypeError(msg)
|
raise TypeError(msg)
|
||||||
|
|
||||||
if not VALIDATE_TOKEN.match(self.token):
|
if not VALIDATE_TOKEN.match(self.token):
|
||||||
msg = 'The API Token specified ({}) is invalid.'.format(token)
|
msg = 'The Personal Access Token specified ({}) is invalid.' \
|
||||||
|
.format(token)
|
||||||
self.logger.warning(msg)
|
self.logger.warning(msg)
|
||||||
raise TypeError(msg)
|
raise TypeError(msg)
|
||||||
|
|
||||||
@ -140,10 +143,11 @@ class NotifyGitter(NotifyBase):
|
|||||||
# error tracking (used for function return)
|
# error tracking (used for function return)
|
||||||
has_error = False
|
has_error = False
|
||||||
|
|
||||||
# Build mapping of room names to their channel id's
|
# Set up our image for display if configured to do so
|
||||||
|
image_url = None if not self.include_image \
|
||||||
|
else self.image_url(notify_type)
|
||||||
|
|
||||||
image_url = self.image_url(notify_type)
|
if image_url:
|
||||||
if self.include_image and image_url:
|
|
||||||
body = '\n{}'.format(image_url, body)
|
body = '\n{}'.format(image_url, body)
|
||||||
|
|
||||||
# Create a copy of the targets list
|
# Create a copy of the targets list
|
||||||
@ -288,7 +292,7 @@ class NotifyGitter(NotifyBase):
|
|||||||
if r.status_code != requests.codes.ok:
|
if r.status_code != requests.codes.ok:
|
||||||
# We had a problem
|
# We had a problem
|
||||||
status_str = \
|
status_str = \
|
||||||
NotifyBase.http_response_code_lookup(r.status_code)
|
NotifyGitter.http_response_code_lookup(r.status_code)
|
||||||
|
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
'Failed to send Gitter POST to {}: '
|
'Failed to send Gitter POST to {}: '
|
||||||
@ -342,14 +346,15 @@ class NotifyGitter(NotifyBase):
|
|||||||
args = {
|
args = {
|
||||||
'format': self.notify_format,
|
'format': self.notify_format,
|
||||||
'overflow': self.overflow_mode,
|
'overflow': self.overflow_mode,
|
||||||
'image': self.include_image,
|
'image': 'yes' if self.include_image else 'no',
|
||||||
}
|
}
|
||||||
|
|
||||||
return '{schema}://{token}/{targets}/?{args}'.format(
|
return '{schema}://{token}/{targets}/?{args}'.format(
|
||||||
schema=self.secure_protocol,
|
schema=self.secure_protocol,
|
||||||
token=self.quote(self.token, safe=''),
|
token=NotifyGitter.quote(self.token, safe=''),
|
||||||
targets='/'.join(self.targets),
|
targets='/'.join(
|
||||||
args=self.urlencode(args))
|
[NotifyGitter.quote(x, safe='') for x in self.targets]),
|
||||||
|
args=NotifyGitter.urlencode(args))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_url(url):
|
def parse_url(url):
|
||||||
@ -364,15 +369,16 @@ class NotifyGitter(NotifyBase):
|
|||||||
# We're done early as we couldn't load the results
|
# We're done early as we couldn't load the results
|
||||||
return results
|
return results
|
||||||
|
|
||||||
results['token'] = results['host']
|
results['token'] = NotifyGitter.unquote(results['host'])
|
||||||
results['targets'] = \
|
|
||||||
[NotifyBase.unquote(x) for x in filter(bool, NotifyBase.split_path(
|
# Get our entries; split_path() looks after unquoting content for us
|
||||||
results['fullpath']))]
|
# by default
|
||||||
|
results['targets'] = NotifyGitter.split_path(results['fullpath'])
|
||||||
|
|
||||||
# Support the 'to' variable so that we can support targets this way too
|
# Support the 'to' variable so that we can support targets this way too
|
||||||
# The 'to' makes it easier to use yaml configuration
|
# The 'to' makes it easier to use yaml configuration
|
||||||
if 'to' in results['qsd'] and len(results['qsd']['to']):
|
if 'to' in results['qsd'] and len(results['qsd']['to']):
|
||||||
results['targets'] += parse_list(results['qsd']['to'])
|
results['targets'] += NotifyGitter.parse_list(results['qsd']['to'])
|
||||||
|
|
||||||
# Include images with our message
|
# Include images with our message
|
||||||
results['include_image'] = \
|
results['include_image'] = \
|
||||||
|
@ -29,6 +29,7 @@ from __future__ import print_function
|
|||||||
from .NotifyBase import NotifyBase
|
from .NotifyBase import NotifyBase
|
||||||
from ..common import NotifyImageSize
|
from ..common import NotifyImageSize
|
||||||
from ..common import NotifyType
|
from ..common import NotifyType
|
||||||
|
from ..utils import parse_bool
|
||||||
|
|
||||||
# Default our global support flag
|
# Default our global support flag
|
||||||
NOTIFY_GNOME_SUPPORT_ENABLED = False
|
NOTIFY_GNOME_SUPPORT_ENABLED = False
|
||||||
@ -109,7 +110,7 @@ class NotifyGnome(NotifyBase):
|
|||||||
# let me know! :)
|
# let me know! :)
|
||||||
_enabled = NOTIFY_GNOME_SUPPORT_ENABLED
|
_enabled = NOTIFY_GNOME_SUPPORT_ENABLED
|
||||||
|
|
||||||
def __init__(self, urgency=None, **kwargs):
|
def __init__(self, urgency=None, include_image=True, **kwargs):
|
||||||
"""
|
"""
|
||||||
Initialize Gnome Object
|
Initialize Gnome Object
|
||||||
"""
|
"""
|
||||||
@ -123,6 +124,10 @@ class NotifyGnome(NotifyBase):
|
|||||||
else:
|
else:
|
||||||
self.urgency = urgency
|
self.urgency = urgency
|
||||||
|
|
||||||
|
# Track whether or not we want to send an image with our notification
|
||||||
|
# or not.
|
||||||
|
self.include_image = include_image
|
||||||
|
|
||||||
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
||||||
"""
|
"""
|
||||||
Perform Gnome Notification
|
Perform Gnome Notification
|
||||||
@ -138,7 +143,8 @@ class NotifyGnome(NotifyBase):
|
|||||||
Notify.init(self.app_id)
|
Notify.init(self.app_id)
|
||||||
|
|
||||||
# image path
|
# image path
|
||||||
icon_path = self.image_path(notify_type, extension='.ico')
|
icon_path = None if not self.include_image \
|
||||||
|
else self.image_path(notify_type, extension='.ico')
|
||||||
|
|
||||||
# Build message body
|
# Build message body
|
||||||
notification = Notify.Notification.new(body)
|
notification = Notify.Notification.new(body)
|
||||||
@ -149,18 +155,19 @@ class NotifyGnome(NotifyBase):
|
|||||||
# Always call throttle before any remote server i/o is made
|
# Always call throttle before any remote server i/o is made
|
||||||
self.throttle()
|
self.throttle()
|
||||||
|
|
||||||
try:
|
if icon_path:
|
||||||
# Use Pixbuf to create the proper image type
|
try:
|
||||||
image = GdkPixbuf.Pixbuf.new_from_file(icon_path)
|
# Use Pixbuf to create the proper image type
|
||||||
|
image = GdkPixbuf.Pixbuf.new_from_file(icon_path)
|
||||||
|
|
||||||
# Associate our image to our notification
|
# Associate our image to our notification
|
||||||
notification.set_icon_from_pixbuf(image)
|
notification.set_icon_from_pixbuf(image)
|
||||||
notification.set_image_from_pixbuf(image)
|
notification.set_image_from_pixbuf(image)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
"Could not load Gnome notification icon ({}): {}"
|
"Could not load Gnome notification icon ({}): {}"
|
||||||
.format(icon_path, e))
|
.format(icon_path, e))
|
||||||
|
|
||||||
notification.show()
|
notification.show()
|
||||||
self.logger.info('Sent Gnome notification.')
|
self.logger.info('Sent Gnome notification.')
|
||||||
@ -177,7 +184,25 @@ class NotifyGnome(NotifyBase):
|
|||||||
Returns the URL built dynamically based on specified arguments.
|
Returns the URL built dynamically based on specified arguments.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return '{schema}://'.format(schema=self.protocol)
|
_map = {
|
||||||
|
GnomeUrgency.LOW: 'low',
|
||||||
|
GnomeUrgency.NORMAL: 'normal',
|
||||||
|
GnomeUrgency.HIGH: 'high',
|
||||||
|
}
|
||||||
|
|
||||||
|
# Define any arguments set
|
||||||
|
args = {
|
||||||
|
'format': self.notify_format,
|
||||||
|
'overflow': self.overflow_mode,
|
||||||
|
'image': 'yes' if self.include_image else 'no',
|
||||||
|
'urgency': 'normal' if self.urgency not in _map
|
||||||
|
else _map[self.urgency]
|
||||||
|
}
|
||||||
|
|
||||||
|
return '{schema}://_/?{args}'.format(
|
||||||
|
schema=self.protocol,
|
||||||
|
args=NotifyGnome.urlencode(args),
|
||||||
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_url(url):
|
def parse_url(url):
|
||||||
@ -188,18 +213,43 @@ class NotifyGnome(NotifyBase):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# return a very basic set of requirements
|
results = NotifyBase.parse_url(url)
|
||||||
return {
|
if not results:
|
||||||
'schema': NotifyGnome.protocol,
|
results = {
|
||||||
'user': None,
|
'schema': NotifyGnome.protocol,
|
||||||
'password': None,
|
'user': None,
|
||||||
'port': None,
|
'password': None,
|
||||||
'host': 'localhost',
|
'port': None,
|
||||||
'fullpath': None,
|
'host': '_',
|
||||||
'path': None,
|
'fullpath': None,
|
||||||
'url': url,
|
'path': None,
|
||||||
'qsd': {},
|
'url': url,
|
||||||
# Set the urgency to None so that we fall back to the default
|
'qsd': {},
|
||||||
# value.
|
}
|
||||||
'urgency': None,
|
|
||||||
}
|
# Include images with our message
|
||||||
|
results['include_image'] = \
|
||||||
|
parse_bool(results['qsd'].get('image', True))
|
||||||
|
|
||||||
|
# Gnome supports urgency, but we we also support the keyword priority
|
||||||
|
# so that it is consistent with some of the other plugins
|
||||||
|
urgency = results['qsd'].get('urgency', results['qsd'].get('priority'))
|
||||||
|
if urgency and len(urgency):
|
||||||
|
_map = {
|
||||||
|
'0': GnomeUrgency.LOW,
|
||||||
|
'l': GnomeUrgency.LOW,
|
||||||
|
'n': GnomeUrgency.NORMAL,
|
||||||
|
'1': GnomeUrgency.NORMAL,
|
||||||
|
'h': GnomeUrgency.HIGH,
|
||||||
|
'2': GnomeUrgency.HIGH,
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Attempt to index/retrieve our urgency
|
||||||
|
results['urgency'] = _map[urgency[0].lower()]
|
||||||
|
|
||||||
|
except KeyError:
|
||||||
|
# No priority was set
|
||||||
|
pass
|
||||||
|
|
||||||
|
return results
|
||||||
|
@ -103,7 +103,7 @@ class NotifyGotify(NotifyBase):
|
|||||||
# Our access token does not get created until we first
|
# Our access token does not get created until we first
|
||||||
# authenticate with our Gotify server. The same goes for the
|
# authenticate with our Gotify server. The same goes for the
|
||||||
# user id below.
|
# user id below.
|
||||||
self.access_token = token
|
self.token = token
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -121,12 +121,12 @@ class NotifyGotify(NotifyBase):
|
|||||||
|
|
||||||
# Define our parameteers
|
# Define our parameteers
|
||||||
params = {
|
params = {
|
||||||
'token': self.access_token,
|
'token': self.token,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Prepare Gotify Object
|
# Prepare Gotify Object
|
||||||
payload = {
|
payload = {
|
||||||
'priority': 2,
|
'priority': self.priority,
|
||||||
'title': title,
|
'title': title,
|
||||||
'message': body,
|
'message': body,
|
||||||
}
|
}
|
||||||
@ -156,7 +156,7 @@ class NotifyGotify(NotifyBase):
|
|||||||
if r.status_code != requests.codes.ok:
|
if r.status_code != requests.codes.ok:
|
||||||
# We had a problem
|
# We had a problem
|
||||||
status_str = \
|
status_str = \
|
||||||
NotifyBase.http_response_code_lookup(r.status_code)
|
NotifyGotify.http_response_code_lookup(r.status_code)
|
||||||
|
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
'Failed to send Gotify notification: '
|
'Failed to send Gotify notification: '
|
||||||
@ -201,11 +201,11 @@ class NotifyGotify(NotifyBase):
|
|||||||
|
|
||||||
return '{schema}://{hostname}{port}/{token}/?{args}'.format(
|
return '{schema}://{hostname}{port}/{token}/?{args}'.format(
|
||||||
schema=self.secure_protocol if self.secure else self.protocol,
|
schema=self.secure_protocol if self.secure else self.protocol,
|
||||||
hostname=self.host,
|
hostname=NotifyGotify.quote(self.host, safe=''),
|
||||||
port='' if self.port is None or self.port == default_port
|
port='' if self.port is None or self.port == default_port
|
||||||
else ':{}'.format(self.port),
|
else ':{}'.format(self.port),
|
||||||
token=self.access_token,
|
token=NotifyGotify.quote(self.token, safe=''),
|
||||||
args=self.urlencode(args),
|
args=NotifyGotify.urlencode(args),
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -220,13 +220,17 @@ class NotifyGotify(NotifyBase):
|
|||||||
# We're done early
|
# We're done early
|
||||||
return results
|
return results
|
||||||
|
|
||||||
|
# Retrieve our escaped entries found on the fullpath
|
||||||
|
entries = NotifyBase.split_path(results['fullpath'])
|
||||||
|
|
||||||
# optionally find the provider key
|
# optionally find the provider key
|
||||||
try:
|
try:
|
||||||
token = [x for x in filter(
|
# The first entry is our token
|
||||||
bool, NotifyBase.split_path(results['fullpath']))][0]
|
results['token'] = entries.pop(0)
|
||||||
|
|
||||||
except (AttributeError, IndexError):
|
except IndexError:
|
||||||
token = None
|
# No token was set
|
||||||
|
results['token'] = None
|
||||||
|
|
||||||
if 'priority' in results['qsd'] and len(results['qsd']['priority']):
|
if 'priority' in results['qsd'] and len(results['qsd']['priority']):
|
||||||
_map = {
|
_map = {
|
||||||
@ -244,7 +248,4 @@ class NotifyGotify(NotifyBase):
|
|||||||
# No priority was set
|
# No priority was set
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Set our token
|
|
||||||
results['token'] = token
|
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
@ -28,6 +28,7 @@ from .gntp import errors
|
|||||||
from ..NotifyBase import NotifyBase
|
from ..NotifyBase import NotifyBase
|
||||||
from ...common import NotifyImageSize
|
from ...common import NotifyImageSize
|
||||||
from ...common import NotifyType
|
from ...common import NotifyType
|
||||||
|
from ...utils import parse_bool
|
||||||
|
|
||||||
|
|
||||||
# Priorities
|
# Priorities
|
||||||
@ -86,7 +87,7 @@ class NotifyGrowl(NotifyBase):
|
|||||||
# Default Growl Port
|
# Default Growl Port
|
||||||
default_port = 23053
|
default_port = 23053
|
||||||
|
|
||||||
def __init__(self, priority=None, version=2, **kwargs):
|
def __init__(self, priority=None, version=2, include_image=True, **kwargs):
|
||||||
"""
|
"""
|
||||||
Initialize Growl Object
|
Initialize Growl Object
|
||||||
"""
|
"""
|
||||||
@ -129,28 +130,26 @@ class NotifyGrowl(NotifyBase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
except errors.NetworkError:
|
except errors.NetworkError:
|
||||||
self.logger.warning(
|
msg = 'A network error occured sending Growl ' \
|
||||||
'A network error occured sending Growl '
|
'notification to {}.'.format(self.host)
|
||||||
'notification to %s.' % self.host)
|
self.logger.warning(msg)
|
||||||
raise TypeError(
|
raise TypeError(msg)
|
||||||
'A network error occured sending Growl '
|
|
||||||
'notification to %s.' % self.host)
|
|
||||||
|
|
||||||
except errors.AuthError:
|
except errors.AuthError:
|
||||||
self.logger.warning(
|
msg = 'An authentication error occured sending Growl ' \
|
||||||
'An authentication error occured sending Growl '
|
'notification to {}.'.format(self.host)
|
||||||
'notification to %s.' % self.host)
|
self.logger.warning(msg)
|
||||||
raise TypeError(
|
raise TypeError(msg)
|
||||||
'An authentication error occured sending Growl '
|
|
||||||
'notification to %s.' % self.host)
|
|
||||||
|
|
||||||
except errors.UnsupportedError:
|
except errors.UnsupportedError:
|
||||||
self.logger.warning(
|
msg = 'An unsupported error occured sending Growl ' \
|
||||||
'An unsupported error occured sending Growl '
|
'notification to {}.'.format(self.host)
|
||||||
'notification to %s.' % self.host)
|
self.logger.warning(msg)
|
||||||
raise TypeError(
|
raise TypeError(msg)
|
||||||
'An unsupported error occured sending Growl '
|
|
||||||
'notification to %s.' % self.host)
|
# Track whether or not we want to send an image with our notification
|
||||||
|
# or not.
|
||||||
|
self.include_image = include_image
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -162,11 +161,13 @@ class NotifyGrowl(NotifyBase):
|
|||||||
icon = None
|
icon = None
|
||||||
if self.version >= 2:
|
if self.version >= 2:
|
||||||
# URL Based
|
# URL Based
|
||||||
icon = self.image_url(notify_type)
|
icon = None if not self.include_image \
|
||||||
|
else self.image_url(notify_type)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Raw
|
# Raw
|
||||||
icon = self.image_raw(notify_type)
|
icon = None if not self.include_image \
|
||||||
|
else self.image_raw(notify_type)
|
||||||
|
|
||||||
payload = {
|
payload = {
|
||||||
'noteType': GROWL_NOTIFICATION_TYPE,
|
'noteType': GROWL_NOTIFICATION_TYPE,
|
||||||
@ -232,6 +233,7 @@ class NotifyGrowl(NotifyBase):
|
|||||||
args = {
|
args = {
|
||||||
'format': self.notify_format,
|
'format': self.notify_format,
|
||||||
'overflow': self.overflow_mode,
|
'overflow': self.overflow_mode,
|
||||||
|
'image': 'yes' if self.include_image else 'no',
|
||||||
'priority':
|
'priority':
|
||||||
_map[GrowlPriority.NORMAL] if self.priority not in _map
|
_map[GrowlPriority.NORMAL] if self.priority not in _map
|
||||||
else _map[self.priority],
|
else _map[self.priority],
|
||||||
@ -239,18 +241,19 @@ class NotifyGrowl(NotifyBase):
|
|||||||
}
|
}
|
||||||
|
|
||||||
auth = ''
|
auth = ''
|
||||||
if self.password:
|
if self.user:
|
||||||
|
# The growl password is stored in the user field
|
||||||
auth = '{password}@'.format(
|
auth = '{password}@'.format(
|
||||||
password=self.quote(self.user, safe=''),
|
password=NotifyGrowl.quote(self.user, safe=''),
|
||||||
)
|
)
|
||||||
|
|
||||||
return '{schema}://{auth}{hostname}{port}/?{args}'.format(
|
return '{schema}://{auth}{hostname}{port}/?{args}'.format(
|
||||||
schema=self.secure_protocol if self.secure else self.protocol,
|
schema=self.secure_protocol if self.secure else self.protocol,
|
||||||
auth=auth,
|
auth=auth,
|
||||||
hostname=self.host,
|
hostname=NotifyGrowl.quote(self.host, safe=''),
|
||||||
port='' if self.port is None or self.port == self.default_port
|
port='' if self.port is None or self.port == self.default_port
|
||||||
else ':{}'.format(self.port),
|
else ':{}'.format(self.port),
|
||||||
args=self.urlencode(args),
|
args=NotifyGrowl.urlencode(args),
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -272,11 +275,11 @@ class NotifyGrowl(NotifyBase):
|
|||||||
# Allow the user to specify the version of the protocol to use.
|
# Allow the user to specify the version of the protocol to use.
|
||||||
try:
|
try:
|
||||||
version = int(
|
version = int(
|
||||||
NotifyBase.unquote(
|
NotifyGrowl.unquote(
|
||||||
results['qsd']['version']).strip().split('.')[0])
|
results['qsd']['version']).strip().split('.')[0])
|
||||||
|
|
||||||
except (AttributeError, IndexError, TypeError, ValueError):
|
except (AttributeError, IndexError, TypeError, ValueError):
|
||||||
NotifyBase.logger.warning(
|
NotifyGrowl.logger.warning(
|
||||||
'An invalid Growl version of "%s" was specified and will '
|
'An invalid Growl version of "%s" was specified and will '
|
||||||
'be ignored.' % results['qsd']['version']
|
'be ignored.' % results['qsd']['version']
|
||||||
)
|
)
|
||||||
@ -306,6 +309,11 @@ class NotifyGrowl(NotifyBase):
|
|||||||
if results.get('password', None) is None:
|
if results.get('password', None) is None:
|
||||||
results['password'] = results.get('user', None)
|
results['password'] = results.get('user', None)
|
||||||
|
|
||||||
|
# Include images with our message
|
||||||
|
results['include_image'] = \
|
||||||
|
parse_bool(results['qsd'].get('image', True))
|
||||||
|
|
||||||
|
# Set our version
|
||||||
if version:
|
if version:
|
||||||
results['version'] = version
|
results['version'] = version
|
||||||
|
|
||||||
|
@ -108,14 +108,17 @@ class NotifyIFTTT(NotifyBase):
|
|||||||
super(NotifyIFTTT, self).__init__(**kwargs)
|
super(NotifyIFTTT, self).__init__(**kwargs)
|
||||||
|
|
||||||
if not webhook_id:
|
if not webhook_id:
|
||||||
raise TypeError('You must specify the Webhooks webhook_id.')
|
msg = 'You must specify the Webhooks webhook_id.'
|
||||||
|
self.logger.warning(msg)
|
||||||
|
raise TypeError(msg)
|
||||||
|
|
||||||
# Store our Events we wish to trigger
|
# Store our Events we wish to trigger
|
||||||
self.events = parse_list(events)
|
self.events = parse_list(events)
|
||||||
|
|
||||||
if not self.events:
|
if not self.events:
|
||||||
raise TypeError(
|
msg = 'You must specify at least one event you wish to trigger on.'
|
||||||
'You must specify at least one event you wish to trigger on.')
|
self.logger.warning(msg)
|
||||||
|
raise TypeError(msg)
|
||||||
|
|
||||||
# Store our APIKey
|
# Store our APIKey
|
||||||
self.webhook_id = webhook_id
|
self.webhook_id = webhook_id
|
||||||
@ -132,9 +135,10 @@ class NotifyIFTTT(NotifyBase):
|
|||||||
self.del_tokens = del_tokens
|
self.del_tokens = del_tokens
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise TypeError(
|
msg = 'del_token must be a list; {} was provided'.format(
|
||||||
'del_token must be a list; {} was provided'.format(
|
str(type(del_tokens)))
|
||||||
str(type(del_tokens))))
|
self.logger.warning(msg)
|
||||||
|
raise TypeError(msg)
|
||||||
|
|
||||||
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
||||||
"""
|
"""
|
||||||
@ -202,7 +206,7 @@ class NotifyIFTTT(NotifyBase):
|
|||||||
if r.status_code != requests.codes.ok:
|
if r.status_code != requests.codes.ok:
|
||||||
# We had a problem
|
# We had a problem
|
||||||
status_str = \
|
status_str = \
|
||||||
NotifyBase.http_response_code_lookup(r.status_code)
|
NotifyIFTTT.http_response_code_lookup(r.status_code)
|
||||||
|
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
'Failed to send IFTTT notification to {}: '
|
'Failed to send IFTTT notification to {}: '
|
||||||
@ -253,9 +257,10 @@ class NotifyIFTTT(NotifyBase):
|
|||||||
|
|
||||||
return '{schema}://{webhook_id}@{events}/?{args}'.format(
|
return '{schema}://{webhook_id}@{events}/?{args}'.format(
|
||||||
schema=self.secure_protocol,
|
schema=self.secure_protocol,
|
||||||
webhook_id=self.webhook_id,
|
webhook_id=NotifyIFTTT.quote(self.webhook_id, safe=''),
|
||||||
events='/'.join([self.quote(x, safe='') for x in self.events]),
|
events='/'.join([NotifyIFTTT.quote(x, safe='')
|
||||||
args=self.urlencode(args),
|
for x in self.events]),
|
||||||
|
args=NotifyIFTTT.urlencode(args),
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -271,15 +276,26 @@ class NotifyIFTTT(NotifyBase):
|
|||||||
# We're done early as we couldn't load the results
|
# We're done early as we couldn't load the results
|
||||||
return results
|
return results
|
||||||
|
|
||||||
|
# Our API Key is the hostname if no user is specified
|
||||||
|
results['webhook_id'] = \
|
||||||
|
results['user'] if results['user'] else results['host']
|
||||||
|
|
||||||
|
# Unquote our API Key
|
||||||
|
results['webhook_id'] = NotifyIFTTT.unquote(results['webhook_id'])
|
||||||
|
|
||||||
# Our Event
|
# Our Event
|
||||||
results['events'] = list()
|
results['events'] = list()
|
||||||
results['events'].append(results['host'])
|
if results['user']:
|
||||||
|
# If a user was defined, then the hostname is actually a event
|
||||||
# Our API Key
|
# too
|
||||||
results['webhook_id'] = results['user']
|
results['events'].append(NotifyIFTTT.unquote(results['host']))
|
||||||
|
|
||||||
# Now fetch the remaining tokens
|
# Now fetch the remaining tokens
|
||||||
results['events'].extend([x for x in filter(
|
results['events'].extend(NotifyIFTTT.split_path(results['fullpath']))
|
||||||
bool, NotifyBase.split_path(results['fullpath']))][0:])
|
|
||||||
|
# The 'to' makes it easier to use yaml configuration
|
||||||
|
if 'to' in results['qsd'] and len(results['qsd']['to']):
|
||||||
|
results['events'] += \
|
||||||
|
NotifyIFTTT.parse_list(results['qsd']['to'])
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
@ -56,7 +56,7 @@ class NotifyJSON(NotifyBase):
|
|||||||
# local anyway
|
# local anyway
|
||||||
request_rate_per_sec = 0
|
request_rate_per_sec = 0
|
||||||
|
|
||||||
def __init__(self, headers, **kwargs):
|
def __init__(self, headers=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
Initialize JSON Object
|
Initialize JSON Object
|
||||||
|
|
||||||
@ -66,12 +66,6 @@ class NotifyJSON(NotifyBase):
|
|||||||
"""
|
"""
|
||||||
super(NotifyJSON, self).__init__(**kwargs)
|
super(NotifyJSON, self).__init__(**kwargs)
|
||||||
|
|
||||||
if self.secure:
|
|
||||||
self.schema = 'https'
|
|
||||||
|
|
||||||
else:
|
|
||||||
self.schema = 'http'
|
|
||||||
|
|
||||||
self.fullpath = kwargs.get('fullpath')
|
self.fullpath = kwargs.get('fullpath')
|
||||||
if not isinstance(self.fullpath, six.string_types):
|
if not isinstance(self.fullpath, six.string_types):
|
||||||
self.fullpath = '/'
|
self.fullpath = '/'
|
||||||
@ -101,12 +95,12 @@ class NotifyJSON(NotifyBase):
|
|||||||
auth = ''
|
auth = ''
|
||||||
if self.user and self.password:
|
if self.user and self.password:
|
||||||
auth = '{user}:{password}@'.format(
|
auth = '{user}:{password}@'.format(
|
||||||
user=self.quote(self.user, safe=''),
|
user=NotifyJSON.quote(self.user, safe=''),
|
||||||
password=self.quote(self.password, safe=''),
|
password=NotifyJSON.quote(self.password, safe=''),
|
||||||
)
|
)
|
||||||
elif self.user:
|
elif self.user:
|
||||||
auth = '{user}@'.format(
|
auth = '{user}@'.format(
|
||||||
user=self.quote(self.user, safe=''),
|
user=NotifyJSON.quote(self.user, safe=''),
|
||||||
)
|
)
|
||||||
|
|
||||||
default_port = 443 if self.secure else 80
|
default_port = 443 if self.secure else 80
|
||||||
@ -114,10 +108,10 @@ class NotifyJSON(NotifyBase):
|
|||||||
return '{schema}://{auth}{hostname}{port}/?{args}'.format(
|
return '{schema}://{auth}{hostname}{port}/?{args}'.format(
|
||||||
schema=self.secure_protocol if self.secure else self.protocol,
|
schema=self.secure_protocol if self.secure else self.protocol,
|
||||||
auth=auth,
|
auth=auth,
|
||||||
hostname=self.host,
|
hostname=NotifyJSON.quote(self.host, safe=''),
|
||||||
port='' if self.port is None or self.port == default_port
|
port='' if self.port is None or self.port == default_port
|
||||||
else ':{}'.format(self.port),
|
else ':{}'.format(self.port),
|
||||||
args=self.urlencode(args),
|
args=NotifyJSON.urlencode(args),
|
||||||
)
|
)
|
||||||
|
|
||||||
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
||||||
@ -148,7 +142,10 @@ class NotifyJSON(NotifyBase):
|
|||||||
if self.user:
|
if self.user:
|
||||||
auth = (self.user, self.password)
|
auth = (self.user, self.password)
|
||||||
|
|
||||||
url = '%s://%s' % (self.schema, self.host)
|
# Set our schema
|
||||||
|
schema = 'https' if self.secure else 'http'
|
||||||
|
|
||||||
|
url = '%s://%s' % (schema, self.host)
|
||||||
if isinstance(self.port, int):
|
if isinstance(self.port, int):
|
||||||
url += ':%d' % self.port
|
url += ':%d' % self.port
|
||||||
|
|
||||||
@ -173,7 +170,7 @@ class NotifyJSON(NotifyBase):
|
|||||||
if r.status_code != requests.codes.ok:
|
if r.status_code != requests.codes.ok:
|
||||||
# We had a problem
|
# We had a problem
|
||||||
status_str = \
|
status_str = \
|
||||||
NotifyBase.http_response_code_lookup(r.status_code)
|
NotifyJSON.http_response_code_lookup(r.status_code)
|
||||||
|
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
'Failed to send JSON notification: '
|
'Failed to send JSON notification: '
|
||||||
@ -219,4 +216,8 @@ class NotifyJSON(NotifyBase):
|
|||||||
results['headers'] = results['qsd-']
|
results['headers'] = results['qsd-']
|
||||||
results['headers'].update(results['qsd+'])
|
results['headers'].update(results['qsd+'])
|
||||||
|
|
||||||
|
# Tidy our header entries by unquoting them
|
||||||
|
results['headers'] = {NotifyJSON.unquote(x): NotifyJSON.unquote(y)
|
||||||
|
for x, y in results['headers'].items()}
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
@ -34,12 +34,13 @@
|
|||||||
# https://play.google.com/store/apps/details?id=com.joaomgcd.join
|
# https://play.google.com/store/apps/details?id=com.joaomgcd.join
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import six
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from .NotifyBase import NotifyBase
|
from .NotifyBase import NotifyBase
|
||||||
from ..common import NotifyImageSize
|
from ..common import NotifyImageSize
|
||||||
from ..common import NotifyType
|
from ..common import NotifyType
|
||||||
|
from ..utils import parse_list
|
||||||
|
from ..utils import parse_bool
|
||||||
|
|
||||||
# Token required as part of the API request
|
# Token required as part of the API request
|
||||||
VALIDATE_APIKEY = re.compile(r'[A-Za-z0-9]{32}')
|
VALIDATE_APIKEY = re.compile(r'[A-Za-z0-9]{32}')
|
||||||
@ -49,9 +50,6 @@ JOIN_HTTP_ERROR_MAP = {
|
|||||||
401: 'Unauthorized - Invalid Token.',
|
401: 'Unauthorized - Invalid Token.',
|
||||||
}
|
}
|
||||||
|
|
||||||
# Used to break path apart into list of devices
|
|
||||||
DEVICE_LIST_DELIM = re.compile(r'[ \t\r\n,\\/]+')
|
|
||||||
|
|
||||||
# Used to detect a device
|
# Used to detect a device
|
||||||
IS_DEVICE_RE = re.compile(r'([A-Za-z0-9]{32})')
|
IS_DEVICE_RE = re.compile(r'([A-Za-z0-9]{32})')
|
||||||
|
|
||||||
@ -99,39 +97,32 @@ class NotifyJoin(NotifyBase):
|
|||||||
# The default group to use if none is specified
|
# The default group to use if none is specified
|
||||||
default_join_group = 'group.all'
|
default_join_group = 'group.all'
|
||||||
|
|
||||||
def __init__(self, apikey, devices, **kwargs):
|
def __init__(self, apikey, targets, include_image=True, **kwargs):
|
||||||
"""
|
"""
|
||||||
Initialize Join Object
|
Initialize Join Object
|
||||||
"""
|
"""
|
||||||
super(NotifyJoin, self).__init__(**kwargs)
|
super(NotifyJoin, self).__init__(**kwargs)
|
||||||
|
|
||||||
if not VALIDATE_APIKEY.match(apikey.strip()):
|
if not VALIDATE_APIKEY.match(apikey.strip()):
|
||||||
self.logger.warning(
|
msg = 'The JOIN API Token specified ({}) is invalid.'\
|
||||||
'The first API Token specified (%s) is invalid.' % apikey,
|
.format(apikey)
|
||||||
)
|
self.logger.warning(msg)
|
||||||
|
raise TypeError(msg)
|
||||||
raise TypeError(
|
|
||||||
'The first API Token specified (%s) is invalid.' % apikey,
|
|
||||||
)
|
|
||||||
|
|
||||||
# The token associated with the account
|
# The token associated with the account
|
||||||
self.apikey = apikey.strip()
|
self.apikey = apikey.strip()
|
||||||
|
|
||||||
if isinstance(devices, six.string_types):
|
# Parse devices specified
|
||||||
self.devices = [x for x in filter(bool, DEVICE_LIST_DELIM.split(
|
self.devices = parse_list(targets)
|
||||||
devices,
|
|
||||||
))]
|
|
||||||
|
|
||||||
elif isinstance(devices, (set, tuple, list)):
|
|
||||||
self.devices = devices
|
|
||||||
|
|
||||||
else:
|
|
||||||
self.devices = list()
|
|
||||||
|
|
||||||
if len(self.devices) == 0:
|
if len(self.devices) == 0:
|
||||||
# Default to everyone
|
# Default to everyone
|
||||||
self.devices.append(self.default_join_group)
|
self.devices.append(self.default_join_group)
|
||||||
|
|
||||||
|
# Track whether or not we want to send an image with our notification
|
||||||
|
# or not.
|
||||||
|
self.include_image = include_image
|
||||||
|
|
||||||
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
||||||
"""
|
"""
|
||||||
Perform Join Notification
|
Perform Join Notification
|
||||||
@ -151,13 +142,12 @@ class NotifyJoin(NotifyBase):
|
|||||||
device = devices.pop(0)
|
device = devices.pop(0)
|
||||||
group_re = IS_GROUP_RE.match(device)
|
group_re = IS_GROUP_RE.match(device)
|
||||||
if group_re:
|
if group_re:
|
||||||
device = 'group.%s' % group_re.group('name').lower()
|
device = 'group.{}'.format(group_re.group('name').lower())
|
||||||
|
|
||||||
elif not IS_DEVICE_RE.match(device):
|
elif not IS_DEVICE_RE.match(device):
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
"The specified device/group '%s' is invalid; skipping." % (
|
'Skipping specified invalid device/group "{}"'
|
||||||
device,
|
.format(device)
|
||||||
)
|
|
||||||
)
|
)
|
||||||
# Mark our failure
|
# Mark our failure
|
||||||
has_error = True
|
has_error = True
|
||||||
@ -170,7 +160,10 @@ class NotifyJoin(NotifyBase):
|
|||||||
'text': body,
|
'text': body,
|
||||||
}
|
}
|
||||||
|
|
||||||
image_url = self.image_url(notify_type)
|
# prepare our image for display if configured to do so
|
||||||
|
image_url = None if not self.include_image \
|
||||||
|
else self.image_url(notify_type)
|
||||||
|
|
||||||
if image_url:
|
if image_url:
|
||||||
url_args['icon'] = image_url
|
url_args['icon'] = image_url
|
||||||
|
|
||||||
@ -178,7 +171,7 @@ class NotifyJoin(NotifyBase):
|
|||||||
payload = {}
|
payload = {}
|
||||||
|
|
||||||
# Prepare the URL
|
# Prepare the URL
|
||||||
url = '%s?%s' % (self.notify_url, NotifyBase.urlencode(url_args))
|
url = '%s?%s' % (self.notify_url, NotifyJoin.urlencode(url_args))
|
||||||
|
|
||||||
self.logger.debug('Join POST URL: %s (cert_verify=%r)' % (
|
self.logger.debug('Join POST URL: %s (cert_verify=%r)' % (
|
||||||
url, self.verify_certificate,
|
url, self.verify_certificate,
|
||||||
@ -199,7 +192,7 @@ class NotifyJoin(NotifyBase):
|
|||||||
if r.status_code != requests.codes.ok:
|
if r.status_code != requests.codes.ok:
|
||||||
# We had a problem
|
# We had a problem
|
||||||
status_str = \
|
status_str = \
|
||||||
NotifyBase.http_response_code_lookup(
|
NotifyJoin.http_response_code_lookup(
|
||||||
r.status_code, JOIN_HTTP_ERROR_MAP)
|
r.status_code, JOIN_HTTP_ERROR_MAP)
|
||||||
|
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
@ -242,13 +235,15 @@ class NotifyJoin(NotifyBase):
|
|||||||
args = {
|
args = {
|
||||||
'format': self.notify_format,
|
'format': self.notify_format,
|
||||||
'overflow': self.overflow_mode,
|
'overflow': self.overflow_mode,
|
||||||
|
'image': 'yes' if self.include_image else 'no',
|
||||||
}
|
}
|
||||||
|
|
||||||
return '{schema}://{apikey}/{devices}/?{args}'.format(
|
return '{schema}://{apikey}/{devices}/?{args}'.format(
|
||||||
schema=self.secure_protocol,
|
schema=self.secure_protocol,
|
||||||
apikey=self.quote(self.apikey, safe=''),
|
apikey=NotifyJoin.quote(self.apikey, safe=''),
|
||||||
devices='/'.join([self.quote(x) for x in self.devices]),
|
devices='/'.join([NotifyJoin.quote(x, safe='')
|
||||||
args=self.urlencode(args))
|
for x in self.devices]),
|
||||||
|
args=NotifyJoin.urlencode(args))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_url(url):
|
def parse_url(url):
|
||||||
@ -263,11 +258,30 @@ class NotifyJoin(NotifyBase):
|
|||||||
# We're done early as we couldn't load the results
|
# We're done early as we couldn't load the results
|
||||||
return results
|
return results
|
||||||
|
|
||||||
# Apply our settings now
|
# Our API Key is the hostname if no user is specified
|
||||||
devices = ' '.join(
|
results['apikey'] = \
|
||||||
filter(bool, NotifyBase.split_path(results['fullpath'])))
|
results['user'] if results['user'] else results['host']
|
||||||
|
|
||||||
results['apikey'] = results['host']
|
# Unquote our API Key
|
||||||
results['devices'] = devices
|
results['apikey'] = NotifyJoin.unquote(results['apikey'])
|
||||||
|
|
||||||
|
# Our Devices
|
||||||
|
results['targets'] = list()
|
||||||
|
if results['user']:
|
||||||
|
# If a user was defined, then the hostname is actually a target
|
||||||
|
# too
|
||||||
|
results['targets'].append(NotifyJoin.unquote(results['host']))
|
||||||
|
|
||||||
|
# Now fetch the remaining tokens
|
||||||
|
results['targets'].extend(
|
||||||
|
NotifyJoin.split_path(results['fullpath']))
|
||||||
|
|
||||||
|
# The 'to' makes it easier to use yaml configuration
|
||||||
|
if 'to' in results['qsd'] and len(results['qsd']['to']):
|
||||||
|
results['targets'] += NotifyJoin.parse_list(results['qsd']['to'])
|
||||||
|
|
||||||
|
# Include images with our message
|
||||||
|
results['include_image'] = \
|
||||||
|
parse_bool(results['qsd'].get('image', True))
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
@ -39,6 +39,7 @@ from ..common import NotifyType
|
|||||||
from ..common import NotifyImageSize
|
from ..common import NotifyImageSize
|
||||||
from ..common import NotifyFormat
|
from ..common import NotifyFormat
|
||||||
from ..utils import parse_bool
|
from ..utils import parse_bool
|
||||||
|
from ..utils import parse_list
|
||||||
|
|
||||||
# Define default path
|
# Define default path
|
||||||
MATRIX_V2_API_PATH = '/_matrix/client/r0'
|
MATRIX_V2_API_PATH = '/_matrix/client/r0'
|
||||||
@ -50,10 +51,6 @@ MATRIX_HTTP_ERROR_MAP = {
|
|||||||
429: 'Rate limit imposed; wait 2s and try again',
|
429: 'Rate limit imposed; wait 2s and try again',
|
||||||
}
|
}
|
||||||
|
|
||||||
# Used to break apart list of potential tags by their delimiter
|
|
||||||
# into a usable list.
|
|
||||||
LIST_DELIM = re.compile(r'[ \t\r\n,\\/]+')
|
|
||||||
|
|
||||||
# Matrix Room Syntax
|
# Matrix Room Syntax
|
||||||
IS_ROOM_ALIAS = re.compile(
|
IS_ROOM_ALIAS = re.compile(
|
||||||
r'^\s*(#|%23)?(?P<room>[a-z0-9-]+)((:|%3A)'
|
r'^\s*(#|%23)?(?P<room>[a-z0-9-]+)((:|%3A)'
|
||||||
@ -120,30 +117,15 @@ class NotifyMatrix(NotifyBase):
|
|||||||
# the server doesn't remind us how long we shoul wait for
|
# the server doesn't remind us how long we shoul wait for
|
||||||
default_wait_ms = 1000
|
default_wait_ms = 1000
|
||||||
|
|
||||||
def __init__(self, rooms=None, webhook=None, thumbnail=True, **kwargs):
|
def __init__(self, targets=None, mode=None, include_image=True,
|
||||||
|
**kwargs):
|
||||||
"""
|
"""
|
||||||
Initialize Matrix Object
|
Initialize Matrix Object
|
||||||
"""
|
"""
|
||||||
super(NotifyMatrix, self).__init__(**kwargs)
|
super(NotifyMatrix, self).__init__(**kwargs)
|
||||||
|
|
||||||
# Prepare a list of rooms to connect and notify
|
# Prepare a list of rooms to connect and notify
|
||||||
if isinstance(rooms, six.string_types):
|
self.rooms = parse_list(targets)
|
||||||
self.rooms = [x for x in filter(bool, LIST_DELIM.split(
|
|
||||||
rooms,
|
|
||||||
))]
|
|
||||||
|
|
||||||
elif isinstance(rooms, (set, tuple, list)):
|
|
||||||
self.rooms = rooms
|
|
||||||
|
|
||||||
else:
|
|
||||||
self.rooms = []
|
|
||||||
|
|
||||||
self.webhook = None \
|
|
||||||
if not isinstance(webhook, six.string_types) else webhook.lower()
|
|
||||||
if self.webhook and self.webhook not in MATRIX_WEBHOOK_MODES:
|
|
||||||
msg = 'The webhook specified ({}) is invalid.'.format(webhook)
|
|
||||||
self.logger.warning(msg)
|
|
||||||
raise TypeError(msg)
|
|
||||||
|
|
||||||
# our home server gets populated after a login/registration
|
# our home server gets populated after a login/registration
|
||||||
self.home_server = None
|
self.home_server = None
|
||||||
@ -154,23 +136,31 @@ class NotifyMatrix(NotifyBase):
|
|||||||
# This gets initialized after a login/registration
|
# This gets initialized after a login/registration
|
||||||
self.access_token = None
|
self.access_token = None
|
||||||
|
|
||||||
# Place a thumbnail image inline with the message body
|
# Place an image inline with the message body
|
||||||
self.thumbnail = thumbnail
|
self.include_image = include_image
|
||||||
|
|
||||||
# maintain a lookup of room alias's we already paired with their id
|
# maintain a lookup of room alias's we already paired with their id
|
||||||
# to speed up future requests
|
# to speed up future requests
|
||||||
self._room_cache = {}
|
self._room_cache = {}
|
||||||
|
|
||||||
|
# Setup our mode
|
||||||
|
self.mode = None \
|
||||||
|
if not isinstance(mode, six.string_types) else mode.lower()
|
||||||
|
if self.mode and self.mode not in MATRIX_WEBHOOK_MODES:
|
||||||
|
msg = 'The mode specified ({}) is invalid.'.format(mode)
|
||||||
|
self.logger.warning(msg)
|
||||||
|
raise TypeError(msg)
|
||||||
|
|
||||||
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
||||||
"""
|
"""
|
||||||
Perform Matrix Notification
|
Perform Matrix Notification
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Call the _send_ function applicable to whatever mode we're in
|
# Call the _send_ function applicable to whatever mode we're in
|
||||||
# - calls _send_webhook_notification if the webhook variable is set
|
# - calls _send_webhook_notification if the mode variable is set
|
||||||
# - calls _send_server_notification if the webhook variable is not set
|
# - calls _send_server_notification if the mode variable is not set
|
||||||
return getattr(self, '_send_{}_notification'.format(
|
return getattr(self, '_send_{}_notification'.format(
|
||||||
'webhook' if self.webhook else 'server'))(
|
'webhook' if self.mode else 'server'))(
|
||||||
body=body, title=title, notify_type=notify_type, **kwargs)
|
body=body, title=title, notify_type=notify_type, **kwargs)
|
||||||
|
|
||||||
def _send_webhook_notification(self, body, title='',
|
def _send_webhook_notification(self, body, title='',
|
||||||
@ -200,7 +190,7 @@ class NotifyMatrix(NotifyBase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Retrieve our payload
|
# Retrieve our payload
|
||||||
payload = getattr(self, '_{}_webhook_payload'.format(self.webhook))(
|
payload = getattr(self, '_{}_webhook_payload'.format(self.mode))(
|
||||||
body=body, title=title, notify_type=notify_type, **kwargs)
|
body=body, title=title, notify_type=notify_type, **kwargs)
|
||||||
|
|
||||||
self.logger.debug('Matrix POST URL: %s (cert_verify=%r)' % (
|
self.logger.debug('Matrix POST URL: %s (cert_verify=%r)' % (
|
||||||
@ -221,7 +211,7 @@ class NotifyMatrix(NotifyBase):
|
|||||||
if r.status_code != requests.codes.ok:
|
if r.status_code != requests.codes.ok:
|
||||||
# We had a problem
|
# We had a problem
|
||||||
status_str = \
|
status_str = \
|
||||||
NotifyBase.http_response_code_lookup(
|
NotifyMatrix.http_response_code_lookup(
|
||||||
r.status_code, MATRIX_HTTP_ERROR_MAP)
|
r.status_code, MATRIX_HTTP_ERROR_MAP)
|
||||||
|
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
@ -318,8 +308,8 @@ class NotifyMatrix(NotifyBase):
|
|||||||
else: # TEXT or MARKDOWN
|
else: # TEXT or MARKDOWN
|
||||||
|
|
||||||
# Ensure our content is escaped
|
# Ensure our content is escaped
|
||||||
title = NotifyBase.escape_html(title)
|
title = NotifyMatrix.escape_html(title)
|
||||||
body = NotifyBase.escape_html(body)
|
body = NotifyMatrix.escape_html(body)
|
||||||
|
|
||||||
payload['text'] = '{}{}'.format(
|
payload['text'] = '{}{}'.format(
|
||||||
'' if not title else '<h4>{}</h4>'.format(title), body)
|
'' if not title else '<h4>{}</h4>'.format(title), body)
|
||||||
@ -375,8 +365,11 @@ class NotifyMatrix(NotifyBase):
|
|||||||
title='' if not title else '{}\r\n'.format(title),
|
title='' if not title else '{}\r\n'.format(title),
|
||||||
body=body)
|
body=body)
|
||||||
|
|
||||||
image_url = self.image_url(notify_type)
|
# Acquire our image url if we're configured to do so
|
||||||
if self.thumbnail and image_url:
|
image_url = None if not self.include_image else \
|
||||||
|
self.image_url(notify_type)
|
||||||
|
|
||||||
|
if image_url:
|
||||||
# Define our payload
|
# Define our payload
|
||||||
image_payload = {
|
image_payload = {
|
||||||
'msgtype': 'm.image',
|
'msgtype': 'm.image',
|
||||||
@ -385,7 +378,7 @@ class NotifyMatrix(NotifyBase):
|
|||||||
}
|
}
|
||||||
# Build our path
|
# Build our path
|
||||||
path = '/rooms/{}/send/m.room.message'.format(
|
path = '/rooms/{}/send/m.room.message'.format(
|
||||||
NotifyBase.quote(room_id))
|
NotifyMatrix.quote(room_id))
|
||||||
|
|
||||||
# Post our content
|
# Post our content
|
||||||
postokay, response = self._fetch(path, payload=image_payload)
|
postokay, response = self._fetch(path, payload=image_payload)
|
||||||
@ -402,7 +395,7 @@ class NotifyMatrix(NotifyBase):
|
|||||||
|
|
||||||
# Build our path
|
# Build our path
|
||||||
path = '/rooms/{}/send/m.room.message'.format(
|
path = '/rooms/{}/send/m.room.message'.format(
|
||||||
NotifyBase.quote(room_id))
|
NotifyMatrix.quote(room_id))
|
||||||
|
|
||||||
# Post our content
|
# Post our content
|
||||||
postokay, response = self._fetch(path, payload=payload)
|
postokay, response = self._fetch(path, payload=payload)
|
||||||
@ -446,7 +439,7 @@ class NotifyMatrix(NotifyBase):
|
|||||||
# Register
|
# Register
|
||||||
postokay, response = \
|
postokay, response = \
|
||||||
self._fetch('/register', payload=payload, params=params)
|
self._fetch('/register', payload=payload, params=params)
|
||||||
if not postokay:
|
if not (postokay and isinstance(response, dict)):
|
||||||
# Failed to register
|
# Failed to register
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -489,7 +482,7 @@ class NotifyMatrix(NotifyBase):
|
|||||||
|
|
||||||
# Build our URL
|
# Build our URL
|
||||||
postokay, response = self._fetch('/login', payload=payload)
|
postokay, response = self._fetch('/login', payload=payload)
|
||||||
if not postokay:
|
if not (postokay and isinstance(response, dict)):
|
||||||
# Failed to login
|
# Failed to login
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -581,7 +574,7 @@ class NotifyMatrix(NotifyBase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Build our URL
|
# Build our URL
|
||||||
path = '/join/{}'.format(NotifyBase.quote(room_id))
|
path = '/join/{}'.format(NotifyMatrix.quote(room_id))
|
||||||
|
|
||||||
# Make our query
|
# Make our query
|
||||||
postokay, _ = self._fetch(path, payload=payload)
|
postokay, _ = self._fetch(path, payload=payload)
|
||||||
@ -612,7 +605,7 @@ class NotifyMatrix(NotifyBase):
|
|||||||
# If we reach here, we need to join the channel
|
# If we reach here, we need to join the channel
|
||||||
|
|
||||||
# Build our URL
|
# Build our URL
|
||||||
path = '/join/{}'.format(NotifyBase.quote(room))
|
path = '/join/{}'.format(NotifyMatrix.quote(room))
|
||||||
|
|
||||||
# Attempt to join the channel
|
# Attempt to join the channel
|
||||||
postokay, response = self._fetch(path, payload=payload)
|
postokay, response = self._fetch(path, payload=payload)
|
||||||
@ -695,7 +688,7 @@ class NotifyMatrix(NotifyBase):
|
|||||||
return list()
|
return list()
|
||||||
|
|
||||||
postokay, response = self._fetch(
|
postokay, response = self._fetch(
|
||||||
'/joined_rooms', payload=None, fn=requests.get)
|
'/joined_rooms', payload=None, method='GET')
|
||||||
if not postokay:
|
if not postokay:
|
||||||
# Failed to retrieve listings
|
# Failed to retrieve listings
|
||||||
return list()
|
return list()
|
||||||
@ -736,14 +729,14 @@ class NotifyMatrix(NotifyBase):
|
|||||||
# Make our request
|
# Make our request
|
||||||
postokay, response = self._fetch(
|
postokay, response = self._fetch(
|
||||||
"/directory/room/{}".format(
|
"/directory/room/{}".format(
|
||||||
self.quote(room)), payload=None, fn=requests.get)
|
NotifyMatrix.quote(room)), payload=None, method='GET')
|
||||||
|
|
||||||
if postokay:
|
if postokay:
|
||||||
return response.get("room_id")
|
return response.get("room_id")
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _fetch(self, path, payload=None, params=None, fn=requests.post):
|
def _fetch(self, path, payload=None, params=None, method='POST'):
|
||||||
"""
|
"""
|
||||||
Wrapper to request.post() to manage it's response better and make
|
Wrapper to request.post() to manage it's response better and make
|
||||||
the send() function cleaner and easier to maintain.
|
the send() function cleaner and easier to maintain.
|
||||||
@ -775,6 +768,9 @@ class NotifyMatrix(NotifyBase):
|
|||||||
# Our response object
|
# Our response object
|
||||||
response = {}
|
response = {}
|
||||||
|
|
||||||
|
# fetch function
|
||||||
|
fn = requests.post if method == 'POST' else requests.get
|
||||||
|
|
||||||
# Define how many attempts we'll make if we get caught in a throttle
|
# Define how many attempts we'll make if we get caught in a throttle
|
||||||
# event
|
# event
|
||||||
retries = self.default_retries if self.default_retries > 0 else 1
|
retries = self.default_retries if self.default_retries > 0 else 1
|
||||||
@ -789,7 +785,7 @@ class NotifyMatrix(NotifyBase):
|
|||||||
self.logger.debug('Matrix Payload: %s' % str(payload))
|
self.logger.debug('Matrix Payload: %s' % str(payload))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
r = requests.post(
|
r = fn(
|
||||||
url,
|
url,
|
||||||
data=dumps(payload),
|
data=dumps(payload),
|
||||||
params=params,
|
params=params,
|
||||||
@ -826,7 +822,7 @@ class NotifyMatrix(NotifyBase):
|
|||||||
elif r.status_code != requests.codes.ok:
|
elif r.status_code != requests.codes.ok:
|
||||||
# We had a problem
|
# We had a problem
|
||||||
status_str = \
|
status_str = \
|
||||||
NotifyBase.http_response_code_lookup(
|
NotifyMatrix.http_response_code_lookup(
|
||||||
r.status_code, MATRIX_HTTP_ERROR_MAP)
|
r.status_code, MATRIX_HTTP_ERROR_MAP)
|
||||||
|
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
@ -877,22 +873,23 @@ class NotifyMatrix(NotifyBase):
|
|||||||
args = {
|
args = {
|
||||||
'format': self.notify_format,
|
'format': self.notify_format,
|
||||||
'overflow': self.overflow_mode,
|
'overflow': self.overflow_mode,
|
||||||
|
'image': 'yes' if self.include_image else 'no',
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.webhook:
|
if self.mode:
|
||||||
args['webhook'] = self.webhook
|
args['mode'] = self.mode
|
||||||
|
|
||||||
# Determine Authentication method
|
# Determine Authentication
|
||||||
auth = ''
|
auth = ''
|
||||||
if self.user and self.password:
|
if self.user and self.password:
|
||||||
auth = '{user}:{password}@'.format(
|
auth = '{user}:{password}@'.format(
|
||||||
user=self.quote(self.user, safe=''),
|
user=NotifyMatrix.quote(self.user, safe=''),
|
||||||
password=self.quote(self.password, safe=''),
|
password=NotifyMatrix.quote(self.password, safe=''),
|
||||||
)
|
)
|
||||||
|
|
||||||
elif self.user:
|
elif self.user:
|
||||||
auth = '{user}@'.format(
|
auth = '{user}@'.format(
|
||||||
user=self.quote(self.user, safe=''),
|
user=NotifyMatrix.quote(self.user, safe=''),
|
||||||
)
|
)
|
||||||
|
|
||||||
default_port = 443 if self.secure else 80
|
default_port = 443 if self.secure else 80
|
||||||
@ -900,11 +897,11 @@ class NotifyMatrix(NotifyBase):
|
|||||||
return '{schema}://{auth}{hostname}{port}/{rooms}?{args}'.format(
|
return '{schema}://{auth}{hostname}{port}/{rooms}?{args}'.format(
|
||||||
schema=self.secure_protocol if self.secure else self.protocol,
|
schema=self.secure_protocol if self.secure else self.protocol,
|
||||||
auth=auth,
|
auth=auth,
|
||||||
hostname=self.host,
|
hostname=NotifyMatrix.quote(self.host, safe=''),
|
||||||
port='' if self.port is None
|
port='' if self.port is None
|
||||||
or self.port == default_port else ':{}'.format(self.port),
|
or self.port == default_port else ':{}'.format(self.port),
|
||||||
rooms=self.quote('/'.join(self.rooms)),
|
rooms=NotifyMatrix.quote('/'.join(self.rooms)),
|
||||||
args=self.urlencode(args),
|
args=NotifyMatrix.urlencode(args),
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -921,15 +918,40 @@ class NotifyMatrix(NotifyBase):
|
|||||||
return results
|
return results
|
||||||
|
|
||||||
# Get our rooms
|
# Get our rooms
|
||||||
results['rooms'] = [
|
results['targets'] = NotifyMatrix.split_path(results['fullpath'])
|
||||||
NotifyBase.unquote(x) for x in filter(bool, NotifyBase.split_path(
|
|
||||||
results['fullpath']))][0:]
|
|
||||||
|
|
||||||
# Use Thumbnail
|
# Support the 'to' variable so that we can support rooms this way too
|
||||||
results['thumbnail'] = \
|
# The 'to' makes it easier to use yaml configuration
|
||||||
parse_bool(results['qsd'].get('thumbnail', False))
|
if 'to' in results['qsd'] and len(results['qsd']['to']):
|
||||||
|
results['targets'] += NotifyMatrix.parse_list(results['qsd']['to'])
|
||||||
|
|
||||||
# Webhook
|
# Thumbnail (old way)
|
||||||
results['webhook'] = results['qsd'].get('webhook')
|
if 'thumbnail' in results['qsd']:
|
||||||
|
# Deprication Notice issued for v0.7.5
|
||||||
|
NotifyMatrix.logger.warning(
|
||||||
|
'DEPRICATION NOTICE - The Matrix URL contains the parameter '
|
||||||
|
'"thumbnail=" which will be depricated in an upcoming '
|
||||||
|
'release. Please use "image=" instead.'
|
||||||
|
)
|
||||||
|
|
||||||
|
# use image= for consistency with the other plugins but we also
|
||||||
|
# support thumbnail= for backwards compatibility.
|
||||||
|
results['include_image'] = \
|
||||||
|
parse_bool(results['qsd'].get(
|
||||||
|
'image', results['qsd'].get('thumbnail', False)))
|
||||||
|
|
||||||
|
# Webhook (old way)
|
||||||
|
if 'webhook' in results['qsd']:
|
||||||
|
# Deprication Notice issued for v0.7.5
|
||||||
|
NotifyMatrix.logger.warning(
|
||||||
|
'DEPRICATION NOTICE - The Matrix URL contains the parameter '
|
||||||
|
'"webhook=" which will be depricated in an upcoming '
|
||||||
|
'release. Please use "mode=" instead.'
|
||||||
|
)
|
||||||
|
|
||||||
|
# use mode= for consistency with the other plugins but we also
|
||||||
|
# support webhook= for backwards compatibility.
|
||||||
|
results['mode'] = results['qsd'].get(
|
||||||
|
'mode', results['qsd'].get('webhook'))
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
@ -30,6 +30,8 @@ from json import dumps
|
|||||||
from .NotifyBase import NotifyBase
|
from .NotifyBase import NotifyBase
|
||||||
from ..common import NotifyImageSize
|
from ..common import NotifyImageSize
|
||||||
from ..common import NotifyType
|
from ..common import NotifyType
|
||||||
|
from ..utils import parse_bool
|
||||||
|
from ..utils import parse_list
|
||||||
|
|
||||||
# Some Reference Locations:
|
# Some Reference Locations:
|
||||||
# - https://docs.mattermost.com/developer/webhooks-incoming.html
|
# - https://docs.mattermost.com/developer/webhooks-incoming.html
|
||||||
@ -71,7 +73,8 @@ class NotifyMatterMost(NotifyBase):
|
|||||||
# Mattermost does not have a title
|
# Mattermost does not have a title
|
||||||
title_maxlen = 0
|
title_maxlen = 0
|
||||||
|
|
||||||
def __init__(self, authtoken, channel=None, **kwargs):
|
def __init__(self, authtoken, channels=None, include_image=True,
|
||||||
|
**kwargs):
|
||||||
"""
|
"""
|
||||||
Initialize MatterMost Object
|
Initialize MatterMost Object
|
||||||
"""
|
"""
|
||||||
@ -88,27 +91,24 @@ class NotifyMatterMost(NotifyBase):
|
|||||||
|
|
||||||
# Validate authtoken
|
# Validate authtoken
|
||||||
if not authtoken:
|
if not authtoken:
|
||||||
self.logger.warning(
|
msg = 'Missing MatterMost Authorization Token.'
|
||||||
'Missing MatterMost Authorization Token.'
|
self.logger.warning(msg)
|
||||||
)
|
raise TypeError(msg)
|
||||||
raise TypeError(
|
|
||||||
'Missing MatterMost Authorization Token.'
|
|
||||||
)
|
|
||||||
|
|
||||||
if not VALIDATE_AUTHTOKEN.match(authtoken):
|
if not VALIDATE_AUTHTOKEN.match(authtoken):
|
||||||
self.logger.warning(
|
msg = 'Invalid MatterMost Authorization Token Specified.'
|
||||||
'Invalid MatterMost Authorization Token Specified.'
|
self.logger.warning(msg)
|
||||||
)
|
raise TypeError(msg)
|
||||||
raise TypeError(
|
|
||||||
'Invalid MatterMost Authorization Token Specified.'
|
|
||||||
)
|
|
||||||
|
|
||||||
# A Channel (optional)
|
# Optional Channels
|
||||||
self.channel = channel
|
self.channels = parse_list(channels)
|
||||||
|
|
||||||
if not self.port:
|
if not self.port:
|
||||||
self.port = self.default_port
|
self.port = self.default_port
|
||||||
|
|
||||||
|
# Place a thumbnail image inline with the message body
|
||||||
|
self.include_image = include_image
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
||||||
@ -116,6 +116,9 @@ class NotifyMatterMost(NotifyBase):
|
|||||||
Perform MatterMost Notification
|
Perform MatterMost Notification
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# Create a copy of our channels, otherwise place a dummy entry
|
||||||
|
channels = list(self.channels) if self.channels else [None, ]
|
||||||
|
|
||||||
headers = {
|
headers = {
|
||||||
'User-Agent': self.app_id,
|
'User-Agent': self.app_id,
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
@ -124,67 +127,91 @@ class NotifyMatterMost(NotifyBase):
|
|||||||
# prepare JSON Object
|
# prepare JSON Object
|
||||||
payload = {
|
payload = {
|
||||||
'text': body,
|
'text': body,
|
||||||
'icon_url': self.image_url(notify_type),
|
'icon_url': None,
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.user:
|
# Acquire our image url if configured to do so
|
||||||
payload['username'] = self.user
|
image_url = None if not self.include_image \
|
||||||
|
else self.image_url(notify_type)
|
||||||
|
|
||||||
else:
|
if image_url:
|
||||||
payload['username'] = self.app_id
|
# Set our image configuration if told to do so
|
||||||
|
payload['icon_url'] = image_url
|
||||||
|
|
||||||
if self.channel:
|
# Set our user
|
||||||
payload['channel'] = self.channel
|
payload['username'] = self.user if self.user else self.app_id
|
||||||
|
|
||||||
url = '%s://%s:%d' % (self.schema, self.host, self.port)
|
# For error tracking
|
||||||
url += '/hooks/%s' % self.authtoken
|
has_error = False
|
||||||
|
|
||||||
self.logger.debug('MatterMost POST URL: %s (cert_verify=%r)' % (
|
while len(channels):
|
||||||
url, self.verify_certificate,
|
# Pop a channel off of the list
|
||||||
))
|
channel = channels.pop(0)
|
||||||
self.logger.debug('MatterMost Payload: %s' % str(payload))
|
|
||||||
|
|
||||||
# Always call throttle before any remote server i/o is made
|
if channel:
|
||||||
self.throttle()
|
payload['channel'] = channel
|
||||||
|
|
||||||
try:
|
url = '%s://%s:%d' % (self.schema, self.host, self.port)
|
||||||
r = requests.post(
|
url += '/hooks/%s' % self.authtoken
|
||||||
url,
|
|
||||||
data=dumps(payload),
|
|
||||||
headers=headers,
|
|
||||||
verify=self.verify_certificate,
|
|
||||||
)
|
|
||||||
if r.status_code != requests.codes.ok:
|
|
||||||
# We had a problem
|
|
||||||
status_str = \
|
|
||||||
NotifyBase.http_response_code_lookup(r.status_code)
|
|
||||||
|
|
||||||
|
self.logger.debug('MatterMost POST URL: %s (cert_verify=%r)' % (
|
||||||
|
url, self.verify_certificate,
|
||||||
|
))
|
||||||
|
self.logger.debug('MatterMost Payload: %s' % str(payload))
|
||||||
|
|
||||||
|
# Always call throttle before any remote server i/o is made
|
||||||
|
self.throttle()
|
||||||
|
|
||||||
|
try:
|
||||||
|
r = requests.post(
|
||||||
|
url,
|
||||||
|
data=dumps(payload),
|
||||||
|
headers=headers,
|
||||||
|
verify=self.verify_certificate,
|
||||||
|
)
|
||||||
|
|
||||||
|
if r.status_code != requests.codes.ok:
|
||||||
|
# We had a problem
|
||||||
|
status_str = \
|
||||||
|
NotifyMatterMost.http_response_code_lookup(
|
||||||
|
r.status_code)
|
||||||
|
|
||||||
|
self.logger.warning(
|
||||||
|
'Failed to send MatterMost notification{}: '
|
||||||
|
'{}{}error={}.'.format(
|
||||||
|
'' if not channel
|
||||||
|
else ' to channel {}'.format(channel),
|
||||||
|
status_str,
|
||||||
|
', ' if status_str else '',
|
||||||
|
r.status_code))
|
||||||
|
|
||||||
|
self.logger.debug(
|
||||||
|
'Response Details:\r\n{}'.format(r.content))
|
||||||
|
|
||||||
|
# Flag our error
|
||||||
|
has_error = True
|
||||||
|
continue
|
||||||
|
|
||||||
|
else:
|
||||||
|
self.logger.info(
|
||||||
|
'Sent MatterMost notification{}.'.format(
|
||||||
|
'' if not channel
|
||||||
|
else ' to channel {}'.format(channel)))
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
'Failed to send MatterMost notification: '
|
'A Connection error occured sending MatterMost '
|
||||||
'{}{}error={}.'.format(
|
'notification{}.'.format(
|
||||||
status_str,
|
'' if not channel
|
||||||
', ' if status_str else '',
|
else ' to channel {}'.format(channel)))
|
||||||
r.status_code))
|
self.logger.debug('Socket Exception: %s' % str(e))
|
||||||
|
|
||||||
self.logger.debug('Response Details:\r\n{}'.format(r.content))
|
# Flag our error
|
||||||
|
has_error = True
|
||||||
|
continue
|
||||||
|
|
||||||
# Return; we're done
|
# Return our overall status
|
||||||
return False
|
return not has_error
|
||||||
|
|
||||||
else:
|
|
||||||
self.logger.info('Sent MatterMost notification.')
|
|
||||||
|
|
||||||
except requests.RequestException as e:
|
|
||||||
self.logger.warning(
|
|
||||||
'A Connection error occured sending MatterMost '
|
|
||||||
'notification.'
|
|
||||||
)
|
|
||||||
self.logger.debug('Socket Exception: %s' % str(e))
|
|
||||||
|
|
||||||
# Return; we're done
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def url(self):
|
def url(self):
|
||||||
"""
|
"""
|
||||||
@ -195,18 +222,25 @@ class NotifyMatterMost(NotifyBase):
|
|||||||
args = {
|
args = {
|
||||||
'format': self.notify_format,
|
'format': self.notify_format,
|
||||||
'overflow': self.overflow_mode,
|
'overflow': self.overflow_mode,
|
||||||
|
'image': 'yes' if self.include_image else 'no',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.channels:
|
||||||
|
# historically the value only accepted one channel and is
|
||||||
|
# therefore identified as 'channel'. Channels have always been
|
||||||
|
# optional, so that is why this setting is nested in an if block
|
||||||
|
args['channel'] = ','.join(self.channels)
|
||||||
|
|
||||||
default_port = 443 if self.secure else self.default_port
|
default_port = 443 if self.secure else self.default_port
|
||||||
default_schema = self.secure_protocol if self.secure else self.protocol
|
default_schema = self.secure_protocol if self.secure else self.protocol
|
||||||
|
|
||||||
return '{schema}://{hostname}{port}/{authtoken}/?{args}'.format(
|
return '{schema}://{hostname}{port}/{authtoken}/?{args}'.format(
|
||||||
schema=default_schema,
|
schema=default_schema,
|
||||||
hostname=self.host,
|
hostname=NotifyMatterMost.quote(self.host, safe=''),
|
||||||
port='' if not self.port or self.port == default_port
|
port='' if not self.port or self.port == default_port
|
||||||
else ':{}'.format(self.port),
|
else ':{}'.format(self.port),
|
||||||
authtoken=self.quote(self.authtoken, safe=''),
|
authtoken=NotifyMatterMost.quote(self.authtoken, safe=''),
|
||||||
args=self.urlencode(args),
|
args=NotifyMatterMost.urlencode(args),
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -222,15 +256,31 @@ class NotifyMatterMost(NotifyBase):
|
|||||||
# We're done early as we couldn't load the results
|
# We're done early as we couldn't load the results
|
||||||
return results
|
return results
|
||||||
|
|
||||||
# Apply our settings now
|
try:
|
||||||
authtoken = NotifyBase.split_path(results['fullpath'])[0]
|
# Apply our settings now
|
||||||
|
results['authtoken'] = \
|
||||||
|
NotifyMatterMost.split_path(results['fullpath'])[0]
|
||||||
|
|
||||||
|
except IndexError:
|
||||||
|
# There was no Authorization Token specified
|
||||||
|
results['authtoken'] = None
|
||||||
|
|
||||||
|
# Define our optional list of channels to notify
|
||||||
|
results['channels'] = list()
|
||||||
|
|
||||||
|
# Support both 'to' (for yaml configuration) and channel=
|
||||||
|
if 'to' in results['qsd'] and len(results['qsd']['to']):
|
||||||
|
# Allow the user to specify the channel to post to
|
||||||
|
results['channels'].append(
|
||||||
|
NotifyMatterMost.parse_list(results['qsd']['to']))
|
||||||
|
|
||||||
channel = None
|
|
||||||
if 'channel' in results['qsd'] and len(results['qsd']['channel']):
|
if 'channel' in results['qsd'] and len(results['qsd']['channel']):
|
||||||
# Allow the user to specify the channel to post to
|
# Allow the user to specify the channel to post to
|
||||||
channel = NotifyBase.unquote(results['qsd']['channel']).strip()
|
results['channels'].append(
|
||||||
|
NotifyMatterMost.parse_list(results['qsd']['channel']))
|
||||||
|
|
||||||
results['authtoken'] = authtoken
|
# Image manipulation
|
||||||
results['channel'] = channel
|
results['include_image'] = \
|
||||||
|
parse_bool(results['qsd'].get('image', False))
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
@ -103,12 +103,9 @@ class NotifyProwl(NotifyBase):
|
|||||||
self.priority = priority
|
self.priority = priority
|
||||||
|
|
||||||
if not VALIDATE_APIKEY.match(apikey):
|
if not VALIDATE_APIKEY.match(apikey):
|
||||||
self.logger.warning(
|
msg = 'The API key specified ({}) is invalid.'.format(apikey)
|
||||||
'The API key specified (%s) is invalid.' % apikey,
|
self.logger.warning(msg)
|
||||||
)
|
raise TypeError(msg)
|
||||||
raise TypeError(
|
|
||||||
'The API key specified (%s) is invalid.' % apikey,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Store the API key
|
# Store the API key
|
||||||
self.apikey = apikey
|
self.apikey = apikey
|
||||||
@ -116,13 +113,12 @@ class NotifyProwl(NotifyBase):
|
|||||||
# Store the provider key (if specified)
|
# Store the provider key (if specified)
|
||||||
if providerkey:
|
if providerkey:
|
||||||
if not VALIDATE_PROVIDERKEY.match(providerkey):
|
if not VALIDATE_PROVIDERKEY.match(providerkey):
|
||||||
self.logger.warning(
|
msg = \
|
||||||
'The Provider key specified (%s) '
|
'The Provider key specified ({}) is invalid.' \
|
||||||
'is invalid.' % providerkey)
|
.format(providerkey)
|
||||||
|
|
||||||
raise TypeError(
|
self.logger.warning(msg)
|
||||||
'The Provider key specified (%s) '
|
raise TypeError(msg)
|
||||||
'is invalid.' % providerkey)
|
|
||||||
|
|
||||||
# Store the Provider Key
|
# Store the Provider Key
|
||||||
self.providerkey = providerkey
|
self.providerkey = providerkey
|
||||||
@ -218,10 +214,10 @@ class NotifyProwl(NotifyBase):
|
|||||||
|
|
||||||
return '{schema}://{apikey}/{providerkey}/?{args}'.format(
|
return '{schema}://{apikey}/{providerkey}/?{args}'.format(
|
||||||
schema=self.secure_protocol,
|
schema=self.secure_protocol,
|
||||||
apikey=self.quote(self.apikey, safe=''),
|
apikey=NotifyProwl.quote(self.apikey, safe=''),
|
||||||
providerkey='' if not self.providerkey
|
providerkey='' if not self.providerkey
|
||||||
else self.quote(self.providerkey, safe=''),
|
else NotifyProwl.quote(self.providerkey, safe=''),
|
||||||
args=self.urlencode(args),
|
args=NotifyProwl.urlencode(args),
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -237,15 +233,16 @@ class NotifyProwl(NotifyBase):
|
|||||||
# We're done early as we couldn't load the results
|
# We're done early as we couldn't load the results
|
||||||
return results
|
return results
|
||||||
|
|
||||||
# Apply our settings now
|
# Set the API Key
|
||||||
|
results['apikey'] = NotifyProwl.unquote(results['host'])
|
||||||
|
|
||||||
# optionally find the provider key
|
# Optionally try to find the provider key
|
||||||
try:
|
try:
|
||||||
providerkey = [x for x in filter(
|
results['providerkey'] = \
|
||||||
bool, NotifyBase.split_path(results['fullpath']))][0]
|
NotifyProwl.split_path(results['fullpath'])[0]
|
||||||
|
|
||||||
except (AttributeError, IndexError):
|
except IndexError:
|
||||||
providerkey = None
|
pass
|
||||||
|
|
||||||
if 'priority' in results['qsd'] and len(results['qsd']['priority']):
|
if 'priority' in results['qsd'] and len(results['qsd']['priority']):
|
||||||
_map = {
|
_map = {
|
||||||
@ -263,7 +260,4 @@ class NotifyProwl(NotifyBase):
|
|||||||
# No priority was set
|
# No priority was set
|
||||||
pass
|
pass
|
||||||
|
|
||||||
results['apikey'] = results['host']
|
|
||||||
results['providerkey'] = providerkey
|
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
@ -23,22 +23,17 @@
|
|||||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
# THE SOFTWARE.
|
# THE SOFTWARE.
|
||||||
|
|
||||||
import re
|
|
||||||
import six
|
|
||||||
import requests
|
import requests
|
||||||
from json import dumps
|
from json import dumps
|
||||||
|
|
||||||
from .NotifyBase import NotifyBase
|
from .NotifyBase import NotifyBase
|
||||||
from ..utils import GET_EMAIL_RE
|
from ..utils import GET_EMAIL_RE
|
||||||
from ..common import NotifyType
|
from ..common import NotifyType
|
||||||
|
from ..utils import parse_list
|
||||||
|
|
||||||
# Flag used as a placeholder to sending to all devices
|
# Flag used as a placeholder to sending to all devices
|
||||||
PUSHBULLET_SEND_TO_ALL = 'ALL_DEVICES'
|
PUSHBULLET_SEND_TO_ALL = 'ALL_DEVICES'
|
||||||
|
|
||||||
# Used to break apart list of potential recipients by their delimiter
|
|
||||||
# into a usable list.
|
|
||||||
RECIPIENTS_LIST_DELIM = re.compile(r'[ \t\r\n,\\/]+')
|
|
||||||
|
|
||||||
# Provide some known codes Pushbullet uses and what they translate to:
|
# Provide some known codes Pushbullet uses and what they translate to:
|
||||||
PUSHBULLET_HTTP_ERROR_MAP = {
|
PUSHBULLET_HTTP_ERROR_MAP = {
|
||||||
401: 'Unauthorized - Invalid Token.',
|
401: 'Unauthorized - Invalid Token.',
|
||||||
@ -65,25 +60,17 @@ class NotifyPushBullet(NotifyBase):
|
|||||||
# PushBullet uses the http protocol with JSON requests
|
# PushBullet uses the http protocol with JSON requests
|
||||||
notify_url = 'https://api.pushbullet.com/v2/pushes'
|
notify_url = 'https://api.pushbullet.com/v2/pushes'
|
||||||
|
|
||||||
def __init__(self, accesstoken, recipients=None, **kwargs):
|
def __init__(self, accesstoken, targets=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
Initialize PushBullet Object
|
Initialize PushBullet Object
|
||||||
"""
|
"""
|
||||||
super(NotifyPushBullet, self).__init__(**kwargs)
|
super(NotifyPushBullet, self).__init__(**kwargs)
|
||||||
|
|
||||||
self.accesstoken = accesstoken
|
self.accesstoken = accesstoken
|
||||||
if isinstance(recipients, six.string_types):
|
|
||||||
self.recipients = [x for x in filter(
|
|
||||||
bool, RECIPIENTS_LIST_DELIM.split(recipients))]
|
|
||||||
|
|
||||||
elif isinstance(recipients, (set, tuple, list)):
|
self.targets = parse_list(targets)
|
||||||
self.recipients = recipients
|
if len(self.targets) == 0:
|
||||||
|
self.targets = (PUSHBULLET_SEND_TO_ALL, )
|
||||||
else:
|
|
||||||
self.recipients = list()
|
|
||||||
|
|
||||||
if len(self.recipients) == 0:
|
|
||||||
self.recipients = (PUSHBULLET_SEND_TO_ALL, )
|
|
||||||
|
|
||||||
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
||||||
"""
|
"""
|
||||||
@ -99,10 +86,10 @@ class NotifyPushBullet(NotifyBase):
|
|||||||
# error tracking (used for function return)
|
# error tracking (used for function return)
|
||||||
has_error = False
|
has_error = False
|
||||||
|
|
||||||
# Create a copy of the recipients list
|
# Create a copy of the targets list
|
||||||
recipients = list(self.recipients)
|
targets = list(self.targets)
|
||||||
while len(recipients):
|
while len(targets):
|
||||||
recipient = recipients.pop(0)
|
recipient = targets.pop(0)
|
||||||
|
|
||||||
# prepare JSON Object
|
# prepare JSON Object
|
||||||
payload = {
|
payload = {
|
||||||
@ -149,7 +136,7 @@ class NotifyPushBullet(NotifyBase):
|
|||||||
if r.status_code != requests.codes.ok:
|
if r.status_code != requests.codes.ok:
|
||||||
# We had a problem
|
# We had a problem
|
||||||
status_str = \
|
status_str = \
|
||||||
NotifyBase.http_response_code_lookup(
|
NotifyPushBullet.http_response_code_lookup(
|
||||||
r.status_code, PUSHBULLET_HTTP_ERROR_MAP)
|
r.status_code, PUSHBULLET_HTTP_ERROR_MAP)
|
||||||
|
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
@ -195,17 +182,17 @@ class NotifyPushBullet(NotifyBase):
|
|||||||
'overflow': self.overflow_mode,
|
'overflow': self.overflow_mode,
|
||||||
}
|
}
|
||||||
|
|
||||||
recipients = '/'.join([self.quote(x) for x in self.recipients])
|
targets = '/'.join([NotifyPushBullet.quote(x) for x in self.targets])
|
||||||
if recipients == PUSHBULLET_SEND_TO_ALL:
|
if targets == PUSHBULLET_SEND_TO_ALL:
|
||||||
# keyword is reserved for internal usage only; it's safe to remove
|
# keyword is reserved for internal usage only; it's safe to remove
|
||||||
# it from the recipients list
|
# it from the recipients list
|
||||||
recipients = ''
|
targets = ''
|
||||||
|
|
||||||
return '{schema}://{accesstoken}/{recipients}/?{args}'.format(
|
return '{schema}://{accesstoken}/{targets}/?{args}'.format(
|
||||||
schema=self.secure_protocol,
|
schema=self.secure_protocol,
|
||||||
accesstoken=self.quote(self.accesstoken, safe=''),
|
accesstoken=NotifyPushBullet.quote(self.accesstoken, safe=''),
|
||||||
recipients=recipients,
|
targets=targets,
|
||||||
args=self.urlencode(args))
|
args=NotifyPushBullet.urlencode(args))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_url(url):
|
def parse_url(url):
|
||||||
@ -220,10 +207,17 @@ class NotifyPushBullet(NotifyBase):
|
|||||||
# We're done early as we couldn't load the results
|
# We're done early as we couldn't load the results
|
||||||
return results
|
return results
|
||||||
|
|
||||||
# Apply our settings now
|
# Fetch our targets
|
||||||
recipients = NotifyBase.unquote(results['fullpath'])
|
results['targets'] = \
|
||||||
|
NotifyPushBullet.split_path(results['fullpath'])
|
||||||
|
|
||||||
results['accesstoken'] = results['host']
|
# The 'to' makes it easier to use yaml configuration
|
||||||
results['recipients'] = recipients
|
if 'to' in results['qsd'] and len(results['qsd']['to']):
|
||||||
|
results['targets'] += \
|
||||||
|
NotifyPushBullet.parse_list(results['qsd']['to'])
|
||||||
|
|
||||||
|
# Setup the token; we store it in Access Token for global
|
||||||
|
# plugin consistency with naming conventions
|
||||||
|
results['accesstoken'] = NotifyPushBullet.unquote(results['host'])
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
@ -24,13 +24,13 @@
|
|||||||
# THE SOFTWARE.
|
# THE SOFTWARE.
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import six
|
|
||||||
import requests
|
import requests
|
||||||
from json import dumps
|
from json import dumps
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
|
|
||||||
from .NotifyBase import NotifyBase
|
from .NotifyBase import NotifyBase
|
||||||
from ..common import NotifyType
|
from ..common import NotifyType
|
||||||
|
from ..utils import parse_list
|
||||||
|
|
||||||
# Used to detect and parse channels
|
# Used to detect and parse channels
|
||||||
IS_CHANNEL = re.compile(r'^#(?P<name>[A-Za-z0-9]+)$')
|
IS_CHANNEL = re.compile(r'^#(?P<name>[A-Za-z0-9]+)$')
|
||||||
@ -38,10 +38,6 @@ IS_CHANNEL = re.compile(r'^#(?P<name>[A-Za-z0-9]+)$')
|
|||||||
# Used to detect and parse a users push id
|
# Used to detect and parse a users push id
|
||||||
IS_USER_PUSHED_ID = re.compile(r'^@(?P<name>[A-Za-z0-9]+)$')
|
IS_USER_PUSHED_ID = re.compile(r'^@(?P<name>[A-Za-z0-9]+)$')
|
||||||
|
|
||||||
# Used to break apart list of potential tags by their delimiter
|
|
||||||
# into a usable list.
|
|
||||||
LIST_DELIM = re.compile(r'[ \t\r\n,\\/]+')
|
|
||||||
|
|
||||||
|
|
||||||
class NotifyPushed(NotifyBase):
|
class NotifyPushed(NotifyBase):
|
||||||
"""
|
"""
|
||||||
@ -71,7 +67,7 @@ class NotifyPushed(NotifyBase):
|
|||||||
# The maximum allowable characters allowed in the body per message
|
# The maximum allowable characters allowed in the body per message
|
||||||
body_maxlen = 140
|
body_maxlen = 140
|
||||||
|
|
||||||
def __init__(self, app_key, app_secret, recipients=None, **kwargs):
|
def __init__(self, app_key, app_secret, targets=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
Initialize Pushed Object
|
Initialize Pushed Object
|
||||||
|
|
||||||
@ -79,14 +75,14 @@ class NotifyPushed(NotifyBase):
|
|||||||
super(NotifyPushed, self).__init__(**kwargs)
|
super(NotifyPushed, self).__init__(**kwargs)
|
||||||
|
|
||||||
if not app_key:
|
if not app_key:
|
||||||
raise TypeError(
|
msg = 'An invalid Application Key was specified.'
|
||||||
'An invalid Application Key was specified.'
|
self.logger.warning(msg)
|
||||||
)
|
raise TypeError(msg)
|
||||||
|
|
||||||
if not app_secret:
|
if not app_secret:
|
||||||
raise TypeError(
|
msg = 'An invalid Application Secret was specified.'
|
||||||
'An invalid Application Secret was specified.'
|
self.logger.warning(msg)
|
||||||
)
|
raise TypeError(msg)
|
||||||
|
|
||||||
# Initialize channel list
|
# Initialize channel list
|
||||||
self.channels = list()
|
self.channels = list()
|
||||||
@ -94,28 +90,15 @@ class NotifyPushed(NotifyBase):
|
|||||||
# Initialize user list
|
# Initialize user list
|
||||||
self.users = list()
|
self.users = list()
|
||||||
|
|
||||||
if recipients is None:
|
|
||||||
recipients = []
|
|
||||||
|
|
||||||
elif isinstance(recipients, six.string_types):
|
|
||||||
recipients = [x for x in filter(bool, LIST_DELIM.split(
|
|
||||||
recipients,
|
|
||||||
))]
|
|
||||||
|
|
||||||
elif not isinstance(recipients, (set, tuple, list)):
|
|
||||||
raise TypeError(
|
|
||||||
'An invalid receipient list was specified.'
|
|
||||||
)
|
|
||||||
|
|
||||||
# Validate recipients and drop bad ones:
|
# Validate recipients and drop bad ones:
|
||||||
for recipient in recipients:
|
for target in parse_list(targets):
|
||||||
result = IS_CHANNEL.match(recipient)
|
result = IS_CHANNEL.match(target)
|
||||||
if result:
|
if result:
|
||||||
# store valid device
|
# store valid device
|
||||||
self.channels.append(result.group('name'))
|
self.channels.append(result.group('name'))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
result = IS_USER_PUSHED_ID.match(recipient)
|
result = IS_USER_PUSHED_ID.match(target)
|
||||||
if result:
|
if result:
|
||||||
# store valid room
|
# store valid room
|
||||||
self.users.append(result.group('name'))
|
self.users.append(result.group('name'))
|
||||||
@ -123,7 +106,7 @@ class NotifyPushed(NotifyBase):
|
|||||||
|
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
'Dropped invalid channel/userid '
|
'Dropped invalid channel/userid '
|
||||||
'(%s) specified.' % recipient,
|
'(%s) specified.' % target,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Store our data
|
# Store our data
|
||||||
@ -229,7 +212,7 @@ class NotifyPushed(NotifyBase):
|
|||||||
if r.status_code != requests.codes.ok:
|
if r.status_code != requests.codes.ok:
|
||||||
# We had a problem
|
# We had a problem
|
||||||
status_str = \
|
status_str = \
|
||||||
NotifyBase.http_response_code_lookup(r.status_code)
|
NotifyPushed.http_response_code_lookup(r.status_code)
|
||||||
|
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
'Failed to send Pushed notification:'
|
'Failed to send Pushed notification:'
|
||||||
@ -269,16 +252,16 @@ class NotifyPushed(NotifyBase):
|
|||||||
|
|
||||||
return '{schema}://{app_key}/{app_secret}/{targets}/?{args}'.format(
|
return '{schema}://{app_key}/{app_secret}/{targets}/?{args}'.format(
|
||||||
schema=self.secure_protocol,
|
schema=self.secure_protocol,
|
||||||
app_key=self.quote(self.app_key, safe=''),
|
app_key=NotifyPushed.quote(self.app_key, safe=''),
|
||||||
app_secret=self.quote(self.app_secret, safe=''),
|
app_secret=NotifyPushed.quote(self.app_secret, safe=''),
|
||||||
targets='/'.join(
|
targets='/'.join(
|
||||||
[self.quote(x) for x in chain(
|
[NotifyPushed.quote(x) for x in chain(
|
||||||
# Channels are prefixed with a pound/hashtag symbol
|
# Channels are prefixed with a pound/hashtag symbol
|
||||||
['#{}'.format(x) for x in self.channels],
|
['#{}'.format(x) for x in self.channels],
|
||||||
# Users are prefixed with an @ symbol
|
# Users are prefixed with an @ symbol
|
||||||
['@{}'.format(x) for x in self.users],
|
['@{}'.format(x) for x in self.users],
|
||||||
)]),
|
)]),
|
||||||
args=self.urlencode(args))
|
args=NotifyPushed.urlencode(args))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_url(url):
|
def parse_url(url):
|
||||||
@ -296,30 +279,28 @@ class NotifyPushed(NotifyBase):
|
|||||||
# Apply our settings now
|
# Apply our settings now
|
||||||
|
|
||||||
# The first token is stored in the hostname
|
# The first token is stored in the hostname
|
||||||
app_key = results['host']
|
app_key = NotifyPushed.unquote(results['host'])
|
||||||
|
|
||||||
# Initialize our recipients
|
|
||||||
recipients = None
|
|
||||||
|
|
||||||
|
entries = NotifyPushed.split_path(results['fullpath'])
|
||||||
# Now fetch the remaining tokens
|
# Now fetch the remaining tokens
|
||||||
try:
|
try:
|
||||||
app_secret = \
|
app_secret = entries.pop(0)
|
||||||
[x for x in filter(bool, NotifyBase.split_path(
|
|
||||||
results['fullpath']))][0]
|
|
||||||
|
|
||||||
except (ValueError, AttributeError, IndexError):
|
except IndexError:
|
||||||
# Force some bad values that will get caught
|
# Force some bad values that will get caught
|
||||||
# in parsing later
|
# in parsing later
|
||||||
app_secret = None
|
app_secret = None
|
||||||
app_key = None
|
app_key = None
|
||||||
|
|
||||||
# Get our recipients
|
# Get our recipients (based on remaining entries)
|
||||||
recipients = \
|
results['targets'] = entries
|
||||||
[x for x in filter(bool, NotifyBase.split_path(
|
|
||||||
results['fullpath']))][1:]
|
# The 'to' makes it easier to use yaml configuration
|
||||||
|
if 'to' in results['qsd'] and len(results['qsd']['to']):
|
||||||
|
results['targets'] += \
|
||||||
|
NotifyPushed.parse_list(results['qsd']['to'])
|
||||||
|
|
||||||
results['app_key'] = app_key
|
results['app_key'] = app_key
|
||||||
results['app_secret'] = app_secret
|
results['app_secret'] = app_secret
|
||||||
results['recipients'] = recipients
|
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
@ -62,6 +62,12 @@ class NotifyPushjet(NotifyBase):
|
|||||||
"""
|
"""
|
||||||
super(NotifyPushjet, self).__init__(**kwargs)
|
super(NotifyPushjet, self).__init__(**kwargs)
|
||||||
|
|
||||||
|
if not secret_key:
|
||||||
|
# You must provide a Pushjet key to work with
|
||||||
|
msg = 'You must specify a Pushjet Secret Key.'
|
||||||
|
self.logger.warning(msg)
|
||||||
|
raise TypeError(msg)
|
||||||
|
|
||||||
# store our key
|
# store our key
|
||||||
self.secret_key = secret_key
|
self.secret_key = secret_key
|
||||||
|
|
||||||
@ -107,11 +113,11 @@ class NotifyPushjet(NotifyBase):
|
|||||||
|
|
||||||
return '{schema}://{secret_key}@{hostname}{port}/?{args}'.format(
|
return '{schema}://{secret_key}@{hostname}{port}/?{args}'.format(
|
||||||
schema=self.secure_protocol if self.secure else self.protocol,
|
schema=self.secure_protocol if self.secure else self.protocol,
|
||||||
secret_key=self.quote(self.secret_key, safe=''),
|
secret_key=NotifyPushjet.quote(self.secret_key, safe=''),
|
||||||
hostname=self.host,
|
hostname=NotifyPushjet.quote(self.host, safe=''),
|
||||||
port='' if self.port is None or self.port == default_port
|
port='' if self.port is None or self.port == default_port
|
||||||
else ':{}'.format(self.port),
|
else ':{}'.format(self.port),
|
||||||
args=self.urlencode(args),
|
args=NotifyPushjet.urlencode(args),
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -133,11 +139,8 @@ class NotifyPushjet(NotifyBase):
|
|||||||
# We're done early as we couldn't load the results
|
# We're done early as we couldn't load the results
|
||||||
return results
|
return results
|
||||||
|
|
||||||
if not results.get('user'):
|
|
||||||
# a username is required
|
|
||||||
return None
|
|
||||||
|
|
||||||
# Store it as it's value
|
# Store it as it's value
|
||||||
results['secret_key'] = results.get('user')
|
results['secret_key'] = \
|
||||||
|
NotifyPushjet.unquote(results.get('user'))
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
@ -24,11 +24,11 @@
|
|||||||
# THE SOFTWARE.
|
# THE SOFTWARE.
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import six
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from .NotifyBase import NotifyBase
|
from .NotifyBase import NotifyBase
|
||||||
from ..common import NotifyType
|
from ..common import NotifyType
|
||||||
|
from ..utils import parse_list
|
||||||
|
|
||||||
# Flag used as a placeholder to sending to all devices
|
# Flag used as a placeholder to sending to all devices
|
||||||
PUSHOVER_SEND_TO_ALL = 'ALL_DEVICES'
|
PUSHOVER_SEND_TO_ALL = 'ALL_DEVICES'
|
||||||
@ -60,9 +60,6 @@ PUSHOVER_PRIORITIES = (
|
|||||||
PushoverPriority.EMERGENCY,
|
PushoverPriority.EMERGENCY,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Used to break path apart into list of devices
|
|
||||||
DEVICE_LIST_DELIM = re.compile(r'[ \t\r\n,\\/]+')
|
|
||||||
|
|
||||||
# Extend HTTP Error Messages
|
# Extend HTTP Error Messages
|
||||||
PUSHOVER_HTTP_ERROR_MAP = {
|
PUSHOVER_HTTP_ERROR_MAP = {
|
||||||
401: 'Unauthorized - Invalid Token.',
|
401: 'Unauthorized - Invalid Token.',
|
||||||
@ -92,7 +89,7 @@ class NotifyPushover(NotifyBase):
|
|||||||
# The maximum allowable characters allowed in the body per message
|
# The maximum allowable characters allowed in the body per message
|
||||||
body_maxlen = 512
|
body_maxlen = 512
|
||||||
|
|
||||||
def __init__(self, token, devices=None, priority=None, **kwargs):
|
def __init__(self, token, targets=None, priority=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
Initialize Pushover Object
|
Initialize Pushover Object
|
||||||
"""
|
"""
|
||||||
@ -104,30 +101,18 @@ class NotifyPushover(NotifyBase):
|
|||||||
|
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
# Token was None
|
# Token was None
|
||||||
self.logger.warning('No API Token was specified.')
|
msg = 'No API Token was specified.'
|
||||||
raise TypeError('No API Token was specified.')
|
self.logger.warning(msg)
|
||||||
|
raise TypeError(msg)
|
||||||
|
|
||||||
if not VALIDATE_TOKEN.match(self.token):
|
if not VALIDATE_TOKEN.match(self.token):
|
||||||
self.logger.warning(
|
msg = 'The API Token specified (%s) is invalid.'.format(token)
|
||||||
'The API Token specified (%s) is invalid.' % token,
|
self.logger.warning(msg)
|
||||||
)
|
raise TypeError(msg)
|
||||||
raise TypeError(
|
|
||||||
'The API Token specified (%s) is invalid.' % token,
|
|
||||||
)
|
|
||||||
|
|
||||||
if isinstance(devices, six.string_types):
|
self.targets = parse_list(targets)
|
||||||
self.devices = [x for x in filter(bool, DEVICE_LIST_DELIM.split(
|
if len(self.targets) == 0:
|
||||||
devices,
|
self.targets = (PUSHOVER_SEND_TO_ALL, )
|
||||||
))]
|
|
||||||
|
|
||||||
elif isinstance(devices, (set, tuple, list)):
|
|
||||||
self.devices = devices
|
|
||||||
|
|
||||||
else:
|
|
||||||
self.devices = list()
|
|
||||||
|
|
||||||
if len(self.devices) == 0:
|
|
||||||
self.devices = (PUSHOVER_SEND_TO_ALL, )
|
|
||||||
|
|
||||||
# The Priority of the message
|
# The Priority of the message
|
||||||
if priority not in PUSHOVER_PRIORITIES:
|
if priority not in PUSHOVER_PRIORITIES:
|
||||||
@ -137,16 +122,14 @@ class NotifyPushover(NotifyBase):
|
|||||||
self.priority = priority
|
self.priority = priority
|
||||||
|
|
||||||
if not self.user:
|
if not self.user:
|
||||||
self.logger.warning('No user was specified.')
|
msg = 'No user was specified.'
|
||||||
raise TypeError('No user was specified.')
|
self.logger.warning(msg)
|
||||||
|
raise TypeError(msg)
|
||||||
|
|
||||||
if not VALIDATE_USERGROUP.match(self.user):
|
if not VALIDATE_USERGROUP.match(self.user):
|
||||||
self.logger.warning(
|
msg = 'The user/group specified (%s) is invalid.' % self.user
|
||||||
'The user/group specified (%s) is invalid.' % self.user,
|
self.logger.warning(msg)
|
||||||
)
|
raise TypeError(msg)
|
||||||
raise TypeError(
|
|
||||||
'The user/group specified (%s) is invalid.' % self.user,
|
|
||||||
)
|
|
||||||
|
|
||||||
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
||||||
"""
|
"""
|
||||||
@ -163,7 +146,7 @@ class NotifyPushover(NotifyBase):
|
|||||||
has_error = False
|
has_error = False
|
||||||
|
|
||||||
# Create a copy of the devices list
|
# Create a copy of the devices list
|
||||||
devices = list(self.devices)
|
devices = list(self.targets)
|
||||||
while len(devices):
|
while len(devices):
|
||||||
device = devices.pop(0)
|
device = devices.pop(0)
|
||||||
|
|
||||||
@ -205,7 +188,7 @@ class NotifyPushover(NotifyBase):
|
|||||||
if r.status_code != requests.codes.ok:
|
if r.status_code != requests.codes.ok:
|
||||||
# We had a problem
|
# We had a problem
|
||||||
status_str = \
|
status_str = \
|
||||||
NotifyBase.http_response_code_lookup(
|
NotifyPushover.http_response_code_lookup(
|
||||||
r.status_code, PUSHOVER_HTTP_ERROR_MAP)
|
r.status_code, PUSHOVER_HTTP_ERROR_MAP)
|
||||||
|
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
@ -262,7 +245,10 @@ class NotifyPushover(NotifyBase):
|
|||||||
else _map[self.priority],
|
else _map[self.priority],
|
||||||
}
|
}
|
||||||
|
|
||||||
devices = '/'.join([self.quote(x) for x in self.devices])
|
# Escape our devices
|
||||||
|
devices = '/'.join([NotifyPushover.quote(x, safe='')
|
||||||
|
for x in self.targets])
|
||||||
|
|
||||||
if devices == PUSHOVER_SEND_TO_ALL:
|
if devices == PUSHOVER_SEND_TO_ALL:
|
||||||
# keyword is reserved for internal usage only; it's safe to remove
|
# keyword is reserved for internal usage only; it's safe to remove
|
||||||
# it from the devices list
|
# it from the devices list
|
||||||
@ -271,10 +257,11 @@ class NotifyPushover(NotifyBase):
|
|||||||
return '{schema}://{auth}{token}/{devices}/?{args}'.format(
|
return '{schema}://{auth}{token}/{devices}/?{args}'.format(
|
||||||
schema=self.secure_protocol,
|
schema=self.secure_protocol,
|
||||||
auth='' if not self.user
|
auth='' if not self.user
|
||||||
else '{user}@'.format(user=self.quote(self.user, safe='')),
|
else '{user}@'.format(
|
||||||
token=self.quote(self.token, safe=''),
|
user=NotifyPushover.quote(self.user, safe='')),
|
||||||
|
token=NotifyPushover.quote(self.token, safe=''),
|
||||||
devices=devices,
|
devices=devices,
|
||||||
args=self.urlencode(args))
|
args=NotifyPushover.urlencode(args))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_url(url):
|
def parse_url(url):
|
||||||
@ -289,21 +276,14 @@ class NotifyPushover(NotifyBase):
|
|||||||
# We're done early as we couldn't load the results
|
# We're done early as we couldn't load the results
|
||||||
return results
|
return results
|
||||||
|
|
||||||
# Apply our settings now
|
# Set our priority
|
||||||
devices = NotifyBase.unquote(results['fullpath'])
|
|
||||||
|
|
||||||
if 'priority' in results['qsd'] and len(results['qsd']['priority']):
|
if 'priority' in results['qsd'] and len(results['qsd']['priority']):
|
||||||
_map = {
|
_map = {
|
||||||
'l': PushoverPriority.LOW,
|
'l': PushoverPriority.LOW,
|
||||||
'-2': PushoverPriority.LOW,
|
|
||||||
'm': PushoverPriority.MODERATE,
|
'm': PushoverPriority.MODERATE,
|
||||||
'-1': PushoverPriority.MODERATE,
|
|
||||||
'n': PushoverPriority.NORMAL,
|
'n': PushoverPriority.NORMAL,
|
||||||
'0': PushoverPriority.NORMAL,
|
|
||||||
'h': PushoverPriority.HIGH,
|
'h': PushoverPriority.HIGH,
|
||||||
'1': PushoverPriority.HIGH,
|
|
||||||
'e': PushoverPriority.EMERGENCY,
|
'e': PushoverPriority.EMERGENCY,
|
||||||
'2': PushoverPriority.EMERGENCY,
|
|
||||||
}
|
}
|
||||||
try:
|
try:
|
||||||
results['priority'] = \
|
results['priority'] = \
|
||||||
@ -313,7 +293,15 @@ class NotifyPushover(NotifyBase):
|
|||||||
# No priority was set
|
# No priority was set
|
||||||
pass
|
pass
|
||||||
|
|
||||||
results['token'] = results['host']
|
# Retrieve all of our targets
|
||||||
results['devices'] = devices
|
results['targets'] = NotifyPushover.split_path(results['fullpath'])
|
||||||
|
|
||||||
|
# The 'to' makes it easier to use yaml configuration
|
||||||
|
if 'to' in results['qsd'] and len(results['qsd']['to']):
|
||||||
|
results['targets'] += \
|
||||||
|
NotifyPushover.parse_list(results['qsd']['to'])
|
||||||
|
|
||||||
|
# Token
|
||||||
|
results['token'] = NotifyPushover.unquote(results['host'])
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
@ -24,13 +24,13 @@
|
|||||||
# THE SOFTWARE.
|
# THE SOFTWARE.
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import six
|
|
||||||
import requests
|
import requests
|
||||||
from json import loads
|
from json import loads
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
|
|
||||||
from .NotifyBase import NotifyBase
|
from .NotifyBase import NotifyBase
|
||||||
from ..common import NotifyType
|
from ..common import NotifyType
|
||||||
|
from ..utils import parse_list
|
||||||
|
|
||||||
IS_CHANNEL = re.compile(r'^#(?P<name>[A-Za-z0-9]+)$')
|
IS_CHANNEL = re.compile(r'^#(?P<name>[A-Za-z0-9]+)$')
|
||||||
IS_ROOM_ID = re.compile(r'^(?P<name>[A-Za-z0-9]+)$')
|
IS_ROOM_ID = re.compile(r'^(?P<name>[A-Za-z0-9]+)$')
|
||||||
@ -72,17 +72,14 @@ class NotifyRocketChat(NotifyBase):
|
|||||||
# The maximum size of the message
|
# The maximum size of the message
|
||||||
body_maxlen = 200
|
body_maxlen = 200
|
||||||
|
|
||||||
def __init__(self, recipients=None, **kwargs):
|
def __init__(self, targets=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
Initialize Notify Rocket.Chat Object
|
Initialize Notify Rocket.Chat Object
|
||||||
"""
|
"""
|
||||||
super(NotifyRocketChat, self).__init__(**kwargs)
|
super(NotifyRocketChat, self).__init__(**kwargs)
|
||||||
|
|
||||||
if self.secure:
|
# Set our schema
|
||||||
self.schema = 'https'
|
self.schema = 'https' if self.secure else 'http'
|
||||||
|
|
||||||
else:
|
|
||||||
self.schema = 'http'
|
|
||||||
|
|
||||||
# Prepare our URL
|
# Prepare our URL
|
||||||
self.api_url = '%s://%s' % (self.schema, self.host)
|
self.api_url = '%s://%s' % (self.schema, self.host)
|
||||||
@ -98,17 +95,6 @@ class NotifyRocketChat(NotifyBase):
|
|||||||
# Initialize room list
|
# Initialize room list
|
||||||
self.rooms = list()
|
self.rooms = list()
|
||||||
|
|
||||||
if recipients is None:
|
|
||||||
recipients = []
|
|
||||||
|
|
||||||
elif isinstance(recipients, six.string_types):
|
|
||||||
recipients = [x for x in filter(bool, LIST_DELIM.split(
|
|
||||||
recipients,
|
|
||||||
))]
|
|
||||||
|
|
||||||
elif not isinstance(recipients, (set, tuple, list)):
|
|
||||||
recipients = []
|
|
||||||
|
|
||||||
if not (self.user and self.password):
|
if not (self.user and self.password):
|
||||||
# Username & Password is required for Rocket Chat to work
|
# Username & Password is required for Rocket Chat to work
|
||||||
raise TypeError(
|
raise TypeError(
|
||||||
@ -116,7 +102,7 @@ class NotifyRocketChat(NotifyBase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Validate recipients and drop bad ones:
|
# Validate recipients and drop bad ones:
|
||||||
for recipient in recipients:
|
for recipient in parse_list(targets):
|
||||||
result = IS_CHANNEL.match(recipient)
|
result = IS_CHANNEL.match(recipient)
|
||||||
if result:
|
if result:
|
||||||
# store valid device
|
# store valid device
|
||||||
@ -135,9 +121,9 @@ class NotifyRocketChat(NotifyBase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if len(self.rooms) == 0 and len(self.channels) == 0:
|
if len(self.rooms) == 0 and len(self.channels) == 0:
|
||||||
raise TypeError(
|
msg = 'No Rocket.Chat room and/or channels specified to notify.'
|
||||||
'No Rocket.Chat room and/or channels specified to notify.'
|
self.logger.warning(msg)
|
||||||
)
|
raise TypeError(msg)
|
||||||
|
|
||||||
# Used to track token headers upon authentication (if successful)
|
# Used to track token headers upon authentication (if successful)
|
||||||
self.headers = {}
|
self.headers = {}
|
||||||
@ -155,8 +141,8 @@ class NotifyRocketChat(NotifyBase):
|
|||||||
|
|
||||||
# Determine Authentication
|
# Determine Authentication
|
||||||
auth = '{user}:{password}@'.format(
|
auth = '{user}:{password}@'.format(
|
||||||
user=self.quote(self.user, safe=''),
|
user=NotifyRocketChat.quote(self.user, safe=''),
|
||||||
password=self.quote(self.password, safe=''),
|
password=NotifyRocketChat.quote(self.password, safe=''),
|
||||||
)
|
)
|
||||||
|
|
||||||
default_port = 443 if self.secure else 80
|
default_port = 443 if self.secure else 80
|
||||||
@ -164,17 +150,17 @@ class NotifyRocketChat(NotifyBase):
|
|||||||
return '{schema}://{auth}{hostname}{port}/{targets}/?{args}'.format(
|
return '{schema}://{auth}{hostname}{port}/{targets}/?{args}'.format(
|
||||||
schema=self.secure_protocol if self.secure else self.protocol,
|
schema=self.secure_protocol if self.secure else self.protocol,
|
||||||
auth=auth,
|
auth=auth,
|
||||||
hostname=self.host,
|
hostname=NotifyRocketChat.quote(self.host, safe=''),
|
||||||
port='' if self.port is None or self.port == default_port
|
port='' if self.port is None or self.port == default_port
|
||||||
else ':{}'.format(self.port),
|
else ':{}'.format(self.port),
|
||||||
targets='/'.join(
|
targets='/'.join(
|
||||||
[self.quote(x) for x in chain(
|
[NotifyRocketChat.quote(x, safe='') for x in chain(
|
||||||
# Channels are prefixed with a pound/hashtag symbol
|
# Channels are prefixed with a pound/hashtag symbol
|
||||||
['#{}'.format(x) for x in self.channels],
|
['#{}'.format(x) for x in self.channels],
|
||||||
# Rooms are as is
|
# Rooms are as is
|
||||||
self.rooms,
|
self.rooms,
|
||||||
)]),
|
)]),
|
||||||
args=self.urlencode(args),
|
args=NotifyRocketChat.urlencode(args),
|
||||||
)
|
)
|
||||||
|
|
||||||
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
||||||
@ -252,7 +238,7 @@ class NotifyRocketChat(NotifyBase):
|
|||||||
if r.status_code != requests.codes.ok:
|
if r.status_code != requests.codes.ok:
|
||||||
# We had a problem
|
# We had a problem
|
||||||
status_str = \
|
status_str = \
|
||||||
NotifyBase.http_response_code_lookup(
|
NotifyRocketChat.http_response_code_lookup(
|
||||||
r.status_code, RC_HTTP_ERROR_MAP)
|
r.status_code, RC_HTTP_ERROR_MAP)
|
||||||
|
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
@ -300,7 +286,7 @@ class NotifyRocketChat(NotifyBase):
|
|||||||
if r.status_code != requests.codes.ok:
|
if r.status_code != requests.codes.ok:
|
||||||
# We had a problem
|
# We had a problem
|
||||||
status_str = \
|
status_str = \
|
||||||
NotifyBase.http_response_code_lookup(
|
NotifyRocketChat.http_response_code_lookup(
|
||||||
r.status_code, RC_HTTP_ERROR_MAP)
|
r.status_code, RC_HTTP_ERROR_MAP)
|
||||||
|
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
@ -353,7 +339,7 @@ class NotifyRocketChat(NotifyBase):
|
|||||||
if r.status_code != requests.codes.ok:
|
if r.status_code != requests.codes.ok:
|
||||||
# We had a problem
|
# We had a problem
|
||||||
status_str = \
|
status_str = \
|
||||||
NotifyBase.http_response_code_lookup(
|
NotifyRocketChat.http_response_code_lookup(
|
||||||
r.status_code, RC_HTTP_ERROR_MAP)
|
r.status_code, RC_HTTP_ERROR_MAP)
|
||||||
|
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
@ -396,7 +382,12 @@ class NotifyRocketChat(NotifyBase):
|
|||||||
# We're done early as we couldn't load the results
|
# We're done early as we couldn't load the results
|
||||||
return results
|
return results
|
||||||
|
|
||||||
# Apply our settings now
|
# Apply our targets
|
||||||
results['recipients'] = NotifyBase.unquote(results['fullpath'])
|
results['targets'] = NotifyRocketChat.split_path(results['fullpath'])
|
||||||
|
|
||||||
|
# The 'to' makes it easier to use yaml configuration
|
||||||
|
if 'to' in results['qsd'] and len(results['qsd']['to']):
|
||||||
|
results['targets'] += \
|
||||||
|
NotifyRocketChat.parse_list(results['qsd']['to'])
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
@ -32,12 +32,14 @@
|
|||||||
# These are important <---^----------------------------------------^
|
# These are important <---^----------------------------------------^
|
||||||
#
|
#
|
||||||
import re
|
import re
|
||||||
|
import six
|
||||||
import requests
|
import requests
|
||||||
from json import dumps
|
from json import dumps
|
||||||
|
|
||||||
from .NotifyBase import NotifyBase
|
from .NotifyBase import NotifyBase
|
||||||
from ..common import NotifyImageSize
|
from ..common import NotifyImageSize
|
||||||
from ..common import NotifyType
|
from ..common import NotifyType
|
||||||
|
from ..utils import parse_bool
|
||||||
|
|
||||||
# Token required as part of the API request
|
# Token required as part of the API request
|
||||||
VALIDATE_TOKEN = re.compile(r'[A-Za-z0-9]{15}')
|
VALIDATE_TOKEN = re.compile(r'[A-Za-z0-9]{15}')
|
||||||
@ -46,18 +48,18 @@ VALIDATE_TOKEN = re.compile(r'[A-Za-z0-9]{15}')
|
|||||||
VALIDATE_ORG = re.compile(r'[A-Za-z0-9-]{3,32}')
|
VALIDATE_ORG = re.compile(r'[A-Za-z0-9-]{3,32}')
|
||||||
|
|
||||||
|
|
||||||
class RyverWebhookType(object):
|
class RyverWebhookMode(object):
|
||||||
"""
|
"""
|
||||||
Ryver supports to webhook types
|
Ryver supports to webhook modes
|
||||||
"""
|
"""
|
||||||
SLACK = 'slack'
|
SLACK = 'slack'
|
||||||
RYVER = 'ryver'
|
RYVER = 'ryver'
|
||||||
|
|
||||||
|
|
||||||
# Define the types in a list for validation purposes
|
# Define the types in a list for validation purposes
|
||||||
RYVER_WEBHOOK_TYPES = (
|
RYVER_WEBHOOK_MODES = (
|
||||||
RyverWebhookType.SLACK,
|
RyverWebhookMode.SLACK,
|
||||||
RyverWebhookType.RYVER,
|
RyverWebhookMode.RYVER,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -84,39 +86,44 @@ class NotifyRyver(NotifyBase):
|
|||||||
# The maximum allowable characters allowed in the body per message
|
# The maximum allowable characters allowed in the body per message
|
||||||
body_maxlen = 1000
|
body_maxlen = 1000
|
||||||
|
|
||||||
def __init__(self, organization, token, webhook=RyverWebhookType.RYVER,
|
def __init__(self, organization, token, mode=RyverWebhookMode.RYVER,
|
||||||
**kwargs):
|
include_image=True, **kwargs):
|
||||||
"""
|
"""
|
||||||
Initialize Ryver Object
|
Initialize Ryver Object
|
||||||
"""
|
"""
|
||||||
super(NotifyRyver, self).__init__(**kwargs)
|
super(NotifyRyver, self).__init__(**kwargs)
|
||||||
|
|
||||||
|
if not token:
|
||||||
|
msg = 'No Ryver token was specified.'
|
||||||
|
self.logger.warning(msg)
|
||||||
|
raise TypeError(msg)
|
||||||
|
|
||||||
|
if not organization:
|
||||||
|
msg = 'No Ryver organization was specified.'
|
||||||
|
self.logger.warning(msg)
|
||||||
|
raise TypeError(msg)
|
||||||
|
|
||||||
if not VALIDATE_TOKEN.match(token.strip()):
|
if not VALIDATE_TOKEN.match(token.strip()):
|
||||||
self.logger.warning(
|
msg = 'The Ryver token specified ({}) is invalid.'\
|
||||||
'The token specified (%s) is invalid.' % token,
|
.format(token)
|
||||||
)
|
self.logger.warning(msg)
|
||||||
raise TypeError(
|
raise TypeError(msg)
|
||||||
'The token specified (%s) is invalid.' % token,
|
|
||||||
)
|
|
||||||
|
|
||||||
if not VALIDATE_ORG.match(organization.strip()):
|
if not VALIDATE_ORG.match(organization.strip()):
|
||||||
self.logger.warning(
|
msg = 'The Ryver organization specified ({}) is invalid.'\
|
||||||
'The organization specified (%s) is invalid.' % organization,
|
.format(organization)
|
||||||
)
|
self.logger.warning(msg)
|
||||||
raise TypeError(
|
raise TypeError(msg)
|
||||||
'The organization specified (%s) is invalid.' % organization,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Store our webhook type
|
# Store our webhook mode
|
||||||
self.webhook = webhook
|
self.mode = None \
|
||||||
|
if not isinstance(mode, six.string_types) else mode.lower()
|
||||||
|
|
||||||
if self.webhook not in RYVER_WEBHOOK_TYPES:
|
if self.mode not in RYVER_WEBHOOK_MODES:
|
||||||
self.logger.warning(
|
msg = 'The Ryver webhook mode specified ({}) is invalid.' \
|
||||||
'The webhook specified (%s) is invalid.' % webhook,
|
.format(mode)
|
||||||
)
|
self.logger.warning(msg)
|
||||||
raise TypeError(
|
raise TypeError(msg)
|
||||||
'The webhook specified (%s) is invalid.' % webhook,
|
|
||||||
)
|
|
||||||
|
|
||||||
# The organization associated with the account
|
# The organization associated with the account
|
||||||
self.organization = organization.strip()
|
self.organization = organization.strip()
|
||||||
@ -124,6 +131,9 @@ class NotifyRyver(NotifyBase):
|
|||||||
# The token associated with the account
|
# The token associated with the account
|
||||||
self.token = token.strip()
|
self.token = token.strip()
|
||||||
|
|
||||||
|
# Place an image inline with the message body
|
||||||
|
self.include_image = include_image
|
||||||
|
|
||||||
# Slack formatting requirements are defined here which Ryver supports:
|
# Slack formatting requirements are defined here which Ryver supports:
|
||||||
# https://api.slack.com/docs/message-formatting
|
# https://api.slack.com/docs/message-formatting
|
||||||
self._re_formatting_map = {
|
self._re_formatting_map = {
|
||||||
@ -151,7 +161,7 @@ class NotifyRyver(NotifyBase):
|
|||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.webhook == RyverWebhookType.SLACK:
|
if self.mode == RyverWebhookMode.SLACK:
|
||||||
# Perform Slack formatting
|
# Perform Slack formatting
|
||||||
title = self._re_formatting_rules.sub( # pragma: no branch
|
title = self._re_formatting_rules.sub( # pragma: no branch
|
||||||
lambda x: self._re_formatting_map[x.group()], title,
|
lambda x: self._re_formatting_map[x.group()], title,
|
||||||
@ -160,20 +170,27 @@ class NotifyRyver(NotifyBase):
|
|||||||
lambda x: self._re_formatting_map[x.group()], body,
|
lambda x: self._re_formatting_map[x.group()], body,
|
||||||
)
|
)
|
||||||
|
|
||||||
url = 'https://%s.ryver.com/application/webhook/%s' % (
|
url = 'https://{}.ryver.com/application/webhook/{}'.format(
|
||||||
self.organization,
|
self.organization,
|
||||||
self.token,
|
self.token,
|
||||||
)
|
)
|
||||||
|
|
||||||
# prepare JSON Object
|
# prepare JSON Object
|
||||||
payload = {
|
payload = {
|
||||||
"body": body if not title else '**{}**\r\n{}'.format(title, body),
|
'body': body if not title else '**{}**\r\n{}'.format(title, body),
|
||||||
'createSource': {
|
'createSource': {
|
||||||
"displayName": self.user,
|
'displayName': self.user,
|
||||||
"avatar": self.image_url(notify_type),
|
'avatar': None,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Acquire our image url if configured to do so
|
||||||
|
image_url = None if not self.include_image else \
|
||||||
|
self.image_url(notify_type)
|
||||||
|
|
||||||
|
if image_url:
|
||||||
|
payload['createSource']['avatar'] = image_url
|
||||||
|
|
||||||
self.logger.debug('Ryver POST URL: %s (cert_verify=%r)' % (
|
self.logger.debug('Ryver POST URL: %s (cert_verify=%r)' % (
|
||||||
url, self.verify_certificate,
|
url, self.verify_certificate,
|
||||||
))
|
))
|
||||||
@ -229,22 +246,23 @@ class NotifyRyver(NotifyBase):
|
|||||||
args = {
|
args = {
|
||||||
'format': self.notify_format,
|
'format': self.notify_format,
|
||||||
'overflow': self.overflow_mode,
|
'overflow': self.overflow_mode,
|
||||||
'webhook': self.webhook,
|
'image': 'yes' if self.include_image else 'no',
|
||||||
|
'mode': self.mode,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Determine if there is a botname present
|
# Determine if there is a botname present
|
||||||
botname = ''
|
botname = ''
|
||||||
if self.user:
|
if self.user:
|
||||||
botname = '{botname}@'.format(
|
botname = '{botname}@'.format(
|
||||||
botname=self.quote(self.user, safe=''),
|
botname=NotifyRyver.quote(self.user, safe=''),
|
||||||
)
|
)
|
||||||
|
|
||||||
return '{schema}://{botname}{organization}/{token}/?{args}'.format(
|
return '{schema}://{botname}{organization}/{token}/?{args}'.format(
|
||||||
schema=self.secure_protocol,
|
schema=self.secure_protocol,
|
||||||
botname=botname,
|
botname=botname,
|
||||||
organization=self.quote(self.organization, safe=''),
|
organization=NotifyRyver.quote(self.organization, safe=''),
|
||||||
token=self.quote(self.token, safe=''),
|
token=NotifyRyver.quote(self.token, safe=''),
|
||||||
args=self.urlencode(args),
|
args=NotifyRyver.urlencode(args),
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -254,31 +272,41 @@ class NotifyRyver(NotifyBase):
|
|||||||
us to substantiate this object.
|
us to substantiate this object.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
results = NotifyBase.parse_url(url)
|
results = NotifyBase.parse_url(url)
|
||||||
|
|
||||||
if not results:
|
if not results:
|
||||||
# We're done early as we couldn't load the results
|
# We're done early as we couldn't load the results
|
||||||
return results
|
return results
|
||||||
|
|
||||||
# Apply our settings now
|
|
||||||
|
|
||||||
# The first token is stored in the hostname
|
# The first token is stored in the hostname
|
||||||
organization = results['host']
|
results['organization'] = NotifyRyver.unquote(results['host'])
|
||||||
|
|
||||||
# Now fetch the remaining tokens
|
# Now fetch the remaining tokens
|
||||||
try:
|
try:
|
||||||
token = [x for x in filter(
|
results['token'] = \
|
||||||
bool, NotifyBase.split_path(results['fullpath']))][0]
|
NotifyRyver.split_path(results['fullpath'])[0]
|
||||||
|
|
||||||
except (ValueError, AttributeError, IndexError):
|
except IndexError:
|
||||||
# We're done
|
# no token
|
||||||
return None
|
results['token'] = None
|
||||||
|
|
||||||
if 'webhook' in results['qsd'] and len(results['qsd']['webhook']):
|
if 'webhook' in results['qsd']:
|
||||||
results['webhook'] = results['qsd']\
|
# Deprication Notice issued for v0.7.5
|
||||||
.get('webhook', RyverWebhookType.RYVER).lower()
|
NotifyRyver.logger.warning(
|
||||||
|
'DEPRICATION NOTICE - The Ryver URL contains the parameter '
|
||||||
|
'"webhook=" which will be depricated in an upcoming '
|
||||||
|
'release. Please use "mode=" instead.'
|
||||||
|
)
|
||||||
|
|
||||||
results['organization'] = organization
|
# use mode= for consistency with the other plugins but we also
|
||||||
results['token'] = token
|
# support webhook= for backwards compatibility.
|
||||||
|
results['mode'] = results['qsd'].get(
|
||||||
|
'mode', results['qsd'].get(
|
||||||
|
'webhook', RyverWebhookMode.RYVER))
|
||||||
|
|
||||||
|
# use image= for consistency with the other plugins
|
||||||
|
results['include_image'] = \
|
||||||
|
parse_bool(results['qsd'].get('image', True))
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
@ -24,7 +24,6 @@
|
|||||||
# THE SOFTWARE.
|
# THE SOFTWARE.
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import six
|
|
||||||
import hmac
|
import hmac
|
||||||
import requests
|
import requests
|
||||||
from hashlib import sha256
|
from hashlib import sha256
|
||||||
@ -35,6 +34,7 @@ from itertools import chain
|
|||||||
|
|
||||||
from .NotifyBase import NotifyBase
|
from .NotifyBase import NotifyBase
|
||||||
from ..common import NotifyType
|
from ..common import NotifyType
|
||||||
|
from ..utils import parse_list
|
||||||
|
|
||||||
# Some Phone Number Detection
|
# Some Phone Number Detection
|
||||||
IS_PHONE_NO = re.compile(r'^\+?(?P<phone>[0-9\s)(+-]+)\s*$')
|
IS_PHONE_NO = re.compile(r'^\+?(?P<phone>[0-9\s)(+-]+)\s*$')
|
||||||
@ -50,10 +50,6 @@ IS_PHONE_NO = re.compile(r'^\+?(?P<phone>[0-9\s)(+-]+)\s*$')
|
|||||||
# ambiguity between a topic that is comprised of all digits and a phone number
|
# ambiguity between a topic that is comprised of all digits and a phone number
|
||||||
IS_TOPIC = re.compile(r'^#?(?P<name>[A-Za-z0-9_-]+)\s*$')
|
IS_TOPIC = re.compile(r'^#?(?P<name>[A-Za-z0-9_-]+)\s*$')
|
||||||
|
|
||||||
# Used to break apart list of potential tags by their delimiter
|
|
||||||
# into a usable list.
|
|
||||||
LIST_DELIM = re.compile(r'[ \t\r\n,\\/]+')
|
|
||||||
|
|
||||||
# Because our AWS Access Key Secret contains slashes, we actually use the
|
# Because our AWS Access Key Secret contains slashes, we actually use the
|
||||||
# region as a delimiter. This is a bit hacky; but it's much easier than having
|
# region as a delimiter. This is a bit hacky; but it's much easier than having
|
||||||
# users of this product search though this Access Key Secret and escape all
|
# users of this product search though this Access Key Secret and escape all
|
||||||
@ -97,26 +93,26 @@ class NotifySNS(NotifyBase):
|
|||||||
title_maxlen = 0
|
title_maxlen = 0
|
||||||
|
|
||||||
def __init__(self, access_key_id, secret_access_key, region_name,
|
def __init__(self, access_key_id, secret_access_key, region_name,
|
||||||
recipients=None, **kwargs):
|
targets=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
Initialize Notify AWS SNS Object
|
Initialize Notify AWS SNS Object
|
||||||
"""
|
"""
|
||||||
super(NotifySNS, self).__init__(**kwargs)
|
super(NotifySNS, self).__init__(**kwargs)
|
||||||
|
|
||||||
if not access_key_id:
|
if not access_key_id:
|
||||||
raise TypeError(
|
msg = 'An invalid AWS Access Key ID was specified.'
|
||||||
'An invalid AWS Access Key ID was specified.'
|
self.logger.warning(msg)
|
||||||
)
|
raise TypeError(msg)
|
||||||
|
|
||||||
if not secret_access_key:
|
if not secret_access_key:
|
||||||
raise TypeError(
|
msg = 'An invalid AWS Secret Access Key was specified.'
|
||||||
'An invalid AWS Secret Access Key was specified.'
|
self.logger.warning(msg)
|
||||||
)
|
raise TypeError(msg)
|
||||||
|
|
||||||
if not (region_name and IS_REGION.match(region_name)):
|
if not (region_name and IS_REGION.match(region_name)):
|
||||||
raise TypeError(
|
msg = 'An invalid AWS Region was specified.'
|
||||||
'An invalid AWS Region was specified.'
|
self.logger.warning(msg)
|
||||||
)
|
raise TypeError(msg)
|
||||||
|
|
||||||
# Initialize topic list
|
# Initialize topic list
|
||||||
self.topics = list()
|
self.topics = list()
|
||||||
@ -147,20 +143,12 @@ class NotifySNS(NotifyBase):
|
|||||||
self.aws_auth_algorithm = 'AWS4-HMAC-SHA256'
|
self.aws_auth_algorithm = 'AWS4-HMAC-SHA256'
|
||||||
self.aws_auth_request = 'aws4_request'
|
self.aws_auth_request = 'aws4_request'
|
||||||
|
|
||||||
if recipients is None:
|
# Get our targets
|
||||||
recipients = []
|
targets = parse_list(targets)
|
||||||
|
|
||||||
elif isinstance(recipients, six.string_types):
|
# Validate targets and drop bad ones:
|
||||||
recipients = [x for x in filter(bool, LIST_DELIM.split(
|
for target in targets:
|
||||||
recipients,
|
result = IS_PHONE_NO.match(target)
|
||||||
))]
|
|
||||||
|
|
||||||
elif not isinstance(recipients, (set, tuple, list)):
|
|
||||||
recipients = []
|
|
||||||
|
|
||||||
# Validate recipients and drop bad ones:
|
|
||||||
for recipient in recipients:
|
|
||||||
result = IS_PHONE_NO.match(recipient)
|
|
||||||
if result:
|
if result:
|
||||||
# Further check our phone # for it's digit count
|
# Further check our phone # for it's digit count
|
||||||
# if it's less than 10, then we can assume it's
|
# if it's less than 10, then we can assume it's
|
||||||
@ -169,7 +157,7 @@ class NotifySNS(NotifyBase):
|
|||||||
if len(result) < 11 or len(result) > 14:
|
if len(result) < 11 or len(result) > 14:
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
'Dropped invalid phone # '
|
'Dropped invalid phone # '
|
||||||
'(%s) specified.' % recipient,
|
'(%s) specified.' % target,
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@ -177,7 +165,7 @@ class NotifySNS(NotifyBase):
|
|||||||
self.phone.append('+{}'.format(result))
|
self.phone.append('+{}'.format(result))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
result = IS_TOPIC.match(recipient)
|
result = IS_TOPIC.match(target)
|
||||||
if result:
|
if result:
|
||||||
# store valid topic
|
# store valid topic
|
||||||
self.topics.append(result.group('name'))
|
self.topics.append(result.group('name'))
|
||||||
@ -185,12 +173,12 @@ class NotifySNS(NotifyBase):
|
|||||||
|
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
'Dropped invalid phone/topic '
|
'Dropped invalid phone/topic '
|
||||||
'(%s) specified.' % recipient,
|
'(%s) specified.' % target,
|
||||||
)
|
)
|
||||||
|
|
||||||
if len(self.phone) == 0 and len(self.topics) == 0:
|
if len(self.phone) == 0 and len(self.topics) == 0:
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
'There are no valid recipient identified to notify.')
|
'There are no valid target identified to notify.')
|
||||||
|
|
||||||
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
||||||
"""
|
"""
|
||||||
@ -278,7 +266,7 @@ class NotifySNS(NotifyBase):
|
|||||||
self.throttle()
|
self.throttle()
|
||||||
|
|
||||||
# Convert our payload from a dict() into a urlencoded string
|
# Convert our payload from a dict() into a urlencoded string
|
||||||
payload = self.urlencode(payload)
|
payload = NotifySNS.urlencode(payload)
|
||||||
|
|
||||||
# Prepare our Notification URL
|
# Prepare our Notification URL
|
||||||
# Prepare our AWS Headers based on our payload
|
# Prepare our AWS Headers based on our payload
|
||||||
@ -300,7 +288,7 @@ class NotifySNS(NotifyBase):
|
|||||||
if r.status_code != requests.codes.ok:
|
if r.status_code != requests.codes.ok:
|
||||||
# We had a problem
|
# We had a problem
|
||||||
status_str = \
|
status_str = \
|
||||||
NotifyBase.http_response_code_lookup(
|
NotifySNS.http_response_code_lookup(
|
||||||
r.status_code, AWS_HTTP_ERROR_MAP)
|
r.status_code, AWS_HTTP_ERROR_MAP)
|
||||||
|
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
@ -541,17 +529,18 @@ class NotifySNS(NotifyBase):
|
|||||||
return '{schema}://{key_id}/{key_secret}/{region}/{targets}/'\
|
return '{schema}://{key_id}/{key_secret}/{region}/{targets}/'\
|
||||||
'?{args}'.format(
|
'?{args}'.format(
|
||||||
schema=self.secure_protocol,
|
schema=self.secure_protocol,
|
||||||
key_id=self.quote(self.aws_access_key_id, safe=''),
|
key_id=NotifySNS.quote(self.aws_access_key_id, safe=''),
|
||||||
key_secret=self.quote(self.aws_secret_access_key, safe=''),
|
key_secret=NotifySNS.quote(
|
||||||
region=self.quote(self.aws_region_name, safe=''),
|
self.aws_secret_access_key, safe=''),
|
||||||
|
region=NotifySNS.quote(self.aws_region_name, safe=''),
|
||||||
targets='/'.join(
|
targets='/'.join(
|
||||||
[self.quote(x) for x in chain(
|
[NotifySNS.quote(x) for x in chain(
|
||||||
# Phone # are prefixed with a plus symbol
|
# Phone # are prefixed with a plus symbol
|
||||||
['+{}'.format(x) for x in self.phone],
|
['+{}'.format(x) for x in self.phone],
|
||||||
# Topics are prefixed with a pound/hashtag symbol
|
# Topics are prefixed with a pound/hashtag symbol
|
||||||
['#{}'.format(x) for x in self.topics],
|
['#{}'.format(x) for x in self.topics],
|
||||||
)]),
|
)]),
|
||||||
args=self.urlencode(args),
|
args=NotifySNS.urlencode(args),
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -567,12 +556,8 @@ class NotifySNS(NotifyBase):
|
|||||||
# We're done early as we couldn't load the results
|
# We're done early as we couldn't load the results
|
||||||
return results
|
return results
|
||||||
|
|
||||||
#
|
|
||||||
# Apply our settings now
|
|
||||||
#
|
|
||||||
|
|
||||||
# The AWS Access Key ID is stored in the hostname
|
# The AWS Access Key ID is stored in the hostname
|
||||||
access_key_id = results['host']
|
access_key_id = NotifySNS.unquote(results['host'])
|
||||||
|
|
||||||
# Our AWS Access Key Secret contains slashes in it which unfortunately
|
# Our AWS Access Key Secret contains slashes in it which unfortunately
|
||||||
# means it is of variable length after the hostname. Since we require
|
# means it is of variable length after the hostname. Since we require
|
||||||
@ -586,9 +571,12 @@ class NotifySNS(NotifyBase):
|
|||||||
# accumulated data.
|
# accumulated data.
|
||||||
secret_access_key_parts = list()
|
secret_access_key_parts = list()
|
||||||
|
|
||||||
|
# Start with a list of entries to work with
|
||||||
|
entries = NotifySNS.split_path(results['fullpath'])
|
||||||
|
|
||||||
# Section 1: Get Region and Access Secret
|
# Section 1: Get Region and Access Secret
|
||||||
index = 0
|
index = 0
|
||||||
for i, entry in enumerate(NotifyBase.split_path(results['fullpath'])):
|
for i, entry in enumerate(entries):
|
||||||
|
|
||||||
# Are we at the region yet?
|
# Are we at the region yet?
|
||||||
result = IS_REGION.match(entry)
|
result = IS_REGION.match(entry)
|
||||||
@ -615,9 +603,13 @@ class NotifySNS(NotifyBase):
|
|||||||
secret_access_key_parts.append(entry)
|
secret_access_key_parts.append(entry)
|
||||||
|
|
||||||
# Section 2: Get our Recipients (basically all remaining entries)
|
# Section 2: Get our Recipients (basically all remaining entries)
|
||||||
results['recipients'] = [
|
results['targets'] = entries[index:]
|
||||||
NotifyBase.unquote(x) for x in filter(bool, NotifyBase.split_path(
|
|
||||||
results['fullpath']))][index:]
|
# Support the 'to' variable so that we can support rooms this way too
|
||||||
|
# The 'to' makes it easier to use yaml configuration
|
||||||
|
if 'to' in results['qsd'] and len(results['qsd']['to']):
|
||||||
|
results['targets'] += \
|
||||||
|
NotifySNS.parse_list(results['qsd']['to'])
|
||||||
|
|
||||||
# Store our other detected data (if at all)
|
# Store our other detected data (if at all)
|
||||||
results['region_name'] = region_name
|
results['region_name'] = region_name
|
||||||
|
@ -45,6 +45,7 @@ from .NotifyBase import NotifyBase
|
|||||||
from ..common import NotifyImageSize
|
from ..common import NotifyImageSize
|
||||||
from ..common import NotifyType
|
from ..common import NotifyType
|
||||||
from ..common import NotifyFormat
|
from ..common import NotifyFormat
|
||||||
|
from ..utils import parse_bool
|
||||||
|
|
||||||
# Token required as part of the API request
|
# Token required as part of the API request
|
||||||
# /AAAAAAAAA/........./........................
|
# /AAAAAAAAA/........./........................
|
||||||
@ -101,41 +102,51 @@ class NotifySlack(NotifyBase):
|
|||||||
|
|
||||||
notify_format = NotifyFormat.MARKDOWN
|
notify_format = NotifyFormat.MARKDOWN
|
||||||
|
|
||||||
def __init__(self, token_a, token_b, token_c, channels, **kwargs):
|
def __init__(self, token_a, token_b, token_c, targets,
|
||||||
|
include_image=True, **kwargs):
|
||||||
"""
|
"""
|
||||||
Initialize Slack Object
|
Initialize Slack Object
|
||||||
"""
|
"""
|
||||||
super(NotifySlack, self).__init__(**kwargs)
|
super(NotifySlack, self).__init__(**kwargs)
|
||||||
|
|
||||||
|
if not token_a:
|
||||||
|
msg = 'The first API token is not specified.'
|
||||||
|
self.logger.warning(msg)
|
||||||
|
raise TypeError(msg)
|
||||||
|
|
||||||
|
if not token_b:
|
||||||
|
msg = 'The second API token is not specified.'
|
||||||
|
self.logger.warning(msg)
|
||||||
|
raise TypeError(msg)
|
||||||
|
|
||||||
|
if not token_c:
|
||||||
|
msg = 'The third API token is not specified.'
|
||||||
|
self.logger.warning(msg)
|
||||||
|
raise TypeError(msg)
|
||||||
|
|
||||||
if not VALIDATE_TOKEN_A.match(token_a.strip()):
|
if not VALIDATE_TOKEN_A.match(token_a.strip()):
|
||||||
self.logger.warning(
|
msg = 'The first API token specified ({}) is invalid.'\
|
||||||
'The first API Token specified (%s) is invalid.' % token_a,
|
.format(token_a)
|
||||||
)
|
self.logger.warning(msg)
|
||||||
raise TypeError(
|
raise TypeError(msg)
|
||||||
'The first API Token specified (%s) is invalid.' % token_a,
|
|
||||||
)
|
|
||||||
|
|
||||||
# The token associated with the account
|
# The token associated with the account
|
||||||
self.token_a = token_a.strip()
|
self.token_a = token_a.strip()
|
||||||
|
|
||||||
if not VALIDATE_TOKEN_B.match(token_b.strip()):
|
if not VALIDATE_TOKEN_B.match(token_b.strip()):
|
||||||
self.logger.warning(
|
msg = 'The second API token specified ({}) is invalid.'\
|
||||||
'The second API Token specified (%s) is invalid.' % token_b,
|
.format(token_b)
|
||||||
)
|
self.logger.warning(msg)
|
||||||
raise TypeError(
|
raise TypeError(msg)
|
||||||
'The second API Token specified (%s) is invalid.' % token_b,
|
|
||||||
)
|
|
||||||
|
|
||||||
# The token associated with the account
|
# The token associated with the account
|
||||||
self.token_b = token_b.strip()
|
self.token_b = token_b.strip()
|
||||||
|
|
||||||
if not VALIDATE_TOKEN_C.match(token_c.strip()):
|
if not VALIDATE_TOKEN_C.match(token_c.strip()):
|
||||||
self.logger.warning(
|
msg = 'The third API token specified ({}) is invalid.'\
|
||||||
'The third API Token specified (%s) is invalid.' % token_c,
|
.format(token_c)
|
||||||
)
|
self.logger.warning(msg)
|
||||||
raise TypeError(
|
raise TypeError(msg)
|
||||||
'The third API Token specified (%s) is invalid.' % token_c,
|
|
||||||
)
|
|
||||||
|
|
||||||
# The token associated with the account
|
# The token associated with the account
|
||||||
self.token_c = token_c.strip()
|
self.token_c = token_c.strip()
|
||||||
@ -144,20 +155,21 @@ class NotifySlack(NotifyBase):
|
|||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
'No user was specified; using %s.' % SLACK_DEFAULT_USER)
|
'No user was specified; using %s.' % SLACK_DEFAULT_USER)
|
||||||
|
|
||||||
if isinstance(channels, six.string_types):
|
if isinstance(targets, six.string_types):
|
||||||
self.channels = [x for x in filter(bool, CHANNEL_LIST_DELIM.split(
|
self.channels = [x for x in filter(bool, CHANNEL_LIST_DELIM.split(
|
||||||
channels,
|
targets,
|
||||||
))]
|
))]
|
||||||
|
|
||||||
elif isinstance(channels, (set, tuple, list)):
|
elif isinstance(targets, (set, tuple, list)):
|
||||||
self.channels = channels
|
self.channels = targets
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.channels = list()
|
self.channels = list()
|
||||||
|
|
||||||
if len(self.channels) == 0:
|
if len(self.channels) == 0:
|
||||||
self.logger.warning('No channel(s) were specified.')
|
msg = 'No channel(s) were specified.'
|
||||||
raise TypeError('No channel(s) were specified.')
|
self.logger.warning(msg)
|
||||||
|
raise TypeError(msg)
|
||||||
|
|
||||||
# Formatting requirements are defined here:
|
# Formatting requirements are defined here:
|
||||||
# https://api.slack.com/docs/message-formatting
|
# https://api.slack.com/docs/message-formatting
|
||||||
@ -176,6 +188,9 @@ class NotifySlack(NotifyBase):
|
|||||||
re.IGNORECASE,
|
re.IGNORECASE,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Place a thumbnail image inline with the message body
|
||||||
|
self.include_image = include_image
|
||||||
|
|
||||||
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
||||||
"""
|
"""
|
||||||
Perform Slack Notification
|
Perform Slack Notification
|
||||||
@ -203,8 +218,6 @@ class NotifySlack(NotifyBase):
|
|||||||
self.token_c,
|
self.token_c,
|
||||||
)
|
)
|
||||||
|
|
||||||
image_url = self.image_url(notify_type)
|
|
||||||
|
|
||||||
# Create a copy of the channel list
|
# Create a copy of the channel list
|
||||||
channels = list(self.channels)
|
channels = list(self.channels)
|
||||||
while len(channels):
|
while len(channels):
|
||||||
@ -247,6 +260,10 @@ class NotifySlack(NotifyBase):
|
|||||||
}],
|
}],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Acquire our to-be footer icon if configured to do so
|
||||||
|
image_url = None if not self.include_image \
|
||||||
|
else self.image_url(notify_type)
|
||||||
|
|
||||||
if image_url:
|
if image_url:
|
||||||
payload['attachments'][0]['footer_icon'] = image_url
|
payload['attachments'][0]['footer_icon'] = image_url
|
||||||
|
|
||||||
@ -267,7 +284,7 @@ class NotifySlack(NotifyBase):
|
|||||||
if r.status_code != requests.codes.ok:
|
if r.status_code != requests.codes.ok:
|
||||||
# We had a problem
|
# We had a problem
|
||||||
status_str = \
|
status_str = \
|
||||||
NotifyBase.http_response_code_lookup(
|
NotifySlack.http_response_code_lookup(
|
||||||
r.status_code, SLACK_HTTP_ERROR_MAP)
|
r.status_code, SLACK_HTTP_ERROR_MAP)
|
||||||
|
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
@ -311,25 +328,26 @@ class NotifySlack(NotifyBase):
|
|||||||
args = {
|
args = {
|
||||||
'format': self.notify_format,
|
'format': self.notify_format,
|
||||||
'overflow': self.overflow_mode,
|
'overflow': self.overflow_mode,
|
||||||
|
'image': 'yes' if self.include_image else 'no',
|
||||||
}
|
}
|
||||||
|
|
||||||
# Determine if there is a botname present
|
# Determine if there is a botname present
|
||||||
botname = ''
|
botname = ''
|
||||||
if self.user:
|
if self.user:
|
||||||
botname = '{botname}@'.format(
|
botname = '{botname}@'.format(
|
||||||
botname=self.quote(self.user, safe=''),
|
botname=NotifySlack.quote(self.user, safe=''),
|
||||||
)
|
)
|
||||||
|
|
||||||
return '{schema}://{botname}{token_a}/{token_b}/{token_c}/{targets}/'\
|
return '{schema}://{botname}{token_a}/{token_b}/{token_c}/{targets}/'\
|
||||||
'?{args}'.format(
|
'?{args}'.format(
|
||||||
schema=self.secure_protocol,
|
schema=self.secure_protocol,
|
||||||
botname=botname,
|
botname=botname,
|
||||||
token_a=self.quote(self.token_a, safe=''),
|
token_a=NotifySlack.quote(self.token_a, safe=''),
|
||||||
token_b=self.quote(self.token_b, safe=''),
|
token_b=NotifySlack.quote(self.token_b, safe=''),
|
||||||
token_c=self.quote(self.token_c, safe=''),
|
token_c=NotifySlack.quote(self.token_c, safe=''),
|
||||||
targets='/'.join(
|
targets='/'.join(
|
||||||
[self.quote(x, safe='') for x in self.channels]),
|
[NotifySlack.quote(x, safe='') for x in self.channels]),
|
||||||
args=self.urlencode(args),
|
args=NotifySlack.urlencode(args),
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -345,26 +363,39 @@ class NotifySlack(NotifyBase):
|
|||||||
# We're done early as we couldn't load the results
|
# We're done early as we couldn't load the results
|
||||||
return results
|
return results
|
||||||
|
|
||||||
# Apply our settings now
|
# Get unquoted entries
|
||||||
|
entries = NotifySlack.split_path(results['fullpath'])
|
||||||
|
|
||||||
# The first token is stored in the hostname
|
# The first token is stored in the hostname
|
||||||
token_a = results['host']
|
results['token_a'] = NotifySlack.unquote(results['host'])
|
||||||
|
|
||||||
# Now fetch the remaining tokens
|
# Now fetch the remaining tokens
|
||||||
try:
|
try:
|
||||||
token_b, token_c = [x for x in filter(
|
results['token_b'] = entries.pop(0)
|
||||||
bool, NotifyBase.split_path(results['fullpath']))][0:2]
|
|
||||||
|
|
||||||
except (ValueError, AttributeError, IndexError):
|
except IndexError:
|
||||||
# We're done
|
# We're done
|
||||||
return None
|
results['token_b'] = None
|
||||||
|
|
||||||
channels = [x for x in filter(
|
try:
|
||||||
bool, NotifyBase.split_path(results['fullpath']))][2:]
|
results['token_c'] = entries.pop(0)
|
||||||
|
|
||||||
results['token_a'] = token_a
|
except IndexError:
|
||||||
results['token_b'] = token_b
|
# We're done
|
||||||
results['token_c'] = token_c
|
results['token_c'] = None
|
||||||
results['channels'] = channels
|
|
||||||
|
# assign remaining entries to the channels we wish to notify
|
||||||
|
results['targets'] = entries
|
||||||
|
|
||||||
|
# Support the 'to' variable so that we can support rooms this way too
|
||||||
|
# The 'to' makes it easier to use yaml configuration
|
||||||
|
if 'to' in results['qsd'] and len(results['qsd']['to']):
|
||||||
|
results['targets'] += [x for x in filter(
|
||||||
|
bool, CHANNEL_LIST_DELIM.split(
|
||||||
|
NotifySlack.unquote(results['qsd']['to'])))]
|
||||||
|
|
||||||
|
# Get Image
|
||||||
|
results['include_image'] = \
|
||||||
|
parse_bool(results['qsd'].get('image', True))
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
@ -107,7 +107,7 @@ class NotifyTelegram(NotifyBase):
|
|||||||
# The maximum allowable characters allowed in the body per message
|
# The maximum allowable characters allowed in the body per message
|
||||||
body_maxlen = 4096
|
body_maxlen = 4096
|
||||||
|
|
||||||
def __init__(self, bot_token, chat_ids, detect_bot_owner=True,
|
def __init__(self, bot_token, targets, detect_bot_owner=True,
|
||||||
include_image=True, **kwargs):
|
include_image=True, **kwargs):
|
||||||
"""
|
"""
|
||||||
Initialize Telegram Object
|
Initialize Telegram Object
|
||||||
@ -133,19 +133,19 @@ class NotifyTelegram(NotifyBase):
|
|||||||
self.bot_token = result.group('key')
|
self.bot_token = result.group('key')
|
||||||
|
|
||||||
# Parse our list
|
# Parse our list
|
||||||
self.chat_ids = parse_list(chat_ids)
|
self.targets = parse_list(targets)
|
||||||
|
|
||||||
if self.user:
|
if self.user:
|
||||||
# Treat this as a channel too
|
# Treat this as a channel too
|
||||||
self.chat_ids.append(self.user)
|
self.targets.append(self.user)
|
||||||
|
|
||||||
if len(self.chat_ids) == 0 and detect_bot_owner:
|
if len(self.targets) == 0 and detect_bot_owner:
|
||||||
_id = self.detect_bot_owner()
|
_id = self.detect_bot_owner()
|
||||||
if _id:
|
if _id:
|
||||||
# Store our id
|
# Store our id
|
||||||
self.chat_ids.append(str(_id))
|
self.targets.append(str(_id))
|
||||||
|
|
||||||
if len(self.chat_ids) == 0:
|
if len(self.targets) == 0:
|
||||||
err = 'No chat_id(s) were specified.'
|
err = 'No chat_id(s) were specified.'
|
||||||
self.logger.warning(err)
|
self.logger.warning(err)
|
||||||
raise TypeError(err)
|
raise TypeError(err)
|
||||||
@ -168,14 +168,25 @@ class NotifyTelegram(NotifyBase):
|
|||||||
'sendPhoto'
|
'sendPhoto'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Acquire our image path if configured to do so; we don't bother
|
||||||
|
# checking to see if selfinclude_image is set here because the
|
||||||
|
# send_image() function itself (this function) checks this flag
|
||||||
|
# already
|
||||||
path = self.image_path(notify_type)
|
path = self.image_path(notify_type)
|
||||||
|
|
||||||
if not path:
|
if not path:
|
||||||
# No image to send
|
# No image to send
|
||||||
self.logger.debug(
|
self.logger.debug(
|
||||||
'Telegram Image does not exist for %s' % (notify_type))
|
'Telegram Image does not exist for %s' % (notify_type))
|
||||||
return None
|
|
||||||
|
|
||||||
files = {'photo': (basename(path), open(path), 'rb')}
|
# No need to fail; we may have been configured this way through
|
||||||
|
# the apprise.AssetObject()
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Configure file payload (for upload)
|
||||||
|
files = {
|
||||||
|
'photo': (basename(path), open(path), 'rb'),
|
||||||
|
}
|
||||||
|
|
||||||
payload = {
|
payload = {
|
||||||
'chat_id': chat_id,
|
'chat_id': chat_id,
|
||||||
@ -196,7 +207,7 @@ class NotifyTelegram(NotifyBase):
|
|||||||
if r.status_code != requests.codes.ok:
|
if r.status_code != requests.codes.ok:
|
||||||
# We had a problem
|
# We had a problem
|
||||||
status_str = \
|
status_str = \
|
||||||
NotifyBase.http_response_code_lookup(r.status_code)
|
NotifyTelegram.http_response_code_lookup(r.status_code)
|
||||||
|
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
'Failed to send Telegram Image: '
|
'Failed to send Telegram Image: '
|
||||||
@ -248,7 +259,7 @@ class NotifyTelegram(NotifyBase):
|
|||||||
if r.status_code != requests.codes.ok:
|
if r.status_code != requests.codes.ok:
|
||||||
# We had a problem
|
# We had a problem
|
||||||
status_str = \
|
status_str = \
|
||||||
NotifyBase.http_response_code_lookup(r.status_code)
|
NotifyTelegram.http_response_code_lookup(r.status_code)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Try to get the error message if we can:
|
# Try to get the error message if we can:
|
||||||
@ -368,10 +379,10 @@ class NotifyTelegram(NotifyBase):
|
|||||||
title = re.sub(' ?', ' ', title, re.I)
|
title = re.sub(' ?', ' ', title, re.I)
|
||||||
|
|
||||||
# HTML
|
# HTML
|
||||||
title = NotifyBase.escape_html(title, whitespace=False)
|
title = NotifyTelegram.escape_html(title, whitespace=False)
|
||||||
|
|
||||||
# HTML
|
# HTML
|
||||||
body = NotifyBase.escape_html(body, whitespace=False)
|
body = NotifyTelegram.escape_html(body, whitespace=False)
|
||||||
|
|
||||||
if title and self.notify_format == NotifyFormat.TEXT:
|
if title and self.notify_format == NotifyFormat.TEXT:
|
||||||
# Text HTML Formatting
|
# Text HTML Formatting
|
||||||
@ -393,9 +404,9 @@ class NotifyTelegram(NotifyBase):
|
|||||||
payload['text'] = body
|
payload['text'] = body
|
||||||
|
|
||||||
# Create a copy of the chat_ids list
|
# Create a copy of the chat_ids list
|
||||||
chat_ids = list(self.chat_ids)
|
targets = list(self.targets)
|
||||||
while len(chat_ids):
|
while len(targets):
|
||||||
chat_id = chat_ids.pop(0)
|
chat_id = targets.pop(0)
|
||||||
chat_id = IS_CHAT_ID_RE.match(chat_id)
|
chat_id = IS_CHAT_ID_RE.match(chat_id)
|
||||||
if not chat_id:
|
if not chat_id:
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
@ -441,7 +452,7 @@ class NotifyTelegram(NotifyBase):
|
|||||||
if r.status_code != requests.codes.ok:
|
if r.status_code != requests.codes.ok:
|
||||||
# We had a problem
|
# We had a problem
|
||||||
status_str = \
|
status_str = \
|
||||||
NotifyBase.http_response_code_lookup(r.status_code)
|
NotifyTelegram.http_response_code_lookup(r.status_code)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Try to get the error message if we can:
|
# Try to get the error message if we can:
|
||||||
@ -489,16 +500,17 @@ class NotifyTelegram(NotifyBase):
|
|||||||
args = {
|
args = {
|
||||||
'format': self.notify_format,
|
'format': self.notify_format,
|
||||||
'overflow': self.overflow_mode,
|
'overflow': self.overflow_mode,
|
||||||
|
'image': self.include_image,
|
||||||
}
|
}
|
||||||
|
|
||||||
# No need to check the user token because the user automatically gets
|
# No need to check the user token because the user automatically gets
|
||||||
# appended into the list of chat ids
|
# appended into the list of chat ids
|
||||||
return '{schema}://{bot_token}/{targets}/?{args}'.format(
|
return '{schema}://{bot_token}/{targets}/?{args}'.format(
|
||||||
schema=self.secure_protocol,
|
schema=self.secure_protocol,
|
||||||
bot_token=self.quote(self.bot_token, safe=''),
|
bot_token=NotifyTelegram.quote(self.bot_token, safe=''),
|
||||||
targets='/'.join(
|
targets='/'.join(
|
||||||
[self.quote('@{}'.format(x)) for x in self.chat_ids]),
|
[NotifyTelegram.quote('@{}'.format(x)) for x in self.targets]),
|
||||||
args=self.urlencode(args))
|
args=NotifyTelegram.urlencode(args))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_url(url):
|
def parse_url(url):
|
||||||
@ -507,9 +519,9 @@ class NotifyTelegram(NotifyBase):
|
|||||||
us to substantiate this object.
|
us to substantiate this object.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# This is a dirty hack; but it's the only work around to
|
# This is a dirty hack; but it's the only work around to tgram://
|
||||||
# tgram:// messages since the bot_token has a colon in it.
|
# messages since the bot_token has a colon in it. It invalidates a
|
||||||
# It invalidates an normal URL.
|
# normal URL.
|
||||||
|
|
||||||
# This hack searches for this bogus URL and corrects it so we can
|
# This hack searches for this bogus URL and corrects it so we can
|
||||||
# properly load it further down. The other alternative is to ask users
|
# properly load it further down. The other alternative is to ask users
|
||||||
@ -550,23 +562,28 @@ class NotifyTelegram(NotifyBase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# The first token is stored in the hostname
|
# The first token is stored in the hostname
|
||||||
bot_token_a = results['host']
|
bot_token_a = NotifyTelegram.unquote(results['host'])
|
||||||
|
|
||||||
|
# Get a nice unquoted list of path entries
|
||||||
|
entries = NotifyTelegram.split_path(results['fullpath'])
|
||||||
|
|
||||||
# Now fetch the remaining tokens
|
# Now fetch the remaining tokens
|
||||||
bot_token_b = [x for x in filter(
|
bot_token_b = entries.pop(0)
|
||||||
bool, NotifyBase.split_path(results['fullpath']))][0]
|
|
||||||
|
|
||||||
bot_token = '%s:%s' % (bot_token_a, bot_token_b)
|
bot_token = '%s:%s' % (bot_token_a, bot_token_b)
|
||||||
|
|
||||||
chat_ids = [x for x in filter(
|
# Store our chat ids (as these are the remaining entries)
|
||||||
bool, NotifyBase.split_path(results['fullpath']))][1:]
|
results['targets'] = entries
|
||||||
|
|
||||||
|
# Support the 'to' variable so that we can support rooms this way too
|
||||||
|
# The 'to' makes it easier to use yaml configuration
|
||||||
|
if 'to' in results['qsd'] and len(results['qsd']['to']):
|
||||||
|
results['targets'] += \
|
||||||
|
NotifyTelegram.parse_list(results['qsd']['to'])
|
||||||
|
|
||||||
# Store our bot token
|
# Store our bot token
|
||||||
results['bot_token'] = bot_token
|
results['bot_token'] = bot_token
|
||||||
|
|
||||||
# Store our chat ids
|
|
||||||
results['chat_ids'] = chat_ids
|
|
||||||
|
|
||||||
# Include images with our message
|
# Include images with our message
|
||||||
results['include_image'] = \
|
results['include_image'] = \
|
||||||
parse_bool(results['qsd'].get('image', False))
|
parse_bool(results['qsd'].get('image', False))
|
||||||
|
@ -26,6 +26,7 @@
|
|||||||
from . import tweepy
|
from . import tweepy
|
||||||
from ..NotifyBase import NotifyBase
|
from ..NotifyBase import NotifyBase
|
||||||
from ...common import NotifyType
|
from ...common import NotifyType
|
||||||
|
from ...utils import parse_list
|
||||||
|
|
||||||
|
|
||||||
class NotifyTwitter(NotifyBase):
|
class NotifyTwitter(NotifyBase):
|
||||||
@ -54,7 +55,7 @@ class NotifyTwitter(NotifyBase):
|
|||||||
# Twitter does have titles when creating a message
|
# Twitter does have titles when creating a message
|
||||||
title_maxlen = 0
|
title_maxlen = 0
|
||||||
|
|
||||||
def __init__(self, ckey, csecret, akey, asecret, **kwargs):
|
def __init__(self, ckey, csecret, akey, asecret, targets=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
Initialize Twitter Object
|
Initialize Twitter Object
|
||||||
|
|
||||||
@ -62,29 +63,32 @@ class NotifyTwitter(NotifyBase):
|
|||||||
super(NotifyTwitter, self).__init__(**kwargs)
|
super(NotifyTwitter, self).__init__(**kwargs)
|
||||||
|
|
||||||
if not ckey:
|
if not ckey:
|
||||||
raise TypeError(
|
msg = 'An invalid Consumer API Key was specified.'
|
||||||
'An invalid Consumer API Key was specified.'
|
self.logger.warning(msg)
|
||||||
)
|
raise TypeError(msg)
|
||||||
|
|
||||||
if not csecret:
|
if not csecret:
|
||||||
raise TypeError(
|
msg = 'An invalid Consumer Secret API Key was specified.'
|
||||||
'An invalid Consumer Secret API Key was specified.'
|
self.logger.warning(msg)
|
||||||
)
|
raise TypeError(msg)
|
||||||
|
|
||||||
if not akey:
|
if not akey:
|
||||||
raise TypeError(
|
msg = 'An invalid Access Token API Key was specified.'
|
||||||
'An invalid Acess Token API Key was specified.'
|
self.logger.warning(msg)
|
||||||
)
|
raise TypeError(msg)
|
||||||
|
|
||||||
if not asecret:
|
if not asecret:
|
||||||
raise TypeError(
|
msg = 'An invalid Access Token Secret API Key was specified.'
|
||||||
'An invalid Acess Token Secret API Key was specified.'
|
self.logger.warning(msg)
|
||||||
)
|
raise TypeError(msg)
|
||||||
|
|
||||||
if not self.user:
|
# Identify our targets
|
||||||
raise TypeError(
|
self.targets = parse_list(targets)
|
||||||
'No user was specified.'
|
|
||||||
)
|
if len(self.targets) == 0 and not self.user:
|
||||||
|
msg = 'No user(s) were specified.'
|
||||||
|
self.logger.warning(msg)
|
||||||
|
raise TypeError(msg)
|
||||||
|
|
||||||
# Store our data
|
# Store our data
|
||||||
self.ckey = ckey
|
self.ckey = ckey
|
||||||
@ -113,28 +117,68 @@ class NotifyTwitter(NotifyBase):
|
|||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Always call throttle before any remote server i/o is made to avoid
|
# Get ourselves a list of targets
|
||||||
# thrashing the remote server and risk being blocked.
|
users = list(self.targets)
|
||||||
self.throttle()
|
if not users:
|
||||||
|
# notify ourselves
|
||||||
|
users.append(self.user)
|
||||||
|
|
||||||
try:
|
# Error Tracking
|
||||||
# Get our API
|
has_error = False
|
||||||
api = tweepy.API(self.auth)
|
|
||||||
|
|
||||||
# Send our Direct Message
|
while len(users) > 0:
|
||||||
api.send_direct_message(self.user, text=body)
|
# Get our user
|
||||||
self.logger.info('Sent Twitter DM notification.')
|
user = users.pop(0)
|
||||||
|
|
||||||
except Exception as e:
|
# Always call throttle before any remote server i/o is made to
|
||||||
self.logger.warning(
|
# avoid thrashing the remote server and risk being blocked.
|
||||||
'A Connection error occured sending Twitter '
|
self.throttle()
|
||||||
'direct message to %s.' % self.user)
|
|
||||||
self.logger.debug('Twitter Exception: %s' % str(e))
|
|
||||||
|
|
||||||
# Return; we're done
|
try:
|
||||||
return False
|
# Get our API
|
||||||
|
api = tweepy.API(self.auth)
|
||||||
|
|
||||||
return True
|
# Send our Direct Message
|
||||||
|
api.send_direct_message(user, text=body)
|
||||||
|
self.logger.info(
|
||||||
|
'Sent Twitter DM notification to {}.'.format(user))
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.warning(
|
||||||
|
'A Connection error occured sending Twitter '
|
||||||
|
'direct message to %s.' % user)
|
||||||
|
self.logger.debug('Twitter Exception: %s' % str(e))
|
||||||
|
|
||||||
|
# Track our error
|
||||||
|
has_error = True
|
||||||
|
|
||||||
|
return not has_error
|
||||||
|
|
||||||
|
def url(self):
|
||||||
|
"""
|
||||||
|
Returns the URL built dynamically based on specified arguments.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Define any arguments set
|
||||||
|
args = {
|
||||||
|
'format': self.notify_format,
|
||||||
|
'overflow': self.overflow_mode,
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(self.targets) > 0:
|
||||||
|
args['to'] = ','.join([NotifyTwitter.quote(x, safe='')
|
||||||
|
for x in self.targets])
|
||||||
|
|
||||||
|
return '{schema}://{auth}{ckey}/{csecret}/{akey}/{asecret}' \
|
||||||
|
'/?{args}'.format(
|
||||||
|
auth='' if not self.user else '{user}@'.format(
|
||||||
|
user=NotifyTwitter.quote(self.user, safe='')),
|
||||||
|
schema=self.secure_protocol,
|
||||||
|
ckey=NotifyTwitter.quote(self.ckey, safe=''),
|
||||||
|
asecret=NotifyTwitter.quote(self.csecret, safe=''),
|
||||||
|
akey=NotifyTwitter.quote(self.akey, safe=''),
|
||||||
|
csecret=NotifyTwitter.quote(self.asecret, safe=''),
|
||||||
|
args=NotifyTwitter.urlencode(args))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_url(url):
|
def parse_url(url):
|
||||||
@ -152,13 +196,12 @@ class NotifyTwitter(NotifyBase):
|
|||||||
# Apply our settings now
|
# Apply our settings now
|
||||||
|
|
||||||
# The first token is stored in the hostname
|
# The first token is stored in the hostname
|
||||||
consumer_key = results['host']
|
consumer_key = NotifyTwitter.unquote(results['host'])
|
||||||
|
|
||||||
# Now fetch the remaining tokens
|
# Now fetch the remaining tokens
|
||||||
try:
|
try:
|
||||||
consumer_secret, access_token_key, access_token_secret = \
|
consumer_secret, access_token_key, access_token_secret = \
|
||||||
[x for x in filter(bool, NotifyBase.split_path(
|
NotifyTwitter.split_path(results['fullpath'])[0:3]
|
||||||
results['fullpath']))][0:3]
|
|
||||||
|
|
||||||
except (ValueError, AttributeError, IndexError):
|
except (ValueError, AttributeError, IndexError):
|
||||||
# Force some bad values that will get caught
|
# Force some bad values that will get caught
|
||||||
@ -172,4 +215,8 @@ class NotifyTwitter(NotifyBase):
|
|||||||
results['akey'] = access_token_key
|
results['akey'] = access_token_key
|
||||||
results['asecret'] = access_token_secret
|
results['asecret'] = access_token_secret
|
||||||
|
|
||||||
|
# Support the to= allowing one to identify more then one user to tweet
|
||||||
|
# too
|
||||||
|
results['targets'] = NotifyTwitter.parse_list(results['qsd'].get('to'))
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
@ -31,6 +31,7 @@ from time import sleep
|
|||||||
from .NotifyBase import NotifyBase
|
from .NotifyBase import NotifyBase
|
||||||
from ..common import NotifyImageSize
|
from ..common import NotifyImageSize
|
||||||
from ..common import NotifyType
|
from ..common import NotifyType
|
||||||
|
from ..utils import parse_bool
|
||||||
|
|
||||||
# Default our global support flag
|
# Default our global support flag
|
||||||
NOTIFY_WINDOWS_SUPPORT_ENABLED = False
|
NOTIFY_WINDOWS_SUPPORT_ENABLED = False
|
||||||
@ -75,6 +76,9 @@ class NotifyWindows(NotifyBase):
|
|||||||
# content to display
|
# content to display
|
||||||
body_max_line_count = 2
|
body_max_line_count = 2
|
||||||
|
|
||||||
|
# The number of seconds to display the popup for
|
||||||
|
default_popup_duration_sec = 12
|
||||||
|
|
||||||
# This entry is a bit hacky, but it allows us to unit-test this library
|
# This entry is a bit hacky, but it allows us to unit-test this library
|
||||||
# in an environment that simply doesn't have the windows packages
|
# in an environment that simply doesn't have the windows packages
|
||||||
# available to us. It also allows us to handle situations where the
|
# available to us. It also allows us to handle situations where the
|
||||||
@ -84,18 +88,23 @@ class NotifyWindows(NotifyBase):
|
|||||||
# let me know! :)
|
# let me know! :)
|
||||||
_enabled = NOTIFY_WINDOWS_SUPPORT_ENABLED
|
_enabled = NOTIFY_WINDOWS_SUPPORT_ENABLED
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, include_image=True, duration=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
Initialize Windows Object
|
Initialize Windows Object
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
super(NotifyWindows, self).__init__(**kwargs)
|
||||||
|
|
||||||
# Number of seconds to display notification for
|
# Number of seconds to display notification for
|
||||||
self.duration = 12
|
self.duration = self.default_popup_duration_sec \
|
||||||
|
if not (isinstance(duration, int) and duration > 0) else duration
|
||||||
|
|
||||||
# Define our handler
|
# Define our handler
|
||||||
self.hwnd = None
|
self.hwnd = None
|
||||||
|
|
||||||
super(NotifyWindows, self).__init__(**kwargs)
|
# Track whether or not we want to send an image with our notification
|
||||||
|
# or not.
|
||||||
|
self.include_image = include_image
|
||||||
|
|
||||||
def _on_destroy(self, hwnd, msg, wparam, lparam):
|
def _on_destroy(self, hwnd, msg, wparam, lparam):
|
||||||
"""
|
"""
|
||||||
@ -140,20 +149,26 @@ class NotifyWindows(NotifyBase):
|
|||||||
self.hinst, None)
|
self.hinst, None)
|
||||||
win32gui.UpdateWindow(self.hwnd)
|
win32gui.UpdateWindow(self.hwnd)
|
||||||
|
|
||||||
# image path
|
# image path (if configured to acquire)
|
||||||
icon_path = self.image_path(notify_type, extension='.ico')
|
icon_path = None if not self.include_image \
|
||||||
icon_flags = win32con.LR_LOADFROMFILE | win32con.LR_DEFAULTSIZE
|
else self.image_path(notify_type, extension='.ico')
|
||||||
|
|
||||||
try:
|
if icon_path:
|
||||||
hicon = win32gui.LoadImage(
|
icon_flags = win32con.LR_LOADFROMFILE | win32con.LR_DEFAULTSIZE
|
||||||
self.hinst, icon_path, win32con.IMAGE_ICON, 0, 0,
|
|
||||||
icon_flags)
|
|
||||||
|
|
||||||
except Exception as e:
|
try:
|
||||||
self.logger.warning(
|
hicon = win32gui.LoadImage(
|
||||||
"Could not load windows notification icon ({}): {}"
|
self.hinst, icon_path, win32con.IMAGE_ICON, 0, 0,
|
||||||
.format(icon_path, e))
|
icon_flags)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.warning(
|
||||||
|
"Could not load windows notification icon ({}): {}"
|
||||||
|
.format(icon_path, e))
|
||||||
|
|
||||||
|
# disable icon
|
||||||
|
hicon = win32gui.LoadIcon(0, win32con.IDI_APPLICATION)
|
||||||
|
else:
|
||||||
# disable icon
|
# disable icon
|
||||||
hicon = win32gui.LoadIcon(0, win32con.IDI_APPLICATION)
|
hicon = win32gui.LoadIcon(0, win32con.IDI_APPLICATION)
|
||||||
|
|
||||||
@ -185,7 +200,18 @@ class NotifyWindows(NotifyBase):
|
|||||||
Returns the URL built dynamically based on specified arguments.
|
Returns the URL built dynamically based on specified arguments.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return '{schema}://'.format(schema=self.protocol)
|
# Define any arguments set
|
||||||
|
args = {
|
||||||
|
'format': self.notify_format,
|
||||||
|
'overflow': self.overflow_mode,
|
||||||
|
'image': 'yes' if self.include_image else 'no',
|
||||||
|
'duration': str(self.duration),
|
||||||
|
}
|
||||||
|
|
||||||
|
return '{schema}://_/?{args}'.format(
|
||||||
|
schema=self.protocol,
|
||||||
|
args=NotifyWindows.urlencode(args),
|
||||||
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_url(url):
|
def parse_url(url):
|
||||||
@ -196,15 +222,31 @@ class NotifyWindows(NotifyBase):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# return a very basic set of requirements
|
results = NotifyBase.parse_url(url)
|
||||||
return {
|
if not results:
|
||||||
'schema': NotifyWindows.protocol,
|
results = {
|
||||||
'user': None,
|
'schema': NotifyWindows.protocol,
|
||||||
'password': None,
|
'user': None,
|
||||||
'port': None,
|
'password': None,
|
||||||
'host': 'localhost',
|
'port': None,
|
||||||
'fullpath': None,
|
'host': '_',
|
||||||
'path': None,
|
'fullpath': None,
|
||||||
'url': url,
|
'path': None,
|
||||||
'qsd': {},
|
'url': url,
|
||||||
}
|
'qsd': {},
|
||||||
|
}
|
||||||
|
|
||||||
|
# Include images with our message
|
||||||
|
results['include_image'] = \
|
||||||
|
parse_bool(results['qsd'].get('image', True))
|
||||||
|
|
||||||
|
# Set duration
|
||||||
|
try:
|
||||||
|
results['duration'] = int(results['qsd'].get('duration'))
|
||||||
|
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
# Not a valid integer; ignore entry
|
||||||
|
pass
|
||||||
|
|
||||||
|
# return results
|
||||||
|
return results
|
||||||
|
@ -29,6 +29,7 @@ from json import dumps
|
|||||||
from .NotifyBase import NotifyBase
|
from .NotifyBase import NotifyBase
|
||||||
from ..common import NotifyType
|
from ..common import NotifyType
|
||||||
from ..common import NotifyImageSize
|
from ..common import NotifyImageSize
|
||||||
|
from ..utils import parse_bool
|
||||||
|
|
||||||
|
|
||||||
class NotifyXBMC(NotifyBase):
|
class NotifyXBMC(NotifyBase):
|
||||||
@ -70,26 +71,27 @@ class NotifyXBMC(NotifyBase):
|
|||||||
# Allows the user to specify the NotifyImageSize object
|
# Allows the user to specify the NotifyImageSize object
|
||||||
image_size = NotifyImageSize.XY_128
|
image_size = NotifyImageSize.XY_128
|
||||||
|
|
||||||
|
# The number of seconds to display the popup for
|
||||||
|
default_popup_duration_sec = 12
|
||||||
|
|
||||||
# XBMC default protocol version (v2)
|
# XBMC default protocol version (v2)
|
||||||
xbmc_remote_protocol = 2
|
xbmc_remote_protocol = 2
|
||||||
|
|
||||||
# KODI default protocol version (v6)
|
# KODI default protocol version (v6)
|
||||||
kodi_remote_protocol = 6
|
kodi_remote_protocol = 6
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, include_image=True, duration=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
Initialize XBMC/KODI Object
|
Initialize XBMC/KODI Object
|
||||||
"""
|
"""
|
||||||
super(NotifyXBMC, self).__init__(**kwargs)
|
super(NotifyXBMC, self).__init__(**kwargs)
|
||||||
|
|
||||||
# Number of micro-seconds to display notification for
|
# Number of seconds to display notification for
|
||||||
self.duration = 12000
|
self.duration = self.default_popup_duration_sec \
|
||||||
|
if not (isinstance(duration, int) and duration > 0) else duration
|
||||||
|
|
||||||
if self.secure:
|
# Build our schema
|
||||||
self.schema = 'https'
|
self.schema = 'https' if self.secure else 'http'
|
||||||
|
|
||||||
else:
|
|
||||||
self.schema = 'http'
|
|
||||||
|
|
||||||
# Prepare the default header
|
# Prepare the default header
|
||||||
self.headers = {
|
self.headers = {
|
||||||
@ -100,6 +102,10 @@ class NotifyXBMC(NotifyBase):
|
|||||||
# Default protocol
|
# Default protocol
|
||||||
self.protocol = kwargs.get('protocol', self.xbmc_remote_protocol)
|
self.protocol = kwargs.get('protocol', self.xbmc_remote_protocol)
|
||||||
|
|
||||||
|
# Track whether or not we want to send an image with our notification
|
||||||
|
# or not.
|
||||||
|
self.include_image = include_image
|
||||||
|
|
||||||
def _payload_60(self, title, body, notify_type, **kwargs):
|
def _payload_60(self, title, body, notify_type, **kwargs):
|
||||||
"""
|
"""
|
||||||
Builds payload for KODI API v6.0
|
Builds payload for KODI API v6.0
|
||||||
@ -114,13 +120,17 @@ class NotifyXBMC(NotifyBase):
|
|||||||
'params': {
|
'params': {
|
||||||
'title': title,
|
'title': title,
|
||||||
'message': body,
|
'message': body,
|
||||||
# displaytime is defined in microseconds
|
# displaytime is defined in microseconds so we need to just
|
||||||
'displaytime': self.duration,
|
# do some simple math
|
||||||
|
'displaytime': int(self.duration * 1000),
|
||||||
},
|
},
|
||||||
'id': 1,
|
'id': 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
image_url = self.image_url(notify_type)
|
# Acquire our image url if configured to do so
|
||||||
|
image_url = None if not self.include_image else \
|
||||||
|
self.image_url(notify_type)
|
||||||
|
|
||||||
if image_url:
|
if image_url:
|
||||||
payload['params']['image'] = image_url
|
payload['params']['image'] = image_url
|
||||||
if notify_type is NotifyType.FAILURE:
|
if notify_type is NotifyType.FAILURE:
|
||||||
@ -148,13 +158,17 @@ class NotifyXBMC(NotifyBase):
|
|||||||
'params': {
|
'params': {
|
||||||
'title': title,
|
'title': title,
|
||||||
'message': body,
|
'message': body,
|
||||||
# displaytime is defined in microseconds
|
# displaytime is defined in microseconds so we need to just
|
||||||
'displaytime': self.duration,
|
# do some simple math
|
||||||
|
'displaytime': int(self.duration * 1000),
|
||||||
},
|
},
|
||||||
'id': 1,
|
'id': 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
image_url = self.image_url(notify_type)
|
# Include our logo if configured to do so
|
||||||
|
image_url = None if not self.include_image \
|
||||||
|
else self.image_url(notify_type)
|
||||||
|
|
||||||
if image_url:
|
if image_url:
|
||||||
payload['params']['image'] = image_url
|
payload['params']['image'] = image_url
|
||||||
|
|
||||||
@ -204,7 +218,7 @@ class NotifyXBMC(NotifyBase):
|
|||||||
if r.status_code != requests.codes.ok:
|
if r.status_code != requests.codes.ok:
|
||||||
# We had a problem
|
# We had a problem
|
||||||
status_str = \
|
status_str = \
|
||||||
NotifyBase.http_response_code_lookup(r.status_code)
|
NotifyXBMC.http_response_code_lookup(r.status_code)
|
||||||
|
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
'Failed to send XBMC/KODI notification: '
|
'Failed to send XBMC/KODI notification: '
|
||||||
@ -242,18 +256,20 @@ class NotifyXBMC(NotifyBase):
|
|||||||
args = {
|
args = {
|
||||||
'format': self.notify_format,
|
'format': self.notify_format,
|
||||||
'overflow': self.overflow_mode,
|
'overflow': self.overflow_mode,
|
||||||
|
'image': 'yes' if self.include_image else 'no',
|
||||||
|
'duration': str(self.duration),
|
||||||
}
|
}
|
||||||
|
|
||||||
# Determine Authentication
|
# Determine Authentication
|
||||||
auth = ''
|
auth = ''
|
||||||
if self.user and self.password:
|
if self.user and self.password:
|
||||||
auth = '{user}:{password}@'.format(
|
auth = '{user}:{password}@'.format(
|
||||||
user=self.quote(self.user, safe=''),
|
user=NotifyXBMC.quote(self.user, safe=''),
|
||||||
password=self.quote(self.password, safe=''),
|
password=NotifyXBMC.quote(self.password, safe=''),
|
||||||
)
|
)
|
||||||
elif self.user:
|
elif self.user:
|
||||||
auth = '{user}@'.format(
|
auth = '{user}@'.format(
|
||||||
user=self.quote(self.user, safe=''),
|
user=NotifyXBMC.quote(self.user, safe=''),
|
||||||
)
|
)
|
||||||
|
|
||||||
default_schema = self.xbmc_protocol if (
|
default_schema = self.xbmc_protocol if (
|
||||||
@ -266,10 +282,10 @@ class NotifyXBMC(NotifyBase):
|
|||||||
return '{schema}://{auth}{hostname}{port}/?{args}'.format(
|
return '{schema}://{auth}{hostname}{port}/?{args}'.format(
|
||||||
schema=default_schema,
|
schema=default_schema,
|
||||||
auth=auth,
|
auth=auth,
|
||||||
hostname=self.host,
|
hostname=NotifyXBMC.quote(self.host, safe=''),
|
||||||
port='' if not self.port or self.port == default_port
|
port='' if not self.port or self.port == default_port
|
||||||
else ':{}'.format(self.port),
|
else ':{}'.format(self.port),
|
||||||
args=self.urlencode(args),
|
args=NotifyXBMC.urlencode(args),
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -298,4 +314,16 @@ class NotifyXBMC(NotifyBase):
|
|||||||
# KODI Support
|
# KODI Support
|
||||||
results['protocol'] = NotifyXBMC.kodi_remote_protocol
|
results['protocol'] = NotifyXBMC.kodi_remote_protocol
|
||||||
|
|
||||||
|
# Include images with our message
|
||||||
|
results['include_image'] = \
|
||||||
|
parse_bool(results['qsd'].get('image', True))
|
||||||
|
|
||||||
|
# Set duration
|
||||||
|
try:
|
||||||
|
results['duration'] = abs(int(results['qsd'].get('duration')))
|
||||||
|
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
# Not a valid integer; ignore entry
|
||||||
|
pass
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
@ -81,12 +81,6 @@ class NotifyXML(NotifyBase):
|
|||||||
</soapenv:Body>
|
</soapenv:Body>
|
||||||
</soapenv:Envelope>"""
|
</soapenv:Envelope>"""
|
||||||
|
|
||||||
if self.secure:
|
|
||||||
self.schema = 'https'
|
|
||||||
|
|
||||||
else:
|
|
||||||
self.schema = 'http'
|
|
||||||
|
|
||||||
self.fullpath = kwargs.get('fullpath')
|
self.fullpath = kwargs.get('fullpath')
|
||||||
if not isinstance(self.fullpath, six.string_types):
|
if not isinstance(self.fullpath, six.string_types):
|
||||||
self.fullpath = '/'
|
self.fullpath = '/'
|
||||||
@ -116,12 +110,12 @@ class NotifyXML(NotifyBase):
|
|||||||
auth = ''
|
auth = ''
|
||||||
if self.user and self.password:
|
if self.user and self.password:
|
||||||
auth = '{user}:{password}@'.format(
|
auth = '{user}:{password}@'.format(
|
||||||
user=self.quote(self.user, safe=''),
|
user=NotifyXML.quote(self.user, safe=''),
|
||||||
password=self.quote(self.password, safe=''),
|
password=NotifyXML.quote(self.password, safe=''),
|
||||||
)
|
)
|
||||||
elif self.user:
|
elif self.user:
|
||||||
auth = '{user}@'.format(
|
auth = '{user}@'.format(
|
||||||
user=self.quote(self.user, safe=''),
|
user=NotifyXML.quote(self.user, safe=''),
|
||||||
)
|
)
|
||||||
|
|
||||||
default_port = 443 if self.secure else 80
|
default_port = 443 if self.secure else 80
|
||||||
@ -129,10 +123,10 @@ class NotifyXML(NotifyBase):
|
|||||||
return '{schema}://{auth}{hostname}{port}/?{args}'.format(
|
return '{schema}://{auth}{hostname}{port}/?{args}'.format(
|
||||||
schema=self.secure_protocol if self.secure else self.protocol,
|
schema=self.secure_protocol if self.secure else self.protocol,
|
||||||
auth=auth,
|
auth=auth,
|
||||||
hostname=self.host,
|
hostname=NotifyXML.quote(self.host, safe=''),
|
||||||
port='' if self.port is None or self.port == default_port
|
port='' if self.port is None or self.port == default_port
|
||||||
else ':{}'.format(self.port),
|
else ':{}'.format(self.port),
|
||||||
args=self.urlencode(args),
|
args=NotifyXML.urlencode(args),
|
||||||
)
|
)
|
||||||
|
|
||||||
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
||||||
@ -150,9 +144,10 @@ class NotifyXML(NotifyBase):
|
|||||||
headers.update(self.headers)
|
headers.update(self.headers)
|
||||||
|
|
||||||
re_map = {
|
re_map = {
|
||||||
'{MESSAGE_TYPE}': NotifyBase.quote(notify_type),
|
'{MESSAGE_TYPE}': NotifyXML.escape_html(
|
||||||
'{SUBJECT}': NotifyBase.quote(title),
|
notify_type, whitespace=False),
|
||||||
'{MESSAGE}': NotifyBase.quote(body),
|
'{SUBJECT}': NotifyXML.escape_html(title, whitespace=False),
|
||||||
|
'{MESSAGE}': NotifyXML.escape_html(body, whitespace=False),
|
||||||
}
|
}
|
||||||
|
|
||||||
# Iterate over above list and store content accordingly
|
# Iterate over above list and store content accordingly
|
||||||
@ -165,7 +160,10 @@ class NotifyXML(NotifyBase):
|
|||||||
if self.user:
|
if self.user:
|
||||||
auth = (self.user, self.password)
|
auth = (self.user, self.password)
|
||||||
|
|
||||||
url = '%s://%s' % (self.schema, self.host)
|
# Set our schema
|
||||||
|
schema = 'https' if self.secure else 'http'
|
||||||
|
|
||||||
|
url = '%s://%s' % (schema, self.host)
|
||||||
if isinstance(self.port, int):
|
if isinstance(self.port, int):
|
||||||
url += ':%d' % self.port
|
url += ':%d' % self.port
|
||||||
|
|
||||||
@ -191,7 +189,7 @@ class NotifyXML(NotifyBase):
|
|||||||
if r.status_code != requests.codes.ok:
|
if r.status_code != requests.codes.ok:
|
||||||
# We had a problem
|
# We had a problem
|
||||||
status_str = \
|
status_str = \
|
||||||
NotifyBase.http_response_code_lookup(r.status_code)
|
NotifyXML.http_response_code_lookup(r.status_code)
|
||||||
|
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
'Failed to send XML notification: '
|
'Failed to send XML notification: '
|
||||||
@ -237,4 +235,8 @@ class NotifyXML(NotifyBase):
|
|||||||
results['headers'] = results['qsd-']
|
results['headers'] = results['qsd-']
|
||||||
results['headers'].update(results['qsd+'])
|
results['headers'].update(results['qsd+'])
|
||||||
|
|
||||||
|
# Tidy our header entries by unquoting them
|
||||||
|
results['headers'] = {NotifyXML.unquote(x): NotifyXML.unquote(y)
|
||||||
|
for x, y in results['headers'].items()}
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
@ -24,7 +24,6 @@
|
|||||||
# THE SOFTWARE.
|
# THE SOFTWARE.
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import six
|
|
||||||
import ssl
|
import ssl
|
||||||
from os.path import isfile
|
from os.path import isfile
|
||||||
|
|
||||||
@ -99,7 +98,7 @@ class NotifyXMPP(NotifyBase):
|
|||||||
# let me know! :)
|
# let me know! :)
|
||||||
_enabled = NOTIFY_XMPP_SUPPORT_ENABLED
|
_enabled = NOTIFY_XMPP_SUPPORT_ENABLED
|
||||||
|
|
||||||
def __init__(self, targets=None, jid=None, xep=None, to=None, **kwargs):
|
def __init__(self, targets=None, jid=None, xep=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
Initialize XMPP Object
|
Initialize XMPP Object
|
||||||
"""
|
"""
|
||||||
@ -177,17 +176,6 @@ class NotifyXMPP(NotifyBase):
|
|||||||
else:
|
else:
|
||||||
self.targets = list()
|
self.targets = list()
|
||||||
|
|
||||||
if isinstance(to, six.string_types):
|
|
||||||
# supporting to= makes yaml configuration easier since the user
|
|
||||||
# just has to identify each user one after another. This is just
|
|
||||||
# an optional extension to also make the url easier to read if
|
|
||||||
# some wish to use it.
|
|
||||||
|
|
||||||
# the to is presumed to be the targets JID
|
|
||||||
self.targets.append(to)
|
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
||||||
"""
|
"""
|
||||||
Perform XMPP Notification
|
Perform XMPP Notification
|
||||||
@ -302,15 +290,16 @@ class NotifyXMPP(NotifyBase):
|
|||||||
}
|
}
|
||||||
|
|
||||||
if self.jid:
|
if self.jid:
|
||||||
args['jid'] = self.quote(self.jid, safe='')
|
args['jid'] = self.jid
|
||||||
|
|
||||||
if self.xep:
|
if self.xep:
|
||||||
args['xep'] = self.quote(
|
# xep are integers, so we need to just iterate over a list and
|
||||||
','.join([str(xep) for xep in self.xep]), safe='')
|
# switch them to a string
|
||||||
|
args['xep'] = ','.join([str(xep) for xep in self.xep])
|
||||||
|
|
||||||
# Target JID(s) can clash with our existing paths, so we just use comma
|
# Target JID(s) can clash with our existing paths, so we just use comma
|
||||||
# and/or space as a delimiters
|
# and/or space as a delimiters - %20 = space
|
||||||
jids = self.quote(' '.join(self.targets), safe='')
|
jids = '%20'.join([NotifyXMPP.quote(x, safe='') for x in self.targets])
|
||||||
|
|
||||||
default_port = self.default_secure_port \
|
default_port = self.default_secure_port \
|
||||||
if self.secure else self.default_unsecure_port
|
if self.secure else self.default_unsecure_port
|
||||||
@ -318,19 +307,21 @@ class NotifyXMPP(NotifyBase):
|
|||||||
default_schema = self.secure_protocol if self.secure else self.protocol
|
default_schema = self.secure_protocol if self.secure else self.protocol
|
||||||
|
|
||||||
if self.user and self.password:
|
if self.user and self.password:
|
||||||
auth = '{}:{}'.format(self.user, self.password)
|
auth = '{}:{}'.format(
|
||||||
|
NotifyXMPP.quote(self.user, safe=''),
|
||||||
|
NotifyXMPP.quote(self.password, safe=''))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
auth = self.password if self.password else self.user
|
auth = self.password if self.password else self.user
|
||||||
|
|
||||||
return '{schema}://{auth}@{hostname}{port}/{jids}?{args}'.format(
|
return '{schema}://{auth}@{hostname}{port}/{jids}?{args}'.format(
|
||||||
auth=self.quote(auth, safe=''),
|
auth=auth,
|
||||||
schema=default_schema,
|
schema=default_schema,
|
||||||
hostname=self.host,
|
hostname=NotifyXMPP.quote(self.host, safe=''),
|
||||||
port='' if not self.port or self.port == default_port
|
port='' if not self.port or self.port == default_port
|
||||||
else ':{}'.format(self.port),
|
else ':{}'.format(self.port),
|
||||||
jids=jids,
|
jids=jids,
|
||||||
args=self.urlencode(args),
|
args=NotifyXMPP.urlencode(args),
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -348,18 +339,20 @@ class NotifyXMPP(NotifyBase):
|
|||||||
|
|
||||||
# Get our targets; we ignore path slashes since they identify
|
# Get our targets; we ignore path slashes since they identify
|
||||||
# our resources
|
# our resources
|
||||||
results['targets'] = parse_list(results['fullpath'])
|
results['targets'] = NotifyXMPP.parse_list(results['fullpath'])
|
||||||
|
|
||||||
# Over-ride the xep plugins
|
# Over-ride the xep plugins
|
||||||
if 'xep' in results['qsd'] and len(results['qsd']['xep']):
|
if 'xep' in results['qsd'] and len(results['qsd']['xep']):
|
||||||
results['xep'] = parse_list(results['qsd']['xep'])
|
results['xep'] = \
|
||||||
|
NotifyXMPP.parse_list(results['qsd']['xep'])
|
||||||
|
|
||||||
# Over-ride the default (and detected) jid
|
# Over-ride the default (and detected) jid
|
||||||
if 'jid' in results['qsd'] and len(results['qsd']['jid']):
|
if 'jid' in results['qsd'] and len(results['qsd']['jid']):
|
||||||
results['jid'] = results['qsd']['jid']
|
results['jid'] = NotifyXMPP.unquote(results['qsd']['jid'])
|
||||||
|
|
||||||
# Over-ride the default (and detected) jid
|
# Over-ride the default (and detected) jid
|
||||||
if 'to' in results['qsd'] and len(results['qsd']['to']):
|
if 'to' in results['qsd'] and len(results['qsd']['to']):
|
||||||
results['to'] = results['qsd']['to']
|
results['targets'] += \
|
||||||
|
NotifyXMPP.parse_list(results['qsd']['to'])
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
@ -101,6 +101,7 @@ def test_notify_gitter_plugin_general(mock_post, mock_get):
|
|||||||
obj = plugins.NotifyGitter(token=token, targets='apprise')
|
obj = plugins.NotifyGitter(token=token, targets='apprise')
|
||||||
assert isinstance(obj, plugins.NotifyGitter) is True
|
assert isinstance(obj, plugins.NotifyGitter) is True
|
||||||
assert isinstance(obj.url(), six.string_types) is True
|
assert isinstance(obj.url(), six.string_types) is True
|
||||||
|
|
||||||
# apprise room was found
|
# apprise room was found
|
||||||
assert obj.send(body="test") is True
|
assert obj.send(body="test") is True
|
||||||
|
|
||||||
|
@ -176,6 +176,87 @@ def test_dbus_plugin(mock_mainloop, mock_byte, mock_bytearray,
|
|||||||
assert(obj.notify(title='', body='body',
|
assert(obj.notify(title='', body='body',
|
||||||
notify_type=apprise.NotifyType.INFO) is True)
|
notify_type=apprise.NotifyType.INFO) is True)
|
||||||
|
|
||||||
|
# Test our arguments through the instantiate call
|
||||||
|
obj = apprise.Apprise.instantiate(
|
||||||
|
'dbus://_/?image=True', suppress_exceptions=False)
|
||||||
|
assert(isinstance(obj, apprise.plugins.NotifyDBus) is True)
|
||||||
|
assert(isinstance(obj.url(), six.string_types) is True)
|
||||||
|
assert(obj.notify(title='title', body='body',
|
||||||
|
notify_type=apprise.NotifyType.INFO) is True)
|
||||||
|
|
||||||
|
obj = apprise.Apprise.instantiate(
|
||||||
|
'dbus://_/?image=False', suppress_exceptions=False)
|
||||||
|
assert(isinstance(obj, apprise.plugins.NotifyDBus) is True)
|
||||||
|
assert(isinstance(obj.url(), six.string_types) is True)
|
||||||
|
assert(obj.notify(title='title', body='body',
|
||||||
|
notify_type=apprise.NotifyType.INFO) is True)
|
||||||
|
|
||||||
|
# Test priority (alias to urgency) handling
|
||||||
|
obj = apprise.Apprise.instantiate(
|
||||||
|
'dbus://_/?priority=invalid', suppress_exceptions=False)
|
||||||
|
assert(isinstance(obj, apprise.plugins.NotifyDBus) is True)
|
||||||
|
assert(isinstance(obj.url(), six.string_types) is True)
|
||||||
|
assert(obj.notify(title='title', body='body',
|
||||||
|
notify_type=apprise.NotifyType.INFO) is True)
|
||||||
|
|
||||||
|
obj = apprise.Apprise.instantiate(
|
||||||
|
'dbus://_/?priority=high', suppress_exceptions=False)
|
||||||
|
assert(isinstance(obj, apprise.plugins.NotifyDBus) is True)
|
||||||
|
assert(isinstance(obj.url(), six.string_types) is True)
|
||||||
|
assert(obj.notify(title='title', body='body',
|
||||||
|
notify_type=apprise.NotifyType.INFO) is True)
|
||||||
|
|
||||||
|
obj = apprise.Apprise.instantiate(
|
||||||
|
'dbus://_/?priority=2', suppress_exceptions=False)
|
||||||
|
assert(isinstance(obj, apprise.plugins.NotifyDBus) is True)
|
||||||
|
assert(isinstance(obj.url(), six.string_types) is True)
|
||||||
|
assert(obj.notify(title='title', body='body',
|
||||||
|
notify_type=apprise.NotifyType.INFO) is True)
|
||||||
|
|
||||||
|
# Test urgency handling
|
||||||
|
obj = apprise.Apprise.instantiate(
|
||||||
|
'dbus://_/?urgency=invalid', suppress_exceptions=False)
|
||||||
|
assert(isinstance(obj, apprise.plugins.NotifyDBus) is True)
|
||||||
|
assert(isinstance(obj.url(), six.string_types) is True)
|
||||||
|
assert(obj.notify(title='title', body='body',
|
||||||
|
notify_type=apprise.NotifyType.INFO) is True)
|
||||||
|
|
||||||
|
obj = apprise.Apprise.instantiate(
|
||||||
|
'dbus://_/?urgency=high', suppress_exceptions=False)
|
||||||
|
assert(isinstance(obj, apprise.plugins.NotifyDBus) is True)
|
||||||
|
assert(isinstance(obj.url(), six.string_types) is True)
|
||||||
|
assert(obj.notify(title='title', body='body',
|
||||||
|
notify_type=apprise.NotifyType.INFO) is True)
|
||||||
|
|
||||||
|
obj = apprise.Apprise.instantiate(
|
||||||
|
'dbus://_/?urgency=2', suppress_exceptions=False)
|
||||||
|
assert(isinstance(obj, apprise.plugins.NotifyDBus) is True)
|
||||||
|
assert(isinstance(obj.url(), six.string_types) is True)
|
||||||
|
assert(obj.notify(title='title', body='body',
|
||||||
|
notify_type=apprise.NotifyType.INFO) is True)
|
||||||
|
|
||||||
|
obj = apprise.Apprise.instantiate(
|
||||||
|
'dbus://_/?urgency=', suppress_exceptions=False)
|
||||||
|
assert(isinstance(obj, apprise.plugins.NotifyDBus) is True)
|
||||||
|
assert(isinstance(obj.url(), six.string_types) is True)
|
||||||
|
assert(obj.notify(title='title', body='body',
|
||||||
|
notify_type=apprise.NotifyType.INFO) is True)
|
||||||
|
|
||||||
|
# Test x/y
|
||||||
|
obj = apprise.Apprise.instantiate(
|
||||||
|
'dbus://_/?x=5&y=5', suppress_exceptions=False)
|
||||||
|
assert(isinstance(obj, apprise.plugins.NotifyDBus) is True)
|
||||||
|
assert(isinstance(obj.url(), six.string_types) is True)
|
||||||
|
assert(obj.notify(title='title', body='body',
|
||||||
|
notify_type=apprise.NotifyType.INFO) is True)
|
||||||
|
|
||||||
|
obj = apprise.Apprise.instantiate(
|
||||||
|
'dbus://_/?x=invalid&y=invalid', suppress_exceptions=False)
|
||||||
|
assert(isinstance(obj, apprise.plugins.NotifyDBus) is True)
|
||||||
|
assert(isinstance(obj.url(), six.string_types) is True)
|
||||||
|
assert(obj.notify(title='title', body='body',
|
||||||
|
notify_type=apprise.NotifyType.INFO) is True)
|
||||||
|
|
||||||
# If our underlining object throws for whatever reason, we will
|
# If our underlining object throws for whatever reason, we will
|
||||||
# gracefully fail
|
# gracefully fail
|
||||||
mock_notify = mock.Mock()
|
mock_notify = mock.Mock()
|
||||||
|
@ -116,24 +116,80 @@ def test_gnome_plugin():
|
|||||||
obj.duration = 0
|
obj.duration = 0
|
||||||
|
|
||||||
# Check that it found our mocked environments
|
# Check that it found our mocked environments
|
||||||
assert(obj._enabled is True)
|
assert obj._enabled is True
|
||||||
|
|
||||||
# Test url() call
|
# Test url() call
|
||||||
assert(isinstance(obj.url(), six.string_types) is True)
|
assert isinstance(obj.url(), six.string_types) is True
|
||||||
|
|
||||||
# test notifications
|
# test notifications
|
||||||
assert(obj.notify(title='title', body='body',
|
assert obj.notify(title='title', body='body',
|
||||||
notify_type=apprise.NotifyType.INFO) is True)
|
notify_type=apprise.NotifyType.INFO) is True
|
||||||
|
|
||||||
# test notification without a title
|
# test notification without a title
|
||||||
assert(obj.notify(title='', body='body',
|
assert obj.notify(title='', body='body',
|
||||||
notify_type=apprise.NotifyType.INFO) is True)
|
notify_type=apprise.NotifyType.INFO) is True
|
||||||
|
|
||||||
|
obj = apprise.Apprise.instantiate(
|
||||||
|
'gnome://_/?image=True', suppress_exceptions=False)
|
||||||
|
assert isinstance(obj, apprise.plugins.NotifyGnome) is True
|
||||||
|
assert obj.notify(title='title', body='body',
|
||||||
|
notify_type=apprise.NotifyType.INFO) is True
|
||||||
|
|
||||||
|
obj = apprise.Apprise.instantiate(
|
||||||
|
'gnome://_/?image=False', suppress_exceptions=False)
|
||||||
|
assert isinstance(obj, apprise.plugins.NotifyGnome) is True
|
||||||
|
assert obj.notify(title='title', body='body',
|
||||||
|
notify_type=apprise.NotifyType.INFO) is True
|
||||||
|
|
||||||
|
# Test Priority (alias of urgency)
|
||||||
|
obj = apprise.Apprise.instantiate(
|
||||||
|
'gnome://_/?priority=invalid', suppress_exceptions=False)
|
||||||
|
assert isinstance(obj, apprise.plugins.NotifyGnome) is True
|
||||||
|
assert obj.urgency == 1
|
||||||
|
assert obj.notify(title='title', body='body',
|
||||||
|
notify_type=apprise.NotifyType.INFO) is True
|
||||||
|
|
||||||
|
obj = apprise.Apprise.instantiate(
|
||||||
|
'gnome://_/?priority=high', suppress_exceptions=False)
|
||||||
|
assert isinstance(obj, apprise.plugins.NotifyGnome) is True
|
||||||
|
assert obj.urgency == 2
|
||||||
|
assert obj.notify(title='title', body='body',
|
||||||
|
notify_type=apprise.NotifyType.INFO) is True
|
||||||
|
|
||||||
|
obj = apprise.Apprise.instantiate(
|
||||||
|
'gnome://_/?priority=2', suppress_exceptions=False)
|
||||||
|
assert isinstance(obj, apprise.plugins.NotifyGnome) is True
|
||||||
|
assert obj.urgency == 2
|
||||||
|
assert obj.notify(title='title', body='body',
|
||||||
|
notify_type=apprise.NotifyType.INFO) is True
|
||||||
|
|
||||||
|
# Test Urgeny
|
||||||
|
obj = apprise.Apprise.instantiate(
|
||||||
|
'gnome://_/?urgency=invalid', suppress_exceptions=False)
|
||||||
|
assert obj.urgency == 1
|
||||||
|
assert isinstance(obj, apprise.plugins.NotifyGnome) is True
|
||||||
|
assert obj.notify(title='title', body='body',
|
||||||
|
notify_type=apprise.NotifyType.INFO) is True
|
||||||
|
|
||||||
|
obj = apprise.Apprise.instantiate(
|
||||||
|
'gnome://_/?urgency=high', suppress_exceptions=False)
|
||||||
|
assert obj.urgency == 2
|
||||||
|
assert isinstance(obj, apprise.plugins.NotifyGnome) is True
|
||||||
|
assert obj.notify(title='title', body='body',
|
||||||
|
notify_type=apprise.NotifyType.INFO) is True
|
||||||
|
|
||||||
|
obj = apprise.Apprise.instantiate(
|
||||||
|
'gnome://_/?urgency=2', suppress_exceptions=False)
|
||||||
|
assert isinstance(obj, apprise.plugins.NotifyGnome) is True
|
||||||
|
assert obj.urgency == 2
|
||||||
|
assert obj.notify(title='title', body='body',
|
||||||
|
notify_type=apprise.NotifyType.INFO) is True
|
||||||
|
|
||||||
# Test our loading of our icon exception; it will still allow the
|
# Test our loading of our icon exception; it will still allow the
|
||||||
# notification to be sent
|
# notification to be sent
|
||||||
mock_pixbuf.new_from_file.side_effect = AttributeError()
|
mock_pixbuf.new_from_file.side_effect = AttributeError()
|
||||||
assert(obj.notify(title='title', body='body',
|
assert obj.notify(title='title', body='body',
|
||||||
notify_type=apprise.NotifyType.INFO) is True)
|
notify_type=apprise.NotifyType.INFO) is True
|
||||||
# Undo our change
|
# Undo our change
|
||||||
mock_pixbuf.new_from_file.side_effect = None
|
mock_pixbuf.new_from_file.side_effect = None
|
||||||
|
|
||||||
@ -142,8 +198,8 @@ def test_gnome_plugin():
|
|||||||
.Notification.new.return_value = None
|
.Notification.new.return_value = None
|
||||||
sys.modules['gi.repository.Notify']\
|
sys.modules['gi.repository.Notify']\
|
||||||
.Notification.new.side_effect = AttributeError()
|
.Notification.new.side_effect = AttributeError()
|
||||||
assert(obj.notify(title='title', body='body',
|
assert obj.notify(title='title', body='body',
|
||||||
notify_type=apprise.NotifyType.INFO) is False)
|
notify_type=apprise.NotifyType.INFO) is False
|
||||||
|
|
||||||
# Undo our change
|
# Undo our change
|
||||||
sys.modules['gi.repository.Notify']\
|
sys.modules['gi.repository.Notify']\
|
||||||
@ -152,11 +208,11 @@ def test_gnome_plugin():
|
|||||||
# Toggle our testing for when we can't send notifications because the
|
# Toggle our testing for when we can't send notifications because the
|
||||||
# package has been made unavailable to us
|
# package has been made unavailable to us
|
||||||
obj._enabled = False
|
obj._enabled = False
|
||||||
assert(obj.notify(title='title', body='body',
|
assert obj.notify(title='title', body='body',
|
||||||
notify_type=apprise.NotifyType.INFO) is False)
|
notify_type=apprise.NotifyType.INFO) is False
|
||||||
|
|
||||||
# Test the setting of a the urgency
|
# Test the setting of a the urgency (through priority keyword)
|
||||||
apprise.plugins.NotifyGnome(urgency=0)
|
apprise.plugins.NotifyGnome(priority=0)
|
||||||
|
|
||||||
# Verify this all works in the event a ValueError is also thronw
|
# Verify this all works in the event a ValueError is also thronw
|
||||||
# out of the call to gi.require_version()
|
# out of the call to gi.require_version()
|
||||||
@ -178,10 +234,10 @@ def test_gnome_plugin():
|
|||||||
|
|
||||||
# Create our instance
|
# Create our instance
|
||||||
obj = apprise.Apprise.instantiate('gnome://', suppress_exceptions=False)
|
obj = apprise.Apprise.instantiate('gnome://', suppress_exceptions=False)
|
||||||
assert(isinstance(obj, apprise.plugins.NotifyGnome) is True)
|
assert isinstance(obj, apprise.plugins.NotifyGnome) is True
|
||||||
obj.duration = 0
|
obj.duration = 0
|
||||||
|
|
||||||
# Our notifications can not work without our gi library having been
|
# Our notifications can not work without our gi library having been
|
||||||
# loaded.
|
# loaded.
|
||||||
assert(obj.notify(title='title', body='body',
|
assert obj.notify(title='title', body='body',
|
||||||
notify_type=apprise.NotifyType.INFO) is False)
|
notify_type=apprise.NotifyType.INFO) is False
|
||||||
|
@ -61,25 +61,25 @@ def test_notify_matrix_plugin_general(mock_post, mock_get):
|
|||||||
mock_post.return_value = request
|
mock_post.return_value = request
|
||||||
|
|
||||||
# Variation Initializations
|
# Variation Initializations
|
||||||
obj = plugins.NotifyMatrix(rooms='#abcd')
|
obj = plugins.NotifyMatrix(targets='#abcd')
|
||||||
assert isinstance(obj, plugins.NotifyMatrix) is True
|
assert isinstance(obj, plugins.NotifyMatrix) is True
|
||||||
assert isinstance(obj.url(), six.string_types) is True
|
assert isinstance(obj.url(), six.string_types) is True
|
||||||
# Registration successful
|
# Registration successful
|
||||||
assert obj.send(body="test") is True
|
assert obj.send(body="test") is True
|
||||||
|
|
||||||
obj = plugins.NotifyMatrix(user='user', rooms='#abcd')
|
obj = plugins.NotifyMatrix(user='user', targets='#abcd')
|
||||||
assert isinstance(obj, plugins.NotifyMatrix) is True
|
assert isinstance(obj, plugins.NotifyMatrix) is True
|
||||||
assert isinstance(obj.url(), six.string_types) is True
|
assert isinstance(obj.url(), six.string_types) is True
|
||||||
# Registration successful
|
# Registration successful
|
||||||
assert obj.send(body="test") is True
|
assert obj.send(body="test") is True
|
||||||
|
|
||||||
obj = plugins.NotifyMatrix(password='passwd', rooms='#abcd')
|
obj = plugins.NotifyMatrix(password='passwd', targets='#abcd')
|
||||||
assert isinstance(obj, plugins.NotifyMatrix) is True
|
assert isinstance(obj, plugins.NotifyMatrix) is True
|
||||||
assert isinstance(obj.url(), six.string_types) is True
|
assert isinstance(obj.url(), six.string_types) is True
|
||||||
# A username gets automatically generated in these cases
|
# A username gets automatically generated in these cases
|
||||||
assert obj.send(body="test") is True
|
assert obj.send(body="test") is True
|
||||||
|
|
||||||
obj = plugins.NotifyMatrix(user='user', password='passwd', rooms='#abcd')
|
obj = plugins.NotifyMatrix(user='user', password='passwd', targets='#abcd')
|
||||||
assert isinstance(obj.url(), six.string_types) is True
|
assert isinstance(obj.url(), six.string_types) is True
|
||||||
assert isinstance(obj, plugins.NotifyMatrix) is True
|
assert isinstance(obj, plugins.NotifyMatrix) is True
|
||||||
# Registration Successful
|
# Registration Successful
|
||||||
@ -94,17 +94,17 @@ def test_notify_matrix_plugin_general(mock_post, mock_get):
|
|||||||
# Fails because we couldn't register because of 404 errors
|
# Fails because we couldn't register because of 404 errors
|
||||||
assert obj.send(body="test") is False
|
assert obj.send(body="test") is False
|
||||||
|
|
||||||
obj = plugins.NotifyMatrix(user='test', rooms='#abcd')
|
obj = plugins.NotifyMatrix(user='test', targets='#abcd')
|
||||||
assert isinstance(obj, plugins.NotifyMatrix) is True
|
assert isinstance(obj, plugins.NotifyMatrix) is True
|
||||||
# Fails because we still couldn't register
|
# Fails because we still couldn't register
|
||||||
assert obj.send(user='test', password='passwd', body="test") is False
|
assert obj.send(user='test', password='passwd', body="test") is False
|
||||||
|
|
||||||
obj = plugins.NotifyMatrix(user='test', password='passwd', rooms='#abcd')
|
obj = plugins.NotifyMatrix(user='test', password='passwd', targets='#abcd')
|
||||||
assert isinstance(obj, plugins.NotifyMatrix) is True
|
assert isinstance(obj, plugins.NotifyMatrix) is True
|
||||||
# Fails because we still couldn't register
|
# Fails because we still couldn't register
|
||||||
assert obj.send(body="test") is False
|
assert obj.send(body="test") is False
|
||||||
|
|
||||||
obj = plugins.NotifyMatrix(password='passwd', rooms='#abcd')
|
obj = plugins.NotifyMatrix(password='passwd', targets='#abcd')
|
||||||
# Fails because we still couldn't register
|
# Fails because we still couldn't register
|
||||||
assert isinstance(obj, plugins.NotifyMatrix) is True
|
assert isinstance(obj, plugins.NotifyMatrix) is True
|
||||||
assert obj.send(body="test") is False
|
assert obj.send(body="test") is False
|
||||||
@ -132,7 +132,7 @@ def test_notify_matrix_plugin_general(mock_post, mock_get):
|
|||||||
request.content = dumps(response_obj)
|
request.content = dumps(response_obj)
|
||||||
request.status_code = requests.codes.ok
|
request.status_code = requests.codes.ok
|
||||||
|
|
||||||
obj = plugins.NotifyMatrix(rooms=None)
|
obj = plugins.NotifyMatrix(targets=None)
|
||||||
assert isinstance(obj, plugins.NotifyMatrix) is True
|
assert isinstance(obj, plugins.NotifyMatrix) is True
|
||||||
|
|
||||||
# Force a empty joined list response
|
# Force a empty joined list response
|
||||||
@ -191,7 +191,8 @@ def test_notify_matrix_plugin_fetch(mock_post, mock_get):
|
|||||||
mock_get.side_effect = fetch_failed
|
mock_get.side_effect = fetch_failed
|
||||||
mock_post.side_effect = fetch_failed
|
mock_post.side_effect = fetch_failed
|
||||||
|
|
||||||
obj = plugins.NotifyMatrix(user='user', password='passwd', thumbnail=True)
|
obj = plugins.NotifyMatrix(
|
||||||
|
user='user', password='passwd', include_image=True)
|
||||||
assert isinstance(obj, plugins.NotifyMatrix) is True
|
assert isinstance(obj, plugins.NotifyMatrix) is True
|
||||||
# We would hve failed to send our image notification
|
# We would hve failed to send our image notification
|
||||||
assert obj.send(user='test', password='passwd', body="test") is False
|
assert obj.send(user='test', password='passwd', body="test") is False
|
||||||
@ -518,3 +519,23 @@ def test_notify_matrix_plugin_rooms(mock_post, mock_get):
|
|||||||
request.status_code = 403
|
request.status_code = 403
|
||||||
obj._room_cache = {}
|
obj._room_cache = {}
|
||||||
assert obj._room_id('#abc123:localhost') is None
|
assert obj._room_id('#abc123:localhost') is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_notify_matrix_url_parsing():
|
||||||
|
"""
|
||||||
|
API: NotifyMatrix() URL Testing
|
||||||
|
|
||||||
|
"""
|
||||||
|
result = plugins.NotifyMatrix.parse_url(
|
||||||
|
'matrix://user:token@localhost?to=#room')
|
||||||
|
assert isinstance(result, dict) is True
|
||||||
|
assert len(result['targets']) == 1
|
||||||
|
assert '#room' in result['targets']
|
||||||
|
|
||||||
|
result = plugins.NotifyMatrix.parse_url(
|
||||||
|
'matrix://user:token@localhost?to=#room1,#room2,#room3')
|
||||||
|
assert isinstance(result, dict) is True
|
||||||
|
assert len(result['targets']) == 3
|
||||||
|
assert '#room1' in result['targets']
|
||||||
|
assert '#room2' in result['targets']
|
||||||
|
assert '#room3' in result['targets']
|
||||||
|
@ -194,13 +194,54 @@ def test_notify_base():
|
|||||||
"<content>'\t \n</content>", convert_new_lines=True) == \
|
"<content>'\t \n</content>", convert_new_lines=True) == \
|
||||||
'<content>'  <br/></content>'
|
'<content>'  <br/></content>'
|
||||||
|
|
||||||
|
# Test invalid data
|
||||||
|
assert NotifyBase.split_path(None) == []
|
||||||
|
assert NotifyBase.split_path(object()) == []
|
||||||
|
assert NotifyBase.split_path(42) == []
|
||||||
|
|
||||||
assert NotifyBase.split_path(
|
assert NotifyBase.split_path(
|
||||||
'/path/?name=Dr%20Disrespect', unquote=False) == \
|
'/path/?name=Dr%20Disrespect', unquote=False) == \
|
||||||
['path', '?name=Dr%20Disrespect']
|
['path', '?name=Dr%20Disrespect']
|
||||||
|
|
||||||
assert NotifyBase.split_path(
|
assert NotifyBase.split_path(
|
||||||
'/path/?name=Dr%20Disrespect', unquote=True) == \
|
'/path/?name=Dr%20Disrespect', unquote=True) == \
|
||||||
['path', '?name=Dr', 'Disrespect']
|
['path', '?name=Dr Disrespect']
|
||||||
|
|
||||||
|
# a slash found inside the path, if escaped properly will not be broken
|
||||||
|
# by split_path while additional concatinated slashes are ignored
|
||||||
|
# FYI: %2F = /
|
||||||
|
assert NotifyBase.split_path(
|
||||||
|
'/%2F///%2F%2F////%2F%2F%2F////', unquote=True) == \
|
||||||
|
['/', '//', '///']
|
||||||
|
|
||||||
|
# Test invalid data
|
||||||
|
assert NotifyBase.parse_list(None) == []
|
||||||
|
assert NotifyBase.parse_list(42) == ['42', ]
|
||||||
|
|
||||||
|
result = NotifyBase.parse_list(
|
||||||
|
',path,?name=Dr%20Disrespect', unquote=False)
|
||||||
|
assert isinstance(result, list) is True
|
||||||
|
assert len(result) == 2
|
||||||
|
assert 'path' in result
|
||||||
|
assert '?name=Dr%20Disrespect' in result
|
||||||
|
|
||||||
|
result = NotifyBase.parse_list(',path,?name=Dr%20Disrespect', unquote=True)
|
||||||
|
assert isinstance(result, list) is True
|
||||||
|
assert len(result) == 2
|
||||||
|
assert 'path' in result
|
||||||
|
assert '?name=Dr Disrespect' in result
|
||||||
|
|
||||||
|
# by parse_list while additional concatinated slashes are ignored
|
||||||
|
# FYI: %2F = /
|
||||||
|
# In this lit there are actually 4 entries, however parse_list
|
||||||
|
# eliminates duplicates in addition to unquoting content by default
|
||||||
|
result = NotifyBase.parse_list(
|
||||||
|
',%2F,%2F%2F, , , ,%2F%2F%2F, %2F', unquote=True)
|
||||||
|
assert isinstance(result, list) is True
|
||||||
|
assert len(result) == 3
|
||||||
|
assert '/' in result
|
||||||
|
assert '//' in result
|
||||||
|
assert '///' in result
|
||||||
|
|
||||||
# Give nothing, get nothing
|
# Give nothing, get nothing
|
||||||
assert NotifyBase.escape_html("") == ""
|
assert NotifyBase.escape_html("") == ""
|
||||||
|
@ -45,9 +45,12 @@ TEST_URLS = (
|
|||||||
('pjets://', {
|
('pjets://', {
|
||||||
'instance': None,
|
'instance': None,
|
||||||
}),
|
}),
|
||||||
|
('pjet://:@/', {
|
||||||
|
'instance': None,
|
||||||
|
}),
|
||||||
# You must specify a username
|
# You must specify a username
|
||||||
('pjet://%s' % ('a' * 32), {
|
('pjet://%s' % ('a' * 32), {
|
||||||
'instance': None,
|
'instance': TypeError,
|
||||||
}),
|
}),
|
||||||
# Specify your own server
|
# Specify your own server
|
||||||
('pjet://%s@localhost' % ('a' * 32), {
|
('pjet://%s@localhost' % ('a' * 32), {
|
||||||
@ -57,9 +60,6 @@ TEST_URLS = (
|
|||||||
('pjets://%s@localhost:8080' % ('a' * 32), {
|
('pjets://%s@localhost:8080' % ('a' * 32), {
|
||||||
'instance': plugins.NotifyPushjet,
|
'instance': plugins.NotifyPushjet,
|
||||||
}),
|
}),
|
||||||
('pjet://:@/', {
|
|
||||||
'instance': None,
|
|
||||||
}),
|
|
||||||
('pjet://%s@localhost:8081' % ('a' * 32), {
|
('pjet://%s@localhost:8081' % ('a' * 32), {
|
||||||
'instance': plugins.NotifyPushjet,
|
'instance': plugins.NotifyPushjet,
|
||||||
# Throws a series of connection and transfer exceptions when this flag
|
# Throws a series of connection and transfer exceptions when this flag
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -51,7 +51,7 @@ def test_object_initialization():
|
|||||||
access_key_id=None,
|
access_key_id=None,
|
||||||
secret_access_key=TEST_ACCESS_KEY_SECRET,
|
secret_access_key=TEST_ACCESS_KEY_SECRET,
|
||||||
region_name=TEST_REGION,
|
region_name=TEST_REGION,
|
||||||
recipients='+1800555999',
|
targets='+1800555999',
|
||||||
)
|
)
|
||||||
# The entries above are invalid, our code should never reach here
|
# The entries above are invalid, our code should never reach here
|
||||||
assert(False)
|
assert(False)
|
||||||
@ -66,7 +66,7 @@ def test_object_initialization():
|
|||||||
access_key_id=TEST_ACCESS_KEY_ID,
|
access_key_id=TEST_ACCESS_KEY_ID,
|
||||||
secret_access_key=None,
|
secret_access_key=None,
|
||||||
region_name=TEST_REGION,
|
region_name=TEST_REGION,
|
||||||
recipients='+1800555999',
|
targets='+1800555999',
|
||||||
)
|
)
|
||||||
# The entries above are invalid, our code should never reach here
|
# The entries above are invalid, our code should never reach here
|
||||||
assert(False)
|
assert(False)
|
||||||
@ -81,7 +81,7 @@ def test_object_initialization():
|
|||||||
access_key_id=TEST_ACCESS_KEY_ID,
|
access_key_id=TEST_ACCESS_KEY_ID,
|
||||||
secret_access_key=TEST_ACCESS_KEY_SECRET,
|
secret_access_key=TEST_ACCESS_KEY_SECRET,
|
||||||
region_name=None,
|
region_name=None,
|
||||||
recipients='+1800555999',
|
targets='+1800555999',
|
||||||
)
|
)
|
||||||
# The entries above are invalid, our code should never reach here
|
# The entries above are invalid, our code should never reach here
|
||||||
assert(False)
|
assert(False)
|
||||||
@ -96,7 +96,7 @@ def test_object_initialization():
|
|||||||
access_key_id=TEST_ACCESS_KEY_ID,
|
access_key_id=TEST_ACCESS_KEY_ID,
|
||||||
secret_access_key=TEST_ACCESS_KEY_SECRET,
|
secret_access_key=TEST_ACCESS_KEY_SECRET,
|
||||||
region_name=TEST_REGION,
|
region_name=TEST_REGION,
|
||||||
recipients=None,
|
targets=None,
|
||||||
)
|
)
|
||||||
# Still valid even without recipients
|
# Still valid even without recipients
|
||||||
assert(True)
|
assert(True)
|
||||||
@ -111,7 +111,7 @@ def test_object_initialization():
|
|||||||
access_key_id=TEST_ACCESS_KEY_ID,
|
access_key_id=TEST_ACCESS_KEY_ID,
|
||||||
secret_access_key=TEST_ACCESS_KEY_SECRET,
|
secret_access_key=TEST_ACCESS_KEY_SECRET,
|
||||||
region_name=TEST_REGION,
|
region_name=TEST_REGION,
|
||||||
recipients=object(),
|
targets=object(),
|
||||||
)
|
)
|
||||||
# Still valid even without recipients
|
# Still valid even without recipients
|
||||||
assert(True)
|
assert(True)
|
||||||
@ -127,7 +127,7 @@ def test_object_initialization():
|
|||||||
access_key_id=TEST_ACCESS_KEY_ID,
|
access_key_id=TEST_ACCESS_KEY_ID,
|
||||||
secret_access_key=TEST_ACCESS_KEY_SECRET,
|
secret_access_key=TEST_ACCESS_KEY_SECRET,
|
||||||
region_name=TEST_REGION,
|
region_name=TEST_REGION,
|
||||||
recipients='+1809',
|
targets='+1809',
|
||||||
)
|
)
|
||||||
# The recipient is invalid, but it's still okay; this Notification
|
# The recipient is invalid, but it's still okay; this Notification
|
||||||
# still becomes pretty much useless at this point though
|
# still becomes pretty much useless at this point though
|
||||||
@ -144,7 +144,7 @@ def test_object_initialization():
|
|||||||
access_key_id=TEST_ACCESS_KEY_ID,
|
access_key_id=TEST_ACCESS_KEY_ID,
|
||||||
secret_access_key=TEST_ACCESS_KEY_SECRET,
|
secret_access_key=TEST_ACCESS_KEY_SECRET,
|
||||||
region_name=TEST_REGION,
|
region_name=TEST_REGION,
|
||||||
recipients='#(invalid-topic-because-of-the-brackets)',
|
targets='#(invalid-topic-because-of-the-brackets)',
|
||||||
)
|
)
|
||||||
# The recipient is invalid, but it's still okay; this Notification
|
# The recipient is invalid, but it's still okay; this Notification
|
||||||
# still becomes pretty much useless at this point though
|
# still becomes pretty much useless at this point though
|
||||||
@ -169,7 +169,7 @@ def test_url_parsing():
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Confirm that there were no recipients found
|
# Confirm that there were no recipients found
|
||||||
assert(len(results['recipients']) == 0)
|
assert(len(results['targets']) == 0)
|
||||||
assert('region_name' in results)
|
assert('region_name' in results)
|
||||||
assert(TEST_REGION == results['region_name'])
|
assert(TEST_REGION == results['region_name'])
|
||||||
assert('access_key_id' in results)
|
assert('access_key_id' in results)
|
||||||
@ -188,9 +188,9 @@ def test_url_parsing():
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Confirm that our recipients were found
|
# Confirm that our recipients were found
|
||||||
assert(len(results['recipients']) == 2)
|
assert(len(results['targets']) == 2)
|
||||||
assert('+18001234567' in results['recipients'])
|
assert('+18001234567' in results['targets'])
|
||||||
assert('MyTopic' in results['recipients'])
|
assert('MyTopic' in results['targets'])
|
||||||
assert('region_name' in results)
|
assert('region_name' in results)
|
||||||
assert(TEST_REGION == results['region_name'])
|
assert(TEST_REGION == results['region_name'])
|
||||||
assert('access_key_id' in results)
|
assert('access_key_id' in results)
|
||||||
|
@ -23,10 +23,16 @@
|
|||||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
# THE SOFTWARE.
|
# THE SOFTWARE.
|
||||||
|
|
||||||
|
import six
|
||||||
|
import mock
|
||||||
|
from random import choice
|
||||||
|
from string import ascii_uppercase as str_alpha
|
||||||
|
from string import digits as str_num
|
||||||
|
|
||||||
from apprise import plugins
|
from apprise import plugins
|
||||||
from apprise import NotifyType
|
from apprise import NotifyType
|
||||||
from apprise import Apprise
|
from apprise import Apprise
|
||||||
import mock
|
from apprise import OverflowMode
|
||||||
|
|
||||||
# Disable logging for a cleaner testing output
|
# Disable logging for a cleaner testing output
|
||||||
import logging
|
import logging
|
||||||
@ -40,6 +46,9 @@ TEST_URLS = (
|
|||||||
('tweet://', {
|
('tweet://', {
|
||||||
'instance': None,
|
'instance': None,
|
||||||
}),
|
}),
|
||||||
|
('tweet://:@/', {
|
||||||
|
'instance': None,
|
||||||
|
}),
|
||||||
('tweet://consumer_key', {
|
('tweet://consumer_key', {
|
||||||
# Missing Keys
|
# Missing Keys
|
||||||
'instance': TypeError,
|
'instance': TypeError,
|
||||||
@ -60,9 +69,11 @@ TEST_URLS = (
|
|||||||
# We're good!
|
# We're good!
|
||||||
'instance': plugins.NotifyTwitter,
|
'instance': plugins.NotifyTwitter,
|
||||||
}),
|
}),
|
||||||
('tweet://:@/', {
|
('tweet://usera@consumer_key/consumer_key/access_token/'
|
||||||
'instance': None,
|
'access_secret/?to=userb', {
|
||||||
}),
|
# We're good!
|
||||||
|
'instance': plugins.NotifyTwitter,
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -73,6 +84,22 @@ def test_plugin(mock_oauth, mock_api):
|
|||||||
API: NotifyTwitter Plugin() (pt1)
|
API: NotifyTwitter Plugin() (pt1)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
# Disable Throttling to speed testing
|
||||||
|
plugins.NotifyBase.NotifyBase.request_rate_per_sec = 0
|
||||||
|
|
||||||
|
# Define how many characters exist per line
|
||||||
|
row = 80
|
||||||
|
|
||||||
|
# Some variables we use to control the data we work with
|
||||||
|
body_len = 1024
|
||||||
|
title_len = 1024
|
||||||
|
|
||||||
|
# Create a large body and title with random data
|
||||||
|
body = ''.join(choice(str_alpha + str_num + ' ') for _ in range(body_len))
|
||||||
|
body = '\r\n'.join([body[i: i + row] for i in range(0, len(body), row)])
|
||||||
|
|
||||||
|
# Create our title using random data
|
||||||
|
title = ''.join(choice(str_alpha + str_num) for _ in range(title_len))
|
||||||
|
|
||||||
# iterate over our dictionary and test it out
|
# iterate over our dictionary and test it out
|
||||||
for (url, meta) in TEST_URLS:
|
for (url, meta) in TEST_URLS:
|
||||||
@ -86,6 +113,9 @@ def test_plugin(mock_oauth, mock_api):
|
|||||||
# Our expected Query response (True, False, or exception type)
|
# Our expected Query response (True, False, or exception type)
|
||||||
response = meta.get('response', True)
|
response = meta.get('response', True)
|
||||||
|
|
||||||
|
# Allow notification type override, otherwise default to INFO
|
||||||
|
notify_type = meta.get('notify_type', NotifyType.INFO)
|
||||||
|
|
||||||
# Allow us to force the server response code to be something other then
|
# Allow us to force the server response code to be something other then
|
||||||
# the defaults
|
# the defaults
|
||||||
response = meta.get(
|
response = meta.get(
|
||||||
@ -94,25 +124,69 @@ def test_plugin(mock_oauth, mock_api):
|
|||||||
try:
|
try:
|
||||||
obj = Apprise.instantiate(url, suppress_exceptions=False)
|
obj = Apprise.instantiate(url, suppress_exceptions=False)
|
||||||
|
|
||||||
if instance is None:
|
if obj is None:
|
||||||
# Check that we got what we came for
|
if instance is not None:
|
||||||
assert obj is instance
|
# We're done (assuming this is what we were expecting)
|
||||||
|
print("{} didn't instantiate itself "
|
||||||
|
"(we expected it to)".format(url))
|
||||||
|
assert False
|
||||||
continue
|
continue
|
||||||
|
|
||||||
assert(isinstance(obj, instance))
|
if instance is None:
|
||||||
|
# Expected None but didn't get it
|
||||||
|
print('%s instantiated %s (but expected None)' % (
|
||||||
|
url, str(obj)))
|
||||||
|
assert False
|
||||||
|
|
||||||
|
assert isinstance(obj, instance) is True
|
||||||
|
|
||||||
|
if isinstance(obj, plugins.NotifyBase.NotifyBase):
|
||||||
|
# We loaded okay; now lets make sure we can reverse this url
|
||||||
|
assert isinstance(obj.url(), six.string_types) is True
|
||||||
|
|
||||||
|
# Instantiate the exact same object again using the URL from
|
||||||
|
# the one that was already created properly
|
||||||
|
obj_cmp = Apprise.instantiate(obj.url())
|
||||||
|
|
||||||
|
# Our object should be the same instance as what we had
|
||||||
|
# originally expected above.
|
||||||
|
if not isinstance(obj_cmp, plugins.NotifyBase.NotifyBase):
|
||||||
|
# Assert messages are hard to trace back with the way
|
||||||
|
# these tests work. Just printing before throwing our
|
||||||
|
# assertion failure makes things easier to debug later on
|
||||||
|
print('TEST FAIL: {} regenerated as {}'.format(
|
||||||
|
url, obj.url()))
|
||||||
|
assert False
|
||||||
|
|
||||||
if self:
|
if self:
|
||||||
# Iterate over our expected entries inside of our object
|
# Iterate over our expected entries inside of our object
|
||||||
for key, val in self.items():
|
for key, val in self.items():
|
||||||
# Test that our object has the desired key
|
# Test that our object has the desired key
|
||||||
assert(hasattr(key, obj))
|
assert hasattr(key, obj) is True
|
||||||
assert(getattr(key, obj) == val)
|
assert getattr(key, obj) == val
|
||||||
|
|
||||||
|
obj.request_rate_per_sec = 0
|
||||||
|
|
||||||
# check that we're as expected
|
# check that we're as expected
|
||||||
assert obj.notify(
|
assert obj.notify(
|
||||||
title='test', body='body',
|
title='test', body='body',
|
||||||
notify_type=NotifyType.INFO) == response
|
notify_type=NotifyType.INFO) == response
|
||||||
|
|
||||||
|
# check that this doesn't change using different overflow
|
||||||
|
# methods
|
||||||
|
assert obj.notify(
|
||||||
|
body=body, title=title,
|
||||||
|
notify_type=notify_type,
|
||||||
|
overflow=OverflowMode.UPSTREAM) == response
|
||||||
|
assert obj.notify(
|
||||||
|
body=body, title=title,
|
||||||
|
notify_type=notify_type,
|
||||||
|
overflow=OverflowMode.TRUNCATE) == response
|
||||||
|
assert obj.notify(
|
||||||
|
body=body, title=title,
|
||||||
|
notify_type=notify_type,
|
||||||
|
overflow=OverflowMode.SPLIT) == response
|
||||||
|
|
||||||
except AssertionError:
|
except AssertionError:
|
||||||
# Don't mess with these entries
|
# Don't mess with these entries
|
||||||
raise
|
raise
|
||||||
|
@ -125,6 +125,43 @@ def test_windows_plugin():
|
|||||||
assert(obj.notify(title='title', body='body',
|
assert(obj.notify(title='title', body='body',
|
||||||
notify_type=apprise.NotifyType.INFO) is True)
|
notify_type=apprise.NotifyType.INFO) is True)
|
||||||
|
|
||||||
|
obj = apprise.Apprise.instantiate(
|
||||||
|
'windows://_/?image=True', suppress_exceptions=False)
|
||||||
|
obj.duration = 0
|
||||||
|
assert(isinstance(obj.url(), six.string_types) is True)
|
||||||
|
assert(obj.notify(title='title', body='body',
|
||||||
|
notify_type=apprise.NotifyType.INFO) is True)
|
||||||
|
|
||||||
|
obj = apprise.Apprise.instantiate(
|
||||||
|
'windows://_/?image=False', suppress_exceptions=False)
|
||||||
|
obj.duration = 0
|
||||||
|
assert(isinstance(obj.url(), six.string_types) is True)
|
||||||
|
assert(obj.notify(title='title', body='body',
|
||||||
|
notify_type=apprise.NotifyType.INFO) is True)
|
||||||
|
|
||||||
|
obj = apprise.Apprise.instantiate(
|
||||||
|
'windows://_/?duration=1', suppress_exceptions=False)
|
||||||
|
assert(isinstance(obj.url(), six.string_types) is True)
|
||||||
|
assert(obj.notify(title='title', body='body',
|
||||||
|
notify_type=apprise.NotifyType.INFO) is True)
|
||||||
|
# loads okay
|
||||||
|
assert obj.duration == 1
|
||||||
|
|
||||||
|
obj = apprise.Apprise.instantiate(
|
||||||
|
'windows://_/?duration=invalid', suppress_exceptions=False)
|
||||||
|
# Falls back to default
|
||||||
|
assert obj.duration == obj.default_popup_duration_sec
|
||||||
|
|
||||||
|
obj = apprise.Apprise.instantiate(
|
||||||
|
'windows://_/?duration=-1', suppress_exceptions=False)
|
||||||
|
# Falls back to default
|
||||||
|
assert obj.duration == obj.default_popup_duration_sec
|
||||||
|
|
||||||
|
obj = apprise.Apprise.instantiate(
|
||||||
|
'windows://_/?duration=0', suppress_exceptions=False)
|
||||||
|
# Falls back to default
|
||||||
|
assert obj.duration == obj.default_popup_duration_sec
|
||||||
|
|
||||||
# Test our loading of our icon exception; it will still allow the
|
# Test our loading of our icon exception; it will still allow the
|
||||||
# notification to be sent
|
# notification to be sent
|
||||||
win32gui.LoadImage.side_effect = AttributeError
|
win32gui.LoadImage.side_effect = AttributeError
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
import six
|
import six
|
||||||
import mock
|
import mock
|
||||||
import sys
|
import sys
|
||||||
# import types
|
import ssl
|
||||||
|
|
||||||
import apprise
|
import apprise
|
||||||
|
|
||||||
@ -129,6 +129,39 @@ def test_xmpp_plugin(tmpdir):
|
|||||||
# Not possible because no password was specified
|
# Not possible because no password was specified
|
||||||
assert obj is None
|
assert obj is None
|
||||||
|
|
||||||
|
# SSL Flags
|
||||||
|
if hasattr(ssl, "PROTOCOL_TLS"):
|
||||||
|
# Test cases where PROTOCOL_TLS simply isn't available
|
||||||
|
ssl_temp_swap = ssl.PROTOCOL_TLS
|
||||||
|
del ssl.PROTOCOL_TLS
|
||||||
|
|
||||||
|
# Test our URL
|
||||||
|
url = 'xmpps://user:pass@example.com'
|
||||||
|
obj = apprise.Apprise.instantiate(url, suppress_exceptions=False)
|
||||||
|
# Test we loaded
|
||||||
|
assert isinstance(obj, apprise.plugins.NotifyXMPP) is True
|
||||||
|
assert obj.notify(
|
||||||
|
title='title', body='body',
|
||||||
|
notify_type=apprise.NotifyType.INFO) is True
|
||||||
|
|
||||||
|
# Restore the variable for remaining tests
|
||||||
|
setattr(ssl, 'PROTOCOL_TLS', ssl_temp_swap)
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Handle case where it is not missing
|
||||||
|
setattr(ssl, 'PROTOCOL_TLS', ssl.PROTOCOL_TLSv1)
|
||||||
|
# Test our URL
|
||||||
|
url = 'xmpps://user:pass@example.com'
|
||||||
|
obj = apprise.Apprise.instantiate(url, suppress_exceptions=False)
|
||||||
|
# Test we loaded
|
||||||
|
assert isinstance(obj, apprise.plugins.NotifyXMPP) is True
|
||||||
|
assert obj.notify(
|
||||||
|
title='title', body='body',
|
||||||
|
notify_type=apprise.NotifyType.INFO) is True
|
||||||
|
|
||||||
|
# Restore settings as they were
|
||||||
|
del ssl.PROTOCOL_TLS
|
||||||
|
|
||||||
# Try Different Variations of our URL
|
# Try Different Variations of our URL
|
||||||
for url in (
|
for url in (
|
||||||
'xmpps://user:pass@example.com',
|
'xmpps://user:pass@example.com',
|
||||||
|
Loading…
Reference in New Issue
Block a user