diff --git a/MANIFEST.in b/MANIFEST.in index eaf04a6f..88601b4b 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,9 +1,9 @@ recursive-include changedetectionio/api * -recursive-include changedetectionio/apprise_plugin * recursive-include changedetectionio/blueprint * recursive-include changedetectionio/content_fetchers * recursive-include changedetectionio/conditions * recursive-include changedetectionio/model * +recursive-include changedetectionio/notification * recursive-include changedetectionio/processors * recursive-include changedetectionio/static * recursive-include changedetectionio/templates * diff --git a/changedetectionio/api/api_schema.py b/changedetectionio/api/api_schema.py index 2f9dcaba..c181d6a0 100644 --- a/changedetectionio/api/api_schema.py +++ b/changedetectionio/api/api_schema.py @@ -1,5 +1,7 @@ # Responsible for building the storage dict into a set of rules ("JSON Schema") acceptable via the API # Probably other ways to solve this when the backend switches to some ORM +from changedetectionio.notification import valid_notification_formats + def build_time_between_check_json_schema(): # Setup time between check schema @@ -98,8 +100,6 @@ def build_watch_json_schema(d): } } - from changedetectionio.notification import valid_notification_formats - schema['properties']['notification_format'] = {'type': 'string', 'enum': list(valid_notification_formats.keys()) } diff --git a/changedetectionio/blueprint/ui/notification.py b/changedetectionio/blueprint/ui/notification.py index ac23ac3c..f20fb527 100644 --- a/changedetectionio/blueprint/ui/notification.py +++ b/changedetectionio/blueprint/ui/notification.py @@ -4,7 +4,6 @@ from loguru import logger from changedetectionio.store import ChangeDetectionStore from changedetectionio.auth_decorator import login_optionally_required -from changedetectionio.notification import process_notification def construct_blueprint(datastore: ChangeDetectionStore): notification_blueprint = Blueprint('ui_notification', __name__, template_folder="../ui/templates") @@ -18,8 +17,11 @@ def construct_blueprint(datastore: ChangeDetectionStore): # Watch_uuid could be unset in the case it`s used in tag editor, global settings import apprise - from ...apprise_plugin.assets import apprise_asset - from ...apprise_plugin.custom_handlers import apprise_http_custom_handler # noqa: F401 + from changedetectionio.notification.handler import process_notification + from changedetectionio.notification.apprise_plugin.assets import apprise_asset + + from changedetectionio.notification.apprise_plugin.custom_handlers import apprise_http_custom_handler + apobj = apprise.Apprise(asset=apprise_asset) is_global_settings_form = request.args.get('mode', '') == 'global-settings' diff --git a/changedetectionio/flask_app.py b/changedetectionio/flask_app.py index 5b720118..b91b93b4 100644 --- a/changedetectionio/flask_app.py +++ b/changedetectionio/flask_app.py @@ -514,7 +514,8 @@ def notification_runner(): sent_obj = None try: - from changedetectionio import notification + from changedetectionio.notification.handler import process_notification + # Fallback to system config if not set if not n_object.get('notification_body') and datastore.data['settings']['application'].get('notification_body'): n_object['notification_body'] = datastore.data['settings']['application'].get('notification_body') @@ -524,8 +525,8 @@ def notification_runner(): if not n_object.get('notification_format') and datastore.data['settings']['application'].get('notification_format'): n_object['notification_format'] = datastore.data['settings']['application'].get('notification_format') - - sent_obj = notification.process_notification(n_object, datastore) + if n_object.get('notification_urls', {}): + sent_obj = process_notification(n_object, datastore) except Exception as e: logger.error(f"Watch URL: {n_object['watch_url']} Error {str(e)}") diff --git a/changedetectionio/forms.py b/changedetectionio/forms.py index 7561d540..74d84c0e 100644 --- a/changedetectionio/forms.py +++ b/changedetectionio/forms.py @@ -306,8 +306,8 @@ class ValidateAppRiseServers(object): def __call__(self, form, field): import apprise - from .apprise_plugin.assets import apprise_asset - from .apprise_plugin.custom_handlers import apprise_http_custom_handler # noqa: F401 + from .notification.apprise_plugin.assets import apprise_asset + from .notification.apprise_plugin.custom_handlers import apprise_http_custom_handler # noqa: F401 apobj = apprise.Apprise(asset=apprise_asset) diff --git a/changedetectionio/model/__init__.py b/changedetectionio/model/__init__.py index 49b3209c..383a9c55 100644 --- a/changedetectionio/model/__init__.py +++ b/changedetectionio/model/__init__.py @@ -2,7 +2,7 @@ import os import uuid from changedetectionio import strtobool -from changedetectionio.notification import default_notification_format_for_watch +default_notification_format_for_watch = 'System default' class watch_base(dict): diff --git a/changedetectionio/notification/__init__.py b/changedetectionio/notification/__init__.py new file mode 100644 index 00000000..0129f244 --- /dev/null +++ b/changedetectionio/notification/__init__.py @@ -0,0 +1,35 @@ +from changedetectionio.model import default_notification_format_for_watch + +ult_notification_format_for_watch = 'System default' +default_notification_format = 'HTML Color' +default_notification_body = '{{watch_url}} had a change.\n---\n{{diff}}\n---\n' +default_notification_title = 'ChangeDetection.io Notification - {{watch_url}}' + +# The values (markdown etc) are from apprise NotifyFormat, +# But to avoid importing the whole heavy module just use the same strings here. +valid_notification_formats = { + 'Text': 'text', + 'Markdown': 'markdown', + 'HTML': 'html', + 'HTML Color': 'htmlcolor', + # Used only for editing a watch (not for global) + default_notification_format_for_watch: default_notification_format_for_watch +} + + +valid_tokens = { + 'base_url': '', + 'current_snapshot': '', + 'diff': '', + 'diff_added': '', + 'diff_full': '', + 'diff_patch': '', + 'diff_removed': '', + 'diff_url': '', + 'preview_url': '', + 'triggered_text': '', + 'watch_tag': '', + 'watch_title': '', + 'watch_url': '', + 'watch_uuid': '', +} diff --git a/changedetectionio/notification/apprise_plugin/__init__.py b/changedetectionio/notification/apprise_plugin/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/changedetectionio/apprise_plugin/assets.py b/changedetectionio/notification/apprise_plugin/assets.py similarity index 100% rename from changedetectionio/apprise_plugin/assets.py rename to changedetectionio/notification/apprise_plugin/assets.py diff --git a/changedetectionio/apprise_plugin/custom_handlers.py b/changedetectionio/notification/apprise_plugin/custom_handlers.py similarity index 100% rename from changedetectionio/apprise_plugin/custom_handlers.py rename to changedetectionio/notification/apprise_plugin/custom_handlers.py diff --git a/changedetectionio/notification.py b/changedetectionio/notification/handler.py similarity index 86% rename from changedetectionio/notification.py rename to changedetectionio/notification/handler.py index 857a137d..2fae5d3d 100644 --- a/changedetectionio/notification.py +++ b/changedetectionio/notification/handler.py @@ -1,47 +1,17 @@ import time -from apprise import NotifyFormat import apprise from loguru import logger -from .apprise_plugin.assets import APPRISE_AVATAR_URL -from .apprise_plugin.custom_handlers import apprise_http_custom_handler # noqa: F401 -from .safe_jinja import render as jinja_render - -valid_tokens = { - 'base_url': '', - 'current_snapshot': '', - 'diff': '', - 'diff_added': '', - 'diff_full': '', - 'diff_patch': '', - 'diff_removed': '', - 'diff_url': '', - 'preview_url': '', - 'triggered_text': '', - 'watch_tag': '', - 'watch_title': '', - 'watch_url': '', - 'watch_uuid': '', -} - -default_notification_format_for_watch = 'System default' -default_notification_format = 'HTML Color' -default_notification_body = '{{watch_url}} had a change.\n---\n{{diff}}\n---\n' -default_notification_title = 'ChangeDetection.io Notification - {{watch_url}}' - -valid_notification_formats = { - 'Text': NotifyFormat.TEXT, - 'Markdown': NotifyFormat.MARKDOWN, - 'HTML': NotifyFormat.HTML, - 'HTML Color': 'htmlcolor', - # Used only for editing a watch (not for global) - default_notification_format_for_watch: default_notification_format_for_watch -} - +from .apprise_plugin.assets import apprise_asset, APPRISE_AVATAR_URL 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 + now = time.time() if n_object.get('notification_timestamp'): logger.trace(f"Time since queued {now-n_object['notification_timestamp']:.3f}s") @@ -58,14 +28,13 @@ def process_notification(n_object, datastore): # Initially text or whatever n_format = datastore.data['settings']['application'].get('notification_format', valid_notification_formats[default_notification_format]) - logger.trace(f"Complete notification body including Jinja and placeholders calculated in {time.time() - now:.3f}s") + logger.trace(f"Complete notification body including Jinja and placeholders calculated in {time.time() - now:.2f}s") # https://github.com/caronc/apprise/wiki/Development_LogCapture # Anything higher than or equal to WARNING (which covers things like Connection errors) # raise it as an exception sent_objs = [] - from .apprise_plugin.assets import apprise_asset if 'as_async' in n_object: apprise_asset.async_mode = n_object.get('as_async') @@ -176,6 +145,7 @@ def process_notification(n_object, datastore): # ( Where we prepare the tokens in the notification to be replaced with actual values ) def create_notification_parameters(n_object, datastore): from copy import deepcopy + from . import valid_tokens # in the case we send a test notification from the main settings, there is no UUID. uuid = n_object['uuid'] if 'uuid' in n_object else '' diff --git a/changedetectionio/tests/test_notification.py b/changedetectionio/tests/test_notification.py index 0a734f0c..1d4a1984 100644 --- a/changedetectionio/tests/test_notification.py +++ b/changedetectionio/tests/test_notification.py @@ -167,7 +167,10 @@ def test_check_notification(client, live_server, measure_memory_usage): assert ':-)' in notification_submission # Check the attachment was added, and that it is a JPEG from the original PNG notification_submission_object = json.loads(notification_submission) + assert notification_submission_object + # We keep PNG screenshots for now + # IF THIS FAILS YOU SHOULD BE TESTING WITH ENV VAR REMOVE_REQUESTS_OLD_SCREENSHOTS=False assert notification_submission_object['attachments'][0]['filename'] == 'last-screenshot.png' assert len(notification_submission_object['attachments'][0]['base64']) assert notification_submission_object['attachments'][0]['mimetype'] == 'image/png' diff --git a/changedetectionio/update_worker.py b/changedetectionio/update_worker.py index 2e9e2294..6f05b7f7 100644 --- a/changedetectionio/update_worker.py +++ b/changedetectionio/update_worker.py @@ -109,7 +109,6 @@ class update_worker(threading.Thread): default_notification_title ) - # Would be better if this was some kind of Object where Watch can reference the parent datastore etc v = watch.get(var_name) if v and not watch.get('notification_muted'):