[2026-09-22] eagle: personal/plans/lampac-watchlist-plugin.md personal/tech/lampac-deployment.md
This commit is contained in:
1 parent
8441f3a4cb
commit
6746ac0990
2 files changed
+355
-8
No files matched your search
@@ -7,7 +7,7 @@ related:
|
|||||||
- '[[family/projects/watchlist-sync]]'
|
- '[[family/projects/watchlist-sync]]'
|
||||||
- '[[family/projects/watchlist-automation]]'
|
- '[[family/projects/watchlist-automation]]'
|
||||||
- '[[family/documents/movies-watchlist]]'
|
- '[[family/documents/movies-watchlist]]'
|
||||||
status: cub-sections-patched-awaiting-tv-restart
|
status: cub-sections-working-awaiting-tv-restart-deploy
|
||||||
tags:
|
tags:
|
||||||
- lampac
|
- lampac
|
||||||
- lampa
|
- lampa
|
||||||
@@ -90,16 +90,18 @@ updated: '2026-09-22'
|
|||||||
> Плюс устранён питфолл `Staticache` (кэш собранного `/catalog.js` переживал
|
> Плюс устранён питфолл `Staticache` (кэш собранного `/catalog.js` переживал
|
||||||
> заливку и выглядел как «патч не применяется» → `docker restart lampac`).
|
> заливку и выглядел как «патч не применяется» → `docker restart lampac`).
|
||||||
>
|
>
|
||||||
> **Осталось (2026-09-22, конец шестой сессии):**
|
> **Осталось (2026-09-22, конец СЕДЬМОЙ сессии):**
|
||||||
> 1. **Секции CUB в UI** — 🔴 инжект залит, но требовал **перезапуска Lampa на ТВ**
|
> 0. 🔴 **ЗАЛИТЬ `plugin-cub.js` НА КРА́КЕН + ПЕРЕЗАПУСТИТЬ LAMPA НА ТВ.** Механизм
|
||||||
> (причина «ничего не поменялось» — `Manifest` в обёртке, см. выше). После перезапуска
|
> (`partNext`) **проверен на живом ТВ через CDP** — секции рендерятся в DOM главной CUB
|
||||||
> проверить через `probe_cub_main.py`: должно быть **8 секций** (4 родные CUB + 4 наши).
|
> (подробности + список probe-скриптов — раздел «СЕДЬМАЯ СЕССИЯ»). Команда деплоя ждёт
|
||||||
> 2. **5 фильмов с Кинопоиск-ID** (`film/NNNNN/`) — «Рассекая волны: экспедиция „Лас Бальсас“»,
|
> апрува: `scp plugin-cub.js kraken:.../catalog/plugin.js` → `docker restart lampac` →
|
||||||
|
> `sh /tmp/tv_restart.sh` → `python3 probe_ui.py`.
|
||||||
|
> 1. **5 фильмов с Кинопоиск-ID** (`film/NNNNN/`) — «Рассекая волны: экспедиция „Лас Бальсас“»,
|
||||||
> «Хамнет», «Эхопраксия», «Цена вершины», «Астрал. Могильный ритуал 2», «О добре и зле»,
|
> «Хамнет», «Эхопраксия», «Цена вершины», «Астрал. Могильный ритуал 2», «О добре и зле»,
|
||||||
> WeWork (2024).
|
> WeWork (2024).
|
||||||
> TMDB не нашёл (2025 год) → нет карточки и постера → клик даёт «здесь пусто».
|
> TMDB не нашёл (2025 год) → нет карточки и постера → клик даёт «здесь пусто».
|
||||||
> **Нужен выбор Alex:** заглушка с кнопкой поиска торрента либо дорисовать `tmdb_id`.
|
> **Нужен выбор Alex:** заглушка с кнопкой поиска торрента либо дорисовать `tmdb_id`.
|
||||||
> 3. **~51 название без постера** — проблема ЗАПИСИ В СПИСКЕ, не скрипта:
|
> 2. **~51 название без постера** — проблема ЗАПИСИ В СПИСКЕ, не скрипта:
|
||||||
> `Плохие парни 1/2` (ДВА фильма одной строкой), `Солярис Тарковский` (уточнение
|
> `Плохие парни 1/2` (ДВА фильма одной строкой), `Солярис Тарковский` (уточнение
|
||||||
> режиссёра), `Самолетом, поездом, машиной (1987)` (русский аналог).
|
> режиссёра), `Самолетом, поездом, машиной (1987)` (русский аналог).
|
||||||
> Генератор такое угадать не может — **правится только список руками**.
|
> Генератор такое угадать не может — **правится только список руками**.
|
||||||
@@ -220,6 +222,185 @@ updated: '2026-09-22'
|
|||||||
> **Не тронуто, ждёт решения Alex:** 310 фильмов из списка, которых нет в Radarr
|
> **Не тронуто, ждёт решения Alex:** 310 фильмов из списка, которых нет в Radarr
|
||||||
> (51 без постера, идут через фолбэк Кинопоиска). Без апрува не делать.
|
> (51 без постера, идут через фолбэк Кинопоиска). Без апрува не делать.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔴🔴 СЕДЬМАЯ СЕССИЯ: РАБОЧИЙ МЕХАНИЗМ НАЙДЕН — ПЕРЕХВАТ `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`:
|
||||||
|
|
||||||
|
```js
|
||||||
|
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`):
|
||||||
|
```json
|
||||||
|
{"Manifest_global":"undefined","Lampa":"object",
|
||||||
|
"Utils":"undefined","network":"undefined"}
|
||||||
|
```
|
||||||
|
Все три — **локальные переменные плагина**. Первая версия инжекта читала
|
||||||
|
`Manifest.catalogs['watchlist']` напрямую → `ReferenceError` → `catch` → молча
|
||||||
|
отдавался родной фид. **Ошибки нет ни в UI, ни в логах.**
|
||||||
|
|
||||||
|
**❌ Баг 3: `Utils.account()` без `Manifest.api_host` подставляет `{localhost}`.**
|
||||||
|
|
||||||
|
```js
|
||||||
|
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 собирает главную:
|
||||||
|
|
||||||
|
```js
|
||||||
|
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`.
|
||||||
|
|
||||||
|
```js
|
||||||
|
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:9118`
|
||||||
|
- `wlAbs(url)` — склейка host + относительный путь **вручную**, `Utils.account` НЕ использовать
|
||||||
|
- `wlGet(url, ok, fail)` — `new Lampa.Reguest().silent(absoluteURL, ...)`, фолбэк `fetch`
|
||||||
|
- `window.__wl_menu` — заполняется в `loadCatalogs` при загрузке `/catalog`; фолбэк — сетевой запрос
|
||||||
|
|
||||||
|
### Сборщик `build_plugin.py` (детерминированный, заменяет `patch_cub_inject.py`)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
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**:
|
||||||
|
```bash
|
||||||
|
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` НЕ установлен):
|
||||||
|
```python
|
||||||
|
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`:
|
||||||
|
|
||||||
|
```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)
|
## 🔴 КАРТОЧКА: механика card_parse (разобрано по коду 2026-09-22)
|
||||||
|
|
||||||
Клик по элементу каталога ведёт в `/catalog/card?plugin=watchlist&uri=<id>&type=movie`.
|
Клик по элементу каталога ведёт в `/catalog/card?plugin=watchlist&uri=<id>&type=movie`.
|
||||||
|
|||||||
@@ -216,6 +216,168 @@ id = href,
|
|||||||
`http://{localhost}:{port}/tmdb/api/3/{cat}/{id}?api_key={CoreInit.conf.cub.api_key}`
|
`http://{localhost}:{port}/tmdb/api/3/{cat}/{id}?api_key={CoreInit.conf.cub.api_key}`
|
||||||
— ключ уже внутри образа, отдельный TMDB-ключ для карточек не нужен.
|
— ключ уже внутри образа, отдельный TMDB-ключ для карточек не нужен.
|
||||||
|
|
||||||
|
### 🔴🔴 СЕДЬМАЯ СЕССИЯ: как РЕАЛЬНО внедрить секции в главную CUB (`partNext`)
|
||||||
|
|
||||||
|
**Каталог и карточки закрыты. Рабочий механизм секций найден: перехват `Lampa.Api.partNext`.**
|
||||||
|
|
||||||
|
#### Почему обёртка `Api.sources.cub.main` НЕ РАБОТАЕТ (three-strike)
|
||||||
|
|
||||||
|
Родной `main$1()` (@1257163 в `app.min.js`) объявлен **внутри замыкания Lampa** и опирается
|
||||||
|
на локальные `permit`, `get$7`, `Lang`, `permit.child_small`, `Helper$b`:
|
||||||
|
|
||||||
|
```js
|
||||||
|
function main$1() {
|
||||||
|
var parts_limit = 6;
|
||||||
|
var parts_data = [
|
||||||
|
function (call) { get$7('?sort=now_playing', params, function (json) {
|
||||||
|
json.title = Lang.translate('title_now_watch'); call(json); }, call, {life: day*2}); },
|
||||||
|
function (call) { if (permit.child_small) return call(); ... }
|
||||||
|
];
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| Симптом | Замер CDP | Корень |
|
||||||
|
|---|---|---|
|
||||||
|
| `cub.main({})` → вечный `pending`, 0 fetch | `{"pending":true}` через 15 с | `permit` = **undefined** вне замыкания → падение на первой проверке → callback не зовётся |
|
||||||
|
| секции не добавлялись, ошибок нет | `{"cubMainSrc":"...Manifest.catalogs['watchlist']..."}` | `Manifest` / `Utils` / `network` = **undefined** вне замыкания → `ReferenceError` → `catch` → родной фид |
|
||||||
|
| «Нет подключения к сети» в логе ТВ | `{localhost}/catalog?uid=...&token={token}` | `Utils.account()` без `Manifest.api_host` подставляет `{localhost}` |
|
||||||
|
|
||||||
|
🔴 **Вывод: `cub.main` невызываем извне by design.** Обёртка — тупик, не «недоработка».
|
||||||
|
|
||||||
|
#### ✅ Правильный механизм: `Lampa.Api.partNext`
|
||||||
|
|
||||||
|
```js
|
||||||
|
// глобальная функция Lampa; CUB передаёт сюда parts_data — массив функций-секций
|
||||||
|
function partNext(parts, parts_limit, partLoaded, partEmpty) {
|
||||||
|
var pieces = parts.filter(function (p) { return typeof p == 'function'; })
|
||||||
|
.slice(0, 0 + parts_limit);
|
||||||
|
if (pieces.length) {
|
||||||
|
new Progress().append(pieces).start(function (result) {
|
||||||
|
var data = result.filter(function (r) { return r && r.results && r.results.length; });
|
||||||
|
...
|
||||||
|
});
|
||||||
|
} else partEmpty();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Патч добавляет свои функции-секции в `parts` **до** вызова оригинала:
|
||||||
|
|
||||||
|
```js
|
||||||
|
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);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Секция = функция `call(json)`, теми же контрактами, что родные CUB:
|
||||||
|
|
||||||
|
```js
|
||||||
|
function wlSection(name, url) {
|
||||||
|
return function (call) {
|
||||||
|
wlGet(url, function (json) {
|
||||||
|
json.title = name;
|
||||||
|
json.source = 'watchlist'; // ← обязательно, иначе карточка не найдётся
|
||||||
|
if (Lampa.Arrays.isArray(json.results))
|
||||||
|
json.results.forEach(function (it) { it.source = 'watchlist'; });
|
||||||
|
call(json);
|
||||||
|
}, function () { call(); }); // ошибка → пустой вызов (не ломает пул)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Преимущество:** работает на **любой** странице, независимо от источника, без `permit`,
|
||||||
|
без `params`. Родной CUB не подменяется — секции добавляются в общий пул.
|
||||||
|
|
||||||
|
#### 🔴 Только ГЛОБАЛЬНЫЕ объекты — внутри замыкания `Manifest`/`Utils`/`network` нет
|
||||||
|
|
||||||
|
| Нужно | ❌ Не работает в этом контексте | ✅ Работает |
|
||||||
|
|---|---|---|
|
||||||
|
| api_host | `Manifest.api_host` | `Lampa.Manifest.api_host` (фолбэк — хардкод `http://192.168.1.14:9118`) |
|
||||||
|
| URL-склейка | `Utils.account(url)` → `{localhost}` | **ручная:** `wlAbs(url)` = host + path |
|
||||||
|
| HTTP | `network.silent(...)` | `new Lampa.Reguest().silent(absURL, ...)`, фолбэк `fetch` |
|
||||||
|
| список секций | `Manifest.catalogs['watchlist'].menu` | `window.__wl_menu` (заполняется в `loadCatalogs` из `/catalog`) |
|
||||||
|
|
||||||
|
```js
|
||||||
|
function wlAbs(url) {
|
||||||
|
if (!url) return '';
|
||||||
|
if (/^https?:\/\//i.test(url)) return url;
|
||||||
|
return wlHost() + (url.charAt(0) === '/' ? url : '/' + url);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Сборщик `build_plugin.py`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /Users/admin/lampac-tmp
|
||||||
|
python3 build_plugin.py # plugin-patched.js + INJECT → plugin-cub.js (14376 Б)
|
||||||
|
node --check plugin-cub.js
|
||||||
|
```
|
||||||
|
|
||||||
|
Детерминирован: режет `[до startPlugin] + INJECT + [новый startPlugin] + '})();'`,
|
||||||
|
проверяет инварианты (`INJECT=1`, `def=1`, `startPlugin=1`, `network.silent в INJECT=0`).
|
||||||
|
|
||||||
|
🔴 **Питфолл прошлого сборщика `patch_cub_inject.py`:** проверки `"X" not in s` шли
|
||||||
|
**по уже модифицированной строке** → повторный запуск вставлял INJECT дважды → плюс лишняя
|
||||||
|
`}` от вырезания → `SyntaxError: Unexpected token '}'` при балансе скобок `{: 123, }: 122`.
|
||||||
|
**Всегда проверять `node --check` + баланс скобок ПЕРЕД заливкой.**
|
||||||
|
|
||||||
|
#### 🔴 CDP-пробы на ТВ (порт 9998) — основной инструмент отладки
|
||||||
|
|
||||||
|
```bash
|
||||||
|
WS=$(curl -s "http://192.168.1.70:9998/json/list" \
|
||||||
|
| jq -r '.[] | select(.title=="Lampac") | .webSocketDebuggerUrl')
|
||||||
|
python3 probe_ui.py "$WS"
|
||||||
|
```
|
||||||
|
|
||||||
|
Питон-клиент: `websocket-client` (`import websocket`). Модуль `websockets` **не установлен**.
|
||||||
|
`Runtime.evaluate` с `returnByValue: True, awaitPromise: True`.
|
||||||
|
|
||||||
|
| Скрипт | Назначение |
|
||||||
|
|---|---|
|
||||||
|
| `probe_ui.py` | **приёмка:** `Lampa.Router.call('main')` → чтение заголовков из DOM |
|
||||||
|
| `probe_inject.py` | инжект `plugin-cub.js` в рантайм **без деплоя** (быстрая итерация) |
|
||||||
|
| `probe_globals.py` | видимость `Manifest`/`Utils`/`network` |
|
||||||
|
| `probe_cub_main.py` | сколько секций отдаёт `cub.main` |
|
||||||
|
| `probe_wait.py` | ждёт результат + перехват `console.log` |
|
||||||
|
| `probe_tv.py`, `probe_cache.py`, `probe_calls.py` | состояние, кэш, перехват fetch |
|
||||||
|
|
||||||
|
🔴 **`eval()` внутри IIFE не создаёт глобальных функций.** После `eval(pluginCode)`
|
||||||
|
`wlHost()` снаружи → `ReferenceError: wlHost is not defined`. Это **нормально** —
|
||||||
|
`cub.main` их видит (одно замыкание), внешние пробы нет. Проверять только через
|
||||||
|
`Lampa.Api.main()` либо чтение DOM.
|
||||||
|
|
||||||
|
#### 🔴 Перезапуск Lampa на ТВ: `luna-send` требует файла, не inline-JSON
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# ❌ печатает help: аргументы JSON не проходят через ssh + script -q -c
|
||||||
|
ssh root@192.168.1.70 'script -q -c "luna-send ... -a {\"id\":\"com.lampac.tv\"}" /dev/null'
|
||||||
|
|
||||||
|
# ✅ положить файл на ТВ и выполнить
|
||||||
|
scp tv_restart.sh root@192.168.1.70:/tmp/
|
||||||
|
ssh root@192.168.1.70 'sh /tmp/tv_restart.sh'
|
||||||
|
```
|
||||||
|
|
||||||
|
```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"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
🔴 **`docker restart lampac` НЕ перезагружает плагин в памяти ТВ** (`catalog.js` кэширован
|
||||||
|
браузером webOS). Признак: `String(Lampa.Api.sources.cub.main)` содержит старый код.
|
||||||
|
Полный порядок деплоя: `scp plugin.js` → `chmod 644`/`chown 1000:1000` → `rm ._*` →
|
||||||
|
`docker restart lampac` (сброс `Staticache`) → **`sh /tmp/tv_restart.sh`** → `probe_ui.py`.
|
||||||
|
|
||||||
### 🔴 Питфолл №6: карточка без `card_parse` даёт HTTP 500
|
### 🔴 Питфолл №6: карточка без `card_parse` даёт HTTP 500
|
||||||
|
|
||||||
Без блока `card_parse` Catalog строит URL карточки как `$"{init.host}/{uri}"`
|
Без блока `card_parse` Catalog строит URL карточки как `$"{init.host}/{uri}"`
|
||||||
@@ -279,12 +441,16 @@ curl -s -o /tmp/c.json -w "HTTP %{http_code}\n" \
|
|||||||
|
|
||||||
Лежит **в образе** (`/lampac/module/Catalog/plugin.js`), не на HDD. Что делает:
|
Лежит **в образе** (`/lampac/module/Catalog/plugin.js`), не на HDD. Что делает:
|
||||||
|
|
||||||
- `main()` — итерирует `catalog.main`, собирает секции **СВЕРХУ** родного каталога
|
- `main()` — итерирует `catalog.main`, собирает секции **СВЕРХУ** родного каталога.
|
||||||
|
🔴 **НО:** работает это только при выбранном нашем источнике, потому что `Api.getCatalog()`
|
||||||
|
читает манифест **текущего** источника. Для секций поверх CUB нужен перехват
|
||||||
|
`Lampa.Api.partNext` — см. «СЕДЬМАЯ СЕССИЯ» выше.
|
||||||
- `menu()` — итерирует `catalog.menu` из YAML
|
- `menu()` — итерирует `catalog.menu` из YAML
|
||||||
- `full()`/`list()`/`search()` — карточка, категория, поиск
|
- `full()`/`list()`/`search()` — карточка, категория, поиск
|
||||||
- `Lampa.Listener.follow('menu', ...)` + `e.abort()` — **перехват кликов** по родным
|
- `Lampa.Listener.follow('menu', ...)` + `e.abort()` — **перехват кликов** по родным
|
||||||
разделам (`movie`/`tv`/`cartoon`/`anime`/`relise`). Срабатывает **только когда
|
разделам (`movie`/`tv`/`cartoon`/`anime`/`relise`). Срабатывает **только когда
|
||||||
выбран источник «Мой список»**, поэтому родной CUB не трогается при других источниках
|
выбран источник «Мой список»**, поэтому родной CUB не трогается при других источниках
|
||||||
|
- `loadCatalogs()` — сохраняет `window.__wl_menu` из `/catalog` (нужно патчу `partNext`)
|
||||||
|
|
||||||
### 🔴 Питфолл №6: источник не виден в UI (`enumerable: false`)
|
### 🔴 Питфолл №6: источник не виден в UI (`enumerable: false`)
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user