Jellyfin integration in M3U generator and statistics
This commit is contained in:
parent
e96e4eb255
commit
f6748ab113
20
js/constants.js
Normal file
20
js/constants.js
Normal file
@ -0,0 +1,20 @@
|
||||
export const API_URLS = {
|
||||
TMDB_BASE: 'https://api.themoviedb.org/3',
|
||||
TMDB_IMAGE_BASE: 'https://image.tmdb.org/t/p',
|
||||
PLEX_TV: 'https://plex.tv/api/resources',
|
||||
YOUTUBE_EMBED: 'https://www.youtube.com/embed/',
|
||||
IMDB_TITLE: 'https://www.imdb.com/title/'
|
||||
};
|
||||
|
||||
export const CONFIG = {
|
||||
DEFAULT_API_KEY: '4e44d9029b1270a757cddc766a1bcb63',
|
||||
DB_NAME: 'PlexDB',
|
||||
DB_VERSION: 6
|
||||
};
|
||||
|
||||
export const STORAGE_KEYS = {
|
||||
USER_HISTORY: 'cineplex_userHistory',
|
||||
USER_PREFERENCES: 'cineplex_userPreferences',
|
||||
FAVORITES: 'cineplex_favorites',
|
||||
RECOMMENDATIONS_CACHE: 'cineplex_recommendations'
|
||||
};
|
19
js/db.js
19
js/db.js
@ -268,12 +268,27 @@ export async function importDatabase(file) {
|
||||
}
|
||||
|
||||
export async function getServers() {
|
||||
const connections = await getFromDB('conexiones_locales');
|
||||
return connections.map(conn => ({
|
||||
const plexConnections = await getFromDB('conexiones_locales');
|
||||
const jellyfinConnections = await getFromDB('jellyfin_settings');
|
||||
|
||||
const plexServers = plexConnections.map(conn => ({
|
||||
id: conn.id,
|
||||
name: conn.nombre,
|
||||
type: 'plex',
|
||||
accessToken: conn.token,
|
||||
publicUrl: conn.protocolo + '://' + conn.ip + ':' + conn.puerto,
|
||||
localUrl: conn.protocolo + '://' + conn.ip + ':' + conn.puerto,
|
||||
}));
|
||||
|
||||
const jellyfinServers = jellyfinConnections.map(conn => ({
|
||||
id: conn.id,
|
||||
name: conn.username || 'Jellyfin Server',
|
||||
type: 'jellyfin',
|
||||
accessToken: conn.apiKey,
|
||||
userId: conn.userId,
|
||||
publicUrl: conn.url,
|
||||
localUrl: conn.url,
|
||||
}));
|
||||
|
||||
return [...plexServers, ...jellyfinServers];
|
||||
}
|
@ -32,7 +32,7 @@ async function authenticateJellyfin(url, username, password) {
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchLibraryViews(url, userId, apiKey) {
|
||||
export async function fetchLibraryViews(url, userId, apiKey) {
|
||||
const viewsUrl = `${url}/Users/${userId}/Views`;
|
||||
try {
|
||||
const response = await fetch(viewsUrl, {
|
||||
@ -49,8 +49,8 @@ async function fetchLibraryViews(url, userId, apiKey) {
|
||||
}
|
||||
|
||||
|
||||
async function fetchItemsFromLibrary(url, userId, apiKey, library) {
|
||||
const itemsUrl = `${url}/Users/${userId}/Items?ParentId=${library.Id}&recursive=true&fields=ProductionYear&includeItemTypes=Movie,Series`;
|
||||
export async function fetchItemsFromLibrary(url, userId, apiKey, library) {
|
||||
const itemsUrl = `${url}/Users/${userId}/Items?ParentId=${library.Id}&recursive=true&fields=ProductionYear,RunTimeTicks,SeriesName,ParentIndexNumber,ImageTags&includeItemTypes=Movie,Series`;
|
||||
|
||||
try {
|
||||
const response = await fetch(itemsUrl, {
|
||||
@ -67,7 +67,10 @@ async function fetchItemsFromLibrary(url, userId, apiKey, library) {
|
||||
title: item.Name,
|
||||
year: item.ProductionYear,
|
||||
type: item.Type,
|
||||
posterTag: item.ImageTags?.Primary,
|
||||
duration: item.RunTimeTicks,
|
||||
seriesTitle: item.SeriesName,
|
||||
seasonNumber: item.ParentIndexNumber,
|
||||
thumb: item.ImageTags?.Primary ? `${url}/Items/${item.Id}/Images/Primary?tag=${item.ImageTags.Primary}` : ''
|
||||
}));
|
||||
|
||||
return { success: true, items, libraryName: library.Name, libraryId: library.Id };
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { getServers } from './db.js';
|
||||
import { fetchLibraries, fetchLibraryContents } from './plex.js';
|
||||
import { fetchLibraries as fetchPlexLibraries, fetchLibraryContents as fetchPlexLibraryContents } from './plex.js';
|
||||
import { fetchLibraryViews as fetchJellyfinLibraries, fetchItemsFromLibrary as fetchJellyfinLibraryContents } from './jellyfin.js';
|
||||
import { showNotification, _ } from './utils.js';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
@ -22,6 +23,10 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
gsap.to(m3uConfigPanel, { autoAlpha: 1, y: 0, duration: 0.8, ease: "power3.out" });
|
||||
gsap.to(m3uInfoPanel, { autoAlpha: 1, y: 0, duration: 0.8, ease: "power3.out", delay: 0.2 });
|
||||
|
||||
await loadServers();
|
||||
}
|
||||
|
||||
async function loadServers() {
|
||||
try {
|
||||
const servers = await getServers();
|
||||
m3uServerSelect.innerHTML = `<option value="">${_('selectAServer')}</option>`;
|
||||
@ -29,6 +34,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
const option = document.createElement('option');
|
||||
option.value = server.id;
|
||||
option.textContent = server.name;
|
||||
option.dataset.type = server.type;
|
||||
m3uServerSelect.appendChild(option);
|
||||
});
|
||||
} catch (error) {
|
||||
@ -37,7 +43,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
}
|
||||
|
||||
m3uServerSelect.addEventListener('change', async () => {
|
||||
const serverId = m3uServerSelect.value;
|
||||
const selectedOption = m3uServerSelect.options[m3uServerSelect.selectedIndex];
|
||||
const serverId = selectedOption.value;
|
||||
const serverType = selectedOption.dataset.type;
|
||||
m3uLibrariesContainer.innerHTML = '';
|
||||
downloadM3uBtn.disabled = true;
|
||||
|
||||
@ -57,7 +65,20 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
const server = servers.find(s => s.id == serverId);
|
||||
if (!server) return;
|
||||
|
||||
const libraries = await fetchLibraries(server.accessToken, server.publicUrl, server.localUrl);
|
||||
let libraries = [];
|
||||
if (serverType === 'plex') {
|
||||
libraries = await fetchPlexLibraries(server.accessToken, server.publicUrl, server.localUrl);
|
||||
} else if (serverType === 'jellyfin') {
|
||||
const jellyfinLibraries = await fetchJellyfinLibraries(server.publicUrl, server.userId, server.accessToken);
|
||||
if(jellyfinLibraries.success) {
|
||||
libraries = jellyfinLibraries.views.map(lib => ({
|
||||
key: lib.Id,
|
||||
title: lib.Name,
|
||||
type: lib.CollectionType === 'movies' ? 'movie' : (lib.CollectionType === 'tvshows' ? 'show' : 'music')
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
const checkboxes = [];
|
||||
libraries.forEach(library => {
|
||||
if (library.type === 'movie' || library.type === 'show' || library.type === 'music') {
|
||||
@ -93,7 +114,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
});
|
||||
|
||||
downloadM3uBtn.addEventListener('click', async () => {
|
||||
const serverId = m3uServerSelect.value;
|
||||
const selectedOption = m3uServerSelect.options[m3uServerSelect.selectedIndex];
|
||||
const serverId = selectedOption.value;
|
||||
const serverType = selectedOption.dataset.type;
|
||||
const selectedLibraries = Array.from(m3uLibrariesContainer.querySelectorAll('input:checked')).map(input => input.value);
|
||||
|
||||
if (!serverId || selectedLibraries.length === 0) {
|
||||
@ -117,7 +140,24 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
const libraryElement = m3uLibrariesContainer.querySelector(`#library-${libraryKey}`);
|
||||
const libraryType = libraryElement.getAttribute('data-type');
|
||||
const libraryTitle = libraryElement ? libraryElement.nextElementSibling.textContent.trim() : '';
|
||||
const items = await fetchLibraryContents(server.accessToken, server.publicUrl, server.localUrl, libraryKey, libraryType, 60000);
|
||||
|
||||
let items = [];
|
||||
if (serverType === 'plex') {
|
||||
items = await fetchPlexLibraryContents(server.accessToken, server.publicUrl, server.localUrl, libraryKey, libraryType, 60000);
|
||||
} else if (serverType === 'jellyfin') {
|
||||
const jellyfinItems = await fetchJellyfinLibraryContents(server.publicUrl, server.userId, server.accessToken, { Id: libraryKey });
|
||||
if(jellyfinItems.success) {
|
||||
items = jellyfinItems.items.map(item => ({
|
||||
title: item.title,
|
||||
duration: item.duration ? item.duration / 10000 : -1,
|
||||
seriesTitle: item.seriesTitle,
|
||||
seasonNumber: item.seasonNumber,
|
||||
thumb: item.thumb,
|
||||
url: `${server.publicUrl}/Videos/${item.id}/stream?static=true&api_key=${server.accessToken}`
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
items.forEach(item => {
|
||||
const duration = item.duration ? Math.round(item.duration / 1000) : -1;
|
||||
const groupTitle = item.seriesTitle && item.seasonNumber
|
||||
@ -178,4 +218,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
initializeM3uGenerator();
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('actualizacion', loadServers);
|
||||
});
|
5
js/ui.js
5
js/ui.js
@ -1079,6 +1079,7 @@ export async function generateStatistics() {
|
||||
|
||||
const allTokens = await getFromDB('tokens');
|
||||
const allConnections = await getFromDB('conexiones_locales');
|
||||
const allJellyfinConnections = await getFromDB('jellyfin_settings');
|
||||
|
||||
const filteredTokens = selectedToken === 'all'
|
||||
? allTokens
|
||||
@ -1088,8 +1089,10 @@ export async function generateStatistics() {
|
||||
? allConnections
|
||||
: allConnections.filter(c => c.tokenPrincipal === selectedToken);
|
||||
|
||||
const totalServers = filteredConnections.length + (selectedToken === 'all' ? allJellyfinConnections.length : 0);
|
||||
|
||||
animateValue('total-tokens', 0, filteredTokens.length, 1000);
|
||||
animateValue('total-servers', 0, filteredConnections.length, 1000);
|
||||
animateValue('total-servers', 0, totalServers, 1000);
|
||||
|
||||
updateTokenDetailsCard(selectedToken, filteredConnections);
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user