mirror of
https://github.com/dgtlmoon/changedetection.io.git
synced 2026-06-13 12:21:04 +00:00
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 62e99d41be | |||
| cdf07983db | |||
| 27401b3202 | |||
| 4baa9489c3 | |||
| 503e67f33e | |||
| f290d0e5fe | |||
| 933d4ce886 | |||
| 50c7e1bf8c | |||
| 73eb00a3e9 | |||
| e75561b2f5 | |||
| 005ed20741 | |||
| 2b0f851cbc | |||
| 56aeefc53e | |||
| f7c7a3dbb8 | |||
| e5775dd68e | |||
| 97e4ae1194 | |||
| de955a54bd | |||
| 5037e66ec1 | |||
| db48cc64f5 |
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
# Read more https://github.com/dgtlmoon/changedetection.io/wiki
|
# Read more https://github.com/dgtlmoon/changedetection.io/wiki
|
||||||
|
|
||||||
__version__ = '0.45.24'
|
__version__ = '0.45.23'
|
||||||
|
|
||||||
from changedetectionio.strtobool import strtobool
|
from changedetectionio.strtobool import strtobool
|
||||||
from json.decoder import JSONDecodeError
|
from json.decoder import JSONDecodeError
|
||||||
|
|||||||
@@ -679,10 +679,7 @@ def changedetection_app(config=None, datastore_o=None):
|
|||||||
|
|
||||||
if request.method == 'POST' and form.validate():
|
if request.method == 'POST' and form.validate():
|
||||||
|
|
||||||
extra_update_obj = {
|
extra_update_obj = {}
|
||||||
'consecutive_filter_failures': 0,
|
|
||||||
'last_error' : False
|
|
||||||
}
|
|
||||||
|
|
||||||
if request.args.get('unpause_on_save'):
|
if request.args.get('unpause_on_save'):
|
||||||
extra_update_obj['paused'] = False
|
extra_update_obj['paused'] = False
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ from changedetectionio.notification import (
|
|||||||
default_notification_title,
|
default_notification_title,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Equal to or greater than this number of FilterNotFoundInResponse exceptions will trigger a filter-not-found notification
|
|
||||||
_FILTER_FAILURE_THRESHOLD_ATTEMPTS_DEFAULT = 6
|
_FILTER_FAILURE_THRESHOLD_ATTEMPTS_DEFAULT = 6
|
||||||
DEFAULT_SETTINGS_HEADERS_USERAGENT='Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36'
|
DEFAULT_SETTINGS_HEADERS_USERAGENT='Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36'
|
||||||
|
|
||||||
|
|||||||
@@ -2,12 +2,13 @@
|
|||||||
|
|
||||||
import time
|
import time
|
||||||
from flask import url_for
|
from flask import url_for
|
||||||
from .util import live_server_setup, wait_for_all_checks
|
from . util import live_server_setup
|
||||||
|
|
||||||
|
|
||||||
def test_basic_auth(client, live_server):
|
def test_basic_auth(client, live_server):
|
||||||
|
|
||||||
live_server_setup(live_server)
|
live_server_setup(live_server)
|
||||||
|
# Give the endpoint time to spin up
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
# Add our URL to the import page
|
# Add our URL to the import page
|
||||||
test_url = url_for('test_basicauth_method', _external=True).replace("//","//myuser:mypass@")
|
test_url = url_for('test_basicauth_method', _external=True).replace("//","//myuser:mypass@")
|
||||||
@@ -18,8 +19,8 @@ def test_basic_auth(client, live_server):
|
|||||||
follow_redirects=True
|
follow_redirects=True
|
||||||
)
|
)
|
||||||
assert b"1 Imported" in res.data
|
assert b"1 Imported" in res.data
|
||||||
wait_for_all_checks(client)
|
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
||||||
# Check form validation
|
# Check form validation
|
||||||
res = client.post(
|
res = client.post(
|
||||||
url_for("edit_page", uuid="first"),
|
url_for("edit_page", uuid="first"),
|
||||||
@@ -28,7 +29,7 @@ def test_basic_auth(client, live_server):
|
|||||||
)
|
)
|
||||||
assert b"Updated watch." in res.data
|
assert b"Updated watch." in res.data
|
||||||
|
|
||||||
wait_for_all_checks(client)
|
time.sleep(1)
|
||||||
res = client.get(
|
res = client.get(
|
||||||
url_for("preview_page", uuid="first"),
|
url_for("preview_page", uuid="first"),
|
||||||
follow_redirects=True
|
follow_redirects=True
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ def test_check_ldjson_price_autodetect(client, live_server):
|
|||||||
|
|
||||||
# Accept it
|
# Accept it
|
||||||
uuid = extract_UUID_from_client(client)
|
uuid = extract_UUID_from_client(client)
|
||||||
time.sleep(1)
|
|
||||||
client.get(url_for('price_data_follower.accept', uuid=uuid, follow_redirects=True))
|
client.get(url_for('price_data_follower.accept', uuid=uuid, follow_redirects=True))
|
||||||
wait_for_all_checks(client)
|
wait_for_all_checks(client)
|
||||||
|
|
||||||
|
|||||||
@@ -62,6 +62,9 @@ def test_check_basic_change_detection_functionality(client, live_server):
|
|||||||
# Make a change
|
# Make a change
|
||||||
set_modified_response()
|
set_modified_response()
|
||||||
|
|
||||||
|
res = urlopen(url_for('test_endpoint', _external=True))
|
||||||
|
assert b'which has this one new line' in res.read()
|
||||||
|
|
||||||
# Force recheck
|
# Force recheck
|
||||||
res = client.get(url_for("form_watch_checknow"), follow_redirects=True)
|
res = client.get(url_for("form_watch_checknow"), follow_redirects=True)
|
||||||
assert b'1 watches queued for rechecking.' in res.data
|
assert b'1 watches queued for rechecking.' in res.data
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
import time
|
import time
|
||||||
from flask import url_for
|
from flask import url_for
|
||||||
from .util import live_server_setup, wait_for_all_checks
|
from .util import live_server_setup
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
@@ -27,6 +27,9 @@ def set_html_response():
|
|||||||
def test_check_encoding_detection(client, live_server):
|
def test_check_encoding_detection(client, live_server):
|
||||||
set_html_response()
|
set_html_response()
|
||||||
|
|
||||||
|
# Give the endpoint time to spin up
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
# Add our URL to the import page
|
# Add our URL to the import page
|
||||||
test_url = url_for('test_endpoint', content_type="text/html", _external=True)
|
test_url = url_for('test_endpoint', content_type="text/html", _external=True)
|
||||||
client.post(
|
client.post(
|
||||||
@@ -36,7 +39,7 @@ def test_check_encoding_detection(client, live_server):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Give the thread time to pick it up
|
# Give the thread time to pick it up
|
||||||
wait_for_all_checks(client)
|
time.sleep(2)
|
||||||
|
|
||||||
res = client.get(
|
res = client.get(
|
||||||
url_for("preview_page", uuid="first"),
|
url_for("preview_page", uuid="first"),
|
||||||
@@ -53,6 +56,9 @@ def test_check_encoding_detection(client, live_server):
|
|||||||
def test_check_encoding_detection_missing_content_type_header(client, live_server):
|
def test_check_encoding_detection_missing_content_type_header(client, live_server):
|
||||||
set_html_response()
|
set_html_response()
|
||||||
|
|
||||||
|
# Give the endpoint time to spin up
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
# Add our URL to the import page
|
# Add our URL to the import page
|
||||||
test_url = url_for('test_endpoint', _external=True)
|
test_url = url_for('test_endpoint', _external=True)
|
||||||
client.post(
|
client.post(
|
||||||
@@ -61,7 +67,8 @@ def test_check_encoding_detection_missing_content_type_header(client, live_serve
|
|||||||
follow_redirects=True
|
follow_redirects=True
|
||||||
)
|
)
|
||||||
|
|
||||||
wait_for_all_checks(client)
|
# Give the thread time to pick it up
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
res = client.get(
|
res = client.get(
|
||||||
url_for("preview_page", uuid="first"),
|
url_for("preview_page", uuid="first"),
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ def test_check_extract_text_from_diff(client, live_server):
|
|||||||
# Load in 5 different numbers/changes
|
# Load in 5 different numbers/changes
|
||||||
last_date=""
|
last_date=""
|
||||||
for n in range(5):
|
for n in range(5):
|
||||||
time.sleep(1)
|
|
||||||
# Give the thread time to pick it up
|
# Give the thread time to pick it up
|
||||||
print("Bumping snapshot and checking.. ", n)
|
print("Bumping snapshot and checking.. ", n)
|
||||||
last_date = str(time.time())
|
last_date = str(time.time())
|
||||||
|
|||||||
@@ -21,11 +21,10 @@ def set_response_with_filter():
|
|||||||
f.write(test_return_data)
|
f.write(test_return_data)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def run_filter_test(client, live_server, content_filter):
|
def run_filter_test(client, content_filter):
|
||||||
|
|
||||||
# Response WITHOUT the filter ID element
|
|
||||||
set_original_response()
|
|
||||||
|
|
||||||
|
# Give the endpoint time to spin up
|
||||||
|
time.sleep(1)
|
||||||
# cleanup for the next
|
# cleanup for the next
|
||||||
client.get(
|
client.get(
|
||||||
url_for("form_delete", uuid="all"),
|
url_for("form_delete", uuid="all"),
|
||||||
@@ -80,7 +79,6 @@ def run_filter_test(client, live_server, content_filter):
|
|||||||
"include_filters": content_filter,
|
"include_filters": content_filter,
|
||||||
"fetch_backend": "html_requests"})
|
"fetch_backend": "html_requests"})
|
||||||
|
|
||||||
# A POST here will also reset the filter failure counter (filter_failure_notification_threshold_attempts)
|
|
||||||
res = client.post(
|
res = client.post(
|
||||||
url_for("edit_page", uuid="first"),
|
url_for("edit_page", uuid="first"),
|
||||||
data=notification_form_data,
|
data=notification_form_data,
|
||||||
@@ -93,21 +91,20 @@ def run_filter_test(client, live_server, content_filter):
|
|||||||
# Now the notification should not exist, because we didnt reach the threshold
|
# Now the notification should not exist, because we didnt reach the threshold
|
||||||
assert not os.path.isfile("test-datastore/notification.txt")
|
assert not os.path.isfile("test-datastore/notification.txt")
|
||||||
|
|
||||||
# recheck it up to just before the threshold, including the fact that in the previous POST it would have rechecked (and incremented)
|
# -2 because we would have checked twice above (on adding and on edit)
|
||||||
for i in range(0, App._FILTER_FAILURE_THRESHOLD_ATTEMPTS_DEFAULT-2):
|
for i in range(0, App._FILTER_FAILURE_THRESHOLD_ATTEMPTS_DEFAULT-2):
|
||||||
client.get(url_for("form_watch_checknow"), follow_redirects=True)
|
res = client.get(url_for("form_watch_checknow"), follow_redirects=True)
|
||||||
wait_for_all_checks(client)
|
wait_for_all_checks(client)
|
||||||
time.sleep(2) # delay for apprise to fire
|
assert not os.path.isfile("test-datastore/notification.txt"), f"test-datastore/notification.txt should not exist - Attempt {i}"
|
||||||
assert not os.path.isfile("test-datastore/notification.txt"), f"test-datastore/notification.txt should not exist - Attempt {i} when threshold is {App._FILTER_FAILURE_THRESHOLD_ATTEMPTS_DEFAULT}"
|
|
||||||
|
|
||||||
# We should see something in the frontend
|
# We should see something in the frontend
|
||||||
res = client.get(url_for("index"))
|
|
||||||
assert b'Warning, no filters were found' in res.data
|
assert b'Warning, no filters were found' in res.data
|
||||||
|
|
||||||
# One more check should trigger the _FILTER_FAILURE_THRESHOLD_ATTEMPTS_DEFAULT threshold
|
# One more check should trigger it (see -2 above)
|
||||||
|
client.get(url_for("form_watch_checknow"), follow_redirects=True)
|
||||||
|
wait_for_all_checks(client)
|
||||||
client.get(url_for("form_watch_checknow"), follow_redirects=True)
|
client.get(url_for("form_watch_checknow"), follow_redirects=True)
|
||||||
wait_for_all_checks(client)
|
wait_for_all_checks(client)
|
||||||
time.sleep(2) # delay for apprise to fire
|
|
||||||
# Now it should exist and contain our "filter not found" alert
|
# Now it should exist and contain our "filter not found" alert
|
||||||
assert os.path.isfile("test-datastore/notification.txt")
|
assert os.path.isfile("test-datastore/notification.txt")
|
||||||
|
|
||||||
@@ -152,9 +149,13 @@ def test_setup(live_server):
|
|||||||
live_server_setup(live_server)
|
live_server_setup(live_server)
|
||||||
|
|
||||||
def test_check_include_filters_failure_notification(client, live_server):
|
def test_check_include_filters_failure_notification(client, live_server):
|
||||||
run_filter_test(client, live_server,'#nope-doesnt-exist')
|
set_original_response()
|
||||||
|
wait_for_all_checks(client)
|
||||||
|
run_filter_test(client, '#nope-doesnt-exist')
|
||||||
|
|
||||||
def test_check_xpath_filter_failure_notification(client, live_server):
|
def test_check_xpath_filter_failure_notification(client, live_server):
|
||||||
run_filter_test(client, live_server, '//*[@id="nope-doesnt-exist"]')
|
set_original_response()
|
||||||
|
time.sleep(1)
|
||||||
|
run_filter_test(client, '//*[@id="nope-doesnt-exist"]')
|
||||||
|
|
||||||
# Test that notification is never sent
|
# Test that notification is never sent
|
||||||
|
|||||||
@@ -69,7 +69,6 @@ def test_rss_and_token(client, live_server):
|
|||||||
|
|
||||||
wait_for_all_checks(client)
|
wait_for_all_checks(client)
|
||||||
set_modified_response()
|
set_modified_response()
|
||||||
time.sleep(1)
|
|
||||||
client.get(url_for("form_watch_checknow"), follow_redirects=True)
|
client.get(url_for("form_watch_checknow"), follow_redirects=True)
|
||||||
wait_for_all_checks(client)
|
wait_for_all_checks(client)
|
||||||
|
|
||||||
@@ -88,7 +87,7 @@ def test_rss_and_token(client, live_server):
|
|||||||
assert b"Access denied, bad token" not in res.data
|
assert b"Access denied, bad token" not in res.data
|
||||||
assert b"Random content" in res.data
|
assert b"Random content" in res.data
|
||||||
|
|
||||||
client.get(url_for("form_delete", uuid="all"), follow_redirects=True)
|
res = client.get(url_for("form_delete", uuid="all"), follow_redirects=True)
|
||||||
|
|
||||||
def test_basic_cdata_rss_markup(client, live_server):
|
def test_basic_cdata_rss_markup(client, live_server):
|
||||||
#live_server_setup(live_server)
|
#live_server_setup(live_server)
|
||||||
|
|||||||
@@ -348,7 +348,7 @@ class update_worker(threading.Thread):
|
|||||||
# Send notification if we reached the threshold?
|
# Send notification if we reached the threshold?
|
||||||
threshold = self.datastore.data['settings']['application'].get('filter_failure_notification_threshold_attempts',
|
threshold = self.datastore.data['settings']['application'].get('filter_failure_notification_threshold_attempts',
|
||||||
0)
|
0)
|
||||||
logger.warning(f"Filter for {uuid} not found, consecutive_filter_failures: {c}")
|
logger.error(f"Filter for {uuid} not found, consecutive_filter_failures: {c}")
|
||||||
if threshold > 0 and c >= threshold:
|
if threshold > 0 and c >= threshold:
|
||||||
if not self.datastore.data['watching'][uuid].get('notification_muted'):
|
if not self.datastore.data['watching'][uuid].get('notification_muted'):
|
||||||
self.send_filter_failure_notification(uuid)
|
self.send_filter_failure_notification(uuid)
|
||||||
@@ -362,6 +362,7 @@ class update_worker(threading.Thread):
|
|||||||
# Yes fine, so nothing todo, don't continue to process.
|
# Yes fine, so nothing todo, don't continue to process.
|
||||||
process_changedetection_results = False
|
process_changedetection_results = False
|
||||||
changed_detected = False
|
changed_detected = False
|
||||||
|
self.datastore.update_watch(uuid=uuid, update_obj={'last_error': False})
|
||||||
except content_fetchers.exceptions.BrowserConnectError as e:
|
except content_fetchers.exceptions.BrowserConnectError as e:
|
||||||
self.datastore.update_watch(uuid=uuid,
|
self.datastore.update_watch(uuid=uuid,
|
||||||
update_obj={'last_error': e.msg
|
update_obj={'last_error': e.msg
|
||||||
|
|||||||
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
# Used by Pyppeteer
|
# Used by Pyppeteer
|
||||||
pyee
|
pyee
|
||||||
|
|
||||||
eventlet>=0.36.1 # fixes SSL error on Python 3.12
|
eventlet==0.35.2 # related to dnspython fixes
|
||||||
feedgen~=0.9
|
feedgen~=0.9
|
||||||
flask-compress
|
flask-compress
|
||||||
# 0.6.3 included compatibility fix for werkzeug 3.x (2.x had deprecation of url handlers)
|
# 0.6.3 included compatibility fix for werkzeug 3.x (2.x had deprecation of url handlers)
|
||||||
@@ -29,7 +29,7 @@ chardet>2.3.0
|
|||||||
wtforms~=3.0
|
wtforms~=3.0
|
||||||
jsonpath-ng~=1.5.3
|
jsonpath-ng~=1.5.3
|
||||||
|
|
||||||
dnspython==2.6.1 # related to eventlet fixes
|
dnspython==2.6.1
|
||||||
|
|
||||||
# jq not available on Windows so must be installed manually
|
# jq not available on Windows so must be installed manually
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user