mirror of https://github.com/caronc/apprise
normalize plugin classes mass code cleanup
parent
c9b957c434
commit
842de28191
|
@ -106,7 +106,7 @@ class URLBase(object):
|
|||
# Secure Mode
|
||||
self.secure = kwargs.get('secure', False)
|
||||
|
||||
self.host = kwargs.get('host', '')
|
||||
self.host = URLBase.unquote(kwargs.get('host'))
|
||||
self.port = kwargs.get('port')
|
||||
if self.port:
|
||||
try:
|
||||
|
@ -116,13 +116,20 @@ class URLBase(object):
|
|||
self.port = None
|
||||
|
||||
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')
|
||||
if self.password:
|
||||
# Always unquote the pssword if it exists
|
||||
self.password = URLBase.unquote(self.password)
|
||||
|
||||
if 'tag' in kwargs:
|
||||
# We want to associate some tags with our notification service.
|
||||
# the code below gets the 'tag' argument if defined, otherwise
|
||||
# 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
|
||||
# is automatically set and controlled through the throttle() call.
|
||||
|
@ -161,7 +168,7 @@ class URLBase(object):
|
|||
elapsed = (reference - self._last_io_datetime).total_seconds()
|
||||
|
||||
if wait is not None:
|
||||
self.logger.debug('Throttling for {}s...'.format(wait))
|
||||
self.logger.debug('Throttling forced for {}s...'.format(wait))
|
||||
sleep(wait)
|
||||
|
||||
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
|
||||
"""
|
||||
|
||||
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:
|
||||
return PATHSPLIT_LIST_DELIM.split(
|
||||
URLBase.unquote(path).lstrip('/'))
|
||||
return PATHSPLIT_LIST_DELIM.split(path.lstrip('/'))
|
||||
content = \
|
||||
[URLBase.unquote(x) for x in filter(bool, content)]
|
||||
|
||||
return content
|
||||
|
||||
@property
|
||||
def app_id(self):
|
||||
|
|
|
@ -33,9 +33,6 @@ from ..common import NOTIFY_FORMATS
|
|||
from ..common import OverflowMode
|
||||
from ..common import OVERFLOW_MODES
|
||||
|
||||
# HTML New Line Delimiter
|
||||
NOTIFY_NEWLINE = '\r\n'
|
||||
|
||||
|
||||
class NotifyBase(URLBase):
|
||||
"""
|
||||
|
@ -94,12 +91,10 @@ class NotifyBase(URLBase):
|
|||
# Store the specified format if specified
|
||||
notify_format = kwargs.get('format', '')
|
||||
if notify_format.lower() not in NOTIFY_FORMATS:
|
||||
self.logger.error(
|
||||
'Invalid notification format %s' % notify_format,
|
||||
)
|
||||
raise TypeError(
|
||||
'Invalid notification format %s' % notify_format,
|
||||
)
|
||||
msg = 'Invalid notification format %s'.format(notify_format)
|
||||
self.logger.error(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
# Provide override
|
||||
self.notify_format = notify_format
|
||||
|
||||
|
@ -107,12 +102,10 @@ class NotifyBase(URLBase):
|
|||
# Store the specified format if specified
|
||||
overflow = kwargs.get('overflow', '')
|
||||
if overflow.lower() not in OVERFLOW_MODES:
|
||||
self.logger.error(
|
||||
'Invalid overflow method %s' % overflow,
|
||||
)
|
||||
raise TypeError(
|
||||
'Invalid overflow method %s' % overflow,
|
||||
)
|
||||
msg = 'Invalid overflow method {}'.format(overflow)
|
||||
self.logger.error(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
# Provide override
|
||||
self.overflow_mode = overflow
|
||||
|
||||
|
|
|
@ -38,6 +38,7 @@ except ImportError:
|
|||
from urllib.parse import urlparse
|
||||
|
||||
from .NotifyBase import NotifyBase
|
||||
from ..utils import parse_bool
|
||||
from ..common import NotifyType
|
||||
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)
|
||||
|
||||
# Device tokens are only referenced when developing.
|
||||
# it's not likely you'll send a message directly to a device, but
|
||||
# if you do; this plugin supports it.
|
||||
# It's not likely you'll send a message directly to a device, but if you do;
|
||||
# this plugin supports it.
|
||||
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
|
||||
|
@ -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_SECRET = re.compile(r'[A-Z0-9_-]{64}', re.I)
|
||||
|
||||
# Used to break apart list of potential tags by their delimiter
|
||||
# into a usable list.
|
||||
# Used to break apart list of potential tags by their delimiter into a useable
|
||||
# list.
|
||||
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
|
||||
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
|
||||
"""
|
||||
|
@ -108,66 +110,62 @@ class NotifyBoxcar(NotifyBase):
|
|||
self.access = access.strip()
|
||||
|
||||
except AttributeError:
|
||||
self.logger.warning(
|
||||
'The specified access key specified is invalid.',
|
||||
)
|
||||
raise TypeError(
|
||||
'The specified access key specified is invalid.',
|
||||
)
|
||||
msg = 'The specified access key is invalid.'
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
try:
|
||||
# Secret Key (associated with project)
|
||||
self.secret = secret.strip()
|
||||
|
||||
except AttributeError:
|
||||
self.logger.warning(
|
||||
'The specified secret key specified is invalid.',
|
||||
)
|
||||
raise TypeError(
|
||||
'The specified secret key specified is invalid.',
|
||||
)
|
||||
msg = 'The specified secret key is invalid.'
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
if not VALIDATE_ACCESS.match(self.access):
|
||||
self.logger.warning(
|
||||
'The access key specified (%s) is invalid.' % self.access,
|
||||
)
|
||||
raise TypeError(
|
||||
'The access key specified (%s) is invalid.' % self.access,
|
||||
)
|
||||
msg = 'The access key specified ({}) is invalid.'\
|
||||
.format(self.access)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
if not VALIDATE_SECRET.match(self.secret):
|
||||
self.logger.warning(
|
||||
'The secret key specified (%s) is invalid.' % self.secret,
|
||||
)
|
||||
raise TypeError(
|
||||
'The secret key specified (%s) is invalid.' % self.secret,
|
||||
)
|
||||
msg = 'The secret key specified ({}) is invalid.'\
|
||||
.format(self.secret)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
if not recipients:
|
||||
if not targets:
|
||||
self.tags.append(DEFAULT_TAG)
|
||||
recipients = []
|
||||
targets = []
|
||||
|
||||
elif isinstance(recipients, six.string_types):
|
||||
recipients = [x for x in filter(bool, TAGS_LIST_DELIM.split(
|
||||
recipients,
|
||||
elif isinstance(targets, six.string_types):
|
||||
targets = [x for x in filter(bool, TAGS_LIST_DELIM.split(
|
||||
targets,
|
||||
))]
|
||||
|
||||
# Validate recipients and drop bad ones:
|
||||
for recipient in recipients:
|
||||
if IS_TAG.match(recipient):
|
||||
# Validate targets and drop bad ones:
|
||||
for target in targets:
|
||||
if IS_TAG.match(target):
|
||||
# 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
|
||||
self.device_tokens.append(recipient)
|
||||
self.device_tokens.append(target)
|
||||
|
||||
else:
|
||||
self.logger.warning(
|
||||
'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):
|
||||
"""
|
||||
Perform Boxcar Notification
|
||||
|
@ -200,7 +198,9 @@ class NotifyBoxcar(NotifyBase):
|
|||
payload['device_tokens'] = self.device_tokens
|
||||
|
||||
# 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:
|
||||
# Set our image
|
||||
payload['@img'] = image_url
|
||||
|
@ -218,7 +218,7 @@ class NotifyBoxcar(NotifyBase):
|
|||
sha1,
|
||||
)
|
||||
|
||||
params = self.urlencode({
|
||||
params = NotifyBoxcar.urlencode({
|
||||
"publishkey": self.access,
|
||||
"signature": h.hexdigest(),
|
||||
})
|
||||
|
@ -244,7 +244,7 @@ class NotifyBoxcar(NotifyBase):
|
|||
if r.status_code != requests.codes.created:
|
||||
# We had a problem
|
||||
status_str = \
|
||||
NotifyBase.http_response_code_lookup(r.status_code)
|
||||
NotifyBoxcar.http_response_code_lookup(r.status_code)
|
||||
|
||||
self.logger.warning(
|
||||
'Failed to send Boxcar notification: '
|
||||
|
@ -282,16 +282,17 @@ class NotifyBoxcar(NotifyBase):
|
|||
args = {
|
||||
'format': self.notify_format,
|
||||
'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,
|
||||
access=self.quote(self.access),
|
||||
secret=self.quote(self.secret),
|
||||
recipients='/'.join([
|
||||
self.quote(x) for x in chain(
|
||||
access=NotifyBoxcar.quote(self.access, safe=''),
|
||||
secret=NotifyBoxcar.quote(self.secret, safe=''),
|
||||
targets='/'.join([
|
||||
NotifyBoxcar.quote(x, safe='') for x in chain(
|
||||
self.tags, self.device_tokens) if x != DEFAULT_TAG]),
|
||||
args=self.urlencode(args),
|
||||
args=NotifyBoxcar.urlencode(args),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
|
@ -307,23 +308,30 @@ class NotifyBoxcar(NotifyBase):
|
|||
return None
|
||||
|
||||
# The first token is stored in the hostname
|
||||
access = results['host']
|
||||
results['access'] = NotifyBoxcar.unquote(results['host'])
|
||||
|
||||
# Now fetch the remaining tokens
|
||||
secret = NotifyBase.split_path(results['fullpath'])[0]
|
||||
# Get our entries; split_path() looks after unquoting content for us
|
||||
# by default
|
||||
entries = NotifyBoxcar.split_path(results['fullpath'])
|
||||
|
||||
# Our recipients
|
||||
recipients = ','.join(
|
||||
NotifyBase.split_path(results['fullpath'])[1:])
|
||||
try:
|
||||
# Now fetch the remaining tokens
|
||||
results['secret'] = entries.pop(0)
|
||||
|
||||
if not (access and secret):
|
||||
# If we did not recive an access and/or secret code
|
||||
# then we're done
|
||||
return None
|
||||
except IndexError:
|
||||
# secret wasn't specified
|
||||
results['secret'] = None
|
||||
|
||||
# Store our required content
|
||||
results['recipients'] = recipients if recipients else None
|
||||
results['access'] = access
|
||||
results['secret'] = secret
|
||||
# Our recipients make up the remaining entries of our array
|
||||
results['targets'] = entries
|
||||
|
||||
# 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
|
||||
|
|
|
@ -30,6 +30,7 @@ from .NotifyBase import NotifyBase
|
|||
from ..common import NotifyImageSize
|
||||
from ..common import NotifyType
|
||||
from ..utils import GET_SCHEMA_RE
|
||||
from ..utils import parse_bool
|
||||
|
||||
# Default our global support flag
|
||||
NOTIFY_DBUS_SUPPORT_ENABLED = False
|
||||
|
@ -170,7 +171,8 @@ class NotifyDBus(NotifyBase):
|
|||
# let me know! :)
|
||||
_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
|
||||
"""
|
||||
|
@ -184,13 +186,10 @@ class NotifyDBus(NotifyBase):
|
|||
self.schema = kwargs.get('schema', 'dbus')
|
||||
|
||||
if self.schema not in MAINLOOP_MAP:
|
||||
# Unsupported Schema
|
||||
self.logger.warning(
|
||||
'The schema specified ({}) is not supported.'
|
||||
.format(self.schema))
|
||||
raise TypeError(
|
||||
'The schema specified ({}) is not supported.'
|
||||
.format(self.schema))
|
||||
msg = 'The schema specified ({}) is not supported.' \
|
||||
.format(self.schema)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
# The urgency of the message
|
||||
if urgency not in DBUS_URGENCIES:
|
||||
|
@ -200,8 +199,12 @@ class NotifyDBus(NotifyBase):
|
|||
self.urgency = urgency
|
||||
|
||||
# Our x/y axis settings
|
||||
self.x_axis = x_axis
|
||||
self.y_axis = y_axis
|
||||
self.x_axis = x_axis if isinstance(x_axis, int) else None
|
||||
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):
|
||||
"""
|
||||
|
@ -229,7 +232,8 @@ class NotifyDBus(NotifyBase):
|
|||
)
|
||||
|
||||
# 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
|
||||
meta_payload = {
|
||||
|
@ -241,7 +245,7 @@ class NotifyDBus(NotifyBase):
|
|||
meta_payload['x'] = self.x_axis
|
||||
meta_payload['y'] = self.y_axis
|
||||
|
||||
if NOTIFY_DBUS_IMAGE_SUPPORT is True:
|
||||
if NOTIFY_DBUS_IMAGE_SUPPORT and icon_path:
|
||||
try:
|
||||
# Use Pixbuf to create the proper image type
|
||||
image = GdkPixbuf.Pixbuf.new_from_file(icon_path)
|
||||
|
@ -299,7 +303,33 @@ class NotifyDBus(NotifyBase):
|
|||
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
|
||||
def parse_url(url):
|
||||
|
@ -314,23 +344,58 @@ class NotifyDBus(NotifyBase):
|
|||
# Content is simply not parseable
|
||||
return None
|
||||
|
||||
# return a very basic set of requirements
|
||||
return {
|
||||
'schema': schema.group('schema').lower(),
|
||||
'user': None,
|
||||
'password': None,
|
||||
'port': None,
|
||||
'host': 'localhost',
|
||||
'fullpath': None,
|
||||
'path': None,
|
||||
'url': url,
|
||||
'qsd': {},
|
||||
# screen lat/lon (in pixels) where x=0 and y=0 if you want to put
|
||||
# the notification in the top left hand side. Accept defaults if
|
||||
# set to None
|
||||
'x_axis': None,
|
||||
'y_axis': None,
|
||||
# Set the urgency to None so that we fall back to the default
|
||||
# value.
|
||||
'urgency': None,
|
||||
}
|
||||
results = NotifyBase.parse_url(url)
|
||||
if not results:
|
||||
results = {
|
||||
'schema': schema.group('schema').lower(),
|
||||
'user': None,
|
||||
'password': None,
|
||||
'port': None,
|
||||
'host': '_',
|
||||
'fullpath': None,
|
||||
'path': None,
|
||||
'url': url,
|
||||
'qsd': {},
|
||||
}
|
||||
|
||||
# Include images with our message
|
||||
results['include_image'] = \
|
||||
parse_bool(results['qsd'].get('image', True))
|
||||
|
||||
# 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
|
||||
|
||||
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
|
||||
|
||||
|
@ -86,14 +86,14 @@ class NotifyDiscord(NotifyBase):
|
|||
super(NotifyDiscord, self).__init__(**kwargs)
|
||||
|
||||
if not webhook_id:
|
||||
raise TypeError(
|
||||
'An invalid Client ID was specified.'
|
||||
)
|
||||
msg = 'An invalid Client ID was specified.'
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
if not webhook_token:
|
||||
raise TypeError(
|
||||
'An invalid Webhook Token was specified.'
|
||||
)
|
||||
msg = 'An invalid Webhook Token was specified.'
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
# Store our data
|
||||
self.webhook_id = webhook_id
|
||||
|
@ -105,11 +105,14 @@ class NotifyDiscord(NotifyBase):
|
|||
# Over-ride Avatar Icon
|
||||
self.avatar = avatar
|
||||
|
||||
# Place a footer icon
|
||||
# Place a footer
|
||||
self.footer = footer
|
||||
|
||||
# include a footer_logo in footer
|
||||
self.footer_logo = footer_logo
|
||||
|
||||
# Place a thumbnail image inline with the message body
|
||||
self.thumbnail = thumbnail
|
||||
self.include_image = include_image
|
||||
|
||||
return
|
||||
|
||||
|
@ -163,15 +166,18 @@ class NotifyDiscord(NotifyBase):
|
|||
payload['embeds'][0]['fields'] = fields[1:]
|
||||
|
||||
if self.footer:
|
||||
# Acquire logo URL
|
||||
logo_url = self.image_url(notify_type, logo=True)
|
||||
|
||||
# Set Footer text to our app description
|
||||
payload['embeds'][0]['footer'] = {
|
||||
'text': self.app_desc,
|
||||
}
|
||||
|
||||
if logo_url:
|
||||
if self.footer_logo and 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'] = {
|
||||
'url': image_url,
|
||||
'height': 256,
|
||||
|
@ -256,14 +262,15 @@ class NotifyDiscord(NotifyBase):
|
|||
'tts': 'yes' if self.tts else 'no',
|
||||
'avatar': 'yes' if self.avatar 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(
|
||||
schema=self.secure_protocol,
|
||||
webhook_id=self.quote(self.webhook_id),
|
||||
webhook_token=self.quote(self.webhook_token),
|
||||
args=self.urlencode(args),
|
||||
webhook_id=NotifyDiscord.quote(self.webhook_id, safe=''),
|
||||
webhook_token=NotifyDiscord.quote(self.webhook_token, safe=''),
|
||||
args=NotifyDiscord.urlencode(args),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
|
@ -283,14 +290,14 @@ class NotifyDiscord(NotifyBase):
|
|||
return results
|
||||
|
||||
# Store our webhook ID
|
||||
webhook_id = results['host']
|
||||
webhook_id = NotifyDiscord.unquote(results['host'])
|
||||
|
||||
# Now fetch our tokens
|
||||
try:
|
||||
webhook_token = [x for x in filter(bool, NotifyBase.split_path(
|
||||
results['fullpath']))][0]
|
||||
webhook_token = \
|
||||
NotifyDiscord.split_path(results['fullpath'])[0]
|
||||
|
||||
except (ValueError, AttributeError, IndexError):
|
||||
except IndexError:
|
||||
# Force some bad values that will get caught
|
||||
# in parsing later
|
||||
webhook_token = None
|
||||
|
@ -304,12 +311,27 @@ class NotifyDiscord(NotifyBase):
|
|||
# Use Footer
|
||||
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
|
||||
results['avatar'] = parse_bool(results['qsd'].get('avatar', True))
|
||||
|
||||
# Use Thumbnail
|
||||
results['thumbnail'] = \
|
||||
parse_bool(results['qsd'].get('thumbnail', False))
|
||||
if 'thumbnail' in results['qsd']:
|
||||
# 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
|
||||
|
||||
|
|
|
@ -450,13 +450,13 @@ class NotifyEmail(NotifyBase):
|
|||
auth = ''
|
||||
if self.user and self.password:
|
||||
auth = '{user}:{password}@'.format(
|
||||
user=self.quote(user, safe=''),
|
||||
password=self.quote(self.password, safe=''),
|
||||
user=NotifyEmail.quote(user, safe=''),
|
||||
password=NotifyEmail.quote(self.password, safe=''),
|
||||
)
|
||||
else:
|
||||
# user url
|
||||
auth = '{user}@'.format(
|
||||
user=self.quote(user, safe=''),
|
||||
user=NotifyEmail.quote(user, safe=''),
|
||||
)
|
||||
|
||||
# Default Port setup
|
||||
|
@ -466,10 +466,10 @@ class NotifyEmail(NotifyBase):
|
|||
return '{schema}://{auth}{hostname}{port}/?{args}'.format(
|
||||
schema=self.secure_protocol if self.secure else self.protocol,
|
||||
auth=auth,
|
||||
hostname=self.host,
|
||||
hostname=NotifyEmail.quote(self.host, safe=''),
|
||||
port='' if self.port is None or self.port == default_port
|
||||
else ':{}'.format(self.port),
|
||||
args=self.urlencode(args),
|
||||
args=NotifyEmail.urlencode(args),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
|
@ -485,21 +485,28 @@ class NotifyEmail(NotifyBase):
|
|||
# We're done early as we couldn't load the results
|
||||
return results
|
||||
|
||||
# Apply our settings now
|
||||
|
||||
# The To: address is pre-determined if to= is not otherwise
|
||||
# specified.
|
||||
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 = ''
|
||||
|
||||
# The server we connect to to send our mail to
|
||||
smtp_host = ''
|
||||
|
||||
# Attempt to detect 'from' email address
|
||||
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:
|
||||
# get 'To' email address
|
||||
from_addr = '%s@%s' % (
|
||||
re.split(
|
||||
r'[\s@]+', NotifyBase.unquote(results['user']))[0],
|
||||
r'[\s@]+', NotifyEmail.unquote(results['user']))[0],
|
||||
results.get('host', '')
|
||||
)
|
||||
# Lets be clever and attempt to make the from
|
||||
|
@ -511,7 +518,7 @@ class NotifyEmail(NotifyBase):
|
|||
|
||||
# Attempt to detect 'to' email address
|
||||
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:
|
||||
# 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']):
|
||||
# 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']):
|
||||
# Extract the timeout to associate with smtp server
|
||||
|
@ -528,7 +535,7 @@ class NotifyEmail(NotifyBase):
|
|||
# Store SMTP Host if specified
|
||||
if 'smtp' in results['qsd'] and len(results['qsd']['smtp']):
|
||||
# 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']):
|
||||
# Extract the secure mode to over-ride the default
|
||||
|
|
|
@ -96,9 +96,10 @@ class NotifyEmby(NotifyBase):
|
|||
self.modal = modal
|
||||
|
||||
if not self.user:
|
||||
# Token was None
|
||||
self.logger.warning('No Username was specified.')
|
||||
raise TypeError('No Username was specified.')
|
||||
# User was not specified
|
||||
msg = 'No Username was specified.'
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
return
|
||||
|
||||
|
@ -169,7 +170,7 @@ class NotifyEmby(NotifyBase):
|
|||
if r.status_code != requests.codes.ok:
|
||||
# We had a problem
|
||||
status_str = \
|
||||
NotifyBase.http_response_code_lookup(r.status_code)
|
||||
NotifyEmby.http_response_code_lookup(r.status_code)
|
||||
|
||||
self.logger.warning(
|
||||
'Failed to authenticate Emby user {} details: '
|
||||
|
@ -329,7 +330,7 @@ class NotifyEmby(NotifyBase):
|
|||
if r.status_code != requests.codes.ok:
|
||||
# We had a problem
|
||||
status_str = \
|
||||
NotifyBase.http_response_code_lookup(r.status_code)
|
||||
NotifyEmby.http_response_code_lookup(r.status_code)
|
||||
|
||||
self.logger.warning(
|
||||
'Failed to acquire Emby session for user {}: '
|
||||
|
@ -412,7 +413,7 @@ class NotifyEmby(NotifyBase):
|
|||
|
||||
# We had a problem
|
||||
status_str = \
|
||||
NotifyBase.http_response_code_lookup(r.status_code)
|
||||
NotifyEmby.http_response_code_lookup(r.status_code)
|
||||
|
||||
self.logger.warning(
|
||||
'Failed to logoff Emby user {}: '
|
||||
|
@ -508,7 +509,7 @@ class NotifyEmby(NotifyBase):
|
|||
requests.codes.no_content):
|
||||
# We had a problem
|
||||
status_str = \
|
||||
NotifyBase.http_response_code_lookup(r.status_code)
|
||||
NotifyEmby.http_response_code_lookup(r.status_code)
|
||||
|
||||
self.logger.warning(
|
||||
'Failed to send Emby notification: '
|
||||
|
@ -555,21 +556,21 @@ class NotifyEmby(NotifyBase):
|
|||
auth = ''
|
||||
if self.user and self.password:
|
||||
auth = '{user}:{password}@'.format(
|
||||
user=self.quote(self.user, safe=''),
|
||||
password=self.quote(self.password, safe=''),
|
||||
user=NotifyEmby.quote(self.user, safe=''),
|
||||
password=NotifyEmby.quote(self.password, safe=''),
|
||||
)
|
||||
else: # self.user is set
|
||||
auth = '{user}@'.format(
|
||||
user=self.quote(self.user, safe=''),
|
||||
user=NotifyEmby.quote(self.user, safe=''),
|
||||
)
|
||||
|
||||
return '{schema}://{auth}{hostname}{port}/?{args}'.format(
|
||||
schema=self.secure_protocol if self.secure else self.protocol,
|
||||
auth=auth,
|
||||
hostname=self.host,
|
||||
hostname=NotifyEmby.quote(self.host, safe=''),
|
||||
port='' if self.port is None or self.port == self.emby_default_port
|
||||
else ':{}'.format(self.port),
|
||||
args=self.urlencode(args),
|
||||
args=NotifyEmby.urlencode(args),
|
||||
)
|
||||
|
||||
@property
|
||||
|
|
|
@ -27,6 +27,7 @@ import requests
|
|||
from .NotifyBase import NotifyBase
|
||||
from ..common import NotifyImageSize
|
||||
from ..common import NotifyType
|
||||
from ..utils import parse_bool
|
||||
|
||||
|
||||
class NotifyFaast(NotifyBase):
|
||||
|
@ -52,14 +53,18 @@ class NotifyFaast(NotifyBase):
|
|||
# Allows the user to specify the NotifyImageSize object
|
||||
image_size = NotifyImageSize.XY_72
|
||||
|
||||
def __init__(self, authtoken, **kwargs):
|
||||
def __init__(self, authtoken, include_image=True, **kwargs):
|
||||
"""
|
||||
Initialize Faast Object
|
||||
"""
|
||||
super(NotifyFaast, self).__init__(**kwargs)
|
||||
|
||||
# Store the Authentication Token
|
||||
self.authtoken = authtoken
|
||||
|
||||
# Associate an image with our post
|
||||
self.include_image = include_image
|
||||
|
||||
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
||||
"""
|
||||
Perform Faast Notification
|
||||
|
@ -77,7 +82,10 @@ class NotifyFaast(NotifyBase):
|
|||
'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:
|
||||
payload['icon_url'] = image_url
|
||||
|
||||
|
@ -99,7 +107,7 @@ class NotifyFaast(NotifyBase):
|
|||
if r.status_code != requests.codes.ok:
|
||||
# We had a problem
|
||||
status_str = \
|
||||
NotifyBase.http_response_code_lookup(r.status_code)
|
||||
NotifyFaast.http_response_code_lookup(r.status_code)
|
||||
|
||||
self.logger.warning(
|
||||
'Failed to send Faast notification:'
|
||||
|
@ -136,12 +144,13 @@ class NotifyFaast(NotifyBase):
|
|||
args = {
|
||||
'format': self.notify_format,
|
||||
'overflow': self.overflow_mode,
|
||||
'image': 'yes' if self.include_image else 'no',
|
||||
}
|
||||
|
||||
return '{schema}://{authtoken}/?{args}'.format(
|
||||
schema=self.protocol,
|
||||
authtoken=self.quote(self.authtoken, safe=''),
|
||||
args=self.urlencode(args),
|
||||
authtoken=NotifyFaast.quote(self.authtoken, safe=''),
|
||||
args=NotifyFaast.urlencode(args),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
|
@ -157,9 +166,11 @@ class NotifyFaast(NotifyBase):
|
|||
# We're done early as we couldn't load the results
|
||||
return results
|
||||
|
||||
# Apply our settings now
|
||||
|
||||
# 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
|
||||
|
|
|
@ -25,15 +25,17 @@
|
|||
|
||||
# To use this plugin, you need to first access https://dev.flock.com/webhooks
|
||||
# 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
|
||||
# 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:
|
||||
# https://api.flock.com/hooks/sendMessage/134b8gh0-eba0-4fa9-ab9c-257ced0e8221
|
||||
# ^
|
||||
# |
|
||||
# This is important <----------------------------------------^
|
||||
#
|
||||
# It becomes your 'token' that you will pass into this class
|
||||
#
|
||||
import re
|
||||
import requests
|
||||
|
@ -44,6 +46,7 @@ from ..common import NotifyType
|
|||
from ..common import NotifyFormat
|
||||
from ..common import NotifyImageSize
|
||||
from ..utils import parse_list
|
||||
from ..utils import parse_bool
|
||||
|
||||
|
||||
# Extend HTTP Error Messages
|
||||
|
@ -89,7 +92,7 @@ class NotifyFlock(NotifyBase):
|
|||
# Allows the user to specify the NotifyImageSize object
|
||||
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
|
||||
"""
|
||||
|
@ -134,6 +137,10 @@ class NotifyFlock(NotifyBase):
|
|||
self.logger.warning(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):
|
||||
"""
|
||||
Perform Flock Notification
|
||||
|
@ -151,8 +158,8 @@ class NotifyFlock(NotifyBase):
|
|||
body = '<flockml>{}</flockml>'.format(body)
|
||||
|
||||
else:
|
||||
title = NotifyBase.escape_html(title, whitespace=False)
|
||||
body = NotifyBase.escape_html(body, whitespace=False)
|
||||
title = NotifyFlock.escape_html(title, whitespace=False)
|
||||
body = NotifyFlock.escape_html(body, whitespace=False)
|
||||
|
||||
body = '<flockml>{}{}</flockml>'.format(
|
||||
'' if not title else '<b>{}</b><br/>'.format(title), body)
|
||||
|
@ -162,7 +169,10 @@ class NotifyFlock(NotifyBase):
|
|||
'flockml': body,
|
||||
'sendAs': {
|
||||
'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:
|
||||
# We had a problem
|
||||
status_str = \
|
||||
NotifyBase.http_response_code_lookup(
|
||||
NotifyFlock.http_response_code_lookup(
|
||||
r.status_code, FLOCK_HTTP_ERROR_MAP)
|
||||
|
||||
self.logger.warning(
|
||||
|
@ -251,15 +261,17 @@ class NotifyFlock(NotifyBase):
|
|||
args = {
|
||||
'format': self.notify_format,
|
||||
'overflow': self.overflow_mode,
|
||||
'image': 'yes' if self.include_image else 'no',
|
||||
}
|
||||
|
||||
return '{schema}://{token}/{targets}?{args}'\
|
||||
.format(
|
||||
schema=self.secure_protocol,
|
||||
token=self.quote(self.token, safe=''),
|
||||
token=NotifyFlock.quote(self.token, safe=''),
|
||||
targets='/'.join(
|
||||
[self.quote(target, safe='') for target in self.targets]),
|
||||
args=self.urlencode(args),
|
||||
[NotifyFlock.quote(target, safe='')
|
||||
for target in self.targets]),
|
||||
args=NotifyFlock.urlencode(args),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
|
@ -274,12 +286,19 @@ class NotifyFlock(NotifyBase):
|
|||
# We're done early as we couldn't load the 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(
|
||||
bool, NotifyBase.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'] += NotifyFlock.parse_list(results['qsd']['to'])
|
||||
|
||||
# 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
|
||||
|
|
|
@ -55,7 +55,7 @@ from ..utils import parse_bool
|
|||
# API Gitter URL
|
||||
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)
|
||||
|
||||
# Used to break path apart into list of targets
|
||||
|
@ -95,9 +95,11 @@ class NotifyGitter(NotifyBase):
|
|||
|
||||
# For Tracking Purposes
|
||||
ratelimit_reset = datetime.utcnow()
|
||||
|
||||
# Default to 1
|
||||
ratelimit_remaining = 1
|
||||
|
||||
# Default Notification Format
|
||||
notify_format = NotifyFormat.MARKDOWN
|
||||
|
||||
def __init__(self, token, targets, include_image=True, **kwargs):
|
||||
|
@ -107,7 +109,7 @@ class NotifyGitter(NotifyBase):
|
|||
super(NotifyGitter, self).__init__(**kwargs)
|
||||
|
||||
try:
|
||||
# The token associated with the account
|
||||
# The personal access token associated with the account
|
||||
self.token = token.strip()
|
||||
|
||||
except AttributeError:
|
||||
|
@ -117,7 +119,8 @@ class NotifyGitter(NotifyBase):
|
|||
raise TypeError(msg)
|
||||
|
||||
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)
|
||||
raise TypeError(msg)
|
||||
|
||||
|
@ -140,10 +143,11 @@ class NotifyGitter(NotifyBase):
|
|||
# error tracking (used for function return)
|
||||
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 self.include_image and image_url:
|
||||
if image_url:
|
||||
body = '![alt]({})\n{}'.format(image_url, body)
|
||||
|
||||
# Create a copy of the targets list
|
||||
|
@ -288,7 +292,7 @@ class NotifyGitter(NotifyBase):
|
|||
if r.status_code != requests.codes.ok:
|
||||
# We had a problem
|
||||
status_str = \
|
||||
NotifyBase.http_response_code_lookup(r.status_code)
|
||||
NotifyGitter.http_response_code_lookup(r.status_code)
|
||||
|
||||
self.logger.warning(
|
||||
'Failed to send Gitter POST to {}: '
|
||||
|
@ -342,14 +346,15 @@ class NotifyGitter(NotifyBase):
|
|||
args = {
|
||||
'format': self.notify_format,
|
||||
'overflow': self.overflow_mode,
|
||||
'image': self.include_image,
|
||||
'image': 'yes' if self.include_image else 'no',
|
||||
}
|
||||
|
||||
return '{schema}://{token}/{targets}/?{args}'.format(
|
||||
schema=self.secure_protocol,
|
||||
token=self.quote(self.token, safe=''),
|
||||
targets='/'.join(self.targets),
|
||||
args=self.urlencode(args))
|
||||
token=NotifyGitter.quote(self.token, safe=''),
|
||||
targets='/'.join(
|
||||
[NotifyGitter.quote(x, safe='') for x in self.targets]),
|
||||
args=NotifyGitter.urlencode(args))
|
||||
|
||||
@staticmethod
|
||||
def parse_url(url):
|
||||
|
@ -364,15 +369,16 @@ class NotifyGitter(NotifyBase):
|
|||
# We're done early as we couldn't load the results
|
||||
return results
|
||||
|
||||
results['token'] = results['host']
|
||||
results['targets'] = \
|
||||
[NotifyBase.unquote(x) for x in filter(bool, NotifyBase.split_path(
|
||||
results['fullpath']))]
|
||||
results['token'] = NotifyGitter.unquote(results['host'])
|
||||
|
||||
# Get our entries; split_path() looks after unquoting content for us
|
||||
# by default
|
||||
results['targets'] = NotifyGitter.split_path(results['fullpath'])
|
||||
|
||||
# Support the 'to' variable so that we can support targets this way too
|
||||
# The 'to' makes it easier to use yaml configuration
|
||||
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
|
||||
results['include_image'] = \
|
||||
|
|
|
@ -29,6 +29,7 @@ from __future__ import print_function
|
|||
from .NotifyBase import NotifyBase
|
||||
from ..common import NotifyImageSize
|
||||
from ..common import NotifyType
|
||||
from ..utils import parse_bool
|
||||
|
||||
# Default our global support flag
|
||||
NOTIFY_GNOME_SUPPORT_ENABLED = False
|
||||
|
@ -109,7 +110,7 @@ class NotifyGnome(NotifyBase):
|
|||
# let me know! :)
|
||||
_enabled = NOTIFY_GNOME_SUPPORT_ENABLED
|
||||
|
||||
def __init__(self, urgency=None, **kwargs):
|
||||
def __init__(self, urgency=None, include_image=True, **kwargs):
|
||||
"""
|
||||
Initialize Gnome Object
|
||||
"""
|
||||
|
@ -123,6 +124,10 @@ class NotifyGnome(NotifyBase):
|
|||
else:
|
||||
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):
|
||||
"""
|
||||
Perform Gnome Notification
|
||||
|
@ -138,7 +143,8 @@ class NotifyGnome(NotifyBase):
|
|||
Notify.init(self.app_id)
|
||||
|
||||
# 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
|
||||
notification = Notify.Notification.new(body)
|
||||
|
@ -149,18 +155,19 @@ class NotifyGnome(NotifyBase):
|
|||
# Always call throttle before any remote server i/o is made
|
||||
self.throttle()
|
||||
|
||||
try:
|
||||
# Use Pixbuf to create the proper image type
|
||||
image = GdkPixbuf.Pixbuf.new_from_file(icon_path)
|
||||
if icon_path:
|
||||
try:
|
||||
# Use Pixbuf to create the proper image type
|
||||
image = GdkPixbuf.Pixbuf.new_from_file(icon_path)
|
||||
|
||||
# Associate our image to our notification
|
||||
notification.set_icon_from_pixbuf(image)
|
||||
notification.set_image_from_pixbuf(image)
|
||||
# Associate our image to our notification
|
||||
notification.set_icon_from_pixbuf(image)
|
||||
notification.set_image_from_pixbuf(image)
|
||||
|
||||
except Exception as e:
|
||||
self.logger.warning(
|
||||
"Could not load Gnome notification icon ({}): {}"
|
||||
.format(icon_path, e))
|
||||
except Exception as e:
|
||||
self.logger.warning(
|
||||
"Could not load Gnome notification icon ({}): {}"
|
||||
.format(icon_path, e))
|
||||
|
||||
notification.show()
|
||||
self.logger.info('Sent Gnome notification.')
|
||||
|
@ -177,7 +184,25 @@ class NotifyGnome(NotifyBase):
|
|||
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
|
||||
def parse_url(url):
|
||||
|
@ -188,18 +213,43 @@ class NotifyGnome(NotifyBase):
|
|||
|
||||
"""
|
||||
|
||||
# return a very basic set of requirements
|
||||
return {
|
||||
'schema': NotifyGnome.protocol,
|
||||
'user': None,
|
||||
'password': None,
|
||||
'port': None,
|
||||
'host': 'localhost',
|
||||
'fullpath': None,
|
||||
'path': None,
|
||||
'url': url,
|
||||
'qsd': {},
|
||||
# Set the urgency to None so that we fall back to the default
|
||||
# value.
|
||||
'urgency': None,
|
||||
}
|
||||
results = NotifyBase.parse_url(url)
|
||||
if not results:
|
||||
results = {
|
||||
'schema': NotifyGnome.protocol,
|
||||
'user': None,
|
||||
'password': None,
|
||||
'port': None,
|
||||
'host': '_',
|
||||
'fullpath': None,
|
||||
'path': None,
|
||||
'url': url,
|
||||
'qsd': {},
|
||||
}
|
||||
|
||||
# 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
|
||||
# authenticate with our Gotify server. The same goes for the
|
||||
# user id below.
|
||||
self.access_token = token
|
||||
self.token = token
|
||||
|
||||
return
|
||||
|
||||
|
@ -121,12 +121,12 @@ class NotifyGotify(NotifyBase):
|
|||
|
||||
# Define our parameteers
|
||||
params = {
|
||||
'token': self.access_token,
|
||||
'token': self.token,
|
||||
}
|
||||
|
||||
# Prepare Gotify Object
|
||||
payload = {
|
||||
'priority': 2,
|
||||
'priority': self.priority,
|
||||
'title': title,
|
||||
'message': body,
|
||||
}
|
||||
|
@ -156,7 +156,7 @@ class NotifyGotify(NotifyBase):
|
|||
if r.status_code != requests.codes.ok:
|
||||
# We had a problem
|
||||
status_str = \
|
||||
NotifyBase.http_response_code_lookup(r.status_code)
|
||||
NotifyGotify.http_response_code_lookup(r.status_code)
|
||||
|
||||
self.logger.warning(
|
||||
'Failed to send Gotify notification: '
|
||||
|
@ -201,11 +201,11 @@ class NotifyGotify(NotifyBase):
|
|||
|
||||
return '{schema}://{hostname}{port}/{token}/?{args}'.format(
|
||||
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
|
||||
else ':{}'.format(self.port),
|
||||
token=self.access_token,
|
||||
args=self.urlencode(args),
|
||||
token=NotifyGotify.quote(self.token, safe=''),
|
||||
args=NotifyGotify.urlencode(args),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
|
@ -220,13 +220,17 @@ class NotifyGotify(NotifyBase):
|
|||
# We're done early
|
||||
return results
|
||||
|
||||
# Retrieve our escaped entries found on the fullpath
|
||||
entries = NotifyBase.split_path(results['fullpath'])
|
||||
|
||||
# optionally find the provider key
|
||||
try:
|
||||
token = [x for x in filter(
|
||||
bool, NotifyBase.split_path(results['fullpath']))][0]
|
||||
# The first entry is our token
|
||||
results['token'] = entries.pop(0)
|
||||
|
||||
except (AttributeError, IndexError):
|
||||
token = None
|
||||
except IndexError:
|
||||
# No token was set
|
||||
results['token'] = None
|
||||
|
||||
if 'priority' in results['qsd'] and len(results['qsd']['priority']):
|
||||
_map = {
|
||||
|
@ -244,7 +248,4 @@ class NotifyGotify(NotifyBase):
|
|||
# No priority was set
|
||||
pass
|
||||
|
||||
# Set our token
|
||||
results['token'] = token
|
||||
|
||||
return results
|
||||
|
|
|
@ -28,6 +28,7 @@ from .gntp import errors
|
|||
from ..NotifyBase import NotifyBase
|
||||
from ...common import NotifyImageSize
|
||||
from ...common import NotifyType
|
||||
from ...utils import parse_bool
|
||||
|
||||
|
||||
# Priorities
|
||||
|
@ -86,7 +87,7 @@ class NotifyGrowl(NotifyBase):
|
|||
# Default Growl Port
|
||||
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
|
||||
"""
|
||||
|
@ -129,28 +130,26 @@ class NotifyGrowl(NotifyBase):
|
|||
)
|
||||
|
||||
except errors.NetworkError:
|
||||
self.logger.warning(
|
||||
'A network error occured sending Growl '
|
||||
'notification to %s.' % self.host)
|
||||
raise TypeError(
|
||||
'A network error occured sending Growl '
|
||||
'notification to %s.' % self.host)
|
||||
msg = 'A network error occured sending Growl ' \
|
||||
'notification to {}.'.format(self.host)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
except errors.AuthError:
|
||||
self.logger.warning(
|
||||
'An authentication error occured sending Growl '
|
||||
'notification to %s.' % self.host)
|
||||
raise TypeError(
|
||||
'An authentication error occured sending Growl '
|
||||
'notification to %s.' % self.host)
|
||||
msg = 'An authentication error occured sending Growl ' \
|
||||
'notification to {}.'.format(self.host)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
except errors.UnsupportedError:
|
||||
self.logger.warning(
|
||||
'An unsupported error occured sending Growl '
|
||||
'notification to %s.' % self.host)
|
||||
raise TypeError(
|
||||
'An unsupported error occured sending Growl '
|
||||
'notification to %s.' % self.host)
|
||||
msg = 'An unsupported error occured sending Growl ' \
|
||||
'notification to {}.'.format(self.host)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
# Track whether or not we want to send an image with our notification
|
||||
# or not.
|
||||
self.include_image = include_image
|
||||
|
||||
return
|
||||
|
||||
|
@ -162,11 +161,13 @@ class NotifyGrowl(NotifyBase):
|
|||
icon = None
|
||||
if self.version >= 2:
|
||||
# URL Based
|
||||
icon = self.image_url(notify_type)
|
||||
icon = None if not self.include_image \
|
||||
else self.image_url(notify_type)
|
||||
|
||||
else:
|
||||
# Raw
|
||||
icon = self.image_raw(notify_type)
|
||||
icon = None if not self.include_image \
|
||||
else self.image_raw(notify_type)
|
||||
|
||||
payload = {
|
||||
'noteType': GROWL_NOTIFICATION_TYPE,
|
||||
|
@ -232,6 +233,7 @@ class NotifyGrowl(NotifyBase):
|
|||
args = {
|
||||
'format': self.notify_format,
|
||||
'overflow': self.overflow_mode,
|
||||
'image': 'yes' if self.include_image else 'no',
|
||||
'priority':
|
||||
_map[GrowlPriority.NORMAL] if self.priority not in _map
|
||||
else _map[self.priority],
|
||||
|
@ -239,18 +241,19 @@ class NotifyGrowl(NotifyBase):
|
|||
}
|
||||
|
||||
auth = ''
|
||||
if self.password:
|
||||
if self.user:
|
||||
# The growl password is stored in the user field
|
||||
auth = '{password}@'.format(
|
||||
password=self.quote(self.user, safe=''),
|
||||
password=NotifyGrowl.quote(self.user, safe=''),
|
||||
)
|
||||
|
||||
return '{schema}://{auth}{hostname}{port}/?{args}'.format(
|
||||
schema=self.secure_protocol if self.secure else self.protocol,
|
||||
auth=auth,
|
||||
hostname=self.host,
|
||||
hostname=NotifyGrowl.quote(self.host, safe=''),
|
||||
port='' if self.port is None or self.port == self.default_port
|
||||
else ':{}'.format(self.port),
|
||||
args=self.urlencode(args),
|
||||
args=NotifyGrowl.urlencode(args),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
|
@ -272,11 +275,11 @@ class NotifyGrowl(NotifyBase):
|
|||
# Allow the user to specify the version of the protocol to use.
|
||||
try:
|
||||
version = int(
|
||||
NotifyBase.unquote(
|
||||
NotifyGrowl.unquote(
|
||||
results['qsd']['version']).strip().split('.')[0])
|
||||
|
||||
except (AttributeError, IndexError, TypeError, ValueError):
|
||||
NotifyBase.logger.warning(
|
||||
NotifyGrowl.logger.warning(
|
||||
'An invalid Growl version of "%s" was specified and will '
|
||||
'be ignored.' % results['qsd']['version']
|
||||
)
|
||||
|
@ -306,6 +309,11 @@ class NotifyGrowl(NotifyBase):
|
|||
if results.get('password', None) is 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:
|
||||
results['version'] = version
|
||||
|
||||
|
|
|
@ -108,14 +108,17 @@ class NotifyIFTTT(NotifyBase):
|
|||
super(NotifyIFTTT, self).__init__(**kwargs)
|
||||
|
||||
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
|
||||
self.events = parse_list(events)
|
||||
|
||||
if not self.events:
|
||||
raise TypeError(
|
||||
'You must specify at least one event you wish to trigger on.')
|
||||
msg = 'You must specify at least one event you wish to trigger on.'
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
# Store our APIKey
|
||||
self.webhook_id = webhook_id
|
||||
|
@ -132,9 +135,10 @@ class NotifyIFTTT(NotifyBase):
|
|||
self.del_tokens = del_tokens
|
||||
|
||||
else:
|
||||
raise TypeError(
|
||||
'del_token must be a list; {} was provided'.format(
|
||||
str(type(del_tokens))))
|
||||
msg = 'del_token must be a list; {} was provided'.format(
|
||||
str(type(del_tokens)))
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
||||
"""
|
||||
|
@ -202,7 +206,7 @@ class NotifyIFTTT(NotifyBase):
|
|||
if r.status_code != requests.codes.ok:
|
||||
# We had a problem
|
||||
status_str = \
|
||||
NotifyBase.http_response_code_lookup(r.status_code)
|
||||
NotifyIFTTT.http_response_code_lookup(r.status_code)
|
||||
|
||||
self.logger.warning(
|
||||
'Failed to send IFTTT notification to {}: '
|
||||
|
@ -253,9 +257,10 @@ class NotifyIFTTT(NotifyBase):
|
|||
|
||||
return '{schema}://{webhook_id}@{events}/?{args}'.format(
|
||||
schema=self.secure_protocol,
|
||||
webhook_id=self.webhook_id,
|
||||
events='/'.join([self.quote(x, safe='') for x in self.events]),
|
||||
args=self.urlencode(args),
|
||||
webhook_id=NotifyIFTTT.quote(self.webhook_id, safe=''),
|
||||
events='/'.join([NotifyIFTTT.quote(x, safe='')
|
||||
for x in self.events]),
|
||||
args=NotifyIFTTT.urlencode(args),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
|
@ -271,15 +276,26 @@ class NotifyIFTTT(NotifyBase):
|
|||
# We're done early as we couldn't load the 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
|
||||
results['events'] = list()
|
||||
results['events'].append(results['host'])
|
||||
|
||||
# Our API Key
|
||||
results['webhook_id'] = results['user']
|
||||
if results['user']:
|
||||
# If a user was defined, then the hostname is actually a event
|
||||
# too
|
||||
results['events'].append(NotifyIFTTT.unquote(results['host']))
|
||||
|
||||
# Now fetch the remaining tokens
|
||||
results['events'].extend([x for x in filter(
|
||||
bool, NotifyBase.split_path(results['fullpath']))][0:])
|
||||
results['events'].extend(NotifyIFTTT.split_path(results['fullpath']))
|
||||
|
||||
# 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
|
||||
|
|
|
@ -56,7 +56,7 @@ class NotifyJSON(NotifyBase):
|
|||
# local anyway
|
||||
request_rate_per_sec = 0
|
||||
|
||||
def __init__(self, headers, **kwargs):
|
||||
def __init__(self, headers=None, **kwargs):
|
||||
"""
|
||||
Initialize JSON Object
|
||||
|
||||
|
@ -66,12 +66,6 @@ class NotifyJSON(NotifyBase):
|
|||
"""
|
||||
super(NotifyJSON, self).__init__(**kwargs)
|
||||
|
||||
if self.secure:
|
||||
self.schema = 'https'
|
||||
|
||||
else:
|
||||
self.schema = 'http'
|
||||
|
||||
self.fullpath = kwargs.get('fullpath')
|
||||
if not isinstance(self.fullpath, six.string_types):
|
||||
self.fullpath = '/'
|
||||
|
@ -101,12 +95,12 @@ class NotifyJSON(NotifyBase):
|
|||
auth = ''
|
||||
if self.user and self.password:
|
||||
auth = '{user}:{password}@'.format(
|
||||
user=self.quote(self.user, safe=''),
|
||||
password=self.quote(self.password, safe=''),
|
||||
user=NotifyJSON.quote(self.user, safe=''),
|
||||
password=NotifyJSON.quote(self.password, safe=''),
|
||||
)
|
||||
elif self.user:
|
||||
auth = '{user}@'.format(
|
||||
user=self.quote(self.user, safe=''),
|
||||
user=NotifyJSON.quote(self.user, safe=''),
|
||||
)
|
||||
|
||||
default_port = 443 if self.secure else 80
|
||||
|
@ -114,10 +108,10 @@ class NotifyJSON(NotifyBase):
|
|||
return '{schema}://{auth}{hostname}{port}/?{args}'.format(
|
||||
schema=self.secure_protocol if self.secure else self.protocol,
|
||||
auth=auth,
|
||||
hostname=self.host,
|
||||
hostname=NotifyJSON.quote(self.host, safe=''),
|
||||
port='' if self.port is None or self.port == default_port
|
||||
else ':{}'.format(self.port),
|
||||
args=self.urlencode(args),
|
||||
args=NotifyJSON.urlencode(args),
|
||||
)
|
||||
|
||||
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
||||
|
@ -148,7 +142,10 @@ class NotifyJSON(NotifyBase):
|
|||
if self.user:
|
||||
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):
|
||||
url += ':%d' % self.port
|
||||
|
||||
|
@ -173,7 +170,7 @@ class NotifyJSON(NotifyBase):
|
|||
if r.status_code != requests.codes.ok:
|
||||
# We had a problem
|
||||
status_str = \
|
||||
NotifyBase.http_response_code_lookup(r.status_code)
|
||||
NotifyJSON.http_response_code_lookup(r.status_code)
|
||||
|
||||
self.logger.warning(
|
||||
'Failed to send JSON notification: '
|
||||
|
@ -219,4 +216,8 @@ class NotifyJSON(NotifyBase):
|
|||
results['headers'] = 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
|
||||
|
|
|
@ -34,12 +34,13 @@
|
|||
# https://play.google.com/store/apps/details?id=com.joaomgcd.join
|
||||
|
||||
import re
|
||||
import six
|
||||
import requests
|
||||
|
||||
from .NotifyBase import NotifyBase
|
||||
from ..common import NotifyImageSize
|
||||
from ..common import NotifyType
|
||||
from ..utils import parse_list
|
||||
from ..utils import parse_bool
|
||||
|
||||
# Token required as part of the API request
|
||||
VALIDATE_APIKEY = re.compile(r'[A-Za-z0-9]{32}')
|
||||
|
@ -49,9 +50,6 @@ JOIN_HTTP_ERROR_MAP = {
|
|||
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
|
||||
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
|
||||
default_join_group = 'group.all'
|
||||
|
||||
def __init__(self, apikey, devices, **kwargs):
|
||||
def __init__(self, apikey, targets, include_image=True, **kwargs):
|
||||
"""
|
||||
Initialize Join Object
|
||||
"""
|
||||
super(NotifyJoin, self).__init__(**kwargs)
|
||||
|
||||
if not VALIDATE_APIKEY.match(apikey.strip()):
|
||||
self.logger.warning(
|
||||
'The first API Token specified (%s) is invalid.' % apikey,
|
||||
)
|
||||
|
||||
raise TypeError(
|
||||
'The first API Token specified (%s) is invalid.' % apikey,
|
||||
)
|
||||
msg = 'The JOIN API Token specified ({}) is invalid.'\
|
||||
.format(apikey)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
# The token associated with the account
|
||||
self.apikey = apikey.strip()
|
||||
|
||||
if isinstance(devices, six.string_types):
|
||||
self.devices = [x for x in filter(bool, DEVICE_LIST_DELIM.split(
|
||||
devices,
|
||||
))]
|
||||
|
||||
elif isinstance(devices, (set, tuple, list)):
|
||||
self.devices = devices
|
||||
|
||||
else:
|
||||
self.devices = list()
|
||||
# Parse devices specified
|
||||
self.devices = parse_list(targets)
|
||||
|
||||
if len(self.devices) == 0:
|
||||
# Default to everyone
|
||||
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):
|
||||
"""
|
||||
Perform Join Notification
|
||||
|
@ -151,13 +142,12 @@ class NotifyJoin(NotifyBase):
|
|||
device = devices.pop(0)
|
||||
group_re = IS_GROUP_RE.match(device)
|
||||
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):
|
||||
self.logger.warning(
|
||||
"The specified device/group '%s' is invalid; skipping." % (
|
||||
device,
|
||||
)
|
||||
'Skipping specified invalid device/group "{}"'
|
||||
.format(device)
|
||||
)
|
||||
# Mark our failure
|
||||
has_error = True
|
||||
|
@ -170,7 +160,10 @@ class NotifyJoin(NotifyBase):
|
|||
'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:
|
||||
url_args['icon'] = image_url
|
||||
|
||||
|
@ -178,7 +171,7 @@ class NotifyJoin(NotifyBase):
|
|||
payload = {}
|
||||
|
||||
# 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)' % (
|
||||
url, self.verify_certificate,
|
||||
|
@ -199,7 +192,7 @@ class NotifyJoin(NotifyBase):
|
|||
if r.status_code != requests.codes.ok:
|
||||
# We had a problem
|
||||
status_str = \
|
||||
NotifyBase.http_response_code_lookup(
|
||||
NotifyJoin.http_response_code_lookup(
|
||||
r.status_code, JOIN_HTTP_ERROR_MAP)
|
||||
|
||||
self.logger.warning(
|
||||
|
@ -242,13 +235,15 @@ class NotifyJoin(NotifyBase):
|
|||
args = {
|
||||
'format': self.notify_format,
|
||||
'overflow': self.overflow_mode,
|
||||
'image': 'yes' if self.include_image else 'no',
|
||||
}
|
||||
|
||||
return '{schema}://{apikey}/{devices}/?{args}'.format(
|
||||
schema=self.secure_protocol,
|
||||
apikey=self.quote(self.apikey, safe=''),
|
||||
devices='/'.join([self.quote(x) for x in self.devices]),
|
||||
args=self.urlencode(args))
|
||||
apikey=NotifyJoin.quote(self.apikey, safe=''),
|
||||
devices='/'.join([NotifyJoin.quote(x, safe='')
|
||||
for x in self.devices]),
|
||||
args=NotifyJoin.urlencode(args))
|
||||
|
||||
@staticmethod
|
||||
def parse_url(url):
|
||||
|
@ -263,11 +258,30 @@ class NotifyJoin(NotifyBase):
|
|||
# We're done early as we couldn't load the results
|
||||
return results
|
||||
|
||||
# Apply our settings now
|
||||
devices = ' '.join(
|
||||
filter(bool, NotifyBase.split_path(results['fullpath'])))
|
||||
# Our API Key is the hostname if no user is specified
|
||||
results['apikey'] = \
|
||||
results['user'] if results['user'] else results['host']
|
||||
|
||||
results['apikey'] = results['host']
|
||||
results['devices'] = devices
|
||||
# Unquote our API Key
|
||||
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
|
||||
|
|
|
@ -39,6 +39,7 @@ from ..common import NotifyType
|
|||
from ..common import NotifyImageSize
|
||||
from ..common import NotifyFormat
|
||||
from ..utils import parse_bool
|
||||
from ..utils import parse_list
|
||||
|
||||
# Define default path
|
||||
MATRIX_V2_API_PATH = '/_matrix/client/r0'
|
||||
|
@ -50,10 +51,6 @@ MATRIX_HTTP_ERROR_MAP = {
|
|||
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
|
||||
IS_ROOM_ALIAS = re.compile(
|
||||
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
|
||||
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
|
||||
"""
|
||||
super(NotifyMatrix, self).__init__(**kwargs)
|
||||
|
||||
# Prepare a list of rooms to connect and notify
|
||||
if isinstance(rooms, six.string_types):
|
||||
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)
|
||||
self.rooms = parse_list(targets)
|
||||
|
||||
# our home server gets populated after a login/registration
|
||||
self.home_server = None
|
||||
|
@ -154,23 +136,31 @@ class NotifyMatrix(NotifyBase):
|
|||
# This gets initialized after a login/registration
|
||||
self.access_token = None
|
||||
|
||||
# Place a thumbnail image inline with the message body
|
||||
self.thumbnail = thumbnail
|
||||
# Place an image inline with the message body
|
||||
self.include_image = include_image
|
||||
|
||||
# maintain a lookup of room alias's we already paired with their id
|
||||
# to speed up future requests
|
||||
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):
|
||||
"""
|
||||
Perform Matrix Notification
|
||||
"""
|
||||
|
||||
# Call the _send_ function applicable to whatever mode we're in
|
||||
# - calls _send_webhook_notification if the webhook variable is set
|
||||
# - calls _send_server_notification if the webhook variable is not set
|
||||
# - calls _send_webhook_notification if the mode variable is set
|
||||
# - calls _send_server_notification if the mode variable is not set
|
||||
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)
|
||||
|
||||
def _send_webhook_notification(self, body, title='',
|
||||
|
@ -200,7 +190,7 @@ class NotifyMatrix(NotifyBase):
|
|||
)
|
||||
|
||||
# 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)
|
||||
|
||||
self.logger.debug('Matrix POST URL: %s (cert_verify=%r)' % (
|
||||
|
@ -221,7 +211,7 @@ class NotifyMatrix(NotifyBase):
|
|||
if r.status_code != requests.codes.ok:
|
||||
# We had a problem
|
||||
status_str = \
|
||||
NotifyBase.http_response_code_lookup(
|
||||
NotifyMatrix.http_response_code_lookup(
|
||||
r.status_code, MATRIX_HTTP_ERROR_MAP)
|
||||
|
||||
self.logger.warning(
|
||||
|
@ -318,8 +308,8 @@ class NotifyMatrix(NotifyBase):
|
|||
else: # TEXT or MARKDOWN
|
||||
|
||||
# Ensure our content is escaped
|
||||
title = NotifyBase.escape_html(title)
|
||||
body = NotifyBase.escape_html(body)
|
||||
title = NotifyMatrix.escape_html(title)
|
||||
body = NotifyMatrix.escape_html(body)
|
||||
|
||||
payload['text'] = '{}{}'.format(
|
||||
'' 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),
|
||||
body=body)
|
||||
|
||||
image_url = self.image_url(notify_type)
|
||||
if self.thumbnail and image_url:
|
||||
# Acquire our image url if we're configured to do so
|
||||
image_url = None if not self.include_image else \
|
||||
self.image_url(notify_type)
|
||||
|
||||
if image_url:
|
||||
# Define our payload
|
||||
image_payload = {
|
||||
'msgtype': 'm.image',
|
||||
|
@ -385,7 +378,7 @@ class NotifyMatrix(NotifyBase):
|
|||
}
|
||||
# Build our path
|
||||
path = '/rooms/{}/send/m.room.message'.format(
|
||||
NotifyBase.quote(room_id))
|
||||
NotifyMatrix.quote(room_id))
|
||||
|
||||
# Post our content
|
||||
postokay, response = self._fetch(path, payload=image_payload)
|
||||
|
@ -402,7 +395,7 @@ class NotifyMatrix(NotifyBase):
|
|||
|
||||
# Build our path
|
||||
path = '/rooms/{}/send/m.room.message'.format(
|
||||
NotifyBase.quote(room_id))
|
||||
NotifyMatrix.quote(room_id))
|
||||
|
||||
# Post our content
|
||||
postokay, response = self._fetch(path, payload=payload)
|
||||
|
@ -446,7 +439,7 @@ class NotifyMatrix(NotifyBase):
|
|||
# Register
|
||||
postokay, response = \
|
||||
self._fetch('/register', payload=payload, params=params)
|
||||
if not postokay:
|
||||
if not (postokay and isinstance(response, dict)):
|
||||
# Failed to register
|
||||
return False
|
||||
|
||||
|
@ -489,7 +482,7 @@ class NotifyMatrix(NotifyBase):
|
|||
|
||||
# Build our URL
|
||||
postokay, response = self._fetch('/login', payload=payload)
|
||||
if not postokay:
|
||||
if not (postokay and isinstance(response, dict)):
|
||||
# Failed to login
|
||||
return False
|
||||
|
||||
|
@ -581,7 +574,7 @@ class NotifyMatrix(NotifyBase):
|
|||
)
|
||||
|
||||
# Build our URL
|
||||
path = '/join/{}'.format(NotifyBase.quote(room_id))
|
||||
path = '/join/{}'.format(NotifyMatrix.quote(room_id))
|
||||
|
||||
# Make our query
|
||||
postokay, _ = self._fetch(path, payload=payload)
|
||||
|
@ -612,7 +605,7 @@ class NotifyMatrix(NotifyBase):
|
|||
# If we reach here, we need to join the channel
|
||||
|
||||
# Build our URL
|
||||
path = '/join/{}'.format(NotifyBase.quote(room))
|
||||
path = '/join/{}'.format(NotifyMatrix.quote(room))
|
||||
|
||||
# Attempt to join the channel
|
||||
postokay, response = self._fetch(path, payload=payload)
|
||||
|
@ -695,7 +688,7 @@ class NotifyMatrix(NotifyBase):
|
|||
return list()
|
||||
|
||||
postokay, response = self._fetch(
|
||||
'/joined_rooms', payload=None, fn=requests.get)
|
||||
'/joined_rooms', payload=None, method='GET')
|
||||
if not postokay:
|
||||
# Failed to retrieve listings
|
||||
return list()
|
||||
|
@ -736,14 +729,14 @@ class NotifyMatrix(NotifyBase):
|
|||
# Make our request
|
||||
postokay, response = self._fetch(
|
||||
"/directory/room/{}".format(
|
||||
self.quote(room)), payload=None, fn=requests.get)
|
||||
NotifyMatrix.quote(room)), payload=None, method='GET')
|
||||
|
||||
if postokay:
|
||||
return response.get("room_id")
|
||||
|
||||
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
|
||||
the send() function cleaner and easier to maintain.
|
||||
|
@ -775,6 +768,9 @@ class NotifyMatrix(NotifyBase):
|
|||
# Our response object
|
||||
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
|
||||
# event
|
||||
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))
|
||||
|
||||
try:
|
||||
r = requests.post(
|
||||
r = fn(
|
||||
url,
|
||||
data=dumps(payload),
|
||||
params=params,
|
||||
|
@ -826,7 +822,7 @@ class NotifyMatrix(NotifyBase):
|
|||
elif r.status_code != requests.codes.ok:
|
||||
# We had a problem
|
||||
status_str = \
|
||||
NotifyBase.http_response_code_lookup(
|
||||
NotifyMatrix.http_response_code_lookup(
|
||||
r.status_code, MATRIX_HTTP_ERROR_MAP)
|
||||
|
||||
self.logger.warning(
|
||||
|
@ -877,22 +873,23 @@ class NotifyMatrix(NotifyBase):
|
|||
args = {
|
||||
'format': self.notify_format,
|
||||
'overflow': self.overflow_mode,
|
||||
'image': 'yes' if self.include_image else 'no',
|
||||
}
|
||||
|
||||
if self.webhook:
|
||||
args['webhook'] = self.webhook
|
||||
if self.mode:
|
||||
args['mode'] = self.mode
|
||||
|
||||
# Determine Authentication method
|
||||
# Determine Authentication
|
||||
auth = ''
|
||||
if self.user and self.password:
|
||||
auth = '{user}:{password}@'.format(
|
||||
user=self.quote(self.user, safe=''),
|
||||
password=self.quote(self.password, safe=''),
|
||||
user=NotifyMatrix.quote(self.user, safe=''),
|
||||
password=NotifyMatrix.quote(self.password, safe=''),
|
||||
)
|
||||
|
||||
elif self.user:
|
||||
auth = '{user}@'.format(
|
||||
user=self.quote(self.user, safe=''),
|
||||
user=NotifyMatrix.quote(self.user, safe=''),
|
||||
)
|
||||
|
||||
default_port = 443 if self.secure else 80
|
||||
|
@ -900,11 +897,11 @@ class NotifyMatrix(NotifyBase):
|
|||
return '{schema}://{auth}{hostname}{port}/{rooms}?{args}'.format(
|
||||
schema=self.secure_protocol if self.secure else self.protocol,
|
||||
auth=auth,
|
||||
hostname=self.host,
|
||||
hostname=NotifyMatrix.quote(self.host, safe=''),
|
||||
port='' if self.port is None
|
||||
or self.port == default_port else ':{}'.format(self.port),
|
||||
rooms=self.quote('/'.join(self.rooms)),
|
||||
args=self.urlencode(args),
|
||||
rooms=NotifyMatrix.quote('/'.join(self.rooms)),
|
||||
args=NotifyMatrix.urlencode(args),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
|
@ -921,15 +918,40 @@ class NotifyMatrix(NotifyBase):
|
|||
return results
|
||||
|
||||
# Get our rooms
|
||||
results['rooms'] = [
|
||||
NotifyBase.unquote(x) for x in filter(bool, NotifyBase.split_path(
|
||||
results['fullpath']))][0:]
|
||||
results['targets'] = NotifyMatrix.split_path(results['fullpath'])
|
||||
|
||||
# Use Thumbnail
|
||||
results['thumbnail'] = \
|
||||
parse_bool(results['qsd'].get('thumbnail', False))
|
||||
# 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'] += NotifyMatrix.parse_list(results['qsd']['to'])
|
||||
|
||||
# Webhook
|
||||
results['webhook'] = results['qsd'].get('webhook')
|
||||
# Thumbnail (old way)
|
||||
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
|
||||
|
|
|
@ -30,6 +30,8 @@ from json import dumps
|
|||
from .NotifyBase import NotifyBase
|
||||
from ..common import NotifyImageSize
|
||||
from ..common import NotifyType
|
||||
from ..utils import parse_bool
|
||||
from ..utils import parse_list
|
||||
|
||||
# Some Reference Locations:
|
||||
# - https://docs.mattermost.com/developer/webhooks-incoming.html
|
||||
|
@ -71,7 +73,8 @@ class NotifyMatterMost(NotifyBase):
|
|||
# Mattermost does not have a title
|
||||
title_maxlen = 0
|
||||
|
||||
def __init__(self, authtoken, channel=None, **kwargs):
|
||||
def __init__(self, authtoken, channels=None, include_image=True,
|
||||
**kwargs):
|
||||
"""
|
||||
Initialize MatterMost Object
|
||||
"""
|
||||
|
@ -88,27 +91,24 @@ class NotifyMatterMost(NotifyBase):
|
|||
|
||||
# Validate authtoken
|
||||
if not authtoken:
|
||||
self.logger.warning(
|
||||
'Missing MatterMost Authorization Token.'
|
||||
)
|
||||
raise TypeError(
|
||||
'Missing MatterMost Authorization Token.'
|
||||
)
|
||||
msg = 'Missing MatterMost Authorization Token.'
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
if not VALIDATE_AUTHTOKEN.match(authtoken):
|
||||
self.logger.warning(
|
||||
'Invalid MatterMost Authorization Token Specified.'
|
||||
)
|
||||
raise TypeError(
|
||||
'Invalid MatterMost Authorization Token Specified.'
|
||||
)
|
||||
msg = 'Invalid MatterMost Authorization Token Specified.'
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
# A Channel (optional)
|
||||
self.channel = channel
|
||||
# Optional Channels
|
||||
self.channels = parse_list(channels)
|
||||
|
||||
if not self.port:
|
||||
self.port = self.default_port
|
||||
|
||||
# Place a thumbnail image inline with the message body
|
||||
self.include_image = include_image
|
||||
|
||||
return
|
||||
|
||||
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
||||
|
@ -116,6 +116,9 @@ class NotifyMatterMost(NotifyBase):
|
|||
Perform MatterMost Notification
|
||||
"""
|
||||
|
||||
# Create a copy of our channels, otherwise place a dummy entry
|
||||
channels = list(self.channels) if self.channels else [None, ]
|
||||
|
||||
headers = {
|
||||
'User-Agent': self.app_id,
|
||||
'Content-Type': 'application/json'
|
||||
|
@ -124,67 +127,91 @@ class NotifyMatterMost(NotifyBase):
|
|||
# prepare JSON Object
|
||||
payload = {
|
||||
'text': body,
|
||||
'icon_url': self.image_url(notify_type),
|
||||
'icon_url': None,
|
||||
}
|
||||
|
||||
if self.user:
|
||||
payload['username'] = self.user
|
||||
# Acquire our image url if configured to do so
|
||||
image_url = None if not self.include_image \
|
||||
else self.image_url(notify_type)
|
||||
|
||||
else:
|
||||
payload['username'] = self.app_id
|
||||
if image_url:
|
||||
# Set our image configuration if told to do so
|
||||
payload['icon_url'] = image_url
|
||||
|
||||
if self.channel:
|
||||
payload['channel'] = self.channel
|
||||
# Set our user
|
||||
payload['username'] = self.user if self.user else self.app_id
|
||||
|
||||
url = '%s://%s:%d' % (self.schema, self.host, self.port)
|
||||
url += '/hooks/%s' % self.authtoken
|
||||
# For error tracking
|
||||
has_error = False
|
||||
|
||||
self.logger.debug('MatterMost POST URL: %s (cert_verify=%r)' % (
|
||||
url, self.verify_certificate,
|
||||
))
|
||||
self.logger.debug('MatterMost Payload: %s' % str(payload))
|
||||
while len(channels):
|
||||
# Pop a channel off of the list
|
||||
channel = channels.pop(0)
|
||||
|
||||
# Always call throttle before any remote server i/o is made
|
||||
self.throttle()
|
||||
if channel:
|
||||
payload['channel'] = channel
|
||||
|
||||
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 = \
|
||||
NotifyBase.http_response_code_lookup(r.status_code)
|
||||
url = '%s://%s:%d' % (self.schema, self.host, self.port)
|
||||
url += '/hooks/%s' % self.authtoken
|
||||
|
||||
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(
|
||||
'Failed to send MatterMost notification: '
|
||||
'{}{}error={}.'.format(
|
||||
status_str,
|
||||
', ' if status_str else '',
|
||||
r.status_code))
|
||||
'A Connection error occured sending MatterMost '
|
||||
'notification{}.'.format(
|
||||
'' if not channel
|
||||
else ' to channel {}'.format(channel)))
|
||||
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 False
|
||||
|
||||
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
|
||||
# Return our overall status
|
||||
return not has_error
|
||||
|
||||
def url(self):
|
||||
"""
|
||||
|
@ -195,18 +222,25 @@ class NotifyMatterMost(NotifyBase):
|
|||
args = {
|
||||
'format': self.notify_format,
|
||||
'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_schema = self.secure_protocol if self.secure else self.protocol
|
||||
|
||||
return '{schema}://{hostname}{port}/{authtoken}/?{args}'.format(
|
||||
schema=default_schema,
|
||||
hostname=self.host,
|
||||
hostname=NotifyMatterMost.quote(self.host, safe=''),
|
||||
port='' if not self.port or self.port == default_port
|
||||
else ':{}'.format(self.port),
|
||||
authtoken=self.quote(self.authtoken, safe=''),
|
||||
args=self.urlencode(args),
|
||||
authtoken=NotifyMatterMost.quote(self.authtoken, safe=''),
|
||||
args=NotifyMatterMost.urlencode(args),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
|
@ -222,15 +256,31 @@ class NotifyMatterMost(NotifyBase):
|
|||
# We're done early as we couldn't load the results
|
||||
return results
|
||||
|
||||
# Apply our settings now
|
||||
authtoken = NotifyBase.split_path(results['fullpath'])[0]
|
||||
try:
|
||||
# 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']):
|
||||
# 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
|
||||
results['channel'] = channel
|
||||
# Image manipulation
|
||||
results['include_image'] = \
|
||||
parse_bool(results['qsd'].get('image', False))
|
||||
|
||||
return results
|
||||
|
|
|
@ -103,12 +103,9 @@ class NotifyProwl(NotifyBase):
|
|||
self.priority = priority
|
||||
|
||||
if not VALIDATE_APIKEY.match(apikey):
|
||||
self.logger.warning(
|
||||
'The API key specified (%s) is invalid.' % apikey,
|
||||
)
|
||||
raise TypeError(
|
||||
'The API key specified (%s) is invalid.' % apikey,
|
||||
)
|
||||
msg = 'The API key specified ({}) is invalid.'.format(apikey)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
# Store the API key
|
||||
self.apikey = apikey
|
||||
|
@ -116,13 +113,12 @@ class NotifyProwl(NotifyBase):
|
|||
# Store the provider key (if specified)
|
||||
if providerkey:
|
||||
if not VALIDATE_PROVIDERKEY.match(providerkey):
|
||||
self.logger.warning(
|
||||
'The Provider key specified (%s) '
|
||||
'is invalid.' % providerkey)
|
||||
msg = \
|
||||
'The Provider key specified ({}) is invalid.' \
|
||||
.format(providerkey)
|
||||
|
||||
raise TypeError(
|
||||
'The Provider key specified (%s) '
|
||||
'is invalid.' % providerkey)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
# Store the Provider Key
|
||||
self.providerkey = providerkey
|
||||
|
@ -218,10 +214,10 @@ class NotifyProwl(NotifyBase):
|
|||
|
||||
return '{schema}://{apikey}/{providerkey}/?{args}'.format(
|
||||
schema=self.secure_protocol,
|
||||
apikey=self.quote(self.apikey, safe=''),
|
||||
apikey=NotifyProwl.quote(self.apikey, safe=''),
|
||||
providerkey='' if not self.providerkey
|
||||
else self.quote(self.providerkey, safe=''),
|
||||
args=self.urlencode(args),
|
||||
else NotifyProwl.quote(self.providerkey, safe=''),
|
||||
args=NotifyProwl.urlencode(args),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
|
@ -237,15 +233,16 @@ class NotifyProwl(NotifyBase):
|
|||
# We're done early as we couldn't load the 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:
|
||||
providerkey = [x for x in filter(
|
||||
bool, NotifyBase.split_path(results['fullpath']))][0]
|
||||
results['providerkey'] = \
|
||||
NotifyProwl.split_path(results['fullpath'])[0]
|
||||
|
||||
except (AttributeError, IndexError):
|
||||
providerkey = None
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
if 'priority' in results['qsd'] and len(results['qsd']['priority']):
|
||||
_map = {
|
||||
|
@ -263,7 +260,4 @@ class NotifyProwl(NotifyBase):
|
|||
# No priority was set
|
||||
pass
|
||||
|
||||
results['apikey'] = results['host']
|
||||
results['providerkey'] = providerkey
|
||||
|
||||
return results
|
||||
|
|
|
@ -23,22 +23,17 @@
|
|||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
|
||||
import re
|
||||
import six
|
||||
import requests
|
||||
from json import dumps
|
||||
|
||||
from .NotifyBase import NotifyBase
|
||||
from ..utils import GET_EMAIL_RE
|
||||
from ..common import NotifyType
|
||||
from ..utils import parse_list
|
||||
|
||||
# Flag used as a placeholder to sending to 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:
|
||||
PUSHBULLET_HTTP_ERROR_MAP = {
|
||||
401: 'Unauthorized - Invalid Token.',
|
||||
|
@ -65,25 +60,17 @@ class NotifyPushBullet(NotifyBase):
|
|||
# PushBullet uses the http protocol with JSON requests
|
||||
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
|
||||
"""
|
||||
super(NotifyPushBullet, self).__init__(**kwargs)
|
||||
|
||||
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.recipients = recipients
|
||||
|
||||
else:
|
||||
self.recipients = list()
|
||||
|
||||
if len(self.recipients) == 0:
|
||||
self.recipients = (PUSHBULLET_SEND_TO_ALL, )
|
||||
self.targets = parse_list(targets)
|
||||
if len(self.targets) == 0:
|
||||
self.targets = (PUSHBULLET_SEND_TO_ALL, )
|
||||
|
||||
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
||||
"""
|
||||
|
@ -99,10 +86,10 @@ class NotifyPushBullet(NotifyBase):
|
|||
# error tracking (used for function return)
|
||||
has_error = False
|
||||
|
||||
# Create a copy of the recipients list
|
||||
recipients = list(self.recipients)
|
||||
while len(recipients):
|
||||
recipient = recipients.pop(0)
|
||||
# Create a copy of the targets list
|
||||
targets = list(self.targets)
|
||||
while len(targets):
|
||||
recipient = targets.pop(0)
|
||||
|
||||
# prepare JSON Object
|
||||
payload = {
|
||||
|
@ -149,7 +136,7 @@ class NotifyPushBullet(NotifyBase):
|
|||
if r.status_code != requests.codes.ok:
|
||||
# We had a problem
|
||||
status_str = \
|
||||
NotifyBase.http_response_code_lookup(
|
||||
NotifyPushBullet.http_response_code_lookup(
|
||||
r.status_code, PUSHBULLET_HTTP_ERROR_MAP)
|
||||
|
||||
self.logger.warning(
|
||||
|
@ -195,17 +182,17 @@ class NotifyPushBullet(NotifyBase):
|
|||
'overflow': self.overflow_mode,
|
||||
}
|
||||
|
||||
recipients = '/'.join([self.quote(x) for x in self.recipients])
|
||||
if recipients == PUSHBULLET_SEND_TO_ALL:
|
||||
targets = '/'.join([NotifyPushBullet.quote(x) for x in self.targets])
|
||||
if targets == PUSHBULLET_SEND_TO_ALL:
|
||||
# keyword is reserved for internal usage only; it's safe to remove
|
||||
# it from the recipients list
|
||||
recipients = ''
|
||||
targets = ''
|
||||
|
||||
return '{schema}://{accesstoken}/{recipients}/?{args}'.format(
|
||||
return '{schema}://{accesstoken}/{targets}/?{args}'.format(
|
||||
schema=self.secure_protocol,
|
||||
accesstoken=self.quote(self.accesstoken, safe=''),
|
||||
recipients=recipients,
|
||||
args=self.urlencode(args))
|
||||
accesstoken=NotifyPushBullet.quote(self.accesstoken, safe=''),
|
||||
targets=targets,
|
||||
args=NotifyPushBullet.urlencode(args))
|
||||
|
||||
@staticmethod
|
||||
def parse_url(url):
|
||||
|
@ -220,10 +207,17 @@ class NotifyPushBullet(NotifyBase):
|
|||
# We're done early as we couldn't load the results
|
||||
return results
|
||||
|
||||
# Apply our settings now
|
||||
recipients = NotifyBase.unquote(results['fullpath'])
|
||||
# Fetch our targets
|
||||
results['targets'] = \
|
||||
NotifyPushBullet.split_path(results['fullpath'])
|
||||
|
||||
results['accesstoken'] = results['host']
|
||||
results['recipients'] = recipients
|
||||
# The 'to' makes it easier to use yaml configuration
|
||||
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
|
||||
|
|
|
@ -24,13 +24,13 @@
|
|||
# THE SOFTWARE.
|
||||
|
||||
import re
|
||||
import six
|
||||
import requests
|
||||
from json import dumps
|
||||
from itertools import chain
|
||||
|
||||
from .NotifyBase import NotifyBase
|
||||
from ..common import NotifyType
|
||||
from ..utils import parse_list
|
||||
|
||||
# Used to detect and parse channels
|
||||
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
|
||||
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):
|
||||
"""
|
||||
|
@ -71,7 +67,7 @@ class NotifyPushed(NotifyBase):
|
|||
# The maximum allowable characters allowed in the body per message
|
||||
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
|
||||
|
||||
|
@ -79,14 +75,14 @@ class NotifyPushed(NotifyBase):
|
|||
super(NotifyPushed, self).__init__(**kwargs)
|
||||
|
||||
if not app_key:
|
||||
raise TypeError(
|
||||
'An invalid Application Key was specified.'
|
||||
)
|
||||
msg = 'An invalid Application Key was specified.'
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
if not app_secret:
|
||||
raise TypeError(
|
||||
'An invalid Application Secret was specified.'
|
||||
)
|
||||
msg = 'An invalid Application Secret was specified.'
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
# Initialize channel list
|
||||
self.channels = list()
|
||||
|
@ -94,28 +90,15 @@ class NotifyPushed(NotifyBase):
|
|||
# Initialize user 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:
|
||||
for recipient in recipients:
|
||||
result = IS_CHANNEL.match(recipient)
|
||||
for target in parse_list(targets):
|
||||
result = IS_CHANNEL.match(target)
|
||||
if result:
|
||||
# store valid device
|
||||
self.channels.append(result.group('name'))
|
||||
continue
|
||||
|
||||
result = IS_USER_PUSHED_ID.match(recipient)
|
||||
result = IS_USER_PUSHED_ID.match(target)
|
||||
if result:
|
||||
# store valid room
|
||||
self.users.append(result.group('name'))
|
||||
|
@ -123,7 +106,7 @@ class NotifyPushed(NotifyBase):
|
|||
|
||||
self.logger.warning(
|
||||
'Dropped invalid channel/userid '
|
||||
'(%s) specified.' % recipient,
|
||||
'(%s) specified.' % target,
|
||||
)
|
||||
|
||||
# Store our data
|
||||
|
@ -229,7 +212,7 @@ class NotifyPushed(NotifyBase):
|
|||
if r.status_code != requests.codes.ok:
|
||||
# We had a problem
|
||||
status_str = \
|
||||
NotifyBase.http_response_code_lookup(r.status_code)
|
||||
NotifyPushed.http_response_code_lookup(r.status_code)
|
||||
|
||||
self.logger.warning(
|
||||
'Failed to send Pushed notification:'
|
||||
|
@ -269,16 +252,16 @@ class NotifyPushed(NotifyBase):
|
|||
|
||||
return '{schema}://{app_key}/{app_secret}/{targets}/?{args}'.format(
|
||||
schema=self.secure_protocol,
|
||||
app_key=self.quote(self.app_key, safe=''),
|
||||
app_secret=self.quote(self.app_secret, safe=''),
|
||||
app_key=NotifyPushed.quote(self.app_key, safe=''),
|
||||
app_secret=NotifyPushed.quote(self.app_secret, safe=''),
|
||||
targets='/'.join(
|
||||
[self.quote(x) for x in chain(
|
||||
[NotifyPushed.quote(x) for x in chain(
|
||||
# Channels are prefixed with a pound/hashtag symbol
|
||||
['#{}'.format(x) for x in self.channels],
|
||||
# Users are prefixed with an @ symbol
|
||||
['@{}'.format(x) for x in self.users],
|
||||
)]),
|
||||
args=self.urlencode(args))
|
||||
args=NotifyPushed.urlencode(args))
|
||||
|
||||
@staticmethod
|
||||
def parse_url(url):
|
||||
|
@ -296,30 +279,28 @@ class NotifyPushed(NotifyBase):
|
|||
# Apply our settings now
|
||||
|
||||
# The first token is stored in the hostname
|
||||
app_key = results['host']
|
||||
|
||||
# Initialize our recipients
|
||||
recipients = None
|
||||
app_key = NotifyPushed.unquote(results['host'])
|
||||
|
||||
entries = NotifyPushed.split_path(results['fullpath'])
|
||||
# Now fetch the remaining tokens
|
||||
try:
|
||||
app_secret = \
|
||||
[x for x in filter(bool, NotifyBase.split_path(
|
||||
results['fullpath']))][0]
|
||||
app_secret = entries.pop(0)
|
||||
|
||||
except (ValueError, AttributeError, IndexError):
|
||||
except IndexError:
|
||||
# Force some bad values that will get caught
|
||||
# in parsing later
|
||||
app_secret = None
|
||||
app_key = None
|
||||
|
||||
# Get our recipients
|
||||
recipients = \
|
||||
[x for x in filter(bool, NotifyBase.split_path(
|
||||
results['fullpath']))][1:]
|
||||
# Get our recipients (based on remaining entries)
|
||||
results['targets'] = entries
|
||||
|
||||
# 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_secret'] = app_secret
|
||||
results['recipients'] = recipients
|
||||
|
||||
return results
|
||||
|
|
|
@ -62,6 +62,12 @@ class NotifyPushjet(NotifyBase):
|
|||
"""
|
||||
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
|
||||
self.secret_key = secret_key
|
||||
|
||||
|
@ -107,11 +113,11 @@ class NotifyPushjet(NotifyBase):
|
|||
|
||||
return '{schema}://{secret_key}@{hostname}{port}/?{args}'.format(
|
||||
schema=self.secure_protocol if self.secure else self.protocol,
|
||||
secret_key=self.quote(self.secret_key, safe=''),
|
||||
hostname=self.host,
|
||||
secret_key=NotifyPushjet.quote(self.secret_key, safe=''),
|
||||
hostname=NotifyPushjet.quote(self.host, safe=''),
|
||||
port='' if self.port is None or self.port == default_port
|
||||
else ':{}'.format(self.port),
|
||||
args=self.urlencode(args),
|
||||
args=NotifyPushjet.urlencode(args),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
|
@ -133,11 +139,8 @@ class NotifyPushjet(NotifyBase):
|
|||
# We're done early as we couldn't load the results
|
||||
return results
|
||||
|
||||
if not results.get('user'):
|
||||
# a username is required
|
||||
return None
|
||||
|
||||
# Store it as it's value
|
||||
results['secret_key'] = results.get('user')
|
||||
results['secret_key'] = \
|
||||
NotifyPushjet.unquote(results.get('user'))
|
||||
|
||||
return results
|
||||
|
|
|
@ -24,11 +24,11 @@
|
|||
# THE SOFTWARE.
|
||||
|
||||
import re
|
||||
import six
|
||||
import requests
|
||||
|
||||
from .NotifyBase import NotifyBase
|
||||
from ..common import NotifyType
|
||||
from ..utils import parse_list
|
||||
|
||||
# Flag used as a placeholder to sending to all devices
|
||||
PUSHOVER_SEND_TO_ALL = 'ALL_DEVICES'
|
||||
|
@ -60,9 +60,6 @@ PUSHOVER_PRIORITIES = (
|
|||
PushoverPriority.EMERGENCY,
|
||||
)
|
||||
|
||||
# Used to break path apart into list of devices
|
||||
DEVICE_LIST_DELIM = re.compile(r'[ \t\r\n,\\/]+')
|
||||
|
||||
# Extend HTTP Error Messages
|
||||
PUSHOVER_HTTP_ERROR_MAP = {
|
||||
401: 'Unauthorized - Invalid Token.',
|
||||
|
@ -92,7 +89,7 @@ class NotifyPushover(NotifyBase):
|
|||
# The maximum allowable characters allowed in the body per message
|
||||
body_maxlen = 512
|
||||
|
||||
def __init__(self, token, devices=None, priority=None, **kwargs):
|
||||
def __init__(self, token, targets=None, priority=None, **kwargs):
|
||||
"""
|
||||
Initialize Pushover Object
|
||||
"""
|
||||
|
@ -104,30 +101,18 @@ class NotifyPushover(NotifyBase):
|
|||
|
||||
except AttributeError:
|
||||
# Token was None
|
||||
self.logger.warning('No API Token was specified.')
|
||||
raise TypeError('No API Token was specified.')
|
||||
msg = 'No API Token was specified.'
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
if not VALIDATE_TOKEN.match(self.token):
|
||||
self.logger.warning(
|
||||
'The API Token specified (%s) is invalid.' % token,
|
||||
)
|
||||
raise TypeError(
|
||||
'The API Token specified (%s) is invalid.' % token,
|
||||
)
|
||||
msg = 'The API Token specified (%s) is invalid.'.format(token)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
if isinstance(devices, six.string_types):
|
||||
self.devices = [x for x in filter(bool, DEVICE_LIST_DELIM.split(
|
||||
devices,
|
||||
))]
|
||||
|
||||
elif isinstance(devices, (set, tuple, list)):
|
||||
self.devices = devices
|
||||
|
||||
else:
|
||||
self.devices = list()
|
||||
|
||||
if len(self.devices) == 0:
|
||||
self.devices = (PUSHOVER_SEND_TO_ALL, )
|
||||
self.targets = parse_list(targets)
|
||||
if len(self.targets) == 0:
|
||||
self.targets = (PUSHOVER_SEND_TO_ALL, )
|
||||
|
||||
# The Priority of the message
|
||||
if priority not in PUSHOVER_PRIORITIES:
|
||||
|
@ -137,16 +122,14 @@ class NotifyPushover(NotifyBase):
|
|||
self.priority = priority
|
||||
|
||||
if not self.user:
|
||||
self.logger.warning('No user was specified.')
|
||||
raise TypeError('No user was specified.')
|
||||
msg = 'No user was specified.'
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
if not VALIDATE_USERGROUP.match(self.user):
|
||||
self.logger.warning(
|
||||
'The user/group specified (%s) is invalid.' % self.user,
|
||||
)
|
||||
raise TypeError(
|
||||
'The user/group specified (%s) is invalid.' % self.user,
|
||||
)
|
||||
msg = 'The user/group specified (%s) is invalid.' % self.user
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
||||
"""
|
||||
|
@ -163,7 +146,7 @@ class NotifyPushover(NotifyBase):
|
|||
has_error = False
|
||||
|
||||
# Create a copy of the devices list
|
||||
devices = list(self.devices)
|
||||
devices = list(self.targets)
|
||||
while len(devices):
|
||||
device = devices.pop(0)
|
||||
|
||||
|
@ -205,7 +188,7 @@ class NotifyPushover(NotifyBase):
|
|||
if r.status_code != requests.codes.ok:
|
||||
# We had a problem
|
||||
status_str = \
|
||||
NotifyBase.http_response_code_lookup(
|
||||
NotifyPushover.http_response_code_lookup(
|
||||
r.status_code, PUSHOVER_HTTP_ERROR_MAP)
|
||||
|
||||
self.logger.warning(
|
||||
|
@ -262,7 +245,10 @@ class NotifyPushover(NotifyBase):
|
|||
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:
|
||||
# keyword is reserved for internal usage only; it's safe to remove
|
||||
# it from the devices list
|
||||
|
@ -271,10 +257,11 @@ class NotifyPushover(NotifyBase):
|
|||
return '{schema}://{auth}{token}/{devices}/?{args}'.format(
|
||||
schema=self.secure_protocol,
|
||||
auth='' if not self.user
|
||||
else '{user}@'.format(user=self.quote(self.user, safe='')),
|
||||
token=self.quote(self.token, safe=''),
|
||||
else '{user}@'.format(
|
||||
user=NotifyPushover.quote(self.user, safe='')),
|
||||
token=NotifyPushover.quote(self.token, safe=''),
|
||||
devices=devices,
|
||||
args=self.urlencode(args))
|
||||
args=NotifyPushover.urlencode(args))
|
||||
|
||||
@staticmethod
|
||||
def parse_url(url):
|
||||
|
@ -289,21 +276,14 @@ class NotifyPushover(NotifyBase):
|
|||
# We're done early as we couldn't load the results
|
||||
return results
|
||||
|
||||
# Apply our settings now
|
||||
devices = NotifyBase.unquote(results['fullpath'])
|
||||
|
||||
# Set our priority
|
||||
if 'priority' in results['qsd'] and len(results['qsd']['priority']):
|
||||
_map = {
|
||||
'l': PushoverPriority.LOW,
|
||||
'-2': PushoverPriority.LOW,
|
||||
'm': PushoverPriority.MODERATE,
|
||||
'-1': PushoverPriority.MODERATE,
|
||||
'n': PushoverPriority.NORMAL,
|
||||
'0': PushoverPriority.NORMAL,
|
||||
'h': PushoverPriority.HIGH,
|
||||
'1': PushoverPriority.HIGH,
|
||||
'e': PushoverPriority.EMERGENCY,
|
||||
'2': PushoverPriority.EMERGENCY,
|
||||
}
|
||||
try:
|
||||
results['priority'] = \
|
||||
|
@ -313,7 +293,15 @@ class NotifyPushover(NotifyBase):
|
|||
# No priority was set
|
||||
pass
|
||||
|
||||
results['token'] = results['host']
|
||||
results['devices'] = devices
|
||||
# Retrieve all of our targets
|
||||
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
|
||||
|
|
|
@ -24,13 +24,13 @@
|
|||
# THE SOFTWARE.
|
||||
|
||||
import re
|
||||
import six
|
||||
import requests
|
||||
from json import loads
|
||||
from itertools import chain
|
||||
|
||||
from .NotifyBase import NotifyBase
|
||||
from ..common import NotifyType
|
||||
from ..utils import parse_list
|
||||
|
||||
IS_CHANNEL = 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
|
||||
body_maxlen = 200
|
||||
|
||||
def __init__(self, recipients=None, **kwargs):
|
||||
def __init__(self, targets=None, **kwargs):
|
||||
"""
|
||||
Initialize Notify Rocket.Chat Object
|
||||
"""
|
||||
super(NotifyRocketChat, self).__init__(**kwargs)
|
||||
|
||||
if self.secure:
|
||||
self.schema = 'https'
|
||||
|
||||
else:
|
||||
self.schema = 'http'
|
||||
# Set our schema
|
||||
self.schema = 'https' if self.secure else 'http'
|
||||
|
||||
# Prepare our URL
|
||||
self.api_url = '%s://%s' % (self.schema, self.host)
|
||||
|
@ -98,17 +95,6 @@ class NotifyRocketChat(NotifyBase):
|
|||
# Initialize room 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):
|
||||
# Username & Password is required for Rocket Chat to work
|
||||
raise TypeError(
|
||||
|
@ -116,7 +102,7 @@ class NotifyRocketChat(NotifyBase):
|
|||
)
|
||||
|
||||
# Validate recipients and drop bad ones:
|
||||
for recipient in recipients:
|
||||
for recipient in parse_list(targets):
|
||||
result = IS_CHANNEL.match(recipient)
|
||||
if result:
|
||||
# store valid device
|
||||
|
@ -135,9 +121,9 @@ class NotifyRocketChat(NotifyBase):
|
|||
)
|
||||
|
||||
if len(self.rooms) == 0 and len(self.channels) == 0:
|
||||
raise TypeError(
|
||||
'No Rocket.Chat room and/or channels specified to notify.'
|
||||
)
|
||||
msg = '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)
|
||||
self.headers = {}
|
||||
|
@ -155,8 +141,8 @@ class NotifyRocketChat(NotifyBase):
|
|||
|
||||
# Determine Authentication
|
||||
auth = '{user}:{password}@'.format(
|
||||
user=self.quote(self.user, safe=''),
|
||||
password=self.quote(self.password, safe=''),
|
||||
user=NotifyRocketChat.quote(self.user, safe=''),
|
||||
password=NotifyRocketChat.quote(self.password, safe=''),
|
||||
)
|
||||
|
||||
default_port = 443 if self.secure else 80
|
||||
|
@ -164,17 +150,17 @@ class NotifyRocketChat(NotifyBase):
|
|||
return '{schema}://{auth}{hostname}{port}/{targets}/?{args}'.format(
|
||||
schema=self.secure_protocol if self.secure else self.protocol,
|
||||
auth=auth,
|
||||
hostname=self.host,
|
||||
hostname=NotifyRocketChat.quote(self.host, safe=''),
|
||||
port='' if self.port is None or self.port == default_port
|
||||
else ':{}'.format(self.port),
|
||||
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
|
||||
['#{}'.format(x) for x in self.channels],
|
||||
# Rooms are as is
|
||||
self.rooms,
|
||||
)]),
|
||||
args=self.urlencode(args),
|
||||
args=NotifyRocketChat.urlencode(args),
|
||||
)
|
||||
|
||||
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
||||
|
@ -252,7 +238,7 @@ class NotifyRocketChat(NotifyBase):
|
|||
if r.status_code != requests.codes.ok:
|
||||
# We had a problem
|
||||
status_str = \
|
||||
NotifyBase.http_response_code_lookup(
|
||||
NotifyRocketChat.http_response_code_lookup(
|
||||
r.status_code, RC_HTTP_ERROR_MAP)
|
||||
|
||||
self.logger.warning(
|
||||
|
@ -300,7 +286,7 @@ class NotifyRocketChat(NotifyBase):
|
|||
if r.status_code != requests.codes.ok:
|
||||
# We had a problem
|
||||
status_str = \
|
||||
NotifyBase.http_response_code_lookup(
|
||||
NotifyRocketChat.http_response_code_lookup(
|
||||
r.status_code, RC_HTTP_ERROR_MAP)
|
||||
|
||||
self.logger.warning(
|
||||
|
@ -353,7 +339,7 @@ class NotifyRocketChat(NotifyBase):
|
|||
if r.status_code != requests.codes.ok:
|
||||
# We had a problem
|
||||
status_str = \
|
||||
NotifyBase.http_response_code_lookup(
|
||||
NotifyRocketChat.http_response_code_lookup(
|
||||
r.status_code, RC_HTTP_ERROR_MAP)
|
||||
|
||||
self.logger.warning(
|
||||
|
@ -396,7 +382,12 @@ class NotifyRocketChat(NotifyBase):
|
|||
# We're done early as we couldn't load the results
|
||||
return results
|
||||
|
||||
# Apply our settings now
|
||||
results['recipients'] = NotifyBase.unquote(results['fullpath'])
|
||||
# Apply our targets
|
||||
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
|
||||
|
|
|
@ -32,12 +32,14 @@
|
|||
# These are important <---^----------------------------------------^
|
||||
#
|
||||
import re
|
||||
import six
|
||||
import requests
|
||||
from json import dumps
|
||||
|
||||
from .NotifyBase import NotifyBase
|
||||
from ..common import NotifyImageSize
|
||||
from ..common import NotifyType
|
||||
from ..utils import parse_bool
|
||||
|
||||
# Token required as part of the API request
|
||||
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}')
|
||||
|
||||
|
||||
class RyverWebhookType(object):
|
||||
class RyverWebhookMode(object):
|
||||
"""
|
||||
Ryver supports to webhook types
|
||||
Ryver supports to webhook modes
|
||||
"""
|
||||
SLACK = 'slack'
|
||||
RYVER = 'ryver'
|
||||
|
||||
|
||||
# Define the types in a list for validation purposes
|
||||
RYVER_WEBHOOK_TYPES = (
|
||||
RyverWebhookType.SLACK,
|
||||
RyverWebhookType.RYVER,
|
||||
RYVER_WEBHOOK_MODES = (
|
||||
RyverWebhookMode.SLACK,
|
||||
RyverWebhookMode.RYVER,
|
||||
)
|
||||
|
||||
|
||||
|
@ -84,39 +86,44 @@ class NotifyRyver(NotifyBase):
|
|||
# The maximum allowable characters allowed in the body per message
|
||||
body_maxlen = 1000
|
||||
|
||||
def __init__(self, organization, token, webhook=RyverWebhookType.RYVER,
|
||||
**kwargs):
|
||||
def __init__(self, organization, token, mode=RyverWebhookMode.RYVER,
|
||||
include_image=True, **kwargs):
|
||||
"""
|
||||
Initialize Ryver Object
|
||||
"""
|
||||
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()):
|
||||
self.logger.warning(
|
||||
'The token specified (%s) is invalid.' % token,
|
||||
)
|
||||
raise TypeError(
|
||||
'The token specified (%s) is invalid.' % token,
|
||||
)
|
||||
msg = 'The Ryver token specified ({}) is invalid.'\
|
||||
.format(token)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
if not VALIDATE_ORG.match(organization.strip()):
|
||||
self.logger.warning(
|
||||
'The organization specified (%s) is invalid.' % organization,
|
||||
)
|
||||
raise TypeError(
|
||||
'The organization specified (%s) is invalid.' % organization,
|
||||
)
|
||||
msg = 'The Ryver organization specified ({}) is invalid.'\
|
||||
.format(organization)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
# Store our webhook type
|
||||
self.webhook = webhook
|
||||
# Store our webhook mode
|
||||
self.mode = None \
|
||||
if not isinstance(mode, six.string_types) else mode.lower()
|
||||
|
||||
if self.webhook not in RYVER_WEBHOOK_TYPES:
|
||||
self.logger.warning(
|
||||
'The webhook specified (%s) is invalid.' % webhook,
|
||||
)
|
||||
raise TypeError(
|
||||
'The webhook specified (%s) is invalid.' % webhook,
|
||||
)
|
||||
if self.mode not in RYVER_WEBHOOK_MODES:
|
||||
msg = 'The Ryver webhook mode specified ({}) is invalid.' \
|
||||
.format(mode)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
# The organization associated with the account
|
||||
self.organization = organization.strip()
|
||||
|
@ -124,6 +131,9 @@ class NotifyRyver(NotifyBase):
|
|||
# The token associated with the account
|
||||
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:
|
||||
# https://api.slack.com/docs/message-formatting
|
||||
self._re_formatting_map = {
|
||||
|
@ -151,7 +161,7 @@ class NotifyRyver(NotifyBase):
|
|||
'Content-Type': 'application/json',
|
||||
}
|
||||
|
||||
if self.webhook == RyverWebhookType.SLACK:
|
||||
if self.mode == RyverWebhookMode.SLACK:
|
||||
# Perform Slack formatting
|
||||
title = self._re_formatting_rules.sub( # pragma: no branch
|
||||
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,
|
||||
)
|
||||
|
||||
url = 'https://%s.ryver.com/application/webhook/%s' % (
|
||||
url = 'https://{}.ryver.com/application/webhook/{}'.format(
|
||||
self.organization,
|
||||
self.token,
|
||||
)
|
||||
|
||||
# prepare JSON Object
|
||||
payload = {
|
||||
"body": body if not title else '**{}**\r\n{}'.format(title, body),
|
||||
'body': body if not title else '**{}**\r\n{}'.format(title, body),
|
||||
'createSource': {
|
||||
"displayName": self.user,
|
||||
"avatar": self.image_url(notify_type),
|
||||
'displayName': self.user,
|
||||
'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)' % (
|
||||
url, self.verify_certificate,
|
||||
))
|
||||
|
@ -229,22 +246,23 @@ class NotifyRyver(NotifyBase):
|
|||
args = {
|
||||
'format': self.notify_format,
|
||||
'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
|
||||
botname = ''
|
||||
if self.user:
|
||||
botname = '{botname}@'.format(
|
||||
botname=self.quote(self.user, safe=''),
|
||||
botname=NotifyRyver.quote(self.user, safe=''),
|
||||
)
|
||||
|
||||
return '{schema}://{botname}{organization}/{token}/?{args}'.format(
|
||||
schema=self.secure_protocol,
|
||||
botname=botname,
|
||||
organization=self.quote(self.organization, safe=''),
|
||||
token=self.quote(self.token, safe=''),
|
||||
args=self.urlencode(args),
|
||||
organization=NotifyRyver.quote(self.organization, safe=''),
|
||||
token=NotifyRyver.quote(self.token, safe=''),
|
||||
args=NotifyRyver.urlencode(args),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
|
@ -254,31 +272,41 @@ class NotifyRyver(NotifyBase):
|
|||
us to substantiate this object.
|
||||
|
||||
"""
|
||||
|
||||
results = NotifyBase.parse_url(url)
|
||||
|
||||
if not results:
|
||||
# We're done early as we couldn't load the results
|
||||
return results
|
||||
|
||||
# Apply our settings now
|
||||
|
||||
# The first token is stored in the hostname
|
||||
organization = results['host']
|
||||
results['organization'] = NotifyRyver.unquote(results['host'])
|
||||
|
||||
# Now fetch the remaining tokens
|
||||
try:
|
||||
token = [x for x in filter(
|
||||
bool, NotifyBase.split_path(results['fullpath']))][0]
|
||||
results['token'] = \
|
||||
NotifyRyver.split_path(results['fullpath'])[0]
|
||||
|
||||
except (ValueError, AttributeError, IndexError):
|
||||
# We're done
|
||||
return None
|
||||
except IndexError:
|
||||
# no token
|
||||
results['token'] = None
|
||||
|
||||
if 'webhook' in results['qsd'] and len(results['qsd']['webhook']):
|
||||
results['webhook'] = results['qsd']\
|
||||
.get('webhook', RyverWebhookType.RYVER).lower()
|
||||
if 'webhook' in results['qsd']:
|
||||
# Deprication Notice issued for v0.7.5
|
||||
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
|
||||
results['token'] = token
|
||||
# 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', RyverWebhookMode.RYVER))
|
||||
|
||||
# use image= for consistency with the other plugins
|
||||
results['include_image'] = \
|
||||
parse_bool(results['qsd'].get('image', True))
|
||||
|
||||
return results
|
||||
|
|
|
@ -24,7 +24,6 @@
|
|||
# THE SOFTWARE.
|
||||
|
||||
import re
|
||||
import six
|
||||
import hmac
|
||||
import requests
|
||||
from hashlib import sha256
|
||||
|
@ -35,6 +34,7 @@ from itertools import chain
|
|||
|
||||
from .NotifyBase import NotifyBase
|
||||
from ..common import NotifyType
|
||||
from ..utils import parse_list
|
||||
|
||||
# Some Phone Number Detection
|
||||
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
|
||||
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
|
||||
# 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
|
||||
|
@ -97,26 +93,26 @@ class NotifySNS(NotifyBase):
|
|||
title_maxlen = 0
|
||||
|
||||
def __init__(self, access_key_id, secret_access_key, region_name,
|
||||
recipients=None, **kwargs):
|
||||
targets=None, **kwargs):
|
||||
"""
|
||||
Initialize Notify AWS SNS Object
|
||||
"""
|
||||
super(NotifySNS, self).__init__(**kwargs)
|
||||
|
||||
if not access_key_id:
|
||||
raise TypeError(
|
||||
'An invalid AWS Access Key ID was specified.'
|
||||
)
|
||||
msg = 'An invalid AWS Access Key ID was specified.'
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
if not secret_access_key:
|
||||
raise TypeError(
|
||||
'An invalid AWS Secret Access Key was specified.'
|
||||
)
|
||||
msg = '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)):
|
||||
raise TypeError(
|
||||
'An invalid AWS Region was specified.'
|
||||
)
|
||||
msg = 'An invalid AWS Region was specified.'
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
# Initialize topic list
|
||||
self.topics = list()
|
||||
|
@ -147,20 +143,12 @@ class NotifySNS(NotifyBase):
|
|||
self.aws_auth_algorithm = 'AWS4-HMAC-SHA256'
|
||||
self.aws_auth_request = 'aws4_request'
|
||||
|
||||
if recipients is None:
|
||||
recipients = []
|
||||
# Get our targets
|
||||
targets = parse_list(targets)
|
||||
|
||||
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 = []
|
||||
|
||||
# Validate recipients and drop bad ones:
|
||||
for recipient in recipients:
|
||||
result = IS_PHONE_NO.match(recipient)
|
||||
# Validate targets and drop bad ones:
|
||||
for target in targets:
|
||||
result = IS_PHONE_NO.match(target)
|
||||
if result:
|
||||
# Further check our phone # for it's digit count
|
||||
# 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:
|
||||
self.logger.warning(
|
||||
'Dropped invalid phone # '
|
||||
'(%s) specified.' % recipient,
|
||||
'(%s) specified.' % target,
|
||||
)
|
||||
continue
|
||||
|
||||
|
@ -177,7 +165,7 @@ class NotifySNS(NotifyBase):
|
|||
self.phone.append('+{}'.format(result))
|
||||
continue
|
||||
|
||||
result = IS_TOPIC.match(recipient)
|
||||
result = IS_TOPIC.match(target)
|
||||
if result:
|
||||
# store valid topic
|
||||
self.topics.append(result.group('name'))
|
||||
|
@ -185,12 +173,12 @@ class NotifySNS(NotifyBase):
|
|||
|
||||
self.logger.warning(
|
||||
'Dropped invalid phone/topic '
|
||||
'(%s) specified.' % recipient,
|
||||
'(%s) specified.' % target,
|
||||
)
|
||||
|
||||
if len(self.phone) == 0 and len(self.topics) == 0:
|
||||
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):
|
||||
"""
|
||||
|
@ -278,7 +266,7 @@ class NotifySNS(NotifyBase):
|
|||
self.throttle()
|
||||
|
||||
# Convert our payload from a dict() into a urlencoded string
|
||||
payload = self.urlencode(payload)
|
||||
payload = NotifySNS.urlencode(payload)
|
||||
|
||||
# Prepare our Notification URL
|
||||
# Prepare our AWS Headers based on our payload
|
||||
|
@ -300,7 +288,7 @@ class NotifySNS(NotifyBase):
|
|||
if r.status_code != requests.codes.ok:
|
||||
# We had a problem
|
||||
status_str = \
|
||||
NotifyBase.http_response_code_lookup(
|
||||
NotifySNS.http_response_code_lookup(
|
||||
r.status_code, AWS_HTTP_ERROR_MAP)
|
||||
|
||||
self.logger.warning(
|
||||
|
@ -541,17 +529,18 @@ class NotifySNS(NotifyBase):
|
|||
return '{schema}://{key_id}/{key_secret}/{region}/{targets}/'\
|
||||
'?{args}'.format(
|
||||
schema=self.secure_protocol,
|
||||
key_id=self.quote(self.aws_access_key_id, safe=''),
|
||||
key_secret=self.quote(self.aws_secret_access_key, safe=''),
|
||||
region=self.quote(self.aws_region_name, safe=''),
|
||||
key_id=NotifySNS.quote(self.aws_access_key_id, safe=''),
|
||||
key_secret=NotifySNS.quote(
|
||||
self.aws_secret_access_key, safe=''),
|
||||
region=NotifySNS.quote(self.aws_region_name, safe=''),
|
||||
targets='/'.join(
|
||||
[self.quote(x) for x in chain(
|
||||
[NotifySNS.quote(x) for x in chain(
|
||||
# Phone # are prefixed with a plus symbol
|
||||
['+{}'.format(x) for x in self.phone],
|
||||
# Topics are prefixed with a pound/hashtag symbol
|
||||
['#{}'.format(x) for x in self.topics],
|
||||
)]),
|
||||
args=self.urlencode(args),
|
||||
args=NotifySNS.urlencode(args),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
|
@ -567,12 +556,8 @@ class NotifySNS(NotifyBase):
|
|||
# We're done early as we couldn't load the results
|
||||
return results
|
||||
|
||||
#
|
||||
# Apply our settings now
|
||||
#
|
||||
|
||||
# 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
|
||||
# means it is of variable length after the hostname. Since we require
|
||||
|
@ -586,9 +571,12 @@ class NotifySNS(NotifyBase):
|
|||
# accumulated data.
|
||||
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
|
||||
index = 0
|
||||
for i, entry in enumerate(NotifyBase.split_path(results['fullpath'])):
|
||||
for i, entry in enumerate(entries):
|
||||
|
||||
# Are we at the region yet?
|
||||
result = IS_REGION.match(entry)
|
||||
|
@ -615,9 +603,13 @@ class NotifySNS(NotifyBase):
|
|||
secret_access_key_parts.append(entry)
|
||||
|
||||
# Section 2: Get our Recipients (basically all remaining entries)
|
||||
results['recipients'] = [
|
||||
NotifyBase.unquote(x) for x in filter(bool, NotifyBase.split_path(
|
||||
results['fullpath']))][index:]
|
||||
results['targets'] = entries[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)
|
||||
results['region_name'] = region_name
|
||||
|
|
|
@ -45,6 +45,7 @@ from .NotifyBase import NotifyBase
|
|||
from ..common import NotifyImageSize
|
||||
from ..common import NotifyType
|
||||
from ..common import NotifyFormat
|
||||
from ..utils import parse_bool
|
||||
|
||||
# Token required as part of the API request
|
||||
# /AAAAAAAAA/........./........................
|
||||
|
@ -101,41 +102,51 @@ class NotifySlack(NotifyBase):
|
|||
|
||||
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
|
||||
"""
|
||||
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()):
|
||||
self.logger.warning(
|
||||
'The first API Token specified (%s) is invalid.' % token_a,
|
||||
)
|
||||
raise TypeError(
|
||||
'The first API Token specified (%s) is invalid.' % token_a,
|
||||
)
|
||||
msg = 'The first API token specified ({}) is invalid.'\
|
||||
.format(token_a)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
# The token associated with the account
|
||||
self.token_a = token_a.strip()
|
||||
|
||||
if not VALIDATE_TOKEN_B.match(token_b.strip()):
|
||||
self.logger.warning(
|
||||
'The second API Token specified (%s) is invalid.' % token_b,
|
||||
)
|
||||
raise TypeError(
|
||||
'The second API Token specified (%s) is invalid.' % token_b,
|
||||
)
|
||||
msg = 'The second API token specified ({}) is invalid.'\
|
||||
.format(token_b)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
# The token associated with the account
|
||||
self.token_b = token_b.strip()
|
||||
|
||||
if not VALIDATE_TOKEN_C.match(token_c.strip()):
|
||||
self.logger.warning(
|
||||
'The third API Token specified (%s) is invalid.' % token_c,
|
||||
)
|
||||
raise TypeError(
|
||||
'The third API Token specified (%s) is invalid.' % token_c,
|
||||
)
|
||||
msg = 'The third API token specified ({}) is invalid.'\
|
||||
.format(token_c)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
# The token associated with the account
|
||||
self.token_c = token_c.strip()
|
||||
|
@ -144,20 +155,21 @@ class NotifySlack(NotifyBase):
|
|||
self.logger.warning(
|
||||
'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(
|
||||
channels,
|
||||
targets,
|
||||
))]
|
||||
|
||||
elif isinstance(channels, (set, tuple, list)):
|
||||
self.channels = channels
|
||||
elif isinstance(targets, (set, tuple, list)):
|
||||
self.channels = targets
|
||||
|
||||
else:
|
||||
self.channels = list()
|
||||
|
||||
if len(self.channels) == 0:
|
||||
self.logger.warning('No channel(s) were specified.')
|
||||
raise TypeError('No channel(s) were specified.')
|
||||
msg = 'No channel(s) were specified.'
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
# Formatting requirements are defined here:
|
||||
# https://api.slack.com/docs/message-formatting
|
||||
|
@ -176,6 +188,9 @@ class NotifySlack(NotifyBase):
|
|||
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):
|
||||
"""
|
||||
Perform Slack Notification
|
||||
|
@ -203,8 +218,6 @@ class NotifySlack(NotifyBase):
|
|||
self.token_c,
|
||||
)
|
||||
|
||||
image_url = self.image_url(notify_type)
|
||||
|
||||
# Create a copy of the channel list
|
||||
channels = list(self.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:
|
||||
payload['attachments'][0]['footer_icon'] = image_url
|
||||
|
||||
|
@ -267,7 +284,7 @@ class NotifySlack(NotifyBase):
|
|||
if r.status_code != requests.codes.ok:
|
||||
# We had a problem
|
||||
status_str = \
|
||||
NotifyBase.http_response_code_lookup(
|
||||
NotifySlack.http_response_code_lookup(
|
||||
r.status_code, SLACK_HTTP_ERROR_MAP)
|
||||
|
||||
self.logger.warning(
|
||||
|
@ -311,25 +328,26 @@ class NotifySlack(NotifyBase):
|
|||
args = {
|
||||
'format': self.notify_format,
|
||||
'overflow': self.overflow_mode,
|
||||
'image': 'yes' if self.include_image else 'no',
|
||||
}
|
||||
|
||||
# Determine if there is a botname present
|
||||
botname = ''
|
||||
if self.user:
|
||||
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}/'\
|
||||
'?{args}'.format(
|
||||
schema=self.secure_protocol,
|
||||
botname=botname,
|
||||
token_a=self.quote(self.token_a, safe=''),
|
||||
token_b=self.quote(self.token_b, safe=''),
|
||||
token_c=self.quote(self.token_c, safe=''),
|
||||
token_a=NotifySlack.quote(self.token_a, safe=''),
|
||||
token_b=NotifySlack.quote(self.token_b, safe=''),
|
||||
token_c=NotifySlack.quote(self.token_c, safe=''),
|
||||
targets='/'.join(
|
||||
[self.quote(x, safe='') for x in self.channels]),
|
||||
args=self.urlencode(args),
|
||||
[NotifySlack.quote(x, safe='') for x in self.channels]),
|
||||
args=NotifySlack.urlencode(args),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
|
@ -345,26 +363,39 @@ class NotifySlack(NotifyBase):
|
|||
# We're done early as we couldn't load the results
|
||||
return results
|
||||
|
||||
# Apply our settings now
|
||||
# Get unquoted entries
|
||||
entries = NotifySlack.split_path(results['fullpath'])
|
||||
|
||||
# The first token is stored in the hostname
|
||||
token_a = results['host']
|
||||
results['token_a'] = NotifySlack.unquote(results['host'])
|
||||
|
||||
# Now fetch the remaining tokens
|
||||
try:
|
||||
token_b, token_c = [x for x in filter(
|
||||
bool, NotifyBase.split_path(results['fullpath']))][0:2]
|
||||
results['token_b'] = entries.pop(0)
|
||||
|
||||
except (ValueError, AttributeError, IndexError):
|
||||
except IndexError:
|
||||
# We're done
|
||||
return None
|
||||
results['token_b'] = None
|
||||
|
||||
channels = [x for x in filter(
|
||||
bool, NotifyBase.split_path(results['fullpath']))][2:]
|
||||
try:
|
||||
results['token_c'] = entries.pop(0)
|
||||
|
||||
results['token_a'] = token_a
|
||||
results['token_b'] = token_b
|
||||
results['token_c'] = token_c
|
||||
results['channels'] = channels
|
||||
except IndexError:
|
||||
# We're done
|
||||
results['token_c'] = None
|
||||
|
||||
# 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
|
||||
|
|
|
@ -107,7 +107,7 @@ class NotifyTelegram(NotifyBase):
|
|||
# The maximum allowable characters allowed in the body per message
|
||||
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):
|
||||
"""
|
||||
Initialize Telegram Object
|
||||
|
@ -133,19 +133,19 @@ class NotifyTelegram(NotifyBase):
|
|||
self.bot_token = result.group('key')
|
||||
|
||||
# Parse our list
|
||||
self.chat_ids = parse_list(chat_ids)
|
||||
self.targets = parse_list(targets)
|
||||
|
||||
if self.user:
|
||||
# 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()
|
||||
if _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.'
|
||||
self.logger.warning(err)
|
||||
raise TypeError(err)
|
||||
|
@ -168,14 +168,25 @@ class NotifyTelegram(NotifyBase):
|
|||
'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)
|
||||
|
||||
if not path:
|
||||
# No image to send
|
||||
self.logger.debug(
|
||||
'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 = {
|
||||
'chat_id': chat_id,
|
||||
|
@ -196,7 +207,7 @@ class NotifyTelegram(NotifyBase):
|
|||
if r.status_code != requests.codes.ok:
|
||||
# We had a problem
|
||||
status_str = \
|
||||
NotifyBase.http_response_code_lookup(r.status_code)
|
||||
NotifyTelegram.http_response_code_lookup(r.status_code)
|
||||
|
||||
self.logger.warning(
|
||||
'Failed to send Telegram Image: '
|
||||
|
@ -248,7 +259,7 @@ class NotifyTelegram(NotifyBase):
|
|||
if r.status_code != requests.codes.ok:
|
||||
# We had a problem
|
||||
status_str = \
|
||||
NotifyBase.http_response_code_lookup(r.status_code)
|
||||
NotifyTelegram.http_response_code_lookup(r.status_code)
|
||||
|
||||
try:
|
||||
# Try to get the error message if we can:
|
||||
|
@ -368,10 +379,10 @@ class NotifyTelegram(NotifyBase):
|
|||
title = re.sub(' ?', ' ', title, re.I)
|
||||
|
||||
# HTML
|
||||
title = NotifyBase.escape_html(title, whitespace=False)
|
||||
title = NotifyTelegram.escape_html(title, whitespace=False)
|
||||
|
||||
# HTML
|
||||
body = NotifyBase.escape_html(body, whitespace=False)
|
||||
body = NotifyTelegram.escape_html(body, whitespace=False)
|
||||
|
||||
if title and self.notify_format == NotifyFormat.TEXT:
|
||||
# Text HTML Formatting
|
||||
|
@ -393,9 +404,9 @@ class NotifyTelegram(NotifyBase):
|
|||
payload['text'] = body
|
||||
|
||||
# Create a copy of the chat_ids list
|
||||
chat_ids = list(self.chat_ids)
|
||||
while len(chat_ids):
|
||||
chat_id = chat_ids.pop(0)
|
||||
targets = list(self.targets)
|
||||
while len(targets):
|
||||
chat_id = targets.pop(0)
|
||||
chat_id = IS_CHAT_ID_RE.match(chat_id)
|
||||
if not chat_id:
|
||||
self.logger.warning(
|
||||
|
@ -441,7 +452,7 @@ class NotifyTelegram(NotifyBase):
|
|||
if r.status_code != requests.codes.ok:
|
||||
# We had a problem
|
||||
status_str = \
|
||||
NotifyBase.http_response_code_lookup(r.status_code)
|
||||
NotifyTelegram.http_response_code_lookup(r.status_code)
|
||||
|
||||
try:
|
||||
# Try to get the error message if we can:
|
||||
|
@ -489,16 +500,17 @@ class NotifyTelegram(NotifyBase):
|
|||
args = {
|
||||
'format': self.notify_format,
|
||||
'overflow': self.overflow_mode,
|
||||
'image': self.include_image,
|
||||
}
|
||||
|
||||
# No need to check the user token because the user automatically gets
|
||||
# appended into the list of chat ids
|
||||
return '{schema}://{bot_token}/{targets}/?{args}'.format(
|
||||
schema=self.secure_protocol,
|
||||
bot_token=self.quote(self.bot_token, safe=''),
|
||||
bot_token=NotifyTelegram.quote(self.bot_token, safe=''),
|
||||
targets='/'.join(
|
||||
[self.quote('@{}'.format(x)) for x in self.chat_ids]),
|
||||
args=self.urlencode(args))
|
||||
[NotifyTelegram.quote('@{}'.format(x)) for x in self.targets]),
|
||||
args=NotifyTelegram.urlencode(args))
|
||||
|
||||
@staticmethod
|
||||
def parse_url(url):
|
||||
|
@ -507,9 +519,9 @@ class NotifyTelegram(NotifyBase):
|
|||
us to substantiate this object.
|
||||
|
||||
"""
|
||||
# This is a dirty hack; but it's the only work around to
|
||||
# tgram:// messages since the bot_token has a colon in it.
|
||||
# It invalidates an normal URL.
|
||||
# This is a dirty hack; but it's the only work around to tgram://
|
||||
# messages since the bot_token has a colon in it. It invalidates a
|
||||
# normal URL.
|
||||
|
||||
# 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
|
||||
|
@ -550,23 +562,28 @@ class NotifyTelegram(NotifyBase):
|
|||
)
|
||||
|
||||
# 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
|
||||
bot_token_b = [x for x in filter(
|
||||
bool, NotifyBase.split_path(results['fullpath']))][0]
|
||||
bot_token_b = entries.pop(0)
|
||||
|
||||
bot_token = '%s:%s' % (bot_token_a, bot_token_b)
|
||||
|
||||
chat_ids = [x for x in filter(
|
||||
bool, NotifyBase.split_path(results['fullpath']))][1:]
|
||||
# Store our chat ids (as these are the remaining entries)
|
||||
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
|
||||
results['bot_token'] = bot_token
|
||||
|
||||
# Store our chat ids
|
||||
results['chat_ids'] = chat_ids
|
||||
|
||||
# Include images with our message
|
||||
results['include_image'] = \
|
||||
parse_bool(results['qsd'].get('image', False))
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
from . import tweepy
|
||||
from ..NotifyBase import NotifyBase
|
||||
from ...common import NotifyType
|
||||
from ...utils import parse_list
|
||||
|
||||
|
||||
class NotifyTwitter(NotifyBase):
|
||||
|
@ -54,7 +55,7 @@ class NotifyTwitter(NotifyBase):
|
|||
# Twitter does have titles when creating a message
|
||||
title_maxlen = 0
|
||||
|
||||
def __init__(self, ckey, csecret, akey, asecret, **kwargs):
|
||||
def __init__(self, ckey, csecret, akey, asecret, targets=None, **kwargs):
|
||||
"""
|
||||
Initialize Twitter Object
|
||||
|
||||
|
@ -62,29 +63,32 @@ class NotifyTwitter(NotifyBase):
|
|||
super(NotifyTwitter, self).__init__(**kwargs)
|
||||
|
||||
if not ckey:
|
||||
raise TypeError(
|
||||
'An invalid Consumer API Key was specified.'
|
||||
)
|
||||
msg = 'An invalid Consumer API Key was specified.'
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
if not csecret:
|
||||
raise TypeError(
|
||||
'An invalid Consumer Secret API Key was specified.'
|
||||
)
|
||||
msg = 'An invalid Consumer Secret API Key was specified.'
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
if not akey:
|
||||
raise TypeError(
|
||||
'An invalid Acess Token API Key was specified.'
|
||||
)
|
||||
msg = 'An invalid Access Token API Key was specified.'
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
if not asecret:
|
||||
raise TypeError(
|
||||
'An invalid Acess Token Secret API Key was specified.'
|
||||
)
|
||||
msg = 'An invalid Access Token Secret API Key was specified.'
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
if not self.user:
|
||||
raise TypeError(
|
||||
'No user was specified.'
|
||||
)
|
||||
# Identify our targets
|
||||
self.targets = parse_list(targets)
|
||||
|
||||
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
|
||||
self.ckey = ckey
|
||||
|
@ -113,28 +117,68 @@ class NotifyTwitter(NotifyBase):
|
|||
)
|
||||
return False
|
||||
|
||||
# Always call throttle before any remote server i/o is made to avoid
|
||||
# thrashing the remote server and risk being blocked.
|
||||
self.throttle()
|
||||
# Get ourselves a list of targets
|
||||
users = list(self.targets)
|
||||
if not users:
|
||||
# notify ourselves
|
||||
users.append(self.user)
|
||||
|
||||
try:
|
||||
# Get our API
|
||||
api = tweepy.API(self.auth)
|
||||
# Error Tracking
|
||||
has_error = False
|
||||
|
||||
# Send our Direct Message
|
||||
api.send_direct_message(self.user, text=body)
|
||||
self.logger.info('Sent Twitter DM notification.')
|
||||
while len(users) > 0:
|
||||
# Get our user
|
||||
user = users.pop(0)
|
||||
|
||||
except Exception as e:
|
||||
self.logger.warning(
|
||||
'A Connection error occured sending Twitter '
|
||||
'direct message to %s.' % self.user)
|
||||
self.logger.debug('Twitter Exception: %s' % str(e))
|
||||
# Always call throttle before any remote server i/o is made to
|
||||
# avoid thrashing the remote server and risk being blocked.
|
||||
self.throttle()
|
||||
|
||||
# Return; we're done
|
||||
return False
|
||||
try:
|
||||
# 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
|
||||
def parse_url(url):
|
||||
|
@ -152,13 +196,12 @@ class NotifyTwitter(NotifyBase):
|
|||
# Apply our settings now
|
||||
|
||||
# The first token is stored in the hostname
|
||||
consumer_key = results['host']
|
||||
consumer_key = NotifyTwitter.unquote(results['host'])
|
||||
|
||||
# Now fetch the remaining tokens
|
||||
try:
|
||||
consumer_secret, access_token_key, access_token_secret = \
|
||||
[x for x in filter(bool, NotifyBase.split_path(
|
||||
results['fullpath']))][0:3]
|
||||
NotifyTwitter.split_path(results['fullpath'])[0:3]
|
||||
|
||||
except (ValueError, AttributeError, IndexError):
|
||||
# Force some bad values that will get caught
|
||||
|
@ -172,4 +215,8 @@ class NotifyTwitter(NotifyBase):
|
|||
results['akey'] = access_token_key
|
||||
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
|
||||
|
|
|
@ -31,6 +31,7 @@ from time import sleep
|
|||
from .NotifyBase import NotifyBase
|
||||
from ..common import NotifyImageSize
|
||||
from ..common import NotifyType
|
||||
from ..utils import parse_bool
|
||||
|
||||
# Default our global support flag
|
||||
NOTIFY_WINDOWS_SUPPORT_ENABLED = False
|
||||
|
@ -75,6 +76,9 @@ class NotifyWindows(NotifyBase):
|
|||
# content to display
|
||||
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
|
||||
# in an environment that simply doesn't have the windows packages
|
||||
# available to us. It also allows us to handle situations where the
|
||||
|
@ -84,18 +88,23 @@ class NotifyWindows(NotifyBase):
|
|||
# let me know! :)
|
||||
_enabled = NOTIFY_WINDOWS_SUPPORT_ENABLED
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
def __init__(self, include_image=True, duration=None, **kwargs):
|
||||
"""
|
||||
Initialize Windows Object
|
||||
"""
|
||||
|
||||
super(NotifyWindows, self).__init__(**kwargs)
|
||||
|
||||
# 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
|
||||
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):
|
||||
"""
|
||||
|
@ -140,20 +149,26 @@ class NotifyWindows(NotifyBase):
|
|||
self.hinst, None)
|
||||
win32gui.UpdateWindow(self.hwnd)
|
||||
|
||||
# image path
|
||||
icon_path = self.image_path(notify_type, extension='.ico')
|
||||
icon_flags = win32con.LR_LOADFROMFILE | win32con.LR_DEFAULTSIZE
|
||||
# image path (if configured to acquire)
|
||||
icon_path = None if not self.include_image \
|
||||
else self.image_path(notify_type, extension='.ico')
|
||||
|
||||
try:
|
||||
hicon = win32gui.LoadImage(
|
||||
self.hinst, icon_path, win32con.IMAGE_ICON, 0, 0,
|
||||
icon_flags)
|
||||
if icon_path:
|
||||
icon_flags = win32con.LR_LOADFROMFILE | win32con.LR_DEFAULTSIZE
|
||||
|
||||
except Exception as e:
|
||||
self.logger.warning(
|
||||
"Could not load windows notification icon ({}): {}"
|
||||
.format(icon_path, e))
|
||||
try:
|
||||
hicon = win32gui.LoadImage(
|
||||
self.hinst, icon_path, win32con.IMAGE_ICON, 0, 0,
|
||||
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
|
||||
hicon = win32gui.LoadIcon(0, win32con.IDI_APPLICATION)
|
||||
|
||||
|
@ -185,7 +200,18 @@ class NotifyWindows(NotifyBase):
|
|||
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
|
||||
def parse_url(url):
|
||||
|
@ -196,15 +222,31 @@ class NotifyWindows(NotifyBase):
|
|||
|
||||
"""
|
||||
|
||||
# return a very basic set of requirements
|
||||
return {
|
||||
'schema': NotifyWindows.protocol,
|
||||
'user': None,
|
||||
'password': None,
|
||||
'port': None,
|
||||
'host': 'localhost',
|
||||
'fullpath': None,
|
||||
'path': None,
|
||||
'url': url,
|
||||
'qsd': {},
|
||||
}
|
||||
results = NotifyBase.parse_url(url)
|
||||
if not results:
|
||||
results = {
|
||||
'schema': NotifyWindows.protocol,
|
||||
'user': None,
|
||||
'password': None,
|
||||
'port': None,
|
||||
'host': '_',
|
||||
'fullpath': None,
|
||||
'path': None,
|
||||
'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 ..common import NotifyType
|
||||
from ..common import NotifyImageSize
|
||||
from ..utils import parse_bool
|
||||
|
||||
|
||||
class NotifyXBMC(NotifyBase):
|
||||
|
@ -70,26 +71,27 @@ class NotifyXBMC(NotifyBase):
|
|||
# Allows the user to specify the NotifyImageSize object
|
||||
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_remote_protocol = 2
|
||||
|
||||
# KODI default protocol version (v6)
|
||||
kodi_remote_protocol = 6
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
def __init__(self, include_image=True, duration=None, **kwargs):
|
||||
"""
|
||||
Initialize XBMC/KODI Object
|
||||
"""
|
||||
super(NotifyXBMC, self).__init__(**kwargs)
|
||||
|
||||
# Number of micro-seconds to display notification for
|
||||
self.duration = 12000
|
||||
# Number of seconds to display notification for
|
||||
self.duration = self.default_popup_duration_sec \
|
||||
if not (isinstance(duration, int) and duration > 0) else duration
|
||||
|
||||
if self.secure:
|
||||
self.schema = 'https'
|
||||
|
||||
else:
|
||||
self.schema = 'http'
|
||||
# Build our schema
|
||||
self.schema = 'https' if self.secure else 'http'
|
||||
|
||||
# Prepare the default header
|
||||
self.headers = {
|
||||
|
@ -100,6 +102,10 @@ class NotifyXBMC(NotifyBase):
|
|||
# Default 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):
|
||||
"""
|
||||
Builds payload for KODI API v6.0
|
||||
|
@ -114,13 +120,17 @@ class NotifyXBMC(NotifyBase):
|
|||
'params': {
|
||||
'title': title,
|
||||
'message': body,
|
||||
# displaytime is defined in microseconds
|
||||
'displaytime': self.duration,
|
||||
# displaytime is defined in microseconds so we need to just
|
||||
# do some simple math
|
||||
'displaytime': int(self.duration * 1000),
|
||||
},
|
||||
'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:
|
||||
payload['params']['image'] = image_url
|
||||
if notify_type is NotifyType.FAILURE:
|
||||
|
@ -148,13 +158,17 @@ class NotifyXBMC(NotifyBase):
|
|||
'params': {
|
||||
'title': title,
|
||||
'message': body,
|
||||
# displaytime is defined in microseconds
|
||||
'displaytime': self.duration,
|
||||
# displaytime is defined in microseconds so we need to just
|
||||
# do some simple math
|
||||
'displaytime': int(self.duration * 1000),
|
||||
},
|
||||
'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:
|
||||
payload['params']['image'] = image_url
|
||||
|
||||
|
@ -204,7 +218,7 @@ class NotifyXBMC(NotifyBase):
|
|||
if r.status_code != requests.codes.ok:
|
||||
# We had a problem
|
||||
status_str = \
|
||||
NotifyBase.http_response_code_lookup(r.status_code)
|
||||
NotifyXBMC.http_response_code_lookup(r.status_code)
|
||||
|
||||
self.logger.warning(
|
||||
'Failed to send XBMC/KODI notification: '
|
||||
|
@ -242,18 +256,20 @@ class NotifyXBMC(NotifyBase):
|
|||
args = {
|
||||
'format': self.notify_format,
|
||||
'overflow': self.overflow_mode,
|
||||
'image': 'yes' if self.include_image else 'no',
|
||||
'duration': str(self.duration),
|
||||
}
|
||||
|
||||
# Determine Authentication
|
||||
auth = ''
|
||||
if self.user and self.password:
|
||||
auth = '{user}:{password}@'.format(
|
||||
user=self.quote(self.user, safe=''),
|
||||
password=self.quote(self.password, safe=''),
|
||||
user=NotifyXBMC.quote(self.user, safe=''),
|
||||
password=NotifyXBMC.quote(self.password, safe=''),
|
||||
)
|
||||
elif self.user:
|
||||
auth = '{user}@'.format(
|
||||
user=self.quote(self.user, safe=''),
|
||||
user=NotifyXBMC.quote(self.user, safe=''),
|
||||
)
|
||||
|
||||
default_schema = self.xbmc_protocol if (
|
||||
|
@ -266,10 +282,10 @@ class NotifyXBMC(NotifyBase):
|
|||
return '{schema}://{auth}{hostname}{port}/?{args}'.format(
|
||||
schema=default_schema,
|
||||
auth=auth,
|
||||
hostname=self.host,
|
||||
hostname=NotifyXBMC.quote(self.host, safe=''),
|
||||
port='' if not self.port or self.port == default_port
|
||||
else ':{}'.format(self.port),
|
||||
args=self.urlencode(args),
|
||||
args=NotifyXBMC.urlencode(args),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
|
@ -298,4 +314,16 @@ class NotifyXBMC(NotifyBase):
|
|||
# KODI Support
|
||||
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
|
||||
|
|
|
@ -81,12 +81,6 @@ class NotifyXML(NotifyBase):
|
|||
</soapenv:Body>
|
||||
</soapenv:Envelope>"""
|
||||
|
||||
if self.secure:
|
||||
self.schema = 'https'
|
||||
|
||||
else:
|
||||
self.schema = 'http'
|
||||
|
||||
self.fullpath = kwargs.get('fullpath')
|
||||
if not isinstance(self.fullpath, six.string_types):
|
||||
self.fullpath = '/'
|
||||
|
@ -116,12 +110,12 @@ class NotifyXML(NotifyBase):
|
|||
auth = ''
|
||||
if self.user and self.password:
|
||||
auth = '{user}:{password}@'.format(
|
||||
user=self.quote(self.user, safe=''),
|
||||
password=self.quote(self.password, safe=''),
|
||||
user=NotifyXML.quote(self.user, safe=''),
|
||||
password=NotifyXML.quote(self.password, safe=''),
|
||||
)
|
||||
elif self.user:
|
||||
auth = '{user}@'.format(
|
||||
user=self.quote(self.user, safe=''),
|
||||
user=NotifyXML.quote(self.user, safe=''),
|
||||
)
|
||||
|
||||
default_port = 443 if self.secure else 80
|
||||
|
@ -129,10 +123,10 @@ class NotifyXML(NotifyBase):
|
|||
return '{schema}://{auth}{hostname}{port}/?{args}'.format(
|
||||
schema=self.secure_protocol if self.secure else self.protocol,
|
||||
auth=auth,
|
||||
hostname=self.host,
|
||||
hostname=NotifyXML.quote(self.host, safe=''),
|
||||
port='' if self.port is None or self.port == default_port
|
||||
else ':{}'.format(self.port),
|
||||
args=self.urlencode(args),
|
||||
args=NotifyXML.urlencode(args),
|
||||
)
|
||||
|
||||
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
||||
|
@ -150,9 +144,10 @@ class NotifyXML(NotifyBase):
|
|||
headers.update(self.headers)
|
||||
|
||||
re_map = {
|
||||
'{MESSAGE_TYPE}': NotifyBase.quote(notify_type),
|
||||
'{SUBJECT}': NotifyBase.quote(title),
|
||||
'{MESSAGE}': NotifyBase.quote(body),
|
||||
'{MESSAGE_TYPE}': NotifyXML.escape_html(
|
||||
notify_type, whitespace=False),
|
||||
'{SUBJECT}': NotifyXML.escape_html(title, whitespace=False),
|
||||
'{MESSAGE}': NotifyXML.escape_html(body, whitespace=False),
|
||||
}
|
||||
|
||||
# Iterate over above list and store content accordingly
|
||||
|
@ -165,7 +160,10 @@ class NotifyXML(NotifyBase):
|
|||
if self.user:
|
||||
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):
|
||||
url += ':%d' % self.port
|
||||
|
||||
|
@ -191,7 +189,7 @@ class NotifyXML(NotifyBase):
|
|||
if r.status_code != requests.codes.ok:
|
||||
# We had a problem
|
||||
status_str = \
|
||||
NotifyBase.http_response_code_lookup(r.status_code)
|
||||
NotifyXML.http_response_code_lookup(r.status_code)
|
||||
|
||||
self.logger.warning(
|
||||
'Failed to send XML notification: '
|
||||
|
@ -237,4 +235,8 @@ class NotifyXML(NotifyBase):
|
|||
results['headers'] = 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
|
||||
|
|
|
@ -24,7 +24,6 @@
|
|||
# THE SOFTWARE.
|
||||
|
||||
import re
|
||||
import six
|
||||
import ssl
|
||||
from os.path import isfile
|
||||
|
||||
|
@ -99,7 +98,7 @@ class NotifyXMPP(NotifyBase):
|
|||
# let me know! :)
|
||||
_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
|
||||
"""
|
||||
|
@ -177,17 +176,6 @@ class NotifyXMPP(NotifyBase):
|
|||
else:
|
||||
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):
|
||||
"""
|
||||
Perform XMPP Notification
|
||||
|
@ -302,15 +290,16 @@ class NotifyXMPP(NotifyBase):
|
|||
}
|
||||
|
||||
if self.jid:
|
||||
args['jid'] = self.quote(self.jid, safe='')
|
||||
args['jid'] = self.jid
|
||||
|
||||
if self.xep:
|
||||
args['xep'] = self.quote(
|
||||
','.join([str(xep) for xep in self.xep]), safe='')
|
||||
# xep are integers, so we need to just iterate over a list and
|
||||
# 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
|
||||
# and/or space as a delimiters
|
||||
jids = self.quote(' '.join(self.targets), safe='')
|
||||
# and/or space as a delimiters - %20 = space
|
||||
jids = '%20'.join([NotifyXMPP.quote(x, safe='') for x in self.targets])
|
||||
|
||||
default_port = self.default_secure_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
|
||||
|
||||
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:
|
||||
auth = self.password if self.password else self.user
|
||||
|
||||
return '{schema}://{auth}@{hostname}{port}/{jids}?{args}'.format(
|
||||
auth=self.quote(auth, safe=''),
|
||||
auth=auth,
|
||||
schema=default_schema,
|
||||
hostname=self.host,
|
||||
hostname=NotifyXMPP.quote(self.host, safe=''),
|
||||
port='' if not self.port or self.port == default_port
|
||||
else ':{}'.format(self.port),
|
||||
jids=jids,
|
||||
args=self.urlencode(args),
|
||||
args=NotifyXMPP.urlencode(args),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
|
@ -348,18 +339,20 @@ class NotifyXMPP(NotifyBase):
|
|||
|
||||
# Get our targets; we ignore path slashes since they identify
|
||||
# our resources
|
||||
results['targets'] = parse_list(results['fullpath'])
|
||||
results['targets'] = NotifyXMPP.parse_list(results['fullpath'])
|
||||
|
||||
# Over-ride the xep plugins
|
||||
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
|
||||
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
|
||||
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
|
||||
|
|
|
@ -101,6 +101,7 @@ def test_notify_gitter_plugin_general(mock_post, mock_get):
|
|||
obj = plugins.NotifyGitter(token=token, targets='apprise')
|
||||
assert isinstance(obj, plugins.NotifyGitter) is True
|
||||
assert isinstance(obj.url(), six.string_types) is True
|
||||
|
||||
# apprise room was found
|
||||
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',
|
||||
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
|
||||
# gracefully fail
|
||||
mock_notify = mock.Mock()
|
||||
|
|
|
@ -116,24 +116,80 @@ def test_gnome_plugin():
|
|||
obj.duration = 0
|
||||
|
||||
# Check that it found our mocked environments
|
||||
assert(obj._enabled is True)
|
||||
assert obj._enabled is True
|
||||
|
||||
# Test url() call
|
||||
assert(isinstance(obj.url(), six.string_types) is True)
|
||||
assert isinstance(obj.url(), six.string_types) is True
|
||||
|
||||
# test notifications
|
||||
assert(obj.notify(title='title', body='body',
|
||||
notify_type=apprise.NotifyType.INFO) is True)
|
||||
assert obj.notify(title='title', body='body',
|
||||
notify_type=apprise.NotifyType.INFO) is True
|
||||
|
||||
# test notification without a title
|
||||
assert(obj.notify(title='', body='body',
|
||||
notify_type=apprise.NotifyType.INFO) is True)
|
||||
assert obj.notify(title='', body='body',
|
||||
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
|
||||
# notification to be sent
|
||||
mock_pixbuf.new_from_file.side_effect = AttributeError()
|
||||
assert(obj.notify(title='title', body='body',
|
||||
notify_type=apprise.NotifyType.INFO) is True)
|
||||
assert obj.notify(title='title', body='body',
|
||||
notify_type=apprise.NotifyType.INFO) is True
|
||||
# Undo our change
|
||||
mock_pixbuf.new_from_file.side_effect = None
|
||||
|
||||
|
@ -142,8 +198,8 @@ def test_gnome_plugin():
|
|||
.Notification.new.return_value = None
|
||||
sys.modules['gi.repository.Notify']\
|
||||
.Notification.new.side_effect = AttributeError()
|
||||
assert(obj.notify(title='title', body='body',
|
||||
notify_type=apprise.NotifyType.INFO) is False)
|
||||
assert obj.notify(title='title', body='body',
|
||||
notify_type=apprise.NotifyType.INFO) is False
|
||||
|
||||
# Undo our change
|
||||
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
|
||||
# package has been made unavailable to us
|
||||
obj._enabled = False
|
||||
assert(obj.notify(title='title', body='body',
|
||||
notify_type=apprise.NotifyType.INFO) is False)
|
||||
assert obj.notify(title='title', body='body',
|
||||
notify_type=apprise.NotifyType.INFO) is False
|
||||
|
||||
# Test the setting of a the urgency
|
||||
apprise.plugins.NotifyGnome(urgency=0)
|
||||
# Test the setting of a the urgency (through priority keyword)
|
||||
apprise.plugins.NotifyGnome(priority=0)
|
||||
|
||||
# Verify this all works in the event a ValueError is also thronw
|
||||
# out of the call to gi.require_version()
|
||||
|
@ -178,10 +234,10 @@ def test_gnome_plugin():
|
|||
|
||||
# Create our instance
|
||||
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
|
||||
|
||||
# Our notifications can not work without our gi library having been
|
||||
# loaded.
|
||||
assert(obj.notify(title='title', body='body',
|
||||
notify_type=apprise.NotifyType.INFO) is False)
|
||||
assert obj.notify(title='title', body='body',
|
||||
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
|
||||
|
||||
# Variation Initializations
|
||||
obj = plugins.NotifyMatrix(rooms='#abcd')
|
||||
obj = plugins.NotifyMatrix(targets='#abcd')
|
||||
assert isinstance(obj, plugins.NotifyMatrix) is True
|
||||
assert isinstance(obj.url(), six.string_types) is True
|
||||
# Registration successful
|
||||
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.url(), six.string_types) is True
|
||||
# Registration successful
|
||||
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.url(), six.string_types) is True
|
||||
# A username gets automatically generated in these cases
|
||||
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, plugins.NotifyMatrix) is True
|
||||
# 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
|
||||
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
|
||||
# Fails because we still couldn't register
|
||||
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
|
||||
# Fails because we still couldn't register
|
||||
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
|
||||
assert isinstance(obj, plugins.NotifyMatrix) is True
|
||||
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.status_code = requests.codes.ok
|
||||
|
||||
obj = plugins.NotifyMatrix(rooms=None)
|
||||
obj = plugins.NotifyMatrix(targets=None)
|
||||
assert isinstance(obj, plugins.NotifyMatrix) is True
|
||||
|
||||
# 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_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
|
||||
# We would hve failed to send our image notification
|
||||
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
|
||||
obj._room_cache = {}
|
||||
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>'  <br/></content>'
|
||||
|
||||
# Test invalid data
|
||||
assert NotifyBase.split_path(None) == []
|
||||
assert NotifyBase.split_path(object()) == []
|
||||
assert NotifyBase.split_path(42) == []
|
||||
|
||||
assert NotifyBase.split_path(
|
||||
'/path/?name=Dr%20Disrespect', unquote=False) == \
|
||||
['path', '?name=Dr%20Disrespect']
|
||||
|
||||
assert NotifyBase.split_path(
|
||||
'/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
|
||||
assert NotifyBase.escape_html("") == ""
|
||||
|
|
|
@ -45,9 +45,12 @@ TEST_URLS = (
|
|||
('pjets://', {
|
||||
'instance': None,
|
||||
}),
|
||||
('pjet://:@/', {
|
||||
'instance': None,
|
||||
}),
|
||||
# You must specify a username
|
||||
('pjet://%s' % ('a' * 32), {
|
||||
'instance': None,
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# Specify your own server
|
||||
('pjet://%s@localhost' % ('a' * 32), {
|
||||
|
@ -57,9 +60,6 @@ TEST_URLS = (
|
|||
('pjets://%s@localhost:8080' % ('a' * 32), {
|
||||
'instance': plugins.NotifyPushjet,
|
||||
}),
|
||||
('pjet://:@/', {
|
||||
'instance': None,
|
||||
}),
|
||||
('pjet://%s@localhost:8081' % ('a' * 32), {
|
||||
'instance': plugins.NotifyPushjet,
|
||||
# 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,
|
||||
secret_access_key=TEST_ACCESS_KEY_SECRET,
|
||||
region_name=TEST_REGION,
|
||||
recipients='+1800555999',
|
||||
targets='+1800555999',
|
||||
)
|
||||
# The entries above are invalid, our code should never reach here
|
||||
assert(False)
|
||||
|
@ -66,7 +66,7 @@ def test_object_initialization():
|
|||
access_key_id=TEST_ACCESS_KEY_ID,
|
||||
secret_access_key=None,
|
||||
region_name=TEST_REGION,
|
||||
recipients='+1800555999',
|
||||
targets='+1800555999',
|
||||
)
|
||||
# The entries above are invalid, our code should never reach here
|
||||
assert(False)
|
||||
|
@ -81,7 +81,7 @@ def test_object_initialization():
|
|||
access_key_id=TEST_ACCESS_KEY_ID,
|
||||
secret_access_key=TEST_ACCESS_KEY_SECRET,
|
||||
region_name=None,
|
||||
recipients='+1800555999',
|
||||
targets='+1800555999',
|
||||
)
|
||||
# The entries above are invalid, our code should never reach here
|
||||
assert(False)
|
||||
|
@ -96,7 +96,7 @@ def test_object_initialization():
|
|||
access_key_id=TEST_ACCESS_KEY_ID,
|
||||
secret_access_key=TEST_ACCESS_KEY_SECRET,
|
||||
region_name=TEST_REGION,
|
||||
recipients=None,
|
||||
targets=None,
|
||||
)
|
||||
# Still valid even without recipients
|
||||
assert(True)
|
||||
|
@ -111,7 +111,7 @@ def test_object_initialization():
|
|||
access_key_id=TEST_ACCESS_KEY_ID,
|
||||
secret_access_key=TEST_ACCESS_KEY_SECRET,
|
||||
region_name=TEST_REGION,
|
||||
recipients=object(),
|
||||
targets=object(),
|
||||
)
|
||||
# Still valid even without recipients
|
||||
assert(True)
|
||||
|
@ -127,7 +127,7 @@ def test_object_initialization():
|
|||
access_key_id=TEST_ACCESS_KEY_ID,
|
||||
secret_access_key=TEST_ACCESS_KEY_SECRET,
|
||||
region_name=TEST_REGION,
|
||||
recipients='+1809',
|
||||
targets='+1809',
|
||||
)
|
||||
# The recipient is invalid, but it's still okay; this Notification
|
||||
# still becomes pretty much useless at this point though
|
||||
|
@ -144,7 +144,7 @@ def test_object_initialization():
|
|||
access_key_id=TEST_ACCESS_KEY_ID,
|
||||
secret_access_key=TEST_ACCESS_KEY_SECRET,
|
||||
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
|
||||
# still becomes pretty much useless at this point though
|
||||
|
@ -169,7 +169,7 @@ def test_url_parsing():
|
|||
)
|
||||
|
||||
# Confirm that there were no recipients found
|
||||
assert(len(results['recipients']) == 0)
|
||||
assert(len(results['targets']) == 0)
|
||||
assert('region_name' in results)
|
||||
assert(TEST_REGION == results['region_name'])
|
||||
assert('access_key_id' in results)
|
||||
|
@ -188,9 +188,9 @@ def test_url_parsing():
|
|||
)
|
||||
|
||||
# Confirm that our recipients were found
|
||||
assert(len(results['recipients']) == 2)
|
||||
assert('+18001234567' in results['recipients'])
|
||||
assert('MyTopic' in results['recipients'])
|
||||
assert(len(results['targets']) == 2)
|
||||
assert('+18001234567' in results['targets'])
|
||||
assert('MyTopic' in results['targets'])
|
||||
assert('region_name' in results)
|
||||
assert(TEST_REGION == results['region_name'])
|
||||
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
|
||||
# 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 NotifyType
|
||||
from apprise import Apprise
|
||||
import mock
|
||||
from apprise import OverflowMode
|
||||
|
||||
# Disable logging for a cleaner testing output
|
||||
import logging
|
||||
|
@ -40,6 +46,9 @@ TEST_URLS = (
|
|||
('tweet://', {
|
||||
'instance': None,
|
||||
}),
|
||||
('tweet://:@/', {
|
||||
'instance': None,
|
||||
}),
|
||||
('tweet://consumer_key', {
|
||||
# Missing Keys
|
||||
'instance': TypeError,
|
||||
|
@ -60,9 +69,11 @@ TEST_URLS = (
|
|||
# We're good!
|
||||
'instance': plugins.NotifyTwitter,
|
||||
}),
|
||||
('tweet://:@/', {
|
||||
'instance': None,
|
||||
}),
|
||||
('tweet://usera@consumer_key/consumer_key/access_token/'
|
||||
'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)
|
||||
|
||||
"""
|
||||
# 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
|
||||
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)
|
||||
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
|
||||
# the defaults
|
||||
response = meta.get(
|
||||
|
@ -94,25 +124,69 @@ def test_plugin(mock_oauth, mock_api):
|
|||
try:
|
||||
obj = Apprise.instantiate(url, suppress_exceptions=False)
|
||||
|
||||
if instance is None:
|
||||
# Check that we got what we came for
|
||||
assert obj is instance
|
||||
if obj is None:
|
||||
if instance is not None:
|
||||
# We're done (assuming this is what we were expecting)
|
||||
print("{} didn't instantiate itself "
|
||||
"(we expected it to)".format(url))
|
||||
assert False
|
||||
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:
|
||||
# Iterate over our expected entries inside of our object
|
||||
for key, val in self.items():
|
||||
# Test that our object has the desired key
|
||||
assert(hasattr(key, obj))
|
||||
assert(getattr(key, obj) == val)
|
||||
assert hasattr(key, obj) is True
|
||||
assert getattr(key, obj) == val
|
||||
|
||||
obj.request_rate_per_sec = 0
|
||||
|
||||
# check that we're as expected
|
||||
assert obj.notify(
|
||||
title='test', body='body',
|
||||
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:
|
||||
# Don't mess with these entries
|
||||
raise
|
||||
|
|
|
@ -125,6 +125,43 @@ def test_windows_plugin():
|
|||
assert(obj.notify(title='title', body='body',
|
||||
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
|
||||
# notification to be sent
|
||||
win32gui.LoadImage.side_effect = AttributeError
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
import six
|
||||
import mock
|
||||
import sys
|
||||
# import types
|
||||
import ssl
|
||||
|
||||
import apprise
|
||||
|
||||
|
@ -129,6 +129,39 @@ def test_xmpp_plugin(tmpdir):
|
|||
# Not possible because no password was specified
|
||||
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
|
||||
for url in (
|
||||
'xmpps://user:pass@example.com',
|
||||
|
|
Loading…
Reference in New Issue