Compare commits

..

1 Commits

Author SHA1 Message Date
dgtlmoon
141aea07b8 JSONP - Attempt to strip out JSONP 2026-03-15 21:53:46 +01:00
22 changed files with 889 additions and 742 deletions

View File

@@ -587,10 +587,6 @@ jobs:
run: | run: |
docker run -e EXTRA_PACKAGES=changedetection.io-osint-processor test-changedetectionio bash -c 'cd changedetectionio;pytest -vvv -s tests/plugins/test_processor.py::test_check_plugin_processor' docker run -e EXTRA_PACKAGES=changedetection.io-osint-processor test-changedetectionio bash -c 'cd changedetectionio;pytest -vvv -s tests/plugins/test_processor.py::test_check_plugin_processor'
- name: Plugin get_html_head_extras hook injects into base.html
run: |
docker run test-changedetectionio bash -c 'cd changedetectionio;pytest -vvv -s tests/plugins/test_html_head_extras.py'
# Container startup tests # Container startup tests
container-tests: container-tests:
runs-on: ubuntu-latest runs-on: ubuntu-latest

View File

@@ -2,7 +2,7 @@
# Read more https://github.com/dgtlmoon/changedetection.io/wiki # Read more https://github.com/dgtlmoon/changedetection.io/wiki
# Semver means never use .01, or 00. Should be .1. # Semver means never use .01, or 00. Should be .1.
__version__ = '0.54.7' __version__ = '0.54.5'
from changedetectionio.strtobool import strtobool from changedetectionio.strtobool import strtobool
from json.decoder import JSONDecodeError from json.decoder import JSONDecodeError

View File

@@ -40,13 +40,12 @@ def construct_blueprint(datastore: ChangeDetectionStore):
contents = '' contents = ''
now = time.time() now = time.time()
try: try:
import asyncio
processor_module = importlib.import_module("changedetectionio.processors.text_json_diff.processor") processor_module = importlib.import_module("changedetectionio.processors.text_json_diff.processor")
update_handler = processor_module.perform_site_check(datastore=datastore, update_handler = processor_module.perform_site_check(datastore=datastore,
watch_uuid=uuid watch_uuid=uuid
) )
asyncio.run(update_handler.call_browser(preferred_proxy_id=preferred_proxy)) update_handler.call_browser(preferred_proxy_id=preferred_proxy)
# title, size is len contents not len xfer # title, size is len contents not len xfer
except content_fetcher_exceptions.Non200ErrorCodeReceived as e: except content_fetcher_exceptions.Non200ErrorCodeReceived as e:
if e.status_code == 404: if e.status_code == 404:

View File

@@ -154,8 +154,9 @@
</span> </span>
</div> </div>
<div class="pure-control-group"> <div class="pure-control-group">
<br> <br>
{{ _('Tip:') }} <a href="{{ url_for('settings.settings_page')}}#proxies">{{ _('Connect using Bright Data proxies, find out more here.') }}</a> {{ _('Tip:') }} <a href="https://github.com/dgtlmoon/changedetection.io/wiki/Proxy-configuration#brightdata-proxy-support">{{ _('Connect using Bright Data and Oxylabs Proxies, find out more here.') }}</a>
</div> </div>
</div> </div>
@@ -351,7 +352,7 @@ nav
</div> </div>
</div> </div>
<p><strong>{{ _('Tip') }}</strong>: {{ _('"Residential" and "Mobile" proxy type can be more successful than "Data Center" for blocked websites.') }}</p> <p><strong>{{ _('Tip') }}</strong>: {{ _('"Residential" and "Mobile" proxy type can be more successfull than "Data Center" for blocked websites.') }}</p>
<div class="pure-control-group" id="extra-proxies-setting"> <div class="pure-control-group" id="extra-proxies-setting">
{{ render_fieldlist_with_inline_errors(form.requests.form.extra_proxies) }} {{ render_fieldlist_with_inline_errors(form.requests.form.extra_proxies) }}

View File

@@ -212,11 +212,6 @@ def _is_safe_valid_url(test_url):
from .validate_url import is_safe_valid_url from .validate_url import is_safe_valid_url
return is_safe_valid_url(test_url) return is_safe_valid_url(test_url)
@app.template_global('get_html_head_extras')
def _get_html_head_extras():
from .pluggy_interface import collect_html_head_extras
return collect_html_head_extras()
@app.template_filter('format_number_locale') @app.template_filter('format_number_locale')
def _jinja2_filter_format_number_locale(value: float) -> str: def _jinja2_filter_format_number_locale(value: float) -> str:

View File

@@ -667,11 +667,9 @@ class ValidateCSSJSONXPATHInput(object):
# `jq` requires full compilation in windows and so isn't generally available # `jq` requires full compilation in windows and so isn't generally available
raise ValidationError("jq not support not found") raise ValidationError("jq not support not found")
from changedetectionio.html_tools import validate_jq_expression
input = line.replace('jq:', '') input = line.replace('jq:', '')
try: try:
validate_jq_expression(input)
jq.compile(input) jq.compile(input)
except (ValueError) as e: except (ValueError) as e:
message = field.gettext('\'%s\' is not a valid jq expression. (%s)') message = field.gettext('\'%s\' is not a valid jq expression. (%s)')
@@ -1007,7 +1005,7 @@ class globalSettingsApplicationForm(commonSettingsForm):
render_kw={"placeholder": "0.1", "style": "width: 8em;"} render_kw={"placeholder": "0.1", "style": "width: 8em;"}
) )
password = SaltyPasswordField(_l('Password'), render_kw={"autocomplete": "new-password"}) password = SaltyPasswordField(_l('Password'))
pager_size = IntegerField(_l('Pager size'), pager_size = IntegerField(_l('Pager size'),
render_kw={"style": "width: 5em;"}, render_kw={"style": "width: 5em;"},
validators=[validators.NumberRange(min=0, validators=[validators.NumberRange(min=0,

View File

@@ -4,7 +4,6 @@ from loguru import logger
from typing import List from typing import List
import html import html
import json import json
import os
import re import re
# HTML added to be sure each result matching a filter (.example) gets converted to a new line by Inscriptis # HTML added to be sure each result matching a filter (.example) gets converted to a new line by Inscriptis
@@ -14,45 +13,6 @@ PERL_STYLE_REGEX = r'^/(.*?)/([a-z]*)?$'
TITLE_RE = re.compile(r"<title[^>]*>(.*?)</title>", re.I | re.S) TITLE_RE = re.compile(r"<title[^>]*>(.*?)</title>", re.I | re.S)
META_CS = re.compile(r'<meta[^>]+charset=["\']?\s*([a-z0-9_\-:+.]+)', re.I) META_CS = re.compile(r'<meta[^>]+charset=["\']?\s*([a-z0-9_\-:+.]+)', re.I)
# jq builtins that can leak sensitive data or cause harm when user-supplied expressions are executed.
# env/$ENV reads all process environment variables (passwords, API keys, etc.)
# include/import can read arbitrary files from disk
# input/inputs reads beyond the supplied JSON data
# debug/stderr leaks data to stderr
# halt/halt_error terminates the process (DoS)
_JQ_BLOCKED_PATTERNS = [
(re.compile(r'\benv\b'), 'env (reads environment variables)'),
(re.compile(r'\$ENV\b'), '$ENV (reads environment variables)'),
(re.compile(r'\binclude\b'), 'include (reads files from disk)'),
(re.compile(r'\bimport\b'), 'import (reads files from disk)'),
(re.compile(r'\binputs?\b'), 'input/inputs (reads beyond provided data)'),
(re.compile(r'\bdebug\b'), 'debug (leaks data to stderr)'),
(re.compile(r'\bstderr\b'), 'stderr (leaks data to stderr)'),
(re.compile(r'\bhalt(?:_error)?\b'), 'halt/halt_error (terminates the process)'),
(re.compile(r'\$__loc__\b'), '$__loc__ (leaks file path information)'),
(re.compile(r'\bbuiltins\b'), 'builtins (enumerates available functions)'),
(re.compile(r'\bmodulemeta\b'), 'modulemeta (leaks module information)'),
(re.compile(r'\$JQ_BUILD_CONFIGURATION\b'), '$JQ_BUILD_CONFIGURATION (leaks build information)'),
]
def validate_jq_expression(expression: str) -> None:
"""Raise ValueError if the jq expression uses any dangerous builtin.
User-supplied jq expressions are executed server-side. Without this check,
builtins like `env` expose every process environment variable (SALTED_PASS,
proxy credentials, API keys, etc.) as watch output.
"""
from changedetectionio.strtobool import strtobool
if strtobool(os.getenv('JQ_ALLOW_RISKY_EXPRESSIONS', 'false')):
return
for pattern, description in _JQ_BLOCKED_PATTERNS:
if pattern.search(expression):
msg = f"jq expression uses disallowed builtin: {description}"
logger.critical(f"Security: blocked jq expression containing '{description}' - expression: {expression!r}")
raise ValueError(msg)
META_CT = re.compile(r'<meta[^>]+http-equiv=["\']?content-type["\']?[^>]*content=["\'][^>]*charset=([a-z0-9_\-:+.]+)', re.I) META_CT = re.compile(r'<meta[^>]+http-equiv=["\']?content-type["\']?[^>]*content=["\'][^>]*charset=([a-z0-9_\-:+.]+)', re.I)
# 'price' , 'lowPrice', 'highPrice' are usually under here # 'price' , 'lowPrice', 'highPrice' are usually under here
@@ -70,12 +30,6 @@ _DEFAULT_UNSAFE_XPATH3_FUNCTIONS = [
'unparsed-text-available', 'unparsed-text-available',
'doc', 'doc',
'doc-available', 'doc-available',
'json-doc',
'json-doc-available',
'collection', # XPath 2.0+: loads XML node collections from arbitrary URIs
'uri-collection', # XPath 3.0+: enumerates URIs from resource collections
'transform', # XPath 3.1: XSLT transformation (currently raises, block proactively)
'load-xquery-module', # XPath 3.1: loads XQuery modules (currently raises, block proactively)
'environment-variable', 'environment-variable',
'available-environment-variables', 'available-environment-variables',
] ]
@@ -424,16 +378,12 @@ def _parse_json(json_data, json_filter):
raise Exception("jq not support not found") raise Exception("jq not support not found")
if json_filter.startswith("jq:"): if json_filter.startswith("jq:"):
expr = json_filter.removeprefix("jq:") jq_expression = jq.compile(json_filter.removeprefix("jq:"))
validate_jq_expression(expr)
jq_expression = jq.compile(expr)
match = jq_expression.input(json_data).all() match = jq_expression.input(json_data).all()
return _get_stripped_text_from_json_match(match) return _get_stripped_text_from_json_match(match)
if json_filter.startswith("jqraw:"): if json_filter.startswith("jqraw:"):
expr = json_filter.removeprefix("jqraw:") jq_expression = jq.compile(json_filter.removeprefix("jqraw:"))
validate_jq_expression(expr)
jq_expression = jq.compile(expr)
match = jq_expression.input(json_data).all() match = jq_expression.input(json_data).all()
return '\n'.join(str(item) for item in match) return '\n'.join(str(item) for item in match)

View File

@@ -174,64 +174,6 @@ class ChangeDetectionSpec:
""" """
pass pass
@hookspec
def get_html_head_extras():
"""Return HTML to inject into the <head> of every page via base.html.
Plugins can use this to add <script>, <style>, or <link> tags that should
be present on all pages. Return a raw HTML string or None.
IMPORTANT: Always use Flask's url_for() for any src/href URLs so that
sub-path deployments (nginx reverse proxy with USE_X_SETTINGS / X-Forwarded-Prefix)
work correctly. This hook is called inside a request context so url_for() is
always available.
For small amounts of CSS/JS, return them inline — no file-serving needed::
from changedetectionio.pluggy_interface import hookimpl
@hookimpl
def get_html_head_extras(self):
return (
'<style>.my-module-banner { color: red; }</style>\\n'
'<script>console.log("my_module_content loaded");</script>'
)
For larger assets, register your own lightweight Flask routes in the plugin
module and point to them with url_for() so the sub-path prefix is handled
automatically::
from flask import url_for, Response
from changedetectionio.pluggy_interface import hookimpl
from changedetectionio.flask_app import app as _app
MY_CSS = ".my-module-example { color: red; }"
MY_JS = "console.log('my_module_content loaded');"
@_app.route('/my_module_content/css')
def my_module_content_css():
return Response(MY_CSS, mimetype='text/css',
headers={'Cache-Control': 'max-age=3600'})
@_app.route('/my_module_content/js')
def my_module_content_js():
return Response(MY_JS, mimetype='application/javascript',
headers={'Cache-Control': 'max-age=3600'})
@hookimpl
def get_html_head_extras(self):
css = url_for('my_module_content_css')
js = url_for('my_module_content_js')
return (
f'<link rel="stylesheet" href="{css}">\\n'
f'<script src="{js}" defer></script>'
)
Returns:
str or None: Raw HTML string to inject inside <head>, or None
"""
pass
# Set up Plugin Manager # Set up Plugin Manager
plugin_manager = pluggy.PluginManager(PLUGIN_NAMESPACE) plugin_manager = pluggy.PluginManager(PLUGIN_NAMESPACE)
@@ -665,19 +607,3 @@ def apply_update_finalize(update_handler, watch, datastore, processing_exception
# Don't let plugin errors crash the worker # Don't let plugin errors crash the worker
logger.error(f"Error in update_finalize hook: {e}") logger.error(f"Error in update_finalize hook: {e}")
logger.exception(f"update_finalize hook exception details:") logger.exception(f"update_finalize hook exception details:")
def collect_html_head_extras():
"""Collect and combine HTML head extras from all plugins.
Called from a Flask template global so it always runs inside a request context.
This means url_for() works correctly in plugin implementations, including when the
app is deployed under a sub-path via USE_X_SETTINGS / X-Forwarded-Prefix (ProxyFix
sets SCRIPT_NAME so url_for() automatically prepends the prefix).
Returns:
str: Combined HTML string to inject inside <head>, or empty string
"""
results = plugin_manager.hook.get_html_head_extras()
parts = [r for r in results if r]
return "\n".join(parts) if parts else ""

View File

@@ -1,7 +1,6 @@
from babel.numbers import parse_decimal from babel.numbers import parse_decimal
from changedetectionio.model.Watch import model as BaseWatch from changedetectionio.model.Watch import model as BaseWatch
from decimal import Decimal, InvalidOperation
from typing import Union from typing import Union
import re import re
@@ -11,8 +10,6 @@ supports_browser_steps = True
supports_text_filters_and_triggers = True supports_text_filters_and_triggers = True
supports_text_filters_and_triggers_elements = True supports_text_filters_and_triggers_elements = True
supports_request_type = True supports_request_type = True
_price_re = re.compile(r"Price:\s*(\d+(?:\.\d+)?)", re.IGNORECASE)
class Restock(dict): class Restock(dict):
@@ -66,17 +63,6 @@ class Restock(dict):
super().__setitem__(key, value) super().__setitem__(key, value)
def get_price_from_history_str(history_str):
m = _price_re.search(history_str)
if not m:
return None
try:
return str(Decimal(m.group(1)))
except InvalidOperation:
return None
class Watch(BaseWatch): class Watch(BaseWatch):
def __init__(self, *arg, **kw): def __init__(self, *arg, **kw):
super().__init__(*arg, **kw) super().__init__(*arg, **kw)
@@ -90,27 +76,13 @@ class Watch(BaseWatch):
def extra_notification_token_values(self): def extra_notification_token_values(self):
values = super().extra_notification_token_values() values = super().extra_notification_token_values()
values['restock'] = self.get('restock', {}) values['restock'] = self.get('restock', {})
values['restock']['previous_price'] = None
if self.history_n >= 2:
history = self.history
if history and len(history) >=2:
"""Unfortunately for now timestamp is stored as string key"""
sorted_keys = sorted(list(history), key=lambda x: int(x))
sorted_keys.reverse()
price_str = self.get_history_snapshot(timestamp=sorted_keys[-1])
if price_str:
values['restock']['previous_price'] = get_price_from_history_str(price_str)
return values return values
def extra_notification_token_placeholder_info(self): def extra_notification_token_placeholder_info(self):
values = super().extra_notification_token_placeholder_info() values = super().extra_notification_token_placeholder_info()
values.append(('restock.price', "Price detected")) values.append(('restock.price', "Price detected"))
values.append(('restock.in_stock', "In stock status"))
values.append(('restock.original_price', "Original price at first check")) values.append(('restock.original_price', "Original price at first check"))
values.append(('restock.previous_price', "Previous price in history"))
return values return values

View File

@@ -199,31 +199,8 @@ def handle_watch_update(socketio, **kwargs):
logger.error(f"Socket.IO error in handle_watch_update: {str(e)}") logger.error(f"Socket.IO error in handle_watch_update: {str(e)}")
def _suppress_werkzeug_ws_abrupt_disconnect_noise():
"""Patch BaseWSGIServer.log to suppress the AssertionError traceback that fires when
a browser closes a WebSocket connection mid-handshake (e.g. closing a tab).
The exception is caught inside run_wsgi and routed to self.server.log() — it never
propagates out, so wrapping run_wsgi doesn't help. Patching the log method is the
only reliable intercept point. The error is cosmetic: Socket.IO already handles the
disconnect correctly via its own disconnect handler and timeout logic."""
try:
from werkzeug.serving import BaseWSGIServer
_original_log = BaseWSGIServer.log
def _filtered_log(self, type, message, *args):
if type == 'error' and 'write() before start_response' in message:
return
_original_log(self, type, message, *args)
BaseWSGIServer.log = _filtered_log
except Exception:
pass
def init_socketio(app, datastore): def init_socketio(app, datastore):
"""Initialize SocketIO with the main Flask app""" """Initialize SocketIO with the main Flask app"""
_suppress_werkzeug_ws_abrupt_disconnect_noise()
import platform import platform
import sys import sys

View File

@@ -116,14 +116,6 @@ $(document).ready(function () {
$('#realtime-conn-error').show(); $('#realtime-conn-error').show();
}); });
// Tell the server we're leaving cleanly so it can release the connection
// immediately rather than waiting for a timeout.
// Note: this only fires for voluntary closes (tab/window close, navigation away).
// Hard kills, crashes and network drops will still timeout normally on the server.
window.addEventListener('beforeunload', function () {
socket.disconnect();
});
socket.on('queue_size', function (data) { socket.on('queue_size', function (data) {
console.log(`${data.event_timestamp} - Queue size update: ${data.q_length}`); console.log(`${data.event_timestamp} - Queue size update: ${data.q_length}`);
if(queueSizePagerInfoText) { if(queueSizePagerInfoText) {

View File

@@ -45,10 +45,6 @@
<script src="{{url_for('static_content', group='js', filename='socket.io.min.js')}}"></script> <script src="{{url_for('static_content', group='js', filename='socket.io.min.js')}}"></script>
<script src="{{url_for('static_content', group='js', filename='realtime.js')}}" defer></script> <script src="{{url_for('static_content', group='js', filename='realtime.js')}}" defer></script>
{% endif %} {% endif %}
{%- set _html_head_extras = get_html_head_extras() -%}
{%- if _html_head_extras %}
{{ _html_head_extras | safe }}
{%- endif %}
</head> </head>
<body class="{{extra_classes}}"> <body class="{{extra_classes}}">

View File

@@ -1,83 +0,0 @@
"""Test that plugins can inject HTML into base.html <head> via get_html_head_extras hookimpl."""
import pytest
from flask import url_for, Response
from changedetectionio.pluggy_interface import hookimpl, plugin_manager
_MY_JS = "console.log('my_module_content loaded');"
_MY_CSS = ".my-module-example { color: red; }"
class _HeadExtrasPlugin:
"""Test plugin that injects tags pointing at its own Flask routes."""
@hookimpl
def get_html_head_extras(self):
css_url = url_for('test_plugin_my_module_content_css')
js_url = url_for('test_plugin_my_module_content_js')
return (
f'<link rel="stylesheet" id="test-head-extra-css" href="{css_url}">\n'
f'<script id="test-head-extra-js" src="{js_url}" defer></script>'
)
@pytest.fixture(scope='module')
def plugin_routes(live_server):
"""Register plugin asset routes once per module (Flask routes can't be added twice)."""
app = live_server.app
@app.route('/test-plugin/my_module_content/css')
def test_plugin_my_module_content_css():
return Response(_MY_CSS, mimetype='text/css',
headers={'Cache-Control': 'max-age=3600'})
@app.route('/test-plugin/my_module_content/js')
def test_plugin_my_module_content_js():
return Response(_MY_JS, mimetype='application/javascript',
headers={'Cache-Control': 'max-age=3600'})
@pytest.fixture
def head_extras_plugin(plugin_routes):
"""Register the hookimpl for one test then unregister it — function-scoped for clean isolation."""
plugin = _HeadExtrasPlugin()
plugin_manager.register(plugin, name="test_head_extras")
yield plugin
plugin_manager.unregister(name="test_head_extras")
def test_plugin_html_injected_into_head(client, live_server, measure_memory_usage, datastore_path, head_extras_plugin):
"""get_html_head_extras output must appear inside <head> in the rendered page."""
res = client.get(url_for("watchlist.index"), follow_redirects=True)
assert res.status_code == 200
assert b'id="test-head-extra-css"' in res.data, "Plugin <link> tag missing from rendered page"
assert b'id="test-head-extra-js"' in res.data, "Plugin <script> tag missing from rendered page"
head_end = res.data.find(b'</head>')
assert head_end != -1
for marker in (b'id="test-head-extra-css"', b'id="test-head-extra-js"'):
pos = res.data.find(marker)
assert pos != -1 and pos < head_end, f"{marker} must appear before </head>"
def test_plugin_js_route_returns_correct_content(client, live_server, measure_memory_usage, datastore_path, plugin_routes):
"""The plugin-registered JS route must return JS with the right Content-Type."""
res = client.get(url_for('test_plugin_my_module_content_js'))
assert res.status_code == 200
assert 'javascript' in res.content_type
assert _MY_JS.encode() in res.data
def test_plugin_css_route_returns_correct_content(client, live_server, measure_memory_usage, datastore_path, plugin_routes):
"""The plugin-registered CSS route must return CSS with the right Content-Type."""
res = client.get(url_for('test_plugin_my_module_content_css'))
assert res.status_code == 200
assert 'css' in res.content_type
assert _MY_CSS.encode() in res.data
def test_no_extras_without_plugin(client, live_server, measure_memory_usage, datastore_path):
"""With no hookimpl registered the markers must not appear (isolation check)."""
res = client.get(url_for("watchlist.index"), follow_redirects=True)
assert b'id="test-head-extra-css"' not in res.data
assert b'id="test-head-extra-js"' not in res.data

View File

@@ -422,28 +422,3 @@ def test_plaintext_even_if_xml_content_and_can_apply_filters(client, live_server
assert b'&lt;foobar' not in res.data assert b'&lt;foobar' not in res.data
res = delete_all_watches(client) res = delete_all_watches(client)
def test_last_error_cleared_on_same_checksum(client, live_server, datastore_path):
"""last_error should be cleared even when content is unchanged (checksumFromPreviousCheckWasTheSame path)"""
set_original_response(datastore_path=datastore_path)
uuid = client.application.config.get('DATASTORE').add_watch(url=url_for('test_endpoint', _external=True))
# First check - establishes baseline checksum
client.get(url_for("ui.form_watch_checknow"), follow_redirects=True)
wait_for_all_checks(client)
# Inject a stale last_error directly (simulates a prior failed check)
datastore = client.application.config.get('DATASTORE')
datastore.update_watch(uuid=uuid, update_obj={'last_error': 'Some previous error'})
assert datastore.data['watching'][uuid].get('last_error') == 'Some previous error'
# Second check - same content, so checksumFromPreviousCheckWasTheSame will fire
client.get(url_for("ui.form_watch_checknow"), follow_redirects=True)
wait_for_all_checks(client)
# last_error must be cleared even though no change was detected
assert datastore.data['watching'][uuid].get('last_error') == False
delete_all_watches(client)

View File

@@ -350,7 +350,6 @@ def test_change_with_notification_values(client, live_server, measure_memory_usa
res = client.get(url_for("settings.settings_page")) res = client.get(url_for("settings.settings_page"))
assert b'{{restock.original_price}}' in res.data assert b'{{restock.original_price}}' in res.data
assert b'{{restock.previous_price}}' in res.data
assert b'Original price at first check' in res.data assert b'Original price at first check' in res.data
##################### #####################
@@ -359,7 +358,7 @@ def test_change_with_notification_values(client, live_server, measure_memory_usa
url_for("settings.settings_page"), url_for("settings.settings_page"),
data={"application-notification_urls": notification_url, data={"application-notification_urls": notification_url,
"application-notification_title": "title new price {{restock.price}}", "application-notification_title": "title new price {{restock.price}}",
"application-notification_body": "new price {{restock.price}} previous price {{restock.previous_price}} instock {{restock.in_stock}}", "application-notification_body": "new price {{restock.price}}",
"application-notification_format": default_notification_format, "application-notification_format": default_notification_format,
"requests-time_between_check-minutes": 180, "requests-time_between_check-minutes": 180,
'application-fetch_backend': "html_requests"}, 'application-fetch_backend': "html_requests"},
@@ -373,6 +372,8 @@ def test_change_with_notification_values(client, live_server, measure_memory_usa
assert b"Settings updated." in res.data assert b"Settings updated." in res.data
set_original_response(props_markup=instock_props[0], price='960.45', datastore_path=datastore_path)
# A change in price, should trigger a change by default # A change in price, should trigger a change by default
set_original_response(props_markup=instock_props[0], price='1950.45', datastore_path=datastore_path) set_original_response(props_markup=instock_props[0], price='1950.45', datastore_path=datastore_path)
client.get(url_for("ui.form_watch_checknow")) client.get(url_for("ui.form_watch_checknow"))
@@ -383,7 +384,6 @@ def test_change_with_notification_values(client, live_server, measure_memory_usa
notification = f.read() notification = f.read()
assert "new price 1950.45" in notification assert "new price 1950.45" in notification
assert "title new price 1950.45" in notification assert "title new price 1950.45" in notification
assert "previous price 960.45" in notification
## Now test the "SEND TEST NOTIFICATION" is working ## Now test the "SEND TEST NOTIFICATION" is working
os.unlink(os.path.join(datastore_path, "notification.txt")) os.unlink(os.path.join(datastore_path, "notification.txt"))

View File

@@ -610,11 +610,6 @@ def test_xpath_blocked_functions_unit():
"unparsed-text-available('file:///etc/passwd')", "unparsed-text-available('file:///etc/passwd')",
"doc('file:///etc/passwd')", "doc('file:///etc/passwd')",
"doc-available('file:///etc/passwd')", "doc-available('file:///etc/passwd')",
"json-doc('file:///datastore/changedetection.json')",
"collection('file:///datastore/')",
"uri-collection('file:///datastore/')",
"transform(map{})",
"load-xquery-module('foo')",
"environment-variable('PATH')", "environment-variable('PATH')",
"available-environment-variables()", "available-environment-variables()",
] ]

View File

@@ -369,7 +369,7 @@ msgstr "Protokol ladění oznámení"
#: changedetectionio/blueprint/settings/templates/settings.html changedetectionio/blueprint/tags/templates/edit-tag.html #: changedetectionio/blueprint/settings/templates/settings.html changedetectionio/blueprint/tags/templates/edit-tag.html
#: changedetectionio/blueprint/ui/templates/edit.html #: changedetectionio/blueprint/ui/templates/edit.html
msgid "General" msgid "General"
msgstr "Obecné" msgstr "Generál"
#: changedetectionio/blueprint/settings/templates/settings.html #: changedetectionio/blueprint/settings/templates/settings.html
msgid "Fetching" msgid "Fetching"
@@ -393,7 +393,7 @@ msgstr "RSS"
#: changedetectionio/blueprint/settings/templates/settings.html #: changedetectionio/blueprint/settings/templates/settings.html
msgid "Backups" msgid "Backups"
msgstr "Zálohy" msgstr "Backups"
#: changedetectionio/blueprint/settings/templates/settings.html #: changedetectionio/blueprint/settings/templates/settings.html
msgid "Time & Date" msgid "Time & Date"
@@ -409,7 +409,7 @@ msgstr "Info"
#: changedetectionio/blueprint/settings/templates/settings.html #: changedetectionio/blueprint/settings/templates/settings.html
msgid "Default recheck time for all watches, current system minimum is" msgid "Default recheck time for all watches, current system minimum is"
msgstr "Výchozí čas opětovné kontroly pro všechna sledování, aktuální systémové minimum je" msgstr "Výchozí čas opětovné kontroly pro všechny monitory, aktuální systémové minimum je"
#: changedetectionio/blueprint/settings/templates/settings.html #: changedetectionio/blueprint/settings/templates/settings.html
msgid "more info" msgid "more info"
@@ -445,7 +445,9 @@ msgstr ""
#: changedetectionio/blueprint/settings/templates/settings.html #: changedetectionio/blueprint/settings/templates/settings.html
msgid "Allow access to the watch change history page when password is enabled (Good for sharing the diff page)" msgid "Allow access to the watch change history page when password is enabled (Good for sharing the diff page)"
msgstr "Povolit přístup na stránku historie změn monitoru, když je povoleno heslo (Vhodné pro sdílení stránky rozdílů)" msgstr ""
"Povolit přístup na stránku historie změn monitoru, když je povoleno heslo (Vhodné pro sdílení stránky rozdílů)Povolit"
" anonymní přístup na stránku historie sledování, když je povoleno heslo"
#: changedetectionio/blueprint/settings/templates/settings.html #: changedetectionio/blueprint/settings/templates/settings.html
msgid "When a request returns no content, or the HTML does not contain any text, is this considered a change?" msgid "When a request returns no content, or the HTML does not contain any text, is this considered a change?"
@@ -453,7 +455,7 @@ msgstr ""
#: changedetectionio/blueprint/settings/templates/settings.html #: changedetectionio/blueprint/settings/templates/settings.html
msgid "Choose a default proxy for all watches" msgid "Choose a default proxy for all watches"
msgstr "Vyberte výchozí proxy pro všechna sledování" msgstr "Vyberte výchozí proxy pro všechny monitory"
#: changedetectionio/blueprint/settings/templates/settings.html #: changedetectionio/blueprint/settings/templates/settings.html
msgid "Base URL used for the" msgid "Base URL used for the"
@@ -477,7 +479,7 @@ msgstr ""
#: changedetectionio/blueprint/settings/templates/settings.html changedetectionio/blueprint/ui/templates/edit.html #: changedetectionio/blueprint/settings/templates/settings.html changedetectionio/blueprint/ui/templates/edit.html
msgid "Use the" msgid "Use the"
msgstr "Použít" msgstr "Použijte"
#: changedetectionio/blueprint/settings/templates/settings.html changedetectionio/blueprint/ui/templates/edit.html #: changedetectionio/blueprint/settings/templates/settings.html changedetectionio/blueprint/ui/templates/edit.html
msgid "Basic" msgid "Basic"
@@ -503,7 +505,7 @@ msgstr ""
#: changedetectionio/blueprint/settings/templates/settings.html changedetectionio/blueprint/ui/templates/edit.html #: changedetectionio/blueprint/settings/templates/settings.html changedetectionio/blueprint/ui/templates/edit.html
msgid "This will wait" msgid "This will wait"
msgstr "Toto počká" msgstr "Tohle počká"
#: changedetectionio/blueprint/settings/templates/settings.html changedetectionio/blueprint/ui/templates/edit.html #: changedetectionio/blueprint/settings/templates/settings.html changedetectionio/blueprint/ui/templates/edit.html
msgid "seconds before extracting the text." msgid "seconds before extracting the text."
@@ -863,7 +865,7 @@ msgstr "povoleny adresy URL pro upozornění v celém systému"
#: changedetectionio/blueprint/tags/templates/edit-tag.html changedetectionio/blueprint/ui/templates/edit.html #: changedetectionio/blueprint/tags/templates/edit-tag.html changedetectionio/blueprint/ui/templates/edit.html
msgid "this form will override notification settings for this watch only" msgid "this form will override notification settings for this watch only"
msgstr "tento formulář přepíše nastavení oznámení pouze pro tato sledování" msgstr "tento formulář přepíše nastavení oznámení pouze pro tyto monitory"
#: changedetectionio/blueprint/tags/templates/edit-tag.html changedetectionio/blueprint/ui/templates/edit.html #: changedetectionio/blueprint/tags/templates/edit-tag.html changedetectionio/blueprint/ui/templates/edit.html
msgid "an empty Notification URL list here will still send notifications." msgid "an empty Notification URL list here will still send notifications."
@@ -880,7 +882,7 @@ msgstr "Přidejte novou značku organizace"
#: changedetectionio/blueprint/tags/templates/groups-overview.html #: changedetectionio/blueprint/tags/templates/groups-overview.html
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html #: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Watch group / tag" msgid "Watch group / tag"
msgstr "Sledovat skupinu / Značka" msgstr "Skupina / Značka"
#: changedetectionio/blueprint/tags/templates/groups-overview.html #: changedetectionio/blueprint/tags/templates/groups-overview.html
msgid "Groups allows you to manage filters and notifications for multiple watches under a single organisational tag." msgid "Groups allows you to manage filters and notifications for multiple watches under a single organisational tag."
@@ -888,15 +890,15 @@ msgstr ""
#: changedetectionio/blueprint/tags/templates/groups-overview.html #: changedetectionio/blueprint/tags/templates/groups-overview.html
msgid "# Watches" msgid "# Watches"
msgstr "# Sledování" msgstr "# monitorů"
#: changedetectionio/blueprint/tags/templates/groups-overview.html #: changedetectionio/blueprint/tags/templates/groups-overview.html
msgid "Tag / Label name" msgid "Tag / Label name"
msgstr "Tag / Název štítku" msgstr "Název štítku / štítku"
#: changedetectionio/blueprint/tags/templates/groups-overview.html #: changedetectionio/blueprint/tags/templates/groups-overview.html
msgid "No website organisational tags/groups configured" msgid "No website organisational tags/groups configured"
msgstr "Žádné skupiny/značky zatím nebyly nastaveny" msgstr "Žádné skupiny/značky"
#: changedetectionio/blueprint/tags/templates/groups-overview.html #: changedetectionio/blueprint/tags/templates/groups-overview.html
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html #: changedetectionio/blueprint/watchlist/templates/watch-overview.html
@@ -906,7 +908,7 @@ msgstr "Upravit"
#: changedetectionio/blueprint/tags/templates/groups-overview.html #: changedetectionio/blueprint/tags/templates/groups-overview.html
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html #: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Recheck" msgid "Recheck"
msgstr "Znovu zkontrolovat" msgstr "Znovu zkontrolujte"
#: changedetectionio/blueprint/tags/templates/groups-overview.html #: changedetectionio/blueprint/tags/templates/groups-overview.html
msgid "Delete Group?" msgid "Delete Group?"
@@ -920,7 +922,7 @@ msgstr ""
#: changedetectionio/blueprint/tags/templates/groups-overview.html changedetectionio/blueprint/ui/templates/edit.html #: changedetectionio/blueprint/tags/templates/groups-overview.html changedetectionio/blueprint/ui/templates/edit.html
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html #: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Delete" msgid "Delete"
msgstr "Smazat" msgstr "Vymazat"
#: changedetectionio/blueprint/tags/templates/groups-overview.html #: changedetectionio/blueprint/tags/templates/groups-overview.html
msgid "Deletes and removes tag" msgid "Deletes and removes tag"
@@ -943,36 +945,36 @@ msgstr "Odpojit"
#: changedetectionio/blueprint/tags/templates/groups-overview.html #: changedetectionio/blueprint/tags/templates/groups-overview.html
msgid "Keep the tag but unlink any watches" msgid "Keep the tag but unlink any watches"
msgstr "Ponechte štítek, ale odpojte všechna sledování" msgstr "Ponechte štítek, ale odpojte všechny monitory"
#: changedetectionio/blueprint/tags/templates/groups-overview.html changedetectionio/blueprint/ui/templates/edit.html #: changedetectionio/blueprint/tags/templates/groups-overview.html changedetectionio/blueprint/ui/templates/edit.html
msgid "RSS Feed for this watch" msgid "RSS Feed for this watch"
msgstr "RSS kanál pro toto sledování" msgstr "RSS kanál pro tyto monitory"
#: changedetectionio/blueprint/ui/__init__.py #: changedetectionio/blueprint/ui/__init__.py
#, python-brace-format #, python-brace-format
msgid "{} watches deleted" msgid "{} watches deleted"
msgstr "{} sledování smazáno" msgstr ""
#: changedetectionio/blueprint/ui/__init__.py #: changedetectionio/blueprint/ui/__init__.py
#, python-brace-format #, python-brace-format
msgid "{} watches paused" msgid "{} watches paused"
msgstr "{} sledování pozastaveno" msgstr "{} monitorů pozastaveno"
#: changedetectionio/blueprint/ui/__init__.py #: changedetectionio/blueprint/ui/__init__.py
#, python-brace-format #, python-brace-format
msgid "{} watches unpaused" msgid "{} watches unpaused"
msgstr "{} sledování opět spuštěno" msgstr ""
#: changedetectionio/blueprint/ui/__init__.py #: changedetectionio/blueprint/ui/__init__.py
#, python-brace-format #, python-brace-format
msgid "{} watches updated" msgid "{} watches updated"
msgstr "{} sledování aktualizováno" msgstr ""
#: changedetectionio/blueprint/ui/__init__.py #: changedetectionio/blueprint/ui/__init__.py
#, python-brace-format #, python-brace-format
msgid "{} watches muted" msgid "{} watches muted"
msgstr "{} sledování ztlumeno" msgstr "{} monitorů ztlumeno"
#: changedetectionio/blueprint/ui/__init__.py #: changedetectionio/blueprint/ui/__init__.py
#, python-brace-format #, python-brace-format
@@ -1011,7 +1013,7 @@ msgstr "Sledujte tuto adresu URL!"
#: changedetectionio/blueprint/ui/__init__.py #: changedetectionio/blueprint/ui/__init__.py
#, python-brace-format #, python-brace-format
msgid "Cleared snapshot history for watch {}" msgid "Cleared snapshot history for watch {}"
msgstr "Historie snímků vymazána pro sledování {}" msgstr "Historie snímků vymazána pro monitor {}"
#: changedetectionio/blueprint/ui/__init__.py #: changedetectionio/blueprint/ui/__init__.py
msgid "History clearing started in background" msgid "History clearing started in background"
@@ -1028,7 +1030,7 @@ msgstr ""
#: changedetectionio/blueprint/ui/__init__.py #: changedetectionio/blueprint/ui/__init__.py
msgid "Deleted." msgid "Deleted."
msgstr "Smazáno" msgstr "Vymazat"
#: changedetectionio/blueprint/ui/__init__.py #: changedetectionio/blueprint/ui/__init__.py
msgid "Cloned, you are editing the new watch." msgid "Cloned, you are editing the new watch."
@@ -1045,7 +1047,7 @@ msgstr ""
#: changedetectionio/blueprint/ui/__init__.py #: changedetectionio/blueprint/ui/__init__.py
#, python-brace-format #, python-brace-format
msgid "Queued {} watches for rechecking ({} already queued or running)." msgid "Queued {} watches for rechecking ({} already queued or running)."
msgstr "Do fronty přidáno {} sledování k opětovné kontrole ({} již ve frontě nebo běží)." msgstr "Do fronty přidáno {} monitorů k opětovné kontrole ({} již ve frontě nebo běží)."
#: changedetectionio/blueprint/ui/__init__.py #: changedetectionio/blueprint/ui/__init__.py
#, python-brace-format #, python-brace-format
@@ -1054,7 +1056,7 @@ msgstr "Do fronty přidáno {} sledování k opětovné kontrole."
#: changedetectionio/blueprint/ui/__init__.py #: changedetectionio/blueprint/ui/__init__.py
msgid "Queueing watches for rechecking in background..." msgid "Queueing watches for rechecking in background..."
msgstr "Přidává se sledování do fronty pro opětovnou kontrolu na pozadí..." msgstr "Přidávání monitorů do fronty pro opětovnou kontrolu na pozadí..."
#: changedetectionio/blueprint/ui/__init__.py #: changedetectionio/blueprint/ui/__init__.py
#, python-brace-format #, python-brace-format
@@ -1103,7 +1105,7 @@ msgstr ""
#: changedetectionio/blueprint/ui/edit.py #: changedetectionio/blueprint/ui/edit.py
msgid "Updated watch." msgid "Updated watch."
msgstr "Sledování aktualizováno." msgstr "Smazat monitory?"
#: changedetectionio/blueprint/ui/preview.py #: changedetectionio/blueprint/ui/preview.py
msgid "Preview unavailable - No fetch/check completed or triggers not reached" msgid "Preview unavailable - No fetch/check completed or triggers not reached"
@@ -1119,7 +1121,7 @@ msgstr "Možná budete chtít použít"
#: changedetectionio/blueprint/ui/templates/clear_all_history.html #: changedetectionio/blueprint/ui/templates/clear_all_history.html
msgid "BACKUP" msgid "BACKUP"
msgstr "ZÁLOHA" msgstr "BACKUP"
#: changedetectionio/blueprint/ui/templates/clear_all_history.html #: changedetectionio/blueprint/ui/templates/clear_all_history.html
msgid "link first." msgid "link first."
@@ -1159,11 +1161,11 @@ msgstr "Sdílet jako obrázek"
#: changedetectionio/blueprint/ui/templates/diff-offscreen-options.html #: changedetectionio/blueprint/ui/templates/diff-offscreen-options.html
msgid "Ignore any lines matching" msgid "Ignore any lines matching"
msgstr "Ignorovat všechny odpovídající řádky" msgstr "Ignorujte všechny odpovídající řádky"
#: changedetectionio/blueprint/ui/templates/diff-offscreen-options.html #: changedetectionio/blueprint/ui/templates/diff-offscreen-options.html
msgid "Ignore any lines matching excluding digits" msgid "Ignore any lines matching excluding digits"
msgstr "Ignorovat všechny odpovídající řádky kromě číslic" msgstr "Ignorujte všechny odpovídající řádky kromě číslic"
#: changedetectionio/blueprint/ui/templates/diff.html #: changedetectionio/blueprint/ui/templates/diff.html
msgid "From" msgid "From"
@@ -1183,7 +1185,7 @@ msgstr "Řádky"
#: changedetectionio/blueprint/ui/templates/diff.html #: changedetectionio/blueprint/ui/templates/diff.html
msgid "Ignore Whitespace" msgid "Ignore Whitespace"
msgstr "Ignorovat mezery" msgstr "Ignorujte mezery"
#: changedetectionio/blueprint/ui/templates/diff.html #: changedetectionio/blueprint/ui/templates/diff.html
msgid "Same/non-changed" msgid "Same/non-changed"
@@ -1207,7 +1209,7 @@ msgstr "Klávesnice:"
#: changedetectionio/blueprint/ui/templates/diff.html changedetectionio/blueprint/ui/templates/preview.html #: changedetectionio/blueprint/ui/templates/diff.html changedetectionio/blueprint/ui/templates/preview.html
msgid "Previous" msgid "Previous"
msgstr "Předchozí" msgstr "Náhled"
#: changedetectionio/blueprint/ui/templates/diff.html changedetectionio/blueprint/ui/templates/preview.html #: changedetectionio/blueprint/ui/templates/diff.html changedetectionio/blueprint/ui/templates/preview.html
msgid "Next" msgid "Next"
@@ -1239,7 +1241,7 @@ msgstr "Aktuální snímek obrazovky"
#: changedetectionio/blueprint/ui/templates/diff.html #: changedetectionio/blueprint/ui/templates/diff.html
msgid "Extract Data" msgid "Extract Data"
msgstr "Extrahovat data" msgstr "Extrahujte data"
#: changedetectionio/blueprint/ui/templates/diff.html #: changedetectionio/blueprint/ui/templates/diff.html
msgid "seconds ago." msgid "seconds ago."
@@ -1267,7 +1269,7 @@ msgstr "NASTAVENÍ"
#: changedetectionio/blueprint/ui/templates/diff.html #: changedetectionio/blueprint/ui/templates/diff.html
msgid "Goto single snapshot" msgid "Goto single snapshot"
msgstr "Přejít na samotný snímek" msgstr "Přejít na jeden snímek"
#: changedetectionio/blueprint/ui/templates/diff.html #: changedetectionio/blueprint/ui/templates/diff.html
msgid "Highlight text to share or add to ignore lists." msgid "Highlight text to share or add to ignore lists."
@@ -1357,15 +1359,15 @@ msgstr ""
#: changedetectionio/blueprint/ui/templates/edit.html #: changedetectionio/blueprint/ui/templates/edit.html
msgid "Check/Scan all" msgid "Check/Scan all"
msgstr "Vše znovu zkontrolovat" msgstr "Znovu zkontrolujte vše"
#: changedetectionio/blueprint/ui/templates/edit.html #: changedetectionio/blueprint/ui/templates/edit.html
msgid "Choose a proxy for this watch" msgid "Choose a proxy for this watch"
msgstr "Vybrat proxy pro toto sledování" msgstr "RSS kanál pro tyto monitory"
#: changedetectionio/blueprint/ui/templates/edit.html #: changedetectionio/blueprint/ui/templates/edit.html
msgid "Using the current global default settings" msgid "Using the current global default settings"
msgstr "Aktuálně je použito globální výchozí nastavení" msgstr "Použití aktuálního globálního výchozího nastavení"
#: changedetectionio/blueprint/ui/templates/edit.html #: changedetectionio/blueprint/ui/templates/edit.html
msgid "Show advanced options" msgid "Show advanced options"
@@ -1389,7 +1391,7 @@ msgstr "Proměnné jsou podporovány v hodnotách hlavičky požadavku"
#: changedetectionio/blueprint/ui/templates/edit.html #: changedetectionio/blueprint/ui/templates/edit.html
msgid "Alert! Extra headers file found and will be added to this watch!" msgid "Alert! Extra headers file found and will be added to this watch!"
msgstr "Upozornění! Byl nalezen další soubor záhlaví a bude přidán do těchto sledování!" msgstr "Upozornění! Byl nalezen další soubor záhlaví a bude přidán do těchto monitorů!"
#: changedetectionio/blueprint/ui/templates/edit.html #: changedetectionio/blueprint/ui/templates/edit.html
msgid "Headers can be also read from a file in your data-directory" msgid "Headers can be also read from a file in your data-directory"
@@ -1425,7 +1427,7 @@ msgstr ""
#: changedetectionio/blueprint/ui/templates/edit.html #: changedetectionio/blueprint/ui/templates/edit.html
msgid "Visual Selector data is not ready, watch needs to be checked atleast once." msgid "Visual Selector data is not ready, watch needs to be checked atleast once."
msgstr "Data Visual Selector nejsou připravena, sledování je třeba alespoň jednou zkontrolovat." msgstr "Data Visual Selector nejsou připravena, monitory je třeba alespoň jednou zkontrolovat."
#: changedetectionio/blueprint/ui/templates/edit.html #: changedetectionio/blueprint/ui/templates/edit.html
msgid "" msgid ""
@@ -1631,11 +1633,11 @@ msgstr ""
#: changedetectionio/blueprint/ui/templates/edit.html #: changedetectionio/blueprint/ui/templates/edit.html
msgid "Delete Watch?" msgid "Delete Watch?"
msgstr "Smazat sledování?" msgstr "Smazat monitory?"
#: changedetectionio/blueprint/ui/templates/edit.html #: changedetectionio/blueprint/ui/templates/edit.html
msgid "Are you sure you want to delete the watch for:" msgid "Are you sure you want to delete the watch for:"
msgstr "Opravdu chcete smazat sledování pro:" msgstr "Opravdu chcete smazat monitory pro:"
#: changedetectionio/blueprint/ui/templates/edit.html #: changedetectionio/blueprint/ui/templates/edit.html
msgid "This action cannot be undone." msgid "This action cannot be undone."
@@ -1659,15 +1661,15 @@ msgstr "Vymazat historii"
#: changedetectionio/blueprint/ui/templates/edit.html #: changedetectionio/blueprint/ui/templates/edit.html
msgid "Clone & Edit" msgid "Clone & Edit"
msgstr "Duplikovat a upravit" msgstr "Klonovat a upravovat"
#: changedetectionio/blueprint/ui/templates/preview.html #: changedetectionio/blueprint/ui/templates/preview.html
msgid "Select timestamp" msgid "Select timestamp"
msgstr "Vybrat časové razítko" msgstr "Vyberte časové razítko"
#: changedetectionio/blueprint/ui/templates/preview.html #: changedetectionio/blueprint/ui/templates/preview.html
msgid "Go" msgid "Go"
msgstr "Přejít" msgstr "Jít"
#: changedetectionio/blueprint/ui/templates/preview.html #: changedetectionio/blueprint/ui/templates/preview.html
msgid "Current erroring screenshot from most recent request" msgid "Current erroring screenshot from most recent request"
@@ -1713,7 +1715,7 @@ msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html #: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Add a new web page change detection watch" msgid "Add a new web page change detection watch"
msgstr "Přidejte nové sledování zjišťování změn webové stránky" msgstr "Přidejte nové monitory zjišťování změn webové stránky"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html #: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Watch this URL!" msgid "Watch this URL!"
@@ -1721,7 +1723,7 @@ msgstr "Monitorovat tuto URL!"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html #: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Edit first then Watch" msgid "Edit first then Watch"
msgstr "Nejdříve upravit, poté sledovat" msgstr "Upravit a monitorovat"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html #: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Pause" msgid "Pause"
@@ -1745,7 +1747,7 @@ msgstr "Štítek"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html #: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Mark viewed" msgid "Mark viewed"
msgstr "Označit jako shlédnuté" msgstr "Mark zobrazil"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html #: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Use default notification" msgid "Use default notification"
@@ -1773,7 +1775,7 @@ msgstr "Vymazat/resetovat historii"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html #: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Delete Watches?" msgid "Delete Watches?"
msgstr "Smazat sledování?" msgstr "Smazat monitory?"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html #: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "<p>Are you sure you want to delete the selected watches?</strong></p><p>This action cannot be undone.</p>" msgid "<p>Are you sure you want to delete the selected watches?</strong></p><p>This action cannot be undone.</p>"
@@ -1821,7 +1823,7 @@ msgstr "importovat seznam"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html #: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Detecting restock and price" msgid "Detecting restock and price"
msgstr "Kontrola zásob a ceny" msgstr "Detekce zásob a ceny"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html #: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "In stock" msgid "In stock"
@@ -1874,7 +1876,7 @@ msgstr "Nepřečtený"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html #: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Recheck all" msgid "Recheck all"
msgstr "Znovu zkontrolovat vše" msgstr "Znovu zkontrolujte vše"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html #: changedetectionio/blueprint/watchlist/templates/watch-overview.html
#, python-format #, python-format
@@ -2024,7 +2026,7 @@ msgstr "neděle"
#: changedetectionio/forms.py #: changedetectionio/forms.py
msgid "Weeks" msgid "Weeks"
msgstr "Týdny" msgstr "týdny"
#: changedetectionio/forms.py #: changedetectionio/forms.py
msgid "Should contain zero or more seconds" msgid "Should contain zero or more seconds"
@@ -2044,7 +2046,7 @@ msgstr "Minuty"
#: changedetectionio/forms.py #: changedetectionio/forms.py
msgid "Seconds" msgid "Seconds"
msgstr "Sekundy" msgstr "sekundy"
#: changedetectionio/forms.py #: changedetectionio/forms.py
msgid "Notification Body and Title is required when a Notification URL is used" msgid "Notification Body and Title is required when a Notification URL is used"
@@ -2149,7 +2151,7 @@ msgstr "Nahrajte soubor .xlsx"
#: changedetectionio/forms.py #: changedetectionio/forms.py
msgid "Must be .xlsx file!" msgid "Must be .xlsx file!"
msgstr "Musí být soubor .xlsx!" msgstr "Musí to být soubor .xlsx!"
#: changedetectionio/forms.py #: changedetectionio/forms.py
msgid "File mapping" msgid "File mapping"
@@ -2173,7 +2175,7 @@ msgstr "Interval mezi kontrolami"
#: changedetectionio/forms.py #: changedetectionio/forms.py
msgid "Use global settings for time between check and scheduler." msgid "Use global settings for time between check and scheduler."
msgstr "Použít globální nastavení pro čas mezi kontrolou a plánovačem." msgstr "Použijte globální nastavení pro čas mezi kontrolou a plánovačem."
#: changedetectionio/forms.py #: changedetectionio/forms.py
msgid "CSS/JSONPath/JQ/XPath Filters" msgid "CSS/JSONPath/JQ/XPath Filters"
@@ -2282,7 +2284,7 @@ msgstr "Připojte snímek obrazovky k oznámení (pokud je to možné)"
#: changedetectionio/forms.py #: changedetectionio/forms.py
msgid "Match" msgid "Match"
msgstr "Shoda" msgstr "# monitory"
#: changedetectionio/forms.py #: changedetectionio/forms.py
msgid "Match all of the following" msgid "Match all of the following"
@@ -2353,11 +2355,11 @@ msgstr "Výchozí proxy"
#: changedetectionio/forms.py #: changedetectionio/forms.py
msgid "Random jitter seconds ± check" msgid "Random jitter seconds ± check"
msgstr "Náhodný rozptyl kontrol ± sekund" msgstr "Náhodné jitter sekundy ± kontrola"
#: changedetectionio/forms.py #: changedetectionio/forms.py
msgid "Number of fetch workers" msgid "Number of fetch workers"
msgstr "Počet procesů kontrol" msgstr "Počet pracovníků aportů"
#: changedetectionio/forms.py #: changedetectionio/forms.py
msgid "Should be between 1 and 50" msgid "Should be between 1 and 50"
@@ -2365,15 +2367,15 @@ msgstr "Mělo by být mezi 1 a 50"
#: changedetectionio/forms.py #: changedetectionio/forms.py
msgid "Requests timeout in seconds" msgid "Requests timeout in seconds"
msgstr "Časový limit vypršení kontrol v sekundách" msgstr "Požaduje časový limit v sekundách"
#: changedetectionio/forms.py #: changedetectionio/forms.py
msgid "Should be between 1 and 999" msgid "Should be between 1 and 999"
msgstr "Nastavit mezi 1 a 999" msgstr "Mělo by být mezi 1 a 999"
#: changedetectionio/forms.py #: changedetectionio/forms.py
msgid "Default User-Agent overrides" msgid "Default User-Agent overrides"
msgstr "Změna výchozího nastavení hodnoty User-Agent" msgstr "Výchozí přepisy User-Agent"
#: changedetectionio/forms.py #: changedetectionio/forms.py
msgid "Both a name, and a Proxy URL is required." msgid "Both a name, and a Proxy URL is required."
@@ -2385,11 +2387,11 @@ msgstr "Otevřete stránku „Historie“ na nové kartě"
#: changedetectionio/forms.py #: changedetectionio/forms.py
msgid "Realtime UI Updates Enabled" msgid "Realtime UI Updates Enabled"
msgstr "Aktualizace UI v reálném čase" msgstr "Aktualizace v reálném čase offline"
#: changedetectionio/forms.py #: changedetectionio/forms.py
msgid "Favicons Enabled" msgid "Favicons Enabled"
msgstr "Povolit favikony" msgstr "zvážit povolení"
#: changedetectionio/forms.py #: changedetectionio/forms.py
msgid "Use page <title> in watch overview list" msgid "Use page <title> in watch overview list"
@@ -2425,7 +2427,7 @@ msgstr "Heslo"
#: changedetectionio/forms.py #: changedetectionio/forms.py
msgid "Pager size" msgid "Pager size"
msgstr "Počet položek na stránku" msgstr "Velikost pageru"
#: changedetectionio/forms.py #: changedetectionio/forms.py
msgid "Should be atleast zero (disabled)" msgid "Should be atleast zero (disabled)"
@@ -2457,7 +2459,7 @@ msgstr "Povolit anonymní přístup na stránku historie sledování, když je p
#: changedetectionio/forms.py #: changedetectionio/forms.py
msgid "Hide muted watches from RSS feed" msgid "Hide muted watches from RSS feed"
msgstr "Skrýt ztlumená sledování pro RSS zdroje" msgstr "Skrýt ztlumené monitory ze zdroje RSS"
#: changedetectionio/forms.py #: changedetectionio/forms.py
msgid "Enable RSS reader mode " msgid "Enable RSS reader mode "

File diff suppressed because it is too large Load Diff

View File

@@ -284,9 +284,6 @@ async def async_update_worker(worker_id, q, notification_q, app, datastore, exec
logger.debug(f'[{uuid}] - checksumFromPreviousCheckWasTheSame - Checksum from previous check was the same, nothing todo here.') logger.debug(f'[{uuid}] - checksumFromPreviousCheckWasTheSame - Checksum from previous check was the same, nothing todo here.')
# Reset the edited flag since we successfully completed the check # Reset the edited flag since we successfully completed the check
watch.reset_watch_edited_flag() watch.reset_watch_edited_flag()
# Page was fetched successfully - clear any previous error state
datastore.update_watch(uuid=uuid, update_obj={'last_error': False})
cleanup_error_artifacts(uuid, datastore)
except content_fetchers_exceptions.BrowserConnectError as e: except content_fetchers_exceptions.BrowserConnectError as e:
datastore.update_watch(uuid=uuid, datastore.update_watch(uuid=uuid,

View File

@@ -28,7 +28,7 @@ services:
# - PLAYWRIGHT_DRIVER_URL=ws://browser-sockpuppet-chrome:3000 # - PLAYWRIGHT_DRIVER_URL=ws://browser-sockpuppet-chrome:3000
# #
# #
# Alternative WebDriver/selenium URL, do not use "'s or 's! (old, deprecated, does not support screenshots very well, Can't handle custom headers etc) # Alternative WebDriver/selenium URL, do not use "'s or 's! (old, deprecated, does not support screenshots very well)
# - WEBDRIVER_URL=http://browser-selenium-chrome:4444/wd/hub # - WEBDRIVER_URL=http://browser-selenium-chrome:4444/wd/hub
# #
# WebDriver proxy settings webdriver_proxyType, webdriver_ftpProxy, webdriver_noProxy, # WebDriver proxy settings webdriver_proxyType, webdriver_ftpProxy, webdriver_noProxy,