let channels = [];
let favorites = [];
let appHistory = [];
let currentFilter = 'all';
let currentPage = 1;
let currentM3UContent = null;
let currentM3UName = null;
let notificationTimeout = null;
let currentGroupOrder = [];
let selectedMovistarLongTokenIdForSettings = null;
let currentView = { type: 'main' };
let navigationHistory = [];
let playerInstances = {};
let activePlayerId = null;
let highestZIndex = 1950;
let hoverPlayTimeout = null;
const HOVER_PLAY_DELAY = 700;
let activeCardPreviewPlayer = null;
let activeCardPreviewElement = null;
let currentTranslations = {};
async function loadLanguage(lang) {
try {
const response = await fetch(chrome.runtime.getURL(`_locales/${lang}/messages.json`));
if (!response.ok) throw new Error(`Could not load ${lang}.json`);
const messages = await response.json();
currentTranslations = {};
for (const key in messages) {
if (Object.hasOwnProperty.call(messages, key)) {
currentTranslations[key] = messages[key].message;
}
}
document.documentElement.lang = lang;
} catch (error) {
console.error("Error loading language file:", error);
if (lang !== 'es') {
await loadLanguage('es');
}
}
}
function applyTranslations() {
document.querySelectorAll('[data-lang-key]').forEach(element => {
const key = element.getAttribute('data-lang-key');
const message = currentTranslations[key];
if (message) {
let finalMessage = message;
if (element.hasAttribute('data-lang-vars')) {
try {
const varsAttr = element.getAttribute('data-lang-vars');
const vars = JSON.parse(varsAttr);
for (const varKey in vars) {
const selector = vars[varKey];
const varElement = document.querySelector(selector);
if (varElement) {
finalMessage = finalMessage.replace(`{${varKey}}`, varElement.innerHTML);
}
}
} catch(e) { console.error(`Error parsing data-lang-vars for key ${key}:`, e)}
}
const attr = element.getAttribute('data-lang-attr');
if (attr) {
element.setAttribute(attr, finalMessage);
} else {
element.innerHTML = finalMessage;
}
}
});
}
async function showLoadFromDBModal() {
if (typeof dbPromise === 'undefined' || !dbPromise) {
showLoading(true, currentTranslations['loading'] || 'Iniciando base de datos local...');
try { if (typeof openDB === 'function') await openDB(); } catch (error) { showNotification(`Error DB: ${error.message}`, 'error'); showLoading(false); return; }
finally { showLoading(false); }
}
showLoading(true, currentTranslations['loadingLists'] || 'Cargando listas guardadas...');
try {
const files = typeof getAllFilesFromDB === 'function' ? await getAllFilesFromDB() : [];
const $list = $('#dbFilesList').empty();
if (!files || files.length === 0) {
$list.append(`
Carga los dispositivos para el token seleccionado arriba.
');
$('#movistarAssociateDeviceForSettingsBtn').prop('disabled', true);
$('#movistarRegisterNewDeviceForSettingsBtn').prop('disabled', false);
}
});
$('#movistarLoadDevicesForSettingsBtn').on('click', handleMovistarLoadDevicesForSettings);
$('#movistarAssociateDeviceForSettingsBtn').on('click', handleMovistarAssociateDeviceForSettings);
$('#movistarRegisterNewDeviceForSettingsBtn').on('click', handleMovistarRegisterNewDeviceForSettings);
$('#movistarRefreshCdnBtnSettings').on('click', handleMovistarRefreshCdnToken);
$('#movistarCopyCdnBtnSettings').on('click', handleMovistarCopyCdnToken);
$('#movistarApplyCdnToChannelsBtnSettings').on('click', handleMovistarApplyCdnToChannels);
$('#clearMovistarVodCacheBtnSettings').on('click', handleClearMovistarVodCache);
$('#loadMovistarVODBtn').on('click', loadMovistarVODData);
$('#movistarVODDateInput').on('change', function() {
movistarVodSelectedDate = new Date($(this).val() + 'T00:00:00');
loadMovistarVODData();
});
$('#movistarVODModal-channel-filter, #movistarVODModal-genre-filter').on('change', renderMovistarVODPrograms);
$('#movistarVODModal-search-input').on('input', debounce(renderMovistarVODPrograms, 300));
$('#movistarVODModal-programs').on('click', '.movistar-vod-card', function() {
const programArrayIndex = parseInt($(this).data('program-array-index'), 10);
if (!isNaN(programArrayIndex) && movistarVodFilteredPrograms[programArrayIndex]) {
const program = movistarVodFilteredPrograms[programArrayIndex];
if (program && typeof handleMovistarVODProgramClick === 'function') {
handleMovistarVODProgramClick(program);
}
} else {
showNotification("Error al seleccionar el programa VOD.", "error");
}
});
$('#movistarVODModal-prev-page').on('click', function() {
if (movistarVodCurrentPage > 1) {
movistarVodCurrentPage--;
displayCurrentMovistarVODPage();
updateMovistarVODPaginationControls();
}
});
$('#movistarVODModal-next-page').on('click', function() {
const totalPages = Math.ceil(movistarVodFilteredPrograms.length / MOVISTAR_VOD_ITEMS_PER_PAGE);
if (movistarVodCurrentPage < totalPages) {
movistarVodCurrentPage++;
displayCurrentMovistarVODPage();
updateMovistarVODPaginationControls();
}
});
}
async function applyUISettings() {
await loadLanguage(userSettings.language);
applyTranslations();
if (typeof populateUserSettingsForm === 'function') populateUserSettingsForm();
if (typeof applyThemeAndFont === 'function') applyThemeAndFont();
const sidebar = $('#sidebar');
const appContainer = $('#app-container');
if (userSettings.sidebarCollapsed && window.innerWidth >= 992) {
sidebar.removeClass('expanded').addClass('collapsed');
appContainer.addClass('sidebar-collapsed');
} else if(window.innerWidth >= 992) {
sidebar.removeClass('collapsed').addClass('expanded');
appContainer.removeClass('sidebar-collapsed');
} else {
sidebar.removeClass('expanded').addClass('collapsed');
appContainer.addClass('sidebar-collapsed');
}
document.documentElement.style.setProperty('--m3u-grid-minmax-size', userSettings.channelCardSize + 'px');
document.documentElement.style.setProperty('--card-logo-aspect-ratio', userSettings.cardLogoAspectRatio === 'auto' ? '16/9' : userSettings.cardLogoAspectRatio);
Object.values(playerInstances).forEach(instance => {
if (typeof updatePlayerConfigFromSettings === 'function') {
updatePlayerConfigFromSettings(instance.player);
}
if (instance.container) {
instance.container.style.setProperty('--player-window-opacity', userSettings.playerWindowOpacity);
}
});
if (typeof initParticles === 'function') initParticles();
if (userSettings.persistFilters) {
if(userSettings.lastSelectedFilterTab) currentFilter = userSettings.lastSelectedFilterTab;
}
if (channels.length > 0) filterAndRenderChannels();
}
function initParticles() {
if (typeof particlesJS === 'function' && document.getElementById('particles-js') && !document.getElementById('particles-js').dataset.initialized && userSettings.particlesEnabled) {
document.getElementById('particles-js').dataset.initialized = 'true';
const particleColor = getComputedStyle(document.documentElement).getPropertyValue('--accent-secondary').trim();
const particleLineColor = getComputedStyle(document.documentElement).getPropertyValue('--accent-primary').trim();
document.documentElement.style.setProperty('--particle-opacity', userSettings.particleOpacity);
particlesJS('particles-js', {
"particles": {
"number": { "value": 30, "density": { "enable": true, "value_area": 1200 } },
"color": { "value": particleColor },
"shape": { "type": "circle" },
"opacity": { "value": 1, "random": true, "anim": { "enable": true, "speed": 0.15, "opacity_min": 0.3, "sync": false } },
"size": { "value": 1.5, "random": true },
"line_linked": { "enable": true, "distance": 160, "color": particleLineColor, "opacity": 0.5, "width": 1 },
"move": { "enable": true, "speed": 0.8, "direction": "none", "random": true, "straight": false, "out_mode": "out" }
},
"interactivity": { "detect_on": "canvas", "events": { "onhover": { "enable": false }, "onclick": { "enable": false }, "resize": true } },
"retina_detect": true
});
const particlesCanvas = document.querySelector('#particles-js canvas');
if(particlesCanvas && particlesCanvas.style) {
particlesCanvas.style.setProperty('opacity', '1', 'important');
}
$('#particles-js').removeClass('disabled');
} else if (!userSettings.particlesEnabled && document.getElementById('particles-js')) {
$('#particles-js').addClass('disabled');
if(typeof pJSDom !== 'undefined' && pJSDom.length > 0 && pJSDom[0].pJS) {
pJSDom[0].pJS.fn.vendors.destroypJS();
pJSDom = [];
if (document.getElementById('particles-js')) document.getElementById('particles-js').dataset.initialized = 'false';
}
}
}
window.updateM3UWithDaznData = function(daznChannelDetailsList) {
if (!channels || channels.length === 0) {
if(typeof showNotification === 'function') showNotification('DAZN: No hay lista M3U cargada para actualizar.', 'info');
return;
}
if (!daznChannelDetailsList || daznChannelDetailsList.length === 0) {
if(typeof showNotification === 'function') showNotification('DAZN: No se recibieron datos de canales para la actualización.', 'info');
return;
}
let updatedCount = 0;
const daznDataMapByLinearId = new Map();
daznChannelDetailsList.forEach(daznChannel => {
if (daznChannel.daznLinearId) {
daznDataMapByLinearId.set(daznChannel.daznLinearId, daznChannel);
}
});
channels.forEach(m3uChannel => {
let currentChannelLinearId = null;
if (m3uChannel.url) {
const urlMatch = m3uChannel.url.match(/dazn-linear-(\d+)/);
if (urlMatch && urlMatch[1]) {
currentChannelLinearId = urlMatch[1];
}
}
if (!currentChannelLinearId && m3uChannel['tvg-id']) {
const tvgIdMatch = String(m3uChannel['tvg-id']).match(/dazn-linear-(\d+)/i);
if (tvgIdMatch && tvgIdMatch[1]) {
currentChannelLinearId = tvgIdMatch[1];
}
}
if (currentChannelLinearId && daznDataMapByLinearId.has(currentChannelLinearId)) {
const daznUpdate = daznDataMapByLinearId.get(currentChannelLinearId);
m3uChannel.url = daznUpdate.baseUrl;
if (!m3uChannel.kodiProps) {
m3uChannel.kodiProps = {};
}
if (!m3uChannel.vlcOptions) {
m3uChannel.vlcOptions = {};
}
if (daznUpdate.cdnTokenName && daznUpdate.cdnTokenValue) {
m3uChannel.kodiProps['inputstream.adaptive.stream_headers'] = `${daznUpdate.cdnTokenName}=${daznUpdate.cdnTokenValue}`;
} else {
delete m3uChannel.kodiProps['inputstream.adaptive.stream_headers'];
}
if (daznUpdate.streamUserAgent) {
m3uChannel.vlcOptions['http-user-agent'] = daznUpdate.streamUserAgent;
}
m3uChannel.sourceOrigin = "DAZN";
updatedCount++;
}
});
if (updatedCount > 0) {
if(typeof showNotification === 'function') showNotification(`DAZN: ${updatedCount} canales actualizados en tu lista M3U.`, 'success');
regenerateCurrentM3UContentFromString();
filterAndRenderChannels();
} else {
if(typeof showNotification === 'function') showNotification('DAZN: No se encontraron canales en tu M3U que coincidieran con los datos de DAZN para actualizar.', 'info');
}
};
function logToMovistarSettingsUI(message, type = 'info') {
const logArea = $('#movistarLogAreaSettings');
if (logArea.length) {
const timestamp = new Date().toLocaleTimeString();
const existingLog = logArea.val();
const newLog = `[${timestamp}] ${message}\n`;
logArea.val(existingLog + newLog);
logArea.scrollTop(logArea[0].scrollHeight);
}
}
async function loadAndDisplayInitialMovistarStatus() {
if (!window.MovistarTokenHandler) return;
logToMovistarSettingsUI("Cargando estado inicial de Movistar+...", "info");
try {
const status = await window.MovistarTokenHandler.getShortTokenStatus();
updateMovistarCdnTokenUI(status.token, status.expiry);
await loadAndRenderLongTokensListSettings();
} catch (error) {
logToMovistarSettingsUI(`Error cargando estado inicial: ${error.message}`, "error");
}
}
function updateMovistarCdnTokenUI(token, expiryTimestamp) {
$('#movistarCdnTokenDisplaySettings').val(token || "");
const expiryDate = expiryTimestamp ? new Date(expiryTimestamp * 1000) : null;
if (expiryDate && expiryTimestamp > Math.floor(Date.now() / 1000)) {
$('#movistarCdnTokenExpirySettings').text(`${chrome.i18n.getMessage("movistarExpiresHeader") || "Expira"}: ${expiryDate.toLocaleString()}`);
$('#movistarCdnTokenExpirySettings').removeClass('text-danger').addClass('text-success');
} else if (expiryDate) {
$('#movistarCdnTokenExpirySettings').text(`Expirado: ${expiryDate.toLocaleString()}`);
$('#movistarCdnTokenExpirySettings').removeClass('text-success').addClass('text-danger');
} else {
$('#movistarCdnTokenExpirySettings').text(`${chrome.i18n.getMessage("movistarExpiresHeader") || "Expira"}: -`);
$('#movistarCdnTokenExpirySettings').removeClass('text-success text-danger');
}
$('#movistarCopyCdnBtnSettings').prop('disabled', !token);
$('#movistarApplyCdnToChannelsBtnSettings').prop('disabled', !token || (expiryTimestamp <= Math.floor(Date.now()/1000)));
}
async function loadAndRenderLongTokensListSettings() {
if (!window.MovistarTokenHandler) return;
const tbody = $('#movistarLongTokensTableBodySettings');
tbody.html(`').data('tokenid', token.id).css('cursor', 'pointer');
tr.append($('').text(`...${token.id.slice(-12)}`).attr('title', token.id));
tr.append($(' | ').text(token.account_nbr || 'N/A'));
tr.append($(' | ').text(token.device_id ? `...${token.device_id.slice(-6)}` : 'NULO').attr('title', token.device_id || 'Sin Device ID'));
tr.append($(' | ').text(expiryString));
tr.append($(' | ').addClass(statusClass).text(statusText));
tr.append($(' | ')
.append($(' |