mirror of
https://github.com/caronc/apprise.git
synced 2025-01-08 15:09:27 +01:00
Apprise API FORM based Attachment Support Added (#877)
This commit is contained in:
parent
b0e64126e6
commit
80ae7228e9
@ -43,6 +43,20 @@ from ..utils import validate_regex
|
|||||||
from ..AppriseLocale import gettext_lazy as _
|
from ..AppriseLocale import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
|
class AppriseAPIMethod:
|
||||||
|
"""
|
||||||
|
Defines the method to post data tot he remote server
|
||||||
|
"""
|
||||||
|
JSON = 'json'
|
||||||
|
FORM = 'form'
|
||||||
|
|
||||||
|
|
||||||
|
APPRISE_API_METHODS = (
|
||||||
|
AppriseAPIMethod.FORM,
|
||||||
|
AppriseAPIMethod.JSON,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class NotifyAppriseAPI(NotifyBase):
|
class NotifyAppriseAPI(NotifyBase):
|
||||||
"""
|
"""
|
||||||
A wrapper for Apprise (Persistent) API Notifications
|
A wrapper for Apprise (Persistent) API Notifications
|
||||||
@ -65,7 +79,7 @@ class NotifyAppriseAPI(NotifyBase):
|
|||||||
|
|
||||||
# Depending on the number of transactions/notifications taking place, this
|
# Depending on the number of transactions/notifications taking place, this
|
||||||
# could take a while. 30 seconds should be enough to perform the task
|
# could take a while. 30 seconds should be enough to perform the task
|
||||||
socket_connect_timeout = 30.0
|
socket_read_timeout = 30.0
|
||||||
|
|
||||||
# Disable throttle rate for Apprise API requests since they are normally
|
# Disable throttle rate for Apprise API requests since they are normally
|
||||||
# local anyway
|
# local anyway
|
||||||
@ -120,6 +134,12 @@ class NotifyAppriseAPI(NotifyBase):
|
|||||||
'name': _('Tags'),
|
'name': _('Tags'),
|
||||||
'type': 'string',
|
'type': 'string',
|
||||||
},
|
},
|
||||||
|
'method': {
|
||||||
|
'name': _('Query Method'),
|
||||||
|
'type': 'choice:string',
|
||||||
|
'values': APPRISE_API_METHODS,
|
||||||
|
'default': APPRISE_API_METHODS[0],
|
||||||
|
},
|
||||||
'to': {
|
'to': {
|
||||||
'alias_of': 'token',
|
'alias_of': 'token',
|
||||||
},
|
},
|
||||||
@ -133,7 +153,8 @@ class NotifyAppriseAPI(NotifyBase):
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, token=None, tags=None, headers=None, **kwargs):
|
def __init__(self, token=None, tags=None, method=None, headers=None,
|
||||||
|
**kwargs):
|
||||||
"""
|
"""
|
||||||
Initialize Apprise API Object
|
Initialize Apprise API Object
|
||||||
|
|
||||||
@ -155,6 +176,14 @@ class NotifyAppriseAPI(NotifyBase):
|
|||||||
self.logger.warning(msg)
|
self.logger.warning(msg)
|
||||||
raise TypeError(msg)
|
raise TypeError(msg)
|
||||||
|
|
||||||
|
self.method = self.template_args['method']['default'] \
|
||||||
|
if not isinstance(method, str) else method.lower()
|
||||||
|
|
||||||
|
if self.method not in APPRISE_API_METHODS:
|
||||||
|
msg = 'The method specified ({}) is invalid.'.format(method)
|
||||||
|
self.logger.warning(msg)
|
||||||
|
raise TypeError(msg)
|
||||||
|
|
||||||
# Build list of tags
|
# Build list of tags
|
||||||
self.__tags = parse_list(tags)
|
self.__tags = parse_list(tags)
|
||||||
|
|
||||||
@ -170,8 +199,13 @@ class NotifyAppriseAPI(NotifyBase):
|
|||||||
Returns the URL built dynamically based on specified arguments.
|
Returns the URL built dynamically based on specified arguments.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Our URL parameters
|
# Define any URL parameters
|
||||||
params = self.url_parameters(privacy=privacy, *args, **kwargs)
|
params = {
|
||||||
|
'method': self.method,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Extend our parameters
|
||||||
|
params.update(self.url_parameters(privacy=privacy, *args, **kwargs))
|
||||||
|
|
||||||
# Append our headers into our parameters
|
# Append our headers into our parameters
|
||||||
params.update({'+{}'.format(k): v for k, v in self.headers.items()})
|
params.update({'+{}'.format(k): v for k, v in self.headers.items()})
|
||||||
@ -219,16 +253,15 @@ class NotifyAppriseAPI(NotifyBase):
|
|||||||
# Prepare HTTP Headers
|
# Prepare HTTP Headers
|
||||||
headers = {
|
headers = {
|
||||||
'User-Agent': self.app_id,
|
'User-Agent': self.app_id,
|
||||||
'Content-Type': 'application/json'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Apply any/all header over-rides defined
|
# Apply any/all header over-rides defined
|
||||||
headers.update(self.headers)
|
headers.update(self.headers)
|
||||||
|
|
||||||
# Track our potential attachments
|
|
||||||
attachments = []
|
attachments = []
|
||||||
|
files = []
|
||||||
if attach:
|
if attach:
|
||||||
for attachment in attach:
|
for no, attachment in enumerate(attach, start=1):
|
||||||
# Perform some simple error checking
|
# Perform some simple error checking
|
||||||
if not attachment:
|
if not attachment:
|
||||||
# We could not access the attachment
|
# We could not access the attachment
|
||||||
@ -238,15 +271,26 @@ class NotifyAppriseAPI(NotifyBase):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(attachment.path, 'rb') as f:
|
if self.method == AppriseAPIMethod.JSON:
|
||||||
# Output must be in a DataURL format (that's what
|
with open(attachment.path, 'rb') as f:
|
||||||
# PushSafer calls it):
|
# Output must be in a DataURL format (that's what
|
||||||
attachments.append({
|
# PushSafer calls it):
|
||||||
'filename': attachment.name,
|
attachments.append({
|
||||||
'base64': base64.b64encode(f.read())
|
'filename': attachment.name,
|
||||||
.decode('utf-8'),
|
'base64': base64.b64encode(f.read())
|
||||||
'mimetype': attachment.mimetype,
|
.decode('utf-8'),
|
||||||
})
|
'mimetype': attachment.mimetype,
|
||||||
|
})
|
||||||
|
|
||||||
|
else: # AppriseAPIMethod.FORM
|
||||||
|
files.append((
|
||||||
|
'file{:02d}'.format(no),
|
||||||
|
(
|
||||||
|
attachment.name,
|
||||||
|
open(attachment.path, 'rb'),
|
||||||
|
attachment.mimetype,
|
||||||
|
)
|
||||||
|
))
|
||||||
|
|
||||||
except (OSError, IOError) as e:
|
except (OSError, IOError) as e:
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
@ -262,9 +306,13 @@ class NotifyAppriseAPI(NotifyBase):
|
|||||||
'body': body,
|
'body': body,
|
||||||
'type': notify_type,
|
'type': notify_type,
|
||||||
'format': self.notify_format,
|
'format': self.notify_format,
|
||||||
'attachments': attachments,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.method == AppriseAPIMethod.JSON:
|
||||||
|
headers['Content-Type'] = 'application/json'
|
||||||
|
payload['attachments'] = attachments
|
||||||
|
payload = dumps(payload)
|
||||||
|
|
||||||
if self.__tags:
|
if self.__tags:
|
||||||
payload['tag'] = self.__tags
|
payload['tag'] = self.__tags
|
||||||
|
|
||||||
@ -285,8 +333,8 @@ class NotifyAppriseAPI(NotifyBase):
|
|||||||
|
|
||||||
# Some entries can not be over-ridden
|
# Some entries can not be over-ridden
|
||||||
headers.update({
|
headers.update({
|
||||||
'User-Agent': self.app_id,
|
# Our response to be in JSON format always
|
||||||
'Content-Type': 'application/json',
|
'Accept': 'application/json',
|
||||||
# Pass our Source UUID4 Identifier
|
# Pass our Source UUID4 Identifier
|
||||||
'X-Apprise-ID': self.asset._uid,
|
'X-Apprise-ID': self.asset._uid,
|
||||||
# Pass our current recursion count to our upstream server
|
# Pass our current recursion count to our upstream server
|
||||||
@ -304,9 +352,10 @@ class NotifyAppriseAPI(NotifyBase):
|
|||||||
try:
|
try:
|
||||||
r = requests.post(
|
r = requests.post(
|
||||||
url,
|
url,
|
||||||
data=dumps(payload),
|
data=payload,
|
||||||
headers=headers,
|
headers=headers,
|
||||||
auth=auth,
|
auth=auth,
|
||||||
|
files=files if files else None,
|
||||||
verify=self.verify_certificate,
|
verify=self.verify_certificate,
|
||||||
timeout=self.request_timeout,
|
timeout=self.request_timeout,
|
||||||
)
|
)
|
||||||
@ -328,7 +377,8 @@ class NotifyAppriseAPI(NotifyBase):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.logger.info('Sent Apprise API notification.')
|
self.logger.info(
|
||||||
|
'Sent Apprise API notification; method=%s.', self.method)
|
||||||
|
|
||||||
except requests.RequestException as e:
|
except requests.RequestException as e:
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
@ -339,6 +389,18 @@ class NotifyAppriseAPI(NotifyBase):
|
|||||||
# Return; we're done
|
# Return; we're done
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
except (OSError, IOError) as e:
|
||||||
|
self.logger.warning(
|
||||||
|
'An I/O error occurred while reading one of the '
|
||||||
|
'attached files.')
|
||||||
|
self.logger.debug('I/O Exception: %s' % str(e))
|
||||||
|
return False
|
||||||
|
|
||||||
|
finally:
|
||||||
|
for file in files:
|
||||||
|
# Ensure all files are closed
|
||||||
|
file[1][1].close()
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -415,4 +477,9 @@ class NotifyAppriseAPI(NotifyBase):
|
|||||||
# re-assemble our full path
|
# re-assemble our full path
|
||||||
results['fullpath'] = '/'.join(entries)
|
results['fullpath'] = '/'.join(entries)
|
||||||
|
|
||||||
|
# Set method if specified
|
||||||
|
if 'method' in results['qsd'] and len(results['qsd']['method']):
|
||||||
|
results['method'] = \
|
||||||
|
NotifyAppriseAPI.unquote(results['qsd']['method'])
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
@ -142,6 +142,20 @@ apprise_url_tests = (
|
|||||||
# Our expected url(privacy=True) startswith() response:
|
# Our expected url(privacy=True) startswith() response:
|
||||||
'privacy_url': 'apprises://localhost:8080/m...4/',
|
'privacy_url': 'apprises://localhost:8080/m...4/',
|
||||||
}),
|
}),
|
||||||
|
('apprises://localhost:8080/abc123/?method=json', {
|
||||||
|
'instance': NotifyAppriseAPI,
|
||||||
|
# Our expected url(privacy=True) startswith() response:
|
||||||
|
'privacy_url': 'apprises://localhost:8080/a...3/',
|
||||||
|
}),
|
||||||
|
('apprises://localhost:8080/abc123/?method=form', {
|
||||||
|
'instance': NotifyAppriseAPI,
|
||||||
|
# Our expected url(privacy=True) startswith() response:
|
||||||
|
'privacy_url': 'apprises://localhost:8080/a...3/',
|
||||||
|
}),
|
||||||
|
# Invalid method specified
|
||||||
|
('apprises://localhost:8080/abc123/?method=invalid', {
|
||||||
|
'instance': TypeError,
|
||||||
|
}),
|
||||||
('apprises://user:password@localhost:8080/mytoken5/', {
|
('apprises://user:password@localhost:8080/mytoken5/', {
|
||||||
'instance': NotifyAppriseAPI,
|
'instance': NotifyAppriseAPI,
|
||||||
|
|
||||||
@ -190,52 +204,65 @@ def test_notify_apprise_api_attachments(mock_post):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
okay_response = requests.Request()
|
okay_response = requests.Request()
|
||||||
okay_response.status_code = requests.codes.ok
|
|
||||||
okay_response.content = ""
|
|
||||||
|
|
||||||
# Assign our mock object our return value
|
for method in ('json', 'form'):
|
||||||
mock_post.return_value = okay_response
|
okay_response.status_code = requests.codes.ok
|
||||||
|
okay_response.content = ""
|
||||||
|
|
||||||
obj = Apprise.instantiate('apprise://user@localhost/mytoken1/')
|
# Assign our mock object our return value
|
||||||
assert isinstance(obj, NotifyAppriseAPI)
|
mock_post.return_value = okay_response
|
||||||
|
|
||||||
# Test Valid Attachment
|
obj = Apprise.instantiate(
|
||||||
path = os.path.join(TEST_VAR_DIR, 'apprise-test.gif')
|
'apprise://user@localhost/mytoken1/?method={}'.format(method))
|
||||||
attach = AppriseAttachment(path)
|
assert isinstance(obj, NotifyAppriseAPI)
|
||||||
assert obj.notify(
|
|
||||||
body='body', title='title', notify_type=NotifyType.INFO,
|
|
||||||
attach=attach) is True
|
|
||||||
|
|
||||||
# Test invalid attachment
|
# Test Valid Attachment
|
||||||
path = os.path.join(TEST_VAR_DIR, '/invalid/path/to/an/invalid/file.jpg')
|
path = os.path.join(TEST_VAR_DIR, 'apprise-test.gif')
|
||||||
assert obj.notify(
|
attach = AppriseAttachment(path)
|
||||||
body='body', title='title', notify_type=NotifyType.INFO,
|
|
||||||
attach=path) is False
|
|
||||||
|
|
||||||
# Test Valid Attachment (load 3)
|
|
||||||
path = (
|
|
||||||
os.path.join(TEST_VAR_DIR, 'apprise-test.gif'),
|
|
||||||
os.path.join(TEST_VAR_DIR, 'apprise-test.gif'),
|
|
||||||
os.path.join(TEST_VAR_DIR, 'apprise-test.gif'),
|
|
||||||
)
|
|
||||||
attach = AppriseAttachment(path)
|
|
||||||
|
|
||||||
# Return our good configuration
|
|
||||||
mock_post.side_effect = None
|
|
||||||
mock_post.return_value = okay_response
|
|
||||||
with mock.patch('builtins.open', side_effect=OSError()):
|
|
||||||
# We can't send the message we can't open the attachment for reading
|
|
||||||
assert obj.notify(
|
assert obj.notify(
|
||||||
body='body', title='title', notify_type=NotifyType.INFO,
|
body='body', title='title', notify_type=NotifyType.INFO,
|
||||||
attach=attach) is False
|
attach=attach) is True
|
||||||
|
|
||||||
# test the handling of our batch modes
|
# Test invalid attachment
|
||||||
obj = Apprise.instantiate('apprise://user@localhost/mytoken1/')
|
path = os.path.join(
|
||||||
assert isinstance(obj, NotifyAppriseAPI)
|
TEST_VAR_DIR, '/invalid/path/to/an/invalid/file.jpg')
|
||||||
|
assert obj.notify(
|
||||||
|
body='body', title='title', notify_type=NotifyType.INFO,
|
||||||
|
attach=path) is False
|
||||||
|
|
||||||
# Now send an attachment normally without issues
|
# Test Valid Attachment (load 3)
|
||||||
mock_post.reset_mock()
|
path = (
|
||||||
assert obj.notify(
|
os.path.join(TEST_VAR_DIR, 'apprise-test.gif'),
|
||||||
body='body', title='title', notify_type=NotifyType.INFO,
|
os.path.join(TEST_VAR_DIR, 'apprise-test.gif'),
|
||||||
attach=attach) is True
|
os.path.join(TEST_VAR_DIR, 'apprise-test.gif'),
|
||||||
assert mock_post.call_count == 1
|
)
|
||||||
|
attach = AppriseAttachment(path)
|
||||||
|
|
||||||
|
# Return our good configuration
|
||||||
|
mock_post.side_effect = None
|
||||||
|
mock_post.return_value = okay_response
|
||||||
|
with mock.patch('builtins.open', side_effect=OSError()):
|
||||||
|
# We can't send the message we can't open the attachment for
|
||||||
|
# reading
|
||||||
|
assert obj.notify(
|
||||||
|
body='body', title='title', notify_type=NotifyType.INFO,
|
||||||
|
attach=attach) is False
|
||||||
|
|
||||||
|
with mock.patch('requests.post', side_effect=OSError()):
|
||||||
|
# Attachment issue
|
||||||
|
assert obj.notify(
|
||||||
|
body='body', title='title', notify_type=NotifyType.INFO,
|
||||||
|
attach=attach) is False
|
||||||
|
|
||||||
|
# test the handling of our batch modes
|
||||||
|
obj = Apprise.instantiate('apprise://user@localhost/mytoken1/')
|
||||||
|
assert isinstance(obj, NotifyAppriseAPI)
|
||||||
|
|
||||||
|
# Now send an attachment normally without issues
|
||||||
|
mock_post.reset_mock()
|
||||||
|
|
||||||
|
assert obj.notify(
|
||||||
|
body='body', title='title', notify_type=NotifyType.INFO,
|
||||||
|
attach=attach) is True
|
||||||
|
assert mock_post.call_count == 1
|
||||||
|
mock_post.reset_mock()
|
||||||
|
Loading…
Reference in New Issue
Block a user