addEventListener('fetch', event => { event.respondWith(handleRequest(event.request)); }); async function handleRequest(request) { const url = new URL(request.url); const pathname = url.pathname; // Handle the home page route if (pathname === '/' && !url.search) { const html = ` M3U Playlist Proxy

M3U Playlist Proxy

Use the form below to generate a playlist with the necessary headers. For example, if the server requires a "Referer: http://example.com" header to allow streaming, you would enter "Referer" as the Header Name and "http://example.com" as the Header Value.


Generated Playlist URL:

Once the URL has been generated, you can use a URL shortener like TinyURL.com to shorten it. This will make it much easier to add the URL as a M3U playlist within your IPTV application.

`; return new Response(html, { headers: { 'Content-Type': 'text/html' } }); } // Handle the /playlist route if (pathname === '/playlist') { const urlParam = url.searchParams.get('url'); const dataParam = url.searchParams.get('data'); if (!urlParam) { return new Response('URL parameter missing', { status: 400 }); } return handlePlaylistRequest(request, urlParam, dataParam); } // Handle proxied URL requests (same logic as before) const params = url.searchParams; const requestUrl = params.has('url') ? decodeURIComponent(params.get('url')) : null; const secondaryUrl = params.has('url2') ? decodeURIComponent(params.get('url2')) : null; const data = params.get('data') ? atob(params.get('data')) : null; // Decode base64 const isMaster = !params.has('url2'); // Check if it's a master playlist const finalRequestUrl = isMaster ? requestUrl : secondaryUrl; if (finalRequestUrl) { // Handle encryption key requests if (params.has('key') && params.get('key') === 'true') { return fetchEncryptionKey(finalRequestUrl, data); } // Set dataType based on whether it's a master playlist (text) or a segment (binary) const dataType = isMaster ? 'text' : 'binary'; // Fetch content from the URL with appropriate headers and dataType const result = await fetchContent(finalRequestUrl, data, dataType); if (result.status >= 400) { return new Response(`Error: ${result.status}`, { status: result.status }); } let content = result.content; // If it's a master playlist, rewrite the URLs and treat as text if (isMaster) { const baseUrl = new URL(result.finalUrl).origin; const proxyUrl = `${url.origin}`; content = rewriteUrls(content, baseUrl, proxyUrl, params.get('data')); } // Send the response with content and headers return new Response(content, { headers: result.headers, status: result.status }); } return new Response('Bad Request', { status: 400 }); } // Function to fetch content from a URL using fetch async function fetchContent(url, data, dataType = 'text') { const headers = new Headers(); if (data) { const headersArray = data.split('|'); headersArray.forEach(header => { const [key, value] = header.split('='); headers.append(key.trim(), value.trim().replace(/['"]/g, '')); }); } const response = await fetch(url, { headers }); const buffer = await response.arrayBuffer(); let content; if (dataType === 'binary') { content = buffer; // Treat as binary } else { content = new TextDecoder('utf-8').decode(buffer); // Default to text } return { content, finalUrl: url, status: response.status, headers: response.headers, }; } // Function to handle playlist requests async function handlePlaylistRequest(request, playlistUrl, data) { const result = await fetchContent(playlistUrl); if (result.status !== 200) { return new Response('Failed to fetch playlist', { status: 500 }); } let playlistContent = result.content; const baseUrl = new URL(request.url).origin; playlistContent = rewritePlaylistUrls(playlistContent, baseUrl, data); return new Response(playlistContent, { headers: { 'Content-Type': 'text/plain' } }); } // Function to fetch encryption key async function fetchEncryptionKey(url, data) { const updatedUrl = url.includes('videohls.ru') ? url.replace('videohls.ru', 'onlinehdhls.ru') : url; const result = await fetchContent(updatedUrl, data, 'binary'); // Pass 'binary' as the dataType if (result.status >= 400) { return new Response(`Failed to fetch encryption key: ${result.status}`, { status: 500 }); } return new Response(result.content, { headers: result.headers }); } // Rewrite URLs in the M3U8 playlist function rewriteUrls(content, baseUrl, proxyUrl, data) { const lines = content.split('\n'); const rewrittenLines = []; let isNextLineUri = false; lines.forEach(line => { if (line.startsWith('#')) { if (line.includes('URI="')) { const uriMatch = line.match(/URI="([^"]+)"/i); let uri = uriMatch[1]; if (!uri.startsWith('http')) { uri = new URL(uri, baseUrl).href; } const rewrittenUri = `${proxyUrl}?url=${encodeURIComponent(uri)}&data=${encodeURIComponent(data)}${line.includes('#EXT-X-KEY') ? '&key=true' : ''}`; line = line.replace(uriMatch[1], rewrittenUri); } rewrittenLines.push(line); if (line.includes('#EXT-X-STREAM-INF')) { isNextLineUri = true; } } else if (line.startsWith('http') || isNextLineUri) { const urlParam = isNextLineUri ? 'url' : 'url2'; let lineUrl = line; if (!lineUrl.startsWith('http')) { lineUrl = new URL(lineUrl, baseUrl).href; } const fullUrl = `${proxyUrl}?${urlParam}=${encodeURIComponent(lineUrl)}&data=${encodeURIComponent(data)}${urlParam === 'url' ? '&type=/index.m3u8' : '&type=/index.ts'}`; rewrittenLines.push(fullUrl); isNextLineUri = false; } else { rewrittenLines.push(line); } }); return rewrittenLines.join('\n'); } // Rewrite playlist URLs and encode headers function rewritePlaylistUrls(content, baseUrl, data) { const lines = content.split('\n'); const rewrittenLines = []; lines.forEach(line => { if (line.startsWith('#EXTINF')) { rewrittenLines.push(line); } else if (line.startsWith('http')) { const headerSeparatorIndex = line.indexOf('|'); const streamUrl = headerSeparatorIndex !== -1 ? line.substring(0, headerSeparatorIndex) : line; let base64Data = ''; if (data) { base64Data = data; // Use provided data directly if it's passed } else if (headerSeparatorIndex !== -1) { const headersString = line.substring(headerSeparatorIndex + 1); const headers = headersString ? headersString.split('|').map(header => { const [key, value] = header.split('='); const cleanValue = value ? value.replace(/^['"]|['"]$/g, '').trim() : ''; return `${key.trim()}=${cleanValue}`; }) : []; base64Data = headers.length > 0 ? btoa(headers.join('|')) : ''; } const newUrl = `${baseUrl}?url=${encodeURIComponent(streamUrl)}&data=${encodeURIComponent(base64Data)}`; rewrittenLines.push(newUrl); } else { rewrittenLines.push(line); } }); return rewrittenLines.join('\n'); }