Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion MANIFEST.in
Original file line number Diff line number Diff line change
@@ -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 *
Expand Down
4 changes: 2 additions & 2 deletions changedetectionio/api/api_schema.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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())
}
Expand Down
8 changes: 5 additions & 3 deletions changedetectionio/blueprint/ui/notification.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

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")
Expand All @@ -18,8 +17,11 @@ def ajax_callback_send_notification_test(watch_uuid=None):

# 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'
Expand Down
7 changes: 4 additions & 3 deletions changedetectionio/flask_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand All @@ -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)}")
Expand Down
4 changes: 2 additions & 2 deletions changedetectionio/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -306,8 +306,8 @@ def __init__(self, message=None):

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)

Expand Down
6 changes: 4 additions & 2 deletions changedetectionio/html_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -477,8 +477,10 @@ def html_to_text(html_content: str, render_anchor_tag_content=False, is_rss=Fals
# Does LD+JSON exist with a @type=='product' and a .price set anywhere?
def has_ldjson_product_info(content):
try:
lc = content.lower()
if 'application/ld+json' in lc and lc.count('"price"') == 1 and '"pricecurrency"' in lc:
# Better than .lower() which can use a lot of ram
if (re.search(r'application/ld\+json', content, re.IGNORECASE) and
re.search(r'"price"', content, re.IGNORECASE) and
re.search(r'"pricecurrency"', content, re.IGNORECASE)):
return True

# On some pages this is really terribly expensive when they dont really need it
Expand Down
2 changes: 1 addition & 1 deletion changedetectionio/model/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
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):

Expand Down
35 changes: 35 additions & 0 deletions changedetectionio/notification/__init__.py
Original file line number Diff line number Diff line change
@@ -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': '',
}
Empty file.
Original file line number Diff line number Diff line change
@@ -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")
Expand All @@ -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')
Expand Down Expand Up @@ -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 ''
Expand Down
3 changes: 3 additions & 0 deletions changedetectionio/tests/test_notification.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
1 change: 0 additions & 1 deletion changedetectionio/update_worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,6 @@ def _check_cascading_vars(self, var_name, watch):
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'):
Expand Down