165 lines
7.8 KiB
JavaScript
165 lines
7.8 KiB
JavaScript
|
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();
|
||
|
}
|
||
|
}
|
||
|
});
|