Files
prodik 257b42f518 Каталог плагинов: полный локальный mirror в ./plugins (один directory-mount в wwwroot/plugins), доки и вычистка неиспользуемых модулей
- plugins/: скачаны все 30 плагинов store локально (sources.json - карта исходных URL);
  патчи: showy.js (абсолютный хост подгрузки), pubtorr/td/torrentmanager - тела вместо лоадеров,
  modss.js - local-patched копия (из каталога исключена, upstream мёртв)
- extensions.json: 32 позиции, все ссылки {localhost}/plugins/<файл>.js, удалены мёртвые
  (Skaz TV x3, export.js, hotkeys/gold_theme/select_weapon/want/mult, MODS's, o.js, др.)
- compose: ./plugins:/lampac/wwwroot/plugins (один маунт вместо 30 file-mount)
- вычистка: удалены неиспользуемые mods (DatabaseEditor, ExternalBind, LogUserRequest-Lite,
  Telemetry, Transcoding, WatchTogether) и дубль каталога modules/
- config/init.conf: секции Tracks/AdminPanel/Music, customPlugins без gst.js
- mods/Tracks: правки Controller.cs/plugin.js
- README.md / AGENT.md: mirror, кеш static, правила mount
2026-10-02 16:37:30 +03:00

366 lines
15 KiB
JavaScript

(function () {
"use strict";
// ===== Logging Helper =====
function log(message, data) {
console.log("[Title Plugin]", message, data || "");
}
function logError(message, error) {
console.error("[Title Plugin]", message, error || "");
}
// ===== Safe Property Access =====
function safeGet(obj, path, defaultValue) {
try {
return path.split('.').reduce((acc, part) => acc && acc[part], obj) || defaultValue;
} catch (e) {
return defaultValue;
}
}
// ===== Version Check =====
function checkCompatibility() {
if (!window.Lampa) {
logError("Lampa not found");
return false;
}
if (!Lampa.Lang || !Lampa.Storage) {
logError("Required APIs not available");
return false;
}
return true;
}
// ===== Локалізація =====
Lampa.Lang.add({
title_plugin: {
ru: "Title Plugin",
en: "Title Plugin",
uk: "Title Plugin",
be: "Title Plugin",
},
reset_to_default: {
ru: "Сбросить по умолчанию",
en: "Reset to Default",
uk: "Скинути за замовчуванням",
be: "Скінуць па змаўчанні",
},
show_en: {
ru: 'Показывать <img src="https://flagcdn.com/us.svg" style="width:1.15em;height:auto;vertical-align:middle;"> EN',
en: 'Show <img src="https://flagcdn.com/us.svg" style="width:1.15em;height:auto;vertical-align:middle;"> EN',
uk: 'Показувати <img src="https://flagcdn.com/us.svg" style="width:1.15em;height:auto;vertical-align:middle;"> EN',
be: 'Паказваць <img src="https://flagcdn.com/us.svg" style="width:1.15em;height:auto;vertical-align:middle;"> EN',
},
show_tl: {
ru: "Показывать Romaji (transliteration)",
en: "Show Romaji (transliteration)",
uk: "Показувати Romaji (transliteration)",
be: "Паказваць Romaji (transliteration)",
},
show_uk: {
ru: 'Показывать <img src="https://flagcdn.com/ua.svg" style="width:1.15em;height:auto;vertical-align:middle;"> UA',
en: 'Show <img src="https://flagcdn.com/ua.svg" style="width:1.15em;height:auto;vertical-align:middle;"> UA',
uk: 'Показувати <img src="https://flagcdn.com/ua.svg" style="width:1.15em;height:auto;vertical-align:middle;"> UA',
be: 'Паказваць <img src="https://flagcdn.com/ua.svg" style="width:1.15em;height:auto;vertical-align:middle;"> UA',
},
show_be: {
ru: 'Показывать <img src="https://flagcdn.com/by.svg" style="width:1.15em;height:auto;vertical-align:middle;"> BE',
en: 'Show <img src="https://flagcdn.com/by.svg" style="width:1.15em;height:auto;vertical-align:middle;"> BE',
uk: 'Показувати <img src="https://flagcdn.com/by.svg" style="width:1.15em;height:auto;vertical-align:middle;"> BE',
be: 'Паказваць <img src="https://flagcdn.com/by.svg" style="width:1.15em;height:auto;vertical-align:middle;"> BE',
},
show_ru: {
ru: 'Показывать <img src="https://flagcdn.com/ru.svg" style="width:1.15em;height:auto;vertical-align:middle;"> RU',
en: 'Show <img src="https://flagcdn.com/ru.svg" style="width:1.15em;height:auto;vertical-align:middle;"> RU',
uk: 'Показувати <img src="https://flagcdn.com/ru.svg" style="width:1.15em;height:auto;vertical-align:middle;"> RU',
be: 'Паказваць <img src="https://flagcdn.com/ru.svg" style="width:1.15em;height:auto;vertical-align:middle;"> RU',
},
});
const LANGS = ["en", "tl", "uk", "be", "ru"];
const STORAGE_ORDER_KEY = "title_plugin_order";
const STORAGE_HIDDEN_KEY = "title_plugin_hidden";
function startPlugin() {
// ===== Settings button =====
Lampa.SettingsApi.addParam({
component: "interface",
param: { type: "button" },
field: {
name: Lampa.Lang.translate("title_plugin"),
description: "Title Plugin settings",
},
onChange: openEditor,
});
// ===== Title display logic =====
const CACHE_TTL = 30 * 24 * 60 * 60 * 1000;
let titleCache = Lampa.Storage.get("title_cache") || {};
function showTitles(card) {
try {
var orig = card.original_title || card.original_name;
var altTitles = card.alternative_titles || {};
var alt = altTitles.titles || altTitles.results || [];
function countryFlag(code) {
if (!code) return '';
return ' <img src="https://flagcdn.com/' + code.toLowerCase() + '.svg"'
+ ' style="width:1.15em;height:auto;vertical-align:inherit;" alt="' + code.toUpperCase() + '">';
}
var TRANSLIT_TYPES = ['Transliteration', 'romaji', 'Romanization', 'Latynization',
'pinyin', 'kana', 'romaji_japanese', 'romaji_korean', 'romaji_chinese', 'latinization'];
var translitObj = alt.find(function (t) { return TRANSLIT_TYPES.indexOf(t.type) !== -1; });
var translit = (translitObj && (translitObj.title || (translitObj.data && (translitObj.data.title || translitObj.data.name)))) || '';
var ru = '', en = '', uk = '', be = '';
var now = Date.now();
var cache = titleCache[card.id];
if (cache && now - cache.timestamp < CACHE_TTL) {
en = cache.en || '';
uk = cache.uk || '';
be = cache.be || '';
ru = cache.ru || '';
translit = translit || cache.tl || '';
console.log('[TitlePlugin] cache hit for id:', card.id);
}
function findLang(list, codes) {
var t = list.find(function (t) { return codes.indexOf(t.iso_3166_1) !== -1 || codes.indexOf(t.iso_639_1) !== -1; });
return t && t.data && (t.data.title || t.data.name) || '';
}
function applyAndRender(tr) {
var translitData = tr.find(function (t) { return ['Transliteration', 'romaji'].indexOf(t.type) !== -1; });
if (translitData) translit = (translitData.title || (translitData.data && (translitData.data.title || translitData.data.name))) || translit;
en = en || findLang(tr, ['US', 'en']);
uk = uk || findLang(tr, ['UA', 'uk']);
be = be || findLang(tr, ['BY', 'be']);
ru = ru || findLang(tr, ['RU', 'ru']);
// fallback из alternative_titles
en = en || (alt.find(function (t) { return t.iso_3166_1 === 'US'; }) || {}).title || '';
uk = uk || (alt.find(function (t) { return t.iso_3166_1 === 'UA'; }) || {}).title || '';
be = be || (alt.find(function (t) { return t.iso_3166_1 === 'BY'; }) || {}).title || '';
ru = ru || (alt.find(function (t) { return t.iso_3166_1 === 'RU'; }) || {}).title || '';
titleCache[card.id] = { ru: ru, en: en, tl: translit, uk: uk, be: be, timestamp: now };
try { Lampa.Storage.set('title_cache', titleCache); } catch (e) { console.error('[TitlePlugin] storage error:', e); }
renderTitles();
}
function renderTitles() {
var active = Lampa.Activity.active();
if (!active || !active.activity) {
console.warn('[TitlePlugin] no active activity');
return;
}
var render = active.activity.render();
if (!render) { console.warn('[TitlePlugin] render is null'); return; }
$('.original_title', render).remove();
var showOrder = Lampa.Storage.get(STORAGE_ORDER_KEY, LANGS.slice());
var hiddenLangs = Lampa.Storage.get(STORAGE_HIDDEN_KEY, []);
var originCountry = (card.origin_country || [])[0] || '';
var lines = ['<div style="font-size:1.25em;">' + orig + ' ' + countryFlag(originCountry) + '</div>'];
var langFlags = { ru: 'RU', en: 'US', uk: 'UA', be: 'BY' };
var langVals = { en: en, uk: uk, be: be, ru: ru };
showOrder.forEach(function (lang) {
if (hiddenLangs.indexOf(lang) !== -1) return;
var val = lang === 'tl' ? translit : langVals[lang];
if (val) lines.push('<div style="font-size:1.25em;">' + val + ' ' + countryFlag(langFlags[lang]) + '</div>');
});
$('.full-start-new__title', render).after(
'<div class="original_title" style="margin-bottom:7px;text-align:right;"><div>'
+ lines.join('') + '</div></div>'
);
}
if (!ru || !en || !translit || !uk || !be) {
var type = card.first_air_date ? 'tv' : 'movie';
var tmdbCacheKey = type + '_' + card.id;
// Спільний кеш трансляцій з іншими плагінами (наприклад rezkacomment.js)
window.__tmdbTranslationsCache = window.__tmdbTranslationsCache || {};
if (window.__tmdbTranslationsCache[tmdbCacheKey]) {
console.log('[TitlePlugin] using shared translations cache for', tmdbCacheKey);
applyAndRender(window.__tmdbTranslationsCache[tmdbCacheKey]);
} else {
console.log('[TitlePlugin] fetching translations for', type, card.id);
Lampa.Api.sources.tmdb.get(
type + '/' + card.id + '?append_to_response=translations',
{},
function (data) {
var tr = (data.translations && data.translations.translations) || [];
window.__tmdbTranslationsCache[tmdbCacheKey] = tr;
applyAndRender(tr);
},
function (e) {
console.error('[TitlePlugin] tmdb error:', e);
applyAndRender([]);
}
);
}
} else {
renderTitles();
}
} catch (e) {
console.error('[TitlePlugin] showTitles crash:', e.message, e.stack);
}
}
// ===== Listener =====
if (!window.title_plugin) {
window.title_plugin = true;
Lampa.Listener.follow("full", (e) => {
if (e.type !== "complite" || !e.data.movie) return;
showTitles(e.data.movie);
});
}
}
// ===== Editor modal =====
function openEditor() {
const order = Lampa.Storage.get(STORAGE_ORDER_KEY, LANGS.slice());
const hidden = Lampa.Storage.get(STORAGE_HIDDEN_KEY, []);
const list = $('<div class="menu-edit-list"></div>');
const style = $(`
<style>
.menu-edit-list__item { display:flex; align-items:center; justify-content:space-between; margin:0.3em 0; }
.menu-edit-list__title { flex:1; font-size:1.1em; padding-left:0.5em; }
.menu-edit-list__move { width:1.8em; height:1.8em; display:flex; align-items:center; justify-content:center; cursor:pointer; font-weight:bold; user-select:none; }
.menu-edit-list__toggle { width:1.8em; height:1.8em; display:flex; align-items:center; justify-content:center; cursor:pointer; margin-left:0.5em; border:2px solid rgba(255,255,255,0.5); border-radius:3px; }
.menu-edit-list__toggle .dot { width:1em; height:1em; }
.folder-reset-button { background: rgba(200,100,100,0.3); margin-top:1em; border-radius:0.3em; padding:0.5em; text-align:center; cursor:pointer; }
</style>
`);
$("body").append(style);
order.forEach((lang) => {
const title = Lampa.Lang.translate("show_" + lang);
const isHidden = hidden.includes(lang);
const item = $(`
<div class="menu-edit-list__item">
<div class="menu-edit-list__title">${title}</div>
<div class="menu-edit-list__move move-up selector">▲</div>
<div class="menu-edit-list__move move-down selector">▼</div>
<div class="menu-edit-list__toggle selector">
<svg width="26" height="26" viewBox="0 0 26 26" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="1.89111" y="1.78369" width="21.793" height="21.793" rx="3.5" stroke="currentColor" stroke-width="3"/>
<path d="M7.44873 12.9658L10.8179 16.3349L18.1269 9.02588" stroke="currentColor" stroke-width="3" class="dot" opacity="${isHidden ? 0 : 1
}" stroke-linecap="round"/>
</svg>
</div>
</div>`);
// ===== Toggle hide/show =====
item.find(".menu-edit-list__toggle").on("hover:enter", function () {
const idx = hidden.indexOf(lang);
if (idx !== -1) {
hidden.splice(idx, 1);
item.find(".dot").attr("opacity", "1");
} else {
hidden.push(lang);
item.find(".dot").attr("opacity", "0");
}
Lampa.Storage.set(STORAGE_HIDDEN_KEY, hidden);
});
// ===== Move up/down =====
item.find(".move-up").on("hover:enter", function () {
const prev = item.prev();
if (prev.length) {
item.insertBefore(prev);
updateOrder();
}
});
item.find(".move-down").on("hover:enter", function () {
const next = item.next();
if (next.length) {
item.insertAfter(next);
updateOrder();
}
});
list.append(item);
});
const resetBtn = $(
'<div class="selector folder-reset-button" style="margin-top:1em;padding:1em;text-align:center;">' +
Lampa.Lang.translate("reset_to_default") +
"</div>"
);
resetBtn.on("hover:enter", function () {
Lampa.Storage.set(STORAGE_ORDER_KEY, LANGS.slice());
Lampa.Storage.set(STORAGE_HIDDEN_KEY, []);
Lampa.Modal.close();
Lampa.Controller.toggle("settings");
});
list.append(resetBtn);
function updateOrder() {
const newOrder = [];
list.find(".menu-edit-list__item").each(function () {
const title = $(this).find(".menu-edit-list__title").text();
const key = LANGS.find(
(l) => Lampa.Lang.translate("show_" + l) === title
);
if (key) newOrder.push(key);
});
Lampa.Storage.set(STORAGE_ORDER_KEY, newOrder);
}
Lampa.Modal.open({
title: Lampa.Lang.translate("title_plugin"),
html: list,
size: "small",
scroll_to_center: true,
onBack: function () {
Lampa.Modal.close();
Lampa.Controller.toggle("settings");
},
});
}
// ===== Init plugin =====
if (window.appready) {
if (typeof Lampa !== 'undefined' &&
Lampa.SettingsApi &&
Lampa.Api &&
Lampa.Api.sources &&
Lampa.Api.sources.tmdb &&
typeof $ !== 'undefined') {
startPlugin();
} else {
console.error('Title Plugin: Required Lampa APIs not available');
}
} else {
Lampa.Listener.follow("app", (e) => {
if (e.type === "ready") {
if (typeof Lampa !== 'undefined' &&
Lampa.SettingsApi &&
Lampa.Api &&
Lampa.Api.sources &&
Lampa.Api.sources.tmdb &&
typeof $ !== 'undefined') {
startPlugin();
} else {
console.error('Title Plugin: Required Lampa APIs not available');
}
}
});
}
})();