diff --git a/_locales/de/messages.json b/_locales/de/messages.json index 53d978e..790802e 100644 --- a/_locales/de/messages.json +++ b/_locales/de/messages.json @@ -346,5 +346,7 @@ "m3uInstruction1": { "message": "Wählen Sie einen Server aus der Liste aus." }, "m3uInstruction2": { "message": "Wählen Sie eine oder mehrere Bibliotheken aus, die Sie einschließen möchten." }, "m3uInstruction3": { "message": "Klicken Sie auf die Download-Schaltfläche." }, - "m3uInstruction4": { "message": "Importieren Sie die .m3u-Datei in Ihren kompatiblen Player." } + "m3uInstruction4": { "message": "Importieren Sie die .m3u-Datei in Ihren kompatiblen Player." }, + "settingsRegionLabel": { "message": "Region für die Inhaltsentdeckung" }, + "allRegions": { "message": "Alle Regionen" } } \ No newline at end of file diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 9bc31ef..04e533e 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -343,5 +343,7 @@ "m3uInstruction1": { "message": "Choose a server from the list." }, "m3uInstruction2": { "message": "Select one or more libraries to include." }, "m3uInstruction3": { "message": "Click the download button." }, - "m3uInstruction4": { "message": "Import the .m3u file into your compatible player." } + "m3uInstruction4": { "message": "Import the .m3u file into your compatible player." }, + "settingsRegionLabel": { "message": "Region for content discovery" }, + "allRegions": { "message": "All regions" } } \ No newline at end of file diff --git a/_locales/es/messages.json b/_locales/es/messages.json index 0a29e0d..2e5c8ba 100644 --- a/_locales/es/messages.json +++ b/_locales/es/messages.json @@ -343,5 +343,7 @@ "m3uInstruction1": { "message": "Elige un servidor de la lista." }, "m3uInstruction2": { "message": "Selecciona una o más bibliotecas para incluir." }, "m3uInstruction3": { "message": "Haz clic en el botón de descarga." }, - "m3uInstruction4": { "message": "Importa el archivo .m3u en tu reproductor compatible." } + "m3uInstruction4": { "message": "Importa el archivo .m3u en tu reproductor compatible." }, + "settingsRegionLabel": { "message": "Región para descubrimiento de contenido" }, + "allRegions": { "message": "Todas las regiones" } } \ No newline at end of file diff --git a/_locales/fr/messages.json b/_locales/fr/messages.json index fc9f7bb..f7a387e 100644 --- a/_locales/fr/messages.json +++ b/_locales/fr/messages.json @@ -343,5 +343,7 @@ "m3uInstruction1": { "message": "Choisissez un serveur dans la liste." }, "m3uInstruction2": { "message": "Sélectionnez une ou plusieurs bibliothèques à inclure." }, "m3uInstruction3": { "message": "Cliquez sur le bouton de téléchargement." }, - "m3uInstruction4": { "message": "Importez le fichier .m3u dans votre lecteur compatible." } + "m3uInstruction4": { "message": "Importez le fichier .m3u dans votre lecteur compatible." }, + "settingsRegionLabel": { "message": "Région pour la découverte de contenu" }, + "allRegions": { "message": "Toutes les régions" } } \ No newline at end of file diff --git a/_locales/it/messages.json b/_locales/it/messages.json index c868873..eaab74d 100644 --- a/_locales/it/messages.json +++ b/_locales/it/messages.json @@ -343,5 +343,7 @@ "m3uInstruction1": { "message": "Scegli un server dalla lista." }, "m3uInstruction2": { "message": "Seleziona una o più librerie da includere." }, "m3uInstruction3": { "message": "Clicca sul pulsante di download." }, - "m3uInstruction4": { "message": "Importa il file .m3u nel tuo lettore compatibile." } + "m3uInstruction4": { "message": "Importa il file .m3u nel tuo lettore compatibile." }, + "settingsRegionLabel": { "message": "Regione per la scoperta di contenuti" }, + "allRegions": { "message": "Tutte le regioni" } } \ No newline at end of file diff --git a/_locales/pt/messages.json b/_locales/pt/messages.json index 5946e1f..75f8760 100644 --- a/_locales/pt/messages.json +++ b/_locales/pt/messages.json @@ -343,5 +343,7 @@ "m3uInstruction1": { "message": "Escolha um servidor da lista." }, "m3uInstruction2": { "message": "Selecione uma ou mais bibliotecas para incluir." }, "m3uInstruction3": { "message": "Clique no botão de download." }, - "m3uInstruction4": { "message": "Importe o arquivo .m3u para o seu reprodutor compatível." } + "m3uInstruction4": { "message": "Importe o arquivo .m3u para o seu reprodutor compatível." }, + "settingsRegionLabel": { "message": "Região para descoberta de conteúdo" }, + "allRegions": { "message": "Todas as regiões" } } \ No newline at end of file diff --git a/js/api.js b/js/api.js index 6e75844..1417410 100644 --- a/js/api.js +++ b/js/api.js @@ -5,31 +5,31 @@ import { getFromDB } from './db.js'; import { _ } from './utils.js'; export async function fetchTMDB(endpoint, params = {}, signal) { - const langMap = { - 'es': 'es-ES', - 'en': 'en-US', - 'fr': 'fr-FR', - 'de': 'de-DE', - 'it': 'it-IT', - 'pt': 'pt-BR' + const region = state.settings.watchRegion || 'US'; + + const regionToLangMap = { + 'ES': 'es-ES', 'MX': 'es-MX', + 'FR': 'fr-FR', 'CA': 'fr-CA', + 'DE': 'de-DE', + 'IT': 'it-IT', + 'PT': 'pt-PT', 'BR': 'pt-BR', + 'US': 'en-US', 'GB': 'en-GB' }; - const tmdbLang = langMap[state.settings.language] || 'en-US'; + + const tmdbLang = regionToLangMap[region] || 'en-US'; const [path, existingQuery] = endpoint.split('?'); const finalParams = new URLSearchParams(existingQuery); finalParams.set('api_key', state.settings.apiKey); finalParams.set('language', tmdbLang); + finalParams.set('watch_region', region); for (const [key, value] of Object.entries(params)) { if (value) { finalParams.set(key, value); } } - - if (state.settings.watchRegion && !finalParams.has('watch_region')) { - finalParams.set('watch_region', state.settings.watchRegion); - } const url = `https://api.themoviedb.org/3/${path}?${finalParams.toString()}`; diff --git a/js/config.js b/js/config.js index 030e502..9cde9d5 100644 --- a/js/config.js +++ b/js/config.js @@ -1,5 +1,5 @@ export const config = { defaultApiKey: '4e44d9029b1270a757cddc766a1bcb63', dbName: 'PlexDB', - dbVersion: 7, + dbVersion: 9, }; \ No newline at end of file diff --git a/js/constants.js b/js/constants.js new file mode 100644 index 0000000..3d1aeaa --- /dev/null +++ b/js/constants.js @@ -0,0 +1,20 @@ +export const API_URLS = { + TMDB_BASE: 'https://api.themoviedb.org/3', + TMDB_IMAGE_BASE: 'https://image.tmdb.org/t/p', + PLEX_TV: 'https://plex.tv/api/resources', + YOUTUBE_EMBED: 'https://www.youtube.com/embed/', + IMDB_TITLE: 'https://www.imdb.com/title/' +}; + +export const CONFIG = { + DEFAULT_API_KEY: '4e44d9029b1270a757cddc766a1bcb63', + DB_NAME: 'PlexDB', + DB_VERSION: 6 +}; + +export const STORAGE_KEYS = { + USER_HISTORY: 'cineplex_userHistory', + USER_PREFERENCES: 'cineplex_userPreferences', + FAVORITES: 'cineplex_favorites', + RECOMMENDATIONS_CACHE: 'cineplex_recommendations' +}; \ No newline at end of file diff --git a/js/main.js b/js/main.js index 7cc609b..8dae4a3 100644 --- a/js/main.js +++ b/js/main.js @@ -13,7 +13,15 @@ async function loadSettings() { const settingsData = await getFromDB('settings'); if (settingsData && settingsData.length > 0) { state.settings = { ...state.settings, ...settingsData[0] }; - } else { + } + + // Ensure a default region is set if none exists + if (!state.settings.watchRegion) { + state.settings.watchRegion = 'US'; + } + + // Ensure language is always set, fallback to UI language + if (!state.settings.language) { state.settings.language = chrome.i18n.getUILanguage().split('-')[0]; } @@ -21,19 +29,6 @@ async function loadSettings() { state.settings.apiKey = config.defaultApiKey; } - if (!state.settings.watchRegion) { - const tmdbLangMap = { - 'es': 'es-ES', - 'en': 'en-US', - 'fr': 'fr-FR', - 'de': 'de-DE', - 'it': 'it-IT', - 'pt': 'pt-BR' - }; - const fullLangCode = tmdbLangMap[state.settings.language] || 'en-US'; - state.settings.watchRegion = fullLangCode.split('-')[1] || 'US'; - } - const jellyfinSettingsData = await getFromDB('jellyfin_settings'); if (jellyfinSettingsData && jellyfinSettingsData.length > 0) { state.jellyfinSettings = { ...state.jellyfinSettings, ...jellyfinSettingsData[0] }; @@ -41,7 +36,8 @@ async function loadSettings() { } catch (error) { console.error("Could not load settings from DB, using defaults.", error); - state.settings.language = chrome.i18n.getUILanguage().split('-')[0]; + // Fallback to defaults in case of any error + state.settings.watchRegion = 'US'; } } diff --git a/js/php-script-generator.js b/js/php-script-generator.js new file mode 100644 index 0000000..1e7c10d --- /dev/null +++ b/js/php-script-generator.js @@ -0,0 +1,59 @@ +import { showNotification, _ } from './utils.js'; + +export const phpScriptGenerator = (() => { + let dom = {}; + + function cacheDom() { + const settingsModal = document.getElementById('settingsModal'); + if (!settingsModal) return false; + + dom.secretKeyCheck = settingsModal.querySelector('#phpSecretKeyCheck'); + dom.secretKey = settingsModal.querySelector('#phpSecretKey'); + dom.savePath = settingsModal.querySelector('#phpSavePath'); + dom.filename = settingsModal.querySelector('#phpFilename'); + dom.fileActionAppendRadio = settingsModal.querySelector('#phpFileActionAppend'); + dom.generatedCode = settingsModal.querySelector('#generatedPhpCode'); + dom.generateBtn = settingsModal.querySelector('#generatePhpScriptBtn'); + dom.copyBtn = settingsModal.querySelector('#copyPhpScriptBtn'); + + return dom.generateBtn && dom.copyBtn; + } + + function init() { + if (!cacheDom()) { + return; + } + dom.generateBtn.addEventListener('click', generatePhpScript); + dom.copyBtn.addEventListener('click', copyScript); + } + + function generatePhpScript() { + const useSecretKey = dom.secretKeyCheck.checked; + const secretKey = dom.secretKey.value.trim(); + const savePath = dom.savePath.value.trim(); + const filename = dom.filename.value.trim() || 'CinePlex_Playlist.m3u'; + const appendToFile = dom.fileActionAppendRadio.checked; + + let script = ` $success, 'message' => $message, 'filename' => $filename]);\n exit;\n}\n`; + if (useSecretKey) { + script += `\n$auth_key = isset($_SERVER['HTTP_X_SECRET_KEY']) ? $_SERVER['HTTP_X_SECRET_KEY'] : '';\nif (!defined('SECRET_KEY') || SECRET_KEY === '' || $auth_key !== SECRET_KEY) {\n sendResponse(false, 'Acceso no autorizado. Clave secreta inválida o no proporcionada.', '', 403);\n}\n`; + } + script += `\n$json_data = file_get_contents('php://input');\n$data = json_decode($json_data, true);\n\nif (json_last_error() !== JSON_ERROR_NONE) {\n sendResponse(false, 'Error: Datos JSON inválidos.');\n}\n\nif (!isset($data['streams']) || !is_array($data['streams']) || empty($data['streams'])) {\n sendResponse(false, 'Error: El JSON debe contener un array "streams" no vacío.');\n}\n\n$save_dir = SAVE_DIRECTORY !== '' ? rtrim(SAVE_DIRECTORY, '/\\') : __DIR__;\n\nif (!is_dir($save_dir) || !is_writable($save_dir)) {\n sendResponse(false, 'Error del servidor: El directorio de destino no existe o no tiene permisos de escritura.', '', 500);\n}\n\n$safe_filename = preg_replace('/[^\\w\\s._-]/', '', basename(FILENAME));\n$safe_filename = preg_replace('/\\s+/', '_', $safe_filename);\n$target_path = $save_dir . DIRECTORY_SEPARATOR . $safe_filename;\n\n$content_to_write = "";\n\nif (FILE_ACTION_APPEND) {\n $file_exists = file_exists($target_path);\n if (!$file_exists) {\n $content_to_write .= "#EXTM3U\\n";\n }\n foreach ($data['streams'] as $stream) {\n if (isset($stream['extinf'], $stream['url'])) {\n $content_to_write .= trim($stream['extinf']) . "\\n";\n $content_to_write .= trim($stream['url']) . "\\n";\n }\n }\n if (file_put_contents($target_path, $content_to_write, FILE_APPEND | LOCK_EX) !== false) {\n sendResponse(true, 'Streams añadidos correctamente al archivo.', $safe_filename, 200);\n } else {\n sendResponse(false, 'Error del servidor: No se pudo añadir contenido al archivo.', '', 500);\n }\n} else { // Overwrite mode\n $content_to_write = "#EXTM3U\\n";\n foreach ($data['streams'] as $stream) {\n if (isset($stream['extinf'], $stream['url'])) {\n $content_to_write .= trim($stream['extinf']) . "\\n";\n $content_to_write .= trim($stream['url']) . "\\n";\n }\n }\n if (file_put_contents($target_path, $content_to_write, LOCK_EX) !== false) {\n sendResponse(true, 'Archivo de streams sobrescrito correctamente.', $safe_filename, 201);\n } else {\n sendResponse(false, 'Error del servidor: No se pudo escribir el archivo.', '', 500);\n }\n}\n?>`; + dom.generatedCode.value = script; + showNotification(_("scriptGenerated"), "success"); + } + + function copyScript() { + if (!dom.generatedCode.value || dom.generatedCode.value.trim() === '') { + showNotification(_("errorGeneratingScript"), "warning"); + return; + } + navigator.clipboard.writeText(dom.generatedCode.value).then(() => { + showNotification(_("scriptCopied"), "success"); + }).catch(err => { + showNotification(_("errorCopyingScript"), "error"); + }); + } + + return { init }; +})(); \ No newline at end of file diff --git a/js/providers.js b/js/providers.js index 24cd180..36d4ab1 100644 --- a/js/providers.js +++ b/js/providers.js @@ -104,13 +104,11 @@ export function renderProviders(providers) { export async function getProviderItems(providerId, page = 1) { try { const watchRegion = state.settings.watchRegion || 'US'; - const language = state.settings.language || 'en-US'; const params = { with_watch_providers: providerId, watch_region: watchRegion, page: page, - language: language }; const moviesResponse = await fetchTMDB('discover/movie', params); diff --git a/js/ui.js b/js/ui.js index 3f77472..0c5b5f4 100644 --- a/js/ui.js +++ b/js/ui.js @@ -1557,7 +1557,6 @@ export function activateSettingsTab(tabId) { export function openSettingsModal() { document.getElementById('tmdbApiKey').value = state.settings.apiKey; - document.getElementById('appLanguage').value = state.settings.language; document.getElementById('phpScriptUrl').value = state.settings.phpScriptUrl || ''; document.getElementById('lightModeToggle').checked = state.settings.theme === 'light'; document.getElementById('showHeroToggle').checked = state.settings.showHero; @@ -1580,11 +1579,10 @@ export function openSettingsModal() { } export async function saveSettings() { - const oldLanguage = state.settings.language; + const oldRegion = state.settings.watchRegion; const newSettings = { id: 'user_settings', apiKey: document.getElementById('tmdbApiKey').value.trim(), - language: document.getElementById('appLanguage').value, theme: document.getElementById('lightModeToggle').checked ? 'light' : 'dark', showHero: document.getElementById('showHeroToggle').checked, phpScriptUrl: document.getElementById('phpScriptUrl').value.trim(), @@ -1615,11 +1613,12 @@ export async function saveSettings() { showNotification(_('settingsSavedSuccess'), 'success'); applyTheme(state.settings.theme); applyHeroVisibility(state.settings.showHero); - bootstrap.Modal.getInstance(document.getElementById('settingsModal'))?.hide(); - if (newSettings.language !== oldLanguage) { - showNotification(_('languageChangeReload'), 'info', 4000); - setTimeout(() => window.location.reload(), 4000); + + if (newSettings.watchRegion !== oldRegion) { + loadInitialContent(); } + + bootstrap.Modal.getInstance(document.getElementById('settingsModal'))?.hide(); } catch (error) { showNotification(_('errorSavingSettings'), 'error'); } diff --git a/plex.html b/plex.html index 202983b..460134b 100644 --- a/plex.html +++ b/plex.html @@ -400,17 +400,6 @@ -