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

644 lines
38 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
created: '2026-09-22'
namespace: personal
related:
- '[[personal/plans/lampac-kraken]]'
- '[[personal/tech/lampac-deployment]]'
- '[[family/projects/watchlist-sync]]'
- '[[family/projects/watchlist-automation]]'
- '[[family/documents/movies-watchlist]]'
status: rework
tags:
- lampac
- lampa
- watchlist
- catalog
- webos
- plugin
- kraken
- radarr
title: Watchlist в UI Lampa — каталог из списка
type: plan
updated: '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 (дословно):**
> «Я предполагал что будет отображаться то что и раньше (список 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 идёт по ссылке, получает страницу поиска, а не фильм.
**Правильный источник — Radarr API.** Alex: «Они уже резолвятся же блядь!! Оно же
добавлялось в arr!» — TMDB ID и постеры уже есть там, потому что фильмы добавлялись
в Radarr.
```bash
# 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 (то, что нужно было брать с самого начала):**
```json
{
"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
```csharp
if (!url.StartsWith("http"))
url = $"{init.host}/{url}"; // ListController.cs:87
```
`host` из YAML сканируется **по HTTP** (`Http.Get` / Playwright / RCH).
**Локальные файлы напрямую Catalog читать не умеет** — только HTTP-ответ.
### Два режима парсинга
```csharp
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)
```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 правки
```jsonc
// 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:
```python
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()`, **два вхождения**):
```js
Object.defineProperty(Lampa.Api.sources, key, { get: function(){ return Api; } });
```
Свойство создаётся не-enumerable → `Object.keys()` его не видит → Lampa не рисует источник
в панели выбора. При этом `Api.sources['Мой список']` доступен напрямую и каталог отдаёт.
**Фикс** (одна правка, два вхождения):
```js
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):
```yaml
# docker-compose.yml
volumes:
- ./catalog/plugin.js:/lampac/module/Catalog/plugin.js:ro
```
```bash
# 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).
## Готовые команды диагностики ТВ (переиспользовать)
```bash
# 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`.
## Откат
```bash
# выключить каталог
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)` после патча.**
### Управление
```bash
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
```
### Откат
```bash
# выключить каталог
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) — решить при реализации.
## Открытые вопросы — БЛОКИРУЮЩИЕ (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.
## Связанные
- [[personal/plans/lampac-kraken]] — базовое развёртывание (шаги 1–6 сделаны)
- [[personal/tech/lampac-deployment]] — эксплуатация Lampac, модуль Catalog
- [[family/how-to/tv-lampac-plugin]] — подключение плагина на ТВ
- [[family/projects/watchlist-sync]] — парсер списка, CLI
- [[family/projects/watchlist-automation]] — форматы маркеров, cron
- [[family/documents/movies-watchlist]] — источник данных