diff --git a/docker-compose.yml b/docker-compose.yml index f867ad4..f7af2d7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,9 @@ version: '3.8' +networks: + iptv: + driver: bridge + services: chzzk-proxy: build: . @@ -9,3 +13,21 @@ services: env_file: - .env restart: unless-stopped + networks: + - iptv + + threadfin: + image: fyb3roptik/threadfin:latest + container_name: threadfin + ports: + - "34400:34400" + environment: + - THREADFIN_PORT=34400 + - THREADFIN_BRANCH=main + volumes: + - ./threadfin-config:/home/threadfin/conf + depends_on: + - chzzk-proxy + restart: unless-stopped + networks: + - iptv diff --git a/index.js b/index.js index 680790f..36d04fc 100644 --- a/index.js +++ b/index.js @@ -33,6 +33,43 @@ app.get('/playlist.m3u', (req, res) => { res.send(m3u); }); +// Dynamic live-only playlist — only includes channels currently broadcasting +// This is the URL you point Threadfin at +app.get('/playlist-live.m3u', async (req, res) => { + const channels = parseChannels(); + const host = req.get('host'); + const protocol = req.protocol; + + const results = await Promise.allSettled( + channels.map(async (ch) => { + try { + const content = await fetchLiveDetail(ch.id); + return { ch, content }; + } catch { + return { ch, content: null }; + } + }) + ); + + let m3u = "#EXTM3U\n"; + + results.forEach(r => { + if (r.status !== 'fulfilled') return; + const { ch, content } = r.value; + // Only include the channel if it is actively live + if (!content || content.status !== 'OPEN') return; + + const title = content.liveTitle ? `${ch.name} — ${content.liveTitle}` : ch.name; + const logo = (content.channel && content.channel.channelImageUrl) || ''; + + m3u += `#EXTINF:-1 tvg-id="${ch.id}" tvg-name="${ch.name}" tvg-logo="${logo}" group-title="Chzzk Live",${title}\n`; + m3u += `${protocol}://${host}/stream/${ch.id}\n`; + }); + + res.header('Content-Type', 'application/vnd.apple.mpegurl'); + res.send(m3u); +}); + // Helper: build request headers with optional Naver auth cookies const buildHeaders = () => { const headers = { @@ -269,7 +306,8 @@ app.get('/key', async (req, res) => { app.listen(port, () => { console.log(`Chzzk IPTV Proxy running on port ${port}`); - console.log(`Playlist URL: http://localhost:${port}/playlist.m3u`); - console.log(`Schedule URL: http://localhost:${port}/schedule`); - console.log(`EPG URL: http://localhost:${port}/epg.xml`); + console.log(`Playlist URL: http://localhost:${port}/playlist.m3u`); + console.log(`Live Playlist URL: http://localhost:${port}/playlist-live.m3u`); + console.log(`Schedule URL: http://localhost:${port}/schedule`); + console.log(`EPG URL: http://localhost:${port}/epg.xml`); });