Compare commits

..

22 Commits

Author SHA1 Message Date
dgtlmoon
c143b38a5f Test the visual selector data loads as JSON 2022-08-31 16:18:31 +02:00
dgtlmoon
2c6faa7c4e Cleaner separation of watch/global notification settings (#894) 2022-08-31 15:49:13 +02:00
dgtlmoon
6168cd2899 Code maintenance - Removing old function (#875) 2022-08-31 15:23:10 +02:00
dgtlmoon
f3c7c969d8 Show screenshot age in [preview] 2022-08-25 11:18:00 +02:00
dgtlmoon
1355c2a245 Update README.md 2022-08-25 11:00:20 +02:00
dgtlmoon
96cf1a06df Update README.md 2022-08-24 23:26:55 +02:00
dgtlmoon
019a4a0375 Update README.md 2022-08-24 09:52:11 +02:00
dgtlmoon
db2f7b80ea Update bug_report.md 2022-08-20 15:30:53 +02:00
dgtlmoon
bfabd7b094 Update bug_report.md 2022-08-20 15:28:01 +02:00
dgtlmoon
d92dbfe765 Update README.md 2022-08-20 00:39:37 +02:00
dgtlmoon
67d2441334 0.39.18 2022-08-19 11:37:26 +02:00
dgtlmoon
3c30bc02d5 More data saving pre-checks (#863) 2022-08-18 23:25:23 +02:00
dgtlmoon
dcb54117d5 Update screenshot 2022-08-18 15:29:34 +02:00
dgtlmoon
b1e32275dc Checkbox operations - reorder buttons for safety 2022-08-18 15:10:05 +02:00
dgtlmoon
e2a6865932 UI feature - Basic checkbox/group operations (#861) 2022-08-18 14:48:21 +02:00
dgtlmoon
f04adb7202 Bug fix - automatically queued watch checks weren't always being processed sequentially 2022-08-18 13:41:28 +02:00
dgtlmoon
1193a7f22c Playwright - Support proxy auth mechanisms (#859) 2022-08-18 09:46:28 +02:00
dgtlmoon
0b976827bb Update README.md 2022-08-17 22:11:04 +02:00
dgtlmoon
280e916033 Update README.md 2022-08-17 22:00:46 +02:00
dgtlmoon
5494e61a05 Skip processing when watch was deleted 2022-08-17 13:29:32 +02:00
dgtlmoon
e461c0b819 Playwright fetcher didn't report low level HTTP errors correctly (like Connection Refused) (#852) 2022-08-17 13:25:08 +02:00
dgtlmoon
d67c654f37 Be sure visual-selector data is set when xPath/CSS filter is not yet found (#851) 2022-08-17 13:21:06 +02:00
19 changed files with 295 additions and 60 deletions

View File

@@ -7,6 +7,20 @@ assignees: 'dgtlmoon'
---
**DO NOT USE THIS FORM TO REPORT THAT A PARTICULAR WEBSITE IS NOT SCRAPING/WATCHING AS EXPECTED**
This form is only for direct bugs and feature requests todo directly with the software.
Please report watched websites (full URL and _any_ settings) that do not work with changedetection.io as expected [**IN THE DISCUSSION FORUMS**](https://github.com/dgtlmoon/changedetection.io/discussions) or your report will be deleted
CONSIDER TAKING OUT A SUBSCRIPTION FOR A SMALL PRICE PER MONTH, YOU GET THE BENEFIT OF USING OUR PAID PROXIES AND FURTHERING THE DEVELOPMENT OF CHANGEDETECTION.IO
THANK YOU
**Describe the bug**
A clear and concise description of what the bug is.

View File

@@ -1,23 +1,16 @@
# changedetection.io
## Web Site Change Detection, Monitoring and Notification.
Live your data-life pro-actively, track website content changes and receive notifications via Discord, Email, Slack, Telegram and 70+ more
[<img src="https://raw.githubusercontent.com/dgtlmoon/changedetection.io/master/docs/screenshot.png" style="max-width:100%;" alt="Self-hosted web page change monitoring" title="Self-hosted web page change monitoring" />](https://lemonade.changedetection.io/start)
[![Release Version][release-shield]][release-link] [![Docker Pulls][docker-pulls]][docker-link] [![License][license-shield]](LICENSE.md)
![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 or SaaS.
Know when important content changes, we support notifications via Discord, Telegram, Home-Assistant, Slack, Email and 70+ more
_Know when web pages change! Stay ontop of new information! get notifications when important website content changes_
Live your data-life *pro-actively* instead of *re-actively*.
Free, Open-source web page monitoring, notification and change detection. Don't have time? [**Try our $6.99/month subscription - unlimited checks and watches!**](https://lemonade.changedetection.io/start)
[<img src="https://raw.githubusercontent.com/dgtlmoon/changedetection.io/master/docs/screenshot.png" style="max-width:100%;" alt="Self-hosted web page change monitoring" title="Self-hosted web page change monitoring" />](https://lemonade.changedetection.io/start)
**Get your own private instance now! Let us host it for you!**
[**Try our $6.99/month subscription - unlimited checks and watches!**](https://lemonade.changedetection.io/start) , _half the price of other website change monitoring services and comes with unlimited watches & checks!_
[**Don't have time? Let us host it for you! try our $6.99/month subscription - use our proxies and support!**](https://lemonade.changedetection.io/start) , _half the price of other website change monitoring services and comes with unlimited watches & checks!_
@@ -46,7 +39,18 @@ Free, Open-source web page monitoring, notification and change detection. Don't
- 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!</a>_
_Need an actual Chrome runner with Javascript support? We support fetching via WebDriver and Playwright!</a>_
#### 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
## Screenshots

View File

@@ -44,7 +44,7 @@ from flask_wtf import CSRFProtect
from changedetectionio import html_tools
from changedetectionio.api import api_v1
__version__ = '0.39.17.2'
__version__ = '0.39.18'
datastore = None
@@ -503,7 +503,7 @@ def changedetection_app(config=None, datastore_o=None):
from changedetectionio import fetch_site_status
# Get the most recent one
newest_history_key = datastore.get_val(uuid, 'newest_history_key')
newest_history_key = datastore.data['watching'][uuid].get('newest_history_key')
# 0 means that theres only one, so that there should be no 'unviewed' history available
if newest_history_key == 0:
@@ -1186,6 +1186,36 @@ def changedetection_app(config=None, datastore_o=None):
flash("{} watches are queued for rechecking.".format(i))
return redirect(url_for('index', tag=tag))
@app.route("/form/checkbox-operations", methods=['POST'])
@login_required
def form_watch_list_checkbox_operations():
op = request.form['op']
uuids = request.form.getlist('uuids')
if (op == 'delete'):
for uuid in uuids:
uuid = uuid.strip()
if datastore.data['watching'].get(uuid):
datastore.delete(uuid.strip())
flash("{} watches deleted".format(len(uuids)))
if (op == 'pause'):
for uuid in uuids:
uuid = uuid.strip()
if datastore.data['watching'].get(uuid):
datastore.data['watching'][uuid.strip()]['paused'] = True
flash("{} watches paused".format(len(uuids)))
if (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)))
return redirect(url_for('index'))
@app.route("/api/share-url", methods=['GET'])
@login_required
def form_share_put_watch():
@@ -1385,13 +1415,18 @@ def ticker_thread_check_time_launch_checks():
seconds_since_last_recheck = now - watch['last_checked']
if seconds_since_last_recheck >= (threshold + watch.jitter_seconds) and seconds_since_last_recheck >= recheck_time_minimum_seconds:
if not uuid in running_uuids and uuid not in [q_uuid for p,q_uuid in update_q.queue]:
print("> Queued watch UUID {} last checked at {} queued at {:0.2f} jitter {:0.2f}s, {:0.2f}s since last checked".format(uuid,
watch['last_checked'],
now,
watch.jitter_seconds,
now - watch['last_checked']))
# Use Epoch time as priority, so we get a "sorted" PriorityQueue, but we can still push a priority 1 into it.
priority = int(time.time())
print(
"> Queued watch UUID {} last checked at {} queued at {:0.2f} priority {} jitter {:0.2f}s, {:0.2f}s since last checked".format(
uuid,
watch['last_checked'],
now,
priority,
watch.jitter_seconds,
now - watch['last_checked']))
# Into the queue with you
update_q.put((5, uuid))
update_q.put((priority, uuid))
# Reset for next time
watch.jitter_seconds = 0

View File

@@ -31,11 +31,12 @@ class JSActionExceptions(Exception):
return
class PageUnloadable(Exception):
def __init__(self, status_code, url, screenshot=False):
def __init__(self, status_code, url, screenshot=False, message=False):
# Set this so we can use it in other parts of the app
self.status_code = status_code
self.url = url
self.screenshot = screenshot
self.message = message
return
class EmptyReply(Exception):
@@ -292,7 +293,15 @@ class base_html_playwright(Fetcher):
# allow per-watch proxy selection override
if proxy_override:
self.proxy = {'server': proxy_override}
# https://playwright.dev/docs/network#http-proxy
from urllib.parse import urlparse
parsed = urlparse(proxy_override)
proxy_url = "{}://{}:{}".format(parsed.scheme, parsed.hostname, parsed.port)
self.proxy = {'server': proxy_url}
if parsed.username:
self.proxy['username'] = parsed.username
if parsed.password:
self.proxy['password'] = parsed.password
def run(self,
url,
@@ -356,7 +365,7 @@ class base_html_playwright(Fetcher):
print(str(e))
context.close()
browser.close()
raise PageUnloadable(url=url, status_code=None)
raise PageUnloadable(url=url, status_code=None, message=e.message)
if response is None:
context.close()

View File

@@ -63,13 +63,11 @@ class perform_site_check():
def run(self, uuid):
timestamp = int(time.time()) # used for storage etc too
changed_detected = False
screenshot = False # as bytes
stripped_text_from_html = ""
watch = self.datastore.data['watching'][uuid]
watch = self.datastore.data['watching'].get(uuid)
# Protect against file:// access
if re.search(r'^file', watch['url'], re.IGNORECASE) and not os.getenv('ALLOW_FILE_URI', False):
@@ -80,7 +78,7 @@ class perform_site_check():
# Unset any existing notification error
update_obj = {'last_notification_error': False, 'last_error': False}
extra_headers = self.datastore.get_val(uuid, 'headers')
extra_headers =self.datastore.data['watching'][uuid].get('headers')
# Tweak the base config with the per-watch ones
request_headers = self.datastore.data['settings']['headers'].copy()
@@ -93,9 +91,9 @@ class perform_site_check():
request_headers['Accept-Encoding'] = request_headers['Accept-Encoding'].replace(', br', '')
timeout = self.datastore.data['settings']['requests']['timeout']
url = self.datastore.get_val(uuid, 'url')
request_body = self.datastore.get_val(uuid, 'body')
request_method = self.datastore.get_val(uuid, 'method')
url = watch.get('url')
request_body = self.datastore.data['watching'][uuid].get('body')
request_method = self.datastore.data['watching'][uuid].get('method')
ignore_status_codes = self.datastore.data['watching'][uuid].get('ignore_status_codes', False)
# source: support

View File

@@ -355,6 +355,8 @@ class watchForm(commonSettingsForm):
filter_failure_notification_send = BooleanField(
'Send a notification when the filter can no longer be found on the page', default=False)
notification_use_default = BooleanField('Use default/system notification settings', default=True)
def validate(self, **kwargs):
if not super().validate():
return False

View File

@@ -35,6 +35,7 @@ class model(dict):
'notification_title': default_notification_title,
'notification_body': default_notification_body,
'notification_format': default_notification_format,
'notification_use_default': True, # Use default for new
'notification_muted': False,
'css_filter': '',
'last_error': False,

View File

@@ -22,5 +22,18 @@ $(function () {
});
});
// checkboxes - check all
$("#check-all").click(function (e) {
$('input[type=checkbox]').not(this).prop('checked', this.checked);
});
// checkboxes - show/hide buttons
$("input[type=checkbox]").click(function (e) {
if ($('input[type=checkbox]:checked').length) {
$('#checkbox-operations').slideDown();
} else {
$('#checkbox-operations').slideUp();
}
});
});

View File

@@ -1,7 +1,7 @@
$(document).ready(function() {
function toggle() {
$(document).ready(function () {
function toggle_fetch_backend() {
if ($('input[name="fetch_backend"]:checked').val() == 'html_webdriver') {
if(playwright_enabled) {
if (playwright_enabled) {
// playwright supports headers, so hide everything else
// See #664
$('#requests-override-options #request-method').hide();
@@ -13,12 +13,8 @@ $(document).ready(function() {
// selenium/webdriver doesnt support anything afaik, hide it all
$('#requests-override-options').hide();
}
$('#webdriver-override-options').show();
} else {
$('#requests-override-options').show();
$('#requests-override-options *:hidden').show();
$('#webdriver-override-options').hide();
@@ -26,8 +22,27 @@ $(document).ready(function() {
}
$('input[name="fetch_backend"]').click(function (e) {
toggle();
toggle_fetch_backend();
});
toggle();
toggle_fetch_backend();
function toggle_default_notifications() {
var n=$('#notification_urls, #notification_title, #notification_body, #notification_format');
if ($('#notification_use_default').is(':checked')) {
$('#notification-field-group').fadeOut();
$(n).each(function (e) {
$(this).attr('readonly', true);
});
} else {
$('#notification-field-group').show();
$(n).each(function (e) {
$(this).attr('readonly', false);
});
}
}
$('#notification_use_default').click(function (e) {
toggle_default_notifications();
});
toggle_default_notifications();
});

View File

@@ -555,3 +555,13 @@ ul {
.snapshot-age.error {
background-color: #ff0000;
color: #fff; }
#checkbox-operations {
background: rgba(0, 0, 0, 0.05);
padding: 1em;
border-radius: 10px;
margin-bottom: 1em;
display: none; }
.checkbox-uuid > * {
vertical-align: middle; }

View File

@@ -774,3 +774,15 @@ ul {
}
}
#checkbox-operations {
background: rgba(0, 0, 0, 0.05);
padding: 1em;
border-radius: 10px;
margin-bottom: 1em;
display: none;
}
.checkbox-uuid {
> * {
vertical-align: middle;
}
}

View File

@@ -244,10 +244,6 @@ class ChangeDetectionStore:
return False
def get_val(self, uuid, val):
# Probably their should be dict...
return self.data['watching'][uuid].get(val)
# Remove a watchs data but keep the entry (URL etc)
def clear_watch_history(self, uuid):
import pathlib
@@ -341,6 +337,8 @@ class ChangeDetectionStore:
# Save as PNG, PNG is larger but better for doing visual diff in the future
def save_screenshot(self, watch_uuid, screenshot: bytes, as_error=False):
if not self.data['watching'].get(watch_uuid):
return
if as_error:
target_path = os.path.join(self.datastore_path, watch_uuid, "last-error-screenshot.png")
@@ -354,14 +352,16 @@ class ChangeDetectionStore:
f.close()
def save_error_text(self, watch_uuid, contents):
if not self.data['watching'].get(watch_uuid):
return
target_path = os.path.join(self.datastore_path, watch_uuid, "last-error.txt")
with open(target_path, 'w') as f:
f.write(contents)
def save_xpath_data(self, watch_uuid, data, as_error=False):
if not self.data['watching'].get(watch_uuid):
return
if as_error:
target_path = os.path.join(self.datastore_path, watch_uuid, "elements-error.json")
else:
@@ -535,4 +535,28 @@ class ChangeDetectionStore:
del(watch['last_changed'])
except:
continue
return
def update_5(self):
from changedetectionio.notification import (
default_notification_body,
default_notification_format,
default_notification_title,
)
for uuid, watch in self.data['watching'].items():
try:
# If it's all the same to the system settings, then prefer system notification settings
# include \r\n -> \n incase they already hit submit and the browser put \r in
if watch.get('notification_body').replace('\r\n', '\n') == default_notification_body.replace('\r\n', '\n') and \
watch.get('notification_format') == default_notification_format and \
watch.get('notification_title').replace('\r\n', '\n') == default_notification_title.replace('\r\n', '\n') and \
watch.get('notification_urls') == self.__data['settings']['application']['notification_urls']:
watch['notification_use_default'] = True
else:
watch['notification_use_default'] = False
except:
continue
return

View File

@@ -135,9 +135,11 @@ User-Agent: wonderbra 1.0") }}
</div>
<div class="tab-pane-inner" id="notifications">
<strong>Note: <i>These settings override the global settings for this watch.</i></strong>
<fieldset>
<div class="field-group">
<div class="pure-control-group inline-radio">
{{ render_checkbox_field(form.notification_use_default) }}
</div>
<div class="field-group" id="notification-field-group">
{{ render_common_settings_form(form, current_base_url, emailprefix) }}
</div>
</fieldset>

View File

@@ -57,6 +57,7 @@
</br>
{% if is_html_webdriver %}
{% if screenshot %}
<div class="snapshot-age">{{watch.snapshot_screenshot_ctime|format_timestamp_timeago}}</div>
<img style="max-width: 80%" id="screenshot-img" alt="Current screenshot from most recent request"/>
{% else %}
No screenshot available just yet! Try rechecking the page.

View File

@@ -24,6 +24,14 @@
</fieldset>
<span style="color:#eee; font-size: 80%;"><img style="height: 1em;display:inline-block;" src="{{url_for('static_content', group='images', filename='spread-white.svg')}}" /> Tip: You can also add 'shared' watches. <a href="https://github.com/dgtlmoon/changedetection.io/wiki/Sharing-a-Watch">More info</a></a></span>
</form>
<form class="pure-form" action="{{ url_for('form_watch_list_checkbox_operations') }}" method="POST" id="watch-list-form">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<div id="checkbox-operations">
<button class="pure-button button-secondary button-xsmall" style="font-size: 70%" name="op" value="pause">Pause</button>
<button class="pure-button button-secondary button-xsmall" style="font-size: 70%" name="op" value="unpause">UnPause</button>
<button class="pure-button button-secondary button-xsmall" style="background: #dd4242; font-size: 70%" name="op" value="delete">Delete</button>
</div>
<div>
<a href="{{url_for('index')}}" class="pure-button button-tag {{'active' if not active_tag }}">All</a>
{% for tag in tags %}
@@ -41,7 +49,7 @@
<table class="pure-table pure-table-striped watch-table">
<thead>
<tr>
<th>#</th>
<th><input style="vertical-align: middle" type="checkbox" id="check-all"/> #</th>
<th></th>
{% set link_order = "desc" if sort_order else "asc" %}
{% set arrow_span = "" %}
@@ -66,7 +74,7 @@
{% if watch.paused is defined and watch.paused != False %}paused{% endif %}
{% if watch.newest_history_key| int > watch.last_viewed and watch.history_n>=2 %}unviewed{% endif %}
{% if watch.uuid in queued_uuids %}queued{% endif %}">
<td class="inline">{{ loop.index }}</td>
<td class="inline checkbox-uuid" ><input name="uuids" type="checkbox" value="{{ watch.uuid}} "/> <span>{{ loop.index }}</span></td>
<td class="inline watch-controls">
<a class="state-{{'on' if watch.paused }}" href="{{url_for('index', op='pause', uuid=watch.uuid, tag=active_tag)}}"><img src="{{url_for('static_content', group='images', filename='pause.svg')}}" alt="Pause checks" title="Pause checks"/></a>
<a class="state-{{'on' if watch.notification_muted}}" href="{{url_for('index', op='mute', uuid=watch.uuid, tag=active_tag)}}"><img src="{{url_for('static_content', group='images', filename='bell-off.svg')}}" alt="Mute notifications" title="Mute notifications"/></a>
@@ -129,5 +137,6 @@
#}
</div>
</form>
</div>
{% endblock %}

View File

@@ -71,6 +71,7 @@ def test_check_notification(client, live_server):
"url": test_url,
"tag": "my tag",
"title": "my title",
# No 'notification_use_default' here, so it's effectively False/off
"headers": "",
"fetch_backend": "html_requests"})
@@ -215,3 +216,82 @@ def test_notification_validation(client, live_server):
url_for("form_delete", uuid="all"),
follow_redirects=True
)
# Check that the default VS watch specific notification is hit
def test_check_notification_use_default(client, live_server):
set_original_response()
notification_url = url_for('test_notification_endpoint', _external=True).replace('http', 'json')
test_url = url_for('test_endpoint', _external=True)
res = client.post(
url_for("form_quick_watch_add"),
data={"url": test_url, "tag": ''},
follow_redirects=True
)
assert b"Watch added" in res.data
## Setup the local one and enable it
res = client.post(
url_for("edit_page", uuid="first"),
data={"notification_urls": notification_url,
"notification_title": "watch-notification",
"notification_body": "watch-body",
'notification_use_default': "True",
"notification_format": "Text",
"url": test_url,
"tag": "my tag",
"title": "my title",
"headers": "",
"fetch_backend": "html_requests"},
follow_redirects=True
)
res = client.post(
url_for("settings_page"),
data={"application-notification_title": "global-notifications-title",
"application-notification_body": "global-notifications-body\n",
"application-notification_format": "Text",
"application-notification_urls": notification_url,
"requests-time_between_check-minutes": 180,
"fetch_backend": "html_requests"
},
follow_redirects=True
)
# A change should by default trigger a notification of the global-notifications
time.sleep(1)
set_modified_response()
client.get(url_for("form_watch_checknow"), follow_redirects=True)
time.sleep(2)
with open("test-datastore/notification.txt", "r") as f:
assert 'global-notifications-title' in f.read()
## Setup the local one and enable it
res = client.post(
url_for("edit_page", uuid="first"),
data={"notification_urls": notification_url,
"notification_title": "watch-notification",
"notification_body": "watch-body",
# No 'notification_use_default' here, so it's effectively False/off = "dont use default, use this one"
"notification_format": "Text",
"url": test_url,
"tag": "my tag",
"title": "my title",
"headers": "",
"fetch_backend": "html_requests"},
follow_redirects=True
)
set_original_response()
client.get(url_for("form_watch_checknow"), follow_redirects=True)
time.sleep(2)
assert os.path.isfile("test-datastore/notification.txt")
with open("test-datastore/notification.txt", "r") as f:
assert 'watch-notification' in f.read()
# cleanup for the next
client.get(
url_for("form_delete", uuid="all"),
follow_redirects=True
)

View File

@@ -7,6 +7,7 @@ from ..util import live_server_setup, wait_for_all_checks, extract_UUID_from_cli
# Add a site in paused mode, add an invalid filter, we should still have visual selector data ready
def test_visual_selector_content_ready(client, live_server):
import os
import json
assert os.getenv('PLAYWRIGHT_DRIVER_URL'), "Needs PLAYWRIGHT_DRIVER_URL set for this test"
live_server_setup(live_server)
@@ -33,3 +34,7 @@ def test_visual_selector_content_ready(client, live_server):
uuid = extract_UUID_from_client(client)
assert os.path.isfile(os.path.join('test-datastore', uuid, 'last-screenshot.png')), "last-screenshot.png should exist"
assert os.path.isfile(os.path.join('test-datastore', uuid, 'elements.json')), "xpath elements.json data should exist"
# Open it and see if it roughly looks correct
with open(os.path.join('test-datastore', uuid, 'elements.json'), 'r') as f:
json.load(f)

View File

@@ -41,7 +41,7 @@ class update_worker(threading.Thread):
)
# Did it have any notification alerts to hit?
if len(watch['notification_urls']):
if not watch.get('notification_use_default') and len(watch['notification_urls']):
print(">>> Notifications queued for UUID from watch {}".format(watch_uuid))
n_object['notification_urls'] = watch['notification_urls']
n_object['notification_title'] = watch['notification_title']
@@ -49,7 +49,7 @@ class update_worker(threading.Thread):
n_object['notification_format'] = watch['notification_format']
# No? maybe theres a global setting, queue them all
elif len(self.datastore.data['settings']['application']['notification_urls']):
elif watch.get('notification_use_default') and len(self.datastore.data['settings']['application']['notification_urls']):
print(">>> Watch notification URLs were empty, using GLOBAL notifications for UUID: {}".format(watch_uuid))
n_object['notification_urls'] = self.datastore.data['settings']['application']['notification_urls']
n_object['notification_title'] = self.datastore.data['settings']['application']['notification_title']
@@ -222,8 +222,10 @@ class update_worker(threading.Thread):
self.datastore.update_watch(uuid=uuid, update_obj={'last_error': err_text,
'last_check_status': e.status_code})
except content_fetcher.PageUnloadable as e:
# @todo connection-refused ?
err_text = "Page request from server didnt respond correctly"
if e.message:
err_text = "{} - {}".format(err_text, e.message)
if e.screenshot:
self.datastore.save_screenshot(watch_uuid=uuid, screenshot=e.screenshot, as_error=True)
@@ -235,6 +237,9 @@ class update_worker(threading.Thread):
# Other serious error
process_changedetection_results = False
else:
# Crash protection, the watch entry could have been removed by this point (during a slow chrome fetch etc)
if not self.datastore.data['watching'].get(uuid):
continue
# Mark that we never had any failures
if not self.datastore.data['watching'][uuid].get('ignore_status_codes'):
@@ -242,10 +247,6 @@ class update_worker(threading.Thread):
self.cleanup_error_artifacts(uuid)
# Crash protection, the watch entry could have been removed by this point (during a slow chrome fetch etc)
if not self.datastore.data['watching'].get(uuid):
continue
# Different exceptions mean that we may or may not want to bump the snapshot, trigger notifications etc
if process_changedetection_results:
try:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 190 KiB

After

Width:  |  Height:  |  Size: 209 KiB