const express = require('express'); const fetch = require('node-fetch'); const cors = require('cors'); const { URL } = require('url'); const app = express(); const PORT = process.env.PORT || 7860; const ALLOWED_ORIGINS = [ 'https://hianimez.xyz', 'https://hianimez.qzz.io', 'https://kaori.qzz.io', 'https://anime-player-f0n.pages.dev', 'https://aniwatchtv.to', 'https://aniwatch.to', 'http://localhost:3000', 'http://localhost:5173', 'http://localhost:3005', 'http://localhost:3004' ]; app.use(cors({ origin: (origin, callback) => { if (!origin) return callback(null, true); const isAllowed = ALLOWED_ORIGINS.includes(origin) || ['hianimez.xyz', 'qzz.io', 'pages.dev', 'localhost', 'aniwatchtv.to', 'aniwatch.to'].some(domain => origin.includes(domain)); if (isAllowed) { callback(null, true); } else { callback(null, true); } }, credentials: true, exposedHeaders: ['Content-Length', 'Content-Range', 'Accept-Ranges', 'Content-Type'] })); function generateHeadersForDomain(targetUrl) { const url = new URL(targetUrl); const hostname = url.hostname.toLowerCase(); const path = url.pathname.toLowerCase(); const headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36", "Accept": "*/*", "Accept-Language": "en-US,en;q=0.9", "Sec-Ch-Ua": '"Not A(Brand";v="99", "Google Chrome";v="133", "Chromium";v="133"', "Sec-Ch-Ua-Mobile": "?0", "Sec-Ch-Ua-Platform": '"Windows"', "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "cross-site", "Pragma": "no-cache", "Cache-Control": "no-cache", "DNT": "1", "Upgrade-Insecure-Requests": "1", "Connection": "keep-alive" }; if (path.endsWith(".vtt")) { headers["Accept"] = "text/vtt, */*;q=0.1"; headers["Sec-Fetch-Dest"] = "track"; } const mirrorDomains = ["watching.onl", "pixvideo.skin", "trycloud.pro", "sugevideo.xyz", "cloudbuzz.lol", "cloudvideo.lat", "dnscd.onl", "vidlink.pro", "vidsrc.me", "vidsrc.to", "rabbitstream.net", "freewave.lol", "livedns.my", "freeproxy.io", "megacz.io"]; let isMirror = false; if (mirrorDomains.some(d => hostname.includes(d))) { headers["Referer"] = "https://vidwish.live/"; headers["Origin"] = "https://vidwish.live"; isMirror = true; } else if (hostname.includes("megaplay.buzz")) { headers["Referer"] = "https://megaplay.buzz/"; headers["Origin"] = "https://megaplay.buzz"; } else if (hostname.includes("vidstreaming") || hostname.includes("vizcloud") || hostname.includes("vidcloud")) { headers["Referer"] = "https://aniwatchtv.to/"; headers["Origin"] = "https://aniwatchtv.to"; } else if (hostname.includes("megacloud")) { headers["Referer"] = "https://megacloud.tv/"; headers["Origin"] = "https://megacloud.tv"; } else if (hostname.includes("vidwish.live") || hostname.includes("vidlink.pro")) { headers["Referer"] = "https://vidwish.live/"; headers["Origin"] = "https://vidwish.live"; } else if (hostname.includes("hianimez.xyz")) { headers["Referer"] = "https://hianimez.xyz/"; headers["Origin"] = "https://hianimez.xyz"; } const isSensitive = hostname.includes("megacloud") || hostname.includes("vizcloud") || hostname.includes("vidstreaming") || hostname.includes("sugevideo.xyz") || isMirror; if (isSensitive) { const randomIP = `${100 + Math.floor(Math.random() * 150)}.${10 + Math.floor(Math.random() * 200)}.${1 + Math.floor(Math.random() * 250)}.${1 + Math.floor(Math.random() * 250)}`; headers["X-Forwarded-For"] = randomIP; headers["X-Real-IP"] = randomIP; } return { headers, isMirror }; } function resolveURL(href, base) { try { return new URL(href, base).toString(); } catch { return href; } } function isVideoSegment(rawURL) { const lower = rawURL.toLowerCase(); // Manifests are NOT segments if (lower.split('?')[0].endsWith('.m3u8')) return false; if (lower.split('?')[0].match(/\.(ts|m4s|mp4|m4v|m2ts)$/)) return true; const segmentPatterns = ['seg-', 'segment-', 'chunk-', 'piece-', 'variant', 'v1-', 'v2-', 'v3-', 'v-a1', 'f1-', 'master']; // Removed 'index' from patterns as it often appears in playlist names return segmentPatterns.some(p => lower.includes(p)); } function isM3U8URL(rawURL, needsForce = false) { const lower = rawURL.toLowerCase(); const path = lower.split('?')[0]; if (path.endsWith('.m3u8') || path.endsWith('.m3u') || lower.includes('m3u8')) return true; if (needsForce && !isVideoSegment(rawURL)) { const segOrAsset = ['.ts', '.m4s', '.mp4', '.jpg', '.jpeg', '.png', '.webp', '.js', '.css']; if (!segOrAsset.some(ext => path.endsWith(ext))) return true; } return false; } const handleProxy = async (req, res) => { const targetUrl = req.query.url; const path = req.path; // CORS is handled by the middleware, removing manual headers to avoid duplicates if (!targetUrl) return res.status(400).send('Missing URL'); const headersParam = req.query.headers; let additionalHeaders = {}; if (headersParam) { try { additionalHeaders = JSON.parse(decodeURIComponent(headersParam)); } catch (e) { } } const range = req.headers['range']; if (range) additionalHeaders['Range'] = range; const { headers: domainHeaders, isMirror } = generateHeadersForDomain(targetUrl); // Header Precedence: Additional headers from the client override defaults, // but mirrors need stealth headers to avoid 403, so we only override if client didn't provide them. const requestHeaders = { ...domainHeaders, ...additionalHeaders }; if (isMirror) { requestHeaders["Referer"] = additionalHeaders["Referer"] || domainHeaders["Referer"]; requestHeaders["Origin"] = additionalHeaders["Origin"] || domainHeaders["Origin"]; } const sensitiveDomains = ["megacloud", "vizcloud", "vidstreaming", "sugevideo.xyz"]; if (sensitiveDomains.some(d => targetUrl.includes(d)) && !isMirror) { delete requestHeaders["X-Forwarded-For"]; delete requestHeaders["X-Real-IP"]; } try { let response = await fetch(targetUrl, { headers: requestHeaders }); // Robust 403 Retry Logic for segments and manifests if (response.status === 403) { console.warn(`[403 Retry] Forbidden for ${targetUrl}, attempting failover referers...`); const retryReferers = [ "", // No referer "https://vidwish.live/", "https://megaplay.buzz/", "https://hianime.to/", "https://aniwatchtv.to/" ]; for (const ref of retryReferers) { const retryHeaders = { ...requestHeaders }; if (ref) { retryHeaders["Referer"] = ref; try { retryHeaders["Origin"] = new URL(ref).origin; } catch (e) { } } else { delete requestHeaders["Referer"]; delete requestHeaders["Origin"]; } // Some origins block random IPs, try removing them on retry delete retryHeaders["X-Forwarded-For"]; delete retryHeaders["X-Real-IP"]; const retryResp = await fetch(targetUrl, { headers: retryHeaders }); if (retryResp.ok) { console.log(`[403 Retry Success] Found working referer: "${ref}" for ${targetUrl}`); response = retryResp; break; } } } if (!response.ok) { console.error(`[Upstream Error] Status ${response.status} | URL: ${targetUrl} | Referer: ${requestHeaders["Referer"] || 'None'}`); return res.status(response.status).send('Upstream Error'); } const ignoredHeaders = ['access-control-allow-origin', 'access-control-allow-credentials', 'access-control-allow-methods', 'access-control-allow-headers', 'vary', 'set-cookie', 'server', 'content-length']; response.headers.forEach((value, key) => { if (!ignoredHeaders.includes(key.toLowerCase())) { res.setHeader(key, value); } }); if (path === '/ts-proxy' || isVideoSegment(targetUrl)) { res.setHeader('Content-Type', 'video/mp2t'); } if (path === '/proxy') { const text = await response.text(); if (!text.trim().startsWith('#EXTM3U')) { return res.send(text); } const protocol = req.headers['x-forwarded-proto'] || req.protocol; const host = req.headers['x-forwarded-host'] || req.get('host'); const origin = `${protocol}://${host}`; const headersSuffix = headersParam ? `&headers=${encodeURIComponent(headersParam)}` : ''; const lines = text.split('\n'); const newLines = []; const forceDomains = ['pixvideo.skin', 'watching.onl', 'trycloud.pro', 'sugevideo.xyz', 'vidlink.pro', 'vidsrc.me', 'vidsrc.to', 'sugevideo', 'vidwish.live', 'qzz.io', 'rabbitstream', 'megaplay', 'vizcloud', 'vidstreaming', 'cloudbuzz.lol', 'cloudvideo.lat', 'dnscd.onl', 'freewave.lol', 'livedns.my', 'freeproxy.io', 'megacz.io']; const realAssets = ['.js', '.css', '.json', '.png', '.jpg', '.jpeg', '.webp', '.ico', '.svg', '.woff2']; const isTargetForceProxd = forceDomains.some(d => targetUrl.includes(d)); for (let line of lines) { const trimmed = line.trim(); if (!trimmed || trimmed.startsWith('#')) { if (trimmed.includes('URI=')) { line = line.replace(/URI="([^"]+)"/g, (match, originalURI) => { const resolved = resolveURL(originalURI, targetUrl); const isRealAsset = realAssets.some(ext => resolved.toLowerCase().endsWith(ext)); const needsForce = isTargetForceProxd || forceDomains.some(d => resolved.includes(d)); const isPlaylist = isM3U8URL(resolved, needsForce); const isSegment = isVideoSegment(resolved); if (isPlaylist) { return `URI="${origin}/proxy?url=${encodeURIComponent(resolved)}${headersSuffix}"`; } if (isSegment) { return `URI="${origin}/ts-proxy?url=${encodeURIComponent(resolved)}${headersSuffix}"`; } if (needsForce) { if (isRealAsset) return `URI="${resolved}"`; return `URI="${origin}/fetch?url=${encodeURIComponent(resolved)}${headersSuffix}"`; } return match; }); } newLines.push(line); continue; } const resolved = resolveURL(trimmed, targetUrl); const isRealAsset = realAssets.some(ext => resolved.toLowerCase().endsWith(ext)); const needsForce = isTargetForceProxd || forceDomains.some(d => resolved.includes(d)); const isPlaylist = isM3U8URL(resolved, needsForce); const isSegment = isVideoSegment(resolved); if (isPlaylist) { newLines.push(`${origin}/proxy?url=${encodeURIComponent(resolved)}${headersSuffix}`); } else if (isSegment) { newLines.push(`${origin}/ts-proxy?url=${encodeURIComponent(resolved)}${headersSuffix}`); } else if (needsForce) { if (isRealAsset) { newLines.push(resolved); } else { newLines.push(`${origin}/fetch?url=${encodeURIComponent(resolved)}${headersSuffix}`); } } else { newLines.push(resolved); } } res.setHeader('Content-Type', 'application/vnd.apple.mpegurl'); res.setHeader('Cache-Control', 'public, max-age=60'); return res.send(newLines.join('\n')); } const isSegment = path === '/ts-proxy' || isVideoSegment(targetUrl); if (isSegment) { res.setHeader('Content-Type', 'video/mp2t'); res.setHeader('Cache-Control', 'public, max-age=31536000, immutable'); } else { res.setHeader('Cache-Control', 'public, max-age=7200'); } response.body.pipe(res); } catch (e) { res.status(500).send('Proxy Error: ' + e.message); } }; app.get('/proxy', handleProxy); app.get('/ts-proxy', handleProxy); app.get('/fetch', handleProxy); app.get('/', (req, res) => { res.send('M3U8 Proxy for Hugging Face is running.'); }); app.listen(PORT, () => { console.log(`Server running on port ${PORT}`); });