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_url
|
||||||
from .utils import parse_bool
|
from .utils import parse_bool
|
||||||
from .utils import parse_list
|
from .utils import parse_list
|
||||||
|
from .utils import parse_phone_no
|
||||||
|
|
||||||
# Used to break a path list into parts
|
# Used to break a path list into parts
|
||||||
PATHSPLIT_LIST_DELIM = re.compile(r'[ \t\r\n,\\/]+')
|
PATHSPLIT_LIST_DELIM = re.compile(r'[ \t\r\n,\\/]+')
|
||||||
@ -560,6 +561,39 @@ class URLBase(object):
|
|||||||
|
|
||||||
return content
|
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
|
@property
|
||||||
def app_id(self):
|
def app_id(self):
|
||||||
return self.asset.app_id if self.asset.app_id else ''
|
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:
|
# The API reference used to build this plugin was documented here:
|
||||||
# https://developers.clicksend.com/docs/rest/v3/
|
# https://developers.clicksend.com/docs/rest/v3/
|
||||||
#
|
#
|
||||||
import re
|
|
||||||
import requests
|
import requests
|
||||||
from json import dumps
|
from json import dumps
|
||||||
from base64 import b64encode
|
from base64 import b64encode
|
||||||
@ -44,7 +43,8 @@ from base64 import b64encode
|
|||||||
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 parse_list
|
from ..utils import is_phone_no
|
||||||
|
from ..utils import parse_phone_no
|
||||||
from ..utils import parse_bool
|
from ..utils import parse_bool
|
||||||
from ..AppriseLocale import gettext_lazy as _
|
from ..AppriseLocale import gettext_lazy as _
|
||||||
|
|
||||||
@ -53,12 +53,6 @@ CLICKSEND_HTTP_ERROR_MAP = {
|
|||||||
401: 'Unauthorized - Invalid Token.',
|
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):
|
class NotifyClickSend(NotifyBase):
|
||||||
"""
|
"""
|
||||||
@ -151,13 +145,10 @@ class NotifyClickSend(NotifyBase):
|
|||||||
self.logger.warning(msg)
|
self.logger.warning(msg)
|
||||||
raise TypeError(msg)
|
raise TypeError(msg)
|
||||||
|
|
||||||
for target in parse_list(targets):
|
for target in parse_phone_no(targets):
|
||||||
# Validate targets and drop bad ones:
|
# Validate targets and drop bad ones:
|
||||||
result = IS_PHONE_NO.match(target)
|
result = is_phone_no(target)
|
||||||
if result:
|
if not 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(
|
self.logger.warning(
|
||||||
'Dropped invalid phone # '
|
'Dropped invalid phone # '
|
||||||
'({}) specified.'.format(target),
|
'({}) specified.'.format(target),
|
||||||
@ -165,12 +156,7 @@ class NotifyClickSend(NotifyBase):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
# store valid phone number
|
# store valid phone number
|
||||||
self.targets.append(result)
|
self.targets.append(result['full'])
|
||||||
continue
|
|
||||||
|
|
||||||
self.logger.warning(
|
|
||||||
'Dropped invalid phone # '
|
|
||||||
'({}) specified.'.format(target))
|
|
||||||
|
|
||||||
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
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
|
# Support the 'to' variable so that we can support rooms this way too
|
||||||
# The 'to' makes it easier to use yaml configuration
|
# The 'to' makes it easier to use yaml configuration
|
||||||
if 'to' in results['qsd'] and len(results['qsd']['to']):
|
if 'to' in results['qsd'] and len(results['qsd']['to']):
|
||||||
results['targets'] += [x for x in filter(
|
results['targets'] += \
|
||||||
bool, TARGET_LIST_DELIM.split(
|
NotifyClickSend.parse_phone_no(results['qsd']['to'])
|
||||||
NotifyClickSend.unquote(results['qsd']['to'])))]
|
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
@ -30,7 +30,6 @@
|
|||||||
# (both user and password) from the API Details section from within your
|
# (both user and password) from the API Details section from within your
|
||||||
# account profile area: https://d7networks.com/accounts/profile/
|
# account profile area: https://d7networks.com/accounts/profile/
|
||||||
|
|
||||||
import re
|
|
||||||
import six
|
import six
|
||||||
import requests
|
import requests
|
||||||
import base64
|
import base64
|
||||||
@ -40,7 +39,8 @@ from json import loads
|
|||||||
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 parse_list
|
from ..utils import is_phone_no
|
||||||
|
from ..utils import parse_phone_no
|
||||||
from ..utils import parse_bool
|
from ..utils import parse_bool
|
||||||
from ..AppriseLocale import gettext_lazy as _
|
from ..AppriseLocale import gettext_lazy as _
|
||||||
|
|
||||||
@ -52,9 +52,6 @@ D7NETWORKS_HTTP_ERROR_MAP = {
|
|||||||
500: 'A Serverside Error Occured Handling the Request.',
|
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
|
# Priorities
|
||||||
class D7SMSPriority(object):
|
class D7SMSPriority(object):
|
||||||
@ -197,18 +194,17 @@ class NotifyD7Networks(NotifyBase):
|
|||||||
self.source = None \
|
self.source = None \
|
||||||
if not isinstance(source, six.string_types) else source.strip()
|
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
|
# Parse our targets
|
||||||
self.targets = list()
|
self.targets = list()
|
||||||
|
for target in parse_phone_no(targets):
|
||||||
for target in parse_list(targets):
|
|
||||||
# Validate targets and drop bad ones:
|
# Validate targets and drop bad ones:
|
||||||
result = IS_PHONE_NO.match(target)
|
result = result = is_phone_no(target)
|
||||||
if result:
|
if not 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(
|
self.logger.warning(
|
||||||
'Dropped invalid phone # '
|
'Dropped invalid phone # '
|
||||||
'({}) specified.'.format(target),
|
'({}) specified.'.format(target),
|
||||||
@ -216,16 +212,7 @@ class NotifyD7Networks(NotifyBase):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
# store valid phone number
|
# store valid phone number
|
||||||
self.targets.append(result)
|
self.targets.append(result['full'])
|
||||||
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)
|
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -235,6 +222,11 @@ class NotifyD7Networks(NotifyBase):
|
|||||||
redirects to the appropriate handling
|
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)
|
# error tracking (used for function return)
|
||||||
has_error = False
|
has_error = False
|
||||||
|
|
||||||
@ -479,6 +471,6 @@ class NotifyD7Networks(NotifyBase):
|
|||||||
# The 'to' makes it easier to use yaml configuration
|
# The 'to' makes it easier to use yaml configuration
|
||||||
if 'to' in results['qsd'] and len(results['qsd']['to']):
|
if 'to' in results['qsd'] and len(results['qsd']['to']):
|
||||||
results['targets'] += \
|
results['targets'] += \
|
||||||
NotifyD7Networks.parse_list(results['qsd']['to'])
|
NotifyD7Networks.parse_phone_no(results['qsd']['to'])
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
@ -32,13 +32,13 @@
|
|||||||
# This provider does not accept +1 (for example) as a country code. You need
|
# This provider does not accept +1 (for example) as a country code. You need
|
||||||
# to specify 001 instead.
|
# to specify 001 instead.
|
||||||
#
|
#
|
||||||
import re
|
|
||||||
import requests
|
import requests
|
||||||
from json import loads
|
from json import loads
|
||||||
|
|
||||||
from .NotifyBase import NotifyBase
|
from .NotifyBase import NotifyBase
|
||||||
from ..common import NotifyType
|
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 ..utils import validate_regex
|
||||||
from ..AppriseLocale import gettext_lazy as _
|
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',
|
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):
|
class NotifyKavenegar(NotifyBase):
|
||||||
"""
|
"""
|
||||||
@ -165,36 +162,23 @@ class NotifyKavenegar(NotifyBase):
|
|||||||
|
|
||||||
self.source = None
|
self.source = None
|
||||||
if source is not None:
|
if source is not None:
|
||||||
result = IS_PHONE_NO.match(source)
|
result = is_phone_no(source)
|
||||||
if not result:
|
if not result:
|
||||||
msg = 'The Kavenegar source specified ({}) is invalid.'\
|
msg = 'The Kavenegar source specified ({}) is invalid.'\
|
||||||
.format(source)
|
.format(source)
|
||||||
self.logger.warning(msg)
|
self.logger.warning(msg)
|
||||||
raise TypeError(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
|
# Store our source
|
||||||
self.source = result
|
self.source = result['full']
|
||||||
|
|
||||||
# Parse our targets
|
# Parse our targets
|
||||||
self.targets = list()
|
self.targets = list()
|
||||||
|
|
||||||
for target in parse_list(targets):
|
for target in parse_phone_no(targets):
|
||||||
# Validate targets and drop bad ones:
|
# Validate targets and drop bad ones:
|
||||||
result = IS_PHONE_NO.match(target)
|
result = is_phone_no(target)
|
||||||
if result:
|
if not 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(
|
self.logger.warning(
|
||||||
'Dropped invalid phone # '
|
'Dropped invalid phone # '
|
||||||
'({}) specified.'.format(target),
|
'({}) specified.'.format(target),
|
||||||
@ -202,16 +186,7 @@ class NotifyKavenegar(NotifyBase):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
# store valid phone number
|
# store valid phone number
|
||||||
self.targets.append(result)
|
self.targets.append(result['full'])
|
||||||
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)
|
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -220,6 +195,11 @@ class NotifyKavenegar(NotifyBase):
|
|||||||
Sends SMS Message
|
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)
|
# error tracking (used for function return)
|
||||||
has_error = False
|
has_error = False
|
||||||
|
|
||||||
@ -364,7 +344,7 @@ class NotifyKavenegar(NotifyBase):
|
|||||||
# The 'to' makes it easier to use yaml configuration
|
# The 'to' makes it easier to use yaml configuration
|
||||||
if 'to' in results['qsd'] and len(results['qsd']['to']):
|
if 'to' in results['qsd'] and len(results['qsd']['to']):
|
||||||
results['targets'] += \
|
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']):
|
if 'from' in results['qsd'] and len(results['qsd']['from']):
|
||||||
results['source'] = \
|
results['source'] = \
|
||||||
|
@ -31,18 +31,15 @@
|
|||||||
# Get details on the API used in this plugin here:
|
# Get details on the API used in this plugin here:
|
||||||
# - https://world.msg91.com/apidoc/textsms/send-sms.php
|
# - https://world.msg91.com/apidoc/textsms/send-sms.php
|
||||||
|
|
||||||
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 parse_list
|
from ..utils import is_phone_no
|
||||||
|
from ..utils import parse_phone_no
|
||||||
from ..utils import validate_regex
|
from ..utils import validate_regex
|
||||||
from ..AppriseLocale import gettext_lazy as _
|
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):
|
class MSG91Route(object):
|
||||||
"""
|
"""
|
||||||
@ -207,13 +204,10 @@ class NotifyMSG91(NotifyBase):
|
|||||||
# Parse our targets
|
# Parse our targets
|
||||||
self.targets = list()
|
self.targets = list()
|
||||||
|
|
||||||
for target in parse_list(targets):
|
for target in parse_phone_no(targets):
|
||||||
# Validate targets and drop bad ones:
|
# Validate targets and drop bad ones:
|
||||||
result = IS_PHONE_NO.match(target)
|
result = is_phone_no(target)
|
||||||
if result:
|
if not 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(
|
self.logger.warning(
|
||||||
'Dropped invalid phone # '
|
'Dropped invalid phone # '
|
||||||
'({}) specified.'.format(target),
|
'({}) specified.'.format(target),
|
||||||
@ -221,19 +215,7 @@ class NotifyMSG91(NotifyBase):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
# store valid phone number
|
# store valid phone number
|
||||||
self.targets.append(result)
|
self.targets.append(result['full'])
|
||||||
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)
|
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -242,6 +224,11 @@ class NotifyMSG91(NotifyBase):
|
|||||||
Perform MSG91 Notification
|
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
|
# Prepare our headers
|
||||||
headers = {
|
headers = {
|
||||||
'User-Agent': self.app_id,
|
'User-Agent': self.app_id,
|
||||||
@ -365,6 +352,6 @@ class NotifyMSG91(NotifyBase):
|
|||||||
# The 'to' makes it easier to use yaml configuration
|
# The 'to' makes it easier to use yaml configuration
|
||||||
if 'to' in results['qsd'] and len(results['qsd']['to']):
|
if 'to' in results['qsd'] and len(results['qsd']['to']):
|
||||||
results['targets'] += \
|
results['targets'] += \
|
||||||
NotifyMSG91.parse_list(results['qsd']['to'])
|
NotifyMSG91.parse_phone_no(results['qsd']['to'])
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
@ -29,18 +29,15 @@
|
|||||||
# - https://dashboard.messagebird.com/en/user/index
|
# - https://dashboard.messagebird.com/en/user/index
|
||||||
#
|
#
|
||||||
|
|
||||||
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 parse_list
|
from ..utils import is_phone_no
|
||||||
|
from ..utils import parse_phone_no
|
||||||
from ..utils import validate_regex
|
from ..utils import validate_regex
|
||||||
from ..AppriseLocale import gettext_lazy as _
|
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):
|
class NotifyMessageBird(NotifyBase):
|
||||||
"""
|
"""
|
||||||
@ -129,28 +126,20 @@ class NotifyMessageBird(NotifyBase):
|
|||||||
self.logger.warning(msg)
|
self.logger.warning(msg)
|
||||||
raise TypeError(msg)
|
raise TypeError(msg)
|
||||||
|
|
||||||
result = IS_PHONE_NO.match(source)
|
result = is_phone_no(source)
|
||||||
if not result:
|
if not result:
|
||||||
msg = 'The MessageBird source specified ({}) is invalid.'\
|
msg = 'The MessageBird source specified ({}) is invalid.'\
|
||||||
.format(source)
|
.format(source)
|
||||||
self.logger.warning(msg)
|
self.logger.warning(msg)
|
||||||
raise TypeError(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
|
# Store our source
|
||||||
self.source = result
|
self.source = result['full']
|
||||||
|
|
||||||
# Parse our targets
|
# Parse our targets
|
||||||
self.targets = list()
|
self.targets = list()
|
||||||
|
|
||||||
targets = parse_list(targets)
|
targets = parse_phone_no(targets)
|
||||||
if not targets:
|
if not targets:
|
||||||
# No sources specified, use our own phone no
|
# No sources specified, use our own phone no
|
||||||
self.targets.append(self.source)
|
self.targets.append(self.source)
|
||||||
@ -159,11 +148,8 @@ class NotifyMessageBird(NotifyBase):
|
|||||||
# otherwise, store all of our target numbers
|
# otherwise, store all of our target numbers
|
||||||
for target in targets:
|
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(target)
|
||||||
if result:
|
if not 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(
|
self.logger.warning(
|
||||||
'Dropped invalid phone # '
|
'Dropped invalid phone # '
|
||||||
'({}) specified.'.format(target),
|
'({}) specified.'.format(target),
|
||||||
@ -171,19 +157,7 @@ class NotifyMessageBird(NotifyBase):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
# store valid phone number
|
# store valid phone number
|
||||||
self.targets.append(result)
|
self.targets.append(result['full'])
|
||||||
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)
|
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -192,6 +166,11 @@ class NotifyMessageBird(NotifyBase):
|
|||||||
Perform MessageBird Notification
|
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)
|
# error tracking (used for function return)
|
||||||
has_error = False
|
has_error = False
|
||||||
|
|
||||||
@ -345,6 +324,7 @@ class NotifyMessageBird(NotifyBase):
|
|||||||
try:
|
try:
|
||||||
# The first path entry is the source/originator
|
# The first path entry is the source/originator
|
||||||
results['source'] = results['targets'].pop(0)
|
results['source'] = results['targets'].pop(0)
|
||||||
|
|
||||||
except IndexError:
|
except IndexError:
|
||||||
# No path specified... this URL is potentially un-parseable; we can
|
# No path specified... this URL is potentially un-parseable; we can
|
||||||
# hope for a from= entry
|
# hope for a from= entry
|
||||||
@ -357,7 +337,7 @@ class NotifyMessageBird(NotifyBase):
|
|||||||
# The 'to' makes it easier to use yaml configuration
|
# The 'to' makes it easier to use yaml configuration
|
||||||
if 'to' in results['qsd'] and len(results['qsd']['to']):
|
if 'to' in results['qsd'] and len(results['qsd']['to']):
|
||||||
results['targets'] += \
|
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']):
|
if 'from' in results['qsd'] and len(results['qsd']['from']):
|
||||||
results['source'] = \
|
results['source'] = \
|
||||||
|
@ -28,20 +28,16 @@
|
|||||||
# Get your (api) key and secret here:
|
# Get your (api) key and secret here:
|
||||||
# - https://dashboard.nexmo.com/getting-started-guide
|
# - https://dashboard.nexmo.com/getting-started-guide
|
||||||
#
|
#
|
||||||
|
|
||||||
import re
|
|
||||||
import requests
|
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 parse_list
|
from ..utils import is_phone_no
|
||||||
|
from ..utils import parse_phone_no
|
||||||
from ..utils import validate_regex
|
from ..utils import validate_regex
|
||||||
from ..AppriseLocale import gettext_lazy as _
|
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):
|
class NotifyNexmo(NotifyBase):
|
||||||
"""
|
"""
|
||||||
@ -185,30 +181,23 @@ class NotifyNexmo(NotifyBase):
|
|||||||
# The Source Phone #
|
# The Source Phone #
|
||||||
self.source = source
|
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 ' \
|
msg = 'The Account (From) Phone # specified ' \
|
||||||
'({}) is invalid.'.format(source)
|
'({}) is invalid.'.format(source)
|
||||||
self.logger.warning(msg)
|
self.logger.warning(msg)
|
||||||
raise TypeError(msg)
|
raise TypeError(msg)
|
||||||
|
|
||||||
# Tidy source
|
# Store our parsed value
|
||||||
self.source = re.sub(r'[^\d]+', '', self.source)
|
self.source = result['full']
|
||||||
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)
|
|
||||||
|
|
||||||
# Parse our targets
|
# Parse our targets
|
||||||
self.targets = list()
|
self.targets = list()
|
||||||
|
|
||||||
for target in parse_list(targets):
|
for target in parse_phone_no(targets):
|
||||||
# Validate targets and drop bad ones:
|
# Validate targets and drop bad ones:
|
||||||
result = IS_PHONE_NO.match(target)
|
result = is_phone_no(target)
|
||||||
if result:
|
if not 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(
|
self.logger.warning(
|
||||||
'Dropped invalid phone # '
|
'Dropped invalid phone # '
|
||||||
'({}) specified.'.format(target),
|
'({}) specified.'.format(target),
|
||||||
@ -216,13 +205,7 @@ class NotifyNexmo(NotifyBase):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
# store valid phone number
|
# store valid phone number
|
||||||
self.targets.append(result)
|
self.targets.append(result['full'])
|
||||||
continue
|
|
||||||
|
|
||||||
self.logger.warning(
|
|
||||||
'Dropped invalid phone # '
|
|
||||||
'({}) specified.'.format(target),
|
|
||||||
)
|
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -393,10 +376,10 @@ class NotifyNexmo(NotifyBase):
|
|||||||
results['ttl'] = \
|
results['ttl'] = \
|
||||||
NotifyNexmo.unquote(results['qsd']['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
|
# The 'to' makes it easier to use yaml configuration
|
||||||
if 'to' in results['qsd'] and len(results['qsd']['to']):
|
if 'to' in results['qsd'] and len(results['qsd']['to']):
|
||||||
results['targets'] += \
|
results['targets'] += \
|
||||||
NotifyNexmo.parse_list(results['qsd']['to'])
|
NotifyNexmo.parse_phone_no(results['qsd']['to'])
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
@ -23,20 +23,17 @@
|
|||||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
# THE SOFTWARE.
|
# THE SOFTWARE.
|
||||||
|
|
||||||
import re
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from .NotifyBase import NotifyBase
|
from .NotifyBase import NotifyBase
|
||||||
from ..common import NotifyType
|
from ..common import NotifyType
|
||||||
from ..utils import is_email
|
from ..utils import is_email
|
||||||
|
from ..utils import is_phone_no
|
||||||
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 ..utils import validate_regex
|
||||||
from ..AppriseLocale import gettext_lazy as _
|
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):
|
class NotifyPopcornNotify(NotifyBase):
|
||||||
"""
|
"""
|
||||||
@ -127,19 +124,10 @@ class NotifyPopcornNotify(NotifyBase):
|
|||||||
|
|
||||||
for target in parse_list(targets):
|
for target in parse_list(targets):
|
||||||
# Validate targets and drop bad ones:
|
# Validate targets and drop bad ones:
|
||||||
result = IS_PHONE_NO.match(target)
|
result = is_phone_no(target)
|
||||||
if result:
|
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
|
# store valid phone number
|
||||||
self.targets.append(result)
|
self.targets.append(result['full'])
|
||||||
continue
|
continue
|
||||||
|
|
||||||
result = is_email(target)
|
result = is_email(target)
|
||||||
|
@ -35,13 +35,11 @@ from itertools import chain
|
|||||||
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 is_phone_no
|
||||||
from ..utils import parse_list
|
from ..utils import parse_list
|
||||||
from ..utils import validate_regex
|
from ..utils import validate_regex
|
||||||
from ..AppriseLocale import gettext_lazy as _
|
from ..AppriseLocale import gettext_lazy as _
|
||||||
|
|
||||||
# Some Phone Number Detection
|
|
||||||
IS_PHONE_NO = re.compile(r'^\+?(?P<phone>[0-9\s)(+-]+)\s*$')
|
|
||||||
|
|
||||||
# Topic Detection
|
# Topic Detection
|
||||||
# Summary: 256 Characters max, only alpha/numeric plus underscore (_) and
|
# Summary: 256 Characters max, only alpha/numeric plus underscore (_) and
|
||||||
# dash (-) additionally allowed.
|
# dash (-) additionally allowed.
|
||||||
@ -198,24 +196,10 @@ class NotifySNS(NotifyBase):
|
|||||||
self.aws_auth_algorithm = 'AWS4-HMAC-SHA256'
|
self.aws_auth_algorithm = 'AWS4-HMAC-SHA256'
|
||||||
self.aws_auth_request = 'aws4_request'
|
self.aws_auth_request = 'aws4_request'
|
||||||
|
|
||||||
# Get our targets
|
|
||||||
targets = parse_list(targets)
|
|
||||||
|
|
||||||
# Validate targets and drop bad ones:
|
# Validate targets and drop bad ones:
|
||||||
for target in targets:
|
for target in parse_list(targets):
|
||||||
result = IS_PHONE_NO.match(target)
|
result = is_phone_no(target)
|
||||||
if result:
|
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
|
# store valid phone number
|
||||||
self.phone.append('+{}'.format(result))
|
self.phone.append('+{}'.format(result))
|
||||||
continue
|
continue
|
||||||
@ -231,12 +215,6 @@ class NotifySNS(NotifyBase):
|
|||||||
'(%s) specified.' % target,
|
'(%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
|
return
|
||||||
|
|
||||||
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
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
|
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
|
# Initiaize our error tracking
|
||||||
error_count = 0
|
error_count = 0
|
||||||
|
|
||||||
|
@ -33,7 +33,6 @@
|
|||||||
# from). Activated phone numbers can be found on your dashboard here:
|
# from). Activated phone numbers can be found on your dashboard here:
|
||||||
# - https://dashboard.sinch.com/numbers/your-numbers/numbers
|
# - https://dashboard.sinch.com/numbers/your-numbers/numbers
|
||||||
#
|
#
|
||||||
import re
|
|
||||||
import six
|
import six
|
||||||
import requests
|
import requests
|
||||||
import json
|
import json
|
||||||
@ -41,15 +40,12 @@ import json
|
|||||||
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 parse_list
|
from ..utils import is_phone_no
|
||||||
|
from ..utils import parse_phone_no
|
||||||
from ..utils import validate_regex
|
from ..utils import validate_regex
|
||||||
from ..AppriseLocale import gettext_lazy as _
|
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):
|
class SinchRegion(object):
|
||||||
"""
|
"""
|
||||||
Defines the Sinch Server Regions
|
Defines the Sinch Server Regions
|
||||||
@ -194,15 +190,6 @@ class NotifySinch(NotifyBase):
|
|||||||
self.logger.warning(msg)
|
self.logger.warning(msg)
|
||||||
raise TypeError(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
|
# Setup our region
|
||||||
self.region = self.template_args['region']['default'] \
|
self.region = self.template_args['region']['default'] \
|
||||||
if not isinstance(region, six.string_types) else region.lower()
|
if not isinstance(region, six.string_types) else region.lower()
|
||||||
@ -211,8 +198,16 @@ class NotifySinch(NotifyBase):
|
|||||||
self.logger.warning(msg)
|
self.logger.warning(msg)
|
||||||
raise TypeError(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
|
# Tidy source
|
||||||
self.source = re.sub(r'[^\d]+', '', self.source)
|
self.source = result['full']
|
||||||
|
|
||||||
if len(self.source) < 11 or len(self.source) > 14:
|
if len(self.source) < 11 or len(self.source) > 14:
|
||||||
# A short code is a special 5 or 6 digit telephone number
|
# A short code is a special 5 or 6 digit telephone number
|
||||||
@ -233,15 +228,10 @@ class NotifySinch(NotifyBase):
|
|||||||
# Parse our targets
|
# Parse our targets
|
||||||
self.targets = list()
|
self.targets = list()
|
||||||
|
|
||||||
for target in parse_list(targets):
|
for target in parse_phone_no(targets):
|
||||||
# Validate targets and drop bad ones:
|
# Parse each phone number we found
|
||||||
result = IS_PHONE_NO.match(target)
|
result = is_phone_no(target)
|
||||||
if result:
|
if not 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(
|
self.logger.warning(
|
||||||
'Dropped invalid phone # '
|
'Dropped invalid phone # '
|
||||||
'({}) specified.'.format(target),
|
'({}) specified.'.format(target),
|
||||||
@ -249,21 +239,7 @@ class NotifySinch(NotifyBase):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
# store valid phone number
|
# store valid phone number
|
||||||
self.targets.append('+{}'.format(result))
|
self.targets.append('+{}'.format(result['full']))
|
||||||
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)
|
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -272,6 +248,14 @@ class NotifySinch(NotifyBase):
|
|||||||
Perform Sinch Notification
|
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)
|
# error tracking (used for function return)
|
||||||
has_error = False
|
has_error = False
|
||||||
|
|
||||||
@ -459,6 +443,7 @@ class NotifySinch(NotifyBase):
|
|||||||
if 'from' in results['qsd'] and len(results['qsd']['from']):
|
if 'from' in results['qsd'] and len(results['qsd']['from']):
|
||||||
results['source'] = \
|
results['source'] = \
|
||||||
NotifySinch.unquote(results['qsd']['from'])
|
NotifySinch.unquote(results['qsd']['from'])
|
||||||
|
|
||||||
if 'source' in results['qsd'] and len(results['qsd']['source']):
|
if 'source' in results['qsd'] and len(results['qsd']['source']):
|
||||||
results['source'] = \
|
results['source'] = \
|
||||||
NotifySinch.unquote(results['qsd']['source'])
|
NotifySinch.unquote(results['qsd']['source'])
|
||||||
@ -472,6 +457,6 @@ class NotifySinch(NotifyBase):
|
|||||||
# The 'to' makes it easier to use yaml configuration
|
# The 'to' makes it easier to use yaml configuration
|
||||||
if 'to' in results['qsd'] and len(results['qsd']['to']):
|
if 'to' in results['qsd'] and len(results['qsd']['to']):
|
||||||
results['targets'] += \
|
results['targets'] += \
|
||||||
NotifySinch.parse_list(results['qsd']['to'])
|
NotifySinch.parse_phone_no(results['qsd']['to'])
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
@ -40,22 +40,18 @@
|
|||||||
# or consider purchasing a short-code from here:
|
# or consider purchasing a short-code from here:
|
||||||
# https://www.twilio.com/docs/glossary/what-is-a-short-code
|
# https://www.twilio.com/docs/glossary/what-is-a-short-code
|
||||||
#
|
#
|
||||||
import re
|
|
||||||
import requests
|
import requests
|
||||||
from json import loads
|
from json import loads
|
||||||
|
|
||||||
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 parse_list
|
from ..utils import is_phone_no
|
||||||
|
from ..utils import parse_phone_no
|
||||||
from ..utils import validate_regex
|
from ..utils import validate_regex
|
||||||
from ..AppriseLocale import gettext_lazy as _
|
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):
|
class NotifyTwilio(NotifyBase):
|
||||||
"""
|
"""
|
||||||
A wrapper for Twilio Notifications
|
A wrapper for Twilio Notifications
|
||||||
@ -181,17 +177,15 @@ class NotifyTwilio(NotifyBase):
|
|||||||
self.logger.warning(msg)
|
self.logger.warning(msg)
|
||||||
raise TypeError(msg)
|
raise TypeError(msg)
|
||||||
|
|
||||||
# The Source Phone # and/or short-code
|
result = is_phone_no(source, min_len=5)
|
||||||
self.source = source
|
if not result:
|
||||||
|
|
||||||
if not IS_PHONE_NO.match(self.source):
|
|
||||||
msg = 'The Account (From) Phone # or Short-code specified ' \
|
msg = 'The Account (From) Phone # or Short-code specified ' \
|
||||||
'({}) is invalid.'.format(source)
|
'({}) is invalid.'.format(source)
|
||||||
self.logger.warning(msg)
|
self.logger.warning(msg)
|
||||||
raise TypeError(msg)
|
raise TypeError(msg)
|
||||||
|
|
||||||
# Tidy source
|
# Store The Source Phone # and/or short-code
|
||||||
self.source = re.sub(r'[^\d]+', '', self.source)
|
self.source = result['full']
|
||||||
|
|
||||||
if len(self.source) < 11 or len(self.source) > 14:
|
if len(self.source) < 11 or len(self.source) > 14:
|
||||||
# https://www.twilio.com/docs/glossary/what-is-a-short-code
|
# https://www.twilio.com/docs/glossary/what-is-a-short-code
|
||||||
@ -213,15 +207,10 @@ class NotifyTwilio(NotifyBase):
|
|||||||
# Parse our targets
|
# Parse our targets
|
||||||
self.targets = list()
|
self.targets = list()
|
||||||
|
|
||||||
for target in parse_list(targets):
|
for target in parse_phone_no(targets):
|
||||||
# Validate targets and drop bad ones:
|
# Validate targets and drop bad ones:
|
||||||
result = IS_PHONE_NO.match(target)
|
result = is_phone_no(target)
|
||||||
if result:
|
if not 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(
|
self.logger.warning(
|
||||||
'Dropped invalid phone # '
|
'Dropped invalid phone # '
|
||||||
'({}) specified.'.format(target),
|
'({}) specified.'.format(target),
|
||||||
@ -230,20 +219,6 @@ class NotifyTwilio(NotifyBase):
|
|||||||
|
|
||||||
# store valid phone number
|
# store valid phone number
|
||||||
self.targets.append('+{}'.format(result))
|
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
|
return
|
||||||
|
|
||||||
@ -252,6 +227,14 @@ class NotifyTwilio(NotifyBase):
|
|||||||
Perform Twilio Notification
|
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)
|
# error tracking (used for function return)
|
||||||
has_error = False
|
has_error = False
|
||||||
|
|
||||||
@ -431,6 +414,6 @@ class NotifyTwilio(NotifyBase):
|
|||||||
# The 'to' makes it easier to use yaml configuration
|
# The 'to' makes it easier to use yaml configuration
|
||||||
if 'to' in results['qsd'] and len(results['qsd']['to']):
|
if 'to' in results['qsd'] and len(results['qsd']['to']):
|
||||||
results['targets'] += \
|
results['targets'] += \
|
||||||
NotifyTwilio.parse_list(results['qsd']['to'])
|
NotifyTwilio.parse_phone_no(results['qsd']['to'])
|
||||||
|
|
||||||
return results
|
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
|
# - user@example.com
|
||||||
# - label+user@example.com
|
# - label+user@example.com
|
||||||
GET_EMAIL_RE = re.compile(
|
GET_EMAIL_RE = re.compile(
|
||||||
r'((?P<name>[^:<]+)?[:<\s]+)?'
|
r'(([\s"\']+)?(?P<name>[^:<"\']+)?[:<\s"\']+)?'
|
||||||
r'(?P<full_email>((?P<label>[^+]+)\+)?'
|
r'(?P<full_email>((?P<label>[^+]+)\+)?'
|
||||||
r'(?P<email>(?P<userid>[a-z0-9$%=_~-]+'
|
r'(?P<email>(?P<userid>[a-z0-9$%=_~-]+'
|
||||||
r'(?:\.[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'[a-z0-9][a-z0-9_-]{5,})))'
|
||||||
r'\s*>?', re.IGNORECASE)
|
r'\s*>?', re.IGNORECASE)
|
||||||
|
|
||||||
# Regular expression used to extract a phone number
|
# A simple verification check to make sure the content specified
|
||||||
GET_PHONE_NO_RE = re.compile(r'^\+?(?P<phone>[0-9\s)(+-]+)\s*$')
|
# 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
|
# Regular expression used to destinguish between multiple URLs
|
||||||
URL_DETECTION_RE = re.compile(
|
URL_DETECTION_RE = re.compile(
|
||||||
@ -273,6 +278,98 @@ def is_uuid(uuid):
|
|||||||
return True if match else False
|
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):
|
def is_email(address):
|
||||||
"""Determine if the specified entry is an email address
|
"""Determine if the specified entry is an email address
|
||||||
|
|
||||||
@ -633,9 +730,46 @@ def parse_bool(arg, default=False):
|
|||||||
return bool(arg)
|
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):
|
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.
|
returns a list.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -207,6 +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(object()) == []
|
||||||
assert NotifyBase.parse_list(42) == []
|
assert NotifyBase.parse_list(42) == []
|
||||||
|
|
||||||
result = NotifyBase.parse_list(
|
result = NotifyBase.parse_list(
|
||||||
@ -234,6 +235,26 @@ def test_notify_base():
|
|||||||
assert '//' in result
|
assert '//' in result
|
||||||
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
|
# Give nothing, get nothing
|
||||||
assert NotifyBase.escape_html("") == ""
|
assert NotifyBase.escape_html("") == ""
|
||||||
assert NotifyBase.escape_html(None) == ""
|
assert NotifyBase.escape_html(None) == ""
|
||||||
|
@ -196,7 +196,10 @@ TEST_URLS = (
|
|||||||
}),
|
}),
|
||||||
('d7sms://user:pass@{}/{}/{}'.format('1' * 10, '2' * 15, 'a' * 13), {
|
('d7sms://user:pass@{}/{}/{}'.format('1' * 10, '2' * 15, 'a' * 13), {
|
||||||
# No valid targets to notify
|
# 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), {
|
('d7sms://user:pass@{}?batch=yes'.format('3' * 14), {
|
||||||
# valid number
|
# valid number
|
||||||
@ -1297,8 +1300,11 @@ TEST_URLS = (
|
|||||||
'instance': TypeError,
|
'instance': TypeError,
|
||||||
}),
|
}),
|
||||||
('kavenegar://{}/{}/{}'.format('1' * 10, '2' * 15, 'a' * 13), {
|
('kavenegar://{}/{}/{}'.format('1' * 10, '2' * 15, 'a' * 13), {
|
||||||
# No valid targets to notify
|
# valid api key and valid authentication
|
||||||
'instance': TypeError,
|
'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), {
|
('kavenegar://{}/{}'.format('a' * 24, '3' * 14), {
|
||||||
# valid api key and valid number
|
# valid api key and valid number
|
||||||
@ -4034,7 +4040,9 @@ TEST_URLS = (
|
|||||||
('sinch://{}:{}@{}'.format('a' * 32, 'b' * 32, '3' * 5), {
|
('sinch://{}:{}@{}'.format('a' * 32, 'b' * 32, '3' * 5), {
|
||||||
# using short-code (5 characters) without a target
|
# using short-code (5 characters) without a target
|
||||||
# We can still instantiate ourselves with a valid short code
|
# 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), {
|
('sinch://{}:{}@{}'.format('a' * 32, 'b' * 32, '3' * 9), {
|
||||||
# spi and token provided and from but invalid from no
|
# 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), {
|
('twilio://AC{}:{}@{}'.format('a' * 32, 'b' * 32, '3' * 5), {
|
||||||
# using short-code (5 characters) without a target
|
# using short-code (5 characters) without a target
|
||||||
# We can still instantiate ourselves with a valid short code
|
# 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), {
|
('twilio://AC{}:{}@{}'.format('a' * 32, 'b' * 32, '3' * 9), {
|
||||||
# sid and token provided and from but invalid from no
|
# sid and token provided and from but invalid from no
|
||||||
@ -5211,16 +5222,25 @@ TEST_URLS = (
|
|||||||
'instance': TypeError,
|
'instance': TypeError,
|
||||||
}),
|
}),
|
||||||
('msg91://{}'.format('a' * 23), {
|
('msg91://{}'.format('a' * 23), {
|
||||||
# No number specified
|
# valid AuthKey
|
||||||
'instance': TypeError,
|
'instance': plugins.NotifyMSG91,
|
||||||
|
# Since there are no targets specified we expect a False return on
|
||||||
|
# send()
|
||||||
|
'notify_response': False,
|
||||||
}),
|
}),
|
||||||
('msg91://{}/123'.format('a' * 23), {
|
('msg91://{}/123'.format('a' * 23), {
|
||||||
# invalid phone number
|
# 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), {
|
('msg91://{}/abcd'.format('a' * 23), {
|
||||||
# No number to notify
|
# 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), {
|
('msg91://{}/15551232000/?country=invalid'.format('a' * 23), {
|
||||||
# invalid country
|
# invalid country
|
||||||
@ -5293,12 +5313,18 @@ TEST_URLS = (
|
|||||||
'privacy_url': 'msgbird://a...a/15551232000',
|
'privacy_url': 'msgbird://a...a/15551232000',
|
||||||
}),
|
}),
|
||||||
('msgbird://{}/15551232000/abcd'.format('a' * 25), {
|
('msgbird://{}/15551232000/abcd'.format('a' * 25), {
|
||||||
# invalid target phone number; we have no one to notify
|
# valid credentials
|
||||||
'instance': TypeError,
|
'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), {
|
('msgbird://{}/15551232000/123'.format('a' * 25), {
|
||||||
# invalid target phone number
|
# valid credentials
|
||||||
'instance': TypeError,
|
'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), {
|
('msgbird://{}/?from=15551233000&to=15551232000'.format('a' * 25), {
|
||||||
# reference to to= and from=
|
# reference to to= and from=
|
||||||
|
@ -39,7 +39,10 @@ TEST_ACCESS_KEY_SECRET = 'bu1dHSdO22pfaaVy/wmNsdljF4C07D3bndi9PQJ9'
|
|||||||
TEST_REGION = 'us-east-2'
|
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
|
API: NotifySNS Plugin() initialization
|
||||||
|
|
||||||
@ -73,44 +76,41 @@ def test_object_initialization():
|
|||||||
targets='+1800555999',
|
targets='+1800555999',
|
||||||
)
|
)
|
||||||
|
|
||||||
with pytest.raises(TypeError):
|
|
||||||
# No recipients
|
# No recipients
|
||||||
plugins.NotifySNS(
|
obj = plugins.NotifySNS(
|
||||||
access_key_id=TEST_ACCESS_KEY_ID,
|
access_key_id=TEST_ACCESS_KEY_ID,
|
||||||
secret_access_key=TEST_ACCESS_KEY_SECRET,
|
secret_access_key=TEST_ACCESS_KEY_SECRET,
|
||||||
region_name=TEST_REGION,
|
region_name=TEST_REGION,
|
||||||
targets=None,
|
targets=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
with pytest.raises(TypeError):
|
# The object initializes properly but would not be able to send anything
|
||||||
# No recipients - garbage recipients object
|
assert obj.notify(body='test', title='test') is False
|
||||||
plugins.NotifySNS(
|
|
||||||
access_key_id=TEST_ACCESS_KEY_ID,
|
|
||||||
secret_access_key=TEST_ACCESS_KEY_SECRET,
|
|
||||||
region_name=TEST_REGION,
|
|
||||||
targets=object(),
|
|
||||||
)
|
|
||||||
|
|
||||||
with pytest.raises(TypeError):
|
|
||||||
# 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(
|
obj = plugins.NotifySNS(
|
||||||
access_key_id=TEST_ACCESS_KEY_ID,
|
access_key_id=TEST_ACCESS_KEY_ID,
|
||||||
secret_access_key=TEST_ACCESS_KEY_SECRET,
|
secret_access_key=TEST_ACCESS_KEY_SECRET,
|
||||||
region_name=TEST_REGION,
|
region_name=TEST_REGION,
|
||||||
targets='+1809',
|
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
|
# The phone number is invalid, and without it, there is nothing
|
||||||
# to notify; we
|
# to notify; we
|
||||||
plugins.NotifySNS(
|
obj = plugins.NotifySNS(
|
||||||
access_key_id=TEST_ACCESS_KEY_ID,
|
access_key_id=TEST_ACCESS_KEY_ID,
|
||||||
secret_access_key=TEST_ACCESS_KEY_SECRET,
|
secret_access_key=TEST_ACCESS_KEY_SECRET,
|
||||||
region_name=TEST_REGION,
|
region_name=TEST_REGION,
|
||||||
targets='#(invalid-topic-because-of-the-brackets)',
|
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():
|
def test_url_parsing():
|
||||||
"""
|
"""
|
||||||
@ -170,16 +170,17 @@ def test_object_parsing():
|
|||||||
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 without valid recipients, the URL is actually useless
|
# This is valid but without valid recipients; while it's still a valid URL
|
||||||
assert a.add('sns://norecipient/norecipient/us-west-2') is False
|
# it won't do much when the user goes to send a notification
|
||||||
assert len(a) == 0
|
assert a.add('sns://norecipient/norecipient/us-west-2') is True
|
||||||
|
assert len(a) == 1
|
||||||
|
|
||||||
# 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) == 1
|
assert len(a) == 2
|
||||||
|
|
||||||
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) == 2
|
assert len(a) == 3
|
||||||
|
|
||||||
|
|
||||||
def test_aws_response_handling():
|
def test_aws_response_handling():
|
||||||
|
@ -694,6 +694,15 @@ def test_is_email():
|
|||||||
assert 'spichai' == results['user']
|
assert 'spichai' == results['user']
|
||||||
assert 'ceo' == results['label']
|
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
|
# An email without name, but contains delimiters
|
||||||
results = utils.is_email(' <spichai@gmail.com>')
|
results = utils.is_email(' <spichai@gmail.com>')
|
||||||
assert '' == results['name']
|
assert '' == results['name']
|
||||||
@ -731,6 +740,198 @@ def test_is_email():
|
|||||||
assert utils.is_email("Name <bademail>") is False
|
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():
|
def test_parse_emails():
|
||||||
"""utils: parse_emails() testing """
|
"""utils: parse_emails() testing """
|
||||||
# A simple single array entry (As str)
|
# A simple single array entry (As str)
|
||||||
@ -764,7 +965,7 @@ def test_parse_emails():
|
|||||||
assert isinstance(results, list)
|
assert isinstance(results, list)
|
||||||
assert len(results) == 0
|
assert len(results) == 0
|
||||||
|
|
||||||
# Now test valid URLs
|
# Now test valid emails
|
||||||
results = utils.parse_emails('user@example.com')
|
results = utils.parse_emails('user@example.com')
|
||||||
assert isinstance(results, list)
|
assert isinstance(results, list)
|
||||||
assert len(results) == 1
|
assert len(results) == 1
|
||||||
|
Loading…
Reference in New Issue
Block a user