mirror of
https://github.com/dgtlmoon/changedetection.io.git
synced 2025-11-23 18:06:09 +00:00
Compare commits
1 Commits
0.50.38
...
docker-bui
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
46d07dbed7 |
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
# Read more https://github.com/dgtlmoon/changedetection.io/wiki
|
# Read more https://github.com/dgtlmoon/changedetection.io/wiki
|
||||||
|
|
||||||
__version__ = '0.50.38'
|
__version__ = '0.50.35'
|
||||||
|
|
||||||
from changedetectionio.strtobool import strtobool
|
from changedetectionio.strtobool import strtobool
|
||||||
from json.decoder import JSONDecodeError
|
from json.decoder import JSONDecodeError
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from flask import Blueprint, request, make_response
|
|||||||
import random
|
import random
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from changedetectionio.notification_service import NotificationContextData, set_basic_notification_vars
|
from changedetectionio.notification_service import NotificationContextData
|
||||||
from changedetectionio.store import ChangeDetectionStore
|
from changedetectionio.store import ChangeDetectionStore
|
||||||
from changedetectionio.auth_decorator import login_optionally_required
|
from changedetectionio.auth_decorator import login_optionally_required
|
||||||
|
|
||||||
@@ -95,44 +95,7 @@ def construct_blueprint(datastore: ChangeDetectionStore):
|
|||||||
n_object['notification_body'] = "Test body"
|
n_object['notification_body'] = "Test body"
|
||||||
|
|
||||||
n_object['as_async'] = False
|
n_object['as_async'] = False
|
||||||
|
n_object.update(watch.extra_notification_token_values())
|
||||||
# Same like in notification service, should be refactored
|
|
||||||
dates = []
|
|
||||||
trigger_text = ''
|
|
||||||
snapshot_contents = ''
|
|
||||||
if watch:
|
|
||||||
watch_history = watch.history
|
|
||||||
dates = list(watch_history.keys())
|
|
||||||
trigger_text = watch.get('trigger_text', [])
|
|
||||||
# Add text that was triggered
|
|
||||||
if len(dates):
|
|
||||||
snapshot_contents = watch.get_history_snapshot(dates[-1])
|
|
||||||
else:
|
|
||||||
snapshot_contents = "No snapshot/history available, the watch should fetch atleast once."
|
|
||||||
|
|
||||||
if len(trigger_text):
|
|
||||||
from . import html_tools
|
|
||||||
triggered_text = html_tools.get_triggered_text(content=snapshot_contents, trigger_text=trigger_text)
|
|
||||||
if triggered_text:
|
|
||||||
triggered_text = '\n'.join(triggered_text)
|
|
||||||
|
|
||||||
# Could be called as a 'test notification' with only 1 snapshot available
|
|
||||||
prev_snapshot = "Example text: example test\nExample text: change detection is cool\nExample text: some more examples\n"
|
|
||||||
current_snapshot = "Example text: example test\nExample text: change detection is fantastic\nExample text: even more examples\nExample text: a lot more examples"
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if len(dates) > 1:
|
|
||||||
prev_snapshot = watch.get_history_snapshot(dates[-2])
|
|
||||||
current_snapshot = watch.get_history_snapshot(dates[-1])
|
|
||||||
|
|
||||||
n_object.update(set_basic_notification_vars(snapshot_contents=snapshot_contents,
|
|
||||||
current_snapshot=current_snapshot,
|
|
||||||
prev_snapshot=prev_snapshot,
|
|
||||||
watch=watch,
|
|
||||||
triggered_text=trigger_text))
|
|
||||||
|
|
||||||
|
|
||||||
sent_obj = process_notification(n_object, datastore)
|
sent_obj = process_notification(n_object, datastore)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -5,15 +5,13 @@ from apprise import NotifyFormat
|
|||||||
from loguru import logger
|
from loguru import logger
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
from .apprise_plugin.assets import apprise_asset, APPRISE_AVATAR_URL
|
from .apprise_plugin.assets import apprise_asset, APPRISE_AVATAR_URL
|
||||||
|
from .apprise_plugin.custom_handlers import SUPPORTED_HTTP_METHODS
|
||||||
from .email_helpers import as_monospaced_html_email
|
from .email_helpers import as_monospaced_html_email
|
||||||
from ..diff import HTML_REMOVED_STYLE, REMOVED_PLACEMARKER_OPEN, REMOVED_PLACEMARKER_CLOSED, ADDED_PLACEMARKER_OPEN, HTML_ADDED_STYLE, \
|
from ..diff import HTML_REMOVED_STYLE, REMOVED_PLACEMARKER_OPEN, REMOVED_PLACEMARKER_CLOSED, ADDED_PLACEMARKER_OPEN, HTML_ADDED_STYLE, \
|
||||||
ADDED_PLACEMARKER_CLOSED, CHANGED_INTO_PLACEMARKER_OPEN, CHANGED_INTO_PLACEMARKER_CLOSED, CHANGED_PLACEMARKER_OPEN, \
|
ADDED_PLACEMARKER_CLOSED, CHANGED_INTO_PLACEMARKER_OPEN, CHANGED_INTO_PLACEMARKER_CLOSED, CHANGED_PLACEMARKER_OPEN, \
|
||||||
CHANGED_PLACEMARKER_CLOSED, HTML_CHANGED_STYLE, HTML_CHANGED_INTO_STYLE
|
CHANGED_PLACEMARKER_CLOSED, HTML_CHANGED_STYLE, HTML_CHANGED_INTO_STYLE
|
||||||
import re
|
from ..notification_service import NotificationContextData, CUSTOM_LINEBREAK_PLACEHOLDER
|
||||||
|
|
||||||
from ..notification_service import NotificationContextData
|
|
||||||
|
|
||||||
newline_re = re.compile(r'\r\n|\r|\n')
|
|
||||||
|
|
||||||
|
|
||||||
def markup_text_links_to_html(body):
|
def markup_text_links_to_html(body):
|
||||||
@@ -129,62 +127,6 @@ def apply_standard_markdown_to_body(n_body):
|
|||||||
return n_body
|
return n_body
|
||||||
|
|
||||||
|
|
||||||
def replace_placemarkers_in_text(text, url, requested_output_format):
|
|
||||||
"""
|
|
||||||
Replace diff placemarkers in text based on the URL service type and requested output format.
|
|
||||||
Used for both notification title and body to ensure consistent placeholder replacement.
|
|
||||||
|
|
||||||
:param text: The text to process
|
|
||||||
:param url: The notification URL (to detect service type)
|
|
||||||
:param requested_output_format: The output format (html, htmlcolor, markdown, text, etc.)
|
|
||||||
:return: Processed text with placemarkers replaced
|
|
||||||
"""
|
|
||||||
if not text:
|
|
||||||
return text
|
|
||||||
|
|
||||||
if url.startswith('tgram://'):
|
|
||||||
# Telegram only supports a limited subset of HTML
|
|
||||||
# Use strikethrough for removed content, bold for added content
|
|
||||||
text = text.replace(REMOVED_PLACEMARKER_OPEN, '<s>')
|
|
||||||
text = text.replace(REMOVED_PLACEMARKER_CLOSED, '</s>')
|
|
||||||
text = text.replace(ADDED_PLACEMARKER_OPEN, '<b>')
|
|
||||||
text = text.replace(ADDED_PLACEMARKER_CLOSED, '</b>')
|
|
||||||
# Handle changed/replaced lines (old → new)
|
|
||||||
text = text.replace(CHANGED_PLACEMARKER_OPEN, '<s>')
|
|
||||||
text = text.replace(CHANGED_PLACEMARKER_CLOSED, '</s>')
|
|
||||||
text = text.replace(CHANGED_INTO_PLACEMARKER_OPEN, '<b>')
|
|
||||||
text = text.replace(CHANGED_INTO_PLACEMARKER_CLOSED, '</b>')
|
|
||||||
elif (url.startswith('discord://') or url.startswith('https://discordapp.com/api/webhooks')
|
|
||||||
or url.startswith('https://discord.com/api')) and requested_output_format == 'html':
|
|
||||||
# Discord doesn't support HTML, use Discord markdown
|
|
||||||
text = apply_discord_markdown_to_body(n_body=text)
|
|
||||||
elif requested_output_format == 'htmlcolor':
|
|
||||||
# https://github.com/dgtlmoon/changedetection.io/issues/821#issuecomment-1241837050
|
|
||||||
text = text.replace(REMOVED_PLACEMARKER_OPEN, f'<span style="{HTML_REMOVED_STYLE}" role="deletion" aria-label="Removed text" title="Removed text">')
|
|
||||||
text = text.replace(REMOVED_PLACEMARKER_CLOSED, f'</span>')
|
|
||||||
text = text.replace(ADDED_PLACEMARKER_OPEN, f'<span style="{HTML_ADDED_STYLE}" role="insertion" aria-label="Added text" title="Added text">')
|
|
||||||
text = text.replace(ADDED_PLACEMARKER_CLOSED, f'</span>')
|
|
||||||
# Handle changed/replaced lines (old → new)
|
|
||||||
text = text.replace(CHANGED_PLACEMARKER_OPEN, f'<span style="{HTML_CHANGED_STYLE}" role="note" aria-label="Changed text" title="Changed text">')
|
|
||||||
text = text.replace(CHANGED_PLACEMARKER_CLOSED, f'</span>')
|
|
||||||
text = text.replace(CHANGED_INTO_PLACEMARKER_OPEN, f'<span style="{HTML_CHANGED_INTO_STYLE}" role="note" aria-label="Changed into" title="Changed into">')
|
|
||||||
text = text.replace(CHANGED_INTO_PLACEMARKER_CLOSED, f'</span>')
|
|
||||||
elif requested_output_format == 'markdown':
|
|
||||||
# Markdown to HTML - Apprise will convert this to HTML
|
|
||||||
text = apply_standard_markdown_to_body(n_body=text)
|
|
||||||
else:
|
|
||||||
# plaintext, html, and default - use simple text markers
|
|
||||||
text = text.replace(REMOVED_PLACEMARKER_OPEN, '(removed) ')
|
|
||||||
text = text.replace(REMOVED_PLACEMARKER_CLOSED, '')
|
|
||||||
text = text.replace(ADDED_PLACEMARKER_OPEN, '(added) ')
|
|
||||||
text = text.replace(ADDED_PLACEMARKER_CLOSED, '')
|
|
||||||
text = text.replace(CHANGED_PLACEMARKER_OPEN, f'(changed) ')
|
|
||||||
text = text.replace(CHANGED_PLACEMARKER_CLOSED, f'')
|
|
||||||
text = text.replace(CHANGED_INTO_PLACEMARKER_OPEN, f'(into) ')
|
|
||||||
text = text.replace(CHANGED_INTO_PLACEMARKER_CLOSED, f'')
|
|
||||||
|
|
||||||
return text
|
|
||||||
|
|
||||||
def apply_service_tweaks(url, n_body, n_title, requested_output_format):
|
def apply_service_tweaks(url, n_body, n_title, requested_output_format):
|
||||||
|
|
||||||
# Re 323 - Limit discord length to their 2000 char limit total or it wont send.
|
# Re 323 - Limit discord length to their 2000 char limit total or it wont send.
|
||||||
@@ -196,12 +138,6 @@ def apply_service_tweaks(url, n_body, n_title, requested_output_format):
|
|||||||
if not n_body or not n_body.strip():
|
if not n_body or not n_body.strip():
|
||||||
return url, n_body, n_title
|
return url, n_body, n_title
|
||||||
|
|
||||||
# Normalize URL scheme to lowercase to prevent case-sensitivity issues
|
|
||||||
# e.g., "Discord://webhook" -> "discord://webhook", "TGRAM://bot123" -> "tgram://bot123"
|
|
||||||
scheme_separator_pos = url.find('://')
|
|
||||||
if scheme_separator_pos > 0:
|
|
||||||
url = url[:scheme_separator_pos].lower() + url[scheme_separator_pos:]
|
|
||||||
|
|
||||||
# So if no avatar_url is specified, add one so it can be correctly calculated into the total payload
|
# So if no avatar_url is specified, add one so it can be correctly calculated into the total payload
|
||||||
parsed = urlparse(url)
|
parsed = urlparse(url)
|
||||||
k = '?' if not parsed.query else '&'
|
k = '?' if not parsed.query else '&'
|
||||||
@@ -213,22 +149,24 @@ def apply_service_tweaks(url, n_body, n_title, requested_output_format):
|
|||||||
and not url.startswith('put'):
|
and not url.startswith('put'):
|
||||||
url += k + f"avatar_url={APPRISE_AVATAR_URL}"
|
url += k + f"avatar_url={APPRISE_AVATAR_URL}"
|
||||||
|
|
||||||
# Replace placemarkers in title first (this was the missing piece causing the bug)
|
|
||||||
# Titles are ALWAYS plain text across all notification services (Discord embeds, Slack attachments,
|
|
||||||
# email Subject headers, etc.), so we always use 'text' format for title placemarker replacement
|
|
||||||
# Looking over apprise library it seems that all plugins only expect plain-text.
|
|
||||||
n_title = replace_placemarkers_in_text(n_title, url, 'text')
|
|
||||||
|
|
||||||
if url.startswith('tgram://'):
|
if url.startswith('tgram://'):
|
||||||
# Telegram only supports a limit subset of HTML, remove the '<br>' we place in.
|
# Telegram only supports a limit subset of HTML, remove the '<br>' we place in.
|
||||||
# re https://github.com/dgtlmoon/changedetection.io/issues/555
|
# re https://github.com/dgtlmoon/changedetection.io/issues/555
|
||||||
# @todo re-use an existing library we have already imported to strip all non-allowed tags
|
# @todo re-use an existing library we have already imported to strip all non-allowed tags
|
||||||
n_body = n_body.replace('<br>', '\n')
|
n_body = n_body.replace('<br>', '\n')
|
||||||
n_body = n_body.replace('</br>', '\n')
|
n_body = n_body.replace('</br>', '\n')
|
||||||
n_body = newline_re.sub('\n', n_body)
|
n_body = n_body.replace(CUSTOM_LINEBREAK_PLACEHOLDER, '\n')
|
||||||
|
|
||||||
# Replace placemarkers for body
|
# Use strikethrough for removed content, bold for added content
|
||||||
n_body = replace_placemarkers_in_text(n_body, url, requested_output_format)
|
n_body = n_body.replace(REMOVED_PLACEMARKER_OPEN, '<s>')
|
||||||
|
n_body = n_body.replace(REMOVED_PLACEMARKER_CLOSED, '</s>')
|
||||||
|
n_body = n_body.replace(ADDED_PLACEMARKER_OPEN, '<b>')
|
||||||
|
n_body = n_body.replace(ADDED_PLACEMARKER_CLOSED, '</b>')
|
||||||
|
# Handle changed/replaced lines (old → new)
|
||||||
|
n_body = n_body.replace(CHANGED_PLACEMARKER_OPEN, '<s>')
|
||||||
|
n_body = n_body.replace(CHANGED_PLACEMARKER_CLOSED, '</s>')
|
||||||
|
n_body = n_body.replace(CHANGED_INTO_PLACEMARKER_OPEN, '<b>')
|
||||||
|
n_body = n_body.replace(CHANGED_INTO_PLACEMARKER_CLOSED, '</b>')
|
||||||
|
|
||||||
# real limit is 4096, but minus some for extra metadata
|
# real limit is 4096, but minus some for extra metadata
|
||||||
payload_max_size = 3600
|
payload_max_size = 3600
|
||||||
@@ -242,7 +180,7 @@ def apply_service_tweaks(url, n_body, n_title, requested_output_format):
|
|||||||
# Discord doesn't support HTML, replace <br> with newlines
|
# Discord doesn't support HTML, replace <br> with newlines
|
||||||
n_body = n_body.strip().replace('<br>', '\n')
|
n_body = n_body.strip().replace('<br>', '\n')
|
||||||
n_body = n_body.replace('</br>', '\n')
|
n_body = n_body.replace('</br>', '\n')
|
||||||
n_body = newline_re.sub('\n', n_body)
|
n_body = n_body.replace(CUSTOM_LINEBREAK_PLACEHOLDER, '\n')
|
||||||
|
|
||||||
# Don't replace placeholders or truncate here - let the custom Discord plugin handle it
|
# Don't replace placeholders or truncate here - let the custom Discord plugin handle it
|
||||||
# The plugin will use embeds (6000 char limit across all embeds) if placeholders are present,
|
# The plugin will use embeds (6000 char limit across all embeds) if placeholders are present,
|
||||||
@@ -252,7 +190,7 @@ def apply_service_tweaks(url, n_body, n_title, requested_output_format):
|
|||||||
if requested_output_format == 'html':
|
if requested_output_format == 'html':
|
||||||
# No diff placeholders, use Discord markdown for any other formatting
|
# No diff placeholders, use Discord markdown for any other formatting
|
||||||
# Use Discord markdown: strikethrough for removed, bold for added
|
# Use Discord markdown: strikethrough for removed, bold for added
|
||||||
n_body = replace_placemarkers_in_text(n_body, url, requested_output_format)
|
n_body = apply_discord_markdown_to_body(n_body=n_body)
|
||||||
|
|
||||||
# Apply 2000 char limit for plain content
|
# Apply 2000 char limit for plain content
|
||||||
payload_max_size = 1700
|
payload_max_size = 1700
|
||||||
@@ -263,17 +201,40 @@ def apply_service_tweaks(url, n_body, n_title, requested_output_format):
|
|||||||
|
|
||||||
# Is not discord/tgram and they want htmlcolor
|
# Is not discord/tgram and they want htmlcolor
|
||||||
elif requested_output_format == 'htmlcolor':
|
elif requested_output_format == 'htmlcolor':
|
||||||
n_body = replace_placemarkers_in_text(n_body, url, requested_output_format)
|
# https://github.com/dgtlmoon/changedetection.io/issues/821#issuecomment-1241837050
|
||||||
n_body = newline_re.sub('<br>\n', n_body)
|
n_body = n_body.replace(REMOVED_PLACEMARKER_OPEN, f'<span style="{HTML_REMOVED_STYLE}" role="deletion" aria-label="Removed text" title="Removed text">')
|
||||||
|
n_body = n_body.replace(REMOVED_PLACEMARKER_CLOSED, f'</span>')
|
||||||
|
n_body = n_body.replace(ADDED_PLACEMARKER_OPEN, f'<span style="{HTML_ADDED_STYLE}" role="insertion" aria-label="Added text" title="Added text">')
|
||||||
|
n_body = n_body.replace(ADDED_PLACEMARKER_CLOSED, f'</span>')
|
||||||
|
# Handle changed/replaced lines (old → new)
|
||||||
|
n_body = n_body.replace(CHANGED_PLACEMARKER_OPEN, f'<span style="{HTML_CHANGED_STYLE}" role="note" aria-label="Changed text" title="Changed text">')
|
||||||
|
n_body = n_body.replace(CHANGED_PLACEMARKER_CLOSED, f'</span>')
|
||||||
|
n_body = n_body.replace(CHANGED_INTO_PLACEMARKER_OPEN, f'<span style="{HTML_CHANGED_INTO_STYLE}" role="note" aria-label="Changed into" title="Changed into">')
|
||||||
|
n_body = n_body.replace(CHANGED_INTO_PLACEMARKER_CLOSED, f'</span>')
|
||||||
|
n_body = n_body.replace('\n', f'{CUSTOM_LINEBREAK_PLACEHOLDER}\n')
|
||||||
elif requested_output_format == 'html':
|
elif requested_output_format == 'html':
|
||||||
n_body = replace_placemarkers_in_text(n_body, url, requested_output_format)
|
n_body = n_body.replace(REMOVED_PLACEMARKER_OPEN, '(removed) ')
|
||||||
n_body = newline_re.sub('<br>\n', n_body)
|
n_body = n_body.replace(REMOVED_PLACEMARKER_CLOSED, '')
|
||||||
|
n_body = n_body.replace(ADDED_PLACEMARKER_OPEN, '(added) ')
|
||||||
|
n_body = n_body.replace(ADDED_PLACEMARKER_CLOSED, '')
|
||||||
|
n_body = n_body.replace(CHANGED_PLACEMARKER_OPEN, f'(changed) ')
|
||||||
|
n_body = n_body.replace(CHANGED_PLACEMARKER_CLOSED, f'')
|
||||||
|
n_body = n_body.replace(CHANGED_INTO_PLACEMARKER_OPEN, f'(into) ')
|
||||||
|
n_body = n_body.replace(CHANGED_INTO_PLACEMARKER_CLOSED, f'')
|
||||||
|
n_body = n_body.replace('\n', f'{CUSTOM_LINEBREAK_PLACEHOLDER}\n')
|
||||||
elif requested_output_format == 'markdown':
|
elif requested_output_format == 'markdown':
|
||||||
# Markdown to HTML - Apprise will convert this to HTML
|
# Markdown to HTML - Apprise will convert this to HTML
|
||||||
n_body = replace_placemarkers_in_text(n_body, url, requested_output_format)
|
n_body = apply_standard_markdown_to_body(n_body=n_body)
|
||||||
|
|
||||||
else: #plaintext etc default
|
else: #plaintext etc default
|
||||||
n_body = replace_placemarkers_in_text(n_body, url, requested_output_format)
|
n_body = n_body.replace(REMOVED_PLACEMARKER_OPEN, '(removed) ')
|
||||||
|
n_body = n_body.replace(REMOVED_PLACEMARKER_CLOSED, '')
|
||||||
|
n_body = n_body.replace(ADDED_PLACEMARKER_OPEN, '(added) ')
|
||||||
|
n_body = n_body.replace(ADDED_PLACEMARKER_CLOSED, '')
|
||||||
|
n_body = n_body.replace(CHANGED_PLACEMARKER_OPEN, f'(changed) ')
|
||||||
|
n_body = n_body.replace(CHANGED_PLACEMARKER_CLOSED, f'')
|
||||||
|
n_body = n_body.replace(CHANGED_INTO_PLACEMARKER_OPEN, f'(into) ')
|
||||||
|
n_body = n_body.replace(CHANGED_INTO_PLACEMARKER_CLOSED, f'')
|
||||||
|
|
||||||
return url, n_body, n_title
|
return url, n_body, n_title
|
||||||
|
|
||||||
@@ -334,18 +295,24 @@ def process_notification(n_object: NotificationContextData, datastore):
|
|||||||
with (apprise.LogCapture(level=apprise.logging.DEBUG) as logs):
|
with (apprise.LogCapture(level=apprise.logging.DEBUG) as logs):
|
||||||
for url in n_object['notification_urls']:
|
for url in n_object['notification_urls']:
|
||||||
|
|
||||||
|
# Get the notification body from datastore
|
||||||
n_body = jinja_render(template_str=n_object.get('notification_body', ''), **notification_parameters)
|
n_body = jinja_render(template_str=n_object.get('notification_body', ''), **notification_parameters)
|
||||||
n_title = jinja_render(template_str=n_object.get('notification_title', ''), **notification_parameters)
|
|
||||||
|
|
||||||
if n_object.get('markup_text_links_to_html_links'):
|
if n_object.get('markup_text_links_to_html_links'):
|
||||||
n_body = markup_text_links_to_html(body=n_body)
|
n_body = markup_text_links_to_html(body=n_body)
|
||||||
|
|
||||||
|
n_title = jinja_render(template_str=n_object.get('notification_title', ''), **notification_parameters)
|
||||||
|
|
||||||
url = url.strip()
|
url = url.strip()
|
||||||
if not url or url.startswith('#'):
|
if url.startswith('#'):
|
||||||
logger.debug(f"Skipping commented out or empty notification URL - '{url}'")
|
logger.trace(f"Skipping commented out notification URL - {url}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
logger.info(f">> Process Notification: AppRise start notifying '{url}'")
|
if not url:
|
||||||
|
logger.warning(f"Process Notification: skipping empty notification URL.")
|
||||||
|
continue
|
||||||
|
|
||||||
|
logger.info(f">> Process Notification: AppRise notifying {url}")
|
||||||
url = jinja_render(template_str=url, **notification_parameters)
|
url = jinja_render(template_str=url, **notification_parameters)
|
||||||
|
|
||||||
# If it's a plaintext document, and they want HTML type email/alerts, so it needs to be escaped
|
# If it's a plaintext document, and they want HTML type email/alerts, so it needs to be escaped
|
||||||
@@ -386,18 +353,25 @@ def process_notification(n_object: NotificationContextData, datastore):
|
|||||||
requested_output_format = NotifyFormat.HTML.value
|
requested_output_format = NotifyFormat.HTML.value
|
||||||
apprise_input_format = NotifyFormat.HTML.value # Changed from MARKDOWN to HTML
|
apprise_input_format = NotifyFormat.HTML.value # Changed from MARKDOWN to HTML
|
||||||
|
|
||||||
|
# Could have arrived at any stage, so we dont end up running .escape on it
|
||||||
|
if 'html' in requested_output_format:
|
||||||
|
n_body = n_body.replace(CUSTOM_LINEBREAK_PLACEHOLDER, '<br>\r\n')
|
||||||
|
else:
|
||||||
|
# texty types
|
||||||
|
n_body = n_body.replace(CUSTOM_LINEBREAK_PLACEHOLDER, '\r\n')
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# ?format was IN the apprise URL, they are kind of on their own here, we will try our best
|
# ?format was IN the apprise URL, they are kind of on their own here, we will try our best
|
||||||
if 'format=html' in url:
|
if 'format=html' in url:
|
||||||
n_body = newline_re.sub('<br>\r\n', n_body)
|
n_body = n_body.replace(CUSTOM_LINEBREAK_PLACEHOLDER, '<br>\r\n')
|
||||||
# This will also prevent apprise from doing conversion
|
# This will also prevent apprise from doing conversion
|
||||||
apprise_input_format = NotifyFormat.HTML.value
|
apprise_input_format = NotifyFormat.HTML.value
|
||||||
requested_output_format = NotifyFormat.HTML.value
|
requested_output_format = NotifyFormat.HTML.value
|
||||||
elif 'format=text' in url:
|
elif 'format=text' in url:
|
||||||
|
n_body = n_body.replace(CUSTOM_LINEBREAK_PLACEHOLDER, '\r\n')
|
||||||
apprise_input_format = NotifyFormat.TEXT.value
|
apprise_input_format = NotifyFormat.TEXT.value
|
||||||
requested_output_format = NotifyFormat.TEXT.value
|
requested_output_format = NotifyFormat.TEXT.value
|
||||||
|
|
||||||
|
|
||||||
sent_objs.append({'title': n_title,
|
sent_objs.append({'title': n_title,
|
||||||
'body': n_body,
|
'body': n_body,
|
||||||
'url': url})
|
'url': url})
|
||||||
|
|||||||
@@ -9,8 +9,11 @@ for both sync and async workers
|
|||||||
from loguru import logger
|
from loguru import logger
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
from changedetectionio.model import USE_SYSTEM_DEFAULT_NOTIFICATION_FORMAT_FOR_WATCH
|
||||||
from changedetectionio.notification import default_notification_format, valid_notification_formats
|
from changedetectionio.notification import default_notification_format, valid_notification_formats
|
||||||
|
|
||||||
|
# This gets modified on notification time (handler.py) depending on the required notification output
|
||||||
|
CUSTOM_LINEBREAK_PLACEHOLDER='@BR@'
|
||||||
|
|
||||||
|
|
||||||
# What is passed around as notification context, also used as the complete list of valid {{ tokens }}
|
# What is passed around as notification context, also used as the complete list of valid {{ tokens }}
|
||||||
@@ -68,34 +71,6 @@ class NotificationContextData(dict):
|
|||||||
|
|
||||||
super().__setitem__(key, value)
|
super().__setitem__(key, value)
|
||||||
|
|
||||||
|
|
||||||
def set_basic_notification_vars(snapshot_contents, current_snapshot, prev_snapshot, watch, triggered_text):
|
|
||||||
now = time.time()
|
|
||||||
from changedetectionio import diff
|
|
||||||
|
|
||||||
n_object = {
|
|
||||||
'current_snapshot': snapshot_contents,
|
|
||||||
'diff': diff.render_diff(prev_snapshot, current_snapshot),
|
|
||||||
'diff_added': diff.render_diff(prev_snapshot, current_snapshot, include_removed=False),
|
|
||||||
'diff_full': diff.render_diff(prev_snapshot, current_snapshot, include_equal=True),
|
|
||||||
'diff_patch': diff.render_diff(prev_snapshot, current_snapshot, patch_format=True),
|
|
||||||
'diff_removed': diff.render_diff(prev_snapshot, current_snapshot, include_added=False),
|
|
||||||
'screenshot': watch.get_screenshot() if watch and watch.get('notification_screenshot') else None,
|
|
||||||
'triggered_text': triggered_text,
|
|
||||||
'uuid': watch.get('uuid') if watch else None,
|
|
||||||
'watch_url': watch.get('url') if watch else None,
|
|
||||||
'watch_uuid': watch.get('uuid') if watch else None,
|
|
||||||
'watch_mime_type': watch.get('content-type')
|
|
||||||
}
|
|
||||||
|
|
||||||
# The \n's in the content from the above will get converted to <br> etc depending on the notification format
|
|
||||||
|
|
||||||
if watch:
|
|
||||||
n_object.update(watch.extra_notification_token_values())
|
|
||||||
|
|
||||||
logger.trace(f"Main rendered notification placeholders (diff_added etc) calculated in {time.time() - now:.3f}s")
|
|
||||||
return n_object
|
|
||||||
|
|
||||||
class NotificationService:
|
class NotificationService:
|
||||||
"""
|
"""
|
||||||
Standalone notification service that handles all notification functionality
|
Standalone notification service that handles all notification functionality
|
||||||
@@ -110,6 +85,7 @@ class NotificationService:
|
|||||||
"""
|
"""
|
||||||
Queue a notification for a watch with full diff rendering and template variables
|
Queue a notification for a watch with full diff rendering and template variables
|
||||||
"""
|
"""
|
||||||
|
from changedetectionio import diff
|
||||||
from changedetectionio.notification import USE_SYSTEM_DEFAULT_NOTIFICATION_FORMAT_FOR_WATCH
|
from changedetectionio.notification import USE_SYSTEM_DEFAULT_NOTIFICATION_FORMAT_FOR_WATCH
|
||||||
|
|
||||||
if not isinstance(n_object, NotificationContextData):
|
if not isinstance(n_object, NotificationContextData):
|
||||||
@@ -118,6 +94,8 @@ class NotificationService:
|
|||||||
dates = []
|
dates = []
|
||||||
trigger_text = ''
|
trigger_text = ''
|
||||||
|
|
||||||
|
now = time.time()
|
||||||
|
|
||||||
if watch:
|
if watch:
|
||||||
watch_history = watch.history
|
watch_history = watch.history
|
||||||
dates = list(watch_history.keys())
|
dates = list(watch_history.keys())
|
||||||
@@ -139,7 +117,7 @@ class NotificationService:
|
|||||||
from . import html_tools
|
from . import html_tools
|
||||||
triggered_text = html_tools.get_triggered_text(content=snapshot_contents, trigger_text=trigger_text)
|
triggered_text = html_tools.get_triggered_text(content=snapshot_contents, trigger_text=trigger_text)
|
||||||
if triggered_text:
|
if triggered_text:
|
||||||
triggered_text = '\n'.join(triggered_text)
|
triggered_text = CUSTOM_LINEBREAK_PLACEHOLDER.join(triggered_text)
|
||||||
|
|
||||||
# Could be called as a 'test notification' with only 1 snapshot available
|
# Could be called as a 'test notification' with only 1 snapshot available
|
||||||
prev_snapshot = "Example text: example test\nExample text: change detection is cool\nExample text: some more examples\n"
|
prev_snapshot = "Example text: example test\nExample text: change detection is cool\nExample text: some more examples\n"
|
||||||
@@ -149,13 +127,25 @@ class NotificationService:
|
|||||||
prev_snapshot = watch.get_history_snapshot(dates[-2])
|
prev_snapshot = watch.get_history_snapshot(dates[-2])
|
||||||
current_snapshot = watch.get_history_snapshot(dates[-1])
|
current_snapshot = watch.get_history_snapshot(dates[-1])
|
||||||
|
|
||||||
|
n_object.update({
|
||||||
|
'current_snapshot': snapshot_contents,
|
||||||
|
'diff': diff.render_diff(prev_snapshot, current_snapshot, line_feed_sep=CUSTOM_LINEBREAK_PLACEHOLDER),
|
||||||
|
'diff_added': diff.render_diff(prev_snapshot, current_snapshot, include_removed=False, line_feed_sep=CUSTOM_LINEBREAK_PLACEHOLDER),
|
||||||
|
'diff_full': diff.render_diff(prev_snapshot, current_snapshot, include_equal=True, line_feed_sep=CUSTOM_LINEBREAK_PLACEHOLDER),
|
||||||
|
'diff_patch': diff.render_diff(prev_snapshot, current_snapshot, line_feed_sep=CUSTOM_LINEBREAK_PLACEHOLDER, patch_format=True),
|
||||||
|
'diff_removed': diff.render_diff(prev_snapshot, current_snapshot, include_added=False, line_feed_sep=CUSTOM_LINEBREAK_PLACEHOLDER),
|
||||||
|
'screenshot': watch.get_screenshot() if watch and watch.get('notification_screenshot') else None,
|
||||||
|
'triggered_text': triggered_text,
|
||||||
|
'uuid': watch.get('uuid') if watch else None,
|
||||||
|
'watch_url': watch.get('url') if watch else None,
|
||||||
|
'watch_uuid': watch.get('uuid') if watch else None,
|
||||||
|
'watch_mime_type': watch.get('content-type')
|
||||||
|
})
|
||||||
|
|
||||||
n_object.update(set_basic_notification_vars(snapshot_contents=snapshot_contents,
|
if watch:
|
||||||
current_snapshot=current_snapshot,
|
n_object.update(watch.extra_notification_token_values())
|
||||||
prev_snapshot=prev_snapshot,
|
|
||||||
watch=watch,
|
|
||||||
triggered_text=triggered_text))
|
|
||||||
|
|
||||||
|
logger.trace(f"Main rendered notification placeholders (diff_added etc) calculated in {time.time()-now:.3f}s")
|
||||||
logger.debug("Queued notification for sending")
|
logger.debug("Queued notification for sending")
|
||||||
self.notification_q.put(n_object)
|
self.notification_q.put(n_object)
|
||||||
|
|
||||||
|
|||||||
@@ -329,18 +329,12 @@ a.pure-button-selected {
|
|||||||
.notifications-wrapper {
|
.notifications-wrapper {
|
||||||
padding-top: 0.5rem;
|
padding-top: 0.5rem;
|
||||||
#notification-test-log {
|
#notification-test-log {
|
||||||
margin-top: 1rem;
|
padding-top: 1rem;
|
||||||
padding: 1rem;
|
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
max-height: 12rem;
|
|
||||||
overflow-y: scroll;
|
|
||||||
border: 1px solid var(--color-border-notification);
|
|
||||||
border-radius: 5px;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -3,9 +3,8 @@ from flask import url_for
|
|||||||
from email import message_from_string
|
from email import message_from_string
|
||||||
from email.policy import default as email_policy
|
from email.policy import default as email_policy
|
||||||
|
|
||||||
from changedetectionio.diff import HTML_REMOVED_STYLE, HTML_ADDED_STYLE, HTML_CHANGED_STYLE, REMOVED_PLACEMARKER_OPEN, \
|
from changedetectionio.diff import HTML_REMOVED_STYLE, HTML_ADDED_STYLE, HTML_CHANGED_STYLE
|
||||||
CHANGED_PLACEMARKER_OPEN, ADDED_PLACEMARKER_OPEN
|
from changedetectionio.notification_service import NotificationContextData, CUSTOM_LINEBREAK_PLACEHOLDER
|
||||||
from changedetectionio.notification_service import NotificationContextData
|
|
||||||
from changedetectionio.tests.util import set_original_response, set_modified_response, set_more_modified_response, live_server_setup, \
|
from changedetectionio.tests.util import set_original_response, set_modified_response, set_more_modified_response, live_server_setup, \
|
||||||
wait_for_all_checks, \
|
wait_for_all_checks, \
|
||||||
set_longer_modified_response, delete_all_watches
|
set_longer_modified_response, delete_all_watches
|
||||||
@@ -101,6 +100,7 @@ def test_check_notification_email_formats_default_HTML(client, live_server, meas
|
|||||||
text_content = text_part.get_content()
|
text_content = text_part.get_content()
|
||||||
assert '(added) So let\'s see what happens.\r\n' in text_content # The plaintext part
|
assert '(added) So let\'s see what happens.\r\n' in text_content # The plaintext part
|
||||||
assert 'fallback-body\r\n' in text_content # The plaintext part
|
assert 'fallback-body\r\n' in text_content # The plaintext part
|
||||||
|
assert CUSTOM_LINEBREAK_PLACEHOLDER not in text_content
|
||||||
|
|
||||||
# Second part should be text/html
|
# Second part should be text/html
|
||||||
html_part = parts[1]
|
html_part = parts[1]
|
||||||
@@ -109,6 +109,7 @@ def test_check_notification_email_formats_default_HTML(client, live_server, meas
|
|||||||
assert 'some text<br>' in html_content # We converted \n from the notification body
|
assert 'some text<br>' in html_content # We converted \n from the notification body
|
||||||
assert 'fallback-body<br>' in html_content # kept the original <br>
|
assert 'fallback-body<br>' in html_content # kept the original <br>
|
||||||
assert '(added) So let\'s see what happens.<br>' in html_content # the html part
|
assert '(added) So let\'s see what happens.<br>' in html_content # the html part
|
||||||
|
assert CUSTOM_LINEBREAK_PLACEHOLDER not in html_content
|
||||||
delete_all_watches(client)
|
delete_all_watches(client)
|
||||||
|
|
||||||
|
|
||||||
@@ -123,8 +124,8 @@ def test_check_notification_plaintext_format(client, live_server, measure_memory
|
|||||||
res = client.post(
|
res = client.post(
|
||||||
url_for("settings.settings_page"),
|
url_for("settings.settings_page"),
|
||||||
data={"application-notification_urls": notification_url,
|
data={"application-notification_urls": notification_url,
|
||||||
"application-notification_title": "fallback-title {{watch_title}} {{ diff_added.splitlines()[0] if diff_added else 'diff added didnt split' }} " + default_notification_title,
|
"application-notification_title": "fallback-title " + default_notification_title,
|
||||||
"application-notification_body": f"some text\n" + default_notification_body + f"\nMore output test\n{ALL_MARKUP_TOKENS}",
|
"application-notification_body": "some text\n" + default_notification_body,
|
||||||
"application-notification_format": 'text',
|
"application-notification_format": 'text',
|
||||||
"requests-time_between_check-minutes": 180,
|
"requests-time_between_check-minutes": 180,
|
||||||
'application-fetch_backend': "html_requests"},
|
'application-fetch_backend': "html_requests"},
|
||||||
@@ -147,17 +148,9 @@ def test_check_notification_plaintext_format(client, live_server, measure_memory
|
|||||||
|
|
||||||
msg_raw = get_last_message_from_smtp_server()
|
msg_raw = get_last_message_from_smtp_server()
|
||||||
assert len(msg_raw) >= 1
|
assert len(msg_raw) >= 1
|
||||||
#time.sleep(60)
|
|
||||||
# Parse the email properly using Python's email library
|
# Parse the email properly using Python's email library
|
||||||
msg = message_from_string(msg_raw, policy=email_policy)
|
msg = message_from_string(msg_raw, policy=email_policy)
|
||||||
# Subject/title got marked up
|
|
||||||
subject = msg['subject']
|
|
||||||
# Subject should always be plaintext and never marked up to anything else
|
|
||||||
assert REMOVED_PLACEMARKER_OPEN not in subject
|
|
||||||
assert CHANGED_PLACEMARKER_OPEN not in subject
|
|
||||||
assert ADDED_PLACEMARKER_OPEN not in subject
|
|
||||||
assert 'diff added didnt split' not in subject
|
|
||||||
assert '(changed) Which is across' in subject
|
|
||||||
|
|
||||||
# The email should be plain text only (not multipart)
|
# The email should be plain text only (not multipart)
|
||||||
assert not msg.is_multipart()
|
assert not msg.is_multipart()
|
||||||
@@ -184,7 +177,7 @@ def test_check_notification_html_color_format(client, live_server, measure_memor
|
|||||||
res = client.post(
|
res = client.post(
|
||||||
url_for("settings.settings_page"),
|
url_for("settings.settings_page"),
|
||||||
data={"application-notification_urls": notification_url,
|
data={"application-notification_urls": notification_url,
|
||||||
"application-notification_title": "fallback-title {{watch_title}} - diff_added_lines_test : '{{ diff_added.splitlines()[0] if diff_added else 'diff added didnt split' }}' " + default_notification_title,
|
"application-notification_title": "fallback-title " + default_notification_title,
|
||||||
"application-notification_body": f"some text\n{default_notification_body}\nMore output test\n{ALL_MARKUP_TOKENS}",
|
"application-notification_body": f"some text\n{default_notification_body}\nMore output test\n{ALL_MARKUP_TOKENS}",
|
||||||
"application-notification_format": 'htmlcolor',
|
"application-notification_format": 'htmlcolor',
|
||||||
"requests-time_between_check-minutes": 180,
|
"requests-time_between_check-minutes": 180,
|
||||||
@@ -218,18 +211,6 @@ def test_check_notification_html_color_format(client, live_server, measure_memor
|
|||||||
|
|
||||||
# Parse the email properly using Python's email library
|
# Parse the email properly using Python's email library
|
||||||
msg = message_from_string(msg_raw, policy=email_policy)
|
msg = message_from_string(msg_raw, policy=email_policy)
|
||||||
# Subject/title got marked up
|
|
||||||
subject = msg['subject']
|
|
||||||
# Subject should always be plaintext and never marked up to anything else
|
|
||||||
assert REMOVED_PLACEMARKER_OPEN not in subject
|
|
||||||
assert CHANGED_PLACEMARKER_OPEN not in subject
|
|
||||||
assert ADDED_PLACEMARKER_OPEN not in subject
|
|
||||||
assert 'diff added didnt split' not in subject
|
|
||||||
assert '(changed) Which is across' in subject
|
|
||||||
assert 'head title' in subject
|
|
||||||
assert "span" not in subject
|
|
||||||
assert 'background-color' not in subject
|
|
||||||
|
|
||||||
|
|
||||||
# The email should have two bodies (multipart/alternative with text/plain and text/html)
|
# The email should have two bodies (multipart/alternative with text/plain and text/html)
|
||||||
assert msg.is_multipart()
|
assert msg.is_multipart()
|
||||||
@@ -268,7 +249,7 @@ def test_check_notification_markdown_format(client, live_server, measure_memory_
|
|||||||
res = client.post(
|
res = client.post(
|
||||||
url_for("settings.settings_page"),
|
url_for("settings.settings_page"),
|
||||||
data={"application-notification_urls": notification_url,
|
data={"application-notification_urls": notification_url,
|
||||||
"application-notification_title": "fallback-title diff_added_lines_test : '{{ diff_added.splitlines()[0] if diff_added else 'diff added didnt split' }}' " + default_notification_title,
|
"application-notification_title": "fallback-title " + default_notification_title,
|
||||||
"application-notification_body": "*header*\n\nsome text\n" + default_notification_body,
|
"application-notification_body": "*header*\n\nsome text\n" + default_notification_body,
|
||||||
"application-notification_format": 'markdown',
|
"application-notification_format": 'markdown',
|
||||||
"requests-time_between_check-minutes": 180,
|
"requests-time_between_check-minutes": 180,
|
||||||
@@ -306,14 +287,6 @@ def test_check_notification_markdown_format(client, live_server, measure_memory_
|
|||||||
# The email should have two bodies (multipart/alternative with text/plain and text/html)
|
# The email should have two bodies (multipart/alternative with text/plain and text/html)
|
||||||
assert msg.is_multipart()
|
assert msg.is_multipart()
|
||||||
assert msg.get_content_type() == 'multipart/alternative'
|
assert msg.get_content_type() == 'multipart/alternative'
|
||||||
subject = msg['subject']
|
|
||||||
# Subject should always be plaintext and never marked up to anything else
|
|
||||||
assert REMOVED_PLACEMARKER_OPEN not in subject
|
|
||||||
assert CHANGED_PLACEMARKER_OPEN not in subject
|
|
||||||
assert ADDED_PLACEMARKER_OPEN not in subject
|
|
||||||
assert 'diff added didnt split' not in subject
|
|
||||||
assert '(changed) Which is across' in subject
|
|
||||||
|
|
||||||
|
|
||||||
# Get the parts
|
# Get the parts
|
||||||
parts = list(msg.iter_parts())
|
parts = list(msg.iter_parts())
|
||||||
@@ -332,10 +305,7 @@ def test_check_notification_markdown_format(client, live_server, measure_memory_
|
|||||||
assert html_part.get_content_type() == 'text/html'
|
assert html_part.get_content_type() == 'text/html'
|
||||||
html_content = html_part.get_content()
|
html_content = html_part.get_content()
|
||||||
assert '<p><em>header</em></p>' in html_content
|
assert '<p><em>header</em></p>' in html_content
|
||||||
assert '<strong>So let\'s see what happens.</strong><br />' in html_content # Additions are <strong> in markdown
|
assert '<strong>So let\'s see what happens.</strong><br>' in html_content # Additions are <strong> in markdown
|
||||||
# the '<br />' will come from apprises conversion, not from our code, we would rather use '<br>' correctly
|
|
||||||
# the '<br />' is actually a nice way to know if apprise done the conversion.
|
|
||||||
|
|
||||||
delete_all_watches(client)
|
delete_all_watches(client)
|
||||||
|
|
||||||
# Custom notification body with HTML, that is either sent as HTML or rendered to plaintext and sent
|
# Custom notification body with HTML, that is either sent as HTML or rendered to plaintext and sent
|
||||||
@@ -782,6 +752,7 @@ def test_check_html_notification_with_apprise_format_is_html(client, live_server
|
|||||||
text_content = text_part.get_content()
|
text_content = text_part.get_content()
|
||||||
assert '(added) So let\'s see what happens.\r\n' in text_content # The plaintext part
|
assert '(added) So let\'s see what happens.\r\n' in text_content # The plaintext part
|
||||||
assert 'fallback-body\r\n' in text_content # The plaintext part
|
assert 'fallback-body\r\n' in text_content # The plaintext part
|
||||||
|
assert CUSTOM_LINEBREAK_PLACEHOLDER not in text_content
|
||||||
|
|
||||||
# Second part should be text/html
|
# Second part should be text/html
|
||||||
html_part = parts[1]
|
html_part = parts[1]
|
||||||
@@ -790,4 +761,5 @@ def test_check_html_notification_with_apprise_format_is_html(client, live_server
|
|||||||
assert 'some text<br>' in html_content # We converted \n from the notification body
|
assert 'some text<br>' in html_content # We converted \n from the notification body
|
||||||
assert 'fallback-body<br>' in html_content # kept the original <br>
|
assert 'fallback-body<br>' in html_content # kept the original <br>
|
||||||
assert '(added) So let\'s see what happens.<br>' in html_content # the html part
|
assert '(added) So let\'s see what happens.<br>' in html_content # the html part
|
||||||
|
assert CUSTOM_LINEBREAK_PLACEHOLDER not in html_content
|
||||||
delete_all_watches(client)
|
delete_all_watches(client)
|
||||||
@@ -404,15 +404,15 @@ def test_notification_custom_endpoint_and_jinja2(client, live_server, measure_me
|
|||||||
|
|
||||||
|
|
||||||
#2510
|
#2510
|
||||||
#@todo run it again as text, html, htmlcolor
|
|
||||||
def test_global_send_test_notification(client, live_server, measure_memory_usage, datastore_path):
|
def test_global_send_test_notification(client, live_server, measure_memory_usage, datastore_path):
|
||||||
|
|
||||||
|
|
||||||
set_original_response(datastore_path=datastore_path)
|
set_original_response(datastore_path=datastore_path)
|
||||||
if os.path.isfile(os.path.join(datastore_path, "notification.txt")):
|
if os.path.isfile(os.path.join(datastore_path, "notification.txt")):
|
||||||
os.unlink(os.path.join(datastore_path, "notification.txt")) \
|
os.unlink(os.path.join(datastore_path, "notification.txt")) \
|
||||||
|
|
||||||
# 1995 UTF-8 content should be encoded
|
# 1995 UTF-8 content should be encoded
|
||||||
test_body = 'change detection is cool 网站监测 内容更新了 - {{diff_full}}'
|
test_body = 'change detection is cool 网站监测 内容更新了'
|
||||||
|
|
||||||
# otherwise other settings would have already existed from previous tests in this file
|
# otherwise other settings would have already existed from previous tests in this file
|
||||||
res = client.post(
|
res = client.post(
|
||||||
@@ -452,14 +452,7 @@ def test_global_send_test_notification(client, live_server, measure_memory_usage
|
|||||||
|
|
||||||
with open(os.path.join(datastore_path, "notification.txt"), 'r') as f:
|
with open(os.path.join(datastore_path, "notification.txt"), 'r') as f:
|
||||||
x = f.read()
|
x = f.read()
|
||||||
assert 'change detection is cool 网站监测 内容更新了' in x
|
assert test_body in x
|
||||||
if 'html' in default_notification_format:
|
|
||||||
# this should come from default text when in global/system mode here changedetectionio/notification_service.py
|
|
||||||
assert 'title="Changed into">Example text:' in x
|
|
||||||
else:
|
|
||||||
assert 'title="Changed into">Example text:' not in x
|
|
||||||
assert 'span' not in x
|
|
||||||
assert 'Example text:' in x
|
|
||||||
|
|
||||||
os.unlink(os.path.join(datastore_path, "notification.txt"))
|
os.unlink(os.path.join(datastore_path, "notification.txt"))
|
||||||
|
|
||||||
@@ -516,47 +509,6 @@ def test_global_send_test_notification(client, live_server, measure_memory_usage
|
|||||||
assert b"Error: You must have atleast one watch configured for 'test notification' to work" in res.data
|
assert b"Error: You must have atleast one watch configured for 'test notification' to work" in res.data
|
||||||
|
|
||||||
|
|
||||||
#2510
|
|
||||||
def test_single_send_test_notification_on_watch(client, live_server, measure_memory_usage, datastore_path):
|
|
||||||
|
|
||||||
set_original_response(datastore_path=datastore_path)
|
|
||||||
if os.path.isfile(os.path.join(datastore_path, "notification.txt")):
|
|
||||||
os.unlink(os.path.join(datastore_path, "notification.txt")) \
|
|
||||||
|
|
||||||
|
|
||||||
test_url = url_for('test_endpoint', _external=True)
|
|
||||||
uuid = client.application.config.get('DATASTORE').add_watch(url=test_url)
|
|
||||||
client.get(url_for("ui.form_watch_checknow"), follow_redirects=True)
|
|
||||||
|
|
||||||
test_notification_url = url_for('test_notification_endpoint', _external=True).replace('http://', 'post://')+"?xxx={{ watch_url }}&+custom-header=123"
|
|
||||||
# 1995 UTF-8 content should be encoded
|
|
||||||
test_body = 'change detection is cool 网站监测 内容更新了 - {{diff_full}}'
|
|
||||||
######### Test global/system settings
|
|
||||||
res = client.post(
|
|
||||||
url_for("ui.ui_notification.ajax_callback_send_notification_test")+f"/{uuid}",
|
|
||||||
data={"notification_urls": test_notification_url,
|
|
||||||
"notification_body": test_body,
|
|
||||||
"notification_format": default_notification_format,
|
|
||||||
"notification_title": "New ChangeDetection.io Notification - {{ watch_url }}",
|
|
||||||
},
|
|
||||||
follow_redirects=True
|
|
||||||
)
|
|
||||||
|
|
||||||
assert res.status_code != 400
|
|
||||||
assert res.status_code != 500
|
|
||||||
|
|
||||||
with open(os.path.join(datastore_path, "notification.txt"), 'r') as f:
|
|
||||||
x = f.read()
|
|
||||||
assert 'change detection is cool 网站监测 内容更新了' in x
|
|
||||||
if 'html' in default_notification_format:
|
|
||||||
# this should come from default text when in global/system mode here changedetectionio/notification_service.py
|
|
||||||
assert 'title="Changed into">Example text:' in x
|
|
||||||
else:
|
|
||||||
assert 'title="Changed into">Example text:' not in x
|
|
||||||
assert 'span' not in x
|
|
||||||
assert 'Example text:' 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):
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user