From 8a455695baa2e914c567af899fdcbe406f671e7e Mon Sep 17 00:00:00 2001 From: Chris Caron Date: Wed, 28 Jul 2021 10:32:10 -0400 Subject: [PATCH] Refactored the way phone numbers are managed (#408) --- apprise/URLBase.py | 34 +++++ apprise/plugins/NotifyClickSend.py | 41 ++--- apprise/plugins/NotifyD7Networks.py | 58 +++---- apprise/plugins/NotifyKavenegar.py | 58 +++---- apprise/plugins/NotifyMSG91.py | 47 +++--- apprise/plugins/NotifyMessageBird.py | 60 +++----- apprise/plugins/NotifyNexmo.py | 51 +++---- apprise/plugins/NotifyPopcornNotify.py | 18 +-- apprise/plugins/NotifySNS.py | 33 +--- apprise/plugins/NotifySinch.py | 77 ++++------ apprise/plugins/NotifyTwilio.py | 65 +++----- apprise/utils.py | 142 ++++++++++++++++- test/test_notify_base.py | 21 +++ test/test_rest_plugins.py | 52 +++++-- test/test_sns_plugin.py | 81 +++++----- test/test_utils.py | 203 ++++++++++++++++++++++++- 16 files changed, 652 insertions(+), 389 deletions(-) diff --git a/apprise/URLBase.py b/apprise/URLBase.py index 6d6f7ba9..f5428dbb 100644 --- a/apprise/URLBase.py +++ b/apprise/URLBase.py @@ -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 '' diff --git a/apprise/plugins/NotifyClickSend.py b/apprise/plugins/NotifyClickSend.py index a7d89c18..9054c6f0 100644 --- a/apprise/plugins/NotifyClickSend.py +++ b/apprise/plugins/NotifyClickSend.py @@ -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[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,26 +145,18 @@ 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: - self.logger.warning( - 'Dropped invalid phone # ' - '({}) specified.'.format(target), - ) - continue - - # store valid phone number - self.targets.append(result) + result = is_phone_no(target) + if not result: + self.logger.warning( + 'Dropped invalid phone # ' + '({}) specified.'.format(target), + ) continue - self.logger.warning( - 'Dropped invalid phone # ' - '({}) specified.'.format(target)) + # store valid phone number + 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 diff --git a/apprise/plugins/NotifyD7Networks.py b/apprise/plugins/NotifyD7Networks.py index f04082c6..728f119a 100644 --- a/apprise/plugins/NotifyD7Networks.py +++ b/apprise/plugins/NotifyD7Networks.py @@ -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[0-9\s)(+-]+)\s*$') - # Priorities class D7SMSPriority(object): @@ -197,36 +194,26 @@ class NotifyD7Networks(NotifyBase): self.source = None \ if not isinstance(source, six.string_types) else source.strip() - # 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: - self.logger.warning( - 'Dropped invalid phone # ' - '({}) specified.'.format(target), - ) - 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.' + 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_phone_no(targets): + # Validate targets and drop bad ones: + result = result = is_phone_no(target) + if not result: + self.logger.warning( + 'Dropped invalid phone # ' + '({}) specified.'.format(target), + ) + continue + + # store valid phone number + self.targets.append(result['full']) + return def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs): @@ -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 diff --git a/apprise/plugins/NotifyKavenegar.py b/apprise/plugins/NotifyKavenegar.py index cd572636..97c69366 100644 --- a/apprise/plugins/NotifyKavenegar.py +++ b/apprise/plugins/NotifyKavenegar.py @@ -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[0-9\s)(+-]+)\s*$') - class NotifyKavenegar(NotifyBase): """ @@ -165,53 +162,31 @@ 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: - self.logger.warning( - 'Dropped invalid phone # ' - '({}) specified.'.format(target), - ) - continue - - # store valid phone number - self.targets.append(result) + result = is_phone_no(target) + if not result: + self.logger.warning( + 'Dropped invalid phone # ' + '({}) specified.'.format(target), + ) 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) + # store valid phone number + 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'] = \ diff --git a/apprise/plugins/NotifyMSG91.py b/apprise/plugins/NotifyMSG91.py index 68176fb9..f32ad818 100644 --- a/apprise/plugins/NotifyMSG91.py +++ b/apprise/plugins/NotifyMSG91.py @@ -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[0-9\s)(+-]+)\s*$') - class MSG91Route(object): """ @@ -207,33 +204,18 @@ 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: - self.logger.warning( - 'Dropped invalid phone # ' - '({}) specified.'.format(target), - ) - continue - - # store valid phone number - self.targets.append(result) + result = is_phone_no(target) + if not result: + self.logger.warning( + 'Dropped invalid phone # ' + '({}) specified.'.format(target), + ) 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) + # store valid phone number + 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 diff --git a/apprise/plugins/NotifyMessageBird.py b/apprise/plugins/NotifyMessageBird.py index 1032f49b..4b1da524 100644 --- a/apprise/plugins/NotifyMessageBird.py +++ b/apprise/plugins/NotifyMessageBird.py @@ -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[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,31 +148,16 @@ 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: - self.logger.warning( - 'Dropped invalid phone # ' - '({}) specified.'.format(target), - ) - continue - - # store valid phone number - self.targets.append(result) + result = is_phone_no(target) + if not result: + self.logger.warning( + 'Dropped invalid phone # ' + '({}) specified.'.format(target), + ) 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) + # store valid phone number + 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'] = \ diff --git a/apprise/plugins/NotifyNexmo.py b/apprise/plugins/NotifyNexmo.py index 05c9f7fc..1c423aad 100644 --- a/apprise/plugins/NotifyNexmo.py +++ b/apprise/plugins/NotifyNexmo.py @@ -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[0-9\s)(+-]+)\s*$') - class NotifyNexmo(NotifyBase): """ @@ -185,44 +181,31 @@ 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: - self.logger.warning( - 'Dropped invalid phone # ' - '({}) specified.'.format(target), - ) - continue - - # store valid phone number - self.targets.append(result) + result = is_phone_no(target) + if not result: + self.logger.warning( + 'Dropped invalid phone # ' + '({}) specified.'.format(target), + ) continue - self.logger.warning( - 'Dropped invalid phone # ' - '({}) specified.'.format(target), - ) + # store valid phone number + 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 diff --git a/apprise/plugins/NotifyPopcornNotify.py b/apprise/plugins/NotifyPopcornNotify.py index 81791518..7352c667 100644 --- a/apprise/plugins/NotifyPopcornNotify.py +++ b/apprise/plugins/NotifyPopcornNotify.py @@ -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[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) diff --git a/apprise/plugins/NotifySNS.py b/apprise/plugins/NotifySNS.py index 1cc161f3..3cc15a56 100644 --- a/apprise/plugins/NotifySNS.py +++ b/apprise/plugins/NotifySNS.py @@ -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[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 diff --git a/apprise/plugins/NotifySinch.py b/apprise/plugins/NotifySinch.py index c3cc3267..61ec452d 100644 --- a/apprise/plugins/NotifySinch.py +++ b/apprise/plugins/NotifySinch.py @@ -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[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,37 +228,18 @@ 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: - self.logger.warning( - 'Dropped invalid phone # ' - '({}) specified.'.format(target), - ) - continue - - # store valid phone number - self.targets.append('+{}'.format(result)) + 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), + ) 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) + # store valid phone number + 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 diff --git a/apprise/plugins/NotifyTwilio.py b/apprise/plugins/NotifyTwilio.py index 4ab19713..c4e5dc8e 100644 --- a/apprise/plugins/NotifyTwilio.py +++ b/apprise/plugins/NotifyTwilio.py @@ -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[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,37 +207,18 @@ 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: - self.logger.warning( - 'Dropped invalid phone # ' - '({}) specified.'.format(target), - ) - continue - - # store valid phone number - self.targets.append('+{}'.format(result)) + result = is_phone_no(target) + if not result: + self.logger.warning( + 'Dropped invalid phone # ' + '({}) specified.'.format(target), + ) 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) + # store valid phone number + self.targets.append('+{}'.format(result)) 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 diff --git a/apprise/utils.py b/apprise/utils.py index 3d5de6f4..120a2110 100644 --- a/apprise/utils.py +++ b/apprise/utils.py @@ -115,7 +115,7 @@ GET_SCHEMA_RE = re.compile(r'\s*(?P[a-z0-9]{2,9})://.*$', re.I) # - user@example.com # - label+user@example.com GET_EMAIL_RE = re.compile( - r'((?P[^:<]+)?[:<\s]+)?' + r'(([\s"\']+)?(?P[^:<"\']+)?[:<\s"\']+)?' r'(?P((?P