import { state } from './state.js'; import { addItemsToStore, clearStore, getFromDB } 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 }; } } export 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 }; } } export async function fetchItemsFromLibrary(url, userId, apiKey, library) { const isMusic = library.CollectionType === 'music'; const itemTypes = isMusic ? 'MusicArtist' : 'Movie,Series'; const fields = isMusic ? 'ImageTags' : 'ProductionYear,RunTimeTicks,SeriesName,ParentIndexNumber,ImageTags'; const itemsUrl = `${url}/Users/${userId}/Items?ParentId=${library.Id}&recursive=true&fields=${fields}&includeItemTypes=${itemTypes}`; 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 => { const mediaStream = item.MediaStreams?.find(s => s.Type === 'Video'); const container = item.Container; let resolution = null; if (mediaStream && mediaStream.Height) { if (mediaStream.Height >= 2160) resolution = '4k'; else if (mediaStream.Height >= 1080) resolution = '1080p'; else if (mediaStream.Height >= 720) resolution = '720p'; } const baseItem = { id: item.Id, title: item.Name, type: item.Type, thumb: item.ImageTags?.Primary ? `${url}/Items/${item.Id}/Images/Primary?tag=${item.ImageTags.Primary}` : '', container: container, resolution: resolution }; if (!isMusic) { baseItem.year = item.ProductionYear; baseItem.duration = item.RunTimeTicks; baseItem.seriesTitle = item.SeriesName; baseItem.seasonNumber = item.ParentIndexNumber; } return baseItem; }); 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 = `
${_('jellyfinConnecting', url)}
`; const authResult = await authenticateJellyfin(url, username, password); if (!authResult.success) { statusDiv.innerHTML = `
${_('jellyfinAuthFailed', authResult.message)}
`; showNotification(_('jellyfinAuthFailed', authResult.message), 'error'); state.isScanningJellyfin = false; scanBtn.innerHTML = originalBtnText; scanBtn.disabled = false; return; } statusDiv.innerHTML = `
${_('jellyfinAuthSuccess')}
${_('jellyfinFetchingLibraries')}
`; const viewsResult = await fetchLibraryViews(url, authResult.userId, authResult.token); if (!viewsResult.success) { statusDiv.innerHTML += `
${_('jellyfinFetchFailed', viewsResult.message)}
`; state.isScanningJellyfin = false; scanBtn.innerHTML = originalBtnText; scanBtn.disabled = false; return; } const mediaLibraries = viewsResult.views.filter(v => v.CollectionType === 'movies' || v.CollectionType === 'tvshows' || v.CollectionType === 'music'); if (mediaLibraries.length === 0) { statusDiv.innerHTML += `
${_('jellyfinNoMediaLibraries')}
`; state.isScanningJellyfin = false; scanBtn.innerHTML = originalBtnText; scanBtn.disabled = false; return; } statusDiv.innerHTML += `
${_('jellyfinLibrariesFound', String(mediaLibraries.length))}
`; await clearStore('jellyfin_movies'); await clearStore('jellyfin_series'); const allArtists = await getFromDB('artists') || []; const plexArtists = allArtists.filter(a => !a.isJellyfin); await clearStore('artists'); if (plexArtists.length > 0) { await addItemsToStore('artists', plexArtists); } let totalMovies = 0; let totalSeries = 0; let totalMusic = 0; const scanPromises = mediaLibraries.map(library => fetchItemsFromLibrary(url, authResult.userId, authResult.token, library) ); const results = await Promise.allSettled(scanPromises); const urlObject = new URL(url); for (const result of results) { if (result.status === 'fulfilled' && result.value.success) { const library = mediaLibraries.find(lib => lib.Id === result.value.libraryId); if (library) { const collectionType = library.CollectionType; let storeName; if (collectionType === 'movies') storeName = 'jellyfin_movies'; else if (collectionType === 'tvshows') storeName = 'jellyfin_series'; else if (collectionType === 'music') storeName = 'artists'; if (storeName) { const titulos = result.value.items; if (storeName === 'artists') { const jellyfinArtists = { ip: urlObject.hostname, puerto: urlObject.port || (urlObject.protocol === 'https:' ? '443' : '80'), token: authResult.token, protocolo: urlObject.protocol.replace(':', ''), serverName: `Jellyfin - ${library.Name}`, titulos: titulos.map(t => ({...t, isJellyfin: true, userId: authResult.userId, serverUrl: url})), tokenPrincipal: authResult.token, isJellyfin: true, userId: authResult.userId, serverUrl: url }; if (titulos.length > 0) { await addItemsToStore(storeName, [jellyfinArtists]); } } else { const dbEntry = { serverUrl: url, libraryId: library.Id, libraryName: library.Name, titulos: titulos, }; if (titulos.length > 0) { await addItemsToStore(storeName, [dbEntry]); } } if (collectionType === 'movies') totalMovies += titulos.length; else if (collectionType === 'tvshows') totalSeries += titulos.length; else if (collectionType === 'music') totalMusic += titulos.length; statusDiv.innerHTML += `
${_('jellyfinLibraryScanSuccess', [library.Name, String(titulos.length)])}
`; } } } else { const libraryName = result.reason?.libraryName || result.value?.libraryName || 'Unknown'; statusDiv.innerHTML += `
${_('jellyfinLibraryScanFailed', libraryName)}
`; } } const newSettings = { id: 'jellyfin_credentials', url: url, username: username, password: password, apiKey: authResult.token, userId: authResult.userId }; await addItemsToStore('jellyfin_settings', [newSettings]); state.jellyfinSettings = newSettings; const message = `Scan finished. Found ${totalMovies} movies, ${totalSeries} series, and ${totalMusic} music artists.`; statusDiv.innerHTML += `
${message}
`; showNotification(message, 'success'); setTimeout(() => { const modalInstance = bootstrap.Modal.getInstance(document.getElementById('settingsModal')); if(modalInstance) modalInstance.hide(); emitirEventoActualizacion(); }, 2000); state.isScanningJellyfin = false; scanBtn.innerHTML = originalBtnText; scanBtn.disabled = false; }