mirror of
https://github.com/caronc/apprise-api.git
synced 2025-08-17 01:50:57 +02:00
Logging improvements and code tidying (#184)
This commit is contained in:
@ -23,8 +23,10 @@
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
from django.test import SimpleTestCase
|
||||
from django.core.exceptions import RequestDataTooBig
|
||||
from apprise import ConfigFormat
|
||||
from unittest.mock import patch
|
||||
from unittest import mock
|
||||
from django.test.utils import override_settings
|
||||
from ..forms import AUTO_DETECT_CONFIG_KEYWORD
|
||||
import json
|
||||
@ -139,6 +141,18 @@ class AddTests(SimpleTestCase):
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
with mock.patch('json.loads') as mock_loads:
|
||||
mock_loads.side_effect = RequestDataTooBig()
|
||||
# Send our notification by specifying the tag in the parameters
|
||||
response = self.client.post(
|
||||
'/add/{}'.format(key),
|
||||
data=json.dumps({'urls': 'mailto://user:pass@yahoo.ca'}),
|
||||
content_type='application/json',
|
||||
)
|
||||
|
||||
# Our notification failed
|
||||
assert response.status_code == 431
|
||||
|
||||
# Test with JSON (and no payload provided)
|
||||
response = self.client.post(
|
||||
'/add/{}'.format(key),
|
||||
|
@ -24,6 +24,7 @@
|
||||
# THE SOFTWARE.
|
||||
from django.test import SimpleTestCase, override_settings
|
||||
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||
from django.core.exceptions import RequestDataTooBig
|
||||
from unittest import mock
|
||||
import requests
|
||||
from ..forms import NotifyForm
|
||||
@ -577,6 +578,22 @@ class NotifyTests(SimpleTestCase):
|
||||
assert response['message'] == form_data['body']
|
||||
assert response['type'] == apprise.NotifyType.WARNING
|
||||
|
||||
# Test case where RequestDataTooBig thrown
|
||||
# Reset our mock object
|
||||
mock_post.reset_mock()
|
||||
|
||||
with mock.patch('json.loads') as mock_loads:
|
||||
mock_loads.side_effect = RequestDataTooBig()
|
||||
# Send our notification by specifying the tag in the parameters
|
||||
response = self.client.post(
|
||||
f'/notify/{key}?tag=home&body=test',
|
||||
form_data,
|
||||
content_type='application/json')
|
||||
|
||||
# Our notification failed
|
||||
assert response.status_code == 431
|
||||
assert mock_post.call_count == 0
|
||||
|
||||
@mock.patch('requests.post')
|
||||
def test_advanced_notify_with_tags(self, mock_post):
|
||||
"""
|
||||
@ -883,7 +900,7 @@ class NotifyTests(SimpleTestCase):
|
||||
|
||||
# Preare our JSON data
|
||||
json_data = {
|
||||
'body': 'test notifiction',
|
||||
'body': 'test notification',
|
||||
'type': apprise.NotifyType.WARNING,
|
||||
}
|
||||
|
||||
@ -1059,6 +1076,7 @@ class NotifyTests(SimpleTestCase):
|
||||
|
||||
headers = {
|
||||
'HTTP_X_APPRISE_LOG_LEVEL': 'debug',
|
||||
# Accept is over-ridden to be that of the content type
|
||||
'HTTP_ACCEPT': 'text/plain',
|
||||
}
|
||||
|
||||
@ -1074,13 +1092,14 @@ class NotifyTests(SimpleTestCase):
|
||||
assert mock_notify.call_count == 1
|
||||
assert response['content-type'] == 'text/plain'
|
||||
|
||||
mock_notify.reset_mock()
|
||||
|
||||
headers = {
|
||||
'HTTP_X_APPRISE_LOG_LEVEL': 'debug',
|
||||
# Accept is over-ridden to be that of the content type
|
||||
'HTTP_ACCEPT': 'text/html',
|
||||
}
|
||||
|
||||
mock_notify.reset_mock()
|
||||
|
||||
# Test referencing a key that doesn't exist
|
||||
response = self.client.post(
|
||||
'/notify/{}'.format(key),
|
||||
@ -1093,6 +1112,38 @@ class NotifyTests(SimpleTestCase):
|
||||
assert mock_notify.call_count == 1
|
||||
assert response['content-type'] == 'text/html'
|
||||
|
||||
mock_notify.reset_mock()
|
||||
|
||||
# Test referencing a key that doesn't exist
|
||||
response = self.client.post(
|
||||
'/notify/{}'.format(key),
|
||||
data={'body': 'test'},
|
||||
**headers,
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert mock_notify.call_count == 1
|
||||
assert response['content-type'].startswith('text/html')
|
||||
|
||||
mock_notify.reset_mock()
|
||||
|
||||
headers = {
|
||||
'HTTP_X_APPRISE_LOG_LEVEL': 'debug',
|
||||
'HTTP_ACCEPT': '*/*',
|
||||
}
|
||||
|
||||
# Test referencing a key that doesn't exist
|
||||
response = self.client.post(
|
||||
'/notify/{}'.format(key),
|
||||
data=json.dumps(json_data),
|
||||
content_type='application/json',
|
||||
**headers,
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert mock_notify.call_count == 1
|
||||
assert response['content-type'] == 'application/json'
|
||||
|
||||
headers = {
|
||||
'HTTP_X_APPRISE_LOG_LEVEL': 'invalid',
|
||||
'HTTP_ACCEPT': 'text/*',
|
||||
@ -1112,6 +1163,19 @@ class NotifyTests(SimpleTestCase):
|
||||
assert mock_notify.call_count == 1
|
||||
assert response['content-type'] == 'text/html'
|
||||
|
||||
mock_notify.reset_mock()
|
||||
|
||||
# Test referencing a key that doesn't exist
|
||||
response = self.client.post(
|
||||
'/notify/{}'.format(key),
|
||||
data=json_data,
|
||||
**headers,
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert mock_notify.call_count == 1
|
||||
assert response['content-type'].startswith('text/html')
|
||||
|
||||
@mock.patch('apprise.plugins.NotifyEmail.NotifyEmail.send')
|
||||
def test_notify_with_filters(self, mock_send):
|
||||
"""
|
||||
|
@ -24,6 +24,7 @@
|
||||
# THE SOFTWARE.
|
||||
from django.test import SimpleTestCase
|
||||
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||
from django.core.exceptions import RequestDataTooBig
|
||||
from django.test.utils import override_settings
|
||||
from unittest import mock
|
||||
from ..forms import NotifyByUrlForm
|
||||
@ -442,6 +443,22 @@ class StatelessNotifyTests(SimpleTestCase):
|
||||
assert response.status_code == 200
|
||||
assert mock_notify.call_count == 1
|
||||
|
||||
# Reset our count
|
||||
mock_notify.reset_mock()
|
||||
|
||||
with mock.patch('json.loads') as mock_loads:
|
||||
mock_loads.side_effect = RequestDataTooBig()
|
||||
# Send our notification
|
||||
response = self.client.post(
|
||||
'/notify/?title=my%20title&format=text&type=info',
|
||||
data=json.dumps(json_data),
|
||||
content_type='application/json',
|
||||
)
|
||||
|
||||
# Our notification failed
|
||||
assert response.status_code == 431
|
||||
assert mock_notify.call_count == 0
|
||||
|
||||
@mock.patch('apprise.Apprise.notify')
|
||||
def test_notify_by_loaded_urls_with_json(self, mock_notify):
|
||||
"""
|
||||
|
@ -27,6 +27,7 @@ from django.http import HttpResponse
|
||||
from django.http import JsonResponse
|
||||
from django.views import View
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import RequestDataTooBig
|
||||
from django.utils.html import escape
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views.decorators.cache import never_cache
|
||||
@ -55,7 +56,6 @@ logger = logging.getLogger('django')
|
||||
|
||||
# Content-Type Parsing
|
||||
# application/x-www-form-urlencoded
|
||||
# application/x-www-form-urlencoded
|
||||
# multipart/form-data
|
||||
MIME_IS_FORM = re.compile(
|
||||
r'(multipart|application)/(x-www-)?form-(data|urlencoded)', re.I)
|
||||
@ -68,6 +68,11 @@ MIME_IS_FORM = re.compile(
|
||||
MIME_IS_JSON = re.compile(
|
||||
r'(text|application)/(x-)?json', re.I)
|
||||
|
||||
# Parsing of Accept; the following amounts to Accept All
|
||||
# */*
|
||||
# <blank>
|
||||
ACCEPT_ALL = re.compile(r'^\s*([*]/[*]|)\s*$', re.I)
|
||||
|
||||
# Tags separated by space , &, or + are and'ed together
|
||||
# Tags separated by commas (even commas wrapped in spaces) are "or'ed" together
|
||||
# We start with a regular expression used to clean up provided tags
|
||||
@ -111,6 +116,7 @@ class ResponseCode(object):
|
||||
method_not_allowed = 405
|
||||
method_not_accepted = 406
|
||||
failed_dependency = 424
|
||||
fields_too_large = 431
|
||||
internal_server_error = 500
|
||||
|
||||
|
||||
@ -215,20 +221,27 @@ class AddView(View):
|
||||
"""
|
||||
Handle a POST request
|
||||
"""
|
||||
# Detect the format our response should be in
|
||||
json_response = \
|
||||
# Detect the format our incoming payload
|
||||
json_payload = \
|
||||
MIME_IS_JSON.match(
|
||||
request.content_type
|
||||
if request.content_type
|
||||
else request.headers.get(
|
||||
'accept', request.headers.get(
|
||||
'content-type', ''))) is not None
|
||||
'content-type', '')) is not None
|
||||
|
||||
# Detect the format our response should be in
|
||||
json_response = True if json_payload \
|
||||
and ACCEPT_ALL.match(request.headers.get('accept', '')) else \
|
||||
MIME_IS_JSON.match(request.headers.get('accept', '')) is not None
|
||||
|
||||
if settings.APPRISE_CONFIG_LOCK:
|
||||
# General Access Control
|
||||
msg = _('The site has been configured to deny this request.')
|
||||
logger.warning(
|
||||
'ADD - %s - Config Lock Active - Request Denied',
|
||||
request.META['REMOTE_ADDR'])
|
||||
msg = _('The site has been configured to deny this request')
|
||||
status = ResponseCode.no_access
|
||||
return HttpResponse(msg, status=status) \
|
||||
return HttpResponse(msg, status=status, content_type='text/plain') \
|
||||
if not json_response else JsonResponse({
|
||||
'error': msg,
|
||||
},
|
||||
@ -239,7 +252,7 @@ class AddView(View):
|
||||
|
||||
# our content
|
||||
content = {}
|
||||
if MIME_IS_FORM.match(request.content_type):
|
||||
if not json_payload:
|
||||
content = {}
|
||||
form = AddByConfigForm(request.POST)
|
||||
if form.is_valid():
|
||||
@ -249,27 +262,49 @@ class AddView(View):
|
||||
if form.is_valid():
|
||||
content.update(form.cleaned_data)
|
||||
|
||||
elif json_response:
|
||||
else: # JSON Payload
|
||||
# Prepare our default response
|
||||
try:
|
||||
# load our JSON content
|
||||
content = json.loads(request.body.decode('utf-8'))
|
||||
|
||||
except (RequestDataTooBig):
|
||||
# DATA_UPLOAD_MAX_MEMORY_SIZE exceeded it's value; this is usually the case
|
||||
# when there is a very large flie attachment that can't be pulled out of the
|
||||
# payload without exceeding memory limitations (default is 3MB)
|
||||
logger.warning(
|
||||
'ADD - %s - JSON Payload Exceeded %dMB; operaton aborted using KEY: %s',
|
||||
request.META['REMOTE_ADDR'], (settings.DATA_UPLOAD_MAX_MEMORY_SIZE / 1048576), key)
|
||||
|
||||
status = ResponseCode.fields_too_large
|
||||
msg = _('JSON Payload provided is to large')
|
||||
return HttpResponse(msg, status=status, content_type='text/plain') \
|
||||
if not json_response else JsonResponse({
|
||||
'error': msg,
|
||||
}, encoder=JSONEncoder, safe=False, status=status)
|
||||
|
||||
except (AttributeError, ValueError):
|
||||
# could not parse JSON response...
|
||||
return JsonResponse({
|
||||
'error': _('Invalid JSON specified.'),
|
||||
},
|
||||
encoder=JSONEncoder,
|
||||
safe=False,
|
||||
status=ResponseCode.bad_request,
|
||||
)
|
||||
logger.warning(
|
||||
'ADD - %s - Invalid JSON Payload provided using KEY: %s',
|
||||
request.META['REMOTE_ADDR'], key)
|
||||
|
||||
status = ResponseCode.bad_request
|
||||
msg = _('Invalid JSON Payload provided')
|
||||
return HttpResponse(msg, status=status, content_type='text/plain') \
|
||||
if not json_response else JsonResponse({
|
||||
'error': msg,
|
||||
}, encoder=JSONEncoder, safe=False, status=status)
|
||||
|
||||
if not content:
|
||||
# No information was posted
|
||||
msg = _('The message format is not supported.')
|
||||
logger.warning(
|
||||
'ADD - %s - Invalid payload structure provided using KEY: %s',
|
||||
request.META['REMOTE_ADDR'], key)
|
||||
|
||||
msg = _('Invalid payload structure provided')
|
||||
status = ResponseCode.bad_request
|
||||
return HttpResponse(msg, status=status) \
|
||||
return HttpResponse(msg, status=status, content_type='text/plain') \
|
||||
if not json_response else JsonResponse({
|
||||
'error': msg,
|
||||
},
|
||||
@ -285,9 +320,13 @@ class AddView(View):
|
||||
a_obj.add(content['urls'])
|
||||
if not len(a_obj):
|
||||
# No URLs were loaded
|
||||
msg = _('No valid URLs were found.')
|
||||
logger.warning(
|
||||
'ADD - %s - No valid URLs defined using KEY: %s',
|
||||
request.META['REMOTE_ADDR'], key)
|
||||
|
||||
msg = _('No valid URLs defined')
|
||||
status = ResponseCode.bad_request
|
||||
return HttpResponse(msg, status=status) \
|
||||
return HttpResponse(msg, status=status, content_type='text/plain') \
|
||||
if not json_response else JsonResponse({
|
||||
'error': msg,
|
||||
},
|
||||
@ -300,9 +339,12 @@ class AddView(View):
|
||||
key, '\r\n'.join([s.url() for s in a_obj]),
|
||||
apprise.ConfigFormat.TEXT):
|
||||
|
||||
msg = _('The configuration could not be saved.')
|
||||
logger.warning(
|
||||
'ADD - %s - configuration could not be saved using KEY: %s',
|
||||
request.META['REMOTE_ADDR'], key)
|
||||
msg = _('The configuration could not be saved')
|
||||
status = ResponseCode.internal_server_error
|
||||
return HttpResponse(msg, status=status) \
|
||||
return HttpResponse(msg, status=status, content_type='text/plain') \
|
||||
if not json_response else JsonResponse({
|
||||
'error': msg,
|
||||
},
|
||||
@ -315,9 +357,12 @@ class AddView(View):
|
||||
fmt = content.get('format', '').lower()
|
||||
if fmt not in [i[0] for i in CONFIG_FORMATS]:
|
||||
# Format must be one supported by apprise
|
||||
msg = _('The format specified is invalid.')
|
||||
logger.warning(
|
||||
'ADD - %s - Invalid configuration format specified (%s) using KEY: %s',
|
||||
request.META['REMOTE_ADDR'], fmt, key)
|
||||
msg = _('Invalid configuration format specified')
|
||||
status = ResponseCode.bad_request
|
||||
return HttpResponse(msg, status=status) \
|
||||
return HttpResponse(msg, status=status, content_type='text/plain') \
|
||||
if not json_response else JsonResponse({
|
||||
'error': msg,
|
||||
},
|
||||
@ -337,9 +382,12 @@ class AddView(View):
|
||||
# Load our configuration
|
||||
if not ac_obj.add_config(content['config'], format=fmt):
|
||||
# The format could not be detected
|
||||
msg = _('The configuration format could not be detected.')
|
||||
logger.warning(
|
||||
'ADD - %s - The configuration format could not be auto-detected using KEY: %s',
|
||||
request.META['REMOTE_ADDR'], key)
|
||||
msg = _('The configuration format could not be auto-detected')
|
||||
status = ResponseCode.bad_request
|
||||
return HttpResponse(msg, status=status) \
|
||||
return HttpResponse(msg, status=status, content_type='text/plain') \
|
||||
if not json_response else JsonResponse({
|
||||
'error': msg,
|
||||
},
|
||||
@ -354,9 +402,12 @@ class AddView(View):
|
||||
if not len(a_obj):
|
||||
# No specified URL(s) were loaded due to
|
||||
# mis-configuration on the caller's part
|
||||
msg = _('No valid URL(s) were specified.')
|
||||
logger.warning(
|
||||
'ADD - %s - No valid URL(s) defined using KEY: %s',
|
||||
request.META['REMOTE_ADDR'], key)
|
||||
msg = _('No valid URL(s) defined')
|
||||
status = ResponseCode.bad_request
|
||||
return HttpResponse(msg, status=status) \
|
||||
return HttpResponse(msg, status=status, content_type='text/plain') \
|
||||
if not json_response else JsonResponse({
|
||||
'error': msg,
|
||||
},
|
||||
@ -368,9 +419,12 @@ class AddView(View):
|
||||
if not ConfigCache.put(
|
||||
key, content['config'], fmt=ac_obj[0].config_format):
|
||||
# Something went very wrong; return 500
|
||||
msg = _('An error occured saving configuration.')
|
||||
logger.error(
|
||||
'ADD - %s - Configuration could not be saved using KEY: %s',
|
||||
request.META['REMOTE_ADDR'], key)
|
||||
msg = _('An error occured saving configuration')
|
||||
status = ResponseCode.internal_server_error
|
||||
return HttpResponse(msg, status=status) \
|
||||
return HttpResponse(msg, status=status, content_type='text/plain') \
|
||||
if not json_response else JsonResponse({
|
||||
'error': msg,
|
||||
},
|
||||
@ -381,9 +435,12 @@ class AddView(View):
|
||||
|
||||
else:
|
||||
# No configuration specified; we're done
|
||||
msg = _('No configuration specified.')
|
||||
msg = _('No configuration provided')
|
||||
logger.warning(
|
||||
'ADD - %s - No configuration provided using KEY: %s',
|
||||
request.META['REMOTE_ADDR'], key)
|
||||
status = ResponseCode.bad_request
|
||||
return HttpResponse(msg, status=status) \
|
||||
return HttpResponse(msg, status=status, content_type='text/plain') \
|
||||
if not json_response else JsonResponse({
|
||||
'error': msg,
|
||||
},
|
||||
@ -394,10 +451,16 @@ class AddView(View):
|
||||
|
||||
# If we reach here; we successfully loaded the configuration so we can
|
||||
# go ahead and write it to disk and alert our caller of the success.
|
||||
return HttpResponse(
|
||||
_('Successfully saved configuration.'),
|
||||
status=ResponseCode.okay,
|
||||
)
|
||||
logger.info(
|
||||
'ADD - %s - Configuration saved using KEY: %s',
|
||||
request.META['REMOTE_ADDR'], key)
|
||||
|
||||
status = ResponseCode.okay
|
||||
msg = _('Successfully saved configuration')
|
||||
return HttpResponse(msg, status=status, content_type='text/plain') \
|
||||
if not json_response else JsonResponse({
|
||||
"error": None,
|
||||
}, encoder=JSONEncoder, safe=False, status=status)
|
||||
|
||||
|
||||
@method_decorator(never_cache, name='dispatch')
|
||||
@ -420,9 +483,12 @@ class DelView(View):
|
||||
|
||||
if settings.APPRISE_CONFIG_LOCK:
|
||||
# General Access Control
|
||||
msg = _('The site has been configured to deny this request.')
|
||||
logger.warning(
|
||||
'DEL - %s - Config Lock Active - Request Denied',
|
||||
request.META['REMOTE_ADDR'])
|
||||
msg = _('The site has been configured to deny this request')
|
||||
status = ResponseCode.no_access
|
||||
return HttpResponse(msg, status=status) \
|
||||
return HttpResponse(msg, status=status, content_type='text/plain') \
|
||||
if not json_response else JsonResponse({
|
||||
'error': msg,
|
||||
},
|
||||
@ -434,9 +500,12 @@ class DelView(View):
|
||||
# Clear the key
|
||||
result = ConfigCache.clear(key)
|
||||
if result is None:
|
||||
msg = _('There was no configuration to remove.')
|
||||
logger.warning(
|
||||
'DEL - %s - No configuration associated using KEY: %s',
|
||||
request.META['REMOTE_ADDR'], key)
|
||||
msg = _('There was no configuration to remove')
|
||||
status = ResponseCode.no_content
|
||||
return HttpResponse(msg, status=status) \
|
||||
return HttpResponse(msg, status=status, content_type='text/plain') \
|
||||
if not json_response else JsonResponse({
|
||||
'error': msg,
|
||||
},
|
||||
@ -447,9 +516,13 @@ class DelView(View):
|
||||
|
||||
elif result is False:
|
||||
# There was a failure at the os level
|
||||
msg = _('The configuration could not be removed.')
|
||||
logger.error(
|
||||
'DEL - %s - Configuration could not be removed associated using KEY: %s',
|
||||
request.META['REMOTE_ADDR'], key)
|
||||
|
||||
msg = _('The configuration could not be removed')
|
||||
status = ResponseCode.internal_server_error
|
||||
return HttpResponse(msg, status=status) \
|
||||
return HttpResponse(msg, status=status, content_type='text/plain') \
|
||||
if not json_response else JsonResponse({
|
||||
'error': msg,
|
||||
},
|
||||
@ -459,10 +532,16 @@ class DelView(View):
|
||||
)
|
||||
|
||||
# Removed content
|
||||
return HttpResponse(
|
||||
_('Successfully removed configuration.'),
|
||||
status=ResponseCode.okay,
|
||||
)
|
||||
logger.info(
|
||||
'DEL - %s - Removed configuration associated using KEY: %s',
|
||||
request.META['REMOTE_ADDR'], key)
|
||||
|
||||
status = ResponseCode.okay
|
||||
msg = _('Successfully removed configuration')
|
||||
return HttpResponse(msg, status=status, content_type='text/plain') \
|
||||
if not json_response else JsonResponse({
|
||||
"error": None,
|
||||
}, encoder=JSONEncoder, safe=False, status=status)
|
||||
|
||||
|
||||
@method_decorator((gzip_page, never_cache), name='dispatch')
|
||||
@ -486,9 +565,13 @@ class GetView(View):
|
||||
|
||||
if settings.APPRISE_CONFIG_LOCK:
|
||||
# General Access Control
|
||||
msg = _('The site has been configured to deny this request.')
|
||||
logger.warning(
|
||||
'VIEW - %s - Config Lock Active - Request Denied',
|
||||
request.META['REMOTE_ADDR'])
|
||||
|
||||
msg = _('The site has been configured to deny this request')
|
||||
status = ResponseCode.no_access
|
||||
return HttpResponse(msg, status=status) \
|
||||
return HttpResponse(msg, status=status, content_type='text/plain') \
|
||||
if not json_response else JsonResponse(
|
||||
{'error': msg},
|
||||
encoder=JSONEncoder,
|
||||
@ -505,9 +588,12 @@ class GetView(View):
|
||||
# config != None: we simply have no data
|
||||
if format is not None:
|
||||
# no content to return
|
||||
msg = _('There was no configuration found.')
|
||||
logger.warning(
|
||||
'VIEW - %s - No configuration associated using KEY: %s',
|
||||
request.META['REMOTE_ADDR'], key)
|
||||
msg = _('There was no configuration found')
|
||||
status = ResponseCode.no_content
|
||||
return HttpResponse(msg, status=status) \
|
||||
return HttpResponse(msg, status=status, content_type='text/plain') \
|
||||
if not json_response else JsonResponse(
|
||||
{'error': msg},
|
||||
encoder=JSONEncoder,
|
||||
@ -515,9 +601,12 @@ class GetView(View):
|
||||
status=status)
|
||||
|
||||
# Something went very wrong; return 500
|
||||
msg = _('An error occured accessing configuration.')
|
||||
logger.error(
|
||||
'VIEW - %s - Configuration could not be accessed associated using KEY: %s',
|
||||
request.META['REMOTE_ADDR'], key)
|
||||
msg = _('An error occured accessing configuration')
|
||||
status = ResponseCode.internal_server_error
|
||||
return HttpResponse(msg, status=status) \
|
||||
return HttpResponse(msg, status=status, content_type='text/plain') \
|
||||
if not json_response else JsonResponse({
|
||||
'error': msg,
|
||||
},
|
||||
@ -532,9 +621,12 @@ class GetView(View):
|
||||
# reference to it through the --config (-c) option in the CLI.
|
||||
content_type = 'text/yaml; charset=utf-8' \
|
||||
if format == apprise.ConfigFormat.YAML \
|
||||
else 'text/html; charset=utf-8'
|
||||
else 'text/plain; charset=utf-8'
|
||||
|
||||
# Return our retrieved content
|
||||
logger.info(
|
||||
'VIEW - %s - Retrieved configuration associated using KEY: %s',
|
||||
request.META['REMOTE_ADDR'], key)
|
||||
return HttpResponse(
|
||||
config,
|
||||
content_type=content_type,
|
||||
@ -557,49 +649,69 @@ class NotifyView(View):
|
||||
"""
|
||||
Handle a POST request
|
||||
"""
|
||||
# Detect the format our response should be in
|
||||
json_response = \
|
||||
# Detect the format our incoming payload
|
||||
json_payload = \
|
||||
MIME_IS_JSON.match(
|
||||
request.content_type
|
||||
if request.content_type
|
||||
else request.headers.get(
|
||||
'accept', request.headers.get(
|
||||
'content-type', ''))) is not None
|
||||
'content-type', '')) is not None
|
||||
|
||||
# Detect the format our response should be in
|
||||
json_response = True if json_payload \
|
||||
and ACCEPT_ALL.match(request.headers.get('accept', '')) else \
|
||||
MIME_IS_JSON.match(request.headers.get('accept', '')) is not None
|
||||
|
||||
# our content
|
||||
content = {}
|
||||
if MIME_IS_FORM.match(request.content_type):
|
||||
if not json_payload:
|
||||
form = NotifyForm(data=request.POST, files=request.FILES)
|
||||
if form.is_valid():
|
||||
content.update(form.cleaned_data)
|
||||
|
||||
elif json_response:
|
||||
else: # JSON Payload
|
||||
# Prepare our default response
|
||||
try:
|
||||
# load our JSON content
|
||||
content = json.loads(request.body.decode('utf-8'))
|
||||
|
||||
except (RequestDataTooBig):
|
||||
# DATA_UPLOAD_MAX_MEMORY_SIZE exceeded it's value; this is usually the case
|
||||
# when there is a very large flie attachment that can't be pulled out of the
|
||||
# payload without exceeding memory limitations (default is 3MB)
|
||||
logger.warning(
|
||||
'NOTIFY - %s - JSON Payload Exceeded %dMB using KEY: %s',
|
||||
request.META['REMOTE_ADDR'], (settings.DATA_UPLOAD_MAX_MEMORY_SIZE / 1048576), key)
|
||||
|
||||
status = ResponseCode.fields_too_large
|
||||
msg = _('JSON Payload provided is to large')
|
||||
return HttpResponse(msg, status=status, content_type='text/plain') \
|
||||
if not json_response else JsonResponse({
|
||||
'error': msg,
|
||||
}, encoder=JSONEncoder, safe=False, status=status)
|
||||
|
||||
except (AttributeError, ValueError):
|
||||
# could not parse JSON response...
|
||||
logger.warning(
|
||||
'NOTIFY - %s - Invalid JSON Payload provided',
|
||||
request.META['REMOTE_ADDR'])
|
||||
'NOTIFY - %s - Invalid JSON Payload provided using KEY: %s',
|
||||
request.META['REMOTE_ADDR'], key)
|
||||
|
||||
return JsonResponse(
|
||||
_('Invalid JSON provided.'),
|
||||
encoder=JSONEncoder,
|
||||
safe=False,
|
||||
status=ResponseCode.bad_request)
|
||||
status = ResponseCode.bad_request
|
||||
msg = _('Invalid JSON Payload provided')
|
||||
return HttpResponse(msg, status=status, content_type='text/plain') \
|
||||
if not json_response else JsonResponse({
|
||||
'error': msg,
|
||||
}, encoder=JSONEncoder, safe=False, status=status)
|
||||
|
||||
if not content:
|
||||
# We could not handle the Content-Type
|
||||
logger.warning(
|
||||
'NOTIFY - %s - Invalid FORM Payload provided',
|
||||
request.META['REMOTE_ADDR'])
|
||||
'NOTIFY - %s - Invalid FORM Payload provided using KEY: %s',
|
||||
request.META['REMOTE_ADDR'], key)
|
||||
|
||||
msg = _('Bad FORM Payload provided.')
|
||||
msg = _('Bad FORM Payload provided')
|
||||
status = ResponseCode.bad_request
|
||||
return HttpResponse(msg, status=status) \
|
||||
return HttpResponse(msg, status=status, content_type='text/plain') \
|
||||
if not json_response else JsonResponse({
|
||||
'error': msg,
|
||||
},
|
||||
@ -632,12 +744,15 @@ class NotifyView(View):
|
||||
except (TypeError, ValueError) as e:
|
||||
# Invalid entry found in list
|
||||
logger.warning(
|
||||
'NOTIFY - %s - Bad attachment: %s',
|
||||
request.META['REMOTE_ADDR'], str(e))
|
||||
'NOTIFY - %s - Bad attachment using KEY: %s - %s',
|
||||
request.META['REMOTE_ADDR'], key, str(e))
|
||||
|
||||
return HttpResponse(
|
||||
_('Bad attachment'),
|
||||
status=ResponseCode.bad_request)
|
||||
status = ResponseCode.bad_request
|
||||
msg = _('Bad Attachment')
|
||||
return HttpResponse(msg, status=status, content_type='text/plain') \
|
||||
if not json_response else JsonResponse({
|
||||
'error': msg,
|
||||
}, encoder=JSONEncoder, safe=False, status=status)
|
||||
|
||||
#
|
||||
# Allow 'tag' value to be specified as part of the URL parameters
|
||||
@ -670,12 +785,12 @@ class NotifyView(View):
|
||||
# Invalid entry found in list
|
||||
logger.warning(
|
||||
'NOTIFY - %s - Ignored invalid tag specified '
|
||||
'(type %s): %s', request.META['REMOTE_ADDR'],
|
||||
str(type(tag)), str(tag)[:12])
|
||||
'(type %s): %s using KEY: %s', request.META['REMOTE_ADDR'],
|
||||
str(type(tag)), str(tag)[:12], key)
|
||||
|
||||
msg = _('Unsupported characters found in tag definition.')
|
||||
msg = _('Unsupported characters found in tag definition')
|
||||
status = ResponseCode.bad_request
|
||||
return HttpResponse(msg, status=status) \
|
||||
return HttpResponse(msg, status=status, content_type='text/plain') \
|
||||
if not json_response else JsonResponse({
|
||||
'error': msg,
|
||||
},
|
||||
@ -704,12 +819,12 @@ class NotifyView(View):
|
||||
else: # Could be int, float or some other unsupported type
|
||||
logger.warning(
|
||||
'NOTIFY - %s - Ignored invalid tag specified (type %s): '
|
||||
'%s', request.META['REMOTE_ADDR'],
|
||||
str(type(tag)), str(tag)[:12])
|
||||
'%s using KEY: %s', request.META['REMOTE_ADDR'],
|
||||
str(type(tag)), str(tag)[:12], key)
|
||||
|
||||
msg = _('Unsupported characters found in tag definition.')
|
||||
msg = _('Unsupported characters found in tag definition')
|
||||
status = ResponseCode.bad_request
|
||||
return HttpResponse(msg, status=status) \
|
||||
return HttpResponse(msg, status=status, content_type='text/plain') \
|
||||
if not json_response else JsonResponse({
|
||||
'error': msg,
|
||||
},
|
||||
@ -744,12 +859,14 @@ class NotifyView(View):
|
||||
not in apprise.NOTIFY_TYPES:
|
||||
|
||||
logger.warning(
|
||||
'NOTIFY - %s - Payload lacks minimum requirements',
|
||||
request.META['REMOTE_ADDR'])
|
||||
'NOTIFY - %s - Payload lacks minimum requirements using KEY: %s',
|
||||
request.META['REMOTE_ADDR'], key)
|
||||
|
||||
return HttpResponse(msg, status=status) \
|
||||
status = ResponseCode.bad_request
|
||||
msg = _('Payload lacks minimum requirements')
|
||||
return HttpResponse(msg, status=status, content_type='text/plain') \
|
||||
if not json_response else JsonResponse({
|
||||
'error': _('Payload lacks minimum requirements.'),
|
||||
'error': msg,
|
||||
},
|
||||
encoder=JSONEncoder,
|
||||
safe=False,
|
||||
@ -761,11 +878,11 @@ class NotifyView(View):
|
||||
if body_format and body_format not in apprise.NOTIFY_FORMATS:
|
||||
logger.warning(
|
||||
'NOTIFY - %s - Format parameter contains an unsupported '
|
||||
'value (%s)', request.META['REMOTE_ADDR'], str(body_format))
|
||||
'value (%s) using KEY: %s', request.META['REMOTE_ADDR'], str(body_format), key)
|
||||
|
||||
msg = _('An invalid body input format was specified.')
|
||||
msg = _('An invalid body input format was specified')
|
||||
status = ResponseCode.bad_request
|
||||
return HttpResponse(msg, status=status) \
|
||||
return HttpResponse(msg, status=status, content_type='text/plain') \
|
||||
if not json_response else JsonResponse({
|
||||
'error': msg,
|
||||
},
|
||||
@ -789,9 +906,10 @@ class NotifyView(View):
|
||||
logger.debug(
|
||||
'NOTIFY - %s - Empty configuration found using KEY: %s',
|
||||
request.META['REMOTE_ADDR'], key)
|
||||
msg = _('There was no configuration found.')
|
||||
|
||||
msg = _('There was no configuration found')
|
||||
status = ResponseCode.no_content
|
||||
return HttpResponse(msg, status=status) \
|
||||
return HttpResponse(msg, status=status, content_type='text/plain') \
|
||||
if not json_response else JsonResponse({
|
||||
'error': msg,
|
||||
},
|
||||
@ -800,13 +918,14 @@ class NotifyView(View):
|
||||
status=status,
|
||||
)
|
||||
|
||||
# Something went very wrong; return 500
|
||||
msg = _('An error occured accessing configuration.')
|
||||
status = ResponseCode.internal_server_error
|
||||
logger.error(
|
||||
'NOTIFY - %s - I/O error accessing configuration '
|
||||
'using KEY: %s', request.META['REMOTE_ADDR'], key)
|
||||
return HttpResponse(msg, status=status) \
|
||||
|
||||
# Something went very wrong; return 500
|
||||
msg = _('An error occured accessing configuration')
|
||||
status = ResponseCode.internal_server_error
|
||||
return HttpResponse(msg, status=status, content_type='text/plain') \
|
||||
if not json_response else JsonResponse({
|
||||
'error': msg,
|
||||
},
|
||||
@ -839,9 +958,13 @@ class NotifyView(View):
|
||||
'NOTIFY - %s - Recursion limit reached (%d > %d)',
|
||||
request.META['REMOTE_ADDR'], recursion,
|
||||
settings.APPRISE_RECURSION_MAX)
|
||||
return HttpResponse(
|
||||
_('The recursion limit has been reached.'),
|
||||
status=ResponseCode.method_not_accepted)
|
||||
|
||||
status = ResponseCode.method_not_accepted
|
||||
msg = _('The recursion limit has been reached')
|
||||
return HttpResponse(msg, status=status, content_type='text/plain') \
|
||||
if not json_response else JsonResponse({
|
||||
'error': msg,
|
||||
}, encoder=JSONEncoder, safe=False, status=status)
|
||||
|
||||
# Store our recursion value for our AppriseAsset() initialization
|
||||
kwargs['_recursion'] = recursion
|
||||
@ -850,9 +973,13 @@ class NotifyView(View):
|
||||
logger.warning(
|
||||
'NOTIFY - %s - Invalid recursion value (%s) provided',
|
||||
request.META['REMOTE_ADDR'], str(recursion))
|
||||
return HttpResponse(
|
||||
_('An invalid recursion value was specified.'),
|
||||
status=ResponseCode.bad_request)
|
||||
|
||||
status = ResponseCode.bad_request
|
||||
msg = _('An invalid recursion value was specified')
|
||||
return HttpResponse(msg, status=status, content_type='text/plain') \
|
||||
if not json_response else JsonResponse({
|
||||
'error': msg,
|
||||
}, encoder=JSONEncoder, safe=False, status=status)
|
||||
|
||||
# Acquire our unique identifier (if defined)
|
||||
uid = request.headers.get('X-Apprise-ID', '').strip()
|
||||
@ -884,11 +1011,15 @@ class NotifyView(View):
|
||||
# we return the logs as they're processed in their text format.
|
||||
# The HTML response type has a bit of overhead where as it's not
|
||||
# the case with text/plain
|
||||
content_type = \
|
||||
'text/html' if re.search(r'text\/(\*|html)',
|
||||
request.headers.get('Accept', ''),
|
||||
re.IGNORECASE) \
|
||||
else 'text/plain'
|
||||
if not json_response:
|
||||
content_type = \
|
||||
'text/html' if re.search(r'text\/(\*|html)',
|
||||
request.headers.get('Accept', ''),
|
||||
re.IGNORECASE) \
|
||||
else 'text/plain'
|
||||
|
||||
else:
|
||||
content_type = 'application/json'
|
||||
|
||||
# Acquire our log level from headers if defined, otherwise use
|
||||
# the global one set in the settings
|
||||
@ -923,13 +1054,17 @@ class NotifyView(View):
|
||||
|
||||
esc = '<!!-!ESC!-!!>'
|
||||
|
||||
# Format is only updated if the content_type is html
|
||||
fmt = '<li class="log_%(levelname)s">' \
|
||||
'<div class="log_time">%(asctime)s</div>' \
|
||||
'<div class="log_level">%(levelname)s</div>' \
|
||||
f'<div class="log_msg">{esc}%(message)s{esc}</div></li>' \
|
||||
if content_type == 'text/html' else \
|
||||
settings.LOGGING['formatters']['standard']['format']
|
||||
if json_response:
|
||||
fmt = f'["%(levelname)s","%(asctime)s","{esc}%(message)s{esc}"]'
|
||||
|
||||
else:
|
||||
# Format is only updated if the content_type is html
|
||||
fmt = '<li class="log_%(levelname)s">' \
|
||||
'<div class="log_time">%(asctime)s</div>' \
|
||||
'<div class="log_level">%(levelname)s</div>' \
|
||||
f'<div class="log_msg">{esc}%(message)s{esc}</div></li>' \
|
||||
if content_type == 'text/html' else \
|
||||
settings.LOGGING['formatters']['standard']['format']
|
||||
|
||||
# Now specify our format (and over-ride the default):
|
||||
with apprise.LogCapture(level=level, fmt=fmt) as logs:
|
||||
@ -942,15 +1077,25 @@ class NotifyView(View):
|
||||
attach=attach,
|
||||
)
|
||||
|
||||
if content_type == 'text/html':
|
||||
if json_response:
|
||||
esc = re.escape(esc)
|
||||
entries = re.findall(
|
||||
r'\r*\n?(?P<head>\["[^"]+","[^"]+",)"{}(?P<to_escape>.+?)'
|
||||
r'{}"(?P<tail>\]\r*\n?$)(?=$|\r*\n?\["[^"]+","[^"]+","{})'.format(
|
||||
esc, esc, esc), logs.getvalue(), re.DOTALL | re.MULTILINE)
|
||||
|
||||
# Prepare ourselves a JSON Response
|
||||
response = json.loads('[{}]'.format(
|
||||
','.join([e[0] + json.dumps(e[1].rstrip()) + e[2] for e in entries])))
|
||||
|
||||
elif content_type == 'text/html':
|
||||
# Iterate over our entries so that we can prepare to escape
|
||||
# things to be presented as HTML
|
||||
esc = re.escape(esc)
|
||||
entries = re.findall(
|
||||
r'(?P<head><li .+?){}(?P<to_escape>.*?)'
|
||||
r'{}(?P<tail>.+li>$)(?=$|<li .+{})'.format(
|
||||
esc, esc, esc), logs.getvalue(),
|
||||
re.DOTALL)
|
||||
r'\r*\n?(?P<head><li class="log_.+?){}(?P<to_escape>.+?)'
|
||||
r'{}(?P<tail></div></li>\r*\n?$)(?=$|\r*\n?<li class="log_.+{})'.format(
|
||||
esc, esc, esc), logs.getvalue(), re.DOTALL | re.MULTILINE)
|
||||
|
||||
# Wrap logs in `<ul>` tag and escape our message body:
|
||||
response = '<ul class="logs">{}</ul>'.format(
|
||||
@ -972,15 +1117,16 @@ class NotifyView(View):
|
||||
if not result:
|
||||
# If at least one notification couldn't be sent; change up
|
||||
# the response to a 424 error code
|
||||
msg = _('One or more notification could not be sent.')
|
||||
msg = _('One or more notification could not be sent')
|
||||
status = ResponseCode.failed_dependency
|
||||
logger.warning(
|
||||
'NOTIFY - %s - One or more notifications not '
|
||||
'sent%s using KEY: %s', request.META['REMOTE_ADDR'],
|
||||
'' if not tag else f' (Tags: {tag})', key)
|
||||
return HttpResponse(response if response else msg, status=status) \
|
||||
return HttpResponse(response if response else msg, status=status, content_type=content_type) \
|
||||
if not json_response else JsonResponse({
|
||||
'error': msg,
|
||||
'details': response,
|
||||
},
|
||||
encoder=JSONEncoder,
|
||||
safe=False,
|
||||
@ -992,13 +1138,13 @@ class NotifyView(View):
|
||||
request.META['REMOTE_ADDR'],
|
||||
'' if not tag else f'Tags: {tag}, ', key)
|
||||
|
||||
# Return our retrieved content
|
||||
return HttpResponse(
|
||||
response if response is not None else
|
||||
_('Notification(s) sent.'),
|
||||
content_type=content_type,
|
||||
status=ResponseCode.okay,
|
||||
)
|
||||
# Return our success message
|
||||
status = ResponseCode.okay
|
||||
return HttpResponse(response, status=status, content_type=content_type) \
|
||||
if not json_response else JsonResponse({
|
||||
'error': None,
|
||||
'details': response,
|
||||
}, encoder=JSONEncoder, safe=False, status=status)
|
||||
|
||||
|
||||
@method_decorator((gzip_page, never_cache), name='dispatch')
|
||||
@ -1010,29 +1156,60 @@ class StatelessNotifyView(View):
|
||||
"""
|
||||
Handle a POST request
|
||||
"""
|
||||
# Detect the format our incoming payload
|
||||
json_payload = \
|
||||
MIME_IS_JSON.match(
|
||||
request.content_type
|
||||
if request.content_type
|
||||
else request.headers.get(
|
||||
'content-type', '')) is not None
|
||||
|
||||
# Detect the format our response should be in
|
||||
json_response = True if json_payload \
|
||||
and ACCEPT_ALL.match(request.headers.get('accept', '')) else \
|
||||
MIME_IS_JSON.match(request.headers.get('accept', '')) is not None
|
||||
|
||||
# our content
|
||||
content = {}
|
||||
if MIME_IS_FORM.match(request.content_type):
|
||||
if not json_payload:
|
||||
content = {}
|
||||
form = NotifyByUrlForm(request.POST, request.FILES)
|
||||
if form.is_valid():
|
||||
content.update(form.cleaned_data)
|
||||
|
||||
elif MIME_IS_JSON.match(request.content_type):
|
||||
else: # JSON Payload
|
||||
# Prepare our default response
|
||||
try:
|
||||
# load our JSON content
|
||||
content = json.loads(request.body.decode('utf-8'))
|
||||
|
||||
except (RequestDataTooBig):
|
||||
# DATA_UPLOAD_MAX_MEMORY_SIZE exceeded it's value; this is usually the case
|
||||
# when there is a very large flie attachment that can't be pulled out of the
|
||||
# payload without exceeding memory limitations (default is 3MB)
|
||||
logger.warning(
|
||||
'NOTIFY - %s - JSON Payload Exceeded %dMB; operaton aborted',
|
||||
request.META['REMOTE_ADDR'], (settings.DATA_UPLOAD_MAX_MEMORY_SIZE / 1048576))
|
||||
|
||||
status = ResponseCode.fields_too_large
|
||||
msg = _('JSON Payload provided is to large')
|
||||
return HttpResponse(msg, status=status, content_type='text/plain') \
|
||||
if not json_response else JsonResponse({
|
||||
'error': msg,
|
||||
}, encoder=JSONEncoder, safe=False, status=status)
|
||||
|
||||
except (AttributeError, ValueError):
|
||||
# could not parse JSON response...
|
||||
logger.warning(
|
||||
'NOTIFY - %s - Invalid JSON Payload provided',
|
||||
request.META['REMOTE_ADDR'])
|
||||
|
||||
return HttpResponse(
|
||||
_('Invalid JSON specified.'),
|
||||
status=ResponseCode.bad_request)
|
||||
status = ResponseCode.bad_request
|
||||
msg = _('Invalid JSON Payload provided')
|
||||
return HttpResponse(msg, status=status, content_type='text/plain') \
|
||||
if not json_response else JsonResponse({
|
||||
'error': msg,
|
||||
}, encoder=JSONEncoder, safe=False, status=status)
|
||||
|
||||
if not content:
|
||||
# We could not handle the Content-Type
|
||||
@ -1040,9 +1217,12 @@ class StatelessNotifyView(View):
|
||||
'NOTIFY - %s - Invalid FORM Payload provided',
|
||||
request.META['REMOTE_ADDR'])
|
||||
|
||||
return HttpResponse(
|
||||
_('Bad FORM Payload provided.'),
|
||||
status=ResponseCode.bad_request)
|
||||
status = ResponseCode.bad_request
|
||||
msg = _('Bad FORM Payload provided')
|
||||
return HttpResponse(msg, status=status, content_type='text/plain') \
|
||||
if not json_response else JsonResponse({
|
||||
'error': msg,
|
||||
}, encoder=JSONEncoder, safe=False, status=status)
|
||||
|
||||
if not content.get('urls') and settings.APPRISE_STATELESS_URLS:
|
||||
# fallback to settings.APPRISE_STATELESS_URLS if no urls were
|
||||
@ -1079,9 +1259,12 @@ class StatelessNotifyView(View):
|
||||
'NOTIFY - %s - Payload lacks minimum requirements',
|
||||
request.META['REMOTE_ADDR'])
|
||||
|
||||
return HttpResponse(
|
||||
_('Payload lacks minimum requirements.'),
|
||||
status=ResponseCode.bad_request)
|
||||
status = ResponseCode.bad_request
|
||||
msg = _('Payload lacks minimum requirements')
|
||||
return HttpResponse(msg, status=status, content_type='text/plain') \
|
||||
if not json_response else JsonResponse({
|
||||
'error': msg,
|
||||
}, encoder=JSONEncoder, safe=False, status=status)
|
||||
|
||||
# Acquire our body format (if identified)
|
||||
body_format = content.get('format', apprise.NotifyFormat.TEXT)
|
||||
@ -1089,9 +1272,13 @@ class StatelessNotifyView(View):
|
||||
logger.warning(
|
||||
'NOTIFY - %s - Format parameter contains an unsupported '
|
||||
'value (%s)', request.META['REMOTE_ADDR'], str(body_format))
|
||||
return HttpResponse(
|
||||
_('An invalid body input format was specified.'),
|
||||
status=ResponseCode.bad_request)
|
||||
|
||||
status = ResponseCode.bad_request
|
||||
msg = _('An invalid body input format was specified')
|
||||
return HttpResponse(msg, status=status, content_type='text/plain') \
|
||||
if not json_response else JsonResponse({
|
||||
'error': msg,
|
||||
}, encoder=JSONEncoder, safe=False, status=status)
|
||||
|
||||
# Prepare our keyword arguments (to be passed into an AppriseAsset
|
||||
# object)
|
||||
@ -1117,9 +1304,13 @@ class StatelessNotifyView(View):
|
||||
'NOTIFY - %s - Recursion limit reached (%d > %d)',
|
||||
request.META['REMOTE_ADDR'], recursion,
|
||||
settings.APPRISE_RECURSION_MAX)
|
||||
return HttpResponse(
|
||||
_('The recursion limit has been reached.'),
|
||||
status=ResponseCode.method_not_accepted)
|
||||
|
||||
status = ResponseCode.method_not_accepted
|
||||
msg = _('The recursion limit has been reached')
|
||||
return HttpResponse(msg, status=status, content_type='text/plain') \
|
||||
if not json_response else JsonResponse({
|
||||
'error': msg,
|
||||
}, encoder=JSONEncoder, safe=False, status=status)
|
||||
|
||||
# Store our recursion value for our AppriseAsset() initialization
|
||||
kwargs['_recursion'] = recursion
|
||||
@ -1128,9 +1319,13 @@ class StatelessNotifyView(View):
|
||||
logger.warning(
|
||||
'NOTIFY - %s - Invalid recursion value (%s) provided',
|
||||
request.META['REMOTE_ADDR'], str(recursion))
|
||||
return HttpResponse(
|
||||
_('An invalid recursion value was specified.'),
|
||||
status=ResponseCode.bad_request)
|
||||
|
||||
status = ResponseCode.bad_request
|
||||
msg = _('An invalid recursion value was specified')
|
||||
return HttpResponse(msg, status=status, content_type='text/plain') \
|
||||
if not json_response else JsonResponse({
|
||||
'error': msg,
|
||||
}, encoder=JSONEncoder, safe=False, status=status)
|
||||
|
||||
# Acquire our unique identifier (if defined)
|
||||
uid = request.headers.get('X-Apprise-ID', '').strip()
|
||||
@ -1151,10 +1346,16 @@ class StatelessNotifyView(View):
|
||||
# Add URLs
|
||||
a_obj.add(content.get('urls'))
|
||||
if not len(a_obj):
|
||||
return HttpResponse(
|
||||
_('There was no services to notify.'),
|
||||
status=ResponseCode.no_content,
|
||||
)
|
||||
logger.warning(
|
||||
'NOTIFY - %s - No valid URLs provided',
|
||||
request.META['REMOTE_ADDR'])
|
||||
|
||||
status = ResponseCode.no_content
|
||||
msg = _('There was no valid URLs provided to notify')
|
||||
return HttpResponse(msg, status=status, content_type='text/plain') \
|
||||
if not json_response else JsonResponse({
|
||||
'error': msg,
|
||||
}, encoder=JSONEncoder, safe=False, status=status)
|
||||
|
||||
# Handle Attachments
|
||||
attach = None
|
||||
@ -1183,9 +1384,12 @@ class StatelessNotifyView(View):
|
||||
'NOTIFY - %s - Bad attachment: %s',
|
||||
request.META['REMOTE_ADDR'], str(e))
|
||||
|
||||
return HttpResponse(
|
||||
_('Bad attachment'),
|
||||
status=ResponseCode.bad_request)
|
||||
status = ResponseCode.bad_request
|
||||
msg = _('Bad Attachment')
|
||||
return HttpResponse(msg, status=status, content_type='text/plain') \
|
||||
if not json_response else JsonResponse({
|
||||
'error': msg,
|
||||
}, encoder=JSONEncoder, safe=False, status=status)
|
||||
|
||||
# Acquire our log level from headers if defined, otherwise use
|
||||
# the global one set in the settings
|
||||
@ -1216,7 +1420,8 @@ class StatelessNotifyView(View):
|
||||
level = logging.DEBUG - 1
|
||||
|
||||
if settings.APPRISE_WEBHOOK_URL:
|
||||
fmt = settings.LOGGING['formatters']['standard']['format']
|
||||
esc = '<!!-!ESC!-!!>'
|
||||
fmt = f'["%(levelname)s","%(asctime)s","{esc}%(message)s{esc}"]'
|
||||
with apprise.LogCapture(level=level, fmt=fmt) as logs:
|
||||
# Perform our notification at this point
|
||||
result = a_obj.notify(
|
||||
@ -1227,7 +1432,15 @@ class StatelessNotifyView(View):
|
||||
attach=attach,
|
||||
)
|
||||
|
||||
response = logs.getvalue()
|
||||
esc = re.escape(esc)
|
||||
entries = re.findall(
|
||||
r'\r*\n?(?P<head>\["[^"]+","[^"]+",)"{}(?P<to_escape>.+?)'
|
||||
r'{}"(?P<tail>\]\r*\n?$)(?=$|\r*\n?\["[^"]+","[^"]+","{})'.format(
|
||||
esc, esc, esc), logs.getvalue(), re.DOTALL | re.MULTILINE)
|
||||
|
||||
# Prepare ourselves a JSON Response
|
||||
response = json.loads('[{}]'.format(
|
||||
','.join([e[0] + json.dumps(e[1].rstrip()) + e[2] for e in entries])))
|
||||
|
||||
webhook_payload = {
|
||||
'source': request.META['REMOTE_ADDR'],
|
||||
@ -1251,19 +1464,27 @@ class StatelessNotifyView(View):
|
||||
if not result:
|
||||
# If at least one notification couldn't be sent; change up the
|
||||
# response to a 424 error code
|
||||
return HttpResponse(
|
||||
_('One or more notification could not be sent.'),
|
||||
status=ResponseCode.failed_dependency)
|
||||
logger.warning(
|
||||
'NOTIFY - %s - One or more stateless notification(s) could not be actioned',
|
||||
request.META['REMOTE_ADDR'])
|
||||
|
||||
status = ResponseCode.failed_dependency
|
||||
msg = _('One or more notification could not be sent')
|
||||
return HttpResponse(msg, status=status, content_type='text/plain') \
|
||||
if not json_response else JsonResponse({
|
||||
'error': msg,
|
||||
}, encoder=JSONEncoder, safe=False, status=status)
|
||||
|
||||
logger.info(
|
||||
'NOTIFY - %s - Delivered Stateless Notification(s)',
|
||||
request.META['REMOTE_ADDR'])
|
||||
|
||||
# Return our retrieved content
|
||||
return HttpResponse(
|
||||
_('Notification(s) sent.'),
|
||||
status=ResponseCode.okay,
|
||||
)
|
||||
# Return our success message
|
||||
status = ResponseCode.okay
|
||||
msg = _('Notification(s) sent')
|
||||
return HttpResponse(msg, status=status, content_type='text/plain') \
|
||||
if not json_response else JsonResponse({
|
||||
}, encoder=JSONEncoder, safe=False, status=status)
|
||||
|
||||
|
||||
@method_decorator((gzip_page, never_cache), name='dispatch')
|
||||
@ -1323,7 +1544,7 @@ class JsonUrlView(View):
|
||||
)
|
||||
|
||||
# Something went very wrong; return 500
|
||||
response['error'] = _('There was no configuration found.')
|
||||
response['error'] = _('There was no configuration found')
|
||||
return JsonResponse(
|
||||
response,
|
||||
encoder=JSONEncoder,
|
||||
|
Reference in New Issue
Block a user