Compare commits

..

13 Commits

Author SHA1 Message Date
dgtlmoon
f9048af6e8 0.54.1
Some checks are pending
Build and push containers / metadata (push) Waiting to run
Build and push containers / build-push-containers (push) Waiting to run
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Build distribution 📦 (push) Waiting to run
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Test the built package works basically. (push) Blocked by required conditions
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Publish Python 🐍 distribution 📦 to PyPI (push) Blocked by required conditions
ChangeDetection.io Container Build Test / Build linux/amd64 (alpine) (push) Waiting to run
ChangeDetection.io Container Build Test / Build linux/arm64 (alpine) (push) Waiting to run
ChangeDetection.io Container Build Test / Build linux/amd64 (main) (push) Waiting to run
ChangeDetection.io Container Build Test / Build linux/arm/v7 (main) (push) Waiting to run
ChangeDetection.io Container Build Test / Build linux/arm/v8 (main) (push) Waiting to run
ChangeDetection.io Container Build Test / Build linux/arm64 (main) (push) Waiting to run
ChangeDetection.io App Test / lint-code (push) Waiting to run
ChangeDetection.io App Test / test-application-3-10 (push) Blocked by required conditions
ChangeDetection.io App Test / test-application-3-11 (push) Blocked by required conditions
ChangeDetection.io App Test / test-application-3-12 (push) Blocked by required conditions
ChangeDetection.io App Test / test-application-3-13 (push) Blocked by required conditions
2026-02-23 23:01:31 +01:00
dgtlmoon
2f7315e29c Tests - Tweaks to upgrade path tests 2026-02-23 22:20:13 +01:00
dgtlmoon
bf3f8eae45 Tests - Run upgrade path test with ALLOW_IANA_RESTRICTED_ADDRESSES=true 2026-02-23 21:59:00 +01:00
dgtlmoon
fe7aa38c65 CVE-2026-27696 - Server-Side Request Forgery (SSRF) via Watch URLs, set env var ALLOW_IANA_RESTRICTED_ADDRESSES to true to access IANA reserved URLs such as http://169.254.169.254, http://10.0.0.1/, http://127.0.0.1/, etc. 2026-02-23 21:56:43 +01:00
dgtlmoon
a385c89abf CVE-2026-27645 - Reflected XSS in RSS Single Watch request 2026-02-23 21:55:59 +01:00
dgtlmoon
98f884bbff 0.53.7
Some checks failed
Build and push containers / metadata (push) Has been cancelled
Build and push containers / build-push-containers (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Build distribution 📦 (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Test the built package works basically. (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Publish Python 🐍 distribution 📦 to PyPI (push) Has been cancelled
ChangeDetection.io App Test / lint-code (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-10 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-11 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-12 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-13 (push) Has been cancelled
2026-02-23 08:11:55 +01:00
dgtlmoon
35499d1171 Libraries/Build - unpin referencing library (#3919)
Some checks failed
Build and push containers / metadata (push) Has been cancelled
Build and push containers / build-push-containers (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Build distribution 📦 (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Test the built package works basically. (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Publish Python 🐍 distribution 📦 to PyPI (push) Has been cancelled
ChangeDetection.io App Test / lint-code (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-10 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-11 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-12 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-13 (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/amd64 (alpine) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/arm64 (alpine) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/amd64 (main) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/arm/v7 (main) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/arm/v8 (main) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/arm64 (main) (push) Has been cancelled
2026-02-23 07:27:00 +01:00
dependabot[bot]
599aed75d1 Bump referencing from 0.35.1 to 0.37.0 (#3677) 2026-02-23 05:32:41 +01:00
dgtlmoon
6df75a5af9 Upgrading flask-socketio and related packages with security updates ( #3910 ) (#3918) 2026-02-23 05:30:24 +01:00
dgtlmoon
f71c4b9865 0.53.6
Some checks failed
Build and push containers / metadata (push) Has been cancelled
Build and push containers / build-push-containers (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Build distribution 📦 (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/amd64 (alpine) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/arm64 (alpine) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/amd64 (main) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/arm/v7 (main) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/arm/v8 (main) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/arm64 (main) (push) Has been cancelled
ChangeDetection.io App Test / lint-code (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Test the built package works basically. (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Publish Python 🐍 distribution 📦 to PyPI (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-10 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-11 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-12 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-13 (push) Has been cancelled
2026-02-21 14:12:37 +01:00
dgtlmoon
82d5d7999c Pip installs - remove flask patch and pin library versions 2026-02-21 13:41:16 +01:00
dgtlmoon
7a51f1e4bf Lazy load flask_compress
Some checks failed
Build and push containers / metadata (push) Has been cancelled
Build and push containers / build-push-containers (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Build distribution 📦 (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Test the built package works basically. (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Publish Python 🐍 distribution 📦 to PyPI (push) Has been cancelled
ChangeDetection.io App Test / lint-code (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-10 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-11 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-12 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-13 (push) Has been cancelled
2026-02-20 08:56:25 +01:00
dgtlmoon
91dee697f9 UI - Content compression was not obeying FLASK_ENABLE_COMPRESSION, should be off by default due to a memory leak in flask_compress & socket.io 2026-02-20 08:54:10 +01:00
41 changed files with 2256 additions and 308 deletions

View File

@@ -715,7 +715,7 @@ jobs:
pip install 'pyOpenSSL>=23.2.0'
echo "=== Running version 0.49.1 to create datastore ==="
python3 ./changedetection.py -C -d /tmp/data &
ALLOW_IANA_RESTRICTED_ADDRESSES=true python3 ./changedetection.py -C -d /tmp/data &
APP_PID=$!
# Wait for app to be ready
@@ -763,7 +763,7 @@ jobs:
pip install -r requirements.txt
echo "=== Running current version (commit ${{ github.sha }}) with old datastore (testing mode) ==="
TESTING_SHUTDOWN_AFTER_DATASTORE_LOAD=1 python3 ./changedetection.py -d /tmp/data > /tmp/upgrade-test.log 2>&1
ALLOW_IANA_RESTRICTED_ADDRESSES=true TESTING_SHUTDOWN_AFTER_DATASTORE_LOAD=1 python3 ./changedetection.py -d /tmp/data > /tmp/upgrade-test.log 2>&1
echo "=== Upgrade test output ==="
cat /tmp/upgrade-test.log
@@ -771,7 +771,7 @@ jobs:
# Now start the current version normally to verify the tag survived
echo "=== Starting current version to verify tag exists after upgrade ==="
timeout 20 python3 ./changedetection.py -d /tmp/data > /tmp/ui-test.log 2>&1 &
ALLOW_IANA_RESTRICTED_ADDRESSES=true timeout 20 python3 ./changedetection.py -d /tmp/data > /tmp/ui-test.log 2>&1 &
APP_PID=$!
# Wait for app to be ready and fetch UI

View File

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

View File

@@ -17,7 +17,7 @@ class Tag(Resource):
self.update_q = kwargs['update_q']
# Get information about a single tag
# curl http://localhost:5000/api/v1/tag/<string:uuid>
# curl http://localhost:5000/api/v1/tag/<uuid_str:uuid>
@auth.check_token
@validate_openapi_request('getTag')
def get(self, uuid):

View File

@@ -57,7 +57,7 @@ class Watch(Resource):
self.update_q = kwargs['update_q']
# Get information about a single watch, excluding the history list (can be large)
# curl http://localhost:5000/api/v1/watch/<string:uuid>
# curl http://localhost:5000/api/v1/watch/<uuid_str:uuid>
# @todo - version2 - ?muted and ?paused should be able to be called together, return the watch struct not "OK"
# ?recheck=true
@auth.check_token
@@ -217,7 +217,7 @@ class WatchHistory(Resource):
self.datastore = kwargs['datastore']
# Get a list of available history for a watch by UUID
# curl http://localhost:5000/api/v1/watch/<string:uuid>/history
# curl http://localhost:5000/api/v1/watch/<uuid_str:uuid>/history
@auth.check_token
@validate_openapi_request('getWatchHistory')
def get(self, uuid):

View File

@@ -94,13 +94,13 @@ def construct_blueprint(datastore: ChangeDetectionStore):
return results
@login_required
@check_proxies_blueprint.route("/<string:uuid>/status", methods=['GET'])
@check_proxies_blueprint.route("/<uuid_str:uuid>/status", methods=['GET'])
def get_recheck_status(uuid):
results = _recalc_check_status(uuid=uuid)
return results
@login_required
@check_proxies_blueprint.route("/<string:uuid>/start", methods=['GET'])
@check_proxies_blueprint.route("/<uuid_str:uuid>/start", methods=['GET'])
def start_check(uuid):
if not datastore.proxy_list:

View File

@@ -15,7 +15,7 @@ def construct_blueprint(datastore: ChangeDetectionStore, update_q: PriorityQueue
price_data_follower_blueprint = Blueprint('price_data_follower', __name__)
@login_required
@price_data_follower_blueprint.route("/<string:uuid>/accept", methods=['GET'])
@price_data_follower_blueprint.route("/<uuid_str:uuid>/accept", methods=['GET'])
def accept(uuid):
datastore.data['watching'][uuid]['track_ldjson_price_data'] = PRICE_DATA_TRACK_ACCEPT
datastore.data['watching'][uuid]['processor'] = 'restock_diff'
@@ -25,7 +25,7 @@ def construct_blueprint(datastore: ChangeDetectionStore, update_q: PriorityQueue
return redirect(url_for("watchlist.index"))
@login_required
@price_data_follower_blueprint.route("/<string:uuid>/reject", methods=['GET'])
@price_data_follower_blueprint.route("/<uuid_str:uuid>/reject", methods=['GET'])
def reject(uuid):
datastore.data['watching'][uuid]['track_ldjson_price_data'] = PRICE_DATA_TRACK_REJECT
datastore.data['watching'][uuid].commit()

View File

@@ -9,11 +9,12 @@ def construct_single_watch_routes(rss_blueprint, datastore):
datastore: The ChangeDetectionStore instance
"""
@rss_blueprint.route("/watch/<string:uuid>", methods=['GET'])
@rss_blueprint.route("/watch/<uuid_str:uuid>", methods=['GET'])
def rss_single_watch(uuid):
import time
from flask import make_response, request
from flask import make_response, request, Response
from flask_babel import lazy_gettext as _l
from feedgen.feed import FeedGenerator
from loguru import logger
@@ -42,12 +43,12 @@ def construct_single_watch_routes(rss_blueprint, datastore):
# Get the watch by UUID
watch = datastore.data['watching'].get(uuid)
if not watch:
return f"Watch with UUID {uuid} not found", 404
return Response(_l("Watch with UUID %(uuid)s not found", uuid=uuid), status=404, mimetype='text/plain')
# Check if watch has at least 2 history snapshots
dates = list(watch.history.keys())
if len(dates) < 2:
return f"Watch {uuid} does not have enough history snapshots to show changes (need at least 2)", 400
return Response(_l("Watch %(uuid)s does not have enough history snapshots to show changes (need at least 2)", uuid=uuid), status=400, mimetype='text/plain')
# Get the number of diffs to include (default: 5)
rss_diff_length = datastore.data['settings']['application'].get('rss_diff_length', 5)

View File

@@ -54,7 +54,7 @@ def construct_blueprint(datastore: ChangeDetectionStore):
return redirect(url_for('tags.tags_overview_page'))
@tags_blueprint.route("/mute/<string:uuid>", methods=['GET'])
@tags_blueprint.route("/mute/<uuid_str:uuid>", methods=['GET'])
@login_optionally_required
def mute(uuid):
tag = datastore.data['settings']['application']['tags'].get(uuid)
@@ -63,7 +63,7 @@ def construct_blueprint(datastore: ChangeDetectionStore):
tag.commit()
return redirect(url_for('tags.tags_overview_page'))
@tags_blueprint.route("/delete/<string:uuid>", methods=['GET'])
@tags_blueprint.route("/delete/<uuid_str:uuid>", methods=['GET'])
@login_optionally_required
def delete(uuid):
# Delete the tag from settings immediately
@@ -90,7 +90,7 @@ def construct_blueprint(datastore: ChangeDetectionStore):
flash(gettext("Tag deleted, removing from watches in background"))
return redirect(url_for('tags.tags_overview_page'))
@tags_blueprint.route("/unlink/<string:uuid>", methods=['GET'])
@tags_blueprint.route("/unlink/<uuid_str:uuid>", methods=['GET'])
@login_optionally_required
def unlink(uuid):
# Unlink tag from all watches in background thread to avoid blocking
@@ -141,7 +141,7 @@ def construct_blueprint(datastore: ChangeDetectionStore):
flash(gettext("All tags deleted, clearing from watches in background"))
return redirect(url_for('tags.tags_overview_page'))
@tags_blueprint.route("/edit/<string:uuid>", methods=['GET'])
@tags_blueprint.route("/edit/<uuid_str:uuid>", methods=['GET'])
@login_optionally_required
def form_tag_edit(uuid):
from changedetectionio.blueprint.tags.form import group_restock_settings_form
@@ -203,7 +203,7 @@ def construct_blueprint(datastore: ChangeDetectionStore):
return output
@tags_blueprint.route("/edit/<string:uuid>", methods=['POST'])
@tags_blueprint.route("/edit/<uuid_str:uuid>", methods=['POST'])
@login_optionally_required
def form_tag_edit_submit(uuid):
from changedetectionio.blueprint.tags.form import group_restock_settings_form

View File

@@ -141,7 +141,7 @@ def construct_blueprint(datastore: ChangeDetectionStore, update_q, worker_pool,
# Import the login decorator
from changedetectionio.auth_decorator import login_optionally_required
@ui_blueprint.route("/clear_history/<string:uuid>", methods=['GET'])
@ui_blueprint.route("/clear_history/<uuid_str:uuid>", methods=['GET'])
@login_optionally_required
def clear_watch_history(uuid):
try:
@@ -366,7 +366,7 @@ def construct_blueprint(datastore: ChangeDetectionStore, update_q, worker_pool,
return redirect(url_for('watchlist.index'))
@ui_blueprint.route("/share-url/<string:uuid>", methods=['GET'])
@ui_blueprint.route("/share-url/<uuid_str:uuid>", methods=['GET'])
@login_optionally_required
def form_share_put_watch(uuid):
"""Given a watch UUID, upload the info and return a share-link

View File

@@ -66,7 +66,7 @@ def construct_blueprint(datastore: ChangeDetectionStore):
return Markup(result)
@diff_blueprint.route("/diff/<string:uuid>", methods=['GET'])
@diff_blueprint.route("/diff/<uuid_str:uuid>", methods=['GET'])
@login_optionally_required
def diff_history_page(uuid):
"""
@@ -128,7 +128,7 @@ def construct_blueprint(datastore: ChangeDetectionStore):
redirect=redirect
)
@diff_blueprint.route("/diff/<string:uuid>/extract", methods=['GET'])
@diff_blueprint.route("/diff/<uuid_str:uuid>/extract", methods=['GET'])
@login_optionally_required
def diff_history_page_extract_GET(uuid):
"""
@@ -182,7 +182,7 @@ def construct_blueprint(datastore: ChangeDetectionStore):
redirect=redirect
)
@diff_blueprint.route("/diff/<string:uuid>/extract", methods=['POST'])
@diff_blueprint.route("/diff/<uuid_str:uuid>/extract", methods=['POST'])
@login_optionally_required
def diff_history_page_extract_POST(uuid):
"""
@@ -238,7 +238,7 @@ def construct_blueprint(datastore: ChangeDetectionStore):
redirect=redirect
)
@diff_blueprint.route("/diff/<string:uuid>/processor-asset/<string:asset_name>", methods=['GET'])
@diff_blueprint.route("/diff/<uuid_str:uuid>/processor-asset/<string:asset_name>", methods=['GET'])
@login_optionally_required
def processor_asset(uuid, asset_name):
"""

View File

@@ -20,7 +20,7 @@ def construct_blueprint(datastore: ChangeDetectionStore, update_q, queuedWatchMe
if tag_uuid in watch.get('tags', []) and (tag.get('include_filters') or tag.get('subtractive_selectors')):
return True
@edit_blueprint.route("/edit/<string:uuid>", methods=['GET', 'POST'])
@edit_blueprint.route("/edit/<uuid_str:uuid>", methods=['GET', 'POST'])
@login_optionally_required
# https://stackoverflow.com/questions/42984453/wtforms-populate-form-with-data-if-data-exists
# https://wtforms.readthedocs.io/en/3.0.x/forms/#wtforms.form.Form.populate_obj ?
@@ -327,7 +327,7 @@ def construct_blueprint(datastore: ChangeDetectionStore, update_q, queuedWatchMe
return output
@edit_blueprint.route("/edit/<string:uuid>/get-html", methods=['GET'])
@edit_blueprint.route("/edit/<uuid_str:uuid>/get-html", methods=['GET'])
@login_optionally_required
def watch_get_latest_html(uuid):
from io import BytesIO
@@ -354,7 +354,7 @@ def construct_blueprint(datastore: ChangeDetectionStore, update_q, queuedWatchMe
# Return a 500 error
abort(500)
@edit_blueprint.route("/edit/<string:uuid>/get-data-package", methods=['GET'])
@edit_blueprint.route("/edit/<uuid_str:uuid>/get-data-package", methods=['GET'])
@login_optionally_required
def watch_get_data_package(uuid):
"""Download all data for a single watch as a zip file"""
@@ -405,7 +405,7 @@ def construct_blueprint(datastore: ChangeDetectionStore, update_q, queuedWatchMe
mimetype='application/zip')
# Ajax callback
@edit_blueprint.route("/edit/<string:uuid>/preview-rendered", methods=['POST'])
@edit_blueprint.route("/edit/<uuid_str:uuid>/preview-rendered", methods=['POST'])
@login_optionally_required
def watch_get_preview_rendered(uuid):
'''For when viewing the "preview" of the rendered text from inside of Edit'''

View File

@@ -10,7 +10,7 @@ from changedetectionio import html_tools
def construct_blueprint(datastore: ChangeDetectionStore):
preview_blueprint = Blueprint('ui_preview', __name__, template_folder="../ui/templates")
@preview_blueprint.route("/preview/<string:uuid>", methods=['GET'])
@preview_blueprint.route("/preview/<uuid_str:uuid>", methods=['GET'])
@login_optionally_required
def preview_page(uuid):
"""
@@ -125,7 +125,7 @@ def construct_blueprint(datastore: ChangeDetectionStore):
return output
@preview_blueprint.route("/preview/<string:uuid>/processor-asset/<string:asset_name>", methods=['GET'])
@preview_blueprint.route("/preview/<uuid_str:uuid>/processor-asset/<string:asset_name>", methods=['GET'])
@login_optionally_required
def processor_asset(uuid, asset_name):
"""

View File

@@ -1,4 +1,5 @@
from loguru import logger
from urllib.parse import urljoin, urlparse
import hashlib
import os
import re
@@ -7,6 +8,7 @@ import asyncio
from changedetectionio import strtobool
from changedetectionio.content_fetchers.exceptions import BrowserStepsInUnsupportedFetcher, EmptyReply, Non200ErrorCodeReceived
from changedetectionio.content_fetchers.base import Fetcher
from changedetectionio.validate_url import is_private_hostname
# "html_requests" is listed as the default fetcher in store.py!
@@ -79,14 +81,48 @@ class fetcher(Fetcher):
if strtobool(os.getenv('ALLOW_FILE_URI', 'false')) and url.startswith('file://'):
from requests_file import FileAdapter
session.mount('file://', FileAdapter())
allow_iana_restricted = strtobool(os.getenv('ALLOW_IANA_RESTRICTED_ADDRESSES', 'false'))
try:
# Fresh DNS check at fetch time — catches DNS rebinding regardless of add-time cache.
if not allow_iana_restricted:
parsed_initial = urlparse(url)
if parsed_initial.hostname and is_private_hostname(parsed_initial.hostname):
raise Exception(f"Fetch blocked: '{url}' resolves to a private/reserved IP address. "
f"Set ALLOW_IANA_RESTRICTED_ADDRESSES=true to allow.")
r = session.request(method=request_method,
data=request_body.encode('utf-8') if type(request_body) is str else request_body,
url=url,
headers=request_headers,
timeout=timeout,
proxies=proxies,
verify=False)
verify=False,
allow_redirects=False)
# Manually follow redirects so each hop's resolved IP can be validated,
# preventing SSRF via an open redirect on a public host.
current_url = url
for _ in range(10):
if not r.is_redirect:
break
location = r.headers.get('Location', '')
redirect_url = urljoin(current_url, location)
if not allow_iana_restricted:
parsed_redirect = urlparse(redirect_url)
if parsed_redirect.hostname and is_private_hostname(parsed_redirect.hostname):
raise Exception(f"Redirect blocked: '{redirect_url}' resolves to a private/reserved IP address.")
current_url = redirect_url
r = session.request('GET', redirect_url,
headers=request_headers,
timeout=timeout,
proxies=proxies,
verify=False,
allow_redirects=False)
else:
raise Exception("Too many redirects")
except Exception as e:
msg = str(e)
if proxies and 'SOCKSHTTPSConnectionPool' in msg:

View File

@@ -27,7 +27,6 @@ from flask import (
session,
url_for,
)
from flask_compress import Compress as FlaskCompress
from flask_restful import abort, Api
from flask_cors import CORS
@@ -69,19 +68,43 @@ socketio_server = None
# Enable CORS, especially useful for the Chrome extension to operate from anywhere
CORS(app)
from werkzeug.routing import BaseConverter, ValidationError
from uuid import UUID
class StrictUUIDConverter(BaseConverter):
# Special sentinel values allowed in addition to strict UUIDs
_ALLOWED_SENTINELS = frozenset({'first'})
def to_python(self, value: str) -> str:
if value in self._ALLOWED_SENTINELS:
return value
try:
u = UUID(value)
except ValueError as e:
raise ValidationError() from e
# Reject non-standard formats (braces, URNs, no-hyphens)
if str(u) != value.lower():
raise ValidationError()
return str(u)
def to_url(self, value) -> str:
return str(value)
# app setup (once)
app.url_map.converters["uuid_str"] = StrictUUIDConverter
# Flask-Compress handles HTTP compression, Socket.IO compression disabled to prevent memory leak.
# There's also a bug between flask compress and socketio that causes some kind of slow memory leak
# It's better to use compression on your reverse proxy (nginx etc) instead.
if strtobool(os.getenv("FLASK_ENABLE_COMPRESSION")):
from flask_compress import Compress as FlaskCompress
app.config['COMPRESS_MIN_SIZE'] = 2096
app.config['COMPRESS_MIMETYPES'] = ['text/html', 'text/css', 'text/javascript', 'application/json', 'application/javascript', 'image/svg+xml']
# Use gzip only - smaller memory footprint than zstd/brotli (4-8KB vs 200-500KB contexts)
app.config['COMPRESS_ALGORITHM'] = ['gzip']
compress = FlaskCompress()
compress.init_app(app)
compress = FlaskCompress()
compress.init_app(app)
app.config['TEMPLATES_AUTO_RELOAD'] = False
@@ -534,22 +557,22 @@ def changedetection_app(config=None, datastore_o=None):
watch_api.add_resource(WatchHistoryDiff,
'/api/v1/watch/<string:uuid>/difference/<string:from_timestamp>/<string:to_timestamp>',
'/api/v1/watch/<uuid_str:uuid>/difference/<string:from_timestamp>/<string:to_timestamp>',
resource_class_kwargs={'datastore': datastore})
watch_api.add_resource(WatchSingleHistory,
'/api/v1/watch/<string:uuid>/history/<string:timestamp>',
'/api/v1/watch/<uuid_str:uuid>/history/<string:timestamp>',
resource_class_kwargs={'datastore': datastore, 'update_q': update_q})
watch_api.add_resource(WatchFavicon,
'/api/v1/watch/<string:uuid>/favicon',
'/api/v1/watch/<uuid_str:uuid>/favicon',
resource_class_kwargs={'datastore': datastore})
watch_api.add_resource(WatchHistory,
'/api/v1/watch/<string:uuid>/history',
'/api/v1/watch/<uuid_str:uuid>/history',
resource_class_kwargs={'datastore': datastore})
watch_api.add_resource(CreateWatch, '/api/v1/watch',
resource_class_kwargs={'datastore': datastore, 'update_q': update_q})
watch_api.add_resource(Watch, '/api/v1/watch/<string:uuid>',
watch_api.add_resource(Watch, '/api/v1/watch/<uuid_str:uuid>',
resource_class_kwargs={'datastore': datastore, 'update_q': update_q})
watch_api.add_resource(SystemInfo, '/api/v1/systeminfo',
@@ -562,7 +585,7 @@ def changedetection_app(config=None, datastore_o=None):
watch_api.add_resource(Tags, '/api/v1/tags',
resource_class_kwargs={'datastore': datastore})
watch_api.add_resource(Tag, '/api/v1/tag', '/api/v1/tag/<string:uuid>',
watch_api.add_resource(Tag, '/api/v1/tag', '/api/v1/tag/<uuid_str:uuid>',
resource_class_kwargs={'datastore': datastore, 'update_q': update_q})
watch_api.add_resource(Search, '/api/v1/search',

View File

@@ -36,7 +36,7 @@ def _task(watch, update_handler):
def prepare_filter_prevew(datastore, watch_uuid, form_data):
'''Used by @app.route("/edit/<string:uuid>/preview-rendered", methods=['POST'])'''
'''Used by @app.route("/edit/<uuid_str:uuid>/preview-rendered", methods=['POST'])'''
from changedetectionio import forms, html_tools
from changedetectionio.model.Watch import model as watch_model
from concurrent.futures import ThreadPoolExecutor

View File

@@ -198,26 +198,11 @@ def handle_watch_update(socketio, **kwargs):
except Exception as e:
logger.error(f"Socket.IO error in handle_watch_update: {str(e)}")
def _patch_flask_request_context_session():
"""Flask 3.1 removed the session setter from RequestContext, but Flask-SocketIO 5.6.0
still assigns to it directly (ctx.session = ...). Restore a setter that writes the
private _session attribute so the two libraries work together.
"""
from flask.ctx import RequestContext
if getattr(RequestContext.session, 'fset', None) is not None:
return # Already has a setter (future Flask version restored it)
original_prop = RequestContext.session
RequestContext.session = original_prop.setter(lambda self, value: setattr(self, '_session', value))
def init_socketio(app, datastore):
"""Initialize SocketIO with the main Flask app"""
import platform
import sys
_patch_flask_request_context_session()
# Platform-specific async_mode selection for better stability
system = platform.system().lower()
python_version = sys.version_info

View File

@@ -44,12 +44,12 @@ data_sanity_test () {
cd ..
TMPDIR=$(mktemp -d)
PORT_N=$((5000 + RANDOM % (6501 - 5000)))
./changedetection.py -p $PORT_N -d $TMPDIR -u "https://localhost?test-url-is-sanity=1" &
ALLOW_IANA_RESTRICTED_ADDRESSES=true ./changedetection.py -p $PORT_N -d $TMPDIR -u "https://localhost?test-url-is-sanity=1" &
PID=$!
sleep 5
kill $PID
sleep 2
./changedetection.py -p $PORT_N -d $TMPDIR &
ALLOW_IANA_RESTRICTED_ADDRESSES=true ./changedetection.py -p $PORT_N -d $TMPDIR &
PID=$!
sleep 5
# On a restart the URL should still be there

View File

@@ -728,8 +728,11 @@ class ChangeDetectionStore(DatastoreUpdatesMixin, FileSavingDataStore):
return False
if not is_safe_valid_url(url):
flash(gettext('Watch protocol is not permitted or invalid URL format'), 'error')
from flask import has_request_context
if has_request_context():
flash(gettext('Watch protocol is not permitted or invalid URL format'), 'error')
else:
logger.error(f"add_watch: URL '{url}' is not permitted or invalid, skipping.")
return None
# Check PAGE_WATCH_LIMIT if set

View File

@@ -13,6 +13,10 @@ import sys
# When test server is slow/unresponsive, workers fail fast instead of holding UUIDs for 45s
# This prevents exponential priority growth from repeated deferrals (priority × 10 each defer)
os.environ['DEFAULT_SETTINGS_REQUESTS_TIMEOUT'] = '5'
# Test server runs on localhost (127.0.0.1) which is a private IP.
# Allow it globally so all existing tests keep working; test_ssrf_protection
# uses monkeypatch to temporarily override this for its own assertions.
os.environ['ALLOW_IANA_RESTRICTED_ADDRESSES'] = 'true'
from changedetectionio.flask_app import init_app_secret, changedetection_app
from changedetectionio.tests.util import live_server_setup, new_live_server_setup

View File

@@ -1,4 +1,5 @@
import os
import pytest
from flask import url_for
@@ -579,3 +580,131 @@ def test_static_directory_traversal(client, live_server, measure_memory_usage, d
# Should get 403 (not authenticated) or 404 (file not found), not a path traversal
assert res.status_code in [403, 404]
def test_ssrf_private_ip_blocked(client, live_server, monkeypatch, measure_memory_usage, datastore_path):
"""
SSRF protection: IANA-reserved/private IP addresses must be blocked by default.
Covers:
1. is_private_hostname() correctly classifies all reserved ranges
2. is_safe_valid_url() rejects private-IP URLs at add-time (env var off)
3. is_safe_valid_url() allows private-IP URLs when ALLOW_IANA_RESTRICTED_ADDRESSES=true
4. UI form rejects private-IP URLs and shows the standard error message
5. Requests fetcher blocks fetch-time DNS rebinding (fresh check on every fetch)
6. Requests fetcher blocks redirects that lead to a private IP (open-redirect bypass)
conftest.py sets ALLOW_IANA_RESTRICTED_ADDRESSES=true globally so the test
server (localhost) keeps working for all other tests. monkeypatch temporarily
overrides it to 'false' here, and is automatically restored after the test.
"""
from unittest.mock import patch, MagicMock
from changedetectionio.validate_url import is_safe_valid_url, is_private_hostname
monkeypatch.setenv('ALLOW_IANA_RESTRICTED_ADDRESSES', 'false')
# Clear any URL results cached while the env var was 'true'
is_safe_valid_url.cache_clear()
# ------------------------------------------------------------------
# 1. is_private_hostname() — unit tests across all reserved ranges
# ------------------------------------------------------------------
private_hosts = [
'127.0.0.1', # loopback
'10.0.0.1', # RFC 1918
'172.16.0.1', # RFC 1918
'192.168.1.1', # RFC 1918
'169.254.169.254', # link-local / AWS metadata endpoint
'::1', # IPv6 loopback
'fc00::1', # IPv6 unique local
'fe80::1', # IPv6 link-local
]
for host in private_hosts:
assert is_private_hostname(host), f"{host} should be identified as private/reserved"
for host in ['8.8.8.8', '1.1.1.1']:
assert not is_private_hostname(host), f"{host} should be identified as public"
# ------------------------------------------------------------------
# 2. is_safe_valid_url() blocks private-IP URLs (env var off)
# ------------------------------------------------------------------
blocked_urls = [
'http://127.0.0.1/',
'http://10.0.0.1/',
'http://172.16.0.1/',
'http://192.168.1.1/',
'http://169.254.169.254/',
'http://169.254.169.254/latest/meta-data/iam/security-credentials/',
'http://[::1]/',
'http://[fc00::1]/',
'http://[fe80::1]/',
]
for url in blocked_urls:
assert not is_safe_valid_url(url), f"{url} should be blocked by is_safe_valid_url"
# ------------------------------------------------------------------
# 3. ALLOW_IANA_RESTRICTED_ADDRESSES=true bypasses the block
# ------------------------------------------------------------------
monkeypatch.setenv('ALLOW_IANA_RESTRICTED_ADDRESSES', 'true')
is_safe_valid_url.cache_clear()
assert is_safe_valid_url('http://127.0.0.1/'), \
"Private IP should be allowed when ALLOW_IANA_RESTRICTED_ADDRESSES=true"
# Restore the block for the remaining assertions
monkeypatch.setenv('ALLOW_IANA_RESTRICTED_ADDRESSES', 'false')
is_safe_valid_url.cache_clear()
# ------------------------------------------------------------------
# 4. UI form rejects private-IP URLs
# ------------------------------------------------------------------
for url in ['http://127.0.0.1/', 'http://169.254.169.254/latest/meta-data/']:
res = client.post(
url_for('ui.ui_views.form_quick_watch_add'),
data={'url': url, 'tags': ''},
follow_redirects=True
)
assert b'Watch protocol is not permitted or invalid URL format' in res.data, \
f"UI should reject {url}"
# ------------------------------------------------------------------
# 5. Fetch-time DNS-rebinding check in the requests fetcher
# Simulates: URL passed add-time validation with a public IP, but
# by fetch time DNS has been rebound to a private IP.
# ------------------------------------------------------------------
from changedetectionio.content_fetchers.requests import fetcher as RequestsFetcher
f = RequestsFetcher()
with patch('changedetectionio.content_fetchers.requests.is_private_hostname', return_value=True):
with pytest.raises(Exception, match='private/reserved'):
f._run_sync(
url='http://example.com/',
timeout=5,
request_headers={},
request_body=None,
request_method='GET',
)
# ------------------------------------------------------------------
# 6. Redirect-to-private-IP blocked (open-redirect SSRF bypass)
# Public host returns a 302 pointing at an IANA-reserved address.
# ------------------------------------------------------------------
mock_redirect = MagicMock()
mock_redirect.is_redirect = True
mock_redirect.status_code = 302
mock_redirect.headers = {'Location': 'http://169.254.169.254/latest/meta-data/'}
def _private_only_for_redirect(hostname):
# Initial host is "public"; the redirect target is private
return hostname in {'169.254.169.254', '10.0.0.1', '172.16.0.1',
'192.168.0.1', '127.0.0.1', '::1'}
with patch('changedetectionio.content_fetchers.requests.is_private_hostname',
side_effect=_private_only_for_redirect):
with patch('requests.Session.request', return_value=mock_redirect):
with pytest.raises(Exception, match='Redirect blocked'):
f._run_sync(
url='http://example.com/',
timeout=5,
request_headers={},
request_body=None,
request_method='GET',
)

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2026-02-05 17:47+0100\n"
"POT-Creation-Date: 2026-02-23 03:54+0100\n"
"PO-Revision-Date: 2026-01-02 11:40+0100\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: cs\n"
@@ -16,7 +16,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.17.0\n"
"Generated-By: Babel 2.16.0\n"
#: changedetectionio/blueprint/backups/__init__.py
msgid "A backup is already running, check back in a few minutes"
@@ -34,34 +34,116 @@ msgstr ""
msgid "Backups were deleted."
msgstr ""
#: changedetectionio/blueprint/backups/templates/overview.html changedetectionio/blueprint/settings/templates/settings.html
msgid "Backups"
msgstr "Backups"
#: changedetectionio/blueprint/backups/restore.py
msgid "Backup zip file"
msgstr ""
#: changedetectionio/blueprint/backups/templates/overview.html
#: changedetectionio/blueprint/backups/restore.py
msgid "Must be a .zip backup file!"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "Include groups"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "Replace existing groups of the same UUID"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "Include watches"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "Replace existing watches of the same UUID"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Restore backup"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "A restore is already running, check back in a few minutes"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "No file uploaded"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "File must be a .zip backup file"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "Invalid or corrupted zip file"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "Restore started in background, check back in a few minutes."
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_create.html
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Create"
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_create.html
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Restore"
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_create.html
msgid "A backup is running!"
msgstr "A backup is running!"
#: changedetectionio/blueprint/backups/templates/overview.html
#: changedetectionio/blueprint/backups/templates/backup_create.html
msgid "Here you can download and request a new backup, when a backup is completed you will see it listed below."
msgstr ""
#: changedetectionio/blueprint/backups/templates/overview.html
#: changedetectionio/blueprint/backups/templates/backup_create.html
msgid "Mb"
msgstr "Mb"
#: changedetectionio/blueprint/backups/templates/overview.html
#: changedetectionio/blueprint/backups/templates/backup_create.html
msgid "No backups found."
msgstr "No backups found."
#: changedetectionio/blueprint/backups/templates/overview.html
#: changedetectionio/blueprint/backups/templates/backup_create.html
msgid "Create backup"
msgstr "Vytvořit zálohu"
#: changedetectionio/blueprint/backups/templates/overview.html
#: changedetectionio/blueprint/backups/templates/backup_create.html
msgid "Remove backups"
msgstr "Odstranit zálohy"
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "A restore is running!"
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Restore a backup. Must be a .zip backup file created on/after v0.53.1 (new database layout)."
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Note: This does not override the main application settings, only watches and groups."
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Include all groups found in backup?"
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Replace any existing groups of the same UUID?"
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Include all watches found in backup?"
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Replace any existing watches of the same UUID?"
msgstr ""
#: changedetectionio/blueprint/imports/importer.py
msgid "Importing 5,000 of the first URLs from your list, the rest can be imported again."
msgstr ""
@@ -120,6 +202,14 @@ msgstr "Distill.io"
msgid ".XLSX & Wachete"
msgstr ".XLSX a Wachete"
#: changedetectionio/blueprint/imports/templates/import.html
msgid "Restoring changedetection.io backups is in the"
msgstr ""
#: changedetectionio/blueprint/imports/templates/import.html
msgid "backups section"
msgstr ""
#: changedetectionio/blueprint/imports/templates/import.html
msgid "Enter one URL per line, and optionally add tags for each URL after a space, delineated by comma (,):"
msgstr ""
@@ -206,6 +296,16 @@ msgstr "Znovu zkontrolovat čas (minuty)"
msgid "Import"
msgstr "IMPORTOVAT"
#: changedetectionio/blueprint/rss/single_watch.py
#, python-format
msgid "Watch with UUID %(uuid)s not found"
msgstr ""
#: changedetectionio/blueprint/rss/single_watch.py
#, python-format
msgid "Watch %(uuid)s does not have enough history snapshots to show changes (need at least 2)"
msgstr ""
#: changedetectionio/blueprint/settings/__init__.py
msgid "Password protection removed."
msgstr ""
@@ -291,6 +391,10 @@ msgstr "API"
msgid "RSS"
msgstr "RSS"
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Backups"
msgstr "Backups"
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Time & Date"
msgstr "Čas a datum"
@@ -307,10 +411,6 @@ msgstr "Info"
msgid "Default recheck time for all watches, current system minimum is"
msgstr "Výchozí čas opětovné kontroly pro všechny monitory, aktuální systémové minimum je"
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "seconds"
msgstr "sekundy"
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "more info"
msgstr "Více informací"
@@ -666,6 +766,10 @@ msgid ""
"whitelist the IP access instead"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Uptime:"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Python version:"
msgstr "Verze Pythonu:"
@@ -919,10 +1023,6 @@ msgstr ""
msgid "Incorrect confirmation text."
msgstr "Žádné informace"
#: changedetectionio/blueprint/ui/__init__.py
msgid "Marking watches as viewed in background..."
msgstr ""
#: changedetectionio/blueprint/ui/__init__.py
#, python-brace-format
msgid "The watch by UUID {} does not exist."
@@ -1527,6 +1627,10 @@ msgstr "Odpověď typu serveru"
msgid "Download latest HTML snapshot"
msgstr "Stáhněte si nejnovější HTML snímek"
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "Download watch data package"
msgstr ""
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "Delete Watch?"
msgstr "Smazat monitory?"
@@ -1784,6 +1888,66 @@ msgstr "v '%(title)s'"
msgid "Not yet"
msgstr "Ještě ne"
#: changedetectionio/flask_app.py
msgid "0 seconds"
msgstr ""
#: changedetectionio/flask_app.py
msgid "year"
msgstr ""
#: changedetectionio/flask_app.py
msgid "years"
msgstr ""
#: changedetectionio/flask_app.py
msgid "month"
msgstr ""
#: changedetectionio/flask_app.py
msgid "months"
msgstr ""
#: changedetectionio/flask_app.py
msgid "week"
msgstr ""
#: changedetectionio/flask_app.py
msgid "weeks"
msgstr ""
#: changedetectionio/flask_app.py
msgid "day"
msgstr ""
#: changedetectionio/flask_app.py
msgid "days"
msgstr ""
#: changedetectionio/flask_app.py
msgid "hour"
msgstr ""
#: changedetectionio/flask_app.py
msgid "hours"
msgstr ""
#: changedetectionio/flask_app.py
msgid "minute"
msgstr ""
#: changedetectionio/flask_app.py
msgid "minutes"
msgstr ""
#: changedetectionio/flask_app.py
msgid "second"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings.html changedetectionio/flask_app.py
msgid "seconds"
msgstr "sekundy"
#: changedetectionio/flask_app.py
msgid "Already logged in"
msgstr ""
@@ -2878,6 +3042,18 @@ msgstr ""
msgid "Note: Wrap in forward slash / to use regex example:"
msgstr ""
#: changedetectionio/templates/edit/text-options.html
msgid "You can also use"
msgstr ""
#: changedetectionio/templates/edit/text-options.html
msgid "conditions"
msgstr ""
#: changedetectionio/templates/edit/text-options.html
msgid "\"Page text\" - with Contains, Starts With, Not Contains and many more"
msgstr ""
#: changedetectionio/templates/edit/text-options.html
msgid "Matching text will be ignored in the text snapshot (you can still see it but it wont trigger a change)"
msgstr ""
@@ -3091,3 +3267,6 @@ msgstr "Hlavní nastavení"
#~ msgid "Tip: You can also add 'shared' watches."
#~ msgstr "Tip: Můžete také přidat „sdílené“ monitory."
#~ msgid "Marking watches as viewed in background..."
#~ msgstr ""

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2026-02-05 17:47+0100\n"
"POT-Creation-Date: 2026-02-23 03:54+0100\n"
"PO-Revision-Date: 2026-01-14 03:57+0100\n"
"Last-Translator: \n"
"Language: de\n"
@@ -16,7 +16,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.17.0\n"
"Generated-By: Babel 2.16.0\n"
#: changedetectionio/blueprint/backups/__init__.py
msgid "A backup is already running, check back in a few minutes"
@@ -34,36 +34,118 @@ msgstr "Backup läuft im Hintergrund, bitte in ein paar Minuten erneut versuchen
msgid "Backups were deleted."
msgstr "Backups wurden gelöscht."
#: changedetectionio/blueprint/backups/templates/overview.html changedetectionio/blueprint/settings/templates/settings.html
msgid "Backups"
msgstr "Backups"
#: changedetectionio/blueprint/backups/restore.py
msgid "Backup zip file"
msgstr ""
#: changedetectionio/blueprint/backups/templates/overview.html
#: changedetectionio/blueprint/backups/restore.py
msgid "Must be a .zip backup file!"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "Include groups"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "Replace existing groups of the same UUID"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "Include watches"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "Replace existing watches of the same UUID"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Restore backup"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "A restore is already running, check back in a few minutes"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "No file uploaded"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "File must be a .zip backup file"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "Invalid or corrupted zip file"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "Restore started in background, check back in a few minutes."
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_create.html
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Create"
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_create.html
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Restore"
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_create.html
msgid "A backup is running!"
msgstr "Ein Backup läuft!"
#: changedetectionio/blueprint/backups/templates/overview.html
#: changedetectionio/blueprint/backups/templates/backup_create.html
msgid "Here you can download and request a new backup, when a backup is completed you will see it listed below."
msgstr ""
"Hier können Sie ein neues Backup herunterladen und anfordern. Sobald ein Backup abgeschlossen ist, wird es unten "
"aufgelistet."
#: changedetectionio/blueprint/backups/templates/overview.html
#: changedetectionio/blueprint/backups/templates/backup_create.html
msgid "Mb"
msgstr "Mb"
#: changedetectionio/blueprint/backups/templates/overview.html
#: changedetectionio/blueprint/backups/templates/backup_create.html
msgid "No backups found."
msgstr "Keine Backups gefunden."
#: changedetectionio/blueprint/backups/templates/overview.html
#: changedetectionio/blueprint/backups/templates/backup_create.html
msgid "Create backup"
msgstr "Backup erstellen"
#: changedetectionio/blueprint/backups/templates/overview.html
#: changedetectionio/blueprint/backups/templates/backup_create.html
msgid "Remove backups"
msgstr "Backups entfernen"
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "A restore is running!"
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Restore a backup. Must be a .zip backup file created on/after v0.53.1 (new database layout)."
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Note: This does not override the main application settings, only watches and groups."
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Include all groups found in backup?"
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Replace any existing groups of the same UUID?"
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Include all watches found in backup?"
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Replace any existing watches of the same UUID?"
msgstr ""
#: changedetectionio/blueprint/imports/importer.py
msgid "Importing 5,000 of the first URLs from your list, the rest can be imported again."
msgstr "Es werden 5.000 der ersten URLs aus Ihrer Liste importiert, der Rest kann erneut importiert werden."
@@ -122,6 +204,14 @@ msgstr "Distill.io"
msgid ".XLSX & Wachete"
msgstr ".XLSX & Wachete"
#: changedetectionio/blueprint/imports/templates/import.html
msgid "Restoring changedetection.io backups is in the"
msgstr ""
#: changedetectionio/blueprint/imports/templates/import.html
msgid "backups section"
msgstr ""
#: changedetectionio/blueprint/imports/templates/import.html
msgid "Enter one URL per line, and optionally add tags for each URL after a space, delineated by comma (,):"
msgstr ""
@@ -210,6 +300,16 @@ msgstr "Nachprüfzeit (Minuten)"
msgid "Import"
msgstr "IMPORT"
#: changedetectionio/blueprint/rss/single_watch.py
#, python-format
msgid "Watch with UUID %(uuid)s not found"
msgstr ""
#: changedetectionio/blueprint/rss/single_watch.py
#, python-format
msgid "Watch %(uuid)s does not have enough history snapshots to show changes (need at least 2)"
msgstr ""
#: changedetectionio/blueprint/settings/__init__.py
msgid "Password protection removed."
msgstr "Passwortschutz entfernt."
@@ -295,6 +395,10 @@ msgstr "API"
msgid "RSS"
msgstr "RSS"
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Backups"
msgstr "Backups"
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Time & Date"
msgstr "Uhrzeit und Datum"
@@ -311,10 +415,6 @@ msgstr "Info"
msgid "Default recheck time for all watches, current system minimum is"
msgstr "Standardmäßige Überprüfungszeit für alle Observationen, derzeitiges Systemminimum ist"
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "seconds"
msgstr "Sekunden"
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "more info"
msgstr "Weitere Informationen"
@@ -676,6 +776,10 @@ msgid ""
"whitelist the IP access instead"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Uptime:"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Python version:"
msgstr "Python-Version:"
@@ -935,10 +1039,6 @@ msgstr ""
msgid "Incorrect confirmation text."
msgstr "Falscher Bestätigungstext"
#: changedetectionio/blueprint/ui/__init__.py
msgid "Marking watches as viewed in background..."
msgstr ""
#: changedetectionio/blueprint/ui/__init__.py
#, python-brace-format
msgid "The watch by UUID {} does not exist."
@@ -1565,6 +1665,10 @@ msgstr "Antwort vom Servertyp"
msgid "Download latest HTML snapshot"
msgstr "Laden Sie den neuesten HTML-Snapshot herunter"
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "Download watch data package"
msgstr ""
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "Delete Watch?"
msgstr "Überwachung löschen?"
@@ -1826,6 +1930,66 @@ msgstr "in '%(title)s'"
msgid "Not yet"
msgstr "Noch nicht"
#: changedetectionio/flask_app.py
msgid "0 seconds"
msgstr ""
#: changedetectionio/flask_app.py
msgid "year"
msgstr ""
#: changedetectionio/flask_app.py
msgid "years"
msgstr ""
#: changedetectionio/flask_app.py
msgid "month"
msgstr ""
#: changedetectionio/flask_app.py
msgid "months"
msgstr ""
#: changedetectionio/flask_app.py
msgid "week"
msgstr ""
#: changedetectionio/flask_app.py
msgid "weeks"
msgstr ""
#: changedetectionio/flask_app.py
msgid "day"
msgstr ""
#: changedetectionio/flask_app.py
msgid "days"
msgstr ""
#: changedetectionio/flask_app.py
msgid "hour"
msgstr ""
#: changedetectionio/flask_app.py
msgid "hours"
msgstr ""
#: changedetectionio/flask_app.py
msgid "minute"
msgstr ""
#: changedetectionio/flask_app.py
msgid "minutes"
msgstr ""
#: changedetectionio/flask_app.py
msgid "second"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings.html changedetectionio/flask_app.py
msgid "seconds"
msgstr "Sekunden"
#: changedetectionio/flask_app.py
msgid "Already logged in"
msgstr "Bereits angemeldet"
@@ -2927,6 +3091,18 @@ msgstr ""
msgid "Note: Wrap in forward slash / to use regex example:"
msgstr ""
#: changedetectionio/templates/edit/text-options.html
msgid "You can also use"
msgstr ""
#: changedetectionio/templates/edit/text-options.html
msgid "conditions"
msgstr ""
#: changedetectionio/templates/edit/text-options.html
msgid "\"Page text\" - with Contains, Starts With, Not Contains and many more"
msgstr ""
#: changedetectionio/templates/edit/text-options.html
msgid "Matching text will be ignored in the text snapshot (you can still see it but it wont trigger a change)"
msgstr ""
@@ -3206,3 +3382,6 @@ msgstr "Haupteinstellungen"
#~ msgid "Tip: You can also add 'shared' watches."
#~ msgstr "Tipp: Sie können auch „gemeinsame“ Überwachungen hinzufügen."
#~ msgid "Marking watches as viewed in background..."
#~ msgstr ""

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: changedetection.io\n"
"Report-Msgid-Bugs-To: https://github.com/dgtlmoon/changedetection.io\n"
"POT-Creation-Date: 2026-02-05 17:47+0100\n"
"POT-Creation-Date: 2026-02-23 03:54+0100\n"
"PO-Revision-Date: 2026-01-12 16:33+0100\n"
"Last-Translator: British English Translation Team\n"
"Language: en_GB\n"
@@ -16,7 +16,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.17.0\n"
"Generated-By: Babel 2.16.0\n"
#: changedetectionio/blueprint/backups/__init__.py
msgid "A backup is already running, check back in a few minutes"
@@ -34,34 +34,116 @@ msgstr ""
msgid "Backups were deleted."
msgstr ""
#: changedetectionio/blueprint/backups/templates/overview.html changedetectionio/blueprint/settings/templates/settings.html
msgid "Backups"
#: changedetectionio/blueprint/backups/restore.py
msgid "Backup zip file"
msgstr ""
#: changedetectionio/blueprint/backups/templates/overview.html
#: changedetectionio/blueprint/backups/restore.py
msgid "Must be a .zip backup file!"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "Include groups"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "Replace existing groups of the same UUID"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "Include watches"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "Replace existing watches of the same UUID"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Restore backup"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "A restore is already running, check back in a few minutes"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "No file uploaded"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "File must be a .zip backup file"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "Invalid or corrupted zip file"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "Restore started in background, check back in a few minutes."
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_create.html
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Create"
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_create.html
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Restore"
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_create.html
msgid "A backup is running!"
msgstr ""
#: changedetectionio/blueprint/backups/templates/overview.html
#: changedetectionio/blueprint/backups/templates/backup_create.html
msgid "Here you can download and request a new backup, when a backup is completed you will see it listed below."
msgstr ""
#: changedetectionio/blueprint/backups/templates/overview.html
#: changedetectionio/blueprint/backups/templates/backup_create.html
msgid "Mb"
msgstr ""
#: changedetectionio/blueprint/backups/templates/overview.html
#: changedetectionio/blueprint/backups/templates/backup_create.html
msgid "No backups found."
msgstr ""
#: changedetectionio/blueprint/backups/templates/overview.html
#: changedetectionio/blueprint/backups/templates/backup_create.html
msgid "Create backup"
msgstr ""
#: changedetectionio/blueprint/backups/templates/overview.html
#: changedetectionio/blueprint/backups/templates/backup_create.html
msgid "Remove backups"
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "A restore is running!"
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Restore a backup. Must be a .zip backup file created on/after v0.53.1 (new database layout)."
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Note: This does not override the main application settings, only watches and groups."
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Include all groups found in backup?"
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Replace any existing groups of the same UUID?"
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Include all watches found in backup?"
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Replace any existing watches of the same UUID?"
msgstr ""
#: changedetectionio/blueprint/imports/importer.py
msgid "Importing 5,000 of the first URLs from your list, the rest can be imported again."
msgstr ""
@@ -120,6 +202,14 @@ msgstr ""
msgid ".XLSX & Wachete"
msgstr ""
#: changedetectionio/blueprint/imports/templates/import.html
msgid "Restoring changedetection.io backups is in the"
msgstr ""
#: changedetectionio/blueprint/imports/templates/import.html
msgid "backups section"
msgstr ""
#: changedetectionio/blueprint/imports/templates/import.html
msgid "Enter one URL per line, and optionally add tags for each URL after a space, delineated by comma (,):"
msgstr ""
@@ -204,6 +294,16 @@ msgstr ""
msgid "Import"
msgstr ""
#: changedetectionio/blueprint/rss/single_watch.py
#, python-format
msgid "Watch with UUID %(uuid)s not found"
msgstr ""
#: changedetectionio/blueprint/rss/single_watch.py
#, python-format
msgid "Watch %(uuid)s does not have enough history snapshots to show changes (need at least 2)"
msgstr ""
#: changedetectionio/blueprint/settings/__init__.py
msgid "Password protection removed."
msgstr ""
@@ -289,6 +389,10 @@ msgstr ""
msgid "RSS"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Backups"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Time & Date"
msgstr ""
@@ -305,10 +409,6 @@ msgstr ""
msgid "Default recheck time for all watches, current system minimum is"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "seconds"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "more info"
msgstr ""
@@ -662,6 +762,10 @@ msgid ""
"whitelist the IP access instead"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Uptime:"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Python version:"
msgstr ""
@@ -915,10 +1019,6 @@ msgstr ""
msgid "Incorrect confirmation text."
msgstr ""
#: changedetectionio/blueprint/ui/__init__.py
msgid "Marking watches as viewed in background..."
msgstr ""
#: changedetectionio/blueprint/ui/__init__.py
#, python-brace-format
msgid "The watch by UUID {} does not exist."
@@ -1523,6 +1623,10 @@ msgstr ""
msgid "Download latest HTML snapshot"
msgstr ""
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "Download watch data package"
msgstr ""
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "Delete Watch?"
msgstr ""
@@ -1780,6 +1884,66 @@ msgstr ""
msgid "Not yet"
msgstr ""
#: changedetectionio/flask_app.py
msgid "0 seconds"
msgstr ""
#: changedetectionio/flask_app.py
msgid "year"
msgstr ""
#: changedetectionio/flask_app.py
msgid "years"
msgstr ""
#: changedetectionio/flask_app.py
msgid "month"
msgstr ""
#: changedetectionio/flask_app.py
msgid "months"
msgstr ""
#: changedetectionio/flask_app.py
msgid "week"
msgstr ""
#: changedetectionio/flask_app.py
msgid "weeks"
msgstr ""
#: changedetectionio/flask_app.py
msgid "day"
msgstr ""
#: changedetectionio/flask_app.py
msgid "days"
msgstr ""
#: changedetectionio/flask_app.py
msgid "hour"
msgstr ""
#: changedetectionio/flask_app.py
msgid "hours"
msgstr ""
#: changedetectionio/flask_app.py
msgid "minute"
msgstr ""
#: changedetectionio/flask_app.py
msgid "minutes"
msgstr ""
#: changedetectionio/flask_app.py
msgid "second"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings.html changedetectionio/flask_app.py
msgid "seconds"
msgstr ""
#: changedetectionio/flask_app.py
msgid "Already logged in"
msgstr ""
@@ -2874,6 +3038,18 @@ msgstr ""
msgid "Note: Wrap in forward slash / to use regex example:"
msgstr ""
#: changedetectionio/templates/edit/text-options.html
msgid "You can also use"
msgstr ""
#: changedetectionio/templates/edit/text-options.html
msgid "conditions"
msgstr ""
#: changedetectionio/templates/edit/text-options.html
msgid "\"Page text\" - with Contains, Starts With, Not Contains and many more"
msgstr ""
#: changedetectionio/templates/edit/text-options.html
msgid "Matching text will be ignored in the text snapshot (you can still see it but it wont trigger a change)"
msgstr ""
@@ -3036,3 +3212,6 @@ msgstr ""
#~ msgid "Tip: You can also add 'shared' watches."
#~ msgstr ""
#~ msgid "Marking watches as viewed in background..."
#~ msgstr ""

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: https://github.com/dgtlmoon/changedetection.io\n"
"POT-Creation-Date: 2026-02-05 17:47+0100\n"
"POT-Creation-Date: 2026-02-23 03:54+0100\n"
"PO-Revision-Date: 2026-01-12 16:37+0100\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: en_US\n"
@@ -16,7 +16,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.17.0\n"
"Generated-By: Babel 2.16.0\n"
#: changedetectionio/blueprint/backups/__init__.py
msgid "A backup is already running, check back in a few minutes"
@@ -34,34 +34,116 @@ msgstr ""
msgid "Backups were deleted."
msgstr ""
#: changedetectionio/blueprint/backups/templates/overview.html changedetectionio/blueprint/settings/templates/settings.html
msgid "Backups"
#: changedetectionio/blueprint/backups/restore.py
msgid "Backup zip file"
msgstr ""
#: changedetectionio/blueprint/backups/templates/overview.html
#: changedetectionio/blueprint/backups/restore.py
msgid "Must be a .zip backup file!"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "Include groups"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "Replace existing groups of the same UUID"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "Include watches"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "Replace existing watches of the same UUID"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Restore backup"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "A restore is already running, check back in a few minutes"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "No file uploaded"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "File must be a .zip backup file"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "Invalid or corrupted zip file"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "Restore started in background, check back in a few minutes."
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_create.html
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Create"
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_create.html
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Restore"
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_create.html
msgid "A backup is running!"
msgstr ""
#: changedetectionio/blueprint/backups/templates/overview.html
#: changedetectionio/blueprint/backups/templates/backup_create.html
msgid "Here you can download and request a new backup, when a backup is completed you will see it listed below."
msgstr ""
#: changedetectionio/blueprint/backups/templates/overview.html
#: changedetectionio/blueprint/backups/templates/backup_create.html
msgid "Mb"
msgstr ""
#: changedetectionio/blueprint/backups/templates/overview.html
#: changedetectionio/blueprint/backups/templates/backup_create.html
msgid "No backups found."
msgstr ""
#: changedetectionio/blueprint/backups/templates/overview.html
#: changedetectionio/blueprint/backups/templates/backup_create.html
msgid "Create backup"
msgstr ""
#: changedetectionio/blueprint/backups/templates/overview.html
#: changedetectionio/blueprint/backups/templates/backup_create.html
msgid "Remove backups"
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "A restore is running!"
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Restore a backup. Must be a .zip backup file created on/after v0.53.1 (new database layout)."
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Note: This does not override the main application settings, only watches and groups."
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Include all groups found in backup?"
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Replace any existing groups of the same UUID?"
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Include all watches found in backup?"
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Replace any existing watches of the same UUID?"
msgstr ""
#: changedetectionio/blueprint/imports/importer.py
msgid "Importing 5,000 of the first URLs from your list, the rest can be imported again."
msgstr ""
@@ -120,6 +202,14 @@ msgstr ""
msgid ".XLSX & Wachete"
msgstr ""
#: changedetectionio/blueprint/imports/templates/import.html
msgid "Restoring changedetection.io backups is in the"
msgstr ""
#: changedetectionio/blueprint/imports/templates/import.html
msgid "backups section"
msgstr ""
#: changedetectionio/blueprint/imports/templates/import.html
msgid "Enter one URL per line, and optionally add tags for each URL after a space, delineated by comma (,):"
msgstr ""
@@ -204,6 +294,16 @@ msgstr ""
msgid "Import"
msgstr ""
#: changedetectionio/blueprint/rss/single_watch.py
#, python-format
msgid "Watch with UUID %(uuid)s not found"
msgstr ""
#: changedetectionio/blueprint/rss/single_watch.py
#, python-format
msgid "Watch %(uuid)s does not have enough history snapshots to show changes (need at least 2)"
msgstr ""
#: changedetectionio/blueprint/settings/__init__.py
msgid "Password protection removed."
msgstr ""
@@ -289,6 +389,10 @@ msgstr ""
msgid "RSS"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Backups"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Time & Date"
msgstr ""
@@ -305,10 +409,6 @@ msgstr ""
msgid "Default recheck time for all watches, current system minimum is"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "seconds"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "more info"
msgstr ""
@@ -662,6 +762,10 @@ msgid ""
"whitelist the IP access instead"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Uptime:"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Python version:"
msgstr ""
@@ -915,10 +1019,6 @@ msgstr ""
msgid "Incorrect confirmation text."
msgstr ""
#: changedetectionio/blueprint/ui/__init__.py
msgid "Marking watches as viewed in background..."
msgstr ""
#: changedetectionio/blueprint/ui/__init__.py
#, python-brace-format
msgid "The watch by UUID {} does not exist."
@@ -1523,6 +1623,10 @@ msgstr ""
msgid "Download latest HTML snapshot"
msgstr ""
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "Download watch data package"
msgstr ""
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "Delete Watch?"
msgstr ""
@@ -1780,6 +1884,66 @@ msgstr ""
msgid "Not yet"
msgstr ""
#: changedetectionio/flask_app.py
msgid "0 seconds"
msgstr ""
#: changedetectionio/flask_app.py
msgid "year"
msgstr ""
#: changedetectionio/flask_app.py
msgid "years"
msgstr ""
#: changedetectionio/flask_app.py
msgid "month"
msgstr ""
#: changedetectionio/flask_app.py
msgid "months"
msgstr ""
#: changedetectionio/flask_app.py
msgid "week"
msgstr ""
#: changedetectionio/flask_app.py
msgid "weeks"
msgstr ""
#: changedetectionio/flask_app.py
msgid "day"
msgstr ""
#: changedetectionio/flask_app.py
msgid "days"
msgstr ""
#: changedetectionio/flask_app.py
msgid "hour"
msgstr ""
#: changedetectionio/flask_app.py
msgid "hours"
msgstr ""
#: changedetectionio/flask_app.py
msgid "minute"
msgstr ""
#: changedetectionio/flask_app.py
msgid "minutes"
msgstr ""
#: changedetectionio/flask_app.py
msgid "second"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings.html changedetectionio/flask_app.py
msgid "seconds"
msgstr ""
#: changedetectionio/flask_app.py
msgid "Already logged in"
msgstr ""
@@ -2874,6 +3038,18 @@ msgstr ""
msgid "Note: Wrap in forward slash / to use regex example:"
msgstr ""
#: changedetectionio/templates/edit/text-options.html
msgid "You can also use"
msgstr ""
#: changedetectionio/templates/edit/text-options.html
msgid "conditions"
msgstr ""
#: changedetectionio/templates/edit/text-options.html
msgid "\"Page text\" - with Contains, Starts With, Not Contains and many more"
msgstr ""
#: changedetectionio/templates/edit/text-options.html
msgid "Matching text will be ignored in the text snapshot (you can still see it but it wont trigger a change)"
msgstr ""
@@ -3036,3 +3212,6 @@ msgstr ""
#~ msgid "Tip: You can also add 'shared' watches."
#~ msgstr ""
#~ msgid "Marking watches as viewed in background..."
#~ msgstr ""

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2026-02-05 17:47+0100\n"
"POT-Creation-Date: 2026-02-23 03:54+0100\n"
"PO-Revision-Date: 2026-01-02 11:40+0100\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: fr\n"
@@ -16,7 +16,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.17.0\n"
"Generated-By: Babel 2.16.0\n"
#: changedetectionio/blueprint/backups/__init__.py
msgid "A backup is already running, check back in a few minutes"
@@ -34,34 +34,116 @@ msgstr "Sauvegarde en cours de création en arrière-plan, revenez dans quelques
msgid "Backups were deleted."
msgstr "Les sauvegardes ont été supprimées."
#: changedetectionio/blueprint/backups/templates/overview.html changedetectionio/blueprint/settings/templates/settings.html
msgid "Backups"
msgstr "SAUVEGARDES"
#: changedetectionio/blueprint/backups/restore.py
msgid "Backup zip file"
msgstr ""
#: changedetectionio/blueprint/backups/templates/overview.html
#: changedetectionio/blueprint/backups/restore.py
msgid "Must be a .zip backup file!"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "Include groups"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "Replace existing groups of the same UUID"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "Include watches"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "Replace existing watches of the same UUID"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Restore backup"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "A restore is already running, check back in a few minutes"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "No file uploaded"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "File must be a .zip backup file"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "Invalid or corrupted zip file"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "Restore started in background, check back in a few minutes."
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_create.html
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Create"
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_create.html
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Restore"
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_create.html
msgid "A backup is running!"
msgstr "Une sauvegarde est en cours !"
#: changedetectionio/blueprint/backups/templates/overview.html
#: changedetectionio/blueprint/backups/templates/backup_create.html
msgid "Here you can download and request a new backup, when a backup is completed you will see it listed below."
msgstr ""
#: changedetectionio/blueprint/backups/templates/overview.html
#: changedetectionio/blueprint/backups/templates/backup_create.html
msgid "Mb"
msgstr "Mo"
#: changedetectionio/blueprint/backups/templates/overview.html
#: changedetectionio/blueprint/backups/templates/backup_create.html
msgid "No backups found."
msgstr "Aucune sauvegarde trouvée."
#: changedetectionio/blueprint/backups/templates/overview.html
#: changedetectionio/blueprint/backups/templates/backup_create.html
msgid "Create backup"
msgstr "Créer sauvegarde"
#: changedetectionio/blueprint/backups/templates/overview.html
#: changedetectionio/blueprint/backups/templates/backup_create.html
msgid "Remove backups"
msgstr "Supprimer sauvegardes"
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "A restore is running!"
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Restore a backup. Must be a .zip backup file created on/after v0.53.1 (new database layout)."
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Note: This does not override the main application settings, only watches and groups."
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Include all groups found in backup?"
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Replace any existing groups of the same UUID?"
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Include all watches found in backup?"
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Replace any existing watches of the same UUID?"
msgstr ""
#: changedetectionio/blueprint/imports/importer.py
msgid "Importing 5,000 of the first URLs from your list, the rest can be imported again."
msgstr "Importation de 5 000 des premières URL de votre liste, le reste peut être importé à nouveau."
@@ -122,6 +204,14 @@ msgstr "Distill.io"
msgid ".XLSX & Wachete"
msgstr ".XLSX et Wachete"
#: changedetectionio/blueprint/imports/templates/import.html
msgid "Restoring changedetection.io backups is in the"
msgstr ""
#: changedetectionio/blueprint/imports/templates/import.html
msgid "backups section"
msgstr ""
#: changedetectionio/blueprint/imports/templates/import.html
msgid "Enter one URL per line, and optionally add tags for each URL after a space, delineated by comma (,):"
msgstr ""
@@ -206,6 +296,16 @@ msgstr "Temps de revérification (minutes)"
msgid "Import"
msgstr "IMPORTER"
#: changedetectionio/blueprint/rss/single_watch.py
#, python-format
msgid "Watch with UUID %(uuid)s not found"
msgstr ""
#: changedetectionio/blueprint/rss/single_watch.py
#, python-format
msgid "Watch %(uuid)s does not have enough history snapshots to show changes (need at least 2)"
msgstr ""
#: changedetectionio/blueprint/settings/__init__.py
msgid "Password protection removed."
msgstr ""
@@ -291,6 +391,10 @@ msgstr "API"
msgid "RSS"
msgstr "RSS"
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Backups"
msgstr "SAUVEGARDES"
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Time & Date"
msgstr "Heure et date"
@@ -307,10 +411,6 @@ msgstr "Info"
msgid "Default recheck time for all watches, current system minimum is"
msgstr "Heure de revérification par défaut pour tous les moniteurs, le minimum actuel du système est"
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "seconds"
msgstr "secondes"
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "more info"
msgstr "Plus d'informations"
@@ -666,6 +766,10 @@ msgid ""
"whitelist the IP access instead"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Uptime:"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Python version:"
msgstr "Version Python :"
@@ -919,10 +1023,6 @@ msgstr ""
msgid "Incorrect confirmation text."
msgstr "Aucune information"
#: changedetectionio/blueprint/ui/__init__.py
msgid "Marking watches as viewed in background..."
msgstr ""
#: changedetectionio/blueprint/ui/__init__.py
#, python-brace-format
msgid "The watch by UUID {} does not exist."
@@ -1529,6 +1629,10 @@ msgstr "Réponse du type de serveur"
msgid "Download latest HTML snapshot"
msgstr "Télécharger le dernier instantané HTML"
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "Download watch data package"
msgstr ""
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "Delete Watch?"
msgstr "Supprimer les montres ?"
@@ -1786,6 +1890,66 @@ msgstr "dans '%(title)s'"
msgid "Not yet"
msgstr "Pas encore"
#: changedetectionio/flask_app.py
msgid "0 seconds"
msgstr ""
#: changedetectionio/flask_app.py
msgid "year"
msgstr ""
#: changedetectionio/flask_app.py
msgid "years"
msgstr ""
#: changedetectionio/flask_app.py
msgid "month"
msgstr ""
#: changedetectionio/flask_app.py
msgid "months"
msgstr ""
#: changedetectionio/flask_app.py
msgid "week"
msgstr ""
#: changedetectionio/flask_app.py
msgid "weeks"
msgstr ""
#: changedetectionio/flask_app.py
msgid "day"
msgstr ""
#: changedetectionio/flask_app.py
msgid "days"
msgstr ""
#: changedetectionio/flask_app.py
msgid "hour"
msgstr ""
#: changedetectionio/flask_app.py
msgid "hours"
msgstr ""
#: changedetectionio/flask_app.py
msgid "minute"
msgstr ""
#: changedetectionio/flask_app.py
msgid "minutes"
msgstr ""
#: changedetectionio/flask_app.py
msgid "second"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings.html changedetectionio/flask_app.py
msgid "seconds"
msgstr "secondes"
#: changedetectionio/flask_app.py
msgid "Already logged in"
msgstr "Déjà connecté"
@@ -2886,6 +3050,18 @@ msgstr ""
msgid "Note: Wrap in forward slash / to use regex example:"
msgstr ""
#: changedetectionio/templates/edit/text-options.html
msgid "You can also use"
msgstr ""
#: changedetectionio/templates/edit/text-options.html
msgid "conditions"
msgstr ""
#: changedetectionio/templates/edit/text-options.html
msgid "\"Page text\" - with Contains, Starts With, Not Contains and many more"
msgstr ""
#: changedetectionio/templates/edit/text-options.html
msgid "Matching text will be ignored in the text snapshot (you can still see it but it wont trigger a change)"
msgstr ""
@@ -3099,3 +3275,6 @@ msgstr "Paramètres principaux"
#~ msgid "Tip: You can also add 'shared' watches."
#~ msgstr "Astuce : Vous pouvez également ajouter des montres « partagées »."
#~ msgid "Marking watches as viewed in background..."
#~ msgstr ""

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2026-02-05 17:47+0100\n"
"POT-Creation-Date: 2026-02-23 03:54+0100\n"
"PO-Revision-Date: 2026-01-02 15:32+0100\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: it\n"
@@ -16,7 +16,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.17.0\n"
"Generated-By: Babel 2.16.0\n"
#: changedetectionio/blueprint/backups/__init__.py
msgid "A backup is already running, check back in a few minutes"
@@ -34,34 +34,116 @@ msgstr "Backup in creazione in background, riprova tra qualche minuto."
msgid "Backups were deleted."
msgstr "I backup sono stati eliminati."
#: changedetectionio/blueprint/backups/templates/overview.html changedetectionio/blueprint/settings/templates/settings.html
msgid "Backups"
msgstr "Backup"
#: changedetectionio/blueprint/backups/restore.py
msgid "Backup zip file"
msgstr ""
#: changedetectionio/blueprint/backups/templates/overview.html
#: changedetectionio/blueprint/backups/restore.py
msgid "Must be a .zip backup file!"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "Include groups"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "Replace existing groups of the same UUID"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "Include watches"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "Replace existing watches of the same UUID"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Restore backup"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "A restore is already running, check back in a few minutes"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "No file uploaded"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "File must be a .zip backup file"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "Invalid or corrupted zip file"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "Restore started in background, check back in a few minutes."
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_create.html
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Create"
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_create.html
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Restore"
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_create.html
msgid "A backup is running!"
msgstr "Un backup è in esecuzione!"
#: changedetectionio/blueprint/backups/templates/overview.html
#: changedetectionio/blueprint/backups/templates/backup_create.html
msgid "Here you can download and request a new backup, when a backup is completed you will see it listed below."
msgstr ""
#: changedetectionio/blueprint/backups/templates/overview.html
#: changedetectionio/blueprint/backups/templates/backup_create.html
msgid "Mb"
msgstr "MB"
#: changedetectionio/blueprint/backups/templates/overview.html
#: changedetectionio/blueprint/backups/templates/backup_create.html
msgid "No backups found."
msgstr "Nessun backup trovato."
#: changedetectionio/blueprint/backups/templates/overview.html
#: changedetectionio/blueprint/backups/templates/backup_create.html
msgid "Create backup"
msgstr "Crea backup"
#: changedetectionio/blueprint/backups/templates/overview.html
#: changedetectionio/blueprint/backups/templates/backup_create.html
msgid "Remove backups"
msgstr "Rimuovi backup"
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "A restore is running!"
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Restore a backup. Must be a .zip backup file created on/after v0.53.1 (new database layout)."
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Note: This does not override the main application settings, only watches and groups."
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Include all groups found in backup?"
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Replace any existing groups of the same UUID?"
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Include all watches found in backup?"
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Replace any existing watches of the same UUID?"
msgstr ""
#: changedetectionio/blueprint/imports/importer.py
msgid "Importing 5,000 of the first URLs from your list, the rest can be imported again."
msgstr "Importazione delle prime 5.000 URL dalla tua lista, il resto può essere importato di nuovo."
@@ -122,6 +204,14 @@ msgstr "Distill.io"
msgid ".XLSX & Wachete"
msgstr ".XLSX & Wachete"
#: changedetectionio/blueprint/imports/templates/import.html
msgid "Restoring changedetection.io backups is in the"
msgstr ""
#: changedetectionio/blueprint/imports/templates/import.html
msgid "backups section"
msgstr ""
#: changedetectionio/blueprint/imports/templates/import.html
msgid "Enter one URL per line, and optionally add tags for each URL after a space, delineated by comma (,):"
msgstr ""
@@ -206,6 +296,16 @@ msgstr "Tempo di ricontrollo (minuti)"
msgid "Import"
msgstr "Importa"
#: changedetectionio/blueprint/rss/single_watch.py
#, python-format
msgid "Watch with UUID %(uuid)s not found"
msgstr ""
#: changedetectionio/blueprint/rss/single_watch.py
#, python-format
msgid "Watch %(uuid)s does not have enough history snapshots to show changes (need at least 2)"
msgstr ""
#: changedetectionio/blueprint/settings/__init__.py
msgid "Password protection removed."
msgstr ""
@@ -291,6 +391,10 @@ msgstr "API"
msgid "RSS"
msgstr "RSS"
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Backups"
msgstr "Backup"
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Time & Date"
msgstr "Data e ora"
@@ -307,10 +411,6 @@ msgstr "Info"
msgid "Default recheck time for all watches, current system minimum is"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "seconds"
msgstr "secondi"
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "more info"
msgstr ""
@@ -664,6 +764,10 @@ msgid ""
"whitelist the IP access instead"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Uptime:"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Python version:"
msgstr ""
@@ -917,10 +1021,6 @@ msgstr ""
msgid "Incorrect confirmation text."
msgstr ""
#: changedetectionio/blueprint/ui/__init__.py
msgid "Marking watches as viewed in background..."
msgstr ""
#: changedetectionio/blueprint/ui/__init__.py
#, python-brace-format
msgid "The watch by UUID {} does not exist."
@@ -1525,6 +1625,10 @@ msgstr ""
msgid "Download latest HTML snapshot"
msgstr ""
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "Download watch data package"
msgstr ""
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "Delete Watch?"
msgstr ""
@@ -1782,6 +1886,66 @@ msgstr ""
msgid "Not yet"
msgstr "Non ancora"
#: changedetectionio/flask_app.py
msgid "0 seconds"
msgstr ""
#: changedetectionio/flask_app.py
msgid "year"
msgstr ""
#: changedetectionio/flask_app.py
msgid "years"
msgstr ""
#: changedetectionio/flask_app.py
msgid "month"
msgstr ""
#: changedetectionio/flask_app.py
msgid "months"
msgstr ""
#: changedetectionio/flask_app.py
msgid "week"
msgstr ""
#: changedetectionio/flask_app.py
msgid "weeks"
msgstr ""
#: changedetectionio/flask_app.py
msgid "day"
msgstr ""
#: changedetectionio/flask_app.py
msgid "days"
msgstr ""
#: changedetectionio/flask_app.py
msgid "hour"
msgstr ""
#: changedetectionio/flask_app.py
msgid "hours"
msgstr ""
#: changedetectionio/flask_app.py
msgid "minute"
msgstr ""
#: changedetectionio/flask_app.py
msgid "minutes"
msgstr ""
#: changedetectionio/flask_app.py
msgid "second"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings.html changedetectionio/flask_app.py
msgid "seconds"
msgstr "secondi"
#: changedetectionio/flask_app.py
msgid "Already logged in"
msgstr "Già autenticato"
@@ -2876,6 +3040,18 @@ msgstr ""
msgid "Note: Wrap in forward slash / to use regex example:"
msgstr ""
#: changedetectionio/templates/edit/text-options.html
msgid "You can also use"
msgstr ""
#: changedetectionio/templates/edit/text-options.html
msgid "conditions"
msgstr ""
#: changedetectionio/templates/edit/text-options.html
msgid "\"Page text\" - with Contains, Starts With, Not Contains and many more"
msgstr ""
#: changedetectionio/templates/edit/text-options.html
msgid "Matching text will be ignored in the text snapshot (you can still see it but it wont trigger a change)"
msgstr ""
@@ -3071,3 +3247,6 @@ msgstr "Impostazioni principali"
#~ msgid "Tip: You can also add 'shared' watches."
#~ msgstr ""
#~ msgid "Marking watches as viewed in background..."
#~ msgstr ""

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2026-02-05 17:47+0100\n"
"POT-Creation-Date: 2026-02-23 03:54+0100\n"
"PO-Revision-Date: 2026-01-02 11:40+0100\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: ko\n"
@@ -16,7 +16,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.17.0\n"
"Generated-By: Babel 2.16.0\n"
#: changedetectionio/blueprint/backups/__init__.py
msgid "A backup is already running, check back in a few minutes"
@@ -34,34 +34,116 @@ msgstr ""
msgid "Backups were deleted."
msgstr ""
#: changedetectionio/blueprint/backups/templates/overview.html changedetectionio/blueprint/settings/templates/settings.html
msgid "Backups"
msgstr "백업"
#: changedetectionio/blueprint/backups/restore.py
msgid "Backup zip file"
msgstr ""
#: changedetectionio/blueprint/backups/templates/overview.html
#: changedetectionio/blueprint/backups/restore.py
msgid "Must be a .zip backup file!"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "Include groups"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "Replace existing groups of the same UUID"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "Include watches"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "Replace existing watches of the same UUID"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Restore backup"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "A restore is already running, check back in a few minutes"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "No file uploaded"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "File must be a .zip backup file"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "Invalid or corrupted zip file"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "Restore started in background, check back in a few minutes."
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_create.html
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Create"
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_create.html
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Restore"
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_create.html
msgid "A backup is running!"
msgstr "백업이 실행 중입니다!"
#: changedetectionio/blueprint/backups/templates/overview.html
#: changedetectionio/blueprint/backups/templates/backup_create.html
msgid "Here you can download and request a new backup, when a backup is completed you will see it listed below."
msgstr ""
#: changedetectionio/blueprint/backups/templates/overview.html
#: changedetectionio/blueprint/backups/templates/backup_create.html
msgid "Mb"
msgstr "MB"
#: changedetectionio/blueprint/backups/templates/overview.html
#: changedetectionio/blueprint/backups/templates/backup_create.html
msgid "No backups found."
msgstr "백업을 찾을 수 없습니다."
#: changedetectionio/blueprint/backups/templates/overview.html
#: changedetectionio/blueprint/backups/templates/backup_create.html
msgid "Create backup"
msgstr "백업 생성"
#: changedetectionio/blueprint/backups/templates/overview.html
#: changedetectionio/blueprint/backups/templates/backup_create.html
msgid "Remove backups"
msgstr "백업 삭제"
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "A restore is running!"
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Restore a backup. Must be a .zip backup file created on/after v0.53.1 (new database layout)."
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Note: This does not override the main application settings, only watches and groups."
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Include all groups found in backup?"
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Replace any existing groups of the same UUID?"
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Include all watches found in backup?"
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Replace any existing watches of the same UUID?"
msgstr ""
#: changedetectionio/blueprint/imports/importer.py
msgid "Importing 5,000 of the first URLs from your list, the rest can be imported again."
msgstr ""
@@ -120,6 +202,14 @@ msgstr "Distill.io"
msgid ".XLSX & Wachete"
msgstr ".XLSX 및 와체테"
#: changedetectionio/blueprint/imports/templates/import.html
msgid "Restoring changedetection.io backups is in the"
msgstr ""
#: changedetectionio/blueprint/imports/templates/import.html
msgid "backups section"
msgstr ""
#: changedetectionio/blueprint/imports/templates/import.html
msgid "Enter one URL per line, and optionally add tags for each URL after a space, delineated by comma (,):"
msgstr ""
@@ -204,6 +294,16 @@ msgstr "재확인 시간(분)"
msgid "Import"
msgstr "수입"
#: changedetectionio/blueprint/rss/single_watch.py
#, python-format
msgid "Watch with UUID %(uuid)s not found"
msgstr ""
#: changedetectionio/blueprint/rss/single_watch.py
#, python-format
msgid "Watch %(uuid)s does not have enough history snapshots to show changes (need at least 2)"
msgstr ""
#: changedetectionio/blueprint/settings/__init__.py
msgid "Password protection removed."
msgstr ""
@@ -289,6 +389,10 @@ msgstr "API"
msgid "RSS"
msgstr "RSS"
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Backups"
msgstr "백업"
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Time & Date"
msgstr "시간 및 날짜"
@@ -305,10 +409,6 @@ msgstr "정보"
msgid "Default recheck time for all watches, current system minimum is"
msgstr "모든 시계의 기본 재확인 시간, 현재 시스템 최소값은 다음과 같습니다."
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "seconds"
msgstr "초"
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "more info"
msgstr "추가 정보"
@@ -662,6 +762,10 @@ msgid ""
"whitelist the IP access instead"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Uptime:"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Python version:"
msgstr "파이썬 버전:"
@@ -915,10 +1019,6 @@ msgstr ""
msgid "Incorrect confirmation text."
msgstr "잘못된 확인 텍스트."
#: changedetectionio/blueprint/ui/__init__.py
msgid "Marking watches as viewed in background..."
msgstr ""
#: changedetectionio/blueprint/ui/__init__.py
#, python-brace-format
msgid "The watch by UUID {} does not exist."
@@ -1523,6 +1623,10 @@ msgstr "서버 유형 응답"
msgid "Download latest HTML snapshot"
msgstr "최신 HTML 스냅샷 다운로드"
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "Download watch data package"
msgstr ""
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "Delete Watch?"
msgstr "시계를 삭제하시겠습니까?"
@@ -1780,6 +1884,66 @@ msgstr "'%(title)s'에서"
msgid "Not yet"
msgstr "아직 아님"
#: changedetectionio/flask_app.py
msgid "0 seconds"
msgstr ""
#: changedetectionio/flask_app.py
msgid "year"
msgstr ""
#: changedetectionio/flask_app.py
msgid "years"
msgstr ""
#: changedetectionio/flask_app.py
msgid "month"
msgstr ""
#: changedetectionio/flask_app.py
msgid "months"
msgstr ""
#: changedetectionio/flask_app.py
msgid "week"
msgstr ""
#: changedetectionio/flask_app.py
msgid "weeks"
msgstr ""
#: changedetectionio/flask_app.py
msgid "day"
msgstr ""
#: changedetectionio/flask_app.py
msgid "days"
msgstr ""
#: changedetectionio/flask_app.py
msgid "hour"
msgstr ""
#: changedetectionio/flask_app.py
msgid "hours"
msgstr ""
#: changedetectionio/flask_app.py
msgid "minute"
msgstr ""
#: changedetectionio/flask_app.py
msgid "minutes"
msgstr ""
#: changedetectionio/flask_app.py
msgid "second"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings.html changedetectionio/flask_app.py
msgid "seconds"
msgstr "초"
#: changedetectionio/flask_app.py
msgid "Already logged in"
msgstr ""
@@ -2874,6 +3038,18 @@ msgstr ""
msgid "Note: Wrap in forward slash / to use regex example:"
msgstr ""
#: changedetectionio/templates/edit/text-options.html
msgid "You can also use"
msgstr ""
#: changedetectionio/templates/edit/text-options.html
msgid "conditions"
msgstr ""
#: changedetectionio/templates/edit/text-options.html
msgid "\"Page text\" - with Contains, Starts With, Not Contains and many more"
msgstr ""
#: changedetectionio/templates/edit/text-options.html
msgid "Matching text will be ignored in the text snapshot (you can still see it but it wont trigger a change)"
msgstr ""
@@ -3192,3 +3368,6 @@ msgstr "기본 설정"
#~ msgid "Tip: You can also add 'shared' watches."
#~ msgstr "팁: '공유' 시계를 추가할 수도 있습니다."
#~ msgid "Marking watches as viewed in background..."
#~ msgstr ""

View File

@@ -6,16 +6,16 @@
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: changedetection.io 0.52.9\n"
"Project-Id-Version: changedetection.io 0.53.6\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2026-02-05 17:47+0100\n"
"POT-Creation-Date: 2026-02-23 03:54+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.17.0\n"
"Generated-By: Babel 2.16.0\n"
#: changedetectionio/blueprint/backups/__init__.py
msgid "A backup is already running, check back in a few minutes"
@@ -33,40 +33,121 @@ msgstr ""
msgid "Backups were deleted."
msgstr ""
#: changedetectionio/blueprint/backups/templates/overview.html changedetectionio/blueprint/settings/templates/settings.html
msgid "Backups"
#: changedetectionio/blueprint/backups/restore.py
msgid "Backup zip file"
msgstr ""
#: changedetectionio/blueprint/backups/templates/overview.html
#: changedetectionio/blueprint/backups/restore.py
msgid "Must be a .zip backup file!"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "Include groups"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "Replace existing groups of the same UUID"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "Include watches"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "Replace existing watches of the same UUID"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Restore backup"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "A restore is already running, check back in a few minutes"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "No file uploaded"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "File must be a .zip backup file"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "Invalid or corrupted zip file"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "Restore started in background, check back in a few minutes."
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_create.html
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Create"
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_create.html
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Restore"
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_create.html
msgid "A backup is running!"
msgstr ""
#: changedetectionio/blueprint/backups/templates/overview.html
#: changedetectionio/blueprint/backups/templates/backup_create.html
msgid "Here you can download and request a new backup, when a backup is completed you will see it listed below."
msgstr ""
#: changedetectionio/blueprint/backups/templates/overview.html
#: changedetectionio/blueprint/backups/templates/backup_create.html
msgid "Mb"
msgstr ""
#: changedetectionio/blueprint/backups/templates/overview.html
#: changedetectionio/blueprint/backups/templates/backup_create.html
msgid "No backups found."
msgstr ""
#: changedetectionio/blueprint/backups/templates/overview.html
#: changedetectionio/blueprint/backups/templates/backup_create.html
msgid "Create backup"
msgstr ""
#: changedetectionio/blueprint/backups/templates/overview.html
#: changedetectionio/blueprint/backups/templates/backup_create.html
msgid "Remove backups"
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "A restore is running!"
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Restore a backup. Must be a .zip backup file created on/after v0.53.1 (new database layout)."
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Note: This does not override the main application settings, only watches and groups."
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Include all groups found in backup?"
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Replace any existing groups of the same UUID?"
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Include all watches found in backup?"
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Replace any existing watches of the same UUID?"
msgstr ""
#: changedetectionio/blueprint/imports/importer.py
msgid "Importing 5,000 of the first URLs from your list, the rest can be imported again."
msgstr ""
#: changedetectionio/blueprint/imports/importer.py
#, python-brace-format
msgid "{} Imported from list in {:.2f}s, {} Skipped."
msgstr ""
@@ -79,7 +160,6 @@ msgid "JSON structure looks invalid, was it broken?"
msgstr ""
#: changedetectionio/blueprint/imports/importer.py
#, python-brace-format
msgid "{} Imported from Distill.io in {:.2f}s, {} Skipped."
msgstr ""
@@ -88,22 +168,18 @@ msgid "Unable to read export XLSX file, something wrong with the file?"
msgstr ""
#: changedetectionio/blueprint/imports/importer.py
#, python-brace-format
msgid "Error processing row number {}, URL value was incorrect, row was skipped."
msgstr ""
#: changedetectionio/blueprint/imports/importer.py
#, python-brace-format
msgid "Error processing row number {}, check all cell data types are correct, row was skipped."
msgstr ""
#: changedetectionio/blueprint/imports/importer.py
#, python-brace-format
msgid "{} imported from Wachete .xlsx in {:.2f}s"
msgstr ""
#: changedetectionio/blueprint/imports/importer.py
#, python-brace-format
msgid "{} imported from custom .xlsx in {:.2f}s"
msgstr ""
@@ -119,6 +195,14 @@ msgstr ""
msgid ".XLSX & Wachete"
msgstr ""
#: changedetectionio/blueprint/imports/templates/import.html
msgid "Restoring changedetection.io backups is in the"
msgstr ""
#: changedetectionio/blueprint/imports/templates/import.html
msgid "backups section"
msgstr ""
#: changedetectionio/blueprint/imports/templates/import.html
msgid "Enter one URL per line, and optionally add tags for each URL after a space, delineated by comma (,):"
msgstr ""
@@ -203,17 +287,25 @@ msgstr ""
msgid "Import"
msgstr ""
#: changedetectionio/blueprint/rss/single_watch.py
#, python-format
msgid "Watch with UUID %(uuid)s not found"
msgstr ""
#: changedetectionio/blueprint/rss/single_watch.py
#, python-format
msgid "Watch %(uuid)s does not have enough history snapshots to show changes (need at least 2)"
msgstr ""
#: changedetectionio/blueprint/settings/__init__.py
msgid "Password protection removed."
msgstr ""
#: changedetectionio/blueprint/settings/__init__.py
#, python-brace-format
msgid "Warning: Worker count ({}) is close to or exceeds available CPU cores ({})"
msgstr ""
#: changedetectionio/blueprint/settings/__init__.py
#, python-brace-format
msgid "Worker count adjusted: {}"
msgstr ""
@@ -222,7 +314,6 @@ msgid "Dynamic worker adjustment not supported for sync workers"
msgstr ""
#: changedetectionio/blueprint/settings/__init__.py
#, python-brace-format
msgid "Error adjusting workers: {}"
msgstr ""
@@ -288,6 +379,10 @@ msgstr ""
msgid "RSS"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Backups"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Time & Date"
msgstr ""
@@ -304,10 +399,6 @@ msgstr ""
msgid "Default recheck time for all watches, current system minimum is"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "seconds"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "more info"
msgstr ""
@@ -661,6 +752,10 @@ msgid ""
"whitelist the IP access instead"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Uptime:"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Python version:"
msgstr ""
@@ -682,7 +777,6 @@ msgid "Clear Snapshot History"
msgstr ""
#: changedetectionio/blueprint/tags/__init__.py
#, python-brace-format
msgid "The tag \"{}\" already exists"
msgstr ""
@@ -843,57 +937,46 @@ msgid "RSS Feed for this watch"
msgstr ""
#: changedetectionio/blueprint/ui/__init__.py
#, python-brace-format
msgid "{} watches deleted"
msgstr ""
#: changedetectionio/blueprint/ui/__init__.py
#, python-brace-format
msgid "{} watches paused"
msgstr ""
#: changedetectionio/blueprint/ui/__init__.py
#, python-brace-format
msgid "{} watches unpaused"
msgstr ""
#: changedetectionio/blueprint/ui/__init__.py
#, python-brace-format
msgid "{} watches updated"
msgstr ""
#: changedetectionio/blueprint/ui/__init__.py
#, python-brace-format
msgid "{} watches muted"
msgstr ""
#: changedetectionio/blueprint/ui/__init__.py
#, python-brace-format
msgid "{} watches un-muted"
msgstr ""
#: changedetectionio/blueprint/ui/__init__.py
#, python-brace-format
msgid "{} watches queued for rechecking"
msgstr ""
#: changedetectionio/blueprint/ui/__init__.py
#, python-brace-format
msgid "{} watches errors cleared"
msgstr ""
#: changedetectionio/blueprint/ui/__init__.py
#, python-brace-format
msgid "{} watches cleared/reset."
msgstr ""
#: changedetectionio/blueprint/ui/__init__.py
#, python-brace-format
msgid "{} watches set to use default notification settings"
msgstr ""
#: changedetectionio/blueprint/ui/__init__.py
#, python-brace-format
msgid "{} watches were tagged"
msgstr ""
@@ -902,7 +985,6 @@ msgid "Watch not found"
msgstr ""
#: changedetectionio/blueprint/ui/__init__.py
#, python-brace-format
msgid "Cleared snapshot history for watch {}"
msgstr ""
@@ -915,11 +997,6 @@ msgid "Incorrect confirmation text."
msgstr ""
#: changedetectionio/blueprint/ui/__init__.py
msgid "Marking watches as viewed in background..."
msgstr ""
#: changedetectionio/blueprint/ui/__init__.py
#, python-brace-format
msgid "The watch by UUID {} does not exist."
msgstr ""
@@ -940,12 +1017,10 @@ msgid "Queued 1 watch for rechecking."
msgstr ""
#: changedetectionio/blueprint/ui/__init__.py
#, python-brace-format
msgid "Queued {} watches for rechecking ({} already queued or running)."
msgstr ""
#: changedetectionio/blueprint/ui/__init__.py
#, python-brace-format
msgid "Queued {} watches for rechecking."
msgstr ""
@@ -954,7 +1029,6 @@ msgid "Queueing watches for rechecking in background..."
msgstr ""
#: changedetectionio/blueprint/ui/__init__.py
#, python-brace-format
msgid "Could not share, something went wrong while communicating with the share server - {}"
msgstr ""
@@ -975,22 +1049,18 @@ msgid "No watches to edit"
msgstr ""
#: changedetectionio/blueprint/ui/edit.py
#, python-brace-format
msgid "No watch with the UUID {} found."
msgstr ""
#: changedetectionio/blueprint/ui/edit.py
#, python-brace-format
msgid "Switched to mode - {}."
msgstr ""
#: changedetectionio/blueprint/ui/edit.py
#, python-brace-format
msgid "Could not load '{}' processor, processor plugin might be missing. Please select a different processor."
msgstr ""
#: changedetectionio/blueprint/ui/edit.py
#, python-brace-format
msgid "Could not load '{}' processor, processor plugin might be missing."
msgstr ""
@@ -1522,6 +1592,10 @@ msgstr ""
msgid "Download latest HTML snapshot"
msgstr ""
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "Download watch data package"
msgstr ""
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "Delete Watch?"
msgstr ""
@@ -1571,7 +1645,6 @@ msgid "Screenshot requires a Content Fetcher ( Sockpuppetbrowser, selenium, etc
msgstr ""
#: changedetectionio/blueprint/ui/views.py
#, python-brace-format
msgid "Warning, URL {} already exists"
msgstr ""
@@ -1584,7 +1657,6 @@ msgid "Watch added."
msgstr ""
#: changedetectionio/blueprint/watchlist/__init__.py
#, python-brace-format
msgid "displaying <b>{start} - {end}</b> {record_name} in total <b>{total}</b>"
msgstr ""
@@ -1779,6 +1851,66 @@ msgstr ""
msgid "Not yet"
msgstr ""
#: changedetectionio/flask_app.py
msgid "0 seconds"
msgstr ""
#: changedetectionio/flask_app.py
msgid "year"
msgstr ""
#: changedetectionio/flask_app.py
msgid "years"
msgstr ""
#: changedetectionio/flask_app.py
msgid "month"
msgstr ""
#: changedetectionio/flask_app.py
msgid "months"
msgstr ""
#: changedetectionio/flask_app.py
msgid "week"
msgstr ""
#: changedetectionio/flask_app.py
msgid "weeks"
msgstr ""
#: changedetectionio/flask_app.py
msgid "day"
msgstr ""
#: changedetectionio/flask_app.py
msgid "days"
msgstr ""
#: changedetectionio/flask_app.py
msgid "hour"
msgstr ""
#: changedetectionio/flask_app.py
msgid "hours"
msgstr ""
#: changedetectionio/flask_app.py
msgid "minute"
msgstr ""
#: changedetectionio/flask_app.py
msgid "minutes"
msgstr ""
#: changedetectionio/flask_app.py
msgid "second"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings.html changedetectionio/flask_app.py
msgid "seconds"
msgstr ""
#: changedetectionio/flask_app.py
msgid "Already logged in"
msgstr ""
@@ -2325,12 +2457,10 @@ msgid "Not enough history to compare. Need at least 2 snapshots."
msgstr ""
#: changedetectionio/processors/image_ssim_diff/difference.py
#, python-brace-format
msgid "Failed to load screenshots: {}"
msgstr ""
#: changedetectionio/processors/image_ssim_diff/difference.py
#, python-brace-format
msgid "Failed to calculate diff: {}"
msgstr ""
@@ -2456,7 +2586,6 @@ msgid "Detects all text changes where possible"
msgstr ""
#: changedetectionio/store/__init__.py
#, python-brace-format
msgid "Error fetching metadata for {}"
msgstr ""
@@ -2465,7 +2594,6 @@ msgid "Watch protocol is not permitted or invalid URL format"
msgstr ""
#: changedetectionio/store/__init__.py
#, python-brace-format
msgid "Watch limit reached ({}/{} watches). Cannot add more watches."
msgstr ""
@@ -2873,6 +3001,18 @@ msgstr ""
msgid "Note: Wrap in forward slash / to use regex example:"
msgstr ""
#: changedetectionio/templates/edit/text-options.html
msgid "You can also use"
msgstr ""
#: changedetectionio/templates/edit/text-options.html
msgid "conditions"
msgstr ""
#: changedetectionio/templates/edit/text-options.html
msgid "\"Page text\" - with Contains, Starts With, Not Contains and many more"
msgstr ""
#: changedetectionio/templates/edit/text-options.html
msgid "Matching text will be ignored in the text snapshot (you can still see it but it wont trigger a change)"
msgstr ""

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2026-02-05 17:47+0100\n"
"POT-Creation-Date: 2026-02-23 03:54+0100\n"
"PO-Revision-Date: 2026-01-18 21:31+0800\n"
"Last-Translator: 吾爱分享 <admin@wuaishare.cn>\n"
"Language: zh\n"
@@ -16,7 +16,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.17.0\n"
"Generated-By: Babel 2.16.0\n"
#: changedetectionio/blueprint/backups/__init__.py
msgid "A backup is already running, check back in a few minutes"
@@ -34,34 +34,116 @@ msgstr "备份正在后台生成,请几分钟后再查看。"
msgid "Backups were deleted."
msgstr "备份已删除。"
#: changedetectionio/blueprint/backups/templates/overview.html changedetectionio/blueprint/settings/templates/settings.html
msgid "Backups"
msgstr "备份"
#: changedetectionio/blueprint/backups/restore.py
msgid "Backup zip file"
msgstr ""
#: changedetectionio/blueprint/backups/templates/overview.html
#: changedetectionio/blueprint/backups/restore.py
msgid "Must be a .zip backup file!"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "Include groups"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "Replace existing groups of the same UUID"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "Include watches"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "Replace existing watches of the same UUID"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Restore backup"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "A restore is already running, check back in a few minutes"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "No file uploaded"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "File must be a .zip backup file"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "Invalid or corrupted zip file"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "Restore started in background, check back in a few minutes."
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_create.html
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Create"
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_create.html
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Restore"
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_create.html
msgid "A backup is running!"
msgstr "备份正在运行!"
#: changedetectionio/blueprint/backups/templates/overview.html
#: changedetectionio/blueprint/backups/templates/backup_create.html
msgid "Here you can download and request a new backup, when a backup is completed you will see it listed below."
msgstr "在此可下载并请求新的备份,备份完成后会在下方列表中显示。"
#: changedetectionio/blueprint/backups/templates/overview.html
#: changedetectionio/blueprint/backups/templates/backup_create.html
msgid "Mb"
msgstr "MB"
#: changedetectionio/blueprint/backups/templates/overview.html
#: changedetectionio/blueprint/backups/templates/backup_create.html
msgid "No backups found."
msgstr "未找到备份。"
#: changedetectionio/blueprint/backups/templates/overview.html
#: changedetectionio/blueprint/backups/templates/backup_create.html
msgid "Create backup"
msgstr "创建备份"
#: changedetectionio/blueprint/backups/templates/overview.html
#: changedetectionio/blueprint/backups/templates/backup_create.html
msgid "Remove backups"
msgstr "删除备份"
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "A restore is running!"
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Restore a backup. Must be a .zip backup file created on/after v0.53.1 (new database layout)."
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Note: This does not override the main application settings, only watches and groups."
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Include all groups found in backup?"
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Replace any existing groups of the same UUID?"
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Include all watches found in backup?"
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Replace any existing watches of the same UUID?"
msgstr ""
#: changedetectionio/blueprint/imports/importer.py
msgid "Importing 5,000 of the first URLs from your list, the rest can be imported again."
msgstr "仅导入列表前 5,000 个 URL其余可稍后继续导入。"
@@ -120,6 +202,14 @@ msgstr "Distill.io"
msgid ".XLSX & Wachete"
msgstr ".XLSX 与 Wachete"
#: changedetectionio/blueprint/imports/templates/import.html
msgid "Restoring changedetection.io backups is in the"
msgstr ""
#: changedetectionio/blueprint/imports/templates/import.html
msgid "backups section"
msgstr ""
#: changedetectionio/blueprint/imports/templates/import.html
msgid "Enter one URL per line, and optionally add tags for each URL after a space, delineated by comma (,):"
msgstr "每行输入一个 URL可在 URL 后用空格追加标签,标签以逗号 (,) 分隔:"
@@ -204,6 +294,16 @@ msgstr "复检间隔(分钟)"
msgid "Import"
msgstr "导入"
#: changedetectionio/blueprint/rss/single_watch.py
#, python-format
msgid "Watch with UUID %(uuid)s not found"
msgstr ""
#: changedetectionio/blueprint/rss/single_watch.py
#, python-format
msgid "Watch %(uuid)s does not have enough history snapshots to show changes (need at least 2)"
msgstr ""
#: changedetectionio/blueprint/settings/__init__.py
msgid "Password protection removed."
msgstr "已移除密码保护。"
@@ -289,6 +389,10 @@ msgstr "API"
msgid "RSS"
msgstr "RSS"
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Backups"
msgstr "备份"
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Time & Date"
msgstr "时间与日期"
@@ -305,10 +409,6 @@ msgstr "信息"
msgid "Default recheck time for all watches, current system minimum is"
msgstr "所有监控项的默认复检间隔,当前系统最小值为"
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "seconds"
msgstr "秒"
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "more info"
msgstr "更多信息"
@@ -662,6 +762,10 @@ msgid ""
"whitelist the IP access instead"
msgstr "带认证的 SOCKS5 代理仅支持“明文请求”抓取器,其他抓取器请改为白名单 IP"
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Uptime:"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Python version:"
msgstr "Python 版本:"
@@ -915,10 +1019,6 @@ msgstr "历史清理已在后台开始"
msgid "Incorrect confirmation text."
msgstr "确认文本不正确。"
#: changedetectionio/blueprint/ui/__init__.py
msgid "Marking watches as viewed in background..."
msgstr "正在后台将监控项标记为已读..."
#: changedetectionio/blueprint/ui/__init__.py
#, python-brace-format
msgid "The watch by UUID {} does not exist."
@@ -1523,6 +1623,10 @@ msgstr "服务器类型响应"
msgid "Download latest HTML snapshot"
msgstr "下载最新的 HTML 快照"
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "Download watch data package"
msgstr ""
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "Delete Watch?"
msgstr "删除监控项?"
@@ -1780,6 +1884,66 @@ msgstr "(“%(title)s”中"
msgid "Not yet"
msgstr "尚未"
#: changedetectionio/flask_app.py
msgid "0 seconds"
msgstr ""
#: changedetectionio/flask_app.py
msgid "year"
msgstr ""
#: changedetectionio/flask_app.py
msgid "years"
msgstr ""
#: changedetectionio/flask_app.py
msgid "month"
msgstr ""
#: changedetectionio/flask_app.py
msgid "months"
msgstr ""
#: changedetectionio/flask_app.py
msgid "week"
msgstr ""
#: changedetectionio/flask_app.py
msgid "weeks"
msgstr ""
#: changedetectionio/flask_app.py
msgid "day"
msgstr ""
#: changedetectionio/flask_app.py
msgid "days"
msgstr ""
#: changedetectionio/flask_app.py
msgid "hour"
msgstr ""
#: changedetectionio/flask_app.py
msgid "hours"
msgstr ""
#: changedetectionio/flask_app.py
msgid "minute"
msgstr ""
#: changedetectionio/flask_app.py
msgid "minutes"
msgstr ""
#: changedetectionio/flask_app.py
msgid "second"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings.html changedetectionio/flask_app.py
msgid "seconds"
msgstr "秒"
#: changedetectionio/flask_app.py
msgid "Already logged in"
msgstr "已登录"
@@ -2874,6 +3038,18 @@ msgstr "每行单独处理(可理解为每行都是“或”)"
msgid "Note: Wrap in forward slash / to use regex example:"
msgstr "注意:使用正则时请用斜杠 / 包裹,例如:"
#: changedetectionio/templates/edit/text-options.html
msgid "You can also use"
msgstr ""
#: changedetectionio/templates/edit/text-options.html
msgid "conditions"
msgstr ""
#: changedetectionio/templates/edit/text-options.html
msgid "\"Page text\" - with Contains, Starts With, Not Contains and many more"
msgstr ""
#: changedetectionio/templates/edit/text-options.html
msgid "Matching text will be ignored in the text snapshot (you can still see it but it wont trigger a change)"
msgstr "匹配的文本会在文本快照中被忽略(仍可见但不会触发变更)"
@@ -3021,3 +3197,6 @@ msgstr "主设置"
#~ msgid "Tip: You can also add 'shared' watches."
#~ msgstr "提示:你也可以添加“共享”的监控项。"
#~ msgid "Marking watches as viewed in background..."
#~ msgstr "正在后台将监控项标记为已读..."

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2026-02-05 17:47+0100\n"
"POT-Creation-Date: 2026-02-23 03:54+0100\n"
"PO-Revision-Date: 2026-01-15 12:00+0800\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: zh_Hant_TW\n"
@@ -16,7 +16,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.17.0\n"
"Generated-By: Babel 2.16.0\n"
#: changedetectionio/blueprint/backups/__init__.py
msgid "A backup is already running, check back in a few minutes"
@@ -34,34 +34,116 @@ msgstr "正在背景建立備份,請稍後再回來查看。"
msgid "Backups were deleted."
msgstr "備份已刪除。"
#: changedetectionio/blueprint/backups/templates/overview.html changedetectionio/blueprint/settings/templates/settings.html
msgid "Backups"
msgstr "備份"
#: changedetectionio/blueprint/backups/restore.py
msgid "Backup zip file"
msgstr ""
#: changedetectionio/blueprint/backups/templates/overview.html
#: changedetectionio/blueprint/backups/restore.py
msgid "Must be a .zip backup file!"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "Include groups"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "Replace existing groups of the same UUID"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "Include watches"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "Replace existing watches of the same UUID"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Restore backup"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "A restore is already running, check back in a few minutes"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "No file uploaded"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "File must be a .zip backup file"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "Invalid or corrupted zip file"
msgstr ""
#: changedetectionio/blueprint/backups/restore.py
msgid "Restore started in background, check back in a few minutes."
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_create.html
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Create"
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_create.html
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Restore"
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_create.html
msgid "A backup is running!"
msgstr "備份正在執行中!"
#: changedetectionio/blueprint/backups/templates/overview.html
#: changedetectionio/blueprint/backups/templates/backup_create.html
msgid "Here you can download and request a new backup, when a backup is completed you will see it listed below."
msgstr "您可以在此下載並請求建立新備份,備份完成後將顯示於下方。"
#: changedetectionio/blueprint/backups/templates/overview.html
#: changedetectionio/blueprint/backups/templates/backup_create.html
msgid "Mb"
msgstr "MB"
#: changedetectionio/blueprint/backups/templates/overview.html
#: changedetectionio/blueprint/backups/templates/backup_create.html
msgid "No backups found."
msgstr "找不到備份。"
#: changedetectionio/blueprint/backups/templates/overview.html
#: changedetectionio/blueprint/backups/templates/backup_create.html
msgid "Create backup"
msgstr "建立備份"
#: changedetectionio/blueprint/backups/templates/overview.html
#: changedetectionio/blueprint/backups/templates/backup_create.html
msgid "Remove backups"
msgstr "移除備份"
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "A restore is running!"
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Restore a backup. Must be a .zip backup file created on/after v0.53.1 (new database layout)."
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Note: This does not override the main application settings, only watches and groups."
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Include all groups found in backup?"
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Replace any existing groups of the same UUID?"
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Include all watches found in backup?"
msgstr ""
#: changedetectionio/blueprint/backups/templates/backup_restore.html
msgid "Replace any existing watches of the same UUID?"
msgstr ""
#: changedetectionio/blueprint/imports/importer.py
msgid "Importing 5,000 of the first URLs from your list, the rest can be imported again."
msgstr "正在匯入清單中的前 5,000 個 URL其餘的可以再次匯入。"
@@ -120,6 +202,14 @@ msgstr "Distill.io"
msgid ".XLSX & Wachete"
msgstr ".XLSX 和 Wachete"
#: changedetectionio/blueprint/imports/templates/import.html
msgid "Restoring changedetection.io backups is in the"
msgstr ""
#: changedetectionio/blueprint/imports/templates/import.html
msgid "backups section"
msgstr ""
#: changedetectionio/blueprint/imports/templates/import.html
msgid "Enter one URL per line, and optionally add tags for each URL after a space, delineated by comma (,):"
msgstr "每行輸入一個 URL可選用空格分隔後為每個 URL 新增標籤,標籤間用逗號 (,) 分隔:"
@@ -204,6 +294,16 @@ msgstr "複查時間(分鐘)"
msgid "Import"
msgstr "匯入"
#: changedetectionio/blueprint/rss/single_watch.py
#, python-format
msgid "Watch with UUID %(uuid)s not found"
msgstr ""
#: changedetectionio/blueprint/rss/single_watch.py
#, python-format
msgid "Watch %(uuid)s does not have enough history snapshots to show changes (need at least 2)"
msgstr ""
#: changedetectionio/blueprint/settings/__init__.py
msgid "Password protection removed."
msgstr "密碼保護已移除。"
@@ -289,6 +389,10 @@ msgstr "API"
msgid "RSS"
msgstr "RSS"
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Backups"
msgstr "備份"
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Time & Date"
msgstr "時間與日期"
@@ -305,10 +409,6 @@ msgstr "資訊"
msgid "Default recheck time for all watches, current system minimum is"
msgstr "所有監測任務的預設複查時間,目前系統最小值為"
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "seconds"
msgstr "秒"
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "more info"
msgstr "更多資訊"
@@ -662,6 +762,10 @@ msgid ""
"whitelist the IP access instead"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Uptime:"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Python version:"
msgstr "Python 版本:"
@@ -915,10 +1019,6 @@ msgstr ""
msgid "Incorrect confirmation text."
msgstr "確認文字不正確。"
#: changedetectionio/blueprint/ui/__init__.py
msgid "Marking watches as viewed in background..."
msgstr ""
#: changedetectionio/blueprint/ui/__init__.py
#, python-brace-format
msgid "The watch by UUID {} does not exist."
@@ -1523,6 +1623,10 @@ msgstr "伺服器類型回應"
msgid "Download latest HTML snapshot"
msgstr "下載最新 HTML 快照"
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "Download watch data package"
msgstr ""
#: changedetectionio/blueprint/ui/templates/edit.html
msgid "Delete Watch?"
msgstr "刪除監測任務?"
@@ -1780,6 +1884,66 @@ msgstr "於 '%(title)s'"
msgid "Not yet"
msgstr "還沒有"
#: changedetectionio/flask_app.py
msgid "0 seconds"
msgstr ""
#: changedetectionio/flask_app.py
msgid "year"
msgstr ""
#: changedetectionio/flask_app.py
msgid "years"
msgstr ""
#: changedetectionio/flask_app.py
msgid "month"
msgstr ""
#: changedetectionio/flask_app.py
msgid "months"
msgstr ""
#: changedetectionio/flask_app.py
msgid "week"
msgstr ""
#: changedetectionio/flask_app.py
msgid "weeks"
msgstr ""
#: changedetectionio/flask_app.py
msgid "day"
msgstr ""
#: changedetectionio/flask_app.py
msgid "days"
msgstr ""
#: changedetectionio/flask_app.py
msgid "hour"
msgstr ""
#: changedetectionio/flask_app.py
msgid "hours"
msgstr ""
#: changedetectionio/flask_app.py
msgid "minute"
msgstr ""
#: changedetectionio/flask_app.py
msgid "minutes"
msgstr ""
#: changedetectionio/flask_app.py
msgid "second"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings.html changedetectionio/flask_app.py
msgid "seconds"
msgstr "秒"
#: changedetectionio/flask_app.py
msgid "Already logged in"
msgstr "已經登入"
@@ -2874,6 +3038,18 @@ msgstr ""
msgid "Note: Wrap in forward slash / to use regex example:"
msgstr ""
#: changedetectionio/templates/edit/text-options.html
msgid "You can also use"
msgstr ""
#: changedetectionio/templates/edit/text-options.html
msgid "conditions"
msgstr ""
#: changedetectionio/templates/edit/text-options.html
msgid "\"Page text\" - with Contains, Starts With, Not Contains and many more"
msgstr ""
#: changedetectionio/templates/edit/text-options.html
msgid "Matching text will be ignored in the text snapshot (you can still see it but it wont trigger a change)"
msgstr ""
@@ -3150,3 +3326,6 @@ msgstr "主設定"
#~ msgid "Tip: You can also add 'shared' watches."
#~ msgstr "提示:您也可以新增「共享」監測任務。"
#~ msgid "Marking watches as viewed in background..."
#~ msgstr ""

View File

@@ -1,3 +1,5 @@
import ipaddress
import socket
from functools import lru_cache
from loguru import logger
from urllib.parse import urlparse, urlunparse, parse_qsl, urlencode
@@ -56,6 +58,23 @@ def normalize_url_encoding(url):
return url
def is_private_hostname(hostname):
"""Return True if hostname resolves to an IANA-restricted (private/reserved) IP address.
Fails closed: unresolvable hostnames return True (block them).
Never cached — callers that need fresh DNS resolution (e.g. at fetch time) can call
this directly without going through the lru_cached is_safe_valid_url().
"""
try:
for info in socket.getaddrinfo(hostname, None):
ip = ipaddress.ip_address(info[4][0])
if ip.is_private or ip.is_loopback or ip.is_link_local or ip.is_reserved:
return True
except socket.gaierror:
return True
return False
@lru_cache(maxsize=10000)
def is_safe_valid_url(test_url):
from changedetectionio import strtobool
@@ -119,4 +138,12 @@ def is_safe_valid_url(test_url):
logger.warning(f'URL f"{test_url}" failed validation, aborting.')
return False
# Block IANA-restricted (private/reserved) IP addresses unless explicitly allowed.
# This is an add-time check; fetch-time re-validation in requests.py handles DNS rebinding.
if not strtobool(os.getenv('ALLOW_IANA_RESTRICTED_ADDRESSES', 'false')):
parsed = urlparse(test_url)
if parsed.hostname and is_private_hostname(parsed.hostname):
logger.warning(f'URL "{test_url}" resolves to a private/reserved IP address, aborting.')
return False
return True

View File

@@ -1,27 +1,21 @@
# eventlet>=0.38.0 # Removed - replaced with threading mode for better Python 3.12+ compatibility
feedgen~=1.0
feedparser~=6.0 # For parsing RSS/Atom feeds
flask-compress
# 0.6.3 included compatibility fix for werkzeug 3.x (2.x had deprecation of url handlers)
flask-login>=0.6.3
flask-paginate
flask_restful
flask_cors # For the Chrome extension to operate
# janus # No longer needed - using pure threading.Queue for multi-loop support
flask_wtf~=1.2
# Flask 3.1 removed the session setter on RequestContext; the patch in
# changedetectionio/realtime/socket_server.py restores it so Flask-SocketIO works.
# Require >=3.1 so the patch is always needed; <4 guards against unknown breaking changes.
flask-socketio>=5.6.1,<6 # Re #3910
flask>=3.1,<4
# Flask-SocketIO 5.x still does ctx.session = ... directly; the patch above handles it.
# >=5.5.0 ensures the threading async_mode we rely on is available.
flask-socketio>=5.5.0,<6
python-socketio>=5.11.0,<6
python-engineio>=4.9.0,<5
flask_cors # For the Chrome extension to operate
flask_restful
flask_wtf~=1.2
inscriptis~=2.2
python-engineio>=4.9.0,<5
python-socketio>=5.11.0,<6
pytz
timeago~=1.0
validators~=0.35
werkzeug==3.1.6
# Set these versions together to avoid a RequestsDependencyWarning
# >= 2.26 also adds Brotli support if brotli is installed
@@ -103,12 +97,8 @@ pytest ~=9.0
pytest-flask ~=1.3
pytest-mock ~=3.15
# Anything 4.0 and up but not 5.0
jsonschema ~= 4.26
# OpenAPI validation support
openapi-core[flask] >= 0.19.0
openapi-core[flask] ~= 0.22
loguru
@@ -130,8 +120,7 @@ greenlet >= 3.0.3
# Default SOCKETIO_MODE=threading is recommended for better compatibility
gevent
# Previously pinned for flask_expects_json (removed 2026-02). Unpinning for now.
referencing
referencing # Don't pin — jsonschema-path (required by openapi-core>=0.18) caps referencing<0.37.0, so pinning 0.37.0 forces openapi-core back to 0.17.2. Revisit once jsonschema-path>=0.3.5 relaxes the cap.
# For conditions
panzi-json-logic