diff --git a/apprise/Apprise.py b/apprise/Apprise.py index 7d37df61..8c2cf533 100644 --- a/apprise/Apprise.py +++ b/apprise/Apprise.py @@ -684,6 +684,7 @@ class Apprise: 'setup_url': getattr(plugin, 'setup_url', None), # Placeholder - populated below 'details': None, + # Differentiat between what is a custom loaded plugin and # which is native. 'category': getattr(plugin, 'category', None) diff --git a/apprise/plugins/NotifyAppriseAPI.py b/apprise/plugins/NotifyAppriseAPI.py index d2f1452a..8da9f6c6 100644 --- a/apprise/plugins/NotifyAppriseAPI.py +++ b/apprise/plugins/NotifyAppriseAPI.py @@ -33,6 +33,7 @@ import re import requests from json import dumps +import base64 from .NotifyBase import NotifyBase from ..URLBase import PrivacyMode @@ -209,15 +210,51 @@ class NotifyAppriseAPI(NotifyBase): token=self.pprint(self.token, privacy, safe=''), params=NotifyAppriseAPI.urlencode(params)) - def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs): + def send(self, body, title='', notify_type=NotifyType.INFO, attach=None, + **kwargs): """ Perform Apprise API Notification """ - headers = {} + # Prepare HTTP Headers + headers = { + 'User-Agent': self.app_id, + 'Content-Type': 'application/json' + } + # Apply any/all header over-rides defined headers.update(self.headers) + # Track our potential attachments + attachments = [] + if attach: + for attachment in attach: + # Perform some simple error checking + if not attachment: + # We could not access the attachment + self.logger.error( + 'Could not access attachment {}.'.format( + attachment.url(privacy=True))) + return False + + try: + with open(attachment.path, 'rb') as f: + # Output must be in a DataURL format (that's what + # PushSafer calls it): + attachments.append({ + 'filename': attachment.name, + 'base64': base64.b64encode(f.read()) + .decode('utf-8'), + 'mimetype': attachment.mimetype, + }) + + except (OSError, IOError) as e: + self.logger.warning( + 'An I/O error occurred while reading {}.'.format( + attachment.name if attachment else 'attachment')) + self.logger.debug('I/O Exception: %s' % str(e)) + return False + # prepare Apprise API Object payload = { # Apprise API Payload @@ -225,6 +262,7 @@ class NotifyAppriseAPI(NotifyBase): 'body': body, 'type': notify_type, 'format': self.notify_format, + 'attachments': attachments, } if self.__tags: diff --git a/test/test_plugin_apprise_api.py b/test/test_plugin_apprise_api.py index a7a17f17..4dd32e4c 100644 --- a/test/test_plugin_apprise_api.py +++ b/test/test_plugin_apprise_api.py @@ -30,14 +30,22 @@ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. +import os +from unittest import mock from apprise.plugins.NotifyAppriseAPI import NotifyAppriseAPI from helpers import AppriseURLTester import requests +from apprise import Apprise +from apprise import AppriseAttachment +from apprise import NotifyType # Disable logging for a cleaner testing output import logging logging.disable(logging.CRITICAL) +# Attachment Directory +TEST_VAR_DIR = os.path.join(os.path.dirname(__file__), 'var') + # Our Testing URLs apprise_url_tests = ( ('apprise://', { @@ -172,3 +180,62 @@ def test_plugin_apprise_urls(): # Run our general tests AppriseURLTester(tests=apprise_url_tests).run_all() + + +@mock.patch('requests.post') +def test_notify_apprise_api_attachments(mock_post): + """ + NotifyAppriseAPI() Attachments + + """ + + okay_response = requests.Request() + okay_response.status_code = requests.codes.ok + okay_response.content = "" + + # Assign our mock object our return value + mock_post.return_value = okay_response + + obj = Apprise.instantiate('apprise://user@localhost/mytoken1/') + assert isinstance(obj, NotifyAppriseAPI) + + # Test Valid Attachment + path = os.path.join(TEST_VAR_DIR, 'apprise-test.gif') + attach = AppriseAttachment(path) + assert obj.notify( + body='body', title='title', notify_type=NotifyType.INFO, + attach=attach) is True + + # Test invalid attachment + path = os.path.join(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 + + # 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( + 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