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(`
  • ${currentTranslations['noFileLoaded'] || "No hay listas guardadas."}
  • `); } else { files.sort((a,b) => new Date(b.timestamp) - new Date(a.timestamp)); files.forEach(file => { const date = file.timestamp ? new Date(file.timestamp).toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' }) : 'Fecha desconocida'; const time = file.timestamp ? new Date(file.timestamp).toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit'}) : ''; const count = typeof file.channelCount === 'number' ? file.channelCount : (typeof countChannels === 'function' ? countChannels(file.content) : 0); $list.append(`
  • ${escapeHtml(file.name)} ${count} canales | ${date} ${time}
  • `); }); $list.off('click', '.load-file-btn').on('click', '.load-file-btn', function () { loadFileToPlayer($(this).data('name')); $('#loadFromDBModal').modal('hide'); }); $list.off('click', '.delete-file-btn').on('click', '.delete-file-btn', function () { handleDeleteFromDB($(this).data('name')); }); } $('#loadFromDBModal').modal('show'); } catch (error) { showNotification(`Error cargando listas guardadas: ${error.message}`, 'error'); $('#dbFilesList').empty().append('
  • Error al cargar listas.
  • '); } finally { showLoading(false); } } async function loadFileToPlayer(name) { showLoading(true, `Cargando "${escapeHtml(name)}" desde BD...`); currentGroupOrder = []; try { const file = typeof getFileFromDB === 'function' ? await getFileFromDB(name) : null; if (!file || !file.content) throw new Error('Lista no encontrada en la base de datos.'); processM3UContent(file.content, file.name, true); if (userSettings.autoSaveM3U) { if (file.content.length < 4 * 1024 * 1024) { await saveAppConfigValue('lastM3UFileContent', file.content); await saveAppConfigValue('lastM3UFileName', file.name); await deleteAppConfigValue('lastM3UUrl'); await deleteAppConfigValue('currentXtreamServerInfo'); } else { await deleteAppConfigValue('lastM3UFileContent'); await deleteAppConfigValue('lastM3UFileName'); await deleteAppConfigValue('lastM3UUrl'); await deleteAppConfigValue('currentXtreamServerInfo'); showNotification('Lista cargada pero demasiado grande para guardado automático futuro.', 'info'); } } showNotification(`Lista "${escapeHtml(name)}" cargada (${channels.length} canales).`, 'success'); } catch (error) { showNotification(`Error cargando "${escapeHtml(name)}": ${error.message}`, 'error'); channels = []; currentM3UContent = null; currentM3UName = null; currentGroupOrder = []; filterAndRenderChannels(); } finally { showLoading(false); } } async function handleDeleteFromDB(name) { const confirmed = await showConfirmationModal(`¿Estás seguro de eliminar la lista "${escapeHtml(name)}" de forma permanente?`, "Confirmar Eliminación", "Sí, Eliminar", "btn-danger"); if (!confirmed) return; showLoading(true, `Eliminando "${escapeHtml(name)}"...`); try { if (typeof deleteFileFromDB === 'function') await deleteFileFromDB(name); else throw new Error("deleteFileFromDB no definido"); showNotification(`Lista "${escapeHtml(name)}" eliminada.`, 'success'); $(`#dbFilesList li button[data-name="${escapeHtml(name)}"]`).closest('li').fadeOut(300, function() { $(this).remove(); if ($('#dbFilesList li').length === 0) { $('#dbFilesList').append(`
  • ${currentTranslations['noFileLoaded'] || "No hay listas guardadas."}
  • `); } }); const lastM3UFileName = await getAppConfigValue('lastM3UFileName'); if (lastM3UFileName === name) { channels = []; currentM3UContent = null; currentM3UName = null; currentGroupOrder = []; filterAndRenderChannels(); await deleteAppConfigValue('lastM3UFileContent'); await deleteAppConfigValue('lastM3UFileName'); showNotification('La lista actualmente cargada fue eliminada.', 'info'); } } catch (error) { showNotification(`Error al eliminar "${escapeHtml(name)}": ${error.message}`, 'error'); } finally { showLoading(false); } } $(document).ready(async function () { shaka.polyfill.installAll(); if (typeof loadUserSettings === 'function') { await loadUserSettings(); } if (typeof applyUISettings === 'function') { await applyUISettings(); } makeWindowsDraggableAndResizable(); bindEvents(); if (typeof bindEpgEvents === 'function') { bindEpgEvents(); } if (typeof MovistarTokenHandler !== 'undefined' && typeof MovistarTokenHandler.setLogCallback === 'function') { MovistarTokenHandler.setLogCallback(logToMovistarSettingsUI); loadAndDisplayInitialMovistarStatus(); } if (typeof initXCodecPanelManagement === 'function') { initXCodecPanelManagement(); } if (typeof phpGenerator !== 'undefined' && typeof phpGenerator.init === 'function') { phpGenerator.init(); } if (userSettings.persistFilters && userSettings.lastSelectedFilterTab) { currentFilter = userSettings.lastSelectedFilterTab; } updateActiveFilterButton(); checkIfChannelsExist(); const urlParams = new URLSearchParams(window.location.search); const channelNameFromUrl = urlParams.get('name'); const channelStreamUrl = urlParams.get('url'); const autoPlayFromUrl = (channelNameFromUrl && channelStreamUrl); if (autoPlayFromUrl) { showNotification(`Cargando ${escapeHtml(channelNameFromUrl)} desde editor...`, 'info'); const channelDataForPlayer = { name: channelNameFromUrl, url: channelStreamUrl, 'tvg-logo': urlParams.get('logo') || '', 'tvg-id': urlParams.get('tvgid') || '', 'group-title': urlParams.get('group') || 'Externo', attributes: { 'tvg-id': urlParams.get('tvgid') || '', 'tvg-logo': urlParams.get('logo') || '', 'group-title': urlParams.get('group') || 'Externo', 'ch-number': urlParams.get('chnumber') || '', ...(urlParams.has('player-buffer') && { 'player-buffer': urlParams.get('player-buffer') }) }, kodiProps: {}, vlcOptions: {}, extHttp: {} }; const licenseType = urlParams.get('licenseType'); const licenseKey = urlParams.get('licenseKey'); const serverCertBase64 = urlParams.get('serverCert'); if (licenseType) channelDataForPlayer.kodiProps['inputstream.adaptive.license_type'] = licenseType; if (licenseKey) channelDataForPlayer.kodiProps['inputstream.adaptive.license_key'] = licenseKey; if (serverCertBase64) channelDataForPlayer.kodiProps['inputstream.adaptive.server_certificate'] = serverCertBase64; const streamHeaders = urlParams.get('streamHeaders'); if (streamHeaders) channelDataForPlayer.kodiProps['inputstream.adaptive.stream_headers'] = streamHeaders; const userAgent = urlParams.get('userAgent'); const referrer = urlParams.get('referrer'); const origin = urlParams.get('origin'); if (userAgent) channelDataForPlayer.vlcOptions['http-user-agent'] = userAgent; if (referrer) channelDataForPlayer.vlcOptions['http-referrer'] = referrer; if (origin) channelDataForPlayer.vlcOptions['http-origin'] = origin; const extHttpJson = urlParams.get('extHttp'); if (extHttpJson) { try { channelDataForPlayer.extHttp = JSON.parse(extHttpJson); } catch (e) { } } if (typeof createPlayerWindow === 'function') { createPlayerWindow(channelDataForPlayer); } } else { await loadLastM3U(); if (userSettings.useMovistarVodAsEpg && typeof updateEpgWithMovistarVodData === 'function') { const today = new Date(); const yyyy = today.getFullYear(); const mm = String(today.getMonth() + 1).padStart(2, '0'); const dd = String(today.getDate()).padStart(2, '0'); await updateEpgWithMovistarVodData(`${yyyy}-${mm}-${dd}`); } if (typeof startDynamicEpgUpdaters === 'function') { startDynamicEpgUpdaters(); } setTimeout(() => { $(window).one('scroll mousemove touchstart', initParticles); setTimeout(initParticles, 5000); }, 100); } }); function launchEditor() { if (!channels || channels.length === 0) { showNotification("No hay ninguna lista M3U cargada para editar.", "warning"); return; } if (typeof editorHandler === 'undefined' || typeof editorHandler.init !== 'function') { showNotification("El módulo del editor no está disponible.", "error"); return; } editorHandler.init(channels, currentM3UName); const editorModal = new bootstrap.Modal(document.getElementById('editorModal')); editorModal.show(); } async function handleChannelCardClick(event) { if ($(event.target).closest('.favorite-btn').length && userSettings.cardShowFavButton) { return; } clearTimeout(hoverPlayTimeout); if (typeof destroyActiveCardPreviewPlayer === 'function' && typeof activeCardPreviewPlayer !== 'undefined' && activeCardPreviewPlayer) { await destroyActiveCardPreviewPlayer(); } const card = $(this); let channelUrl, seriesChannel, seasonData, episodeData; try { const seasonDataAttr = card.data('season-data'); if (seasonDataAttr) { seasonData = (typeof seasonDataAttr === 'string') ? JSON.parse(seasonDataAttr) : seasonDataAttr; const episodes = await loadXtreamSeasonEpisodes(seasonData.series_id, seasonData.season_number); if (episodes && episodes.length > 0) { pushNavigationState(); currentView = { type: 'episode_list', data: episodes, title: `${seasonData['group-title']} - ${seasonData.name}` }; renderCurrentView(); } else { showNotification('No se encontraron episodios para esta temporada.', 'info'); } return; } const episodeDataAttr = card.data('episode-data'); if (episodeDataAttr) { episodeData = (typeof episodeDataAttr === 'string') ? JSON.parse(episodeDataAttr) : episodeDataAttr; createPlayerWindow(episodeData); return; } channelUrl = card.data('url'); seriesChannel = channels.find(c => c.url === channelUrl); if (seriesChannel && seriesChannel.attributes && seriesChannel.attributes['xtream-type'] === 'series') { const seriesId = seriesChannel.attributes['xtream-series-id']; const seasons = await loadXtreamSeasons(seriesId, seriesChannel.name); if (seasons && seasons.length > 0) { pushNavigationState(); currentView = { type: 'season_list', data: seasons, title: seriesChannel.name }; renderCurrentView(); } else { showNotification('No se encontraron temporadas para esta serie.', 'info'); } return; } if (seriesChannel) { const isMovistarStream = seriesChannel.url && (seriesChannel.url.toLowerCase().includes('telefonica.com') || seriesChannel.url.toLowerCase().includes('movistarplus.es')); const existingMovistarWindow = isMovistarStream ? Object.values(playerInstances).find(inst => inst.channel && (inst.channel.url.toLowerCase().includes('telefonica.com') || inst.channel.url.toLowerCase().includes('movistarplus.es')) ) : null; if (existingMovistarWindow) { const existingId = Object.keys(playerInstances).find(key => playerInstances[key] === existingMovistarWindow); if (existingId) { showNotification("Reutilizando la ventana de Movistar+ para el nuevo canal.", "info"); playChannelInShaka(seriesChannel, existingId); const instance = playerInstances[existingId]; instance.container.querySelector('.player-window-title').textContent = seriesChannel.name; setActivePlayer(existingId); } } else { createPlayerWindow(seriesChannel); } } else { showNotification('Error: Canal no encontrado para reproducir.', 'error'); } } catch (e) { showNotification('Error al procesar la acción: ' + e.message, 'error'); } } function handleGlobalKeyPress(e) { if (!activePlayerId || !playerInstances[activePlayerId]) return; const instance = playerInstances[activePlayerId]; if (instance.container.style.display === 'none') return; const player = instance.player; const ui = instance.ui; const video = instance.videoElement; if (!video) return; if ($(e.target).is('input, textarea, [contenteditable="true"], .shaka-text-input')) return; let currentChannelIndex = -1; const currentFilteredChannels = getFilteredChannels(); if (instance.channel && instance.channel.url) { currentChannelIndex = currentFilteredChannels.findIndex(ch => ch.url === instance.channel.url); } switch (e.key.toLowerCase()) { case ' ': e.preventDefault(); video.paused ? video.play() : video.pause(); break; case 'f': e.preventDefault(); if (ui) ui.toggleFullScreen(); break; case 'i': e.preventDefault(); showPlayerInfobar(instance.channel, instance.container.querySelector('.player-infobar')); break; case 'm': e.preventDefault(); video.muted = !video.muted; break; case 'arrowleft': e.preventDefault(); if (video.duration && video.currentTime > 0) { video.currentTime = Math.max(0, video.currentTime - (e.shiftKey ? 15 : 5)); } break; case 'arrowright': e.preventDefault(); if (video.duration && video.currentTime < video.duration) { video.currentTime = Math.min(video.duration, video.currentTime + (e.shiftKey ? 15 : 5)); } break; case 'arrowup': e.preventDefault(); video.volume = Math.min(1, video.volume + 0.05); break; case 'arrowdown': e.preventDefault(); video.volume = Math.max(0, video.volume - 0.05); break; case 'pageup': e.preventDefault(); if (currentChannelIndex > 0 && currentFilteredChannels.length > 0) { const prevChannel = currentFilteredChannels[currentChannelIndex - 1]; playChannelInShaka(prevChannel, activePlayerId); } break; case 'pagedown': e.preventDefault(); if (currentChannelIndex !== -1 && currentChannelIndex < currentFilteredChannels.length - 1) { const nextChannel = currentFilteredChannels[currentChannelIndex + 1]; playChannelInShaka(nextChannel, activePlayerId); } break; case 'escape': if (ui && ui.isFullScreen()) { e.preventDefault(); ui.toggleFullScreen(); } break; } } function bindEvents() { $('#sidebarToggleBtn').on('click', async () => { const sidebar = $('#sidebar'); const appContainer = $('#app-container'); sidebar.toggleClass('collapsed expanded'); appContainer.toggleClass('sidebar-collapsed'); userSettings.sidebarCollapsed = sidebar.hasClass('collapsed'); if(userSettings.persistFilters) await saveAppConfigValue('userSettings', userSettings); }); $('#loadUrl').on('click', () => { const url = $('#urlInput').val().trim(); if (url) { if (typeof isXtreamUrl === 'function' && isXtreamUrl(url)) { if (typeof handleXtreamUrl === 'function') handleXtreamUrl(url); } else { loadUrl(url); } } else { showNotification('Introduce una URL válida.', 'info'); } }); $('#fileInput').on('change', loadFile); $('#loadFromDBBtnHeader').on('click', showLoadFromDBModal); $('#saveToDBBtnHeader').on('click', () => { if (!currentM3UContent) { showNotification('No hay lista cargada para guardar.', 'info'); return; } let defaultName = currentM3UName || 'mi_lista'; defaultName = defaultName.replace(/\.(m3u8?|txt|pls|m3uplus)$/i, '').replace(/^\/|\/$/g, ''); if(defaultName.includes('/')) { defaultName = defaultName.substring(defaultName.lastIndexOf('/') + 1); } defaultName = defaultName.replace(/[^\w\s._-]/g, '_').replace(/\s+/g, '_'); if (!defaultName || defaultName === '_') defaultName = 'lista_guardada'; $('#saveM3UNameInput').val(defaultName); const saveModal = new bootstrap.Modal(document.getElementById('saveM3UModal')); saveModal.show(); }); $('#confirmSaveM3UBtn').on('click', handleSaveToDB); $('#openEditorBtn').on('click', launchEditor); $('#applyEditorChangesBtn').on('click', () => { if (typeof editorHandler !== 'undefined' && typeof editorHandler.getFinalData === 'function') { const editorResult = editorHandler.getFinalData(); channels = editorResult.channels; currentGroupOrder = editorResult.groupOrder; regenerateCurrentM3UContentFromString(); filterAndRenderChannels(); showNotification("Cambios del editor aplicados y guardados.", "success"); const editorModalInstance = bootstrap.Modal.getInstance(document.getElementById('editorModal')); if (editorModalInstance) { editorModalInstance.hide(); } } else { showNotification("Error: No se pudieron aplicar los cambios del editor.", "error"); } }); $('#downloadM3UBtnHeader').on('click', downloadCurrentM3U); $('#loadOrangeTvBtnHeader').on('click', async () => { if (typeof generateM3uOrangeTv === 'function') { const orangeTvSourceName = "OrangeTV"; if (typeof removeChannelsBySourceOrigin === 'function') { removeChannelsBySourceOrigin(orangeTvSourceName); } const m3uString = await generateM3uOrangeTv(); if (m3uString && !m3uString.includes("Error general en el proceso") && !m3uString.includes("No se pudieron obtener canales")) { if (typeof appendM3UContent === 'function') { appendM3UContent(m3uString, orangeTvSourceName); } } else { showNotification('No se generaron canales de OrangeTV o hubo un error durante el proceso.', 'warning'); if (channels.length === 0) { if (typeof filterAndRenderChannels === 'function') filterAndRenderChannels(); } } } else { showNotification("Función para cargar OrangeTV no encontrada.", "error"); } }); $('#loadAtresplayerBtnHeader').on('click', async () => { if (typeof generateM3UAtresplayer === 'function') { await generateM3UAtresplayer(); } else { if (typeof showNotification === 'function') showNotification("Funcionalidad Atresplayer no cargada.", "error"); } }); $('#loadBarTvBtnHeader').on('click', async () => { if (typeof generateM3uBarTv === 'function') { await generateM3uBarTv(); } else { if (typeof showNotification === 'function') showNotification("Funcionalidad BarTV no cargada.", "error"); } }); $('#searchInput').on('input', debounce(filterAndRenderChannels, 300)); $('#groupFilterSidebar').on('change', async function() { const selectedGroup = $(this).val(); currentPage = 1; filterAndRenderChannels(); if (userSettings.persistFilters) { userSettings.lastSelectedGroup = selectedGroup; await saveAppConfigValue('userSettings', userSettings); } }); $('#sidebarGroupList').on('click', '.list-group-item', function () { const groupName = $(this).data('group-name'); $('#groupFilterSidebar').val(groupName).trigger('change'); }); $('#showAllChannels').on('click', () => switchFilter('all')); $('#showFavorites').on('click', () => switchFilter('favorites')); $('#showHistory').on('click', () => switchFilter('history')); $('#openEpgModalBtn').on('click', () => $('#epgModal').modal('show')); $('#openMovistarVODModalBtn').on('click', openMovistarVODModal); $('#xtreamBackButton').on('click', popNavigationState); $('#updateDaznBtn').on('click', async () => { if (typeof orchestrateDaznUpdate === 'function') { if (!channels || channels.length === 0) { showNotification('Carga una lista M3U que contenga canales de DAZN primero.', 'info'); return; } let daznM3uUserAgent = null; const daznChannelInM3U = channels.find(ch => (ch.sourceOrigin && ch.sourceOrigin.toLowerCase() === 'dazn') || (ch.url && ch.url.toLowerCase().includes('dazn')) || (ch['tvg-id'] && ch['tvg-id'].toLowerCase().includes('dazn')) ); if (daznChannelInM3U && daznChannelInM3U.vlcOptions && daznChannelInM3U.vlcOptions['http-user-agent']) { daznM3uUserAgent = daznChannelInM3U.vlcOptions['http-user-agent']; } await orchestrateDaznUpdate(daznM3uUserAgent); } else { showNotification('Error: Funcionalidad DAZN no cargada.', 'error'); } }); $('#openXtreamModalBtn').on('click', () => { if (typeof showXtreamConnectionModal === 'function') showXtreamConnectionModal(); }); $('#openManageXCodecPanelsModalBtn').on('click', () => { if (typeof bootstrap !== 'undefined' && typeof bootstrap.Modal !== 'undefined') { const xcodecModalEl = document.getElementById('manageXCodecPanelsModal'); if (xcodecModalEl) { const xcodecModalInstance = bootstrap.Modal.getOrCreateInstance(xcodecModalEl); xcodecModalInstance.show(); if (typeof loadSavedXCodecPanels === 'function') loadSavedXCodecPanels(); } else { showNotification("Error: Modal XCodec no encontrado.", "error"); } } else { showNotification("Error: Bootstrap no cargado, no se puede abrir modal XCodec.", "error"); } }); $('#openSettingsModalBtn').on('click', () => { $('#settingsModal').modal('show'); if (typeof updateMovistarVodCacheStatsUI === 'function') { updateMovistarVodCacheStatsUI(); } }); $(document).on('keydown', handleGlobalKeyPress); $('#player-taskbar').on('click', '.taskbar-item', function() { const windowId = $(this).data('windowId'); if (windowId) { setActivePlayer(windowId); } }); $('#prevPage').on('click', () => changePage(currentPage - 1)); $('#nextPage').on('click', () => changePage(currentPage + 1)); $('#channelGrid').on('click', '.channel-card', handleChannelCardClick); $('#channelGrid').on('mouseenter', '.channel-card', function() { if (!userSettings.enableHoverPreview) return; const card = $(this); if (Object.keys(playerInstances).length > 0) return; if (activeCardPreviewPlayer && activeCardPreviewElement && activeCardPreviewElement[0] !== card[0]) { if (typeof destroyActiveCardPreviewPlayer === 'function') destroyActiveCardPreviewPlayer(); } clearTimeout(hoverPlayTimeout); hoverPlayTimeout = setTimeout(async () => { const channelUrl = card.data('url'); if (!channelUrl) return; const channel = channels.find(c => c.url === channelUrl); if (channel) { if (channel.attributes && channel.attributes['xtream-type'] === 'series') { return; } if (typeof playChannelInCardPreview === 'function') { activeCardPreviewElement = card; card.addClass('is-playing-preview'); await playChannelInCardPreview(channel, card.find('.card-video-preview-container')[0]); } } }, HOVER_PLAY_DELAY); }); $('#channelGrid').on('mouseleave', '.channel-card', function() { clearTimeout(hoverPlayTimeout); if (activeCardPreviewElement && activeCardPreviewElement[0] === $(this)[0]) { if (typeof destroyActiveCardPreviewPlayer === 'function') destroyActiveCardPreviewPlayer(); } }); $('#channelGrid').on('click', '.favorite-btn', handleFavoriteButtonClick); $('#channelGrid').on('error', '.channel-logo', function () { this.classList.add('error'); this.style.display = 'none'; const placeholder = $(this).siblings('.epg-icon-placeholder'); if (placeholder.length) { placeholder.show(); } else { $(this).parent().addClass('no-logo-fallback'); } }); $('#saveSettingsBtn').on('click', () => { if(typeof saveUserSettings === 'function') saveUserSettings(); }); const rangeInputsSelector = '#epgNameMatchThreshold, #playerBufferInput, #channelCardSizeInput, #abrDefaultBandwidthEstimateInput, #manifestRetryMaxAttemptsInput, #manifestRetryTimeoutInput, #segmentRetryMaxAttemptsInput, #segmentRetryTimeoutInput, #epgDensityInput, #channelsPerPageInput, #particleOpacityInput, #shakaDefaultPresentationDelayInput, #shakaAudioVideoSyncThresholdInput, #playerWindowOpacityInput'; $(rangeInputsSelector).on('input', function () { const id = this.id; const value = $(this).val(); if(id === 'epgNameMatchThreshold') $('#epgNameMatchThresholdValue').text(value + '%'); if(id === 'playerBufferInput') $('#playerBufferValue').text(value + 's'); if(id === 'channelCardSizeInput') { const size = value + 'px'; $('#channelCardSizeValue').text(size); document.documentElement.style.setProperty('--m3u-grid-minmax-size', size); } if(id === 'channelsPerPageInput') $('#channelsPerPageValue').text(value); if(id === 'abrDefaultBandwidthEstimateInput') $('#abrDefaultBandwidthEstimateValue').text(value + ' Kbps'); if(id === 'manifestRetryMaxAttemptsInput') $('#manifestRetryMaxAttemptsValue').text(value); if(id === 'manifestRetryTimeoutInput') $('#manifestRetryTimeoutValue').text(value); if(id === 'segmentRetryMaxAttemptsInput') $('#segmentRetryMaxAttemptsValue').text(value); if(id === 'segmentRetryTimeoutInput') $('#segmentRetryTimeoutValue').text(value); if(id === 'epgDensityInput') $('#epgDensityValue').text(value + 'px/h'); if(id === 'particleOpacityInput') $('#particleOpacityValue').text(value + '%'); if(id === 'shakaDefaultPresentationDelayInput') $('#shakaDefaultPresentationDelayValue').text(parseFloat(value).toFixed(parseFloat(value) % 1 === 0 ? 0 : 1) + 's'); if(id === 'shakaAudioVideoSyncThresholdInput') $('#shakaAudioVideoSyncThresholdValue').text(parseFloat(value).toFixed(parseFloat(value) % 1 === 0 ? 0 : 2) + 's'); if(id === 'playerWindowOpacityInput') { $('#playerWindowOpacityValue').text(Math.round(value * 100) + '%'); Object.values(playerInstances).forEach(instance => { if (instance.container) { instance.container.style.setProperty('--player-window-opacity', value); } }); } }); $('#exportSettingsBtn').on('click', () => { if(typeof exportSettings === 'function') exportSettings(); }); $('#importSettingsInput').on('change', (event) => { if(typeof importSettings === 'function') importSettings(event); }); $('#clearCacheBtn').on('click', clearCacheAndReload); $('#connectXtreamServerBtn').on('click', () => { if (typeof handleConnectXtreamServer === 'function') handleConnectXtreamServer(); }); $('#xtreamConfirmGroupSelectionBtn').on('click', () => { if (typeof handleXtreamGroupSelection === 'function') handleXtreamGroupSelection(); }); $('#saveXtreamServerBtn').on('click', () => { if (typeof handleSaveXtreamServer === 'function') handleSaveXtreamServer(); }); $('#savedXtreamServersList').on('click', '.load-xtream-server-btn', function() { const serverId = parseInt($(this).data('id'), 10); if (typeof loadXtreamServerToForm === 'function') loadXtreamServerToForm(serverId); }); $('#savedXtreamServersList').on('click', '.delete-xtream-server-btn', function() { const serverId = parseInt($(this).data('id'), 10); if (typeof handleDeleteXtreamServer === 'function') handleDeleteXtreamServer(serverId); }); $('#sendM3UToServerBtn').on('click', () => { const urlFromInput = $('#m3uUploadServerUrlInput').val()?.trim(); if (typeof sendM3UToServer === 'function') { sendM3UToServer(urlFromInput); } else { showNotification("Error: Función para enviar M3U no encontrada.", "error"); } }); $('#movistarLoginBtnSettings').on('click', handleMovistarLogin); $('#movistarValidateAllBtnSettings').on('click', handleMovistarValidateAllTokens); $('#movistarDeleteExpiredBtnSettings').on('click', handleMovistarDeleteExpiredTokens); $('#movistarAddManualTokenBtnSettings').on('click', handleMovistarAddManualToken); $('#movistarLongTokensTableBodySettings').on('click', '.delete-long-token-btn-settings', function() { const tokenId = $(this).closest('tr').data('tokenid'); if (tokenId) handleMovistarDeleteSingleLongToken(tokenId); }); $('#movistarLongTokensTableBodySettings').on('click', '.validate-long-token-btn-settings', function() { const tokenId = $(this).closest('tr').data('tokenid'); if (tokenId) handleMovistarValidateSingleLongToken(tokenId); }); $('#movistarLongTokensTableBodySettings').on('click', 'tr', function(event) { const tokenId = $(this).data('tokenid'); if (tokenId && !$(event.target).closest('button').length) { selectedMovistarLongTokenIdForSettings = tokenId; $('#movistarLongTokensTableBodySettings tr').removeClass('table-active'); $(this).addClass('table-active'); $('#selectedLongTokenIdDisplaySettings').text(`...${tokenId.slice(-12)}`); $('#movistarDeviceManagementSectionSettings').show(); $('#movistarLoadDevicesForSettingsBtn').prop('disabled', false); $('#movistarDevicesListForSettings').html('
    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(` ${chrome.i18n.getMessage("movistarLoading") || "Cargando..."}`); selectedMovistarLongTokenIdForSettings = null; $('#movistarDeviceManagementSectionSettings').hide(); $('#movistarLoadDevicesForSettingsBtn').prop('disabled', true); try { const tokens = await window.MovistarTokenHandler.getAllLongTokens(); if (tokens.length === 0) { tbody.html(`${chrome.i18n.getMessage("xtreamNoSavedServers") || "No hay tokens largos guardados."}`); return; } tokens.sort((a, b) => (b.expiry_tstamp || 0) - (a.expiry_tstamp || 0)); tbody.empty(); const nowSeconds = Math.floor(Date.now() / 1000); tokens.forEach(token => { const isExpired = (token.expiry_tstamp || 0) < nowSeconds; const expiryDate = token.expiry_tstamp ? new Date(token.expiry_tstamp * 1000) : null; const expiryString = expiryDate ? expiryDate.toLocaleDateString() : 'N/D'; let statusText = isExpired ? 'Expirado' : (token.device_id ? 'Válido' : 'Sin DeviceID'); let statusClass = isExpired ? 'text-danger' : (token.device_id ? 'text-success' : 'text-warning'); const tr = $('').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($('