From 479a3bd6ed7e71bf0db82498634a8fb8366d5990 Mon Sep 17 00:00:00 2001 From: Chris Caron Date: Sat, 17 Feb 2024 21:39:11 -0500 Subject: [PATCH] Support "attach" keyword (alias of "attachment") (#173) --- README.md | 14 ++++++ apprise_api/api/templates/config.html | 6 +++ apprise_api/api/templates/welcome.html | 11 +++++ apprise_api/api/tests/test_notify.py | 45 ++++++++++++++++++- .../api/tests/test_stateless_notify.py | 43 ++++++++++++++++++ apprise_api/api/views.py | 39 +++++++++++++--- 6 files changed, 150 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index d08ed5c..a8b3a1d 100644 --- a/README.md +++ b/README.md @@ -168,6 +168,13 @@ curl -X POST -d 'urls=mailto://user:pass@gmail.com' \ curl -X POST -d '{"urls": "mailto://user:pass@gmail.com", "body":"test message"}' \ -H "Content-Type: application/json" \ http://localhost:8000/notify + +# attach= is an alias of attachment= +# Send a notification with a URL based attachment +curl -X POST \ + -F 'urls=mailto://user:pass@gmail.com' \ + -F attach=attach=https://raw.githubusercontent.com/caronc/apprise/master/apprise/assets/themes/default/apprise-logo.png \ + http://localhost:8000/notify ``` You can also send notifications that are URLs. Apprise will download the item so that it can send it along to all end points that should be notified about it. @@ -252,6 +259,13 @@ curl -X POST \ -F attach1=@Screenshot-1.png \ -F attach2=@/my/path/to/Apprise.doc \ http://localhost:8000/notify/abc123 + +# attach= is an alias of attachment= +# Send a notification with a URL based attachment +curl -X POST \ + -F 'urls=mailto://user:pass@gmail.com' \ + -F attach=attach=https://raw.githubusercontent.com/caronc/apprise/master/apprise/assets/themes/default/apprise-logo.png \ + http://localhost:8000/notify/abc123 ``` 🏷️ You can also leverage *tagging* which allows you to associate one or more tags with your Apprise URLs. By doing this, notifications only need to be referred to by their easy to remember notify tag name such as `devops`, `admin`, `family`, etc. You can very easily group more than one notification service under the same *tag* allowing you to notify a group of services at once. This is accomplished through configuration files ([documented here](https://github.com/caronc/apprise/wiki/config)) that can be saved to the persistent storage previously associated with a `{KEY}`. diff --git a/apprise_api/api/templates/config.html b/apprise_api/api/templates/config.html index 53cb98e..c91ed27 100644 --- a/apprise_api/api/templates/config.html +++ b/apprise_api/api/templates/config.html @@ -115,6 +115,12 @@     -F attach1=@Screenshot-1.png \
    -F attach2=@/my/path/to/Apprise.doc \
    http{% if request.is_secure %}s{% endif %}://{{request.META.HTTP_HOST}}{{BASE_URL}}/notify/{{key}} + {% blocktrans %}Sends a notification to our endpoints with an attachment{% endblocktrans %} +

+        curl -X POST \
+     -F "tag=all" \
+     -F "attach=https://raw.githubusercontent.com/caronc/apprise/master/apprise/assets/themes/default/apprise-logo.png" \
+     "{{request.scheme}}://{{request.META.HTTP_HOST}}{{BASE_URL}}/notify/{{key}}"

diff --git a/apprise_api/api/templates/welcome.html b/apprise_api/api/templates/welcome.html index 96cbd55..0592e83 100644 --- a/apprise_api/api/templates/welcome.html +++ b/apprise_api/api/templates/welcome.html @@ -99,6 +99,11 @@     -F attach2=@Screenshot-2.png \
    "{{request.scheme}}://{{request.META.HTTP_HOST}}{{BASE_URL}}/notify/" +

+                    # {% blocktrans %}Send an web based file attachment to a Discord server:{% endblocktrans %}
+ curl -X POST -F 'urls=discord://credentials' \
+     -F "attach=https://raw.githubusercontent.com/caronc/apprise/master/apprise/assets/themes/default/apprise-logo.png" \
+     "{{request.scheme}}://{{request.META.HTTP_HOST}}{{BASE_URL}}/notify/"
  • @@ -492,6 +497,12 @@     -F "tag=all" \
        -F "body=test body" \
        -F "title=test title" \
    +     "{{request.scheme}}://{{request.META.HTTP_HOST}}{{BASE_URL}}/notify/{{key}}" +
    
    +                        # {% blocktrans %}Sends a notification to our endpoints with an attachment{% endblocktrans %}
    + curl -X POST \
    +     -F "tag=all" \
    +     -F "attach=https://raw.githubusercontent.com/caronc/apprise/master/apprise/assets/themes/default/apprise-logo.png" \
        "{{request.scheme}}://{{request.META.HTTP_HOST}}{{BASE_URL}}/notify/{{key}}"
    
     								    # {% blocktrans %}Notifies all URLs assigned the devops tag{% endblocktrans %}
    diff --git a/apprise_api/api/tests/test_notify.py b/apprise_api/api/tests/test_notify.py index 6df89c7..eb598bb 100644 --- a/apprise_api/api/tests/test_notify.py +++ b/apprise_api/api/tests/test_notify.py @@ -847,6 +847,22 @@ class NotifyTests(SimpleTestCase): assert response.status_code == 400 assert mock_notify.call_count == 0 + # Reset our mock object + mock_notify.reset_mock() + + # Preare our form data + form_data = { + 'body': 'test notifiction', + 'attach': 'https://localhost/invalid/path/to/image.png', + } + + # Send our notification + response = self.client.post( + '/notify/{}'.format(key), form_data) + # We fail because we couldn't retrieve our attachment + assert response.status_code == 400 + assert mock_notify.call_count == 0 + @mock.patch('apprise.Apprise.notify') def test_notify_by_loaded_urls_with_json(self, mock_notify): """ @@ -998,11 +1014,38 @@ class NotifyTests(SimpleTestCase): assert response.status_code == 200 assert mock_notify.call_count == 1 + # Reset our mock object mock_notify.reset_mock() + # If an empty format is specified, it is accepted and + # no imput format is specified + json_data = { + 'body': 'test message', + 'format': None, + 'attach': 'https://localhost/invalid/path/to/image.png', + } + + # Test case with format changed + response = self.client.post( + '/notify/{}'.format(key), + data=json.dumps(json_data), + content_type='application/json', + ) + + # We failed to send notification because we couldn't fetch the + # attachment + assert response.status_code == 400 + assert mock_notify.call_count == 0 + + # Reset our mock object + mock_notify.reset_mock() + + json_data = { + 'body': 'test message', + } + # Same results for any empty string: - json_data['format'] = '' response = self.client.post( '/notify/{}'.format(key), data=json.dumps(json_data), diff --git a/apprise_api/api/tests/test_stateless_notify.py b/apprise_api/api/tests/test_stateless_notify.py index e487ef2..2a32d66 100644 --- a/apprise_api/api/tests/test_stateless_notify.py +++ b/apprise_api/api/tests/test_stateless_notify.py @@ -250,6 +250,49 @@ class StatelessNotifyTests(SimpleTestCase): assert response.status_code == 400 assert mock_notify.call_count == 0 + # Reset our mock object + mock_notify.reset_mock() + + # Preare our form data (support attach keyword) + form_data = { + 'body': 'test notifiction', + 'urls': ', '.join([ + 'mailto://user:pass@hotmail.com', + 'mailto://user:pass@gmail.com', + ]), + 'attach': 'https://localhost/invalid/path/to/image.png', + } + + # Send our notification + response = self.client.post('/notify', form_data) + # We fail because we couldn't retrieve our attachment + assert response.status_code == 400 + assert mock_notify.call_count == 0 + + # Reset our mock object + mock_notify.reset_mock() + + # Preare our json data (and support attach keyword as alias) + json_data = { + 'body': 'test notifiction', + 'urls': ', '.join([ + 'mailto://user:pass@hotmail.com', + 'mailto://user:pass@gmail.com', + ]), + 'attach': 'https://localhost/invalid/path/to/image.png', + } + + # Same results + response = self.client.post( + '/notify/', + data=json.dumps(json_data), + content_type='application/json', + ) + + # We fail because we couldn't retrieve our attachment + assert response.status_code == 400 + assert mock_notify.call_count == 0 + @override_settings(APPRISE_RECURSION_MAX=1) @mock.patch('apprise.Apprise.notify') def test_stateless_notify_recursion(self, mock_notify): diff --git a/apprise_api/api/views.py b/apprise_api/api/views.py index 4696256..6f9c586 100644 --- a/apprise_api/api/views.py +++ b/apprise_api/api/views.py @@ -610,9 +610,19 @@ class NotifyView(View): # Handle Attachments attach = None - if not content.get('attachment') and 'attachment' in request.POST: - # Acquire attachments to work with them - content['attachment'] = request.POST.getlist('attachment') + if not content.get('attachment'): + if 'attachment' in request.POST: + # Acquire attachments to work with them + content['attachment'] = request.POST.getlist('attachment') + + elif 'attach' in request.POST: + # Acquire kw (alias) attach to work with them + content['attachment'] = request.POST.getlist('attach') + + elif content.get('attach'): + # Acquire kw (alias) attach from payload to work with + content['attachment'] = content['attach'] + del content['attach'] if 'attachment' in content or request.FILES: try: @@ -1029,7 +1039,7 @@ class StatelessNotifyView(View): logger.warning( 'NOTIFY - %s - Invalid FORM Payload provided', request.META['REMOTE_ADDR']) - + return HttpResponse( _('Bad FORM Payload provided.'), status=ResponseCode.bad_request) @@ -1148,9 +1158,19 @@ class StatelessNotifyView(View): # Handle Attachments attach = None - if not content.get('attachment') and 'attachment' in request.POST: - # Acquire attachments to work with them - content['attachment'] = request.POST.getlist('attachment') + if not content.get('attachment'): + if 'attachment' in request.POST: + # Acquire attachments to work with them + content['attachment'] = request.POST.getlist('attachment') + + elif 'attach' in request.POST: + # Acquire kw (alias) attach to work with them + content['attachment'] = request.POST.getlist('attach') + + elif content.get('attach'): + # Acquire kw (alias) attach from payload to work with + content['attachment'] = content['attach'] + del content['attach'] if 'attachment' in content or request.FILES: try: @@ -1158,6 +1178,11 @@ class StatelessNotifyView(View): content.get('attachment'), request.FILES) except (TypeError, ValueError): + # Invalid entry found in list + logger.warning( + 'NOTIFY - %s - Bad attachment specified', + request.META['REMOTE_ADDR']) + return HttpResponse( _('Bad attachment'), status=ResponseCode.bad_request)