Compare commits

..

19 Commits

Author SHA1 Message Date
dgtlmoon d80a38a1b1 API - watch.link was accidently a tuple, enforcing string 2026-04-29 19:36:34 +10:00
dgtlmoon e25387f588 Improve LiteLLM deps #4093 (#4102) 2026-04-29 09:08:20 +02:00
dgtlmoon e4bc048280 UI - AI/LLM - "Summary" button should set last viewed (#4095)
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
ChangeDetection.io App Test / lint-translations (push) Has been cancelled
ChangeDetection.io App Test / lint-template-i18n (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
ChangeDetection.io App Test / test-application-3-14 (push) Has been cancelled
2026-04-28 19:47:15 +10:00
skkzsh 2839a4276e Ruff INT (flake8-gettext) (#4096) 2026-04-28 19:46:58 +10:00
dgtlmoon 5759a28d89 0.55.3
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 App Test / lint-code (push) Has been cancelled
ChangeDetection.io App Test / lint-translations (push) Has been cancelled
ChangeDetection.io App Test / lint-template-i18n (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
ChangeDetection.io App Test / test-application-3-14 (push) Has been cancelled
2026-04-28 15:26:20 +10:00
dgtlmoon eae9521924 Recompile languages 2026-04-28 15:23:01 +10:00
dgtlmoon 1dbbbd6819 0.55.2 2026-04-28 15:20:38 +10:00
redphx d07b57b816 typo: {{diff_url}} token mentioned twice (#4094) 2026-04-28 05:52:58 +02:00
skkzsh 22ef98d58e i18n: UI - Align desktop "Last Checked" / "Last Changed" with mobile (#4090) 2026-04-28 02:46:40 +02:00
dgtlmoon 9f6e4ea0ad UI - AI/LLM - OpenRouter config UI was missing the correct fields. #4091 2026-04-28 10:44:28 +10:00
skkzsh 7d2803e179 Freeze POT-Creation-Date at sentinel to stop per-locale churn (#4092)
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 / lint-translations (push) Has been cancelled
ChangeDetection.io App Test / lint-template-i18n (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 App Test / test-application-3-14 (push) Has been cancelled
2026-04-28 00:37:14 +10:00
dgtlmoon f93dc7746d i18n - Recompile languages
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 / lint-translations (push) Has been cancelled
ChangeDetection.io App Test / lint-template-i18n (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 App Test / test-application-3-14 (push) Has been cancelled
2026-04-27 17:11:06 +10:00
dgtlmoon d427dbc2b2 0.55.1 2026-04-27 17:03:18 +10:00
dgtlmoon 52b189fc7c Security - Hardening XML parser against XXE 2026-04-27 17:00:42 +10:00
dgtlmoon 866b442576 Security - Stored XSS via Tag Name in Modal Dialog 2026-04-27 16:36:28 +10:00
dgtlmoon ba20f66cee Security - Arbitrary Local File Read via crafted backup restore 2026-04-27 16:35:07 +10:00
Junhan Koo e064bcea13 i18n - Update Korean language (#4084)
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 / lint-translations (push) Has been cancelled
ChangeDetection.io App Test / lint-template-i18n (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 App Test / test-application-3-14 (push) Has been cancelled
2026-04-27 05:01:37 +02:00
dgtlmoon 74a7eb1b11 [i18n] "Usage" tab label in AI / LLM settings is ambiguous across contexts #4086 (#4088)
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 / lint-translations (push) Has been cancelled
ChangeDetection.io App Test / lint-template-i18n (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 App Test / test-application-3-14 (push) Has been cancelled
2026-04-26 15:25:22 +02:00
dgtlmoon 79d75f7926 Translations - Playwright macro unused, add extra linting for translations, add TRANSLATORS.md (#4087)
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 / lint-translations (push) Has been cancelled
ChangeDetection.io App Test / lint-template-i18n (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 App Test / test-application-3-14 (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-04-26 12:36:50 +02:00
51 changed files with 1239 additions and 976 deletions
+2 -2
View File
@@ -11,8 +11,8 @@ jobs:
- name: Lint with Ruff
run: |
pip install ruff
# Check for syntax errors and undefined names
ruff check . --select E9,F63,F7,F82
# Check for syntax errors and undefined names, and gettext misuse
ruff check . --select E9,F63,F7,F82,INT
# Complete check with errors treated as warnings
ruff check . --exit-zero
- name: Validate OpenAPI spec
+5 -1
View File
@@ -20,10 +20,11 @@ exclude = [
select = [
"B", # flake8-bugbear
"B9",
"C",
"C",
"E", # pycodestyle
"F", # Pyflakes
"I", # isort
"INT", # flake8-gettext
"N", # pep8-naming
"UP", # pyupgrade
"W", # pycodestyle
@@ -43,6 +44,9 @@ ignore = [
[lint.mccabe]
max-complexity = 12
[lint.flake8-gettext]
extend-function-names = ["_l", "lazy_gettext", "pgettext", "npgettext"]
[format]
indent-style = "space"
quote-style = "preserve"
+1 -1
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.54.10'
__version__ = '0.55.3'
from changedetectionio.strtobool import strtobool
from json.decoder import JSONDecodeError
+1 -1
View File
@@ -103,7 +103,7 @@ class Watch(Resource):
# attr .last_changed will check for the last written text snapshot on change
watch['last_changed'] = watch_obj.last_changed
watch['viewed'] = watch_obj.viewed
watch['link'] = watch_obj.link,
watch['link'] = watch_obj.link
return watch
@@ -7,12 +7,14 @@
<div class="tab-pane-inner" id="ai">
<script src="{{ url_for('static_content', group='js', filename='sub-tabs.js') }}"></script>
{# TRANSLATORS: 'Usage' here means token consumption/cost stats for the AI provider, not a how-to guide #}
{% set _usage_label = pgettext('AI usage stats', 'Usage') %}
{% call stab_shell('ai-settings', [
{'id': 'overview', 'label': _('Overview'), 'icon': '✦'},
{'id': 'provider', 'label': _('Provider'), 'icon': '⚙'},
{'id': 'prompts', 'label': _('Prompts'), 'icon': '≡'},
{'id': 'behaviour', 'label': _('Behaviour'), 'icon': '⚑'},
{'id': 'usage', 'label': _('Usage'), 'icon': '$'},
{'id': 'usage', 'label': _usage_label, 'icon': '$'},
]) %}
{# ── Overview ──────────────────────────────────────────────────────────── #}
@@ -375,7 +377,7 @@
<script>
(function () {
const LIVE_PROVIDERS = ['openai', 'anthropic', 'gemini', 'ollama'];
const LIVE_PROVIDERS = ['openai', 'anthropic', 'gemini', 'ollama', 'openrouter'];
const BASE_DEFAULTS = { ollama: 'http://localhost:11434' };
const KEY_HINTS = {
openai: '{{ _("platform.openai.com → API keys") }}',
@@ -399,7 +401,7 @@
fetchGroup.style.display = LIVE_PROVIDERS.includes(provider) ? '' : 'none';
const needsBase = provider === 'ollama' || provider === 'openrouter';
const needsBase = provider === 'ollama';
baseGroup.style.display = needsBase ? '' : 'none';
if (BASE_DEFAULTS[provider] !== undefined) {
if (!baseField.value) baseField.value = BASE_DEFAULTS[provider];
+4
View File
@@ -283,6 +283,8 @@ def construct_blueprint(datastore: ChangeDetectionStore):
# Check cache — keyed by version pair + prompt hash (invalidates if prompt changes)
cached = watch.get_llm_diff_summary(from_version, to_version, prompt=cache_prompt)
if cached:
import time
datastore.set_last_viewed(uuid, int(time.time()))
return jsonify({'summary': cached, 'error': None, 'cached': True})
# Check global monthly token budget before making an LLM call
@@ -316,6 +318,8 @@ def construct_blueprint(datastore: ChangeDetectionStore):
except Exception as e:
logger.warning(f"Could not cache llm summary for {uuid}: {e}")
import time
datastore.set_last_viewed(uuid, int(time.time()))
return jsonify({'summary': summary, 'error': None, 'cached': False})
@diff_blueprint.route("/diff/<uuid_str:uuid>/extract", methods=['GET'])
@@ -223,8 +223,8 @@ window.watchOverviewI18n = {
{%- if any_has_restock_price_processor -%}
<th>{{ _('Restock & Price') }}</th>
{%- endif -%}
<th><a class="{{ 'active '+link_order if sort_attribute == 'last_checked' else 'inactive' }}" href="{{url_for('watchlist.index', sort='last_checked', order=link_order, tag=active_tag_uuid)}}"><span class="hide-on-mobile">{{ _('Last') }}</span> {{ _('Checked') }} <span class='arrow {{link_order}}'></span></a></th>
<th><a class="{{ 'active '+link_order if sort_attribute == 'last_changed' else 'inactive' }}" href="{{url_for('watchlist.index', sort='last_changed', order=link_order, tag=active_tag_uuid)}}"><span class="hide-on-mobile">{{ _('Last') }}</span> {{ _('Changed') }} <span class='arrow {{link_order}}'></span></a></th>
<th><a class="{{ 'active '+link_order if sort_attribute == 'last_checked' else 'inactive' }}" href="{{url_for('watchlist.index', sort='last_checked', order=link_order, tag=active_tag_uuid)}}"><span class="hide-on-mobile">{{ _('Last Checked') }}</span><span class="hide-on-desktop">{{ _('Checked') }}</span> <span class='arrow {{link_order}}'></span></a></th>
<th><a class="{{ 'active '+link_order if sort_attribute == 'last_changed' else 'inactive' }}" href="{{url_for('watchlist.index', sort='last_changed', order=link_order, tag=active_tag_uuid)}}"><span class="hide-on-mobile">{{ _('Last Changed') }}</span><span class="hide-on-desktop">{{ _('Changed') }}</span> <span class='arrow {{link_order}}'></span></a></th>
<th class="empty-cell"></th>
</tr>
</thead>
+5
View File
@@ -981,6 +981,11 @@ def changedetection_app(config=None, datastore_o=None):
"queued_data": all_queued
})
if strtobool(os.getenv('HISTORY_SNAPSHOT_FILE_ALLOW_OUTSIDE_WATCH_DATADIR', 'False')):
logger.warning("SECURITY WARNING: HISTORY_SNAPSHOT_FILE_ALLOW_OUTSIDE_WATCH_DATADIR is enabled — "
"snapshot reads are NOT confined to the watch data directory. "
"This disables protection against path traversal via restored backups (GHSA-8757-69j2-hx56).")
# Start the async workers during app initialization
# Can be overridden by ENV or use the default settings
n_workers = int(os.getenv("FETCH_WORKERS", datastore.data['settings']['requests']['workers']))
+2 -2
View File
@@ -282,7 +282,7 @@ def xpath_filter(xpath_filter, html_content, append_pretty_line_formatting=False
try:
if is_xml:
# So that we can keep CDATA for cdata_in_document_to_text() to process
parser = etree.XMLParser(strip_cdata=False)
parser = etree.XMLParser(strip_cdata=False, resolve_entities=False, no_network=True)
# For XML/RSS content, use etree.fromstring to properly handle XML declarations
tree = etree.fromstring(html_content.encode('utf-8') if isinstance(html_content, str) else html_content, parser=parser)
else:
@@ -346,7 +346,7 @@ def xpath1_filter(xpath_filter, html_content, append_pretty_line_formatting=Fals
try:
if is_xml:
# So that we can keep CDATA for cdata_in_document_to_text() to process
parser = etree.XMLParser(strip_cdata=False)
parser = etree.XMLParser(strip_cdata=False, resolve_entities=False, no_network=True)
# For XML/RSS content, use etree.fromstring to properly handle XML declarations
tree = etree.fromstring(html_content.encode('utf-8') if isinstance(html_content, str) else html_content, parser=parser)
else:
+23 -15
View File
@@ -465,22 +465,21 @@ class model(EntityPersistenceMixin, watch_base):
if ',' in i:
k, v = i.strip().split(',', 2)
# The index history could contain a relative path, so we need to make the fullpath
# so that python can read it
# Cross-platform: check for any path separator (works on Windows and Unix)
if os.sep not in v and '/' not in v and '\\' not in v:
# Relative filename only, no path separators
v = os.path.join(self.data_dir, v)
else:
# It's possible that they moved the datadir on older versions
# So the snapshot exists but is in a different path
# Cross-platform: use os.path.basename instead of split('/')
snapshot_fname = os.path.basename(v)
proposed_new_path = os.path.join(self.data_dir, snapshot_fname)
if not os.path.exists(v) and os.path.exists(proposed_new_path):
v = proposed_new_path
# Always resolve history entries to within the watch's own data directory.
# Entries restored from backup could contain absolute or traversal paths —
# never trust them. Use realpath to also block symlink-based escapes.
safe_data_dir = os.path.realpath(self.data_dir)
snapshot_fname = os.path.basename(v.strip())
resolved_path = os.path.realpath(os.path.join(self.data_dir, snapshot_fname))
tmp_history[k] = v
if not resolved_path.startswith(safe_data_dir + os.sep) and resolved_path != safe_data_dir:
logger.warning(f"Skipping unsafe history entry for {self.get('uuid')}: {v!r}")
continue
if not os.path.exists(resolved_path):
continue
tmp_history[k] = resolved_path
if len(tmp_history):
self.__newest_history_key = list(tmp_history.keys())[-1]
@@ -563,6 +562,15 @@ class model(EntityPersistenceMixin, watch_base):
if not filepath:
filepath = self.history[timestamp]
# Confine every read to the watch's own data directory — defence in depth
# against any path that bypasses the history parser (e.g. direct filepath= callers).
# Set HISTORY_SNAPSHOT_FILE_ALLOW_OUTSIDE_WATCH_DATADIR=true to disable (not recommended).
if self.data_dir and not strtobool(os.getenv('HISTORY_SNAPSHOT_FILE_ALLOW_OUTSIDE_WATCH_DATADIR', 'False')):
safe_data_dir = os.path.realpath(self.data_dir)
resolved = os.path.realpath(filepath)
if not (resolved.startswith(safe_data_dir + os.sep) or resolved == safe_data_dir):
raise PermissionError(f"Snapshot path {filepath!r} is outside the watch data directory")
# Check if binary file (image, PDF, etc.)
# Binary files are NEVER saved with .br compression, only text files are
binary_extensions = ('.png', '.jpg', '.jpeg', '.gif', '.webp', '.pdf', '.bin', '.jfif')
+18 -9
View File
@@ -3,6 +3,13 @@
* Provides accessible, animated confirmation dialogs
*/
// Escapes a string for safe insertion via innerHTML
function _modalEscapeHTML(str) {
const div = document.createElement('div');
div.textContent = str;
return div.innerHTML;
}
const ModalDialog = {
/**
* Show a confirmation dialog
@@ -125,9 +132,10 @@ const ModalDialog = {
* @param {Function} onConfirm - Callback when confirmed
*/
confirmDelete: function(itemName, onConfirm) {
const safeName = _modalEscapeHTML(itemName);
return this.confirm({
title: 'Delete ' + itemName + '?',
message: `<p>Are you sure you want to delete <strong>${itemName}</strong>?</p><p>This action cannot be undone.</p>`,
title: 'Delete ' + safeName + '?',
message: `<p>Are you sure you want to delete <strong>${safeName}</strong>?</p><p>This action cannot be undone.</p>`,
type: 'danger',
confirmText: 'Delete',
cancelText: 'Cancel',
@@ -141,9 +149,10 @@ const ModalDialog = {
* @param {Function} onConfirm - Callback when confirmed
*/
confirmUnlink: function(itemName, onConfirm) {
const safeName = _modalEscapeHTML(itemName);
return this.confirm({
title: 'Unlink ' + itemName + '?',
message: `<p>Are you sure you want to unlink all watches from <strong>${itemName}</strong>?</p><p>The tag will be kept but watches will be removed from it.</p>`,
title: 'Unlink ' + safeName + '?',
message: `<p>Are you sure you want to unlink all watches from <strong>${safeName}</strong>?</p><p>The tag will be kept but watches will be removed from it.</p>`,
type: 'warning',
confirmText: 'Unlink',
cancelText: 'Cancel',
@@ -172,11 +181,11 @@ $(document).ready(function() {
const url = $element.attr('href');
const config = {
type: $element.data('confirm-type') || 'danger',
title: $element.data('confirm-title') || 'Confirm Action',
message: $element.data('confirm-message') || '<p>Are you sure you want to proceed?</p>',
confirmText: $element.data('confirm-button') || 'Confirm',
cancelText: $element.data('cancel-button') || 'Cancel',
type: $element.attr('data-confirm-type') || 'danger',
title: $element.attr('data-confirm-title') || 'Confirm Action',
message: $element.attr('data-confirm-message') || '<p>Are you sure you want to proceed?</p>',
confirmText: $element.attr('data-confirm-button') || 'Confirm',
cancelText: $element.attr('data-cancel-button') || 'Cancel',
onConfirm: function() {
// If it's a link, navigate to the URL
if ($element.is('a')) {
@@ -180,4 +180,10 @@ $grid-gap: 0.5rem;
.pure-table td {
padding: 3px !important;
}
}
@media (min-width: 768px) {
.watch-table thead tr th .hide-on-desktop {
display: none;
}
}
File diff suppressed because one or more lines are too long
@@ -52,10 +52,6 @@
<td><code>{{ '{{diff_url}}' }}</code></td>
<td>{{ _('The URL of the diff output for the watch.') }}</td>
</tr>
<tr>
<td><code>{{ '{{diff_url}}' }}</code></td>
<td>{{ _('The URL of the diff output for the watch.') }}</td>
</tr>
<tr>
<td><code>{{ '{{diff}}' }}</code></td>
<td>{{ _('The diff output - only changes, additions, and removals') }}<br>
+2
View File
@@ -102,6 +102,8 @@ def test_api_simple(client, live_server, measure_memory_usage, datastore_path):
#705 `last_changed` should be zero on the first check
assert before_recheck_info['last_changed'] == 0
assert before_recheck_info['title'] == 'My test URL'
assert isinstance(before_recheck_info['link'], str), "link must be a plain string, not a tuple or list"
assert before_recheck_info['link'] == test_url
# Check the limit by tag doesnt return anything when nothing found
res = client.get(
@@ -336,6 +336,58 @@ def test_hardcoded_fallback_when_nothing_set(
delete_all_watches(client)
def test_llm_summary_ajax_sets_last_viewed(
client, live_server, measure_memory_usage, datastore_path):
"""
Calling /diff/<uuid>/llm-summary via AJAX should mark the watch as viewed
(set last_viewed) for both fresh and cached responses.
"""
from unittest.mock import patch, MagicMock
_configure_llm(client)
ds = client.application.config.get('DATASTORE')
test_url = url_for('test_endpoint', content_type='text/html', content='v1', _external=True)
uuid = ds.add_watch(url=test_url)
watch = ds.data['watching'][uuid]
watch.save_history_blob('old content\n', '4000000000', 'snap-old')
watch.save_history_blob('new content\n', '4000000001', 'snap-new')
assert watch['last_viewed'] == 0, "last_viewed should start at 0"
mock_response = MagicMock()
mock_response.choices = [MagicMock()]
mock_response.choices[0].message.content = 'Content changed from old to new.'
mock_response.usage = MagicMock(total_tokens=50, prompt_tokens=40, completion_tokens=10)
with patch('litellm.completion', return_value=mock_response):
res = client.get(
url_for('ui.ui_diff.diff_llm_summary', uuid=uuid,
from_version='4000000000', to_version='4000000001'),
)
assert res.status_code == 200
data = res.get_json()
assert data['summary'] == 'Content changed from old to new.'
assert watch['last_viewed'] > 0, "last_viewed should be set after fresh LLM summary"
# Reset and verify the cached path also sets last_viewed
watch['last_viewed'] = 0
with patch('litellm.completion', return_value=mock_response):
res2 = client.get(
url_for('ui.ui_diff.diff_llm_summary', uuid=uuid,
from_version='4000000000', to_version='4000000001'),
)
assert res2.status_code == 200
data2 = res2.get_json()
assert data2.get('cached') is True
assert watch['last_viewed'] > 0, "last_viewed should be set even when returning cached summary"
delete_all_watches(client)
def test_global_default_saved_and_loaded_via_settings_form(
client, live_server, measure_memory_usage, datastore_path):
"""
+76
View File
@@ -1,7 +1,11 @@
import io
import os
import re
import time
import pytest
from flask import url_for
from zipfile import ZipFile, ZIP_DEFLATED
from changedetectionio.tests.util import set_modified_response
from .util import live_server_setup, wait_for_all_checks, delete_all_watches
@@ -823,3 +827,75 @@ def test_unresolvable_hostname_is_allowed(client, live_server, monkeypatch):
res = client.get(url_for('watchlist.index'))
assert b'this-host-does-not-exist-xyz987.invalid' in res.data, \
"Unresolvable hostname watch should appear in the watch overview list"
def test_ghsa_8757_69j2_hx56_backup_restore_history_path_traversal(client, live_server, measure_memory_usage, datastore_path):
"""
GHSA-8757-69j2-hx56: Crafted backup ZIP with absolute path in history.txt must not
expose arbitrary local files through the preview or API endpoints.
Attack chain:
1. Attacker creates a backup ZIP with a malicious history.txt containing an absolute
path (e.g. /etc/passwd) as a snapshot reference.
2. Victim restores the backup.
3. Attacker reads the targeted file via the Preview page.
The fix ensures history entries are always resolved to os.path.basename() joined with
the watch's data_dir, and rejects entries that escape that directory.
"""
set_original_response(datastore_path=datastore_path)
datastore = live_server.app.config['DATASTORE']
watch_url = url_for('test_endpoint', _external=True)
# Create a real watch and trigger a check so we have a valid backup structure
uuid = datastore.add_watch(url=watch_url)
client.get(url_for("ui.form_watch_checknow"), follow_redirects=True)
wait_for_all_checks(client)
# Download a legitimate backup to use as a template
client.get(url_for("backups.request_backup"), follow_redirects=True)
time.sleep(4)
res = client.get(url_for("backups.download_backup", filename="latest"), follow_redirects=True)
assert res.content_type == "application/zip"
# Tamper: replace the history.txt inside the backup with a malicious entry
# that points at /etc/passwd (a file that exists on any Unix system)
original_zip = ZipFile(io.BytesIO(res.data))
tampered_buf = io.BytesIO()
with ZipFile(tampered_buf, 'w', ZIP_DEFLATED) as new_zip:
for item in original_zip.infolist():
data = original_zip.read(item.filename)
# Replace the watch's history.txt with a malicious absolute path entry
if item.filename.endswith('history.txt') and uuid in item.filename:
data = b'1776969105,/etc/passwd\n'
new_zip.writestr(item, data)
tampered_buf.seek(0)
tampered_zip_data = tampered_buf.read()
# Restore the tampered backup
res = client.post(
url_for("backups.restore.backups_restore_start"),
data={
'zip_file': (io.BytesIO(tampered_zip_data), 'malicious_backup.zip'),
'include_watches': 'y',
'include_watches_replace_existing': 'y',
},
content_type='multipart/form-data',
follow_redirects=True
)
assert res.status_code == 200
time.sleep(2)
# Now try to read the /etc/passwd contents via the Preview page using the injected timestamp
res = client.get(
url_for("ui.ui_preview.preview_page", uuid=uuid) + "?timestamp=1776969105",
follow_redirects=True
)
# The preview must NOT contain typical /etc/passwd content
assert b'root:' not in res.data, \
"Preview must not expose /etc/passwd — history path traversal not blocked"
assert b'/bin/' not in res.data or b'No history' in res.data or res.status_code in [404, 500], \
"Preview must not serve arbitrary local files from a malicious history entry"
@@ -311,5 +311,72 @@ class TestLLMDiffSummaryCache(unittest.TestCase):
assert watch.get_llm_diff_summary('1000', '2000', prompt=self.PROMPT) == 'Updated summary'
class TestHistoryPathTraversal(unittest.TestCase):
"""GHSA-8757-69j2-hx56: history.txt must not allow reads outside the watch data dir."""
def _make_watch(self):
mock_datastore = {'settings': {'application': {}}, 'watching': {}}
watch = Watch.model(datastore_path='/tmp', __datastore=mock_datastore, default={})
watch.ensure_data_dir_exists()
return watch
def _write_history_txt(self, watch, lines):
"""Directly write raw lines to history.txt to simulate a restored backup."""
fname = os.path.join(watch.data_dir, watch.history_index_filename)
with open(fname, 'w', encoding='utf-8') as f:
f.writelines(lines)
def test_absolute_path_in_history_is_rejected(self):
"""An absolute path like /etc/passwd must not appear in history."""
watch = self._make_watch()
self._write_history_txt(watch, ['1000000000,/etc/passwd\n'])
history = watch.history
self.assertEqual(history, {}, "Absolute path entry must be rejected")
def test_traversal_path_in_history_is_rejected(self):
"""A relative traversal path like ../../etc/passwd must not appear in history."""
watch = self._make_watch()
self._write_history_txt(watch, ['1000000000,../../etc/passwd\n'])
history = watch.history
self.assertEqual(history, {}, "Path traversal entry must be rejected")
def test_normal_snapshot_entry_is_accepted(self):
"""A bare filename written by save_history_blob must still load correctly."""
import uuid as uuid_builder
watch = self._make_watch()
watch.save_history_blob(contents="hello world", timestamp=1000000000, snapshot_id=str(uuid_builder.uuid4()))
history = watch.history
self.assertEqual(len(history), 1, "Normal snapshot entry must be accepted")
self.assertTrue(
list(history.values())[0].startswith(watch.data_dir),
"Resolved path must be inside the watch data directory"
)
def test_get_history_snapshot_blocks_outside_path_directly(self):
"""get_history_snapshot(filepath=...) must raise if the path escapes data_dir."""
watch = self._make_watch()
with self.assertRaises(PermissionError):
watch.get_history_snapshot(filepath='/etc/passwd')
def test_get_history_snapshot_blocks_traversal_directly(self):
"""get_history_snapshot(filepath=...) must raise on ../../ traversal paths."""
watch = self._make_watch()
with self.assertRaises(PermissionError):
watch.get_history_snapshot(filepath=os.path.join(watch.data_dir, '../../etc/passwd'))
def test_resolved_path_stays_inside_data_dir(self):
"""All resolved history paths must reside within the watch's data_dir."""
import uuid as uuid_builder
watch = self._make_watch()
for ts in [1000000001, 1000000002, 1000000003]:
watch.save_history_blob(contents=f"content {ts}", timestamp=ts, snapshot_id=str(uuid_builder.uuid4()))
safe_dir = os.path.realpath(watch.data_dir)
for path in watch.history.values():
self.assertTrue(
os.path.realpath(path).startswith(safe_dir),
f"Path {path!r} escapes the watch data directory"
)
if __name__ == '__main__':
unittest.main()
@@ -0,0 +1,35 @@
#!/usr/bin/env python3
# run from dir above changedetectionio/ dir
# python3 -m pytest changedetectionio/tests/unit/test_xml_security.py
import pytest
from changedetectionio import html_tools
def _xxe_payload(file_path: str) -> str:
return f"""<?xml version="1.0"?>
<!DOCTYPE root [
<!ENTITY xxe SYSTEM "file://{file_path}">
]>
<root><item>&xxe;</item></root>"""
def test_xxe_not_expanded_xpath_filter(tmp_path):
"""xpath_filter must not expand external entities (CVE-2026-41895)."""
sentinel_file = tmp_path / "sentinel.txt"
sentinel = "xxe_sentinel_should_never_appear_in_output"
sentinel_file.write_text(sentinel)
result = html_tools.xpath_filter("//item", _xxe_payload(sentinel_file), is_xml=True)
assert sentinel not in result
def test_xxe_not_expanded_xpath1_filter(tmp_path):
"""xpath1_filter must not expand external entities (CVE-2026-41895)."""
sentinel_file = tmp_path / "sentinel.txt"
sentinel = "xxe_sentinel_should_never_appear_in_output"
sentinel_file.write_text(sentinel)
result = html_tools.xpath1_filter("//item", _xxe_payload(sentinel_file), is_xml=True)
assert sentinel not in result
+3
View File
@@ -213,6 +213,9 @@ Never fix one language and move on.
pybabel init -i changedetectionio/translations/messages.pot \
-d changedetectionio/translations \
-l NEW_LANG_CODE
# Reset POT-Creation-Date to the sentinel so it matches the other catalogs
sed -i 's|^"POT-Creation-Date: .*\\n"$|"POT-Creation-Date: 1970-01-01 00:00+0000\\n"|' \
changedetectionio/translations/NEW_LANG_CODE/LC_MESSAGES/messages.po
python setup.py compile_catalog
```
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2026-04-26 20:03+1000\n"
"POT-Creation-Date: 1970-01-01 00:00+0000\n"
"PO-Revision-Date: 2026-01-02 11:40+0100\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: cs\n"
@@ -769,6 +769,12 @@ msgstr "Zpět"
msgid "Clear Snapshot History"
msgstr "Vymazat historii snímků"
#. 'Usage' here means token consumption/cost stats for the AI provider, not a how-to guide
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
msgctxt "AI usage stats"
msgid "Usage"
msgstr "Využití"
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
msgid "Overview"
msgstr ""
@@ -785,10 +791,6 @@ msgstr ""
msgid "Behaviour"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
msgid "Usage"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
msgid "AI-powered change monitoring"
msgstr ""
@@ -2215,13 +2217,17 @@ msgid "Checked"
msgstr "Zkontrolováno"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last"
msgstr "Poslední"
msgid "Last Checked"
msgstr "Poslední Zkontrolováno"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Changed"
msgstr "Změněno"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Changed"
msgstr "Poslední Změněno"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "No web page change detection watches configured, please add a URL in the box above, or"
msgstr "Nejsou nakonfigurována žádná sledování webových stránek, do výše uvedeného pole přidejte adresu URL nebo"
@@ -2270,18 +2276,10 @@ msgstr "Cena"
msgid "No information"
msgstr "Žádné informace"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Checked"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html changedetectionio/templates/base.html
msgid "Checking now"
msgstr "Probíhá kontrola"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Changed"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Queued"
msgstr "Ve frontě"
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2026-04-26 20:03+1000\n"
"POT-Creation-Date: 1970-01-01 00:00+0000\n"
"PO-Revision-Date: 2026-01-14 03:57+0100\n"
"Last-Translator: \n"
"Language: de\n"
@@ -785,6 +785,12 @@ msgstr "Zurück"
msgid "Clear Snapshot History"
msgstr "Snapshot-Verlauf löschen"
#. 'Usage' here means token consumption/cost stats for the AI provider, not a how-to guide
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
msgctxt "AI usage stats"
msgid "Usage"
msgstr "Nutzung"
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
msgid "Overview"
msgstr ""
@@ -801,10 +807,6 @@ msgstr ""
msgid "Behaviour"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
msgid "Usage"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
msgid "AI-powered change monitoring"
msgstr ""
@@ -2266,13 +2268,17 @@ msgid "Checked"
msgstr "Geprüft"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last"
msgstr "Zuletzt"
msgid "Last Checked"
msgstr "Zuletzt Geprüft"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Changed"
msgstr "Geändert"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Changed"
msgstr "Zuletzt Geändert"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "No web page change detection watches configured, please add a URL in the box above, or"
msgstr "Es sind keine Website-Überwachungen konfiguriert. Bitte fügen Sie im Feld oben eine URL hinzu, oder"
@@ -2321,18 +2327,10 @@ msgstr "Preis"
msgid "No information"
msgstr "Keine Informationen"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Checked"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html changedetectionio/templates/base.html
msgid "Checking now"
msgstr "Jetzt prüfen"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Changed"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Queued"
msgstr "Wartend"
@@ -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-04-26 20:03+1000\n"
"POT-Creation-Date: 1970-01-01 00:00+0000\n"
"PO-Revision-Date: 2026-01-12 16:33+0100\n"
"Last-Translator: British English Translation Team\n"
"Language: en_GB\n"
@@ -767,6 +767,12 @@ msgstr ""
msgid "Clear Snapshot History"
msgstr ""
#. 'Usage' here means token consumption/cost stats for the AI provider, not a how-to guide
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
msgctxt "AI usage stats"
msgid "Usage"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
msgid "Overview"
msgstr ""
@@ -783,10 +789,6 @@ msgstr ""
msgid "Behaviour"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
msgid "Usage"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
msgid "AI-powered change monitoring"
msgstr ""
@@ -2211,13 +2213,17 @@ msgid "Checked"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last"
msgid "Last Checked"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Changed"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Changed"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "No web page change detection watches configured, please add a URL in the box above, or"
msgstr ""
@@ -2266,18 +2272,10 @@ msgstr ""
msgid "No information"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Checked"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html changedetectionio/templates/base.html
msgid "Checking now"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Changed"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Queued"
msgstr ""
@@ -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-04-26 20:03+1000\n"
"POT-Creation-Date: 1970-01-01 00:00+0000\n"
"PO-Revision-Date: 2026-01-12 16:37+0100\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: en_US\n"
@@ -767,6 +767,12 @@ msgstr ""
msgid "Clear Snapshot History"
msgstr ""
#. 'Usage' here means token consumption/cost stats for the AI provider, not a how-to guide
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
msgctxt "AI usage stats"
msgid "Usage"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
msgid "Overview"
msgstr ""
@@ -783,10 +789,6 @@ msgstr ""
msgid "Behaviour"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
msgid "Usage"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
msgid "AI-powered change monitoring"
msgstr ""
@@ -2211,13 +2213,17 @@ msgid "Checked"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last"
msgid "Last Checked"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Changed"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Changed"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "No web page change detection watches configured, please add a URL in the box above, or"
msgstr ""
@@ -2266,18 +2272,10 @@ msgstr ""
msgid "No information"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Checked"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html changedetectionio/templates/base.html
msgid "Checking now"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Changed"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Queued"
msgstr ""
@@ -3,7 +3,7 @@ msgid ""
msgstr ""
"Project-Id-Version: changedetection.io 0.53.6\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2026-04-26 20:03+1000\n"
"POT-Creation-Date: 1970-01-01 00:00+0000\n"
"PO-Revision-Date: 2026-03-20 18:13+0100\n"
"Last-Translator: Adrian Gonzalez <adrian@example.com>\n"
"Language: es\n"
@@ -803,6 +803,12 @@ msgstr "Atrás"
msgid "Clear Snapshot History"
msgstr "Borrar historial de instantáneas"
#. 'Usage' here means token consumption/cost stats for the AI provider, not a how-to guide
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
msgctxt "AI usage stats"
msgid "Usage"
msgstr "Uso"
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
msgid "Overview"
msgstr ""
@@ -819,10 +825,6 @@ msgstr ""
msgid "Behaviour"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
msgid "Usage"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
msgid "AI-powered change monitoring"
msgstr ""
@@ -2280,13 +2282,17 @@ msgid "Checked"
msgstr "Comprobado"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last"
msgstr "Último"
msgid "Last Checked"
msgstr "Último Comprobado"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Changed"
msgstr "Cambiadp"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Changed"
msgstr "Último Cambiadp"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "No web page change detection watches configured, please add a URL in the box above, or"
msgstr "No hay monitores de detección de cambios de página web configuradas; agregue una URL en el cuadro de arriba, o"
@@ -2335,18 +2341,10 @@ msgstr "Precio"
msgid "No information"
msgstr "Sin información"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Checked"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html changedetectionio/templates/base.html
msgid "Checking now"
msgstr "Comprobando ahora"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Changed"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Queued"
msgstr "En cola"
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2026-04-26 20:03+1000\n"
"POT-Creation-Date: 1970-01-01 00:00+0000\n"
"PO-Revision-Date: 2026-01-02 11:40+0100\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: fr\n"
@@ -773,6 +773,12 @@ msgstr "Retour"
msgid "Clear Snapshot History"
msgstr "Effacer/réinitialiser l'historique"
#. 'Usage' here means token consumption/cost stats for the AI provider, not a how-to guide
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
msgctxt "AI usage stats"
msgid "Usage"
msgstr "Utilisation"
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
msgid "Overview"
msgstr ""
@@ -789,10 +795,6 @@ msgstr ""
msgid "Behaviour"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
msgid "Usage"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
msgid "AI-powered change monitoring"
msgstr ""
@@ -2222,13 +2224,17 @@ msgid "Checked"
msgstr "Vérification"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last"
msgstr "Dernier"
msgid "Last Checked"
msgstr "Dernier Vérification"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Changed"
msgstr "Modifié"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Changed"
msgstr "Dernier Modifié"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "No web page change detection watches configured, please add a URL in the box above, or"
msgstr "Aucune surveillance de site Web configurée, veuillez ajouter une URL dans la case ci-dessus, ou"
@@ -2277,18 +2283,10 @@ msgstr "Prix"
msgid "No information"
msgstr "Aucune information"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Checked"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html changedetectionio/templates/base.html
msgid "Checking now"
msgstr "Vérifier maintenant"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Changed"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Queued"
msgstr "En file d'attente"
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2026-04-26 20:03+1000\n"
"POT-Creation-Date: 1970-01-01 00:00+0000\n"
"PO-Revision-Date: 2026-01-02 15:32+0100\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: it\n"
@@ -769,6 +769,12 @@ msgstr "Indietro"
msgid "Clear Snapshot History"
msgstr "Cancella cronologia snapshot"
#. 'Usage' here means token consumption/cost stats for the AI provider, not a how-to guide
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
msgctxt "AI usage stats"
msgid "Usage"
msgstr "Utilizzo"
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
msgid "Overview"
msgstr ""
@@ -785,10 +791,6 @@ msgstr ""
msgid "Behaviour"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
msgid "Usage"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
msgid "AI-powered change monitoring"
msgstr ""
@@ -2213,13 +2215,17 @@ msgid "Checked"
msgstr "Controllo"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last"
msgstr "Ultimo"
msgid "Last Checked"
msgstr "Ultimo Controllo"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Changed"
msgstr "Modifica"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Changed"
msgstr "Ultimo Modifica"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "No web page change detection watches configured, please add a URL in the box above, or"
msgstr "Nessun monitoraggio configurato, aggiungi un URL nella casella sopra, oppure"
@@ -2268,18 +2274,10 @@ msgstr "Prezzo"
msgid "No information"
msgstr "Nessuna informazione"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Checked"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html changedetectionio/templates/base.html
msgid "Checking now"
msgstr "Controllo in corso"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Changed"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Queued"
msgstr "In coda"
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: changedetection.io 0.53.6\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2026-04-26 20:03+1000\n"
"POT-Creation-Date: 1970-01-01 00:00+0000\n"
"PO-Revision-Date: 2026-03-31 23:52+0900\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: ja\n"
@@ -774,6 +774,12 @@ msgstr "戻る"
msgid "Clear Snapshot History"
msgstr "スナップショット履歴をクリア"
#. 'Usage' here means token consumption/cost stats for the AI provider, not a how-to guide
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
msgctxt "AI usage stats"
msgid "Usage"
msgstr "使用量"
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
msgid "Overview"
msgstr ""
@@ -790,10 +796,6 @@ msgstr ""
msgid "Behaviour"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
msgid "Usage"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
msgid "AI-powered change monitoring"
msgstr ""
@@ -2230,13 +2232,17 @@ msgid "Checked"
msgstr "チェック済み"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last"
msgstr "最終"
msgid "Last Checked"
msgstr "前回チェック"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Changed"
msgstr "変更済み"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Changed"
msgstr "前回更新"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "No web page change detection watches configured, please add a URL in the box above, or"
msgstr "ウェブページ変更検知ウォッチが設定されていません。上のボックスにURLを追加するか、"
@@ -2285,18 +2291,10 @@ msgstr "価格"
msgid "No information"
msgstr "情報なし"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Checked"
msgstr "前回チェック"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html changedetectionio/templates/base.html
msgid "Checking now"
msgstr "今すぐチェック"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Changed"
msgstr "前回更新"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Queued"
msgstr "キュー済み"
File diff suppressed because it is too large Load Diff
+13 -15
View File
@@ -6,9 +6,9 @@
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: changedetection.io 0.54.10\n"
"Project-Id-Version: changedetection.io 0.55.3\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2026-04-26 20:03+1000\n"
"POT-Creation-Date: 2026-04-28 15:26+1000\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"
@@ -766,6 +766,12 @@ msgstr ""
msgid "Clear Snapshot History"
msgstr ""
#. 'Usage' here means token consumption/cost stats for the AI provider, not a how-to guide
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
msgctxt "AI usage stats"
msgid "Usage"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
msgid "Overview"
msgstr ""
@@ -782,10 +788,6 @@ msgstr ""
msgid "Behaviour"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
msgid "Usage"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
msgid "AI-powered change monitoring"
msgstr ""
@@ -2210,13 +2212,17 @@ msgid "Checked"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last"
msgid "Last Checked"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Changed"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Changed"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "No web page change detection watches configured, please add a URL in the box above, or"
msgstr ""
@@ -2265,18 +2271,10 @@ msgstr ""
msgid "No information"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Checked"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html changedetectionio/templates/base.html
msgid "Checking now"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Changed"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Queued"
msgstr ""
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: changedetection.io 0.54.8\n"
"Report-Msgid-Bugs-To: mstrey@gmail.com\n"
"POT-Creation-Date: 2026-04-26 20:03+1000\n"
"POT-Creation-Date: 1970-01-01 00:00+0000\n"
"PO-Revision-Date: 2026-04-07 22:00-0300\n"
"Last-Translator: Gemini AI\n"
"Language: pt_BR\n"
@@ -792,6 +792,12 @@ msgstr "Voltar"
msgid "Clear Snapshot History"
msgstr "Limpar Histórico de Instantâneos"
#. 'Usage' here means token consumption/cost stats for the AI provider, not a how-to guide
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
msgctxt "AI usage stats"
msgid "Usage"
msgstr "Uso"
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
msgid "Overview"
msgstr ""
@@ -808,10 +814,6 @@ msgstr ""
msgid "Behaviour"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
msgid "Usage"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
msgid "AI-powered change monitoring"
msgstr ""
@@ -2259,13 +2261,17 @@ msgid "Checked"
msgstr "Verificado"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last"
msgstr "Último"
msgid "Last Checked"
msgstr "Último Verificado"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Changed"
msgstr "Alterado"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Changed"
msgstr "Último Alterado"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "No web page change detection watches configured, please add a URL in the box above, or"
msgstr "Nenhum monitoramento configurado, adicione uma URL na caixa acima ou"
@@ -2314,18 +2320,10 @@ msgstr "Preço"
msgid "No information"
msgstr "Sem informações"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Checked"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html changedetectionio/templates/base.html
msgid "Checking now"
msgstr "Verificando agora"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Changed"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Queued"
msgstr "Enfileirado"
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: changedetection.io 0.53.6\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2026-04-26 20:03+1000\n"
"POT-Creation-Date: 1970-01-01 00:00+0000\n"
"PO-Revision-Date: 2026-04-10 20:38+0300\n"
"Last-Translator: \n"
"Language: tr\n"
@@ -802,6 +802,12 @@ msgstr "Geri"
msgid "Clear Snapshot History"
msgstr "Anlık Görüntü Geçmişini Temizle"
#. 'Usage' here means token consumption/cost stats for the AI provider, not a how-to guide
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
msgctxt "AI usage stats"
msgid "Usage"
msgstr "Kullanım"
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
msgid "Overview"
msgstr ""
@@ -818,10 +824,6 @@ msgstr ""
msgid "Behaviour"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
msgid "Usage"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
msgid "AI-powered change monitoring"
msgstr ""
@@ -2264,13 +2266,17 @@ msgid "Checked"
msgstr "Kontrol Edildi"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last"
msgstr "Son"
msgid "Last Checked"
msgstr "Son Kontrol Edildi"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Changed"
msgstr "Değiştirildi"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Changed"
msgstr "Son Değiştirildi"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "No web page change detection watches configured, please add a URL in the box above, or"
msgstr "Yapılandırılmış web sayfası değişiklik tespiti izleyicisi yok, lütfen yukarıdaki kutuya bir URL ekleyin veya"
@@ -2319,18 +2325,10 @@ msgstr "Fiyat"
msgid "No information"
msgstr "Bilgi yok"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Checked"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html changedetectionio/templates/base.html
msgid "Checking now"
msgstr "Şimdi kontrol ediliyor"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Changed"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Queued"
msgstr "Sırada"
@@ -6,7 +6,7 @@ msgid ""
msgstr ""
"Project-Id-Version: changedetection.io\n"
"Report-Msgid-Bugs-To: https://github.com/dgtlmoon/changedetection.io\n"
"POT-Creation-Date: 2026-04-26 20:03+1000\n"
"POT-Creation-Date: 1970-01-01 00:00+0000\n"
"PO-Revision-Date: 2026-02-19 12:30+0100\n"
"Last-Translator: \n"
"Language: uk\n"
@@ -780,6 +780,12 @@ msgstr "Назад"
msgid "Clear Snapshot History"
msgstr "Очистити історію знімків"
#. 'Usage' here means token consumption/cost stats for the AI provider, not a how-to guide
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
msgctxt "AI usage stats"
msgid "Usage"
msgstr "Використання"
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
msgid "Overview"
msgstr ""
@@ -796,10 +802,6 @@ msgstr ""
msgid "Behaviour"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
msgid "Usage"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
msgid "AI-powered change monitoring"
msgstr ""
@@ -2241,13 +2243,17 @@ msgid "Checked"
msgstr "Перевірено"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last"
msgstr "Останній"
msgid "Last Checked"
msgstr "Останній Перевірено"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Changed"
msgstr "Змінено"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Changed"
msgstr "Останній Змінено"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "No web page change detection watches configured, please add a URL in the box above, or"
msgstr "Немає налаштованих завдань для відстеження змін, будь ласка, додайте URL у поле вище або"
@@ -2296,18 +2302,10 @@ msgstr "Ціна"
msgid "No information"
msgstr "Немає інформації"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Checked"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html changedetectionio/templates/base.html
msgid "Checking now"
msgstr "Перевірка..."
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Changed"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Queued"
msgstr "В черзі"
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2026-04-26 20:03+1000\n"
"POT-Creation-Date: 1970-01-01 00:00+0000\n"
"PO-Revision-Date: 2026-01-18 21:31+0800\n"
"Last-Translator: 吾爱分享 <admin@wuaishare.cn>\n"
"Language: zh\n"
@@ -771,6 +771,12 @@ msgstr "返回"
msgid "Clear Snapshot History"
msgstr "清除快照历史"
#. 'Usage' here means token consumption/cost stats for the AI provider, not a how-to guide
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
msgctxt "AI usage stats"
msgid "Usage"
msgstr "使用量"
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
msgid "Overview"
msgstr ""
@@ -787,10 +793,6 @@ msgstr ""
msgid "Behaviour"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
msgid "Usage"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
msgid "AI-powered change monitoring"
msgstr ""
@@ -2216,13 +2218,17 @@ msgid "Checked"
msgstr "检查"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last"
msgstr "最近"
msgid "Last Checked"
msgstr "最近检查"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Changed"
msgstr "变更"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Changed"
msgstr "最近变更"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "No web page change detection watches configured, please add a URL in the box above, or"
msgstr "尚未配置网站监控项,请在上方输入 URL 或"
@@ -2271,18 +2277,10 @@ msgstr "价格"
msgid "No information"
msgstr "暂无信息"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Checked"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html changedetectionio/templates/base.html
msgid "Checking now"
msgstr "正在检查"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Changed"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Queued"
msgstr "队列中"
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2026-04-26 20:03+1000\n"
"POT-Creation-Date: 1970-01-01 00:00+0000\n"
"PO-Revision-Date: 2026-01-15 12:00+0800\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: zh_Hant_TW\n"
@@ -770,6 +770,12 @@ msgstr "返回"
msgid "Clear Snapshot History"
msgstr "清除快照歷史記錄"
#. 'Usage' here means token consumption/cost stats for the AI provider, not a how-to guide
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
msgctxt "AI usage stats"
msgid "Usage"
msgstr "使用量"
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
msgid "Overview"
msgstr ""
@@ -786,10 +792,6 @@ msgstr ""
msgid "Behaviour"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
msgid "Usage"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
msgid "AI-powered change monitoring"
msgstr ""
@@ -1427,7 +1429,7 @@ msgstr "已將 1 個監測任務排入複查佇列。"
#: changedetectionio/blueprint/ui/__init__.py
#, python-brace-format
msgid "Queued {} watches for rechecking ({} already queued or running)."
msgstr "已將 {} 個監測任務排入複查佇列。"
msgstr "已將 {} 個監測任務排入複查佇列{} 個已在佇列中或正在執行)。"
#: changedetectionio/blueprint/ui/__init__.py
#, python-brace-format
@@ -2215,13 +2217,17 @@ msgid "Checked"
msgstr "檢查"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last"
msgstr "上次"
msgid "Last Checked"
msgstr "上次檢查"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Changed"
msgstr "變更"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Changed"
msgstr "上次變更"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "No web page change detection watches configured, please add a URL in the box above, or"
msgstr "未設定網站監測任務,請在上方欄位新增 URL,或"
@@ -2270,18 +2276,10 @@ msgstr "價格"
msgid "No information"
msgstr "無資訊"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Checked"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html changedetectionio/templates/base.html
msgid "Checking now"
msgstr "正在檢查"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Changed"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Queued"
msgstr "已排程"
+9 -3
View File
@@ -99,6 +99,12 @@ pytest-mock ~=3.15
# OpenAPI validation support
openapi-core[flask] ~= 0.23
# openapi-spec-validator (pulled in by openapi-core) requires jsonschema>=4.24.0.
# litellm 1.83.11.83.14 exact-pin jsonschema==4.23.0, which is below that floor —
# the two can never coexist. Without this pin, pip walks back through ~14 litellm
# patch releases before finding 1.83.0 (jsonschema>=4.23.0,<5.0.0, accepts 4.24.x).
# Pinning >=4.24.0 here lets the resolver reject incompatible litellm versions immediately.
jsonschema>=4.24.0,<5.0.0
loguru
@@ -120,7 +126,7 @@ greenlet >= 3.0.3
# Default SOCKETIO_MODE=threading is recommended for better compatibility
gevent
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.
referencing==0.37.0 # jsonschema-path>=0.4.x allows <0.38.0; 0.37.0 is current latest
# For conditions
panzi-json-logic
@@ -131,7 +137,7 @@ price-parser
# Lightweight MIME type detection (saves ~14MB memory vs python-magic/libmagic)
# Used for detecting correct favicon type and content-type detection
puremagic
puremagic<2.0 # 2.x requires Python >=3.12; unpin once 3.10/3.11 support is dropped
# Scheduler - Windows seemed to miss a lot of default timezone info (even "UTC" !)
tzdata
@@ -141,7 +147,7 @@ tzdata
pluggy ~= 1.6
# LLM intent-based change evaluation (multi-provider via litellm)
litellm>=1.40.0
litellm>=1.40.0,<1.83.1 # 1.83.11.83.14 exact-pin jsonschema==4.23.0, conflicting with openapi-spec-validator's >=4.24.0 floor; re-evaluate when litellm fixes this
# BM25 relevance trimming for large snapshots (pure Python, no ML)
rank-bm25>=0.2.2
+1
View File
@@ -22,6 +22,7 @@ domain = messages
# Options for consistent formatting
width = 120
no_fuzzy_matching = true
ignore_pot_creation_date = true
ignore_obsolete = true
[compile_catalog]