This commit is contained in:
Dongho Kim
2026-06-22 03:05:00 +02:00
parent 64f0d27468
commit 514be76202
+92 -2
View File
@@ -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`);