209 lines
7.8 KiB
JavaScript
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;
|
|
} |