fix headers

This commit is contained in:
Pas 2025-05-14 17:37:17 -06:00
parent 29d35419d4
commit 28e6e3c632
2 changed files with 126 additions and 111 deletions

View file

@ -3,8 +3,6 @@
* @description Proxies m3u8 files and their segments * @description Proxies m3u8 files and their segments
*/ */
import { setResponseHeaders } from 'h3';
// Helper function to parse URLs // Helper function to parse URLs
function parseURL(req_url: string, baseUrl?: string) { function parseURL(req_url: string, baseUrl?: string) {
if (baseUrl) { if (baseUrl) {
@ -42,34 +40,53 @@ function parseURL(req_url: string, baseUrl?: string) {
} }
} }
/** export default async function(request: Request, env: any, ctx: any) {
* Proxies m3u8 files and replaces the content to point to the proxy // Handle CORS preflight requests
*/ if (request.method === 'OPTIONS') {
async function proxyM3U8(event: any) { return new Response(null, {
const url = getQuery(event).url as string; headers: {
const headersParam = getQuery(event).headers as string; 'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, OPTIONS',
if (!url) { 'Access-Control-Allow-Headers': '*',
return sendError(event, createError({ 'Access-Control-Max-Age': '86400',
statusCode: 400, }
statusMessage: 'URL parameter is required' });
}));
} }
let headers = {}; // 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 });
}
console.log("Processing m3u8 request for URL:", targetUrl);
let customHeaders = {};
try { try {
headers = headersParam ? JSON.parse(headersParam) : {}; customHeaders = headersParam ? JSON.parse(headersParam) : {};
console.log("With headers:", JSON.stringify(customHeaders));
} catch (e) { } catch (e) {
return sendError(event, createError({ return new Response('Invalid headers format', { status: 400 });
statusCode: 400,
statusMessage: 'Invalid headers format'
}));
} }
try { try {
// Use native fetch instead of axios // Create base request headers
const response = await fetch(url, { const requestHeaders = new Headers({
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',
'Accept': '*/*',
'Accept-Language': 'en-US,en;q=0.9',
...customHeaders
});
console.log("Making request to:", targetUrl);
console.log("With headers:", JSON.stringify(Object.fromEntries(requestHeaders.entries())));
// Use fetch API which is natively available in Workers
const response = await fetch(targetUrl, {
headers: requestHeaders
}); });
if (!response.ok) { if (!response.ok) {
@ -79,9 +96,17 @@ async function proxyM3U8(event: any) {
const m3u8Content = await response.text(); const m3u8Content = await response.text();
// Get the base URL for the host // Get the base URL for the host
const host = getRequestHost(event); const host = url.hostname;
const proto = getRequestProtocol(event); const proto = url.protocol;
const baseProxyUrl = `${proto}://${host}`; const baseProxyUrl = `${proto}//${host}`;
const responseHeaders = {
'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=")) { if (m3u8Content.includes("RESOLUTION=")) {
// This is a master playlist with multiple quality variants // This is a master playlist with multiple quality variants
@ -95,7 +120,7 @@ async function proxyM3U8(event: any) {
const regex = /https?:\/\/[^\""\s]+/g; const regex = /https?:\/\/[^\""\s]+/g;
const keyUrl = regex.exec(line)?.[0]; const keyUrl = regex.exec(line)?.[0];
if (keyUrl) { if (keyUrl) {
const proxyKeyUrl = `${baseProxyUrl}/ts-proxy?url=${encodeURIComponent(keyUrl)}&headers=${encodeURIComponent(JSON.stringify(headers))}`; const proxyKeyUrl = `${baseProxyUrl}/ts-proxy?url=${encodeURIComponent(keyUrl)}&headers=${encodeURIComponent(JSON.stringify(customHeaders))}`;
newLines.push(line.replace(keyUrl, proxyKeyUrl)); newLines.push(line.replace(keyUrl, proxyKeyUrl));
} else { } else {
newLines.push(line); newLines.push(line);
@ -105,7 +130,7 @@ async function proxyM3U8(event: any) {
const regex = /https?:\/\/[^\""\s]+/g; const regex = /https?:\/\/[^\""\s]+/g;
const mediaUrl = regex.exec(line)?.[0]; const mediaUrl = regex.exec(line)?.[0];
if (mediaUrl) { if (mediaUrl) {
const proxyMediaUrl = `${baseProxyUrl}/m3u8-proxy?url=${encodeURIComponent(mediaUrl)}&headers=${encodeURIComponent(JSON.stringify(headers))}`; const proxyMediaUrl = `${baseProxyUrl}/m3u8-proxy?url=${encodeURIComponent(mediaUrl)}&headers=${encodeURIComponent(JSON.stringify(customHeaders))}`;
newLines.push(line.replace(mediaUrl, proxyMediaUrl)); newLines.push(line.replace(mediaUrl, proxyMediaUrl));
} else { } else {
newLines.push(line); newLines.push(line);
@ -115,9 +140,9 @@ async function proxyM3U8(event: any) {
} }
} else if (line.trim()) { } else if (line.trim()) {
// This is a quality variant URL // This is a quality variant URL
const variantUrl = parseURL(line, url); const variantUrl = parseURL(line, targetUrl);
if (variantUrl) { if (variantUrl) {
newLines.push(`${baseProxyUrl}/m3u8-proxy?url=${encodeURIComponent(variantUrl)}&headers=${encodeURIComponent(JSON.stringify(headers))}`); newLines.push(`${baseProxyUrl}/m3u8-proxy?url=${encodeURIComponent(variantUrl)}&headers=${encodeURIComponent(JSON.stringify(customHeaders))}`);
} else { } else {
newLines.push(line); newLines.push(line);
} }
@ -127,16 +152,9 @@ async function proxyM3U8(event: any) {
} }
} }
// Set appropriate headers return new Response(newLines.join("\n"), {
setResponseHeaders(event, { headers: responseHeaders
'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");
} else { } else {
// This is a media playlist with segments // This is a media playlist with segments
const lines = m3u8Content.split("\n"); const lines = m3u8Content.split("\n");
@ -149,7 +167,7 @@ async function proxyM3U8(event: any) {
const regex = /https?:\/\/[^\""\s]+/g; const regex = /https?:\/\/[^\""\s]+/g;
const keyUrl = regex.exec(line)?.[0]; const keyUrl = regex.exec(line)?.[0];
if (keyUrl) { if (keyUrl) {
const proxyKeyUrl = `${baseProxyUrl}/ts-proxy?url=${encodeURIComponent(keyUrl)}&headers=${encodeURIComponent(JSON.stringify(headers))}`; const proxyKeyUrl = `${baseProxyUrl}/ts-proxy?url=${encodeURIComponent(keyUrl)}&headers=${encodeURIComponent(JSON.stringify(customHeaders))}`;
newLines.push(line.replace(keyUrl, proxyKeyUrl)); newLines.push(line.replace(keyUrl, proxyKeyUrl));
} else { } else {
newLines.push(line); newLines.push(line);
@ -159,9 +177,9 @@ async function proxyM3U8(event: any) {
} }
} else if (line.trim() && !line.startsWith("#")) { } else if (line.trim() && !line.startsWith("#")) {
// This is a segment URL (.ts file) // This is a segment URL (.ts file)
const segmentUrl = parseURL(line, url); const segmentUrl = parseURL(line, targetUrl);
if (segmentUrl) { if (segmentUrl) {
newLines.push(`${baseProxyUrl}/ts-proxy?url=${encodeURIComponent(segmentUrl)}&headers=${encodeURIComponent(JSON.stringify(headers))}`); newLines.push(`${baseProxyUrl}/ts-proxy?url=${encodeURIComponent(segmentUrl)}&headers=${encodeURIComponent(JSON.stringify(customHeaders))}`);
} else { } else {
newLines.push(line); newLines.push(line);
} }
@ -171,29 +189,17 @@ async function proxyM3U8(event: any) {
} }
} }
// Set appropriate headers return new Response(newLines.join("\n"), {
setResponseHeaders(event, { headers: responseHeaders
'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) { } catch (error: any) {
console.error('Error proxying M3U8:', error); console.error('Error proxying M3U8:', error);
return sendError(event, createError({ return new Response(error.message || 'Error proxying M3U8 file', {
statusCode: 500, status: 500,
statusMessage: error.message || 'Error proxying M3U8 file' headers: {
})); 'Access-Control-Allow-Origin': '*'
}
});
} }
} }
export default defineEventHandler(async (event) => {
// Handle CORS preflight requests
if (isPreflightRequest(event)) return handleCors(event, {});
return await proxyM3U8(event);
});

View file

@ -3,64 +3,73 @@
* @description Proxies TS (transport stream) files * @description Proxies TS (transport stream) files
*/ */
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 // Handle CORS preflight requests
if (isPreflightRequest(event)) return handleCors(event, {}); if (request.method === 'OPTIONS') {
return new Response(null, {
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 {
const response = await fetch(url, {
method: 'GET',
headers: { headers: {
...headers as HeadersInit, 'Access-Control-Allow-Origin': '*',
'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', 'Access-Control-Allow-Methods': 'GET, OPTIONS',
'Access-Control-Allow-Headers': '*',
'Access-Control-Max-Age': '86400',
} }
}); });
}
// 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 });
}
let customHeaders = {};
try {
customHeaders = headersParam ? JSON.parse(headersParam) : {};
} catch (e) {
return new Response('Invalid headers format', { status: 400 });
}
try {
// 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
});
const response = await fetch(targetUrl, {
method: 'GET',
headers: requestHeaders
});
if (!response.ok) { if (!response.ok) {
throw new Error(`Failed to fetch TS file: ${response.status} ${response.statusText}`); throw new Error(`Failed to fetch TS file: ${response.status} ${response.statusText}`);
} }
// Set appropriate headers for each video segment // Get response as arrayBuffer
setResponseHeaders(event, { const data = await response.arrayBuffer();
'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 the response with appropriate headers
return new Uint8Array(await response.arrayBuffer()); 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
}
});
} catch (error: any) { } catch (error: any) {
console.error('Error proxying TS file:', error); console.error('Error proxying TS file:', error);
return sendError(event, createError({ return new Response(error.message || 'Error proxying TS file', {
statusCode: error.response?.status || 500, status: 500,
statusMessage: error.message || 'Error proxying TS file' headers: {
})); 'Access-Control-Allow-Origin': '*'
}
});
} }
}); }