mirror of
https://github.com/dgtlmoon/changedetection.io.git
synced 2026-01-16 12:10:21 +00:00
Compare commits
1 Commits
brotli-nat
...
3740-html-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a6ce040397 |
@@ -86,6 +86,10 @@
|
||||
<div class="tab-pane-inner" id="notifications">
|
||||
<fieldset>
|
||||
{{ render_common_settings_form(form.application.form, emailprefix, settings_application, extra_notification_token_placeholder_info) }}
|
||||
</fieldset>
|
||||
<fieldset class="pure-group">
|
||||
{{ render_checkbox_field(form.application.form.notification_html_word_diff_enabled) }}
|
||||
<span class="pure-form-message-inline">HTML notifications - Use "word by word" difference where possible.</span>
|
||||
</fieldset>
|
||||
<div class="pure-control-group" id="notification-base-url">
|
||||
{{ render_field(form.application.form.base_url, class="m-d") }}
|
||||
|
||||
@@ -991,6 +991,7 @@ class globalSettingsApplicationForm(commonSettingsForm):
|
||||
render_kw={"placeholder": os.getenv('BASE_URL', 'Not set')}
|
||||
)
|
||||
empty_pages_are_a_change = BooleanField(_l('Treat empty pages as a change?'), default=False)
|
||||
notification_html_word_diff_enabled = BooleanField(_l('Notification HTML as word-by-word difference'), default=True, validators=[validators.Optional()])
|
||||
fetch_backend = RadioField(_l('Fetch Method'), default="html_requests", choices=content_fetchers.available_fetchers(), validators=[ValidateContentFetcherIsReady()])
|
||||
global_ignore_text = StringListField(_l('Ignore Text'), [ValidateListRegex()])
|
||||
global_subtractive_selectors = StringListField(_l('Remove elements'), [ValidateCSSJSONXPATHInput(allow_json=False)])
|
||||
|
||||
@@ -49,6 +49,7 @@ class model(dict):
|
||||
'ssim_threshold': '0.96', # Default SSIM threshold for screenshot comparison
|
||||
'notification_body': default_notification_body,
|
||||
'notification_format': default_notification_format,
|
||||
'notification_html_word_diff': True,
|
||||
'notification_title': default_notification_title,
|
||||
'notification_urls': [], # Apprise URL list
|
||||
'pager_size': 50,
|
||||
|
||||
@@ -309,6 +309,9 @@ def process_notification(n_object: NotificationContextData, datastore):
|
||||
if not isinstance(n_object, NotificationContextData):
|
||||
raise TypeError(f"Expected NotificationContextData, got {type(n_object)}")
|
||||
|
||||
if not n_object.get('notification_urls'):
|
||||
return None
|
||||
|
||||
now = time.time()
|
||||
if n_object.get('notification_timestamp'):
|
||||
logger.trace(f"Time since queued {now-n_object['notification_timestamp']:.3f}s")
|
||||
@@ -348,16 +351,15 @@ def process_notification(n_object: NotificationContextData, datastore):
|
||||
apprise.plugins.N_MGR.remove('discord')
|
||||
apprise.plugins.N_MGR.add(NotifyDiscordCustom, schemas='discord')
|
||||
|
||||
if not n_object.get('notification_urls'):
|
||||
return None
|
||||
# Should always be false for 'text' mode or its too hard to read, otherwise it's a setting (for html style).
|
||||
word_diff_enable = requested_output_format_original == 'text' or (
|
||||
n_object.get('notification_html_word_diff_enabled', True) and requested_output_format_original.startswith('html'))
|
||||
|
||||
n_object.update(add_rendered_diff_to_notification_vars(
|
||||
notification_scan_text=n_object.get('notification_body', '')+n_object.get('notification_title', ''),
|
||||
current_snapshot=n_object.get('current_snapshot'),
|
||||
prev_snapshot=n_object.get('prev_snapshot'),
|
||||
# Should always be false for 'text' mode or its too hard to read
|
||||
# But otherwise, this could be some setting
|
||||
word_diff=False if requested_output_format_original == 'text' else True,
|
||||
word_diff=word_diff_enable
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -250,6 +250,7 @@ class NotificationService:
|
||||
if n_object.get('notification_format') == USE_SYSTEM_DEFAULT_NOTIFICATION_FORMAT_FOR_WATCH:
|
||||
n_object['notification_format'] = self.datastore.data['settings']['application'].get('notification_format')
|
||||
|
||||
n_object['notification_html_word_diff_enabled'] = self.datastore.data['settings']['application'].get('notification_html_word_diff_enabled', True)
|
||||
|
||||
triggered_text = ''
|
||||
if len(trigger_text):
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
{% from '_helpers.html' import render_field %}
|
||||
{% from '_helpers.html' import render_field, render_checkbox_field %}
|
||||
|
||||
{% macro show_token_placeholders(extra_notification_token_placeholder_info, suffix="") %}
|
||||
|
||||
@@ -8,9 +8,7 @@
|
||||
<span class="pure-form-message-inline">
|
||||
Body for all notifications ‐ You can use <a target="newwindow" href="https://jinja.palletsprojects.com/en/3.0.x/templates/">Jinja2</a> templating in the notification title, body and URL, and tokens from below.
|
||||
</span><br>
|
||||
<div data-target="#notification-tokens-info{{ suffix }}" class="toggle-show pure-button button-tag button-xsmall">Show
|
||||
token/placeholders
|
||||
</div>
|
||||
<div data-target="#notification-tokens-info{{ suffix }}" class="toggle-show pure-button button-tag button-xsmall">Show extra help and tokens</div>
|
||||
</div>
|
||||
<div class="pure-controls" style="display: none;" id="notification-tokens-info{{ suffix }}">
|
||||
<table class="pure-table" id="token-table">
|
||||
@@ -105,11 +103,30 @@
|
||||
{% endif %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<span class="pure-form-message-inline">
|
||||
<br>
|
||||
<div class="pure-form-message-inline">
|
||||
Warning: Contents of <code>{{ '{{diff}}' }}</code>, <code>{{ '{{diff_removed}}' }}</code>, and <code>{{ '{{diff_added}}' }}</code> depend on how the difference algorithm perceives the change. <br>
|
||||
For example, an addition or removal could be perceived as a change in some cases. <a target="newwindow" href="https://github.com/dgtlmoon/changedetection.io/wiki/Using-the-%7B%7Bdiff%7D%7D,-%7B%7Bdiff_added%7D%7D,-and-%7B%7Bdiff_removed%7D%7D-notification-tokens">More Here</a> <br>
|
||||
</span>
|
||||
For example, an addition or removal could be perceived as a change in some cases. <a target="newwindow" href="https://github.com/dgtlmoon/changedetection.io/wiki/Using-the-%7B%7Bdiff%7D%7D,-%7B%7Bdiff_added%7D%7D,-and-%7B%7Bdiff_removed%7D%7D-notification-tokens">More Here</a> <br>
|
||||
</div>
|
||||
<br><br>
|
||||
<div class="pure-form-message-inline">
|
||||
<ul>
|
||||
<li><span class="pure-form-message-inline">
|
||||
For JSON payloads, use <strong>|tojson</strong> without quotes for automatic escaping, for example - <code>{ "name": {{ '{{ watch_title|tojson }}' }} }</code>
|
||||
</span></li>
|
||||
<li><span class="pure-form-message-inline">
|
||||
URL encoding, use <strong>|urlencode</strong>, for example - <code>gets://hook-website.com/test.php?title={{ '{{ watch_title|urlencode }}' }}</code>
|
||||
</span></li>
|
||||
<li><span class="pure-form-message-inline">
|
||||
Regular-expression replace, use <strong>|regex_replace</strong>, for example - <code>{{ "{{ \"hello world 123\" | regex_replace('[0-9]+', 'no-more-numbers') }}" }}</code>
|
||||
</span></li>
|
||||
<li><span class="pure-form-message-inline">
|
||||
For a complete reference of all Jinja2 built-in filters, users can refer to the <a
|
||||
href="https://jinja.palletsprojects.com/en/3.1.x/templates/#builtin-filters">https://jinja.palletsprojects.com/en/3.1.x/templates/#builtin-filters</a>
|
||||
</span></li>
|
||||
</ul>
|
||||
<br>
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
@@ -151,28 +168,11 @@
|
||||
{{ render_field(form.notification_title, class="m-d notification-title", placeholder=settings_application['notification_title']) }}
|
||||
<span class="pure-form-message-inline">Title for all notifications</span>
|
||||
</div>
|
||||
<div class="pure-control-group">
|
||||
<div>
|
||||
{{ render_field(form.notification_body , rows=5, class="notification-body", placeholder=settings_application['notification_body']) }}
|
||||
{{ show_token_placeholders(extra_notification_token_placeholder_info=extra_notification_token_placeholder_info) }}
|
||||
<div class="pure-form-message-inline">
|
||||
<ul>
|
||||
<li><span class="pure-form-message-inline">
|
||||
For JSON payloads, use <strong>|tojson</strong> without quotes for automatic escaping, for example - <code>{ "name": {{ '{{ watch_title|tojson }}' }} }</code>
|
||||
</span></li>
|
||||
<li><span class="pure-form-message-inline">
|
||||
URL encoding, use <strong>|urlencode</strong>, for example - <code>gets://hook-website.com/test.php?title={{ '{{ watch_title|urlencode }}' }}</code>
|
||||
</span></li>
|
||||
<li><span class="pure-form-message-inline">
|
||||
Regular-expression replace, use <strong>|regex_replace</strong>, for example - <code>{{ "{{ \"hello world 123\" | regex_replace('[0-9]+', 'no-more-numbers') }}" }}</code>
|
||||
</span></li>
|
||||
<li><span class="pure-form-message-inline">
|
||||
For a complete reference of all Jinja2 built-in filters, users can refer to the <a href="https://jinja.palletsprojects.com/en/3.1.x/templates/#builtin-filters">https://jinja.palletsprojects.com/en/3.1.x/templates/#builtin-filters</a>
|
||||
</span></li>
|
||||
</ul>
|
||||
<br>
|
||||
</div>
|
||||
</div>
|
||||
<div class="">
|
||||
<div>
|
||||
{{ render_field(form.notification_format , class="notification-format") }}
|
||||
<span class="pure-form-message-inline">Format for all notifications</span>
|
||||
</div>
|
||||
|
||||
@@ -532,7 +532,7 @@ def test_single_send_test_notification_on_watch(client, live_server, measure_mem
|
||||
assert 'Current snapshot: Example text: example test' in x
|
||||
os.unlink(os.path.join(datastore_path, "notification.txt"))
|
||||
|
||||
def _test_color_notifications(client, notification_body_token, datastore_path):
|
||||
def _test_color_notifications(client, notification_body_token, datastore_path, word_diff_enabled = True):
|
||||
|
||||
set_original_response(datastore_path=datastore_path)
|
||||
|
||||
@@ -551,6 +551,7 @@ def _test_color_notifications(client, notification_body_token, datastore_path):
|
||||
"application-minutes_between_check": 180,
|
||||
"application-notification_body": notification_body_token,
|
||||
"application-notification_format": "htmlcolor",
|
||||
"application-notification_html_word_diff_enabled": 'y' if word_diff_enabled else '',
|
||||
"application-notification_urls": test_notification_url,
|
||||
"application-notification_title": "New ChangeDetection.io Notification - {{ watch_url }}",
|
||||
},
|
||||
@@ -559,17 +560,13 @@ def _test_color_notifications(client, notification_body_token, datastore_path):
|
||||
assert b'Settings updated' in res.data
|
||||
|
||||
test_url = url_for('test_endpoint', _external=True)
|
||||
res = client.post(
|
||||
url_for("ui.ui_views.form_quick_watch_add"),
|
||||
data={"url": test_url, "tags": 'nice one'},
|
||||
follow_redirects=True
|
||||
)
|
||||
|
||||
assert b"Watch added" in res.data
|
||||
uuid = client.application.config.get('DATASTORE').add_watch(url=test_url)
|
||||
res = client.get(url_for("ui.form_watch_checknow"), follow_redirects=True)
|
||||
assert b'Queued 1 watch for rechecking.' in res.data
|
||||
|
||||
wait_for_all_checks(client)
|
||||
|
||||
set_modified_response(datastore_path=datastore_path)
|
||||
extras='XXX ' if word_diff_enabled else ''
|
||||
set_modified_response(datastore_path=datastore_path, extras=extras)
|
||||
|
||||
|
||||
res = client.get(url_for("ui.form_watch_checknow"), follow_redirects=True)
|
||||
@@ -579,9 +576,13 @@ def _test_color_notifications(client, notification_body_token, datastore_path):
|
||||
wait_for_notification_endpoint_output(datastore_path=datastore_path)
|
||||
|
||||
with open(os.path.join(datastore_path, "notification.txt"), 'r') as f:
|
||||
x = f.read()
|
||||
contents = f.read()
|
||||
s = f'<span style="{HTML_CHANGED_STYLE}" role="note" aria-label="Changed text" title="Changed text">Which is across multiple lines</span><br>'
|
||||
assert s in x
|
||||
assert s in contents
|
||||
if word_diff_enabled:
|
||||
assert '>XXX</span>' in contents
|
||||
else:
|
||||
assert '>XXX</span>' not in contents
|
||||
|
||||
client.get(
|
||||
url_for("ui.form_delete", uuid="all"),
|
||||
@@ -590,6 +591,12 @@ def _test_color_notifications(client, notification_body_token, datastore_path):
|
||||
|
||||
# Just checks the format of the colour notifications was correct
|
||||
def test_html_color_notifications(client, live_server, measure_memory_usage, datastore_path):
|
||||
_test_color_notifications(client, '{{diff}}',datastore_path=datastore_path)
|
||||
_test_color_notifications(client, '{{diff_full}}',datastore_path=datastore_path)
|
||||
# Word-level diff only triggers when difflib.SequenceMatcher identifies a single-line to single-line replacement.
|
||||
# If you have multiple changed lines close together, you need at least 1 unchanged content line (not empty) between them to
|
||||
# prevent them from being grouped into a multi-line replacement that falls back to line-level diff.
|
||||
|
||||
_test_color_notifications(client, '{{diff}}',datastore_path=datastore_path, word_diff_enabled = True)
|
||||
_test_color_notifications(client, '{{diff_full}}',datastore_path=datastore_path, word_diff_enabled = True)
|
||||
|
||||
_test_color_notifications(client, '{{diff}}',datastore_path=datastore_path, word_diff_enabled = False)
|
||||
_test_color_notifications(client, '{{diff_full}}',datastore_path=datastore_path, word_diff_enabled = False)
|
||||
@@ -7,7 +7,7 @@ import logging
|
||||
import time
|
||||
import os
|
||||
|
||||
def set_original_response(datastore_path, extra_title=''):
|
||||
def set_original_response(datastore_path, extra_title='', extras=''):
|
||||
test_return_data = f"""<html>
|
||||
<head><title>head title{extra_title}</title></head>
|
||||
<body>
|
||||
@@ -15,6 +15,9 @@ def set_original_response(datastore_path, extra_title=''):
|
||||
<p>Which is across multiple lines</p>
|
||||
<br>
|
||||
So let's see what happens. <br>
|
||||
with more text that helps word-diff if needed<br>
|
||||
and more text that helps word-diff if needed<br>
|
||||
and even more text {extras}that helps word-diff if needed<br>
|
||||
<span class="foobar-detection" style='display:none'></span>
|
||||
</body>
|
||||
</html>
|
||||
@@ -24,14 +27,17 @@ def set_original_response(datastore_path, extra_title=''):
|
||||
f.write(test_return_data)
|
||||
return None
|
||||
|
||||
def set_modified_response(datastore_path):
|
||||
test_return_data = """<html>
|
||||
def set_modified_response(datastore_path, extras=''):
|
||||
test_return_data =f"""<html>
|
||||
<head><title>modified head title</title></head>
|
||||
<body>
|
||||
Some initial text<br>
|
||||
<p>which has this one new line</p>
|
||||
<br>
|
||||
So let's see what happens. <br>
|
||||
with more text that helps word-diff if needed<br>
|
||||
and more text that helps word-diff if needed<br>
|
||||
and even more text {extras}that helps word-diff if needed<br>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
@@ -92,8 +98,8 @@ def wait_for_notification_endpoint_output(datastore_path):
|
||||
#@todo - could check the apprise object directly instead of looking for this file
|
||||
from os.path import isfile
|
||||
notification_file = os.path.join(datastore_path, "notification.txt")
|
||||
for i in range(1, 20):
|
||||
time.sleep(1)
|
||||
for i in range(1, 100):
|
||||
time.sleep(0.3)
|
||||
if isfile(notification_file):
|
||||
return True
|
||||
|
||||
|
||||
Reference in New Issue
Block a user