Files
obsidian-vault/personal/plans/lampac-watchlist-plugin.md
T

60 KiB
Raw Blame History

created, namespace, related, status, tags, title, type, updated
created namespace related status tags title type updated
2026-09-22 personal
personal/plans/lampac-kraken
personal/tech/lampac-deployment
family/projects/watchlist-sync
family/projects/watchlist-automation
family/documents/movies-watchlist
rework
lampac
lampa
watchlist
catalog
webos
plugin
kraken
radarr
Watchlist в UI Lampa — каталог из списка plan 2026-09-22

Watchlist в UI Lampa — каталог из списка

Реализовано 2026-09-22. План был переписан после разведки кода модуля Catalog. Базовое развёртывание — personal/plans/lampac-kraken (шаги 1–6 закрыты).

🔴 АРХИТЕКТУРА ПРИЗНАНА НЕВЕРНОЙ 2026-09-22 — переписывается. Сделано menu (подмена родных разделов) вместо main (секции СВЕРХУ). Плюс источник данных — парсинг текстового .md вместо Radarr API, где уже есть TMDB ID и постеры. Alex: «Я предполагал что будет отображаться то что и раньше (список cub) но В НЕГО будет сверху добавлено несколько секций в которых будут РЕАЛЬНЫЕ ЭЛЕМЕНТЫ - а не сгенерируют пустые заглушки!» См. «🔴 АРХИТЕКТУРНАЯ ОШИБКА» — читать ПЕРВЫМ.

Итог одной строкой: серверная часть каталога работает (4 категории, 360/33/3/7), но реализована не по замыслу: подменяет каталог вместо добавления секций, и не использует Radarr как источник постеров/ID.

Обновление 2026-09-22 (вечер): Alex лично нашёл источник в UI («остальное → основной источник → Мой список»), увидел результат и подтвердил диагноз: секции подменяют CUB, нет превью, элементы — сырые строки файла. Затем сам сформулировал целевую схему (Radarr = источник метаданных без автоскачки, просмотр через TorrServer, файлы на HDD не пишутся) → записано в «ПЕРЕДЕЛКА 2». План работ на апруве, ничего не выполнено.

Аудит «как это реализуемо» — ✅ ЗАВЕРШЁН 2026-09-22. Проверено всё: TorrServer на диск не пишет (UseDisk: false, RAM-кэш 96 МБ) — главный вопрос закрыт; поиск торрентов работает через Jackett-API самого Lampac (отдельный Jackett не нужен); найдены оба виновника автоскачки — Radarr (45 monitored, очередь 6) и Sonarr (13 monitored, очередь 19). См. «✅ ПРОВЕРКА "как это реализуемо"». ⚠️ Порядок работ теперь включает Sonarr — раньше он в плане не учитывался.

🔴 АРХИТЕКТУРНАЯ ОШИБКА 2026-09-22 — почему каталог «не работает»

Что хотел Alex (дословно):

«Я предполагал что будет отображаться то что и раньше (список cub) но В НЕГО будет сверху добавлено несколько секций в которых будут РЕАЛЬНЫЕ ЭЛЕМЕНТЫ — а не сгенерируют пустые заглушки! Нахуя это надо то?! Чё блядь значит оно так и должно быть?!»

Что сделано вместо этого — две независимые ошибки:

Ошибка 1 — структура: menu вместо main

Модуль Catalog умеет два независимых блока, и они делают РАЗНОЕ:

Блок YAML Что делает Поведение
menu подменяет родные разделы Lampa (Фильмы/Сериалы/Аниме) ❌ заполнен → родной каталог исчезает
main добавляет секции на главную СВЕРХУ ✅ родной каталог остаётся

Реализация заполнила оба (menu и main) → при выборе источника «Мой список» исчез весь CUB-контент.

Симптом по словам Alex: «при его выборе исчезло все что было до этого из списка CUB во всех разделах. Нет превью.»

Фикс: в sites/watchlist.yaml убрать блок menu, оставить только main. Тогда родной каталог (CUB/tmdb) работает как раньше, а твои секции идут рядами сверху.

Код: plugin.js → function menu() собирает из catalog.menu; если ключа нет — menu не подменяется (вернётся пустой, Lampa использует родные). main() строит parts_data из catalog.main и подмешивает их в Lampa.Api.partNext.

Ошибка 2 — источник данных: парсинг .md вместо Radarr API

Генератор читал movies-watchlist.md — текстовый список названий. В нём нет ни TMDB ID, ни постеров. Результат:

Метрика Значение
Всего позиций (films) 360
Из них с постером (img != null) 0
Из них с реальным ID (film/...) 18

Остальные 342 отдавались с id = ссылка на поиск Кинопоиска (index.php?kp_query=...). Отсюда жалоба «при открытии элемента пишет здесь пусто»: Lampa идёт по ссылке, получает страницу поиска, а не фильм.

Замер 2026-09-22 (доказательство, воспроизводимо):

curl -s "http://192.168.1.14:9118/catalog/list?plugin=watchlist&cat=films" \
  | jq '[.results[] | select(.img == null)] | length'      # → 360
curl -s "..." | jq '.results | length'                     # → 360
curl -s "..." | jq '[.results[] | select(.id | startswith("film/"))] | length'  # → 18

То есть постеров 0 из 360, рабочих карточек 18 из 360.

Почему «девять» с маленькой буквы и Gods don't give gifts — это не баг каталога, а буквально строки файла (проверено grep -n):

56:  - [ ] Gods don't give gifts
64:  - [ ] ❓ девять

Каталог отдаёт строки как есть. Обе — в секции ## Новинки (отступ 2 пробела, подсписок). Alex: «Отображаются элементы типа "девять"(с маленькой буквы), Gods don't give gifts — который вообще ещё не вышел — короче не реальные фильмы а буквально пункты списка.»

Правильный источник — Radarr API. Alex: «Они уже резолвятся же блядь!! Оно же добавлялось в arr!» — TMDB ID и постеры уже есть там, потому что фильмы добавлялись в Radarr.

# API-ключ Radarr
grep -oE "<ApiKey>[^<]+</ApiKey>" /srv/dev-disk-by-uuid-6194539b-.../docker/radarr/config.xml
# → cbcb8ec320104359920c0f611d503c7b

# ⚠️ ВАЖНО: -L обязателен! Без него Radarr отдаёт 307, тело пустое
curl -sL "http://localhost:7878/api/v3/movie?apikey=$KEY" | jq length
# → 50

Форма ответа Radarr (то, что нужно было брать с самого начала):

{
  "tmdbId": 1292695,
  "title": "They Will Kill You",
  "year": 2026,
  "hasFile": true,
  "images": [{"coverType":"poster",
              "remoteUrl":"https://image.tmdb.org/t/p/original/6oI4oQKTWMVUlr8Ivqydp28R"}]
}

Есть всё: TMDB ID → рабочий href, remoteUrl → постер, title/year → название, hasFile → скачан или нет. Плюс постеры локально: /config/MediaCover/ (46 каталогов).

⚠️ Покрытие: в Radarr 50 фильмов, в .md — 360. Разница (310) — то, что в список записано, но в Radarr не заказано. Что с ними делать — решение Alex не получено, открытый вопрос №1 ниже.

Что НЕ надо было делать (записано, чтобы не повторять)

  • Парсить текст вместо API, где данные уже резолвлены
  • Складывать menu + main одновременно
  • Считать «список названий» готовым каталогом для UI с постерами

Alex: корневая претензия к процессу

«А нахуя ты это так реализовал?? Подумать блядь вообще не твое!»

Разбор: задача звучала как «покажи список в UI» — и была прочитана буквально («отдать строки файла»). Надо было сначала выяснить, откуда брать постеры и ID (а ответ — Radarr, куда фильмы и добавлялись), а не начинать с парсера текста.

Задача

Показывать список фильмов Alex (family/documents/movies-watchlist.md) в UI Lampa на ТВ — каталогом. Не локальный плейбэк. Формулировка Alex: «отображение фильмов из списка».

Уточнение от 2026-09-22 (важное!): каталог добавляется СЕКЦИЯМИ СВЕРХУ к родному каталогу Lampa. Родной CUB-контент не подменяется и не исчезает. Элементы — реальные фильмы с постерами и карточками, а не пустые заглушки (см. «🔴 АРХИТЕКТУРНАЯ ОШИБКА»).

⚠️ Что НЕ спрашивать повторно. Задача — отображение, не воспроизведение. Список — это перечень, а не источник видео. Вопрос «а что играть по клику?» — выдумка сверх постановки, Alex на него реагировал раздражением («я чё тебе сказал в постановке задачи»). Штатное воспроизведение Lampa (торренты/онлайн) работает само, отдельно решать нечего.

✅ Затык снят: чем это делается

В образе Lampac уже есть модуль Catalog — YAML-каталоги → плагин Lampa /catalog.js. Сейчас выключен. Писать плагин с нуля не нужно.

Как работает Catalog (разобрано по коду 2026-09-22)

Файлы в контейнере: /lampac/module/Catalog/ — Controllers/, plugin.js, README.md, sites/*.yaml (10 готовых примеров: vokino, rezka, kinopub, anime, …).

Механика

Что Как
Данные sites/<site>.yaml — описание сайта: host, menu, search, list, content
Подстановки override/<site>.yaml и override/_.yaml — переопределения поверх базового
Роуты /catalog.js (плагин), /catalog (JSON каналов), /catalog/list, /catalog/card
WAF ^/catalog/ → 10 запросов/сек
Имя файла только [a-z0-9-]; есть fallback-поиск по displayname

Ключевое: обязателен HTTP

if (!url.StartsWith("http"))
    url = $"{init.host}/{url}";     // ListController.cs:87

host из YAML сканируется по HTTP (Http.Get / Playwright / RCH). Локальные файлы напрямую Catalog читать не умеет — только HTTP-ответ.

Два режима парсинга

bool? jsonPath = contentParse.jsonPath ?? init.jsonPath;

if (jsonPath == true)  json = JToken.Parse(html);   // JSON-режим
else                   doc.LoadHtml(html);          // HTML-режим
  • jsonPath: true → ответ парсится как JSON, элементы берутся по nodes ($.channels[*].details)
  • иначе → HTML через HtmlAgilityPack

🔴 Это и есть рычаг: любой свой HTTP-эндпоинт, отдающий JSON нужной формы, подойдёт как host. Тогда задача сводится к генерации JSON + YAML-обёртки, без правки кода Lampac.

Форма элемента (content в YAML)

content:
  nodes: "$.channels[*].details"     # jsonPath-выражение (JSON) или CSS/XPath (HTML)
  name:         { attribute: name }
  original_name:{ attribute: originalname }
  href:         { attribute: ident }
  image:        { attribute: poster }
  year:         { attribute: released }
  serial_key:   { attribute: type, format: |  return value is "serial" ? value : null; }
  args:
    - { name_arg: vote_average, attribute: rating_kp }

Карточка — отдельная секция card_parse + card_args (описание, рейтинги, жанры, страна). Полный живой пример — sites/vokino.yaml (копия в personal/tech/lampac-deployment, раздел Catalog).

Включение модуля — 2 правки

// current.conf (внутри контейнера /lampac/current.conf)
"SkipModules": [ "Tracks", "Transcoding", ... ]    // ← убрать "Catalog" (строка ~277)
"plugins": { ..., "catalog": true }                // ← было false (строка ~1653)

🔴 ПИТФОЛЛ — init.conf НЕ смонтирован. В нашем docker-compose.yml нет volume на /lampac/init.conf, файл живёт в образе. Правки через docker exec исчезнут при пересоздании контейнера (docker compose up, watchtower-обновление образа). Варианты:

  • (а) добавить volume ./config/init.conf:/lampac/init.conf:ro — надёжно, но нужен первичный docker cp
  • (б) править через AdminPanel /adminpanel (модуль, правит init.conf + current.conf)
  • (в) docker cp + commit — плохо

Рекомендация: (а) — конфиг на HDD, переживает пересоздание.

Архитектура решения

family/documents/movies-watchlist.md          ← источник (vault, git-синхрон)
        │  существующий парсер: watchlist-sync/parser.py
        ▼
генератор (Python, на Кра́кене)
        │  пишет JSON нужной формы
        ▼
/lampac/... JSON-эндпоинт, доступный по HTTP с Lampac
        │
        ▼
sites/watchlist.yaml  ← Catalog-описание, host=<наш JSON URL>
   menu: Фильмы / Мультфильмы / Аниме  (по секциям .md)
        │
        ▼
Lampa на ТВ: плагин http://192.168.1.14:9118/catalog.js

Откуда отдавать JSON — варианты (выбрать на этапе реализации):

# Где Плюс Минус
1 статика в /lampac/wwwroot/ ничего не поднимать, Lampac сам отдаёт только чтение, обновление = перезапись файла
2 отдельный лёгкий HTTP-сервер на Кра́кене есть API/логика ещё один контейнер
3 file:// — ❌ не поддерживается (только http)

Склоняюсь к (1) — генерировать wwwroot/watchlist.json, Lampac отдаёт его сам, без новых сущностей.

Обновление каталога — реализовано

Скрипт ~/scripts/watchlist-catalog.sh (root:root, ставить только sudo install -m 755):

  1. python3 $LAMPAC/scripts/gen_watchlist.py <watchlist.md> $LAMPAC/wwwroot/watchlist.json
  2. docker restart lampac — обязательно, сбрасывает кэш Catalog (см. питфолл 3)

Cron: 30 2 * * * → лог ~/logs/watchlist-catalog.log. Время выбрано после watchlist-nightly (01:00) и watchlist-cleanup (02:00).

Бэкап crontab перед установкой: ~/crontab-backup-20260922-112915.txt.

⚠️ watchlist-catalog.sh сделан отдельной задачей, а не врезан в watchlist-nightly.sh. Причина: nightly — рабочий боевой скрипт, его падение остановило бы весь пайплайн списка. Отдельный cron падает изолированно.

Что осталось — ПЕРЕДЕЛКА (после признания архитектуры неверной)

# Шаг Статус
1 Генератор берёт данные из Radarr API (localhost:7878/api/v3/movie), а не парсит текст ⏳ ждёт апрува
2 Подставлять в id → tmdbId, в img → images[].remoteUrl (постер), year → year ⏳
3 Из sites/watchlist.yaml убрать блок menu, оставить main → родной CUB остаётся ⏳
4 Постеры кэшировать локально (Radarr MediaCover/ или проксировать через tmdbproxy) ⏳
5 Фикс plugin.js (enumerable:true) — отдельная независимая правка, см. ниже ⏳ ждёт апрува

Порядок: шаги 1–4 закрывают жалобу «пустые заглушки + исчез CUB». Шаг 5 — про то, что источника вообще не видно в списке выбора.

Приёмка (формулировка Alex): родной список CUB на месте, сверху добавлены ряды с реальными фильмами из списка, у элементов есть постеры и рабочие карточки.

Диагностический скрипт для ТВ (/tmp/lampa_probe.py)

Правки в UI Lampa по SSH напрямую не сделать, но состояние читается через DevTools:

pages = json.load(urllib.request.urlopen("http://192.168.1.70:9998/json", timeout=8))
target = next(p for p in pages if "com.lampac.tv" in p.get("url",""))
ws = websocket.create_connection(target["webSocketDebuggerUrl"], timeout=10)
# Runtime.evaluate:
#   Lampa.Storage.field('source')
#   Object.keys(Lampa.Api.sources).join('|')
#   Object.getOwnPropertyDescriptor(Lampa.Api.sources,'Мой список')
#   Lampa.Api.sources['Мой список'].getCatalog()

Требует pip3 install websocket-client (стоит). См. family/how-to/tv-lampac-plugin.

Известные ограничения

Ограничение Детали
Постеров нет image пустой. Опционально — тянуть через TMDB-кэш Lampac (tmdbproxy)
Карточки нет Только список названий. card_parse не настроен — Lampa может показывать пустую карточку
🗓️ upcoming показываются Не прячем. Решение Alex не запрашивалось
Перезапуск контейнера Каждое обновление каталога = docker restart lampac (~30 сек простоя). Кэш не инвалидируется иначе

⚠️ Что проверено, а что нет

Проверено по HTTP (серверная часть):

Проверка Результат
/catalog.js по IP 200, ~32 КБ, api_host:'http://192.168.1.14:9118'
Каталог в /catalog 7 ключей, «Мой список» первым (Rezka, Kinobase, FilmixApp, VoKino, Eneyida, KinoUkr, Мой список)
JSON-файлы watchlist.json + watchlist-{films,new,cartoons,anime}.json — все 200
Выдача по категориям films 360 · new 33 · cartoons 3 · anime 7 (после фикса сирот 2026-09-22)
Формат ответа /catalog/list {page, results, total_pages}, total_pages: 1 для films
Сортировка ⭐ → рейтинг ↓ → ужасы
Чистота названий эмодзи-маркеров 0
Сирота «Конец света» ❌ отсутствует во всех 4 категориях

НЕ проверено:

  • ✅ UI Lampa на ТВ — ПРИЧИНА НАЙДЕНА 2026-09-22 (см. ниже «🔴 ГЛАВНЫЙ ПИТФОЛЛ»). Диагностика через Chrome DevTools ТВ (http://192.168.1.70:9998/json):
    • ТВ физически достаёт Кра́кен — curl с ТВ отдаёт /catalog 200 за 0.26 с.
    • Lampa работает и ходит на сервер — соединение 192.168.1.70 → 192.168.1.14:9118 ESTABLISHED.
    • Storage.field('source') = 'Мой список' — источник уже выбран!
    • Lampa.Api.sources['Мой список'] существует и отдаёт каталог (getCatalog() → search, search_lazy, movie, cartoons, anime, menu, main).
    • ❌ НО: Object.keys(Lampa.Api.sources) = 'tmdb|cub' — источника нет в перечислении.
    • Причина: defineProperty(..., {get: ..., enumerable:false, configurable:false}).
    • Диагноз: баг в plugin.js самого Lampac, а не в нашей настройке. UI Lampa строит список источников через Object.keys() — «Мой список» из него выпадает, поэтому в UI источник не видно и выбрать его нельзя.
  • ❌ Боевой прогон крона в 02:30 (скрипт отработал вручную успешно).
  • ❌ Карточка/постеры в UI.

🔴 ГЛАВНЫЙ ПИТФОЛЛ: источник не виден в UI Lampa (enumerable: false)

Симптом. Alex: «Ни на главной ни в Фильмах нет! Там только Сейчас смотрят, рекомендуем итд». Плагин добавлен, серверная часть отдаёт всё корректно, а в UI источника нет.

Диагностика (по возрастанию точности):

  1. ssh kraken 'sudo ss -tn | grep ":9118"' — сначала показал 0 соединений (ложный вывод: замер сделан в момент старта Lampa). Через 30 с на ТВ: netstat -tn | grep 9118 → 1 ESTABLISHED + 6 TIME_WAIT — Lampa скачала все файлы.
  2. Foreground-приложение: luna-send -n 1 -f luna://com.webos.applicationManager/getForegroundAppInfo {} → com.lampac.tv. Запускать надо launch с экранированным JSON в кавычках, иначе errorCode: 2 invalid parameters.
  3. Доступ к отладчику ТВ: curl http://192.168.1.70:9998/json — webOS отдаёт Chrome DevTools Protocol на порту 9998 (у WebAppMgr в аргументах --remote-debugging-port=9998). Через websocket + Runtime.evaluate читаются переменные живой Lampa.
  4. Ключевой замер: JSON.stringify(Object.getOwnPropertyDescriptor(Lampa.Api.sources,'Мой список')) → {"enumerable":false,"configurable":false}.

Корень. В /lampac/module/Catalog/plugin.js (строка 59, loadCatalogs(), два вхождения):

Object.defineProperty(Lampa.Api.sources, key, { get: function(){ return Api; } });

Свойство создаётся не-enumerable → Object.keys() его не видит → Lampa не рисует источник в панели выбора. При этом Api.sources['Мой список'] доступен напрямую и каталог отдаёт.

Фикс (одна правка, два вхождения):

Object.defineProperty(Lampa.Api.sources, key,
  { get: function(){ return Api; }, enumerable: true, configurable: true });

Как применять — plugin.js живёт в образе, не на HDD! (проверено: docker inspect не показывает монтирования на module/Catalog, монтируются только catalog/sites и catalog/override). Правка через docker exec исчезнет при обновлении образа.

Предложенный путь (на апруве у Alex, 2026-09-22):

# docker-compose.yml
volumes:
  - ./catalog/plugin.js:/lampac/module/Catalog/plugin.js:ro
# 1. забрать, поправить локально (sed/grep по файлу не применять — только patch)
ssh kraken 'docker exec lampac cat /lampac/module/Catalog/plugin.js' > /tmp/catalog-plugin.js
# 2. бэкап
cp /tmp/catalog-plugin.js /tmp/catalog-plugin.js.bak-$(date +%Y%m%d-%H%M%S)
# 3. правка двух вхождений defineProperty → +enumerable:true,configurable:true
# 4. проверка синтаксиса ПЕРЕД заливкой
node --check /tmp/catalog-plugin.js
# 5. на HDD, добавить volume, пересоздать
scp /tmp/catalog-plugin.js kraken:/srv/.../lampac/catalog/plugin.js
ssh kraken 'cd /srv/.../lampac && docker compose up -d'
# 6. проверка на ТВ: Object.keys(Lampa.Api.sources) должен стать 'tmdb|cub|Мой список'

Откат: убрать строку volume из compose → docker compose up -d. Плюс бэкап файла в /tmp.

Статус: правка НЕ применена — ждёт апрува Alex (2026-09-22).

Готовые команды диагностики ТВ (переиспользовать)

# 1. Что запущено на ТВ (luna-send пишет в tty → нужен script -q -c)
ssh root@192.168.1.70 'script -q -c "luna-send -n 1 -f \
  luna://com.webos.applicationManager/getForegroundAppInfo {}" /dev/null'

# 2. Запустить приложение (JSON экранирован И обёрнут в кавычки)
ssh root@192.168.1.70 'script -q -c "luna-send -n 1 -f \
  luna://com.webos.applicationManager/launch \"{\\\"id\\\":\\\"com.lampac.tv\\\"}\"" /dev/null'

# 3. Достаёт ли ТВ Кра́кен
ssh root@192.168.1.70 'curl -s -o /dev/null -w "%{http_code} %{time_total}s\n" \
  --max-time 8 http://192.168.1.14:9118/catalog'

# 4. Живые соединения с Lampac (на ТВ, не на сервере!)
ssh root@192.168.1.70 'netstat -tn 2>/dev/null | grep 9118'

# 5. DevTools-цель Lampa на ТВ
curl -s --max-time 8 "http://192.168.1.70:9998/json" | /usr/bin/jq -r '.[] | "\(.type): \(.url)"'

Скрипты-пробы DevTools: /tmp/lampa_probe.py (состояние источника), /tmp/lampa_probe3.py (асинхронный вызов menu() каталога).

⚠️ tcpdump на Кра́кене нет — для съёма трафика ставить не стали, использован DevTools-подход (дал больше: видно состояние JS, а не только пакеты).

⚠️ luna://com.webos.service.capture/takeScreenShot на этом ТВ не существует (Unknown method) — скриншот так не снять.

Файлы реализации (на HDD Кра́кена)

Корень: /srv/dev-disk-by-uuid-6194539b-03e0-4f33-986b-4da09834e82f/lampac/

Путь Роль
config-module/init.conf включает Catalog → /lampac/init.conf:ro
catalog/sites/watchlist.yaml описание каталога
catalog/override/ переопределения (пусто)
wwwroot/watchlist.json все секции
wwwroot/watchlist-<code>.json по файлу на категорию
scripts/gen_watchlist.py генератор
docker-compose.yml 7 монтирований; бэкапы .bak-*

На хосте: ~/scripts/watchlist-catalog.sh, лог ~/logs/watchlist-catalog.log.

Откат

# выключить каталог
ssh kraken 'sed -i "s/\"catalog\": true/\"catalog\": false/" \
  /srv/.../lampac/config-module/init.conf && docker restart lampac'
# в Lampa убрать плагин catalog.js
# cron: crontab -l | grep -v watchlist-catalog | crontab -

Ничего в коде Lampac не патчилось — только конфиг и данные.

Статус: ✅ РЕАЛИЗОВАНО 2026-09-22

Каталог «Мой список» работает, отдаётся в Lampa через /catalog.js.

Что сделано

# Шаг Результат
1 Включён модуль Catalog SkipModules без Catalog + catalog: true → /catalog.js 200
2 init.conf смонтирован ./config-module/init.conf:/lampac/init.conf:ro — правится на HDD, не теряется
3 sites/ + override/ смонтированы ./catalog/sites, ./catalog/override
4 wwwroot смонтирован ./wwwroot — Lampac сам отдаёт JSON по HTTP
5 Генератор написан lampac/scripts/gen_watchlist.py
6 sites/watchlist.yaml создан lampac/catalog/sites/watchlist.yaml
7 Автообновление в cron 30 2 * * * → ~/scripts/watchlist-catalog.sh
8 Проверка 4 категории: films 360, new 34, cartoons 3, anime 7

Архитектура (финальная)

family/documents/movies-watchlist.md          ← источник
        │  gen_watchlist.py (свой парсер, ~200 строк)
        ▼
lampac/wwwroot/watchlist.json                 ← все секции
lampac/wwwroot/watchlist-<code>.json          ← по файлу на категорию
        │  Lampac отдаёт wwwroot по HTTP сам
        ▼
lampac/catalog/sites/watchlist.yaml           ← описание для Catalog
        │  plugin=watchlist, jsonPath: true, nodes: $.items[*]
        ▼
Lampa на ТВ: плагин http://192.168.1.14:9118/catalog.js

🔴 Питфоллы, найденные по ходу (не повторять)

  1. Catalog ВЫБРАСЫВАЕТ элемент с пустым href. ListController.cs: if (!IsNullOrWhiteSpace(name) && !IsNullOrWhiteSpace(href)). Симптом: из 404 позиций отдалось 52 — ровно те, у кого есть ссылка Кинопоиска. Фикс: генератор подставляет fallback-href https://www.kinopoisk.ru/index.php?kp_query=<название>.

  2. Catalog НЕ подставляет {cat} в nodes. JSONPath статичен, отфильтровать секцию внутри одного файла нечем. Симптом: все категории отдавали весь список (404 вместо 360/34/3/7). Фикс: генератор пишет отдельный плоский файл на категорию (watchlist-films.json и т.д.), nodes: "$.items[*]" одинаков для всех.

  3. Кэш Catalog без версии данных. Ключ catalog:{plugin}:{search}:{sort}:{cat}:{page} — не зависит от содержимого. Симптом: после перегенерации JSON выдача не менялась. Фикс: docker restart lampac в скрипте обновления.

  4. init.conf не был смонтирован. Файл живёт в образе; правки через docker exec теряются при пересоздании. Правки шли в current.conf (сгенерированный) — неверная цель. Фикс: volume :ro + свой минимальный init.conf (перекрывает base.conf).

  5. api_host подставляется из запроса ({localhost} → хост, по которому пришли). По IP 192.168.1.14 отдаёт правильный адрес — специально ничего настраивать не нужно.

  6. Кириллица в inline-jq внутри ssh '...' ломает команду (tirith + quoting). Выносить в файл или убирать строки с кириллицей.

  7. Осиротевшие строки в ## Новинки давали дубли в каталоге. Строки без 👍/👎 (например - [ ] ⬇️ Название) process-thumbs не трогал — они висели в Новинках навсегда. Если фильм уже [x] в главном списке — получался дубль: непросмотренный «Конец света» при просмотренном оригинале. Фикс в watchlist-sync/parser.py (удаление сирот + флаг _patch_dropped) и cli.py. Разбор: family/projects/watchlist-automation, секция «ОСИРОТЕВШИЕ СТРОКИ».

  8. Двойное экранирование при правке parser.py скриптами. r'\1' превращался в байт \x01, r'\b' — в \x08. Синтаксис валиден, регекспы молчат. Проверять файл на chr(1) и chr(8) после патча.

Управление

ssh kraken
cd /srv/dev-disk-by-uuid-6194539b-03e0-4f33-986b-4da09834e82f/lampac

# перегенерировать каталог + сбросить кэш
bash ~/scripts/watchlist-catalog.sh

# логи
tail -f ~/logs/watchlist-catalog.log

# только генератор (без перезапуска)
python3 scripts/gen_watchlist.py \
  /home/kraken/obsidian/family/documents/movies-watchlist.md \
  wwwroot/watchlist.json

Откат

# выключить каталог
ssh kraken 'sed -i "s/\"catalog\": true/\"catalog\": false/" \
  /srv/.../lampac/config-module/init.conf && docker restart lampac'
# в Lampa убрать плагин catalog.js

Правила отбора и сортировки (утверждено Alex 2026-09-22)

Фильтрация

Правило Действие
[ ] непросмотренные показывать
[x] просмотренные ❌ скрыть
👎 ❌ скрыть
🗓️ upcoming показывать (решить при реализации — есть в файле)
❓ ambiguous показывать (название как есть)

Сортировка внутри списка — трёхключевая

  1. ⭐ (U+2B50) — первыми
  2. Рейтинг — по убыванию (IMDb/KP из строки файла; строки [название](kp) (год) IMDb N.N KP N.N)
  3. Ужасы (🧟♀️, TMDB genre_id=27) — последними

⚠️ Только ⭐ (U+2B50) — как в sync-down (см. family/projects/watchlist-automation). ✭ (U+272D) приоритетом не является.

Порядок секций

  1. Главный список — первым. Это ## Фильмы
  2. Дальше — остальные по порядку в файле: ## Новинки - 👍/👎, ## Мультфильмы, ## Аниме, …

⚠️ ## Новинки👎 — не показывать (это скрытая секция отказов). ⚠️ Секция ## Новинки содержит 👍/👎; 👎-строки прячем, 👍 показываем.

Источник рейтинга

В строках со ссылками Кинопоиска рейтинг уже в тексте: IMDb 7.6 KP 8.2. Для строк без рейтинга (например - [ ] Gods don't give gifts) — сортировать как 0, в конец группы. Опционально: добирать через TMDB (кэш tmdbproxy в Lampac) — решить при реализации.

🎯 ПЕРЕДЕЛКА 2 — Radarr как источник метаданных без автоскачки (план Alex, 2026-09-22)

Логика Alex (дословно)

«Мы блядь убираем автоскачку на диск так?! При этом ты оставляешь radarr/sonarr источником, так?? И в него из списка добавляется скриптом, но не грузит автоматом так? Сейчас ты отменяешь текущие загрузки, так??»

Расшифровка:

# Что Как
1 Автоскачка на HDD — выключить Radarr/Sonarr ничего не качают сами
2 Radarr/Sonarr остаются источником TMDB ID + постеры для каталога Lampa
3 Скрипт добавляет в них из списка .md → Radarr API, фильм получает метаданные
4 Но не грузит автоматом Добавление ≠ загрузка
5 Текущие загрузки — отменить 6 записей в очереди

Как это в Radarr технически (проверено 2026-09-22)

⚠️ Важно для понимания: в Radarr «добавить, но не качать» = monitored: false. Отдельного состояния «источник, но не грузить» нет. При добавлении через API Radarr сам ходит в TMDB и тянет метаданные (постер, год, описание) — даже при monitored: false. Это и есть решение задачи №2+№3+№4.

Доступ к Radarr API (проверено 2026-09-22, воспроизводимо)

# API-ключ лежит в config.xml (не в БД)
ssh kraken 'grep -oE "<ApiKey>[^<]+</ApiKey>" \
  /srv/dev-disk-by-uuid-6194539b-03e0-4f33-986b-4da09834e82f/docker/radarr/config/config.xml' \
  | sed 's/<[^>]*>//g'      # → cbcb8ec320104359920c0f611d503c7b

🔴 ПИТФОЛЛ: -L обязателен. Без него Radarr отдаёт 307 с пустым телом и jq length падает:

curl -s  "http://localhost:7878/api/v3/movie?apikey=$KEY" | jq length  # → пустой вывод, 0 байт
curl -sL "http://localhost:7878/api/v3/movie?apikey=$KEY" | jq length  # → 200, 50

Конфиг Radarr (замер):

curl -sL ".../api/v3/config/indexer?apikey=$KEY" | jq .rssSyncInterval   # → 30 (мин)

rssSyncInterval: 30 = Radarr каждые 30 минут сам ходит в RSS-фиды и качает новое. Это вторая причина автоскачки помимо monitored: true. Выключать надо оба.

Окружение Radarr на Кра́кене (для будущих сессий)

Параметр Значение
Порт 7878 (0.0.0.0)
Контейнер radarr, образ lscr.io/linuxserver/radarr:latest
Конфиг на HDD /srv/...6194539b.../docker/radarr/config/
БД radarr.db (+ -shm, -wal)
Постеры /config/MediaCover/ — 46 каталогов
sqlite3 в контейнере ❌ нет (на хосте Кра́кена — есть, /usr/bin/sqlite3)
Соседи sonarr:8989, prowlarr:9696, transmission

⚠️ В контейнерах sqlite3 отсутствует (см. профиль Alex) — БД читать через strings или вытаскивать файл наружу и смотреть хостовым sqlite3.

Текущее состояние Radarr (замер 2026-09-22)

Метрика Значение
Фильмов всего 50
monitored: true 45 ← активны, ищут и качают
hasFile: true 24 (уже на диске)
rssSyncInterval 30 мин ← Radarr ходит в RSS каждые полчаса
Очередь 6 записей (все warning, ничего не качается активно)
Transmission простаивает (1% CPU)

Очередь (id для отмены):

598407147  Brokeback Mountain.mkv
1092005446 Гладиатор (2000) (DTS X) DV.mkv
914899324  Sorry.to.Bother.You.BDRemux.1080p.NNMClub.pk.mkv
1671545331 Мортал Комбат 2 (DTS-HD MA).mkv
1486734395 Gorod.boga.2002.BDRemux.1080p.mkv
139445264  День хомячка (2003)

✅ ПРОВЕРКА «как это реализуемо» — полный аудит 2026-09-22 (вечер)

Alex: «Мне надо чтоб lampa источники видела и играла фильмы из списка и cub. При этом мы перестаем качать торренты на диск! Как это реализуемо?? Проверь блядь все чтобы ты опять не обосрался».

Проверено всё, что может качать или мешать. Ничего не менялось — только чтение.

🎯 Главная находка: TorrServer на диск НЕ пишет

Ключевой факт, снимающий главный вопрос Alex («при этом фильмы не будут скачиваться на hdd я правильно понимаю?!»). Конфиг TorrServer внутри Lampac:

ssh kraken 'docker exec lampac cat /lampac/data/ts/settings.json' | jq .
{
  "BitTorr": {
    "CacheSize": 100663296,        // 96 МБ — RAM-кэш
    "UseDisk": false,              // 🔴 НА ДИСК НЕ ПИШЕТ
    "TorrentsSavePath": "",        // путь сохранения пуст
    "TorrentDisconnectTimeout": 180,  // раздача живёт 3 мин после остановки
    "PreloadCache": 0,
    "ReaderReadAHead": 83
  }
}

UseDisk: false — TorrServer держит всё в RAM (96 МБ) и на диск не пишет ничего. Это ровно то, что нужно Alex. Настраивать не надо, уже так.

TorrServer жив и работает (проверено)

curl -s "http://192.168.1.14:9118/ts/echo"        # → MatriX.135 (версия, живой)
curl -s "http://192.168.1.14:9118/ts/torrents"    # → 404 (это не тот эндпоинт)
curl -s -X POST "http://192.168.1.14:9118/ts/torrents" \
  -H 'Content-Type: application/json' -d '{"action":"list"}'   # → [] пусто, ничего не висит

Кэш Lampac: /lampac/cache/ = 31 МБ (SQLite, Playwright, gstreamer-registry) — не фильмы. /lampac/data/ = 44 МБ (ts/settings.json, dlna/thumbs). Диск не забит.

Поиск торрентов — Jackett внутри Lampac, отдельный НЕ нужен

🔴 ПИТФОЛЛ-ЛОВУШКА: в lampainit.js стоит parser_torrent_type = 'jackett', но отдельного контейнера Jackett НЕТ. Это не ошибка — Lampac сам отдаёт Jackett-совместимый API.

# что прописывает Lampac клиенту:
curl -s "http://192.168.1.14:9118/lampainit.js" | grep -oE "Lampa.Storage.set\('[^']+', ?'[^']*'\)"
#   Lampa.Storage.set('jackett_url', '192.168.1.14:9118')
#   Lampa.Storage.set('jackett_key', '1')
#   Lampa.Storage.set('parser_torrent_type', 'jackett')
#   Lampa.Storage.set('parser_use', 'true')

jackett_url указывает на сам Lampac, а не на сторонний сервис. Проверка API:

# ⚠️ работает БЕЗ префикса /jackett — на корне
curl -s "http://192.168.1.14:9118/api/v2.0/indexers/all/results?apikey=1&Query=matrix"
# → 200, реальные результаты: rutor, магнит-ссылки, Size, Seeders, ffprobe

curl -s -o /dev/null -w "%{http_code}" "http://192.168.1.14:9118/jackett/api/v2.0/..."  # → 404
curl -s -o /dev/null -w "%{http_code}" "http://192.168.1.14:9118/api/v2.0/..."           # → 200 ✅

Вывод: поиск торрентов работает штатно, ничего поднимать не надо.

Кто качает на диск — полный реестр (единственные виновники)

ssh kraken 'docker ps --format "{{.Names}}\t{{.Image}}"'
Сервис Качает? Замер Нужен для задачи?
Radarr ✅ да 50 фильмов, 45 monitored, очередь 6, RSS 30 мин ❌ мешает
Sonarr ✅ да 13 сериалов, 13 monitored, очередь 19 ❌ мешает
Transmission ⚠️ исполнитель 257 .torrent, CPU 0.76% (простаивает), NetIO 290G/26.7G ⚠️ движок Radarr/Sonarr
TorrServer ❌ НЕТ UseDisk: false, список торрентов [] ✅ вот чем играет Lampa

Полный список контейнеров Кра́кена: lampac · radarr · jellyfin · transmission · portainer · flaresolverr · prowlarr · sonarr · watchtower · rclone · xray-reverse-bridge · hermes-kraken · cloudflared

Sonarr API (для будущей работы, по аналогии с Radarr):

# ключ: /srv/.../docker/sonarr/config/config.xml → 15fbec32f1cb4b05b1c2b229ce27359b
curl -sL "http://localhost:8989/api/v3/series?apikey=$SONARR_KEY" | jq 'length'   # → 13
curl -sL "http://localhost:8989/api/v3/queue?apikey=$SONARR_KEY" | jq '.totalRecords'  # → 19

⚠️ Sonarr — тоже источник автоскачки, не только Radarr. Alex упомянул его не зря. Отключать надо оба, иначе Sonarr продолжит качать сериалы.

Transmission — настройки (замер)

ssh kraken 'docker exec transmission cat /config/settings.json' > /tmp/trans-settings.json
{"download-dir": "/downloads", "rpc-port": 9091,
 "speed-limit-down-enabled": false, "peer-port": 51413,
 "alt-speed-enabled": false}

/config/torrents/*.torrent = 257 файлов — это накопленный список раздач, не признак активной качки (CPU 0.76%). Файлы на диске не трогаем.

Кто чем играет в Lampa (подтверждено)

curl -s "http://192.168.1.14:9118/lampainit.js" | grep -iE "torrents_use|source"
#   lampa_settings.torrents_use = true     ← кнопка торрентов включена
#   Lampa.Storage.set('source', 'cub')     ← CUB — основной источник ✅
#   Lampa.Storage.set('proxy_tmdb', 'true')

В plugins_add (серверные плагины Lampac, не список Lampa): tmdbproxy.js · cubproxy.js · online.js · catalog.js · sisi.js · startpage.js · timecode.js · ts.js — все status: 1.

Итоговая схема (что получится после переделки)

ТВ: Lampa (com.lampac.tv)
  ├── источник CUB        ── онлайн, работает как раньше ✅
  └── источник «Мой список» ── секции СВЕРХУ, постеры из Radarr
        │
        ▼ клик по фильму
      поиск торрента через Lampac (Jackett-API на :9118)
        │
        ▼
      TorrServer (UseDisk: false) → RAM-кэш 96 МБ → на HDD НЕ ПИШЕТ ✅

Radarr/Sonarr после переделки = ЧИСТЫЙ СПРАВОЧНИК МЕТАДАННЫХ
  (monitored: false + rssSyncInterval: 0) — не ищут, не качают

⚠️ Порядок работ (обновлён с учётом Sonarr)

# Действие Сервис Статус
0 Бэкап radarr.db + config.xml (+ то же для Sonarr) оба ⏳
1 Отменить очередь: 6 записей Radarr + 19 Sonarr оба ⏳
2 monitored: false — 45 Radarr + 13 Sonarr оба ⏳
3 rssSyncInterval: 0 оба ⏳
4 Проверка: monitored 0, очередь 0, RSS 0, Transmission тих оба ⏳
5 Генератор каталога → данные из Radarr API (TMDB ID + постер) — ⏳
6 В sites/watchlist.yaml убрать menu, оставить main — ⏳
7 Фикс plugin.js (enumerable:true) — ⏳
8 Добавление 310 фильмов из .md в Radarr с monitored: false Radarr ⏳

Не трогаем: Prowlarr, Transmission, 24 файла на диске, Jellyfin, остальные контейнеры.

❓ Открытый вопрос к Alex

Radarr/Sonarr нужны как справочник (тогда постеры сохраняются) или выключаются совсем (тогда каталог Lampa теряет постеры — их брать неоткуда)? Ответа нет.

План работ (на апруве Alex)

Шаг 0 — бэкап (обязательно, до всего)

ssh kraken 'cp /srv/.../docker/radarr/config/radarr.db /srv/.../radarr.db.bak-$(date +%Y%m%d-%H%M%S)'
ssh kraken 'cp /srv/.../docker/radarr/config/config.xml /srv/.../config.xml.bak-$(date +%Y%m%d-%H%M%S)'

Причина: radarr.db — боевая база, содержит всю библиотеку.

Шаг 1 — отменить текущие загрузки (6 шт)

curl -sL -X DELETE "http://localhost:7878/api/v3/queue/<id>?removeFromClient=true&blocklist=false&apikey=$KEY"

⚠️ Не использовать blocklist=true — иначе релизы попадут в чёрный список и не найдутся при ручной загрузке. Файлы на диске не трогаются.

Шаг 2 — снять monitored со всех фильмов

curl -sL -X PUT "http://localhost:7878/api/v3/movie/editor?apikey=$KEY" \
  -H "Content-Type: application/json" \
  -d '{"movieIds":[<все 50>],"monitored":false}'

Результат: Radarr не ищет и не качает. Фильмы в базе с метаданными остаются.

Шаг 3 — выключить авто-поиск по RSS

curl -sL -X PUT "http://localhost:7878/api/v3/config/indexer?apikey=$KEY" \
  -H "Content-Type: application/json" \
  -d '{"rssSyncInterval":0,"id":1, ...остальные поля как есть}'

0 = Radarr не ходит в RSS. Ничего не находит сам.

Шаг 4 — проверить

jq '[.[] | select(.monitored==true)] | length'   # → 0
jq '.rssSyncInterval'                            # → 0
jq '.totalRecords'                               # очередь → 0

Что НЕ трогаем: Prowlarr, Transmission, библиотеку на диске (24 файла), Sonarr (отдельная проверка ниже).

⚠️ Незакрытые вопросы к плану

  1. Sonarr — Alex упомянул его вместе с Radarr. Проверить его настройки отдельно (сериалы в списке есть?). Это отдельная задача. Вопрос задан Alex, ответа нет.
  2. Ручная загрузка после отключения — если Alex захочет скачать фильм вручную, нужно временно включить monitored или искать вручную через Interactive Search. Работает, но не автоматически.
  3. 310 фильмов, которых нет в Radarr — скрипт будет добавлять их с monitored: false, Radarr сам подтянет постеры из TMDB. Но часть названий найдётся криво («девять»).

✅ Подтверждено Alex 2026-09-22 (семантика задачи — не переспрашивать)

Alex сам сформулировал схему и подтвердил её понимание ассистентом:

«При этом фильмы не будут скачиваться на hdd я правильно понимаю?! Играть будет через torrserver так?» → ответ: да, правильно.

Разделение обязанностей (запомнить, чтобы не путать):

Слой Роль Куда пишет
Radarr/Sonarr + Transmission библиотека, автоскачка релизов на HDD (библиотека)
Lampac TorrServer стриминг по требованию из UI Lampa ничего не сохраняет — только временный кэш кусков
«Мой список» (Catalog) только показ секций с постерами ничего не качает

То есть после выключения автоскачки в Radarr: фильмы, которые Alex смотрит через Lampa, на HDD не попадают вообще — TorrServer отдаёт поток и выбрасывает буфер. Файлы на диске остаются только у 24 уже скачанных (нужны Jellyfin).

⚠️ transmission НЕ связан с Lampa. Radarr качает через него релизы в свою библиотеку — это отдельный контур. Вопрос «через что играет TorrServer» и «что качает Radarr» — разные вещи, не смешивать в объяснениях.

🔴 Правка plugin.js (enumerable:true) — всё ещё НЕ применена

Независимая от ПЕРЕДЕЛКИ 2 правка. Файл лежит локально /tmp/catalog-plugin.js (10738 байт, node --check чистый, 2 вхождения исправлены), бэкап /tmp/catalog-plugin.js.bak-20260922-121452. На Кра́кене и в образе — оригинал. Нужен volume ./catalog/plugin.js:/lampac/module/Catalog/plugin.js:ro в compose. Подробности — раздел «🔴 ГЛАВНЫЙ ПИТФОЛЛ» выше.

Порядок: правку plugin.js имеет смысл применять вместе с ПЕРЕДЕЛКОЙ 2, чтобы не пересоздавать контейнер дважды.

Откат

# вернуть monitored (одним запросом)
curl -sL -X PUT ".../api/v3/movie/editor" -d '{"movieIds":[...],"monitored":true}'
# вернуть RSS
curl -sL -X PUT ".../api/v3/config/indexer" -d '{"rssSyncInterval":30,"id":1}'
# восстановить базу при необходимости
cp radarr.db.bak-<дата> radarr.db && docker restart radarr

Статус: план на апруве. Ничего не выполнено.

Открытые вопросы — БЛОКИРУЮЩИЕ (2026-09-22, ждут решения Alex)

  1. Что делать с 310 фильмами, которых нет в Radarr? В .md — 360, в Radarr — 50. У 310 нет TMDB ID и постера. Варианты: (а) показывать только 50 из Radarr; (б) добирать остальные через TMDB-search (часть названий найдётся криво: «девять» → не то); (в) показывать заглушкой без постера. Без решения шаг 1 переделки не начать.
  2. Нужны 4 секции или одна? Секции «Фильмы / Новинки / Мультфильмы / Аниме» или одна «Мой список» сверху. Вопрос задан, ответа нет.

Открытые вопросы (не блокируют)

  1. Постеры — тянуть через TMDB-кэш Lampac, через Radarr MediaCover/, или remoteUrl CDN?
  2. Карточка — card_parse не настроен. С реальным TMDB ID карточка должна подтянуться штатным механизмом Lampa — проверить после переделки.
  3. 🗓️ upcoming — показывать или прятать? Сейчас показываем.
  4. ❓ ambiguous — сейчас показываем с названием как есть.

⚠️ Отменённые/ошибочные указания (не повторять)

  • ❌ «Настройки → Источник» — такого пункта в Lampa не существует. Выдумано, Alex не нашёл. Источник переключается в разделе «остальное → основной источник» (Alex нашёл сам 2026-09-22).
  • ❌ «влево → панель источников» — тоже не подтверждено.
  • ❌ plugins_add в lampainit.js — это серверные плагины Lampac (tmdbproxy.js, cubproxy.js, online.js, catalog.js…), а не список плагинов Lampa. catalog.js там есть при "catalog": true в init.conf.

Связанные