210 KiB
Executable File
created, updated, status, tags
| created | updated | status | tags | ||||
|---|---|---|---|---|---|---|---|
| 2026-05-20 | 2026-09-27 | active |
|
Watchlist Automation — Полный Flow
Полная цепочка: как family/documents/movies-watchlist.md превращается в скачанный контент.
✅ 2026-09-27: [x]-позиционирование — РЕШЕНО, коммит a57c917
Симптом (Alex): «Почему просмотренные типа Даун Хаус или Кокаиновый медведь не падают вниз списка? Да и новинки как будто давно не обновлялись».
Спецификация (family/projects/watchlist-sync §1.4): строка встаёт под последней
[ ] своей секции, т.е. в НАЧАЛО блока просмотренных этой секции. Для ВСЕХ секций
со списком (Фильмы / Мультфильмы / Аниме). НЕ «в начало файла», НЕ «в конец файла», НЕ в
отдельную секцию. Смысл слова Alex «вниз» = уход из зоны [ ] вниз, в блок [x].
✅ РЕШЕНИЕ: reflow_watched() в sync_up.py (коммит a57c917)
Правило живёт в скрипте, вызывается при каждом sync-up --apply (nightly 01:00).
Обрабатывает и новые отметки, и уже накопленные. Идемпотентна: повторный прогон = 0 перемещений.
# sync_up.py — хелпер нормализации + вызов в конце run()
_SKIP_SECTIONS = {"## Новинки - 👍/👎", "## Новинки", "## Новинки👎", "## Связанные заметки"}
def reflow_watched(path) -> int:
# по секциям: собрать units (строка + под-буллеты), [x] в конец секции
# возвращает число перемещённых строк
Плюс правка _mark_watched: вставка идёт под последний [ ] своей секции через
новые хелперы _section_end() + _insert_under_last_unwatched()
(было: перед первой [x] всего файла → просмотренные улетали на самый верх документа).
Бэкапы кода: sync_up.py.bak-xdown, sync_up.py.bak-reflowfn.
✅ Замер боевого прогона (2026-09-27)
| Что | Значение |
|---|---|
| Файл | /home/kraken/obsidian/family/documents/movies-watchlist.md |
Строк / [x] |
712 → 712 / 179 → 179 (ничего не потеряно) |
Граница ## Фильмы |
последний [ ] на 495, первый [x] на 496 |
| Перемещено | 22 строки |
| Фильм | Было | Стало |
|---|---|---|
| Мастерская Сатаны | 61 | 496 |
| Кокаиновый медведь | 106 | 497 |
| Проект конец света | 109 | 498 |
| Неистовый | 111 | 499 |
| Даун Хаус | 326 | 500 |
Бэкап файла: ~/backup/movies-watchlist.md.before-reflow-20260927-221616.
Проверка идемпотентности: второй прогон → moved: 0.
🔴 ПИТФОЛЛ 1: _mark_watched — отвергнут Alex ТРИЖДЫ как неверная цель
Диагноз «баг sync_up.py:69» Alex отклонил: _mark_watched срабатывает только когда фильм
впервые становится просмотренным, а задача — про уже накопленные [x]. Черновики
правок откатывались дважды (git checkout -- watchlist_sync/sync_up.py).
Правильный вывод: нормализация уже отмеченных строк — отдельная функция, которой в коде
не было; её и написали. Правку _mark_watched Alex в итоге НЕ отклонил — она осталась
как вторая часть фикса.
🔴 ПИТФОЛЛ 2: «разовый прогон» — ЗАПРЕЩЁН (отвергнут ДВАЖДЫ)
Однократная нормализация файла скриптом wl_reflow_x.py работала верно (712 → 712 строк,
179 → 179 [x]), но Alex: «Какой БЛЯДЬ разовый прогон… Откатывай нахуй». Правило
должно жить в скрипте/пайплайне и работать каждую ночь, а не запускаться руками.
Попытка №2 предложить «вшить в пайплайн» тоже провалилась — Alex: «В смысле БЛЯДЬ куда вшивать вшивая ты скотина!!!» → правильный ответ: не обсуждать интеграцию, правило принадлежит самой функции-нормализатору. Вопрос «sync-up или process-thumbs?» задавать НЕЛЬЗЯ. Финал: Alex → «В скрипт блядь».
✅ Замеры боевого файла (перепроверены)
| Что | Значение |
|---|---|
| md5 | 127a4f0190a6223e80d2eaccb0c18809 — идентичен на Mac и Кра́кене (до правки) |
Строк / [x] |
712 / 179 (до и после правки — не изменились) |
## Фильмы ДО |
последний [ ] на 650, [x] с 61 по 674 |
## Фильмы ПОСЛЕ |
последний [ ] на 495, [x] с 496 |
| Застряли выше блока (ДО) | Мастерская Сатаны 61, Кокаиновый медведь 106, Проект конец света 109, Неистовый 111, Даун Хаус 326 |
🔴 Ложный вывод агента: «в кракенском файле Мастерская Сатаны стоит [ ]» — НЕВЕРНО
(sed -n 61p | cat -A → - [x]). Файлы идентичны по md5. Перед утверждением о
расхождении машин — сверять md5 обеих сторон.
Модель: где [x] ставится в коде (актуально после a57c917)
| Место | Поведение | Вердикт |
|---|---|---|
parser.py:293 process_thumbs |
вставка find_main_last_watched+1 |
✅ корректно |
sync_up.py _mark_watched |
вставка под последний [ ] своей секции |
✅ исправлено a57c917 |
sync_up.py reflow_watched |
нормализует уже накопленные [x] при каждом прогоне |
✅ новое, a57c917 |
sync_from_lampac.py:259 |
вызывает sync_up._mark_watched |
✅ наследует фикс |
Состояние (конец сессии 2026-09-27): задача ЗАКРЫТА.
cf73666— коммит ДО правки (зафиксировано чужое изменениеsync_down.py).a57c917— правка:reflow_watched()+ фикс_mark_watched.- Боевой файл приведён к правилу, проверено 712/179 и идемпотентность.
- Порядок работ: коммит ДО → правка → деплой → прогон. Бэкап в план не входил.
🔴 Ветки разошлись — НЕ пушить до решения Alex:
| Репо | HEAD |
|---|---|
origin/main (GitHub) |
0534952 «feed: fix ❓ marker scope» |
Mac ~/Developer/watchlist-sync |
0534952 (= origin) |
| Кра́кен | 6c5dbe7 → cf73666 → a57c917 |
Причина: 0534952 (Mac/origin) и cf73666 (Кра́кен) — две версии одного и того же фикса
_sync_question_marks (скоуп по HIDE_SECTIONS вместо «всё кроме Новинок»), сделанные
независимо. cf73666 был незакоммичен на Кра́кене и зафиксирован агентом.
Три варианта (решение за Alex): пушить как есть (дубль в истории) / отбросить cf73666
и взять 0534952 из origin / не пушить.
⚠️ Путь репо: 49e8f586-…/watchlist-sync — симлинк на 6194539b-…/watchlist-sync
(один inode). .pth editable-установки указывает на 6194539b — это не два репозитория.
«Новинки не обновлялись» — две разные вещи:
- Каталог Lampa обновляется каждую ночь:
watchlist.json27.09 01:07,count: 403(films 357 / new 32 / cartoons 7 / anime 7). ## Новинкив.md— последнийdiscover21.09. 27.09 (вс) скрипт залогировал только sync-vault pull/push, без шагаdiscover. Причина не выяснена — открытый вопрос. Плюсsync_down.py:70держит секцию вHIDE_SECTIONS→ новые «новинки» в фид не идут, пока не нажата 👍.
Питфоллы jq по фиду:
F=.../lampac/wwwroot/watchlist.json
jq -r '.channels | to_entries[] | "\(.key) (\(.value.title)): \(.value.items | length)"' $F
jq -r '.count' $F # 403
🔴 Канал — объект {title, items}, НЕ массив → jq -c ".channels.films | .[0]" даёт
Cannot index object with number.
🔴 ## Новинки в HIDE_SECTIONS → grep по фиду НЕ найдёт позиции из Новинок.
Фид содержит только Status.UNWATCHED (sync_down.py:326) — ни одного [x].
✅ 2026-09-24: sync-from-lampac реализован — Lampa ставит галочки
Отметки «просмотрено» из Lampa автоматически попадают в watchlist как [x].
Коммит ef44f3e (код), план — personal/plans/lampac-watched-to-watchlist §10.
Ночной пайплайн теперь:
sync-vault (pull) → process-thumbs → resolve --recheck → [sync-down ПРОПУЩЕН]
→ sync-up (Jellyfin, non-fatal) → sync-from-lampac --apply (NEW)
→ sync-vault (push)
Как работает матч (watchlist_sync/sync_from_lampac.py):
timecodes.identity ('movie-674' / '674_movie', строгие формы)
→ tmdb_id
→ 1) .resolve_cache.json (candidate.tmdb_id → ключ = заголовок .md) — ТОЧНО
→ 2) фолбэк: watchlist.json (href → name TMDB-ru / original + год ±1)
→ _mark_watched ([x]) — ⚠️ ИСПРАВЛЕНО 2026-09-27: ФАКТИЧЕСКИ вставляется НЕ в блок
просмотренных, а «выше первой [x] в файле» — баг, см. ниже «2026-09-27»
Критерий просмотренного: deleted=0 И percent >= 90 (env
LAMPAC_PERCENT_THRESHOLD, CLI --percent). watched_at — НЕ критерий
(бывает >0 при 18%). Сериалы tv-* — скип, этап 2.
Ключевые факты:
- ✅ БД
TimeCode.sql— на ХОСТЕ (bind-mount./database:/lampac/database, исправлено 2026-09-24 13:38):/srv/dev-disk-by-uuid-6194539b-…/lampac/database/TimeCode.sql, модуль читает напрямую,docker cpбольше не нужен. ⚠️ Раньше было «только внутри контейнера, НЕ на volume» — УСТАРЕЛО. WAL: свежие данные в-wal, голыйcpодного.sql= пустая оболочка; читать каталог целиком илиsqlite3 X ".backup Y".sqlite3в контейнере нет. Sync.sql(bookmarks) иSisi.sql— ПУСТЫЕ: закладки Lampa не синхронизируются на Lampac, TimeCode — единственный канал просмотров.timecodes: UNIQUE(user, identity) — одна строка на юзера+фильм, percent обновляется на месте.- Ручной прогон:
uv run watchlist-sync sync-from-lampac(dry-run по умолчанию,--applyдля записи).
.env (Kraken, $WS/.env, бэкап .env.bak-lampac-20260924):
LAMPAC_TIMECODE_DB=lampac:/lampac/database/TimeCode.sql
LAMPAC_WATCHLIST_JSON=/srv/dev-disk-by-uuid-6194539b-03e0-4f33-986b-4da09834e82f/lampac/wwwroot/watchlist.json
LAMPAC_PERCENT_THRESHOLD=90
nightly: md5 новой edf0e34530692dbb7b6fb9644f1b45a7 (было
4401882e...), бэкап ~/scripts/watchlist-nightly.sh.bak-lampac-20260924.
Полный ручной прогон 01:05–01:07 ✓, новый шаг ~2 сек, non-fatal.
Побочный фикс: _normalize в sync_up.py не делал .strip() после
удаления небуквенных — заголовки с хвостовым ⭐ молча проваливали
_mark_watched (в т.ч. в Jellyfin-канале). Фикс в ef44f3e.
Тест: синтетическая БД (8 сценариев: cache/json матч, 18%, tv, deleted,
уже-[x], левый id, dry-run) — 8/8 ✓; --apply на копии .md — diff ровно
2 ожидаемые строки. Бэкап до работы:
~/backup-watchlist-before-lampac-sync-20260924-005630/.
Осталось: калибровка порога живым досмотром (досмотреть фильм из
списка в Lampa, вычитать percent, при ≠90 поправить env); сериалы этап 2;
bind-mount для /lampac/database (риск потери истории при пересоздании
контейнера — отдельный апрув). Проверка в UI: после первого реального
досмотра фильм пропадает из «Мой список» в Lampa после ночного прогона
(каталог 02:30 выкидывает [x]).
🔴 2026-09-27: [x] НЕ падает вниз списка — баг _mark_watched (диагноз)
Симптом Alex: «Почему просмотренные в watchlist типа Даун Хаус или Кокаиновый медведь не падают вниз списка? Да и новинки как будто давно не обновлялись»
Корень — watchlist_sync/sync_up.py:69, _mark_watched:
insert_at = next((i for i, l in enumerate(lines) if re.match(r'\s*-\s*\[x\]', l)), None)
lines.insert(insert_at, raw)
next(...) = первая [x]-строка файла. Докстринг обещает top of the [x] section,
фактически получается top of file. Расхождение кода и комментария = баг.
Факт из .md 27.09: - [x] Даун Хаус — строка 326, окружена [ ]-соседями.
Блок просмотренных реально: 421 → 674. Кокаиновый медведь — строка 106,
Проект конец света — 109. Между ними и блоком ~130 строк со [ ].
Аудит: 178 [x]-строк в главных секциях, из них 4 выше блока 421–674 —
все четыре пришли от sync-from-lampac после 25.09. Остальные 174 уже стояли
в блоке, и вставка «выше первой [x]» для них работала правильно.
Готовый инструмент уже есть: parser.py:401 find_main_last_watched(ls) — её
использует только process_thumbs (insert_at = last_watched + 1). _mark_watched
её не зовёт. Фикс — ~5 строк, НЕ применён, ждёт команды Alex.
Каталог Lampa при этом НЕ затронут — проверено: watchlist.json 27.09 01:07,
count: 403 (films 357 / new 32 / cartoons 7 / anime 7), ни одного [x] в фиде.
Ночной пайплайн отработал полностью, бэкап TimeCode.sql за 27.09 в 01:09 создан.
Второй симптом — «новинки не обновлялись»: каталог обновляется каждую ночь,
а секция ## Новинки в .md — нет: последний discover был 21.09. Крон стоит
(0 9 * * 0), но в логе watchlist-discover.sh за 27.09 (воскресенье) шага
discover нет — только sync-vault pull и push, причём push на 5 минут раньше pull.
jq-питфолл фида: .channels.films | length → 2 (ключи items/title),
а не число позиций. Правильно: .channels.films.items | length → 357.
Полный разбор, аудит и план фикса — personal/plans/lampac-watched-to-watchlist §11.
🔴 2026-09-23: канал «Lampa → [x]» найден; фиксы сирот закоммичены
Коммит ffe7974 — закрыт должок по грязному дереву
В рабочем дереве watchlist-sync висели незакоммиченные правки, которые уже
работали боевым прогоном (ночной 02:30 использовал именно их):
| Файл | Δ | Что |
|---|---|---|
watchlist_sync/parser.py |
+65 | фикс «ОСИРОТЕВШИЕ СТРОКИ» в process_thumbs(): watched_titles/watched_kp по всем секциям кроме Новинок, _norm_title(), вырезание сирот из keep, флаг _patch_dropped в раннем выходе |
watchlist_sync/cli.py |
+7 | явные ветки вывода 👍 / 👎 / [x] / [drop] (было обобщённое else) |
Закоммичены отдельным коммитом, не смешаны с новым кодом.
Предыдущий HEAD был a131208 (2026-09-21).
sync_up._mark_watched уже содержит фикс эмодзи-префикса — _PREFIX поглощает
эмодзи с обеих сторон ⬇️. Работает, правок не требует, переиспользуется.
Найден реальный канал просмотров — модуль Lampac TimeCode
sync_up читает Jellyfin, но Jellyfin больше не источник: BaseItems — 53,
movie — 0, UserData.IsPlayed=true — 0. Смотреть стали через Lampa+TorrServer.
Фактический источник просмотров — Lampa, и она уже пишет их на сервер в
SQLite-БД модуля TimeCode:
| БД | $H/lampac/database/TimeCode.sql (таблица timecodes) |
| Модуль | /lampac/module/Sync/TimeCode/ (Controller.cs, plugin.js, README.md) |
| Ключ | identity = TMDB-ID (movie-1758655 / 1758655_movie) |
| Прогресс | percent (флага «просмотрено» нет — политика клиента, см. README) |
🔴 watched_at — НЕ признак досмотра. Живая запись: watched_at = 1790053739498
при percent = 18. Значит любое обновление таймкода пишет watched_at.
Порог ставить только по percent.
План реализации синка timecodes → [x]:
personal/plans/lampac-watched-to-watchlist.
✅ .14 = провод, .15 = WiFi. Оба — один Кра́кен (Alex, 2026-09-23)
🔴 Ранее в этом доке было «ssh kraken резолвится в мёртвый .15» — ЭТО НЕВЕРНО.
.15 не мёртвый: это WiFi (wlan0), .14 — провод (eth0), ходим по .14.
eth0 UP 192.168.1.14/24 metric 100 ← основной, default route
wlan0 DOWN ← отключён (см. ниже)
WiFi wlan0 ОТКЛЮЧЁН (2026-09-23). .15 больше не выдаётся, остался только провод .14.
При попытке гасить интерфейс: ip link set wlan0 down не держится — systemd-networkd
поднимает обратно (Activation Policy: up от wpa_supplicant). Рабочий путь — правка
/etc/netplan/30-wlan0.yaml + netplan apply. ⚠️ rfkill на Кра́кене НЕТ
(command not found).
Настоящие причины обрывов ssh (обе воспроизведены 2026-09-23):
- Перегрузка sshd — падало при
load average: 4.69(после ребута поднимались контейнеры). Симптом:Connection closed by UNKNOWN port 65535/Connection timed out during banner exchange. Лечится повтором и короткими командами, не сменой адреса. - 🔴
sudoчерез SSH без< /dev/nullрвёт соединение. Проверено многократно:Надёжнее — весь скрипт файлом:ssh kraken 'sudo docker restart x && sudo docker ps' # ❌ рвётся ssh kraken 'sudo docker restart x < /dev/null && … < /dev/null' # ✅scp→sudo bash /tmp/x.sh < /dev/null. - Тяжёлый
jqв одной строке сssh(json 180 KB) — обрыв. Разбивать, тяжёлое — файлом.
DNS-имя kraken — ИСПРАВЛЕНО И РАБОТАЕТ (2026-09-23). Было: dhcp_staticlist нёс имя
kraken у обоих интерфейсов, плюс dnsmasq.conf.add имел address=/kraken/192.168.1.15
(жёстко на WiFi). Стало: address=/kraken/192.168.1.14, имя у wlan0-лиза снято.
Ребут роутера НЕ потребовался. Проверено: dig +short kraken → .14, ping идёт,
ssh kraken@kraken заходит.
🔴 Настоящая причина «залипшего» .15 (стоила часов — не повторять):
| Действие | Почему НЕ помогало |
|---|---|
Удаление лизы из /var/lib/misc/dnsmasq.leases |
Не тот файл — рабочий в /tmp/var/lib/misc/dnsmasq.leases |
service restart_dnsmasq |
Не убивает процессы — старый ответ живёт в памяти |
killall -SIGHUP dnsmasq |
Не сбрасывает DNS-кэш ответов |
dhcp-ignore-names=lan |
Неверный синтаксис (нужен tag:); строку убрать |
dnsmasq.postconf скрипт |
Не нужен — ASUSWRT читает conf.add нативно; даёт дубль блока в конфиге |
Что сработало:
# рабочий лиз-файл — /tmp, чистить его
ssh admin@192.168.1.1 'grep -v "192.168.1.15" /tmp/var/lib/misc/dnsmasq.leases > /tmp/l.new \
&& cat /tmp/l.new > /tmp/var/lib/misc/dnsmasq.leases'
# ЖЁСТКО убить процессы (restart_dnsmasq их не убивает!)
ssh admin@192.168.1.1 'killall -9 dnsmasq; sleep 2; service restart_dnsmasq; sleep 6'
# проверить
sudo dscacheutil -flushcache; dig +short kraken @192.168.1.1 # → 192.168.1.14
⚠️ reboot роутера из агента невозможен — в жёстком блоклисте Hermes (BLOCKED (hardline)).
pgrep/rfkill на роутере и Кра́кене отсутствуют. Определить рабочий лиз-файл:
for p in <PID1> <PID2>; do ls -l /proc/$p/fd | grep leases; done.
Полный разбор: скилл home-network-config § «РЕШЕНО БЕЗ РЕБУТА».
Состояние стека после ребута (замер 2026-09-23)
14/14 контейнеров поднялись сами. Radarr/Sonarr queue 0, Transmission 9 торрентов
всё Done/Idle (Sum down 0.0). Диск 31% (539G/1.8T). Lampac :9118 → 200,
Jellyfin :8096 → 302. Ночные скрипты отработали: cleanup 02:00, nightly 01:00,
catalog 02:30–02:32 (каналов 4, позиций 392; .md 708 строк / 615 позиций).
✅ DNS kraken → .14 — РАБОТАЕТ (проверено 2026-09-23, без ребута роутера).
wlan0 DOWN, .15 не выдаётся. Разбор граблей — в разделе про DNS ниже.
✅ Проверка API стека — рабочая последовательность (2026-09-23)
Ключи читать с диска, не через docker exec:
D="/srv/dev-disk-by-uuid-49e8f586-3839-4c5d-a1e1-58bfc3579ade/docker"
RK=$(grep -o '<ApiKey>[^<]*' $D/radarr/config/config.xml | cut -d'>' -f2)
curl -s --max-time 10 "http://localhost:7878/radarr/api/v3/queue?pageSize=50" \
-H "X-Api-Key: $RK" | jq -r '.totalRecords' # → 0
# sonarr: порт 8989, путь .../sonarr/config/config.xml
⚠️ docker exec без < /dev/null рвёт SSH-сессию. Скрипт передавать целиком:
ssh kraken 'bash -s' < /tmp/script.sh — устойчиво, в отличие от длинных inline-команд.
Результат 2026-09-23: Radarr queue 0, Sonarr queue 0.
🔴 2026-09-22: АВТОСКАЧКА НА HDD ВЫКЛЮЧЕНА (по требованию Alex)
✅ 2026-09-24: защита СНЯТА (по требованию Alex) — актуальное состояние
Логика: из watchlist в arr автоматом ничего не добавляется, защита мешала только ручным добавлениям («для вручную добавленных закачка должна стартовать»).
| Что | Было (защита 2026-09-22) | Стало (2026-09-24) |
|---|---|---|
arr.py: monitored + searchForMovie / searchForMissingEpisodes |
False |
True — коммит ef7e7af (push): добавление сразу ищет и качает. 🔴 Вечером того же дня arr.py УДАЛЁН (a524f5d) вместе с переходом sync-down на фид Lampa — см. «Консолидация» ниже |
Radarr rssSyncInterval |
0 |
15 мин (PUT /api/v3/config/indexer, HTTP 202) |
Sonarr rssSyncInterval |
0 |
15 мин (там же) |
sync-down в кроне |
вырезан | остался вырезан — авто-пути из watchlist нет, добавление только руками |
monitored существующих (34 фильма / 13 сериалов, все без файлов) |
снят | контент удалён целиком — см. ниже «контент удалён» |
Проверки после: очереди Radarr/Sonarr 0, monitored=true: 0 из 34, auth не тронут
(forms / /radarr / 7878), RSS-задача next − last = 900s.
🔴 Питфолл Radarr 6.4 / Sonarr 4.0.20: rssSyncInterval живёт НЕ в /api/v3/config/host
(поля там физически нет), а в /api/v3/config/indexer — PUT полным объектом с id
(частичный молча возвращает старое). Через host-config тупик: валидатор требует непустой
allowedHosts при authenticationRequired != Enabled и не принимает звёздочку.
✅ Консолидация РЕАЛИЗОВАНА 2026-09-24: генератор каталога слит в sync-down.
Было две независимых цепочки — lampac/scripts/gen_watchlist.py (878 строк, свой матчер
TMDB, не знал про resolve и его маркеры) и sync_down.py (arr-путь). Теперь один
модуль: sync-down резолвит через resolver.resolve() и пишет фид Lampa
(wwwroot/watchlist.json, watchlist-<cat>.json, cards/, cards.json, missing.json).
arr.py удалён (git rm, коммит a524f5d), ⬇️ больше не ставится нигде.
Детали, три открытия при переносе (фильтры рейтинга/ужасов в фиде НЕ нужны;
❓ не исключает позицию из фида; спорные позиции берутся из all_candidates[0] + ❓)
и открытые пункты — personal/plans/lampac-watchlist-plugin § «ПЕРЕПИСЫВАНИЕ».
🔴 Блокер выката (2026-09-24): резолвер проекта матчит хуже старого генератора —
сверка по tmdb_id дала 86 позиций с другим фильмом. Причина: в gen_watchlist.py
есть ступень «КП-по-номеру из ссылки» (kinopoisk.ru/film/N/ → КП → TMDB), в
resolver.py её нет; до удаления arr-контента это маскировал Radarr-фастпатч.
На живой фид НЕ выкатывалось. Поэтому lampac/scripts/gen_watchlist.py,
mark_missing.py и watchlist-catalog.sh (крон 02:30) пока НЕ удалены —
оттуда берётся код kp_search_by_id / _docs_to_tmdb.
Метрики dry-run: 268 (с фильтрами) → 332 (без фильтров) → 397 (с all_candidates[0]).
✅ 2026-09-24 (вечер): контент удалён — Radarr/Sonarr пустые
Alex: «если они ни в чем не участвуют, удали все фильмы/сериалы». Проверка перед удалением:
каталог Lampa (gen_watchlist.py) использовал Radarr только как фаст-патч постеров/tmdbId
— 9 карточек из 392 (2%), остальные 362 через TMDB API (основной путь, кэш 434 записи),
сериалов в каталоге нет вообще, import-lists пустые, файлов у контента ноль.
Удалено: 34/34 фильма (Radarr) + 13/13 сериалов (Sonarr), deleteFiles=false,
addImportExclusion=false, ноль ошибок. Бэкапы: radarr.db.bak-20260924-163600,
sonarr.db.bak-20260924-163600 (в config-каталогах; онлайн .backup, рестарта не было).
Очереди после: 0/0.
Регрессионный тест (прогон генератора с пустым Radarr в /tmp, живой каталог не трогал):
| Метрика | утро (с Radarr) | после удаления |
|---|---|---|
| карточек | 392 | 392 |
| без TMDB (→❓) | 30 | 30 |
| постеров | 388 | 390 |
| источники | Radarr 9, TMDB 362 | Radarr 0, TMDB 371 |
Ноль потерь. 3 карточки сменили матч (в .md нет года, Radarr и TMDB матчили вслепую):
Unbroken2003 → Несломленный 2014 (вероятно правильнее)Shogun1968 → Убийца сёгуна 1980Олдбой2003 → Олдбой 2013 (⚠️ ремейк Спайка Ли; если имелся в виду оригинал Пака Чхан-ука — добавить год в строку 134.md:Олдбой 2003)
Фаст-патч Radarr в генераторе оставлен — при пустом Radarr молча не срабатывает, оживёт сам при ручных добавлениях.
✅ 2026-09-24 (вечер): отдельный генератор Lampa ВЫПИЛЕН — фид пишет sync-down
Требование Alex: «не отдельный скрипт на лампу, а один sync скрипт и писал сразу
lampa feed». Раньше связка была двумя механизмами: sync-down (отдельный шаг) +
lampac/scripts/gen_watchlist.py (878 строк) по своему крону в 02:30.
Стало: watchlist_sync/sync_down.py собирает фид сам, в ночном пайплайне на
своём прежнем месте (01:00), генератор и его обвязка удалены:
| Было | Стало |
|---|---|
sync-down → Radarr/Sonarr (arr.py) |
sync-down → wwwroot/watchlist.json + cards/ + missing.json |
lampac/scripts/gen_watchlist.py (878) + mark_missing.py (179) |
.bak-removed-20260924-181942 |
~/scripts/watchlist-catalog.sh + крон 30 2 * * * |
убран (.bak-removed-…), крон почищен |
arr.py (Radarr/Sonarr API) |
удалён из репы (в истории git) |
маркер ⬇️ |
больше не ставится — закачки нет, маркер ничего не значит |
Коммиты: a524f5d (перенос), 471e9df (ступень КП + тайбрейк), 0534952 (скоуп ❓).
Резолв фида — 2 ступени (sync_down.run):
- КП-по-номеру из ссылки
.md(resolver.resolve_by_kp_id) — машинный ключ, не зависит от переименований. Без этой ступени на названиях расходилось ~86 позиций. - Матч по названию (
resolver.resolve). Неоднозначные → берётся кандидат с лучшимscore, ничьи разбивает рейтинг (Alex 2026-09-24) + ❓ в.md. 🔴 Только рейтинг использовать НЕЛЬЗЯ: выбрал бы «Семь Самураев» (★8.5) вместо «Семь» (1995, ★8.4) и «Злая наука» вместо нужного фильма.
Питфоллы, найденные по ходу:
- 🔴 Фильтры рейтинга/ужасов в фиде — НЕДОПУСТИМЫ: это правила закачки Radarr, а не каталога. С ними терялось ~120 позиций (37 ужасов). Каталог отдаёт всё.
- 🔴
resolve_by_kp_idне заменяет поиск: у 66 строк с ❓ ссылки на Кинопоиск в.mdНЕТ, они идут в слепой матч по названию. Куда показывать — уточнять год. - 🔴
resolveс годом-хинтом не фильтровал КП-кандидатов: «Злая 2024» →Cattiva(1991) со score 1.0. Исправлено — год отсекает КП-документы. - 🔴
_sync_question_marksсчитал скрытым всё, кроме секции «Новинки» → ❓ не попадал в Фильмы/Мультфильмы/Аниме. Скрывать можно толькоHIDE_SECTIONS. WatchItemнельзя класть в выходной JSON (не сериализуется) — только словари.
Проверено на живом: фид 390 позиций (4 канала), lampac HTTP 200, фид 200,
карточка 200, ночной прогон целиком зелёный, vault запушен.
Бэкап фида до правки: /tmp/feed-before-*.json; ночного скрипта —
watchlist-nightly.sh.bak-feed-20260924-181926.
🔴 Рестарт lampac после обновления фида НЕ НУЖЕН (уточнено 2026-09-24, вопрос Alex).
wwwroot/ — bind-mount, скрипт пишет файл с хоста, внутри контейнера он виден
сразу (проверено: один и тот же inode 119669729 и размер на хосте и в lampac).
Алекс: «обновление фида же не потребует передёрга?» — верно, не требует.
docker restart lampac был унаследован от старого catalog.sh, который так сбрасывал
hybridCache каталога; цена — приложение падает на ~минуту (компиляция модулей,
HTTP 000). Из ночного скрипта рестарт убран. Если каталог в Lampa залип
(кэш-ключ catalog:{plugin}:{search}:{sort}:{cat}:{page} живёт в памяти процесса) —
обновить страницу в Lampa или переключить категорию, а не ронять контейнер.
Дальше — история и cookbook той защиты.
Контекст. Задача — Lampa на ТВ показывает каталог из movies-watchlist.md и играет
через TorrServer. Alex: «мы перестаем качать торренты на диск!» → автоскачка Radarr/Sonarr
выключена фактически. Модуль Catalog/Lampa — в personal/plans/lampac-watchlist-plugin.
Выполнено (замер до → после)
| Сервис | Очередь | monitored |
rssSyncInterval |
|---|---|---|---|
| Radarr (:7878) | 6 → 0 | 45 → 0 | 30 → 0 |
| Sonarr (:8989) | 19 → 0 | 13 → 0 | 15 → 0 |
Бэкапы: radarr.db / radarr.db.bak-20260922-123404, sonarr.db / .bak-20260922-123404
(+ оба config.xml) в /srv/...6194539b.../docker/{radarr,sonarr}/config/.
Метаданные НЕ удалены — это важно: 50 фильмов остались в Radarr с 24 файла на диске (Jellyfin) не тронуты.
🔴 Устарело 2026-09-24: контент удалён полностью; каталог Lampa перешёл на TMDB-фолбэк
без потерь (392 карточки, см. выше «контент удалён»). Опасение «нужны каталогу» не
подтвердилось — Radarr обслуживал 2% карточек как кэш.tmdbId (все 50),
постерами (46) и рейтингом. Они нужны каталогу Lampa.
Проверка пассивности: Transmission за 45 с — NET 290GB/26.7GB, BLOCK 214GB/393kB
не сдвинулись. Логи Radarr (grep -iE "search|download|grab") за 2 мин — пусто.
✅ 2026-09-22 (вечер): sync-down вырезан из крона — дыра закрыта СНАРУЖИ
Второй слой защиты после ручного снятия monitored. Даже если arr.py не тронут,
автоматического пути в закачку больше нет вообще:
watchlist-nightly.sh вызывал sync-down --apply --batch 2. Вызов убран.
Крон теперь: sync-vault → process-thumbs → resolve --recheck → sync-up → sync-vault.
Бэкап: /home/kraken/scripts/watchlist-nightly.sh.bak-20260922-182655.
Сумма новой: 4401882e3b1307a73b3330a70b59ecfe.
🔴 Порядок защиты (действовал 2026-09-22 → 2026-09-24; частично снята — см. секцию выше):
- Ручное снятие
monitored+rssSyncInterval: 0— отменено 2026-09-24 (rss=15;monitoredсуществующих оставлен снятым). sync-downвне крона — остаётся и после 2026-09-24 (авто-добавлений из watchlist нет).
sync_down.py как код оставлен — ручной запуск возможен.
Проверка отсутствия автозакачки:
ssh kraken "grep -ln 'radarr_add\|sonarr_add\|radarr_search\|sync-down --apply' \
/home/kraken/scripts/*.sh"
Должны быть только комментарии. Детали — family/projects/watchlist-sync.
✅ 2026-09-23: пост-ребут проверка стека — все чисто
После перезагрузки Кра́кена (uptime 23 мин на момент проверки) — все 14 контейнеров поднялись сами, ручного вмешательства не потребовалось.
| Проверка | Значение |
|---|---|
| Radarr queue | totalRecords: 0 |
| Sonarr queue | totalRecords: 0 |
| Transmission | 9 торрентов, все 100% Done / Idle, Sum down 0.0 |
Lampac :9118 |
HTTP 200 |
Jellyfin :8096 |
HTTP 302 |
Диск /dev/sda1 |
539G / 1.8T = 31% |
Ночные скрипты отработали (все 2026-09-23): cleanup 02:00 (27.9% → ниже порога,
no-op), nightly 01:00, catalog 02:30→02:32 (каналов 4, позиций 392), простановка ❓
02:32 (708 строк, 615 позиций, .bak-questions). Фид watchlist.json 106 867 Б и
movies-watchlist.md 30 479 Б — оба mtime 02:32. Vault git чистый, последний коммит
2026-09-23 14:47.
🔴 2026-09-23: [x] из Lampa НЕ проставляется — канал найден, но не подключён
Вопрос Alex: «А у нас из лампы уходят отметки просмотрено в список галочками как было в jellyfin?»
Ответ: НЕТ. Ни Lampa, ни Jellyfin сейчас не дают [x]. Задача в работе — см.
personal/plans/lampac-watched-to-watchlist.
Почему Jellyfin-канал мёртв (замеры 2026-09-23):
Проверка Jellyfin (BaseItems / UserData) |
Значение |
|---|---|
BaseItems всего |
53 |
из них movie |
0 |
из них series |
1 (плейлист/коллекция) |
UserData.IsPlayed=true |
0 |
PlaybackActivity |
0 |
Причина: смотрят через Lampa + TorrServer, а не Jellyfin. Плюс с 2026-09-22
автозакачки выключены → новые файлы в Jellyfin не приходят. sync_up.py
(GET /Users/{id}/Items?IsPlayed=true → [x]) читает пустоту.
🔑 Где реально живёт история просмотров — модуль Lampac TimeCode:
| Элемент | Значение |
|---|---|
| Модуль | /lampac/module/Sync/TimeCode/ (Controller.cs, plugin.js, SqlContext.cs) |
| БД | /lampac/database/TimeCode.sql (SQLite). На хосте: $H/lampac/database/TimeCode.sql |
| Таблица | timecodes |
| Колонки | Id, user, identity, card, item, position, duration, percent, profile, deleted, watched_at, updated_at, extra |
| Пишет | Lampa → POST /timecode/add (легаси) / /timecode/set (нативный), пачками ≤10 req/s |
| Ключ личности | identity = {tmdb_id}_movie — готовый ключ для матча со списком по TMDB-ID |
Живая запись на 2026-09-23 (единственная в БД):
identity = 1758655_movie card = movie-1758655
position = 1222.0 duration = 6898.0 percent = 18.0
watched_at = 0 deleted = 0
🔴 Питфолл: sqlite3 в контейнере lampac НЕТ (sh: sqlite3: not found).
Читать БД так: docker cp lampac:/lampac/database/TimeCode.sql /tmp/ + на хосте
python3 -c "import sqlite3; ...". На хосте Кра́кена python3 есть.
⚠️ viewed.json и config.db в /lampac/data/ts/ — это TorrServer'овские, к
TS-истории просмотров Lampa отношения не имеют (не путать).
Решение (план, ждёт апрува): новый модуль sync_from_lampac.py в watchlist-sync —
читает timecodes (deleted=0 и (percent>=90 или watched_at>0)) → матч по tmdb_id
из watchlist.json (у 346/346 films tmdb_id есть) → [x] через существующий
sync_up._mark_watched → вызов в watchlist-nightly.sh после sync-up.
Полный план, грабли и чек-лист приёмки — personal/plans/lampac-watched-to-watchlist.
🔜 2026-09-22: УДАЛЕНИЕ авто-закачек — инвентаризация снята, ЖДЁТ КОМАНДЫ
Alex: «закачки отменять и проверить еще раз что заново не добавятся» → «удалить все добавленные автоматикой закачки в т.ч. файлы. и radarr и sonarr».
Сделано: полный дамп + разбор, что именно авто-добавлено. Удаление НЕ запущено — Alex не подтвердил список (16 позиций vs все 50).
дамп (единственный откат, корзины у Radarr нет):
/tmp/radarr-sonarr-dump-20260922-184351/radarr-movies.json
/tmp/radarr-sonarr-dump-20260922-184351/sonarr-series.json
Критерий «авто»: added >= 2026-09-20 (окно работы sync-down; пайплайн
watchlist'а включён в сентябре). Всё добавленное 18.05–04.06 в Radarr — ручное.
| Система | Всего | Авто-добавлено | Комментарий |
|---|---|---|---|
| Radarr | 50 фильмов | 16 (12 с файлами, 4 без) | added 20–21.09 |
| Sonarr | 13 сериалов | 0 | все 18.05–01.06, авто нет |
Sonarr-список (для протокола, НЕ удалять): Our Flag Means Death, Stranger Things, The Cyberfarm, Magical Police Department, Место встречи изменить нельзя, MOZU, Delicious in Dungeon, Rick and Morty, Severance,
Brassic, Double or Quits, Futurama, True Detective.
🔴 Полная таблица 16 Radarr-позиций с id и hasFile — в
personal/tech/lampac-deployment#📋 Инвентаризация авто-закачек в Radarr/Sonarr (2026-09-22).
Там же — команда удаления и питфолл deleteFiles=true
(Radarr по умолчанию НЕ удаляет файл с диска).
Гарантия «заново не добавятся» — проверена фактом (два независимых барьера):
1. sync-down вне крона → автозапуска нет
2. все 50 фильмов Radarr: monitored=false → поиск раздач не стартует
+ очередь (queue) 0 записей
+ wanted/missing 0 записей
⚠️ НЕ сделано: коммит правки watchlist-nightly.sh в git (вырезанный
sync-down живёт только на Кра́кене, в репо — старая версия).
Правильные вызовы API (проверено)
# 1. отмена загрузки — файлы на диске НЕ трогаются, blocklist НЕ трогать
DELETE /api/v3/queue/<id>?removeFromClient=true&blocklist=false
# 2. снятие monitored — КЛЮЧ ПОЛЯ РАЗНЫЙ у Radarr и Sonarr!
PUT /api/v3/movie/editor {"movieIds": [...], "monitored": false} # Radarr
PUT /api/v3/series/editor {"seriesIds": [...], "monitored": false} # Sonarr, НЕ "ids"
# 3. RSS off — отправлять ВЕСЬ объект конфига с id, иначе молча вернёт старое
PUT /api/v3/config/indexer {"rssSyncInterval": 0, "id": 1, ...остальное}
Ключи API: Radarr cbcb8ec320104359920c0f611d503c7b, Sonarr 15fbec32f1cb4b05b1c2b229ce27359b
(из <ApiKey> в config.xml).
🔴 Питфоллы этой операции
urllibНЕ идёт по 307, аcurl -sL— идёт. Первый прогон: все 25 DELETE вернулиHTTP Error 307, ничего не удалилось. Штатныйurllibна 307/308 превращает DELETE в GET и теряет тело. Фикс — свойHTTPRedirectHandler, сохраняющий метод и тело (код в personal/tech/lampac-deployment, раздел Radarr).- Sonarr группирует эпизоды сезона под одним
id— после удаления первой записи остальные дают404 Not Found. Это не сбой, они ушли вместе с родительской. PUT config/indexerтребует весь объект сid— частичный{"rssSyncInterval":0}молча возвращает прежнее значение (Radarr понадобился второй заход).- Кириллица в inline-
jqвнутриssh '...'снова сломала команду (unexpected INVALID_CHARACTER). Ключи объекта — латиницей.
Откат
curl -sL -X PUT ".../api/v3/movie/editor" -d '{"movieIds":[...],"monitored":true}'
curl -sL -X PUT ".../api/v3/series/editor" -d '{"seriesIds":[...],"monitored":true}'
curl -sL -X PUT ".../api/v3/config/indexer" -d '{"rssSyncInterval":30,"id":1,...}'
cp radarr.db.bak-20260922-123404 radarr.db && docker restart radarr
Полная цепочка
[вс 09:00] watchlist-discover ← KP API топ фильмов 2024-2025
↓ добавляет в секцию "Новинки 👍/👎"
movies-watchlist.md
Alex ставит 👍 / 👎
↓
[ежедн 09:00] watchlist-resolve ← SSH Кракен → resolve → commit
[ежедн 10:00] watchlist-sync-down
→ process-thumbs (перемещает 👍 → начало списка)
→ sync-down (добавляет батч в Radarr/Sonarr)
↓
Radarr (7878) / Sonarr (8989)
→ Prowlarr (9696) → RuTracker / Kinozal(M) / NoNaMe / etc.
→ Transmission (9091)
→ /media/movies-radarr или /media/series-sonarr
↓
router.py (Custom Script) → читает жанры из NFO/TMDb
→ symlink в /media/{movies,cartoons,documentaries,...}/
↓
Jellyfin (8096) → rescan → виден контент
Статусы в movies-watchlist.md
| Символ | Значение | Действие |
|---|---|---|
[ ] |
не смотрел, в очереди | sync-down добавляет в Radarr/Sonarr |
[x] |
просмотрено | sync-up из Jellyfin watched history |
⬇️ |
добавлено в Radarr/Sonarr | ждёт скачки |
👍 |
нравится (новинка) | process-thumbs переносит в начало → попадёт в sync-down |
👎 |
не хочу (новинка) | process-thumbs перемещает в ## Новинки👎 |
🗓️ |
upcoming (ещё не вышел) | discover помечает, не скачивается |
❓ |
не найдено / ambiguous | ждёт ручного уточнения |
🧟♀️ |
horror (genre_id=27) | не скачивается (фильтр в sync-down с 2026-09-21), если нет ⭐ |
⭐ |
приоритет к загрузке (U+2B50) | sync-down берёт в первую очередь, обходит ВСЕ фильтры (рейтинг + ужасы) ✅ 2026-09-20/21 |
🔜 С 2026-09-22 список читается ещё и в UI Lampa на ТВ — непросмотренные
[ ]отдаются каталогом «Мой список» через модульCatalogв Lampac. ⭐ первыми, рейтинг ↓, ужасы последними. План: personal/plans/lampac-watchlist-plugin. ⚠️ Отсюда требование:## Новинкине должна содержать строк, чей фильм уже[x]— иначе дубль в каталоге. Чистится автоматическиprocess-thumbs(фикс 2026-09-22, см. ниже).
⚠️ ✭ (U+272D) — НЕ приоритет. Alex 2026-09-20 явно указал: звёздочкой-приоритетом считается только ⭐ (U+2B50). Символ ✭ используется в файле как обычная пометка «выделенное», приоритетом не является.
Также: ⭐ засчитывается только в строке фильма (- [ ] Название ⭐ …), НЕ в под-буллетах кандидатов, которые генерирует mark_ambiguous (там формат «Название (год) ★N.N»). [x] (просмотренные) с ⭐ игнорируются. Работает из всех секций (## Фильмы, ## Новинки, ## Мультфильмы, ## Аниме).
Правила отбора в sync-down (актуально с 2026-09-21)
Приоритет определяется двумя факторами, а не позицией строки:
| Условие | Поведение |
|---|---|
⭐ (U+2B50) в строке фильма |
первыми, фильтры рейтинга И ужасов не применяются |
ужас (is_horror(), genre_id=27) без ⭐ |
отсеивается, с логом [🧟] … horror, skipped ⬅️ новое 2026-09-21 |
все остальные [ ] |
по порядку файла (сверху вниз), рейтинг ≥ 6.0 |
рейтинг < 6.0 (включая 0.0) |
отсеивается, с логом [−] … skipped |
[x] |
пропускается |
❓ / 🗓️ (ambiguous) |
пропускается, обрабатывается отдельно |
⚠️ Стоп-кран по месту (2026-09-21): перед любым добавлением проверяется MIN_FREE_GB = 100.
Если свободно меньше — прогон останавливается ([STOP] free space … — nothing queued).
Проверка через shutil.disk_usage, не df. Именно отсутствие этой проверки заполнило диск
на 100% 2026-09-21 — см. family/plans/watchlist-disk-overflow-fix.
Фильтр ужасов работает по ЖАНРУ (candidate.is_horror(), TMDB genre_id=27), а не по эмодзи 🧟♀️.
В dry-run 2026-09-21: 33 horror item(s) skipped, 14 rating skipped.
Результат для Alex: 41 фильм с 🧟♀️ (многие — свежие релизы 2025–2026) больше не качается.
Обход — поставить ⭐ в строку фильма.
⚠️ Порядок файла ≠ рейтинг. До 2026-09-20 код сортировал по рейтингу TMDB и игнорировал позицию в списке — из-за этого в батч попадали фильмы «не из начала списка». Исправлено коммитом ada47da.
⚠️ --batch снижен 5 → 2 (2026-09-21) после инцидента с диском. Меняется в
~/scripts/watchlist-nightly.sh (root:root — править через sudo cp).
⚠️ ⭐ = обход фильтров, а не смена сортировки. Это принципиальная позиция Alex («Я блядь сказал в приоритете завела»): звёздочка означает явное «хочу», поэтому рейтинг для неё не проверяется вовсе.
Структура секций movies-watchlist.md
## Новинки - 👍/👎 ← discover добавляет сюда
## Фильмы ← основной список, отступ 2 пробела ( - [ ])
## Мультфильмы
## Аниме
## Новинки👎 ← без пробела перед 👎
## Связанные заметки
Важно: process_thumbs автоматически определяет отступ из ## Фильмы и сохраняет при переносе. SECTION_THUMBDN = "## Новинки👎" (без пробела) — должно точно совпадать с заголовком.
✅ Крон pipeline ВОССТАНОВЛЕН (2026-09-20 21:21)
Задача закрыта. Pipeline был мёртв с 2026-06-20 (проверено аудитом ниже), теперь расписание вернули.
Установлено в crontab юзера kraken:
# Watchlist: process-thumbs + resolve + sync-down + sync-up (ежедневно 01:00)
0 1 * * * bash /home/kraken/scripts/watchlist-nightly.sh >> /tmp/watchlist-nightly.log 2>&1
# Watchlist: discover — новинки из KP API (воскресенье 09:00)
0 9 * * 0 bash /home/kraken/scripts/watchlist-discover.sh >> /tmp/watchlist-discover.log 2>&1
Как ставилось (идемпотентный скрипт, бэкап перед установкой):
crontab -l > ~/crontab-backup-$(date +%Y%m%d-%H%M%S).txt # бэкап
crontab /tmp/crontab-new.txt # установка
crontab -l # проверка
Бэкап: ~/crontab-backup-20260920-212155.txt
Проверено после установки:
systemctl is-active cron→activegrep -c watchlist /var/spool/cron/crontabs/kraken→ 2 (обе строки подхвачены демоном)- Логи пишутся в
/tmp/— теряются при перезагрузке. Перенести в постоянное место, если нужно сохранять историю.
⚠️ В docker exec hermes-kraken hermes cron list по-прежнему «No scheduled jobs» — это НЕ про watchlist. Watchlist-скрипты детерминированные, живут в системном cron, в Hermes-планировщике их не должно быть. Не искать их там.
🔴 2026-09-22: ОСИРОТЕВШИЕ СТРОКИ в ## Новинки — баг и фикс
Симптом (сообщил Alex): «Проект Конец света» показывался в каталоге Lampa как непросмотренный,
хотя в главном списке он [x] (просмотрен) и был скачан. Alex предположил «опять синк не работает».
Корень — НЕ синк, а process_thumbs() в parser.py.
Регексп функции матчит только строки с плиткой:
_THUMBS_RE = r'^(\s*-\s*\[.\]\s*)(👍|👎)?\s*(.*?)\s*(👍|👎)?$'
Ветвление (актуальное поведение):
👍→ строка копируется в начало## Фильмы, из## Новинкиудаляется👎→ переносится в## Новинки👎[x]→ переносится в конец[x]-блока главного списка- всё остальное (в т.ч.
⬇️без плитки) → попадает вkeep= остаётся в## НовинкиНАВСЕГДА
Осиротевшая строка в файле (была):
- [ ] ⬇️ [Проект «Конец света»](kinopoisk/1382256/) (2026) IMDb 8.4 KP 8.4
⬇️ = «добавлен в Radarr», но плитки нет → ни под одно правило не подпадала → вечно [ ]
в ## Новинки → вечно в каталоге Lampa как непросмотренная.
Почему не нашёл поиском по названию: написания разные —
Проект «Конец света» (Новинки, со ссылкой KP) vs Проект конец света (главный список, [x], без ссылки).
Фикс 1 — parser.py::process_thumbs(): удаление сирот
Добавлен блок: собирает ключи всех [x]-строк из ВСЕХ секций (кроме ## Новинки и ## Новинки👎),
затем выкидывает из keep строки, чей ключ совпал.
Сравнение по двум ключам: ссылка Кинопоиска (kinopoisk/NNN/) или нормализованное название.
# Нормализация: срезаем эмодзи, год, рейтинги, регистр, пробелы, «ё»→«е»
_norm_title = lambda s: re.sub(r'[^а-яa-z0-9]+', '',
re.sub(r'\b(?:IMDb|KP)\s*[\d.,]+', '', re.sub(r'\(?\b(19|20)\d{2}\b\)?', '', s.lower()))).replace('ё','е')
Результат: Проект «Конец света» (2026) IMDb 8.4 KP 8.4 → проектконецсвета,
Проект конец света → проектконецсвета. Совпадают.
⚠️ Критично — снятие раннего return []. Блок стоит ПОСЛЕ сбора keep, но в функции есть
проверка if not thumbs_up and not thumbs_down and not watched_items: return [].
Когда патч удаляет последнюю сироту, все три списка пусты → функция выходила до записи файла,
и удаление терялось. Условие расширено флагом orphans_removed, чтобы запись состоялась.
Фикс 2 — cli.py: корректный вывод действия
[drop] попадал в else-ветку и печатался как перенос в «Новинки 👎» (косметика, но путает логи).
Разведены явные ветки по типу действия.
Проверка
Прогон на копии боевого файла: удаляется ровно 1 строка, 683 → 682, потерь нет.
После прогона на боевом: каталог Lampa 404 → 403 позиции.
## Новинки: 34 → 33. «Конец света» отсутствует во всех 4 категориях.
Бэкап перед правкой: ~/backup-watchlist-before-orphan-fix-20260922-113925.md
🩹 ГЛАВНЫЕ ГРАБЛИ: patch-скрипты съедают обратные слэши (наступил 3 раза)
При правке через write_file+python двойное экранирование превращает escape-последовательности
в сырые байты. Синтаксис остаётся валидным, ast.parse() проходит, но регекспы молча не работают.
| Написано | Осело в файл | Эффект |
|---|---|---|
r'\1' |
байт \x01 |
подстановка ссылки ломается |
r'\b' |
байт \x08 (backspace) |
\b не матчит границу слова → рейтинги IMDb 8.4 KP 8.4 не срезаются |
Как диагностировать: ключ проектконецсветаimdb84kp84 — рейтинги остались в ключе, значит \b мёртв.
Проверять регекспы побайтово (repr() строки из файла), а не глазами через read_file.
Правило: для regex-замен в этих скриптах использовать chr(92)+'1' / chr(92)+'b' либо
проверять repr() после записи. Отлаживать через печать repr() реально загруженной строки.
📌 Следствие для будущего
Ночной cron (01:00, watchlist-nightly) теперь сам вычищает такие сироты при каждом прогоне.
Если в каталоге снова появится просмотренный фильм — первым делом grep по ## Новинки.
🔴 Инцидент переполнения диска (2026-09-21) — историческая справка
Симптом: диск /dev/sda1 на Kraken — 0 байт свободно (df 100%). Radarr API отдавал
disk I/O error (SQLite не может писать при нуле места) — метаданные недоступны.
Причина (НЕ cleanup): sync-down в кроне watchlist-nightly (01:00) добавлял
5 фильмов ежедневно (--batch 5), без проверки свободного места и без фильтра ужасов.
Radarr качал remux'ы по 57–93 ГБ, пока диск не кончился. Профиль WEB-4K (no remux) фильтрует
тип релиза, но НЕ размер файла и НЕ проверяет место — от переполнения не защищает.
Освобождено вручную: удалены 5 remux = 403 ГБ (Babylon 93 / Schindler's List 90 /
Midsommar 86 / Triangle of Sadness 80 / Unhinged 57 ГБ), всем выставлен monitored=false
через Radarr API. Диск: 100% → 77% (404 ГБ).
Починено в коде (коммиты 2642882, a131208):
| Что | Деталь |
|---|---|
| Стоп-кран по месту | MIN_FREE_GB = 100.0 в sync_down.py, проверка через shutil.disk_usage (не df) |
| Фильтр ужасов | candidate.is_horror() (genre_id=27) пропускает ужасы, если нет ⭐ |
mark_queued |
возвращает bool, поглощает эмодзи-префикс, логирует промах через _mark() |
unmark_queued |
новая функция — снятие ⬇️ (не существовала) |
_clean |
чистит множественные эмодзи (^(?:[chars]+\s*)+) и одиночный год (* вместо +) |
sync_up._mark_watched |
видит эмодзи-префикс (иначе фильм не переходил в [x]) |
--batch |
5 → 2 в ~/scripts/watchlist-nightly.sh (root:root, править sudo cp) |
Питфоллы, найденные в процессе:
~/scripts/на Kraken — root:root 755,scpпадает сPermission denied. Толькоsudo cp.- Крон-файлы вернуть можно только через
crontab /tmp/файл;crontab -l > fдля бэкапа обязателен. duпо USB-HDD не влезает в SSH-таймаут на 100%-заполненном диске — запускать в фоне черезsystemd-run.- Когда диск в нуле, сначала освободить место, только потом любые API-вызовы Radarr: SQLite мёртв.
Открытая задача: cleanup (family/plans/watchlist-cleanup) не покрывает /media/movies —
смотрит только Radarr-библиотеку, поэтому просмотренное из ручных торрентов не удаляется.
Пересечение [x] ∩ Radarr-файлы = 0.
Полный разбор инцидента: family/plans/watchlist-disk-overflow-fix
🔴 Аудит 2026-09-20: как выглядело состояние ДО восстановления (историческая справка)
На момент аудита автодобавление фильмов НЕ работало по расписанию. Скрипты были живы, но их никто не запускал.
Доказательства (проверено 2026-09-20):
crontab -lюзераkraken— watchlist-строк НЕТ. Есть толькоsync-vault.sh(каждый час) и дваpgs-to-srt(часовой + 03:00).- root crontab и
/etc/cron.d/:grep -rl watchlist→ пусто. - systemd timers (user + system) → 0 штук.
- Внутри Hermes-контейнера на Кракене:
docker exec hermes-kraken /opt/hermes/.venv/bin/hermes cron list→ "No scheduled jobs." - Последний коммит автором
Hermes(= nightly-скрипт) — 2026-06-20. Распределение по месяцам: май 1, июнь 9, июль/август/сентябрь — 0. Единственные коммиты в vault после июня — отeagle,syncthing,taiga.
Скрипты существуют и исполняемы, но не в кроне:
~/scripts/watchlist-nightly.sh mtime 2026-05-29
~/scripts/watchlist-discover.sh mtime 2026-05-29
⚠️ Изначальная гипотеза «файл засран от многомесячного простоя» — НЕВЕРНА. Дубли создавались на каждом прогоне
resolveиз-за бага вmark_ambiguous()(см. ниже, исправлен). 33 строки у «Nightmare Alley» = 11 прогонов. Простой лишь позволил мусору накопиться незамеченным. Мусор вычищен, баг закрыт.
✅ Баг дублирования кандидатов в mark_ambiguous() — НАЙДЕН И ИСПРАВЛЕН (2026-09-20)
Симптом
Под каждой ❓-строкой копились одинаковые дампы кандидатов. Рост ровно +100 байт за прогон (3 строки кандидатов) — подтверждено экспериментом: 26430 → 26530 → 26630 → 26730 байт.
Корень (точная механика)
В watchlist_sync/parser.py, функция mark_ambiguous():
pattern = re.compile(
r'(^\s*-\s*\[ \]\s*)[❓]?\s*(' + escaped + r')\s*$', # ← матчит ТОЛЬКО строку ❓
re.MULTILINE
)
...
replacement = rf'\1❓ \2\n{cand_lines}' # ← новые кандидаты
new_text = pattern.sub(replacement, text, count=1) # ← ВСТАВЛЯЮТСЯ перед старыми
Паттерн снимал только саму ❓-строку. Замена вставляла \n + 3 новых кандидата, а старые 33 строки оставались ниже — реплейс не знал о них. Итог: каждый прогон добавляет ещё один блок кандидатов поверх существующих.
Важно: ❓-строки НЕ дублировались (каждая — одна). Дублировались под-буллеты под ними.
Фикс
Паттерн теперь захватывает весь блок: строку-маркер ПЛЮС её под-буллеты, и перезаписывает целиком.
# Candidate sub-bullet: indent of 4+ spaces, a bullet, NOT a checkbox line.
# Each iteration REQUIRES its own leading newline -- otherwise `*` walks onto
# the following line and swallows neighbouring items.
_SUB_BULLETS = r'(?:\n[ \t]{4,}-[ \t]*(?!\[[ xX]\])[^\n]*)'
pattern = re.compile(
r'(^[ \t]*-[ \t]*\[ \][ \t]*)[❓]?[ \t]*(' + escaped + r')[ \t]*'
r'(?P<subs>' + _SUB_BULLETS + r'*)',
re.MULTILINE
)
⚠️ Две НЕУДАЧНЫЕ версии паттерна (не повторять)
- v1 —
(?:\n[ \t]+-[^\n]*\n?)*:\n?опционален, поэтому*перешагивал на следующую строку без разделителя и проглатывал соседние фильмы. Потеряны❓ The kill room,The burial,Past lives. Откачено. - v2 — то же плюс негативный lookahead: не помогло,
[^\n]*из предыдущей итерации уже захватил хвост следующей строки. - v3 (рабочая) — перенос обязателен внутри каждой итерации (
(?:+\n+ ...) +*), якорь не нужен.
Правило для парсеров этого файла: под-буллет кандидата = отступ ≥4 пробела, это буллет и он НЕ чекбокс (- [ ] / - [x]). Строки фильмов имеют отступ 2 пробела — именно поэтому их надо исключать явно.
Проверка фикса
На чистой копии (md5 40295fac...) — 3 прогона mark_ambiguous():
run 1: size=25430
run 2: size=25430
run 3: size=25430
Размер стабилен. Потеряно 0 не-кандидатных строк. Past lives на месте. 838 → 808 строк (минус 30 = старые дубли, 33 → 3).
Чистка существующих дублей
Скрипт /tmp/wl_dedupe.py (дедуп по title|year|rating, первое вхождение сохраняется, порядок не меняется; строки фильмов не трогаются). Прогон --apply:
- 168 дублей в 15 блоках, 26430 → 21032 байт (838 → 670 строк).
- Топ:
Nightmare alley33→3,Stand by me30→3,Фарго30→3,Left behind21→4,Крестный отец12→3,Godfathers12→3.
Ручной прогон watchlist-nightly.sh (от юзера kraken)
bash ~/scripts/watchlist-nightly.sh
Прошёл до Done, без падений. Результат: 270 resolved (38 horror), 37 ambiguous, 22 not found. sync-down добавил 4 фильма в Radarr (Американская история X, Мастерская сатаны, Я ругаюсь, Гладиатор), Scream уже был → помечен ⬇️. sync-up: 7 played items из Jellyfin, 1 отмечен. Vault закоммичен и запушен (Committed: family/documents/movies-watchlist.md, Pushed ✓).
После прогона дублей: 0 (проверено отдельным скриптом). Секции целы: Новинки / Фильмы / Мультфильмы / Аниме / Новинки👎 / Связанные заметки.
🔴 БАГ: sync-down игнорирует порядок файла — сортирует по рейтингу (найден 2026-09-20)
Симптом (сообщил Alex): в батч на закачку попали фильмы, которые «далеко не в начале списка» — Гладиатор, Американская история X, Я ругаюсь, Мастерская Сатаны.
Корень: watchlist_sync/sync_down.py:41:
resolved.sort(key=lambda x: x[1].rating or 0, reverse=True)
Код полностью игнорирует позицию строки в файле и берёт топ-5 по рейтингу TMDB/KP из всех неотмеченных [ ]-строк. Порядок списка не имеет значения вообще.
Расхождение с докой: в watchlist-sync §1.3 заявлено «Приоритет: порядок в файле (выше = важнее)» — это не соответствует коду. Реальный приоритет = рейтинг.
Вторая половина причины: в том же прогоне process-thumbs выполняется до sync-down и переносит 👍-позиции из ## Новинки в начало основного списка. Среди перенесённых оказались Мастерская Сатаны (8.3) и Я ругаюсь (8.2) — с высокими рейтингами, поэтому они сразу выиграли отбор.
Как считался батч 2026-09-20 (5 слотов):
| Слот | Фильм | Рейтинг | Итог |
|---|---|---|---|
| 1 | Крик / Scream | 10.0 | уже в Radarr → помечен ⬇️ |
| 2 | Волшебный участок | 9.0 | 400 «already added» в Sonarr |
| 3 | Гладиатор | 8.225 | добавлен |
| 4 | Я ругаюсь | 8.229 | добавлен |
| 5 | Мастерская Сатаны | 8.3 | добавлен |
| — | Американская история X | ~8.2 | добавлен (слот освободился после первых двух) |
Saving Private Ryan (8.223) и Иди и смотри (8.214) проиграли Гладиатору (8.225) на тысячные доли.
❌ В sync_down ОТСУТСТВУЕТ фильтр по рейтингу
Проверено grep -rn "6\.0\|min_rating" watchlist_sync/*.py:
cli.py:92—< 6.0только для сброса кэша при--recheck, не отборdiscover.py:105,203— фильтр только для шага discoverresolver.py:264—0.5это сравнение рейтингов при ничьей, не порог
В sync_down порога нет вообще. Порог min_rating: 6.0 из доки watchlist-sync §1.2 в коде не реализован. Итог: в очередь могут попасть фильмы с рейтингом 0.0-5.9.
Замер 2026-09-20: из 251 resolved-кандидатов 18 имеют рейтинг < 6.0 — например Шестой элемент 4.3, Мегалополис 5.2, Бешеная любовь 3.1, Ворон 2024 5.8.
⚠️ Отдельный случай: rating == 0.0 означает «TMDB не дал оценку», а НЕ «плохой фильм». Таких двое: Уроки благи, Годзилла и конг. Наивный фильтр < 6.0 их отсечёт и потеряет нормальное кино.
✅ РЕАЛИЗОВАНО (2026-09-20): sync_down следует порядку файла + фильтр MIN_RATING
План из «📋 ПЛАН» выполнен. Alex подтвердил: порядок файла = приоритет, рейтинг = отбор, нулевые рейтинги (0.0) отсекать вместе с остальными ниже порога.
Коммиты в репо watchlist-sync (оба сделаны в этой сессии):
ada47da feat(sync-down): follow file order, filter by MIN_RATING
2dab180 fix(parser): mark_ambiguous replaces candidate block instead of prepending
Предыдущий HEAD до сессии — 20ca511. Рабочее дерево чистое.
Правка 1 — убран resolved.sort() (был на строке 41). resolved заполняется циклом по parse(), то есть остаётся в порядке файла сверху вниз.
Правка 2 — добавлена константа и фильтр в начало sync_down.py:
# Minimum TMDB/KP rating for a title to be queued. 0.0 means "no rating in
# the database" -- per Alex it is discarded together with everything below
# the threshold, so unrated items never reach Radarr/Sonarr.
MIN_RATING = 6.0
if result.status == "ok":
if (result.candidate.rating or 0) < MIN_RATING:
skipped_low += 1
print(f" [−] {item.title!r} — rating "
f"{result.candidate.rating or 0:.1f} < {MIN_RATING}, skipped")
continue
resolved.append((item, result.candidate))
else:
deferred.append((item, result))
Плюс итоговая строка (N item(s) below rating 6.0 skipped) — чтобы отсев был виден в логе, а не молчал.
process_thumbs сознательно НЕ тронут — 👍 по-прежнему уезжают в верх основного списка, то есть свежие новинки намеренно идут первыми (выбор Alex, «вариант 1»).
Dry-run после правки (проверено):
[−] ... 18 строк «skipped» (все < 6.0, включая два нуля: Уроки благи 0.0, Годзилла и конг 0.0)
(18 item(s) below rating 6.0 skipped)
[→] 'Под огнём' (2025) ★7.1 [dry-run]
[→] 'Секретный агент' (2025) ★7.3 [dry-run]
[→] 'Охота на воров 2: Пантера' (2025) ★6.6 [dry-run]
[→] 'Гадкая сестра' (2025) ★7.3 [dry-run]
[→] 'Семья в аренду' (2025) ★7.8 [dry-run]
Queued 5/5 items [dry-run]
Порядок ровно как в файле, с самого верха ## Новинки. Рейтинг больше не влияет на очерёдность — только на отбор.
⚠️ Реальный прогон (bash ~/scripts/watchlist-nightly.sh) после этой правки был ПРЕРВАН — не отработал. Vault проверен: рабочее дерево чистое, movies-watchlist.md остался на коммите d7ce0a9 (результат первого прогона), частичных изменений нет.
✅ ⭐ Приоритет по звёздочке ⭐ (U+2B50) — РЕАЛИЗОВАНО (2026-09-20)
Требование Alex: фильмы, отмеченные звёздочкой ⭐, идут в первую очередь к загрузке — приоритет поверх порядка файла, из всех секций списка.
Правила (финальные, подтверждены Alex):
- Только символ
⭐(U+2B50). Символ✭(U+272D) приоритетом НЕ является. - Только строки фильмов (
- [ ] Название ⭐ …), НЕ под-буллеты кандидатов отmark_ambiguous(«Название (год) ★N.N»). [x](просмотренные) — пропускаются.- Из всех секций — не только
## Фильмы/## Новинки(уточнение Alex). - ⭐ обходит фильтр
MIN_RATING— «Я блядь сказал в приоритете завела». Звёздочка = явное «хочу», рейтинг не важен. ⭐срезается из поискового запроса перед отправкой в TMDB (очевидное требование Alex).
Коммит: 7db9385 feat: ⭐ (U+2B50) items are queued first, bypassing the rating filter
Правка 1 — parser.py::_clean().
⚠️ Найденный баг: ⭐ стоит в середине названия (Простите за беспокойство ⭐ 2018), а старый regex был привязан к концу строки (\s*[...]+\s*$) — поэтому класс не матчился и символ утекал в запрос TMDB. Работало случайно (TMDB прощал лишний глиф).
# ⭐ (U+2B50) is a priority marker and may sit ANYWHERE in the title
# ("Простите за беспокойство ⭐ 2018"), so strip it without the end anchor.
s = s.replace('⭐', ' ')
s = re.sub(r'\s+', ' ', s).strip()
s = re.sub(r'\s*[✭★☆✩✪✫✬✮✯✰]+\s*$', '', s).strip() # остальные звёзды — по-прежнему end-anchored
return s
Критично: ⭐ вырезается из title (чистый запрос), но остаётся в raw — приоритет проверяется по item.raw, поэтому оба свойства сохраняются.
Правка 2 — sync_down.py, двухпроходная выборка:
# ⭐ (U+2B50) marks a must-have: such items are queued FIRST and bypass
# the rating filter entirely. Order within each group stays file order.
starred = [i for i in candidates_raw if "\u2b50" in i.raw]
rest = [i for i in candidates_raw if "\u2b50" not in i.raw]
if starred:
print(f" ⭐ {len(starred)} priority item(s) {[i.title for i in starred]}")
for item in starred + rest:
result = resolve(item.title, tmdb_key, kp_key)
if result.status == "ok":
if "\u2b50" not in item.raw and (result.candidate.rating or 0) < MIN_RATING:
skipped_low += 1
print(f" [−] {item.title!r} — rating "
f"{result.candidate.rating or 0:.1f} < {MIN_RATING}, skipped")
continue
resolved.append((item, result.candidate))
else:
deferred.append((item, result))
Обрати внимание на условие: "\u2b50" not in item.raw and rating < MIN_RATING — фильтр применяется только к не-⭐. Это и есть «обходит фильтр».
Итоговые правила отбора в sync-down:
| ⭐ | обычные | |
|---|---|---|
| Очерёдность | первыми | после, по порядку файла |
Фильтр MIN_RATING = 6.0 |
не применяется | применяется |
[x] |
пропуск | пропуск |
Проверка (2026-09-20):
_clean('Простите за беспокойство ⭐ 2018') -> 'Простите за беспокойство 2018' (⭐ срезан)
raw: 'Простите за беспокойство ⭐ 2018' star in raw: True (приоритет работает)
title: 'Простите за беспокойство 2018' star in title: False (чистый запрос)
candidates: 250 | starred: 1 | rest: 249 — ⭐ первым в итерации
Dry-run после правки:
⭐ 1 priority item(s) ['Простите за беспокойство 2018']
[→] 'Простите за беспокойство 2018' → 'Простите за беспокойство' (movie, 2018, ★6.8) [dry-run]
[→] 'Гадкая сестра' (2025) ★7.3 ...
Queued 5/5 items [dry-run]
Единственный ⭐ в файле — строка 79, секция ## Фильмы: - [ ] Простите за беспокойство ⭐ 2018.
✅ Реальный прогон 2026-09-20 19:53 — прошёл чисто
bash ~/scripts/watchlist-nightly.sh от юзера kraken → Done, ошибок нет. Вывод (/tmp/wl-run.log):
(18 item(s) below rating 6.0 skipped)
[=] 'Простите за беспокойство' already in Radarr, marking queued ← ⭐ обработан ПЕРВЫМ
[=] 'Гадкая сестра' already in Radarr, marking queued
[✅] 'Список заветных желаний' added to Radarr, search triggered
[✅] 'Отпуск на двоих' added to Radarr, search triggered
[✅] 'Простая случайность' added to Radarr, search triggered
Queued 5/5 items
[19:53:32] ▶ Done
⭐-фильм попал в очередь первым, но добавлять было нечего (уже в Radarr с прерванного прогона) — значит приоритет сработал. Дубли после прогона: 0. Файл: 21332 байт / 676 строк.
🩹 Почему первая версия правки не сработала (не повторять)
Первая попытка добавить ⭐ в существующий класс [✭★☆✩✪✫✬✮✯✰] не дала эффекта — класс end-anchored ($), а ⭐ стоит в середине названия. Проверка на тестах показала star gone: False. Вывод: для маркеров, которые могут стоять в любой позиции, нужен str.replace(), а не класс с якорем.
📋 Как задача ставилась (для контекста будущих сессий)
Alex формулировал требования короткими репликами, и план сначала был неверно истолкован дважды:
- «⭐ - в приоритете, из всех списков. Естественно в tmdb ее срезать» — уточнение, что секции не ограничены и глиф не должен уходить в запрос.
- «Что блядь значит рейтинг остался?! Я блядь сказал в приоритете завела» — категорическое: ⭐ обходит фильтр рейтинга, а не просто меняет очерёдность.
Вывод для будущего: когда Alex говорит «в приоритете» — это означает обход фильтров, а не смену сортировки. Уточнять это ДО предложения плана, а не после.
📦 Результат закачки по итогам прогона 2026-09-20
sync-down добавил 4 фильма (Scream уже был → ⬇️). Проверено через Radarr API + Transmission:
| Фильм | Взятый релиз | Статус |
|---|---|---|
| Gladiator (2000) | UHD BDRip [H.265/2160p] [4K] |
качается |
| American History X (1998) | BDRip [AVC] |
качается |
| I Swear (2025) / Я ругаюсь | BDRip [H.264/1080p] [DVO] |
качается |
| Oficina do Diabo (2025) / Мастерская Сатаны | — | релиз не найден, ждёт RSS |
✅ Профиль качества работает как задумано: Gladiator взят в 4K, но BDRip, не Remux — профиль 7 WEB-4K (no remux) фильтрует корректно.
Застрявшие в очереди Radarr (со статусом warning, к этому прогону не относятся): Brokeback Mountain, Мортал Комбат 2, Sorry to Bother You, Город бога, День хомячка. Отдельная задача — разобрать.
🟡 Побочная находка: Zulip-пинг не настроен на Кракене
_zulip_ping() требует ZULIP_URL, ZULIP_API_KEY, ZULIP_BOT_EMAIL, но их нет в .env репо watchlist-sync. При 59 нерезолвленных items → [warn] ... not set — skipping ping. Уведомления о нерезолвленных до Alex не доходят.
🟡 Побочная находка: Sonarr 400 «This series has already been added»
При sync-down для «Волшебный участок»: 400 Bad Request на /sonarr/api/v3/series. Причина безобидная — серия уже в Sonarr (tvdbId=442038, «Magical Police Department»). arr.py::sonarr_add() не проверяет дубли перед POST, поэтому Sonarr отвечает SeriesExistsValidator. Не сбой, но шумит в логе — стоит добавить pre-check (как в radarr_add).
Бэкапы (перед правками)
~/obsidian/family/documents/movies-watchlist.md.bak-20260920-192909 (838 строк, 26430 б)
~/obsidian/family/documents/movies-watchlist.md.dedupe-backup-20260920-193111
/srv/.../watchlist-sync/watchlist_sync/parser.py.bak-20260920-192909
Статус на конец сессии (2026-09-20, финал)
Три коммита в репо watchlist-sync (порядок применения):
7db9385 feat: ⭐ (U+2B50) items are queued first, bypassing the rating filter
ada47da feat(sync-down): follow file order, filter by MIN_RATING
2dab180 fix(parser): mark_ambiguous replaces candidate block instead of prepending
20ca511 ← HEAD до сессии
Рабочее дерево чистое. .bak-* добавлены в .gitignore.
- ✅ Фикс
parser.py(mark_ambiguous) применён, проверен 3 прогонами на чистой копии, закоммичен. - ✅ Фикс
sync_down.py— порядок файла +MIN_RATING = 6.0, закоммичен. Dry-run подтверждён. - ✅ Приоритет
⭐(U+2B50) реализован, закоммичен. Обходит фильтр рейтинга, работает из всех секций,[x]пропускает. - ✅ Файл watchlist вычищен (168 дублей → 0) и закоммичен в vault.
- ✅ Два реальных прогона
watchlist-nightly.shпрошли чисто. Второй (19:53) — уже с приоритетом ⭐:Done, ошибок нет, 3 фильма ушли в закачку. - ✅ Дубли после всех прогонов: 0.
- 🔴 Крон по-прежнему отсутствует — pipeline живёт только вручную. ГЛАВНАЯ незакрытая задача.
- 🟡 Zulip-пинг не настроен (
ZULIP_*нет в.env). - ℹ️ Доки watchlist-automation и watchlist-sync обновлялись агентом Eagle в этой же сессии (коммиты
bd743da,abee5a8в vault) — при правках сверяться.
Хронология прогонов 2026-09-20:
| Время | Что | Результат |
|---|---|---|
| ~19:33 | Прогон после фикса дедупа (до правки sync_down) |
4 фильма в Radarr: Gladiator, American History X, I Swear, Oficina do Diabo (без релиза) |
| ~19:45 | Прогон после правки sync_down |
ПРЕРВАН (прерван пользователем), правок не оставил |
| 19:53 | Финальный прогон (файловый порядок + MIN_RATING + ⭐) |
Done. +3 фильма: Список заветных желаний, Отпуск на двоих, Простая случайность |
Бэкапы (все созданы в этой сессии):
~/obsidian/family/documents/movies-watchlist.md.bak-20260920-192909 (838 строк, 26430 б — ДО фикса)
~/obsidian/family/documents/movies-watchlist.md.dedupe-backup-20260920-193111 (до дедупа)
/srv/.../watchlist-sync/watchlist_sync/parser.py.bak-20260920-192909 (md5 0f597606fa14f357ff3b25547bd18ea6)
/srv/.../watchlist-sync/watchlist_sync/parser.py.bak-20260920-195100 (до правки ⭐)
/srv/.../watchlist-sync/watchlist_sync/sync_down.py.bak-20260920-194447
/srv/.../watchlist-sync/watchlist_sync/sync_down.py.bak-20260920-195100 (до правки ⭐)
Скрипты диагностики/фикса (на Кракене, /tmp/): wl_dedupe.py (дедуп кандидатов, --apply), wl_postcheck.py (проверка дублей после прогона), wl_orphan.py (проверка структуры блоков), wl_verify_final.py, wl_order2.py, wl_grab_check.py (Radarr queue + Transmission), wl_fix_parser3.py, wl_fix_syncdown.py, wl_fix_star.py, wl_fix_clean2.py, wl_star.py, wl_star_check.py, wl_final_star.py.
Скрипты повторного использования: wl_dedupe.py — при накоплении дублей; wl_postcheck.py — контроль после каждого прогона; wl_grab_check.py — что реально ушло в закачку.
Пути к конфигам *arr (проверено 2026-09-20, в доке ранее были ошибочны)
Конфиги Radarr/Sonarr/Transmission лежат на втором диске (6194539b-03e0-4f33-986b-4da09834e82f), не на том же, что медиа:
Radarr API key: /srv/dev-disk-by-uuid-6194539b-.../docker/radarr/config/config.xml
Sonarr: /srv/dev-disk-by-uuid-6194539b-.../docker/sonarr/config/config.xml
Transmission: /srv/dev-disk-by-uuid-6194539b-.../docker/transmission/settings.json
Radarr API base: http://localhost:7878/radarr/api/v3/ (ключ из config.xml)
Sonarr API base: http://localhost:8989/sonarr/api/v3/
Проверка закачки: queue в Radarr показывает grabbed/downloading; history даёт точное имя взятого релиза. Transmission — docker exec transmission transmission-remote --list (RPC-юзер в settings.json пустой).
🔴 2026-09-21: ДИСК ЗАПОЛНЕН 100%, КРОН nightly/discover ОСТАНОВЛЕН
Причина остановки: sync-down --batch 5 (5 фильмов ежедневно) работал без проверки свободного места. Диск заполнился до нуля байт.
Признак «сервисы ломаются» — Radarr отдаёт disk I/O error
curl -s "http://localhost:7878/radarr/api/v3/movie?apikey=$KEY"
# → {"message": "disk I/O error\ndisk I/O error",
# "description": "...System.Data.SQLite.SQLiteException (0x87AF045D): disk I/O error..."}
SQLite не может писать при нуле свободного места. Метаданные Radarr через API не читаются, пока диск не освобождён. Это первое, что надо проверять при «Radarr молчит» на заполненном диске.
Фактическое заполнение (2026-09-21)
df -h → /dev/sda1 1.8T 1.7T 0 100% /srv/dev-disk-by-uuid-6194539b-...
df -h → /dev/mmcblk0p2 14G 4.7G 8.4G 36% / (SD-карта, не медиа-диск)
Docker: 29.4.3, Debian 12 bookworm, arch aarch64, 25 контейнеров, 22 образа.
Что именно съело место — 5 REMUX-файлов = 403.4 ГБ
Замер find -printf по /media/movies-radarr (27 видеофайлов). Настоящих remux'ов ровно 5 — по имени файла (Remux/BDREMUX/BDRemux):
| Размер | Фильм | Файл |
|---|---|---|
| 92.0 GB | Babylon (2022) | Babylon (2022) - UHD BD-Remux by Wild_Cat.mkv |
| 89.4 GB | Schindler's List (1993) | Schindler's.List.1993.2160p.HDR.BDRemux.mkv |
| 85.6 GB | Midsommar (2019) | Midsommar.2019.DC.BDREMUX.2160p.HDR.seleZen.mkv |
| 79.7 GB | Triangle of Sadness (2022) | Triangle.of.Sadness.2022.BDREMUX.2160p.HDR.seleZen.mkv |
| 56.7 GB | Unhinged (2020) | Неистовый.2020.UHD.Blu-Ray.Remux.2160p.mkv |
| 403.4 GB | итого |
⚠️ Остальные 22 файла — НЕ remux (WEB-DL / BDRip / x264 / x265, 4–29 ГБ). Крупные, но кодированные — не трогать. Например Oldboy (2003)/Oldeuboi [2003].mkv 28.6 ГБ и Project.Hail.Mary...2160p.iT.WEB-DL.DV.HDR.H.265.mkv 28.1 ГБ — не remux, несмотря на размер.
Корень: захардкоженный qualityProfileId: 1 (профиль Any) в arr.py — починен только 2026-09-04. Всё, что качалось до этого, брало remux'ы по 60–98 ГБ.
✅ ИСПРАВЛЕНО 2026-09-21: ВСЕ 5 фильмов ЕСТЬ в watchlist
⚠️ Ранее в этой доке стояло ошибочное «Schindler's List не найден в файле». Это был ложный вывод из одностороннего грепа: фильм записан по-русски — «Список Шиндлера» (строка 396), а искали по латинице. Alex поправил. Правило: искать и ru, и en —
grep -i "schindler\|шиндлер\|список"— ПРЕЖДЕ чем утверждать «нет в списке».
| Фильм | Строка в watchlist | Метка ⬇️ |
|---|---|---|
| Неистовый (Unhinged) | 107: - [x] ⬇️ Неистовый |
✅ есть (и [x]) |
| Список Шиндлера | 396: - [ ] ⬇️ Список Шиндлера |
✅ есть |
| Babylon (2022) | 83: - [ ] Babylon (2022) |
❌ потеряна (баг mark_queued) |
| Midsommar | 81: - [ ] 🧟♀️ midsommar 2019 |
❌ потеряна |
| Triangle of Sadness | 82: - [ ] Triangle of Sadness 2022 |
❌ потеряна |
Корень потери 3 меток — баг mark_queued (parser.py:77): regex требует название сразу после - [ ],
но перед ним бывает эмодзи (- [ ] 🧟♀️ midsommar 2019) → не матчит; при провале функция молча ничего не пишет
(if new_text != text), а sync_down.py её результат не проверяет. Детали — family/plans/watchlist-disk-overflow-fix, Фаза 1.
❌ «Восстановить потерянные ⬇️» — отменено (неверная постановка)
⬇️ = «добавлен в Radarr / качается». После удаления файлов метка должна быть снята, а не восстановлена.
У Babylon/Midsommar/Triangle метки и не было → ничего не делать (консистентно: файла нет, метки нет).
Снять ⬇️ нужно у Списка Шиндлера (396 → [ ]) и Неистового (107 → [x], [x] не трогать).
⚠️ Функции unmark_queued() в коде НЕТ — писать в рамках Фазы 1.
✅ Порядок работ — Фазы 0 и 0b ВЫПОЛНЕНЫ (2026-09-21)
- ✅ Удалены 5 remux-файлов (403 ГБ) →
df100% → 77%, свободно 404 ГБ → Radarr API ожил (49 фильмов). Удаление — файлами напрямую (rmпо точным путям), не через Radarr API (тот лежал). Папки с.nfo/постерами не тронуты. - ✅ Radarr:
RescanMovie+ снятиеmonitoredу id 4/18/35/36/37 → всеmonitored=false,hasFile=false. ⚠️ Без этого Radarr перекачал бы все 403 ГБ. Порядок:monitored=falseчерез PUT до любого поиска. - ✅ Снят
⬇️у Шиндлера (396) и Неистового (107) — через новуюunmark_queued(). - ✅
MIN_FREE_GB = 100.0вsync-down— согласован с Alex («Да»), коммит2642882. - ✅ Лимит качества Alex считает уже настроенным (
WEB-4K (no remux), id 7) — новый лимит не делали.
Бэкапы: ~/backup-movies-watchlist-20260921-150759.md, ~/backup-radarr-unmonitor-20260921-151117.json,
~/crontab-backup-20260921.txt. Скрипты: /tmp/rm_remux.sh, /tmp/unmonitor_remux.sh.
Остановка крона (сделано 2026-09-21)
crontab -l > ~/crontab-backup-$(date +%Y%m%d-%H%M%S).txt # бэкап ПЕРЕД
crontab -l | grep -v "watchlist-nightly\|watchlist-discover" > /tmp/crontab-new.txt
crontab /tmp/crontab-new.txt
crontab -l | grep -c "watchlist-nightly\|watchlist-discover" # → 0 (проверка)
Бэкап: ~/crontab-backup-20260921.txt.
Осталось в кроне: sync-vault (часовой), 2× pgs-to-srt, watchlist-cleanup (02:00).
Убрано: watchlist-nightly (01:00), watchlist-discover (вс 09:00).
🔴 Незакрытые дефекты скрипта (моя ответственность)
| # | Дефект | Следствие | Статус |
|---|---|---|---|
| 1 | Нет проверки свободного места перед sync-down |
Radarr качает, пока диск не кончится | ✅ исправлено 2026-09-21 |
| 2 | Нет лимита размера файла | Один фильм = 60–100 ГБ | ⚠️ Alex считает лимит качества достаточным — не делаем |
| 3 | Нет стоп-крана при заполнении > порога | Нет автоматической остановки | ✅ = п.1 |
| 4 | cleanup не видит /media/movies |
Просмотренное не удаляется, диск не освобождается | ⏸ отдельная задача |
| 5 | mark_queued молча проваливается |
часть фильмов без метки ⬇️ |
✅ исправлено 2026-09-21 |
Правило на будущее: любой скрипт, ставящий закачки в очередь, ОБЯЗАН проверять свободное место до добавления. Лимита качества недостаточно — он ограничивает тип релиза, но не размер.
✅ 2026-09-21 (продолжение): Фазы 1–4 РЕМОНТА ВЫПОЛНЕНЫ — коммит 2642882
Полный план и разбор — family/plans/watchlist-disk-overflow-fix. Ниже — операционная выжимка для будущих сессий.
Коммит: 2642882 fix(sync-down): MIN_FREE_GB guard, horror filter (star overrides), reliable mark_queued
Предыдущий HEAD — aafbaf7. Рабочее дерево чистое.
Фаза 1 — MIN_FREE_GB (стоп-кран по месту)
MIN_FREE_GB = 100.0
def _free_gb(path: Path) -> float:
p = Path(path).resolve()
while not p.exists() and p != p.parent:
p = p.parent
return shutil.disk_usage(p).free / (1024 ** 3)
Проверка стоит в run() сразу после получения Jellyfin-user, перед parse():
при нехватке места печатает [STOP] free space ... < 100 GiB — nothing queued и return.
Путь берётся из env WATCHLIST_MEDIA_ROOT, иначе watchlist.parent.
⚠️ shutil.disk_usage, НЕ df — df врёт на ~4% (учитывает root-резерв ext4).
⚠️ Питфолл из watchlist-cleanup: /media — не медиа-диск, а пустая папка на SD-карте. Учитывать при задании WATCHLIST_MEDIA_ROOT.
Фаза 2 — Фильтр ужасов + ⭐ > 🧟
Расширен существующий фильтр (был только рейтинговый), точка — бывший sync_down.py:54:
| Условие | Действие |
|---|---|
есть ⭐ (U+2B50) |
качаем всегда — continue до всех фильтров |
candidate.is_horror() без ⭐ |
пропускаем |
рейтинг < MIN_RATING (6.0) без ⭐ |
пропускаем |
Счётчик skipped_horror, в конце лога: (N horror item(s) skipped; add ⭐ to force).
⚠️ is_horror() работает по жанрам TMDB, не по эмодзи. genres — список int-id, 27 = Horror.
Поэтому отсекается 33 (dry-run), хотя меток 🧟♀️ в файле 41 — часть строк не резолвится в TMDB.
В .resolve_cache.json 51 запись содержит genre id 27. Проверка:
jq -r "[to_entries[] | select((.value.genres // .value.candidate.genres // []) | index(27))] | length" .resolve_cache.json
⚠️ ⭐ (U+2B50) ≠ ✭ (U+272D). Приоритет — только ⭐.
Dry-run подтверждён:
free space on /home/kraken/obsidian/family/documents: 403.4 GiB (threshold 100 GiB)
⭐ 1 priority item(s) ['Простите за беспокойство 2018']
(14 item(s) below rating 6.0 skipped)
(33 horror item(s) skipped; add ⭐ to force)
Queued 5/5 items [dry-run]
Фаза 3 — Надёжный mark_queued
parser.py::mark_queued() переписан:
- возвращает
bool - регексп поглощает опциональный эмодзи-префикс между чекбоксом и названием
(
🧟 🗓️ ❤ ⭐ ✭ ❓ ✅ ⬇️ ▶️ ZWJ VS16 ♀️+ пробелы) - метка пишется перед префиксом:
- [ ] ⬇️ 🧟♀️ midsommar 2019 - в
sync_down.py— обёртка_mark()(4 места вызова), приFalseпечатает[!] could not write ⬇️ marker for ... (line not matched — check the watchlist syntax)
Тесты: 7/7. Проверка на копии боевого файла: строки 81/82/83 получили метку, 677 строк → 677.
Фаза 4 — unmark_queued() добавлена и применена
unmark_queued() в parser.py — инверсия mark_queued, тоже возвращает bool, поглощает эмодзи-префикс
с обеих сторон маркера. Применено на боевом файле:
107: - [x] ⬇️ Неистовый → - [x] Неистовый
396: - [ ] ⬇️ Список Шиндлера → - [ ] Список Шиндлера
Тесты: unmark 5/5 + round-trip mark → unmark → исходная строка ✅
⚠️ Не «восстанавливать» ⬇️ после удаления файла — метка = «добавлен в Radarr»; после
удаления + monitored=false она врёт и блокировала бы повторную постановку.
🔴 Открытые баги в parser.py::_clean() — НЕ исправлены
Существовали до правок 2026-09-21, проверены на старом коде — не регрессия. Решение Alex не принято.
| # | Баг | Строка | Эффект |
|---|---|---|---|
| A | re.sub(r'\s*\(\d{4}(?:/\d{4})+\)\s*$', ...) — квантификатор + требует 2+ варианта года, одиночный год не срезается |
parser.py:47 |
_clean('⬇️ Babylon (2022)') → 'Babylon (2022)' — год уходит в resolve() (запрос в TMDB) и mark_queued |
| B | _EMOJI_PREFIX = ^[<символы>]+\s* заканчивается на \s* → захватывает только первый эмодзи |
parser.py:19-38 |
'⬇️ 🧟♀️ midsommar 2019' → '🧟♀️ midsommar 2019' (эмодзи остаётся в title). Лечится формой ^(?:[<символы>]+\s*)+ |
Почему важно: грязный title идёт в resolve() (TMDB/KP), в _normalize() при sync_up
(не сойдётся с названием из Jellyfin) и в mark_queued. Фикс B частично компенсирован
правкой Фазы 3 (регексп поглощает префикс), но title всё равно уходит в resolve грязным.
Фаза 3.5 — _mark_watched в sync_up.py (исправлено локально, НЕ залито/НЕ закоммичено)
Тот же класс бага: регексп r'\s*-\s*\[ \]\s*⬇️?\s*(.*)' не пропускал эмодзи-префикс, поэтому
строка - [ ] ⬇️ 🧟♀️ midsommar 2019 никогда не перешла бы в [x]. Исправлено симметрично
(/tmp/wlsync/sync_up.py), тесты 5/6 (падение на Babylon (2022) — баг A, не от правки).
⚠️ На Kraken НЕ залито, в git НЕ закоммичено. На момент правки строк с ⬇️ + эмодзи-префиксом
в файле не было ни одной — баг проявился бы после первого боевого прогона sync-down
(правка Фазы 3 создаёт именно такой формат). Чинить синхронно.
Бэкапы этой сессии
~/backup-movies-watchlist-20260921-150759.md (перед удалением remux)
~/backup-radarr-unmonitor-20260921-151117.json (состояние Radarr до unmonitor)
~/backup-wlsync-20260921-151855/ (sync_down.py + parser.py до правок)
~/backup-movies-watchlist-unmark-20260921-152018.md (перед снятием ⬇️)
~/crontab-backup-20260921.txt (crontab до остановки)
Скрипты сессии (на Кракене /tmp/, теряются при перезагрузке)
rm_remux.sh (удаление 5 remux), unmonitor_remux.sh (снятие monitored + RescanMovie),
commit_wlsync.sh, test_marker_real.sh (тест mark_queued на копии боевого файла),
fase4_unmark.sh (снятие ⬇️).
Cron Jobs (на Кракене, восстановлены 2026-09-20, дополнены 2026-09-21)
Весь watchlist pipeline работает на Кракене (мигрировал с Eagle 2026-05-27). Крон был мёртв с 2026-06-20 по 2026-09-20, восстановлен (детали в начале документа).
⚠️ С 2026-09-21
watchlist-nightlyиwatchlist-discoverОСТАНОВЛЕНЫ (диск 100%, см. секцию выше). Таблица ниже — целевое состояние; фактически активен толькоwatchlist-cleanup.
| Job | Расписание | Скрипт | Факт 2026-09-21 |
|---|---|---|---|
watchlist-nightly |
ежедн. 01:00 | ~/scripts/watchlist-nightly.sh |
⏹ ОСТАНОВЛЕН |
watchlist-cleanup |
ежедн. 02:00 | ~/scripts/watchlist-cleanup.sh --apply |
✅ активен |
watchlist-discover |
вс 09:00 | ~/scripts/watchlist-discover.sh |
⏹ ОСТАНОВЛЕН |
⚠️ Фактический путь — ~/scripts/, не ~/.hermes/scripts/. Оба скрипта вызывают CLI напрямую через uv run --project $WS, без LLM. uv лежит в ~/.local/bin/uv.
⚠️ ~/scripts/ — root:root 755. Добавить/заменить скрипт можно только sudo install -m 755 (у kraken sudo без пароля). Обычная запись (>, cp) откажет с Permission denied.
⚠️ watchlist-cleanup.sh без аргумента = dry-run. В кроне обязателен --apply, иначе удаление не произойдёт и лог будет писать «nothing to delete». Это самая тихая из возможных поломок — проверять флаг при любой правке крона.
WS в обоих скриптах: /srv/dev-disk-by-uuid-49e8f586-3839-4c5d-a1e1-58bfc3579ade/watchlist-sync — другой UUID, чем фактический корень репо (см. ниже). Возможный источник поломки: проверить, что путь резолвится.
watchlist-nightly.sh (на Кракене):
sync-vault.sh(pull) →process-thumbs --apply→sync-vault.sh(push)resolve --recheck --apply→sync-down --apply --batch 5→sync-up --apply→sync-vault.sh(push)
Пути на Кракене (HDD UUID 49e8f586-3839-4c5d-a1e1-58bfc3579ade):
/home/kraken/obsidian→ symlink на/srv/dev-disk-by-uuid-.../obsidian/home/kraken/Developer/watchlist-sync→ symlink на/srv/dev-disk-by-uuid-49e8f586-.../watchlist-sync
✅ Путь репо в скриптах ВЕРНЫЙ (проверено 2026-09-20). В watchlist-nightly.sh и watchlist-discover.sh:
WS=/srv/dev-disk-by-uuid-49e8f586-3839-4c5d-a1e1-58bfc3579ade/watchlist-sync
UV=~/.local/bin/uv
ls -la подтверждает: /srv/.../watchlist-sync существует, внутри watchlist_sync/, .venv/, .resolve_cache.json, pyproject.toml, .env (есть, содержит TMDB_KEY/KP_KEY/RADARR_/SONARR_; нет ZULIP_*).
Ранее в доке фигурировал путь
.../6194539b-.../watchlist-sync«как реальный корень». На 2026-09-20 рабочий путь —49e8f586, и он же используется в скриптах. Не «исправлять» его обратно.
Ещё одно подтверждённое отличие: ~/scripts/ — реальная директория (drwxr-xr-x root root), а ~/.hermes/ на Кракане содержит только skills/. Скрипты лежат в ~/scripts/, не в ~/.hermes/scripts/.
Как запускать CLI вручную (рабочая команда)
ssh kraken
cd /srv/dev-disk-by-uuid-49e8f586-3839-4c5d-a1e1-58bfc3579ade/watchlist-sync
uv run --project . watchlist-sync <команда> # resolve / sync-down / sync-up / process-thumbs / discover
Или целиком пайплайн: bash ~/scripts/watchlist-nightly.sh
Discover — Фильтрация
| Тип | Порог |
|---|---|
| Мировые | IMDb ≥ 7.0 или KP ≥ 7.0 |
| Российские | KP ≥ 8.5 (защита от накрутки) |
| Upcoming | год ≥ текущий−1 |
| Минимум голосов | IMDb ≥ 1000 или KP ≥ 500 |
KP API endpoint: https://api.poiskkino.dev/v1.4/movie (redirect с api.kinopoisk.dev).
*arr Stack — Индексеры (Prowlarr)
| ID | Имя | Тип | Примечание |
|---|---|---|---|
| 1 | RuTracker | semi-private | блокирует при rate limit на сутки |
| 2 | RuTor | public | поиск по imdbid |
| 3 | Byrutor | public | |
| 4 | NoNaMe Club | semi-private | анонимный |
| 5 | Kinozal | semi-private | резерв |
| 6 | Kinozal (M) | semi-private | магнет ✅ — без grab limit, предпочтительный |
Pi-hole питфолл: блокирует skyhook.sonarr.tv → Sonarr/Radarr поиск молча не запускается. Фикс: dns: [8.8.8.8] в docker-compose.yml.
Router Script — Маршрутизация по жанру
После импорта Sonarr/Radarr: router.py --apply создаёт symlinks:
- Animation (не R/18+) →
/media/cartoons/или/media/cartoons-series/ - Documentary →
/media/documentaries/или/media/documentaries-series/ - Прочее →
/media/movies/или/media/series/
Копирует NFO + постер рядом с symlink (Jellyfin не видит метадату через symlink напрямую).
watchlist-sync — Python CLI
Репо: ~/Developer/watchlist-sync (GitHub: mallexxx/watchlist-sync).
watchlist-sync resolve — TMDB + KP API → ID, тип (movie/tv)
watchlist-sync sync-down — добавить батч (max 5) в Radarr/Sonarr, поставить ⬇️
watchlist-sync sync-up — Jellyfin watched → [x] в .md
Резолвер: TMDB /search/multi + KP fallback. Пороги: min_rating: 6.0, min_score: 0.75. При 2+ кандидатах → ❓ (LLM разрешает в cron каждые 6ч, нерешённые → уточнение в Zulip).
Stage 2 (после ≥50 просмотров): AI-curated recommendations — три списка (с женой / один / семья) на основе TMDB профиля, добавляются в конец watchlist как ## 🤖 Рекомендации (YYYY-WNN).
Jellyfin library mapping: Movies → movies, Cartoons → mixed (movies + episodes).
Питфоллы
KP Markdown Link Parsing Bug
Фильмы из Kinopoisk с синтаксисом [Title](https://kinopoisk.ru/film/12345/) (2025) передавались как есть в TMDB search → не находились → помечались ❓ навсегда (not i.ambiguous пропускает при следующем запуске). Фикс: commit 2db1ed9 — извлекать plain title из markdown link до передачи в TMDB. Ошибочно помеченные ❓ требуют ручного --recheck.
KP API Endpoint
api.kinopoisk.dev редиректит на api.poiskkino.dev/v1.4/movie. Использовать destination redirect напрямую.
🔴 Жёстко зашитый qualityProfileId в arr.py (корень жирных remux) — РЕШЕНО
Найдено 2026-09-04. В watchlist_sync/arr.py изначально был захардкожен qualityProfileId: 1 и для Radarr, и для Sonarr:
# radarr_add (~стр. 77) / sonarr_add (~стр. 112)
"qualityProfileId": 1,
Профиль 1 (Radarr) = 'Any' — разрешает ВСЁ вплоть до Remux-2160p и BR-DISK, поэтому каждый новый фильм/сериал попадал в «Any» и Radarr скачивал 60GB remux'ы.
✅ Решение принято и применено 2026-09-04: создан профиль WEB-4K (no remux) → Radarr id 7, Sonarr id 7; arr.py теперь пишет qualityProfileId: 7. Подробности профиля — в arr-stack-kraken, секция «Качество загрузки для НОВЫХ фильмов/сериалов».
✅ Баг накопления кандидатов в mark_ambiguous() (найден и исправлен 2026-09-20)
Кандидаты-под-буллеты под ❓-строками дублировались на каждом прогоне resolve (+3 строки за раз), потому что re.sub снимал только саму ❓-строку, а новые кандидаты вставлялись перед старыми. Паттерн исправлен на захват всего блока (маркер + под-буллеты). Полная механика, две неудачные версии паттерна и чистка 168 дублей — в family/projects/watchlist-automation.
Правило для парсеров watchlist: под-буллет кандидата = отступ ≥4 пробела + буллет + НЕ чекбокс (- [ ]/- [x]). Строки фильмов имеют отступ 2 пробела. Внутри *-группы перенос строки обязателен — иначе regex проглатывает соседние фильмы.
Open Tasks
- ✅ [2026-09-20] Фикс
sync_down.pyРЕАЛИЗОВАН — коммитada47da.resolved.sort()убран, добавленMIN_RATING = 6.0с видимымprint. Нулевые рейтинги отсекаются (решение Alex: «отсекать»). - ✅ [2026-09-20] Приоритет
⭐(U+2B50) РЕАЛИЗОВАН — коммит7db9385. ⭐ первыми из всех секций, обходит фильтрMIN_RATING.⭐срезается из запроса TMDB (parser._cleanчерезstr.replace, НЕ через end-anchored класс).✭(U+272D) приоритетом не считается;[x]пропускается; под-буллеты кандидатов не считаются. - ✅ [2026-09-20] Реальный прогон с новой логикой выполнен — 19:53,
Done, +3 фильма. Батч по порядку файла, ⭐ первым. - ✅ [2026-09-20] Крон ВОССТАНОВЛЕН — установлен 21:21 в crontab юзера
kraken, бэкап~/crontab-backup-20260920-212155.txt.nightly01:00 +discoverвс 09:00, логи в/tmp/. Первая проверка в бою — прогон в 01:00. - 🟡 [2026-09-20] Логи крона в
/tmp/— теряются при перезагрузке. Перенести в постоянное место (~/logs/или на диск), если нужна история прогонов. - ✅ [2026-09-21] Удаление просмотренных фильмов при заполнении диска >90% — ЗАКРЫТО И В КРОНЕ. Обёртка
~/scripts/watchlist-cleanup.sh(была в/tmp— перенесена черезsudo install, у kraken sudo без пароля), cron0 2 * * * ... --apply, лог/home/kraken/logs/watchlist-cleanup.log. Корзина Radarr (recycleBin) — решено НЕ настраивать (контейнерный/хостовый пути расходятся; Alex больше не переспрашивать). Обёртка по умолчанию делает dry-run — без--applyв кроне был бы вечный холостой ход. Полные детали — family/plans/watchlist-cleanup. ⚠️ Питфолл:/media— не медиа-диск, а пустая папка на SD-карте (/dev/mmcblk0p2), отдаёт 33.9%. Реальный диск/dev/sda1в/srv/dev-disk-by-uuid-6194539b-.../. Использоватьos.statvfs, неshutil.disk_usage("/media"). - ✅ [2026-09-21] ДИСК ОСВОБОЖДЁН: 100% → 77%, свободно 404 ГБ. Удалены 5 remux-файлов (403 ГБ); в Radarr снят
monitoredу id 4/18/35/36/37 (hasFile=false) — иначе он перекачал бы всё заново. Radarr API ожил (49 фильмов). Бэкапы:~/backup-movies-watchlist-20260921-150759.md,~/backup-radarr-unmonitor-20260921-151117.json. - ✅ [2026-09-21]
watchlist-nightly+watchlist-discoverОСТАНОВЛЕНЫ, ЗАТЕМ ВОЗВРАЩЕНЫ. Останавливали при диске 100% (sync-down --batch 5без проверки места). Возвращены в тот же день после Фаз 1–4, уже с--batch 2. Бэкапы crontab:~/crontab-backup-20260921.txt(до),~/crontab-backup-20260921-152741.txt(после). Итоговый crontab:0 1 * * * watchlist-nightly.sh # batch 2 0 9 * * 0 watchlist-discover.sh 0 2 * * * watchlist-cleanup.sh --apply - ✅ [2026-09-21] ФАЗА 1 РЕМОНТА ВЫПОЛНЕНА:
MIN_FREE_GB = 100.0вsync-down— коммит2642882. Проверка черезshutil.disk_usage(НЕdf, врёт на ~4%), путь из envWATCHLIST_MEDIA_ROOTиначеwatchlist.parent. Печатает[STOP] free space ... < 100 GiB — nothing queuedи выходит. Подтверждено dry-run:403.4 GiB (threshold 100 GiB). Полный план — family/plans/watchlist-disk-overflow-fix. - ✅ [2026-09-21] ФАЗА 2 ВЫПОЛНЕНА: фильтр ужасов +
⭐ > 🧟— коммит2642882. Точка — бывшийsync_down.py:54.⭐обходит все фильтры (continueдо проверок); ужас без⭐пропускается; рейтинг < 6.0 пропускается. Dry-run: 33 horror skipped, 14 по рейтингу. ⚠️is_horror()считает по жанрам TMDB (id 27), не по эмодзи — поэтому 33, а не 41. - ✅ [2026-09-21] ФАЗА 3 ВЫПОЛНЕНА: надёжный
mark_queued— коммит2642882. Возвращаетbool, поглощает эмодзи-префикс (- [ ] ⬇️ 🧟♀️ Title), обёртка_mark()вsync_down.pyлогирует провал[!]. Тесты 7/7 + проверка на копии боевого файла. Корень потери 3 меток⬇️. - ✅ [2026-09-21] ФАЗА 4 ВЫПОЛНЕНА:
unmark_queued()добавлена,⬇️снят — у Шиндлера (396) и Неистового (107). Функции в коде не было. Тесты unmark 5/5 + round-trip. Бэкап~/backup-movies-watchlist-unmark-20260921-152018.md. - ✅ [2026-09-22] ОСИРОТЕВШИЕ СТРОКИ в
## Новинки— ИСПРАВЛЕНО. Симптом Alex: «Проект Конец света» в каталоге Lampa как непросмотренный, хотя в главном списке он[x]. Корень:process_thumbsобрабатывает только👍/👎/[x]; строка- [ ] ⬇️ [Название](kp) (2025) IMDb 7.1(без оценки) уходила вkeepнавсегда, а при наличии[x]-двойника давала дубль в каталоге. Фикс: сборwatched_titles/watched_kpпо всем секциям кроме Новинок + удаление сирот изkeep; нормализация обязана срезать год и рейтинги, иначепроектконецсвета≠проектконецсвета2026imdb84kp84. Флаг_patch_droppedдобавлен в ранний выход (иначе запись файла пропускалась).cli.py— явные ветки вывода👍/👎/[x]/drop. Проверено: удаляется ровно 1 строка, 683 → 682; каталог 404 → 403. Бэкапы:~/backup-watchlist-before-orphan-fix-20260922-113925.md, код —parser.py.bak-orphan-*,.bak-guard-*,.bak-bs-*,cli.py.bak-cli-*. Полный разбор — секция «ОСИРОТЕВШИЕ СТРОКИ» выше; связь с каталогом Lampa — personal/plans/lampac-watchlist-plugin. - 🔴 [2026-09-22] ПИТФОЛЛ правки
parser.pyскриптами: двойное экранирование съедает слэши.r'\1'→ реальный байт\x01,r'\b'→ байт\x08(backspace).compile()/ast.parse()проходят — синтаксис валиден, а регекспы молча не работают (нормализация не срезала рейтинги, сироты не находились). Правило: после записи патча проверять файл наchr(1)иchr(8), а не только на синтаксис. По возможности править черезpatch/Obsidian, а не генератором строк. - 🔴 [2026-09-21] ФАЗА 5: ВЕРНУТЬ КРОН — ждёт.
watchlist-nightly(01:00) +watchlist-discover(вс 09:00) остановлены, бэкап~/crontab-backup-20260921.txt. Возвращать с--batch 2(не 5) и только после проверки Фаз 1–2 в бою (сначала dry-run). - 🔴 [2026-09-21] ОТКРЫТЫЕ БАГИ
_clean()— решение Alex НЕ принято. Баг A (parser.py:47):(?:/\d{4})+требует 2+ варианта года → одиночный(2022)не срезается, уходит вresolve(). Баг B (parser.py:19-38):_EMOJI_PREFIXзаканчивается на\s*→ берёт только первый эмодзи,🧟♀️остаётся вtitle. Оба существовали до правок (проверено на старом коде), не регрессия. Лечение B — форма^(?:[<символы>]+\s*)+. - 🔴 [2026-09-21]
_mark_watchedвsync_up.py— исправлено локально, НЕ залито/НЕ закоммичено. Тот же класс бага: строка- [ ] ⬇️ 🧟♀️ Titleне перешла бы в[x]. ⚠️ Правка Фазы 3 создаёт именно такой формат → чинить синхронно с возвратом крона, иначеsync-upперестанет видеть новые закачки. - 🔴 [2026-09-21] КОРЕНЬ:
[x]не проставляется на новые закачки.sync-up(Jellyfin watched →[x]) видит только 6 старых фильмов из/media/movies, ни одного из/media/movies-radarr. Пока это не починено, cleanup бесполезен, а диск будет заполняться. - 🟡 [2026-09-21] Cleanup не видит
/media/movies— смотрит только Radarr-библиотеку, поэтому просмотренное из папки ручных торрентов не удаляется. Расширять — отдельно и осторожно (удаление файлов). - 🟡 [2026-09-20]
sync-downберёт всего 5 за прогон — при 250 кандидатах разбор очереди займёт ~50 ночей. Рассмотреть увеличение--batch(сейчас--batch 5вwatchlist-nightly.sh). Особенно актуально, раз фильтр<6.0уже отсеял 18. - 🟡 [2026-09-20] Разобрать 5 застрявших закачек в очереди Radarr со статусом
warning:Brokeback Mountain,Мортал Комбат 2,Sorry to Bother You,Город бога,День хомячка. Висят давно, релиз не находится/не качается. - 🟡 [2026-09-20]
Oficina do Diabo/Мастерская Сатаны(2025) — в Radarr, но релиз не найден. Ждёт RSS. Проверить через несколько дней. - 🟡 [2026-09-20] Настроить Zulip-пинг —
ZULIP_URL/ZULIP_API_KEY/ZULIP_BOT_EMAILотсутствуют в.envwatchlist-sync, 59 нерезолвленных items ушли в никуда без уведомления. - 🟡 [2026-09-20] Добавить pre-check дублей в
sonarr_add()— сейчас при уже существующей серии прилетает400 SeriesExistsValidatorи шумит в логе. Вradarr_add()проверка есть. - 🟡 [2026-09-20] Синхронизировать документацию с кодом — watchlist-sync §1.2 и §1.3 описывают порог
min_rating: 6.0и «приоритет = порядок в файле», которых в коде не было. Порог теперь реализован (MIN_RATING), приоритет по порядку тоже, плюс появился приоритет ⭐ — обновить формулировки в watchlist-sync. - 🔴 [2026-09-04] Решить стратегию качества загрузки новых фильмов — уже РЕШЕНО 2026-09-04 (профиль
WEB-4K (no remux), id 7 для Radarr и Sonarr). Осталось только убедиться, что старые⬇️-позиции, добавленные до фикса, не тянут remux. - 🟡 Оставшиеся
❓-позиции (71 маркер) — намеренно не чистятся вручную (решение Alex 2026-09-20). Разбираются черезresolve --recheckпри работающем кроне. - 🟡 Передавать Transmission locations через router script при каждом импорте (сейчас вручную)
- 🔴 rsync невыполненных фильмов с HTPC на Кракен (
Фильмы не перенесенные с htpc на кракен.md) - 🟡 Watchlist sync-up: добавить перенесённые фильмы в нужные разделы
Связанные заметки
- watchlist-discover — шаг discover: KP API, format, CLI команды
- watchlist-sync — основной проект (resolve, sync-down, sync-up)
- arr-stack-kraken — детальные питфоллы *arr стека
- jellyfin-transcode-rpi5 — Jellyfin PGS/ASS питфоллы `:
| Строка | Что делает |
|---|---|
👍 |
копирует в начало главного списка, из Новинок удаляет |
👎 |
переносит в ## Новинки👎 |
[x] |
переносит в конец [x] главного списка |
| всё остальное | остаётся в keep = навсегда в ## Новинки |
Строка вида - [ ] ⬇️ [Название](kp) (2025) IMDb 7.1 не имеет ни 👍, ни 👎 → попадает в keep.
Если тот же фильм уже [x] в главном списке — он висит в Новинках как непросмотренный.
В каталоге Lampa это дубль.
Фикс — два блока в parser.py, оба в process_thumbs():
-
Сбор просмотренного — обход всех строк файла, исключая
## Новинкии## Новинки👎:watched_titles = set() # нормализованные названия [x]-строк watched_kp = set() # KP-id из [x]-строкНормализация (
_norm_title) обязана срезать год и рейтинги — иначе ключи не совпадут:s = re.sub(r'\[([^\]]+)\]\([^)]*\)', r'\1', s) # ссылка -> текст s = re.sub(r'\(\s*\d{4}(?:\s*/\s*\d{4})*\s*\)', ' ', s) # (2025), (2006/2020) s = re.sub(r'\b(?:IMDb|KP)\s*[\d.,]+', ' ', s, flags=re.I) # IMDb 8.4 KP 8.4 s = re.sub(r'[^0-9a-zа-яё]+', '', s.lower(), flags=re.I)⚠️ Без среза года/рейтингов в главном списке ключ
проектконецсвета, а в Новинкахпроектконецсвета2026imdb84kp84— не совпадают. -
Удаление сирот из
keep— поwatched_kp(точное) илиwatched_titles(нормализованное), с записью вactionsкак('[drop] уже просмотрено в другой секции', ...). -
Флаг
_patch_dropped— ранний выходif not thumbs_up and not thumbs_down and not watched_items: return []пропускал запись файла, даже когда сироты найдены. Добавленоand not _patch_dropped.
cli.py: вывод [drop] был неотличим от 👎 (обобщённый else) → явные ветки
👍 / 👎 / [x] / drop.
Проверка (2026-09-22): на копии боевого файла — удаляется ровно 1 строка, 683 → 682, «Конец света» пропадает из всех 4 категорий каталога (404 → 403 позиции).
Бэкап до правки: ~/backup-watchlist-before-orphan-fix-20260922-113925.md.
Бэкапы кода: parser.py.bak-orphan-*, .bak-guard-*, .bak-bs-*; cli.py.bak-cli-*.
🔴 Питфолл при правке parser.py скриптами
Двойное экранирование в patch-скриптах съедает обратные слэши. Дважды получил битые байты:
r'\1'→ реальный байт\x01в файлеr'\b'→ реальный байт\x08(backspace)
Оба раза compile() проходил (валидный питон), но регекспы молча не работали.
Правило: после записи патча проверять файл на байты chr(1) и chr(8),
а не только на синтаксис. Либо править через Obsidian-patch напрямую, а не генератором строк.
🔴 2026-09-21: ДИСК 100% — инцидент и починка sync-down
Симптом: диск /dev/sda1 на Kraken — 0 байт свободно (df 100%). Radarr API отдавал
disk I/O error (SQLite не может писать при нуле места) — метаданные недоступны.
Причина (НЕ cleanup): sync-down в кроне watchlist-nightly (01:00) добавлял
5 фильмов ежедневно (--batch 5), без проверки свободного места и без фильтра ужасов.
Radarr качал remux'ы по 57–93 ГБ, пока диск не кончился. Профиль WEB-4K (no remux) фильтрует
тип релиза, но НЕ размер файла и НЕ проверяет место — от переполнения не защищает.
Освобождено вручную: удалены 5 remux = 403 ГБ (Babylon 93 / Schindler's List 90 /
Midsommar 86 / Triangle of Sadness 80 / Unhinged 57 ГБ), всем выставлен monitored=false
через Radarr API. Диск: 100% → 77% (404 ГБ).
Починено в коде (коммиты 2642882, a131208):
| Что | Деталь |
|---|---|
| Стоп-кран по месту | MIN_FREE_GB = 100.0 в sync_down.py, проверка через shutil.disk_usage (не df) |
| Фильтр ужасов | candidate.is_horror() (genre_id=27) пропускает ужасы, если нет ⭐ |
mark_queued |
возвращает bool, поглощает эмодзи-префикс, логирует промах через _mark() |
unmark_queued |
новая функция — снятие ⬇️ (не существовала) |
_clean |
чистит множественные эмодзи (^(?:[chars]+\s*)+) и одиночный год (* вместо +) |
sync_up._mark_watched |
видит эмодзи-префикс (иначе фильм не переходил в [x]) |
--batch |
5 → 2 в ~/scripts/watchlist-nightly.sh (root:root, править sudo cp) |
Питфоллы, найденные в процессе:
~/scripts/на Kraken — root:root 755,scpпадает сPermission denied. Толькоsudo cp.- Крон-файлы вернуть можно только через
crontab /tmp/файл;crontab -l > fдля бэкапа обязателен. duпо USB-HDD не влезает в SSH-таймаут на 100%-заполненном диске — запускать в фоне черезsystemd-run.- Когда диск в нуле, сначала освободить место, только потом любые API-вызовы Radarr: SQLite мёртв.
Открытая задача: cleanup (family/plans/watchlist-cleanup) не покрывает /media/movies —
смотрит только Radarr-библиотеку, поэтому просмотренное из ручных торрентов не удаляется.
Пересечение [x] ∩ Radarr-файлы = 0.
Полный разбор инцидента: family/plans/watchlist-disk-overflow-fix
🔴 Аудит 2026-09-20: как выглядело состояние ДО восстановления (историческая справка)
На момент аудита автодобавление фильмов НЕ работало по расписанию. Скрипты были живы, но их никто не запускал.
Доказательства (проверено 2026-09-20):
crontab -lюзераkraken— watchlist-строк НЕТ. Есть толькоsync-vault.sh(каждый час) и дваpgs-to-srt(часовой + 03:00).- root crontab и
/etc/cron.d/:grep -rl watchlist→ пусто. - systemd timers (user + system) → 0 штук.
- Внутри Hermes-контейнера на Кракене:
docker exec hermes-kraken /opt/hermes/.venv/bin/hermes cron list→ "No scheduled jobs." - Последний коммит автором
Hermes(= nightly-скрипт) — 2026-06-20. Распределение по месяцам: май 1, июнь 9, июль/август/сентябрь — 0. Единственные коммиты в vault после июня — отeagle,syncthing,taiga.
Скрипты существуют и исполняемы, но не в кроне:
~/scripts/watchlist-nightly.sh mtime 2026-05-29
~/scripts/watchlist-discover.sh mtime 2026-05-29
⚠️ Изначальная гипотеза «файл засран от многомесячного простоя» — НЕВЕРНА. Дубли создавались на каждом прогоне
resolveиз-за бага вmark_ambiguous()(см. ниже, исправлен). 33 строки у «Nightmare Alley» = 11 прогонов. Простой лишь позволил мусору накопиться незамеченным. Мусор вычищен, баг закрыт.
✅ Баг дублирования кандидатов в mark_ambiguous() — НАЙДЕН И ИСПРАВЛЕН (2026-09-20)
Симптом
Под каждой ❓-строкой копились одинаковые дампы кандидатов. Рост ровно +100 байт за прогон (3 строки кандидатов) — подтверждено экспериментом: 26430 → 26530 → 26630 → 26730 байт.
Корень (точная механика)
В watchlist_sync/parser.py, функция mark_ambiguous():
pattern = re.compile(
r'(^\s*-\s*\[ \]\s*)[❓]?\s*(' + escaped + r')\s*$', # ← матчит ТОЛЬКО строку ❓
re.MULTILINE
)
...
replacement = rf'\1❓ \2\n{cand_lines}' # ← новые кандидаты
new_text = pattern.sub(replacement, text, count=1) # ← ВСТАВЛЯЮТСЯ перед старыми
Паттерн снимал только саму ❓-строку. Замена вставляла \n + 3 новых кандидата, а старые 33 строки оставались ниже — реплейс не знал о них. Итог: каждый прогон добавляет ещё один блок кандидатов поверх существующих.
Важно: ❓-строки НЕ дублировались (каждая — одна). Дублировались под-буллеты под ними.
Фикс
Паттерн теперь захватывает весь блок: строку-маркер ПЛЮС её под-буллеты, и перезаписывает целиком.
# Candidate sub-bullet: indent of 4+ spaces, a bullet, NOT a checkbox line.
# Each iteration REQUIRES its own leading newline -- otherwise `*` walks onto
# the following line and swallows neighbouring items.
_SUB_BULLETS = r'(?:\n[ \t]{4,}-[ \t]*(?!\[[ xX]\])[^\n]*)'
pattern = re.compile(
r'(^[ \t]*-[ \t]*\[ \][ \t]*)[❓]?[ \t]*(' + escaped + r')[ \t]*'
r'(?P<subs>' + _SUB_BULLETS + r'*)',
re.MULTILINE
)
⚠️ Две НЕУДАЧНЫЕ версии паттерна (не повторять)
- v1 —
(?:\n[ \t]+-[^\n]*\n?)*:\n?опционален, поэтому*перешагивал на следующую строку без разделителя и проглатывал соседние фильмы. Потеряны❓ The kill room,The burial,Past lives. Откачено. - v2 — то же плюс негативный lookahead: не помогло,
[^\n]*из предыдущей итерации уже захватил хвост следующей строки. - v3 (рабочая) — перенос обязателен внутри каждой итерации (
(?:+\n+ ...) +*), якорь не нужен.
Правило для парсеров этого файла: под-буллет кандидата = отступ ≥4 пробела, это буллет и он НЕ чекбокс (- [ ] / - [x]). Строки фильмов имеют отступ 2 пробела — именно поэтому их надо исключать явно.
Проверка фикса
На чистой копии (md5 40295fac...) — 3 прогона mark_ambiguous():
run 1: size=25430
run 2: size=25430
run 3: size=25430
Размер стабилен. Потеряно 0 не-кандидатных строк. Past lives на месте. 838 → 808 строк (минус 30 = старые дубли, 33 → 3).
Чистка существующих дублей
Скрипт /tmp/wl_dedupe.py (дедуп по title|year|rating, первое вхождение сохраняется, порядок не меняется; строки фильмов не трогаются). Прогон --apply:
- 168 дублей в 15 блоках, 26430 → 21032 байт (838 → 670 строк).
- Топ:
Nightmare alley33→3,Stand by me30→3,Фарго30→3,Left behind21→4,Крестный отец12→3,Godfathers12→3.
Ручной прогон watchlist-nightly.sh (от юзера kraken)
bash ~/scripts/watchlist-nightly.sh
Прошёл до Done, без падений. Результат: 270 resolved (38 horror), 37 ambiguous, 22 not found. sync-down добавил 4 фильма в Radarr (Американская история X, Мастерская сатаны, Я ругаюсь, Гладиатор), Scream уже был → помечен ⬇️. sync-up: 7 played items из Jellyfin, 1 отмечен. Vault закоммичен и запушен (Committed: family/documents/movies-watchlist.md, Pushed ✓).
После прогона дублей: 0 (проверено отдельным скриптом). Секции целы: Новинки / Фильмы / Мультфильмы / Аниме / Новинки👎 / Связанные заметки.
🔴 БАГ: sync-down игнорирует порядок файла — сортирует по рейтингу (найден 2026-09-20)
Симптом (сообщил Alex): в батч на закачку попали фильмы, которые «далеко не в начале списка» — Гладиатор, Американская история X, Я ругаюсь, Мастерская Сатаны.
Корень: watchlist_sync/sync_down.py:41:
resolved.sort(key=lambda x: x[1].rating or 0, reverse=True)
Код полностью игнорирует позицию строки в файле и берёт топ-5 по рейтингу TMDB/KP из всех неотмеченных [ ]-строк. Порядок списка не имеет значения вообще.
Расхождение с докой: в watchlist-sync §1.3 заявлено «Приоритет: порядок в файле (выше = важнее)» — это не соответствует коду. Реальный приоритет = рейтинг.
Вторая половина причины: в том же прогоне process-thumbs выполняется до sync-down и переносит 👍-позиции из ## Новинки в начало основного списка. Среди перенесённых оказались Мастерская Сатаны (8.3) и Я ругаюсь (8.2) — с высокими рейтингами, поэтому они сразу выиграли отбор.
Как считался батч 2026-09-20 (5 слотов):
| Слот | Фильм | Рейтинг | Итог |
|---|---|---|---|
| 1 | Крик / Scream | 10.0 | уже в Radarr → помечен ⬇️ |
| 2 | Волшебный участок | 9.0 | 400 «already added» в Sonarr |
| 3 | Гладиатор | 8.225 | добавлен |
| 4 | Я ругаюсь | 8.229 | добавлен |
| 5 | Мастерская Сатаны | 8.3 | добавлен |
| — | Американская история X | ~8.2 | добавлен (слот освободился после первых двух) |
Saving Private Ryan (8.223) и Иди и смотри (8.214) проиграли Гладиатору (8.225) на тысячные доли.
❌ В sync_down ОТСУТСТВУЕТ фильтр по рейтингу
Проверено grep -rn "6\.0\|min_rating" watchlist_sync/*.py:
cli.py:92—< 6.0только для сброса кэша при--recheck, не отборdiscover.py:105,203— фильтр только для шага discoverresolver.py:264—0.5это сравнение рейтингов при ничьей, не порог
В sync_down порога нет вообще. Порог min_rating: 6.0 из доки watchlist-sync §1.2 в коде не реализован. Итог: в очередь могут попасть фильмы с рейтингом 0.0-5.9.
Замер 2026-09-20: из 251 resolved-кандидатов 18 имеют рейтинг < 6.0 — например Шестой элемент 4.3, Мегалополис 5.2, Бешеная любовь 3.1, Ворон 2024 5.8.
⚠️ Отдельный случай: rating == 0.0 означает «TMDB не дал оценку», а НЕ «плохой фильм». Таких двое: Уроки благи, Годзилла и конг. Наивный фильтр < 6.0 их отсечёт и потеряет нормальное кино.
✅ РЕАЛИЗОВАНО (2026-09-20): sync_down следует порядку файла + фильтр MIN_RATING
План из «📋 ПЛАН» выполнен. Alex подтвердил: порядок файла = приоритет, рейтинг = отбор, нулевые рейтинги (0.0) отсекать вместе с остальными ниже порога.
Коммиты в репо watchlist-sync (оба сделаны в этой сессии):
ada47da feat(sync-down): follow file order, filter by MIN_RATING
2dab180 fix(parser): mark_ambiguous replaces candidate block instead of prepending
Предыдущий HEAD до сессии — 20ca511. Рабочее дерево чистое.
Правка 1 — убран resolved.sort() (был на строке 41). resolved заполняется циклом по parse(), то есть остаётся в порядке файла сверху вниз.
Правка 2 — добавлена константа и фильтр в начало sync_down.py:
# Minimum TMDB/KP rating for a title to be queued. 0.0 means "no rating in
# the database" -- per Alex it is discarded together with everything below
# the threshold, so unrated items never reach Radarr/Sonarr.
MIN_RATING = 6.0
if result.status == "ok":
if (result.candidate.rating or 0) < MIN_RATING:
skipped_low += 1
print(f" [−] {item.title!r} — rating "
f"{result.candidate.rating or 0:.1f} < {MIN_RATING}, skipped")
continue
resolved.append((item, result.candidate))
else:
deferred.append((item, result))
Плюс итоговая строка (N item(s) below rating 6.0 skipped) — чтобы отсев был виден в логе, а не молчал.
process_thumbs сознательно НЕ тронут — 👍 по-прежнему уезжают в верх основного списка, то есть свежие новинки намеренно идут первыми (выбор Alex, «вариант 1»).
Dry-run после правки (проверено):
[−] ... 18 строк «skipped» (все < 6.0, включая два нуля: Уроки благи 0.0, Годзилла и конг 0.0)
(18 item(s) below rating 6.0 skipped)
[→] 'Под огнём' (2025) ★7.1 [dry-run]
[→] 'Секретный агент' (2025) ★7.3 [dry-run]
[→] 'Охота на воров 2: Пантера' (2025) ★6.6 [dry-run]
[→] 'Гадкая сестра' (2025) ★7.3 [dry-run]
[→] 'Семья в аренду' (2025) ★7.8 [dry-run]
Queued 5/5 items [dry-run]
Порядок ровно как в файле, с самого верха ## Новинки. Рейтинг больше не влияет на очерёдность — только на отбор.
⚠️ Реальный прогон (bash ~/scripts/watchlist-nightly.sh) после этой правки был ПРЕРВАН — не отработал. Vault проверен: рабочее дерево чистое, movies-watchlist.md остался на коммите d7ce0a9 (результат первого прогона), частичных изменений нет.
✅ ⭐ Приоритет по звёздочке ⭐ (U+2B50) — РЕАЛИЗОВАНО (2026-09-20)
Требование Alex: фильмы, отмеченные звёздочкой ⭐, идут в первую очередь к загрузке — приоритет поверх порядка файла, из всех секций списка.
Правила (финальные, подтверждены Alex):
- Только символ
⭐(U+2B50). Символ✭(U+272D) приоритетом НЕ является. - Только строки фильмов (
- [ ] Название ⭐ …), НЕ под-буллеты кандидатов отmark_ambiguous(«Название (год) ★N.N»). [x](просмотренные) — пропускаются.- Из всех секций — не только
## Фильмы/## Новинки(уточнение Alex). - ⭐ обходит фильтр
MIN_RATING— «Я блядь сказал в приоритете завела». Звёздочка = явное «хочу», рейтинг не важен. ⭐срезается из поискового запроса перед отправкой в TMDB (очевидное требование Alex).
Коммит: 7db9385 feat: ⭐ (U+2B50) items are queued first, bypassing the rating filter
Правка 1 — parser.py::_clean().
⚠️ Найденный баг: ⭐ стоит в середине названия (Простите за беспокойство ⭐ 2018), а старый regex был привязан к концу строки (\s*[...]+\s*$) — поэтому класс не матчился и символ утекал в запрос TMDB. Работало случайно (TMDB прощал лишний глиф).
# ⭐ (U+2B50) is a priority marker and may sit ANYWHERE in the title
# ("Простите за беспокойство ⭐ 2018"), so strip it without the end anchor.
s = s.replace('⭐', ' ')
s = re.sub(r'\s+', ' ', s).strip()
s = re.sub(r'\s*[✭★☆✩✪✫✬✮✯✰]+\s*$', '', s).strip() # остальные звёзды — по-прежнему end-anchored
return s
Критично: ⭐ вырезается из title (чистый запрос), но остаётся в raw — приоритет проверяется по item.raw, поэтому оба свойства сохраняются.
Правка 2 — sync_down.py, двухпроходная выборка:
# ⭐ (U+2B50) marks a must-have: such items are queued FIRST and bypass
# the rating filter entirely. Order within each group stays file order.
starred = [i for i in candidates_raw if "\u2b50" in i.raw]
rest = [i for i in candidates_raw if "\u2b50" not in i.raw]
if starred:
print(f" ⭐ {len(starred)} priority item(s) {[i.title for i in starred]}")
for item in starred + rest:
result = resolve(item.title, tmdb_key, kp_key)
if result.status == "ok":
if "\u2b50" not in item.raw and (result.candidate.rating or 0) < MIN_RATING:
skipped_low += 1
print(f" [−] {item.title!r} — rating "
f"{result.candidate.rating or 0:.1f} < {MIN_RATING}, skipped")
continue
resolved.append((item, result.candidate))
else:
deferred.append((item, result))
Обрати внимание на условие: "\u2b50" not in item.raw and rating < MIN_RATING — фильтр применяется только к не-⭐. Это и есть «обходит фильтр».
Итоговые правила отбора в sync-down:
| ⭐ | обычные | |
|---|---|---|
| Очерёдность | первыми | после, по порядку файла |
Фильтр MIN_RATING = 6.0 |
не применяется | применяется |
[x] |
пропуск | пропуск |
Проверка (2026-09-20):
_clean('Простите за беспокойство ⭐ 2018') -> 'Простите за беспокойство 2018' (⭐ срезан)
raw: 'Простите за беспокойство ⭐ 2018' star in raw: True (приоритет работает)
title: 'Простите за беспокойство 2018' star in title: False (чистый запрос)
candidates: 250 | starred: 1 | rest: 249 — ⭐ первым в итерации
Dry-run после правки:
⭐ 1 priority item(s) ['Простите за беспокойство 2018']
[→] 'Простите за беспокойство 2018' → 'Простите за беспокойство' (movie, 2018, ★6.8) [dry-run]
[→] 'Гадкая сестра' (2025) ★7.3 ...
Queued 5/5 items [dry-run]
Единственный ⭐ в файле — строка 79, секция ## Фильмы: - [ ] Простите за беспокойство ⭐ 2018.
✅ Реальный прогон 2026-09-20 19:53 — прошёл чисто
bash ~/scripts/watchlist-nightly.sh от юзера kraken → Done, ошибок нет. Вывод (/tmp/wl-run.log):
(18 item(s) below rating 6.0 skipped)
[=] 'Простите за беспокойство' already in Radarr, marking queued ← ⭐ обработан ПЕРВЫМ
[=] 'Гадкая сестра' already in Radarr, marking queued
[✅] 'Список заветных желаний' added to Radarr, search triggered
[✅] 'Отпуск на двоих' added to Radarr, search triggered
[✅] 'Простая случайность' added to Radarr, search triggered
Queued 5/5 items
[19:53:32] ▶ Done
⭐-фильм попал в очередь первым, но добавлять было нечего (уже в Radarr с прерванного прогона) — значит приоритет сработал. Дубли после прогона: 0. Файл: 21332 байт / 676 строк.
🩹 Почему первая версия правки не сработала (не повторять)
Первая попытка добавить ⭐ в существующий класс [✭★☆✩✪✫✬✮✯✰] не дала эффекта — класс end-anchored ($), а ⭐ стоит в середине названия. Проверка на тестах показала star gone: False. Вывод: для маркеров, которые могут стоять в любой позиции, нужен str.replace(), а не класс с якорем.
📋 Как задача ставилась (для контекста будущих сессий)
Alex формулировал требования короткими репликами, и план сначала был неверно истолкован дважды:
- «⭐ - в приоритете, из всех списков. Естественно в tmdb ее срезать» — уточнение, что секции не ограничены и глиф не должен уходить в запрос.
- «Что блядь значит рейтинг остался?! Я блядь сказал в приоритете завела» — категорическое: ⭐ обходит фильтр рейтинга, а не просто меняет очерёдность.
Вывод для будущего: когда Alex говорит «в приоритете» — это означает обход фильтров, а не смену сортировки. Уточнять это ДО предложения плана, а не после.
📦 Результат закачки по итогам прогона 2026-09-20
sync-down добавил 4 фильма (Scream уже был → ⬇️). Проверено через Radarr API + Transmission:
| Фильм | Взятый релиз | Статус |
|---|---|---|
| Gladiator (2000) | UHD BDRip [H.265/2160p] [4K] |
качается |
| American History X (1998) | BDRip [AVC] |
качается |
| I Swear (2025) / Я ругаюсь | BDRip [H.264/1080p] [DVO] |
качается |
| Oficina do Diabo (2025) / Мастерская Сатаны | — | релиз не найден, ждёт RSS |
✅ Профиль качества работает как задумано: Gladiator взят в 4K, но BDRip, не Remux — профиль 7 WEB-4K (no remux) фильтрует корректно.
Застрявшие в очереди Radarr (со статусом warning, к этому прогону не относятся): Brokeback Mountain, Мортал Комбат 2, Sorry to Bother You, Город бога, День хомячка. Отдельная задача — разобрать.
🟡 Побочная находка: Zulip-пинг не настроен на Кракене
_zulip_ping() требует ZULIP_URL, ZULIP_API_KEY, ZULIP_BOT_EMAIL, но их нет в .env репо watchlist-sync. При 59 нерезолвленных items → [warn] ... not set — skipping ping. Уведомления о нерезолвленных до Alex не доходят.
🟡 Побочная находка: Sonarr 400 «This series has already been added»
При sync-down для «Волшебный участок»: 400 Bad Request на /sonarr/api/v3/series. Причина безобидная — серия уже в Sonarr (tvdbId=442038, «Magical Police Department»). arr.py::sonarr_add() не проверяет дубли перед POST, поэтому Sonarr отвечает SeriesExistsValidator. Не сбой, но шумит в логе — стоит добавить pre-check (как в radarr_add).
Бэкапы (перед правками)
~/obsidian/family/documents/movies-watchlist.md.bak-20260920-192909 (838 строк, 26430 б)
~/obsidian/family/documents/movies-watchlist.md.dedupe-backup-20260920-193111
/srv/.../watchlist-sync/watchlist_sync/parser.py.bak-20260920-192909
Статус на конец сессии (2026-09-20, финал)
Три коммита в репо watchlist-sync (порядок применения):
7db9385 feat: ⭐ (U+2B50) items are queued first, bypassing the rating filter
ada47da feat(sync-down): follow file order, filter by MIN_RATING
2dab180 fix(parser): mark_ambiguous replaces candidate block instead of prepending
20ca511 ← HEAD до сессии
Рабочее дерево чистое. .bak-* добавлены в .gitignore.
- ✅ Фикс
parser.py(mark_ambiguous) применён, проверен 3 прогонами на чистой копии, закоммичен. - ✅ Фикс
sync_down.py— порядок файла +MIN_RATING = 6.0, закоммичен. Dry-run подтверждён. - ✅ Приоритет
⭐(U+2B50) реализован, закоммичен. Обходит фильтр рейтинга, работает из всех секций,[x]пропускает. - ✅ Файл watchlist вычищен (168 дублей → 0) и закоммичен в vault.
- ✅ Два реальных прогона
watchlist-nightly.shпрошли чисто. Второй (19:53) — уже с приоритетом ⭐:Done, ошибок нет, 3 фильма ушли в закачку. - ✅ Дубли после всех прогонов: 0.
- 🔴 Крон по-прежнему отсутствует — pipeline живёт только вручную. ГЛАВНАЯ незакрытая задача.
- 🟡 Zulip-пинг не настроен (
ZULIP_*нет в.env). - ℹ️ Доки watchlist-automation и watchlist-sync обновлялись агентом Eagle в этой же сессии (коммиты
bd743da,abee5a8в vault) — при правках сверяться.
Хронология прогонов 2026-09-20:
| Время | Что | Результат |
|---|---|---|
| ~19:33 | Прогон после фикса дедупа (до правки sync_down) |
4 фильма в Radarr: Gladiator, American History X, I Swear, Oficina do Diabo (без релиза) |
| ~19:45 | Прогон после правки sync_down |
ПРЕРВАН (прерван пользователем), правок не оставил |
| 19:53 | Финальный прогон (файловый порядок + MIN_RATING + ⭐) |
Done. +3 фильма: Список заветных желаний, Отпуск на двоих, Простая случайность |
Бэкапы (все созданы в этой сессии):
~/obsidian/family/documents/movies-watchlist.md.bak-20260920-192909 (838 строк, 26430 б — ДО фикса)
~/obsidian/family/documents/movies-watchlist.md.dedupe-backup-20260920-193111 (до дедупа)
/srv/.../watchlist-sync/watchlist_sync/parser.py.bak-20260920-192909 (md5 0f597606fa14f357ff3b25547bd18ea6)
/srv/.../watchlist-sync/watchlist_sync/parser.py.bak-20260920-195100 (до правки ⭐)
/srv/.../watchlist-sync/watchlist_sync/sync_down.py.bak-20260920-194447
/srv/.../watchlist-sync/watchlist_sync/sync_down.py.bak-20260920-195100 (до правки ⭐)
Скрипты диагностики/фикса (на Кракене, /tmp/): wl_dedupe.py (дедуп кандидатов, --apply), wl_postcheck.py (проверка дублей после прогона), wl_orphan.py (проверка структуры блоков), wl_verify_final.py, wl_order2.py, wl_grab_check.py (Radarr queue + Transmission), wl_fix_parser3.py, wl_fix_syncdown.py, wl_fix_star.py, wl_fix_clean2.py, wl_star.py, wl_star_check.py, wl_final_star.py.
Скрипты повторного использования: wl_dedupe.py — при накоплении дублей; wl_postcheck.py — контроль после каждого прогона; wl_grab_check.py — что реально ушло в закачку.
Пути к конфигам *arr (проверено 2026-09-20, в доке ранее были ошибочны)
Конфиги Radarr/Sonarr/Transmission лежат на втором диске (6194539b-03e0-4f33-986b-4da09834e82f), не на том же, что медиа:
Radarr API key: /srv/dev-disk-by-uuid-6194539b-.../docker/radarr/config/config.xml
Sonarr: /srv/dev-disk-by-uuid-6194539b-.../docker/sonarr/config/config.xml
Transmission: /srv/dev-disk-by-uuid-6194539b-.../docker/transmission/settings.json
Radarr API base: http://localhost:7878/radarr/api/v3/ (ключ из config.xml)
Sonarr API base: http://localhost:8989/sonarr/api/v3/
Проверка закачки: queue в Radarr показывает grabbed/downloading; history даёт точное имя взятого релиза. Transmission — docker exec transmission transmission-remote --list (RPC-юзер в settings.json пустой).
🔴 2026-09-21: ДИСК ЗАПОЛНЕН 100%, КРОН nightly/discover ОСТАНОВЛЕН
Причина остановки: sync-down --batch 5 (5 фильмов ежедневно) работал без проверки свободного места. Диск заполнился до нуля байт.
Признак «сервисы ломаются» — Radarr отдаёт disk I/O error
curl -s "http://localhost:7878/radarr/api/v3/movie?apikey=$KEY"
# → {"message": "disk I/O error\ndisk I/O error",
# "description": "...System.Data.SQLite.SQLiteException (0x87AF045D): disk I/O error..."}
SQLite не может писать при нуле свободного места. Метаданные Radarr через API не читаются, пока диск не освобождён. Это первое, что надо проверять при «Radarr молчит» на заполненном диске.
Фактическое заполнение (2026-09-21)
df -h → /dev/sda1 1.8T 1.7T 0 100% /srv/dev-disk-by-uuid-6194539b-...
df -h → /dev/mmcblk0p2 14G 4.7G 8.4G 36% / (SD-карта, не медиа-диск)
Docker: 29.4.3, Debian 12 bookworm, arch aarch64, 25 контейнеров, 22 образа.
Что именно съело место — 5 REMUX-файлов = 403.4 ГБ
Замер find -printf по /media/movies-radarr (27 видеофайлов). Настоящих remux'ов ровно 5 — по имени файла (Remux/BDREMUX/BDRemux):
| Размер | Фильм | Файл |
|---|---|---|
| 92.0 GB | Babylon (2022) | Babylon (2022) - UHD BD-Remux by Wild_Cat.mkv |
| 89.4 GB | Schindler's List (1993) | Schindler's.List.1993.2160p.HDR.BDRemux.mkv |
| 85.6 GB | Midsommar (2019) | Midsommar.2019.DC.BDREMUX.2160p.HDR.seleZen.mkv |
| 79.7 GB | Triangle of Sadness (2022) | Triangle.of.Sadness.2022.BDREMUX.2160p.HDR.seleZen.mkv |
| 56.7 GB | Unhinged (2020) | Неистовый.2020.UHD.Blu-Ray.Remux.2160p.mkv |
| 403.4 GB | итого |
⚠️ Остальные 22 файла — НЕ remux (WEB-DL / BDRip / x264 / x265, 4–29 ГБ). Крупные, но кодированные — не трогать. Например Oldboy (2003)/Oldeuboi [2003].mkv 28.6 ГБ и Project.Hail.Mary...2160p.iT.WEB-DL.DV.HDR.H.265.mkv 28.1 ГБ — не remux, несмотря на размер.
Корень: захардкоженный qualityProfileId: 1 (профиль Any) в arr.py — починен только 2026-09-04. Всё, что качалось до этого, брало remux'ы по 60–98 ГБ.
✅ ИСПРАВЛЕНО 2026-09-21: ВСЕ 5 фильмов ЕСТЬ в watchlist
⚠️ Ранее в этой доке стояло ошибочное «Schindler's List не найден в файле». Это был ложный вывод из одностороннего грепа: фильм записан по-русски — «Список Шиндлера» (строка 396), а искали по латинице. Alex поправил. Правило: искать и ru, и en —
grep -i "schindler\|шиндлер\|список"— ПРЕЖДЕ чем утверждать «нет в списке».
| Фильм | Строка в watchlist | Метка ⬇️ |
|---|---|---|
| Неистовый (Unhinged) | 107: - [x] ⬇️ Неистовый |
✅ есть (и [x]) |
| Список Шиндлера | 396: - [ ] ⬇️ Список Шиндлера |
✅ есть |
| Babylon (2022) | 83: - [ ] Babylon (2022) |
❌ потеряна (баг mark_queued) |
| Midsommar | 81: - [ ] 🧟♀️ midsommar 2019 |
❌ потеряна |
| Triangle of Sadness | 82: - [ ] Triangle of Sadness 2022 |
❌ потеряна |
Корень потери 3 меток — баг mark_queued (parser.py:77): regex требует название сразу после - [ ],
но перед ним бывает эмодзи (- [ ] 🧟♀️ midsommar 2019) → не матчит; при провале функция молча ничего не пишет
(if new_text != text), а sync_down.py её результат не проверяет. Детали — family/plans/watchlist-disk-overflow-fix, Фаза 1.
❌ «Восстановить потерянные ⬇️» — отменено (неверная постановка)
⬇️ = «добавлен в Radarr / качается». После удаления файлов метка должна быть снята, а не восстановлена.
У Babylon/Midsommar/Triangle метки и не было → ничего не делать (консистентно: файла нет, метки нет).
Снять ⬇️ нужно у Списка Шиндлера (396 → [ ]) и Неистового (107 → [x], [x] не трогать).
⚠️ Функции unmark_queued() в коде НЕТ — писать в рамках Фазы 1.
✅ Порядок работ — Фазы 0 и 0b ВЫПОЛНЕНЫ (2026-09-21)
- ✅ Удалены 5 remux-файлов (403 ГБ) →
df100% → 77%, свободно 404 ГБ → Radarr API ожил (49 фильмов). Удаление — файлами напрямую (rmпо точным путям), не через Radarr API (тот лежал). Папки с.nfo/постерами не тронуты. - ✅ Radarr:
RescanMovie+ снятиеmonitoredу id 4/18/35/36/37 → всеmonitored=false,hasFile=false. ⚠️ Без этого Radarr перекачал бы все 403 ГБ. Порядок:monitored=falseчерез PUT до любого поиска. - ✅ Снят
⬇️у Шиндлера (396) и Неистового (107) — через новуюunmark_queued(). - ✅
MIN_FREE_GB = 100.0вsync-down— согласован с Alex («Да»), коммит2642882. - ✅ Лимит качества Alex считает уже настроенным (
WEB-4K (no remux), id 7) — новый лимит не делали.
Бэкапы: ~/backup-movies-watchlist-20260921-150759.md, ~/backup-radarr-unmonitor-20260921-151117.json,
~/crontab-backup-20260921.txt. Скрипты: /tmp/rm_remux.sh, /tmp/unmonitor_remux.sh.
Остановка крона (сделано 2026-09-21)
crontab -l > ~/crontab-backup-$(date +%Y%m%d-%H%M%S).txt # бэкап ПЕРЕД
crontab -l | grep -v "watchlist-nightly\|watchlist-discover" > /tmp/crontab-new.txt
crontab /tmp/crontab-new.txt
crontab -l | grep -c "watchlist-nightly\|watchlist-discover" # → 0 (проверка)
Бэкап: ~/crontab-backup-20260921.txt.
Осталось в кроне: sync-vault (часовой), 2× pgs-to-srt, watchlist-cleanup (02:00).
Убрано: watchlist-nightly (01:00), watchlist-discover (вс 09:00).
🔴 Незакрытые дефекты скрипта (моя ответственность)
| # | Дефект | Следствие | Статус |
|---|---|---|---|
| 1 | Нет проверки свободного места перед sync-down |
Radarr качает, пока диск не кончится | ✅ исправлено 2026-09-21 |
| 2 | Нет лимита размера файла | Один фильм = 60–100 ГБ | ⚠️ Alex считает лимит качества достаточным — не делаем |
| 3 | Нет стоп-крана при заполнении > порога | Нет автоматической остановки | ✅ = п.1 |
| 4 | cleanup не видит /media/movies |
Просмотренное не удаляется, диск не освобождается | ⏸ отдельная задача |
| 5 | mark_queued молча проваливается |
часть фильмов без метки ⬇️ |
✅ исправлено 2026-09-21 |
Правило на будущее: любой скрипт, ставящий закачки в очередь, ОБЯЗАН проверять свободное место до добавления. Лимита качества недостаточно — он ограничивает тип релиза, но не размер.
✅ 2026-09-21 (продолжение): Фазы 1–4 РЕМОНТА ВЫПОЛНЕНЫ — коммит 2642882
Полный план и разбор — family/plans/watchlist-disk-overflow-fix. Ниже — операционная выжимка для будущих сессий.
Коммит: 2642882 fix(sync-down): MIN_FREE_GB guard, horror filter (star overrides), reliable mark_queued
Предыдущий HEAD — aafbaf7. Рабочее дерево чистое.
Фаза 1 — MIN_FREE_GB (стоп-кран по месту)
MIN_FREE_GB = 100.0
def _free_gb(path: Path) -> float:
p = Path(path).resolve()
while not p.exists() and p != p.parent:
p = p.parent
return shutil.disk_usage(p).free / (1024 ** 3)
Проверка стоит в run() сразу после получения Jellyfin-user, перед parse():
при нехватке места печатает [STOP] free space ... < 100 GiB — nothing queued и return.
Путь берётся из env WATCHLIST_MEDIA_ROOT, иначе watchlist.parent.
⚠️ shutil.disk_usage, НЕ df — df врёт на ~4% (учитывает root-резерв ext4).
⚠️ Питфолл из watchlist-cleanup: /media — не медиа-диск, а пустая папка на SD-карте. Учитывать при задании WATCHLIST_MEDIA_ROOT.
Фаза 2 — Фильтр ужасов + ⭐ > 🧟
Расширен существующий фильтр (был только рейтинговый), точка — бывший sync_down.py:54:
| Условие | Действие |
|---|---|
есть ⭐ (U+2B50) |
качаем всегда — continue до всех фильтров |
candidate.is_horror() без ⭐ |
пропускаем |
рейтинг < MIN_RATING (6.0) без ⭐ |
пропускаем |
Счётчик skipped_horror, в конце лога: (N horror item(s) skipped; add ⭐ to force).
⚠️ is_horror() работает по жанрам TMDB, не по эмодзи. genres — список int-id, 27 = Horror.
Поэтому отсекается 33 (dry-run), хотя меток 🧟♀️ в файле 41 — часть строк не резолвится в TMDB.
В .resolve_cache.json 51 запись содержит genre id 27. Проверка:
jq -r "[to_entries[] | select((.value.genres // .value.candidate.genres // []) | index(27))] | length" .resolve_cache.json
⚠️ ⭐ (U+2B50) ≠ ✭ (U+272D). Приоритет — только ⭐.
Dry-run подтверждён:
free space on /home/kraken/obsidian/family/documents: 403.4 GiB (threshold 100 GiB)
⭐ 1 priority item(s) ['Простите за беспокойство 2018']
(14 item(s) below rating 6.0 skipped)
(33 horror item(s) skipped; add ⭐ to force)
Queued 5/5 items [dry-run]
Фаза 3 — Надёжный mark_queued
parser.py::mark_queued() переписан:
- возвращает
bool - регексп поглощает опциональный эмодзи-префикс между чекбоксом и названием
(
🧟 🗓️ ❤ ⭐ ✭ ❓ ✅ ⬇️ ▶️ ZWJ VS16 ♀️+ пробелы) - метка пишется перед префиксом:
- [ ] ⬇️ 🧟♀️ midsommar 2019 - в
sync_down.py— обёртка_mark()(4 места вызова), приFalseпечатает[!] could not write ⬇️ marker for ... (line not matched — check the watchlist syntax)
Тесты: 7/7. Проверка на копии боевого файла: строки 81/82/83 получили метку, 677 строк → 677.
Фаза 4 — unmark_queued() добавлена и применена
unmark_queued() в parser.py — инверсия mark_queued, тоже возвращает bool, поглощает эмодзи-префикс
с обеих сторон маркера. Применено на боевом файле:
107: - [x] ⬇️ Неистовый → - [x] Неистовый
396: - [ ] ⬇️ Список Шиндлера → - [ ] Список Шиндлера
Тесты: unmark 5/5 + round-trip mark → unmark → исходная строка ✅
⚠️ Не «восстанавливать» ⬇️ после удаления файла — метка = «добавлен в Radarr»; после
удаления + monitored=false она врёт и блокировала бы повторную постановку.
🔴 Открытые баги в parser.py::_clean() — НЕ исправлены
Существовали до правок 2026-09-21, проверены на старом коде — не регрессия. Решение Alex не принято.
| # | Баг | Строка | Эффект |
|---|---|---|---|
| A | re.sub(r'\s*\(\d{4}(?:/\d{4})+\)\s*$', ...) — квантификатор + требует 2+ варианта года, одиночный год не срезается |
parser.py:47 |
_clean('⬇️ Babylon (2022)') → 'Babylon (2022)' — год уходит в resolve() (запрос в TMDB) и mark_queued |
| B | _EMOJI_PREFIX = ^[<символы>]+\s* заканчивается на \s* → захватывает только первый эмодзи |
parser.py:19-38 |
'⬇️ 🧟♀️ midsommar 2019' → '🧟♀️ midsommar 2019' (эмодзи остаётся в title). Лечится формой ^(?:[<символы>]+\s*)+ |
Почему важно: грязный title идёт в resolve() (TMDB/KP), в _normalize() при sync_up
(не сойдётся с названием из Jellyfin) и в mark_queued. Фикс B частично компенсирован
правкой Фазы 3 (регексп поглощает префикс), но title всё равно уходит в resolve грязным.
Фаза 3.5 — _mark_watched в sync_up.py (исправлено локально, НЕ залито/НЕ закоммичено)
Тот же класс бага: регексп r'\s*-\s*\[ \]\s*⬇️?\s*(.*)' не пропускал эмодзи-префикс, поэтому
строка - [ ] ⬇️ 🧟♀️ midsommar 2019 никогда не перешла бы в [x]. Исправлено симметрично
(/tmp/wlsync/sync_up.py), тесты 5/6 (падение на Babylon (2022) — баг A, не от правки).
⚠️ На Kraken НЕ залито, в git НЕ закоммичено. На момент правки строк с ⬇️ + эмодзи-префиксом
в файле не было ни одной — баг проявился бы после первого боевого прогона sync-down
(правка Фазы 3 создаёт именно такой формат). Чинить синхронно.
Бэкапы этой сессии
~/backup-movies-watchlist-20260921-150759.md (перед удалением remux)
~/backup-radarr-unmonitor-20260921-151117.json (состояние Radarr до unmonitor)
~/backup-wlsync-20260921-151855/ (sync_down.py + parser.py до правок)
~/backup-movies-watchlist-unmark-20260921-152018.md (перед снятием ⬇️)
~/crontab-backup-20260921.txt (crontab до остановки)
Скрипты сессии (на Кракене /tmp/, теряются при перезагрузке)
rm_remux.sh (удаление 5 remux), unmonitor_remux.sh (снятие monitored + RescanMovie),
commit_wlsync.sh, test_marker_real.sh (тест mark_queued на копии боевого файла),
fase4_unmark.sh (снятие ⬇️).
Cron Jobs (на Кракене, восстановлены 2026-09-20, дополнены 2026-09-21)
Весь watchlist pipeline работает на Кракене (мигрировал с Eagle 2026-05-27). Крон был мёртв с 2026-06-20 по 2026-09-20, восстановлен (детали в начале документа).
⚠️ С 2026-09-21
watchlist-nightlyиwatchlist-discoverОСТАНОВЛЕНЫ (диск 100%, см. секцию выше). Таблица ниже — целевое состояние; фактически активен толькоwatchlist-cleanup.
| Job | Расписание | Скрипт | Факт 2026-09-21 |
|---|---|---|---|
watchlist-nightly |
ежедн. 01:00 | ~/scripts/watchlist-nightly.sh |
⏹ ОСТАНОВЛЕН |
watchlist-cleanup |
ежедн. 02:00 | ~/scripts/watchlist-cleanup.sh --apply |
✅ активен |
watchlist-discover |
вс 09:00 | ~/scripts/watchlist-discover.sh |
⏹ ОСТАНОВЛЕН |
⚠️ Фактический путь — ~/scripts/, не ~/.hermes/scripts/. Оба скрипта вызывают CLI напрямую через uv run --project $WS, без LLM. uv лежит в ~/.local/bin/uv.
⚠️ ~/scripts/ — root:root 755. Добавить/заменить скрипт можно только sudo install -m 755 (у kraken sudo без пароля). Обычная запись (>, cp) откажет с Permission denied.
⚠️ watchlist-cleanup.sh без аргумента = dry-run. В кроне обязателен --apply, иначе удаление не произойдёт и лог будет писать «nothing to delete». Это самая тихая из возможных поломок — проверять флаг при любой правке крона.
WS в обоих скриптах: /srv/dev-disk-by-uuid-49e8f586-3839-4c5d-a1e1-58bfc3579ade/watchlist-sync — другой UUID, чем фактический корень репо (см. ниже). Возможный источник поломки: проверить, что путь резолвится.
watchlist-nightly.sh (на Кракене):
sync-vault.sh(pull) →process-thumbs --apply→sync-vault.sh(push)resolve --recheck --apply→sync-down --apply --batch 5→sync-up --apply→sync-vault.sh(push)
Пути на Кракене (HDD UUID 49e8f586-3839-4c5d-a1e1-58bfc3579ade):
/home/kraken/obsidian→ symlink на/srv/dev-disk-by-uuid-.../obsidian/home/kraken/Developer/watchlist-sync→ symlink на/srv/dev-disk-by-uuid-49e8f586-.../watchlist-sync
✅ Путь репо в скриптах ВЕРНЫЙ (проверено 2026-09-20). В watchlist-nightly.sh и watchlist-discover.sh:
WS=/srv/dev-disk-by-uuid-49e8f586-3839-4c5d-a1e1-58bfc3579ade/watchlist-sync
UV=~/.local/bin/uv
ls -la подтверждает: /srv/.../watchlist-sync существует, внутри watchlist_sync/, .venv/, .resolve_cache.json, pyproject.toml, .env (есть, содержит TMDB_KEY/KP_KEY/RADARR_/SONARR_; нет ZULIP_*).
Ранее в доке фигурировал путь
.../6194539b-.../watchlist-sync«как реальный корень». На 2026-09-20 рабочий путь —49e8f586, и он же используется в скриптах. Не «исправлять» его обратно.
Ещё одно подтверждённое отличие: ~/scripts/ — реальная директория (drwxr-xr-x root root), а ~/.hermes/ на Кракане содержит только skills/. Скрипты лежат в ~/scripts/, не в ~/.hermes/scripts/.
Как запускать CLI вручную (рабочая команда)
ssh kraken
cd /srv/dev-disk-by-uuid-49e8f586-3839-4c5d-a1e1-58bfc3579ade/watchlist-sync
uv run --project . watchlist-sync <команда> # resolve / sync-down / sync-up / process-thumbs / discover
Или целиком пайплайн: bash ~/scripts/watchlist-nightly.sh
Discover — Фильтрация
| Тип | Порог |
|---|---|
| Мировые | IMDb ≥ 7.0 или KP ≥ 7.0 |
| Российские | KP ≥ 8.5 (защита от накрутки) |
| Upcoming | год ≥ текущий−1 |
| Минимум голосов | IMDb ≥ 1000 или KP ≥ 500 |
KP API endpoint: https://api.poiskkino.dev/v1.4/movie (redirect с api.kinopoisk.dev).
*arr Stack — Индексеры (Prowlarr)
| ID | Имя | Тип | Примечание |
|---|---|---|---|
| 1 | RuTracker | semi-private | блокирует при rate limit на сутки |
| 2 | RuTor | public | поиск по imdbid |
| 3 | Byrutor | public | |
| 4 | NoNaMe Club | semi-private | анонимный |
| 5 | Kinozal | semi-private | резерв |
| 6 | Kinozal (M) | semi-private | магнет ✅ — без grab limit, предпочтительный |
Pi-hole питфолл: блокирует skyhook.sonarr.tv → Sonarr/Radarr поиск молча не запускается. Фикс: dns: [8.8.8.8] в docker-compose.yml.
Router Script — Маршрутизация по жанру
После импорта Sonarr/Radarr: router.py --apply создаёт symlinks:
- Animation (не R/18+) →
/media/cartoons/или/media/cartoons-series/ - Documentary →
/media/documentaries/или/media/documentaries-series/ - Прочее →
/media/movies/или/media/series/
Копирует NFO + постер рядом с symlink (Jellyfin не видит метадату через symlink напрямую).
watchlist-sync — Python CLI
Репо: ~/Developer/watchlist-sync (GitHub: mallexxx/watchlist-sync).
watchlist-sync resolve — TMDB + KP API → ID, тип (movie/tv)
watchlist-sync sync-down — добавить батч (max 5) в Radarr/Sonarr, поставить ⬇️
watchlist-sync sync-up — Jellyfin watched → [x] в .md
Резолвер: TMDB /search/multi + KP fallback. Пороги: min_rating: 6.0, min_score: 0.75. При 2+ кандидатах → ❓ (LLM разрешает в cron каждые 6ч, нерешённые → уточнение в Zulip).
Stage 2 (после ≥50 просмотров): AI-curated recommendations — три списка (с женой / один / семья) на основе TMDB профиля, добавляются в конец watchlist как ## 🤖 Рекомендации (YYYY-WNN).
Jellyfin library mapping: Movies → movies, Cartoons → mixed (movies + episodes).
Питфоллы
KP Markdown Link Parsing Bug
Фильмы из Kinopoisk с синтаксисом [Title](https://kinopoisk.ru/film/12345/) (2025) передавались как есть в TMDB search → не находились → помечались ❓ навсегда (not i.ambiguous пропускает при следующем запуске). Фикс: commit 2db1ed9 — извлекать plain title из markdown link до передачи в TMDB. Ошибочно помеченные ❓ требуют ручного --recheck.
KP API Endpoint
api.kinopoisk.dev редиректит на api.poiskkino.dev/v1.4/movie. Использовать destination redirect напрямую.
🔴 Жёстко зашитый qualityProfileId в arr.py (корень жирных remux) — РЕШЕНО
Найдено 2026-09-04. В watchlist_sync/arr.py изначально был захардкожен qualityProfileId: 1 и для Radarr, и для Sonarr:
# radarr_add (~стр. 77) / sonarr_add (~стр. 112)
"qualityProfileId": 1,
Профиль 1 (Radarr) = 'Any' — разрешает ВСЁ вплоть до Remux-2160p и BR-DISK, поэтому каждый новый фильм/сериал попадал в «Any» и Radarr скачивал 60GB remux'ы.
✅ Решение принято и применено 2026-09-04: создан профиль WEB-4K (no remux) → Radarr id 7, Sonarr id 7; arr.py теперь пишет qualityProfileId: 7. Подробности профиля — в arr-stack-kraken, секция «Качество загрузки для НОВЫХ фильмов/сериалов».
✅ Баг накопления кандидатов в mark_ambiguous() (найден и исправлен 2026-09-20)
Кандидаты-под-буллеты под ❓-строками дублировались на каждом прогоне resolve (+3 строки за раз), потому что re.sub снимал только саму ❓-строку, а новые кандидаты вставлялись перед старыми. Паттерн исправлен на захват всего блока (маркер + под-буллеты). Полная механика, две неудачные версии паттерна и чистка 168 дублей — в family/projects/watchlist-automation.
Правило для парсеров watchlist: под-буллет кандидата = отступ ≥4 пробела + буллет + НЕ чекбокс (- [ ]/- [x]). Строки фильмов имеют отступ 2 пробела. Внутри *-группы перенос строки обязателен — иначе regex проглатывает соседние фильмы.
Open Tasks
- ✅ [2026-09-20] Фикс
sync_down.pyРЕАЛИЗОВАН — коммитada47da.resolved.sort()убран, добавленMIN_RATING = 6.0с видимымprint. Нулевые рейтинги отсекаются (решение Alex: «отсекать»). - ✅ [2026-09-20] Приоритет
⭐(U+2B50) РЕАЛИЗОВАН — коммит7db9385. ⭐ первыми из всех секций, обходит фильтрMIN_RATING.⭐срезается из запроса TMDB (parser._cleanчерезstr.replace, НЕ через end-anchored класс).✭(U+272D) приоритетом не считается;[x]пропускается; под-буллеты кандидатов не считаются. - ✅ [2026-09-20] Реальный прогон с новой логикой выполнен — 19:53,
Done, +3 фильма. Батч по порядку файла, ⭐ первым. - ✅ [2026-09-20] Крон ВОССТАНОВЛЕН — установлен 21:21 в crontab юзера
kraken, бэкап~/crontab-backup-20260920-212155.txt.nightly01:00 +discoverвс 09:00, логи в/tmp/. Первая проверка в бою — прогон в 01:00. - 🟡 [2026-09-20] Логи крона в
/tmp/— теряются при перезагрузке. Перенести в постоянное место (~/logs/или на диск), если нужна история прогонов. - ✅ [2026-09-21] Удаление просмотренных фильмов при заполнении диска >90% — ЗАКРЫТО И В КРОНЕ. Обёртка
~/scripts/watchlist-cleanup.sh(была в/tmp— перенесена черезsudo install, у kraken sudo без пароля), cron0 2 * * * ... --apply, лог/home/kraken/logs/watchlist-cleanup.log. Корзина Radarr (recycleBin) — решено НЕ настраивать (контейнерный/хостовый пути расходятся; Alex больше не переспрашивать). Обёртка по умолчанию делает dry-run — без--applyв кроне был бы вечный холостой ход. Полные детали — family/plans/watchlist-cleanup. ⚠️ Питфолл:/media— не медиа-диск, а пустая папка на SD-карте (/dev/mmcblk0p2), отдаёт 33.9%. Реальный диск/dev/sda1в/srv/dev-disk-by-uuid-6194539b-.../. Использоватьos.statvfs, неshutil.disk_usage("/media"). - ✅ [2026-09-21] ДИСК ОСВОБОЖДЁН: 100% → 77%, свободно 404 ГБ. Удалены 5 remux-файлов (403 ГБ); в Radarr снят
monitoredу id 4/18/35/36/37 (hasFile=false) — иначе он перекачал бы всё заново. Radarr API ожил (49 фильмов). Бэкапы:~/backup-movies-watchlist-20260921-150759.md,~/backup-radarr-unmonitor-20260921-151117.json. - ✅ [2026-09-21]
watchlist-nightly+watchlist-discoverОСТАНОВЛЕНЫ, ЗАТЕМ ВОЗВРАЩЕНЫ. Останавливали при диске 100% (sync-down --batch 5без проверки места). Возвращены в тот же день после Фаз 1–4, уже с--batch 2. Бэкапы crontab:~/crontab-backup-20260921.txt(до),~/crontab-backup-20260921-152741.txt(после). Итоговый crontab:0 1 * * * watchlist-nightly.sh # batch 2 0 9 * * 0 watchlist-discover.sh 0 2 * * * watchlist-cleanup.sh --apply - ✅ [2026-09-21] ФАЗА 1 РЕМОНТА ВЫПОЛНЕНА:
MIN_FREE_GB = 100.0вsync-down— коммит2642882. Проверка черезshutil.disk_usage(НЕdf, врёт на ~4%), путь из envWATCHLIST_MEDIA_ROOTиначеwatchlist.parent. Печатает[STOP] free space ... < 100 GiB — nothing queuedи выходит. Подтверждено dry-run:403.4 GiB (threshold 100 GiB). Полный план — family/plans/watchlist-disk-overflow-fix. - ✅ [2026-09-21] ФАЗА 2 ВЫПОЛНЕНА: фильтр ужасов +
⭐ > 🧟— коммит2642882. Точка — бывшийsync_down.py:54.⭐обходит все фильтры (continueдо проверок); ужас без⭐пропускается; рейтинг < 6.0 пропускается. Dry-run: 33 horror skipped, 14 по рейтингу. ⚠️is_horror()считает по жанрам TMDB (id 27), не по эмодзи — поэтому 33, а не 41. - ✅ [2026-09-21] ФАЗА 3 ВЫПОЛНЕНА: надёжный
mark_queued— коммит2642882. Возвращаетbool, поглощает эмодзи-префикс (- [ ] ⬇️ 🧟♀️ Title), обёртка_mark()вsync_down.pyлогирует провал[!]. Тесты 7/7 + проверка на копии боевого файла. Корень потери 3 меток⬇️. - ✅ [2026-09-21] ФАЗА 4 ВЫПОЛНЕНА:
unmark_queued()добавлена,⬇️снят — у Шиндлера (396) и Неистового (107). Функции в коде не было. Тесты unmark 5/5 + round-trip. Бэкап~/backup-movies-watchlist-unmark-20260921-152018.md. - ✅ [2026-09-22] ОСИРОТЕВШИЕ СТРОКИ в
## Новинки— ИСПРАВЛЕНО. Симптом Alex: «Проект Конец света» в каталоге Lampa как непросмотренный, хотя в главном списке он[x]. Корень:process_thumbsобрабатывает только👍/👎/[x]; строка- [ ] ⬇️ [Название](kp) (2025) IMDb 7.1(без оценки) уходила вkeepнавсегда, а при наличии[x]-двойника давала дубль в каталоге. Фикс: сборwatched_titles/watched_kpпо всем секциям кроме Новинок + удаление сирот изkeep; нормализация обязана срезать год и рейтинги, иначепроектконецсвета≠проектконецсвета2026imdb84kp84. Флаг_patch_droppedдобавлен в ранний выход (иначе запись файла пропускалась).cli.py— явные ветки вывода👍/👎/[x]/drop. Проверено: удаляется ровно 1 строка, 683 → 682; каталог 404 → 403. Бэкапы:~/backup-watchlist-before-orphan-fix-20260922-113925.md, код —parser.py.bak-orphan-*,.bak-guard-*,.bak-bs-*,cli.py.bak-cli-*. Полный разбор — секция «ОСИРОТЕВШИЕ СТРОКИ» выше; связь с каталогом Lampa — personal/plans/lampac-watchlist-plugin. - 🔴 [2026-09-22] ПИТФОЛЛ правки
parser.pyскриптами: двойное экранирование съедает слэши.r'\1'→ реальный байт\x01,r'\b'→ байт\x08(backspace).compile()/ast.parse()проходят — синтаксис валиден, а регекспы молча не работают (нормализация не срезала рейтинги, сироты не находились). Правило: после записи патча проверять файл наchr(1)иchr(8), а не только на синтаксис. По возможности править черезpatch/Obsidian, а не генератором строк. - 🔴 [2026-09-21] ФАЗА 5: ВЕРНУТЬ КРОН — ждёт.
watchlist-nightly(01:00) +watchlist-discover(вс 09:00) остановлены, бэкап~/crontab-backup-20260921.txt. Возвращать с--batch 2(не 5) и только после проверки Фаз 1–2 в бою (сначала dry-run). - 🔴 [2026-09-21] ОТКРЫТЫЕ БАГИ
_clean()— решение Alex НЕ принято. Баг A (parser.py:47):(?:/\d{4})+требует 2+ варианта года → одиночный(2022)не срезается, уходит вresolve(). Баг B (parser.py:19-38):_EMOJI_PREFIXзаканчивается на\s*→ берёт только первый эмодзи,🧟♀️остаётся вtitle. Оба существовали до правок (проверено на старом коде), не регрессия. Лечение B — форма^(?:[<символы>]+\s*)+. - 🔴 [2026-09-21]
_mark_watchedвsync_up.py— исправлено локально, НЕ залито/НЕ закоммичено. Тот же класс бага: строка- [ ] ⬇️ 🧟♀️ Titleне перешла бы в[x]. ⚠️ Правка Фазы 3 создаёт именно такой формат → чинить синхронно с возвратом крона, иначеsync-upперестанет видеть новые закачки. - 🔴 [2026-09-21] КОРЕНЬ:
[x]не проставляется на новые закачки.sync-up(Jellyfin watched →[x]) видит только 6 старых фильмов из/media/movies, ни одного из/media/movies-radarr. Пока это не починено, cleanup бесполезен, а диск будет заполняться. - 🟡 [2026-09-21] Cleanup не видит
/media/movies— смотрит только Radarr-библиотеку, поэтому просмотренное из папки ручных торрентов не удаляется. Расширять — отдельно и осторожно (удаление файлов). - 🟡 [2026-09-20]
sync-downберёт всего 5 за прогон — при 250 кандидатах разбор очереди займёт ~50 ночей. Рассмотреть увеличение--batch(сейчас--batch 5вwatchlist-nightly.sh). Особенно актуально, раз фильтр<6.0уже отсеял 18. - 🟡 [2026-09-20] Разобрать 5 застрявших закачек в очереди Radarr со статусом
warning:Brokeback Mountain,Мортал Комбат 2,Sorry to Bother You,Город бога,День хомячка. Висят давно, релиз не находится/не качается. - 🟡 [2026-09-20]
Oficina do Diabo/Мастерская Сатаны(2025) — в Radarr, но релиз не найден. Ждёт RSS. Проверить через несколько дней. - 🟡 [2026-09-20] Настроить Zulip-пинг —
ZULIP_URL/ZULIP_API_KEY/ZULIP_BOT_EMAILотсутствуют в.envwatchlist-sync, 59 нерезолвленных items ушли в никуда без уведомления. - 🟡 [2026-09-20] Добавить pre-check дублей в
sonarr_add()— сейчас при уже существующей серии прилетает400 SeriesExistsValidatorи шумит в логе. Вradarr_add()проверка есть. - 🟡 [2026-09-20] Синхронизировать документацию с кодом — watchlist-sync §1.2 и §1.3 описывают порог
min_rating: 6.0и «приоритет = порядок в файле», которых в коде не было. Порог теперь реализован (MIN_RATING), приоритет по порядку тоже, плюс появился приоритет ⭐ — обновить формулировки в watchlist-sync. - 🔴 [2026-09-04] Решить стратегию качества загрузки новых фильмов — уже РЕШЕНО 2026-09-04 (профиль
WEB-4K (no remux), id 7 для Radarr и Sonarr). Осталось только убедиться, что старые⬇️-позиции, добавленные до фикса, не тянут remux. - 🟡 Оставшиеся
❓-позиции (71 маркер) — намеренно не чистятся вручную (решение Alex 2026-09-20). Разбираются черезresolve --recheckпри работающем кроне. - 🟡 Передавать Transmission locations через router script при каждом импорте (сейчас вручную)
- 🔴 rsync невыполненных фильмов с HTPC на Кракен (
Фильмы не перенесенные с htpc на кракен.md) - 🟡 Watchlist sync-up: добавить перенесённые фильмы в нужные разделы
Связанные заметки
- watchlist-discover — шаг discover: KP API, format, CLI команды
- watchlist-sync — основной проект (resolve, sync-down, sync-up)
- arr-stack-kraken — детальные питфоллы *arr стека
- jellyfin-transcode-rpi5 — Jellyfin PGS/ASS питфоллы