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 = `
${_('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'); 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'); let totalMovies = 0; let totalSeries = 0; const scanPromises = mediaLibraries.map(library => fetchItemsFromLibrary(url, authResult.userId, authResult.token, library) ); const results = await Promise.allSettled(scanPromises); 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 storeName = library.CollectionType === 'movies' ? 'jellyfin_movies' : 'jellyfin_series'; const dbEntry = { serverUrl: url, libraryId: library.Id, libraryName: library.Name, titulos: result.value.items, }; await addItemsToStore(storeName, [dbEntry]); if (storeName === 'jellyfin_movies') { totalMovies += result.value.items.length; } else { totalSeries += result.value.items.length; } statusDiv.innerHTML += `
${_('jellyfinLibraryScanSuccess', [library.Name, String(result.value.items.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 = _('jellyfinScanSuccess', [String(totalMovies), String(totalSeries)]); 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; }