diff --git a/README.md b/README.md index 03e76a5e..395e3ac8 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,8 @@ Know when ... - Festivals with changes - Realestate listing changes - COVID related news from government websites +- Detect and monitor changes in JSON API responses +- API monitoring and alerting _Need an actual Chrome runner with Javascript support? see the experimental Javascript/Chrome support changedetection.io branch!_ @@ -88,9 +90,21 @@ Just some examples +### JSON API Monitoring + +Detect changes and monitor data in JSON API's by using the built-in JSONPath selectors as a filter. + + +![image](https://user-images.githubusercontent.com/275001/125165842-0ce01980-e1dc-11eb-9e73-d8137dd162dc.png) + +This will re-parse the JSON and apply indent to the text, making it super easy to monitor and detect changes in JSON API results + +![image](https://user-images.githubusercontent.com/275001/125165995-d9ea5580-e1dc-11eb-8030-f0deced2661a.png) + + ### Proxy -A proxy for ChangeDectection.io can be configured by setting environment the +A proxy for ChangeDetection.io can be configured by setting environment the `HTTP_PROXY`, `HTTPS_PROXY` variables, examples are also in the `docker-compose.yml` `NO_PROXY` exclude list can be specified by following `"localhost,192.168.0.0/24"` diff --git a/backend/fetch_site_status.py b/backend/fetch_site_status.py index 12216e19..dcefab15 100644 --- a/backend/fetch_site_status.py +++ b/backend/fetch_site_status.py @@ -88,12 +88,27 @@ class perform_site_check(): html = r.text - # CSS Filter, extract the HTML that matches and feed that into the existing inscriptis::get_text + is_html = True css_filter_rule = self.datastore.data['watching'][uuid]['css_filter'] if css_filter_rule and len(css_filter_rule.strip()): - html = html_tools.css_filter(css_filter=css_filter_rule, html_content=r.content) + if 'json:' in css_filter_rule: + # POC hack, @todo rename vars, see how it fits in with the javascript version + import json + from jsonpath_ng import jsonpath, parse - stripped_text_from_html = get_text(html) + json_data = json.loads(html) + jsonpath_expression = parse(css_filter_rule.replace('json:','')) + match = jsonpath_expression.find(json_data) + stripped_text_from_html = json.dumps(match[0].value, indent=4) + + is_html = False + + else: + # CSS Filter, extract the HTML that matches and feed that into the existing inscriptis::get_text + html = html_tools.css_filter(css_filter=css_filter_rule, html_content=r.content) + + if is_html: + stripped_text_from_html = get_text(html) # Usually from networkIO/requests level except (requests.exceptions.ConnectionError, requests.exceptions.ReadTimeout) as e: diff --git a/backend/forms.py b/backend/forms.py index ea8fd995..72bda422 100644 --- a/backend/forms.py +++ b/backend/forms.py @@ -82,7 +82,7 @@ class StringDictKeyValue(StringField): else: self.data = {} -class ListRegex(object): +class ValidateListRegex(object): """ Validates that anything that looks like a regex passes as a regex """ @@ -102,6 +102,28 @@ class ListRegex(object): message = field.gettext('RegEx \'%s\' is not a valid regular expression.') raise ValidationError(message % (line)) +class ValidateCSSJSONInput(object): + """ + Filter validation + @todo CSS validator ;) + """ + + def __init__(self, message=None): + self.message = message + + def __call__(self, form, field): + if 'json:' in field.data: + from jsonpath_ng.exceptions import JsonPathParserError + from jsonpath_ng import jsonpath, parse + + input = field.data.replace('json:', '') + + try: + parse(input) + except JsonPathParserError as e: + message = field.gettext('\'%s\' is not a valid JSONPath expression. (%s)') + raise ValidationError(message % (input, str(e))) + class watchForm(Form): # https://wtforms.readthedocs.io/en/2.3.x/fields/#module-wtforms.fields.html5 @@ -111,10 +133,10 @@ class watchForm(Form): tag = StringField('Tag', [validators.Optional(), validators.Length(max=35)]) minutes_between_check = html5.IntegerField('Maximum time in minutes until recheck', [validators.Optional(), validators.NumberRange(min=1)]) - css_filter = StringField('CSS Filter') + css_filter = StringField('CSS/JSON Filter', [ValidateCSSJSONInput()]) title = StringField('Title') - ignore_text = StringListField('Ignore Text', [ListRegex()]) + ignore_text = StringListField('Ignore Text', [ValidateListRegex()]) notification_urls = StringListField('Notification URL List') headers = StringDictKeyValue('Request Headers') trigger_check = BooleanField('Send test notification on save') diff --git a/backend/templates/edit.html b/backend/templates/edit.html index b6275477..cdeb139b 100644 --- a/backend/templates/edit.html +++ b/backend/templates/edit.html @@ -23,9 +23,12 @@