Spaces:
Sleeping
Sleeping
| 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}`); | |
| }); | |