mirror of
https://github.com/dgtlmoon/changedetection.io.git
synced 2026-05-06 09:41:28 +00:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| aef133351d | |||
| cf31823d53 | |||
| aadf8df7ae | |||
| 44ac324a41 | |||
| 7831a499b2 | |||
| e25387f588 | |||
| e4bc048280 | |||
| 2839a4276e | |||
| 5759a28d89 |
@@ -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
|
||||
@@ -31,6 +31,42 @@ jobs:
|
||||
echo "Checking $f"
|
||||
msgfmt --check-format -o /dev/null "$f"
|
||||
done
|
||||
- name: Lint .po/.pot files with dennis (errors only)
|
||||
run: |
|
||||
pip install "$(grep -E '^dennis ?>=' requirements.txt)"
|
||||
dennis-cmd lint --errorsonly changedetectionio/translations/
|
||||
- name: Lint .pot template with dennis (baseline-limited warnings)
|
||||
# BASELINE: dennis warnings present when this check was introduced. Ratchet down only.
|
||||
# Each time a warning is fixed, lower BASELINE_LIMIT to lock in the improvement.
|
||||
# Once it reaches 0, replace this step with a strict (`warnings > 0` fails) check,
|
||||
# matching the `.po` step.
|
||||
env:
|
||||
BASELINE_LIMIT: 12
|
||||
run: |
|
||||
output=$(dennis-cmd lint changedetectionio/translations/messages.pot)
|
||||
echo "$output"
|
||||
warnings=$(echo "$output" | awk '/Warnings:/ {print $NF; exit}')
|
||||
if (( ${warnings:-0} > BASELINE_LIMIT )); then
|
||||
echo "ERROR: ${warnings} dennis warning(s) exceed baseline of ${BASELINE_LIMIT} in messages.pot"
|
||||
echo "Fix the new warning(s). BASELINE_LIMIT may only ratchet downward."
|
||||
exit 1
|
||||
fi
|
||||
- name: Lint .po files with dennis (warnings)
|
||||
# W302 (unchanged) and W303 (html mismatch) are excluded due to
|
||||
# high false-positive rate in this codebase:
|
||||
# many msgstrs intentionally match msgid (units like "Mb", proper nouns),
|
||||
# and many msgids contain literal "<title>"/"<description>" text that isn't actual HTML.
|
||||
run: |
|
||||
output=$(dennis-cmd lint \
|
||||
--excluderules=W302,W303 \
|
||||
changedetectionio/translations/*/LC_MESSAGES/messages.po)
|
||||
echo "$output"
|
||||
warnings=$(echo "$output" | awk '/Total number of warnings:/ {print $NF; exit}')
|
||||
if (( ${warnings:-0} > 0 )); then
|
||||
echo "ERROR: ${warnings} dennis warning(s) detected in .po files"
|
||||
echo "Fix the warning(s)."
|
||||
exit 1
|
||||
fi
|
||||
- name: Check translation catalog is up-to-date
|
||||
run: |
|
||||
pip install "$(grep -E '^babel==' requirements.txt)"
|
||||
|
||||
+5
-1
@@ -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"
|
||||
|
||||
@@ -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.55.2'
|
||||
__version__ = '0.55.3'
|
||||
|
||||
from changedetectionio.strtobool import strtobool
|
||||
from json.decoder import JSONDecodeError
|
||||
|
||||
@@ -103,7 +103,28 @@ 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
|
||||
|
||||
# Resolved processor config: tag override wins over watch-level config (mirrors restock processor logic)
|
||||
import json
|
||||
_restock_path = os.path.join(watch_obj.data_dir, 'restock_diff.json') if watch_obj.data_dir else None
|
||||
restock_config = {}
|
||||
if _restock_path and os.path.isfile(_restock_path):
|
||||
try:
|
||||
with open(_restock_path, 'r', encoding='utf-8') as _f:
|
||||
restock_config = json.load(_f).get('restock_diff') or {}
|
||||
except (json.JSONDecodeError, IOError) as e:
|
||||
logger.warning(f"Failed to read restock_diff.json for watch {uuid}: {e}")
|
||||
restock_source = 'watch'
|
||||
tags = self.datastore.data['settings']['application'].get('tags', {})
|
||||
for tag_uuid in (watch_obj.get('tags') or []):
|
||||
tag = tags.get(tag_uuid, {})
|
||||
if tag.get('overrides_watch'):
|
||||
restock_config = dict(tag.get('processor_config_restock_diff') or {})
|
||||
restock_source = f'tag:{tag_uuid}'
|
||||
break
|
||||
watch['processor_config_restock_diff'] = restock_config
|
||||
watch['processor_config_restock_diff_source'] = restock_source
|
||||
|
||||
return watch
|
||||
|
||||
|
||||
@@ -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'])
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>{{ '{{change_datetime}}' }}</code></td>
|
||||
<td>{{ _('Date/time of the change, accepts format=, change_datetime(format=\'%A\')\', default is \'%Y-%m-%d %H:%M:%S %Z\'') }}</td>
|
||||
<td>{{ _("Date/time of the change, accepts format=, %(call)s, default is '%(default)s'", call="change_datetime(format='%A')", default="%Y-%m-%d %H:%M:%S %Z") }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>{{ '{{diff_url}}' }}</code></td>
|
||||
|
||||
@@ -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(
|
||||
@@ -903,6 +905,101 @@ def test_api_restock_processor_config(client, live_server, measure_memory_usage,
|
||||
delete_all_watches(client)
|
||||
|
||||
|
||||
def test_api_watch_get_returns_resolved_restock_processor_config(client, live_server, measure_memory_usage, datastore_path):
|
||||
"""
|
||||
GET /api/v1/watch/{uuid} must include processor_config_restock_diff and
|
||||
processor_config_restock_diff_source in the response.
|
||||
|
||||
Two cases:
|
||||
- Watch-level config only: source == 'watch', config reflects the watch's own settings.
|
||||
- Tag with overrides_watch=True: source == 'tag:<uuid>', config reflects the tag's settings
|
||||
regardless of what the watch itself has stored.
|
||||
"""
|
||||
api_key = live_server.app.config['DATASTORE'].data['settings']['application'].get('api_access_token')
|
||||
test_url = url_for('test_endpoint', _external=True)
|
||||
|
||||
# --- Case 1: watch-level config, no tag override ---
|
||||
res = client.post(
|
||||
url_for("createwatch"),
|
||||
data=json.dumps({
|
||||
"url": test_url,
|
||||
"processor": "restock_diff",
|
||||
"processor_config_restock_diff": {
|
||||
"in_stock_processing": "all_changes",
|
||||
"follow_price_changes": False,
|
||||
"price_change_min": 1.23,
|
||||
}
|
||||
}),
|
||||
headers={'content-type': 'application/json', 'x-api-key': api_key},
|
||||
)
|
||||
assert res.status_code == 201
|
||||
watch_uuid = res.json.get('uuid')
|
||||
|
||||
res = client.get(url_for("watch", uuid=watch_uuid), headers={'x-api-key': api_key})
|
||||
assert res.status_code == 200
|
||||
data = res.json
|
||||
assert 'processor_config_restock_diff' in data, "GET should include processor_config_restock_diff"
|
||||
assert 'processor_config_restock_diff_source' in data, "GET should include processor_config_restock_diff_source"
|
||||
assert data['processor_config_restock_diff_source'] == 'watch'
|
||||
assert data['processor_config_restock_diff'].get('in_stock_processing') == 'all_changes'
|
||||
assert data['processor_config_restock_diff'].get('follow_price_changes') == False
|
||||
assert data['processor_config_restock_diff'].get('price_change_min') == 1.23
|
||||
|
||||
# --- Case 2: tag with overrides_watch=True overrides watch-level config ---
|
||||
res = client.post(
|
||||
url_for("tag"),
|
||||
data=json.dumps({
|
||||
"title": "Override tag",
|
||||
"overrides_watch": True,
|
||||
"processor_config_restock_diff": {
|
||||
"in_stock_processing": "in_stock_only",
|
||||
"follow_price_changes": True,
|
||||
"price_change_min": 999.0,
|
||||
}
|
||||
}),
|
||||
headers={'content-type': 'application/json', 'x-api-key': api_key},
|
||||
)
|
||||
assert res.status_code == 201
|
||||
tag_uuid = res.json.get('uuid')
|
||||
|
||||
# Assign the tag to the watch
|
||||
res = client.put(
|
||||
url_for("watch", uuid=watch_uuid),
|
||||
data=json.dumps({"tags": [tag_uuid]}),
|
||||
headers={'content-type': 'application/json', 'x-api-key': api_key},
|
||||
)
|
||||
assert res.status_code == 200
|
||||
|
||||
res = client.get(url_for("watch", uuid=watch_uuid), headers={'x-api-key': api_key})
|
||||
assert res.status_code == 200
|
||||
data = res.json
|
||||
assert data['processor_config_restock_diff_source'] == f'tag:{tag_uuid}', \
|
||||
"Source should show the overriding tag UUID"
|
||||
assert data['processor_config_restock_diff'].get('in_stock_processing') == 'in_stock_only', \
|
||||
"Tag config should override watch-level config"
|
||||
assert data['processor_config_restock_diff'].get('price_change_min') == 999.0, \
|
||||
"Tag price_change_min should override watch-level value"
|
||||
|
||||
# processor_config_restock_diff is readonly — PUT attempts to set the resolved field should be
|
||||
# silently ignored (the field is stripped before the watch is updated, same as other readOnly fields)
|
||||
res = client.put(
|
||||
url_for("watch", uuid=watch_uuid),
|
||||
data=json.dumps({"processor_config_restock_diff": {"in_stock_processing": "off"}}),
|
||||
headers={'content-type': 'application/json', 'x-api-key': api_key},
|
||||
)
|
||||
# PUT with processor_config_restock_diff is still valid (sets watch-level config),
|
||||
# but the GET response continues to reflect the tag override
|
||||
assert res.status_code == 200
|
||||
res = client.get(url_for("watch", uuid=watch_uuid), headers={'x-api-key': api_key})
|
||||
data = res.json
|
||||
assert data['processor_config_restock_diff_source'] == f'tag:{tag_uuid}', \
|
||||
"Tag override should still be active after PUT"
|
||||
assert data['processor_config_restock_diff'].get('in_stock_processing') == 'in_stock_only', \
|
||||
"Tag config should still win after PUT attempted to change watch-level config"
|
||||
|
||||
delete_all_watches(client)
|
||||
|
||||
|
||||
def test_api_conflict_UI_password(client, live_server, measure_memory_usage, datastore_path):
|
||||
|
||||
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
|
||||
@@ -3430,7 +3430,7 @@ msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
#, python-format
|
||||
msgid "Date/time of the change, accepts format=, change_datetime(format='%A')', default is '%Y-%m-%d %H:%M:%S %Z'"
|
||||
msgid "Date/time of the change, accepts format=, %(call)s, default is '%(default)s'"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
|
||||
@@ -3484,7 +3484,7 @@ msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
#, python-format
|
||||
msgid "Date/time of the change, accepts format=, change_datetime(format='%A')', default is '%Y-%m-%d %H:%M:%S %Z'"
|
||||
msgid "Date/time of the change, accepts format=, %(call)s, default is '%(default)s'"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
|
||||
@@ -3424,7 +3424,7 @@ msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
#, python-format
|
||||
msgid "Date/time of the change, accepts format=, change_datetime(format='%A')', default is '%Y-%m-%d %H:%M:%S %Z'"
|
||||
msgid "Date/time of the change, accepts format=, %(call)s, default is '%(default)s'"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
|
||||
@@ -3424,7 +3424,7 @@ msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
#, python-format
|
||||
msgid "Date/time of the change, accepts format=, change_datetime(format='%A')', default is '%Y-%m-%d %H:%M:%S %Z'"
|
||||
msgid "Date/time of the change, accepts format=, %(call)s, default is '%(default)s'"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
|
||||
@@ -3495,7 +3495,7 @@ msgstr "La URL de la página de vista previa generada por changetection.io."
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
#, python-format
|
||||
msgid "Date/time of the change, accepts format=, change_datetime(format='%A')', default is '%Y-%m-%d %H:%M:%S %Z'"
|
||||
msgid "Date/time of the change, accepts format=, %(call)s, default is '%(default)s'"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
|
||||
@@ -3437,7 +3437,7 @@ msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
#, python-format
|
||||
msgid "Date/time of the change, accepts format=, change_datetime(format='%A')', default is '%Y-%m-%d %H:%M:%S %Z'"
|
||||
msgid "Date/time of the change, accepts format=, %(call)s, default is '%(default)s'"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
|
||||
@@ -3426,7 +3426,7 @@ msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
#, python-format
|
||||
msgid "Date/time of the change, accepts format=, change_datetime(format='%A')', default is '%Y-%m-%d %H:%M:%S %Z'"
|
||||
msgid "Date/time of the change, accepts format=, %(call)s, default is '%(default)s'"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
|
||||
Binary file not shown.
@@ -3443,8 +3443,8 @@ msgstr "changedetection.io が生成したプレビューページのURL。"
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
#, python-format
|
||||
msgid "Date/time of the change, accepts format=, change_datetime(format='%A')', default is '%Y-%m-%d %H:%M:%S %Z'"
|
||||
msgstr "変更の日時。format= を受け付けます(例: change_datetime(format='%A'))。デフォルトは '%Y-%m-%d %H:%M:%S %Z'。"
|
||||
msgid "Date/time of the change, accepts format=, %(call)s, default is '%(default)s'"
|
||||
msgstr "変更の日時。format= を受け付けます(例: %(call)s)。デフォルトは '%(default)s'。"
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "The URL of the diff output for the watch."
|
||||
|
||||
Binary file not shown.
@@ -3434,8 +3434,8 @@ msgstr "changedetection.io가 생성한 미리보기 페이지의 URL입니다."
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
#, python-format
|
||||
msgid "Date/time of the change, accepts format=, change_datetime(format='%A')', default is '%Y-%m-%d %H:%M:%S %Z'"
|
||||
msgstr "변경 발생 일시입니다. format= 인자를 사용할 수 있으며 change_datetime(format='%A') 형식입니다. 기본값은 '%Y-%m-%d %H:%M:%S %Z'입니다."
|
||||
msgid "Date/time of the change, accepts format=, %(call)s, default is '%(default)s'"
|
||||
msgstr "변경 발생 일시입니다. format= 인자를 사용할 수 있으며 %(call)s 형식입니다. 기본값은 '%(default)s'입니다."
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "The URL of the diff output for the watch."
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: changedetection.io 0.55.2\n"
|
||||
"Project-Id-Version: changedetection.io 0.55.3\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2026-04-28 15:22+1000\n"
|
||||
"POT-Creation-Date: 2026-04-28 16:31+0900\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"
|
||||
@@ -3423,7 +3423,7 @@ msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
#, python-format
|
||||
msgid "Date/time of the change, accepts format=, change_datetime(format='%A')', default is '%Y-%m-%d %H:%M:%S %Z'"
|
||||
msgid "Date/time of the change, accepts format=, %(call)s, default is '%(default)s'"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
|
||||
Binary file not shown.
@@ -3472,8 +3472,8 @@ msgstr "A URL da página de pré-visualização gerada pelo changedetection.io."
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
#, python-format
|
||||
msgid "Date/time of the change, accepts format=, change_datetime(format='%A')', default is '%Y-%m-%d %H:%M:%S %Z'"
|
||||
msgstr "Data/hora da mudança, aceita format=, change_datetime(format='%A')', o padrão é '%Y-%m-%d %H:%M:%S %Z'"
|
||||
msgid "Date/time of the change, accepts format=, %(call)s, default is '%(default)s'"
|
||||
msgstr "Data/hora da mudança, aceita format=, %(call)s, o padrão é '%(default)s'"
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "The URL of the diff output for the watch."
|
||||
|
||||
@@ -3477,7 +3477,7 @@ msgstr "changedetection.io tarafından oluşturulan önizleme sayfasının URL's
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
#, python-format
|
||||
msgid "Date/time of the change, accepts format=, change_datetime(format='%A')', default is '%Y-%m-%d %H:%M:%S %Z'"
|
||||
msgid "Date/time of the change, accepts format=, %(call)s, default is '%(default)s'"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
|
||||
@@ -3454,7 +3454,7 @@ msgstr "URL сторінки попереднього перегляду, ств
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
#, python-format
|
||||
msgid "Date/time of the change, accepts format=, change_datetime(format='%A')', default is '%Y-%m-%d %H:%M:%S %Z'"
|
||||
msgid "Date/time of the change, accepts format=, %(call)s, default is '%(default)s'"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
|
||||
@@ -3429,7 +3429,7 @@ msgstr "changedetection.io 生成的预览页面 URL。"
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
#, python-format
|
||||
msgid "Date/time of the change, accepts format=, change_datetime(format='%A')', default is '%Y-%m-%d %H:%M:%S %Z'"
|
||||
msgid "Date/time of the change, accepts format=, %(call)s, default is '%(default)s'"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
|
||||
@@ -3428,7 +3428,7 @@ msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
#, python-format
|
||||
msgid "Date/time of the change, accepts format=, change_datetime(format='%A')', default is '%Y-%m-%d %H:%M:%S %Z'"
|
||||
msgid "Date/time of the change, accepts format=, %(call)s, default is '%(default)s'"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
|
||||
+32
-1
@@ -28,7 +28,7 @@ info:
|
||||
|
||||
For example: `x-api-key: YOUR_API_KEY`
|
||||
|
||||
version: 0.1.6
|
||||
version: 0.1.7
|
||||
contact:
|
||||
name: ChangeDetection.io
|
||||
url: https://github.com/dgtlmoon/changedetection.io
|
||||
@@ -727,6 +727,37 @@ components:
|
||||
description: Number of history snapshots available
|
||||
readOnly: true
|
||||
x-computed: true
|
||||
processor_config_restock_diff:
|
||||
type: object
|
||||
readOnly: true
|
||||
x-computed: true
|
||||
description: |
|
||||
Resolved restock/price processor config for this watch.
|
||||
If a tag with `overrides_watch: true` is assigned to this watch, the tag's config is
|
||||
returned here instead of the watch's own config. Use `processor_config_restock_diff_source`
|
||||
to determine where the config originated.
|
||||
properties:
|
||||
in_stock_processing:
|
||||
type: string
|
||||
enum: [in_stock_only, all_changes, 'off']
|
||||
follow_price_changes:
|
||||
type: boolean
|
||||
price_change_min:
|
||||
type: [number, 'null']
|
||||
price_change_max:
|
||||
type: [number, 'null']
|
||||
price_change_threshold_percent:
|
||||
type: [number, 'null']
|
||||
minimum: 0
|
||||
maximum: 100
|
||||
processor_config_restock_diff_source:
|
||||
type: string
|
||||
readOnly: true
|
||||
x-computed: true
|
||||
description: |
|
||||
Indicates the origin of `processor_config_restock_diff`.
|
||||
- `watch`: config comes from the watch itself
|
||||
- `tag:<uuid>`: config is overridden by the tag with the given UUID
|
||||
|
||||
CreateWatch:
|
||||
allOf:
|
||||
|
||||
+23
-7
File diff suppressed because one or more lines are too long
+11
-4
@@ -51,7 +51,7 @@ linkify-it-py
|
||||
# - Requires extra wheel for rPi, adds build time for arm/v8 which is not in piwheels
|
||||
# Pinned to 44.x for ARM compatibility and sslyze compatibility (sslyze requires <45) and (45.x may not have pre-built ARM wheels)
|
||||
# Also pinned because dependabot wants specific versions
|
||||
cryptography==44.0.0
|
||||
cryptography==47.0.0
|
||||
|
||||
# apprise mqtt https://github.com/dgtlmoon/changedetection.io/issues/315
|
||||
# use any version other than 2.0.x due to https://github.com/eclipse/paho.mqtt.python/issues/814
|
||||
@@ -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.1–1.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.1–1.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
|
||||
|
||||
@@ -150,6 +156,7 @@ psutil==7.2.2
|
||||
|
||||
ruff >= 0.11.2
|
||||
pre_commit >= 4.2.0
|
||||
dennis >= 1.2.0
|
||||
|
||||
# For events between checking and socketio updates
|
||||
blinker
|
||||
|
||||
Reference in New Issue
Block a user