598 lines
28 KiB
JavaScript
598 lines
28 KiB
JavaScript
(function () {
|
|
'use strict';
|
|
|
|
var PLUGIN = { name: 'mymovies', title: 'mymovies_title', menu_action: 'mymovies' };
|
|
var DEFAULT_BASE = 'https://movies-lmp-18.dynv6.net/';
|
|
|
|
// ---------- Remote logger (always on) ----------
|
|
// Forwards console.* from the device to the server so logs are visible centrally.
|
|
// Device is identified by userAgent + Lampa device/platform + server-seen IP.
|
|
(function () {
|
|
try {
|
|
var LOG_URL = 'https://movies-lmp-18.dynv6.net:8899/mymovies-log';
|
|
function deviceInfo() {
|
|
var ua = navigator.userAgent || 'unknown';
|
|
var dev = '';
|
|
try { dev = (Lampa.Device && (Lampa.Device.name || Lampa.Device.platform)) || ''; } catch (e) {}
|
|
if (!dev) { try { dev = (window.Platform && window.Platform.name && window.Platform.name()) || ''; } catch (e) {} }
|
|
return ua + (dev ? ' [' + dev + ']' : '');
|
|
}
|
|
function send(level, args) {
|
|
var msg = [].map.call(args, function (a) {
|
|
try { return (typeof a === 'object') ? JSON.stringify(a) : String(a); }
|
|
catch (e) { return String(a); }
|
|
}).join(' ');
|
|
var payload = 'level=' + encodeURIComponent(level) +
|
|
'&dev=' + encodeURIComponent(deviceInfo()) +
|
|
'&msg=' + encodeURIComponent(msg);
|
|
// fire-and-forget; ignore failures (e.g. offline)
|
|
var img = new Image();
|
|
img.src = LOG_URL + '?' + payload + '&_=' + Date.now();
|
|
}
|
|
var orig = {
|
|
log: console.log.bind(console),
|
|
warn: console.warn.bind(console),
|
|
error: console.error.bind(console),
|
|
info: console.info.bind(console)
|
|
};
|
|
console.log = function () { send('log', arguments); orig.log.apply(console, arguments); };
|
|
console.warn = function () { send('warn', arguments); orig.warn.apply(console, arguments); };
|
|
console.error = function () { send('error', arguments); orig.error.apply(console, arguments); };
|
|
console.info = function () { send('info', arguments); orig.info.apply(console, arguments); };
|
|
window.onerror = function (m, s, l, c, e) { send('error', ['[onerror]', m, s + ':' + l]); };
|
|
} catch (e) {}
|
|
})();
|
|
|
|
console.log('[mymovies] plugin loaded', PLUGIN.name, 'base', DEFAULT_BASE);
|
|
|
|
// Forward torrent download progress to the remote log.
|
|
// PlayerInfo emits 'stat' while Torserver preloads the torrent; we mirror it
|
|
// into console.log so the central logger captures % / peers / speed.
|
|
// Registered once globally (not per play) to avoid duplicate listeners.
|
|
try {
|
|
if (Lampa.PlayerInfo && Lampa.PlayerInfo.listener) {
|
|
Lampa.PlayerInfo.listener.follow('stat', function (e) {
|
|
if (!e || !e.data) return;
|
|
var d = e.data;
|
|
if (d.preloaded_bytes == null && d.active_peers == null) return;
|
|
var pb = d.preloaded_bytes || 0,
|
|
ps = d.preload_size || 0,
|
|
prog = ps ? Math.min(100, Math.round(pb * 100 / ps)) : 0;
|
|
console.log('[mymovies] torrent', prog + '%',
|
|
'peers:', d.active_peers || 0,
|
|
'speed:', d.download_speed || 0);
|
|
});
|
|
}
|
|
} catch (err) {}
|
|
|
|
// Localized strings. Register once, then use Lampa.Lang.translate(KEY).
|
|
var STRINGS = {
|
|
mymovies_title: { ru: 'Мои фильмы', en: 'My Movies' }
|
|
};
|
|
function t(key) {
|
|
try { return Lampa.Lang.translate(key); } catch (e) { return key; }
|
|
}
|
|
|
|
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(/^\/+/, '');
|
|
}
|
|
|
|
// Append a cache-busting param so Lampa.Reguest does not serve a stale copy.
|
|
function bust(url) {
|
|
var sep = url.indexOf('?') >= 0 ? '&' : '?';
|
|
return url + sep + '_=' + Date.now();
|
|
}
|
|
|
|
function esc(s) {
|
|
return String(s == null ? '' : s)
|
|
.replace(/&/g, '&').replace(/</g, '<')
|
|
.replace(/>/g, '>').replace(/"/g, '"');
|
|
}
|
|
|
|
// Load catalog via Lampa native network layer (TV-safe, fetch fails on some devices)
|
|
function loadCatalog(cb) {
|
|
var url = bust(base() + 'movies/catalog.json');
|
|
if (Lampa.Reguest && Lampa.Reguest.get) {
|
|
Lampa.Reguest.get(url, function (html) {
|
|
try {
|
|
cb(null, JSON.parse(html));
|
|
} catch (e) {
|
|
cb(e, null);
|
|
}
|
|
}, function (err) {
|
|
cb(err || new Error('request failed'), null);
|
|
});
|
|
} else {
|
|
fetch(url).then(function (r) { return r.json(); })
|
|
.then(function (d) { cb(null, d); })
|
|
.catch(function (e) { cb(e, null); });
|
|
}
|
|
}
|
|
|
|
// Plugin styles.
|
|
// NOTE: Lampa.Template.add() only stores the string in memory — it does NOT
|
|
// inject <style> into the page. So we inject CSS manually into <head>.
|
|
// Movie cards use NATIVE Lampa markup/styles (.card / .card__img / .card__title),
|
|
// so they look identical to every other Lampa section.
|
|
function injectStyles() {
|
|
if (window.__mymovies_css_injected) return;
|
|
window.__mymovies_css_injected = true;
|
|
|
|
// Inject CSS into <head> so it actually applies.
|
|
// Only layout wrappers; the movie cards and detail screen use NATIVE Lampa
|
|
// styles (.card / .full-start-new / .full-descr / .full-start__button).
|
|
var css =
|
|
'.mymovies{padding:2.2em 3.4em;overflow-y:auto;height:100%;box-sizing:border-box}' +
|
|
// Native Lampa catalog layout: flex-wrap rows with equal-width cards
|
|
// and gaps via card margin (same look as every other Lampa section).
|
|
'.movies-grid{display:flex;flex-wrap:wrap;align-content:flex-start}' +
|
|
'.movies-grid .card{width:calc(100% / 6 - 1.4em);margin:0.7em}' +
|
|
'.mymovies-detail{padding:2em 3em;box-sizing:border-box}' +
|
|
'.mymovies-detail .full-start-new__img{opacity:1 !important}' +
|
|
// Follow Lampa "simplified interface" (light--version): square corners like catalog cards
|
|
'body.light--version .mymovies-detail .full-start-new__img{border-radius:0 !important}' +
|
|
'body.light--version .mymovies-detail .full-start-new__poster{border-radius:0 !important}' +
|
|
'.mymovies-detail .full-start__button.button--book{min-width:150px;justify-content:center}' +
|
|
'.mymovies-detail .full-start__button.button--book span{display:inline !important}' +
|
|
'.mymovies-empty{padding:2em;opacity:.6;font-size:1.2em}';
|
|
$('<style id="mymovies-style">').text(css).appendTo('head');
|
|
}
|
|
|
|
// ---------- Component: movie list ----------
|
|
// Native Lampa cards + native navigation (Controller/Navigator). Scrolling uses
|
|
// the standard overflow container so cards stay in view (Lampa.Scroll was dropped
|
|
// because it hijacks scrollTop and needs internal Item classes we don't replicate).
|
|
function MyMovies() {
|
|
var comp = this;
|
|
var body;
|
|
var grid;
|
|
|
|
// Keep the focused card visible while navigating down/up (native Lampa behaviour).
|
|
// Also remember the focused card + scroll offset so we can restore it on return.
|
|
function syncSelection() {
|
|
var el = document.querySelector('.mymovies .card.selector.focus');
|
|
var cont = document.querySelector('.mymovies');
|
|
if (el) comp._selectedId = el.getAttribute('data-id');
|
|
if (cont) comp._scrollTop = cont.scrollTop;
|
|
}
|
|
// Smooth scroll (native Lampa feel) via JS tween of scrollTop.
|
|
// Works on every platform (CSS transition of scrollTop is unreliable).
|
|
function smoothScrollTo(cont, target) {
|
|
target = Math.max(0, Math.min(target, cont.scrollHeight - cont.clientHeight));
|
|
var start = cont.scrollTop;
|
|
var dist = target - start;
|
|
if (!dist) return;
|
|
var dur = 220, t0 = performance.now();
|
|
if (cont.__scroll_raf) cancelAnimationFrame(cont.__scroll_raf);
|
|
function step(now) {
|
|
var p = Math.min(1, (now - t0) / dur);
|
|
// ease-out cubic
|
|
var e = 1 - Math.pow(1 - p, 3);
|
|
cont.scrollTop = start + dist * e;
|
|
if (p < 1) cont.__scroll_raf = requestAnimationFrame(step);
|
|
}
|
|
cont.__scroll_raf = requestAnimationFrame(step);
|
|
}
|
|
|
|
function scrollFocused() {
|
|
var cont = document.querySelector('.mymovies');
|
|
var el = document.querySelector('.mymovies .card.selector.focus');
|
|
if (cont && el) {
|
|
var delta = el.getBoundingClientRect().top - cont.getBoundingClientRect().top;
|
|
smoothScrollTo(cont, cont.scrollTop + delta - 100);
|
|
comp._selectedId = el.getAttribute('data-id');
|
|
comp._scrollTop = cont.scrollTop;
|
|
}
|
|
}
|
|
|
|
this.create = function (b) {
|
|
body = b;
|
|
// Back (ESC / Backspace) is handled natively by Lampa -> Controller.back().
|
|
// Our keydown listener only drives scroll on arrow navigation to avoid a
|
|
// double-back (our call + Lampa's native call) that skips a screen.
|
|
comp._onKey = function (e) {
|
|
if (e.key === 'ArrowDown' || e.key === 'ArrowUp' ||
|
|
e.key === 'ArrowLeft' || e.key === 'ArrowRight') {
|
|
setTimeout(function () { scrollFocused(); syncSelection(); }, 30);
|
|
}
|
|
};
|
|
document.addEventListener('keydown', comp._onKey, true);
|
|
};
|
|
|
|
this.destroy = function () {
|
|
if (comp._onKey) document.removeEventListener('keydown', comp._onKey, true);
|
|
};
|
|
|
|
this.render = function () {
|
|
injectStyles();
|
|
var html = document.createElement('div');
|
|
html.className = 'mymovies';
|
|
grid = document.createElement('div');
|
|
grid.className = 'movies-grid';
|
|
html.appendChild(grid);
|
|
|
|
if (comp.activity) comp.activity.loader(true);
|
|
|
|
loadCatalog(function (err, data) {
|
|
if (comp.activity) comp.activity.loader(false);
|
|
if (err) {
|
|
console.error('[mymovies] catalog error', err);
|
|
grid.innerHTML = '<div class="mymovies-empty">Catalog load error</div>';
|
|
return;
|
|
}
|
|
var movies = (data && data.movies) || [];
|
|
movies = sortMovies(movies);
|
|
comp._movies = movies;
|
|
window.__mymovies_catalog = movies;
|
|
console.log('[mymovies] loaded', movies.length, 'films');
|
|
renderMovies(movies);
|
|
// Focus the first card once it exists (start() may have run before load).
|
|
Lampa.Controller.toggle('content');
|
|
});
|
|
|
|
return html;
|
|
};
|
|
|
|
// Required by ActivitySlide contract: wire remote/keyboard navigation.
|
|
// Cards are native .selector elements; Navigator moves focus, we scroll the
|
|
// container so the focused card stays visible (native Lampa behaviour).
|
|
// On return from detail we restore the previously focused card + scroll offset.
|
|
this.start = function () {
|
|
var started = false;
|
|
Lampa.Controller.add('content', {
|
|
toggle: function () {
|
|
var wrap = $('.mymovies');
|
|
var cards = wrap.find('.card.selector');
|
|
if (!cards.length) return;
|
|
Lampa.Controller.collectionSet(wrap);
|
|
// If no card is currently focused, focus one (restore last, else first).
|
|
// This self-heals a lost cursor after returning from detail / menu.
|
|
var focused = wrap.find('.card.selector.focus');
|
|
if (!focused.length) {
|
|
started = false;
|
|
}
|
|
if (started) {
|
|
syncSelection();
|
|
return;
|
|
}
|
|
started = true;
|
|
console.log('[mymovies] catalog opened, films:', (comp._movies || []).length);
|
|
var target = comp._selectedId
|
|
? cards.filter('[data-id="' + comp._selectedId + '"]')[0]
|
|
: cards[0];
|
|
if (!target) target = cards[0];
|
|
Lampa.Controller.collectionFocus(target, wrap);
|
|
if (comp._scrollTop) {
|
|
var cont = wrap[0];
|
|
smoothScrollTo(cont, comp._scrollTop);
|
|
}
|
|
},
|
|
left: function () { if (Navigator.canmove('left')) Navigator.move('left'); else Lampa.Controller.toggle('menu'); },
|
|
right: function () { Navigator.move('right'); Lampa.Controller.collectionFocus(document.querySelector('.mymovies .card.selector.focus'), $('.mymovies')); },
|
|
up: function () { Navigator.move('up'); },
|
|
down: function () { Navigator.move('down'); },
|
|
back: function () { Lampa.Activity.backward(); }
|
|
});
|
|
Lampa.Controller.toggle('content');
|
|
};
|
|
|
|
// Build each card and append to the grid.
|
|
function renderMovies(movies) {
|
|
grid.empty();
|
|
if (!movies.length) {
|
|
grid.append('<div class="mymovies-empty">Catalog is empty</div>');
|
|
return;
|
|
}
|
|
movies.forEach(function (m) {
|
|
var card = $(
|
|
'<div class="card selector card--loaded" data-id="' + esc(m.id) + '">' +
|
|
'<div class="card__view">' +
|
|
'<img src="' + resolve(m.poster || m.cover) + '" class="card__img" />' +
|
|
'<div class="card__icons"><div class="card__icons-inner"></div></div>' +
|
|
'</div>' +
|
|
'<div class="card__title">' + esc(m.title || m.id) + '</div>' +
|
|
(m.year ? '<div class="card__age">' + esc(String(m.year)) + '</div>' : '') +
|
|
'</div>');
|
|
card.on('hover:enter', function () { openMovie(m); });
|
|
grid.append(card);
|
|
});
|
|
}
|
|
}
|
|
Lampa.Component.add(PLUGIN.name, MyMovies);
|
|
|
|
// Keep user-defined display order (Supergirl, Interstellar, Inception, Matrix)
|
|
var DESIRED_ORDER = ['supergirl-2026', 'interstellar', 'inception', 'matrix'];
|
|
function sortMovies(movies) {
|
|
return movies.slice().sort(function (a, b) {
|
|
var ia = DESIRED_ORDER.indexOf(a.id);
|
|
var ib = DESIRED_ORDER.indexOf(b.id);
|
|
if (ia === -1) ia = 999;
|
|
if (ib === -1) ib = 999;
|
|
return ia - ib;
|
|
});
|
|
}
|
|
|
|
// ---------- Component: movie detail ----------
|
|
var selected = null;
|
|
function resolveMovie(m) {
|
|
// Lampa sometimes wraps the pushed object; normalize to the real film.
|
|
if (m && m.object && (m.object.id || m.object.poster || m.object.title)) return m.object;
|
|
return m;
|
|
}
|
|
function MyMoviesDetail() {
|
|
var comp = this;
|
|
var body;
|
|
|
|
this.create = function (b) {
|
|
body = b;
|
|
// Back (ESC / Backspace) is handled natively by Lampa -> Controller.back(),
|
|
// which calls our controller's `back` below. No keydown listener needed
|
|
// (a listener here would duplicate the native back and skip a screen).
|
|
};
|
|
|
|
this.destroy = function () {
|
|
};
|
|
|
|
this.start = function () {
|
|
var apply = function () {
|
|
var btns = $('.mymovies-detail .full-start-new__buttons .full-start__button.selector');
|
|
if (!btns.length) return;
|
|
// Register the controller only once. Repeated Lampa.Controller.add('content')
|
|
// + toggle() re-applies focus to btns[0] and causes the button to "jump".
|
|
if (!comp._detailBound) {
|
|
comp._detailBound = true;
|
|
console.log('[mymovies] detail opened:', (function () {
|
|
var m = selected || (comp.activity && comp.activity.object);
|
|
m = resolveMovie(m);
|
|
return (m && (m.title || m.id)) || '?';
|
|
})());
|
|
Lampa.Controller.add('content', {
|
|
toggle: function () {
|
|
Lampa.Controller.collectionSet($('.mymovies-detail .full-start-new__buttons'));
|
|
// Only focus the first button if nothing is focused yet
|
|
// (avoids re-focusing / flicker on every toggle).
|
|
if (!$('.mymovies-detail .full-start-new__buttons .full-start__button.selector.focus').length) {
|
|
Lampa.Controller.collectionFocus(btns[0], $('.mymovies-detail .full-start-new__buttons'));
|
|
}
|
|
},
|
|
back: function () {
|
|
Lampa.Activity.backward();
|
|
},
|
|
right: function () { Navigator.move('right'); },
|
|
left: function () {
|
|
if (Navigator.canmove('left')) Navigator.move('left');
|
|
else Lampa.Controller.toggle('menu');
|
|
},
|
|
up: function () {
|
|
if (Navigator.canmove('up')) Navigator.move('up');
|
|
else Lampa.Controller.toggle('head');
|
|
},
|
|
down: function () { Navigator.move('down'); },
|
|
enter: function () {
|
|
var el = $('.mymovies-detail .full-start-new__buttons .full-start__button.selector.focus')[0] || btns[0];
|
|
if (!el) return;
|
|
var m = selected || (comp.activity && comp.activity.object);
|
|
if (!m) return;
|
|
m = resolveMovie(m);
|
|
if (!m || !m.poster && window.__mymovies_catalog) {
|
|
var f = window.__mymovies_catalog.filter(function (x) { return x.id === m.id; })[0];
|
|
if (f) m = f;
|
|
}
|
|
if ($(el).hasClass('button--book') || $(el).hasClass('button--download')) download(m);
|
|
else play(m);
|
|
},
|
|
gone: function () { Lampa.Controller.clear('content'); comp._detailBound = false; }
|
|
});
|
|
}
|
|
Lampa.Controller.toggle('content');
|
|
};
|
|
// Buttons may not be in the DOM on the very first synchronous call (Lampa
|
|
// builds them lazily). Retry a couple of times, but never re-add the
|
|
// controller or steal focus once it's already set.
|
|
apply();
|
|
// Detail background: blurred fanart, matching Lampa's native canvas blur.
|
|
// Lampa.Background.immediately() is throttled / gated by settings, so we
|
|
// also set the .background element directly. Mimic Lampa's look:
|
|
// heavy blur + darken + slight scale so edges don't show.
|
|
try {
|
|
var dm = resolveMovie((comp.activity && comp.activity.object) || selected);
|
|
var bg = resolve(dm.img || dm.poster || dm.cover);
|
|
console.log('[mymovies] bg=', bg);
|
|
if (bg && Lampa.Background && Lampa.Background.immediately) {
|
|
Lampa.Background.immediately(bg);
|
|
}
|
|
var bgel = document.querySelector('.background');
|
|
if (bg && bgel) {
|
|
bgel.style.backgroundImage = 'url("' + bg + '")';
|
|
bgel.style.backgroundSize = 'cover';
|
|
bgel.style.backgroundPosition = 'center';
|
|
bgel.style.backgroundRepeat = 'no-repeat';
|
|
bgel.style.filter = 'blur(30px) brightness(0.45)';
|
|
bgel.style.transform = 'scale(1.1)';
|
|
bgel.style.opacity = '1';
|
|
}
|
|
} catch (e) { console.log('[mymovies] bg err', e); }
|
|
if (!$('.mymovies-detail .full-start-new__buttons .full-start__button.selector').length) {
|
|
setTimeout(apply, 300);
|
|
setTimeout(apply, 900);
|
|
}
|
|
};
|
|
|
|
this.render = function () {
|
|
var raw = (comp.activity && comp.activity.object) || selected;
|
|
var m = resolveMovie(raw);
|
|
// Fallback: lookup full record from the cached catalog by id.
|
|
if ((!m || !m.poster) && window.__mymovies_catalog) {
|
|
var id = m && m.id;
|
|
var found = window.__mymovies_catalog.filter(function (x) { return x.id === id; })[0];
|
|
if (found) m = found;
|
|
}
|
|
if (!m || !m.title) { Lampa.Activity.back(); return document.createElement('div'); }
|
|
|
|
injectStyles();
|
|
|
|
// Native Lampa detail markup (matches the built-in 'full' screen)
|
|
var html = document.createElement('div');
|
|
html.className = 'mymovies-detail full full--movie';
|
|
|
|
var start = document.createElement('div');
|
|
start.className = 'full-start-new';
|
|
start.innerHTML =
|
|
'<div class="full-start-new__body">' +
|
|
'<div class="full-start-new__left">' +
|
|
'<div class="full-start-new__poster">' +
|
|
'<img class="full-start-new__img full--poster" src="' + resolve(m.poster || m.cover) + '" alt="' + (m.title || '') + '" />' +
|
|
'</div>' +
|
|
'</div>' +
|
|
'<div class="full-start-new__right">' +
|
|
(m.original_title ? '<div class="full-start-new__head">' + esc(m.original_title) + '</div>' : '') +
|
|
'<div class="full-start-new__title">' + esc(m.title) + '</div>' +
|
|
(m.tagline ? '<div class="full-start-new__tagline full--tagline">' + esc(m.tagline) + '</div>' : '') +
|
|
'<div class="full-start-new__details"></div>' +
|
|
'<div class="full-start-new__reactions"></div>' +
|
|
'<div class="full-start-new__buttons"></div>' +
|
|
'</div>' +
|
|
'</div>';
|
|
html.appendChild(start);
|
|
|
|
// Details line: year ● genre1, genre2
|
|
var info = [];
|
|
if (m.year) info.push(esc(String(m.year)));
|
|
if (m.genre && m.genre.length) info.push(esc(m.genre.join(', ')));
|
|
start.querySelector('.full-start-new__details').innerHTML = info.join('<span class="full-start-new__split">●</span>');
|
|
|
|
// Description block (native full-descr)
|
|
if (m.description) {
|
|
var descr = document.createElement('div');
|
|
descr.className = 'full-descr';
|
|
descr.innerHTML =
|
|
'<div class="full-descr__left">' +
|
|
'<div class="full-descr__text selector">' + esc(m.description) + '</div>' +
|
|
'<div class="full-descr__details"></div>' +
|
|
'<div class="full-descr__tags"></div>' +
|
|
'</div>';
|
|
html.appendChild(descr);
|
|
}
|
|
|
|
// Action buttons (native style) — only "Смотреть" (native torrent playback)
|
|
var buttons = start.querySelector('.full-start-new__buttons');
|
|
var btnPlay = document.createElement('div');
|
|
btnPlay.className = 'full-start__button selector button--play';
|
|
btnPlay.innerHTML = '<svg><use xlink:href="#sprite-play"></use></svg><span>Смотреть</span>';
|
|
buttons.appendChild(btnPlay);
|
|
$(btnPlay).on('hover:enter', function () { play(m); });
|
|
|
|
return html;
|
|
};
|
|
}
|
|
Lampa.Component.add(PLUGIN.name + '_detail', MyMoviesDetail);
|
|
|
|
function openMovie(m) {
|
|
selected = m;
|
|
Lampa.Activity.push({
|
|
url: '',
|
|
component: PLUGIN.name + '_detail',
|
|
title: m.title,
|
|
page: m.title,
|
|
object: m,
|
|
movie: m
|
|
});
|
|
}
|
|
|
|
function play(m) {
|
|
if (!m) return;
|
|
// Native Lampa torrent path (same as a parser card):
|
|
// Torrent.start(element) -> Torserver.hash({link: element.MagnetUri})
|
|
// -> Torserver.files(hash) -> Player.play. No parser screen.
|
|
var magnet = m.magnet || ('magnet:?xt=urn:btih:' + (m.hash || ''));
|
|
if (magnet.indexOf('magnet:') !== 0) return;
|
|
var imgUrl = resolve(m.img || m.poster || m.cover);
|
|
// The torrent modal (.files template) reads `params.movie.img` directly
|
|
// into <img src>. We pass a full http(s) URL so it loads as-is.
|
|
// NOTE: do NOT set `backdrop_path` — Lampa runs it through Api.img()
|
|
// (TMDB), which mangles a full URL into an invalid path.
|
|
var element = {
|
|
MagnetUri: magnet,
|
|
title: m.title,
|
|
poster: imgUrl,
|
|
img: imgUrl
|
|
};
|
|
var movie = Object.assign({}, m, { img: imgUrl, poster: imgUrl });
|
|
console.log('[mymovies] play img=', imgUrl);
|
|
Lampa.Torrent.start(element, movie);
|
|
}
|
|
|
|
// ---------- Menu item ----------
|
|
// Lampa builds the main menu on the 'menu:start' event. We add our item via the
|
|
// native Lampa.Menu.addButton() API (adds to .menu__list and wires hover:enter).
|
|
var menuSubscribed = false;
|
|
var menuInterval = null;
|
|
function addMenuButton() {
|
|
// Guard: only one copy (addButton does not set data-action, so we add it ourselves).
|
|
if ($('[data-action="' + PLUGIN.menu_action + '"]').length) return;
|
|
// Use Lampa's native menu API (adds to .menu__list and wires hover:enter).
|
|
var el = Lampa.Menu.addButton(
|
|
'<svg class="sprite-folder"><use xlink:href="#sprite-folder"></use></svg>',
|
|
t(PLUGIN.title),
|
|
function () {
|
|
Lampa.Activity.push({
|
|
url: '',
|
|
component: PLUGIN.name,
|
|
title: t(PLUGIN.title),
|
|
page: t(PLUGIN.title)
|
|
});
|
|
}
|
|
);
|
|
if (el && el.attr) el.attr('data-action', PLUGIN.menu_action);
|
|
}
|
|
function start() {
|
|
if (menuSubscribed) return;
|
|
menuSubscribed = true;
|
|
|
|
// Register localized strings for this plugin.
|
|
try { Lampa.Lang.add(STRINGS); } catch (e) { /* older Lampa: ignore */ }
|
|
|
|
function tryAdd() {
|
|
if ($('.menu .menu__list').length && !$('[data-action="' + PLUGIN.menu_action + '"]').length) {
|
|
addMenuButton();
|
|
if (menuInterval) { clearInterval(menuInterval); menuInterval = null; }
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
Lampa.Listener.follow('menu', function (e) {
|
|
if (e.type === 'start' && e.body) {
|
|
var list = e.body.find('.menu__list');
|
|
if (!list.length) return;
|
|
// guard against duplicate copies in this menu body
|
|
if (list.find('[data-action="' + PLUGIN.menu_action + '"]').length) return;
|
|
addMenuButton();
|
|
if (menuInterval) { clearInterval(menuInterval); menuInterval = null; }
|
|
}
|
|
});
|
|
|
|
// The main menu may already be built (or built later) by the time this plugin
|
|
// loads. Poll briefly (every 500ms) until it appears, then add the item once.
|
|
if (!tryAdd()) {
|
|
menuInterval = setInterval(tryAdd, 500);
|
|
// safety: stop polling after 30s regardless
|
|
setTimeout(function () { if (menuInterval) { clearInterval(menuInterval); menuInterval = null; } }, 30000);
|
|
}
|
|
}
|
|
|
|
if (window.Lampa && Lampa.Listener && Lampa.Activity) {
|
|
start();
|
|
} else {
|
|
document.addEventListener('lampa', start);
|
|
}
|
|
})();
|