mirror of
https://github.com/caronc/apprise.git
synced 2024-11-25 17:44:00 +01:00
Refactored the way phone numbers are managed (#408)
This commit is contained in:
parent
93c7aef433
commit
8a455695ba
@ -47,6 +47,7 @@ from .AppriseAsset import AppriseAsset
|
||||
from .utils import parse_url
|
||||
from .utils import parse_bool
|
||||
from .utils import parse_list
|
||||
from .utils import parse_phone_no
|
||||
|
||||
# Used to break a path list into parts
|
||||
PATHSPLIT_LIST_DELIM = re.compile(r'[ \t\r\n,\\/]+')
|
||||
@ -560,6 +561,39 @@ class URLBase(object):
|
||||
|
||||
return content
|
||||
|
||||
@staticmethod
|
||||
def parse_phone_no(content, unquote=True):
|
||||
"""A wrapper to utils.parse_phone_no() with unquoting support
|
||||
|
||||
Parses a specified set of data and breaks it into a list.
|
||||
|
||||
Args:
|
||||
content (str): The path to split up into a list. If a list is
|
||||
provided, then it's individual entries are processed.
|
||||
|
||||
unquote (:obj:`bool`, optional): call unquote on each element
|
||||
added to the returned list.
|
||||
|
||||
Returns:
|
||||
list: A unique list containing all of the elements in the path
|
||||
"""
|
||||
|
||||
if unquote:
|
||||
try:
|
||||
content = URLBase.unquote(content)
|
||||
except TypeError:
|
||||
# Nothing further to do
|
||||
return []
|
||||
|
||||
except AttributeError:
|
||||
# This exception ONLY gets thrown under Python v2.7 if an
|
||||
# object() is passed in place of the content
|
||||
return []
|
||||
|
||||
content = parse_phone_no(content)
|
||||
|
||||
return content
|
||||
|
||||
@property
|
||||
def app_id(self):
|
||||
return self.asset.app_id if self.asset.app_id else ''
|
||||
|
@ -36,7 +36,6 @@
|
||||
# The API reference used to build this plugin was documented here:
|
||||
# https://developers.clicksend.com/docs/rest/v3/
|
||||
#
|
||||
import re
|
||||
import requests
|
||||
from json import dumps
|
||||
from base64 import b64encode
|
||||
@ -44,7 +43,8 @@ from base64 import b64encode
|
||||
from .NotifyBase import NotifyBase
|
||||
from ..URLBase import PrivacyMode
|
||||
from ..common import NotifyType
|
||||
from ..utils import parse_list
|
||||
from ..utils import is_phone_no
|
||||
from ..utils import parse_phone_no
|
||||
from ..utils import parse_bool
|
||||
from ..AppriseLocale import gettext_lazy as _
|
||||
|
||||
@ -53,12 +53,6 @@ CLICKSEND_HTTP_ERROR_MAP = {
|
||||
401: 'Unauthorized - Invalid Token.',
|
||||
}
|
||||
|
||||
# Some Phone Number Detection
|
||||
IS_PHONE_NO = re.compile(r'^\+?(?P<phone>[0-9\s)(+-]+)\s*$')
|
||||
|
||||
# Used to break path apart into list of channels
|
||||
TARGET_LIST_DELIM = re.compile(r'[ \t\r\n,#\\/]+')
|
||||
|
||||
|
||||
class NotifyClickSend(NotifyBase):
|
||||
"""
|
||||
@ -151,13 +145,10 @@ class NotifyClickSend(NotifyBase):
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
for target in parse_list(targets):
|
||||
for target in parse_phone_no(targets):
|
||||
# Validate targets and drop bad ones:
|
||||
result = IS_PHONE_NO.match(target)
|
||||
if result:
|
||||
# Further check our phone # for it's digit count
|
||||
result = ''.join(re.findall(r'\d+', result.group('phone')))
|
||||
if len(result) < 11 or len(result) > 14:
|
||||
result = is_phone_no(target)
|
||||
if not result:
|
||||
self.logger.warning(
|
||||
'Dropped invalid phone # '
|
||||
'({}) specified.'.format(target),
|
||||
@ -165,12 +156,7 @@ class NotifyClickSend(NotifyBase):
|
||||
continue
|
||||
|
||||
# store valid phone number
|
||||
self.targets.append(result)
|
||||
continue
|
||||
|
||||
self.logger.warning(
|
||||
'Dropped invalid phone # '
|
||||
'({}) specified.'.format(target))
|
||||
self.targets.append(result['full'])
|
||||
|
||||
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
||||
"""
|
||||
@ -321,8 +307,7 @@ class NotifyClickSend(NotifyBase):
|
||||
# Support the 'to' variable so that we can support rooms this way too
|
||||
# The 'to' makes it easier to use yaml configuration
|
||||
if 'to' in results['qsd'] and len(results['qsd']['to']):
|
||||
results['targets'] += [x for x in filter(
|
||||
bool, TARGET_LIST_DELIM.split(
|
||||
NotifyClickSend.unquote(results['qsd']['to'])))]
|
||||
results['targets'] += \
|
||||
NotifyClickSend.parse_phone_no(results['qsd']['to'])
|
||||
|
||||
return results
|
||||
|
@ -30,7 +30,6 @@
|
||||
# (both user and password) from the API Details section from within your
|
||||
# account profile area: https://d7networks.com/accounts/profile/
|
||||
|
||||
import re
|
||||
import six
|
||||
import requests
|
||||
import base64
|
||||
@ -40,7 +39,8 @@ from json import loads
|
||||
from .NotifyBase import NotifyBase
|
||||
from ..URLBase import PrivacyMode
|
||||
from ..common import NotifyType
|
||||
from ..utils import parse_list
|
||||
from ..utils import is_phone_no
|
||||
from ..utils import parse_phone_no
|
||||
from ..utils import parse_bool
|
||||
from ..AppriseLocale import gettext_lazy as _
|
||||
|
||||
@ -52,9 +52,6 @@ D7NETWORKS_HTTP_ERROR_MAP = {
|
||||
500: 'A Serverside Error Occured Handling the Request.',
|
||||
}
|
||||
|
||||
# Some Phone Number Detection
|
||||
IS_PHONE_NO = re.compile(r'^\+?(?P<phone>[0-9\s)(+-]+)\s*$')
|
||||
|
||||
|
||||
# Priorities
|
||||
class D7SMSPriority(object):
|
||||
@ -197,18 +194,17 @@ class NotifyD7Networks(NotifyBase):
|
||||
self.source = None \
|
||||
if not isinstance(source, six.string_types) else source.strip()
|
||||
|
||||
if not (self.user and self.password):
|
||||
msg = 'A D7 Networks user/pass was not provided.'
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
# Parse our targets
|
||||
self.targets = list()
|
||||
|
||||
for target in parse_list(targets):
|
||||
for target in parse_phone_no(targets):
|
||||
# Validate targets and drop bad ones:
|
||||
result = IS_PHONE_NO.match(target)
|
||||
if result:
|
||||
# Further check our phone # for it's digit count
|
||||
# if it's less than 10, then we can assume it's
|
||||
# a poorly specified phone no and spit a warning
|
||||
result = ''.join(re.findall(r'\d+', result.group('phone')))
|
||||
if len(result) < 11 or len(result) > 14:
|
||||
result = result = is_phone_no(target)
|
||||
if not result:
|
||||
self.logger.warning(
|
||||
'Dropped invalid phone # '
|
||||
'({}) specified.'.format(target),
|
||||
@ -216,16 +212,7 @@ class NotifyD7Networks(NotifyBase):
|
||||
continue
|
||||
|
||||
# store valid phone number
|
||||
self.targets.append(result)
|
||||
continue
|
||||
|
||||
self.logger.warning(
|
||||
'Dropped invalid phone # ({}) specified.'.format(target))
|
||||
|
||||
if len(self.targets) == 0:
|
||||
msg = 'There are no valid targets identified to notify.'
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
self.targets.append(result['full'])
|
||||
|
||||
return
|
||||
|
||||
@ -235,6 +222,11 @@ class NotifyD7Networks(NotifyBase):
|
||||
redirects to the appropriate handling
|
||||
"""
|
||||
|
||||
if len(self.targets) == 0:
|
||||
# There were no services to notify
|
||||
self.logger.warning('There were no D7 Networks targets to notify.')
|
||||
return False
|
||||
|
||||
# error tracking (used for function return)
|
||||
has_error = False
|
||||
|
||||
@ -479,6 +471,6 @@ class NotifyD7Networks(NotifyBase):
|
||||
# The 'to' makes it easier to use yaml configuration
|
||||
if 'to' in results['qsd'] and len(results['qsd']['to']):
|
||||
results['targets'] += \
|
||||
NotifyD7Networks.parse_list(results['qsd']['to'])
|
||||
NotifyD7Networks.parse_phone_no(results['qsd']['to'])
|
||||
|
||||
return results
|
||||
|
@ -32,13 +32,13 @@
|
||||
# This provider does not accept +1 (for example) as a country code. You need
|
||||
# to specify 001 instead.
|
||||
#
|
||||
import re
|
||||
import requests
|
||||
from json import loads
|
||||
|
||||
from .NotifyBase import NotifyBase
|
||||
from ..common import NotifyType
|
||||
from ..utils import parse_list
|
||||
from ..utils import is_phone_no
|
||||
from ..utils import parse_phone_no
|
||||
from ..utils import validate_regex
|
||||
from ..AppriseLocale import gettext_lazy as _
|
||||
|
||||
@ -68,9 +68,6 @@ KAVENEGAR_HTTP_ERROR_MAP = {
|
||||
501: 'SMS can only be sent to the account holder number',
|
||||
}
|
||||
|
||||
# Some Phone Number Detection
|
||||
IS_PHONE_NO = re.compile(r'^\+?(?P<phone>[0-9\s)(+-]+)\s*$')
|
||||
|
||||
|
||||
class NotifyKavenegar(NotifyBase):
|
||||
"""
|
||||
@ -165,36 +162,23 @@ class NotifyKavenegar(NotifyBase):
|
||||
|
||||
self.source = None
|
||||
if source is not None:
|
||||
result = IS_PHONE_NO.match(source)
|
||||
result = is_phone_no(source)
|
||||
if not result:
|
||||
msg = 'The Kavenegar source specified ({}) is invalid.'\
|
||||
.format(source)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
# Further check our phone # for it's digit count
|
||||
result = ''.join(re.findall(r'\d+', result.group('phone')))
|
||||
if len(result) < 11 or len(result) > 14:
|
||||
msg = 'The MessageBird source # specified ({}) is invalid.'\
|
||||
.format(source)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
# Store our source
|
||||
self.source = result
|
||||
self.source = result['full']
|
||||
|
||||
# Parse our targets
|
||||
self.targets = list()
|
||||
|
||||
for target in parse_list(targets):
|
||||
for target in parse_phone_no(targets):
|
||||
# Validate targets and drop bad ones:
|
||||
result = IS_PHONE_NO.match(target)
|
||||
if result:
|
||||
# Further check our phone # for it's digit count
|
||||
# if it's less than 10, then we can assume it's
|
||||
# a poorly specified phone no and spit a warning
|
||||
result = ''.join(re.findall(r'\d+', result.group('phone')))
|
||||
if len(result) < 11 or len(result) > 14:
|
||||
result = is_phone_no(target)
|
||||
if not result:
|
||||
self.logger.warning(
|
||||
'Dropped invalid phone # '
|
||||
'({}) specified.'.format(target),
|
||||
@ -202,16 +186,7 @@ class NotifyKavenegar(NotifyBase):
|
||||
continue
|
||||
|
||||
# store valid phone number
|
||||
self.targets.append(result)
|
||||
continue
|
||||
|
||||
self.logger.warning(
|
||||
'Dropped invalid phone # ({}) specified.'.format(target))
|
||||
|
||||
if len(self.targets) == 0:
|
||||
msg = 'There are no valid targets identified to notify.'
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
self.targets.append(result['full'])
|
||||
|
||||
return
|
||||
|
||||
@ -220,6 +195,11 @@ class NotifyKavenegar(NotifyBase):
|
||||
Sends SMS Message
|
||||
"""
|
||||
|
||||
if len(self.targets) == 0:
|
||||
# There were no services to notify
|
||||
self.logger.warning('There were no Kavenegar targets to notify.')
|
||||
return False
|
||||
|
||||
# error tracking (used for function return)
|
||||
has_error = False
|
||||
|
||||
@ -364,7 +344,7 @@ class NotifyKavenegar(NotifyBase):
|
||||
# The 'to' makes it easier to use yaml configuration
|
||||
if 'to' in results['qsd'] and len(results['qsd']['to']):
|
||||
results['targets'] += \
|
||||
NotifyKavenegar.parse_list(results['qsd']['to'])
|
||||
NotifyKavenegar.parse_phone_no(results['qsd']['to'])
|
||||
|
||||
if 'from' in results['qsd'] and len(results['qsd']['from']):
|
||||
results['source'] = \
|
||||
|
@ -31,18 +31,15 @@
|
||||
# Get details on the API used in this plugin here:
|
||||
# - https://world.msg91.com/apidoc/textsms/send-sms.php
|
||||
|
||||
import re
|
||||
import requests
|
||||
|
||||
from .NotifyBase import NotifyBase
|
||||
from ..common import NotifyType
|
||||
from ..utils import parse_list
|
||||
from ..utils import is_phone_no
|
||||
from ..utils import parse_phone_no
|
||||
from ..utils import validate_regex
|
||||
from ..AppriseLocale import gettext_lazy as _
|
||||
|
||||
# Some Phone Number Detection
|
||||
IS_PHONE_NO = re.compile(r'^\+?(?P<phone>[0-9\s)(+-]+)\s*$')
|
||||
|
||||
|
||||
class MSG91Route(object):
|
||||
"""
|
||||
@ -207,13 +204,10 @@ class NotifyMSG91(NotifyBase):
|
||||
# Parse our targets
|
||||
self.targets = list()
|
||||
|
||||
for target in parse_list(targets):
|
||||
for target in parse_phone_no(targets):
|
||||
# Validate targets and drop bad ones:
|
||||
result = IS_PHONE_NO.match(target)
|
||||
if result:
|
||||
# Further check our phone # for it's digit count
|
||||
result = ''.join(re.findall(r'\d+', result.group('phone')))
|
||||
if len(result) < 11 or len(result) > 14:
|
||||
result = is_phone_no(target)
|
||||
if not result:
|
||||
self.logger.warning(
|
||||
'Dropped invalid phone # '
|
||||
'({}) specified.'.format(target),
|
||||
@ -221,19 +215,7 @@ class NotifyMSG91(NotifyBase):
|
||||
continue
|
||||
|
||||
# store valid phone number
|
||||
self.targets.append(result)
|
||||
continue
|
||||
|
||||
self.logger.warning(
|
||||
'Dropped invalid phone # '
|
||||
'({}) 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)
|
||||
self.targets.append(result['full'])
|
||||
|
||||
return
|
||||
|
||||
@ -242,6 +224,11 @@ class NotifyMSG91(NotifyBase):
|
||||
Perform MSG91 Notification
|
||||
"""
|
||||
|
||||
if len(self.targets) == 0:
|
||||
# There were no services to notify
|
||||
self.logger.warning('There were no MSG91 targets to notify.')
|
||||
return False
|
||||
|
||||
# Prepare our headers
|
||||
headers = {
|
||||
'User-Agent': self.app_id,
|
||||
@ -365,6 +352,6 @@ class NotifyMSG91(NotifyBase):
|
||||
# The 'to' makes it easier to use yaml configuration
|
||||
if 'to' in results['qsd'] and len(results['qsd']['to']):
|
||||
results['targets'] += \
|
||||
NotifyMSG91.parse_list(results['qsd']['to'])
|
||||
NotifyMSG91.parse_phone_no(results['qsd']['to'])
|
||||
|
||||
return results
|
||||
|
@ -29,18 +29,15 @@
|
||||
# - https://dashboard.messagebird.com/en/user/index
|
||||
#
|
||||
|
||||
import re
|
||||
import requests
|
||||
|
||||
from .NotifyBase import NotifyBase
|
||||
from ..common import NotifyType
|
||||
from ..utils import parse_list
|
||||
from ..utils import is_phone_no
|
||||
from ..utils import parse_phone_no
|
||||
from ..utils import validate_regex
|
||||
from ..AppriseLocale import gettext_lazy as _
|
||||
|
||||
# Some Phone Number Detection
|
||||
IS_PHONE_NO = re.compile(r'^\+?(?P<phone>[0-9\s)(+-]+)\s*$')
|
||||
|
||||
|
||||
class NotifyMessageBird(NotifyBase):
|
||||
"""
|
||||
@ -129,28 +126,20 @@ class NotifyMessageBird(NotifyBase):
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
result = IS_PHONE_NO.match(source)
|
||||
result = is_phone_no(source)
|
||||
if not result:
|
||||
msg = 'The MessageBird source specified ({}) is invalid.'\
|
||||
.format(source)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
# Further check our phone # for it's digit count
|
||||
result = ''.join(re.findall(r'\d+', result.group('phone')))
|
||||
if len(result) < 11 or len(result) > 14:
|
||||
msg = 'The MessageBird source # specified ({}) is invalid.'\
|
||||
.format(source)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
# Store our source
|
||||
self.source = result
|
||||
self.source = result['full']
|
||||
|
||||
# Parse our targets
|
||||
self.targets = list()
|
||||
|
||||
targets = parse_list(targets)
|
||||
targets = parse_phone_no(targets)
|
||||
if not targets:
|
||||
# No sources specified, use our own phone no
|
||||
self.targets.append(self.source)
|
||||
@ -159,11 +148,8 @@ class NotifyMessageBird(NotifyBase):
|
||||
# otherwise, store all of our target numbers
|
||||
for target in targets:
|
||||
# Validate targets and drop bad ones:
|
||||
result = IS_PHONE_NO.match(target)
|
||||
if result:
|
||||
# Further check our phone # for it's digit count
|
||||
result = ''.join(re.findall(r'\d+', result.group('phone')))
|
||||
if len(result) < 11 or len(result) > 14:
|
||||
result = is_phone_no(target)
|
||||
if not result:
|
||||
self.logger.warning(
|
||||
'Dropped invalid phone # '
|
||||
'({}) specified.'.format(target),
|
||||
@ -171,19 +157,7 @@ class NotifyMessageBird(NotifyBase):
|
||||
continue
|
||||
|
||||
# store valid phone number
|
||||
self.targets.append(result)
|
||||
continue
|
||||
|
||||
self.logger.warning(
|
||||
'Dropped invalid phone # '
|
||||
'({}) 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)
|
||||
self.targets.append(result['full'])
|
||||
|
||||
return
|
||||
|
||||
@ -192,6 +166,11 @@ class NotifyMessageBird(NotifyBase):
|
||||
Perform MessageBird Notification
|
||||
"""
|
||||
|
||||
if len(self.targets) == 0:
|
||||
# There were no services to notify
|
||||
self.logger.warning('There were no MessageBird targets to notify.')
|
||||
return False
|
||||
|
||||
# error tracking (used for function return)
|
||||
has_error = False
|
||||
|
||||
@ -345,6 +324,7 @@ class NotifyMessageBird(NotifyBase):
|
||||
try:
|
||||
# The first path entry is the source/originator
|
||||
results['source'] = results['targets'].pop(0)
|
||||
|
||||
except IndexError:
|
||||
# No path specified... this URL is potentially un-parseable; we can
|
||||
# hope for a from= entry
|
||||
@ -357,7 +337,7 @@ class NotifyMessageBird(NotifyBase):
|
||||
# The 'to' makes it easier to use yaml configuration
|
||||
if 'to' in results['qsd'] and len(results['qsd']['to']):
|
||||
results['targets'] += \
|
||||
NotifyMessageBird.parse_list(results['qsd']['to'])
|
||||
NotifyMessageBird.parse_phone_no(results['qsd']['to'])
|
||||
|
||||
if 'from' in results['qsd'] and len(results['qsd']['from']):
|
||||
results['source'] = \
|
||||
|
@ -28,20 +28,16 @@
|
||||
# Get your (api) key and secret here:
|
||||
# - https://dashboard.nexmo.com/getting-started-guide
|
||||
#
|
||||
|
||||
import re
|
||||
import requests
|
||||
|
||||
from .NotifyBase import NotifyBase
|
||||
from ..URLBase import PrivacyMode
|
||||
from ..common import NotifyType
|
||||
from ..utils import parse_list
|
||||
from ..utils import is_phone_no
|
||||
from ..utils import parse_phone_no
|
||||
from ..utils import validate_regex
|
||||
from ..AppriseLocale import gettext_lazy as _
|
||||
|
||||
# Some Phone Number Detection
|
||||
IS_PHONE_NO = re.compile(r'^\+?(?P<phone>[0-9\s)(+-]+)\s*$')
|
||||
|
||||
|
||||
class NotifyNexmo(NotifyBase):
|
||||
"""
|
||||
@ -185,30 +181,23 @@ class NotifyNexmo(NotifyBase):
|
||||
# The Source Phone #
|
||||
self.source = source
|
||||
|
||||
if not IS_PHONE_NO.match(self.source):
|
||||
result = is_phone_no(source)
|
||||
if not result:
|
||||
msg = 'The Account (From) Phone # specified ' \
|
||||
'({}) is invalid.'.format(source)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
# Tidy source
|
||||
self.source = re.sub(r'[^\d]+', '', self.source)
|
||||
if len(self.source) < 11 or len(self.source) > 14:
|
||||
msg = 'The Account (From) Phone # specified ' \
|
||||
'({}) contains an invalid digit count.'.format(source)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
# Store our parsed value
|
||||
self.source = result['full']
|
||||
|
||||
# Parse our targets
|
||||
self.targets = list()
|
||||
|
||||
for target in parse_list(targets):
|
||||
for target in parse_phone_no(targets):
|
||||
# Validate targets and drop bad ones:
|
||||
result = IS_PHONE_NO.match(target)
|
||||
if result:
|
||||
# Further check our phone # for it's digit count
|
||||
result = ''.join(re.findall(r'\d+', result.group('phone')))
|
||||
if len(result) < 11 or len(result) > 14:
|
||||
result = is_phone_no(target)
|
||||
if not result:
|
||||
self.logger.warning(
|
||||
'Dropped invalid phone # '
|
||||
'({}) specified.'.format(target),
|
||||
@ -216,13 +205,7 @@ class NotifyNexmo(NotifyBase):
|
||||
continue
|
||||
|
||||
# store valid phone number
|
||||
self.targets.append(result)
|
||||
continue
|
||||
|
||||
self.logger.warning(
|
||||
'Dropped invalid phone # '
|
||||
'({}) specified.'.format(target),
|
||||
)
|
||||
self.targets.append(result['full'])
|
||||
|
||||
return
|
||||
|
||||
@ -393,10 +376,10 @@ class NotifyNexmo(NotifyBase):
|
||||
results['ttl'] = \
|
||||
NotifyNexmo.unquote(results['qsd']['ttl'])
|
||||
|
||||
# Support the 'to' variable so that we can support targets this way too
|
||||
# Support the 'to' variable so that we can support rooms this way too
|
||||
# The 'to' makes it easier to use yaml configuration
|
||||
if 'to' in results['qsd'] and len(results['qsd']['to']):
|
||||
results['targets'] += \
|
||||
NotifyNexmo.parse_list(results['qsd']['to'])
|
||||
NotifyNexmo.parse_phone_no(results['qsd']['to'])
|
||||
|
||||
return results
|
||||
|
@ -23,20 +23,17 @@
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
|
||||
import re
|
||||
import requests
|
||||
|
||||
from .NotifyBase import NotifyBase
|
||||
from ..common import NotifyType
|
||||
from ..utils import is_email
|
||||
from ..utils import is_phone_no
|
||||
from ..utils import parse_list
|
||||
from ..utils import parse_bool
|
||||
from ..utils import validate_regex
|
||||
from ..AppriseLocale import gettext_lazy as _
|
||||
|
||||
# Some Phone Number Detection
|
||||
IS_PHONE_NO = re.compile(r'^\+?(?P<phone>[0-9\s)(+-]+)\s*$')
|
||||
|
||||
|
||||
class NotifyPopcornNotify(NotifyBase):
|
||||
"""
|
||||
@ -127,19 +124,10 @@ class NotifyPopcornNotify(NotifyBase):
|
||||
|
||||
for target in parse_list(targets):
|
||||
# Validate targets and drop bad ones:
|
||||
result = IS_PHONE_NO.match(target)
|
||||
result = is_phone_no(target)
|
||||
if result:
|
||||
# Further check our phone # for it's digit count
|
||||
result = ''.join(re.findall(r'\d+', result.group('phone')))
|
||||
if len(result) < 11 or len(result) > 14:
|
||||
self.logger.warning(
|
||||
'Dropped invalid phone # '
|
||||
'({}) specified.'.format(target),
|
||||
)
|
||||
continue
|
||||
|
||||
# store valid phone number
|
||||
self.targets.append(result)
|
||||
self.targets.append(result['full'])
|
||||
continue
|
||||
|
||||
result = is_email(target)
|
||||
|
@ -35,13 +35,11 @@ from itertools import chain
|
||||
from .NotifyBase import NotifyBase
|
||||
from ..URLBase import PrivacyMode
|
||||
from ..common import NotifyType
|
||||
from ..utils import is_phone_no
|
||||
from ..utils import parse_list
|
||||
from ..utils import validate_regex
|
||||
from ..AppriseLocale import gettext_lazy as _
|
||||
|
||||
# Some Phone Number Detection
|
||||
IS_PHONE_NO = re.compile(r'^\+?(?P<phone>[0-9\s)(+-]+)\s*$')
|
||||
|
||||
# Topic Detection
|
||||
# Summary: 256 Characters max, only alpha/numeric plus underscore (_) and
|
||||
# dash (-) additionally allowed.
|
||||
@ -198,24 +196,10 @@ class NotifySNS(NotifyBase):
|
||||
self.aws_auth_algorithm = 'AWS4-HMAC-SHA256'
|
||||
self.aws_auth_request = 'aws4_request'
|
||||
|
||||
# Get our targets
|
||||
targets = parse_list(targets)
|
||||
|
||||
# Validate targets and drop bad ones:
|
||||
for target in targets:
|
||||
result = IS_PHONE_NO.match(target)
|
||||
for target in parse_list(targets):
|
||||
result = is_phone_no(target)
|
||||
if result:
|
||||
# Further check our phone # for it's digit count
|
||||
# if it's less than 10, then we can assume it's
|
||||
# a poorly specified phone no and spit a warning
|
||||
result = ''.join(re.findall(r'\d+', result.group('phone')))
|
||||
if len(result) < 11 or len(result) > 14:
|
||||
self.logger.warning(
|
||||
'Dropped invalid phone # '
|
||||
'(%s) specified.' % target,
|
||||
)
|
||||
continue
|
||||
|
||||
# store valid phone number
|
||||
self.phone.append('+{}'.format(result))
|
||||
continue
|
||||
@ -231,12 +215,6 @@ class NotifySNS(NotifyBase):
|
||||
'(%s) specified.' % target,
|
||||
)
|
||||
|
||||
if len(self.phone) == 0 and len(self.topics) == 0:
|
||||
# We have a bot token and no target(s) to message
|
||||
msg = 'No AWS targets to notify.'
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
return
|
||||
|
||||
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
||||
@ -244,6 +222,11 @@ class NotifySNS(NotifyBase):
|
||||
wrapper to send_notification since we can alert more then one channel
|
||||
"""
|
||||
|
||||
if len(self.phone) == 0 and len(self.topics) == 0:
|
||||
# We have a bot token and no target(s) to message
|
||||
self.logger.warning('No AWS targets to notify.')
|
||||
return False
|
||||
|
||||
# Initiaize our error tracking
|
||||
error_count = 0
|
||||
|
||||
|
@ -33,7 +33,6 @@
|
||||
# from). Activated phone numbers can be found on your dashboard here:
|
||||
# - https://dashboard.sinch.com/numbers/your-numbers/numbers
|
||||
#
|
||||
import re
|
||||
import six
|
||||
import requests
|
||||
import json
|
||||
@ -41,15 +40,12 @@ import json
|
||||
from .NotifyBase import NotifyBase
|
||||
from ..URLBase import PrivacyMode
|
||||
from ..common import NotifyType
|
||||
from ..utils import parse_list
|
||||
from ..utils import is_phone_no
|
||||
from ..utils import parse_phone_no
|
||||
from ..utils import validate_regex
|
||||
from ..AppriseLocale import gettext_lazy as _
|
||||
|
||||
|
||||
# Some Phone Number Detection
|
||||
IS_PHONE_NO = re.compile(r'^\+?(?P<phone>[0-9\s)(+-]+)\s*$')
|
||||
|
||||
|
||||
class SinchRegion(object):
|
||||
"""
|
||||
Defines the Sinch Server Regions
|
||||
@ -194,15 +190,6 @@ class NotifySinch(NotifyBase):
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
# The Source Phone # and/or short-code
|
||||
self.source = source
|
||||
|
||||
if not IS_PHONE_NO.match(self.source):
|
||||
msg = 'The Account (From) Phone # or Short-code specified ' \
|
||||
'({}) is invalid.'.format(source)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
# Setup our region
|
||||
self.region = self.template_args['region']['default'] \
|
||||
if not isinstance(region, six.string_types) else region.lower()
|
||||
@ -211,8 +198,16 @@ class NotifySinch(NotifyBase):
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
# The Source Phone # and/or short-code
|
||||
result = is_phone_no(source, min_len=5)
|
||||
if not result:
|
||||
msg = 'The Account (From) Phone # or Short-code specified ' \
|
||||
'({}) is invalid.'.format(source)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
# Tidy source
|
||||
self.source = re.sub(r'[^\d]+', '', self.source)
|
||||
self.source = result['full']
|
||||
|
||||
if len(self.source) < 11 or len(self.source) > 14:
|
||||
# A short code is a special 5 or 6 digit telephone number
|
||||
@ -233,15 +228,10 @@ class NotifySinch(NotifyBase):
|
||||
# Parse our targets
|
||||
self.targets = list()
|
||||
|
||||
for target in parse_list(targets):
|
||||
# Validate targets and drop bad ones:
|
||||
result = IS_PHONE_NO.match(target)
|
||||
if result:
|
||||
# Further check our phone # for it's digit count
|
||||
# if it's less than 10, then we can assume it's
|
||||
# a poorly specified phone no and spit a warning
|
||||
result = ''.join(re.findall(r'\d+', result.group('phone')))
|
||||
if len(result) < 11 or len(result) > 14:
|
||||
for target in parse_phone_no(targets):
|
||||
# Parse each phone number we found
|
||||
result = is_phone_no(target)
|
||||
if not result:
|
||||
self.logger.warning(
|
||||
'Dropped invalid phone # '
|
||||
'({}) specified.'.format(target),
|
||||
@ -249,21 +239,7 @@ class NotifySinch(NotifyBase):
|
||||
continue
|
||||
|
||||
# store valid phone number
|
||||
self.targets.append('+{}'.format(result))
|
||||
continue
|
||||
|
||||
self.logger.warning(
|
||||
'Dropped invalid phone # '
|
||||
'({}) specified.'.format(target),
|
||||
)
|
||||
|
||||
if not self.targets:
|
||||
if len(self.source) in (5, 6):
|
||||
# raise a warning since we're a short-code. We need
|
||||
# a number to message
|
||||
msg = 'There are no valid Sinch targets to notify.'
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
self.targets.append('+{}'.format(result['full']))
|
||||
|
||||
return
|
||||
|
||||
@ -272,6 +248,14 @@ class NotifySinch(NotifyBase):
|
||||
Perform Sinch Notification
|
||||
"""
|
||||
|
||||
if not self.targets:
|
||||
if len(self.source) in (5, 6):
|
||||
# Generate a warning since we're a short-code. We need
|
||||
# a number to message at minimum
|
||||
self.logger.warning(
|
||||
'There are no valid Sinch targets to notify.')
|
||||
return False
|
||||
|
||||
# error tracking (used for function return)
|
||||
has_error = False
|
||||
|
||||
@ -459,6 +443,7 @@ class NotifySinch(NotifyBase):
|
||||
if 'from' in results['qsd'] and len(results['qsd']['from']):
|
||||
results['source'] = \
|
||||
NotifySinch.unquote(results['qsd']['from'])
|
||||
|
||||
if 'source' in results['qsd'] and len(results['qsd']['source']):
|
||||
results['source'] = \
|
||||
NotifySinch.unquote(results['qsd']['source'])
|
||||
@ -472,6 +457,6 @@ class NotifySinch(NotifyBase):
|
||||
# The 'to' makes it easier to use yaml configuration
|
||||
if 'to' in results['qsd'] and len(results['qsd']['to']):
|
||||
results['targets'] += \
|
||||
NotifySinch.parse_list(results['qsd']['to'])
|
||||
NotifySinch.parse_phone_no(results['qsd']['to'])
|
||||
|
||||
return results
|
||||
|
@ -40,22 +40,18 @@
|
||||
# or consider purchasing a short-code from here:
|
||||
# https://www.twilio.com/docs/glossary/what-is-a-short-code
|
||||
#
|
||||
import re
|
||||
import requests
|
||||
from json import loads
|
||||
|
||||
from .NotifyBase import NotifyBase
|
||||
from ..URLBase import PrivacyMode
|
||||
from ..common import NotifyType
|
||||
from ..utils import parse_list
|
||||
from ..utils import is_phone_no
|
||||
from ..utils import parse_phone_no
|
||||
from ..utils import validate_regex
|
||||
from ..AppriseLocale import gettext_lazy as _
|
||||
|
||||
|
||||
# Some Phone Number Detection
|
||||
IS_PHONE_NO = re.compile(r'^\+?(?P<phone>[0-9\s)(+-]+)\s*$')
|
||||
|
||||
|
||||
class NotifyTwilio(NotifyBase):
|
||||
"""
|
||||
A wrapper for Twilio Notifications
|
||||
@ -181,17 +177,15 @@ class NotifyTwilio(NotifyBase):
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
# The Source Phone # and/or short-code
|
||||
self.source = source
|
||||
|
||||
if not IS_PHONE_NO.match(self.source):
|
||||
result = is_phone_no(source, min_len=5)
|
||||
if not result:
|
||||
msg = 'The Account (From) Phone # or Short-code specified ' \
|
||||
'({}) is invalid.'.format(source)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
# Tidy source
|
||||
self.source = re.sub(r'[^\d]+', '', self.source)
|
||||
# Store The Source Phone # and/or short-code
|
||||
self.source = result['full']
|
||||
|
||||
if len(self.source) < 11 or len(self.source) > 14:
|
||||
# https://www.twilio.com/docs/glossary/what-is-a-short-code
|
||||
@ -213,15 +207,10 @@ class NotifyTwilio(NotifyBase):
|
||||
# Parse our targets
|
||||
self.targets = list()
|
||||
|
||||
for target in parse_list(targets):
|
||||
for target in parse_phone_no(targets):
|
||||
# Validate targets and drop bad ones:
|
||||
result = IS_PHONE_NO.match(target)
|
||||
if result:
|
||||
# Further check our phone # for it's digit count
|
||||
# if it's less than 10, then we can assume it's
|
||||
# a poorly specified phone no and spit a warning
|
||||
result = ''.join(re.findall(r'\d+', result.group('phone')))
|
||||
if len(result) < 11 or len(result) > 14:
|
||||
result = is_phone_no(target)
|
||||
if not result:
|
||||
self.logger.warning(
|
||||
'Dropped invalid phone # '
|
||||
'({}) specified.'.format(target),
|
||||
@ -230,20 +219,6 @@ class NotifyTwilio(NotifyBase):
|
||||
|
||||
# store valid phone number
|
||||
self.targets.append('+{}'.format(result))
|
||||
continue
|
||||
|
||||
self.logger.warning(
|
||||
'Dropped invalid phone # '
|
||||
'({}) specified.'.format(target),
|
||||
)
|
||||
|
||||
if not self.targets:
|
||||
if len(self.source) in (5, 6):
|
||||
# raise a warning since we're a short-code. We need
|
||||
# a number to message
|
||||
msg = 'There are no valid Twilio targets to notify.'
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
return
|
||||
|
||||
@ -252,6 +227,14 @@ class NotifyTwilio(NotifyBase):
|
||||
Perform Twilio Notification
|
||||
"""
|
||||
|
||||
if not self.targets:
|
||||
if len(self.source) in (5, 6):
|
||||
# Generate a warning since we're a short-code. We need
|
||||
# a number to message at minimum
|
||||
self.logger.warning(
|
||||
'There are no valid Twilio targets to notify.')
|
||||
return False
|
||||
|
||||
# error tracking (used for function return)
|
||||
has_error = False
|
||||
|
||||
@ -431,6 +414,6 @@ class NotifyTwilio(NotifyBase):
|
||||
# The 'to' makes it easier to use yaml configuration
|
||||
if 'to' in results['qsd'] and len(results['qsd']['to']):
|
||||
results['targets'] += \
|
||||
NotifyTwilio.parse_list(results['qsd']['to'])
|
||||
NotifyTwilio.parse_phone_no(results['qsd']['to'])
|
||||
|
||||
return results
|
||||
|
142
apprise/utils.py
142
apprise/utils.py
@ -115,7 +115,7 @@ GET_SCHEMA_RE = re.compile(r'\s*(?P<schema>[a-z0-9]{2,9})://.*$', re.I)
|
||||
# - user@example.com
|
||||
# - label+user@example.com
|
||||
GET_EMAIL_RE = re.compile(
|
||||
r'((?P<name>[^:<]+)?[:<\s]+)?'
|
||||
r'(([\s"\']+)?(?P<name>[^:<"\']+)?[:<\s"\']+)?'
|
||||
r'(?P<full_email>((?P<label>[^+]+)\+)?'
|
||||
r'(?P<email>(?P<userid>[a-z0-9$%=_~-]+'
|
||||
r'(?:\.[a-z0-9$%+=_~-]+)'
|
||||
@ -125,8 +125,13 @@ GET_EMAIL_RE = re.compile(
|
||||
r'[a-z0-9][a-z0-9_-]{5,})))'
|
||||
r'\s*>?', re.IGNORECASE)
|
||||
|
||||
# Regular expression used to extract a phone number
|
||||
GET_PHONE_NO_RE = re.compile(r'^\+?(?P<phone>[0-9\s)(+-]+)\s*$')
|
||||
# A simple verification check to make sure the content specified
|
||||
# rougly conforms to a phone number before we parse it further
|
||||
IS_PHONE_NO = re.compile(r'^\+?(?P<phone>[0-9\s)(+-]+)\s*$')
|
||||
|
||||
# Regular expression used to destinguish between multiple phone numbers
|
||||
PHONE_NO_DETECTION_RE = re.compile(
|
||||
r'\s*([+(\s]*[0-9][0-9()\s-]+[0-9])(?=$|[\s,+(]+[0-9])', re.I)
|
||||
|
||||
# Regular expression used to destinguish between multiple URLs
|
||||
URL_DETECTION_RE = re.compile(
|
||||
@ -273,6 +278,98 @@ def is_uuid(uuid):
|
||||
return True if match else False
|
||||
|
||||
|
||||
def is_phone_no(phone, min_len=11):
|
||||
"""Determine if the specified entry is a phone number
|
||||
|
||||
Args:
|
||||
phone (str): The string you want to check.
|
||||
min_len (int): Defines the smallest expected length of the phone
|
||||
before it's to be considered invalid. By default
|
||||
the phone number can't be any larger then 14
|
||||
|
||||
Returns:
|
||||
bool: Returns False if the address specified is not a phone number
|
||||
and a dictionary of the parsed email if it is as:
|
||||
{
|
||||
'country': '1',
|
||||
'area': '800',
|
||||
'line': '1234567',
|
||||
'full': '18001234567',
|
||||
'pretty': '+1 800-123-4567',
|
||||
}
|
||||
|
||||
Non conventional numbers such as 411 would look like provided that
|
||||
`min_len` is set to at least a 3:
|
||||
{
|
||||
'country': '',
|
||||
'area': '',
|
||||
'line': '411',
|
||||
'full': '411',
|
||||
'pretty': '411',
|
||||
}
|
||||
|
||||
"""
|
||||
|
||||
try:
|
||||
if not IS_PHONE_NO.match(phone):
|
||||
# not parseable content as it does not even conform closely to a
|
||||
# phone number)
|
||||
return False
|
||||
|
||||
except TypeError:
|
||||
return False
|
||||
|
||||
# Tidy phone number up first
|
||||
phone = re.sub(r'[^\d]+', '', phone)
|
||||
if len(phone) > 14 or len(phone) < min_len:
|
||||
# Invalid phone number
|
||||
return False
|
||||
|
||||
# Full phone number without any markup is as is now
|
||||
full = phone
|
||||
|
||||
# Break apart our phone number
|
||||
line = phone[-7:]
|
||||
phone = phone[:len(phone) - 7] if len(phone) > 7 else ''
|
||||
|
||||
# the area code (if present)
|
||||
area = phone[-3:] if phone else ''
|
||||
|
||||
# The country code is the leftovers
|
||||
country = phone[:len(phone) - 3] if len(phone) > 3 else ''
|
||||
|
||||
# Prepare a nicely (consistently) formatted phone number
|
||||
pretty = ''
|
||||
|
||||
if country:
|
||||
# The leftover is the country code
|
||||
pretty += '+{} '.format(country)
|
||||
|
||||
if area:
|
||||
pretty += '{}-'.format(area)
|
||||
|
||||
if len(line) >= 7:
|
||||
pretty += '{}-{}'.format(line[:3], line[3:])
|
||||
|
||||
else:
|
||||
pretty += line
|
||||
|
||||
return {
|
||||
# The line code (last 7 digits)
|
||||
'line': line,
|
||||
# Area code
|
||||
'area': area,
|
||||
# The country code (if identified)
|
||||
'country': country,
|
||||
|
||||
# A nicely formatted phone no
|
||||
'pretty': pretty,
|
||||
|
||||
# All digits in-line
|
||||
'full': full,
|
||||
}
|
||||
|
||||
|
||||
def is_email(address):
|
||||
"""Determine if the specified entry is an email address
|
||||
|
||||
@ -633,9 +730,46 @@ def parse_bool(arg, default=False):
|
||||
return bool(arg)
|
||||
|
||||
|
||||
def parse_phone_no(*args, **kwargs):
|
||||
"""
|
||||
Takes a string containing phone numbers separated by comma's and/or spaces
|
||||
and returns a list.
|
||||
"""
|
||||
|
||||
# for Python 2.7 support, store_unparsable is not in the url above
|
||||
# as just parse_emails(*args, store_unparseable=True) since it is
|
||||
# an invalid syntax. This is the workaround to be backards compatible:
|
||||
store_unparseable = kwargs.get('store_unparseable', True)
|
||||
|
||||
result = []
|
||||
for arg in args:
|
||||
if isinstance(arg, six.string_types) and arg:
|
||||
_result = PHONE_NO_DETECTION_RE.findall(arg)
|
||||
if _result:
|
||||
result += _result
|
||||
|
||||
elif not _result and store_unparseable:
|
||||
# we had content passed into us that was lost because it was
|
||||
# so poorly formatted that it didn't even come close to
|
||||
# meeting the regular expression we defined. We intentially
|
||||
# keep it as part of our result set so that parsing done
|
||||
# at a higher level can at least report this to the end user
|
||||
# and hopefully give them some indication as to what they
|
||||
# may have done wrong.
|
||||
result += \
|
||||
[x for x in filter(bool, re.split(STRING_DELIMITERS, arg))]
|
||||
|
||||
elif isinstance(arg, (set, list, tuple)):
|
||||
# Use recursion to handle the list of phone numbers
|
||||
result += parse_phone_no(
|
||||
*arg, store_unparseable=store_unparseable)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def parse_emails(*args, **kwargs):
|
||||
"""
|
||||
Takes a string containing URLs separated by comma's and/or spaces and
|
||||
Takes a string containing emails separated by comma's and/or spaces and
|
||||
returns a list.
|
||||
"""
|
||||
|
||||
|
@ -207,6 +207,7 @@ def test_notify_base():
|
||||
|
||||
# Test invalid data
|
||||
assert NotifyBase.parse_list(None) == []
|
||||
assert NotifyBase.parse_list(object()) == []
|
||||
assert NotifyBase.parse_list(42) == []
|
||||
|
||||
result = NotifyBase.parse_list(
|
||||
@ -234,6 +235,26 @@ def test_notify_base():
|
||||
assert '//' in result
|
||||
assert '///' in result
|
||||
|
||||
# Phone number parsing
|
||||
assert NotifyBase.parse_phone_no(None) == []
|
||||
assert NotifyBase.parse_phone_no(object()) == []
|
||||
assert NotifyBase.parse_phone_no(42) == []
|
||||
|
||||
result = NotifyBase.parse_phone_no(
|
||||
'+1-800-123-1234,(800) 123-4567', unquote=False)
|
||||
assert isinstance(result, list) is True
|
||||
assert len(result) == 2
|
||||
assert '+1-800-123-1234' in result
|
||||
assert '(800) 123-4567' in result
|
||||
|
||||
# %2B == +
|
||||
result = NotifyBase.parse_phone_no(
|
||||
'%2B1-800-123-1234,%2B1%20800%20123%204567', unquote=True)
|
||||
assert isinstance(result, list) is True
|
||||
assert len(result) == 2
|
||||
assert '+1-800-123-1234' in result
|
||||
assert '+1 800 123 4567' in result
|
||||
|
||||
# Give nothing, get nothing
|
||||
assert NotifyBase.escape_html("") == ""
|
||||
assert NotifyBase.escape_html(None) == ""
|
||||
|
@ -196,7 +196,10 @@ TEST_URLS = (
|
||||
}),
|
||||
('d7sms://user:pass@{}/{}/{}'.format('1' * 10, '2' * 15, 'a' * 13), {
|
||||
# No valid targets to notify
|
||||
'instance': TypeError,
|
||||
'instance': plugins.NotifyD7Networks,
|
||||
# Since there are no targets specified we expect a False return on
|
||||
# send()
|
||||
'notify_response': False,
|
||||
}),
|
||||
('d7sms://user:pass@{}?batch=yes'.format('3' * 14), {
|
||||
# valid number
|
||||
@ -1297,8 +1300,11 @@ TEST_URLS = (
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('kavenegar://{}/{}/{}'.format('1' * 10, '2' * 15, 'a' * 13), {
|
||||
# No valid targets to notify
|
||||
'instance': TypeError,
|
||||
# valid api key and valid authentication
|
||||
'instance': plugins.NotifyKavenegar,
|
||||
# Since there are no targets specified we expect a False return on
|
||||
# send()
|
||||
'notify_response': False,
|
||||
}),
|
||||
('kavenegar://{}/{}'.format('a' * 24, '3' * 14), {
|
||||
# valid api key and valid number
|
||||
@ -4034,7 +4040,9 @@ TEST_URLS = (
|
||||
('sinch://{}:{}@{}'.format('a' * 32, 'b' * 32, '3' * 5), {
|
||||
# using short-code (5 characters) without a target
|
||||
# We can still instantiate ourselves with a valid short code
|
||||
'instance': TypeError,
|
||||
'instance': plugins.NotifySinch,
|
||||
# Expected notify() response because we have no one to notify
|
||||
'notify_response': False,
|
||||
}),
|
||||
('sinch://{}:{}@{}'.format('a' * 32, 'b' * 32, '3' * 9), {
|
||||
# spi and token provided and from but invalid from no
|
||||
@ -4942,7 +4950,10 @@ TEST_URLS = (
|
||||
('twilio://AC{}:{}@{}'.format('a' * 32, 'b' * 32, '3' * 5), {
|
||||
# using short-code (5 characters) without a target
|
||||
# We can still instantiate ourselves with a valid short code
|
||||
'instance': TypeError,
|
||||
'instance': plugins.NotifyTwilio,
|
||||
# Since there are no targets specified we expect a False return on
|
||||
# send()
|
||||
'notify_response': False,
|
||||
}),
|
||||
('twilio://AC{}:{}@{}'.format('a' * 32, 'b' * 32, '3' * 9), {
|
||||
# sid and token provided and from but invalid from no
|
||||
@ -5211,16 +5222,25 @@ TEST_URLS = (
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('msg91://{}'.format('a' * 23), {
|
||||
# No number specified
|
||||
'instance': TypeError,
|
||||
# valid AuthKey
|
||||
'instance': plugins.NotifyMSG91,
|
||||
# Since there are no targets specified we expect a False return on
|
||||
# send()
|
||||
'notify_response': False,
|
||||
}),
|
||||
('msg91://{}/123'.format('a' * 23), {
|
||||
# invalid phone number
|
||||
'instance': TypeError,
|
||||
'instance': plugins.NotifyMSG91,
|
||||
# Since there are no targets specified we expect a False return on
|
||||
# send()
|
||||
'notify_response': False,
|
||||
}),
|
||||
('msg91://{}/abcd'.format('a' * 23), {
|
||||
# No number to notify
|
||||
'instance': TypeError,
|
||||
'instance': plugins.NotifyMSG91,
|
||||
# Since there are no targets specified we expect a False return on
|
||||
# send()
|
||||
'notify_response': False,
|
||||
}),
|
||||
('msg91://{}/15551232000/?country=invalid'.format('a' * 23), {
|
||||
# invalid country
|
||||
@ -5293,12 +5313,18 @@ TEST_URLS = (
|
||||
'privacy_url': 'msgbird://a...a/15551232000',
|
||||
}),
|
||||
('msgbird://{}/15551232000/abcd'.format('a' * 25), {
|
||||
# invalid target phone number; we have no one to notify
|
||||
'instance': TypeError,
|
||||
# valid credentials
|
||||
'instance': plugins.NotifyMessageBird,
|
||||
# Since there are no targets specified we expect a False return on
|
||||
# send()
|
||||
'notify_response': False,
|
||||
}),
|
||||
('msgbird://{}/15551232000/123'.format('a' * 25), {
|
||||
# invalid target phone number
|
||||
'instance': TypeError,
|
||||
# valid credentials
|
||||
'instance': plugins.NotifyMessageBird,
|
||||
# Since there are no targets specified we expect a False return on
|
||||
# send()
|
||||
'notify_response': False,
|
||||
}),
|
||||
('msgbird://{}/?from=15551233000&to=15551232000'.format('a' * 25), {
|
||||
# reference to to= and from=
|
||||
|
@ -39,7 +39,10 @@ TEST_ACCESS_KEY_SECRET = 'bu1dHSdO22pfaaVy/wmNsdljF4C07D3bndi9PQJ9'
|
||||
TEST_REGION = 'us-east-2'
|
||||
|
||||
|
||||
def test_object_initialization():
|
||||
# We initialize a post object just incase a test fails below
|
||||
# we don't want it sending any notifications upstream
|
||||
@mock.patch('requests.post')
|
||||
def test_object_initialization(mock_post):
|
||||
"""
|
||||
API: NotifySNS Plugin() initialization
|
||||
|
||||
@ -73,44 +76,41 @@ def test_object_initialization():
|
||||
targets='+1800555999',
|
||||
)
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
# No recipients
|
||||
plugins.NotifySNS(
|
||||
obj = plugins.NotifySNS(
|
||||
access_key_id=TEST_ACCESS_KEY_ID,
|
||||
secret_access_key=TEST_ACCESS_KEY_SECRET,
|
||||
region_name=TEST_REGION,
|
||||
targets=None,
|
||||
)
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
# No recipients - garbage recipients object
|
||||
plugins.NotifySNS(
|
||||
access_key_id=TEST_ACCESS_KEY_ID,
|
||||
secret_access_key=TEST_ACCESS_KEY_SECRET,
|
||||
region_name=TEST_REGION,
|
||||
targets=object(),
|
||||
)
|
||||
# The object initializes properly but would not be able to send anything
|
||||
assert obj.notify(body='test', title='test') is False
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
# The phone number is invalid, and without it, there is nothing
|
||||
# to notify
|
||||
plugins.NotifySNS(
|
||||
obj = plugins.NotifySNS(
|
||||
access_key_id=TEST_ACCESS_KEY_ID,
|
||||
secret_access_key=TEST_ACCESS_KEY_SECRET,
|
||||
region_name=TEST_REGION,
|
||||
targets='+1809',
|
||||
)
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
# The object initializes properly but would not be able to send anything
|
||||
assert obj.notify(body='test', title='test') is False
|
||||
|
||||
# The phone number is invalid, and without it, there is nothing
|
||||
# to notify; we
|
||||
plugins.NotifySNS(
|
||||
obj = plugins.NotifySNS(
|
||||
access_key_id=TEST_ACCESS_KEY_ID,
|
||||
secret_access_key=TEST_ACCESS_KEY_SECRET,
|
||||
region_name=TEST_REGION,
|
||||
targets='#(invalid-topic-because-of-the-brackets)',
|
||||
)
|
||||
|
||||
# The object initializes properly but would not be able to send anything
|
||||
assert obj.notify(body='test', title='test') is False
|
||||
|
||||
|
||||
def test_url_parsing():
|
||||
"""
|
||||
@ -170,16 +170,17 @@ def test_object_parsing():
|
||||
assert a.add('sns://nosecret') is False
|
||||
assert a.add('sns://nosecret/noregion/') is False
|
||||
|
||||
# This is valid but without valid recipients, the URL is actually useless
|
||||
assert a.add('sns://norecipient/norecipient/us-west-2') is False
|
||||
assert len(a) == 0
|
||||
# This is valid but without valid recipients; while it's still a valid URL
|
||||
# it won't do much when the user goes to send a notification
|
||||
assert a.add('sns://norecipient/norecipient/us-west-2') is True
|
||||
assert len(a) == 1
|
||||
|
||||
# Parse a good one
|
||||
assert a.add('sns://oh/yeah/us-west-2/abcdtopic/+12223334444') is True
|
||||
assert len(a) == 1
|
||||
assert len(a) == 2
|
||||
|
||||
assert a.add('sns://oh/yeah/us-west-2/12223334444') is True
|
||||
assert len(a) == 2
|
||||
assert len(a) == 3
|
||||
|
||||
|
||||
def test_aws_response_handling():
|
||||
|
@ -694,6 +694,15 @@ def test_is_email():
|
||||
assert 'spichai' == results['user']
|
||||
assert 'ceo' == results['label']
|
||||
|
||||
# Support Quotes
|
||||
results = utils.is_email('"Chris Hemsworth" <ch@test.com>')
|
||||
assert 'Chris Hemsworth' == results['name']
|
||||
assert 'ch@test.com' == results['email']
|
||||
assert 'ch@test.com' == results['full_email']
|
||||
assert 'test.com' == results['domain']
|
||||
assert 'ch' == results['user']
|
||||
assert '' == results['label']
|
||||
|
||||
# An email without name, but contains delimiters
|
||||
results = utils.is_email(' <spichai@gmail.com>')
|
||||
assert '' == results['name']
|
||||
@ -731,6 +740,198 @@ def test_is_email():
|
||||
assert utils.is_email("Name <bademail>") is False
|
||||
|
||||
|
||||
def test_is_phone_no():
|
||||
"""
|
||||
API: is_phone_no() function
|
||||
|
||||
"""
|
||||
# Invalid numbers
|
||||
assert utils.is_phone_no(None) is False
|
||||
assert utils.is_phone_no(42) is False
|
||||
assert utils.is_phone_no(object) is False
|
||||
assert utils.is_phone_no('') is False
|
||||
assert utils.is_phone_no('1') is False
|
||||
assert utils.is_phone_no('12') is False
|
||||
assert utils.is_phone_no('abc') is False
|
||||
assert utils.is_phone_no('+()') is False
|
||||
assert utils.is_phone_no('+') is False
|
||||
assert utils.is_phone_no(None) is False
|
||||
assert utils.is_phone_no(42) is False
|
||||
assert utils.is_phone_no(object, min_len=0) is False
|
||||
assert utils.is_phone_no('', min_len=1) is False
|
||||
assert utils.is_phone_no('abc', min_len=0) is False
|
||||
assert utils.is_phone_no('', min_len=0) is False
|
||||
|
||||
# Ambigious, but will document it here in this test as such
|
||||
results = utils.is_phone_no('+((()))--+', min_len=0)
|
||||
assert '' == results['country']
|
||||
assert '' == results['area']
|
||||
assert '' == results['line']
|
||||
assert '' == results['pretty']
|
||||
assert '' == results['full']
|
||||
|
||||
# Valid phone numbers
|
||||
assert utils.is_phone_no('+(0)') is False
|
||||
results = utils.is_phone_no('+(0)', min_len=1)
|
||||
assert '' == results['country']
|
||||
assert '' == results['area']
|
||||
assert '0' == results['line']
|
||||
assert '0' == results['pretty']
|
||||
assert '0' == results['full']
|
||||
|
||||
assert utils.is_phone_no('1') is False
|
||||
results = utils.is_phone_no('1', min_len=1)
|
||||
assert '' == results['country']
|
||||
assert '' == results['area']
|
||||
assert '1' == results['line']
|
||||
assert '1' == results['pretty']
|
||||
assert '1' == results['full']
|
||||
|
||||
assert utils.is_phone_no('12') is False
|
||||
results = utils.is_phone_no('12', min_len=2)
|
||||
assert '' == results['country']
|
||||
assert '' == results['area']
|
||||
assert '12' == results['line']
|
||||
assert '12' == results['pretty']
|
||||
assert '12' == results['full']
|
||||
|
||||
assert utils.is_phone_no('911') is False
|
||||
results = utils.is_phone_no('911', min_len=3)
|
||||
assert isinstance(results, dict)
|
||||
assert '' == results['country']
|
||||
assert '' == results['area']
|
||||
assert '911' == results['line']
|
||||
assert '911' == results['pretty']
|
||||
assert '911' == results['full']
|
||||
|
||||
assert utils.is_phone_no('1234') is False
|
||||
results = utils.is_phone_no('1234', min_len=4)
|
||||
assert isinstance(results, dict)
|
||||
assert '' == results['country']
|
||||
assert '' == results['area']
|
||||
assert '1234' == results['line']
|
||||
assert '1234' == results['pretty']
|
||||
assert '1234' == results['full']
|
||||
|
||||
assert utils.is_phone_no('12345') is False
|
||||
results = utils.is_phone_no('12345', min_len=5)
|
||||
assert isinstance(results, dict)
|
||||
assert '' == results['country']
|
||||
assert '' == results['area']
|
||||
assert '12345' == results['line']
|
||||
assert '12345' == results['pretty']
|
||||
assert '12345' == results['full']
|
||||
|
||||
assert utils.is_phone_no('123456') is False
|
||||
results = utils.is_phone_no('123456', min_len=6)
|
||||
assert isinstance(results, dict)
|
||||
assert '' == results['country']
|
||||
assert '' == results['area']
|
||||
assert '123456' == results['line']
|
||||
assert '123456' == results['pretty']
|
||||
assert '123456' == results['full']
|
||||
|
||||
# at 7 digits, the format hyphenates in the `pretty` section
|
||||
assert utils.is_phone_no('1234567') is False
|
||||
results = utils.is_phone_no('1234567', min_len=7)
|
||||
assert isinstance(results, dict)
|
||||
assert '' == results['country']
|
||||
assert '' == results['area']
|
||||
assert '1234567' == results['line']
|
||||
assert '123-4567' == results['pretty']
|
||||
assert '1234567' == results['full']
|
||||
|
||||
results = utils.is_phone_no('1(800) 123-4567')
|
||||
assert isinstance(results, dict)
|
||||
assert '1' == results['country']
|
||||
assert '800' == results['area']
|
||||
assert '1234567' == results['line']
|
||||
assert '+1 800-123-4567' == results['pretty']
|
||||
assert '18001234567' == results['full']
|
||||
|
||||
|
||||
def test_parse_phone_no():
|
||||
"""utils: parse_phone_no() testing """
|
||||
# A simple single array entry (As str)
|
||||
results = utils.parse_phone_no('')
|
||||
assert isinstance(results, list)
|
||||
assert len(results) == 0
|
||||
|
||||
# just delimeters
|
||||
results = utils.parse_phone_no(', ,, , ,,, ')
|
||||
assert isinstance(results, list)
|
||||
assert len(results) == 0
|
||||
|
||||
results = utils.parse_phone_no(',')
|
||||
assert isinstance(results, list)
|
||||
assert len(results) == 0
|
||||
|
||||
results = utils.parse_phone_no(None)
|
||||
assert isinstance(results, list)
|
||||
assert len(results) == 0
|
||||
|
||||
results = utils.parse_phone_no(42)
|
||||
assert isinstance(results, list)
|
||||
assert len(results) == 0
|
||||
|
||||
results = utils.parse_phone_no('this is not a parseable phoneno at all')
|
||||
assert isinstance(results, list)
|
||||
assert len(results) == 8
|
||||
# Now we do it again with the store_unparsable flag set to False
|
||||
results = utils.parse_phone_no(
|
||||
'this is not a parseable email at all', store_unparseable=False)
|
||||
assert isinstance(results, list)
|
||||
assert len(results) == 0
|
||||
|
||||
results = utils.parse_phone_no('+', store_unparseable=False)
|
||||
assert isinstance(results, list)
|
||||
assert len(results) == 0
|
||||
|
||||
results = utils.parse_phone_no('(', store_unparseable=False)
|
||||
assert isinstance(results, list)
|
||||
assert len(results) == 0
|
||||
|
||||
# Number is too short
|
||||
results = utils.parse_phone_no('0', store_unparseable=False)
|
||||
assert isinstance(results, list)
|
||||
assert len(results) == 0
|
||||
|
||||
results = utils.parse_phone_no('12', store_unparseable=False)
|
||||
assert isinstance(results, list)
|
||||
assert len(results) == 0
|
||||
|
||||
# Now test valid phone numbers
|
||||
results = utils.parse_phone_no('+1 (124) 245 2345')
|
||||
assert isinstance(results, list)
|
||||
assert len(results) == 1
|
||||
assert '+1 (124) 245 2345' in results
|
||||
|
||||
results = utils.parse_phone_no('911', store_unparseable=False)
|
||||
assert isinstance(results, list)
|
||||
assert len(results) == 1
|
||||
assert '911' in results
|
||||
|
||||
results = utils.parse_phone_no(
|
||||
'911, 123-123-1234', store_unparseable=False)
|
||||
assert isinstance(results, list)
|
||||
assert len(results) == 2
|
||||
assert '911' in results
|
||||
assert '123-123-1234' in results
|
||||
|
||||
# Space variations
|
||||
results = utils.parse_phone_no(' 911 , +1 (123) 123-1234')
|
||||
assert isinstance(results, list)
|
||||
assert len(results) == 2
|
||||
assert '911' in results
|
||||
assert '+1 (123) 123-1234' in results
|
||||
|
||||
results = utils.parse_phone_no(' 911 , + 1 ( 123 ) 123-1234')
|
||||
assert isinstance(results, list)
|
||||
assert len(results) == 2
|
||||
assert '911' in results
|
||||
assert '+ 1 ( 123 ) 123-1234' in results
|
||||
|
||||
|
||||
def test_parse_emails():
|
||||
"""utils: parse_emails() testing """
|
||||
# A simple single array entry (As str)
|
||||
@ -764,7 +965,7 @@ def test_parse_emails():
|
||||
assert isinstance(results, list)
|
||||
assert len(results) == 0
|
||||
|
||||
# Now test valid URLs
|
||||
# Now test valid emails
|
||||
results = utils.parse_emails('user@example.com')
|
||||
assert isinstance(results, list)
|
||||
assert len(results) == 1
|
||||
|
Loading…
Reference in New Issue
Block a user