Skip to content

Commit

Permalink
Support "attach" keyword (alias of "attachment") (#173)
Browse files Browse the repository at this point in the history
  • Loading branch information
caronc authored Feb 18, 2024
1 parent b4805f0 commit 479a3bd
Show file tree
Hide file tree
Showing 6 changed files with 150 additions and 8 deletions.
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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}`.
Expand Down
6 changes: 6 additions & 0 deletions apprise_api/api/templates/config.html
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,12 @@ <h6>{% trans "Using CURL" %}</h6>
&nbsp;&nbsp;&nbsp;&nbsp;-F attach1=@Screenshot-1.png \<br/>
&nbsp;&nbsp;&nbsp;&nbsp;-F attach2=@/my/path/to/Apprise.doc \<br/>
&nbsp;&nbsp;&nbsp;&nbsp;http{% if request.is_secure %}s{% endif %}://{{request.META.HTTP_HOST}}{{BASE_URL}}/notify/<em>{{key}}</em></code></pre>
{% blocktrans %}Sends a notification to our endpoints with an attachment{% endblocktrans %}
<pre><code class="bash">
curl -X POST \<br/>
&nbsp;&nbsp;&nbsp;&nbsp;-F "tag=all" \ <br/>
&nbsp;&nbsp;&nbsp;&nbsp;-F "attach=https://raw.githubusercontent.com/caronc/apprise/master/apprise/assets/themes/default/apprise-logo.png" \ <br/>
&nbsp;&nbsp;&nbsp;&nbsp;"{{request.scheme}}://{{request.META.HTTP_HOST}}{{BASE_URL}}/notify/<em>{{key}}</em>"</code></pre>
</p>
</div>
<div class="section">
Expand Down
11 changes: 11 additions & 0 deletions apprise_api/api/templates/welcome.html
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,11 @@ <h4>{% trans "Stateless Endpoints" %}</h4>
&nbsp;&nbsp;&nbsp;&nbsp;-F attach2=@Screenshot-2.png \ <br/>
&nbsp;&nbsp;&nbsp;&nbsp;"{{request.scheme}}://{{request.META.HTTP_HOST}}{{BASE_URL}}/notify/"
</code></pre>
<pre><code class="bash">
# {% blocktrans %}Send an web based file attachment to a <a href="https://github.com/caronc/apprise/wiki/Notify_discord" target="_blank">Discord</a> server:{% endblocktrans %}<br/>
curl -X POST -F 'urls=discord://credentials' \<br/>
&nbsp;&nbsp;&nbsp;&nbsp;-F "attach=https://raw.githubusercontent.com/caronc/apprise/master/apprise/assets/themes/default/apprise-logo.png" \ <br/>
&nbsp;&nbsp;&nbsp;&nbsp;"{{request.scheme}}://{{request.META.HTTP_HOST}}{{BASE_URL}}/notify/"</code></pre>
</div>
</li>
<li>
Expand Down Expand Up @@ -492,6 +497,12 @@ <h4>{% trans "Persistent Store Endpoints" %}</h4>
&nbsp;&nbsp;&nbsp;&nbsp;-F "tag=all" \ <br/>
&nbsp;&nbsp;&nbsp;&nbsp;-F "body=test body" \ <br/>
&nbsp;&nbsp;&nbsp;&nbsp;-F "title=test title" \ <br/>
&nbsp;&nbsp;&nbsp;&nbsp;"{{request.scheme}}://{{request.META.HTTP_HOST}}{{BASE_URL}}/notify/<em>{{key}}</em>"</code></pre>
<pre><code class="bash">
# {% blocktrans %}Sends a notification to our endpoints with an attachment{% endblocktrans %}<br/>
curl -X POST \<br/>
&nbsp;&nbsp;&nbsp;&nbsp;-F "tag=all" \ <br/>
&nbsp;&nbsp;&nbsp;&nbsp;-F "attach=https://raw.githubusercontent.com/caronc/apprise/master/apprise/assets/themes/default/apprise-logo.png" \ <br/>
&nbsp;&nbsp;&nbsp;&nbsp;"{{request.scheme}}://{{request.META.HTTP_HOST}}{{BASE_URL}}/notify/<em>{{key}}</em>"</code></pre>
<pre><code class="bash">
# {% blocktrans %}Notifies all URLs assigned the <em>devops</em> tag{% endblocktrans %}<br/>
Expand Down
45 changes: 44 additions & 1 deletion apprise_api/api/tests/test_notify.py
Original file line number Diff line number Diff line change
Expand Up @@ -847,6 +847,22 @@ def test_partial_notify_by_loaded_urls(self, mock_notify):
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):
"""
Expand Down Expand Up @@ -998,11 +1014,38 @@ def test_notify_by_loaded_urls_with_json(self, mock_notify):
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),
Expand Down
43 changes: 43 additions & 0 deletions apprise_api/api/tests/test_stateless_notify.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,49 @@ def test_partial_notify(self, mock_notify):
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):
Expand Down
39 changes: 32 additions & 7 deletions apprise_api/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -610,9 +610,19 @@ def post(self, request, key):

# 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:
Expand Down Expand Up @@ -1029,7 +1039,7 @@ def post(self, request):
logger.warning(
'NOTIFY - %s - Invalid FORM Payload provided',
request.META['REMOTE_ADDR'])

return HttpResponse(
_('Bad FORM Payload provided.'),
status=ResponseCode.bad_request)
Expand Down Expand Up @@ -1148,16 +1158,31 @@ def post(self, request):

# 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:
attach = parse_attachments(
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)
Expand Down

0 comments on commit 479a3bd

Please sign in to comment.