CinePlex/js/jellyfin.js

209 lines
7.8 KiB
JavaScript

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 = `<span class="spinner-border spinner-border-sm me-2"></span>${_('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 = `<div class="text-info">${_('jellyfinConnecting', url)}</div>`;
const authResult = await authenticateJellyfin(url, username, password);
if (!authResult.success) {
statusDiv.innerHTML = `<div class="text-danger">${_('jellyfinAuthFailed', authResult.message)}</div>`;
showNotification(_('jellyfinAuthFailed', authResult.message), 'error');
state.isScanningJellyfin = false;
scanBtn.innerHTML = originalBtnText;
scanBtn.disabled = false;
return;
}
statusDiv.innerHTML = `<div class="text-success">${_('jellyfinAuthSuccess')}</div><div class="text-info">${_('jellyfinFetchingLibraries')}</div>`;
const viewsResult = await fetchLibraryViews(url, authResult.userId, authResult.token);
if (!viewsResult.success) {
statusDiv.innerHTML += `<div class="text-danger">${_('jellyfinFetchFailed', viewsResult.message)}</div>`;
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 += `<div class="text-warning">${_('jellyfinNoMediaLibraries')}</div>`;
state.isScanningJellyfin = false;
scanBtn.innerHTML = originalBtnText;
scanBtn.disabled = false;
return;
}
statusDiv.innerHTML += `<div class="text-info">${_('jellyfinLibrariesFound', String(mediaLibraries.length))}</div>`;
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 += `<div class="text-success-secondary">${_('jellyfinLibraryScanSuccess', [library.Name, String(result.value.items.length)])}</div>`;
}
} else {
const libraryName = result.reason?.libraryName || result.value?.libraryName || 'Unknown';
statusDiv.innerHTML += `<div class="text-warning">${_('jellyfinLibraryScanFailed', libraryName)}</div>`;
}
}
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 += `<div class="text-success mt-2">${message}</div>`;
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;
}