[2026-09-22] eagle: personal/plans/lampac-watchlist-plugin.md personal/tech/lampac-deployment.md
This commit is contained in:
1 parent
e1593fd110
commit
bd00f6f145
2 files changed
+319
-3
No files matched your search
@@ -47,10 +47,15 @@ related:
|
||||
├── config-module/init.conf → /lampac/init.conf:ro (наш конфиг)
|
||||
├── catalog/sites/ → /lampac/module/Catalog/sites
|
||||
├── catalog/override/ → /lampac/module/Catalog/override
|
||||
├── catalog/plugin.js → /lampac/module/Catalog/plugin.js:ro (ПАТЧ, см. ниже)
|
||||
├── wwwroot/ → /lampac/wwwroot (JSON-каталог watchlist)
|
||||
└── scripts/gen_watchlist.py (генератор .md → JSON)
|
||||
```
|
||||
|
||||
Локальные копии рабочих артефактов (на Mac, `/Users/admin/lampac-tmp/`):
|
||||
`gen_watchlist_v2.py`, `patch_plugin.py`, `plugin-patched.js`, `watchlist.yaml`,
|
||||
`docker-compose.yml`, `deploy_lampac.py`.
|
||||
|
||||
Владелец `kraken:kraken`, права `755`.
|
||||
|
||||
⚠️ Всё пишущее — на data-диск (1.8T). **НЕ на SD** (`/`, 14G, 8.6G свободно).
|
||||
@@ -79,6 +84,9 @@ services:
|
||||
# Каталоги Catalog
|
||||
- ./catalog/sites:/lampac/module/Catalog/sites
|
||||
- ./catalog/override:/lampac/module/Catalog/override
|
||||
# Патч plugin.js. Файл живёт В ОБРАЗЕ — без монтирования патч теряется
|
||||
# при обновлении образа. Нужен для видимости источника в UI (см. ниже).
|
||||
- ./catalog/plugin.js:/lampac/module/Catalog/plugin.js:ro
|
||||
# Статика: JSON-каталог watchlist (Lampac отдаёт wwwroot по HTTP сам)
|
||||
- ./wwwroot:/lampac/wwwroot
|
||||
```
|
||||
@@ -103,6 +111,138 @@ services:
|
||||
|
||||
После любой правки — `docker compose up -d` (пересоздаст контейнер) или `docker restart lampac`.
|
||||
|
||||
## Модуль Catalog — внутренняя механика (разобрано по коду 2026-09-22)
|
||||
|
||||
Исходники внутри образа: `/lampac/module/Catalog/` —
|
||||
`Controllers/{ApiController,ListController,CardController}.cs`, `ModInit.cs`, `plugin.js`, `sites/*.yaml`.
|
||||
|
||||
### Маршруты
|
||||
|
||||
| Роут | Назначение |
|
||||
|---|---|
|
||||
| `/catalog.js` | плагин для Lampa (клиентский JS) |
|
||||
| `/catalog` | JSON: список каналов с их `menu` и `main` |
|
||||
| `/catalog/list?plugin=<p>&cat=<c>&page=N` | список элементов категории |
|
||||
| `/catalog/card?plugin=<p>&uri=<id>&type=movie` | карточка фильма |
|
||||
|
||||
WAF на префиксе `/catalog/` — **10 запросов/сек**.
|
||||
|
||||
### 🔴 Питфолл №1: первая категория игнорирует `format`
|
||||
|
||||
`ListController.cs:50` строит базовый URL **до** обработки `format`:
|
||||
|
||||
```csharp
|
||||
string url = $"{init.host}/{(page==1 && init.list?.firstpage != null
|
||||
? init.list?.firstpage : init.list?.uri)}"; // строка 50
|
||||
else if (!string.IsNullOrEmpty(cat)) {
|
||||
var menu = init.menu.FirstOrDefault(i => i.categories.Values.Contains(cat));
|
||||
string eval = (cat != null && sort != null) ? getFormat("sort") : getFormat("-");
|
||||
if (!string.IsNullOrEmpty(eval)) { url = CSharpEval(...); } // строки 78–85
|
||||
if (!url.StartsWith("http")) url = $"{init.host}/{url}"; // строка 87
|
||||
}
|
||||
```
|
||||
|
||||
**Категория, стоящая ПЕРВОЙ в `menu.categories`, берёт базовый `url` из `list.firstpage`
|
||||
и `format` для неё не применяется.** Симптом: первая категория отдаёт данные чужого файла
|
||||
(например все 360 фильмов из общего `watchlist.json`), у элементов `img: null`,
|
||||
`id` = ссылка на поиск Кинопоиска → в UI «здесь пусто».
|
||||
|
||||
**Правило:** `list.uri`/`firstpage` и первая категория в `menu.categories`
|
||||
должны указывать на **один файл**. Переставляешь категории — правь `list.uri`.
|
||||
|
||||
### 🔴 Питфолл №2: `host` + `uri` склеиваются
|
||||
|
||||
Строка 50 склеивает `init.host` + `/` + `uri`. Значит `uri`/`firstpage` обязаны быть
|
||||
**относительными** (`watchlist-films.json`), иначе двойной префикс:
|
||||
`http://127.0.0.1:9118/http://127.0.0.1:9118/watchlist-films.json` → пусто.
|
||||
|
||||
### 🔴 Питфолл №3: `main` в YAML запрещён
|
||||
|
||||
`CatalogSettings` не имеет поля `main` для входного YAML → YamlDotNet давится
|
||||
неизвестным полем → `ModInit.goInit()` → `null` → `ApiController.cs:70` отбрасывает сайт:
|
||||
|
||||
```csharp
|
||||
if (init == null || !init.enable || init.menu == null || init.hide) continue;
|
||||
```
|
||||
|
||||
**В логах — тишина.** Сайт молча исчезает из `/catalog`. Обе формы (список и словарь) ломают.
|
||||
|
||||
`main` Catalog **генерирует сам** из `menu` — в JSON-ответе `/catalog` присутствуют оба
|
||||
ключа с одинаковым содержимым. Писать `main` в YAML не нужно и нельзя.
|
||||
|
||||
⚠️ `menu` **обязателен** — он регистрирует канал, а не подменяет разделы.
|
||||
|
||||
### 🔴 Питфолл №4: элемент без `href` выбрасывается
|
||||
|
||||
```csharp
|
||||
if (!string.IsNullOrWhiteSpace(name) && !IsNullOrWhiteSpace(href)) // ListController.cs
|
||||
```
|
||||
|
||||
Элемент включается **только если есть и `name`, и `href`**. Симптом: из 404 позиций
|
||||
отдалось 52 — ровно те, у кого есть ссылка. **Фикс:** генератор подставляет fallback
|
||||
`https://www.kinopoisk.ru/index.php?kp_query=<название>`.
|
||||
|
||||
### Как `href` превращается в `id` (карточка фильма)
|
||||
|
||||
`ListController.cs:314–322`:
|
||||
|
||||
```csharp
|
||||
else if (href.StartsWith("http"))
|
||||
href = Regex.Replace(href, "https?://[^/]+/", ""); // вырезает домен
|
||||
...
|
||||
id = href,
|
||||
```
|
||||
|
||||
Поэтому **полезно отдавать TMDB-URL**: `https://www.themoviedb.org/movie/687163` →
|
||||
`id = "movie/687163"` → `CardController` идёт в TMDB и отдаёт полноценную карточку
|
||||
(постер, жанры, описание, рекомендации). Работает для `movie/N` и `tv/N`.
|
||||
|
||||
`CardController` использует **встроенный TMDB-прокси Lampac**:
|
||||
`http://{localhost}:{port}/tmdb/api/3/{cat}/{id}?api_key={CoreInit.conf.cub.api_key}`
|
||||
— ключ уже внутри образа, отдельный TMDB-ключ для карточек не нужен.
|
||||
|
||||
### 🔴 Питфолл №5: кэш Catalog
|
||||
|
||||
Ключ: `catalog:{plugin}:{search}:{sort}:{cat}:{page}` — **не зависит от содержимого файлов**.
|
||||
`hybridCache` живёт **в памяти процесса** (не в `cache/fdb/`, не сбрасывается рестартом
|
||||
контейнера — проверено). После перегенерации JSON выдача может не меняться, пока
|
||||
ключ тот же. Приём `?nocache={Guid.NewGuid()}` в `format` **не помогает**.
|
||||
|
||||
Практический вывод: при отладке менять сам **URL/ключ** (другой `cat` или `sort`),
|
||||
а не надеяться на инвалидацию кэша.
|
||||
|
||||
### `plugin.js` — клиентская часть
|
||||
|
||||
Лежит **в образе** (`/lampac/module/Catalog/plugin.js`), не на HDD. Что делает:
|
||||
|
||||
- `main()` — итерирует `catalog.main`, собирает секции **СВЕРХУ** родного каталога
|
||||
- `menu()` — итерирует `catalog.menu` из YAML
|
||||
- `full()`/`list()`/`search()` — карточка, категория, поиск
|
||||
- `Lampa.Listener.follow('menu', ...)` + `e.abort()` — **перехват кликов** по родным
|
||||
разделам (`movie`/`tv`/`cartoon`/`anime`/`relise`). Срабатывает **только когда
|
||||
выбран источник «Мой список»**, поэтому родной CUB не трогается при других источниках
|
||||
|
||||
### 🔴 Питфолл №6: источник не виден в UI (`enumerable: false`)
|
||||
|
||||
```js
|
||||
Object.defineProperty(Lampa.Api.sources, key, { get: function(){ return Api; } });
|
||||
```
|
||||
|
||||
Свойство создаётся **не-enumerable** → `Object.keys(Lampa.Api.sources)` его не видит →
|
||||
Lampa не рисует источник в панели выбора. При этом `Api.sources['Мой список']`
|
||||
доступен напрямую и каталог отдаёт. Замер через DevTools ТВ
|
||||
(`http://192.168.1.70:9998/json`) → `{"enumerable":false,"configurable":false}`.
|
||||
|
||||
**Фикс:** добавить `enumerable:true,configurable:true` в **оба** вхождения. Правка
|
||||
применяется скриптом `patch_plugin.py` (не sed/grep), проверка синтаксиса —
|
||||
`node --check` перед заливкой. Монтируется через compose (см. выше).
|
||||
|
||||
### Сканирование `sites/`
|
||||
|
||||
`Path.Combine(AppContext.BaseDirectory, ModInit.modpath, "sites")` — папка сканируется
|
||||
на старте контейнера. Кэш-фикс: `docker restart lampac` (старый подход) **не всегда
|
||||
достаточен** — см. питфолл №5.
|
||||
|
||||
## Управление
|
||||
|
||||
```bash
|
||||
@@ -118,6 +258,35 @@ docker compose down
|
||||
Правка `docker-compose.yml` — без `sudo` (каталог `kraken:kraken`).
|
||||
Создание новых каталогов в корне диска — **через `sudo`** (корень `root:root`). У `kraken` sudo без пароля.
|
||||
|
||||
### Развёртывание каталога watchlist (`deploy_lampac.py`)
|
||||
|
||||
Локальный скрипт `/Users/admin/lampac-tmp/deploy_lampac.py` — 7 шагов с бэкапами
|
||||
и самопроверкой. Запуск:
|
||||
|
||||
```bash
|
||||
python3 /Users/admin/lampac-tmp/deploy_lampac.py # полный цикл
|
||||
python3 /Users/admin/lampac-tmp/deploy_lampac.py --skip-plugin # только JSON+YAML
|
||||
```
|
||||
|
||||
Порядок: бэкап JSON в `wwwroot/.bak/` → заливка 5 JSON → `watchlist.yaml` →
|
||||
патченный `plugin.js` → volume в compose (если нет) → `docker compose up -d` →
|
||||
проверка (`/catalog`, 4 категории с подсчётом постеров и TMDB-ID, факт патча).
|
||||
|
||||
### Генератор каталога (`gen_watchlist_v2.py`)
|
||||
|
||||
`/Users/admin/lampac-tmp/gen_watchlist_v2.py <watchlist.md> <out_dir>`
|
||||
|
||||
Читает `.md`, резолвит каждый фильм: **сначала Radarr** (мгновенно, там уже `tmdbId`
|
||||
и постер), **затем TMDB** по названию (2 языка × movie/tv), **фолбэк** — ссылка Кинопоиска.
|
||||
Кэш `~/.lampac_tmdb_cache.json`, ключ `title|year`.
|
||||
|
||||
Первый прогон: **330 постеров из 403** (Radarr 7, TMDB 327, фолбэк 69).
|
||||
Занимает **8–10 минут** (403 фильма × TMDB, `sleep(0.02)`).
|
||||
🔴 **Запускать в background** — foreground упрётся в лимит 600 с.
|
||||
|
||||
Требует `TMDB_KEY` из `/Users/admin/Developer/watchlist-sync/.env`.
|
||||
⚠️ TMDB-хук `api.themoviedb.org` с Кра́кена доступен (проверено: 401 без ключа = сеть есть).
|
||||
|
||||
## Проверка работоспособности
|
||||
|
||||
```bash
|
||||
|
||||
Reference in new issue
Block a user