const WORKER_PATH = '/cf-proxy'; export default { async fetch(request) { const url = new URL(request.url); if (request.method === 'OPTIONS') { return new Response(null, { status: 204, headers: corsHeaders() }); } const uParam = url.searchParams.get('u') || ''; const refParam = url.searchParams.get('ref') || ''; let targetUrl; try { targetUrl = atob(uParam); if (!targetUrl.startsWith('http')) throw 0; } catch { return new Response('Missing or invalid u param', { status: 400 }); } let referer; try { referer = refParam ? atob(refParam) : new URL(targetUrl).origin + '/'; if (!referer.endsWith('/')) referer += '/'; } catch { referer = new URL(targetUrl).origin + '/'; } let resp; try { resp = await fetch(targetUrl, { headers: { 'Referer': referer, 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', 'Accept': '*/*', 'Accept-Language': 'tr-TR,tr;q=0.9,en;q=0.8', }, }); } catch (e) { return new Response('Upstream fetch failed: ' + e.message, { status: 502 }); } if (!resp.ok && resp.status !== 206) { // Referer ile de olmadıysa referer'sız dene (bazı CDN'ler Cloudflare'dan gelen Referer'ı reddeder) try { const fallbackResp = await fetch(targetUrl, { headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', 'Accept': '*/*', }, }); if (fallbackResp.ok || fallbackResp.status === 206) { resp = fallbackResp; } } catch {} } const ct = resp.headers.get('content-type') || ''; const isM3u8 = ct.includes('mpegurl') || targetUrl.toLowerCase().includes('.m3u8'); if (isM3u8) { const body = await resp.text(); if (body.trimStart().startsWith('#EXTM3U')) { const baseDir = targetUrl.substring(0, targetUrl.lastIndexOf('/') + 1); const workerBase = url.origin + WORKER_PATH + '?u='; const refSuffix = '&ref=' + btoa(referer); const rewritten = body.split('\n').map(line => { const t = line.trimEnd(); if (t === '') return ''; if (t.startsWith('#')) { return t.replace(/URI="([^"]+)"/g, (_, uri) => { const abs = uri.startsWith('http') ? uri : baseDir + uri; return `URI="${workerBase}${btoa(abs)}${refSuffix}"`; }); } const abs = t.startsWith('http') ? t : baseDir + t; return workerBase + btoa(abs) + refSuffix; }).join('\n'); return new Response(rewritten, { status: 200, headers: { ...corsHeaders(), 'Content-Type': 'application/vnd.apple.mpegurl', 'Cache-Control': 'no-cache' }, }); } // m3u8 gibi görünüp değil — binary olarak geçir return new Response(body, { status: resp.status, headers: { ...corsHeaders(), 'Content-Type': ct || 'video/mp2t' }, }); } // Segment (.ts, .key, .png vb.) — body'yi doğrudan stream et (belleğe alma) let segCt = ct; if (!segCt || segCt.startsWith('image/') || segCt === 'application/octet-stream') { segCt = 'video/mp2t'; } return new Response(resp.body, { status: resp.status, headers: { ...corsHeaders(), 'Content-Type': segCt, 'Cache-Control': 'public, max-age=3600' }, }); } }; function corsHeaders() { return { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, OPTIONS', 'Access-Control-Allow-Headers': 'Range, Origin', 'Access-Control-Expose-Headers': 'Content-Length, Content-Range, Content-Type', }; }