diff --git a/changedetectionio/blueprint/settings/templates/settings.html b/changedetectionio/blueprint/settings/templates/settings.html
index 60d5e138f..cb1b9d24d 100644
--- a/changedetectionio/blueprint/settings/templates/settings.html
+++ b/changedetectionio/blueprint/settings/templates/settings.html
@@ -5,6 +5,7 @@
{% from '_common_fields.html' import render_common_settings_form %}
diff --git a/changedetectionio/blueprint/ui/notification.py b/changedetectionio/blueprint/ui/notification.py
index a9e523be0..41e0182c2 100644
--- a/changedetectionio/blueprint/ui/notification.py
+++ b/changedetectionio/blueprint/ui/notification.py
@@ -1,4 +1,4 @@
-from flask import Blueprint, request, make_response
+from flask import Blueprint, request, make_response, jsonify
import random
from loguru import logger
@@ -8,20 +8,28 @@ from changedetectionio.auth_decorator import login_optionally_required
def construct_blueprint(datastore: ChangeDetectionStore):
notification_blueprint = Blueprint('ui_notification', __name__, template_folder="../ui/templates")
-
+
+
+ @notification_blueprint.route("/notification/render-preview/", methods=['POST'])
+ @notification_blueprint.route("/notification/render-preview", methods=['POST'])
+ @notification_blueprint.route("/notification/render-preview/", methods=['POST'])
+ @login_optionally_required
+ def ajax_callback_test_render_preview(watch_uuid=None):
+ return ajax_callback_send_notification_test(watch_uuid=watch_uuid, send_as_null_test=True)
+
# AJAX endpoint for sending a test
@notification_blueprint.route("/notification/send-test/", methods=['POST'])
@notification_blueprint.route("/notification/send-test", methods=['POST'])
@notification_blueprint.route("/notification/send-test/", methods=['POST'])
@login_optionally_required
- def ajax_callback_send_notification_test(watch_uuid=None):
+ def ajax_callback_send_notification_test(watch_uuid=None, send_as_null_test=False):
# Watch_uuid could be unset in the case it`s used in tag editor, global settings
import apprise
from changedetectionio.notification.apprise_plugin.assets import apprise_asset
# Necessary so that we import our custom handlers
- from changedetectionio.notification.apprise_plugin.custom_handlers import apprise_http_custom_handler
+ from changedetectionio.notification.apprise_plugin.custom_handlers import apprise_http_custom_handler, apprise_null_custom_handler
apobj = apprise.Apprise(asset=apprise_asset)
sent_obj = {}
@@ -40,10 +48,12 @@ def construct_blueprint(datastore: ChangeDetectionStore):
watch = datastore.data['watching'].get(watch_uuid)
- notification_urls = None
+ notification_urls = []
+ if send_as_null_test:
+ notification_urls.append('null://null-test-just-to-render-everything-on-the-same-codepath-and-get-preview')
if request.form.get('notification_urls'):
- notification_urls = request.form['notification_urls'].strip().splitlines()
+ notification_urls += request.form['notification_urls'].strip().splitlines()
if not notification_urls:
logger.debug("Test notification - Trying by group/tag in the edit form if available")
diff --git a/changedetectionio/notification/apprise_plugin/custom_handlers.py b/changedetectionio/notification/apprise_plugin/custom_handlers.py
index 1fd28b416..a3293407e 100644
--- a/changedetectionio/notification/apprise_plugin/custom_handlers.py
+++ b/changedetectionio/notification/apprise_plugin/custom_handlers.py
@@ -19,6 +19,11 @@ def notify_supported_methods(func):
return func
+def notify_null_method(func):
+ func = notify(on="null")(func)
+ return func
+
+
def _get_auth(parsed_url: dict) -> str | tuple[str, str]:
user: str | None = parsed_url.get("user")
password: str | None = parsed_url.get("password")
@@ -110,3 +115,21 @@ def apprise_http_custom_handler(
except Exception as e:
logger.error(f"Unexpected error occurred while sending custom notification to {url}: {e}")
return False
+
+
+@notify_null_method
+def apprise_null_custom_handler(
+ body: str,
+ title: str,
+ notify_type: str,
+ meta: dict,
+ *args,
+ **kwargs,
+) -> bool:
+ url: str = meta.get("url")
+ schema: str = meta.get("schema")
+ method: str = re.sub(r"s$", "", schema).upper()
+ logger.info(f"Processed 'null' notification")
+
+ return True
+
diff --git a/changedetectionio/notification/handler.py b/changedetectionio/notification/handler.py
index fc7628fe3..c955c9f69 100644
--- a/changedetectionio/notification/handler.py
+++ b/changedetectionio/notification/handler.py
@@ -8,7 +8,10 @@ def process_notification(n_object, datastore):
from changedetectionio.safe_jinja import render as jinja_render
from . import default_notification_format_for_watch, default_notification_format, valid_notification_formats
# be sure its registered
- from .apprise_plugin.custom_handlers import apprise_http_custom_handler
+ from .apprise_plugin.custom_handlers import apprise_http_custom_handler, apprise_null_custom_handler
+
+ n_body = ''
+ n_title = ''
now = time.time()
if n_object.get('notification_timestamp'):
@@ -118,14 +121,15 @@ def process_notification(n_object, datastore):
'url': url,
'body_format': n_format})
- # Blast off the notifications tht are set in .add()
- apobj.notify(
- title=n_title,
- body=n_body,
- body_format=n_format,
- # False is not an option for AppRise, must be type None
- attach=n_object.get('screenshot', None)
- )
+ if n_object.get('notification_urls'):
+ # Blast off the notifications tht are set in .add()
+ apobj.notify(
+ title=n_title,
+ body=n_body,
+ body_format=n_format,
+ # False is not an option for AppRise, must be type None
+ attach=n_object.get('screenshot', None)
+ )
# Returns empty string if nothing found, multi-line string otherwise
diff --git a/changedetectionio/static/js/notifications.js b/changedetectionio/static/js/notifications.js
index dda20c714..61e2f8787 100644
--- a/changedetectionio/static/js/notifications.js
+++ b/changedetectionio/static/js/notifications.js
@@ -1,5 +1,18 @@
$(document).ready(function () {
+
+ function getNotificationData() {
+ data = {
+ notification_body: $('#notification_body').val(),
+ notification_format: $('#notification_format').val(),
+ notification_title: $('#notification_title').val(),
+ notification_urls: $('.notification-urls').val(),
+ tags: $('#tags').val(),
+ window_url: window.location.href,
+ }
+ return data
+ }
+
$('#add-email-helper').click(function (e) {
e.preventDefault();
email = prompt("Destination email");
@@ -15,7 +28,26 @@ $(document).ready(function () {
"Preview": "#notification-preview"
});
- function setPreview(content) {
+ $(document).on('click', '[data-target="#notification-preview"]', function (e) {
+ var data = getNotificationData();
+ $.ajax({
+ type: "POST",
+ url: notification_test_render_preview_rul,
+ data: data,
+/*
+ statusCode: {
+ 400: function (data) {
+ $("#notification-test-log>span").text(data.responseText);
+ },
+ }
+*/
+ }).done(function (data) {
+ setPreview(data['result']);
+ })
+
+ });
+
+ function setPreview(data) {
const iframe = document.getElementById("notification-iframe");
iframe.srcdoc = `
@@ -30,7 +62,7 @@ $(document).ready(function () {
}
- ${content}
+ ${data['body']}