diff --git a/README-pip.md b/README-pip.md index 2ed19d73..746175db 100644 --- a/README-pip.md +++ b/README-pip.md @@ -1,45 +1,48 @@ -# changedetection.io -![changedetection.io](https://github.com/dgtlmoon/changedetection.io/actions/workflows/test-only.yml/badge.svg?branch=master) - - - - - - +## Web Site Change Detection, Monitoring and Notification. -## Self-hosted open source change monitoring of web pages. +Live your data-life pro-actively, track website content changes and receive notifications via Discord, Email, Slack, Telegram and 70+ more -_Know when web pages change! Stay ontop of new information!_ +[](https://lemonade.changedetection.io/start?src=pip) -Live your data-life *pro-actively* instead of *re-actively*, do not rely on manipulative social media for consuming important information. - - - - - -**Get your own private instance now! Let us host it for you!** - -[**Try our $6.99/month subscription - unlimited checks, watches and notifications!**](https://lemonade.changedetection.io/start), choose from different geographical locations, let us handle everything for you. +[**Don't have time? Let us host it for you! try our extremely affordable subscription use our proxies and support!**](https://lemonade.changedetection.io/start) #### Example use cases -Know when ... - -- Government department updates (changes are often only on their websites) -- Local government news (changes are often only on their websites) +- Products and services have a change in pricing +- _Out of stock notification_ and _Back In stock notification_ +- Governmental department updates (changes are often only on their websites) - New software releases, security advisories when you're not on their mailing list. - Festivals with changes - Realestate listing changes +- Know when your favourite whiskey is on sale, or other special deals are announced before anyone else - COVID related news from government websites +- University/organisation news from their website - Detect and monitor changes in JSON API responses -- API monitoring and alerting +- JSON API monitoring and alerting +- Changes in legal and other documents +- Trigger API calls via notifications when text appears on a website +- Glue together APIs using the JSON filter and JSON notifications +- Create RSS feeds based on changes in web content +- Monitor HTML source code for unexpected changes, strengthen your PCI compliance +- You have a very sensitive list of URLs to watch and you do _not_ want to use the paid alternatives. (Remember, _you_ are the product) + +_Need an actual Chrome runner with Javascript support? We support fetching via WebDriver and Playwright!_ + +#### Key Features + +- Lots of trigger filters, such as "Trigger on text", "Remove text by selector", "Ignore text", "Extract text", also using regular-expressions! +- Target elements with xPath and CSS Selectors, Easily monitor complex JSON with JsonPath rules +- Switch between fast non-JS and Chrome JS based "fetchers" +- Easily specify how often a site should be checked +- Execute JS before extracting text (Good for logging in, see examples in the UI!) +- Override Request Headers, Specify `POST` or `GET` and other methods +- Use the "Visual Selector" to help target specific elements -**Get monitoring now!** ```bash -$ pip3 install changedetection.io +$ pip3 install changedetection.io ``` Specify a target for the *datastore path* with `-d` (required) and a *listening port* with `-p` (defaults to `5000`) @@ -51,17 +54,5 @@ $ changedetection.io -d /path/to/empty/data/dir -p 5000 Then visit http://127.0.0.1:5000 , You should now be able to access the UI. -### Features -- Website monitoring -- Change detection of content and analyses -- Filters on change (Select by CSS or JSON) -- Triggers (Wait for text, wait for regex) -- Notification support -- JSON API Monitoring -- Parse JSON embedded in HTML -- (Reverse) Proxy support -- Javascript support via WebDriver -- RaspberriPi (arm v6/v7/64 support) - See https://github.com/dgtlmoon/changedetection.io for more information. diff --git a/README.md b/README.md index 6139888b..f2e75672 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Live your data-life pro-actively, track website content changes and receive notifications via Discord, Email, Slack, Telegram and 70+ more -[](https://lemonade.changedetection.io/start) +[](https://lemonade.changedetection.io/start?src=github) [![Release Version][release-shield]][release-link] [![Docker Pulls][docker-pulls]][docker-link] [![License][license-shield]](LICENSE.md) diff --git a/changedetectionio/__init__.py b/changedetectionio/__init__.py index d75f6a73..c9177403 100644 --- a/changedetectionio/__init__.py +++ b/changedetectionio/__init__.py @@ -1,16 +1,5 @@ #!/usr/bin/python3 - -# @todo logging -# @todo extra options for url like , verify=False etc. -# @todo enable https://urllib3.readthedocs.io/en/latest/user-guide.html#ssl as option? -# @todo option for interval day/6 hour/etc -# @todo on change detected, config for calling some API -# @todo fetch title into json -# https://distill.io/features -# proxy per check -# - flask_cors, itsdangerous,MarkupSafe - import datetime import os import queue @@ -44,7 +33,7 @@ from flask_wtf import CSRFProtect from changedetectionio import html_tools from changedetectionio.api import api_v1 -__version__ = '0.39.18' +__version__ = '0.39.19.1' datastore = None @@ -552,10 +541,6 @@ def changedetection_app(config=None, datastore_o=None): # be sure we update with a copy instead of accidently editing the live object by reference default = deepcopy(datastore.data['watching'][uuid]) - # Show system wide default if nothing configured - if datastore.data['watching'][uuid]['fetch_backend'] is None: - default['fetch_backend'] = datastore.data['settings']['application']['fetch_backend'] - # Show system wide default if nothing configured if all(value == 0 or value == None for value in datastore.data['watching'][uuid]['time_between_check'].values()): default['time_between_check'] = deepcopy(datastore.data['settings']['requests']['time_between_check']) @@ -598,10 +583,8 @@ def changedetection_app(config=None, datastore_o=None): if form.fetch_backend.data == datastore.data['settings']['application']['fetch_backend']: extra_update_obj['fetch_backend'] = None - # Notification URLs - datastore.data['watching'][uuid]['notification_urls'] = form.notification_urls.data - # Ignore text + # Ignore text form_ignore_text = form.ignore_text.data datastore.data['watching'][uuid]['ignore_text'] = form_ignore_text @@ -655,9 +638,11 @@ def changedetection_app(config=None, datastore_o=None): watch=datastore.data['watching'][uuid], form=form, has_empty_checktime=using_default_check_time, + has_default_notification_urls=True if len(datastore.data['settings']['application']['notification_urls']) else False, using_global_webdriver_wait=default['webdriver_delay'] is None, current_base_url=datastore.data['settings']['application']['base_url'], emailprefix=os.getenv('NOTIFICATION_MAIL_BUTTON_PREFIX', False), + settings_application=datastore.data['settings']['application'], visualselector_data_is_ready=visualselector_data_is_ready, visualselector_enabled=visualselector_enabled, playwright_enabled=os.getenv('PLAYWRIGHT_DRIVER_URL', False) @@ -687,6 +672,10 @@ def changedetection_app(config=None, datastore_o=None): form = forms.globalSettingsForm(formdata=request.form if request.method == 'POST' else None, data=default ) + + # Remove the last option 'System default' + form.application.form.notification_format.choices.pop() + if datastore.proxy_list is None: # @todo - Couldn't get setattr() etc dynamic addition working, so remove it instead del form.requests.form.proxy @@ -732,7 +721,8 @@ def changedetection_app(config=None, datastore_o=None): current_base_url = datastore.data['settings']['application']['base_url'], hide_remove_pass=os.getenv("SALTED_PASS", False), api_key=datastore.data['settings']['application'].get('api_access_token'), - emailprefix=os.getenv('NOTIFICATION_MAIL_BUTTON_PREFIX', False)) + emailprefix=os.getenv('NOTIFICATION_MAIL_BUTTON_PREFIX', False), + settings_application=datastore.data['settings']['application']) return output @@ -1199,7 +1189,7 @@ def changedetection_app(config=None, datastore_o=None): datastore.delete(uuid.strip()) flash("{} watches deleted".format(len(uuids))) - if (op == 'pause'): + elif (op == 'pause'): for uuid in uuids: uuid = uuid.strip() if datastore.data['watching'].get(uuid): @@ -1207,13 +1197,40 @@ def changedetection_app(config=None, datastore_o=None): flash("{} watches paused".format(len(uuids))) - if (op == 'unpause'): + elif (op == 'unpause'): for uuid in uuids: uuid = uuid.strip() if datastore.data['watching'].get(uuid): datastore.data['watching'][uuid.strip()]['paused'] = False flash("{} watches unpaused".format(len(uuids))) + elif (op == 'mute'): + for uuid in uuids: + uuid = uuid.strip() + if datastore.data['watching'].get(uuid): + datastore.data['watching'][uuid.strip()]['notification_muted'] = True + flash("{} watches muted".format(len(uuids))) + + elif (op == 'unmute'): + for uuid in uuids: + uuid = uuid.strip() + if datastore.data['watching'].get(uuid): + datastore.data['watching'][uuid.strip()]['notification_muted'] = False + flash("{} watches un-muted".format(len(uuids))) + + elif (op == 'notification-default'): + from changedetectionio.notification import ( + default_notification_format_for_watch + ) + for uuid in uuids: + uuid = uuid.strip() + if datastore.data['watching'].get(uuid): + datastore.data['watching'][uuid.strip()]['notification_title'] = None + datastore.data['watching'][uuid.strip()]['notification_body'] = None + datastore.data['watching'][uuid.strip()]['notification_urls'] = [] + datastore.data['watching'][uuid.strip()]['notification_format'] = default_notification_format_for_watch + flash("{} watches set to use default notification settings".format(len(uuids))) + return redirect(url_for('index')) @app.route("/api/share-url", methods=['GET']) diff --git a/changedetectionio/forms.py b/changedetectionio/forms.py index 13d576a4..279f7c7f 100644 --- a/changedetectionio/forms.py +++ b/changedetectionio/forms.py @@ -314,14 +314,14 @@ class quickWatchForm(Form): # Common to a single watch and the global settings class commonSettingsForm(Form): - - notification_urls = StringListField('Notification URL list', validators=[validators.Optional(), ValidateNotificationBodyAndTitleWhenURLisSet(), ValidateAppRiseServers()]) - notification_title = StringField('Notification title', default=default_notification_title, validators=[validators.Optional(), ValidateTokensList()]) - notification_body = TextAreaField('Notification body', default=default_notification_body, validators=[validators.Optional(), ValidateTokensList()]) - notification_format = SelectField('Notification format', choices=valid_notification_formats.keys(), default=default_notification_format) + notification_urls = StringListField('Notification URL list', validators=[validators.Optional(), ValidateAppRiseServers()]) + notification_title = StringField('Notification title', validators=[validators.Optional(), ValidateTokensList()]) + notification_body = TextAreaField('Notification body', validators=[validators.Optional(), ValidateTokensList()]) + notification_format = SelectField('Notification format', choices=valid_notification_formats.keys()) fetch_backend = RadioField(u'Fetch method', choices=content_fetcher.available_fetchers(), validators=[ValidateContentFetcherIsReady()]) extract_title_as_title = BooleanField('Extract
{diff_url}
) require the BASE_URL
environment variable set.BASE_URL
var is currently "{{current_base_url}}"
+ Your BASE_URL
var is currently "{{settings_application['current_base_url']}}"