Enable Markdown and HTML notifications. (#288)

This change enable defining the notification body as HTML or Markdown. This can be very
useful to have more user-friendly notifications such as:
* applying a heading style to the `{watch_title}` to make it stand out
* creating clickable links using the `{watch_url}`, `{preview_url}` and `{diff_url}`.

Changes
=======
* Add a `notification_format` to the notification settings, defaults to plain text.
* Use the `body_format` parameter of Apprise's `notify` method.

Co-authored-by: Malo Jaffré <malo.jaffre@dunnhumby.com>
pull/271/head
ghjklw 3 years ago committed by GitHub
parent ff6dc842c0
commit ecba130fdb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -415,6 +415,7 @@ def changedetection_app(config=None, datastore_o=None):
'trigger_text': form.trigger_text.data, 'trigger_text': form.trigger_text.data,
'notification_title': form.notification_title.data, 'notification_title': form.notification_title.data,
'notification_body': form.notification_body.data, 'notification_body': form.notification_body.data,
'notification_format': form.notification_format.data,
'extract_title_as_title': form.extract_title_as_title.data 'extract_title_as_title': form.extract_title_as_title.data
} }
@ -454,7 +455,8 @@ def changedetection_app(config=None, datastore_o=None):
n_object = {'watch_url': form.url.data.strip(), n_object = {'watch_url': form.url.data.strip(),
'notification_urls': form.notification_urls.data, 'notification_urls': form.notification_urls.data,
'notification_title': form.notification_title.data, 'notification_title': form.notification_title.data,
'notification_body' : form.notification_body.data 'notification_body' : form.notification_body.data,
'notification_format' : form.notification_format.data,
} }
notification_q.put(n_object) notification_q.put(n_object)
@ -501,6 +503,7 @@ def changedetection_app(config=None, datastore_o=None):
form.fetch_backend.data = datastore.data['settings']['application']['fetch_backend'] form.fetch_backend.data = datastore.data['settings']['application']['fetch_backend']
form.notification_title.data = datastore.data['settings']['application']['notification_title'] form.notification_title.data = datastore.data['settings']['application']['notification_title']
form.notification_body.data = datastore.data['settings']['application']['notification_body'] form.notification_body.data = datastore.data['settings']['application']['notification_body']
form.notification_format.data = datastore.data['settings']['application']['notification_format']
form.base_url.data = datastore.data['settings']['application']['base_url'] form.base_url.data = datastore.data['settings']['application']['base_url']
# Password unset is a GET # Password unset is a GET
@ -519,6 +522,7 @@ def changedetection_app(config=None, datastore_o=None):
datastore.data['settings']['application']['fetch_backend'] = form.fetch_backend.data datastore.data['settings']['application']['fetch_backend'] = form.fetch_backend.data
datastore.data['settings']['application']['notification_title'] = form.notification_title.data datastore.data['settings']['application']['notification_title'] = form.notification_title.data
datastore.data['settings']['application']['notification_body'] = form.notification_body.data datastore.data['settings']['application']['notification_body'] = form.notification_body.data
datastore.data['settings']['application']['notification_format'] = form.notification_format.data
datastore.data['settings']['application']['notification_urls'] = form.notification_urls.data datastore.data['settings']['application']['notification_urls'] = form.notification_urls.data
datastore.data['settings']['application']['base_url'] = form.base_url.data datastore.data['settings']['application']['base_url'] = form.base_url.data
@ -526,7 +530,8 @@ def changedetection_app(config=None, datastore_o=None):
n_object = {'watch_url': "Test from changedetection.io!", n_object = {'watch_url': "Test from changedetection.io!",
'notification_urls': form.notification_urls.data, 'notification_urls': form.notification_urls.data,
'notification_title': form.notification_title.data, 'notification_title': form.notification_title.data,
'notification_body': form.notification_body.data 'notification_body': form.notification_body.data,
'notification_format': form.notification_format.data,
} }
notification_q.put(n_object) notification_q.put(n_object)
flash('Notifications queued.') flash('Notifications queued.')

@ -6,6 +6,8 @@ from wtforms.fields import html5
from changedetectionio import content_fetcher from changedetectionio import content_fetcher
import re import re
from changedetectionio.notification import default_notification_format, valid_notification_formats
class StringListField(StringField): class StringListField(StringField):
widget = widgets.TextArea() widget = widgets.TextArea()
@ -203,6 +205,7 @@ class commonSettingsForm(Form):
notification_urls = StringListField('Notification URL List', validators=[validators.Optional(), ValidateAppRiseServers()]) notification_urls = StringListField('Notification URL List', validators=[validators.Optional(), ValidateAppRiseServers()])
notification_title = StringField('Notification Title', default='ChangeDetection.io Notification - {watch_url}', validators=[validators.Optional(), ValidateTokensList()]) notification_title = StringField('Notification Title', default='ChangeDetection.io Notification - {watch_url}', validators=[validators.Optional(), ValidateTokensList()])
notification_body = TextAreaField('Notification Body', default='{watch_url} had a change.', validators=[validators.Optional(), ValidateTokensList()]) notification_body = TextAreaField('Notification Body', default='{watch_url} had a change.', validators=[validators.Optional(), ValidateTokensList()])
notification_format = SelectField('Notification Format', choices=valid_notification_formats.keys(), default=default_notification_format)
trigger_check = BooleanField('Send test notification on save') trigger_check = BooleanField('Send test notification on save')
fetch_backend = RadioField(u'Fetch Method', choices=content_fetcher.available_fetchers(), validators=[ValidateContentFetcherIsReady()]) fetch_backend = RadioField(u'Fetch Method', choices=content_fetcher.available_fetchers(), validators=[ValidateContentFetcherIsReady()])
extract_title_as_title = BooleanField('Extract <title> from document and use as watch title', default=False) extract_title_as_title = BooleanField('Extract <title> from document and use as watch title', default=False)

@ -1,5 +1,6 @@
import os import os
import apprise import apprise
from apprise import NotifyFormat
valid_tokens = { valid_tokens = {
'base_url': '', 'base_url': '',
@ -12,6 +13,13 @@ valid_tokens = {
'current_snapshot': '' 'current_snapshot': ''
} }
valid_notification_formats = {
'Text': NotifyFormat.TEXT,
'Markdown': NotifyFormat.MARKDOWN,
'HTML': NotifyFormat.HTML,
}
default_notification_format = 'Text'
def process_notification(n_object, datastore): def process_notification(n_object, datastore):
import logging import logging
@ -27,6 +35,11 @@ def process_notification(n_object, datastore):
# Get the notification body from datastore # Get the notification body from datastore
n_body = n_object['notification_body'] n_body = n_object['notification_body']
n_title = n_object['notification_title'] n_title = n_object['notification_title']
n_format = valid_notification_formats.get(
n_object['notification_format'],
valid_notification_formats[default_notification_format],
)
# Insert variables into the notification content # Insert variables into the notification content
notification_parameters = create_notification_parameters(n_object, datastore) notification_parameters = create_notification_parameters(n_object, datastore)
@ -39,7 +52,8 @@ def process_notification(n_object, datastore):
apobj.notify( apobj.notify(
body=n_body, body=n_body,
title=n_title title=n_title,
body_format=n_format,
) )
# Notification title + body content parameters get created here. # Notification title + body content parameters get created here.

@ -47,6 +47,7 @@ class ChangeDetectionStore:
# Custom notification content # Custom notification content
'notification_title': None, 'notification_title': None,
'notification_body': None, 'notification_body': None,
'notification_format': None,
} }
} }
} }
@ -73,6 +74,7 @@ class ChangeDetectionStore:
'notification_urls': [], # List of URLs to add to the notification Queue (Usually AppRise) 'notification_urls': [], # List of URLs to add to the notification Queue (Usually AppRise)
'notification_title': None, 'notification_title': None,
'notification_body': None, 'notification_body': None,
'notification_format': None,
'css_filter': "", 'css_filter': "",
'trigger_text': [], # List of text or regex to wait for until a change is detected 'trigger_text': [], # List of text or regex to wait for until a change is detected
'fetch_backend': None, 'fetch_backend': None,

@ -24,6 +24,10 @@
{{ render_field(form.notification_body , rows=5) }} {{ render_field(form.notification_body , rows=5) }}
<span class="pure-form-message-inline">Body for all notifications</span> <span class="pure-form-message-inline">Body for all notifications</span>
</div> </div>
<div class="pure-control-group">
{{ render_field(form.notification_format , rows=5) }}
<span class="pure-form-message-inline">Format for all notifications</span>
</div>
<div class="pure-controls"> <div class="pure-controls">
<span class="pure-form-message-inline"> <span class="pure-form-message-inline">
These tokens can be used in the notification body and title to These tokens can be used in the notification body and title to

@ -56,6 +56,7 @@ def test_check_notification(client, live_server):
"Diff URL: {diff_url}\n" "Diff URL: {diff_url}\n"
"Snapshot: {current_snapshot}\n" "Snapshot: {current_snapshot}\n"
":-)", ":-)",
"notification_format": "Text",
"url": test_url, "url": test_url,
"tag": "my tag", "tag": "my tag",
"title": "my title", "title": "my title",
@ -181,6 +182,7 @@ def test_check_notification(client, live_server):
url_for("settings_page"), url_for("settings_page"),
data={"notification_title": "New ChangeDetection.io Notification - {watch_url}", data={"notification_title": "New ChangeDetection.io Notification - {watch_url}",
"notification_body": "Rubbish: {rubbish}\n", "notification_body": "Rubbish: {rubbish}\n",
"notification_format": "Text",
"notification_urls": "json://foobar.com", "notification_urls": "json://foobar.com",
"minutes_between_check": 180, "minutes_between_check": 180,
"fetch_backend": "html_requests" "fetch_backend": "html_requests"

@ -87,6 +87,7 @@ class update_worker(threading.Thread):
n_object['notification_urls'] = watch['notification_urls'] n_object['notification_urls'] = watch['notification_urls']
n_object['notification_title'] = watch['notification_title'] n_object['notification_title'] = watch['notification_title']
n_object['notification_body'] = watch['notification_body'] n_object['notification_body'] = watch['notification_body']
n_object['notification_format'] = watch['notification_format']
self.notification_q.put(n_object) self.notification_q.put(n_object)
# No? maybe theres a global setting, queue them all # No? maybe theres a global setting, queue them all
@ -95,6 +96,7 @@ class update_worker(threading.Thread):
n_object['notification_urls'] = self.datastore.data['settings']['application']['notification_urls'] n_object['notification_urls'] = self.datastore.data['settings']['application']['notification_urls']
n_object['notification_title'] = self.datastore.data['settings']['application']['notification_title'] n_object['notification_title'] = self.datastore.data['settings']['application']['notification_title']
n_object['notification_body'] = self.datastore.data['settings']['application']['notification_body'] n_object['notification_body'] = self.datastore.data['settings']['application']['notification_body']
n_object['notification_format'] = self.datastore.data['settings']['application']['notification_format']
self.notification_q.put(n_object) self.notification_q.put(n_object)
else: else:
print(">>> NO notifications queued, watch and global notification URLs were empty.") print(">>> NO notifications queued, watch and global notification URLs were empty.")

Loading…
Cancel
Save