feat: return raw CDN URLs for streams (browser loads directly via CORS *); keep subtitle proxy for third-party CDNs

This commit is contained in:
Sʜɪɴᴇɪ Nᴏᴜᴢᴇɴ
2026-06-25 08:22:34 +05:30
parent 76c47368a7
commit 6cd2b9a19e
2 changed files with 88 additions and 44 deletions
+69 -20
View File
@@ -3,13 +3,36 @@ const axios = require("axios");
const PROXY_KEY = Buffer.from("a54d389c18527d9fd3e7f0643e27edbe", "hex");
const PRU_BASE = "https://pru.ultracloud.cc/";
function encodeForPru(rawUrl) {
const urlBytes = Buffer.from(rawUrl, "utf-8");
const xored = Buffer.alloc(urlBytes.length);
for (let i = 0; i < urlBytes.length; i++) {
xored[i] = urlBytes[i] ^ PROXY_KEY[i % PROXY_KEY.length];
function base64urlXor(str) {
const buf = Buffer.from(str, "utf-8");
const xored = Buffer.alloc(buf.length);
for (let i = 0; i < buf.length; i++) {
xored[i] = buf[i] ^ PROXY_KEY[i % PROXY_KEY.length];
}
return PRU_BASE + xored.toString("base64url");
return xored.toString("base64url");
}
function decodePruUrl(pruUrl) {
try {
const path = new URL(pruUrl).pathname.replace(/^\//, "").replace(/\/pl\.m3u8$/, "").replace(/\/seg\.jpg$/, "");
const tildeIdx = path.indexOf("~");
if (tildeIdx === -1) return null;
const urlPart = path.substring(0, tildeIdx);
const refPart = path.substring(tildeIdx + 1);
const urlBuf = Buffer.from(urlPart, "base64url");
const url = Buffer.from(Array.from(urlBuf).map((b, i) => b ^ PROXY_KEY[i % PROXY_KEY.length])).toString("utf-8");
const refBuf = Buffer.from(refPart, "base64url");
const referer = Buffer.from(Array.from(refBuf).map((b, i) => b ^ PROXY_KEY[i % PROXY_KEY.length])).toString("utf-8");
return { url, referer };
} catch {
return null;
}
}
function encodeForPru(rawUrl, referer) {
const urlEnc = base64urlXor(rawUrl);
const refEnc = base64urlXor(referer || new URL(rawUrl).origin + "/");
return `${PRU_BASE}${urlEnc}~${refEnc}/pl.m3u8`;
}
module.exports = async function handler(req, res) {
@@ -23,13 +46,31 @@ module.exports = async function handler(req, res) {
if (!url) return res.status(400).json({ success: false, message: "url query parameter is required" });
try {
const headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36",
Referer: referer || "https://vidtube.site/",
};
const decoded = decodePruUrl(url);
const isPru = !!decoded;
const targetUrl = decoded ? decoded.url : url;
const targetReferer = decoded ? decoded.referer : (referer || "https://www.miruro.tv/");
const response = await axios.get(url, {
headers,
let fetchUrl;
let fetchHeaders;
if (isPru) {
fetchUrl = encodeForPru(targetUrl, targetReferer);
fetchHeaders = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36",
Referer: "https://www.miruro.tv/",
Origin: "https://www.miruro.tv",
};
} else {
fetchUrl = targetUrl;
fetchHeaders = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36",
Referer: targetReferer,
};
}
const response = await axios.get(fetchUrl, {
headers: fetchHeaders,
timeout: 15000,
responseType: "arraybuffer",
validateStatus: (s) => s < 500,
@@ -38,26 +79,34 @@ module.exports = async function handler(req, res) {
const contentType = response.headers["content-type"] || "";
res.setHeader("Cache-Control", "public, max-age=300");
if (contentType.includes("mpegurl") || url.endsWith(".m3u8") || contentType.includes("x-mpegURL")) {
if (isPru && (contentType.includes("mpegurl") || targetUrl.endsWith(".m3u8") || contentType.includes("x-mpegURL"))) {
let m3u8 = Buffer.from(response.data).toString("utf-8");
const baseUrl = new URL(url);
// Rewrite all URLs to go through pru.ultracloud.cc proxy
m3u8 = m3u8.replace(/^(https?:\/\/[^\s]+)$/gm, (match) => {
return encodeForPru(match);
if (match.includes("pru.ultracloud.cc")) {
const dec = decodePruUrl(match);
if (dec) return `/api/proxy?url=${encodeURIComponent(dec.url)}&referer=${encodeURIComponent(dec.referer)}`;
}
return `/api/proxy?url=${encodeURIComponent(match)}&referer=${encodeURIComponent(targetReferer)}`;
});
// Rewrite relative URLs
m3u8 = m3u8.replace(/^([^\s#<][^\s]*\.(m3u8|ts|m4s|m4v|mp4|cmfv|cmfa|vtt|srt|ass|json|aac|mp3))(?:\?[^\s]*)?$/gm, (match) => {
const absolute = new URL(match, baseUrl).href;
return encodeForPru(absolute);
try {
const absolute = new URL(match, new URL(targetUrl)).href;
return `/api/proxy?url=${encodeURIComponent(absolute)}&referer=${encodeURIComponent(targetReferer)}`;
} catch {
return match;
}
});
res.setHeader("Content-Type", "application/vnd.apple.mpegurl");
return res.send(m3u8);
}
// For segments/subtitles, pass through directly (they have CORS)
res.setHeader("Content-Type", contentType);
if (response.headers["content-length"]) {
res.setHeader("Content-Length", response.headers["content-length"]);
}
res.send(Buffer.from(response.data));
} catch (err) {
res.status(500).json({ success: false, message: "Proxy failed: " + err.message });