mirror of
https://github.com/caronc/apprise.git
synced 2024-11-29 03:24:18 +01:00
Add Twilio API key support (#440)
This commit is contained in:
parent
e0f928f0d9
commit
3c552a686f
@ -101,7 +101,7 @@ The table below identifies the services this tool supports and some example serv
|
||||
| [MSG91](https://github.com/caronc/apprise/wiki/Notify_msg91) | msg91:// | (TCP) 443 | msg91://AuthKey/ToPhoneNo<br/>msg91://SenderID@AuthKey/ToPhoneNo<br/>msg91://AuthKey/ToPhoneNo1/ToPhoneNo2/ToPhoneNoN/
|
||||
| [Nexmo](https://github.com/caronc/apprise/wiki/Notify_nexmo) | nexmo:// | (TCP) 443 | nexmo://ApiKey:ApiSecret@FromPhoneNo<br/>nexmo://ApiKey:ApiSecret@FromPhoneNo/ToPhoneNo<br/>nexmo://ApiKey:ApiSecret@FromPhoneNo/ToPhoneNo1/ToPhoneNo2/ToPhoneNoN/
|
||||
| [Sinch](https://github.com/caronc/apprise/wiki/Notify_sinch) | sinch:// | (TCP) 443 | sinch://ServicePlanId:ApiToken@FromPhoneNo<br/>sinch://ServicePlanId:ApiToken@FromPhoneNo/ToPhoneNo<br/>sinch://ServicePlanId:ApiToken@FromPhoneNo/ToPhoneNo1/ToPhoneNo2/ToPhoneNoN/<br/>sinch://ServicePlanId:ApiToken@ShortCode/ToPhoneNo<br/>sinch://ServicePlanId:ApiToken@ShortCode/ToPhoneNo1/ToPhoneNo2/ToPhoneNoN/
|
||||
| [Twilio](https://github.com/caronc/apprise/wiki/Notify_twilio) | twilio:// | (TCP) 443 | twilio://AccountSid:AuthToken@FromPhoneNo<br/>twilio://AccountSid:AuthToken@FromPhoneNo/ToPhoneNo<br/>twilio://AccountSid:AuthToken@FromPhoneNo/ToPhoneNo1/ToPhoneNo2/ToPhoneNoN/<br/>twilio://AccountSid:AuthToken@ShortCode/ToPhoneNo<br/>twilio://AccountSid:AuthToken@ShortCode/ToPhoneNo1/ToPhoneNo2/ToPhoneNoN/
|
||||
| [Twilio](https://github.com/caronc/apprise/wiki/Notify_twilio) | twilio:// | (TCP) 443 | twilio://AccountSid:AuthToken@FromPhoneNo<br/>twilio://AccountSid:AuthToken@FromPhoneNo/ToPhoneNo<br/>twilio://AccountSid:AuthToken@FromPhoneNo/ToPhoneNo1/ToPhoneNo2/ToPhoneNoN/<br/>twilio://AccountSid:AuthToken@FromPhoneNo/ToPhoneNo?apikey=Key<br/>twilio://AccountSid:AuthToken@ShortCode/ToPhoneNo<br/>twilio://AccountSid:AuthToken@ShortCode/ToPhoneNo1/ToPhoneNo2/ToPhoneNoN/
|
||||
|
||||
## Desktop Notification Support
|
||||
| Notification Service | Service ID | Default Port | Example Syntax |
|
||||
|
@ -108,7 +108,7 @@ class NotifyTwilio(NotifyBase):
|
||||
'type': 'string',
|
||||
'private': True,
|
||||
'required': True,
|
||||
'regex': (r'^[a-f0-9]+$', 'i'),
|
||||
'regex': (r'^[a-z0-9]+$', 'i'),
|
||||
},
|
||||
'from_phone': {
|
||||
'name': _('From Phone No'),
|
||||
@ -150,10 +150,16 @@ class NotifyTwilio(NotifyBase):
|
||||
'token': {
|
||||
'alias_of': 'auth_token',
|
||||
},
|
||||
'apikey': {
|
||||
'name': _('API Key'),
|
||||
'type': 'string',
|
||||
'private': True,
|
||||
'regex': (r'^SK[a-f0-9]+$', 'i'),
|
||||
},
|
||||
})
|
||||
|
||||
def __init__(self, account_sid, auth_token, source, targets=None,
|
||||
**kwargs):
|
||||
apikey=None, ** kwargs):
|
||||
"""
|
||||
Initialize Twilio Object
|
||||
"""
|
||||
@ -177,6 +183,10 @@ class NotifyTwilio(NotifyBase):
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
# The API Key associated with the account (optional)
|
||||
self.apikey = validate_regex(
|
||||
apikey, *self.template_args['apikey']['regex'])
|
||||
|
||||
result = is_phone_no(source, min_len=5)
|
||||
if not result:
|
||||
msg = 'The Account (From) Phone # or Short-code specified ' \
|
||||
@ -218,7 +228,7 @@ class NotifyTwilio(NotifyBase):
|
||||
continue
|
||||
|
||||
# store valid phone number
|
||||
self.targets.append('+{}'.format(result))
|
||||
self.targets.append('+{}'.format(result['full']))
|
||||
|
||||
return
|
||||
|
||||
@ -259,8 +269,8 @@ class NotifyTwilio(NotifyBase):
|
||||
# Create a copy of the targets list
|
||||
targets = list(self.targets)
|
||||
|
||||
# Set up our authentication
|
||||
auth = (self.account_sid, self.auth_token)
|
||||
# Set up our authentication. Prefer the API Key if provided.
|
||||
auth = (self.apikey or self.account_sid, self.auth_token)
|
||||
|
||||
if len(targets) == 0:
|
||||
# No sources specified, use our own phone no
|
||||
@ -354,6 +364,10 @@ class NotifyTwilio(NotifyBase):
|
||||
# Our URL parameters
|
||||
params = self.url_parameters(privacy=privacy, *args, **kwargs)
|
||||
|
||||
if self.apikey is not None:
|
||||
# apikey specified; pass it back on the url
|
||||
params['apikey'] = self.apikey
|
||||
|
||||
return '{schema}://{sid}:{token}@{source}/{targets}/?{params}'.format(
|
||||
schema=self.secure_protocol,
|
||||
sid=self.pprint(
|
||||
@ -400,6 +414,10 @@ class NotifyTwilio(NotifyBase):
|
||||
results['account_sid'] = \
|
||||
NotifyTwilio.unquote(results['qsd']['sid'])
|
||||
|
||||
# API Key
|
||||
if 'apikey' in results['qsd'] and len(results['qsd']['apikey']):
|
||||
results['apikey'] = results['qsd']['apikey']
|
||||
|
||||
# Support the 'from' and 'source' variable so that we can support
|
||||
# targets this way too.
|
||||
# The 'from' makes it easier to use yaml configuration
|
||||
|
@ -289,7 +289,7 @@ def is_phone_no(phone, min_len=11):
|
||||
|
||||
Returns:
|
||||
bool: Returns False if the address specified is not a phone number
|
||||
and a dictionary of the parsed email if it is as:
|
||||
and a dictionary of the parsed phone number if it is as:
|
||||
{
|
||||
'country': '1',
|
||||
'area': '800',
|
||||
|
105
test/test_twilio.py
Normal file
105
test/test_twilio.py
Normal file
@ -0,0 +1,105 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2021 Chris Caron <lead2gold@gmail.com>
|
||||
# All rights reserved.
|
||||
#
|
||||
# This code is licensed under the MIT License.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files(the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions :
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
|
||||
import six
|
||||
import mock
|
||||
import requests
|
||||
from apprise import plugins
|
||||
from apprise import Apprise
|
||||
|
||||
# Disable logging for a cleaner testing output
|
||||
import logging
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
|
||||
@mock.patch('requests.post')
|
||||
def test_twilio_auth(mock_post):
|
||||
"""
|
||||
API: NotifyTwilio() Tests using:
|
||||
- account-wide auth token
|
||||
- API key and its own auth token
|
||||
|
||||
"""
|
||||
# Disable Throttling to speed testing
|
||||
plugins.NotifyBase.request_rate_per_sec = 0
|
||||
|
||||
response = mock.Mock()
|
||||
response.content = ''
|
||||
response.status_code = requests.codes.ok
|
||||
|
||||
# Prepare Mock
|
||||
mock_post.return_value = response
|
||||
|
||||
# Initialize some generic (but valid) tokens
|
||||
account_sid = 'AC{}'.format('b' * 32)
|
||||
apikey = 'SK{}'.format('b' * 32)
|
||||
auth_token = '{}'.format('b' * 32)
|
||||
source = '+1 (555) 123-3456'
|
||||
dest = '+1 (555) 987-6543'
|
||||
message_contents = "test"
|
||||
|
||||
# Variation of initialization without API key
|
||||
obj = Apprise.instantiate(
|
||||
'twilio://{}:{}@{}/{}'
|
||||
.format(account_sid, auth_token, source, dest))
|
||||
assert isinstance(obj, plugins.NotifyTwilio) is True
|
||||
assert isinstance(obj.url(), six.string_types) is True
|
||||
|
||||
# Send Notification
|
||||
assert obj.send(body=message_contents) is True
|
||||
|
||||
# Variation of initialization with API key
|
||||
obj = Apprise.instantiate(
|
||||
'twilio://{}:{}@{}/{}?apikey={}'
|
||||
.format(account_sid, auth_token, source, dest, apikey))
|
||||
assert isinstance(obj, plugins.NotifyTwilio) is True
|
||||
assert isinstance(obj.url(), six.string_types) is True
|
||||
|
||||
# Send Notification
|
||||
assert obj.send(body=message_contents) is True
|
||||
|
||||
# Validate expected call parameters
|
||||
assert mock_post.call_count == 2
|
||||
first_call = mock_post.call_args_list[0]
|
||||
second_call = mock_post.call_args_list[1]
|
||||
|
||||
# URL and message parameters are the same for both calls
|
||||
assert first_call[0][0] == \
|
||||
second_call[0][0] == \
|
||||
'https://api.twilio.com/2010-04-01/Accounts/{}/Messages.json'.format(
|
||||
account_sid)
|
||||
assert first_call[1]['data']['Body'] == \
|
||||
second_call[1]['data']['Body'] == \
|
||||
message_contents
|
||||
assert first_call[1]['data']['From'] == \
|
||||
second_call[1]['data']['From'] == \
|
||||
'+15551233456'
|
||||
assert first_call[1]['data']['To'] == \
|
||||
second_call[1]['data']['To'] == \
|
||||
'+15559876543'
|
||||
|
||||
# Auth differs depending on if API Key is used
|
||||
assert first_call[1]['auth'] == (account_sid, auth_token)
|
||||
assert second_call[1]['auth'] == (apikey, auth_token)
|
Loading…
Reference in New Issue
Block a user