From 514be762025a9bfa66fd7ad93038ed2ea9e73254 Mon Sep 17 00:00:00 2001 From: Dongho Kim Date: Mon, 22 Jun 2026 03:05:00 +0200 Subject: [PATCH] update --- index.js | 94 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 92 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index 2b86ca5..7f09e77 100644 --- a/index.js +++ b/index.js @@ -67,8 +67,10 @@ app.get('/stream/:channelId', async (req, res) => { const hlsMedia = media.find(m => m.mediaId === 'HLS' || m.mediaId === 'LLHLS'); if (hlsMedia && hlsMedia.path) { - // Redirect Jellyfin to the actual M3U8 url - res.redirect(302, hlsMedia.path); + // Redirect Jellyfin to our proxy endpoint to intercept keys + const host = req.get('host'); + const protocol = req.protocol; + res.redirect(302, `${protocol}://${host}/m3u8?url=${encodeURIComponent(hlsMedia.path)}`); } else { res.status(404).send('HLS stream not found in playback info'); } @@ -78,6 +80,94 @@ app.get('/stream/:channelId', async (req, res) => { } }); +app.get('/m3u8', async (req, res) => { + const m3u8Url = req.query.url; + if (!m3u8Url) return res.status(400).send('Missing url parameter'); + + try { + const response = await axios.get(m3u8Url); + let m3u8Data = response.data; + const host = req.get('host'); + const protocol = req.protocol; + const baseUrl = `${protocol}://${host}`; + + // Process m3u8 lines + const lines = m3u8Data.split('\n'); + const processedLines = lines.map(line => { + let processed = line.trim(); + if (!processed) return processed; + + // Rewrite AES Key URIs to use our proxy + if (processed.startsWith('#EXT-X-KEY:')) { + processed = processed.replace(/URI="(https?:\/\/[^"]+)"/, (match, keyUrl) => { + return `URI="${baseUrl}/key?url=${encodeURIComponent(keyUrl)}"`; + }); + return processed; + } + + // If it's a tag with a URI (like #EXT-X-MEDIA:URI="..."), make it absolute and proxy it if it's an m3u8 + if (processed.startsWith('#EXT-X-MEDIA:') && processed.includes('URI=')) { + processed = processed.replace(/URI="(.*?)"/, (match, uri) => { + if (uri && !uri.startsWith('http')) { + uri = new URL(uri, m3u8Url).toString(); + } + if (uri.includes('.m3u8')) { + uri = `${baseUrl}/m3u8?url=${encodeURIComponent(uri)}`; + } + return `URI="${uri}"`; + }); + return processed; + } + + // If it's a URL (variant playlist or segment) + if (!processed.startsWith('#')) { + let uri = processed; + if (!uri.startsWith('http')) { + uri = new URL(uri, m3u8Url).toString(); + } + // If it's a variant playlist, proxy it + if (uri.includes('.m3u8')) { + uri = `${baseUrl}/m3u8?url=${encodeURIComponent(uri)}`; + } + return uri; + } + + return processed; + }); + + res.header('Content-Type', 'application/vnd.apple.mpegurl'); + res.send(processedLines.join('\n')); + } catch (error) { + console.error(`Error proxying m3u8: ${error.message}`); + res.status(500).send('Error proxying m3u8'); + } +}); + +app.get('/key', async (req, res) => { + const keyUrl = req.query.url; + if (!keyUrl) return res.status(400).send('Missing url parameter'); + + try { + const headers = { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36' + }; + + const cookies = []; + if (process.env.NID_AUT) cookies.push(`NID_AUT=${process.env.NID_AUT}`); + if (process.env.NID_SES) cookies.push(`NID_SES=${process.env.NID_SES}`); + if (cookies.length > 0) { + headers['Cookie'] = cookies.join('; '); + } + + const response = await axios.get(keyUrl, { headers, responseType: 'arraybuffer' }); + res.header('Content-Type', 'application/octet-stream'); + res.send(response.data); + } catch (error) { + console.error(`Error fetching key: ${error.message}`); + res.status(500).send('Error fetching key'); + } +}); + app.listen(port, () => { console.log(`Chzzk IPTV Proxy running on port ${port}`); console.log(`Playlist URL: http://localhost:${port}/playlist.m3u`);