CinePlex/js/m3u-generator.js

165 lines
7.8 KiB
JavaScript
Raw Normal View History

2025-07-26 18:53:03 +02:00
import { getServers } from './db.js';
import { fetchLibraries, fetchLibraryContents } from './plex.js';
import { showNotification, _ } from './utils.js';
document.addEventListener('DOMContentLoaded', () => {
const m3uServerSelect = document.getElementById('m3u-server-select');
const m3uLibrariesContainer = document.getElementById('m3u-libraries-container');
const m3uLibrariesLoader = document.getElementById('m3u-libraries-loader');
const m3uLibrariesStep = document.getElementById('m3u-libraries-step');
const m3uConfigPanel = document.querySelector('.m3u-config-panel');
const m3uInfoPanel = document.querySelector('.m3u-info-panel');
const downloadM3uBtn = document.getElementById('download-m3u-btn');
let isInitialized = false;
// GSAP animations
gsap.set([m3uConfigPanel, m3uInfoPanel], { autoAlpha: 0, y: 50 });
async function initializeM3uGenerator() {
if (isInitialized) return;
isInitialized = true;
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 });
try {
const servers = await getServers();
m3uServerSelect.innerHTML = `<option value="">${_('selectAServer')}</option>`;
servers.forEach(server => {
const option = document.createElement('option');
option.value = server.id;
option.textContent = server.name;
m3uServerSelect.appendChild(option);
});
} catch (error) {
console.error('Error loading servers for M3U generator:', error);
}
}
m3uServerSelect.addEventListener('change', async () => {
const serverId = m3uServerSelect.value;
m3uLibrariesContainer.innerHTML = '';
downloadM3uBtn.disabled = true;
if (!serverId) {
gsap.to(m3uLibrariesStep, { autoAlpha: 0, height: 0, duration: 0.3, onComplete: () => m3uLibrariesStep.style.display = 'none' });
return;
}
m3uLibrariesStep.style.display = 'block';
gsap.fromTo(m3uLibrariesStep, { autoAlpha: 0, height: 0 }, { autoAlpha: 1, height: 'auto', duration: 0.5, ease: "power3.out" });
m3uLibrariesContainer.innerHTML = ''; // Clear previous libraries
m3uLibrariesLoader.style.display = 'block'; // Show loader
downloadM3uBtn.disabled = true;
try {
const servers = await getServers();
const server = servers.find(s => s.id == serverId);
if (!server) return;
const libraries = await fetchLibraries(server.accessToken, server.publicUrl, server.localUrl);
const checkboxes = [];
libraries.forEach(library => {
if (library.type === 'movie' || library.type === 'show' || library.type === 'music') {
const checkbox = document.createElement('div');
checkbox.classList.add('form-check');
checkbox.innerHTML = `
<input class="form-check-input" type="checkbox" value="${library.key}" id="library-${library.key}" data-type="${library.type}">
<label class="form-check-label" for="library-${library.key}">
<i class="fas ${library.type === 'movie' ? 'fa-film' : (library.type === 'show' ? 'fa-tv' : 'fa-music')} me-2"></i>
${library.title}
</label>
`;
m3uLibrariesContainer.appendChild(checkbox);
checkboxes.push(checkbox);
// Make the entire form-check div clickable
checkbox.addEventListener('click', () => {
const input = checkbox.querySelector('.form-check-input');
input.checked = !input.checked;
// Update download button state
downloadM3uBtn.disabled = m3uLibrariesContainer.querySelectorAll('input:checked').length === 0;
});
}
});
gsap.from(checkboxes, { opacity: 0, y: 20, stagger: 0.05, duration: 0.3 });
downloadM3uBtn.disabled = m3uLibrariesContainer.querySelectorAll('input:checked').length === 0;
} catch (error) {
console.error('Error fetching libraries:', error);
showNotification('Error fetching libraries.', 'error');
} finally {
m3uLibrariesLoader.style.display = 'none'; // Hide loader
}
});
downloadM3uBtn.addEventListener('click', async () => {
const serverId = m3uServerSelect.value;
const selectedLibraries = Array.from(m3uLibrariesContainer.querySelectorAll('input:checked')).map(input => input.value);
if (!serverId || selectedLibraries.length === 0) {
showNotification('Please select a server and at least one library.', 'error');
return;
}
downloadM3uBtn.disabled = true;
downloadM3uBtn.innerHTML = `<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> Generating...`;
try {
const servers = await getServers();
const server = servers.find(s => s.id == serverId);
if (!server) return;
let m3uContent = '#EXTM3U\n';
for (const libraryKey of selectedLibraries) {
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);
items.forEach(item => {
const duration = item.duration ? Math.round(item.duration / 1000) : -1;
const groupTitle = item.seriesTitle && item.seasonNumber
? `group-title="${item.seriesTitle} - Season ${item.seasonNumber}"`
: `group-title="${libraryTitle}"`;
const tvgLogo = item.thumb ? `tvg-logo="${item.thumb}"` : '';
m3uContent += `#EXTINF:${duration} ${tvgLogo} ${groupTitle},${item.title}\n`;
m3uContent += `${item.url}\n`;
});
}
const blob = new Blob([m3uContent], { type: 'audio/x-mpegurl;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'playlist.m3u';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
showNotification('M3U playlist downloaded successfully.', 'success');
} catch (error) {
console.error('Error generating M3U file:', error);
showNotification('Error generating M3U file.', 'error');
} finally {
downloadM3uBtn.disabled = false;
downloadM3uBtn.innerHTML = `<i class="fas fa-download"></i> __MSG_downloadM3u__`;
}
});
const navLink = document.getElementById('nav-m3u-generator');
if (navLink) {
const observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
if (mutation.attributeName === 'class' && mutation.target.classList.contains('active')) {
initializeM3uGenerator();
}
});
});
observer.observe(navLink, { attributes: true });
// Initial check in case it's already active
if (navLink.classList.contains('active')) {
initializeM3uGenerator();
}
}
});