init: Lampa mymovies static server (nginx + CORS)
This commit is contained in:
commit
9214407a03
9 files changed
+306
No files matched your search
@@ -0,0 +1,5 @@
|
||||
.git
|
||||
.gitignore
|
||||
.dockerignore
|
||||
Dockerfile
|
||||
README.md
|
||||
@@ -0,0 +1,3 @@
|
||||
FROM nginx:alpine
|
||||
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||
COPY . /usr/share/nginx/html
|
||||
@@ -0,0 +1,71 @@
|
||||
# Lampa Plugin — «Мои фильмы»
|
||||
|
||||
Плагин добавляет в меню Lampa раздел **«Мои фильмы»**: каталог фильмов с обложками,
|
||||
описанием и торрентами. Клик по фильму → детали → «Смотреть» → воспроизведение
|
||||
через настроенный в Lampa torrserver (localhost:8090).
|
||||
|
||||
## Структура на сервере
|
||||
|
||||
Всё раздаётся по одному базовому URL (например `https://movies.example.com/`):
|
||||
|
||||
```
|
||||
server-root/
|
||||
├── catalog.json описание каталога (массив фильмов)
|
||||
├── covers/
|
||||
│ ├── inception.jpg
|
||||
│ └── interstellar.jpg
|
||||
├── torrents/
|
||||
│ ├── inception.torrent
|
||||
│ └── interstellar.torrent
|
||||
└── plugin.js (можно раздавать отсюда же или с отдельного хоста)
|
||||
```
|
||||
|
||||
`catalog.json` — см. `catalog.example.json`. Поля фильма:
|
||||
|
||||
| поле | обязательно | назначение |
|
||||
|-----------------|-------------|---------------------------------------------|
|
||||
| `id` | да | уникальный id |
|
||||
| `title` | да | название |
|
||||
| `original_title`| нет | оригинальное название |
|
||||
| `year` | нет | год |
|
||||
| `genre` | нет | массив жанров |
|
||||
| `rating` | нет | рейтинг |
|
||||
| `description` | нет | описание |
|
||||
| `poster` | нет | путь к обложке в `covers/` (или полный URL) |
|
||||
| `torrent` | да* | путь к `.torrent` в `torrents/` (или URL) |
|
||||
| `magnet` | нет | magnet-ссылка (приоритетнее `torrent`) |
|
||||
|
||||
\* нужен либо `torrent`, либо `magnet`.
|
||||
|
||||
## Требования к серверу
|
||||
|
||||
- Отдавать `catalog.json`, `covers/*`, `torrents/*` по HTTPS (или HTTP, если Lampa на
|
||||
том же устройстве).
|
||||
- **CORS**: заголовок `Access-Control-Allow-Origin: *` на `catalog.json` и обложки
|
||||
(Lampa грузит их из WebView). Торрент torrserver забирает сам — CORS для него не нужен,
|
||||
но файл должен отдаваться с корректным типом.
|
||||
- Любой статический хостинг: nginx, Caddy, GitHub Pages, хостинг торрент-бокса и т.п.
|
||||
|
||||
Пример nginx:
|
||||
|
||||
```nginx
|
||||
location / {
|
||||
add_header Access-Control-Allow-Origin *;
|
||||
try_files $uri $uri/ =404;
|
||||
}
|
||||
```
|
||||
|
||||
## Установка плагина в Lampa
|
||||
|
||||
1. Положи `plugin.js` на доступный по URL хост, запомни прямую ссылку
|
||||
(например `https://movies.example.com/plugin.js`).
|
||||
2. В Lampa: **Настройки → Плагины → Добавить плагин** → вставить URL `plugin.js` → OK.
|
||||
3. В **Настройки → Плагины → Мои фильмы** (параметр «Адрес сервера каталога»)
|
||||
вписать базовый URL сервера (с `/` на конце), напр. `https://movies.example.com/`.
|
||||
4. В главном меню появится пункт **«Мои фильмы»**.
|
||||
|
||||
## Поток воспроизведения
|
||||
|
||||
При нажатии «Смотреть» плагин передаёт torrent/magnet в `Lampa.Player`. Дальше
|
||||
работает уже настроенный в Lampa torrserver (локально `localhost:8090`), который
|
||||
раздаёт видеопоток плееру. Плагин сам torrserver не трогает.
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "Мой видеокаталог",
|
||||
"updated": "2026-08-08",
|
||||
"movies": [
|
||||
{
|
||||
"id": "inception",
|
||||
"title": "Начало",
|
||||
"original_title": "Inception",
|
||||
"year": 2010,
|
||||
"genre": ["фантастика", "боевик", "триллер"],
|
||||
"rating": 8.8,
|
||||
"description": "Дом Кобб — талантливый вор, специалист по извлечению секретов сквозь сновидения.",
|
||||
"poster": "covers/inception.jpg",
|
||||
"torrent": "torrents/inception.torrent"
|
||||
},
|
||||
{
|
||||
"id": "interstellar",
|
||||
"title": "Интерстеллар",
|
||||
"original_title": "Interstellar",
|
||||
"year": 2014,
|
||||
"genre": ["фантастика", "драма"],
|
||||
"rating": 8.6,
|
||||
"description": "Группа исследователей отправляется сквозь червоточину в поисках нового дома для человечества.",
|
||||
"poster": "covers/interstellar.jpg",
|
||||
"torrent": "torrents/interstellar.torrent"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "Мой видеокаталог",
|
||||
"updated": "2026-08-08",
|
||||
"movies": [
|
||||
{
|
||||
"id": "inception",
|
||||
"title": "Начало",
|
||||
"original_title": "Inception",
|
||||
"year": 2010,
|
||||
"genre": ["фантастика", "боевик", "триллер"],
|
||||
"rating": 8.8,
|
||||
"description": "Дом Кобб — талантливый вор, специалист по извлечению секретов сквозь сновидения.",
|
||||
"poster": "covers/inception.jpg",
|
||||
"torrent": "torrents/inception.torrent"
|
||||
},
|
||||
{
|
||||
"id": "interstellar",
|
||||
"title": "Интерстеллар",
|
||||
"original_title": "Interstellar",
|
||||
"year": 2014,
|
||||
"genre": ["фантастика", "драма"],
|
||||
"rating": 8.6,
|
||||
"description": "Группа исследователей отправляется сквозь червоточину в поисках нового дома для человечества.",
|
||||
"poster": "covers/interstellar.jpg",
|
||||
"torrent": "torrents/interstellar.torrent"
|
||||
}
|
||||
]
|
||||
}
|
||||
Whitespace-only changes.
+29
@@ -0,0 +1,29 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
# Каталог и обложки: нужен CORS для WebView Lampa
|
||||
location = /catalog.json {
|
||||
add_header Access-Control-Allow-Origin * always;
|
||||
default_type application/json;
|
||||
try_files /catalog.json =404;
|
||||
}
|
||||
|
||||
location /covers/ {
|
||||
add_header Access-Control-Allow-Origin * always;
|
||||
try_files $uri =404;
|
||||
}
|
||||
|
||||
# Торренты torrserver забирает сам (CORS не нужен), но отдадим тоже
|
||||
location /torrents/ {
|
||||
add_header Access-Control-Allow-Origin * always;
|
||||
types { application/x-bittorrent torrent; }
|
||||
try_files $uri =404;
|
||||
}
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ =404;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var PLUGIN = { name: 'mymovies', title: 'Мои фильмы' };
|
||||
var DEFAULT_BASE = 'https://your-server.com/';
|
||||
|
||||
function base() {
|
||||
var b = Lampa.Storage.get(PLUGIN.name + '_base', DEFAULT_BASE);
|
||||
return b.replace(/\/+$/, '') + '/';
|
||||
}
|
||||
function resolve(p) {
|
||||
if (!p) return '';
|
||||
if (/^https?:\/\//.test(p)) return p;
|
||||
if (/^magnet:/.test(p)) return p;
|
||||
return base() + p.replace(/^\/+/, '');
|
||||
}
|
||||
|
||||
// ---- Компонент: список фильмов ----
|
||||
Lampa.Component.add(PLUGIN.name, {
|
||||
create: function () {
|
||||
var comp = this;
|
||||
comp.activity.loader(true);
|
||||
var content = comp.activity.render().find('.main__content');
|
||||
content.html('<div class="mymovies"><div class="movies-grid"></div></div>');
|
||||
|
||||
fetch(base() + 'catalog.json')
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (data) {
|
||||
comp.activity.loader(false);
|
||||
renderMovies(content.find('.movies-grid'), data.movies || []);
|
||||
})
|
||||
.catch(function () {
|
||||
comp.activity.loader(false);
|
||||
Lampa.Noty.show('Не удалось загрузить каталог');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
function renderMovies(grid, movies) {
|
||||
grid.empty();
|
||||
if (!movies.length) {
|
||||
grid.append('<div class="mymovies-empty">Каталог пуст</div>');
|
||||
return;
|
||||
}
|
||||
movies.forEach(function (m) {
|
||||
var card = $('<div class="card selector" data-id="' + m.id + '">');
|
||||
card.append($('<img class="card__img">').attr('src', resolve(m.poster || m.cover)).attr('loading', 'lazy'));
|
||||
card.append($('<div class="card__title">').text(m.title));
|
||||
if (m.year) card.append($('<div class="card__subtitle">').text(m.year));
|
||||
card.on('hover:enter', function () { openMovie(m); });
|
||||
grid.append(card);
|
||||
});
|
||||
Lampa.Selector.update();
|
||||
}
|
||||
|
||||
// ---- Компонент: детали фильма ----
|
||||
var selected = null;
|
||||
Lampa.Component.add(PLUGIN.name + '_detail', {
|
||||
create: function () {
|
||||
var m = selected;
|
||||
if (!m) { Lampa.Activity.back(); return; }
|
||||
var html = '<div class="mymovies-detail">';
|
||||
html += '<div class="detail-head">';
|
||||
html += '<img class="detail-poster" src="' + resolve(m.poster || m.cover) + '">';
|
||||
html += '<div class="detail-info"><h1>' + esc(m.title) + '</h1>';
|
||||
if (m.original_title) html += '<div class="detail-orig">' + esc(m.original_title) + '</div>';
|
||||
if (m.year) html += '<div class="detail-year">' + m.year + '</div>';
|
||||
if (m.genre && m.genre.length) html += '<div class="detail-genre">' + m.genre.join(', ') + '</div>';
|
||||
if (m.rating) html += '<div class="detail-rating">★ ' + m.rating + '</div>';
|
||||
if (m.description) html += '<div class="detail-desc">' + esc(m.description) + '</div>';
|
||||
html += '</div></div>';
|
||||
html += '<div class="detail-actions"><div class="btn selector" data-act="play">Смотреть</div></div>';
|
||||
html += '</div>';
|
||||
|
||||
var content = this.activity.render().find('.main__content');
|
||||
content.html(html);
|
||||
content.find('[data-act="play"]').on('hover:enter', function () { play(m); });
|
||||
Lampa.Selector.update();
|
||||
}
|
||||
});
|
||||
|
||||
function openMovie(m) {
|
||||
selected = m;
|
||||
Lampa.Activity.push({
|
||||
url: '',
|
||||
component: PLUGIN.name + '_detail',
|
||||
title: m.title,
|
||||
page: m.title
|
||||
});
|
||||
}
|
||||
|
||||
function play(m) {
|
||||
var url = m.magnet ? m.magnet : resolve(m.torrent);
|
||||
Lampa.Player.play({
|
||||
url: url,
|
||||
title: m.title,
|
||||
img: resolve(m.poster || m.cover),
|
||||
season: 0,
|
||||
episode: 0
|
||||
});
|
||||
}
|
||||
|
||||
function esc(s) {
|
||||
return String(s == null ? '' : s)
|
||||
.replace(/&/g, '&').replace(/</g, '<')
|
||||
.replace(/>/g, '>').replace(/"/g, '"');
|
||||
}
|
||||
|
||||
// ---- Пункт меню ----
|
||||
function addMenuItem() {
|
||||
if ($('.menu__item[data-component="' + PLUGIN.name + '"]').length) return;
|
||||
var item = $('<div class="menu__item selector" data-component="' + PLUGIN.name + '">')
|
||||
.append($('<span>').text(PLUGIN.title));
|
||||
$('.menu__list').append(item);
|
||||
item.on('hover:enter', function () {
|
||||
Lampa.Activity.push({ url: '', component: PLUGIN.name, title: PLUGIN.title, page: PLUGIN.title });
|
||||
});
|
||||
Lampa.Selector.update();
|
||||
}
|
||||
|
||||
// ---- Настройка: адрес сервера ----
|
||||
Lampa.Settings.addParam({
|
||||
component: PLUGIN.name,
|
||||
param: 'base',
|
||||
name: 'Адрес сервера каталога',
|
||||
type: 'input',
|
||||
default: DEFAULT_BASE
|
||||
});
|
||||
|
||||
function start() {
|
||||
addMenuItem();
|
||||
Lampa.Listener.follow('app', function (e) {
|
||||
if (e.type === 'ready') addMenuItem();
|
||||
});
|
||||
Lampa.Listener.follow('menu', function (e) {
|
||||
if (e.type === 'open' || e.type === 'update') addMenuItem();
|
||||
});
|
||||
}
|
||||
|
||||
if (window.Lampa) start();
|
||||
else document.addEventListener('lampa', start);
|
||||
})();
|
||||
Whitespace-only changes.
Reference in new issue
Block a user