diff --git a/_locales/de/messages.json b/_locales/de/messages.json index 04ea2f3..f4618a0 100644 --- a/_locales/de/messages.json +++ b/_locales/de/messages.json @@ -58,6 +58,7 @@ "settingsTitleFull": { "message": "Einstellungen und Konfiguration" }, "settingsTabGeneral": { "message": "Allgemein" }, "settingsTabPlex": { "message": "Plex" }, + "settingsTabJellyfin": { "message": "Jellyfin" }, "settingsTabPhpGen": { "message": "PHP-Generator" }, "settingsTabData": { "message": "Daten" }, "settingsApiServer": { "message": "API- und Serverkonfiguration" }, @@ -80,6 +81,12 @@ "settingsPlexTokens": { "message": "Plex-Tokens" }, "settingsPlexTokensDesc": { "message": "Bearbeite die Liste der Plex-Tokens (JSON-Format)." }, "settingsSaveTokens": { "message": "Tokens speichern" }, + "settingsJellyfinTitle": { "message": "Jellyfin-Einstellungen" }, + "settingsJellyfinDesc": { "message": "Füge die Daten deines Jellyfin-Servers hinzu, um dessen Inhalt zu scannen." }, + "jellyfinUrlLabel": { "message": "Jellyfin Server-URL" }, + "jellyfinUserLabel": { "message": "Benutzername" }, + "jellyfinPassLabel": { "message": "Passwort" }, + "jellyfinConnectAndScan": { "message": "Verbinden und Scannen" }, "settingsPhpGenTitle": { "message": "PHP-Server-Skript-Generator" }, "settingsPhpFileOptions": { "message": "Dateioptionen" }, "settingsPhpSavePathLabel": { "message": "Speicherpfad auf dem Server" }, @@ -286,5 +293,26 @@ "errorParsingPlexXml": { "message": "Fehler beim Parsen von Plex-XML." }, "untitled": { "message": "Ohne Titel" }, "itemCount": { "message": "$count$ Elemente", "placeholders": { "count": { "content": "$1" } } }, - "noPhotoServers": { "message": "Keine Foto-Server" } + "noPhotoServers": { "message": "Keine Foto-Server" }, + "jellyfinScanInProgress": { "message": "Jellyfin-Scan läuft bereits." }, + "jellyfinScanning": { "message": "Scanne Jellyfin..." }, + "jellyfinMissingCredentials": { "message": "Bitte vervollständige die Jellyfin-URL und den Benutzernamen." }, + "jellyfinConnecting": { "message": "Verbinde mit Jellyfin unter: $url$", "placeholders": { "url": { "content": "$1" } } }, + "jellyfinAuthFailed": { "message": "Jellyfin-Authentifizierung fehlgeschlagen: $message$", "placeholders": { "message": { "content": "$1" } } }, + "jellyfinAuthSuccess": { "message": "Jellyfin-Authentifizierung erfolgreich." }, + "jellyfinFetchingLibraries": { "message": "Bibliotheken werden abgerufen..." }, + "jellyfinFetchFailed": { "message": "Fehler beim Abrufen der Bibliotheken: $message$", "placeholders": { "message": { "content": "$1" } } }, + "jellyfinNoMediaLibraries": { "message": "Keine Film- oder Serienbibliotheken auf Jellyfin gefunden." }, + "jellyfinLibrariesFound": { "message": "$count$ Medienbibliothek(en) gefunden.", "placeholders": { "count": { "content": "$1" } } }, + "jellyfinLibraryScanSuccess": { "message": "[Erfolg] '$libraryName gescannt, $count$ Titel hinzugefügt.", "placeholders": { "libraryName": { "content": "$1" }, "count": { "content": "$2" } } }, + "jellyfinLibraryScanFailed": { "message": "Fehler beim Scannen der Bibliothek '$libraryName.", "placeholders": { "libraryName": { "content": "$1" } } }, + "jellyfinScanSuccess": { "message": "Jellyfin-Scan abgeschlossen. $movies$ Filme und $series$ Serien hinzugefügt.", "placeholders": { "movies": { "content": "$1" }, "series": { "content": "$2" } } }, + "noJellyfinCredentials": { "message": "Jellyfin-Anmeldeinformationen nicht konfiguriert." }, + "notFoundOnJellyfin": { "message": "\"$query$\" auf Jellyfin nicht gefunden.", "placeholders": { "query": { "content": "$1" } } }, + "notFoundOnAnyServer": { "message": "\"$query$\" auf keinem Server gefunden.", "placeholders": { "query": { "content": "$1" } } }, + "localOnPlex": { "message": "Auf Plex" }, + "searchOnPlex": { "message": "Auf Plex suchen" }, + "jellyfinTitle": { "message": "Jellyfin-Inhalt" }, + "noJellyfinContent": { "message": "Kein Jellyfin-Inhalt gefunden." }, + "noJellyfinContentSub": { "message": "Stelle sicher, dass du deinen Jellyfin-Server in den Einstellungen gescannt hast." } } \ No newline at end of file diff --git a/_locales/en/messages.json b/_locales/en/messages.json index faa49c6..555b084 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -58,6 +58,7 @@ "settingsTitleFull": { "message": "Settings and Configuration" }, "settingsTabGeneral": { "message": "General" }, "settingsTabPlex": { "message": "Plex" }, + "settingsTabJellyfin": { "message": "Jellyfin" }, "settingsTabPhpGen": { "message": "PHP Generator" }, "settingsTabData": { "message": "Data" }, "settingsApiServer": { "message": "API and Server Configuration" }, @@ -80,6 +81,12 @@ "settingsPlexTokens": { "message": "Plex Tokens" }, "settingsPlexTokensDesc": { "message": "Edit the list of Plex tokens (JSON format)." }, "settingsSaveTokens": { "message": "Save Tokens" }, + "settingsJellyfinTitle": { "message": "Jellyfin Settings" }, + "settingsJellyfinDesc": { "message": "Add your Jellyfin server details to scan its content." }, + "jellyfinUrlLabel": { "message": "Jellyfin Server URL" }, + "jellyfinUserLabel": { "message": "Username" }, + "jellyfinPassLabel": { "message": "Password" }, + "jellyfinConnectAndScan": { "message": "Connect and Scan" }, "settingsPhpGenTitle": { "message": "PHP Server Script Generator" }, "settingsPhpFileOptions": { "message": "File Options" }, "settingsPhpSavePathLabel": { "message": "Save Path on Server" }, @@ -286,5 +293,26 @@ "errorParsingPlexXml": { "message": "Error parsing Plex XML." }, "untitled": { "message": "Untitled" }, "itemCount": { "message": "$count$ items", "placeholders": { "count": { "content": "$1" } } }, - "noPhotoServers": { "message": "No photo servers" } + "noPhotoServers": { "message": "No photo servers" }, + "jellyfinScanInProgress": { "message": "Jellyfin scan is already in progress." }, + "jellyfinScanning": { "message": "Scanning Jellyfin..." }, + "jellyfinMissingCredentials": { "message": "Please complete the Jellyfin URL and username." }, + "jellyfinConnecting": { "message": "Connecting to Jellyfin at: $url$", "placeholders": { "url": { "content": "$1" } } }, + "jellyfinAuthFailed": { "message": "Jellyfin authentication failed: $message$", "placeholders": { "message": { "content": "$1" } } }, + "jellyfinAuthSuccess": { "message": "Jellyfin authentication successful." }, + "jellyfinFetchingLibraries": { "message": "Fetching libraries..." }, + "jellyfinFetchFailed": { "message": "Failed to fetch libraries: $message$", "placeholders": { "message": { "content": "$1" } } }, + "jellyfinNoMediaLibraries": { "message": "No movie or series libraries found on Jellyfin." }, + "jellyfinLibrariesFound": { "message": "$count$ media library(s) found.", "placeholders": { "count": { "content": "$1" } } }, + "jellyfinLibraryScanSuccess": { "message": "[Success] Scanned '$libraryName, added $count$ titles.", "placeholders": { "libraryName": { "content": "$1" }, "count": { "content": "$2" } } }, + "jellyfinLibraryScanFailed": { "message": "Failed to scan library '$libraryName." , "placeholders": { "libraryName": { "content": "$1" } } }, + "jellyfinScanSuccess": { "message": "Jellyfin scan completed. Added $movies$ movies and $series$ series.", "placeholders": { "movies": { "content": "$1" }, "series": { "content": "$2" } } }, + "noJellyfinCredentials": { "message": "Jellyfin credentials not configured." }, + "notFoundOnJellyfin": { "message": "\"$query$\" not found on Jellyfin.", "placeholders": { "query": { "content": "$1" } } }, + "notFoundOnAnyServer": { "message": "\"$query$\" not found on any server.", "placeholders": { "query": { "content": "$1" } } }, + "localOnPlex": { "message": "On Plex" }, + "searchOnPlex": { "message": "Search on Plex" }, + "jellyfinTitle": { "message": "Jellyfin Content" }, + "noJellyfinContent": { "message": "No Jellyfin content found." }, + "noJellyfinContentSub": { "message": "Make sure you have scanned your Jellyfin server in the settings." } } \ No newline at end of file diff --git a/_locales/es/messages.json b/_locales/es/messages.json index 92dbc94..f208a0e 100644 --- a/_locales/es/messages.json +++ b/_locales/es/messages.json @@ -58,6 +58,7 @@ "settingsTitleFull": { "message": "Ajustes y Configuración" }, "settingsTabGeneral": { "message": "General" }, "settingsTabPlex": { "message": "Plex" }, + "settingsTabJellyfin": { "message": "Jellyfin" }, "settingsTabPhpGen": { "message": "Generador PHP" }, "settingsTabData": { "message": "Datos" }, "settingsApiServer": { "message": "Configuración de API y Servidor" }, @@ -80,6 +81,12 @@ "settingsPlexTokens": { "message": "Tokens de Plex" }, "settingsPlexTokensDesc": { "message": "Edita la lista de tokens de Plex (formato JSON)." }, "settingsSaveTokens": { "message": "Guardar Tokens" }, + "settingsJellyfinTitle": { "message": "Configuración de Jellyfin" }, + "settingsJellyfinDesc": { "message": "Añade los datos de tu servidor Jellyfin para escanear su contenido." }, + "jellyfinUrlLabel": { "message": "URL del Servidor Jellyfin" }, + "jellyfinUserLabel": { "message": "Nombre de Usuario" }, + "jellyfinPassLabel": { "message": "Contraseña" }, + "jellyfinConnectAndScan": { "message": "Conectar y Escanear" }, "settingsPhpGenTitle": { "message": "Generador de Script PHP para el Servidor" }, "settingsPhpFileOptions": { "message": "Opciones del Archivo" }, "settingsPhpSavePathLabel": { "message": "Ruta de Guardado en el Servidor" }, @@ -273,7 +280,7 @@ "invalidStreamInfo": {"message": "Información inválida."}, "dbUnavailableForStreams": {"message": "Base de datos local no disponible."}, "noPlexServersForStreams": {"message": "No hay servidores Plex."}, - "notFoundOnServers": {"message": "No se encontró \"$query$\" en los servidores.", "placeholders": {"query": {"content": "$1"}}}, + "notFoundOnServers": {"message": "No se encontró \"$query$\" en los servidores de Plex.", "placeholders": {"query": {"content": "$1"}}}, "relativeTime_justNow": { "message": "Ahora mismo" }, "relativeTime_minutesAgo": { "message": "Hace $count$ minutos", "placeholders": { "count": { "content": "$1" } } }, "relativeTime_hoursAgo": { "message": "Hace $count$ horas", "placeholders": { "count": { "content": "$1" } } }, @@ -286,5 +293,26 @@ "errorParsingPlexXml": { "message": "Error al analizar el XML de Plex." }, "untitled": { "message": "Sin título" }, "itemCount": { "message": "$count$ elementos", "placeholders": { "count": { "content": "$1" } } }, - "noPhotoServers": { "message": "No hay servidores de fotos" } + "noPhotoServers": { "message": "No hay servidores de fotos" }, + "jellyfinScanInProgress": { "message": "El escaneo Jellyfin ya está en curso." }, + "jellyfinScanning": { "message": "Escaneando Jellyfin..." }, + "jellyfinMissingCredentials": { "message": "Por favor, completa la URL y el usuario de Jellyfin." }, + "jellyfinConnecting": { "message": "Conectando a Jellyfin en: $url$", "placeholders": { "url": { "content": "$1" } } }, + "jellyfinAuthFailed": { "message": "Autenticación Jellyfin fallida: $message$", "placeholders": { "message": { "content": "$1" } } }, + "jellyfinAuthSuccess": { "message": "Autenticación Jellyfin exitosa." }, + "jellyfinFetchingLibraries": { "message": "Obteniendo bibliotecas..." }, + "jellyfinFetchFailed": { "message": "Error al obtener bibliotecas: $message$", "placeholders": { "message": { "content": "$1" } } }, + "jellyfinNoMediaLibraries": { "message": "No se encontraron bibliotecas de películas o series en Jellyfin." }, + "jellyfinLibrariesFound": { "message": "$count$ biblioteca(s) de medios encontrada(s).", "placeholders": { "count": { "content": "$1" } } }, + "jellyfinLibraryScanSuccess": { "message": "[Éxito] '$libraryName$' escaneada, $count$ títulos añadidos.", "placeholders": { "libraryName": { "content": "$1" }, "count": { "content": "$2" } } }, + "jellyfinLibraryScanFailed": { "message": "Error al escanear la biblioteca '$libraryName$'.", "placeholders": { "libraryName": { "content": "$1" } } }, + "jellyfinScanSuccess": { "message": "Escaneo Jellyfin completado. Añadidas $movies$ películas y $series$ series.", "placeholders": { "movies": { "content": "$1" }, "series": { "content": "$2" } } }, + "noJellyfinCredentials": { "message": "Credenciales de Jellyfin no configuradas." }, + "notFoundOnJellyfin": { "message": "No se encontró \"$query$\" en Jellyfin.", "placeholders": { "query": { "content": "$1" } } }, + "notFoundOnAnyServer": { "message": "No se encontró \"$query$\" en ningún servidor.", "placeholders": { "query": { "content": "$1" } } }, + "localOnPlex": { "message": "En Plex" }, + "searchOnPlex": { "message": "Buscar en Plex" }, + "jellyfinTitle": { "message": "Contenido de Jellyfin" }, + "noJellyfinContent": { "message": "No se encontró contenido de Jellyfin." }, + "noJellyfinContentSub": { "message": "Asegúrate de haber escaneado tu servidor Jellyfin en los ajustes." } } \ No newline at end of file diff --git a/_locales/fr/messages.json b/_locales/fr/messages.json index 4575828..b4db336 100644 --- a/_locales/fr/messages.json +++ b/_locales/fr/messages.json @@ -58,6 +58,7 @@ "settingsTitleFull": { "message": "Paramètres et Configuration" }, "settingsTabGeneral": { "message": "Général" }, "settingsTabPlex": { "message": "Plex" }, + "settingsTabJellyfin": { "message": "Jellyfin" }, "settingsTabPhpGen": { "message": "Générateur PHP" }, "settingsTabData": { "message": "Données" }, "settingsApiServer": { "message": "Configuration API et Serveur" }, @@ -80,6 +81,12 @@ "settingsPlexTokens": { "message": "Tokens Plex" }, "settingsPlexTokensDesc": { "message": "Modifiez la liste des tokens Plex (format JSON)." }, "settingsSaveTokens": { "message": "Sauvegarder les Tokens" }, + "settingsJellyfinTitle": { "message": "Paramètres Jellyfin" }, + "settingsJellyfinDesc": { "message": "Ajoutez les informations de votre serveur Jellyfin pour scanner son contenu." }, + "jellyfinUrlLabel": { "message": "URL du serveur Jellyfin" }, + "jellyfinUserLabel": { "message": "Nom d'utilisateur" }, + "jellyfinPassLabel": { "message": "Mot de passe" }, + "jellyfinConnectAndScan": { "message": "Connecter et Scanner" }, "settingsPhpGenTitle": { "message": "Générateur de Script PHP pour Serveur" }, "settingsPhpFileOptions": { "message": "Options du Fichier" }, "settingsPhpSavePathLabel": { "message": "Chemin de Sauvegarde sur le Serveur" }, @@ -286,5 +293,26 @@ "errorParsingPlexXml": { "message": "Erreur d'analyse du XML de Plex." }, "untitled": { "message": "Sans titre" }, "itemCount": { "message": "$count$ éléments", "placeholders": { "count": { "content": "$1" } } }, - "noPhotoServers": { "message": "Aucun serveur de photos" } + "noPhotoServers": { "message": "Aucun serveur de photos" }, + "jellyfinScanInProgress": { "message": "Le scan Jellyfin est déjà en cours." }, + "jellyfinScanning": { "message": "Scan de Jellyfin en cours..." }, + "jellyfinMissingCredentials": { "message": "Veuillez compléter l'URL et le nom d'utilisateur de Jellyfin." }, + "jellyfinConnecting": { "message": "Connexion à Jellyfin à : $url$", "placeholders": { "url": { "content": "$1" } } }, + "jellyfinAuthFailed": { "message": "Échec de l'authentification Jellyfin : $message$", "placeholders": { "message": { "content": "$1" } } }, + "jellyfinAuthSuccess": { "message": "Authentification Jellyfin réussie." }, + "jellyfinFetchingLibraries": { "message": "Récupération des bibliothèques..." }, + "jellyfinFetchFailed": { "message": "Échec de la récupération des bibliothèques : $message$", "placeholders": { "message": { "content": "$1" } } }, + "jellyfinNoMediaLibraries": { "message": "Aucune bibliothèque de films ou de séries trouvée sur Jellyfin." }, + "jellyfinLibrariesFound": { "message": "$count$ bibliothèque(s) multimédia(s) trouvée(s).", "placeholders": { "count": { "content": "$1" } } }, + "jellyfinLibraryScanSuccess": { "message": "[Succès] '$libraryName scannée, $count$ titres ajoutés.", "placeholders": { "libraryName": { "content": "$1" }, "count": { "content": "$2" } } }, + "jellyfinLibraryScanFailed": { "message": "Échec du scan de la bibliothèque '$libraryName.", "placeholders": { "libraryName": { "content": "$1" } } }, + "jellyfinScanSuccess": { "message": "Scan Jellyfin terminé. $movies$ films et $series$ séries ajoutés.", "placeholders": { "movies": { "content": "$1" }, "series": { "content": "$2" } } }, + "noJellyfinCredentials": { "message": "Identifiants Jellyfin non configurés." }, + "notFoundOnJellyfin": { "message": "\"$query$\" non trouvé sur Jellyfin.", "placeholders": { "query": { "content": "$1" } } }, + "notFoundOnAnyServer": { "message": "\"$query$\" non trouvé sur aucun serveur.", "placeholders": { "query": { "content": "$1" } } }, + "localOnPlex": { "message": "Sur Plex" }, + "searchOnPlex": { "message": "Rechercher sur Plex" }, + "jellyfinTitle": { "message": "Contenu Jellyfin" }, + "noJellyfinContent": { "message": "Aucun contenu Jellyfin trouvé." }, + "noJellyfinContentSub": { "message": "Assurez-vous d'avoir scanné votre serveur Jellyfin dans les paramètres." } } \ No newline at end of file diff --git a/_locales/it/messages.json b/_locales/it/messages.json index bf13777..102444b 100644 --- a/_locales/it/messages.json +++ b/_locales/it/messages.json @@ -58,6 +58,7 @@ "settingsTitleFull": { "message": "Impostazioni e Configurazione" }, "settingsTabGeneral": { "message": "Generale" }, "settingsTabPlex": { "message": "Plex" }, + "settingsTabJellyfin": { "message": "Jellyfin" }, "settingsTabPhpGen": { "message": "Generatore PHP" }, "settingsTabData": { "message": "Dati" }, "settingsApiServer": { "message": "Configurazione API e Server" }, @@ -80,6 +81,12 @@ "settingsPlexTokens": { "message": "Token Plex" }, "settingsPlexTokensDesc": { "message": "Modifica la lista dei token Plex (formato JSON)." }, "settingsSaveTokens": { "message": "Salva Token" }, + "settingsJellyfinTitle": { "message": "Impostazioni Jellyfin" }, + "settingsJellyfinDesc": { "message": "Aggiungi i dati del tuo server Jellyfin per scansionarne il contenuto." }, + "jellyfinUrlLabel": { "message": "URL Server Jellyfin" }, + "jellyfinUserLabel": { "message": "Nome utente" }, + "jellyfinPassLabel": { "message": "Password" }, + "jellyfinConnectAndScan": { "message": "Connetti e Scansiona" }, "settingsPhpGenTitle": { "message": "Generatore di Script PHP per Server" }, "settingsPhpFileOptions": { "message": "Opzioni File" }, "settingsPhpSavePathLabel": { "message": "Percorso di salvataggio sul server" }, @@ -286,5 +293,26 @@ "errorParsingPlexXml": { "message": "Errore nell'analisi dell'XML di Plex." }, "untitled": { "message": "Senza titolo" }, "itemCount": { "message": "$count$ elementi", "placeholders": { "count": { "content": "$1" } } }, - "noPhotoServers": { "message": "Nessun server di foto" } + "noPhotoServers": { "message": "Nessun server di foto" }, + "jellyfinScanInProgress": { "message": "Scansione Jellyfin già in corso." }, + "jellyfinScanning": { "message": "Scansione di Jellyfin in corso..." }, + "jellyfinMissingCredentials": { "message": "Per favore, completa l'URL e il nome utente di Jellyfin." }, + "jellyfinConnecting": { "message": "Connessione a Jellyfin a: $url$", "placeholders": { "url": { "content": "$1" } } }, + "jellyfinAuthFailed": { "message": "Autenticazione Jellyfin fallita: $message$", "placeholders": { "message": { "content": "$1" } } }, + "jellyfinAuthSuccess": { "message": "Autenticazione Jellyfin riuscita." }, + "jellyfinFetchingLibraries": { "message": "Recupero delle librerie..." }, + "jellyfinFetchFailed": { "message": "Recupero delle librerie fallito: $message$", "placeholders": { "message": { "content": "$1" } } }, + "jellyfinNoMediaLibraries": { "message": "Nessuna libreria di film o serie trovata su Jellyfin." }, + "jellyfinLibrariesFound": { "message": "$count$ libreria(e) multimediale(i) trovata(e).", "placeholders": { "count": { "content": "$1" } } }, + "jellyfinLibraryScanSuccess": { "message": "[Successo] Scansionata '$libraryName, aggiunti $count$ titoli.", "placeholders": { "libraryName": { "content": "$1" }, "count": { "content": "$2" } } }, + "jellyfinLibraryScanFailed": { "message": "Scansione della libreria '$libraryName fallita.", "placeholders": { "libraryName": { "content": "$1" } } }, + "jellyfinScanSuccess": { "message": "Scansione Jellyfin completata. Aggiunti $movies$ film e $series$ serie.", "placeholders": { "movies": { "content": "$1" }, "series": { "content": "$2" } } }, + "noJellyfinCredentials": { "message": "Credenziali di Jellyfin non configurate." }, + "notFoundOnJellyfin": { "message": "\"$query$\" non trovato su Jellyfin.", "placeholders": { "query": { "content": "$1" } } }, + "notFoundOnAnyServer": { "message": "\"$query$\" non trovato su nessun server.", "placeholders": { "query": { "content": "$1" } } }, + "localOnPlex": { "message": "Su Plex" }, + "searchOnPlex": { "message": "Cerca su Plex" }, + "jellyfinTitle": { "message": "Contenuto Jellyfin" }, + "noJellyfinContent": { "message": "Nessun contenuto Jellyfin trovato." }, + "noJellyfinContentSub": { "message": "Assicurati di aver scansionato il tuo server Jellyfin nelle impostazioni." } } \ No newline at end of file diff --git a/_locales/pt/messages.json b/_locales/pt/messages.json index 937b5d1..35a6ef3 100644 --- a/_locales/pt/messages.json +++ b/_locales/pt/messages.json @@ -58,6 +58,7 @@ "settingsTitleFull": { "message": "Configurações e Ajustes" }, "settingsTabGeneral": { "message": "Geral" }, "settingsTabPlex": { "message": "Plex" }, + "settingsTabJellyfin": { "message": "Jellyfin" }, "settingsTabPhpGen": { "message": "Gerador de PHP" }, "settingsTabData": { "message": "Dados" }, "settingsApiServer": { "message": "Configuração de API e Servidor" }, @@ -80,6 +81,12 @@ "settingsPlexTokens": { "message": "Tokens do Plex" }, "settingsPlexTokensDesc": { "message": "Edite a lista de tokens do Plex (formato JSON)." }, "settingsSaveTokens": { "message": "Salvar Tokens" }, + "settingsJellyfinTitle": { "message": "Configurações do Jellyfin" }, + "settingsJellyfinDesc": { "message": "Adicione os detalhes do seu servidor Jellyfin para analisar seu conteúdo." }, + "jellyfinUrlLabel": { "message": "URL do Servidor Jellyfin" }, + "jellyfinUserLabel": { "message": "Nome de usuário" }, + "jellyfinPassLabel": { "message": "Senha" }, + "jellyfinConnectAndScan": { "message": "Conectar e Analisar" }, "settingsPhpGenTitle": { "message": "Gerador de Script PHP para Servidor" }, "settingsPhpFileOptions": { "message": "Opções de Arquivo" }, "settingsPhpSavePathLabel": { "message": "Caminho para Salvar no Servidor" }, @@ -286,5 +293,26 @@ "errorParsingPlexXml": { "message": "Erro ao analisar o XML do Plex." }, "untitled": { "message": "Sem título" }, "itemCount": { "message": "$count$ itens", "placeholders": { "count": { "content": "$1" } } }, - "noPhotoServers": { "message": "Nenhum servidor de fotos" } + "noPhotoServers": { "message": "Nenhum servidor de fotos" }, + "jellyfinScanInProgress": { "message": "A análise do Jellyfin já está em andamento." }, + "jellyfinScanning": { "message": "Analisando Jellyfin..." }, + "jellyfinMissingCredentials": { "message": "Por favor, complete a URL e o nome de usuário do Jellyfin." }, + "jellyfinConnecting": { "message": "Conectando ao Jellyfin em: $url$", "placeholders": { "url": { "content": "$1" } } }, + "jellyfinAuthFailed": { "message": "A autenticação do Jellyfin falhou: $message$", "placeholders": { "message": { "content": "$1" } } }, + "jellyfinAuthSuccess": { "message": "Autenticação do Jellyfin bem-sucedida." }, + "jellyfinFetchingLibraries": { "message": "Buscando bibliotecas..." }, + "jellyfinFetchFailed": { "message": "Falha ao buscar bibliotecas: $message$", "placeholders": { "message": { "content": "$1" } } }, + "jellyfinNoMediaLibraries": { "message": "Nenhuma biblioteca de filmes ou séries encontrada no Jellyfin." }, + "jellyfinLibrariesFound": { "message": "$count$ biblioteca(s) de mídia encontrada(s).", "placeholders": { "count": { "content": "$1" } } }, + "jellyfinLibraryScanSuccess": { "message": "[Sucesso] Análise de '$libraryName concluída, $count$ títulos adicionados.", "placeholders": { "libraryName": { "content": "$1" }, "count": { "content": "$2" } } }, + "jellyfinLibraryScanFailed": { "message": "Falha ao analisar a biblioteca '$libraryName.", "placeholders": { "libraryName": { "content": "$1" } } }, + "jellyfinScanSuccess": { "message": "Análise do Jellyfin concluída. Adicionados $movies$ filmes e $series$ séries.", "placeholders": { "movies": { "content": "$1" }, "series": { "content": "$2" } } }, + "noJellyfinCredentials": { "message": "Credenciais do Jellyfin não configuradas." }, + "notFoundOnJellyfin": { "message": "\"$query$\" não encontrado no Jellyfin.", "placeholders": { "query": { "content": "$1" } } }, + "notFoundOnAnyServer": { "message": "\"$query$\" não encontrado em nenhum servidor.", "placeholders": { "query": { "content": "$1" } } }, + "localOnPlex": { "message": "No Plex" }, + "searchOnPlex": { "message": "Pesquisar no Plex" }, + "jellyfinTitle": { "message": "Conteúdo do Jellyfin" }, + "noJellyfinContent": { "message": "Nenhum conteúdo do Jellyfin encontrado." }, + "noJellyfinContentSub": { "message": "Certifique-se de que você analisou seu servidor Jellyfin nas configurações." } } \ No newline at end of file diff --git a/css/navbar.css b/css/navbar.css index c8a01df..e87050a 100644 --- a/css/navbar.css +++ b/css/navbar.css @@ -155,9 +155,9 @@ body.light-theme .sidebar-nav { } @media (min-width: 992px) { - #sidebar-toggle { + /* #sidebar-toggle { display: none; - } + } */ .sidebar-nav { transform: translateX(0); } @@ -215,4 +215,12 @@ body.light-theme .sidebar-nav { height: 36px; font-size: 1.1rem; } -} \ No newline at end of file +} + +body.sidebar-collapsed .sidebar-nav { + transform: translateX(-100%); +} + +body.sidebar-collapsed #main-container { + padding-left: 0; +} diff --git a/js/api.js b/js/api.js index c421770..480b93c 100644 --- a/js/api.js +++ b/js/api.js @@ -220,4 +220,108 @@ export async function fetchAllStreamsFromPlex(busqueda, tipoContenido) { } else { return { success: false, streams: [], message: _('notFoundOnServers', busqueda) }; } +} + +export async function fetchAllStreamsFromJellyfin(busqueda, tipoContenido) { + if (!busqueda || !tipoContenido) return { success: false, streams: [], message: _('invalidStreamInfo') }; + + const { url, userId, apiKey } = state.jellyfinSettings; + if (!url || !userId || !apiKey) return { success: false, streams: [], message: _('noJellyfinCredentials') }; + + const jellyfinSearchType = tipoContenido === 'movie' ? 'Movie' : 'Series'; + const searchUrl = `${url}/Users/${userId}/Items?searchTerm=${encodeURIComponent(busqueda)}&IncludeItemTypes=${jellyfinSearchType}&Recursive=true`; + + try { + const response = await fetch(searchUrl, { headers: { 'X-Emby-Token': apiKey } }); + if (!response.ok) throw new Error(`Error buscando en Jellyfin: ${response.status}`); + const searchData = await response.json(); + + if (!searchData.Items || searchData.Items.length === 0) { + return { success: false, streams: [], message: _('notFoundOnJellyfin', busqueda) }; + } + + const item = searchData.Items.find(i => i.Name.toLowerCase() === busqueda.toLowerCase()) || searchData.Items[0]; + const itemId = item.Id; + const itemName = item.Name; + const itemYear = item.ProductionYear; + const posterTag = item.ImageTags?.Primary; + const posterUrl = posterTag ? `${url}/Items/${itemId}/Images/Primary?tag=${posterTag}` : ''; + + let streams = []; + + if (item.Type === 'Movie') { + const streamUrl = `${url}/Videos/${itemId}/stream?api_key=${apiKey}`; + const extinfName = `${itemName}${itemYear ? ` (${itemYear})` : ''}`; + const groupTitle = extinfName.replace(/"/g, "'"); + + streams.push({ + url: streamUrl, + title: extinfName, + extinf: `#EXTINF:-1 tvg-name="${extinfName.replace(/"/g, "'")}" tvg-logo="${posterUrl}" group-title="${groupTitle}",${extinfName}` + }); + } else if (item.Type === 'Series') { + const episodesUrl = `${url}/Shows/${itemId}/Episodes?userId=${userId}`; + const episodesResponse = await fetch(episodesUrl, { headers: { 'X-Emby-Token': apiKey } }); + if (!episodesResponse.ok) throw new Error(`Error obteniendo episodios: ${episodesResponse.status}`); + const episodesData = await episodesResponse.json(); + + const sortedEpisodes = episodesData.Items.sort((a,b) => { + if (a.ParentIndexNumber !== b.ParentIndexNumber) return (a.ParentIndexNumber || 0) - (b.ParentIndexNumber || 0); + return (a.IndexNumber || 0) - (b.IndexNumber || 0); + }); + + sortedEpisodes.forEach(ep => { + const streamUrl = `${url}/Videos/${ep.Id}/stream?api_key=${apiKey}`; + const seasonNum = ep.ParentIndexNumber || 'S'; + const episodeNum = ep.IndexNumber || 'E'; + const episodeTitle = ep.Name || 'Episodio'; + const groupTitle = `${itemName} - Temporada ${seasonNum}`.replace(/"/g, "'"); + const extinfName = `${itemName} T${seasonNum}E${episodeNum} ${episodeTitle}`; + + streams.push({ + url: streamUrl, + title: extinfName, + extinf: `#EXTINF:-1 tvg-name="${extinfName.replace(/"/g, "'")}" tvg-logo="${posterUrl}" group-title="${groupTitle}",${extinfName}` + }); + }); + } + + return { success: true, streams }; + + } catch (error) { + console.error("Error fetching streams from Jellyfin:", error); + return { success: false, streams: [], message: error.message }; + } +} + +export async function fetchAllAvailableStreams(title, type) { + const plexPromise = fetchAllStreamsFromPlex(title, type); + const jellyfinPromise = fetchAllStreamsFromJellyfin(title, type); + + const results = await Promise.allSettled([plexPromise, jellyfinPromise]); + + let allStreams = []; + const errorMessages = []; + + results.forEach((result, index) => { + const sourceName = index === 0 ? 'Plex' : 'Jellyfin'; + if (result.status === 'fulfilled' && result.value.success) { + allStreams.push(...result.value.streams); + } else if (result.status === 'fulfilled' && !result.value.success) { + if (result.value.message !== _('noPlexServersForStreams') && result.value.message !== _('noJellyfinCredentials')) { + errorMessages.push(`${sourceName}: ${result.value.message}`); + } + } else if (result.status === 'rejected') { + errorMessages.push(`${sourceName}: ${result.reason.message}`); + } + }); + + const uniqueStreamsMap = new Map(allStreams.map(stream => [stream.url, stream])); + const uniqueStreams = Array.from(uniqueStreamsMap.values()); + + if (uniqueStreams.length > 0) { + return { success: true, streams: uniqueStreams, message: `Found ${uniqueStreams.length} streams.` }; + } else { + return { success: false, streams: [], message: errorMessages.join('; ') || _('notFoundOnAnyServer', title) }; + } } \ No newline at end of file diff --git a/js/config.js b/js/config.js index a13cb2d..030e502 100644 --- a/js/config.js +++ b/js/config.js @@ -1,5 +1,5 @@ export const config = { defaultApiKey: '4e44d9029b1270a757cddc766a1bcb63', dbName: 'PlexDB', - dbVersion: 6, + dbVersion: 7, }; \ No newline at end of file diff --git a/js/db.js b/js/db.js index ef61075..3055a17 100644 --- a/js/db.js +++ b/js/db.js @@ -13,14 +13,17 @@ export function initDB() { request.onupgradeneeded = e => { state.db = e.target.result; const transaction = e.target.transaction; - const storesToCreate = ['movies', 'series', 'artists', 'photos', 'tokens', 'conexiones_locales', 'settings']; + const storesToCreate = ['movies', 'series', 'artists', 'photos', 'tokens', 'conexiones_locales', 'settings', 'jellyfin_settings', 'jellyfin_movies', 'jellyfin_series']; storesToCreate.forEach(storeName => { if (!state.db.objectStoreNames.contains(storeName)) { let storeOptions; - if (storeName === 'settings') { + if (['settings', 'jellyfin_settings'].includes(storeName)) { storeOptions = { keyPath: 'id' }; - } else { + } else if (['jellyfin_movies', 'jellyfin_series'].includes(storeName)) { + storeOptions = { keyPath: 'libraryId' }; + } + else { storeOptions = { keyPath: 'id', autoIncrement: true }; } const store = state.db.createObjectStore(storeName, storeOptions); @@ -126,7 +129,7 @@ export function addItemsToStore(storeName, items) { export async function clearContentData() { showNotification(_("deletingContentData"), "info"); mostrarSpinner(); - const storesToDelete = ['movies', 'series', 'artists', 'photos', 'conexiones_locales']; + const storesToDelete = ['movies', 'series', 'artists', 'photos', 'conexiones_locales', 'jellyfin_movies', 'jellyfin_series']; try { if (!state.db) throw new Error(_("dbNotAvailable")); const storesPresent = storesToDelete.filter(name => state.db.objectStoreNames.contains(name)); diff --git a/js/eventListeners.js b/js/eventListeners.js index 33add26..8c005c5 100644 --- a/js/eventListeners.js +++ b/js/eventListeners.js @@ -3,6 +3,7 @@ import { switchView, resetView, showMainView, showItemDetails, applyFilters, sea import { debounce, showNotification, _ } from './utils.js'; import { clearContentData, loadTokensToEditor, saveTokensFromEditor, exportDatabase, importDatabase } from './db.js'; import { startPlexScan } from './plex.js'; +import { startJellyfinScan } from './jellyfin.js'; import { Equalizer } from './equalizer.js'; async function handleDatabaseUpdate() { @@ -28,9 +29,16 @@ async function handleDatabaseUpdate() { } export function setupEventListeners() { + const savedSidebarState = localStorage.getItem('sidebarCollapsed'); + if (savedSidebarState === 'true') { + document.body.classList.add('sidebar-collapsed'); + } else { + document.body.classList.remove('sidebar-collapsed'); + } document.getElementById('sidebar-toggle').addEventListener('click', () => { - document.getElementById('sidebar-nav').classList.toggle('open'); - document.getElementById('main-container').classList.toggle('sidebar-open'); + document.body.classList.toggle('sidebar-collapsed'); + const isCollapsed = document.body.classList.contains('sidebar-collapsed'); + localStorage.setItem('sidebarCollapsed', isCollapsed); }); document.getElementById('nav-movies').addEventListener('click', (e) => { e.preventDefault(); switchView('movies'); }); @@ -109,6 +117,8 @@ export function setupEventListeners() { } }); + document.getElementById('jellyfinScanBtn').addEventListener('click', startJellyfinScan); + document.getElementById('clearDataBtn').addEventListener('click', () => { if (confirm(_('confirmClearContent'))) { clearContentData(); @@ -235,7 +245,7 @@ function handleMainViewClick(e) { handlePhotoGridClick(photoCard); return; } - + const card = e.target.closest('.item-card'); if (!card) return; diff --git a/js/jellyfin.js b/js/jellyfin.js new file mode 100644 index 0000000..f6ae85a --- /dev/null +++ b/js/jellyfin.js @@ -0,0 +1,209 @@ +import { state } from './state.js'; +import { addItemsToStore, clearStore } from './db.js'; +import { showNotification, _, emitirEventoActualizacion } from './utils.js'; + +async function authenticateJellyfin(url, username, password) { + const authUrl = `${url}/Users/AuthenticateByName`; + const body = JSON.stringify({ + Username: username, + Pw: password + }); + + try { + const response = await fetch(authUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-Emby-Authorization': 'MediaBrowser Client="CinePlex", Device="Chrome", DeviceId="cineplex-jellyfin-integration", Version="1.0"' + }, + body: body + }); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})); + throw new Error(errorData.AuthenticationResult?.ErrorMessage || `Error ${response.status}`); + } + + const data = await response.json(); + return { success: true, token: data.AccessToken, userId: data.User.Id }; + + } catch (error) { + return { success: false, message: error.message }; + } +} + +async function fetchLibraryViews(url, userId, apiKey) { + const viewsUrl = `${url}/Users/${userId}/Views`; + try { + const response = await fetch(viewsUrl, { + headers: { + 'X-Emby-Token': apiKey + } + }); + if (!response.ok) throw new Error(`Error ${response.status} fetching library views`); + const data = await response.json(); + return { success: true, views: data.Items }; + } catch (error) { + return { success: false, message: error.message }; + } +} + + +async function fetchItemsFromLibrary(url, userId, apiKey, library) { + const itemsUrl = `${url}/Users/${userId}/Items?ParentId=${library.Id}&recursive=true&fields=ProductionYear&includeItemTypes=Movie,Series`; + + try { + const response = await fetch(itemsUrl, { + headers: { + 'X-Emby-Token': apiKey + } + }); + + if (!response.ok) throw new Error(`Error ${response.status}`); + + const data = await response.json(); + const items = data.Items.map(item => ({ + id: item.Id, + title: item.Name, + year: item.ProductionYear, + type: item.Type, + posterTag: item.ImageTags?.Primary, + })); + + return { success: true, items, libraryName: library.Name, libraryId: library.Id }; + + } catch (error) { + return { success: false, message: error.message, libraryName: library.Name, libraryId: library.Id }; + } +} + +export async function startJellyfinScan() { + if (state.isScanningJellyfin) { + showNotification(_('jellyfinScanInProgress'), 'warning'); + return; + } + state.isScanningJellyfin = true; + + const statusDiv = document.getElementById('jellyfinScanStatus'); + const scanBtn = document.getElementById('jellyfinScanBtn'); + const originalBtnText = scanBtn.innerHTML; + scanBtn.innerHTML = `${_('jellyfinScanning')}`; + scanBtn.disabled = true; + + const urlInput = document.getElementById('jellyfinServerUrl'); + const usernameInput = document.getElementById('jellyfinUsername'); + const passwordInput = document.getElementById('jellyfinPassword'); + + let url = urlInput.value.trim(); + const username = usernameInput.value.trim(); + const password = passwordInput.value; + + if (!url || !username) { + showNotification(_('jellyfinMissingCredentials'), 'error'); + state.isScanningJellyfin = false; + scanBtn.innerHTML = originalBtnText; + scanBtn.disabled = false; + return; + } + + url = url.replace(/\/web\/.*$/, '').replace(/\/$/, ''); + statusDiv.innerHTML = `