From 173e069628285e65896ea01d34aee9fe4c8170dd Mon Sep 17 00:00:00 2001 From: Pas <74743263+Pasithea0@users.noreply.github.com> Date: Wed, 14 May 2025 17:45:13 -0600 Subject: [PATCH] revert --- src/routes/m3u8-proxy.ts | 433 +++++++++++++++------------------------ src/routes/ts-proxy.ts | 129 ++++-------- 2 files changed, 210 insertions(+), 352 deletions(-) diff --git a/src/routes/m3u8-proxy.ts b/src/routes/m3u8-proxy.ts index 3708524..9a0233d 100644 --- a/src/routes/m3u8-proxy.ts +++ b/src/routes/m3u8-proxy.ts @@ -3,300 +3,197 @@ * @description Proxies m3u8 files and their segments */ -// Helper function to safely create a URL object -function safeCreateURL(url: string, base?: string): URL | null { - try { - return base ? new URL(url, base) : new URL(url); - } catch (error) { - console.error("URL parsing error:", error, "URL:", url, "Base:", base); - return null; - } -} +import { setResponseHeaders } from 'h3'; // Helper function to parse URLs function parseURL(req_url: string, baseUrl?: string) { - try { - if (baseUrl) { - const url = safeCreateURL(req_url, baseUrl); - return url ? url.toString() : null; - } - - // If it's already a fully qualified URL - if (/^https?:\/\//i.test(req_url)) { - const url = safeCreateURL(req_url); - return url ? url.toString() : null; - } - - // If it starts with // (protocol-relative URL) - if (req_url.startsWith('//')) { - const url = safeCreateURL(`https:${req_url}`); - return url ? url.toString() : null; - } - - // If it's a relative URL and we have a base URL - if (baseUrl) { - const url = safeCreateURL(req_url, baseUrl); - return url ? url.toString() : null; - } - + if (baseUrl) { + return new URL(req_url, baseUrl).href; + } + + const match = req_url.match(/^(?:(https?:)?\/\/)?(([^\/?]+?)(?::(\d{0,5})(?=[\/?]|$))?)([\/?][\S\s]*|$)/i); + + if (!match) { return null; + } + + if (!match[1]) { + if (/^https?:/i.test(req_url)) { + return null; + } + + // Scheme is omitted + if (req_url.lastIndexOf("//", 0) === -1) { + // "//" is omitted + req_url = "//" + req_url; + } + req_url = (match[4] === "443" ? "https:" : "http:") + req_url; + } + + try { + const parsed = new URL(req_url); + if (!parsed.hostname) { + // "http://:1/" and "http:/notenoughslashes" could end up here + return null; + } + return parsed.href; } catch (error) { - console.error("URL parsing error:", error, "for URL:", req_url, "with base:", baseUrl); return null; } } -export default async function(request: Request, env: any, ctx: any) { - // Handle CORS preflight requests - if (request.method === 'OPTIONS') { - return new Response(null, { - headers: { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Methods': 'GET, OPTIONS', - 'Access-Control-Allow-Headers': '*', - 'Access-Control-Max-Age': '86400', - } - }); +/** + * Proxies m3u8 files and replaces the content to point to the proxy + */ +async function proxyM3U8(event: any) { + const url = getQuery(event).url as string; + const headersParam = getQuery(event).headers as string; + + if (!url) { + return sendError(event, createError({ + statusCode: 400, + statusMessage: 'URL parameter is required' + })); + } + + let headers = {}; + try { + headers = headersParam ? JSON.parse(headersParam) : {}; + } catch (e) { + return sendError(event, createError({ + statusCode: 400, + statusMessage: 'Invalid headers format' + })); } try { - // Parse URL parameters - const url = new URL(request.url); - const targetUrl = url.searchParams.get('url'); - const headersParam = url.searchParams.get('headers'); + // Use native fetch instead of axios + const response = await fetch(url, { + headers: headers as HeadersInit + }); - if (!targetUrl) { - return new Response('URL parameter is required', { status: 400 }); + if (!response.ok) { + throw new Error(`Failed to fetch M3U8: ${response.status} ${response.statusText}`); } - console.log("Processing m3u8 request for URL:", targetUrl); + const m3u8Content = await response.text(); - let customHeaders = {}; - try { - customHeaders = headersParam ? JSON.parse(headersParam) : {}; - console.log("With headers:", JSON.stringify(customHeaders)); - } catch (e) { - return new Response('Invalid headers format', { status: 400 }); - } + // Get the base URL for the host + const host = getRequestHost(event); + const proto = getRequestProtocol(event); + const baseProxyUrl = `${proto}://${host}`; - // Validate target URL - const validatedUrl = safeCreateURL(targetUrl); - if (!validatedUrl) { - return new Response(`Invalid target URL: ${targetUrl}`, { - status: 400, - headers: { 'Access-Control-Allow-Origin': '*' } - }); - } - - try { - // Create base request headers - const requestHeaders = new Headers({ - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36', - 'Accept': '*/*', - 'Accept-Language': 'en-US,en;q=0.9', - ...customHeaders - }); + if (m3u8Content.includes("RESOLUTION=")) { + // This is a master playlist with multiple quality variants + const lines = m3u8Content.split("\n"); + const newLines: string[] = []; - console.log("Making request to:", validatedUrl.toString()); - console.log("With headers:", JSON.stringify(Object.fromEntries(requestHeaders.entries()))); - - // Use fetch API which is natively available in Workers - const response = await fetch(validatedUrl.toString(), { - headers: requestHeaders - }); - - if (!response.ok) { - throw new Error(`Failed to fetch M3U8: ${response.status} ${response.statusText}`); + for (const line of lines) { + if (line.startsWith("#")) { + if (line.startsWith("#EXT-X-KEY:")) { + // Proxy the key URL + const regex = /https?:\/\/[^\""\s]+/g; + const keyUrl = regex.exec(line)?.[0]; + if (keyUrl) { + const proxyKeyUrl = `${baseProxyUrl}/ts-proxy?url=${encodeURIComponent(keyUrl)}&headers=${encodeURIComponent(JSON.stringify(headers))}`; + newLines.push(line.replace(keyUrl, proxyKeyUrl)); + } else { + newLines.push(line); + } + } else if (line.startsWith("#EXT-X-MEDIA:")) { + // Proxy alternative media URLs (like audio streams) + const regex = /https?:\/\/[^\""\s]+/g; + const mediaUrl = regex.exec(line)?.[0]; + if (mediaUrl) { + const proxyMediaUrl = `${baseProxyUrl}/m3u8-proxy?url=${encodeURIComponent(mediaUrl)}&headers=${encodeURIComponent(JSON.stringify(headers))}`; + newLines.push(line.replace(mediaUrl, proxyMediaUrl)); + } else { + newLines.push(line); + } + } else { + newLines.push(line); + } + } else if (line.trim()) { + // This is a quality variant URL + const variantUrl = parseURL(line, url); + if (variantUrl) { + newLines.push(`${baseProxyUrl}/m3u8-proxy?url=${encodeURIComponent(variantUrl)}&headers=${encodeURIComponent(JSON.stringify(headers))}`); + } else { + newLines.push(line); + } + } else { + // Empty line, preserve it + newLines.push(line); + } } - const m3u8Content = await response.text(); - - // Get the base URL for the host - const host = url.hostname; - const proto = url.protocol; - const baseProxyUrl = `${proto}//${host}`; - - const responseHeaders = { + // Set appropriate headers + setResponseHeaders(event, { 'Content-Type': 'application/vnd.apple.mpegurl', 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Headers': '*', 'Access-Control-Allow-Methods': '*', 'Cache-Control': 'no-cache, no-store, must-revalidate' - }; - - if (m3u8Content.includes("RESOLUTION=")) { - // This is a master playlist with multiple quality variants - const lines = m3u8Content.split("\n"); - const newLines: string[] = []; - - for (const line of lines) { - if (line.startsWith("#")) { - if (line.startsWith("#EXT-X-KEY:")) { - // Proxy the key URL - const regex = /URI="([^"]+)"/g; - const match = regex.exec(line); - const keyUrl = match ? match[1] : null; - - if (keyUrl) { - let fullKeyUrl; - try { - // Try to get the full URL - const keyUrlObj = keyUrl.startsWith('http') - ? safeCreateURL(keyUrl) - : safeCreateURL(keyUrl, validatedUrl.toString()); - - if (!keyUrlObj) { - throw new Error("Could not create valid URL for key"); - } - - fullKeyUrl = keyUrlObj.toString(); - const proxyKeyUrl = `${baseProxyUrl}/ts-proxy?url=${encodeURIComponent(fullKeyUrl)}&headers=${encodeURIComponent(JSON.stringify(customHeaders))}`; - newLines.push(line.replace(keyUrl, proxyKeyUrl)); - } catch (error) { - console.error("Error processing key URL:", keyUrl, error); - newLines.push(line); // Keep original if error - } - } else { - newLines.push(line); - } - } else if (line.startsWith("#EXT-X-MEDIA:")) { - // Proxy alternative media URLs (like audio streams) - const regex = /URI="([^"]+)"/g; - const match = regex.exec(line); - const mediaUrl = match ? match[1] : null; - - if (mediaUrl) { - try { - // Try to get the full URL - const mediaUrlObj = mediaUrl.startsWith('http') - ? safeCreateURL(mediaUrl) - : safeCreateURL(mediaUrl, validatedUrl.toString()); - - if (!mediaUrlObj) { - throw new Error("Could not create valid URL for media"); - } - - const fullMediaUrl = mediaUrlObj.toString(); - const proxyMediaUrl = `${baseProxyUrl}/m3u8-proxy?url=${encodeURIComponent(fullMediaUrl)}&headers=${encodeURIComponent(JSON.stringify(customHeaders))}`; - newLines.push(line.replace(mediaUrl, proxyMediaUrl)); - } catch (error) { - console.error("Error processing media URL:", mediaUrl, error); - newLines.push(line); // Keep original if error - } - } else { - newLines.push(line); - } - } else { - newLines.push(line); - } - } else if (line.trim()) { - // This is a quality variant URL - try { - // Try to create a full URL - const variantUrlObj = line.startsWith('http') - ? safeCreateURL(line) - : safeCreateURL(line, validatedUrl.toString()); - - if (!variantUrlObj) { - throw new Error("Could not create valid URL for variant"); - } - - const variantUrl = variantUrlObj.toString(); - newLines.push(`${baseProxyUrl}/m3u8-proxy?url=${encodeURIComponent(variantUrl)}&headers=${encodeURIComponent(JSON.stringify(customHeaders))}`); - } catch (error) { - console.error("Error processing variant URL:", line, error); - newLines.push(line); // Keep original if error - } - } else { - // Empty line, preserve it - newLines.push(line); - } - } - - return new Response(newLines.join("\n"), { - headers: responseHeaders - }); - } else { - // This is a media playlist with segments - const lines = m3u8Content.split("\n"); - const newLines: string[] = []; - - for (const line of lines) { - if (line.startsWith("#")) { - if (line.startsWith("#EXT-X-KEY:")) { - // Proxy the key URL - const regex = /URI="([^"]+)"/g; - const match = regex.exec(line); - const keyUrl = match ? match[1] : null; - - if (keyUrl) { - try { - // Try to get the full URL - const keyUrlObj = keyUrl.startsWith('http') - ? safeCreateURL(keyUrl) - : safeCreateURL(keyUrl, validatedUrl.toString()); - - if (!keyUrlObj) { - throw new Error("Could not create valid URL for key"); - } - - const fullKeyUrl = keyUrlObj.toString(); - const proxyKeyUrl = `${baseProxyUrl}/ts-proxy?url=${encodeURIComponent(fullKeyUrl)}&headers=${encodeURIComponent(JSON.stringify(customHeaders))}`; - newLines.push(line.replace(keyUrl, proxyKeyUrl)); - } catch (error) { - console.error("Error processing key URL:", keyUrl, error); - newLines.push(line); // Keep original if error - } - } else { - newLines.push(line); - } - } else { - newLines.push(line); - } - } else if (line.trim() && !line.startsWith("#")) { - // This is a segment URL (.ts file) - try { - // Try to create a full URL - const segmentUrlObj = line.startsWith('http') - ? safeCreateURL(line) - : safeCreateURL(line, validatedUrl.toString()); - - if (!segmentUrlObj) { - throw new Error("Could not create valid URL for segment"); - } - - const segmentUrl = segmentUrlObj.toString(); - newLines.push(`${baseProxyUrl}/ts-proxy?url=${encodeURIComponent(segmentUrl)}&headers=${encodeURIComponent(JSON.stringify(customHeaders))}`); - } catch (error) { - console.error("Error processing segment URL:", line, error); - newLines.push(line); // Keep original if error - } - } else { - // Comment or empty line, preserve it - newLines.push(line); - } - } - - return new Response(newLines.join("\n"), { - headers: responseHeaders - }); - } - } catch (error: any) { - console.error('Error proxying M3U8:', error); - return new Response(error.message || 'Error proxying M3U8 file', { - status: 500, - headers: { - 'Access-Control-Allow-Origin': '*' - } }); + + return newLines.join("\n"); + } else { + // This is a media playlist with segments + const lines = m3u8Content.split("\n"); + const newLines: string[] = []; + + for (const line of lines) { + if (line.startsWith("#")) { + if (line.startsWith("#EXT-X-KEY:")) { + // Proxy the key URL + const regex = /https?:\/\/[^\""\s]+/g; + const keyUrl = regex.exec(line)?.[0]; + if (keyUrl) { + const proxyKeyUrl = `${baseProxyUrl}/ts-proxy?url=${encodeURIComponent(keyUrl)}&headers=${encodeURIComponent(JSON.stringify(headers))}`; + newLines.push(line.replace(keyUrl, proxyKeyUrl)); + } else { + newLines.push(line); + } + } else { + newLines.push(line); + } + } else if (line.trim() && !line.startsWith("#")) { + // This is a segment URL (.ts file) + const segmentUrl = parseURL(line, url); + if (segmentUrl) { + newLines.push(`${baseProxyUrl}/ts-proxy?url=${encodeURIComponent(segmentUrl)}&headers=${encodeURIComponent(JSON.stringify(headers))}`); + } else { + newLines.push(line); + } + } else { + // Comment or empty line, preserve it + newLines.push(line); + } + } + + // Set appropriate headers + setResponseHeaders(event, { + 'Content-Type': 'application/vnd.apple.mpegurl', + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Headers': '*', + 'Access-Control-Allow-Methods': '*', + 'Cache-Control': 'no-cache, no-store, must-revalidate' + }); + + return newLines.join("\n"); } } catch (error: any) { - console.error('Unexpected error in m3u8-proxy:', error); - return new Response(error.message || 'Unexpected error in m3u8-proxy', { - status: 500, - headers: { - 'Access-Control-Allow-Origin': '*' - } - }); + console.error('Error proxying M3U8:', error); + return sendError(event, createError({ + statusCode: 500, + statusMessage: error.message || 'Error proxying M3U8 file' + })); } -} \ No newline at end of file +} + +export default defineEventHandler(async (event) => { + // Handle CORS preflight requests + if (isPreflightRequest(event)) return handleCors(event, {}); + + return await proxyM3U8(event); +}); \ No newline at end of file diff --git a/src/routes/ts-proxy.ts b/src/routes/ts-proxy.ts index aa4fe17..bbe4e0b 100644 --- a/src/routes/ts-proxy.ts +++ b/src/routes/ts-proxy.ts @@ -3,103 +3,64 @@ * @description Proxies TS (transport stream) files */ -// Helper function to safely create a URL object -function safeCreateURL(url: string, base?: string): URL | null { - try { - return base ? new URL(url, base) : new URL(url); - } catch (error) { - console.error("URL parsing error:", error, "URL:", url, "Base:", base); - return null; - } -} +import { setResponseHeaders } from 'h3'; -export default async function(request: Request, env: any, ctx: any) { +/** + * Proxies TS (transport stream) files + */ +export default defineEventHandler(async (event) => { // Handle CORS preflight requests - if (request.method === 'OPTIONS') { - return new Response(null, { - headers: { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Methods': 'GET, OPTIONS', - 'Access-Control-Allow-Headers': '*', - 'Access-Control-Max-Age': '86400', - } - }); + if (isPreflightRequest(event)) return handleCors(event, {}); + + const url = getQuery(event).url as string; + const headersParam = getQuery(event).headers as string; + + if (!url) { + return sendError(event, createError({ + statusCode: 400, + statusMessage: 'URL parameter is required' + })); + } + + let headers = {}; + try { + headers = headersParam ? JSON.parse(headersParam) : {}; + } catch (e) { + return sendError(event, createError({ + statusCode: 400, + statusMessage: 'Invalid headers format' + })); } try { - // Parse URL parameters - const url = new URL(request.url); - const targetUrl = url.searchParams.get('url'); - const headersParam = url.searchParams.get('headers'); - - if (!targetUrl) { - return new Response('URL parameter is required', { - status: 400, - headers: { 'Access-Control-Allow-Origin': '*' } - }); - } - - let customHeaders = {}; - try { - customHeaders = headersParam ? JSON.parse(headersParam) : {}; - } catch (e) { - return new Response('Invalid headers format', { - status: 400, - headers: { 'Access-Control-Allow-Origin': '*' } - }); - } - - // Validate the URL - const validatedUrl = safeCreateURL(targetUrl); - if (!validatedUrl) { - console.error('Invalid target URL:', targetUrl); - return new Response('Invalid target URL', { - status: 400, - headers: { 'Access-Control-Allow-Origin': '*' } - }); - } - - // Create request headers - const requestHeaders = new Headers({ - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36', - 'Accept': '*/*', - 'Accept-Language': 'en-US,en;q=0.9', - ...customHeaders - }); - - console.log('Fetching TS from:', validatedUrl.toString()); - console.log('With headers:', JSON.stringify(Object.fromEntries(requestHeaders.entries()))); - - // Fetch the TS file - const response = await fetch(validatedUrl.toString(), { + const response = await fetch(url, { method: 'GET', - headers: requestHeaders + headers: { + ...headers as HeadersInit, + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36', + } }); if (!response.ok) { throw new Error(`Failed to fetch TS file: ${response.status} ${response.statusText}`); } - // Get response as arrayBuffer - const data = await response.arrayBuffer(); - - // Return the response with appropriate headers - return new Response(data, { - headers: { - 'Content-Type': 'video/mp2t', - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Headers': '*', - 'Access-Control-Allow-Methods': '*', - 'Cache-Control': 'public, max-age=3600' // Allow caching of TS segments - } + // Set appropriate headers for each video segment + setResponseHeaders(event, { + 'Content-Type': 'video/mp2t', + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Headers': '*', + 'Access-Control-Allow-Methods': '*', + 'Cache-Control': 'public, max-age=3600' // Allow caching of TS segments }); + + // Return the binary data directly - Nitro should handle this properly + return new Uint8Array(await response.arrayBuffer()); } catch (error: any) { console.error('Error proxying TS file:', error); - return new Response(error.message || 'Error proxying TS file', { - status: 500, - headers: { - 'Access-Control-Allow-Origin': '*' - } - }); + return sendError(event, createError({ + statusCode: error.response?.status || 500, + statusMessage: error.message || 'Error proxying TS file' + })); } -} \ No newline at end of file +}); \ No newline at end of file