190 KiB
created, namespace, related, status, tags, title, type, updated
| created | namespace | related | status | tags | title | type | updated | |||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 2026-09-22 | personal | main-screen-filter-WORKS-2026-09-25-accepted-latency-prefetch-pending |
|
Watchlist в UI Lampa — каталог из списка | plan | 2026-09-25 |
Watchlist в UI Lampa — каталог из списка
🔍 2026-09-25: РАССЛЕДОВАНИЕ — фид виден на всех экранах + «долго грузится»
Жалоба Alex (дословно): «есть проблема с watchlist фидом в лампе: он отображается не только на "главной" но даже когда я выбираю категорию например "документальные" в разделе Фильмы, то есть независимо от активного экрана. и он как будто долго прогружается относительно основного CUB списка, почему он не моментально проявляется, он же статичный».
Статус: ✅ ПРИЁМКА ПРОЙДЕНА 2026-09-25 — фильтр работает. Фид снят со всех экранов
кроме главной. Осталось: предзагрузка __wl_menu (латентность) + 66 с на карточке.
См. «✅ ИТОГ 2026-09-25» в конце раздела.
✅ РЕШЕНИЕ: wlOnMain() — белый список по активному activity
Alex: «В смысле всё кроме? Только главный экран!» — предикат сделан строгим
(=== 'main'), не «всё кроме списка исключений». Любое незнакомое имя activity
обязано по умолчанию секции НЕ получать.
⚠️ ПЕРВАЯ ВЕРСИЯ (ниже) НЕРАБОЧАЯ — оставлена как история ошибки. Она читала
a.activity.url; правильный уровень — a.url (измерено на ТВ, см. «РЕГРЕСС И ВТОРОЙ
ФИКС»). Актуальный текст — wlUrl() + wlOnMain() в разделе регресса.
// ❌ НЕРАБОЧАЯ ПЕРВАЯ ВЕРСИЯ — оставлена как история ошибки, НЕ КОПИРОВАТЬ
function wlOnMain() {
try {
var a = (window.Lampa && Lampa.Activity && Lampa.Activity.active)
? Lampa.Activity.active() : null;
var act = (a && a.activity && a.activity.url) ? a.activity.url : '';
if (act === 'main') return true;
console.log('[watchlist] не главная (' + (act || '?') + ') — секции пропущены');
return false;
} catch (e) {
console.log('[watchlist] wlOnMain error', e);
return false;
}
}
Гард в перехватчике (plugin-cub.js:193):
if (wlOnMain() && Lampa.Arrays.isArray(parts) && !parts.__wl_done) { ... wlInject(parts, menu); }
🔴 Второе, неочевидное изменение: parts.__wl_done = true теперь ставится ТОЛЬКО
внутри ветки фактической вставки. Раньше флаг мог выставиться до готовности
window.__wl_menu — и тогда вставка была бы закрыта НАВСЕГДА, главная осталась бы без
секций после возврата из категории.
Файлы:
/Users/admin/lampac-tmp/plugin-cub.js— рабочая сборка (бэкапplugin-cub.js.bak-before-filter-<ts>)/Users/admin/lampac-tmp/build_plugin.py— тот же текст в константеINJECT(сборщик детерминированный;plugin-cub.jsруками не правим) + 2 новых инварианта:wlOnMain defиwlOnMain guard— сборка падает, если фильтр пропал
Проверки перед заливкой (все пройдены):
python3 build_plugin.py → 10/10 инвариантов, 17241 байт
node --check plugin-cub.js → SYNTAX OK
Деплой (выполнен):
- бэкап боевого —
catalog/plugin.js.bak-before-mainfilter-20260925-220809(17476 Б) scp plugin-cub.js kraken:...6194539b.../lampac/catalog/plugin.js→ 19576 Б, md553ccdc306cf6a14f343eaeb67a8a29c3- bind-mount проверен:
docker exec lampac grep -c wlOnMain /lampac/module/Catalog/plugin.js→ 3 - по HTTP тоже отдаётся:
curl http://127.0.0.1:9118/catalog.js→ 41050 Б,wlOnMain×3
Осталось — перезапуск Lampa на ТВ (webOS кэширует catalog.js, docker restart lampac его НЕ сбрасывает):
scp /Users/admin/lampac-tmp/tv_restart.sh root@192.168.1.70:/tmp/ \
&& ssh root@192.168.1.70 'sh /tmp/tv_restart.sh'
Приёмка = видно в UI (не «API отвечает»): главная — 4 секции Watchlist сверху;
Фильмы → документальные — секций НЕТ. Если фид вылезет ещё где-то — в логе ТВ будет
строка [watchlist] не главная (<имя>), по ней добавить имя в разбор.
Что НЕ трогалось: window.__wl_menu, wlGet/wlAbs/wlHost, unshift,
watchlist.yaml, sync_down.py, nightly-пайплайн.
Причина 1 (диагноз) — в plugin-cub.js НЕ БЫЛО фильтра по экрану
Файл: /Users/admin/lampac-tmp/plugin-cub.js (сборка build_plugin.py;
это тот же файл, что залит в Lampac как module/Catalog/plugin.js).
wlPatchPartNext() — перехват глобального Lampa.Api.partNext — до фикса выглядел так:
Lampa.Api.partNext = function (parts, parts_limit, partLoaded, partEmpty) {
try {
if (Lampa.Arrays.isArray(parts) && !parts.__wl_done) {
var menu = window.__wl_menu;
if (menu && Object.keys(menu).length) {
parts.__wl_done = true;
wlInject(parts, menu); // ← БЕЗУСЛОВНАЯ вставка
}
Ни одного обращения к активному экрану — это не регресс, а исходное отсутствие
проверки. Механика: partNext — глобальный API, через него строят страницу ВСЕ
источники. plugin.js:30 (main()) и plugin.js:36 (category()) оба вызывают
Lampa.Api.partNext(parts_data, parts_limit, ...). Отсюда:
- CUB рисует главную → перехват срабатывает → наши 4 секции сверху;
- вход в категорию («документальные» внутри Фильмов) → Lampa перестраивает страницу
через тот же
partNext→ перехват срабатывает снова → фид снова сверху.
wlInject() кладёт секции через unshift в начало пула, поэтому они рисуются
поверх содержимого любой категории — ровно то, на что жаловался Alex.
Причина 2 — «долго грузится»: замеры (не догадки)
Всё замерено curl на Кра́кене по 127.0.0.1:9118 (порт только изнутри хоста):
| Запрос | Время | size_download |
|---|---|---|
GET /catalog/list?plugin=watchlist&cat=films — повторный |
4 мс | 107 640 |
| тот же — первый (холодный кэш) | 2 250 мс | 107 640 |
GET /watchlist-films.json — чистая статика мимо Catalog |
1 мс | 88 156 |
GET /catalog/list?plugin=watchlist&cat=new |
2–147 мс | 8 512 |
GET /catalog/card?plugin=watchlist&uri=movie%2F424781&type=movie |
66 118 мс | 939 |
🔴 ПИТФОЛЛ ЗАМЕРА: curl -w '%{size_download}' показывает размер СЖАТОГО тела.
Я сначала прочитал 107 640 как «фид всего 105 КБ, грузиться нечему» — это ошибка.
Реальные размеры: watchlist-films.json = 88 156 несжатых при 347 позициях,
cards.json = 548 219 (данные ls -la на Кра́кене). Для замеров объёма брать
ls -la или %{size_download} вместе с Accept-Encoding: identity.
Что из этого следует:
- 4 мс на прогретом кэше — данные отдаются мгновенно, «статичный» — правда.
- 2.25 с на ПЕРВОМ холодном запросе категории — это парсинг JSON парсерами
модуля Catalog (
content.nodes,args) при пустомhybridCache. Дальше — кэш. Отсюда ощущение «не моментально проявляется»: категория, которую давно не открывали, платит 2+ секунды, а родной CUB-список тем временем уже отрисован. - 66 секунд на карточке фильма — отдельный, более серьёзный баг. 66 с на файл
939 байт из локального каталога — это не диск.
card_parse.initUrlисполняется как C#,node: card, читаетсяwwwroot/cards/card-movie-NNN.json. ВнутриCardControllerчто-то ждёт (таймаут внешнего вызова /reactionsGetк cub). Не диагностировано до конца — следующий шаг. - Наши секции стартуют позже родных:
partNextсобирает пул,parts_limit = 6, наши секции идут черезunshiftи грузятся отдельным HTTP-запросом на каждую (/catalog/list?plugin=watchlist&cat=<code>) — 4 запроса. Родной фид CUB приходит одним запросом, поэтому «проявляется» раньше.
Контракт Lampac (проверено, НЕ трогать без причины)
catalog/sites/watchlist.yaml на Кра́кене
(/srv/dev-disk-by-uuid-6194539b-.../lampac/catalog/sites/watchlist.yaml):
plugin: watchlist(🔴 без него ключ =displayname= «Мой список» →ModInit.goInitвырезает кириллицу регуляркой[^a-z0-9\-]→ site="" → HTTP 400 → «ЗДЕСЬ ПУСТО»).host: http://127.0.0.1:9118, категорииНовинки/Films/Мультфильмы/Аниме.- 🔴 Первая категория в списке ИГНОРИРУЕТ
formatи берётlist.uriнапрямую — поэтому первой стоит «Новинки» (list.uri: watchlist-new.json). card_parse.initUrlконвертируетurimovie/424781→cards/card-movie-424781.json,node: card.card_args.tmdb_idобязан быть числом (черезformat: long.TryParse), иначе карточка = «здесь пусто».
Nightly-пайплайн (/home/kraken/scripts/watchlist-nightly.sh, крон 0 1 * * *,
пользователь kraken НА КРА́КЕНЕ): sync-vault pull → process-thumbs --apply →
resolve --recheck --apply → sync-down --apply (сборка фида, LAMPAC_ROOT=UUID-путь)
→ sync-up --apply → backup TimeCode.sql (sqlite3 .backup) → sync-from-lampac --apply
→ sync-vault push. Рестарта lampac в скрипте НЕТ и он не нужен (wwwroot/ bind-mount,
тот же inode) — но при залипшем каталоге в UI помогает обновление страницы / переключение
категории. На 2026-09-25 watchlist-catalog.sh уже убран в .bak-removed-20260924-181942,
отдельной строки крона 30 2 * * * в crontab нет (п.2 из списка незакрытого — закрыт).
🔴 РЕГРЕСС И ВТОРОЙ ФИКС 2026-09-25 (после первой заливки)
Alex после перезапуска Lampa: «Нету теперь списка на главной. Смотри логи.» Секции исчезли ПОЛНОСТЬЮ — первая версия фильтра оказалась нерабочей.
Логи сняты с живой Lampa через CDP (ТВ 192.168.1.70:9998, скрипт
/Users/admin/lampac-tmp/wl_console.py + /tmp/wl_probe*.py; websocket-client,
Runtime.enable + Log.enable):
act = undefined ← фильтр сравнивал с 'main', получал пустое
comp = [object Object]
wl_menu_type = undefined ← window.__wl_menu НЕ создан
catalog_all = NOMANIFEST ← Lampa.Manifest.catalogs вообще пуст
patch = true ← перехватчик стоял, фильтр работал
src = cub
🔴 КОРНЕВАЯ ПРИЧИНА №1 — url лежит НА ВЕРХНЕМ уровне activity, а не во вложенном.
Измерено на живом ТВ (/tmp/wl_probe5.py, вывод дословно):
Lampa.Activity.active() → {url, title, component, source, page, params, activity}
Lampa.Activity.active().activity → {component, object, slide, body, is_stopped, is_started}
↑ url ЗДЕСЬ НЕТ
Первая версия читала active().activity.url → всегда undefined → === 'main'
всегда false → секции пропускались на главной ВСЕГДА. Фильтр не «слишком строгий» —
он смотрел не туда. Проверять форму объекта ЗАМЕРОМ, а не по памяти: в доках
сессии 7 форма не была зафиксирована.
🔴 КОРНЕВАЯ ПРИЧИНА №2 — Manifest.catalogs пуст → loadCatalogs() не получил
/catalog от Lampac → window.__wl_menu не создан → вставлять нечего, даже если
фильтр пропускает. Строгая первая версия заодно отсекала ЕДИНСТВЕННЫЙ вызов wlMenu(),
который дозагружает меню повторным запросом. То есть фильтр не только не пропускал —
он снял путь восстановления.
Фикс (залит 2026-09-25 22:14): новая функция wlUrl(a) читает оба уровня,
верхний первым, вложенный — фолбэк; плюс смягчающая ветка «пустой url + пустой
catalogs = инициализация главной → пропустить»:
function wlUrl(a) {
if (!a) return '';
if (a.url) return a.url; // ← ПРАВИЛЬНЫЙ уровень
if (a.activity && a.activity.url) return a.activity.url; // фолбэк
return '';
}
function wlOnMain() {
var a = (window.Lampa && Lampa.Activity && Lampa.Activity.active)
? Lampa.Activity.active() : null;
var act = wlUrl(a);
if (act === 'main') return true;
if (!act) { // главная ещё инициализируется
var c = (window.Lampa && Lampa.Manifest && Lampa.Manifest.catalogs) || {};
if (!Object.keys(c).length) return true;
}
console.log('[watchlist] не главная (' + (act || '?') + ') — секции пропущены');
return false;
}
Ложная вставка на чужом экране при этом невозможна: без __wl_menu вставлять нечего.
Сборка и деплой (все инварианты пройдены):
python3 build_plugin.py → 12/12, 18281 байт
node --check plugin-cub.js → SYNTAX OK
Новые инварианты сборщика: wlUrl def, wlUrl верхний уровень
(if (a.url) return a.url;) — сборка падает, если чтение уровня откатится назад.
Бэкап: catalog/plugin.js.bak-before-fixurl-<ts>. Залито → 20934 Б,
docker exec lampac grep -n "function wlUrl" -A4 /lampac/module/Catalog/plugin.js → на месте.
⚠️ НА МОМЕНТ ЗАПИСИ ФИКС НЕ ПРИЁМКОВАН. Замер после заливки: url = ""
на ОБОИХ уровнях, hasCats = false, menu = 0. То есть Lampa стоит на главной давно
(URL страницы component=main&source=cub), но Activity.active().url пуст — это НЕ
стадия инициализации. Пока /catalog не отвечает, __wl_menu пуст и секции взять
неоткуда: перезапуск Lampa без починенного /catalog ничего не даст.
Следующий шаг (обязательный, не сделан): проверить боевой запрос Lampa → Lampac
/catalog — отвечает ли он с ТВ, отвечает ли с Кра́кена, не сломан ли он вообще.
✅ ИТОГ 2026-09-25: ПРИЁМКА ПРОЙДЕНА
Alex: «Так. Есть. Но сильно после первых специй.» — секции на главной появились, фильтр работает. Осталась претензия к задержке появления.
Доказательство работы фильтра (замер на живой Lampa после перезапуска)
ready = true
patch = true ← перехватчик установлен
act_url = "movie" ← в момент замера экран = Фильмы (не главная)
menu = Новинки,Фильмы,Мультфильмы,Аниме ← __wl_menu заполнен, всё есть
dom_titles = Сейчас смотрят | Клиника | Мыс страха | Мейр из Исттауна | ...
↑ Watchlist-секций в DOM НЕТ
🔴 Ключевой вывод: на экране movie (Фильмы) наших секций в DOM нет, при том что
menu заполнен и patch активен. Это и есть доказательство, что фильтр режет по экрану,
а не «случайно ничего не нашёл»: вставлять было чем, но wlOnMain() вернул false.
🔴 Почему «сильно после первых специй» — АРХИТЕКТУРНАЯ, не сетевая
Разобрано по коду loadCatalogs() + wlPatchPartNext:
loadCatalogs()идёт по веткеelseи делаетnetwork.silent('/catalog', ...)— только после ответа пишетManifest.catalogsиwindow.__wl_menu. То есть меню приходит асинхронно.- Главная CUB рисуется своим
cub.main(), не дожидаясь этого запроса. Родные секции («Сейчас смотрят», «Рекомендуем посмотреть») стартуют сразу. - Первый вызов
partNextзастаёт__wl_menuпустым → уходит в веткуwlMenu()— асинхронный запрос/catalog. Пока он летит, родные секции уже отрисованы. - Когда
/catalogвернулся,wlInjectделаетunshift, ноpartNextуже отработал срезslice(0, parts_limit)(parts_limit = 6). Наши секции попадают в пул только на следующем раунде, который добираетdata.length >= 3.
Отсюда отставание: наши 4 секции — это отдельный сетевой запрос на каждую
(/catalog/list?plugin=watchlist&cat=<code> = 4 запроса), стартующие позже родных,
плюс попадание в пул не первым раундом. Родной фид CUB приходит одним запросом.
План ускорения (согласован Alex: «Коммить и переходим к предзагрузке»):
подставить menu из /catalog синхронно, ДО первого partNext — в startPlugin(),
параллельно с loadCatalogs(). Тогда к моменту отрисовки главной __wl_menu уже в
памяти, wlInject срабатывает в первом же раунде, секции встают в голову без отставания.
🔴 Коммитить правку НЕКУДА — это выяснено, не пытаться снова
Alex: «Коммить и переходим к предзагрузке». Проверка обеих площадок:
/Users/admin/lampac-tmp → fatal: not a git repository
/srv/.../lampac (на Кра́кене) → fatal: not a git repository
Ни папка сборки, ни папка lampac на Кра́кене под git не лежат. plugin.js — файл,
который монтируется в контейнер (./catalog/plugin.js:/lampac/module/Catalog/plugin.js:ro),
рядом только .bak-копии. То есть версионировать «в лоб» нечего.
Что служит страховкой вместо коммита:
| Артефакт | Путь | Размер |
|---|---|---|
| бэкап «до фильтра» | catalog/plugin.js.bak-before-mainfilter-20260925-220809 |
17476 Б |
| бэкап «до фикса url» | catalog/plugin.js.bak-before-fixurl-<ts> |
19576 Б |
| текущий боевой | catalog/plugin.js |
20934 Б |
| локальные копии | lampac-tmp/plugin-cub.js + build_plugin.py |
20934 / 14619 Б |
Плюс build_plugin.py держит весь INJECT в себе → файл воспроизводится
детерминированно, с 12 проверками инвариантов.
Предложенная развилка (решение за Alex, не сделано): завести в репе
watchlist-sync папку lampac_plugin/ и положить туда build_plugin.py +
plugin-cub.js под git — тогда правка плагина поедет вместе с проектом и попадёт
в историю. Второе правильнее, но это новая сущность в репе → спрашивать один раз.
⚠️ Замер «плагин не загрузился» — это была гонка, а не поломка
Сразу после luna-send-перезапуска проба дала:
ready = false plugin = false patch = false
act_url = ERR: Cannot read property 'url' of undefined
cats = NOMANIFEST
Выглядело как «наш плагин вообще не доехал» (window.plugin_catalog ставит сам
startPlugin()). Фактически это был замер раньше, чем Lampa дошла до главной —
через несколько секунд те же пробы дали ready=true, plugin=true, patch=true.
Также цель CDP на этот период имела title="" и url=about:blank — Lampa грузилась.
🔴 ПРАВИЛО: после перезапуска Lampa на ТВ не верить первому замеру. Либо
опрашивать ready, пока не станет true, либо искать CDP-цель по url-подстроке
com.lampac.tv, а не по title == "Lampac" (в момент загрузки title пустой,
и скрипт молча не находит цель). Alex на паузу без проверки отреагировал резко:
«Чё нахуй за слип 20?!» — не спать, а опрашивать состояние.
Инструменты диагностики ТВ, написанные в этой сессии (переиспользовать)
| Скрипт | Что делает |
|---|---|
/Users/admin/lampac-tmp/wl_console.py |
CDP-клиент: сбор console-логов Lampa за 12 с + снимок состояния (act, patch, menu, pending, src) |
/tmp/wl_probe2..5.py |
точечные пробы структуры Activity.active() (ключи, типы, значения) |
🔴 Питфоллы CDP-проб, найденные на живом ТВ:
JSON.stringify(Lampa.Activity.active())→TypeError: Converting circular structure to JSON. Возвращать поля по одному или собирать строку вручную.- Обращаться к отсутствующему полю в
Runtime.evaluateбез обёртки — CDP отвечает безresult.value, и выглядит это как «все пробы пустые» (первый прогон дал девять<none>подряд). Каждую пробу оборачивать вString(...)/typeof ...и проверятьsubtype == "error". Log.enable+Runtime.enableдают 0 строк лога, если Lampa уже загрузилась — консольные сообщения, напечатанные до подключения, не отдаются. Логи читать СРАЗУ после перезапуска приложения, либо подтверждать состояние пробами, а не логами.
Открытые шаги (для следующей сессии)
- ✅ ПРИЁМКА ПРОЙДЕНА 2026-09-25 — фильтр
wlOnMain()работает, фид виден только на главной. Осталась латентность, см. п.2. - 🔄 ПРЕДЗАГРУЗКА
__wl_menu(согласовано Alex: «Коммить и переходим к предзагрузке»). Корень отставания — асинхронный/catalogвloadCatalogs(): меню приходит ПОСЛЕ первогоpartNext. Решение: подтягиватьmenuсинхронно вstartPlugin(), параллельно сloadCatalogs(), чтобыwlInjectсрабатывал в первом раунде среза. Правка идёт в те же два файла (plugin-cub.js+INJECTвbuild_plugin.py). - Диагностировать 66 с на карточке —
docker logs lampac+ трейсCardController. Запрос:GET /catalog/card?plugin=watchlist&uri=movie%2F424781&type=movie→ 66 118 мс, 939 Б. - Matcher-parity (главный блокер с 2026-09-24, см. ниже) — всё ещё не закрыт.
- Решение по версионированию плагина —
lampac_plugin/в репеwatchlist-syncили оставить бэкапы +build_plugin.py(см. «Коммитить НЕКУДА»).
Правило диагностики этой сессии: не читать %{size_download} как «размер фида»
(это gzip) — брать ls -la или Accept-Encoding: identity. И — ifconfig на Кра́кене
отсутствует: каждая ssh-команда печатает bash: line 1: ifconfig: command not found
безвредным шумом, это НЕ ошибка команды.
🔴 Питфолл инструментов этой сессии: execute_code падал на import
(No module named 'psutil') — расследование и правки сделаны через terminal +
read_file + patch, БЕЗ python-скриптов. scp файла wwwroot/catalog.js не нашёл:
такого пути НЕТ, plugin.js живёт в lampac/catalog/plugin.js и монтируется в
/lampac/module/Catalog/plugin.js. Alex отдельно и дважды требовал результат
расследования (не план, не вопрос): «задача блядь поставлена», «хули ты творишь. ты
блядь не можешь в код проекта посмотреть и в доки и все выяснить?» — сначала читать
код проекта и доки, потом отвечать.
🔴 2026-09-24 (вечер): ПЕРЕПИСЫВАНИЕ — один sync-скрипт вместо отдельного генератора
Требование Alex: «перепиши по нормальному чтобы не отдельный скрипт на лампу был а один sync скрипт и писал сразу lampa feed… обнови синк скрипт чтобы писал фид вместо добавления закачек в arr, а отдельный lampac script удали».
Причина (найдено в этой сессии). gen_watchlist.py (Lampac, 878 строк) — ВТОРОЙ,
независимый матчер TMDB: он не читает ни .resolve_cache.json, ни маркеры ✅/🗓️/❓.
Годами его связывал с резолвером Radarr неявно (матч по названию → те же версии фильмов,
что выбрал resolve). После удаления контента из arr 2026-09-24 мост исчез, и каталог
впервые матчил вслепую → 3 карточки сменили фильм (Олдбой 2003→2013 ремейк,
Unbroken 2003→Несломленный 2014, Shogun 1968→Убийца сёгуна 1980). Лечим не годами
в .md, а слиянием матчеров.
Статус: ✅ РЕАЛИЗОВАНО 2026-09-24 (вечер). Отказ от отдельного feed.py/CLI-шага —
Alex уточнил требование: «это же чистая доработка sync-down?» → sync-down сам пишет фид.
Отдельного шага feed в CLI и в nightly НЕ появилось.
| Что | Как сделано |
|---|---|
Новый watchlist_sync/sync_down.py |
~380 строк вместо 195: resolve → wwwroot/watchlist.json + watchlist-<cat>.json + cards/card-<movie-NNN>.json + cards.json + missing.json; опционально ❓-синк |
| CLI | sync-down [--apply] [--feed-root]; --batch оставлен deprecated (в фид идёт весь список) |
| Пути | --feed-root → env LAMPAC_ROOT → <repo>/../lampac (fallback для Мак-чекаута). Хардкода UUID-путей в коде нет |
arr.py |
🔴 УДАЛЁН (git rm, коммит a524f5d) — история сохранена в гите. Бэкапы НЕ делались (решение Alex: «в истории гита останется») |
| ⬇️ | не ставится больше нигде. Alex: «⬇️ больше ставить не надо, ведь закачки нет». mark_queued в parser.py оставлен для исторических строк |
| Коммит | a524f5d — «sync-down: build Lampa feed instead of adding to arr; drop arr.py» |
🔴 Три открытия при переносе (стоили трёх итераций — не повторять):
-
Фильтры рейтинга/ужасов в фиде НЕ НУЖНЫ. Я притащил в фид правила закачки (
rating >= 6.0, horror-skip) — фид упал до 268 позиций из 392. Это правила Radarr («не качаем то, что не смотрим»), а каталог — не очередь закачки: старый генератор отдавал horror тоже (Вивариум, Субстанция, Ужасающий, Зубастики есть в живом фиде). Вsync_down.pyоставлен явный комментарий с запретом возвращать эти фильтры. -
item.ambiguous(❓) НЕ исключает позицию из фида. ❓ означает «название в.mdнеточное, пригодился бы год», а НЕ «нерезолвлено». Резолвер для таких всё равно отдаёт кандидата, и старый генератор их публиковал (Альфа, Кибердеревня, Орвилл, Ножницы — все с постерами в живом фиде). Из фида исключают только: нетtmdb_idиresult.year_ambiguous(🗓️ — реально два разных фильма). -
Фид должен брать лучшего кандидата, а не отказываться.
resolve()при неоднозначности возвращаетstatus="ambiguous",candidate=None, но топ-3 лежат вall_candidates— среди них верный. Старый генератор брал первый результат TMDB. Если строить фид строго поresult.status == "ok", теряется 54 позиции (девять, Мементо, Babylon, Orville, Матрица-класс названий: Казино, Сияние, Оно, Отступники, Бешеные псы…). ✅ РЕШЕНО 2026-09-24: Alex — «бери лучшего кандидата и пиши вопрос». Фид берётall_candidates[0], а название уходит вmissing→ ❓ в.md(год, добавленный после ❓, пиннит позицию на следующем прогоне). Реализовано вsync_down.py.
🔴 ГЛАВНЫЙ БЛОКЕР, вскрытый переносом (не закрыт): резолвер проекта матчит
хуже старого генератора. Сверка по TMDB-номеру (не по названию — в фиде имена
из TMDB, в .md из строки, сравнение по имени бесполезно):
было в живом фиде, в новом с ДРУГИМ tmdb_id: 86 позиций
примеры: Злая: Часть 2, Разделение, Дракула, Ван Хельсинг, Судья Дредд, Дюна-класс
плюс мусорные матчи по кривым строкам: «[Убийственная вечеринка] (2025)» → фильм 2007
Причина: в старом генераторе 4 ступени резолва — Radarr → TMDB → КП-по-номеру
из ссылки kinopoisk.ru/film/N/ → КП-по-названию. Самая надёжная ступень
(номер из ссылки — машинный ключ, не зависит от переименований) в resolver.py
отсутствует вообще. До переноса это маскировал Radarr-фастпатч; после удаления
контента arr маскировка исчезла и расхождение вылезло.
Варианты (ждёт решения Alex):
- А (рекомендован): перенести в
sync_down/resolverступень «КП-по-номеру из ссылки» — код разобран и лежит в бэкапеgen_watchlist.py(kp_search_by_id+_docs_to_tmdbс_by_id). ~30 мин, проверяется тем же diff по tmdb_id. - Б: выкатить как есть и вычистить 86 расхождений руками — вернутся после прогона.
Замеры dry-run по ходу работы: 268 (с фильтрами) → 332 (без фильтров, ambiguous
исключается) → 397 (all_candidates[0] + ❓; больше 392, потому что старый
генератор терял часть позиций на Radarr-фастпатче и ❓-строках).
На живой фид НЕ выкатывалось: сначала matcher-parity.
Реперные метрики живого фида на 2026-09-24 (для сверки после переноса):
каналы: new 32 / films 346 / cartoons 7 / anime 7 → карточек 392
источники: Radarr 0, TMDB 371, автопоиск по названию 17,
по номеру из ссылки 4, без TMDB 30, постеров 390
🟡 Рабочее правило (Alex): код писать сразу в проекте на Маке
(~/Developer/watchlist-sync/), /tmp — только для чтения чужих файлов и одноразовых
выборок. Мак-репа и Кракен держатся на одном коммите (4266e37 на момент старта).
Границы (что не трогаем): catalog/sites/watchlist.yaml (контракт Lampac:
plugin: watchlist, категории, card_parse/card_args с format-выражениями),
catalog/plugin.js (инжект секций в CUB через Api.partNext), структура
cards/card-movie-NNN.json. Механика missing.json → ❓ осталась, переехала внутрь
sync_down.py (_sync_question_marks, двусторонняя — снимает протухший ❓).
🔴 Незакрытое на конец сессии 2026-09-24 (для следующей сессии):
- Matcher-parity (ГЛАВНОЕ) — см. «ГЛАВНЫЙ БЛОКЕР» выше: перенести ступень «КП-по-номеру из ссылки» в резолв фида (вариант А) либо вычистить 86 расхождений руками (вариант Б). Без этого выкат на живой фид покажет не те фильмы на ~86 позициях.
- Возврат
sync-downвwatchlist-nightly.sh(строка 40 сейчас «ПРОПУЩЕН»; в 02:30 крутится отдельныйwatchlist-catalog.sh) + удалениеwatchlist-catalog.shи строки крона30 2 * * *. - Удаление
lampac/scripts/gen_watchlist.py(878 строк) иmark_missing.py(179). 🔴 НЕ удалять до закрытия п.1: оттуда берётся кодkp_search_by_id/_docs_to_tmdb. - Прогон
--applyна живой фид +docker restart lampac(сброс кэша Catalog)- приёмка в UI Lampa.
- Правка
sync_down.py: ветка «не нашлось» (not_found) обязана добавлять позицию вmissing— за это отвечает_sync_question_marks(двусторонний ❓).
Реализовано 2026-09-22. План был переписан после разведки кода модуля Catalog. Базовое развёртывание — personal/plans/lampac-kraken (шаги 1–6 закрыты).
🔴 АРХИТЕКТУРА ПРИЗНАНА НЕВЕРНОЙ 2026-09-22 — переписывается. Сделано
menu(подмена родных разделов) вместоmain(секции СВЕРХУ). Плюс источник данных — парсинг текстового.mdвместо Radarr API, где уже есть TMDB ID и постеры. Alex: «Я предполагал что будет отображаться то что и раньше (список cub) но В НЕГО будет сверху добавлено несколько секций в которых будут РЕАЛЬНЫЕ ЭЛЕМЕНТЫ - а не сгенерируют пустые заглушки!» См. «🔴 АРХИТЕКТУРНАЯ ОШИБКА» — читать ПЕРВЫМ.Итог одной строкой: серверная часть каталога работает полностью — «Мой список» первым в
/catalog, 4 категории (403 позиции, 348 постеров), карточка по клику отдаёт все поля: 352/352 с числовымtmdb_id. Родной CUB не тронут. Осталась приёмка глазами на ТВ.Обновление 2026-09-22 (четвёртая сессия): 🔴🔴 НАЙДЕНА ИСТИННАЯ ПРИЧИНА «ЗДЕСЬ ПУСТО» — КИРИЛЛИЦА В КЛЮЧЕ КАТАЛОГА. Все предыдущие фиксы (числовой
tmdb_id, права, macOS-tar) были не той причиной.tcpdumpна Кра́кене при клике на ТВ показал реальный запрос:GET /catalog/card?plugin=%D0%9C%D0%BE%D0%B9%20%D1%81%D0%BF%D0%B8%D1%81%D0%BE%D0%BA&uri=... → 400 Bad Request.plugin=Мой список— кириллица.ModInit.goInitстроки 51-52 чистит имя регуляркой[^a-z0-9\-], то есть вырезает всю кириллицу целиком →""→File.Exists("sites/.yaml")= false → fallbackFindSiteByDisplayName("")тоже мимо (сравнивает уже с пустой строкой) →null→BadRequest("init not found")→ 400 → «Здесь пусто». ФИКС: явныйplugin: watchlistвwatchlist.yaml(переопределяет ключ поApiController.cs:136);displayname: Мой списокостаётся надписью в UI. Ключ каталога сталwatchlist. Проверено:plugin=Мой список→ 400,plugin=watchlist→ 200 с полными данными. 🔴 Урок диагностики: проверять карточку curl'ом надо ТЕМ ЖЕ ключом, который шлёт ТВ (я подставлял латиницу руками → 200 → ложный вывод «проблема в клиенте»). Детали — personal/tech/lampac-deployment раздел «🔴🔴 ГЛАВНАЯ ПРИЧИНА».⚠️ ЗАДАЧА СЕССИИ (требование Alex): ✅ ВЫПОЛНЕНО — СЕКЦИИ ВНЕДРЯЮТСЯ В CUB-ФИД. Дословно требование: «надо блядь добавить в CUB список новые секции а не ручное блядь переключение!». Реализовано в
plugin-cub.jsчерез обёрткуLampa.Api.sources.cub.main: наши секции добавляются в массивparts_dataCUB-фида (не подменяют раздел), карточкам проставляетсяsource: 'watchlist'. Вызов — по событиюapp:ready. Залито, отдаётся в сборке/catalog.js(35566 Б). Ожидает приёмки в UI. Механика — personal/tech/lampac-deployment раздел «Внедрение секций Watchlist в CUB-фид».Обновление 2026-09-22 (ВОСЬМАЯ СЕССИЯ): ✅✅ ЭТАП «СЕКЦИИ ВНУТРИ CUB» ЗАКРЫТ — ФИД СМЕРЖИЛСЯ. Alex: «Отлично, фид смержился. Закрываем этот этап в плане.»
Корень, который держал этап (нашёл только по коду
app.min.js@1292588):function partNext(parts, parts_limit, partLoaded, partEmpty) { var pieces = parts.filter(function (p) { return typeof p == 'function'; }).slice(0, 0 + parts_limit); // ← limit = 6, ЖЁСТКИЙ СРЕЗ ГОЛОВЫ
cub.main$1кладёт вparts_data28 родных секций и вызываетpartNext(parts, 6, …). Наш инжект делалparts.push()→ секции уходили в хвост (позиции 29–32) →slice(0, 6)их отрезал. Трасса пула на живом ТВ:before: 28 → after: 32, в DOM — ни одной нашей секции.ФИКС:
parts.unshift()вместоparts.push()— в голову пула, в срез, рисуются СВЕРХУ (ровно требование Alex). Порядок меню обходится в обратном порядке, потому что каждыйunshiftсдвигает предыдущие вправо.Ложный след, потративший время: тест
g: {wlSection: "undefined", …}читался как «функции недоступны из замыкания». На самом деле инжект внутри замыкания плагина иwlSectionвиден перехватчику — проба смотрела снаружи. Доказательство: прямой вызовpartNext(empty,…)далlen: 4. Проверять пул ТРАССОЙ (before/after), а не наличием функций наwindow.Приёмка (серверная часть): 18/18.
/catalog.js33550 Б сunshift;/catalog→ ключwatchlist, menu 4 категории; список: new 33/30 постеров, films 360/308/312 ID, cartoons 3/3, anime 7/7; карточкаtmdb_id: 424781числом; регресс-тестplugin=Мой список→ 400 (кириллица по-прежнему не проходит).Артефакты:
build_plugin.py(+инвариантыwlInject def,unshift (не push),push в INJECT (должно 0)),verify_unshift.py(проверка фикса в рантайме БЕЗ деплоя),full_check.py(полный цикл сервер+ТВ),tv_accept.py,diag_trace.py,diag_calls.py. Бэкапы:plugin.js.bak-20260922-165613(последний рабочий),plugin-cub.js.bak-*.🔴 Питфолл рантайм-проверки: повторная загрузка
/catalog.jsскрипт-тегом НЕ перезапускаетstartPlugin(флагwindow.plugin_catalogуже стоит) →patched: false. Для быстрой итерации без деплоя оборачиватьLampa.Api.partNextвручную сunshift(verify_unshift.py).location.reload()на webOS догружает новыйcatalog.jsдольше 30 с — ждать до 120 с.
Обновление 2026-09-22 (седьмая сессия): 🔴🔴 ПРИЧИНА «В CUB НИХУЯ НЕ ПОМЕНЯЛОСЬ» —
ManifestНЕДОСТУПЕН В ОБЁРТКЕ. Alex: «Ты вообще нихуя не поменял!!». Проверка через CDP-пробу на живом ТВ подтвердила его правоту: обёрткаApi.sources.cub.mainстояла (__wl_injected: true), но вызовcub.main({})возвращал 4 родные секции и ни одной нашей. Корень: первая версия инжекта читалаManifest.catalogs['watchlist']напрямую, аManifestобъявлен локально в замыкании плагина →ReferenceError: Manifest is not defined→catch→ молча отдавался родной фид. Ошибки нет ни в UI, ни в логах. Фикс:window.__wl_menu(заполняется вloadCatalogs) +wlEnsureMenu()с фолбэком на запрос/catalog. Переписан сборщик:build_plugin.py(детерминированный, с инвариантамиINJECT=1/def=1/startPlugin=1,node --check+ баланс скобок), вместо неидемпотентногоpatch_cub_inject.py(он вставлял INJECT дважды и ломал скобки). Залито (сборка 35440 Б), ждёт перезапуска Lampa на ТВ —docker restart lampacНЕ перезагружает плагин в памяти ТВ. Механика + CDP-пробы — personal/tech/lampac-deployment раздел «🔴🔴 ШЕСТАЯ СЕССИЯ: ИСТИННАЯ ПРИЧИНА…».
Обновление 2026-09-22 (пятая сессия): ✅✅ ПРИЁМКА ALEX ПРОЙДЕНА — ФИЛЬМЫ ОТКРЫВАЮТСЯ И ПРОИГРЫВАЮТСЯ. Дословно: «фильмы из списка Watchlist теперь открываются и проигрываются». Закрыт последний блокер — кириллический ключ плагина (
plugin: watchlist). Плюс устранён питфоллStaticache(кэш собранного/catalog.jsпереживал заливку и выглядел как «патч не применяется» →docker restart lampac).Осталось (2026-09-22, конец СЕДЬМОЙ сессии): 0. ✅ ЗАКРЫТО 2026-09-22 (восьмая сессия):
plugin-cub.jsзалит на Кра́кен, фиксunshiftвместоpush— фид смержился, секции Watchlist рисуются внутри главной CUB рядом с родными. См. блок «ВОСЬМАЯ СЕССИЯ» выше.
- 5 фильмов с Кинопоиск-ID (
film/NNNNN/) — «Рассекая волны: экспедиция „Лас Бальсас“», «Хамнет», «Эхопраксия», «Цена вершины», «Астрал. Могильный ритуал 2», «О добре и зле», WeWork (2024). TMDB не нашёл (2025 год) → нет карточки и постера → клик даёт «здесь пусто». Нужен выбор Alex: заглушка с кнопкой поиска торрента либо дорисоватьtmdb_id.- ~51 название без постера — проблема ЗАПИСИ В СПИСКЕ, не скрипта:
Плохие парни 1/2(ДВА фильма одной строкой),Солярис Тарковский(уточнение режиссёра),Самолетом, поездом, машиной (1987)(русский аналог). Генератор такое угадать не может — правится только список руками.Обновление 2026-09-22 (четвёртая сессия, ранее): ✅ ЧИСЛОВОЙ
tmdb_idВ КАРТОЧКАХ. 352 из 352 карточек отдаютtmdb_idчислом,source,runtime,vote_average, жанры. Механика:ModInit.cs:285 nodeValueвозвращает любойJValueСТРОКОЙ → вcard_argsнуженformat: "long.TryParse(value, out long v) ? v : 0"(format= CSharpEval,CatalogNodeValue(value, host)). Кэшcatalog:card:{plugin}:{uri}живёт в ПАМЯТИ и переживаетdocker restart— после правки данных обязателен рестарт И проверка на НЕзакэшированном uri. Детали — personal/tech/lampac-deployment.Обновление 2026-09-22 (третья сессия): ✅ КАРТОЧКА ПОЧИНЕНА ОКОНЧАТЕЛЬНО. «Здесь пусто» давало отсутствие числового
tmdb_id:plugin.jsделаетif(json.tmdb_id), аModInit.cs:285возвращает любоеJValueСТРОКОЙ. Фикс —format: "long.TryParse(...)"вcard_args. Плюс кэшcatalog:card:*в ПАМЯТИ переживалdocker restartи отдавал старое. См. «🔴 ФИНАЛЬНЫЙ ФИКС».Обновление 2026-09-22 (вечер): Alex лично нашёл источник в UI («остальное → основной источник → Мой список»), увидел результат и подтвердил диагноз: секции подменяют CUB, нет превью, элементы — сырые строки файла. Затем сам сформулировал целевую схему (Radarr = источник метаданных без автоскачки, просмотр через TorrServer, файлы на HDD не пишутся) → записано в «ПЕРЕДЕЛКА 2». План работ на апруве, ничего не выполнено.
Обновление 2026-09-22 (поздний вечер): ✅ ШАГИ 0–4 ПЕРЕДЕЛКИ 2 ВЫПОЛНЕНЫ. Автоскачка на HDD выключена фактически: Radarr (45→0 monitored, очередь 6→0, RSS 30→0) и Sonarr (13→0, 19→0, 15→0). Метаданные целы — 50 фильмов, 46 с постерами, 24 файла на диске не тронуты. Проверено: Transmission не сдвинулся ни на байт за 45 с, логи Radarr пусты. Бэкапы
radarr.db/sonarr.db/config.xml— метка20260922-123404. См. «✅ Порядок работ» и «🔴 Питфоллы выполнения».Обновление 2026-09-22 (ночь): 🔴 ПОПЫТКА УБРАТЬ
menuПРОВАЛЕНА — ГИПОТЕЗА БЫЛА НЕВЕРНА. Попытка «оставитьmain, убратьmenu» сломала канал: «Мой список» исчез из/catalog,catalog/list?plugin=watchlistотдавал пустоту. Три раунда правок YAML, три рестарта. Установлено тестом:
menuОБЯЗАТЕЛЕН — он регистрирует канал, а не подменяет разделы (ApiController.cs:70: безmenuсайт отбрасывается). Убрать нельзя.- блок
mainв YAML НЕ СУЩЕСТВУЕТ — любые формы (список/словарь) ломают парсинг,goInit→ null, сайт молча пропадает. Errors в логах нет.mainCatalog генерирует САМ изmenu— в JSON-ответе/catalogесть оба ключа с одинаковым содержимым.- Подмену разделов делает
plugin.js, не YAML (Listener.follow('menu')+e.abort()), срабатывает только при выбранном источнике «Мой список».host+uriСКЛЕИВАЮТСЯ (ListController.cs:53) →uriобязан быть относительным. Также найден питфолл: явныйformatвmenuперезагружает страницу при выборе раздела.Текущее состояние: каталог рабочий (waтchlist.yaml откачен к рабочей форме, «Мой список» на месте, 4 категории 360/33/3/7). Правки 5–8 (постеры через
resolver.py,plugin.js,arr.py) не начаты — ждут решения Alex по вопросу «CUB и Мой список одновременно». См. «🔴 ПИТФОЛЛ YAML:mainЗАПРЕЩЁН».Аудит «как это реализуемо» — ✅ ЗАВЕРШЁН 2026-09-22. Проверено всё: TorrServer на диск не пишет (
UseDisk: false, RAM-кэш 96 МБ) — главный вопрос закрыт; поиск торрентов работает через Jackett-API самого Lampac (отдельный Jackett не нужен); найдены оба виновника автоскачки — Radarr (45 monitored, очередь 6) и Sonarr (13 monitored, очередь 19). См. «✅ ПРОВЕРКА "как это реализуемо"». ⚠️ Порядок работ теперь включает Sonarr — раньше он в плане не учитывался.Обновление 2026-09-22 (ночь, вторая сессия): 🎯 НАЙДЕНА НАСТОЯЩАЯ ПРИЧИНА «пустого каталога» + артефакты готовы.
Фильмы(первая категория вmenu.categories) возвращала 360 элементов изwatchlist.jsonвместоwatchlist-films.json— то есть игнорировала блокformat. Отсюда «здесь пусто»: элементы шли сid= ссылка на поиск Кинопоиска иimg: null. Фикс: первая категория вmenu.categories— это «Новинки», не «Фильмы». Все 4 категории проверены: films/new/cartoons/anime → правильные файлы. Написаны:gen_watchlist_v2.py(постеры через TMDB + Radarr; первый прогон 330 постеров из 403),patch_plugin.py(+enumerable:true,node --checkчист),deploy_lampac.py(заливка + проверка одной командой), обновлённыйdocker-compose.yml(volume наplugin.js). Правкаarr.pyприменена и закоммичена9872016. См. «🟢 РЕШЕНИЕ: первая категория игнорирует format» — читать ПЕРВЫМ.Обновление 2026-09-22 (ночь, третья сессия): ✅ ДЕПЛОЙ ПРОШЁЛ, ПОСТЕРЫ И ПОИСК В UI ЕСТЬ. КАРТОЧКА В РАБОТЕ. Всё залито на Кра́кен и проверено на живом сервере:
- «Мой список» первый в
/catalog,main=[Новинки, Фильмы, Мультфильмы, Аниме];- films 360 / постеров 308, new 33/30, cartoons 3/3, anime 7/7;
idэлементов =movie/424781— рабочие TMDB-карточки (не поиск Кинопоиска);- патч
plugin.jsприменён (enumerable:true, файл 10738 Б), volume смонтирован — провереноdocker inspect, переживёт обновление образа;arr.pyфикс в бою, Radarr/Sonarr monitored 0, RSS 0, очередь 0.🔴 ОСТАЛОСЬ ОДНО: карточка по клику. Была HTTP 500 → сейчас HTTP 400
parse.node. Корень найден по коду:CardController.Index(plugin, uri, type)— параметраcatНЕТ (поэтому{cat}вinitUrlпустой →cards-.json→ 404 → 500), аparse.node— статичный JSONPath (json.SelectToken(parse.node), строка 95), подстановок не делает, и/в пути = вложенность.Решение дописано, но НЕ залито: файл на каждый фильм
wwwroot/cards/card-movie-NNN.jsonс обёрткой{"card": {...}},initUrlсобирает путь C#-выражением$"{host}/cards/card-{uri.Replace("/", "-")}.json",node: card.deploy_lampac.pyпока НЕ умеет лить каталогcards/— доклеитьscp -r. См. «🔴 КАРТОЧКА: механика card_parse».
Обновление 2026-09-22 (ночь, четвёртая сессия): ✅ КАРТОЧКА ПОЧИНЕНА. 339/339 HTTP 200. СЕРВЕРНАЯ ЧАСТЬ ЗАКРЫТА. Схема «файл на фильм» залита и проверена на живом сервере:
wwwroot/cards/card-movie-NNN.json(345 файлов), обёртка{"card": {...}}, владелец1000:1000, режим644;card_parse.initUrl= C#return $"{host}/cards/card-{uri.Replace("/", "-")}.json";,node: card(статичный — потому и нужен файл-на-фильм);- все 339 карточек с TMDB-ID отдают HTTP 200, имена читаются («Я буду жить», «На цепи», «Простите за беспокойство»);
deploy_lampac.pyдополнен шагом 2b (заливка каталогаcards/через tar) и проверкой карточек в шаге 7.Итог каталога: Новинки 33/30, Фильмы 360/308, Мультфильмы 3/3, Аниме 7/7 — 403 позиции, 348 постеров, 352 рабочих карточки, постеров-подложек нет.
🔴 Три причины поломки, все устранены (детали — «🔴 ПИТФОЛЛЫ, найденные по ходу»):
- права: tar распаковывал от root →
root:root 600→ dotnetPermission denied→ статика молча 404 (выглядит как «файла нет»);- macOS AppleDouble: BSD
tarклал._*.jsonвwwwroot→ нужен--no-mac-metadata;- 🔴 ложный диагноз — потерянный час: карточку проверял по
/card, где всегда 404. Правильный путь —/catalog/card?plugin=watchlist&uri=movie/NNN.ОСТАЛОСЬ: приёмка глазами на ТВ (
192.168.1.70): секции сверху с постерами, клик → карточка с описанием → воспроизведение через TorrServer. Приёмка = результат виден в UI, не «API отвечает».Не тронуто, ждёт решения Alex: 310 фильмов из списка, которых нет в Radarr (51 без постера, идут через фолбэк Кинопоиска). Без апрува не делать.
🟢 ВОСЬМАЯ СЕССИЯ 2026-09-25: ФИЛЬТР «СЕКЦИИ ТОЛЬКО НА ГЛАВНОЙ» (PLAN)
Задача Alex: «watchlist фид отображается не только на главной, но даже когда я выбираю категорию например "документальные" в разделе Фильмы» + «он как будто долго прогружается относительно основного CUB списка». Требование: ТОЛЬКО ГЛАВНЫЙ ЭКРАН (белый список, не чёрный — Alex: «В смысле всё кроме? Только главный экран!»).
Диагноз (по коду, замеры)
-
Причина «на любом экране».
plugin-cub.js:164—wlPatchPartNextвставляет секции БЕЗУСЛОВНО:if (Lampa.Arrays.isArray(parts) && !parts.__wl_done) { var menu = window.__wl_menu; if (menu && Object.keys(menu).length) { parts.__wl_done = true; wlInject(parts, menu); }Lampa.Api.partNext— глобальный: через него строят страницу Иmain()(plugin.js:30), Иcategory()(plugin.js:36). Никакой проверки активного экрана нет →unshiftкладёт секции в голову пула на КАЖДОМ экране. Фильтра по activity в коде НЕ БЫЛО — это не регресс, а исходное отсутствие. -
Замеры скорости (Kraken,
127.0.0.1:9118):catalog/list?cat=filmsпрогретый кэш — 4 мс; первый холодный — 2 250 мс (hybridCacheмодуля Catalog парсит JSON целиком при первом запросе)watchlist-films.json(чистая статика) — 1 мс- 🔴
catalog/card?uri=movie/424781— 66 118 мс (66 сек!) на 939 байт. Это НЕ диск и не фид:card_parse.initUrlисполняется как C# и ждёт внутри CardController. Отдельный баг, в эту правку не входит. - ⚠️
curl -w %{size_download}показывает размер после распаковки gzip — на этом легко обмануться:watchlist-films.json= 88 КБ несжатых при 347 позициях, аcards.json= 548 КБ. Реальный объём фида в разы больше «видимого».
Решение
Белый список по активному activity Lampa:
function wlOnMain() {
try {
var a = Lampa.Activity && Lampa.Activity.active && Lampa.Activity.active();
var act = (a && a.activity && a.activity.url) ? a.activity.url : '';
return act === 'main'; // ТОЛЬКО главная
} catch (e) { return false; }
}
Плюс parts.__wl_done = true ставить ТОЛЬКО при фактической вставке (иначе после
возврата из категории на главную секции не доберутся). console.log при пропуске —
для отладки на живом ТВ.
Шаги
- правка
plugin-cub.js+build_plugin.py(INJECT + новый инвариантwlOnMain) python3 build_plugin.py && node --check plugin-cub.js- бэкап боевого
catalog/plugin.js→scpнового - перезапуск Lampa на ТВ обязателен (
tv_restart.shчерезluna-send;docker restart lampacкэш webOS не сбрасывает) - 🔴 приёмка = видно в UI: главная — 4 секции сверху; Фильмы → документальные —
секций НЕТ. CDP-проба читает
Lampa.Activity.active()на обоих экранах.
✅ ИТОГ 2026-09-25: фильтр РАБОТАЕТ, закоммичено
- Коммит
5e55a47в репозитории~/Developer/lampac-scripts(Giteagit_admin/lampac-scripts), запушен вorigin/main. Туда переехалиscripts/build_plugin.py+scripts/plugin-cub.js— изlampac-tmp, который вне git.lampac-tmpбольше не источник истины. - Приёмка на живом ТВ (CDP):
act_url = "movie"(экран Фильмов) → секций Watchlist в DOM НЕТ. Именно то, что просил Alex. - Две правки по факту замеров, обе стоили итерации:
urlчитается с ВЕРХНЕГО уровня —active().url, НЕactive().activity.url(там его нет вообще:{component, object, slide, body, is_stopped, is_started}). Первая версия читала.activity.url→ всегдаundefined→ секции исчезли → «Нету».- Пустой
url+ пустойManifest.catalogs= инициализация → пропускаем. Иначе строгая проверка отрезала единственный вызовwlMenu(), добирающий меню после сбоя/catalog.
- 🔴 Питфолл замеров: CDP-проба сразу после рестарта Lampa даёт
ready=false,plugin=false,patch=false,act_url=ERR— это НЕ поломка, плагин ещё грузится. Мерить надо через ~30 с, иначе легко уйти в ложную диагностику «плагин не подъехал». - 🔴 Задержка секций — архитектурная, в работу ещё НЕ внесена. Замер:
menuзаполнен, аManifest.catalogs = NOMANIFEST. Цепочка: главная CUB рисуется своимcub.main(), родные секции стартуют сразу; первыйpartNextвидит пустой__wl_menu→ уходит в асинхронныйwlMenu()→ пока летит запрос, родные секции отрисованы,slice(0, parts_limit)уже отработал → наши 4 секции попадают в пул только в следующем раунде. План: тянуть/catalogвstartPlugin()параллельно сloadCatalogs(), чтобы__wl_menuлежал в памяти к первой отрисовке.
Питфоллы, найденные при расследовании (не повторять)
ssh krakenна каждом вызове печатаетbash: line 1: ifconfig: command not found— это безвредный шум из~/.bashrc, НЕ ошибка команды.- 🔴
execute_codeв этой сессии падает наimport(No module named 'psutil') — работаем черезterminal+read_file+jq, БЕЗ python. - Обрыв
scpиз-за не найденного пути:wwwroot/catalog.jsНЕ существует (в wwwroot лежат только JSON фида) —plugin.jsживёт вlampac/catalog/plugin.jsи монтируется в/lampac/module/Catalog/plugin.js.
🔴🔴 СЕДЬМАЯ СЕССИЯ: РАБОЧИЙ МЕХАНИЗМ НАЙДЕН — ПЕРЕХВАТ Lampa.Api.partNext
Статус: ✅ секции Watchlist рендерятся ВНУТРИ главной CUB. Проверено на живом ТВ через CDP.
Alex: «В cub секции сейчас смотрят, рекомендуем, последнее добавленное, огонь, в высоком качестве. Все.» и «до сих пор хамнет, рассекая волны, эхопраксия, WeWork итд — без постера и "здесь пусто"» + «Ты вообще нихуя не поменял!!».
Итог проверки на ТВ (CDP, probe_ui.py)
ЛЕВО (1) → главная CUB, activity: "main"
titles: ["Простите за беспокойство", "Смерть единорога",
"Рассекая волны: экспедиция «Лас Бальсас»", "Я ругаюсь", "F1",
"Мастерская Сатаны", "Хамнет: История, вдохновившая «Гамлета»", ...,
"Эхопраксия", "WeWork создание и крах компании", ...]
То есть наши фильмы физически в DOM главной CUB — те самые, что Alex называл.
Три бага подряд — почему «ничего не поменялось»
❌ Баг 1 (главный): обёртка Api.sources.cub.main не работает В ПРИНЦИПЕ.
Родной main$1() (в app.min.js, @1257163) внутри замыкания Lampa зависит от
локальных permit, get$7, Lang:
function main$1() {
var parts_limit = 6;
var parts_data = [
function (call) { get$7('?sort=now_playing', params, ...); }, // ← get$7 локальная
function (call) { if (permit.child_small) return call(); ... } // ← permit локальная
];
...
}
При вызове извне замыкания (cub.main({})) он падает на permit.child_small
(permit → undefined в глобальном контексте) и никогда не зовёт callback →
вечный pending. Проверено CDP-пробой: {"pending":true} через 15 секунд, ни одного fetch.
Обёртка мертва by design — не потому что код плохой, а потому что метод невызываемый извне.
❌ Баг 2: Manifest / Utils / network недоступны вне замыкания плагина.
Замер через CDP (probe_globals.py):
{"Manifest_global":"undefined","Lampa":"object",
"Utils":"undefined","network":"undefined"}
Все три — локальные переменные плагина. Первая версия инжекта читала
Manifest.catalogs['watchlist'] напрямую → ReferenceError → catch → молча
отдавался родной фид. Ошибки нет ни в UI, ни в логах.
❌ Баг 3: Utils.account() без Manifest.api_host подставляет {localhost}.
Lampa.Utils.account(menu[name]) // menu[name] = "/catalog/list?plugin=watchlist&cat=new"
→ {localhost}/catalog?uid=...&token={token} → в логе ТВ:
Request GET time: 4ms error of {localhost}/catalog?uid=...&token={token}
: Нет подключения к сети
Utils.account требует Manifest.api_host в области видимости. Вне замыкания его нет.
✅ РЕШЕНИЕ: перехват Lampa.Api.partNext
partNext — глобальный API (Lampa.Api.partNext), которым сам CUB собирает главную:
function partNext(parts, parts_limit, partLoaded, partEmpty) {
var pieces = parts.filter(function (p) { return typeof p == 'function'; })
.slice(0, 0 + parts_limit);
...Progress().append(pieces).start(...)
}
CUB передаёт туда parts_data — массив функций-секций. Перехватив partNext,
наши секции добавляются в тот же пул, что и родные: работают на любой странице,
независимо от источника, без permit и без params.
function wlPatchPartNext() {
var orig = Lampa.Api.partNext;
Lampa.Api.partNext = function (parts, parts_limit, partLoaded, partEmpty) {
if (Lampa.Arrays.isArray(parts) && !parts.__wl_done) {
var menu = window.__wl_menu;
if (menu && Object.keys(menu).length) {
parts.__wl_done = true; // идемпотентность
Object.keys(menu).forEach(function (name) {
parts.push(wlSection(name, menu[name]));
});
}
}
return orig.apply(this, arguments);
};
}
Плюс обязательные спутники (глобальные только!):
wlHost()—Lampa.Manifest.api_host, фолбэк хардкод192.168.1.14:9118wlAbs(url)— склейка host + относительный путь вручную,Utils.accountНЕ использоватьwlGet(url, ok, fail)—new Lampa.Reguest().silent(absoluteURL, ...), фолбэкfetchwindow.__wl_menu— заполняется вloadCatalogsпри загрузке/catalog; фолбэк — сетевой запрос
Сборщик build_plugin.py (детерминированный, заменяет patch_cub_inject.py)
cd /Users/admin/lampac-tmp
python3 build_plugin.py # plugin-patched.js + INJECT → plugin-cub.js
node --check plugin-cub.js # обязательная проверка
Инварианты на выходе (скрипт сам их проверяет и падает при нарушении):
INJECT=1, def wlPatchPartNext=1, startPlugin=1, network.silent в INJECT=0.
🔴 Почему не patch_cub_inject.py: он был неидемпотентен — при повторном запуске
вставлял INJECT дважды и ломал баланс скобок (SyntaxError: Unexpected token '}'),
потому что проверки шли по уже модифицированной строке. build_plugin.py режет файл
детерминированно: [до startPlugin] + INJECT + [новый startPlugin] + '})();'.
🔴 CDP-пробы на живой ТВ (новый инструментарий)
ТВ LG отдаёт Chrome DevTools Protocol на порту 9998:
curl -s "http://192.168.1.70:9998/json/list" | jq -r '.[] | select(.title=="Lampac") | .webSocketDebuggerUrl'
# ws://192.168.1.70:9998/devtools/page/<ID>
Клиент на Mac — websocket-client (модуль websockets НЕ установлен):
import websocket
ws = websocket.create_connection(WS_URL, timeout=25)
ws.send(json.dumps({"id":1,"method":"Runtime.evaluate",
"params":{"expression":EXPR,"returnByValue":True,"awaitPromise":True}}))
Скрипты в /Users/admin/lampac-tmp/:
| Скрипт | Что проверяет |
|---|---|
probe_tv.py |
базовое состояние: source, hasCub, cubKeys, wlInjected, sourcesList |
probe_globals.py |
доступность Manifest/Utils/network в глобальном контексте |
probe_cache.py |
откуда ТВ берёт catalog.js (script-теги, кэш) |
probe_cub_main.py |
что отдаёт обёрнутый cub.main (сколько секций) |
probe_inject.py |
инжект нового plugin-cub.js в рантайм БЕЗ деплоя (быстрая итерация) |
probe_wait.py |
ждёт завершения вызова + перехватывает console.log |
probe_ui.py |
финальная приёмка: переход на главную + чтение заголовков из DOM |
restart_lampa.sh / tv_restart.sh |
перезапуск Lampa на ТВ через luna-send |
🔴 Питфолл luna-send через ssh: аргументы JSON не проходят через
ssh 'script -q -c "luna-send ... -a {\"id\":...}"' — luna-send печатает help
(«send specified appId in message (default is none)»). Фикс: положить скрипт на ТВ
файлом (scp tv_restart.sh) и вызвать sh /tmp/tv_restart.sh:
#!/bin/sh
luna-send -n 1 -f luna://com.webos.applicationManager/close '{"id":"com.lampac.tv"}'
sleep 5
luna-send -n 1 -f luna://com.webos.applicationManager/launch '{"id":"com.lampac.tv"}'
🔴 eval() не делает функции глобальными. Инжект eval(code) внутри IIFE плагина
оставляет wlHost/wlAbs в замыкании → снаружи (wlHost()) ReferenceError.
Это нормально: cub.main их видит, внешние пробы — нет. Проверять только через
вызов Lampa.Api.main() или чтение DOM.
Перезапуск Lampa на ТВ — обязателен
🔴 docker restart lampac НЕ перезагружает плагин в памяти ТВ. catalog.js
закэширован браузером webOS. Признак «ТВ видит старую версию»:
String(Lampa.Api.sources.cub.main) содержит старый код.
Порядок: залить plugin.js на Кра́кен → docker restart lampac (сброс Staticache) →
перезапустить Lampa на ТВ (tv_restart.sh) → probe_ui.py.
🔴 КАРТОЧКА: механика card_parse (разобрано по коду 2026-09-22)
Клик по элементу каталога ведёт в /catalog/card?plugin=watchlist&uri=<id>&type=movie.
Без card_parse Catalog строит URL как $"{init.host}/{uri}" → http://127.0.0.1:9118/movie/424781
— такого пути нет, HTTP 500, в UI «здесь пусто».
Что даёт card_parse (по CardController.cs):
| Поле | Механика | Подстановки |
|---|---|---|
initUrl |
исполняется C#-выражением через CSharpEval, объект CatalogInitUrlCard(host, args, uri, query, type) |
✅ есть uri, type, host |
node |
json.SelectToken(parse.node) — чистый JSONPath |
❌ нет, статичен |
name / image / description |
ModInit.nodeValue(...) → attribute |
— |
Сигнатура роута (критично):
[Route("catalog/card")]
public async Task<ActionResult> Index(string plugin, string uri, string type)
🔴 Параметра cat НЕТ. Поэтому initUrl: $"…/cards-{cat}.json" даёт cards-.json → 404
→ html == null → BadRequest("html") → HTTP 500. Карточка не знает категорию — она
получает только uri и type.
Питфолл JSONPath: ключ movie/424781 в SelectToken трактуется как вложенность
(movie → 424781) → узел не найден → HTTP 400 parse.node. Слэш в JSONPath = разделитель.
Итоговое решение (в YAML уже записано, деплой не доделан):
card_parse:
initUrl: |
return $"{host}/cards/card-{uri.Replace("/", "-")}.json";
node: card
name: { attribute: name }
original_name: { attribute: original }
year: { attribute: year }
image: { attribute: poster }
description: { attribute: overview }
Файлы wwwroot/cards/card-<media>-<id>.json (345 шт., ~1 КБ) с обёрткой {"card": {...}}
и фиксированным путём $.card — потому что node статичен и один общий файл по uri
не отфильтровать.
Диагностика карточки (воспроизводимо):
curl -s -o /tmp/c.json -w "HTTP %{http_code}\n" \
"http://192.168.1.14:9118/catalog/card?plugin=watchlist&uri=movie%2F424781&type=movie"
# 500 (нет card_parse) / 400 parse.node (node не нашёл узел) / 200 + JObject (ок)
⚠️ Ошибки карточки в логи не пишутся (docker logs lampac пуст) — диагностировать только
по HTTP-коду и телу ответа (parse.node, html, json).
🔴 ПИТФОЛЛ ДЕПЛОЯ: sudo через ssh без tty
ssh kraken "sudo docker cp …" падает с sudo: a terminal is required to read the password.
-t обязателен, но при этом нельзя передавать a && b — цепочка рвёт tty-сессию
(«Connection to kraken closed»). Каждую команду вызывать отдельно:
full = f"ssh -t {KRAKEN} {cmd}" # одна команда, без &&
⚠️ docker compose up -d НЕ пересоздаёт контейнер, если менялся только список volumes.
Симптом: docker inspect не показывает новое монтирование, патч не виден.
Фикс: docker compose up -d --force-recreate. После — проверить
docker inspect lampac --format '{{json .Mounts}}' | jq -r '.[].Destination'.
✅ ДЕПЛОЙ: что залито и чем (2026-09-22, третья сессия)
Скрипты (на Mac, /Users/admin/lampac-tmp/):
| Файл | Роль |
|---|---|
gen_watchlist_v2.py |
генератор: .md → постеры/TMDB-ID/карточки (Radarr → TMDB → Кинопоиск-фолбэк) |
patch_plugin.py |
патч plugin.js: +enumerable:true,configurable:true в оба defineProperty |
plugin-patched.js |
результат патча (10738 Б), node --check чист |
deploy_lampac.py |
заливка JSON/YAML/plugin.js + volume + пересоздание + самопроверка |
watchlist.yaml |
описание каталога для Catalog (4 категории + card_parse) |
docker-compose.yml |
+ volume ./catalog/plugin.js:/lampac/module/Catalog/plugin.js:ro |
movies-watchlist.md |
локальная копия списка (682 строки) для прогонов |
Кэши генератора (в $HOME):
~/.lampac_tmdb_cache.json—{title|year: [tmdb_id, media_type, poster_path]}(поиск)~/.lampac_tmdb_details.json—{movie/ID: {overview, genres, cast, runtime, vote, …}}(детали карточки)
Что даёт прогон (замер):
films: 360 постеров 308 TMDB-ID 312
new: 33 постеров 30 TMDB-ID 30
cartoons 3 постеров 3 TMDB-ID 3
anime: 7 постеров 7 TMDB-ID 7
источники: Radarr 9, TMDB 343, Кинопоиск-фолбэк 51, карточек 352
Артефакты в wwwroot/ (все на HDD Кра́кена):
watchlist.json · watchlist-{films,new,cartoons,anime}.json · cards.json · cards/card-<key>.json
Команды (порядок):
cd /Users/admin/lampac-tmp
python3 gen_watchlist_v2.py movies-watchlist.md /tmp/wl-out/watchlist.json
python3 deploy_lampac.py /tmp/wl-out
⚠️ После правки генератора перезапускать его, а не только деплой — иначе в wwwroot
уедет старый формат файлов (один раз на этом потерял cards.json как список вместо словаря).
🔴 АРХИТЕКТУРНАЯ ОШИБКА 2026-09-22 — почему каталог «не работает»
Что хотел Alex (дословно):
«Я предполагал что будет отображаться то что и раньше (список cub) но В НЕГО будет сверху добавлено несколько секций в которых будут РЕАЛЬНЫЕ ЭЛЕМЕНТЫ — а не сгенерируют пустые заглушки! Нахуя это надо то?! Чё блядь значит оно так и должно быть?!»
Что сделано вместо этого — две независимые ошибки:
⚠️ ОПРОВЕРГНУТО ТЕСТОМ 2026-09-22 (поздний вечер). Раздел ниже описывает неверную гипотезу («
menuподменяет /mainдобавляет»). Проверка на живом сервере показала: блокаmainв YAML не существует — он ломает парсинг. Подмену разделов делаетplugin.js, а не YAML. Читать раздел «🔴 ПИТФОЛЛ YAML:mainЗАПРЕЩЁН» ниже — там правильная механика.
Ошибка 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):
python3 $LAMPAC/scripts/gen_watchlist.py <watchlist.md> $LAMPAC/wwwroot/watchlist.jsondocker 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), а не парсит текст |
✅ сделано (9 из Radarr) |
| 2 | Подставлять в id → tmdbId, в img → images[].remoteUrl (постер), year → year |
⏳ |
| 3 | sites/watchlist.yaml убрать блок menu, оставить mainmain в YAML ломает парсинг, menu обязателен. Подмену делает plugin.js. См. «🔴 ПИТФОЛЛ YAML» |
❌ опровергнуто |
| 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с ТВ отдаёт/catalog200 за 0.26 с. - Lampa работает и ходит на сервер — соединение
192.168.1.70 → 192.168.1.14:9118ESTABLISHED. 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 источника нет.
Диагностика (по возрастанию точности):
ssh kraken 'sudo ss -tn | grep ":9118"'— сначала показал 0 соединений (ложный вывод: замер сделан в момент старта Lampa). Через 30 с на ТВ:netstat -tn | grep 9118→ 1 ESTABLISHED + 6 TIME_WAIT — Lampa скачала все файлы.- Foreground-приложение:
luna-send -n 1 -f luna://com.webos.applicationManager/getForegroundAppInfo {}→com.lampac.tv. Запускать надоlaunchс экранированным JSON в кавычках, иначеerrorCode: 2 invalid parameters. - Доступ к отладчику ТВ:
curl http://192.168.1.70:9998/json— webOS отдаёт Chrome DevTools Protocol на порту 9998 (уWebAppMgrв аргументах--remote-debugging-port=9998). Черезwebsocket+Runtime.evaluateчитаются переменные живой Lampa. - Ключевой замер:
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
🔴 Питфоллы, найденные по ходу (не повторять)
-
Catalog ВЫБРАСЫВАЕТ элемент с пустым
href.ListController.cs:if (!IsNullOrWhiteSpace(name) && !IsNullOrWhiteSpace(href)). Симптом: из 404 позиций отдалось 52 — ровно те, у кого есть ссылка Кинопоиска. Фикс: генератор подставляет fallback-hrefhttps://www.kinopoisk.ru/index.php?kp_query=<название>. -
Catalog НЕ подставляет
{cat}вnodes. JSONPath статичен, отфильтровать секцию внутри одного файла нечем. Симптом: все категории отдавали весь список (404 вместо 360/34/3/7). Фикс: генератор пишет отдельный плоский файл на категорию (watchlist-films.jsonи т.д.),nodes: "$.items[*]"одинаков для всех. -
Кэш Catalog без версии данных. Ключ
catalog:{plugin}:{search}:{sort}:{cat}:{page}— не зависит от содержимого. Симптом: после перегенерации JSON выдача не менялась. Фикс:docker restart lampacв скрипте обновления. -
init.confне был смонтирован. Файл живёт в образе; правки черезdocker execтеряются при пересоздании. Правки шли вcurrent.conf(сгенерированный) — неверная цель. Фикс: volume:ro+ свой минимальныйinit.conf(перекрываетbase.conf). -
api_hostподставляется из запроса ({localhost}→ хост, по которому пришли). По IP192.168.1.14отдаёт правильный адрес — специально ничего настраивать не нужно. -
Кириллица в inline-jq внутри
ssh '...'ломает команду (tirith + quoting). Выносить в файл или убирать строки с кириллицей. -
Осиротевшие строки в
## Новинкидавали дубли в каталоге. Строки без👍/👎(например- [ ] ⬇️ Название)process-thumbsне трогал — они висели в Новинках навсегда. Если фильм уже[x]в главном списке — получался дубль: непросмотренный «Конец света» при просмотренном оригинале. Фикс вwatchlist-sync/parser.py(удаление сирот + флаг_patch_dropped) иcli.py. Разбор: family/projects/watchlist-automation, секция «ОСИРОТЕВШИЕ СТРОКИ». -
Двойное экранирование при правке
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 |
показывать (название как есть) |
Сортировка внутри списка — трёхключевая
⭐(U+2B50) — первыми- Рейтинг — по убыванию (IMDb/KP из строки файла; строки
[название](kp) (год) IMDb N.N KP N.N) - Ужасы (
🧟♀️, TMDB genre_id=27) — последними
⚠️ Только ⭐ (U+2B50) — как в sync-down (см. family/projects/watchlist-automation).
✭ (U+272D) приоритетом не является.
Порядок секций
- Главный список — первым. Это
## Фильмы - Дальше — остальные по порядку в файле:
## Новинки - 👍/👎,## Мультфильмы,## Аниме, …
⚠️ ## Новинки👎 — не показывать (это скрытая секция отказов).
⚠️ Секция ## Новинки содержит 👍/👎; 👎-строки прячем, 👍 показываем.
Источник рейтинга
В строках со ссылками Кинопоиска рейтинг уже в тексте: IMDb 7.6 KP 8.2.
Для строк без рейтинга (например - [ ] Gods don't give gifts) — сортировать как 0, в конец группы.
Опционально: добирать через TMDB (кэш tmdbproxy в Lampac) — решить при реализации.
🟢 РЕШЕНИЕ: первая категория игнорирует format (2026-09-22, вторая сессия)
Это была настоящая причина «пустого каталога». Не main, не кэш, не монтирование.
Симптом
cat=films отдавал 360 элементов с img: null и id = index.php?kp_query=...
(ссылка на поиск Кинопоиска). При этом watchlist-films.json на диске содержал
совсем другое. Клик по элементу → Lampa идёт на страницу поиска → «здесь пусто».
Диагностика (воспроизводимая)
Подменил watchlist-films.json пробником из 1 элемента («Project Hail Mary»
с реальным TMDB ID и постером). Результат запросов:
| Запрос | Ответ | Что это значит |
|---|---|---|
cat=films |
360 (старые данные) | файл НЕ прочитан |
cat=films&sort=rating |
1 ✅ | файл прочитан |
cat=new |
33 ✅ | файл прочитан |
cat=cartoons |
3 ✅ | файл прочитан |
cat=anime |
7 ✅ | файл прочитан |
Пробник был 343 байта, HTTP внутри контейнера отдавал 343 байта — но Catalog возвращал 360. Значит Catalog читал другой URL.
Корень — ListController.cs:50–87
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
}
Блок format применяется только в ветке else if (!string.IsNullOrEmpty(cat)) —
то есть не для реально первой категории в переборе. Эмпирически: категория,
стоящая первой в menu.categories, берёт базовый url из list.firstpage,
и format для неё не срабатывает.
У меня первой стояла Фильмы → она читала list.uri/firstpage, а он в тот момент
указывал на общий watchlist-films.json, содержимое которого подтягивалось из
watchlist.json (все каналы, 360 позиций films).
Фикс
Поставить первой в menu.categories ту категорию, файл которой и лежит в list.uri.
В рабочем watchlist.yaml:
menu:
- categories:
Новинки: new # ← ПЕРВОЙ (читает list.uri = watchlist-new.json)
Фильмы: films # ← через format
Мультфильмы: cartoons
Аниме: anime
format:
"-": |
return $"http://127.0.0.1:9118/watchlist-{cat}.json";
list:
uri: watchlist-new.json # ← согласовано с первой категорией
firstpage: watchlist-new.json
total_pages: 1
⚠️ Правило на будущее: list.uri и первая категория в menu.categories
должны указывать на один и тот же файл. Если переставить категории местами —
поправить list.uri, иначе первая категория снова отдаст чужие данные.
Этот комментарий вписан прямо в watchlist.yaml на HDD.
Что ещё опробовано и НЕ сработало (не повторять)
?nocache={Guid.NewGuid()}вformat— не помогло, дело не в кэше- Перезапуск контейнера — не помогло (кэш ни при чём,
hybridCacheв памяти процесса) - Разбивка на 4 отдельных
menu-записи с разнымиformat— сломало всё, канал отдавал пустоту - Явный
format["sort"]— тоже путь в никуда,sortу меня не используется
Артефакты, написанные в этой сессии (локально, /Users/admin/lampac-tmp/)
| Файл | Роль | Статус |
|---|---|---|
gen_watchlist_v2.py |
генератор с постерами: Radarr → TMDB → фолбэк Кинопоиск | ✅ прогнан, 330 постеров из 403 |
patch_plugin.py |
патч plugin.js (+enumerable:true в 2 местах) |
✅ node --check чист |
plugin-patched.js |
результат патча (10738 Б) | ✅ готов |
watchlist.yaml |
исправленный порядок категорий + комментарий-питфолл | ✅ готов |
docker-compose.yml |
+ volume ./catalog/plugin.js:/lampac/module/Catalog/plugin.js:ro |
✅ готов |
deploy_lampac.py |
заливка 7 шагов: бэкап → JSON → YAML → plugin → volume → restart → проверка | ✅ готов, не запущен |
movies-watchlist.md |
локальная копия источника (682 строки) | ✅ |
Генерация занимает ~8–10 минут (403 фильма × запросы к TMDB, time.sleep(0.02)).
Кэш: ~/.lampac_tmdb_cache.json. Прогресс — в stderr, каждые 25 позиций.
🔴 Запускать в background (notify_on_complete=true) — foreground упрётся в 600 с.
Питфоллы gen_watchlist_v2.py (найдены в работе)
- Хвостовой год в названии ломает поиск TMDB.
«Простите за беспокойство 2018», «Одиссея 2026» — TMDB не находит.
Фикс: в
clean_title()срез\s+(?:19|20)\d{2}\)?$, и вtmdb_search()год переносится вyear-хинт. - Ключ кэша должен включать год (
title|year), иначе «Дракула 1992» и «Дракула 2025» схлопнутся в одну запись. - Распознавание media_type: искать и
movie, иtv(аниме/сериалы в списке есть).
Ключевой факт про href в каталоге (из ListController.cs:314–322)
else if (href.StartsWith("http"))
href = Regex.Replace(href, "https?://[^/]+/", ""); // вырезает домен
Поэтому https://www.themoviedb.org/movie/687163 → id = "movie/687163" →
CardController получает полноценный TMDB-URI → карточка с постером, жанрами,
описанием подтягивается штатным механизмом Lampa. Работает для movie/N и tv/N.
Проверка результата (то, что должен увидеть Alex)
curl -s "http://192.168.1.14:9118/catalog/list?plugin=watchlist&cat=films&page=1" \
| jq '{total: (.results|length),
posters: ([.results[]|select(.img)]|length),
tmdb: ([.results[]|select(.id|test("^(movie|tv)/"))]|length)}'
🟢 РЕШЕНИЕ: первая категория игнорирует format (2026-09-22, проверено)
Это была настоящая причина «здесь пусто» в UI. Читать первым, если каталог отдаёт
названия, но без постеров и с id = ссылкой на поиск Кинопоиска.
Симптом (замер):
cat=films → 360 элементов, постеров 0, id = "index.php?kp_query=…"
cat=films&sort=rating → 1 элемент (правильный файл!)
cat=new → 33 ✅ cat=cartoons → 3 ✅ cat=anime → 7 ✅
Диагноз: cat=films читала watchlist.json (общий файл, 360 = channels.films.items),
а не watchlist-films.json. То есть блок format для неё не применился.
Причина — по коду ListController.cs:
// строка 50: базовый URL
string url = $"{init.host}/{(page == 1 && init.list?.firstpage != null
? init.list?.firstpage : init.list?.uri)}";
// строки 59-85: ветка при непустом 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.BaseExecute<string>(eval, new CatalogGlobalsMenuRoute(...));
Категория, стоящая ПЕРВОЙ в списке categories, до применения format уже получает
базовый URL из list.uri. Пока Фильмы была первой, она читала list.uri
(= watchlist.json при том значении, что стояло) вместо своего format-URL.
Фикс (одна перестановка):
menu:
- categories:
Новинки: new # ← первой ставим НОВИНКИ
Фильмы: films # (она читает list.uri = watchlist-new.json)
Мультфильмы: cartoons
Аниме: anime
format:
"-": |
return $"http://127.0.0.1:9118/watchlist-{cat}.json";
⚠️ list.uri и list.firstpage обязаны совпадать с файлом первой категории
(здесь — watchlist-new.json), иначе первая категория отдаст чужой файл.
В генераторе это зафиксировано комментарием-питфоллом.
Проверка после правки (все 4 должны дать свои числа, а не общий список):
for c in new films cartoons anime; do
curl -s "http://192.168.1.14:9118/catalog/list?plugin=watchlist&cat=$c&page=1" \
| jq -r "\"$c: \(.results|length) элементов, постеров \([.results[]|select(.img!=null)]|length)\""
done
🔴 ГЛАВНЫЙ ПИТФОЛЛ YAML: main ЗАПРЕЩЁН, он генерируется сам (2026-09-22)
Симптом: добавил блок main: в sites/watchlist.yaml → «Мой список» полностью
исчез из /catalog (остались только Eneyida, FilmixApp, KinoUkr, Kinobase, Rezka, VoKino).
Запрос catalog/list?plugin=watchlist отдавал пустоту. В логах Lampac — тишина,
ошибка не пишется, сайт просто отбрасывается.
Причина: CatalogSettings не имеет поля main для входного YAML. YamlDotNet
давится неизвестным полем → ModInit.goInit(site) возвращает null →
ApiController.cs:70 отбрасывает сайт:
if (init == null || !init.enable || init.menu == null || init.hide)
continue;
⚠️ Проверено обе формы — обе ломают:
main: # список — НЕ работает
- categories:
Фильмы: films
main: # словарь — НЕ работает
Фильмы: http://...
Как на самом деле: Catalog сам генерирует main в JSON-ответе /catalog,
выводя его из блока menu. Проверено — в ответе есть и menu, и main с одинаковым
содержимым:
"menu": { "Фильмы": "/catalog/list?plugin=watchlist&cat=films", ... },
"main": { "Фильмы": "/catalog/list?plugin=watchlist&cat=films", ... }
Писать main в YAML не нужно и нельзя. Достаточно menu.
Правильная форма YAML (проверенная, рабочая)
enable: true
jsonPath: true
displayindex: 1
displayname: Мой список
host: http://127.0.0.1:9118
menu: # ← ЕДИНСТВЕННЫЙ обязательный блок категорий
- categories:
Фильмы: films
Новинки: new
Мультфильмы: cartoons
Аниме: anime
format:
"-": |
return $"http://127.0.0.1:9118/watchlist-{cat}.json";
search:
uri: watchlist.json
list:
uri: watchlist-films.json
firstpage: watchlist-films.json
total_pages: 1
content:
nodes: "$.items[*]"
name: { attribute: name }
original_name: { attribute: original }
year: { attribute: year }
href: { attribute: href }
image: { attribute: image }
args:
- { name_arg: rating, attribute: rating }
Сопутствующие питфоллы (все проверены на живом сервере)
- 🔴
menuОБЯЗАТЕЛЕН. Не «подменяет разделы», а регистрирует канал.ApiController.cs:70: безmenuсайт отбрасывается. Убирать нельзя. - 🔴
host+uriСКЛЕИВАЮТСЯ.ListController.cs:53:Значитstring url = $"{init.host}/{(page==1 && init.list?.firstpage != null ? init.list?.firstpage : init.list?.uri)}";uri/firstpageдолжны быть относительными (watchlist-films.json). Полный URL даёт двойной префикс → пусто:http://127.0.0.1:9118/http://127.0.0.1:9118/watchlist-films.json - 🔴 Явно указанный
formatвmenuвызывает перезагрузку страницы при каждом выборе категории. Если не нужен — убратьformatизmenu, оставить толькоcategories. (Наблюдение Alex: «перезагружает прогрузку страницы при каждом выборе раздела».)
Кто на самом деле подменял родные разделы Lampa
НЕ YAML. Это plugin.js (он же /catalog.js):
Lampa.Listener.follow('menu', function(e){
if (e.type=='action' && (e.action=='tv'||'cartoon'||'anime'||'movie'||'relise')) {
var catalog = Api.getCatalog();
if (!catalog) return;
...
Lampa.Router.call('category', {...});
e.abort(); // ← перехват клика по родному разделу
}
});
Перехват срабатывает ТОЛЬКО когда выбран источник «Мой список». Родной CUB/tmdb не трогается, пока источник не переключён на наш.
Как реально добиться «CUB + мой список сверху»
| Путь | Реализуемость |
|---|---|
Блок main в YAML |
❌ невозможно — ломает парсинг |
Оставить menu |
⚠️ источник работает, но разделы подменяются, пока он выбран |
Патч plugin.js |
⚠️ возможно: убрать Listener.follow('menu') — перехвата не будет, но и секции сверху не появятся |
Открытый вопрос для Alex: родной CUB и «Мой список» — это два источника Lampa,
переключаются в «остальное → основной источник». Одновременно показывать нельзя без
патча plugin.js. Решение за Alex.
✅ РЕШЕНИЕ НАЙДЕНО 2026-09-22 — полная архитектура (проверено)
Что уже есть в системе (нашёл, проверено)
Модуль watchlist_sync/resolver.py (/srv/.../watchlist-sync/) — уже решает задачу
постеров и ID. Умеет:
- поиск через TMDB (
api.themoviedb.org/3) вru-RUиen-US - поиск через Кинопоиск (
api.poiskkino.dev/v1.4) - возвращает
Candidate:tmdb_id,kp_id,title,year,score,rating,genres is_horror()— определяет ужасы по TMDBgenre_id=27(для сортировки 🧟♀️)- кэш в
.resolve_cache.json - различает
ok/ambiguous(score < 0.75) /not_found
Модуль watchlist_sync/arr.py — клиент Radarr/Sonarr. 🔴 ВОТ ИСТОЧНИК СКАЧКИ:
"monitored": True,
"addOptions": {"searchForMovie": True}, # ← качает сразу после добавления
Для сериалов: "addOptions": {"searchForMissingEpisodes": True} — то же самое.
Ключевая связка ID
| База | Где есть | Формат |
|---|---|---|
| Кинопоиск (kp) | в .md: 68 строк со ссылками kinopoisk.ru/film/<id>/ |
11825624 |
| TMDB | в Radarr (tmdbId), в resolver (Candidate.tmdb_id) |
1292695 |
| IMDb | в Radarr (imdbId), в .md только рейтинг |
tt31728330 |
⚠️ Прямой связи kp↔tmdb в файле нет. Связка делается через resolver.py: он по
названию находит и kp_id, и tmdb_id одновременно. Это его основная работа.
Замер Radarr (после шагов 1–3, 2026-09-22)
| Метрика | Значение |
|---|---|
| Фильмов | 50 |
monitored |
0 ← автоскачка выключена |
rssSyncInterval |
0 ← в RSS не ходит |
| Очередь | 0 |
С tmdbId |
50 (все) |
| С постером | 46 |
| С рейтингом TMDB | 50 (все) |
| С файлом на диске | 24 (не трогали) |
Sonarr: 13 сериалов, monitored 0, RSS 0, очередь 0.
Поля Radarr для каталога (проверено, GET /api/v3/movie)
{
"tmdbId": 1292695,
"imdbId": "tt31728330",
"title": "They Will Kill You",
"originalTitle": "They Will Kill You",
"year": 2026,
"titleSlug": "1292695",
"hasFile": true,
"genres": ["Horror"],
"ratings": {"tmdb": {"value": 6.945}},
"images": [{"coverType": "poster",
"remoteUrl": "https://image.tmdb.org/t/p/original/6oI4oQKTWMVUlr8Ivqydp28Ruu6.jpg"}]
}
Названия в Radarr — латиницей (They Will Kill You), в .md — по-русски. Перевод
делает TMDB Proxy (proxy_tmdb = true в lampainit.js) — уже включён.
КАК ИГРАЕТ LAMPA (проверено — на диск НЕ пишется)
| Компонент | Значение | Смысл |
|---|---|---|
| TorrServer | /ts/echo → MatriX.135 |
жив, встроен в Lampac |
UseDisk |
false |
🔴 на диск НЕ пишет |
CacheSize |
100663296 (96 МБ) | кэш в RAM |
TorrentsSavePath |
"" (пусто) |
пути сохранения нет |
TorrentDisconnectTimeout |
180 | раздача живёт 3 мин после стопа |
| Занятые торренты | [] (пусто) |
ничего не висит |
Поиск торрентов: Lampa → jackett_url = 192.168.1.14:9118 → Lampac сам отдаёт
Jackett-совместимый API:
curl "http://192.168.1.14:9118/api/v2.0/indexers/all/results?apikey=1&Query=matrix"
# → {"Results":[{"Tracker":"rutor","Title":"...","MagnetUri":"magnet:?xt=...","Seeders":0}]}
✅ Проверено: вернул реальные результаты. Отдельный Jackett не нужен (его нет в системе).
🔴 parser_torrent_type = 'jackett' в lampainit.js — это НЕ отдельный Jackett,
а эндпоинт внутри Lampac. Так и задумано производителем.
Итоговая архитектура (то, что нужно поставить)
family/documents/movies-watchlist.md ← источник (360 фильмов)
│
├─► resolver.py ──► TMDB + Кинопоиск
│ │ tmdb_id + постер + рейтинг + жанры
│ ▼
│ wwwroot/watchlist-<cat>.json ← каталог ДЛЯ LAMPA (с постерами!)
│ │
│ ▼
│ sites/watchlist.yaml (main: только секции СВЕРХУ, БЕЗ menu)
│ │
│ ▼
│ Lampa: плагин catalog.js → секции с постерами СВЕРХУ родного CUB
│
└─► arr.py (searchForMovie: False) ──► Radarr/Sonarr
└─► метаданные в базу БЕЗ скачки
Просмотр: Lampa → TorrServer (RAM, UseDisk:false) → на HDD ничего
Разделение ролей:
- resolver.py — постеры/ID для каталога (360 фильмов, включая те, что не в Radarr)
- Radarr — справочник того, что уже добавлено (50), источник
hasFile - TorrServer — воспроизведение, на диск не пишет
Три правки (на апруве)
| # | Файл | Правка | Зачем |
|---|---|---|---|
| 1 | watchlist_sync/arr.py |
searchForMovie: True → False (+ у дочки то же для сериалов) |
добавление без скачки |
| 2 | catalog/sites/watchlist.yaml — убрать блок menu, оставить mainplugin.js / свой плагин. Решение Alex |
❌ отменено | |
| 3 | генератор каталога | брать постер/ID через resolver |
реальные элементы вместо заглушек |
⚠️ Правка №1 может уже не понадобиться: шаги 1–3 (unmonitor + RSS 0) закрыли скачку
на уровне состояния. Но searchForMovie: True в коде включит её снова при следующем
добавлении из списка. Поэтому фикс нужен — иначе вернётся.
🎯 ПЕРЕДЕЛКА 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) — не ищут, не качают
✅ Порядок работ — ШАГИ 0–3 ВЫПОЛНЕНЫ 2026-09-22
| # | Действие | Сервис | Статус |
|---|---|---|---|
| 0 | Бэкап radarr.db + config.xml + sonarr.db + config.xml |
оба | ✅ сделано |
| 1 | Отменить очередь: 6 Radarr + 19 Sonarr | оба | ✅ сделано (оба 0) |
| 2 | monitored: false — 45 Radarr + 13 Sonarr |
оба | ✅ сделано (оба 0) |
| 3 | rssSyncInterval: 0 |
оба | ✅ сделано (оба 0) |
| 4 | Проверка: monitored 0, очередь 0, RSS 0, Transmission тих | оба | ✅ сделано |
| 5 | Генератор каталога → постеры/ID через TMDB + Radarr (gen_watchlist_v2.py) |
— | ✅ написан, прогнан (330/403 постеров) |
| 6 | sites/watchlist.yaml убрать menu, оставить main |
— | ❌ опровергнуто |
| 6b | Порядок категорий в watchlist.yaml: «Новинки» первой, list.uri = watchlist-new.json |
— | ✅ исправлено |
| 7 | Фикс plugin.js (enumerable:true) |
— | ✅ файл пропатчен, заливка ждёт |
| 8 | Фикс arr.py: searchForMovie → False |
— | ✅ сделано, коммит 9872016 |
| 9 | Заливка на Кра́кен + пересоздание контейнера + приёмка в UI | — | ⏳ deploy_lampac.py готов, не запущен |
Не трогаем: Prowlarr, Transmission, 24 файла на диске, Jellyfin, остальные контейнеры.
📋 Как выполнены шаги 1–3 (скрипты и команды — воспроизводимо)
Шаг 0 — бэкапы (метка 20260922-123404)
D=/srv/dev-disk-by-uuid-6194539b-03e0-4f33-986b-4da09834e82f/docker
TS=$(date +%Y%m%d-%H%M%S)
sudo cp $D/radarr/config/radarr.db $D/radarr/config/radarr.db.bak-$TS
sudo cp $D/radarr/config/config.xml $D/radarr/config/config.xml.bak-$TS
sudo cp $D/sonarr/config/sonarr.db $D/sonarr/config/sonarr.db.bak-$TS
sudo cp $D/sonarr/config/config.xml $D/sonarr/config/config.xml.bak-$TS
Результат: radarr.db 4 374 528 Б, sonarr.db 4 870 144 Б + оба config.xml 650/648 Б.
Шаг 1 — очистка очереди. Скрипт /tmp/arr_clear_queue.py (на Mac → scp → запуск на Кра́кене).
DELETE /api/v3/queue/<id>?removeFromClient=true&blocklist=false.
Результат: Radarr 6 → 0, Sonarr 19 → 0.
Шаг 2 — снятие monitored.
- Radarr:
PUT /api/v3/movie/editorс ключомmovieIds→ 45 → 0 - Sonarr:
PUT /api/v3/series/editorс ключомseriesIds→ 13 → 0
Скрипты: /tmp/arr_disable_autograb.py (Radarr, упал на Sonarr с HTTP 500),
/tmp/sonarr_unmonitor.py (Sonarr, исправленный ключ).
Шаг 3 — rssSyncInterval: 0. Скрипт /tmp/radarr_rss_off.py.
Sonarr: 15 → 0 с первого раза. Radarr: со второго — первый заход вернул 30
(не отправлял id), фикс — отправлять весь объект конфига с id: 1.
📊 Состояние ПОСЛЕ выполнения (замер 2026-09-22)
| Метрика | Radarr | Sonarr |
|---|---|---|
| Всего записей | 50 фильмов | 13 сериалов |
monitored |
0 (было 45) | 0 (было 13) |
rssSyncInterval |
0 (было 30) | 0 (было 15) |
| Очередь | 0 (было 6) | 0 (было 19) |
С tmdbId |
50 (все) | — |
| С постером | 46 | — |
| С рейтингом TMDB | 50 (все) | — |
| С файлом на диске | 24 (не трогали) | — |
Проверка пассивности: Transmission за 45 секунд — NET 290GB/26.7GB и
BLOCK 214GB/393kB не сдвинулись ни на байт. Логи Radarr (grep search|download|grab)
за 2 минуты — пусто. TorrServer: список торрентов [], echo → MatriX.135.
🔴 Питфоллы выполнения (найденные в этой сессии)
-
urllibНЕ идёт по 307 автоматически, аcurl -sL— идёт. Симптом: все 25 DELETE в первом прогоне вернулиHTTP Error 307: Temporary Redirect, ничего не удалилось, состояние не изменилось. Фикс: свойHTTPRedirectHandlerподкласс, который для 307/308 сохраняет метод и тело (штатныйurllibпревращает DELETE в GET):class RedirectKeepMethod(urllib.request.HTTPRedirectHandler): def redirect_request(self, req, fp, code, msg, headers, newurl): if code in (307, 308): new = urllib.request.Request(newurl, data=req.data, method=req.get_method()) for k, v in req.headers.items(): new.add_header(k, v) return new return super().redirect_request(req, fp, code, msg, headers, newurl)Тот же корень, что у
curlбез-L(питфолл в памяти) — только на стороне Python. -
Sonarr группирует эпизоды сезона под одним
id. Симптом: после удаления первой записи остальные возвращаютHTTP 404: Not Found. Это не сбой — они исчезли вместе с родительской. Итог всё равно 0. -
Ключ поля в
*/editorразличается между Radarr и Sonarr. Radarr/movie/editor→movieIds; Sonarr/series/editor→seriesIds.ids→ HTTP 500 Internal Server Error (Sonarr). -
PUT /api/v3/config/indexerтребует весь объект сid. Частичный{"rssSyncInterval": 0}молча возвращает старое значение. Отправлять весь объект, полученныйGET, с изменённым полем. -
Кириллица в inline-
jqвнутриssh '...'снова сломала команду (unexpected INVALID_CHARACTER) — 6 ошибок в одном вызове. Ключи объекта писать латиницей (total,monitored,queue), невсего/очередь.
❓ Открытый вопрос к Alex — ЗАКРЫТ
Был вопрос «Radarr/Sonarr нужны как справочник или выключаются совсем?». Alex ответил резко: «Как ты блядь думаешь мне нужны блядь постеры или нет блядь?! Меня не ебет откуда оно их берет сделай чтобы работало как поставлено в задаче». → Остаются как справочник метаданных. Вопрос постеров больше не задавать.
План работ (на апруве Alex) — ШАГИ 0–4 ВЫПОЛНЕНЫ
Шаг 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 (отдельная проверка ниже).
⚠️ Незакрытые вопросы к плану
- Sonarr — Alex упомянул его вместе с Radarr. Проверить его настройки отдельно (сериалы в списке есть?). Это отдельная задача. Вопрос задан Alex, ответа нет.
- Ручная загрузка после отключения — если Alex захочет скачать фильм вручную,
нужно временно включить
monitoredили искать вручную через Interactive Search. Работает, но не автоматически. - 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)
- Что делать с 310 фильмами, которых нет в Radarr?
В
.md— 360, в Radarr — 50. У 310 нет TMDB ID и постера. Варианты: (а) показывать только 50 из Radarr; (б) добирать остальные через TMDB-search (часть названий найдётся криво: «девять» → не то); (в) показывать заглушкой без постера. Без решения шаг 1 переделки не начать. - Нужны 4 секции или одна? Секции «Фильмы / Новинки / Мультфильмы / Аниме» или одна «Мой список» сверху. Вопрос задан, ответа нет.
Открытые вопросы (не блокируют)
- Постеры — тянуть через TMDB-кэш Lampac, через Radarr
MediaCover/, илиremoteUrlCDN? - Карточка —
card_parseне настроен. С реальным TMDB ID карточка должна подтянуться штатным механизмом Lampa — проверить после переделки. 🗓️upcoming — показывать или прятать? Сейчас показываем.❓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 — источник данных
✅ РАБОТАЕТ (2026-09-22, конец дня)
Карточка фильма починена. Проверено: 339 из 339 карточек → HTTP 200.
Итог по каталогу
| Секция | Элементов | Постеров | Рабочих TMDB-ID |
|---|---|---|---|
| Новинки | 33 | 30 | 30 |
| Фильмы | 360 | 308 | 312 |
| Мультфильмы | 3 | 3 | 3 |
| Аниме | 7 | 7 | 7 |
| Всего | 403 | 348 | 352 |
Источники метаданных: Radarr 9, TMDB 343, Кинопоиск-фолбэк 51.
Что сделано
- Генератор
gen_watchlist_v2.py— пишет файл на каждый фильм:wwwroot/cards/card-movie-NNN.json, владелец1000:1000, режим 644. watchlist.yaml→card_parse.initUrlсобирает путь изuri:return $"{host}/cards/card-{uri.Replace("/", "-")}.json";,node: card.deploy_lampac.py— добавлен шаг2b(заливкаcards/через tar), плюс проверка карточки в шаге 7.arr.py—monitored: False,searchFor*: False(коммит9872016).plugin.js— патчenumerable:true, смонтирован volume, переживает обновление образа.
🔴 ПИТФОЛЛЫ, найденные по ходу (все стоили времени)
- Проверять карточку надо по
/catalog/card, НЕ по/card./cardотдаёт 404 и выглядит как поломка каталога. Потерян час на ложном диагнозе. parse.node— СТАТИЧНЫЙ JSONPath.{uri}/{cat}в него НЕ подставляются (json.SelectToken(parse.node)). Слэш в ключеmovie/NNNтрактуется как вложенность JSON →SelectTokenвозвращает null →BadRequest("parse.node"). Именно поэтому выбран формат «файл на фильм» с фиксированным$.card.card_parse.initUrlИСПОЛНЯЕТСЯ как C# (CSharpEval, объектCatalogInitUrlCardс полямиhost/uri/type/args). Только здесь можно подставитьuri. Кавычки внутри YAML-heredoc писать экранированно.CardController.Index(plugin, uri, type)— параметраcatНЕТ.{cat}в карточке всегда пустой.- macOS
tarкладёт в архив AppleDouble-мусор._*— файлы попадают вwwwrootи мешают. Использоватьtar --no-mac-metadata+rm -f ._*. - Права решают. Если файлы отдать
root:root 600— dotnet отвечаетPermission denied, статика молча отдаёт 404. Нужно1000:1000+755/644. find -execчерезssh -tрвётся:missing argument to -exec. Использоватьchmod -Rотдельными вызовами.- Счётчик TMDB-ID врал нулём:
idприходит какmovie/424781(префикс), а проверялось"/movie/"(вхождение со слэшем спереди). Проверятьstartswith. - Первая категория в
menu.categoriesигнорируетformatи берётlist.uriнапрямую. Поэтому «Новинки» стоят первыми, и ихlist.uri = watchlist-new.json. - Ключи карточки.
CardControllerотдаётtitle/original_title, аcard_parseищетname/original. В JSON пишем оба набора ключей.
Воспроизведение
cd /Users/admin/lampac-tmp
python3 gen_watchlist_v2.py movies-watchlist.md /tmp/wl-out/watchlist.json
python3 deploy_lampac.py /tmp/wl-out --skip-plugin # ~2 мин
Cron: генератор в 02:30, после watchlist-nightly (01:00).
Осталось на ТВ
Проверить глазами в Lampa на 192.168.1.70: секции сверху с постерами,
клик → карточка с описанием → воспроизведение через TorrServer.
Приёмка = результат виден в UI.
Не сделано (ждёт решения, без апрува не трогать)
310 фильмов из списка, которых нет в Radarr — пока показываются через фолбэк Кинопоиска (51 без постера). Решить судьбу отдельно.
🔴 ФИНАЛЬНЫЙ ФИКС: карточка = «здесь пусто» без ЧИСЛОВОГО tmdb_id
2026-09-22, третья сессия. Карточка заработала: 352/352 отдают tmdb_id.
Alex после первого «готово»: «при открытии все так же "здесь пусто" и никакого инфо
или воспроизведения». Сервер отдавал HTTP 200 с title/img/overview — значит
дело было не в запросе, а в полноте ответа.
Причина — одна, но скрытая за двумя масками
plugin.js при клике (full()):
if(json.tmdb_id){ status.need++;
Lampa.Api.sources.cub.reactionsGet(
{id: json.tmdb_id, method: json.original_name ? 'tv' : 'movie'}, ...); }
Без tmdb_id Lampa считает карточку неполной → пустой экран. Причём нужен
числовой tmdb_id, не строка.
Два бага в цепочке card_args:
-
ModInit.cs:285—nodeValueвозвращает ЛЮБОЕJValueкак строку.string value = valueToken switch { JValue jValue => jValue.Value?.ToString(), // ← число 424781 → "424781" ... };Поэтому
tmdb_idиз JSON-файла приходил строкой, иif(json.tmdb_id)не давала числа. Никакой конфиг это не обходит — толькоformat. -
Кэш
catalog:card:{plugin}:{uri}живёт в ПАМЯТИ процесса и переживаетdocker restart. После правки данных старые ответы продолжали отдаваться. Именно поэтомуmovie/424781(первый элемент «Фильмов», по нему Alex и кликал) упорно оставалсяnull, хотя исправленный файл уже лежал на диске. Проверять фикс только на НЕзакэшированномuri.
Фикс — format в card_args (исполняется как C#, возвращает настоящий тип)
card_args:
- name_arg: tmdb_id
attribute: tmdb_id
format: "long.TryParse(value, out long v) ? v : 0"
- name_arg: source
attribute: media_type
- name_arg: overview
attribute: overview
- name_arg: genres
attribute: genres
- name_arg: runtime
attribute: runtime
format: "long.TryParse(value, out long v) ? v : 0"
- name_arg: vote_average
attribute: vote_average
format: "double.TryParse(value, out double v) ? v : 0"
- name_arg: rating
attribute: rating # остаётся строкой — Lampa показывает в бейдже
format вызывается как CSharpEval.Execute<object>(nd.format, new CatalogNodeValue(value, host), options) (ModInit.cs:255 и :317) —
value это уже строка, поэтому нужен TryParse.
Эталонный рабочий ответ
{ "id": "movie/424781", "title": "Простите за беспокойство",
"original_title": "Простите за беспокойство", "tmdb_id": 424781,
"source": "movie", "runtime": 111, "rating": "6.8", "vote_average": 68,
"genres": [{"name":"фэнтези"},{"name":"фантастика"},{"name":"комедия"}],
"overview": "...", "img": "http://192.168.1.14:9118/proxyimg:210:0/..." }
Итог проверки
352 из 352 карточек (все, у кого есть TMDB ID) отдают числовой tmdb_id,
source, runtime, рейтинг и жанры. Ошибок — 0.
🔴 CUB: «вижу только фильмы из списка»
CUB не пропал — это два независимых источника Lampa, одновременно показать нельзя. Переключение: Настройки → Остальное → Основной источник. Пункта «Источник» в настройках НЕ существует.
Питфолл приёмки на ТВ
Lampa на ТВ кэширует манифест и ответы. После правок надо полностью выгрузить приложение (не свернуть), иначе клиент держит старый кэш и показывает дореформенный результат.
🔴 СЕССИЯ 4 (2026-09-22, поздняя ночь): приёмка НЕ пройдена, требование изменено
Что сказал Alex
- «у фильмов в "Мой список" появились норм названия, годы и у большинства — постеры. Но при открытии все так же "здесь пусто" и никакого инфо или воспроизведения!»
- «че блядь значит надо Настройки → Остальное → Основной источник?! мы блядь это уже обсуждали и надо было добавить в CUB список новые секции а не ручное блядь переключение!»
Статус по факту
| Пункт | Состояние |
|---|---|
| Серверная карточка | ✅ исправна — curl отдаёт tmdb_id: 424781 числом, source, жанры, постер, описание |
| Приёмка на ТВ | ❌ НЕ пройдена — «здесь пусто» на экране |
| Соединения ТВ → Lampac | ❌ ноль (sudo ss -tn | grep 9118 пусто в момент проверки) |
| Секции внутри CUB | ❌ не реализовано — сейчас подмена через e.abort() |
Диагностика, выполненная в этой сессии
Разобран app.min.js (2.1 МБ) — эталон CUB-источника:
CUB.full()ставитjson.source = 'cub'сам (строка ~34252).Explorerчитаетthis.object.movie || this.object.card.fullпушится сsource: element.data.source || 'tmdb'(строка 44291) — 🔴 фолбэк на tmdb, если у элемента нет поляsource. В ответе/catalog/listполяsourceнет → проверить первым делом.plugin.js:42-43требует от карточки:tmdb_id(число),original_name(илиoriginal_title) для определения tv/movie,img,overview.
Полная анатомия — personal/tech/lampac-deployment → «Анатомия CUB-источника».
🎯 ЗАДАЧА СЛЕДУЮЩЕЙ СЕССИИ (переформулирована Alex'ом)
Не переключать источник, а дорисовать наши секции ВНУТРЬ CUB.
- Проверить гипотезу
source. Элементы/catalog/listне содержатsource. Еслиfullуходит вtmdbвместоwatchlist— это и есть «здесь пусто». Проверка:curl "/catalog/list?plugin=watchlist&cat=films&page=1" | jq '.results[0].source'. - Отвязать плагин от перехвата
menu. Патчить рендер главной CUB (app.min.jsстроки ~44260-44310:feed/category_full) либо добавить ряды черезLampa.Listener.follow('activity').e.abort()— НЕ использовать (отменяет родной раздел, чего Alex не хочет). - Приёмка = глазами на ТВ. Открыть обычный CUB → сверху видны наши секции с постерами → клик → карточка с описанием → кнопка воспроизведения ведёт в TorrServer. Никакого ручного переключения источника.
Питфоллы, найденные в этой сессии
- 🔴 Проверять карточку по
/catalog/card, НЕ по/card./cardотдаёт 404 и выглядит как поломка каталога — потерян час на ложном диагнозе. - 🔴 Кэш
catalog:card:{plugin}:{uri}живёт в ПАМЯТИ и переживаетdocker restart. После правки данных старые ответы продолжают отдаваться. Проверять на незэкешированномuri(взять другой фильм). - 🔴
nodeValue(ModInit.cs:285) возвращает любоеJValueСТРОКОЙ → вcard_argsнуженformat: "long.TryParse(value, out long v) ? v : 0". - 🔴
tmdb_idДОЛЖЕН быть числом —plugin.jsделаетif(json.tmdb_id). - macOS
tarкладёт AppleDouble._*вwwwroot→tar --no-mac-metadata. - Права
root:root 600→Permission denied→ статика молча отдаёт 404. Нужно1000:1000+755/644. find -execчерезssh -tрвётся (missing argument to -exec) →chmod -R.&&-цепочка рвёт tty-сессию ssh → каждая команда отдельным вызовом.tcpdumpна Кра́кене не был установлен →sudo apt-get install -y tcpdump.com.webos.service.devtoolsне существует на webOS 5.4.2 Lite.luna-sendпишет в tty → только черезscript -q -c "..." /tmp/out.txt.
Артефакты
- Скрипты:
/Users/admin/lampac-tmp/{gen_watchlist_v2.py,deploy_lampac.py,watchlist.yaml}. - Копия UI Lampa для разбора:
/tmp/app-min.js(2.1 МБ). - Бэкап плана:
~/backup-lampac-plugin-plan-20260922-150459.md. - Доклады по карточке — personal/tech/lampac-deployment (конец файла).
Связанные
✅ РАБОТАЕТ НА ТВ (2026-09-22, вечер) — подтверждено Alex
«Фильмы из списка Watchlist теперь открываются и проигрываются.»
🔴🔴 КОРНЕВАЯ ПРИЧИНА «ЗДЕСЬ ПУСТО» — РУССКИЙ КЛЮЧ ПЛАГИНА
Поймано tcpdump'ом на Кра́кене, запрос с ТВ:
GET /catalog/card?plugin=%D0%9C%D0%BE%D0%B9%20%D1%81%D0%BF%D0%B8%D1%81%D0%BE%D0%BA&uri=movie%2F1280015&type=movie
HTTP/1.1 400 Bad Request
plugin=Мой список — ключ каталога брался из displayname.
ModInit.goInit (строки 51-52):
site = site.ToLowerAndTrim();
site = Regex.Replace(site, "[^a-z0-9\\-]", "", RegexOptions.IgnoreCase);
Кириллица вырезается ЦЕЛИКОМ → site = "" →
File.Exists("/sites/.yaml") = false →
FindSiteByDisplayName("") тоже мимо (пустой site не совпадёт с displayname) →
return null → init not found → HTTP 400 → в Lampa «ЗДЕСЬ ПУСТО».
🔴 Fallback по displayname НЕ РАБОТАЕТ с кириллицей — regex убивает имя
раньше, чем запускается поиск. Это баг/особенность lampac-nextgen.
ФИКС: задать plugin: watchlist в YAML. Строка ApiController.cs:136:
string siteKey = !string.IsNullOrEmpty(init.plugin) ? init.plugin : init.displayname ?? site;
заданный plugin переопределяет ключ каталога → ТВ зовёт plugin=watchlist → файл находится.
Приёмка:
| Ключ | Ответ |
|---|---|
plugin=Мой список |
400 init not found |
plugin=watchlist |
200, tmdb_id: 424781, source: "movie" |
⚠️ МЕТОДОЛОГИЧЕСКИЙ ВЫВОД (важно): я проверял карточку своим curl'ом с
plugin=watchlist и был уверен, что всё работает. Надо было ловить РЕАЛЬНЫЙ
запрос с устройства (tcpdump на порту сервиса). Урок: при «сервер отвечает 200,
а в UI пусто» — первым делом снять трафик между устройством и сервером.
Что сделано в этом заходе
plugin: watchlistвsites/watchlist.yaml— ключ каталога латиницей.card_argsсformatдля числовых полей:tmdb_id→long.TryParse,runtime→long.TryParse,vote_average→double.TryParse. Причина:ModInit.cs:285nodeValueвозвращает ЛЮБОЙJValueкак строку, аplugin.jsделаетif(json.tmdb_id)— нужен настоящийlong.deploy_lampac.py: проверка ключаwatchlist(не «Мой список»), проверка карточек по/catalog/card, заливкаcards/через tar.gen_watchlist_v2.py:tmdb_idчислом; дубли ключей (name+title,original+original_title,poster+img).
⏳ ОСТАЛОСЬ ДОБИТЬ
- Несколько фильмов всё ещё «здесь ничего нет» — «Рассекая волны», «Хамнет».
Похоже, для них нет
card-movie-NNN.json(TMDB не нашёл / Кинопоиск-фолбэк без tmdb id) →initUrlведёт на несуществующий файл. Лечить: фолбэк-карточка с тем, что есть (название+постер+поиск торрента), либо дотягивать tmdb_id по точному названию+году. - ~51 фильм без постера — TMDB не нашёл ни напрямую, ни через Кинопоиск.
Разобрать поштучно: возможно, помогают альтернативные названия (
original_title). - Секции CUB. В «Мой список» только 3 секции (Фильмы/Мультфильмы/Аниме),
«Новинки» куда-то делись? И главное — фильмов из CUB-фида в списке нет.
Требование Alex: секции должны быть ВНУТРИ CUB, без ручного переключения
источника. Механизм: хук
Listener.follow('menu')вplugin.js(строка 59) перехватываетmovie/cartoon/anime/tv/reliseи делаетe.abort(), НО работает только когда активен наш источник (Api.getCatalog()читает манифест текущего источника). Нужно доделать так, чтобы при источнике CUB наши секции добавлялись к CUB-фиду.