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