// 1. Global State and Constants (Declare these first!) let currentPage = 1; const perPage = 12; const renderedVideoIds = new Set(); // 2. Observer Definition (Must be defined before initApp uses it) const observer = new IntersectionObserver((entries) => { if (entries[0].isIntersecting) loadVideos(); }, { threshold: 1.0 }); // 3. Logic Functions async function InitializeLocalStorage() { if (!localStorage.getItem('config')) { localStorage.setItem('config', JSON.stringify({ servers: [{ "https://getfigleaf.com": {} }] })); } // We always run this to make sure session is fresh await InitializeServerStatus(); } async function InitializeServerStatus() { const config = JSON.parse(localStorage.getItem('config')); if (!config || !config.servers) return; const statusPromises = config.servers.map(async (serverObj) => { const server = Object.keys(serverObj)[0]; try { const response = await fetch(`/api/status`, { method: "POST", body: JSON.stringify({ server: server }), headers: { "Content-Type": "application/json" }, }); const status = await response.json(); serverObj[server] = status; } catch (err) { serverObj[server] = { online: false, channels: [] }; } }); await Promise.all(statusPromises); localStorage.setItem('config', JSON.stringify(config)); const firstServerKey = Object.keys(config.servers[0])[0]; const serverData = config.servers[0][firstServerKey]; if (serverData.channels && serverData.channels.length > 0) { const channel = serverData.channels[0]; let options = {}; if (channel.options) { channel.options.forEach(element => { // Ensure the options structure matches your API expectations options[element.id] = element.options[0]; }); } const sessionData = { server: firstServerKey, channel: channel, options: options, }; localStorage.setItem('session', JSON.stringify(sessionData)); } } async function loadVideos() { const session = JSON.parse(localStorage.getItem('session')); if (!session) return; // Build the request body let body = { channel: session.channel.id, query: "", page: currentPage, perPage: perPage, server: session.server }; // Correct way to loop through the options object Object.entries(session.options).forEach(([key, value]) => { body[key] = value.id; }); try { const response = await fetch('/api/videos', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }); const videos = await response.json(); renderVideos(videos); currentPage++; } catch (err) { console.error("Failed to load videos:", err); } } function renderVideos(videos) { const grid = document.getElementById('video-grid'); if (!grid) return; videos.items.forEach(v => { if (renderedVideoIds.has(v.id)) return; const card = document.createElement('div'); card.className = 'video-card'; const durationText = v.duration === 0 ? '' : ` • ${v.duration}s`; card.innerHTML = ` ${v.title}

${v.title}

${v.channel}${durationText}

`; card.onclick = () => openPlayer(v.url); grid.appendChild(card); renderedVideoIds.add(v.id); }); } // 4. Initialization (Run this last) async function initApp() { // Clear old data if you want a fresh start every refresh // localStorage.clear(); await InitializeLocalStorage(); const sentinel = document.getElementById('sentinel'); if (sentinel) { observer.observe(sentinel); } await loadVideos(); } function openPlayer(url) { const modal = document.getElementById('video-modal'); const video = document.getElementById('player'); video.src = `/api/stream?url=${encodeURIComponent(url)}`; modal.style.display = 'flex'; document.body.style.overflow = 'hidden'; } function closePlayer() { const modal = document.getElementById('video-modal'); const video = document.getElementById('player'); video.pause(); video.src = ''; modal.style.display = 'none'; document.body.style.overflow = 'auto'; } initApp();