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

398 lines
22 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: active
tags:
- lampac
- lampa
- watchlist
- catalog
- webos
- plugin
- kraken
title: Watchlist в UI Lampa — каталог из списка
type: plan
updated: '2026-09-22'
---
# Watchlist в UI Lampa — каталог из списка
> Реализовано 2026-09-22. План был переписан после разведки кода модуля Catalog.
> Базовое развёртывание — [[personal/plans/lampac-kraken]] (шаги 1–6 закрыты).
>
> **Итог одной строкой:** Lampac отдаёт `movies-watchlist.md` как каталог Lampa
> через модуль `Catalog` → 4 категории (360/33/3/7 позиций), автообновление кроном 02:30.
> Работает на стороне сервера; на ТВ остаётся добавить плагин
> `http://192.168.1.14:9118/catalog.js` и выбрать источник (см. «Что осталось на Alex»).
## Задача
Показывать список фильмов Alex (`family/documents/movies-watchlist.md`) **в UI Lampa на ТВ** — каталогом.
Не локальный плейбэк. Формулировка Alex: «отображение фильмов из списка».
⚠️ **Что НЕ спрашивать повторно.** Задача — **отображение**, не воспроизведение.
Список — это перечень, а не источник видео. Вопрос «а что играть по клику?» —
**выдумка сверх постановки**, 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 падает изолированно.
## Что осталось на Alex (единственное незакрытое)
Добавить плагин на ТВ — **пультом**, 1 минута:
1. Lampa → **Настройки → Плагины** → добавить URL `http://192.168.1.14:9118/catalog.js`
2. **Полностью перезапустить Lampa** (не сворачивать) — каталоги регистрируются только при старте
3. На главной встать на ряд каталога и нажать **влево** → в выехавшей панели выбрать **«Мой список»**
🔴 **Пункта «Настройки → Источник» в Lampa НЕ СУЩЕСТВУЕТ** — я его выдумал, Alex дважды указал на ошибку.
Источник переключается панелью слева в меню каталогов. Подробно: [[family/how-to/tv-lampac-plugin]].
Приёмка = категории видны в UI Lampa, названия совпадают с `movies-watchlist.md`.
Автоматизация этого шага по SSH **не проверялась** — см. [[family/how-to/tv-lampac-plugin]]
(там заготовка подхода и питфоллы localStorage).
## Известные ограничения
| Ограничение | Детали |
|---|---|
| **Постеров нет** | `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 на ТВ** — Alex добавил плагин, но каталог в UI не увидел.
Соединений ТВ → `:9118` на момент диагностики — **ноль**. Гипотеза: не сделан полный
перезапуск Lampa (каталоги регистрируются только при старте) либо не выбран источник
в панели слева. **Требуется подтверждение от Alex.**
- ❌ Боевой прогон крона в 02:30 (скрипт отработал вручную успешно).
- ❌ Карточка/постеры в UI.
## Файлы реализации (на 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) — решить при реализации.
## Открытые вопросы (не блокируют, решение Alex не запрошено)
1. **Постеры** — тянуть через TMDB-кэш Lampac или не тянуть вовсе? Сейчас пустые.
2. **Карточка** — нужна ли (описание/рейтинг внутри Lampa), или достаточно списка названий?
3. **`🗓️` upcoming** — показывать или прятать? Сейчас показываем.
4. **`❓` ambiguous** — сейчас показываем с названием как есть.
Ни один из них **не мешает работе** — каталог отдаётся, категории на месте.
Спрашивать только если Alex сам затронет тему.
## Связанные
- [[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]] — источник данных