mirror of https://github.com/caronc/apprise
parent
44a21651b3
commit
c6922d8f3a
|
@ -40,6 +40,7 @@ except ImportError:
|
|||
from .NotifyBase import NotifyBase
|
||||
from ..URLBase import PrivacyMode
|
||||
from ..utils import parse_bool
|
||||
from ..utils import validate_regex
|
||||
from ..common import NotifyType
|
||||
from ..common import NotifyImageSize
|
||||
from ..AppriseLocale import gettext_lazy as _
|
||||
|
@ -58,11 +59,6 @@ IS_TAG = re.compile(r'^[@](?P<name>[A-Z0-9]{1,63})$', re.I)
|
|||
# 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
|
||||
# you create on the boxcar website
|
||||
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 useable
|
||||
# list.
|
||||
TAGS_LIST_DELIM = re.compile(r'[ \t\r\n,\\/]+')
|
||||
|
@ -105,30 +101,30 @@ class NotifyBoxcar(NotifyBase):
|
|||
'access_key': {
|
||||
'name': _('Access Key'),
|
||||
'type': 'string',
|
||||
'regex': (r'[A-Z0-9_-]{64}', 'i'),
|
||||
'private': True,
|
||||
'required': True,
|
||||
'regex': (r'^[A-Z0-9_-]{64}$', 'i'),
|
||||
'map_to': 'access',
|
||||
},
|
||||
'secret_key': {
|
||||
'name': _('Secret Key'),
|
||||
'type': 'string',
|
||||
'regex': (r'[A-Z0-9_-]{64}', 'i'),
|
||||
'private': True,
|
||||
'required': True,
|
||||
'regex': (r'^[A-Z0-9_-]{64}$', 'i'),
|
||||
'map_to': 'secret',
|
||||
},
|
||||
'target_tag': {
|
||||
'name': _('Target Tag ID'),
|
||||
'type': 'string',
|
||||
'prefix': '@',
|
||||
'regex': (r'[A-Z0-9]{1,63}', 'i'),
|
||||
'regex': (r'^[A-Z0-9]{1,63}$', 'i'),
|
||||
'map_to': 'targets',
|
||||
},
|
||||
'target_device': {
|
||||
'name': _('Target Device ID'),
|
||||
'type': 'string',
|
||||
'regex': (r'[A-Z0-9]{64}', 'i'),
|
||||
'regex': (r'^[A-Z0-9]{64}$', 'i'),
|
||||
'map_to': 'targets',
|
||||
},
|
||||
'targets': {
|
||||
|
@ -163,33 +159,21 @@ class NotifyBoxcar(NotifyBase):
|
|||
# Initialize device_token list
|
||||
self.device_tokens = list()
|
||||
|
||||
try:
|
||||
# Access Key (associated with project)
|
||||
self.access = access.strip()
|
||||
|
||||
except AttributeError:
|
||||
msg = 'The specified access key is invalid.'
|
||||
self.access = validate_regex(
|
||||
access, *self.template_tokens['access_key']['regex'])
|
||||
if not self.access:
|
||||
msg = 'An invalid Boxcar Access Key ' \
|
||||
'({}) was specified.'.format(access)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
try:
|
||||
# Secret Key (associated with project)
|
||||
self.secret = secret.strip()
|
||||
|
||||
except AttributeError:
|
||||
msg = 'The specified secret key is invalid.'
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
if not VALIDATE_ACCESS.match(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):
|
||||
msg = 'The secret key specified ({}) is invalid.'\
|
||||
.format(self.secret)
|
||||
self.secret = validate_regex(
|
||||
secret, *self.template_tokens['secret_key']['regex'])
|
||||
if not self.secret:
|
||||
msg = 'An invalid Boxcar Secret Key ' \
|
||||
'({}) was specified.'.format(secret)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
|
@ -228,7 +212,6 @@ class NotifyBoxcar(NotifyBase):
|
|||
"""
|
||||
Perform Boxcar Notification
|
||||
"""
|
||||
|
||||
headers = {
|
||||
'User-Agent': self.app_id,
|
||||
'Content-Type': 'application/json'
|
||||
|
|
|
@ -112,7 +112,7 @@ class NotifyClickSend(NotifyBase):
|
|||
'name': _('Target Phone No'),
|
||||
'type': 'string',
|
||||
'prefix': '+',
|
||||
'regex': (r'[0-9\s)(+-]+', 'i'),
|
||||
'regex': (r'^[0-9\s)(+-]+$', 'i'),
|
||||
'map_to': 'targets',
|
||||
},
|
||||
'targets': {
|
||||
|
|
|
@ -131,7 +131,7 @@ class NotifyD7Networks(NotifyBase):
|
|||
'name': _('Target Phone No'),
|
||||
'type': 'string',
|
||||
'prefix': '+',
|
||||
'regex': (r'[0-9\s)(+-]+', 'i'),
|
||||
'regex': (r'^[0-9\s)(+-]+$', 'i'),
|
||||
'map_to': 'targets',
|
||||
},
|
||||
'targets': {
|
||||
|
@ -227,6 +227,8 @@ class NotifyD7Networks(NotifyBase):
|
|||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
return
|
||||
|
||||
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
||||
"""
|
||||
Depending on whether we are set to batch mode or single mode this
|
||||
|
|
|
@ -240,6 +240,8 @@ class NotifyDBus(NotifyBase):
|
|||
# or not.
|
||||
self.include_image = include_image
|
||||
|
||||
return
|
||||
|
||||
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
||||
"""
|
||||
Perform DBus Notification
|
||||
|
|
|
@ -49,6 +49,7 @@ from ..common import NotifyImageSize
|
|||
from ..common import NotifyFormat
|
||||
from ..common import NotifyType
|
||||
from ..utils import parse_bool
|
||||
from ..utils import validate_regex
|
||||
from ..AppriseLocale import gettext_lazy as _
|
||||
|
||||
|
||||
|
@ -144,20 +145,22 @@ class NotifyDiscord(NotifyBase):
|
|||
"""
|
||||
super(NotifyDiscord, self).__init__(**kwargs)
|
||||
|
||||
if not webhook_id:
|
||||
msg = 'An invalid Client ID was specified.'
|
||||
# Webhook ID (associated with project)
|
||||
self.webhook_id = validate_regex(webhook_id)
|
||||
if not self.webhook_id:
|
||||
msg = 'An invalid Discord Webhook ID ' \
|
||||
'({}) was specified.'.format(webhook_id)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
if not webhook_token:
|
||||
msg = 'An invalid Webhook Token was specified.'
|
||||
# Webhook Token (associated with project)
|
||||
self.webhook_token = validate_regex(webhook_token)
|
||||
if not self.webhook_token:
|
||||
msg = 'An invalid Discord Webhook Token ' \
|
||||
'({}) was specified.'.format(webhook_token)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
# Store our data
|
||||
self.webhook_id = webhook_id
|
||||
self.webhook_token = webhook_token
|
||||
|
||||
# Text To Speech
|
||||
self.tts = tts
|
||||
|
||||
|
|
|
@ -139,7 +139,7 @@ class NotifyEmby(NotifyBase):
|
|||
|
||||
if not self.user:
|
||||
# User was not specified
|
||||
msg = 'No Username was specified.'
|
||||
msg = 'No Emby username was specified.'
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
|
|
|
@ -91,6 +91,8 @@ class NotifyFaast(NotifyBase):
|
|||
# Associate an image with our post
|
||||
self.include_image = include_image
|
||||
|
||||
return
|
||||
|
||||
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
||||
"""
|
||||
Perform Faast Notification
|
||||
|
|
|
@ -47,6 +47,7 @@ from ..common import NotifyFormat
|
|||
from ..common import NotifyImageSize
|
||||
from ..utils import parse_list
|
||||
from ..utils import parse_bool
|
||||
from ..utils import validate_regex
|
||||
from ..AppriseLocale import gettext_lazy as _
|
||||
|
||||
|
||||
|
@ -56,12 +57,8 @@ FLOCK_HTTP_ERROR_MAP = {
|
|||
}
|
||||
|
||||
# Used to detect a channel/user
|
||||
IS_CHANNEL_RE = re.compile(r'^(#|g:)(?P<id>[A-Z0-9_]{12})$', re.I)
|
||||
IS_USER_RE = re.compile(r'^(@|u:)?(?P<id>[A-Z0-9_]{12})$', re.I)
|
||||
|
||||
# Token required as part of the API request
|
||||
# /134b8gh0-eba0-4fa9-ab9c-257ced0e8221
|
||||
IS_API_TOKEN = re.compile(r'^[a-z0-9-]{24}$', re.I)
|
||||
IS_CHANNEL_RE = re.compile(r'^(#|g:)(?P<id>[A-Z0-9_]+)$', re.I)
|
||||
IS_USER_RE = re.compile(r'^(@|u:)?(?P<id>[A-Z0-9_]+)$', re.I)
|
||||
|
||||
|
||||
class NotifyFlock(NotifyBase):
|
||||
|
@ -103,7 +100,7 @@ class NotifyFlock(NotifyBase):
|
|||
'token': {
|
||||
'name': _('Access Key'),
|
||||
'type': 'string',
|
||||
'regex': (r'[a-z0-9-]{24}', 'i'),
|
||||
'regex': (r'^[a-z0-9-]{24}$', 'i'),
|
||||
'private': True,
|
||||
'required': True,
|
||||
},
|
||||
|
@ -115,14 +112,14 @@ class NotifyFlock(NotifyBase):
|
|||
'name': _('To User ID'),
|
||||
'type': 'string',
|
||||
'prefix': '@',
|
||||
'regex': (r'[A-Z0-9_]{12}', 'i'),
|
||||
'regex': (r'^[A-Z0-9_]{12}$', 'i'),
|
||||
'map_to': 'targets',
|
||||
},
|
||||
'to_channel': {
|
||||
'name': _('To Channel ID'),
|
||||
'type': 'string',
|
||||
'prefix': '#',
|
||||
'regex': (r'[A-Z0-9_]{12}', 'i'),
|
||||
'regex': (r'^[A-Z0-9_]{12}$', 'i'),
|
||||
'map_to': 'targets',
|
||||
},
|
||||
'targets': {
|
||||
|
@ -153,15 +150,18 @@ class NotifyFlock(NotifyBase):
|
|||
# Build ourselves a target list
|
||||
self.targets = list()
|
||||
|
||||
# Initialize our token object
|
||||
self.token = token.strip()
|
||||
|
||||
if not IS_API_TOKEN.match(self.token):
|
||||
msg = 'The Flock API Token specified ({}) is invalid.'.format(
|
||||
self.token)
|
||||
self.token = validate_regex(
|
||||
token, *self.template_tokens['token']['regex'])
|
||||
if not self.token:
|
||||
msg = 'An invalid Flock Access Key ' \
|
||||
'({}) was specified.'.format(token)
|
||||
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
|
||||
|
||||
# Track any issues
|
||||
has_error = False
|
||||
|
||||
|
@ -183,15 +183,13 @@ class NotifyFlock(NotifyBase):
|
|||
self.logger.warning(
|
||||
'Ignoring invalid target ({}) specified.'.format(target))
|
||||
|
||||
if has_error and len(self.targets) == 0:
|
||||
if has_error and not self.targets:
|
||||
# We have a bot token and no target(s) to message
|
||||
msg = 'No targets found with specified Flock Bot Token.'
|
||||
msg = 'No Flock targets to notify.'
|
||||
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
|
||||
|
||||
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
||||
"""
|
||||
|
|
|
@ -50,14 +50,12 @@ from ..common import NotifyFormat
|
|||
from ..common import NotifyType
|
||||
from ..utils import parse_list
|
||||
from ..utils import parse_bool
|
||||
from ..utils import validate_regex
|
||||
from ..AppriseLocale import gettext_lazy as _
|
||||
|
||||
# API Gitter URL
|
||||
GITTER_API_URL = 'https://api.gitter.im/v1'
|
||||
|
||||
# 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
|
||||
TARGET_LIST_DELIM = re.compile(r'[ \t\r\n,\\/]+')
|
||||
|
||||
|
@ -112,9 +110,9 @@ class NotifyGitter(NotifyBase):
|
|||
'token': {
|
||||
'name': _('Token'),
|
||||
'type': 'string',
|
||||
'regex': (r'[a-z0-9]{40}', 'i'),
|
||||
'private': True,
|
||||
'required': True,
|
||||
'regex': (r'^[a-z0-9]{40}$', 'i'),
|
||||
},
|
||||
'targets': {
|
||||
'name': _('Rooms'),
|
||||
|
@ -141,24 +139,21 @@ class NotifyGitter(NotifyBase):
|
|||
"""
|
||||
super(NotifyGitter, self).__init__(**kwargs)
|
||||
|
||||
try:
|
||||
# The personal access token associated with the account
|
||||
self.token = token.strip()
|
||||
|
||||
except AttributeError:
|
||||
# Token was None
|
||||
msg = 'No API Token was specified.'
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
if not VALIDATE_TOKEN.match(self.token):
|
||||
msg = 'The Personal Access Token specified ({}) is invalid.' \
|
||||
.format(token)
|
||||
# Secret Key (associated with project)
|
||||
self.token = validate_regex(
|
||||
token, *self.template_tokens['token']['regex'])
|
||||
if not self.token:
|
||||
msg = 'An invalid Gitter API Token ' \
|
||||
'({}) was specified.'.format(token)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
# Parse our targets
|
||||
self.targets = parse_list(targets)
|
||||
if not self.targets:
|
||||
msg = 'There are no valid Gitter targets to notify.'
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
# Used to track maping of rooms to their numeric id lookup for
|
||||
# messaging
|
||||
|
@ -168,6 +163,8 @@ class NotifyGitter(NotifyBase):
|
|||
# or not.
|
||||
self.include_image = include_image
|
||||
|
||||
return
|
||||
|
||||
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
||||
"""
|
||||
Perform Gitter Notification
|
||||
|
@ -183,8 +180,6 @@ class NotifyGitter(NotifyBase):
|
|||
if image_url:
|
||||
body = '![alt]({})\n{}'.format(image_url, body)
|
||||
|
||||
# Create a copy of the targets list
|
||||
targets = list(self.targets)
|
||||
if self._room_mapping is None:
|
||||
# Populate our room mapping
|
||||
self._room_mapping = {}
|
||||
|
@ -225,10 +220,8 @@ class NotifyGitter(NotifyBase):
|
|||
'uri': entry['uri'],
|
||||
}
|
||||
|
||||
if len(targets) == 0:
|
||||
# No targets specified
|
||||
return False
|
||||
|
||||
# Create a copy of the targets list
|
||||
targets = list(self.targets)
|
||||
while len(targets):
|
||||
target = targets.pop(0).lower()
|
||||
|
||||
|
|
|
@ -150,6 +150,8 @@ class NotifyGnome(NotifyBase):
|
|||
# or not.
|
||||
self.include_image = include_image
|
||||
|
||||
return
|
||||
|
||||
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
||||
"""
|
||||
Perform Gnome Notification
|
||||
|
|
|
@ -31,12 +31,12 @@
|
|||
# f2c2688f0b5e6a816bbcec768ca1c0de5af76b88/ADD_MESSAGE_EXAMPLES.md#python
|
||||
# API: https://gotify.net/docs/swagger-docs
|
||||
|
||||
import six
|
||||
import requests
|
||||
from json import dumps
|
||||
|
||||
from .NotifyBase import NotifyBase
|
||||
from ..common import NotifyType
|
||||
from ..utils import validate_regex
|
||||
from ..AppriseLocale import gettext_lazy as _
|
||||
|
||||
|
||||
|
@ -121,9 +121,12 @@ class NotifyGotify(NotifyBase):
|
|||
"""
|
||||
super(NotifyGotify, self).__init__(**kwargs)
|
||||
|
||||
if not isinstance(token, six.string_types):
|
||||
msg = 'An invalid Gotify token was specified.'
|
||||
self.logger.warning('msg')
|
||||
# Token (associated with project)
|
||||
self.token = validate_regex(token)
|
||||
if not self.token:
|
||||
msg = 'An invalid Gotify Token ' \
|
||||
'({}) was specified.'.format(token)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
if priority not in GOTIFY_PRIORITIES:
|
||||
|
@ -138,11 +141,6 @@ class NotifyGotify(NotifyBase):
|
|||
else:
|
||||
self.schema = 'http'
|
||||
|
||||
# Our access token does not get created until we first
|
||||
# authenticate with our Gotify server. The same goes for the
|
||||
# user id below.
|
||||
self.token = token
|
||||
|
||||
return
|
||||
|
||||
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
||||
|
|
|
@ -324,7 +324,6 @@ class NotifyGrowl(NotifyBase):
|
|||
# We're done early as we couldn't load the results
|
||||
return results
|
||||
|
||||
# Apply our settings now
|
||||
version = None
|
||||
if 'version' in results['qsd'] and len(results['qsd']['version']):
|
||||
# Allow the user to specify the version of the protocol to use.
|
||||
|
|
|
@ -46,6 +46,7 @@ from json import dumps
|
|||
from .NotifyBase import NotifyBase
|
||||
from ..common import NotifyType
|
||||
from ..utils import parse_list
|
||||
from ..utils import validate_regex
|
||||
from ..AppriseLocale import gettext_lazy as _
|
||||
|
||||
|
||||
|
@ -148,22 +149,21 @@ class NotifyIFTTT(NotifyBase):
|
|||
"""
|
||||
super(NotifyIFTTT, self).__init__(**kwargs)
|
||||
|
||||
if not webhook_id:
|
||||
msg = 'You must specify the Webhooks webhook_id.'
|
||||
# Webhook ID (associated with project)
|
||||
self.webhook_id = validate_regex(webhook_id)
|
||||
if not self.webhook_id:
|
||||
msg = 'An invalid IFTTT Webhook ID ' \
|
||||
'({}) was specified.'.format(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:
|
||||
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
|
||||
|
||||
# Tokens to include in post
|
||||
self.add_tokens = {}
|
||||
if add_tokens:
|
||||
|
|
|
@ -41,18 +41,16 @@ from ..common import NotifyImageSize
|
|||
from ..common import NotifyType
|
||||
from ..utils import parse_list
|
||||
from ..utils import parse_bool
|
||||
from ..utils import validate_regex
|
||||
from ..AppriseLocale import gettext_lazy as _
|
||||
|
||||
# Token required as part of the API request
|
||||
VALIDATE_APIKEY = re.compile(r'[a-z0-9]{32}', re.I)
|
||||
|
||||
# Extend HTTP Error Messages
|
||||
JOIN_HTTP_ERROR_MAP = {
|
||||
401: 'Unauthorized - Invalid Token.',
|
||||
}
|
||||
|
||||
# Used to detect a device
|
||||
IS_DEVICE_RE = re.compile(r'([a-z0-9]{32})', re.I)
|
||||
IS_DEVICE_RE = re.compile(r'^[a-z0-9]{32}$', re.I)
|
||||
|
||||
# Used to detect a device
|
||||
IS_GROUP_RE = re.compile(
|
||||
|
@ -64,6 +62,24 @@ IS_GROUP_RE = re.compile(
|
|||
JOIN_IMAGE_XY = NotifyImageSize.XY_72
|
||||
|
||||
|
||||
# Priorities
|
||||
class JoinPriority(object):
|
||||
LOW = -2
|
||||
MODERATE = -1
|
||||
NORMAL = 0
|
||||
HIGH = 1
|
||||
EMERGENCY = 2
|
||||
|
||||
|
||||
JOIN_PRIORITIES = (
|
||||
JoinPriority.LOW,
|
||||
JoinPriority.MODERATE,
|
||||
JoinPriority.NORMAL,
|
||||
JoinPriority.HIGH,
|
||||
JoinPriority.EMERGENCY,
|
||||
)
|
||||
|
||||
|
||||
class NotifyJoin(NotifyBase):
|
||||
"""
|
||||
A wrapper for Join Notifications
|
||||
|
@ -104,14 +120,14 @@ class NotifyJoin(NotifyBase):
|
|||
'apikey': {
|
||||
'name': _('API Key'),
|
||||
'type': 'string',
|
||||
'regex': (r'[a-z0-9]{32}', 'i'),
|
||||
'regex': (r'^[a-z0-9]{32}$', 'i'),
|
||||
'private': True,
|
||||
'required': True,
|
||||
},
|
||||
'device': {
|
||||
'name': _('Device ID'),
|
||||
'type': 'string',
|
||||
'regex': (r'[a-z0-9]{32}', 'i'),
|
||||
'regex': (r'^[a-z0-9]{32}$', 'i'),
|
||||
'map_to': 'targets',
|
||||
},
|
||||
'group': {
|
||||
|
@ -136,37 +152,79 @@ class NotifyJoin(NotifyBase):
|
|||
'default': False,
|
||||
'map_to': 'include_image',
|
||||
},
|
||||
'priority': {
|
||||
'name': _('Priority'),
|
||||
'type': 'choice:int',
|
||||
'values': JOIN_PRIORITIES,
|
||||
'default': JoinPriority.NORMAL,
|
||||
},
|
||||
'to': {
|
||||
'alias_of': 'targets',
|
||||
},
|
||||
})
|
||||
|
||||
def __init__(self, apikey, targets, include_image=True, **kwargs):
|
||||
def __init__(self, apikey, targets=None, include_image=True, priority=None,
|
||||
**kwargs):
|
||||
"""
|
||||
Initialize Join Object
|
||||
"""
|
||||
super(NotifyJoin, self).__init__(**kwargs)
|
||||
|
||||
if not VALIDATE_APIKEY.match(apikey.strip()):
|
||||
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()
|
||||
|
||||
# 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
|
||||
|
||||
# API Key (associated with project)
|
||||
self.apikey = validate_regex(
|
||||
apikey, *self.template_tokens['apikey']['regex'])
|
||||
if not self.apikey:
|
||||
msg = 'An invalid Join API Key ' \
|
||||
'({}) was specified.'.format(apikey)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
# The Priority of the message
|
||||
if priority not in JOIN_PRIORITIES:
|
||||
self.priority = self.template_args['priority']['default']
|
||||
|
||||
else:
|
||||
self.priority = priority
|
||||
|
||||
# Prepare a list of targets to store entries into
|
||||
self.targets = list()
|
||||
|
||||
# Prepare a parsed list of targets
|
||||
targets = parse_list(targets)
|
||||
if len(targets) == 0:
|
||||
# Default to everyone if our list was empty
|
||||
self.targets.append(self.default_join_group)
|
||||
return
|
||||
|
||||
# If we reach here we have some targets to parse
|
||||
while len(targets):
|
||||
# Parse our targets
|
||||
target = targets.pop(0)
|
||||
group_re = IS_GROUP_RE.match(target)
|
||||
if group_re:
|
||||
self.targets.append(
|
||||
'group.{}'.format(group_re.group('name').lower()))
|
||||
continue
|
||||
|
||||
elif IS_DEVICE_RE.match(target):
|
||||
self.targets.append(target)
|
||||
continue
|
||||
|
||||
self.logger.warning(
|
||||
'Ignoring invalid Join device/group "{}"'.format(target)
|
||||
)
|
||||
|
||||
if not self.targets:
|
||||
msg = 'No Join targets to notify.'
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
return
|
||||
|
||||
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
||||
"""
|
||||
Perform Join Notification
|
||||
|
@ -180,26 +238,17 @@ class NotifyJoin(NotifyBase):
|
|||
# error tracking (used for function return)
|
||||
has_error = False
|
||||
|
||||
# Create a copy of the devices list
|
||||
devices = list(self.devices)
|
||||
while len(devices):
|
||||
device = devices.pop(0)
|
||||
group_re = IS_GROUP_RE.match(device)
|
||||
if group_re:
|
||||
device = 'group.{}'.format(group_re.group('name').lower())
|
||||
# Capture a list of our targets to notify
|
||||
targets = list(self.targets)
|
||||
|
||||
elif not IS_DEVICE_RE.match(device):
|
||||
self.logger.warning(
|
||||
'Skipping specified invalid device/group "{}"'
|
||||
.format(device)
|
||||
)
|
||||
# Mark our failure
|
||||
has_error = True
|
||||
continue
|
||||
while len(targets):
|
||||
# Pop the first element off of our list
|
||||
target = targets.pop(0)
|
||||
|
||||
url_args = {
|
||||
'apikey': self.apikey,
|
||||
'deviceId': device,
|
||||
'deviceId': target,
|
||||
'priority': str(self.priority),
|
||||
'title': title,
|
||||
'text': body,
|
||||
}
|
||||
|
@ -242,7 +291,7 @@ class NotifyJoin(NotifyBase):
|
|||
self.logger.warning(
|
||||
'Failed to send Join notification to {}: '
|
||||
'{}{}error={}.'.format(
|
||||
device,
|
||||
target,
|
||||
status_str,
|
||||
', ' if status_str else '',
|
||||
r.status_code))
|
||||
|
@ -255,12 +304,12 @@ class NotifyJoin(NotifyBase):
|
|||
continue
|
||||
|
||||
else:
|
||||
self.logger.info('Sent Join notification to %s.' % device)
|
||||
self.logger.info('Sent Join notification to %s.' % target)
|
||||
|
||||
except requests.RequestException as e:
|
||||
self.logger.warning(
|
||||
'A Connection error occured sending Join:%s '
|
||||
'notification.' % device
|
||||
'notification.' % target
|
||||
)
|
||||
self.logger.debug('Socket Exception: %s' % str(e))
|
||||
|
||||
|
@ -274,20 +323,30 @@ class NotifyJoin(NotifyBase):
|
|||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
"""
|
||||
_map = {
|
||||
JoinPriority.LOW: 'low',
|
||||
JoinPriority.MODERATE: 'moderate',
|
||||
JoinPriority.NORMAL: 'normal',
|
||||
JoinPriority.HIGH: 'high',
|
||||
JoinPriority.EMERGENCY: 'emergency',
|
||||
}
|
||||
|
||||
# Define any arguments set
|
||||
args = {
|
||||
'format': self.notify_format,
|
||||
'overflow': self.overflow_mode,
|
||||
'priority':
|
||||
_map[self.template_args['priority']['default']]
|
||||
if self.priority not in _map else _map[self.priority],
|
||||
'image': 'yes' if self.include_image else 'no',
|
||||
'verify': 'yes' if self.verify_certificate else 'no',
|
||||
}
|
||||
|
||||
return '{schema}://{apikey}/{devices}/?{args}'.format(
|
||||
return '{schema}://{apikey}/{targets}/?{args}'.format(
|
||||
schema=self.secure_protocol,
|
||||
apikey=self.pprint(self.apikey, privacy, safe=''),
|
||||
devices='/'.join([NotifyJoin.quote(x, safe='')
|
||||
for x in self.devices]),
|
||||
targets='/'.join([NotifyJoin.quote(x, safe='')
|
||||
for x in self.targets]),
|
||||
args=NotifyJoin.urlencode(args))
|
||||
|
||||
@staticmethod
|
||||
|
@ -310,6 +369,23 @@ class NotifyJoin(NotifyBase):
|
|||
# Unquote our API Key
|
||||
results['apikey'] = NotifyJoin.unquote(results['apikey'])
|
||||
|
||||
# Set our priority
|
||||
if 'priority' in results['qsd'] and len(results['qsd']['priority']):
|
||||
_map = {
|
||||
'l': JoinPriority.LOW,
|
||||
'm': JoinPriority.MODERATE,
|
||||
'n': JoinPriority.NORMAL,
|
||||
'h': JoinPriority.HIGH,
|
||||
'e': JoinPriority.EMERGENCY,
|
||||
}
|
||||
try:
|
||||
results['priority'] = \
|
||||
_map[results['qsd']['priority'][0].lower()]
|
||||
|
||||
except KeyError:
|
||||
# No priority was set
|
||||
pass
|
||||
|
||||
# Our Devices
|
||||
results['targets'] = list()
|
||||
if results['user']:
|
||||
|
|
|
@ -33,27 +33,14 @@
|
|||
# The API reference used to build this plugin was documented here:
|
||||
# https://docs.kumulos.com/messaging/api/#sending-in-app-messages
|
||||
#
|
||||
import re
|
||||
import requests
|
||||
from json import dumps
|
||||
|
||||
from .NotifyBase import NotifyBase
|
||||
from ..common import NotifyType
|
||||
from ..utils import validate_regex
|
||||
from ..AppriseLocale import gettext_lazy as _
|
||||
|
||||
#
|
||||
# API Key is a UUID; below is the regex matching
|
||||
UUID4_RE = \
|
||||
r'[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}'
|
||||
|
||||
# Secret Key Regex Mapping
|
||||
SERVER_KEY_RE = r'[A-Z0-9+]{36}'
|
||||
|
||||
# API Key
|
||||
VALIDATE_APIKEY = re.compile(UUID4_RE, re.I)
|
||||
|
||||
VALIDATE_SERVER_KEY = re.compile(SERVER_KEY_RE, re.I)
|
||||
|
||||
# Extend HTTP Error Messages
|
||||
KUMULOS_HTTP_ERROR_MAP = {
|
||||
401: 'Unauthorized - Invalid API and/or Server Key.',
|
||||
|
@ -61,9 +48,6 @@ KUMULOS_HTTP_ERROR_MAP = {
|
|||
400: 'Bad Request - Targeted users do not exist or have unsubscribed.',
|
||||
}
|
||||
|
||||
# Used to break path apart into list of channels
|
||||
TARGET_LIST_DELIM = re.compile(r'[ \t\r\n,#\\/]+')
|
||||
|
||||
|
||||
class NotifyKumulos(NotifyBase):
|
||||
"""
|
||||
|
@ -103,14 +87,16 @@ class NotifyKumulos(NotifyBase):
|
|||
'type': 'string',
|
||||
'private': True,
|
||||
'required': True,
|
||||
'regex': (UUID4_RE, 'i'),
|
||||
# UUID4
|
||||
'regex': (r'^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-'
|
||||
r'[89ab][0-9a-f]{3}-[0-9a-f]{12}$', 'i')
|
||||
},
|
||||
'serverkey': {
|
||||
'name': _('Server Key'),
|
||||
'type': 'string',
|
||||
'private': True,
|
||||
'required': True,
|
||||
'regex': (SERVER_KEY_RE, 'i'),
|
||||
'regex': (r'^[A-Z0-9+]{36}$', 'i'),
|
||||
},
|
||||
})
|
||||
|
||||
|
@ -120,27 +106,21 @@ class NotifyKumulos(NotifyBase):
|
|||
"""
|
||||
super(NotifyKumulos, self).__init__(**kwargs)
|
||||
|
||||
if not apikey:
|
||||
msg = 'The Kumulos API Key is not specified.'
|
||||
# API Key (associated with project)
|
||||
self.apikey = validate_regex(
|
||||
apikey, *self.template_tokens['apikey']['regex'])
|
||||
if not self.apikey:
|
||||
msg = 'An invalid Kumulos API Key ' \
|
||||
'({}) was specified.'.format(apikey)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
self.apikey = apikey.strip()
|
||||
if not VALIDATE_APIKEY.match(self.apikey):
|
||||
msg = 'The Kumulos API Key specified ({}) is invalid.'\
|
||||
.format(apikey)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
if not serverkey:
|
||||
msg = 'The Kumulos Server Key is not specified.'
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
self.serverkey = serverkey.strip()
|
||||
if not VALIDATE_SERVER_KEY.match(self.serverkey):
|
||||
msg = 'The Kumulos Server Key specified ({}) is invalid.'\
|
||||
.format(serverkey)
|
||||
# Server Key (associated with project)
|
||||
self.serverkey = validate_regex(
|
||||
serverkey, *self.template_tokens['serverkey']['regex'])
|
||||
if not self.serverkey:
|
||||
msg = 'An invalid Kumulos Server Key ' \
|
||||
'({}) was specified.'.format(serverkey)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
|
|
|
@ -37,11 +37,9 @@ import requests
|
|||
from .NotifyBase import NotifyBase
|
||||
from ..common import NotifyType
|
||||
from ..utils import parse_list
|
||||
from ..utils import validate_regex
|
||||
from ..AppriseLocale import gettext_lazy as _
|
||||
|
||||
# Token required as part of the API request
|
||||
VALIDATE_AUTHKEY = re.compile(r'^[a-z0-9]+$', re.I)
|
||||
|
||||
# Some Phone Number Detection
|
||||
IS_PHONE_NO = re.compile(r'^\+?(?P<phone>[0-9\s)(+-]+)\s*$')
|
||||
|
||||
|
@ -118,13 +116,14 @@ class NotifyMSG91(NotifyBase):
|
|||
'name': _('Authentication Key'),
|
||||
'type': 'string',
|
||||
'required': True,
|
||||
'regex': (r'[a-z0-9]+', 'i'),
|
||||
'private': True,
|
||||
'regex': (r'^[a-z0-9]+$', 'i'),
|
||||
},
|
||||
'target_phone': {
|
||||
'name': _('Target Phone No'),
|
||||
'type': 'string',
|
||||
'prefix': '+',
|
||||
'regex': (r'[0-9\s)(+-]+', 'i'),
|
||||
'regex': (r'^[0-9\s)(+-]+$', 'i'),
|
||||
'map_to': 'targets',
|
||||
},
|
||||
'targets': {
|
||||
|
@ -162,19 +161,12 @@ class NotifyMSG91(NotifyBase):
|
|||
"""
|
||||
super(NotifyMSG91, self).__init__(**kwargs)
|
||||
|
||||
try:
|
||||
# The authentication key associated with the account
|
||||
self.authkey = authkey.strip()
|
||||
|
||||
except AttributeError:
|
||||
# Token was None
|
||||
msg = 'No MSG91 authentication key was specified.'
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
if not VALIDATE_AUTHKEY.match(self.authkey):
|
||||
msg = 'The MSG91 authentication key specified ({}) is invalid.'\
|
||||
.format(self.authkey)
|
||||
# Authentication Key (associated with project)
|
||||
self.authkey = validate_regex(
|
||||
authkey, *self.template_tokens['authkey']['regex'])
|
||||
if not self.authkey:
|
||||
msg = 'An invalid MSG91 Authentication Key ' \
|
||||
'({}) was specified.'.format(authkey)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
|
@ -237,16 +229,19 @@ class NotifyMSG91(NotifyBase):
|
|||
'({}) specified.'.format(target),
|
||||
)
|
||||
|
||||
if not self.targets:
|
||||
# We have a bot token and no target(s) to message
|
||||
msg = 'No MSG91 targets to notify.'
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
return
|
||||
|
||||
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
||||
"""
|
||||
Perform MSG91 Notification
|
||||
"""
|
||||
|
||||
if not len(self.targets):
|
||||
# There were no services to notify
|
||||
self.logger.warning('There were no MSG91 targets to notify')
|
||||
return False
|
||||
|
||||
# Prepare our headers
|
||||
headers = {
|
||||
'User-Agent': self.app_id,
|
||||
|
|
|
@ -69,24 +69,13 @@ from ..common import NotifyImageSize
|
|||
from ..common import NotifyType
|
||||
from ..common import NotifyFormat
|
||||
from ..utils import parse_bool
|
||||
from ..utils import validate_regex
|
||||
from ..AppriseLocale import gettext_lazy as _
|
||||
|
||||
# Used to prepare our UUID regex matching
|
||||
UUID4_RE = \
|
||||
r'[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}'
|
||||
|
||||
# Token required as part of the API request
|
||||
# /AAAAAAAAA@AAAAAAAAA/........./.........
|
||||
VALIDATE_TOKEN_A = re.compile(r'{}@{}'.format(UUID4_RE, UUID4_RE), re.I)
|
||||
|
||||
# Token required as part of the API request
|
||||
# /................../BBBBBBBBB/..........
|
||||
VALIDATE_TOKEN_B = re.compile(r'[A-Za-z0-9]{32}')
|
||||
|
||||
# Token required as part of the API request
|
||||
# /........./........./CCCCCCCCCCCCCCCCCCCCCCCC
|
||||
VALIDATE_TOKEN_C = re.compile(UUID4_RE, re.I)
|
||||
|
||||
|
||||
class NotifyMSTeams(NotifyBase):
|
||||
"""
|
||||
|
@ -124,26 +113,32 @@ class NotifyMSTeams(NotifyBase):
|
|||
|
||||
# Define our template tokens
|
||||
template_tokens = dict(NotifyBase.template_tokens, **{
|
||||
# Token required as part of the API request
|
||||
# /AAAAAAAAA@AAAAAAAAA/........./.........
|
||||
'token_a': {
|
||||
'name': _('Token A'),
|
||||
'type': 'string',
|
||||
'private': True,
|
||||
'required': True,
|
||||
'regex': (r'{}@{}'.format(UUID4_RE, UUID4_RE), 'i'),
|
||||
'regex': (r'^{}@{}$'.format(UUID4_RE, UUID4_RE), 'i'),
|
||||
},
|
||||
# Token required as part of the API request
|
||||
# /................../BBBBBBBBB/..........
|
||||
'token_b': {
|
||||
'name': _('Token B'),
|
||||
'type': 'string',
|
||||
'private': True,
|
||||
'required': True,
|
||||
'regex': (r'[a-z0-9]{32}', 'i'),
|
||||
'regex': (r'^[A-Za-z0-9]{32}$', 'i'),
|
||||
},
|
||||
# Token required as part of the API request
|
||||
# /........./........./CCCCCCCCCCCCCCCCCCCCCCCC
|
||||
'token_c': {
|
||||
'name': _('Token C'),
|
||||
'type': 'string',
|
||||
'private': True,
|
||||
'required': True,
|
||||
'regex': (UUID4_RE, 'i'),
|
||||
'regex': (r'^{}$'.format(UUID4_RE), 'i'),
|
||||
},
|
||||
})
|
||||
|
||||
|
@ -164,51 +159,35 @@ class NotifyMSTeams(NotifyBase):
|
|||
"""
|
||||
super(NotifyMSTeams, self).__init__(**kwargs)
|
||||
|
||||
if not token_a:
|
||||
msg = 'The first MSTeams API token is not specified.'
|
||||
self.token_a = validate_regex(
|
||||
token_a, *self.template_tokens['token_a']['regex'])
|
||||
if not self.token_a:
|
||||
msg = 'An invalid MSTeams (first) Token ' \
|
||||
'({}) was specified.'.format(token_a)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
if not token_b:
|
||||
msg = 'The second MSTeams API token is not specified.'
|
||||
self.token_b = validate_regex(
|
||||
token_b, *self.template_tokens['token_b']['regex'])
|
||||
if not self.token_b:
|
||||
msg = 'An invalid MSTeams (second) Token ' \
|
||||
'({}) was specified.'.format(token_b)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
if not token_c:
|
||||
msg = 'The third MSTeams API token is not specified.'
|
||||
self.token_c = validate_regex(
|
||||
token_c, *self.template_tokens['token_c']['regex'])
|
||||
if not self.token_c:
|
||||
msg = 'An invalid MSTeams (third) Token ' \
|
||||
'({}) was specified.'.format(token_c)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
if not VALIDATE_TOKEN_A.match(token_a.strip()):
|
||||
msg = 'The first MSTeams 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()):
|
||||
msg = 'The second MSTeams 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()):
|
||||
msg = 'The third MSTeams 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()
|
||||
|
||||
# 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):
|
||||
"""
|
||||
Perform Microsoft Teams Notification
|
||||
|
|
|
@ -57,6 +57,7 @@ from .NotifyBase import NotifyBase
|
|||
from ..common import NotifyType
|
||||
from ..utils import parse_list
|
||||
from ..utils import is_email
|
||||
from ..utils import validate_regex
|
||||
from ..AppriseLocale import gettext_lazy as _
|
||||
|
||||
# Provide some known codes Mailgun uses and what they translate to:
|
||||
|
@ -169,19 +170,17 @@ class NotifyMailgun(NotifyBase):
|
|||
"""
|
||||
super(NotifyMailgun, self).__init__(**kwargs)
|
||||
|
||||
try:
|
||||
# The personal access apikey associated with the account
|
||||
self.apikey = apikey.strip()
|
||||
|
||||
except AttributeError:
|
||||
# Token was None
|
||||
msg = 'No API Key was specified.'
|
||||
# API Key (associated with project)
|
||||
self.apikey = validate_regex(apikey)
|
||||
if not self.apikey:
|
||||
msg = 'An invalid Mailgun API Key ' \
|
||||
'({}) was specified.'.format(apikey)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
# Validate our username
|
||||
if not self.user:
|
||||
msg = 'No username was specified.'
|
||||
msg = 'No Mailgun username was specified.'
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
|
@ -198,7 +197,7 @@ class NotifyMailgun(NotifyBase):
|
|||
raise
|
||||
except:
|
||||
# Invalid region specified
|
||||
msg = 'The region specified ({}) is invalid.' \
|
||||
msg = 'The Mailgun region specified ({}) is invalid.' \
|
||||
.format(region_name)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
|
|
@ -23,7 +23,6 @@
|
|||
# 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
|
||||
|
@ -33,15 +32,13 @@ from ..common import NotifyImageSize
|
|||
from ..common import NotifyType
|
||||
from ..utils import parse_bool
|
||||
from ..utils import parse_list
|
||||
from ..utils import validate_regex
|
||||
from ..AppriseLocale import gettext_lazy as _
|
||||
|
||||
# Some Reference Locations:
|
||||
# - https://docs.mattermost.com/developer/webhooks-incoming.html
|
||||
# - https://docs.mattermost.com/administration/config-settings.html
|
||||
|
||||
# Used to validate Authorization Token
|
||||
VALIDATE_AUTHTOKEN = re.compile(r'[a-z0-9]{24,32}', re.I)
|
||||
|
||||
|
||||
class NotifyMatterMost(NotifyBase):
|
||||
"""
|
||||
|
@ -97,7 +94,7 @@ class NotifyMatterMost(NotifyBase):
|
|||
'authtoken': {
|
||||
'name': _('Access Key'),
|
||||
'type': 'string',
|
||||
'regex': (r'[a-z0-9]{24,32}', 'i'),
|
||||
'regex': (r'^[a-z0-9]{24,32}$', 'i'),
|
||||
'private': True,
|
||||
'required': True,
|
||||
},
|
||||
|
@ -152,17 +149,12 @@ class NotifyMatterMost(NotifyBase):
|
|||
self.fullpath = '' if not isinstance(
|
||||
fullpath, six.string_types) else fullpath.strip()
|
||||
|
||||
# Our Authorization Token
|
||||
self.authtoken = authtoken
|
||||
|
||||
# Validate authtoken
|
||||
if not authtoken:
|
||||
msg = 'Missing MatterMost Authorization Token.'
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
if not VALIDATE_AUTHTOKEN.match(authtoken):
|
||||
msg = 'Invalid MatterMost Authorization Token Specified.'
|
||||
# Authorization Token (associated with project)
|
||||
self.authtoken = validate_regex(
|
||||
authtoken, *self.template_tokens['authtoken']['regex'])
|
||||
if not self.authtoken:
|
||||
msg = 'An invalid MatterMost Authorization Token ' \
|
||||
'({}) was specified.'.format(authtoken)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
|
@ -340,7 +332,6 @@ class NotifyMatterMost(NotifyBase):
|
|||
# all entries before it will be our path
|
||||
tokens = NotifyMatterMost.split_path(results['fullpath'])
|
||||
|
||||
# Apply our settings now
|
||||
results['authtoken'] = None if not tokens else tokens.pop()
|
||||
|
||||
# Store our path
|
||||
|
|
|
@ -35,11 +35,9 @@ import requests
|
|||
from .NotifyBase import NotifyBase
|
||||
from ..common import NotifyType
|
||||
from ..utils import parse_list
|
||||
from ..utils import validate_regex
|
||||
from ..AppriseLocale import gettext_lazy as _
|
||||
|
||||
# Token required as part of the API request
|
||||
VALIDATE_APIKEY = re.compile(r'^[a-z0-9]{25}$', re.I)
|
||||
|
||||
# Some Phone Number Detection
|
||||
IS_PHONE_NO = re.compile(r'^\+?(?P<phone>[0-9\s)(+-]+)\s*$')
|
||||
|
||||
|
@ -83,20 +81,21 @@ class NotifyMessageBird(NotifyBase):
|
|||
'name': _('API Key'),
|
||||
'type': 'string',
|
||||
'required': True,
|
||||
'regex': (r'[a-z0-9]{25}', 'i'),
|
||||
'private': True,
|
||||
'regex': (r'^[a-z0-9]{25}$', 'i'),
|
||||
},
|
||||
'source': {
|
||||
'name': _('Source Phone No'),
|
||||
'type': 'string',
|
||||
'prefix': '+',
|
||||
'required': True,
|
||||
'regex': (r'[0-9\s)(+-]+', 'i'),
|
||||
'regex': (r'^[0-9\s)(+-]+$', 'i'),
|
||||
},
|
||||
'target_phone': {
|
||||
'name': _('Target Phone No'),
|
||||
'type': 'string',
|
||||
'prefix': '+',
|
||||
'regex': (r'[0-9\s)(+-]+', 'i'),
|
||||
'regex': (r'^[0-9\s)(+-]+$', 'i'),
|
||||
'map_to': 'targets',
|
||||
},
|
||||
'targets': {
|
||||
|
@ -121,19 +120,12 @@ class NotifyMessageBird(NotifyBase):
|
|||
"""
|
||||
super(NotifyMessageBird, self).__init__(**kwargs)
|
||||
|
||||
try:
|
||||
# The authentication key associated with the account
|
||||
self.apikey = apikey.strip()
|
||||
|
||||
except AttributeError:
|
||||
# Token was None
|
||||
msg = 'No MessageBird authentication key was specified.'
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
if not VALIDATE_APIKEY.match(self.apikey):
|
||||
msg = 'The MessageBird authentication key specified ({}) is ' \
|
||||
'invalid.'.format(self.apikey)
|
||||
# API Key (associated with project)
|
||||
self.apikey = validate_regex(
|
||||
apikey, *self.template_tokens['apikey']['regex'])
|
||||
if not self.apikey:
|
||||
msg = 'An invalid MessageBird API Key ' \
|
||||
'({}) was specified.'.format(apikey)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
|
@ -158,7 +150,14 @@ class NotifyMessageBird(NotifyBase):
|
|||
# Parse our targets
|
||||
self.targets = list()
|
||||
|
||||
for target in parse_list(targets):
|
||||
targets = parse_list(targets)
|
||||
if not targets:
|
||||
# No sources specified, use our own phone no
|
||||
self.targets.append(self.source)
|
||||
return
|
||||
|
||||
# otherwise, store all of our target numbers
|
||||
for target in targets:
|
||||
# Validate targets and drop bad ones:
|
||||
result = IS_PHONE_NO.match(target)
|
||||
if result:
|
||||
|
@ -180,6 +179,14 @@ class NotifyMessageBird(NotifyBase):
|
|||
'({}) specified.'.format(target),
|
||||
)
|
||||
|
||||
if not self.targets:
|
||||
# We have a bot token and no target(s) to message
|
||||
msg = 'No MessageBird targets to notify.'
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
return
|
||||
|
||||
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
||||
"""
|
||||
Perform MessageBird Notification
|
||||
|
@ -202,13 +209,10 @@ class NotifyMessageBird(NotifyBase):
|
|||
'body': body,
|
||||
|
||||
}
|
||||
|
||||
# Create a copy of the targets list
|
||||
targets = list(self.targets)
|
||||
|
||||
if len(targets) == 0:
|
||||
# No sources specified, use our own phone no
|
||||
targets.append(self.source)
|
||||
|
||||
while len(targets):
|
||||
# Get our target to notify
|
||||
target = targets.pop(0)
|
||||
|
|
|
@ -36,12 +36,9 @@ from .NotifyBase import NotifyBase
|
|||
from ..URLBase import PrivacyMode
|
||||
from ..common import NotifyType
|
||||
from ..utils import parse_list
|
||||
from ..utils import validate_regex
|
||||
from ..AppriseLocale import gettext_lazy as _
|
||||
|
||||
# Token required as part of the API request
|
||||
VALIDATE_APIKEY = re.compile(r'^[a-z0-9]{8}$', re.I)
|
||||
VALIDATE_SECRET = re.compile(r'^[a-z0-9]{16}$', re.I)
|
||||
|
||||
# Some Phone Number Detection
|
||||
IS_PHONE_NO = re.compile(r'^\+?(?P<phone>[0-9\s)(+-]+)\s*$')
|
||||
|
||||
|
@ -94,27 +91,28 @@ class NotifyNexmo(NotifyBase):
|
|||
'name': _('API Key'),
|
||||
'type': 'string',
|
||||
'required': True,
|
||||
'regex': (r'AC[a-z0-9]{8}', 'i'),
|
||||
'regex': (r'^AC[a-z0-9]{8}$', 'i'),
|
||||
'private': True,
|
||||
},
|
||||
'secret': {
|
||||
'name': _('API Secret'),
|
||||
'type': 'string',
|
||||
'private': True,
|
||||
'required': True,
|
||||
'regex': (r'[a-z0-9]{16}', 'i'),
|
||||
'regex': (r'^[a-z0-9]{16}$', 'i'),
|
||||
},
|
||||
'from_phone': {
|
||||
'name': _('From Phone No'),
|
||||
'type': 'string',
|
||||
'required': True,
|
||||
'regex': (r'\+?[0-9\s)(+-]+', 'i'),
|
||||
'regex': (r'^\+?[0-9\s)(+-]+$', 'i'),
|
||||
'map_to': 'source',
|
||||
},
|
||||
'target_phone': {
|
||||
'name': _('Target Phone No'),
|
||||
'type': 'string',
|
||||
'prefix': '+',
|
||||
'regex': (r'[0-9\s)(+-]+', 'i'),
|
||||
'regex': (r'^[0-9\s)(+-]+$', 'i'),
|
||||
'map_to': 'targets',
|
||||
},
|
||||
'targets': {
|
||||
|
@ -153,35 +151,21 @@ class NotifyNexmo(NotifyBase):
|
|||
"""
|
||||
super(NotifyNexmo, self).__init__(**kwargs)
|
||||
|
||||
try:
|
||||
# The Account SID associated with the account
|
||||
self.apikey = apikey.strip()
|
||||
|
||||
except AttributeError:
|
||||
# Token was None
|
||||
msg = 'No Nexmo APIKey was specified.'
|
||||
# API Key (associated with project)
|
||||
self.apikey = validate_regex(
|
||||
apikey, *self.template_tokens['apikey']['regex'])
|
||||
if not self.apikey:
|
||||
msg = 'An invalid Nexmo API Key ' \
|
||||
'({}) was specified.'.format(apikey)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
if not VALIDATE_APIKEY.match(self.apikey):
|
||||
msg = 'The Nexmo API Key specified ({}) is invalid.'\
|
||||
.format(self.apikey)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
try:
|
||||
# The Account SID associated with the account
|
||||
self.secret = secret.strip()
|
||||
|
||||
except AttributeError:
|
||||
# Token was None
|
||||
msg = 'No Nexmo API Secret was specified.'
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
if not VALIDATE_SECRET.match(self.secret):
|
||||
msg = 'The Nexmo API Secret specified ({}) is invalid.'\
|
||||
.format(self.secret)
|
||||
# API Secret (associated with project)
|
||||
self.secret = validate_regex(
|
||||
secret, *self.template_tokens['secret']['regex'])
|
||||
if not self.secret:
|
||||
msg = 'An invalid Nexmo API Secret ' \
|
||||
'({}) was specified.'.format(secret)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
|
@ -242,6 +226,8 @@ class NotifyNexmo(NotifyBase):
|
|||
'({}) specified.'.format(target),
|
||||
)
|
||||
|
||||
return
|
||||
|
||||
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
||||
"""
|
||||
Perform Nexmo Notification
|
||||
|
|
|
@ -23,19 +23,13 @@
|
|||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
|
||||
import re
|
||||
import requests
|
||||
|
||||
from .NotifyBase import NotifyBase
|
||||
from ..common import NotifyType
|
||||
from ..utils import validate_regex
|
||||
from ..AppriseLocale import gettext_lazy as _
|
||||
|
||||
# Used to validate API Key
|
||||
VALIDATE_APIKEY = re.compile(r'[A-Za-z0-9]{40}')
|
||||
|
||||
# Used to validate Provider Key
|
||||
VALIDATE_PROVIDERKEY = re.compile(r'[A-Za-z0-9]{40}')
|
||||
|
||||
|
||||
# Priorities
|
||||
class ProwlPriority(object):
|
||||
|
@ -104,11 +98,13 @@ class NotifyProwl(NotifyBase):
|
|||
'type': 'string',
|
||||
'private': True,
|
||||
'required': True,
|
||||
'regex': (r'^[A-Za-z0-9]{40}$', 'i'),
|
||||
},
|
||||
'providerkey': {
|
||||
'name': _('Provider Key'),
|
||||
'type': 'string',
|
||||
'private': True,
|
||||
'regex': (r'^[A-Za-z0-9]{40}$', 'i'),
|
||||
},
|
||||
})
|
||||
|
||||
|
@ -129,31 +125,35 @@ class NotifyProwl(NotifyBase):
|
|||
super(NotifyProwl, self).__init__(**kwargs)
|
||||
|
||||
if priority not in PROWL_PRIORITIES:
|
||||
self.priority = ProwlPriority.NORMAL
|
||||
self.priority = self.template_args['priority']['default']
|
||||
|
||||
else:
|
||||
self.priority = priority
|
||||
|
||||
if not VALIDATE_APIKEY.match(apikey):
|
||||
msg = 'The API key specified ({}) is invalid.'.format(apikey)
|
||||
# API Key (associated with project)
|
||||
self.apikey = validate_regex(
|
||||
apikey, *self.template_tokens['apikey']['regex'])
|
||||
if not self.apikey:
|
||||
msg = 'An invalid Prowl API Key ' \
|
||||
'({}) was specified.'.format(apikey)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
# Store the API key
|
||||
self.apikey = apikey
|
||||
|
||||
# Store the provider key (if specified)
|
||||
if providerkey:
|
||||
if not VALIDATE_PROVIDERKEY.match(providerkey):
|
||||
msg = \
|
||||
'The Provider key specified ({}) is invalid.' \
|
||||
.format(providerkey)
|
||||
|
||||
self.providerkey = validate_regex(
|
||||
providerkey, *self.template_tokens['providerkey']['regex'])
|
||||
if not self.providerkey:
|
||||
msg = 'An invalid Prowl Provider Key ' \
|
||||
'({}) was specified.'.format(providerkey)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
# Store the Provider Key
|
||||
self.providerkey = providerkey
|
||||
else:
|
||||
# No provider key was set
|
||||
self.providerkey = None
|
||||
|
||||
return
|
||||
|
||||
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
||||
"""
|
||||
|
|
|
@ -30,6 +30,7 @@ from .NotifyBase import NotifyBase
|
|||
from ..utils import GET_EMAIL_RE
|
||||
from ..common import NotifyType
|
||||
from ..utils import parse_list
|
||||
from ..utils import validate_regex
|
||||
from ..AppriseLocale import gettext_lazy as _
|
||||
|
||||
# Flag used as a placeholder to sending to all devices
|
||||
|
@ -110,12 +111,20 @@ class NotifyPushBullet(NotifyBase):
|
|||
"""
|
||||
super(NotifyPushBullet, self).__init__(**kwargs)
|
||||
|
||||
self.accesstoken = accesstoken
|
||||
# Access Token (associated with project)
|
||||
self.accesstoken = validate_regex(accesstoken)
|
||||
if not self.accesstoken:
|
||||
msg = 'An invalid PushBullet Access Token ' \
|
||||
'({}) was specified.'.format(accesstoken)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
self.targets = parse_list(targets)
|
||||
if len(self.targets) == 0:
|
||||
self.targets = (PUSHBULLET_SEND_TO_ALL, )
|
||||
|
||||
return
|
||||
|
||||
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
||||
"""
|
||||
Perform PushBullet Notification
|
||||
|
|
|
@ -32,10 +32,11 @@ from .NotifyBase import NotifyBase
|
|||
from ..URLBase import PrivacyMode
|
||||
from ..common import NotifyType
|
||||
from ..utils import parse_list
|
||||
from ..utils import validate_regex
|
||||
from ..AppriseLocale import gettext_lazy as _
|
||||
|
||||
# Used to detect and parse channels
|
||||
IS_CHANNEL = re.compile(r'^#(?P<name>[A-Za-z0-9]+)$')
|
||||
IS_CHANNEL = re.compile(r'^#?(?P<name>[A-Za-z0-9]+)$')
|
||||
|
||||
# Used to detect and parse a users push id
|
||||
IS_USER_PUSHED_ID = re.compile(r'^@(?P<name>[A-Za-z0-9]+)$')
|
||||
|
@ -121,13 +122,19 @@ class NotifyPushed(NotifyBase):
|
|||
"""
|
||||
super(NotifyPushed, self).__init__(**kwargs)
|
||||
|
||||
if not app_key:
|
||||
msg = 'An invalid Application Key was specified.'
|
||||
# Application Key (associated with project)
|
||||
self.app_key = validate_regex(app_key)
|
||||
if not self.app_key:
|
||||
msg = 'An invalid Pushed Application Key ' \
|
||||
'({}) was specified.'.format(app_key)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
if not app_secret:
|
||||
msg = 'An invalid Application Secret was specified.'
|
||||
# Access Secret (associated with project)
|
||||
self.app_secret = validate_regex(app_secret)
|
||||
if not self.app_secret:
|
||||
msg = 'An invalid Pushed Application Secret ' \
|
||||
'({}) was specified.'.format(app_secret)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
|
@ -137,8 +144,11 @@ class NotifyPushed(NotifyBase):
|
|||
# Initialize user list
|
||||
self.users = list()
|
||||
|
||||
# Get our targets
|
||||
targets = parse_list(targets)
|
||||
if targets:
|
||||
# Validate recipients and drop bad ones:
|
||||
for target in parse_list(targets):
|
||||
for target in targets:
|
||||
result = IS_CHANNEL.match(target)
|
||||
if result:
|
||||
# store valid device
|
||||
|
@ -156,9 +166,12 @@ class NotifyPushed(NotifyBase):
|
|||
'(%s) specified.' % target,
|
||||
)
|
||||
|
||||
# Store our data
|
||||
self.app_key = app_key
|
||||
self.app_secret = app_secret
|
||||
if len(self.channels) + len(self.users) == 0:
|
||||
# We have no valid channels or users to notify after
|
||||
# explicitly identifying at least one.
|
||||
msg = 'No Pushed targets to notify.'
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
return
|
||||
|
||||
|
@ -325,8 +338,6 @@ class NotifyPushed(NotifyBase):
|
|||
# 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
|
||||
app_key = NotifyPushed.unquote(results['host'])
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@ from json import dumps
|
|||
from .NotifyBase import NotifyBase
|
||||
from ..URLBase import PrivacyMode
|
||||
from ..common import NotifyType
|
||||
from ..utils import validate_regex
|
||||
from ..AppriseLocale import gettext_lazy as _
|
||||
|
||||
|
||||
|
@ -107,14 +108,15 @@ 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.'
|
||||
# Secret Key (associated with project)
|
||||
self.secret_key = validate_regex(secret_key)
|
||||
if not self.secret_key:
|
||||
msg = 'An invalid Pushjet Secret Key ' \
|
||||
'({}) was specified.'.format(secret_key)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
# store our key
|
||||
self.secret_key = secret_key
|
||||
return
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
|
@ -125,9 +127,6 @@ class NotifyPushjet(NotifyBase):
|
|||
args = {
|
||||
'format': self.notify_format,
|
||||
'overflow': self.overflow_mode,
|
||||
'secret': self.pprint(
|
||||
self.secret_key, privacy,
|
||||
mode=PrivacyMode.Secret, quote=False),
|
||||
'verify': 'yes' if self.verify_certificate else 'no',
|
||||
}
|
||||
|
||||
|
@ -142,12 +141,14 @@ class NotifyPushjet(NotifyBase):
|
|||
self.password, privacy, mode=PrivacyMode.Secret, safe=''),
|
||||
)
|
||||
|
||||
return '{schema}://{auth}{hostname}{port}/?{args}'.format(
|
||||
return '{schema}://{auth}{hostname}{port}/{secret}/?{args}'.format(
|
||||
schema=self.secure_protocol if self.secure else self.protocol,
|
||||
auth=auth,
|
||||
hostname=NotifyPushjet.quote(self.host, safe=''),
|
||||
port='' if self.port is None or self.port == default_port
|
||||
else ':{}'.format(self.port),
|
||||
secret=self.pprint(
|
||||
self.secret_key, privacy, mode=PrivacyMode.Secret, safe=''),
|
||||
args=NotifyPushjet.urlencode(args),
|
||||
)
|
||||
|
||||
|
@ -273,7 +274,7 @@ class NotifyPushjet(NotifyBase):
|
|||
# through it in addition to supporting the secret key
|
||||
if 'secret' in results['qsd'] and len(results['qsd']['secret']):
|
||||
results['secret_key'] = \
|
||||
NotifyPushjet.parse_list(results['qsd']['secret'])
|
||||
NotifyPushjet.unquote(results['qsd']['secret'])
|
||||
|
||||
if results.get('secret_key') is None:
|
||||
# Deprication Notice issued for v0.7.9
|
||||
|
|
|
@ -30,18 +30,13 @@ import requests
|
|||
from .NotifyBase import NotifyBase
|
||||
from ..common import NotifyType
|
||||
from ..utils import parse_list
|
||||
from ..utils import validate_regex
|
||||
from ..AppriseLocale import gettext_lazy as _
|
||||
|
||||
# Flag used as a placeholder to sending to all devices
|
||||
PUSHOVER_SEND_TO_ALL = 'ALL_DEVICES'
|
||||
|
||||
# Used to validate API Key
|
||||
VALIDATE_TOKEN = re.compile(r'^[a-z0-9]{30}$', re.I)
|
||||
|
||||
# Used to detect a User and/or Group
|
||||
VALIDATE_USER_KEY = re.compile(r'^[a-z0-9]{30}$', re.I)
|
||||
|
||||
# Used to detect a User and/or Group
|
||||
# Used to detect a Device
|
||||
VALIDATE_DEVICE = re.compile(r'^[a-z0-9_]{1,25}$', re.I)
|
||||
|
||||
|
||||
|
@ -158,20 +153,19 @@ class NotifyPushover(NotifyBase):
|
|||
'type': 'string',
|
||||
'private': True,
|
||||
'required': True,
|
||||
'regex': (r'[a-z0-9]{30}', 'i'),
|
||||
'map_to': 'user',
|
||||
'regex': (r'^[a-z0-9]{30}$', 'i'),
|
||||
},
|
||||
'token': {
|
||||
'name': _('Access Token'),
|
||||
'type': 'string',
|
||||
'private': True,
|
||||
'required': True,
|
||||
'regex': (r'[a-z0-9]{30}', 'i'),
|
||||
'regex': (r'^[a-z0-9]{30}$', 'i'),
|
||||
},
|
||||
'target_device': {
|
||||
'name': _('Target Device'),
|
||||
'type': 'string',
|
||||
'regex': (r'[a-z0-9_]{1,25}', 'i'),
|
||||
'regex': (r'^[a-z0-9_]{1,25}$', 'i'),
|
||||
'map_to': 'targets',
|
||||
},
|
||||
'targets': {
|
||||
|
@ -191,7 +185,7 @@ class NotifyPushover(NotifyBase):
|
|||
'sound': {
|
||||
'name': _('Sound'),
|
||||
'type': 'string',
|
||||
'regex': (r'[a-z]{1,12}', 'i'),
|
||||
'regex': (r'^[a-z]{1,12}$', 'i'),
|
||||
'default': PushoverSound.PUSHOVER,
|
||||
},
|
||||
'retry': {
|
||||
|
@ -212,26 +206,28 @@ class NotifyPushover(NotifyBase):
|
|||
},
|
||||
})
|
||||
|
||||
def __init__(self, token, targets=None, priority=None, sound=None,
|
||||
retry=None, expire=None,
|
||||
**kwargs):
|
||||
def __init__(self, user_key, token, targets=None, priority=None,
|
||||
sound=None, retry=None, expire=None, **kwargs):
|
||||
"""
|
||||
Initialize Pushover Object
|
||||
"""
|
||||
super(NotifyPushover, self).__init__(**kwargs)
|
||||
|
||||
try:
|
||||
# The token associated with the account
|
||||
self.token = token.strip()
|
||||
|
||||
except AttributeError:
|
||||
# Token was None
|
||||
msg = 'No API Token was specified.'
|
||||
# Access Token (associated with project)
|
||||
self.token = validate_regex(
|
||||
token, *self.template_tokens['token']['regex'])
|
||||
if not self.token:
|
||||
msg = 'An invalid Pushover Access Token ' \
|
||||
'({}) was specified.'.format(token)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
if not VALIDATE_TOKEN.match(self.token):
|
||||
msg = 'The API Token specified (%s) is invalid.'.format(token)
|
||||
# User Key (associated with project)
|
||||
self.user_key = validate_regex(
|
||||
user_key, *self.template_tokens['user_key']['regex'])
|
||||
if not self.user_key:
|
||||
msg = 'An invalid Pushover User Key ' \
|
||||
'({}) was specified.'.format(user_key)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
|
@ -249,7 +245,7 @@ class NotifyPushover(NotifyBase):
|
|||
|
||||
# The Priority of the message
|
||||
if priority not in PUSHOVER_PRIORITIES:
|
||||
self.priority = PushoverPriority.NORMAL
|
||||
self.priority = self.template_args['priority']['default']
|
||||
|
||||
else:
|
||||
self.priority = priority
|
||||
|
@ -258,7 +254,7 @@ class NotifyPushover(NotifyBase):
|
|||
if self.priority == PushoverPriority.EMERGENCY:
|
||||
|
||||
# How often to resend notification, in seconds
|
||||
self.retry = NotifyPushover.template_args['retry']['default']
|
||||
self.retry = self.template_args['retry']['default']
|
||||
try:
|
||||
self.retry = int(retry)
|
||||
except (ValueError, TypeError):
|
||||
|
@ -266,7 +262,7 @@ class NotifyPushover(NotifyBase):
|
|||
pass
|
||||
|
||||
# How often to resend notification, in seconds
|
||||
self.expire = NotifyPushover.template_args['expire']['default']
|
||||
self.expire = self.template_args['expire']['default']
|
||||
try:
|
||||
self.expire = int(expire)
|
||||
except (ValueError, TypeError):
|
||||
|
@ -274,23 +270,16 @@ class NotifyPushover(NotifyBase):
|
|||
pass
|
||||
|
||||
if self.retry < 30:
|
||||
msg = 'Retry must be at least 30.'
|
||||
msg = 'Pushover retry must be at least 30 seconds.'
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
if self.expire < 0 or self.expire > 10800:
|
||||
msg = 'Expire has a max value of at most 10800 seconds.'
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
if not self.user:
|
||||
msg = 'No user key was specified.'
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
if not VALIDATE_USER_KEY.match(self.user):
|
||||
msg = 'The user key specified (%s) is invalid.' % self.user
|
||||
msg = 'Pushover expire must reside in the range of ' \
|
||||
'0 to 10800 seconds.'
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
return
|
||||
|
||||
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
||||
"""
|
||||
|
@ -323,7 +312,7 @@ class NotifyPushover(NotifyBase):
|
|||
# prepare JSON Object
|
||||
payload = {
|
||||
'token': self.token,
|
||||
'user': self.user,
|
||||
'user': self.user_key,
|
||||
'priority': str(self.priority),
|
||||
'title': title,
|
||||
'message': body,
|
||||
|
@ -406,8 +395,8 @@ class NotifyPushover(NotifyBase):
|
|||
'format': self.notify_format,
|
||||
'overflow': self.overflow_mode,
|
||||
'priority':
|
||||
_map[PushoverPriority.NORMAL] if self.priority not in _map
|
||||
else _map[self.priority],
|
||||
_map[self.template_args['priority']['default']]
|
||||
if self.priority not in _map else _map[self.priority],
|
||||
'verify': 'yes' if self.verify_certificate else 'no',
|
||||
}
|
||||
# Only add expire and retry for emergency messages,
|
||||
|
@ -426,7 +415,7 @@ class NotifyPushover(NotifyBase):
|
|||
|
||||
return '{schema}://{user_key}@{token}/{devices}/?{args}'.format(
|
||||
schema=self.secure_protocol,
|
||||
user_key=self.pprint(self.user, privacy, safe=''),
|
||||
user_key=self.pprint(self.user_key, privacy, safe=''),
|
||||
token=self.pprint(self.token, privacy, safe=''),
|
||||
devices=devices,
|
||||
args=NotifyPushover.urlencode(args))
|
||||
|
@ -464,6 +453,9 @@ class NotifyPushover(NotifyBase):
|
|||
# Retrieve all of our targets
|
||||
results['targets'] = NotifyPushover.split_path(results['fullpath'])
|
||||
|
||||
# User Key is retrieved from the user
|
||||
results['user_key'] = NotifyPushover.unquote(results['user'])
|
||||
|
||||
# Get the sound
|
||||
if 'sound' in results['qsd'] and len(results['qsd']['sound']):
|
||||
results['sound'] = \
|
||||
|
|
|
@ -40,14 +40,9 @@ from .NotifyBase import NotifyBase
|
|||
from ..common import NotifyImageSize
|
||||
from ..common import NotifyType
|
||||
from ..utils import parse_bool
|
||||
from ..utils import validate_regex
|
||||
from ..AppriseLocale import gettext_lazy as _
|
||||
|
||||
# Token required as part of the API request
|
||||
VALIDATE_TOKEN = re.compile(r'[A-Z0-9]{15}', re.I)
|
||||
|
||||
# Organization required as part of the API request
|
||||
VALIDATE_ORG = re.compile(r'[A-Z0-9_-]{3,32}', re.I)
|
||||
|
||||
|
||||
class RyverWebhookMode(object):
|
||||
"""
|
||||
|
@ -99,12 +94,14 @@ class NotifyRyver(NotifyBase):
|
|||
'name': _('Organization'),
|
||||
'type': 'string',
|
||||
'required': True,
|
||||
'regex': (r'^[A-Z0-9_-]{3,32}$', 'i'),
|
||||
},
|
||||
'token': {
|
||||
'name': _('Token'),
|
||||
'type': 'string',
|
||||
'required': True,
|
||||
'private': True,
|
||||
'regex': (r'^[A-Z0-9]{15}$', 'i'),
|
||||
},
|
||||
'user': {
|
||||
'name': _('Bot Name'),
|
||||
|
@ -135,25 +132,21 @@ class NotifyRyver(NotifyBase):
|
|||
"""
|
||||
super(NotifyRyver, self).__init__(**kwargs)
|
||||
|
||||
if not token:
|
||||
msg = 'No Ryver token was specified.'
|
||||
# API Token (associated with project)
|
||||
self.token = validate_regex(
|
||||
token, *self.template_tokens['token']['regex'])
|
||||
if not self.token:
|
||||
msg = 'An invalid Ryver API Token ' \
|
||||
'({}) was specified.'.format(token)
|
||||
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()):
|
||||
msg = 'The Ryver token specified ({}) is invalid.'\
|
||||
.format(token)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
if not VALIDATE_ORG.match(organization.strip()):
|
||||
msg = 'The Ryver organization specified ({}) is invalid.'\
|
||||
.format(organization)
|
||||
# Organization (associated with project)
|
||||
self.organization = validate_regex(
|
||||
organization, *self.template_tokens['organization']['regex'])
|
||||
if not self.organization:
|
||||
msg = 'An invalid Ryver Organization ' \
|
||||
'({}) was specified.'.format(organization)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
|
@ -167,12 +160,6 @@ class NotifyRyver(NotifyBase):
|
|||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
# The organization associated with the account
|
||||
self.organization = organization.strip()
|
||||
|
||||
# The token associated with the account
|
||||
self.token = token.strip()
|
||||
|
||||
# Place an image inline with the message body
|
||||
self.include_image = include_image
|
||||
|
||||
|
@ -193,6 +180,8 @@ class NotifyRyver(NotifyBase):
|
|||
re.IGNORECASE,
|
||||
)
|
||||
|
||||
return
|
||||
|
||||
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
||||
"""
|
||||
Perform Ryver Notification
|
||||
|
|
|
@ -36,6 +36,7 @@ from .NotifyBase import NotifyBase
|
|||
from ..URLBase import PrivacyMode
|
||||
from ..common import NotifyType
|
||||
from ..utils import parse_list
|
||||
from ..utils import validate_regex
|
||||
from ..AppriseLocale import gettext_lazy as _
|
||||
|
||||
# Some Phone Number Detection
|
||||
|
@ -117,21 +118,21 @@ class NotifySNS(NotifyBase):
|
|||
'name': _('Region'),
|
||||
'type': 'string',
|
||||
'required': True,
|
||||
'regex': (r'[a-z]{2}-[a-z]+-[0-9]+', 'i'),
|
||||
'regex': (r'^[a-z]{2}-[a-z]+-[0-9]+$', 'i'),
|
||||
'map_to': 'region_name',
|
||||
},
|
||||
'target_phone_no': {
|
||||
'name': _('Target Phone No'),
|
||||
'type': 'string',
|
||||
'map_to': 'targets',
|
||||
'regex': (r'[0-9\s)(+-]+', 'i')
|
||||
'regex': (r'^[0-9\s)(+-]+$', 'i')
|
||||
},
|
||||
'target_topic': {
|
||||
'name': _('Target Topic'),
|
||||
'type': 'string',
|
||||
'map_to': 'targets',
|
||||
'prefix': '#',
|
||||
'regex': (r'[A-Za-z0-9_-]+', 'i'),
|
||||
'regex': (r'^[A-Za-z0-9_-]+$', 'i'),
|
||||
},
|
||||
'targets': {
|
||||
'name': _('Targets'),
|
||||
|
@ -153,18 +154,28 @@ class NotifySNS(NotifyBase):
|
|||
"""
|
||||
super(NotifySNS, self).__init__(**kwargs)
|
||||
|
||||
if not access_key_id:
|
||||
# Store our AWS API Access Key
|
||||
self.aws_access_key_id = validate_regex(access_key_id)
|
||||
if not self.aws_access_key_id:
|
||||
msg = 'An invalid AWS Access Key ID was specified.'
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
if not secret_access_key:
|
||||
msg = 'An invalid AWS Secret Access Key was specified.'
|
||||
# Store our AWS API Secret Access key
|
||||
self.aws_secret_access_key = validate_regex(secret_access_key)
|
||||
if not self.aws_secret_access_key:
|
||||
msg = 'An invalid AWS Secret Access Key ' \
|
||||
'({}) was specified.'.format(secret_access_key)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
if not (region_name and IS_REGION.match(region_name)):
|
||||
msg = 'An invalid AWS Region was specified.'
|
||||
# Acquire our AWS Region Name:
|
||||
# eg. us-east-1, cn-north-1, us-west-2, ...
|
||||
self.aws_region_name = validate_regex(
|
||||
region_name, *self.template_tokens['region']['regex'])
|
||||
if not self.aws_region_name:
|
||||
msg = 'An invalid AWS Region ({}) was specified.'.format(
|
||||
region_name)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
|
@ -174,16 +185,6 @@ class NotifySNS(NotifyBase):
|
|||
# Initialize numbers list
|
||||
self.phone = list()
|
||||
|
||||
# Store our AWS API Key
|
||||
self.aws_access_key_id = access_key_id
|
||||
|
||||
# Store our AWS API Secret Access key
|
||||
self.aws_secret_access_key = secret_access_key
|
||||
|
||||
# Acquire our AWS Region Name:
|
||||
# eg. us-east-1, cn-north-1, us-west-2, ...
|
||||
self.aws_region_name = region_name
|
||||
|
||||
# Set our notify_url based on our region
|
||||
self.notify_url = 'https://sns.{}.amazonaws.com/'\
|
||||
.format(self.aws_region_name)
|
||||
|
@ -231,8 +232,12 @@ class NotifySNS(NotifyBase):
|
|||
)
|
||||
|
||||
if len(self.phone) == 0 and len(self.topics) == 0:
|
||||
self.logger.warning(
|
||||
'There are no valid target(s) identified to notify.')
|
||||
# We have a bot token and no target(s) to message
|
||||
msg = 'No AWS targets to notify.'
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
return
|
||||
|
||||
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
||||
"""
|
||||
|
|
|
@ -43,7 +43,6 @@
|
|||
# - https://sendgrid.com/docs/ui/sending-email/\
|
||||
# how-to-send-an-email-with-dynamic-transactional-templates/
|
||||
|
||||
import re
|
||||
import requests
|
||||
from json import dumps
|
||||
|
||||
|
@ -52,10 +51,9 @@ from ..common import NotifyFormat
|
|||
from ..common import NotifyType
|
||||
from ..utils import parse_list
|
||||
from ..utils import GET_EMAIL_RE
|
||||
from ..utils import validate_regex
|
||||
from ..AppriseLocale import gettext_lazy as _
|
||||
|
||||
IS_APIKEY_RE = re.compile(r'^([A-Z0-9._-]+)$', re.I)
|
||||
|
||||
# Extend HTTP Error Messages
|
||||
SENDGRID_HTTP_ERROR_MAP = {
|
||||
401: 'Unauthorized - You do not have authorization to make the request.',
|
||||
|
@ -109,6 +107,7 @@ class NotifySendGrid(NotifyBase):
|
|||
'type': 'string',
|
||||
'private': True,
|
||||
'required': True,
|
||||
'regex': (r'^[A-Z0-9._-]+$', 'i'),
|
||||
},
|
||||
'from_email': {
|
||||
'name': _('Source Email'),
|
||||
|
@ -162,16 +161,12 @@ class NotifySendGrid(NotifyBase):
|
|||
"""
|
||||
super(NotifySendGrid, self).__init__(**kwargs)
|
||||
|
||||
# The API Key needed to perform all SendMail API i/o
|
||||
self.apikey = apikey
|
||||
try:
|
||||
result = IS_APIKEY_RE.match(self.apikey)
|
||||
if not result:
|
||||
# let outer exception handle this
|
||||
raise TypeError
|
||||
|
||||
except (TypeError, AttributeError):
|
||||
msg = 'Invalid API Key specified: {}'.format(self.apikey)
|
||||
# API Key (associated with project)
|
||||
self.apikey = validate_regex(
|
||||
apikey, *self.template_tokens['apikey']['regex'])
|
||||
if not self.apikey:
|
||||
msg = 'An invalid SendGrid API Key ' \
|
||||
'({}) was specified.'.format(apikey)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@ import requests
|
|||
from .NotifyBase import NotifyBase
|
||||
from ..URLBase import PrivacyMode
|
||||
from ..common import NotifyType
|
||||
from ..utils import validate_regex
|
||||
from ..AppriseLocale import gettext_lazy as _
|
||||
|
||||
# Default our global support flag
|
||||
|
@ -120,11 +121,26 @@ class NotifySimplePush(NotifyBase):
|
|||
"""
|
||||
super(NotifySimplePush, self).__init__(**kwargs)
|
||||
|
||||
# Store the API key
|
||||
self.apikey = apikey
|
||||
# API Key (associated with project)
|
||||
self.apikey = validate_regex(apikey)
|
||||
if not self.apikey:
|
||||
msg = 'An invalid SimplePush API Key ' \
|
||||
'({}) was specified.'.format(apikey)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
# Event Name
|
||||
self.event = event
|
||||
if event:
|
||||
# Event Name (associated with project)
|
||||
self.event = validate_regex(event)
|
||||
if not self.event:
|
||||
msg = 'An invalid SimplePush Event Name ' \
|
||||
'({}) was specified.'.format(event)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
else:
|
||||
# Default Event Name
|
||||
self.event = None
|
||||
|
||||
# Encrypt Message (providing support is available)
|
||||
if self.password and self.user and not CRYPTOGRAPHY_AVAILABLE:
|
||||
|
@ -182,7 +198,6 @@ class NotifySimplePush(NotifyBase):
|
|||
payload = {
|
||||
'key': self.apikey,
|
||||
}
|
||||
event = self.event
|
||||
|
||||
if self.password and self.user and CRYPTOGRAPHY_AVAILABLE:
|
||||
body = self._encrypt(body)
|
||||
|
@ -198,8 +213,9 @@ class NotifySimplePush(NotifyBase):
|
|||
'title': title,
|
||||
})
|
||||
|
||||
if event:
|
||||
payload['event'] = event
|
||||
if self.event:
|
||||
# Store Event
|
||||
payload['event'] = self.event
|
||||
|
||||
self.logger.debug('SimplePush POST URL: %s (cert_verify=%r)' % (
|
||||
self.notify_url, self.verify_certificate,
|
||||
|
|
|
@ -46,20 +46,9 @@ from ..common import NotifyType
|
|||
from ..common import NotifyFormat
|
||||
from ..utils import parse_bool
|
||||
from ..utils import parse_list
|
||||
from ..utils import validate_regex
|
||||
from ..AppriseLocale import gettext_lazy as _
|
||||
|
||||
# Token required as part of the API request
|
||||
# /AAAAAAAAA/........./........................
|
||||
VALIDATE_TOKEN_A = re.compile(r'[A-Z0-9]{9}')
|
||||
|
||||
# Token required as part of the API request
|
||||
# /........./BBBBBBBBB/........................
|
||||
VALIDATE_TOKEN_B = re.compile(r'[A-Z0-9]{9}')
|
||||
|
||||
# Token required as part of the API request
|
||||
# /........./........./CCCCCCCCCCCCCCCCCCCCCCCC
|
||||
VALIDATE_TOKEN_C = re.compile(r'[A-Za-z0-9]{24}')
|
||||
|
||||
# Extend HTTP Error Messages
|
||||
SLACK_HTTP_ERROR_MAP = {
|
||||
401: 'Unauthorized - Invalid Token.',
|
||||
|
@ -68,9 +57,6 @@ SLACK_HTTP_ERROR_MAP = {
|
|||
# Used to break path apart into list of channels
|
||||
CHANNEL_LIST_DELIM = re.compile(r'[ \t\r\n,#\\/]+')
|
||||
|
||||
# Used to detect a channel
|
||||
IS_VALID_TARGET_RE = re.compile(r'[+#@]?([A-Z0-9_]{1,32})', re.I)
|
||||
|
||||
|
||||
class NotifySlack(NotifyBase):
|
||||
"""
|
||||
|
@ -116,26 +102,32 @@ class NotifySlack(NotifyBase):
|
|||
'type': 'string',
|
||||
'map_to': 'user',
|
||||
},
|
||||
# Token required as part of the API request
|
||||
# /AAAAAAAAA/........./........................
|
||||
'token_a': {
|
||||
'name': _('Token A'),
|
||||
'type': 'string',
|
||||
'private': True,
|
||||
'required': True,
|
||||
'regex': (r'[A-Z0-9]{9}', 'i'),
|
||||
'regex': (r'^[A-Z0-9]{9}$', 'i'),
|
||||
},
|
||||
# Token required as part of the API request
|
||||
# /........./BBBBBBBBB/........................
|
||||
'token_b': {
|
||||
'name': _('Token B'),
|
||||
'type': 'string',
|
||||
'private': True,
|
||||
'required': True,
|
||||
'regex': (r'[A-Z0-9]{9}', 'i'),
|
||||
'regex': (r'^[A-Z0-9]{9}$', 'i'),
|
||||
},
|
||||
# Token required as part of the API request
|
||||
# /........./........./CCCCCCCCCCCCCCCCCCCCCCCC
|
||||
'token_c': {
|
||||
'name': _('Token C'),
|
||||
'type': 'string',
|
||||
'private': True,
|
||||
'required': True,
|
||||
'regex': (r'[A-Za-z0-9]{24}', 'i'),
|
||||
'regex': (r'^[A-Za-z0-9]{24}$', 'i'),
|
||||
},
|
||||
'target_encoded_id': {
|
||||
'name': _('Target Encoded ID'),
|
||||
|
@ -181,48 +173,30 @@ class NotifySlack(NotifyBase):
|
|||
"""
|
||||
super(NotifySlack, self).__init__(**kwargs)
|
||||
|
||||
if not token_a:
|
||||
msg = 'The first API token is not specified.'
|
||||
self.token_a = validate_regex(
|
||||
token_a, *self.template_tokens['token_a']['regex'])
|
||||
if not self.token_a:
|
||||
msg = 'An invalid Slack (first) Token ' \
|
||||
'({}) was specified.'.format(token_a)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
if not token_b:
|
||||
msg = 'The second API token is not specified.'
|
||||
self.token_b = validate_regex(
|
||||
token_b, *self.template_tokens['token_b']['regex'])
|
||||
if not self.token_b:
|
||||
msg = 'An invalid Slack (second) Token ' \
|
||||
'({}) was specified.'.format(token_b)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
if not token_c:
|
||||
msg = 'The third API token is not specified.'
|
||||
self.token_c = validate_regex(
|
||||
token_c, *self.template_tokens['token_c']['regex'])
|
||||
if not self.token_c:
|
||||
msg = 'An invalid Slack (third) Token ' \
|
||||
'({}) was specified.'.format(token_c)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
if not VALIDATE_TOKEN_A.match(token_a.strip()):
|
||||
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()):
|
||||
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()):
|
||||
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()
|
||||
|
||||
if not self.user:
|
||||
self.logger.warning(
|
||||
'No user was specified; using "%s".' % self.app_id)
|
||||
|
@ -255,6 +229,8 @@ class NotifySlack(NotifyBase):
|
|||
# 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):
|
||||
"""
|
||||
Perform Slack Notification
|
||||
|
@ -303,27 +279,30 @@ class NotifySlack(NotifyBase):
|
|||
channel = channels.pop(0)
|
||||
|
||||
if channel is not None:
|
||||
_channel = validate_regex(
|
||||
channel, r'[+#@]?([A-Z0-9_]{1,32})')
|
||||
|
||||
if not _channel:
|
||||
# Channel over-ride was specified
|
||||
if not IS_VALID_TARGET_RE.match(channel):
|
||||
self.logger.warning(
|
||||
"The specified target {} is invalid;"
|
||||
"skipping.".format(channel))
|
||||
"skipping.".format(_channel))
|
||||
|
||||
# Mark our failure
|
||||
has_error = True
|
||||
continue
|
||||
|
||||
if len(channel) > 1 and channel[0] == '+':
|
||||
if len(_channel) > 1 and _channel[0] == '+':
|
||||
# Treat as encoded id if prefixed with a +
|
||||
payload['channel'] = channel[1:]
|
||||
payload['channel'] = _channel[1:]
|
||||
|
||||
elif len(channel) > 1 and channel[0] == '@':
|
||||
elif len(_channel) > 1 and _channel[0] == '@':
|
||||
# Treat @ value 'as is'
|
||||
payload['channel'] = channel
|
||||
payload['channel'] = _channel
|
||||
|
||||
else:
|
||||
# Prefix with channel hash tag
|
||||
payload['channel'] = '#%s' % channel
|
||||
payload['channel'] = '#{}'.format(_channel)
|
||||
|
||||
# Acquire our to-be footer icon if configured to do so
|
||||
image_url = None if not self.include_image \
|
||||
|
@ -478,9 +457,9 @@ class NotifySlack(NotifyBase):
|
|||
|
||||
result = re.match(
|
||||
r'^https?://hooks\.slack\.com/services/'
|
||||
r'(?P<token_a>[A-Z0-9]{9})/'
|
||||
r'(?P<token_b>[A-Z0-9]{9})/'
|
||||
r'(?P<token_c>[A-Z0-9]{24})/?'
|
||||
r'(?P<token_a>[A-Z0-9]+)/'
|
||||
r'(?P<token_b>[A-Z0-9]+)/'
|
||||
r'(?P<token_c>[A-Z0-9]+)/?'
|
||||
r'(?P<args>\?[.+])?$', url, re.I)
|
||||
|
||||
if result:
|
||||
|
|
|
@ -47,12 +47,12 @@
|
|||
# - https://push.techulus.com/ - Main Website
|
||||
# - https://pushtechulus.docs.apiary.io - API Documentation
|
||||
|
||||
import re
|
||||
import requests
|
||||
from json import dumps
|
||||
|
||||
from .NotifyBase import NotifyBase
|
||||
from ..common import NotifyType
|
||||
from ..utils import validate_regex
|
||||
from ..AppriseLocale import gettext_lazy as _
|
||||
|
||||
# Token required as part of the API request
|
||||
|
@ -60,9 +60,6 @@ from ..AppriseLocale import gettext_lazy as _
|
|||
UUID4_RE = \
|
||||
r'[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}'
|
||||
|
||||
# API Key
|
||||
VALIDATE_APIKEY = re.compile(UUID4_RE, re.I)
|
||||
|
||||
|
||||
class NotifyTechulusPush(NotifyBase):
|
||||
"""
|
||||
|
@ -99,7 +96,7 @@ class NotifyTechulusPush(NotifyBase):
|
|||
'type': 'string',
|
||||
'private': True,
|
||||
'required': True,
|
||||
'regex': (UUID4_RE, 'i'),
|
||||
'regex': (r'^{}$'.format(UUID4_RE), 'i'),
|
||||
},
|
||||
})
|
||||
|
||||
|
@ -109,19 +106,14 @@ class NotifyTechulusPush(NotifyBase):
|
|||
"""
|
||||
super(NotifyTechulusPush, self).__init__(**kwargs)
|
||||
|
||||
if not apikey:
|
||||
msg = 'The Techulus Push apikey is not specified.'
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
if not VALIDATE_APIKEY.match(apikey.strip()):
|
||||
msg = 'The Techulus Push apikey specified ({}) is invalid.'\
|
||||
.format(apikey)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
# The apikey associated with the account
|
||||
self.apikey = apikey.strip()
|
||||
self.apikey = validate_regex(
|
||||
apikey, *self.template_tokens['apikey']['regex'])
|
||||
if not self.apikey:
|
||||
msg = 'An invalid Techulus Push API key ' \
|
||||
'({}) was specified.'.format(apikey)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
||||
"""
|
||||
|
|
|
@ -61,17 +61,11 @@ from ..common import NotifyImageSize
|
|||
from ..common import NotifyFormat
|
||||
from ..utils import parse_bool
|
||||
from ..utils import parse_list
|
||||
from ..utils import validate_regex
|
||||
from ..AppriseLocale import gettext_lazy as _
|
||||
|
||||
TELEGRAM_IMAGE_XY = NotifyImageSize.XY_256
|
||||
|
||||
# Token required as part of the API request
|
||||
# allow the word 'bot' infront
|
||||
VALIDATE_BOT_TOKEN = re.compile(
|
||||
r'^(bot)?(?P<key>[0-9]+:[a-z0-9_-]+)/*$',
|
||||
re.IGNORECASE,
|
||||
)
|
||||
|
||||
# Chat ID is required
|
||||
# If the Chat ID is positive, then it's addressed to a single person
|
||||
# If the Chat ID is negative, then it's targeting a group
|
||||
|
@ -119,14 +113,16 @@ class NotifyTelegram(NotifyBase):
|
|||
'type': 'string',
|
||||
'private': True,
|
||||
'required': True,
|
||||
'regex': (r'(bot)?[0-9]+:[a-z0-9_-]+', 'i'),
|
||||
# Token required as part of the API request, allow the word 'bot'
|
||||
# infront of it
|
||||
'regex': (r'^(bot)?(?P<key>[0-9]+:[a-z0-9_-]+)$', 'i'),
|
||||
},
|
||||
'target_user': {
|
||||
'name': _('Target Chat ID'),
|
||||
'type': 'string',
|
||||
'map_to': 'targets',
|
||||
'map_to': 'targets',
|
||||
'regex': (r'((-?[0-9]{1,32})|([a-z_-][a-z0-9_-]+))', 'i'),
|
||||
'regex': (r'^((-?[0-9]{1,32})|([a-z_-][a-z0-9_-]+))$', 'i'),
|
||||
},
|
||||
'targets': {
|
||||
'name': _('Targets'),
|
||||
|
@ -160,24 +156,15 @@ class NotifyTelegram(NotifyBase):
|
|||
"""
|
||||
super(NotifyTelegram, self).__init__(**kwargs)
|
||||
|
||||
try:
|
||||
self.bot_token = bot_token.strip()
|
||||
|
||||
except AttributeError:
|
||||
# Token was None
|
||||
err = 'No Bot Token was specified.'
|
||||
self.bot_token = validate_regex(
|
||||
bot_token, *self.template_tokens['bot_token']['regex'],
|
||||
fmt='{key}')
|
||||
if not self.bot_token:
|
||||
err = 'The Telegram Bot Token specified ({}) is invalid.'.format(
|
||||
bot_token)
|
||||
self.logger.warning(err)
|
||||
raise TypeError(err)
|
||||
|
||||
result = VALIDATE_BOT_TOKEN.match(self.bot_token)
|
||||
if not result:
|
||||
err = 'The Bot Token specified (%s) is invalid.' % bot_token
|
||||
self.logger.warning(err)
|
||||
raise TypeError(err)
|
||||
|
||||
# Store our Bot Token
|
||||
self.bot_token = result.group('key')
|
||||
|
||||
# Parse our list
|
||||
self.targets = parse_list(targets)
|
||||
|
||||
|
|
|
@ -48,13 +48,10 @@ from .NotifyBase import NotifyBase
|
|||
from ..URLBase import PrivacyMode
|
||||
from ..common import NotifyType
|
||||
from ..utils import parse_list
|
||||
from ..utils import validate_regex
|
||||
from ..AppriseLocale import gettext_lazy as _
|
||||
|
||||
|
||||
# Used to validate your personal access apikey
|
||||
VALIDATE_AUTH_TOKEN = re.compile(r'^[a-f0-9]{32}$', re.I)
|
||||
VALIDATE_ACCOUNT_SID = re.compile(r'^AC[a-f0-9]{32}$', re.I)
|
||||
|
||||
# Some Phone Number Detection
|
||||
IS_PHONE_NO = re.compile(r'^\+?(?P<phone>[0-9\s)(+-]+)\s*$')
|
||||
|
||||
|
@ -108,33 +105,33 @@ class NotifyTwilio(NotifyBase):
|
|||
'type': 'string',
|
||||
'private': True,
|
||||
'required': True,
|
||||
'regex': (r'AC[a-f0-9]{32}', 'i'),
|
||||
'regex': (r'^AC[a-f0-9]+$', 'i'),
|
||||
},
|
||||
'auth_token': {
|
||||
'name': _('Auth Token'),
|
||||
'type': 'string',
|
||||
'private': True,
|
||||
'required': True,
|
||||
'regex': (r'[a-f0-9]{32}', 'i'),
|
||||
'regex': (r'^[a-f0-9]+$', 'i'),
|
||||
},
|
||||
'from_phone': {
|
||||
'name': _('From Phone No'),
|
||||
'type': 'string',
|
||||
'required': True,
|
||||
'regex': (r'\+?[0-9\s)(+-]+', 'i'),
|
||||
'regex': (r'^\+?[0-9\s)(+-]+$', 'i'),
|
||||
'map_to': 'source',
|
||||
},
|
||||
'target_phone': {
|
||||
'name': _('Target Phone No'),
|
||||
'type': 'string',
|
||||
'prefix': '+',
|
||||
'regex': (r'[0-9\s)(+-]+', 'i'),
|
||||
'regex': (r'^[0-9\s)(+-]+$', 'i'),
|
||||
'map_to': 'targets',
|
||||
},
|
||||
'short_code': {
|
||||
'name': _('Target Short Code'),
|
||||
'type': 'string',
|
||||
'regex': (r'[0-9]{5,6}', 'i'),
|
||||
'regex': (r'^[0-9]{5,6}$', 'i'),
|
||||
'map_to': 'targets',
|
||||
},
|
||||
'targets': {
|
||||
|
@ -166,35 +163,21 @@ class NotifyTwilio(NotifyBase):
|
|||
"""
|
||||
super(NotifyTwilio, self).__init__(**kwargs)
|
||||
|
||||
try:
|
||||
# The Account SID associated with the account
|
||||
self.account_sid = account_sid.strip()
|
||||
|
||||
except AttributeError:
|
||||
# Token was None
|
||||
msg = 'No Account SID was specified.'
|
||||
self.account_sid = validate_regex(
|
||||
account_sid, *self.template_tokens['account_sid']['regex'])
|
||||
if not self.account_sid:
|
||||
msg = 'An invalid Twilio Account SID ' \
|
||||
'({}) was specified.'.format(account_sid)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
if not VALIDATE_ACCOUNT_SID.match(self.account_sid):
|
||||
msg = 'The Account SID specified ({}) is invalid.' \
|
||||
.format(account_sid)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
try:
|
||||
# The authentication token associated with the account
|
||||
self.auth_token = auth_token.strip()
|
||||
|
||||
except AttributeError:
|
||||
# Token was None
|
||||
msg = 'No Auth Token was specified.'
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
if not VALIDATE_AUTH_TOKEN.match(self.auth_token):
|
||||
msg = 'The Auth Token specified ({}) is invalid.' \
|
||||
.format(auth_token)
|
||||
# The Authentication Token associated with the account
|
||||
self.auth_token = validate_regex(
|
||||
auth_token, *self.template_tokens['auth_token']['regex'])
|
||||
if not self.auth_token:
|
||||
msg = 'An invalid Twilio Authentication Token ' \
|
||||
'({}) was specified.'.format(auth_token)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
|
@ -254,14 +237,16 @@ class NotifyTwilio(NotifyBase):
|
|||
'({}) specified.'.format(target),
|
||||
)
|
||||
|
||||
if len(self.targets) == 0:
|
||||
msg = 'There are no valid targets identified to notify.'
|
||||
if not self.targets:
|
||||
if len(self.source) in (5, 6):
|
||||
# raise a warning since we're a short-code. We need
|
||||
# a number to message
|
||||
msg = 'There are no valid Twilio targets to notify.'
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
return
|
||||
|
||||
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
||||
"""
|
||||
Perform Twilio Notification
|
||||
|
|
|
@ -37,6 +37,7 @@ from ..URLBase import PrivacyMode
|
|||
from ..common import NotifyType
|
||||
from ..utils import parse_list
|
||||
from ..utils import parse_bool
|
||||
from ..utils import validate_regex
|
||||
from ..AppriseLocale import gettext_lazy as _
|
||||
|
||||
IS_USER = re.compile(r'^\s*@?(?P<user>[A-Z0-9_]+)$', re.I)
|
||||
|
@ -186,23 +187,27 @@ class NotifyTwitter(NotifyBase):
|
|||
"""
|
||||
super(NotifyTwitter, self).__init__(**kwargs)
|
||||
|
||||
if not ckey:
|
||||
msg = 'An invalid Consumer API Key was specified.'
|
||||
self.ckey = validate_regex(ckey)
|
||||
if not self.ckey:
|
||||
msg = 'An invalid Twitter Consumer Key was specified.'
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
if not csecret:
|
||||
msg = 'An invalid Consumer Secret API Key was specified.'
|
||||
self.csecret = validate_regex(csecret)
|
||||
if not self.csecret:
|
||||
msg = 'An invalid Twitter Consumer Secret was specified.'
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
if not akey:
|
||||
msg = 'An invalid Access Token API Key was specified.'
|
||||
self.akey = validate_regex(akey)
|
||||
if not self.akey:
|
||||
msg = 'An invalid Twitter Access Key was specified.'
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
if not asecret:
|
||||
msg = 'An invalid Access Token Secret API Key was specified.'
|
||||
self.asecret = validate_regex(asecret)
|
||||
if not self.asecret:
|
||||
msg = 'An invalid Access Secret was specified.'
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
|
@ -219,6 +224,9 @@ class NotifyTwitter(NotifyBase):
|
|||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
# Track any errors
|
||||
has_error = False
|
||||
|
||||
# Identify our targets
|
||||
self.targets = []
|
||||
for target in parse_list(targets):
|
||||
|
@ -227,15 +235,19 @@ class NotifyTwitter(NotifyBase):
|
|||
self.targets.append(match.group('user'))
|
||||
continue
|
||||
|
||||
has_error = True
|
||||
self.logger.warning(
|
||||
'Dropped invalid user ({}) specified.'.format(target),
|
||||
)
|
||||
|
||||
# Store our data
|
||||
self.ckey = ckey
|
||||
self.csecret = csecret
|
||||
self.akey = akey
|
||||
self.asecret = asecret
|
||||
if has_error and not self.targets:
|
||||
# We have specified that we want to notify one or more individual
|
||||
# and we failed to load any of them. Since it's also valid to
|
||||
# notify no one at all (which means we notify ourselves), it's
|
||||
# important we don't switch from the users original intentions
|
||||
msg = 'No Twitter targets to notify.'
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
return
|
||||
|
||||
|
@ -297,7 +309,7 @@ class NotifyTwitter(NotifyBase):
|
|||
}
|
||||
}
|
||||
|
||||
# Lookup our users
|
||||
# Lookup our users (otherwise we look up ourselves)
|
||||
targets = self._whoami(lazy=self.cache) if not len(self.targets) \
|
||||
else self._user_lookup(self.targets, lazy=self.cache)
|
||||
|
||||
|
|
|
@ -63,11 +63,9 @@ from json import dumps
|
|||
from .NotifyBase import NotifyBase
|
||||
from ..common import NotifyType
|
||||
from ..common import NotifyFormat
|
||||
from ..utils import validate_regex
|
||||
from ..AppriseLocale import gettext_lazy as _
|
||||
|
||||
# Token required as part of the API request
|
||||
VALIDATE_TOKEN = re.compile(r'[a-z0-9]{80}', re.I)
|
||||
|
||||
# Extend HTTP Error Messages
|
||||
# Based on: https://developer.webex.com/docs/api/basics/rate-limiting
|
||||
WEBEX_HTTP_ERROR_MAP = {
|
||||
|
@ -119,7 +117,7 @@ class NotifyWebexTeams(NotifyBase):
|
|||
'type': 'string',
|
||||
'private': True,
|
||||
'required': True,
|
||||
'regex': (r'[a-z0-9]{80}', 'i'),
|
||||
'regex': (r'^[a-z0-9]{80}$', 'i'),
|
||||
},
|
||||
})
|
||||
|
||||
|
@ -129,20 +127,15 @@ class NotifyWebexTeams(NotifyBase):
|
|||
"""
|
||||
super(NotifyWebexTeams, self).__init__(**kwargs)
|
||||
|
||||
if not token:
|
||||
msg = 'The Webex Teams token is not specified.'
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
if not VALIDATE_TOKEN.match(token.strip()):
|
||||
# The token associated with the account
|
||||
self.token = validate_regex(
|
||||
token, *self.template_tokens['token']['regex'])
|
||||
if not self.token:
|
||||
msg = 'The Webex Teams token specified ({}) is invalid.'\
|
||||
.format(token)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
# The token associated with the account
|
||||
self.token = token.strip()
|
||||
|
||||
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
||||
"""
|
||||
Perform Webex Teams Notification
|
||||
|
|
|
@ -157,7 +157,7 @@ class NotifyXMPP(NotifyBase):
|
|||
'name': _('XEP'),
|
||||
'type': 'list:string',
|
||||
'prefix': 'xep-',
|
||||
'regex': (r'[1-9][0-9]{0,3}', 'i'),
|
||||
'regex': (r'^[1-9][0-9]{0,3}$', 'i'),
|
||||
},
|
||||
'jid': {
|
||||
'name': _('Source JID'),
|
||||
|
|
|
@ -61,15 +61,13 @@ import requests
|
|||
from .NotifyBase import NotifyBase
|
||||
from ..common import NotifyType
|
||||
from ..utils import parse_list
|
||||
from ..utils import validate_regex
|
||||
from ..utils import GET_EMAIL_RE
|
||||
from ..AppriseLocale import gettext_lazy as _
|
||||
|
||||
# A Valid Bot Name
|
||||
VALIDATE_BOTNAME = re.compile(r'(?P<name>[A-Z0-9_]{1,32})(-bot)?', re.I)
|
||||
|
||||
# A Valid Bot Token is 32 characters of alpha/numeric
|
||||
VALIDATE_TOKEN = re.compile(r'[A-Z0-9]{32}', re.I)
|
||||
|
||||
# Organization required as part of the API request
|
||||
VALIDATE_ORG = re.compile(
|
||||
r'(?P<org>[A-Z0-9_-]{1,32})(\.(?P<hostname>[^\s]+))?', re.I)
|
||||
|
@ -124,18 +122,20 @@ class NotifyZulip(NotifyBase):
|
|||
'botname': {
|
||||
'name': _('Bot Name'),
|
||||
'type': 'string',
|
||||
'regex': (r'^[A-Z0-9_]{1,32}(-bot)?$', 'i'),
|
||||
},
|
||||
'organization': {
|
||||
'name': _('Organization'),
|
||||
'type': 'string',
|
||||
'required': True,
|
||||
'regex': (r'^[A-Z0-9_-]{1,32})$', 'i')
|
||||
},
|
||||
'token': {
|
||||
'name': _('Token'),
|
||||
'type': 'string',
|
||||
'required': True,
|
||||
'private': True,
|
||||
'regex': (r'[A-Z0-9]{32}', 'i'),
|
||||
'regex': (r'^[A-Z0-9]{32}$', 'i'),
|
||||
},
|
||||
'target_user': {
|
||||
'name': _('Target User'),
|
||||
|
@ -208,20 +208,14 @@ class NotifyZulip(NotifyBase):
|
|||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
try:
|
||||
if not VALIDATE_TOKEN.match(token.strip()):
|
||||
# let outer exception handle this
|
||||
raise TypeError
|
||||
|
||||
except (TypeError, AttributeError):
|
||||
self.token = validate_regex(
|
||||
token, *self.template_tokens['token']['regex'])
|
||||
if not self.token:
|
||||
msg = 'The Zulip token specified ({}) is invalid.'\
|
||||
.format(token)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
# The token associated with the account
|
||||
self.token = token.strip()
|
||||
|
||||
self.targets = parse_list(targets)
|
||||
if len(self.targets) == 0:
|
||||
# No channels identified, use default
|
||||
|
|
|
@ -378,6 +378,16 @@ def details(plugin):
|
|||
# Argument/Option Handling
|
||||
for key in list(template_args.keys()):
|
||||
|
||||
if 'alias_of' in template_args[key]:
|
||||
# Check if the mapped reference is a list; if it is, then
|
||||
# we need to store a different delimiter
|
||||
alias_of = template_tokens.get(template_args[key]['alias_of'], {})
|
||||
if alias_of.get('type', '').startswith('list') \
|
||||
and 'delim' not in template_args[key]:
|
||||
# Set a default delimiter of a comma and/or space if one
|
||||
# hasn't already been specified
|
||||
template_args[key]['delim'] = (',', ' ')
|
||||
|
||||
# _lookup_default looks up what the default value
|
||||
if '_lookup_default' in template_args[key]:
|
||||
template_args[key]['default'] = getattr(
|
||||
|
|
|
@ -28,6 +28,7 @@ import six
|
|||
import contextlib
|
||||
import os
|
||||
from os.path import expanduser
|
||||
from functools import reduce
|
||||
|
||||
try:
|
||||
# Python 2.7
|
||||
|
@ -113,10 +114,17 @@ GET_EMAIL_RE = re.compile(
|
|||
re.IGNORECASE,
|
||||
)
|
||||
|
||||
# Regular expression used to extract a phone number
|
||||
GET_PHONE_NO_RE = re.compile(r'^\+?(?P<phone>[0-9\s)(+-]+)\s*$')
|
||||
|
||||
# Regular expression used to destinguish between multiple URLs
|
||||
URL_DETECTION_RE = re.compile(
|
||||
r'([a-z0-9]+?:\/\/.*?)[\s,]*(?=$|[a-z0-9]+?:\/\/)', re.I)
|
||||
|
||||
# validate_regex() utilizes this mapping to track and re-use pre-complied
|
||||
# regular expressions
|
||||
REGEX_VALIDATE_LOOKUP = {}
|
||||
|
||||
|
||||
def is_hostname(hostname):
|
||||
"""
|
||||
|
@ -512,14 +520,6 @@ def parse_list(*args):
|
|||
elif isinstance(arg, (set, list, tuple)):
|
||||
result += parse_list(*arg)
|
||||
|
||||
elif arg is None:
|
||||
# Ignore
|
||||
continue
|
||||
|
||||
else:
|
||||
# Convert whatever it is to a string and work with it
|
||||
result += parse_list(str(arg))
|
||||
|
||||
#
|
||||
# filter() eliminates any empty entries
|
||||
#
|
||||
|
@ -573,6 +573,11 @@ def is_exclusive_match(logic, data, match_all='all'):
|
|||
# treat these entries as though all elements found
|
||||
# must exist in the notification service
|
||||
entries = set(parse_list(entry))
|
||||
if not entries:
|
||||
# We got a bogus set of tags to parse
|
||||
# If there is no logic to apply then we're done early; we only
|
||||
# match if there is also no data to match against
|
||||
return not data
|
||||
|
||||
if len(entries.intersection(data.union({match_all}))) == len(entries):
|
||||
# our set contains all of the entries found
|
||||
|
@ -587,6 +592,82 @@ def is_exclusive_match(logic, data, match_all='all'):
|
|||
return matched
|
||||
|
||||
|
||||
def validate_regex(value, regex=r'[^\s]+', flags=re.I, strip=True, fmt=None):
|
||||
"""
|
||||
A lot of the tokens, secrets, api keys, etc all have some regular
|
||||
expression validation they support. This hashes the regex after it's
|
||||
compiled and returns it's content if matched, otherwise it returns None.
|
||||
|
||||
This function greatly increases performance as it prevents apprise modules
|
||||
from having to pre-compile all of their regular expressions.
|
||||
|
||||
value is the element being tested
|
||||
regex is the regular expression to be compiled and tested. By default
|
||||
we extract the first chunk of code while eliminating surrounding
|
||||
whitespace (if present)
|
||||
|
||||
flags is the regular expression flags that should be applied
|
||||
format is used to alter the response format if the regular
|
||||
expression matches. You identify your format using {tags}.
|
||||
Effectively nesting your ID's between {}. Consider a regex of:
|
||||
'(?P<year>[0-9]{2})[0-9]+(?P<value>[A-Z])'
|
||||
to which you could set your format up as '{value}-{year}'. This
|
||||
would substitute the matched groups and format a response.
|
||||
"""
|
||||
|
||||
if flags:
|
||||
# Regex String -> Flag Lookup Map
|
||||
_map = {
|
||||
# Ignore Case
|
||||
'i': re.I,
|
||||
# Multi Line
|
||||
'm': re.M,
|
||||
# Dot Matches All
|
||||
's': re.S,
|
||||
# Locale Dependant
|
||||
'L': re.L,
|
||||
# Unicode Matching
|
||||
'u': re.U,
|
||||
# Verbose
|
||||
'x': re.X,
|
||||
}
|
||||
|
||||
if isinstance(flags, six.string_types):
|
||||
# Convert a string of regular expression flags into their
|
||||
# respected integer (expected) Python values and perform
|
||||
# a bit-wise or on each match found:
|
||||
flags = reduce(
|
||||
lambda x, y: x | y,
|
||||
[0] + [_map[f] for f in flags if f in _map])
|
||||
|
||||
else:
|
||||
# Handles None/False/'' cases
|
||||
flags = 0
|
||||
|
||||
# A key is used to store our compiled regular expression
|
||||
key = '{}{}'.format(regex, flags)
|
||||
|
||||
if key not in REGEX_VALIDATE_LOOKUP:
|
||||
REGEX_VALIDATE_LOOKUP[key] = re.compile(regex, flags)
|
||||
|
||||
# Perform our lookup usig our pre-compiled result
|
||||
try:
|
||||
result = REGEX_VALIDATE_LOOKUP[key].match(value)
|
||||
if not result:
|
||||
# let outer exception handle this
|
||||
raise TypeError
|
||||
|
||||
if fmt:
|
||||
# Map our format back to our response
|
||||
value = fmt.format(**result.groupdict())
|
||||
|
||||
except (TypeError, AttributeError):
|
||||
return None
|
||||
|
||||
# Return our response
|
||||
return value.strip() if strip else value
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def environ(*remove, **update):
|
||||
"""
|
||||
|
|
431
test/test_api.py
431
test/test_api.py
|
@ -68,11 +68,11 @@ def test_apprise():
|
|||
a = Apprise()
|
||||
|
||||
# no items
|
||||
assert(len(a) == 0)
|
||||
assert len(a) == 0
|
||||
|
||||
# Apprise object can also be directly tested with 'if' keyword
|
||||
# No entries results in a False response
|
||||
assert(not a)
|
||||
assert not a
|
||||
|
||||
# Create an Asset object
|
||||
asset = AppriseAsset(theme='default')
|
||||
|
@ -89,66 +89,62 @@ def test_apprise():
|
|||
a = Apprise(servers=servers)
|
||||
|
||||
# 2 servers loaded
|
||||
assert(len(a) == 2)
|
||||
assert len(a) == 2
|
||||
|
||||
# Apprise object can also be directly tested with 'if' keyword
|
||||
# At least one entry results in a True response
|
||||
assert(a)
|
||||
assert a
|
||||
|
||||
# We can retrieve our URLs this way:
|
||||
assert(len(a.urls()) == 2)
|
||||
assert len(a.urls()) == 2
|
||||
|
||||
# We can add another server
|
||||
assert(
|
||||
a.add('mmosts://mattermost.server.local/'
|
||||
'3ccdd113474722377935511fc85d3dd4') is True)
|
||||
assert(len(a) == 3)
|
||||
assert a.add('mmosts://mattermost.server.local/'
|
||||
'3ccdd113474722377935511fc85d3dd4') is True
|
||||
assert len(a) == 3
|
||||
|
||||
# We can pop an object off of our stack by it's indexed value:
|
||||
obj = a.pop(0)
|
||||
assert(isinstance(obj, NotifyBase) is True)
|
||||
assert(len(a) == 2)
|
||||
assert isinstance(obj, NotifyBase) is True
|
||||
assert len(a) == 2
|
||||
|
||||
# We can retrieve elements from our list too by reference:
|
||||
assert(isinstance(a[0].url(), six.string_types) is True)
|
||||
assert isinstance(a[0].url(), six.string_types) is True
|
||||
|
||||
# We can iterate over our list too:
|
||||
count = 0
|
||||
for o in a:
|
||||
assert(isinstance(o.url(), six.string_types) is True)
|
||||
assert isinstance(o.url(), six.string_types) is True
|
||||
count += 1
|
||||
# verify that we did indeed iterate over each element
|
||||
assert(len(a) == count)
|
||||
assert len(a) == count
|
||||
|
||||
# We can empty our set
|
||||
a.clear()
|
||||
assert(len(a) == 0)
|
||||
assert len(a) == 0
|
||||
|
||||
# An invalid schema
|
||||
assert(
|
||||
a.add('this is not a parseable url at all') is False)
|
||||
assert(len(a) == 0)
|
||||
assert a.add('this is not a parseable url at all') is False
|
||||
assert len(a) == 0
|
||||
|
||||
# An unsupported schema
|
||||
assert(
|
||||
a.add('invalid://we.just.do.not.support.this.plugin.type') is False)
|
||||
assert(len(a) == 0)
|
||||
assert a.add(
|
||||
'invalid://we.just.do.not.support.this.plugin.type') is False
|
||||
assert len(a) == 0
|
||||
|
||||
# A poorly formatted URL
|
||||
assert(
|
||||
a.add('json://user:@@@:bad?no.good') is False)
|
||||
assert(len(a) == 0)
|
||||
assert a.add('json://user:@@@:bad?no.good') is False
|
||||
assert len(a) == 0
|
||||
|
||||
# Add a server with our asset we created earlier
|
||||
assert(
|
||||
a.add('mmosts://mattermost.server.local/'
|
||||
'3ccdd113474722377935511fc85d3dd4', asset=asset) is True)
|
||||
assert a.add('mmosts://mattermost.server.local/'
|
||||
'3ccdd113474722377935511fc85d3dd4', asset=asset) is True
|
||||
|
||||
# Clear our server listings again
|
||||
a.clear()
|
||||
|
||||
# No servers to notify
|
||||
assert(a.notify(title="my title", body="my body") is False)
|
||||
assert a.notify(title="my title", body="my body") is False
|
||||
|
||||
class BadNotification(NotifyBase):
|
||||
def __init__(self, **kwargs):
|
||||
|
@ -183,26 +179,26 @@ def test_apprise():
|
|||
# Just to explain what is happening here, we would have parsed the
|
||||
# url properly but failed when we went to go and create an instance
|
||||
# of it.
|
||||
assert(a.add('bad://localhost') is False)
|
||||
assert(len(a) == 0)
|
||||
assert a.add('bad://localhost') is False
|
||||
assert len(a) == 0
|
||||
|
||||
assert(a.add('good://localhost') is True)
|
||||
assert(len(a) == 1)
|
||||
assert a.add('good://localhost') is True
|
||||
assert len(a) == 1
|
||||
|
||||
# Bad Notification Type is still allowed as it is presumed the user
|
||||
# know's what their doing
|
||||
assert(a.notify(
|
||||
title="my title", body="my body", notify_type='bad') is True)
|
||||
assert a.notify(
|
||||
title="my title", body="my body", notify_type='bad') is True
|
||||
|
||||
# No Title/Body combo's
|
||||
assert(a.notify(title=None, body=None) is False)
|
||||
assert(a.notify(title='', body=None) is False)
|
||||
assert(a.notify(title=None, body='') is False)
|
||||
assert a.notify(title=None, body=None) is False
|
||||
assert a.notify(title='', body=None) is False
|
||||
assert a.notify(title=None, body='') is False
|
||||
|
||||
# As long as one is present, we're good
|
||||
assert(a.notify(title=None, body='present') is True)
|
||||
assert(a.notify(title='present', body=None) is True)
|
||||
assert(a.notify(title="present", body="present") is True)
|
||||
assert a.notify(title=None, body='present') is True
|
||||
assert a.notify(title='present', body=None) is True
|
||||
assert a.notify(title="present", body="present") is True
|
||||
|
||||
# Clear our server listings again
|
||||
a.clear()
|
||||
|
@ -244,14 +240,14 @@ def test_apprise():
|
|||
# Store our good notification in our schema map
|
||||
SCHEMA_MAP['runtime'] = RuntimeNotification
|
||||
|
||||
assert(a.add('runtime://localhost') is True)
|
||||
assert(a.add('throw://localhost') is True)
|
||||
assert(a.add('fail://localhost') is True)
|
||||
assert(len(a) == 3)
|
||||
assert a.add('runtime://localhost') is True
|
||||
assert a.add('throw://localhost') is True
|
||||
assert a.add('fail://localhost') is True
|
||||
assert len(a) == 3
|
||||
|
||||
# Test when our notify both throws an exception and or just
|
||||
# simply returns False
|
||||
assert(a.notify(title="present", body="present") is False)
|
||||
assert a.notify(title="present", body="present") is False
|
||||
|
||||
# Create a Notification that throws an unexected exception
|
||||
class ThrowInstantiateNotification(NotifyBase):
|
||||
|
@ -267,7 +263,7 @@ def test_apprise():
|
|||
|
||||
# Reset our object
|
||||
a.clear()
|
||||
assert(len(a) == 0)
|
||||
assert len(a) == 0
|
||||
|
||||
# Instantiate a bad object
|
||||
plugin = a.instantiate(object, tag="bad_object")
|
||||
|
@ -275,40 +271,37 @@ def test_apprise():
|
|||
|
||||
# Instantiate a good object
|
||||
plugin = a.instantiate('good://localhost', tag="good")
|
||||
assert(isinstance(plugin, NotifyBase))
|
||||
assert isinstance(plugin, NotifyBase)
|
||||
|
||||
# Test simple tagging inside of the object
|
||||
assert("good" in plugin)
|
||||
assert("bad" not in plugin)
|
||||
assert "good" in plugin
|
||||
assert "bad" not in plugin
|
||||
|
||||
# the in (__contains__ override) is based on or'ed content; so although
|
||||
# 'bad' isn't tagged as being in the plugin, 'good' is, so the return
|
||||
# value of this is True
|
||||
assert(["bad", "good"] in plugin)
|
||||
assert(set(["bad", "good"]) in plugin)
|
||||
assert(("bad", "good") in plugin)
|
||||
assert ["bad", "good"] in plugin
|
||||
assert set(["bad", "good"]) in plugin
|
||||
assert ("bad", "good") in plugin
|
||||
|
||||
# We an add already substatiated instances into our Apprise object
|
||||
a.add(plugin)
|
||||
assert(len(a) == 1)
|
||||
assert len(a) == 1
|
||||
|
||||
# We can add entries as a list too (to add more then one)
|
||||
a.add([plugin, plugin, plugin])
|
||||
assert(len(a) == 4)
|
||||
assert len(a) == 4
|
||||
|
||||
# Reset our object again
|
||||
a.clear()
|
||||
try:
|
||||
with pytest.raises(TypeError):
|
||||
a.instantiate('throw://localhost', suppress_exceptions=False)
|
||||
assert(False)
|
||||
|
||||
except TypeError:
|
||||
assert(True)
|
||||
assert(len(a) == 0)
|
||||
assert len(a) == 0
|
||||
|
||||
assert(a.instantiate(
|
||||
'throw://localhost', suppress_exceptions=True) is None)
|
||||
assert(len(a) == 0)
|
||||
assert a.instantiate(
|
||||
'throw://localhost', suppress_exceptions=True) is None
|
||||
assert len(a) == 0
|
||||
|
||||
#
|
||||
# We rince and repeat the same tests as above, however we do them
|
||||
|
@ -317,50 +310,53 @@ def test_apprise():
|
|||
|
||||
# Reset our object
|
||||
a.clear()
|
||||
assert(len(a) == 0)
|
||||
assert len(a) == 0
|
||||
|
||||
# Instantiate a good object
|
||||
plugin = a.instantiate({
|
||||
'schema': 'good',
|
||||
'host': 'localhost'}, tag="good")
|
||||
assert(isinstance(plugin, NotifyBase))
|
||||
assert isinstance(plugin, NotifyBase)
|
||||
|
||||
# Test simple tagging inside of the object
|
||||
assert("good" in plugin)
|
||||
assert("bad" not in plugin)
|
||||
assert "good" in plugin
|
||||
assert "bad" not in plugin
|
||||
|
||||
# the in (__contains__ override) is based on or'ed content; so although
|
||||
# 'bad' isn't tagged as being in the plugin, 'good' is, so the return
|
||||
# value of this is True
|
||||
assert(["bad", "good"] in plugin)
|
||||
assert(set(["bad", "good"]) in plugin)
|
||||
assert(("bad", "good") in plugin)
|
||||
assert ["bad", "good"] in plugin
|
||||
assert set(["bad", "good"]) in plugin
|
||||
assert ("bad", "good") in plugin
|
||||
|
||||
# We an add already substatiated instances into our Apprise object
|
||||
a.add(plugin)
|
||||
assert(len(a) == 1)
|
||||
assert len(a) == 1
|
||||
|
||||
# We can add entries as a list too (to add more then one)
|
||||
a.add([plugin, plugin, plugin])
|
||||
assert(len(a) == 4)
|
||||
assert len(a) == 4
|
||||
|
||||
# Reset our object again
|
||||
a.clear()
|
||||
try:
|
||||
with pytest.raises(TypeError):
|
||||
a.instantiate({
|
||||
'schema': 'throw',
|
||||
'host': 'localhost'}, suppress_exceptions=False)
|
||||
assert(False)
|
||||
|
||||
except TypeError:
|
||||
assert(True)
|
||||
assert(len(a) == 0)
|
||||
assert len(a) == 0
|
||||
|
||||
assert(a.instantiate({
|
||||
assert a.instantiate({
|
||||
'schema': 'throw',
|
||||
'host': 'localhost'}, suppress_exceptions=True) is None)
|
||||
assert(len(a) == 0)
|
||||
'host': 'localhost'}, suppress_exceptions=True) is None
|
||||
assert len(a) == 0
|
||||
|
||||
|
||||
def test_apprise_pretty_print(tmpdir):
|
||||
"""
|
||||
API: Apprise() Pretty Print tests
|
||||
|
||||
"""
|
||||
# Privacy Print
|
||||
# PrivacyMode.Secret always returns the same thing to avoid guessing
|
||||
assert URLBase.pprint(
|
||||
|
@ -410,6 +406,10 @@ def test_apprise():
|
|||
assert URLBase.pprint(
|
||||
"abcdefghijk", privacy=True, mode=PrivacyMode.Tail) == '...hijk'
|
||||
|
||||
# Quoting settings
|
||||
assert URLBase.pprint(" ", privacy=False, safe='') == '%20'
|
||||
assert URLBase.pprint(" ", privacy=False, quote=False, safe='') == ' '
|
||||
|
||||
|
||||
@mock.patch('requests.get')
|
||||
@mock.patch('requests.post')
|
||||
|
@ -437,90 +437,94 @@ def test_apprise_tagging(mock_post, mock_get):
|
|||
a = Apprise()
|
||||
|
||||
# An invalid addition can't add the tag
|
||||
assert(a.add('averyinvalidschema://localhost', tag='uhoh') is False)
|
||||
assert(a.add({
|
||||
assert a.add('averyinvalidschema://localhost', tag='uhoh') is False
|
||||
assert a.add({
|
||||
'schema': 'averyinvalidschema',
|
||||
'host': 'localhost'}, tag='uhoh') is False)
|
||||
'host': 'localhost'}, tag='uhoh') is False
|
||||
|
||||
# Add entry and assign it to a tag called 'awesome'
|
||||
assert(a.add('json://localhost/path1/', tag='awesome') is True)
|
||||
assert(a.add({
|
||||
assert a.add('json://localhost/path1/', tag='awesome') is True
|
||||
assert a.add({
|
||||
'schema': 'json',
|
||||
'host': 'localhost',
|
||||
'fullpath': '/path1/'}, tag='awesome') is True)
|
||||
'fullpath': '/path1/'}, tag='awesome') is True
|
||||
|
||||
# Add another notification and assign it to a tag called 'awesome'
|
||||
# and another tag called 'local'
|
||||
assert(a.add('json://localhost/path2/', tag=['mmost', 'awesome']) is True)
|
||||
assert a.add('json://localhost/path2/', tag=['mmost', 'awesome']) is True
|
||||
|
||||
# notify the awesome tag; this would notify both services behind the
|
||||
# scenes
|
||||
assert(a.notify(title="my title", body="my body", tag='awesome') is True)
|
||||
assert a.notify(title="my title", body="my body", tag='awesome') is True
|
||||
|
||||
# notify all of the tags
|
||||
assert(a.notify(
|
||||
title="my title", body="my body", tag=['awesome', 'mmost']) is True)
|
||||
assert a.notify(
|
||||
title="my title", body="my body", tag=['awesome', 'mmost']) is True
|
||||
|
||||
# When we query against our loaded notifications for a tag that simply
|
||||
# isn't assigned to anything, we return None. None (different then False)
|
||||
# tells us that we litterally had nothing to query. We didn't fail...
|
||||
# but we also didn't do anything...
|
||||
assert(a.notify(
|
||||
title="my title", body="my body", tag='missing') is None)
|
||||
assert a.notify(
|
||||
title="my title", body="my body", tag='missing') is None
|
||||
|
||||
# Now to test the ability to and and/or notifications
|
||||
a = Apprise()
|
||||
|
||||
# Add a tag by tuple
|
||||
assert(a.add('json://localhost/tagA/', tag=("TagA", )) is True)
|
||||
assert a.add('json://localhost/tagA/', tag=("TagA", )) is True
|
||||
# Add 2 tags by string
|
||||
assert(a.add('json://localhost/tagAB/', tag="TagA, TagB") is True)
|
||||
assert a.add('json://localhost/tagAB/', tag="TagA, TagB") is True
|
||||
# Add a tag using a set
|
||||
assert(a.add('json://localhost/tagB/', tag=set(["TagB"])) is True)
|
||||
assert a.add('json://localhost/tagB/', tag=set(["TagB"])) is True
|
||||
# Add a tag by string (again)
|
||||
assert(a.add('json://localhost/tagC/', tag="TagC") is True)
|
||||
assert a.add('json://localhost/tagC/', tag="TagC") is True
|
||||
# Add 2 tags using a list
|
||||
assert(a.add('json://localhost/tagCD/', tag=["TagC", "TagD"]) is True)
|
||||
assert a.add('json://localhost/tagCD/', tag=["TagC", "TagD"]) is True
|
||||
# Add a tag by string (again)
|
||||
assert(a.add('json://localhost/tagD/', tag="TagD") is True)
|
||||
assert a.add('json://localhost/tagD/', tag="TagD") is True
|
||||
# add a tag set by set (again)
|
||||
assert(a.add('json://localhost/tagCDE/',
|
||||
tag=set(["TagC", "TagD", "TagE"])) is True)
|
||||
assert a.add('json://localhost/tagCDE/',
|
||||
tag=set(["TagC", "TagD", "TagE"])) is True
|
||||
|
||||
# Expression: TagC and TagD
|
||||
# Matches the following only:
|
||||
# - json://localhost/tagCD/
|
||||
# - json://localhost/tagCDE/
|
||||
assert(a.notify(
|
||||
title="my title", body="my body", tag=[('TagC', 'TagD')]) is True)
|
||||
assert a.notify(
|
||||
title="my title", body="my body", tag=[('TagC', 'TagD')]) is True
|
||||
|
||||
# Expression: (TagY and TagZ) or TagX
|
||||
# Matches nothing, None is returned in this case
|
||||
assert(a.notify(
|
||||
assert a.notify(
|
||||
title="my title", body="my body",
|
||||
tag=[('TagY', 'TagZ'), 'TagX']) is None)
|
||||
tag=[('TagY', 'TagZ'), 'TagX']) is None
|
||||
|
||||
# Expression: (TagY and TagZ) or TagA
|
||||
# Matches the following only:
|
||||
# - json://localhost/tagAB/
|
||||
assert(a.notify(
|
||||
assert a.notify(
|
||||
title="my title", body="my body",
|
||||
tag=[('TagY', 'TagZ'), 'TagA']) is True)
|
||||
tag=[('TagY', 'TagZ'), 'TagA']) is True
|
||||
|
||||
# Expression: (TagE and TagD) or TagB
|
||||
# Matches the following only:
|
||||
# - json://localhost/tagCDE/
|
||||
# - json://localhost/tagAB/
|
||||
# - json://localhost/tagB/
|
||||
assert(a.notify(
|
||||
assert a.notify(
|
||||
title="my title", body="my body",
|
||||
tag=[('TagE', 'TagD'), 'TagB']) is True)
|
||||
tag=[('TagE', 'TagD'), 'TagB']) is True
|
||||
|
||||
# Garbage Entries; we can't do anything with the tag so we have nothing to
|
||||
# notify as a result. So we simply return None
|
||||
assert(a.notify(
|
||||
# Garbage Entries in tag field just get stripped out. the below
|
||||
# is the same as notifying no tags at all. Since we have not added
|
||||
# any entries that do not have tags (that we can match against)
|
||||
# we fail. None is returned as a way of letting us know that we
|
||||
# had Notifications to notify, but since none of them matched our tag
|
||||
# none were notified.
|
||||
assert a.notify(
|
||||
title="my title", body="my body",
|
||||
tag=[(object, ), ]) is None)
|
||||
tag=[(object, ), ]) is None
|
||||
|
||||
|
||||
def test_apprise_notify_formats(tmpdir):
|
||||
|
@ -536,7 +540,7 @@ def test_apprise_notify_formats(tmpdir):
|
|||
a = Apprise()
|
||||
|
||||
# no items
|
||||
assert(len(a) == 0)
|
||||
assert len(a) == 0
|
||||
|
||||
class TextNotification(NotifyBase):
|
||||
# set our default notification format
|
||||
|
@ -594,26 +598,29 @@ def test_apprise_notify_formats(tmpdir):
|
|||
# defined plugin above was defined to default to HTML which triggers
|
||||
# a markdown to take place if the body_format specified on the notify
|
||||
# call
|
||||
assert(a.add('html://localhost') is True)
|
||||
assert(a.add('html://another.server') is True)
|
||||
assert(a.add('html://and.another') is True)
|
||||
assert(a.add('text://localhost') is True)
|
||||
assert(a.add('text://another.server') is True)
|
||||
assert(a.add('text://and.another') is True)
|
||||
assert(a.add('markdown://localhost') is True)
|
||||
assert(a.add('markdown://another.server') is True)
|
||||
assert(a.add('markdown://and.another') is True)
|
||||
assert a.add('html://localhost') is True
|
||||
assert a.add('html://another.server') is True
|
||||
assert a.add('html://and.another') is True
|
||||
assert a.add('text://localhost') is True
|
||||
assert a.add('text://another.server') is True
|
||||
assert a.add('text://and.another') is True
|
||||
assert a.add('markdown://localhost') is True
|
||||
assert a.add('markdown://another.server') is True
|
||||
assert a.add('markdown://and.another') is True
|
||||
|
||||
assert(len(a) == 9)
|
||||
assert len(a) == 9
|
||||
|
||||
assert(a.notify(title="markdown", body="## Testing Markdown",
|
||||
body_format=NotifyFormat.MARKDOWN) is True)
|
||||
assert a.notify(
|
||||
title="markdown", body="## Testing Markdown",
|
||||
body_format=NotifyFormat.MARKDOWN) is True
|
||||
|
||||
assert(a.notify(title="text", body="Testing Text",
|
||||
body_format=NotifyFormat.TEXT) is True)
|
||||
assert a.notify(
|
||||
title="text", body="Testing Text",
|
||||
body_format=NotifyFormat.TEXT) is True
|
||||
|
||||
assert(a.notify(title="html", body="<b>HTML</b>",
|
||||
body_format=NotifyFormat.HTML) is True)
|
||||
assert a.notify(
|
||||
title="html", body="<b>HTML</b>",
|
||||
body_format=NotifyFormat.HTML) is True
|
||||
|
||||
|
||||
def test_apprise_asset(tmpdir):
|
||||
|
@ -623,7 +630,7 @@ def test_apprise_asset(tmpdir):
|
|||
"""
|
||||
a = AppriseAsset(theme=None)
|
||||
# Default theme
|
||||
assert(a.theme == 'default')
|
||||
assert a.theme == 'default'
|
||||
|
||||
a = AppriseAsset(
|
||||
theme='dark',
|
||||
|
@ -634,58 +641,49 @@ def test_apprise_asset(tmpdir):
|
|||
a.default_html_color = '#abcabc'
|
||||
a.html_notify_map[NotifyType.INFO] = '#aaaaaa'
|
||||
|
||||
assert(a.color('invalid', tuple) == (171, 202, 188))
|
||||
assert(a.color(NotifyType.INFO, tuple) == (170, 170, 170))
|
||||
assert a.color('invalid', tuple) == (171, 202, 188)
|
||||
assert a.color(NotifyType.INFO, tuple) == (170, 170, 170)
|
||||
|
||||
assert(a.color('invalid', int) == 11258556)
|
||||
assert(a.color(NotifyType.INFO, int) == 11184810)
|
||||
assert a.color('invalid', int) == 11258556
|
||||
assert a.color(NotifyType.INFO, int) == 11184810
|
||||
|
||||
assert(a.color('invalid', None) == '#abcabc')
|
||||
assert(a.color(NotifyType.INFO, None) == '#aaaaaa')
|
||||
assert a.color('invalid', None) == '#abcabc'
|
||||
assert a.color(NotifyType.INFO, None) == '#aaaaaa'
|
||||
# None is the default
|
||||
assert(a.color(NotifyType.INFO) == '#aaaaaa')
|
||||
assert a.color(NotifyType.INFO) == '#aaaaaa'
|
||||
|
||||
# Invalid Type
|
||||
try:
|
||||
a.color(NotifyType.INFO, dict)
|
||||
# We should not get here (exception should be thrown)
|
||||
assert(False)
|
||||
|
||||
except ValueError:
|
||||
with pytest.raises(ValueError):
|
||||
# The exception we expect since dict is not supported
|
||||
assert(True)
|
||||
a.color(NotifyType.INFO, dict)
|
||||
|
||||
except Exception:
|
||||
# Any other exception is not good
|
||||
assert(False)
|
||||
assert a.image_url(NotifyType.INFO, NotifyImageSize.XY_256) == \
|
||||
'http://localhost/dark/info-256x256.png'
|
||||
|
||||
assert(a.image_url(NotifyType.INFO, NotifyImageSize.XY_256) ==
|
||||
'http://localhost/dark/info-256x256.png')
|
||||
|
||||
assert(a.image_path(
|
||||
assert a.image_path(
|
||||
NotifyType.INFO,
|
||||
NotifyImageSize.XY_256,
|
||||
must_exist=False) == '/dark/info-256x256.png')
|
||||
must_exist=False) == '/dark/info-256x256.png'
|
||||
|
||||
# This path doesn't exist so image_raw will fail (since we just
|
||||
# randompyl picked it for testing)
|
||||
assert(a.image_raw(NotifyType.INFO, NotifyImageSize.XY_256) is None)
|
||||
assert a.image_raw(NotifyType.INFO, NotifyImageSize.XY_256) is None
|
||||
|
||||
assert(a.image_path(
|
||||
assert a.image_path(
|
||||
NotifyType.INFO,
|
||||
NotifyImageSize.XY_256,
|
||||
must_exist=True) is None)
|
||||
must_exist=True) is None
|
||||
|
||||
# Create a new object (with our default settings)
|
||||
a = AppriseAsset()
|
||||
|
||||
# Our default configuration can access our file
|
||||
assert(a.image_path(
|
||||
assert a.image_path(
|
||||
NotifyType.INFO,
|
||||
NotifyImageSize.XY_256,
|
||||
must_exist=True) is not None)
|
||||
must_exist=True) is not None
|
||||
|
||||
assert(a.image_raw(NotifyType.INFO, NotifyImageSize.XY_256) is not None)
|
||||
assert a.image_raw(NotifyType.INFO, NotifyImageSize.XY_256) is not None
|
||||
|
||||
# Create a temporary directory
|
||||
sub = tmpdir.mkdir("great.theme")
|
||||
|
@ -703,14 +701,14 @@ def test_apprise_asset(tmpdir):
|
|||
)
|
||||
|
||||
# We'll be able to read file we just created
|
||||
assert(a.image_raw(NotifyType.INFO, NotifyImageSize.XY_256) is not None)
|
||||
assert a.image_raw(NotifyType.INFO, NotifyImageSize.XY_256) is not None
|
||||
|
||||
# We can retrieve the filename at this point even with must_exist set
|
||||
# to True
|
||||
assert(a.image_path(
|
||||
assert a.image_path(
|
||||
NotifyType.INFO,
|
||||
NotifyImageSize.XY_256,
|
||||
must_exist=True) is not None)
|
||||
must_exist=True) is not None
|
||||
|
||||
# If we make the file un-readable however, we won't be able to read it
|
||||
# This test is just showing that we won't throw an exception
|
||||
|
@ -720,37 +718,37 @@ def test_apprise_asset(tmpdir):
|
|||
pytest.skip('The Root user can not run file permission tests.')
|
||||
|
||||
chmod(dirname(sub.strpath), 0o000)
|
||||
assert(a.image_raw(NotifyType.INFO, NotifyImageSize.XY_256) is None)
|
||||
assert a.image_raw(NotifyType.INFO, NotifyImageSize.XY_256) is None
|
||||
|
||||
# Our path doesn't exist anymore using this logic
|
||||
assert(a.image_path(
|
||||
assert a.image_path(
|
||||
NotifyType.INFO,
|
||||
NotifyImageSize.XY_256,
|
||||
must_exist=True) is None)
|
||||
must_exist=True) is None
|
||||
|
||||
# Return our permission so we don't have any problems with our cleanup
|
||||
chmod(dirname(sub.strpath), 0o700)
|
||||
|
||||
# Our content is retrivable again
|
||||
assert(a.image_raw(NotifyType.INFO, NotifyImageSize.XY_256) is not None)
|
||||
assert a.image_raw(NotifyType.INFO, NotifyImageSize.XY_256) is not None
|
||||
|
||||
# our file path is accessible again too
|
||||
assert(a.image_path(
|
||||
assert a.image_path(
|
||||
NotifyType.INFO,
|
||||
NotifyImageSize.XY_256,
|
||||
must_exist=True) is not None)
|
||||
must_exist=True) is not None
|
||||
|
||||
# We do the same test, but set the permission on the file
|
||||
chmod(a.image_path(NotifyType.INFO, NotifyImageSize.XY_256), 0o000)
|
||||
|
||||
# our path will still exist in this case
|
||||
assert(a.image_path(
|
||||
assert a.image_path(
|
||||
NotifyType.INFO,
|
||||
NotifyImageSize.XY_256,
|
||||
must_exist=True) is not None)
|
||||
must_exist=True) is not None
|
||||
|
||||
# but we will not be able to open it
|
||||
assert(a.image_raw(NotifyType.INFO, NotifyImageSize.XY_256) is None)
|
||||
assert a.image_raw(NotifyType.INFO, NotifyImageSize.XY_256) is None
|
||||
|
||||
# Restore our permissions
|
||||
chmod(a.image_path(NotifyType.INFO, NotifyImageSize.XY_256), 0o640)
|
||||
|
@ -759,12 +757,12 @@ def test_apprise_asset(tmpdir):
|
|||
a = AppriseAsset(image_path_mask=False, image_url_mask=False)
|
||||
|
||||
# We always return none in these calls now
|
||||
assert(a.image_raw(NotifyType.INFO, NotifyImageSize.XY_256) is None)
|
||||
assert(a.image_url(NotifyType.INFO, NotifyImageSize.XY_256) is None)
|
||||
assert(a.image_path(NotifyType.INFO, NotifyImageSize.XY_256,
|
||||
must_exist=False) is None)
|
||||
assert(a.image_path(NotifyType.INFO, NotifyImageSize.XY_256,
|
||||
must_exist=True) is None)
|
||||
assert a.image_raw(NotifyType.INFO, NotifyImageSize.XY_256) is None
|
||||
assert a.image_url(NotifyType.INFO, NotifyImageSize.XY_256) is None
|
||||
assert a.image_path(NotifyType.INFO, NotifyImageSize.XY_256,
|
||||
must_exist=False) is None
|
||||
assert a.image_path(NotifyType.INFO, NotifyImageSize.XY_256,
|
||||
must_exist=True) is None
|
||||
|
||||
# Test our default extension out
|
||||
a = AppriseAsset(
|
||||
|
@ -772,28 +770,28 @@ def test_apprise_asset(tmpdir):
|
|||
image_url_mask='http://localhost/{THEME}/{TYPE}-{XY}{EXTENSION}',
|
||||
default_extension='.jpeg',
|
||||
)
|
||||
assert(a.image_path(
|
||||
assert a.image_path(
|
||||
NotifyType.INFO,
|
||||
NotifyImageSize.XY_256,
|
||||
must_exist=False) == '/default/info-256x256.jpeg')
|
||||
must_exist=False) == '/default/info-256x256.jpeg'
|
||||
|
||||
assert(a.image_url(
|
||||
assert a.image_url(
|
||||
NotifyType.INFO,
|
||||
NotifyImageSize.XY_256) == 'http://localhost/'
|
||||
'default/info-256x256.jpeg')
|
||||
NotifyImageSize.XY_256) == \
|
||||
'http://localhost/default/info-256x256.jpeg'
|
||||
|
||||
# extension support
|
||||
assert(a.image_path(
|
||||
assert a.image_path(
|
||||
NotifyType.INFO,
|
||||
NotifyImageSize.XY_128,
|
||||
must_exist=False,
|
||||
extension='.ico') == '/default/info-128x128.ico')
|
||||
extension='.ico') == '/default/info-128x128.ico'
|
||||
|
||||
assert(a.image_url(
|
||||
assert a.image_url(
|
||||
NotifyType.INFO,
|
||||
NotifyImageSize.XY_256,
|
||||
extension='.test') == 'http://localhost/'
|
||||
'default/info-256x256.test')
|
||||
extension='.test') == \
|
||||
'http://localhost/default/info-256x256.test'
|
||||
|
||||
|
||||
def test_apprise_details():
|
||||
|
@ -894,6 +892,11 @@ def test_apprise_details():
|
|||
#
|
||||
'_exists_if': 'always_true',
|
||||
},
|
||||
# alias_of testing
|
||||
'test_alias_of': {
|
||||
'alias_of': 'mylistB',
|
||||
'delim': ('-', ' ')
|
||||
}
|
||||
})
|
||||
|
||||
def url(self):
|
||||
|
@ -1116,10 +1119,11 @@ def test_apprise_details_plugin_verification():
|
|||
assert '{} is an invalid regex'\
|
||||
.format(arg['regex'][0])
|
||||
|
||||
# Regex should never start and/or end with ^/$; leave
|
||||
# that up to the user making use of the regex instead
|
||||
assert re.match(r'^[()\s]*\^', arg['regex'][0]) is None
|
||||
assert re.match(r'[()\s$]*\$', arg['regex'][0]) is None
|
||||
# Regex should always start and/or end with ^/$
|
||||
assert re.match(
|
||||
r'^\^.+?$', arg['regex'][0]) is not None
|
||||
assert re.match(
|
||||
r'^.+?\$$', arg['regex'][0]) is not None
|
||||
|
||||
if arg['type'].startswith('list'):
|
||||
# Delimiters MUST be defined
|
||||
|
@ -1132,9 +1136,56 @@ def test_apprise_details_plugin_verification():
|
|||
assert isinstance(arg['alias_of'], six.string_types)
|
||||
# Track our alias_of object
|
||||
map_to_aliases.add(arg['alias_of'])
|
||||
# 2 entries (name, and alias_of only!)
|
||||
|
||||
# Ensure we're not already in the tokens section
|
||||
# The alias_of object has no value here
|
||||
assert section != 'tokens'
|
||||
|
||||
# We can't be an alias_of ourselves
|
||||
if key == arg['alias_of']:
|
||||
# This is acceptable as long as we exist in the tokens
|
||||
# table because that is truely what we map back to
|
||||
assert key in entry['details']['tokens']
|
||||
|
||||
else:
|
||||
# Throw the problem into an assert tag for debugging
|
||||
# purposes... the mapping is not acceptable
|
||||
assert key != arg['alias_of']
|
||||
|
||||
# alias_of always references back to tokens
|
||||
assert \
|
||||
arg['alias_of'] in entry['details']['tokens'] or \
|
||||
arg['alias_of'] in entry['details']['args']
|
||||
|
||||
# Find a list directive in our tokens
|
||||
t_match = entry['details']['tokens']\
|
||||
.get(arg['alias_of'], {})\
|
||||
.get('type', '').startswith('list')
|
||||
|
||||
a_match = entry['details']['args']\
|
||||
.get(arg['alias_of'], {})\
|
||||
.get('type', '').startswith('list')
|
||||
|
||||
if not (t_match or a_match):
|
||||
# Ensure the only token we have is the alias_of
|
||||
assert len(entry['details'][section][key]) == 1
|
||||
|
||||
else:
|
||||
# We're a list, we allow up to 2 variables
|
||||
# Obviously we have the alias_of entry; that's why
|
||||
# were at this part of the code. But we can
|
||||
# additionally provide a 'delim' over-ride.
|
||||
assert len(entry['details'][section][key]) <= 2
|
||||
if len(entry['details'][section][key]) == 2:
|
||||
# Verify that it is in fact the 'delim' tag
|
||||
assert 'delim' in entry['details'][section][key]
|
||||
# If we do have a delim value set, it must be of
|
||||
# a list/set/tuple type
|
||||
assert isinstance(
|
||||
entry['details'][section][key]['delim'],
|
||||
(tuple, set, list),
|
||||
)
|
||||
|
||||
if six.PY2:
|
||||
# inspect our object
|
||||
# getargspec() is depricated in Python v3
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
|
||||
import sys
|
||||
import six
|
||||
import pytest
|
||||
from apprise.AppriseAsset import AppriseAsset
|
||||
from apprise.config.ConfigBase import ConfigBase
|
||||
from apprise.config import __load_matrix
|
||||
|
@ -41,22 +42,12 @@ def test_config_base():
|
|||
"""
|
||||
|
||||
# invalid types throw exceptions
|
||||
try:
|
||||
with pytest.raises(TypeError):
|
||||
ConfigBase(**{'format': 'invalid'})
|
||||
# We should never reach here as an exception should be thrown
|
||||
assert(False)
|
||||
|
||||
except TypeError:
|
||||
assert(True)
|
||||
|
||||
# Config format types are not the same as ConfigBase ones
|
||||
try:
|
||||
with pytest.raises(TypeError):
|
||||
ConfigBase(**{'format': 'markdown'})
|
||||
# We should never reach here as an exception should be thrown
|
||||
assert(False)
|
||||
|
||||
except TypeError:
|
||||
assert(True)
|
||||
|
||||
cb = ConfigBase(**{'format': 'yaml'})
|
||||
assert isinstance(cb, ConfigBase)
|
||||
|
|
|
@ -274,17 +274,17 @@ def test_email_plugin(mock_smtp, mock_smtpssl):
|
|||
# Expected None but didn't get it
|
||||
print('%s instantiated %s (but expected None)' % (
|
||||
url, str(obj)))
|
||||
assert(False)
|
||||
assert False
|
||||
|
||||
assert(isinstance(obj, instance))
|
||||
assert isinstance(obj, instance)
|
||||
|
||||
if isinstance(obj, plugins.NotifyBase):
|
||||
# We loaded okay; now lets make sure we can reverse this url
|
||||
assert(isinstance(obj.url(), six.string_types) is True)
|
||||
assert isinstance(obj.url(), six.string_types) is True
|
||||
|
||||
# Test url() with privacy=True
|
||||
assert(isinstance(
|
||||
obj.url(privacy=True), six.string_types) is True)
|
||||
assert isinstance(
|
||||
obj.url(privacy=True), six.string_types) is True
|
||||
|
||||
# Some Simple Invalid Instance Testing
|
||||
assert instance.parse_url(None) is None
|
||||
|
@ -307,14 +307,14 @@ def test_email_plugin(mock_smtp, mock_smtpssl):
|
|||
# assertion failure makes things easier to debug later on
|
||||
print('TEST FAIL: {} regenerated as {}'.format(
|
||||
url, obj.url()))
|
||||
assert(False)
|
||||
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)
|
||||
assert getattr(key, obj) == val
|
||||
|
||||
try:
|
||||
if test_smtplib_exceptions is False:
|
||||
|
@ -389,7 +389,7 @@ def test_webbase_lookup(mock_smtp, mock_smtpssl):
|
|||
obj = Apprise.instantiate(
|
||||
'mailto://user:pass@l2g.com', suppress_exceptions=True)
|
||||
|
||||
assert(isinstance(obj, plugins.NotifyEmail))
|
||||
assert isinstance(obj, plugins.NotifyEmail)
|
||||
assert len(obj.targets) == 1
|
||||
assert 'user@l2g.com' in obj.targets
|
||||
assert obj.from_addr == 'user@l2g.com'
|
||||
|
@ -417,7 +417,7 @@ def test_smtplib_init_fail(mock_smtplib):
|
|||
|
||||
obj = Apprise.instantiate(
|
||||
'mailto://user:pass@gmail.com', suppress_exceptions=False)
|
||||
assert(isinstance(obj, plugins.NotifyEmail))
|
||||
assert isinstance(obj, plugins.NotifyEmail)
|
||||
|
||||
# Support Exception handling of smtplib.SMTP
|
||||
mock_smtplib.side_effect = RuntimeError('Test')
|
||||
|
@ -443,7 +443,7 @@ def test_smtplib_send_okay(mock_smtplib):
|
|||
# Defaults to HTML
|
||||
obj = Apprise.instantiate(
|
||||
'mailto://user:pass@gmail.com', suppress_exceptions=False)
|
||||
assert(isinstance(obj, plugins.NotifyEmail))
|
||||
assert isinstance(obj, plugins.NotifyEmail)
|
||||
|
||||
# Support an email simulation where we can correctly quit
|
||||
mock_smtplib.starttls.return_value = True
|
||||
|
@ -451,16 +451,16 @@ def test_smtplib_send_okay(mock_smtplib):
|
|||
mock_smtplib.sendmail.return_value = True
|
||||
mock_smtplib.quit.return_value = True
|
||||
|
||||
assert(obj.notify(
|
||||
body='body', title='test', notify_type=NotifyType.INFO) is True)
|
||||
assert obj.notify(
|
||||
body='body', title='test', notify_type=NotifyType.INFO) is True
|
||||
|
||||
# Set Text
|
||||
obj = Apprise.instantiate(
|
||||
'mailto://user:pass@gmail.com?format=text', suppress_exceptions=False)
|
||||
assert(isinstance(obj, plugins.NotifyEmail))
|
||||
assert isinstance(obj, plugins.NotifyEmail)
|
||||
|
||||
assert(obj.notify(
|
||||
body='body', title='test', notify_type=NotifyType.INFO) is True)
|
||||
assert obj.notify(
|
||||
body='body', title='test', notify_type=NotifyType.INFO) is True
|
||||
|
||||
|
||||
def test_email_url_escaping():
|
||||
|
|
|
@ -26,7 +26,7 @@ import six
|
|||
import mock
|
||||
import requests
|
||||
from apprise import plugins
|
||||
# from apprise import AppriseAsset
|
||||
|
||||
from json import dumps
|
||||
from datetime import datetime
|
||||
|
||||
|
@ -87,16 +87,6 @@ def test_notify_gitter_plugin_general(mock_post, mock_get):
|
|||
mock_get.return_value = request
|
||||
mock_post.return_value = request
|
||||
|
||||
# Variation Initializations (no token)
|
||||
try:
|
||||
obj = plugins.NotifyGitter(token=None, targets='apprise')
|
||||
# No Token should throw an exception
|
||||
assert False
|
||||
|
||||
except TypeError:
|
||||
# We should get here
|
||||
assert True
|
||||
|
||||
# Variation Initializations
|
||||
obj = plugins.NotifyGitter(token=token, targets='apprise')
|
||||
assert isinstance(obj, plugins.NotifyGitter) is True
|
||||
|
@ -181,8 +171,22 @@ def test_notify_gitter_plugin_general(mock_post, mock_get):
|
|||
assert obj.send(body="test") is True
|
||||
|
||||
# Variation Initializations
|
||||
obj = plugins.NotifyGitter(token=token, targets='missing')
|
||||
obj = plugins.NotifyGitter(token=token, targets='apprise')
|
||||
assert isinstance(obj, plugins.NotifyGitter) is True
|
||||
assert isinstance(obj.url(), six.string_types) is True
|
||||
# missing room was found
|
||||
# apprise room was not found
|
||||
assert obj.send(body="test") is False
|
||||
|
||||
# Test exception handling
|
||||
mock_post.side_effect = \
|
||||
requests.ConnectionError(0, 'requests.ConnectionError()')
|
||||
|
||||
# Create temporary _room_mapping object so we will find the apprise
|
||||
# channel on our second call to send()
|
||||
obj._room_mapping = {
|
||||
'apprise': {
|
||||
'id': '5c981cecd73408ce4fbbad31',
|
||||
'uri': 'apprise-notifications/community',
|
||||
}
|
||||
}
|
||||
assert obj.send(body='test body', title='test title') is False
|
||||
|
|
|
@ -136,141 +136,149 @@ def test_dbus_plugin(mock_mainloop, mock_byte, mock_bytearray,
|
|||
|
||||
# Create our instance (identify all supported types)
|
||||
obj = apprise.Apprise.instantiate('dbus://', suppress_exceptions=False)
|
||||
assert(isinstance(obj, apprise.plugins.NotifyDBus) is True)
|
||||
assert isinstance(obj, apprise.plugins.NotifyDBus) is True
|
||||
obj = apprise.Apprise.instantiate('kde://', suppress_exceptions=False)
|
||||
assert(isinstance(obj, apprise.plugins.NotifyDBus) is True)
|
||||
assert isinstance(obj, apprise.plugins.NotifyDBus) is True
|
||||
obj = apprise.Apprise.instantiate('qt://', suppress_exceptions=False)
|
||||
assert(isinstance(obj, apprise.plugins.NotifyDBus) is True)
|
||||
assert isinstance(obj, apprise.plugins.NotifyDBus) is True
|
||||
obj = apprise.Apprise.instantiate('glib://', suppress_exceptions=False)
|
||||
assert(isinstance(obj, apprise.plugins.NotifyDBus) is True)
|
||||
assert isinstance(obj, apprise.plugins.NotifyDBus) is True
|
||||
obj.duration = 0
|
||||
|
||||
# Check that it found our mocked environments
|
||||
assert(obj._enabled is True)
|
||||
assert obj._enabled is True
|
||||
|
||||
# Test our class loading using a series of arguments
|
||||
try:
|
||||
with pytest.raises(TypeError):
|
||||
apprise.plugins.NotifyDBus(**{'schema': 'invalid'})
|
||||
# We should not reach here as the invalid schema
|
||||
# should force an exception
|
||||
assert(False)
|
||||
except TypeError:
|
||||
# Expected behaviour
|
||||
assert(True)
|
||||
|
||||
# Invalid URLs
|
||||
assert apprise.plugins.NotifyDBus.parse_url('') is None
|
||||
|
||||
# Set our X and Y coordinate and try the notification
|
||||
assert(
|
||||
apprise.plugins.NotifyDBus(
|
||||
x_axis=0, y_axis=0, **{'schema': 'dbus'})
|
||||
assert apprise.plugins.NotifyDBus(
|
||||
x_axis=0, y_axis=0, **{'schema': 'dbus'})\
|
||||
.notify(title='', body='body',
|
||||
notify_type=apprise.NotifyType.INFO) is True)
|
||||
notify_type=apprise.NotifyType.INFO) 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
|
||||
|
||||
# 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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
assert isinstance(obj, apprise.plugins.NotifyDBus) is True
|
||||
assert isinstance(obj.url(), six.string_types) is True
|
||||
assert obj.notify(
|
||||
title='title', body='body',
|
||||
notify_type=apprise.NotifyType.INFO) is True
|
||||
|
||||
# If our underlining object throws for whatever reason, we will
|
||||
# If our underlining object throws for whatever rea on, we will
|
||||
# gracefully fail
|
||||
mock_notify = mock.Mock()
|
||||
mock_interface.return_value = mock_notify
|
||||
mock_notify.Notify.side_effect = AttributeError()
|
||||
assert(obj.notify(title='', body='body',
|
||||
notify_type=apprise.NotifyType.INFO) is False)
|
||||
assert obj.notify(
|
||||
title='', body='body',
|
||||
notify_type=apprise.NotifyType.INFO) is False
|
||||
mock_notify.Notify.side_effect = None
|
||||
|
||||
# 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
|
||||
|
||||
|
@ -278,8 +286,9 @@ def test_dbus_plugin(mock_mainloop, mock_byte, mock_bytearray,
|
|||
# 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.NotifyDBus(urgency=0)
|
||||
|
@ -306,15 +315,16 @@ def test_dbus_plugin(mock_mainloop, mock_byte, mock_bytearray,
|
|||
|
||||
# Create our instance
|
||||
obj = apprise.Apprise.instantiate('glib://', suppress_exceptions=False)
|
||||
assert(isinstance(obj, apprise.plugins.NotifyDBus) is True)
|
||||
assert isinstance(obj, apprise.plugins.NotifyDBus) is True
|
||||
obj.duration = 0
|
||||
|
||||
# Test url() call
|
||||
assert(isinstance(obj.url(), six.string_types) is True)
|
||||
assert isinstance(obj.url(), six.string_types) is True
|
||||
|
||||
# Our notification succeeds even though the gi library was not loaded
|
||||
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
|
||||
|
||||
# Verify this all works in the event a ValueError is also thronw
|
||||
# out of the call to gi.require_version()
|
||||
|
@ -336,15 +346,16 @@ def test_dbus_plugin(mock_mainloop, mock_byte, mock_bytearray,
|
|||
|
||||
# Create our instance
|
||||
obj = apprise.Apprise.instantiate('glib://', suppress_exceptions=False)
|
||||
assert(isinstance(obj, apprise.plugins.NotifyDBus) is True)
|
||||
assert isinstance(obj, apprise.plugins.NotifyDBus) is True
|
||||
obj.duration = 0
|
||||
|
||||
# Test url() call
|
||||
assert(isinstance(obj.url(), six.string_types) is True)
|
||||
assert isinstance(obj.url(), six.string_types) is True
|
||||
|
||||
# Our notification succeeds even though the gi library was not loaded
|
||||
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
|
||||
|
||||
# Force a global import error
|
||||
_session_bus = sys.modules['dbus']
|
||||
|
@ -358,15 +369,16 @@ def test_dbus_plugin(mock_mainloop, mock_byte, mock_bytearray,
|
|||
|
||||
# Create our instance
|
||||
obj = apprise.Apprise.instantiate('glib://', suppress_exceptions=False)
|
||||
assert(isinstance(obj, apprise.plugins.NotifyDBus) is True)
|
||||
assert isinstance(obj, apprise.plugins.NotifyDBus) is True
|
||||
obj.duration = 0
|
||||
|
||||
# Test url() call
|
||||
assert(isinstance(obj.url(), six.string_types) is True)
|
||||
assert isinstance(obj.url(), six.string_types) is True
|
||||
|
||||
# Our notification fail because the dbus library wasn't present
|
||||
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
|
||||
|
||||
# Since playing with the sys.modules is not such a good idea,
|
||||
# let's just put it back now :)
|
||||
|
|
|
@ -211,7 +211,7 @@ def test_growl_plugin(mock_gntp):
|
|||
try:
|
||||
obj = Apprise.instantiate(url, suppress_exceptions=False)
|
||||
|
||||
assert(exception is None)
|
||||
assert exception is None
|
||||
|
||||
if obj is None:
|
||||
# We're done
|
||||
|
@ -219,17 +219,17 @@ def test_growl_plugin(mock_gntp):
|
|||
|
||||
if instance is None:
|
||||
# Expected None but didn't get it
|
||||
assert(False)
|
||||
assert False
|
||||
|
||||
assert(isinstance(obj, instance) is True)
|
||||
assert isinstance(obj, instance) is True
|
||||
|
||||
if isinstance(obj, plugins.NotifyBase):
|
||||
# We loaded okay; now lets make sure we can reverse this url
|
||||
assert(isinstance(obj.url(), six.string_types) is True)
|
||||
assert isinstance(obj.url(), six.string_types) is True
|
||||
|
||||
# Test our privacy=True flag
|
||||
assert(isinstance(
|
||||
obj.url(privacy=True), six.string_types) is True)
|
||||
assert isinstance(
|
||||
obj.url(privacy=True), six.string_types) is True
|
||||
|
||||
# Instantiate the exact same object again using the URL from
|
||||
# the one that was already created properly
|
||||
|
@ -243,14 +243,14 @@ def test_growl_plugin(mock_gntp):
|
|||
# assertion failure makes things easier to debug later on
|
||||
print('TEST FAIL: {} regenerated as {}'.format(
|
||||
url, obj.url()))
|
||||
assert(False)
|
||||
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)
|
||||
assert getattr(key, obj) == val
|
||||
|
||||
try:
|
||||
if test_growl_notify_exceptions is False:
|
||||
|
@ -292,5 +292,5 @@ def test_growl_plugin(mock_gntp):
|
|||
except Exception as e:
|
||||
# Handle our exception
|
||||
print('%s / %s' % (url, str(e)))
|
||||
assert(exception is not None)
|
||||
assert(isinstance(e, exception))
|
||||
assert exception is not None
|
||||
assert isinstance(e, exception)
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
import six
|
||||
import pytest
|
||||
from datetime import datetime
|
||||
from datetime import timedelta
|
||||
|
||||
|
@ -43,22 +44,12 @@ def test_notify_base():
|
|||
"""
|
||||
|
||||
# invalid types throw exceptions
|
||||
try:
|
||||
with pytest.raises(TypeError):
|
||||
NotifyBase(**{'format': 'invalid'})
|
||||
# We should never reach here as an exception should be thrown
|
||||
assert(False)
|
||||
|
||||
except TypeError:
|
||||
assert(True)
|
||||
|
||||
# invalid types throw exceptions
|
||||
try:
|
||||
with pytest.raises(TypeError):
|
||||
NotifyBase(**{'overflow': 'invalid'})
|
||||
# We should never reach here as an exception should be thrown
|
||||
assert(False)
|
||||
|
||||
except TypeError:
|
||||
assert(True)
|
||||
|
||||
# Bad port information
|
||||
nb = NotifyBase(port='invalid')
|
||||
|
@ -216,7 +207,7 @@ def test_notify_base():
|
|||
|
||||
# Test invalid data
|
||||
assert NotifyBase.parse_list(None) == []
|
||||
assert NotifyBase.parse_list(42) == ['42', ]
|
||||
assert NotifyBase.parse_list(42) == []
|
||||
|
||||
result = NotifyBase.parse_list(
|
||||
',path,?name=Dr%20Disrespect', unquote=False)
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -24,6 +24,7 @@
|
|||
# THE SOFTWARE.
|
||||
|
||||
import mock
|
||||
import pytest
|
||||
import requests
|
||||
from apprise import plugins
|
||||
from apprise import Apprise
|
||||
|
@ -45,7 +46,7 @@ def test_object_initialization():
|
|||
"""
|
||||
|
||||
# Initializes the plugin with a valid access, but invalid access key
|
||||
try:
|
||||
with pytest.raises(TypeError):
|
||||
# No access_key_id specified
|
||||
plugins.NotifySNS(
|
||||
access_key_id=None,
|
||||
|
@ -53,14 +54,8 @@ def test_object_initialization():
|
|||
region_name=TEST_REGION,
|
||||
targets='+1800555999',
|
||||
)
|
||||
# The entries above are invalid, our code should never reach here
|
||||
assert(False)
|
||||
|
||||
except TypeError:
|
||||
# Exception correctly caught
|
||||
assert(True)
|
||||
|
||||
try:
|
||||
with pytest.raises(TypeError):
|
||||
# No secret_access_key specified
|
||||
plugins.NotifySNS(
|
||||
access_key_id=TEST_ACCESS_KEY_ID,
|
||||
|
@ -68,14 +63,8 @@ def test_object_initialization():
|
|||
region_name=TEST_REGION,
|
||||
targets='+1800555999',
|
||||
)
|
||||
# The entries above are invalid, our code should never reach here
|
||||
assert(False)
|
||||
|
||||
except TypeError:
|
||||
# Exception correctly caught
|
||||
assert(True)
|
||||
|
||||
try:
|
||||
with pytest.raises(TypeError):
|
||||
# No region_name specified
|
||||
plugins.NotifySNS(
|
||||
access_key_id=TEST_ACCESS_KEY_ID,
|
||||
|
@ -83,14 +72,8 @@ def test_object_initialization():
|
|||
region_name=None,
|
||||
targets='+1800555999',
|
||||
)
|
||||
# The entries above are invalid, our code should never reach here
|
||||
assert(False)
|
||||
|
||||
except TypeError:
|
||||
# Exception correctly caught
|
||||
assert(True)
|
||||
|
||||
try:
|
||||
with pytest.raises(TypeError):
|
||||
# No recipients
|
||||
plugins.NotifySNS(
|
||||
access_key_id=TEST_ACCESS_KEY_ID,
|
||||
|
@ -98,14 +81,8 @@ def test_object_initialization():
|
|||
region_name=TEST_REGION,
|
||||
targets=None,
|
||||
)
|
||||
# Still valid even without recipients
|
||||
assert(True)
|
||||
|
||||
except TypeError:
|
||||
# Exception correctly caught
|
||||
assert(False)
|
||||
|
||||
try:
|
||||
with pytest.raises(TypeError):
|
||||
# No recipients - garbage recipients object
|
||||
plugins.NotifySNS(
|
||||
access_key_id=TEST_ACCESS_KEY_ID,
|
||||
|
@ -113,14 +90,8 @@ def test_object_initialization():
|
|||
region_name=TEST_REGION,
|
||||
targets=object(),
|
||||
)
|
||||
# Still valid even without recipients
|
||||
assert(True)
|
||||
|
||||
except TypeError:
|
||||
# Exception correctly caught
|
||||
assert(False)
|
||||
|
||||
try:
|
||||
with pytest.raises(TypeError):
|
||||
# The phone number is invalid, and without it, there is nothing
|
||||
# to notify
|
||||
plugins.NotifySNS(
|
||||
|
@ -129,15 +100,8 @@ def test_object_initialization():
|
|||
region_name=TEST_REGION,
|
||||
targets='+1809',
|
||||
)
|
||||
# The recipient is invalid, but it's still okay; this Notification
|
||||
# still becomes pretty much useless at this point though
|
||||
assert(True)
|
||||
|
||||
except TypeError:
|
||||
# Exception correctly caught
|
||||
assert(False)
|
||||
|
||||
try:
|
||||
with pytest.raises(TypeError):
|
||||
# The phone number is invalid, and without it, there is nothing
|
||||
# to notify; we
|
||||
plugins.NotifySNS(
|
||||
|
@ -146,13 +110,6 @@ def test_object_initialization():
|
|||
region_name=TEST_REGION,
|
||||
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
|
||||
assert(True)
|
||||
|
||||
except TypeError:
|
||||
# Exception correctly caught
|
||||
assert(False)
|
||||
|
||||
|
||||
def test_url_parsing():
|
||||
|
@ -169,13 +126,13 @@ def test_url_parsing():
|
|||
)
|
||||
|
||||
# Confirm that there were no recipients found
|
||||
assert(len(results['targets']) == 0)
|
||||
assert('region_name' in results)
|
||||
assert(TEST_REGION == results['region_name'])
|
||||
assert('access_key_id' in results)
|
||||
assert(TEST_ACCESS_KEY_ID == results['access_key_id'])
|
||||
assert('secret_access_key' in results)
|
||||
assert(TEST_ACCESS_KEY_SECRET == results['secret_access_key'])
|
||||
assert len(results['targets']) == 0
|
||||
assert 'region_name' in results
|
||||
assert TEST_REGION == results['region_name']
|
||||
assert 'access_key_id' in results
|
||||
assert TEST_ACCESS_KEY_ID == results['access_key_id']
|
||||
assert 'secret_access_key' in results
|
||||
assert TEST_ACCESS_KEY_SECRET == results['secret_access_key']
|
||||
|
||||
# Detect recipients
|
||||
results = plugins.NotifySNS.parse_url('sns://%s/%s/%s/%s/%s/' % (
|
||||
|
@ -188,15 +145,15 @@ def test_url_parsing():
|
|||
)
|
||||
|
||||
# Confirm that our recipients were found
|
||||
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)
|
||||
assert(TEST_ACCESS_KEY_ID == results['access_key_id'])
|
||||
assert('secret_access_key' in results)
|
||||
assert(TEST_ACCESS_KEY_SECRET == results['secret_access_key'])
|
||||
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
|
||||
assert TEST_ACCESS_KEY_ID == results['access_key_id']
|
||||
assert 'secret_access_key' in results
|
||||
assert TEST_ACCESS_KEY_SECRET == results['secret_access_key']
|
||||
|
||||
|
||||
def test_object_parsing():
|
||||
|
@ -209,20 +166,20 @@ def test_object_parsing():
|
|||
a = Apprise()
|
||||
|
||||
# Now test failing variations of our URL
|
||||
assert(a.add('sns://') is False)
|
||||
assert(a.add('sns://nosecret') is False)
|
||||
assert(a.add('sns://nosecret/noregion/') is False)
|
||||
assert a.add('sns://') is False
|
||||
assert a.add('sns://nosecret') is False
|
||||
assert a.add('sns://nosecret/noregion/') is False
|
||||
|
||||
# This is valid, but a rather useless URL; there is nothing to notify
|
||||
assert(a.add('sns://norecipient/norecipient/us-west-2') is True)
|
||||
assert(len(a) == 1)
|
||||
# This is valid but without valid recipients, the URL is actually useless
|
||||
assert a.add('sns://norecipient/norecipient/us-west-2') is False
|
||||
assert len(a) == 0
|
||||
|
||||
# Parse a good one
|
||||
assert(a.add('sns://oh/yeah/us-west-2/abcdtopic/+12223334444') is True)
|
||||
assert(len(a) == 2)
|
||||
assert a.add('sns://oh/yeah/us-west-2/abcdtopic/+12223334444') is True
|
||||
assert len(a) == 1
|
||||
|
||||
assert(a.add('sns://oh/yeah/us-west-2/12223334444') is True)
|
||||
assert(len(a) == 3)
|
||||
assert a.add('sns://oh/yeah/us-west-2/12223334444') is True
|
||||
assert len(a) == 2
|
||||
|
||||
|
||||
def test_aws_response_handling():
|
||||
|
@ -232,25 +189,25 @@ def test_aws_response_handling():
|
|||
"""
|
||||
# Not a string
|
||||
response = plugins.NotifySNS.aws_response_to_dict(None)
|
||||
assert(response['type'] is None)
|
||||
assert(response['request_id'] is None)
|
||||
assert response['type'] is None
|
||||
assert response['request_id'] is None
|
||||
|
||||
# Invalid XML
|
||||
response = plugins.NotifySNS.aws_response_to_dict(
|
||||
'<Bad Response xmlns="http://sns.amazonaws.com/doc/2010-03-31/">')
|
||||
assert(response['type'] is None)
|
||||
assert(response['request_id'] is None)
|
||||
assert response['type'] is None
|
||||
assert response['request_id'] is None
|
||||
|
||||
# Single Element in XML
|
||||
response = plugins.NotifySNS.aws_response_to_dict(
|
||||
'<SingleElement></SingleElement>')
|
||||
assert(response['type'] == 'SingleElement')
|
||||
assert(response['request_id'] is None)
|
||||
assert response['type'] == 'SingleElement'
|
||||
assert response['request_id'] is None
|
||||
|
||||
# Empty String
|
||||
response = plugins.NotifySNS.aws_response_to_dict('')
|
||||
assert(response['type'] is None)
|
||||
assert(response['request_id'] is None)
|
||||
assert response['type'] is None
|
||||
assert response['request_id'] is None
|
||||
|
||||
response = plugins.NotifySNS.aws_response_to_dict(
|
||||
"""
|
||||
|
@ -263,9 +220,9 @@ def test_aws_response_handling():
|
|||
</ResponseMetadata>
|
||||
</PublishResponse>
|
||||
""")
|
||||
assert(response['type'] == 'PublishResponse')
|
||||
assert(response['request_id'] == 'dc258024-d0e6-56bb-af1b-d4fe5f4181a4')
|
||||
assert(response['message_id'] == '5e16935a-d1fb-5a31-a716-c7805e5c1d2e')
|
||||
assert response['type'] == 'PublishResponse'
|
||||
assert response['request_id'] == 'dc258024-d0e6-56bb-af1b-d4fe5f4181a4'
|
||||
assert response['message_id'] == '5e16935a-d1fb-5a31-a716-c7805e5c1d2e'
|
||||
|
||||
response = plugins.NotifySNS.aws_response_to_dict(
|
||||
"""
|
||||
|
@ -278,9 +235,9 @@ def test_aws_response_handling():
|
|||
</ResponseMetadata>
|
||||
</CreateTopicResponse>
|
||||
""")
|
||||
assert(response['type'] == 'CreateTopicResponse')
|
||||
assert(response['request_id'] == '604bef0f-369c-50c5-a7a4-bbd474c83d6a')
|
||||
assert(response['topic_arn'] == 'arn:aws:sns:us-east-1:000000000000:abcd')
|
||||
assert response['type'] == 'CreateTopicResponse'
|
||||
assert response['request_id'] == '604bef0f-369c-50c5-a7a4-bbd474c83d6a'
|
||||
assert response['topic_arn'] == 'arn:aws:sns:us-east-1:000000000000:abcd'
|
||||
|
||||
response = plugins.NotifySNS.aws_response_to_dict(
|
||||
"""
|
||||
|
@ -294,12 +251,12 @@ def test_aws_response_handling():
|
|||
<RequestId>b5614883-babe-56ca-93b2-1c592ba6191e</RequestId>
|
||||
</ErrorResponse>
|
||||
""")
|
||||
assert(response['type'] == 'ErrorResponse')
|
||||
assert(response['request_id'] == 'b5614883-babe-56ca-93b2-1c592ba6191e')
|
||||
assert(response['error_type'] == 'Sender')
|
||||
assert(response['error_code'] == 'InvalidParameter')
|
||||
assert(response['error_message'].startswith('Invalid parameter:'))
|
||||
assert(response['error_message'].endswith('required parameter'))
|
||||
assert response['type'] == 'ErrorResponse'
|
||||
assert response['request_id'] == 'b5614883-babe-56ca-93b2-1c592ba6191e'
|
||||
assert response['error_type'] == 'Sender'
|
||||
assert response['error_code'] == 'InvalidParameter'
|
||||
assert response['error_message'].startswith('Invalid parameter:')
|
||||
assert response['error_message'].endswith('required parameter')
|
||||
|
||||
|
||||
@mock.patch('requests.post')
|
||||
|
@ -356,7 +313,7 @@ def test_aws_topic_handling(mock_post):
|
|||
'12223334444/TopicA'])
|
||||
|
||||
# CreateTopic fails
|
||||
assert(a.notify(title='', body='test') is False)
|
||||
assert a.notify(title='', body='test') is False
|
||||
|
||||
def post(url, data, **kwargs):
|
||||
"""
|
||||
|
@ -383,7 +340,7 @@ def test_aws_topic_handling(mock_post):
|
|||
mock_post.side_effect = post
|
||||
|
||||
# Publish fails
|
||||
assert(a.notify(title='', body='test') is False)
|
||||
assert a.notify(title='', body='test') is False
|
||||
|
||||
# Disable our side effect
|
||||
mock_post.side_effect = None
|
||||
|
@ -395,14 +352,14 @@ def test_aws_topic_handling(mock_post):
|
|||
|
||||
# Assign ourselves a new function
|
||||
mock_post.return_value = robj
|
||||
assert(a.notify(title='', body='test') is False)
|
||||
assert a.notify(title='', body='test') is False
|
||||
|
||||
# Handle case where we fails get a bad response
|
||||
robj = mock.Mock()
|
||||
robj.content = ''
|
||||
robj.status_code = requests.codes.bad_request
|
||||
mock_post.return_value = robj
|
||||
assert(a.notify(title='', body='test') is False)
|
||||
assert a.notify(title='', body='test') is False
|
||||
|
||||
# Handle case where we get a valid response and TopicARN
|
||||
robj = mock.Mock()
|
||||
|
@ -410,4 +367,4 @@ def test_aws_topic_handling(mock_post):
|
|||
robj.status_code = requests.codes.ok
|
||||
mock_post.return_value = robj
|
||||
# We would have failed to make Post
|
||||
assert(a.notify(title='', body='test') is True)
|
||||
assert a.notify(title='', body='test') is True
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
|
||||
import six
|
||||
import mock
|
||||
import pytest
|
||||
import requests
|
||||
from json import dumps
|
||||
from datetime import datetime
|
||||
|
@ -41,57 +42,40 @@ def test_twitter_plugin_init():
|
|||
|
||||
"""
|
||||
|
||||
try:
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifyTwitter(
|
||||
ckey=None, csecret=None, akey=None, asecret=None)
|
||||
assert False
|
||||
except TypeError:
|
||||
# All keys set to none
|
||||
assert True
|
||||
|
||||
try:
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifyTwitter(
|
||||
ckey='value', csecret=None, akey=None, asecret=None)
|
||||
assert False
|
||||
except TypeError:
|
||||
# csecret not set
|
||||
assert True
|
||||
|
||||
try:
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifyTwitter(
|
||||
ckey='value', csecret='value', akey=None, asecret=None)
|
||||
assert False
|
||||
except TypeError:
|
||||
# akey not set
|
||||
assert True
|
||||
|
||||
try:
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifyTwitter(
|
||||
ckey='value', csecret='value', akey='value', asecret=None)
|
||||
assert False
|
||||
except TypeError:
|
||||
# asecret not set
|
||||
assert True
|
||||
|
||||
try:
|
||||
assert isinstance(
|
||||
plugins.NotifyTwitter(
|
||||
ckey='value', csecret='value', akey='value', asecret='value')
|
||||
assert True
|
||||
except TypeError:
|
||||
# user not set; but this is okay
|
||||
# We should not reach here
|
||||
assert False
|
||||
ckey='value', csecret='value', akey='value', asecret='value'),
|
||||
plugins.NotifyTwitter,
|
||||
)
|
||||
|
||||
try:
|
||||
assert isinstance(
|
||||
plugins.NotifyTwitter(
|
||||
ckey='value', csecret='value', akey='value', asecret='value',
|
||||
user='l2gnux')
|
||||
# We should initialize properly
|
||||
assert True
|
||||
user='l2gnux'),
|
||||
plugins.NotifyTwitter,
|
||||
)
|
||||
|
||||
except TypeError:
|
||||
# We should not reach here
|
||||
assert False
|
||||
# Invalid Target User
|
||||
with pytest.raises(TypeError):
|
||||
plugins.NotifyTwitter(
|
||||
ckey='value', csecret='value', akey='value', asecret='value',
|
||||
targets='%G@rB@g3')
|
||||
|
||||
|
||||
@mock.patch('requests.get')
|
||||
|
|
|
@ -45,293 +45,290 @@ def test_parse_qsd():
|
|||
"utils: parse_qsd() testing """
|
||||
|
||||
result = utils.parse_qsd('a=1&b=&c&d=abcd')
|
||||
assert(isinstance(result, dict) is True)
|
||||
assert(len(result) == 3)
|
||||
assert isinstance(result, dict) is True
|
||||
assert len(result) == 3
|
||||
assert 'qsd' in result
|
||||
assert 'qsd+' in result
|
||||
assert 'qsd-' in result
|
||||
|
||||
assert(len(result['qsd']) == 4)
|
||||
assert len(result['qsd']) == 4
|
||||
assert 'a' in result['qsd']
|
||||
assert 'b' in result['qsd']
|
||||
assert 'c' in result['qsd']
|
||||
assert 'd' in result['qsd']
|
||||
|
||||
assert(len(result['qsd-']) == 0)
|
||||
assert(len(result['qsd+']) == 0)
|
||||
assert len(result['qsd-']) == 0
|
||||
assert len(result['qsd+']) == 0
|
||||
|
||||
|
||||
def test_parse_url():
|
||||
"utils: parse_url() testing """
|
||||
|
||||
result = utils.parse_url('http://hostname')
|
||||
assert(result['schema'] == 'http')
|
||||
assert(result['host'] == 'hostname')
|
||||
assert(result['port'] is None)
|
||||
assert(result['user'] is None)
|
||||
assert(result['password'] is None)
|
||||
assert(result['fullpath'] is None)
|
||||
assert(result['path'] is None)
|
||||
assert(result['query'] is None)
|
||||
assert(result['url'] == 'http://hostname')
|
||||
assert(result['qsd'] == {})
|
||||
assert(result['qsd-'] == {})
|
||||
assert(result['qsd+'] == {})
|
||||
assert result['schema'] == 'http'
|
||||
assert result['host'] == 'hostname'
|
||||
assert result['port'] is None
|
||||
assert result['user'] is None
|
||||
assert result['password'] is None
|
||||
assert result['fullpath'] is None
|
||||
assert result['path'] is None
|
||||
assert result['query'] is None
|
||||
assert result['url'] == 'http://hostname'
|
||||
assert result['qsd'] == {}
|
||||
assert result['qsd-'] == {}
|
||||
assert result['qsd+'] == {}
|
||||
|
||||
result = utils.parse_url('http://hostname/')
|
||||
assert(result['schema'] == 'http')
|
||||
assert(result['host'] == 'hostname')
|
||||
assert(result['port'] is None)
|
||||
assert(result['user'] is None)
|
||||
assert(result['password'] is None)
|
||||
assert(result['fullpath'] == '/')
|
||||
assert(result['path'] == '/')
|
||||
assert(result['query'] is None)
|
||||
assert(result['url'] == 'http://hostname/')
|
||||
assert(result['qsd'] == {})
|
||||
assert(result['qsd-'] == {})
|
||||
assert(result['qsd+'] == {})
|
||||
assert result['schema'] == 'http'
|
||||
assert result['host'] == 'hostname'
|
||||
assert result['port'] is None
|
||||
assert result['user'] is None
|
||||
assert result['password'] is None
|
||||
assert result['fullpath'] == '/'
|
||||
assert result['path'] == '/'
|
||||
assert result['query'] is None
|
||||
assert result['url'] == 'http://hostname/'
|
||||
assert result['qsd'] == {}
|
||||
assert result['qsd-'] == {}
|
||||
assert result['qsd+'] == {}
|
||||
|
||||
result = utils.parse_url('hostname')
|
||||
assert(result['schema'] == 'http')
|
||||
assert(result['host'] == 'hostname')
|
||||
assert(result['port'] is None)
|
||||
assert(result['user'] is None)
|
||||
assert(result['password'] is None)
|
||||
assert(result['fullpath'] is None)
|
||||
assert(result['path'] is None)
|
||||
assert(result['query'] is None)
|
||||
assert(result['url'] == 'http://hostname')
|
||||
assert(result['qsd'] == {})
|
||||
assert(result['qsd-'] == {})
|
||||
assert(result['qsd+'] == {})
|
||||
assert result['schema'] == 'http'
|
||||
assert result['host'] == 'hostname'
|
||||
assert result['port'] is None
|
||||
assert result['user'] is None
|
||||
assert result['password'] is None
|
||||
assert result['fullpath'] is None
|
||||
assert result['path'] is None
|
||||
assert result['query'] is None
|
||||
assert result['url'] == 'http://hostname'
|
||||
assert result['qsd'] == {}
|
||||
assert result['qsd-'] == {}
|
||||
assert result['qsd+'] == {}
|
||||
|
||||
result = utils.parse_url('http://hostname/?-KeY=Value')
|
||||
assert(result['schema'] == 'http')
|
||||
assert(result['host'] == 'hostname')
|
||||
assert(result['port'] is None)
|
||||
assert(result['user'] is None)
|
||||
assert(result['password'] is None)
|
||||
assert(result['fullpath'] == '/')
|
||||
assert(result['path'] == '/')
|
||||
assert(result['query'] is None)
|
||||
assert(result['url'] == 'http://hostname/')
|
||||
assert('-key' in result['qsd'])
|
||||
assert(unquote(result['qsd']['-key']) == 'Value')
|
||||
assert('KeY' in result['qsd-'])
|
||||
assert(unquote(result['qsd-']['KeY']) == 'Value')
|
||||
assert(result['qsd+'] == {})
|
||||
assert result['schema'] == 'http'
|
||||
assert result['host'] == 'hostname'
|
||||
assert result['port'] is None
|
||||
assert result['user'] is None
|
||||
assert result['password'] is None
|
||||
assert result['fullpath'] == '/'
|
||||
assert result['path'] == '/'
|
||||
assert result['query'] is None
|
||||
assert result['url'] == 'http://hostname/'
|
||||
assert '-key' in result['qsd']
|
||||
assert unquote(result['qsd']['-key']) == 'Value'
|
||||
assert 'KeY' in result['qsd-']
|
||||
assert unquote(result['qsd-']['KeY']) == 'Value'
|
||||
assert result['qsd+'] == {}
|
||||
|
||||
result = utils.parse_url('http://hostname/?+KeY=Value')
|
||||
assert(result['schema'] == 'http')
|
||||
assert(result['host'] == 'hostname')
|
||||
assert(result['port'] is None)
|
||||
assert(result['user'] is None)
|
||||
assert(result['password'] is None)
|
||||
assert(result['fullpath'] == '/')
|
||||
assert(result['path'] == '/')
|
||||
assert(result['query'] is None)
|
||||
assert(result['url'] == 'http://hostname/')
|
||||
assert('+key' in result['qsd'])
|
||||
assert('KeY' in result['qsd+'])
|
||||
assert(result['qsd+']['KeY'] == 'Value')
|
||||
assert(result['qsd-'] == {})
|
||||
assert result['schema'] == 'http'
|
||||
assert result['host'] == 'hostname'
|
||||
assert result['port'] is None
|
||||
assert result['user'] is None
|
||||
assert result['password'] is None
|
||||
assert result['fullpath'] == '/'
|
||||
assert result['path'] == '/'
|
||||
assert result['query'] is None
|
||||
assert result['url'] == 'http://hostname/'
|
||||
assert '+key' in result['qsd']
|
||||
assert 'KeY' in result['qsd+']
|
||||
assert result['qsd+']['KeY'] == 'Value'
|
||||
assert result['qsd-'] == {}
|
||||
|
||||
result = utils.parse_url(
|
||||
'http://hostname/?+KeY=ValueA&-kEy=ValueB&KEY=Value%20+C')
|
||||
assert(result['schema'] == 'http')
|
||||
assert(result['host'] == 'hostname')
|
||||
assert(result['port'] is None)
|
||||
assert(result['user'] is None)
|
||||
assert(result['password'] is None)
|
||||
assert(result['fullpath'] == '/')
|
||||
assert(result['path'] == '/')
|
||||
assert(result['query'] is None)
|
||||
assert(result['url'] == 'http://hostname/')
|
||||
assert('+key' in result['qsd'])
|
||||
assert('-key' in result['qsd'])
|
||||
assert('key' in result['qsd'])
|
||||
assert('KeY' in result['qsd+'])
|
||||
assert(result['qsd+']['KeY'] == 'ValueA')
|
||||
assert('kEy' in result['qsd-'])
|
||||
assert(result['qsd-']['kEy'] == 'ValueB')
|
||||
assert(result['qsd']['key'] == 'Value C')
|
||||
assert(result['qsd']['+key'] == result['qsd+']['KeY'])
|
||||
assert(result['qsd']['-key'] == result['qsd-']['kEy'])
|
||||
assert result['schema'] == 'http'
|
||||
assert result['host'] == 'hostname'
|
||||
assert result['port'] is None
|
||||
assert result['user'] is None
|
||||
assert result['password'] is None
|
||||
assert result['fullpath'] == '/'
|
||||
assert result['path'] == '/'
|
||||
assert result['query'] is None
|
||||
assert result['url'] == 'http://hostname/'
|
||||
assert '+key' in result['qsd']
|
||||
assert '-key' in result['qsd']
|
||||
assert 'key' in result['qsd']
|
||||
assert 'KeY' in result['qsd+']
|
||||
assert result['qsd+']['KeY'] == 'ValueA'
|
||||
assert 'kEy' in result['qsd-']
|
||||
assert result['qsd-']['kEy'] == 'ValueB'
|
||||
assert result['qsd']['key'] == 'Value C'
|
||||
assert result['qsd']['+key'] == result['qsd+']['KeY']
|
||||
assert result['qsd']['-key'] == result['qsd-']['kEy']
|
||||
|
||||
result = utils.parse_url('http://hostname////')
|
||||
assert(result['schema'] == 'http')
|
||||
assert(result['host'] == 'hostname')
|
||||
assert(result['port'] is None)
|
||||
assert(result['user'] is None)
|
||||
assert(result['password'] is None)
|
||||
assert(result['fullpath'] == '/')
|
||||
assert(result['path'] == '/')
|
||||
assert(result['query'] is None)
|
||||
assert(result['url'] == 'http://hostname/')
|
||||
assert(result['qsd'] == {})
|
||||
assert(result['qsd-'] == {})
|
||||
assert(result['qsd+'] == {})
|
||||
assert result['schema'] == 'http'
|
||||
assert result['host'] == 'hostname'
|
||||
assert result['port'] is None
|
||||
assert result['user'] is None
|
||||
assert result['password'] is None
|
||||
assert result['fullpath'] == '/'
|
||||
assert result['path'] == '/'
|
||||
assert result['query'] is None
|
||||
assert result['url'] == 'http://hostname/'
|
||||
assert result['qsd'] == {}
|
||||
assert result['qsd-'] == {}
|
||||
assert result['qsd+'] == {}
|
||||
|
||||
result = utils.parse_url('http://hostname:40////')
|
||||
assert(result['schema'] == 'http')
|
||||
assert(result['host'] == 'hostname')
|
||||
assert(result['port'] == 40)
|
||||
assert(result['user'] is None)
|
||||
assert(result['password'] is None)
|
||||
assert(result['fullpath'] == '/')
|
||||
assert(result['path'] == '/')
|
||||
assert(result['query'] is None)
|
||||
assert(result['url'] == 'http://hostname:40/')
|
||||
assert(result['qsd'] == {})
|
||||
assert(result['qsd-'] == {})
|
||||
assert(result['qsd+'] == {})
|
||||
assert result['schema'] == 'http'
|
||||
assert result['host'] == 'hostname'
|
||||
assert result['port'] == 40
|
||||
assert result['user'] is None
|
||||
assert result['password'] is None
|
||||
assert result['fullpath'] == '/'
|
||||
assert result['path'] == '/'
|
||||
assert result['query'] is None
|
||||
assert result['url'] == 'http://hostname:40/'
|
||||
assert result['qsd'] == {}
|
||||
assert result['qsd-'] == {}
|
||||
assert result['qsd+'] == {}
|
||||
|
||||
result = utils.parse_url('HTTP://HoStNaMe:40/test.php')
|
||||
assert(result['schema'] == 'http')
|
||||
assert(result['host'] == 'HoStNaMe')
|
||||
assert(result['port'] == 40)
|
||||
assert(result['user'] is None)
|
||||
assert(result['password'] is None)
|
||||
assert(result['fullpath'] == '/test.php')
|
||||
assert(result['path'] == '/')
|
||||
assert(result['query'] == 'test.php')
|
||||
assert(result['url'] == 'http://HoStNaMe:40/test.php')
|
||||
assert(result['qsd'] == {})
|
||||
assert(result['qsd-'] == {})
|
||||
assert(result['qsd+'] == {})
|
||||
assert result['schema'] == 'http'
|
||||
assert result['host'] == 'HoStNaMe'
|
||||
assert result['port'] == 40
|
||||
assert result['user'] is None
|
||||
assert result['password'] is None
|
||||
assert result['fullpath'] == '/test.php'
|
||||
assert result['path'] == '/'
|
||||
assert result['query'] == 'test.php'
|
||||
assert result['url'] == 'http://HoStNaMe:40/test.php'
|
||||
assert result['qsd'] == {}
|
||||
assert result['qsd-'] == {}
|
||||
assert result['qsd+'] == {}
|
||||
|
||||
result = utils.parse_url('HTTPS://user@hostname/test.py')
|
||||
assert(result['schema'] == 'https')
|
||||
assert(result['host'] == 'hostname')
|
||||
assert(result['port'] is None)
|
||||
assert(result['user'] == 'user')
|
||||
assert(result['password'] is None)
|
||||
assert(result['fullpath'] == '/test.py')
|
||||
assert(result['path'] == '/')
|
||||
assert(result['query'] == 'test.py')
|
||||
assert(result['url'] == 'https://user@hostname/test.py')
|
||||
assert(result['qsd'] == {})
|
||||
assert(result['qsd-'] == {})
|
||||
assert(result['qsd+'] == {})
|
||||
assert result['schema'] == 'https'
|
||||
assert result['host'] == 'hostname'
|
||||
assert result['port'] is None
|
||||
assert result['user'] == 'user'
|
||||
assert result['password'] is None
|
||||
assert result['fullpath'] == '/test.py'
|
||||
assert result['path'] == '/'
|
||||
assert result['query'] == 'test.py'
|
||||
assert result['url'] == 'https://user@hostname/test.py'
|
||||
assert result['qsd'] == {}
|
||||
assert result['qsd-'] == {}
|
||||
assert result['qsd+'] == {}
|
||||
|
||||
result = utils.parse_url(' HTTPS://///user@@@hostname///test.py ')
|
||||
assert(result['schema'] == 'https')
|
||||
assert(result['host'] == 'hostname')
|
||||
assert(result['port'] is None)
|
||||
assert(result['user'] == 'user')
|
||||
assert(result['password'] is None)
|
||||
assert(result['fullpath'] == '/test.py')
|
||||
assert(result['path'] == '/')
|
||||
assert(result['query'] == 'test.py')
|
||||
assert(result['url'] == 'https://user@hostname/test.py')
|
||||
assert(result['qsd'] == {})
|
||||
assert(result['qsd-'] == {})
|
||||
assert(result['qsd+'] == {})
|
||||
assert result['schema'] == 'https'
|
||||
assert result['host'] == 'hostname'
|
||||
assert result['port'] is None
|
||||
assert result['user'] == 'user'
|
||||
assert result['password'] is None
|
||||
assert result['fullpath'] == '/test.py'
|
||||
assert result['path'] == '/'
|
||||
assert result['query'] == 'test.py'
|
||||
assert result['url'] == 'https://user@hostname/test.py'
|
||||
assert result['qsd'] == {}
|
||||
assert result['qsd-'] == {}
|
||||
assert result['qsd+'] == {}
|
||||
|
||||
result = utils.parse_url(
|
||||
'HTTPS://user:password@otherHost/full///path/name/',
|
||||
)
|
||||
assert(result['schema'] == 'https')
|
||||
assert(result['host'] == 'otherHost')
|
||||
assert(result['port'] is None)
|
||||
assert(result['user'] == 'user')
|
||||
assert(result['password'] == 'password')
|
||||
assert(result['fullpath'] == '/full/path/name/')
|
||||
assert(result['path'] == '/full/path/name/')
|
||||
assert(result['query'] is None)
|
||||
assert(result['url'] == 'https://user:password@otherHost/full/path/name/')
|
||||
assert(result['qsd'] == {})
|
||||
assert(result['qsd-'] == {})
|
||||
assert(result['qsd+'] == {})
|
||||
assert result['schema'] == 'https'
|
||||
assert result['host'] == 'otherHost'
|
||||
assert result['port'] is None
|
||||
assert result['user'] == 'user'
|
||||
assert result['password'] == 'password'
|
||||
assert result['fullpath'] == '/full/path/name/'
|
||||
assert result['path'] == '/full/path/name/'
|
||||
assert result['query'] is None
|
||||
assert result['url'] == 'https://user:password@otherHost/full/path/name/'
|
||||
assert result['qsd'] == {}
|
||||
assert result['qsd-'] == {}
|
||||
assert result['qsd+'] == {}
|
||||
|
||||
# Handle garbage
|
||||
assert(utils.parse_url(None) is None)
|
||||
assert utils.parse_url(None) is None
|
||||
|
||||
result = utils.parse_url(
|
||||
'mailto://user:password@otherHost/lead2gold@gmail.com' +
|
||||
'?from=test@test.com&name=Chris%20Caron&format=text'
|
||||
)
|
||||
assert(result['schema'] == 'mailto')
|
||||
assert(result['host'] == 'otherHost')
|
||||
assert(result['port'] is None)
|
||||
assert(result['user'] == 'user')
|
||||
assert(result['password'] == 'password')
|
||||
assert(unquote(result['fullpath']) == '/lead2gold@gmail.com')
|
||||
assert(result['path'] == '/')
|
||||
assert(unquote(result['query']) == 'lead2gold@gmail.com')
|
||||
assert(unquote(
|
||||
result['url']) ==
|
||||
'mailto://user:password@otherHost/lead2gold@gmail.com')
|
||||
assert(len(result['qsd']) == 3)
|
||||
assert('name' in result['qsd'])
|
||||
assert(unquote(result['qsd']['name']) == 'Chris Caron')
|
||||
assert('from' in result['qsd'])
|
||||
assert(unquote(result['qsd']['from']) == 'test@test.com')
|
||||
assert('format' in result['qsd'])
|
||||
assert(unquote(result['qsd']['format']) == 'text')
|
||||
assert(result['qsd-'] == {})
|
||||
assert(result['qsd+'] == {})
|
||||
assert result['schema'] == 'mailto'
|
||||
assert result['host'] == 'otherHost'
|
||||
assert result['port'] is None
|
||||
assert result['user'] == 'user'
|
||||
assert result['password'] == 'password'
|
||||
assert unquote(result['fullpath']) == '/lead2gold@gmail.com'
|
||||
assert result['path'] == '/'
|
||||
assert unquote(result['query']) == 'lead2gold@gmail.com'
|
||||
assert unquote(result['url']) == \
|
||||
'mailto://user:password@otherHost/lead2gold@gmail.com'
|
||||
assert len(result['qsd']) == 3
|
||||
assert 'name' in result['qsd']
|
||||
assert unquote(result['qsd']['name']) == 'Chris Caron'
|
||||
assert 'from' in result['qsd']
|
||||
assert unquote(result['qsd']['from']) == 'test@test.com'
|
||||
assert 'format' in result['qsd']
|
||||
assert unquote(result['qsd']['format']) == 'text'
|
||||
assert result['qsd-'] == {}
|
||||
assert result['qsd+'] == {}
|
||||
|
||||
# Test Passwords with question marks ?; not supported
|
||||
result = utils.parse_url(
|
||||
'http://user:pass.with.?question@host'
|
||||
)
|
||||
assert(result is None)
|
||||
assert result is None
|
||||
|
||||
# just hostnames
|
||||
result = utils.parse_url(
|
||||
'nuxref.com'
|
||||
)
|
||||
assert(result['schema'] == 'http')
|
||||
assert(result['host'] == 'nuxref.com')
|
||||
assert(result['port'] is None)
|
||||
assert(result['user'] is None)
|
||||
assert(result['password'] is None)
|
||||
assert(result['fullpath'] is None)
|
||||
assert(result['path'] is None)
|
||||
assert(result['query'] is None)
|
||||
assert(result['url'] == 'http://nuxref.com')
|
||||
assert(result['qsd'] == {})
|
||||
assert(result['qsd-'] == {})
|
||||
assert(result['qsd+'] == {})
|
||||
assert result['schema'] == 'http'
|
||||
assert result['host'] == 'nuxref.com'
|
||||
assert result['port'] is None
|
||||
assert result['user'] is None
|
||||
assert result['password'] is None
|
||||
assert result['fullpath'] is None
|
||||
assert result['path'] is None
|
||||
assert result['query'] is None
|
||||
assert result['url'] == 'http://nuxref.com'
|
||||
assert result['qsd'] == {}
|
||||
assert result['qsd-'] == {}
|
||||
assert result['qsd+'] == {}
|
||||
|
||||
# just host and path
|
||||
result = utils.parse_url(
|
||||
'invalid/host'
|
||||
)
|
||||
assert(result['schema'] == 'http')
|
||||
assert(result['host'] == 'invalid')
|
||||
assert(result['port'] is None)
|
||||
assert(result['user'] is None)
|
||||
assert(result['password'] is None)
|
||||
assert(result['fullpath'] == '/host')
|
||||
assert(result['path'] == '/')
|
||||
assert(result['query'] == 'host')
|
||||
assert(result['url'] == 'http://invalid/host')
|
||||
assert(result['qsd'] == {})
|
||||
assert(result['qsd-'] == {})
|
||||
assert(result['qsd+'] == {})
|
||||
result = utils.parse_url('invalid/host')
|
||||
assert result['schema'] == 'http'
|
||||
assert result['host'] == 'invalid'
|
||||
assert result['port'] is None
|
||||
assert result['user'] is None
|
||||
assert result['password'] is None
|
||||
assert result['fullpath'] == '/host'
|
||||
assert result['path'] == '/'
|
||||
assert result['query'] == 'host'
|
||||
assert result['url'] == 'http://invalid/host'
|
||||
assert result['qsd'] == {}
|
||||
assert result['qsd-'] == {}
|
||||
assert result['qsd+'] == {}
|
||||
|
||||
# just all out invalid
|
||||
assert(utils.parse_url('?') is None)
|
||||
assert(utils.parse_url('/') is None)
|
||||
assert utils.parse_url('?') is None
|
||||
assert utils.parse_url('/') is None
|
||||
|
||||
# A default port of zero is still considered valid, but
|
||||
# is removed in the response.
|
||||
result = utils.parse_url('http://nuxref.com:0')
|
||||
assert(result['schema'] == 'http')
|
||||
assert(result['host'] == 'nuxref.com')
|
||||
assert(result['port'] is None)
|
||||
assert(result['user'] is None)
|
||||
assert(result['password'] is None)
|
||||
assert(result['fullpath'] is None)
|
||||
assert(result['path'] is None)
|
||||
assert(result['query'] is None)
|
||||
assert(result['url'] == 'http://nuxref.com')
|
||||
assert(result['qsd'] == {})
|
||||
assert(result['qsd-'] == {})
|
||||
assert(result['qsd+'] == {})
|
||||
assert result['schema'] == 'http'
|
||||
assert result['host'] == 'nuxref.com'
|
||||
assert result['port'] is None
|
||||
assert result['user'] is None
|
||||
assert result['password'] is None
|
||||
assert result['fullpath'] is None
|
||||
assert result['path'] is None
|
||||
assert result['query'] is None
|
||||
assert result['url'] == 'http://nuxref.com'
|
||||
assert result['qsd'] == {}
|
||||
assert result['qsd-'] == {}
|
||||
assert result['qsd+'] == {}
|
||||
|
||||
# Test some illegal strings
|
||||
result = utils.parse_url(object, verify_host=False)
|
||||
|
@ -345,7 +342,7 @@ def test_parse_url():
|
|||
|
||||
# Do it again without host validation
|
||||
result = utils.parse_url('test://', verify_host=False)
|
||||
assert(result['schema'] == 'test')
|
||||
assert result['schema'] == 'test'
|
||||
# It's worth noting that the hostname is an empty string and is NEVER set
|
||||
# to None if it wasn't specified.
|
||||
assert result['host'] == ''
|
||||
|
@ -423,10 +420,10 @@ def test_parse_url():
|
|||
assert result['port'] is None
|
||||
assert result['user'] == ''
|
||||
assert result['password'] == ''
|
||||
assert(unquote(result['fullpath']) == '/_/@^&/jack.json')
|
||||
assert(unquote(result['path']) == '/_/@^&/')
|
||||
assert unquote(result['fullpath']) == '/_/@^&/jack.json'
|
||||
assert unquote(result['path']) == '/_/@^&/'
|
||||
assert result['query'] == 'jack.json'
|
||||
assert(unquote(result['url']) == 'crazy://:@/_/@^&/jack.json')
|
||||
assert unquote(result['url']) == 'crazy://:@/_/@^&/jack.json'
|
||||
assert result['qsd'] == {}
|
||||
assert result['qsd-'] == {}
|
||||
|
||||
|
@ -434,42 +431,42 @@ def test_parse_url():
|
|||
def test_parse_bool():
|
||||
"utils: parse_bool() testing """
|
||||
|
||||
assert(utils.parse_bool('Enabled', None) is True)
|
||||
assert(utils.parse_bool('Disabled', None) is False)
|
||||
assert(utils.parse_bool('Allow', None) is True)
|
||||
assert(utils.parse_bool('Deny', None) is False)
|
||||
assert(utils.parse_bool('Yes', None) is True)
|
||||
assert(utils.parse_bool('YES', None) is True)
|
||||
assert(utils.parse_bool('Always', None) is True)
|
||||
assert(utils.parse_bool('No', None) is False)
|
||||
assert(utils.parse_bool('NO', None) is False)
|
||||
assert(utils.parse_bool('NEVER', None) is False)
|
||||
assert(utils.parse_bool('TrUE', None) is True)
|
||||
assert(utils.parse_bool('tRUe', None) is True)
|
||||
assert(utils.parse_bool('FAlse', None) is False)
|
||||
assert(utils.parse_bool('F', None) is False)
|
||||
assert(utils.parse_bool('T', None) is True)
|
||||
assert(utils.parse_bool('0', None) is False)
|
||||
assert(utils.parse_bool('1', None) is True)
|
||||
assert(utils.parse_bool('True', None) is True)
|
||||
assert(utils.parse_bool('Yes', None) is True)
|
||||
assert(utils.parse_bool(1, None) is True)
|
||||
assert(utils.parse_bool(0, None) is False)
|
||||
assert(utils.parse_bool(True, None) is True)
|
||||
assert(utils.parse_bool(False, None) is False)
|
||||
assert utils.parse_bool('Enabled', None) is True
|
||||
assert utils.parse_bool('Disabled', None) is False
|
||||
assert utils.parse_bool('Allow', None) is True
|
||||
assert utils.parse_bool('Deny', None) is False
|
||||
assert utils.parse_bool('Yes', None) is True
|
||||
assert utils.parse_bool('YES', None) is True
|
||||
assert utils.parse_bool('Always', None) is True
|
||||
assert utils.parse_bool('No', None) is False
|
||||
assert utils.parse_bool('NO', None) is False
|
||||
assert utils.parse_bool('NEVER', None) is False
|
||||
assert utils.parse_bool('TrUE', None) is True
|
||||
assert utils.parse_bool('tRUe', None) is True
|
||||
assert utils.parse_bool('FAlse', None) is False
|
||||
assert utils.parse_bool('F', None) is False
|
||||
assert utils.parse_bool('T', None) is True
|
||||
assert utils.parse_bool('0', None) is False
|
||||
assert utils.parse_bool('1', None) is True
|
||||
assert utils.parse_bool('True', None) is True
|
||||
assert utils.parse_bool('Yes', None) is True
|
||||
assert utils.parse_bool(1, None) is True
|
||||
assert utils.parse_bool(0, None) is False
|
||||
assert utils.parse_bool(True, None) is True
|
||||
assert utils.parse_bool(False, None) is False
|
||||
|
||||
# only the int of 0 will return False since the function
|
||||
# casts this to a boolean
|
||||
assert(utils.parse_bool(2, None) is True)
|
||||
assert utils.parse_bool(2, None) is True
|
||||
# An empty list is still false
|
||||
assert(utils.parse_bool([], None) is False)
|
||||
assert utils.parse_bool([], None) is False
|
||||
# But a list that contains something is True
|
||||
assert(utils.parse_bool(['value', ], None) is True)
|
||||
assert utils.parse_bool(['value', ], None) is True
|
||||
|
||||
# Use Default (which is False)
|
||||
assert(utils.parse_bool('OhYeah') is False)
|
||||
assert utils.parse_bool('OhYeah') is False
|
||||
# Adjust Default and get a different result
|
||||
assert(utils.parse_bool('OhYeah', True) is True)
|
||||
assert utils.parse_bool('OhYeah', True) is True
|
||||
|
||||
|
||||
def test_is_hostname():
|
||||
|
@ -608,14 +605,15 @@ def test_parse_list():
|
|||
results = utils.parse_list(
|
||||
'.mkv,.avi,.divx,.xvid,.mov,.wmv,.mp4,.mpg,.mpeg,.vob,.iso')
|
||||
|
||||
assert(results == sorted([
|
||||
assert results == sorted([
|
||||
'.divx', '.iso', '.mkv', '.mov', '.mpg', '.avi', '.mpeg', '.vob',
|
||||
'.xvid', '.wmv', '.mp4',
|
||||
]))
|
||||
])
|
||||
|
||||
class StrangeObject(object):
|
||||
def __str__(self):
|
||||
return '.avi'
|
||||
|
||||
# Now 2 lists with lots of duplicates and other delimiters
|
||||
results = utils.parse_list(
|
||||
'.mkv,.avi,.divx,.xvid,.mov,.wmv,.mp4,.mpg .mpeg,.vob,,; ;',
|
||||
|
@ -623,10 +621,13 @@ def test_parse_list():
|
|||
'.vob,.iso', ['.vob', ['.vob', '.mkv', StrangeObject(), ], ],
|
||||
StrangeObject())
|
||||
|
||||
assert(results == sorted([
|
||||
assert results == sorted([
|
||||
'.divx', '.iso', '.mkv', '.mov', '.mpg', '.avi', '.mpeg', '.vob',
|
||||
'.xvid', '.wmv', '.mp4',
|
||||
]))
|
||||
])
|
||||
|
||||
# Garbage in is removed
|
||||
assert utils.parse_list(object(), 42, None) == []
|
||||
|
||||
# Now a list with extras we want to add as strings
|
||||
# empty entries are removed
|
||||
|
@ -634,10 +635,10 @@ def test_parse_list():
|
|||
'.divx', '.iso', '.mkv', '.mov', '', ' ', '.avi', '.mpeg', '.vob',
|
||||
'.xvid', '.mp4'], '.mov,.wmv,.mp4,.mpg')
|
||||
|
||||
assert(results == sorted([
|
||||
assert results == sorted([
|
||||
'.divx', '.wmv', '.iso', '.mkv', '.mov', '.mpg', '.avi', '.vob',
|
||||
'.xvid', '.mpeg', '.mp4',
|
||||
]))
|
||||
])
|
||||
|
||||
|
||||
def test_exclusive_match():
|
||||
|
@ -735,6 +736,46 @@ def test_exclusive_match():
|
|||
logic='match_me', data=data, match_all='match_me') is True
|
||||
|
||||
|
||||
def test_apprise_validate_regex(tmpdir):
|
||||
"""
|
||||
API: Apprise() Validate Regex tests
|
||||
|
||||
"""
|
||||
assert utils.validate_regex(None) is None
|
||||
assert utils.validate_regex(object) is None
|
||||
assert utils.validate_regex(42) is None
|
||||
assert utils.validate_regex("") is None
|
||||
assert utils.validate_regex(" ") is None
|
||||
assert utils.validate_regex("abc") == "abc"
|
||||
|
||||
# value is a keyword that is extracted (if found)
|
||||
assert utils.validate_regex(
|
||||
"- abcd -", r'-(?P<value>[^-]+)-', fmt="{value}") == "abcd"
|
||||
assert utils.validate_regex(
|
||||
"- abcd -", r'-(?P<value>[^-]+)-', strip=False,
|
||||
fmt="{value}") == " abcd "
|
||||
|
||||
# String flags supported in addition to numeric
|
||||
assert utils.validate_regex(
|
||||
"- abcd -", r'-(?P<value>[^-]+)-', 'i', fmt="{value}") == "abcd"
|
||||
assert utils.validate_regex(
|
||||
"- abcd -", r'-(?P<value>[^-]+)-', re.I, fmt="{value}") == "abcd"
|
||||
|
||||
# Test multiple flag settings
|
||||
assert utils.validate_regex(
|
||||
"- abcd -", r'-(?P<value>[^-]+)-', 'isax', fmt="{value}") == "abcd"
|
||||
|
||||
# Invalid flags are just ignored. The below fails to match
|
||||
# because the default value of 'i' is over-ridden by what is
|
||||
# identfied below, and no flag is set at the end of the day
|
||||
assert utils.validate_regex(
|
||||
"- abcd -", r'-(?P<value>[ABCD]+)-', '-%2gb', fmt="{value}") is None
|
||||
assert utils.validate_regex(
|
||||
"- abcd -", r'-(?P<value>[ABCD]+)-', '', fmt="{value}") is None
|
||||
assert utils.validate_regex(
|
||||
"- abcd -", r'-(?P<value>[ABCD]+)-', None, fmt="{value}") is None
|
||||
|
||||
|
||||
def test_environ_temporary_change():
|
||||
"""utils: environ() testing
|
||||
"""
|
||||
|
|
|
@ -113,37 +113,41 @@ def test_windows_plugin():
|
|||
obj.duration = 0
|
||||
|
||||
# Test URL functionality
|
||||
assert(isinstance(obj.url(), six.string_types) is True)
|
||||
assert isinstance(obj.url(), six.string_types) is True
|
||||
|
||||
# Check that it found our mocked environments
|
||||
assert(obj._enabled is True)
|
||||
assert obj._enabled is True
|
||||
|
||||
# _on_destroy check
|
||||
obj._on_destroy(0, '', 0, 0)
|
||||
|
||||
# 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
|
||||
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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
|
||||
|
||||
|
@ -165,20 +169,23 @@ def test_windows_plugin():
|
|||
# Test our loading of our icon exception; it will still allow the
|
||||
# notification to be sent
|
||||
win32gui.LoadImage.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
|
||||
win32gui.LoadImage.side_effect = None
|
||||
|
||||
# Test our global exception handling
|
||||
win32gui.UpdateWindow.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
|
||||
win32gui.UpdateWindow.side_effect = None
|
||||
|
||||
# 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
|
||||
|
|
Loading…
Reference in New Issue