Skip to content

Commit

Permalink
Added result webhook support
Browse files Browse the repository at this point in the history
  • Loading branch information
caronc committed Oct 8, 2023
1 parent cd2135b commit 59a2bda
Show file tree
Hide file tree
Showing 4 changed files with 150 additions and 48 deletions.
59 changes: 58 additions & 1 deletion apprise_api/api/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@
import hashlib
import errno
import base64

import requests
from json import dumps
from django.conf import settings

# import the logging library
Expand Down Expand Up @@ -583,3 +584,59 @@ def apply_global_filters():
logger.warning(
'APPRISE_DENY_SERVICES plugin %s:// was not found -'
' ignoring.', name)


def send_webhook(payload):
"""
POST our webhook results
"""

# Prepare HTTP Headers
headers = {
'User-Agent': 'Apprise-API',
'Content-Type': 'application/json',
}

if not apprise.utils.VALID_URL_RE.match(
settings.APPRISE_WEBHOOK_RESULTS_URL):
logger.warning(
'The Apprise Webhook Result URL is not a valid web based URI')
return

# Parse our URL
results = apprise.URLBase.parse_url(settings.APPRISE_WEBHOOK_RESULTS_URL)
if not results:
logger.warning('The Apprise Webhook Result URL is not parseable')
return

if results['schema'] not in ('http', 'https'):
logger.warning(
'The Apprise Webhook Result URL is not using the HTTP protocol')
return

# Load our URL
base = apprise.URLBase(**results)

# Our Query String Dictionary; we use this to track arguments
# specified that aren't otherwise part of this class
params = {k: v for k, v in results.get('qsd', {}).items()
if k not in base.template_args}

try:
requests.post(
base.request_url,
data=dumps(payload),
params=params,
headers=headers,
auth=base.request_auth,
verify=base.verify_certificate,
timeout=base.request_timeout,
)

except requests.RequestException as e:
logger.warning(
'A Connection error occurred sending the Apprise Webhook '
'results to %s.' % base.url(privacy=True))
logger.debug('Socket Exception: %s' % str(e))

return
130 changes: 83 additions & 47 deletions apprise_api/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
from .utils import parse_attachments
from .utils import ConfigCache
from .utils import apply_global_filters
from .utils import send_webhook
from .forms import AddByUrlForm
from .forms import AddByConfigForm
from .forms import NotifyForm
Expand Down Expand Up @@ -823,51 +824,25 @@ def post(self, request, key):
level = request.headers.get(
'X-Apprise-Log-Level',
settings.LOGGING['loggers']['apprise']['level']).upper()
if level not in ('CRITICAL', 'ERROR' 'WARNING', 'INFO', 'DEBUG'):
level = settings.LOGGING['loggers']['apprise']['level'].upper()

# Initialize our response object
response = None

if level in ('CRITICAL', 'ERROR' 'WARNING', 'INFO', 'DEBUG'):
level = getattr(apprise.logging, level)
esc = '<!!-!ESC!-!!>'

esc = '<!!-!ESC!-!!>'
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']
# 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:
# Perform our notification at this point
result = a_obj.notify(
content.get('body'),
title=content.get('title', ''),
notify_type=content.get('type', apprise.NotifyType.INFO),
tag=content.get('tag'),
attach=attach,
)

if 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)

# Wrap logs in `<ul>` tag and escape our message body:
response = '<ul class="logs">{}</ul>'.format(
''.join([e[0] + escape(e[1]) + e[2] for e in entries]))

else: # content_type == 'text/plain'
response = logs.getvalue()

else:
# Perform our notification at this point without logging
# Now specify our format (and over-ride the default):
with apprise.LogCapture(level=level, fmt=fmt) as logs:
# Perform our notification at this point
result = a_obj.notify(
content.get('body'),
title=content.get('title', ''),
Expand All @@ -876,6 +851,34 @@ def post(self, request, key):
attach=attach,
)

if 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)

# Wrap logs in `<ul>` tag and escape our message body:
response = '<ul class="logs">{}</ul>'.format(
''.join([e[0] + escape(e[1]) + e[2] for e in entries]))

else: # content_type == 'text/plain'
response = logs.getvalue()

if settings.APPRISE_WEBHOOK_RESULTS_URL:
webhook_payload = {
'source': request.META['REMOTE_ADDR'],
'status': 0 if result else 1,
'output': response,
}

# Send our webhook (pass or fail)
send_webhook(
webhook_payload, settings.APPRISE_WEBHOOK_RESULTS_TIMEOUT)

if not result:
# If at least one notification couldn't be sent; change up
# the response to a 424 error code
Expand Down Expand Up @@ -1017,14 +1020,47 @@ def post(self, request):
attach = parse_attachments(
content.get('attachments'), request.FILES)

# Perform our notification at this point
result = a_obj.notify(
content.get('body'),
title=content.get('title', ''),
notify_type=content.get('type', apprise.NotifyType.INFO),
tag='all',
attach=attach,
)
# Acquire our log level from headers if defined, otherwise use
# the global one set in the settings
level = request.headers.get(
'X-Apprise-Log-Level',
settings.LOGGING['loggers']['apprise']['level']).upper()
if level not in ('CRITICAL', 'ERROR' 'WARNING', 'INFO', 'DEBUG'):
level = settings.LOGGING['loggers']['apprise']['level'].upper()

if settings.APPRISE_WEBHOOK_RESULTS_URL:
fmt = settings.LOGGING['formatters']['standard']['format']
with apprise.LogCapture(level=level, fmt=fmt) as logs:
# Perform our notification at this point
result = a_obj.notify(
content.get('body'),
title=content.get('title', ''),
notify_type=content.get('type', apprise.NotifyType.INFO),
tag='all',
attach=attach,
)

response = logs.getvalue()

webhook_payload = {
'source': request.META['REMOTE_ADDR'],
'status': 0 if result else 1,
'output': response,
}

# Send our webhook (pass or fail)
send_webhook(
webhook_payload, settings.APPRISE_WEBHOOK_RESULTS_TIMEOUT)

else:
# Perform our notification at this point
result = a_obj.notify(
content.get('body'),
title=content.get('title', ''),
notify_type=content.get('type', apprise.NotifyType.INFO),
tag='all',
attach=attach,
)

if not result:
# If at least one notification couldn't be sent; change up the
Expand Down
6 changes: 6 additions & 0 deletions apprise_api/core/settings/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,12 @@
APPRISE_DEFAULT_THEME = \
os.environ.get('APPRISE_DEFAULT_THEME', SiteTheme.LIGHT)

# Webhook that is posted to upon executed results
# Set it to something like https://myserver.com/path/
# Requets are done as a POST
APPRISE_WEBHOOK_RESULTS_URL = \
os.environ.get('APPRISE_WEBHOOK_RESULTS_URL', '')

# The location to store Apprise configuration files
APPRISE_CONFIG_DIR = os.environ.get(
'APPRISE_CONFIG_DIR', os.path.join(BASE_DIR, 'var', 'config'))
Expand Down
3 changes: 3 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ django
gevent
gunicorn

## for webhook support
requests

## 3rd Party Service support
paho-mqtt
gntp
Expand Down

0 comments on commit 59a2bda

Please sign in to comment.