Better notification handling

This commit is contained in:
dgtlmoon
2025-10-16 12:08:08 +02:00
parent 9455b66e4d
commit 2c445bc6a4
4 changed files with 46 additions and 26 deletions
@@ -54,7 +54,10 @@ jobs:
- name: Spin up ancillary SMTP+Echo message test server
run: |
# Debug SMTP server/echo message back server
# Debug SMTP server/echo message back server, telnet 11080 to it should immediately bounce back the most recent message that tried to send (then you can see if cdio tried to send, the format, etc)
# 11025 is the SMTP port for testing
# apprise example would be 'mailto://changedetection@localhost:11025/?to=fff@home.com (it will also echo to STDOUT)
# telnet localhost 11080
docker run --network changedet-network -d -p 11025:11025 -p 11080:11080 --hostname mailserver test-changedetectionio bash -c 'pip3 install aiosmtpd && python changedetectionio/tests/smtp/smtp-test-server.py'
docker ps
+32
View File
@@ -6,6 +6,34 @@ from .apprise_plugin.assets import apprise_asset, APPRISE_AVATAR_URL
from ..notification_service import NotificationContextData
def markup_notification_message(body):
"""
Convert plaintext to HTML with clickable links.
Automatically linkifies URLs and converts newlines to <br>.
"""
from linkify_it import LinkifyIt
from html import escape
linkify = LinkifyIt()
# Escape HTML first
safe_body = escape(body)
# Convert URLs to links
matches = linkify.match(safe_body)
if matches:
# Process matches in reverse order to maintain string positions
for match in reversed(matches):
url = match.url
safe_body = (
safe_body[:match.index] +
f'<a href="{url}">{url}</a>' +
safe_body[match.last_index:]
)
# Convert newlines to <br>
return safe_body.replace('\n', '<br>')
def process_notification(n_object: NotificationContextData, datastore):
from changedetectionio.jinja2_custom import render as jinja_render
from . import default_notification_format_for_watch, default_notification_format, valid_notification_formats
@@ -53,6 +81,10 @@ def process_notification(n_object: NotificationContextData, datastore):
# Get the notification body from datastore
n_body = jinja_render(template_str=n_object.get('notification_body', ''), **notification_parameters)
if n_object.get('markup_text_to_html'):
n_body = markup_notification_message(body=n_body)
if n_object.get('notification_format', '').startswith('HTML'):
n_body = n_body.replace("\n", '<br>')
+8 -23
View File
@@ -30,7 +30,8 @@ class NotificationContextData(dict):
'diff_url': None,
'preview_url': None,
'watch_tag': None,
'watch_title': None
'watch_title': None,
'markup_text_to_html': False, # If automatic conversion of plaintext to HTML should happen
})
# Apply any initial data passed in
@@ -218,19 +219,6 @@ class NotificationService:
return queued
def markup_notification_message(self, body):
"""
Convert plaintext or Markdown to HTML.
Automatically linkifies URLs (requires markdown >= 3.4).
"""
import markdown
return markdown.markdown(
body,
extensions=["linkify", "nl2br", "sane_lists", "extra"]
)
def send_filter_failure_notification(self, watch_uuid):
"""
Send notification when CSS/XPath filters fail consecutively
@@ -245,6 +233,7 @@ class NotificationService:
body = f"""Hello,
Your configured CSS/xPath filters of '{filter_list}' for {{{{watch_url}}}} did not appear on the page after {threshold} attempts.
It's possible the page changed layout and the filter needs updating ( Try the 'Visual Selector' tab )
Edit link: {{{{base_url}}}}/edit/{{{{watch_uuid}}}}
@@ -252,14 +241,11 @@ Edit link: {{{{base_url}}}}/edit/{{{{watch_uuid}}}}
Thanks - Your omniscient changedetection.io installation.
"""
if n_format.lower().startswith('html'):
body = self.markup_notification_message(body=body)
n_object = NotificationContextData({
'notification_title': 'Changedetection.io - Alert - CSS/xPath filter was not present in the page',
'notification_body': body,
'notification_format': n_format
'notification_format': n_format,
'markup_text_to_html': n_format.lower().startswith('html')
})
if len(watch['notification_urls']):
@@ -294,6 +280,7 @@ Thanks - Your omniscient changedetection.io installation.
body = f"""Hello,
Your configured browser step at position {step} for the web page watch {{{{watch_url}}}} did not appear on the page after {threshold} attempts, did the page change layout?
The element may have moved and needs editing, or does it need a delay added?
Edit link: {{{{base_url}}}}/edit/{{{{watch_uuid}}}}
@@ -301,13 +288,11 @@ Edit link: {{{{base_url}}}}/edit/{{{{watch_uuid}}}}
Thanks - Your omniscient changedetection.io installation.
"""
if n_format.lower().startswith('html'):
body = self.markup_notification_message(body=body)
n_object = NotificationContextData({
'notification_title': f"Changedetection.io - Alert - Browser step at position {step} could not be run",
'notification_body': body,
'notification_format': n_format
'notification_format': n_format,
'markup_text_to_html': n_format.lower().startswith('html')
})
if len(watch['notification_urls']):
+2 -2
View File
@@ -42,8 +42,8 @@ jsonpath-ng~=1.5.3
# Notification library
apprise==1.9.5
# Should come with apprise but needed for HTML conversion of error notifications
markdown >=3.5
# Lightweight URL linkifier for notifications
linkify-it-py
# - Needed for apprise/spush, and maybe others? hopefully doesnt trigger a rust compile.
# - Requires extra wheel for rPi, adds build time for arm/v8 which is not in piwheels