fixed vidvault

This commit is contained in:
2026-11-10 13:57:22 -08:00
parent 4ff64819d7
commit 60785220b6

View File

@@ -1,168 +1,178 @@
import { formatBytes, USER_AGENT } from '../utils/helpers.js';
// vidvault i see you change your api again over and over, dm me if you want me to remove your source and no longer add you
const BASE_URL = 'https://vidvault.to';
const API_BASE = `${BASE_URL}/api`;
const DL_PROXY_BASE = 'https://dl.gemlelispe.workers.dev';
const SUB_PROXY_BASE = 'https://sub.k5s7sjozpn.workers.dev';
const HEADERS = {
Referer: `${BASE_URL}/`,
Origin: BASE_URL,
'User-Agent': USER_AGENT,
Accept: 'application/json, text/plain, */*',
'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': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
'Accept-Language': 'en-US,en;q=0.9',
'Referer': `${BASE_URL}/`,
};
let cookieJar = null;
let cookiePromise = null;
const PLAYBACK_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',
'Referer': `${BASE_URL}/`,
};
function ensureCookies() {
if (cookieJar !== null) return Promise.resolve();
if (cookiePromise) return cookiePromise;
cookiePromise = (async () => {
try {
const res = await fetch(`${BASE_URL}/`, {
headers: {
'User-Agent': USER_AGENT,
Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'en-US,en;q=0.9',
},
signal: AbortSignal.timeout(10000),
});
const setCookie = typeof res.headers.getSetCookie === 'function'
? res.headers.getSetCookie()
: (res.headers.get('set-cookie') ? [res.headers.get('set-cookie')] : []);
cookieJar = setCookie.map(c => c.split(';')[0].trim()).filter(Boolean).join('; ');
} catch {
cookieJar = '';
}
})();
return cookiePromise;
function getDlEndpoint(type, id, s, e, quality) {
return s != null && e != null
? `${BASE_URL}/download-link/${type}/${encodeURIComponent(id)}/${encodeURIComponent(s)}/${encodeURIComponent(e)}/${encodeURIComponent(quality)}`
: `${BASE_URL}/download-link/${type}/${encodeURIComponent(id)}/${encodeURIComponent(quality)}`;
}
function withCookies(headers = {}) {
return cookieJar ? { ...headers, Cookie: cookieJar } : headers;
function getStartConversionEndpoint(type, id, s, e, quality) {
return s != null && e != null
? `${BASE_URL}/start-conversion/${type}/${encodeURIComponent(id)}/${encodeURIComponent(s)}/${encodeURIComponent(e)}/${encodeURIComponent(quality)}`
: `${BASE_URL}/start-conversion/${type}/${encodeURIComponent(id)}/${encodeURIComponent(quality)}`;
}
async function getToken() {
await ensureCookies();
const res = await fetch(`${API_BASE}/get-token`, {
method: 'GET',
headers: withCookies(HEADERS),
signal: AbortSignal.timeout(10000),
});
if (!res.ok) throw new Error(`Token fetch failed: ${res.status}`);
const data = await res.json();
return data?.t;
}
function getCaptionType(url) {
return String(url).toLowerCase().includes('.vtt') ? 'vtt' : 'srt';
}
async function fetchJson(url, options = {}) {
async function fetchDownloadUrl(type, id, s, e, quality) {
try {
const res = await fetch(url, options);
if (!res.ok) return null;
return await res.json();
} catch {
return null;
}
}
async function resolveDirect(directPath, headers) {
const fullUrl = directPath.startsWith('http') ? directPath : `${BASE_URL}${directPath}`;
try {
const res = await fetch(fullUrl, {
method: 'HEAD',
redirect: 'manual',
headers: withCookies(headers),
const res = await fetch(getDlEndpoint(type, id, s, e, quality), {
headers: { ...HEADERS, Accept: 'application/json' },
signal: AbortSignal.timeout(4000),
});
const loc = res.headers.get('location');
if (loc) return loc;
if (!res.ok) return null;
const data = await res.json().catch(() => null);
if (data?.url) {
let clean = String(data.url).split('?')[0].split('#')[0];
if (!clean.endsWith('.mkv') && !clean.endsWith('.mp4')) clean += '.mkv';
return clean;
}
} catch { }
return fullUrl;
return null;
}
async function pollJob(jobId, maxWaitMs = 12000) {
const start = Date.now();
while (Date.now() - start < maxWaitMs) {
try {
const res = await fetch(`${BASE_URL}/job/${jobId}?t=${Date.now()}`, {
headers: HEADERS,
signal: AbortSignal.timeout(3000),
});
if (res.ok) {
const data = await res.json();
if (data.status === 'done') return true;
if (data.status === 'error') return false;
}
} catch { }
await new Promise((r) => setTimeout(r, 600));
}
return false;
}
export async function getStream(args) {
const { id, s, e } = args || {};
if (!id) return null;
const isTv = s != null && e != null;
const type = isTv ? 'tv' : 'movie';
const targetUrl = isTv
? `${BASE_URL}/tv/${id}/${s}/${e}`
: `${BASE_URL}/movie/${id}`;
const mediaPath = isTv
? `${BASE_URL}/media/tv/${id}/${s}/${e}`
: `${BASE_URL}/media/movie/${id}`;
try {
const res = await fetch(targetUrl, {
headers: HEADERS,
signal: AbortSignal.timeout(args?.config?.timeout || 25000),
});
await ensureCookies();
if (!res.ok) return null;
const [mediaData, tokenData] = await Promise.all([
fetchJson(mediaPath, { headers: withCookies(HEADERS) }).catch(() => null),
fetchJson(`${BASE_URL}/api/get-token`, { headers: withCookies(HEADERS) }).catch(() => null),
]);
const html = await res.text();
const allUrls = [];
const seen = new Set();
const allUrls = [];
const seen = new Set();
const itemRegex = /<div[^>]*class=["'][^"']*quality-item[^"']*["'][^>]*>([\s\S]*?)<\/div>/gi;
let itemMatch;
while ((itemMatch = itemRegex.exec(html)) !== null) {
const block = itemMatch[1];
const urlMatch = block.match(/window\.location\.href\s*=\s*['"]([^'"]+)['"]/i);
if (!urlMatch) continue;
const streamUrl = urlMatch[1].trim();
if (seen.has(streamUrl)) continue;
seen.add(streamUrl);
const qMatch = block.match(/Download\s*([0-9a-zA-Z]+)/i);
const sizeMatch = block.match(/<small>\(([^)]+)\)<\/small>/i);
const quality = qMatch ? qMatch[1].trim() : 'Auto';
const size = sizeMatch ? sizeMatch[1].trim() : '';
allUrls.push({
url: streamUrl,
server: `VidVault - ${quality.toUpperCase()}${size ? ` (${size})` : ''}`,
quality,
type: 'mkv',
headers: PLAYBACK_HEADERS,
});
}
if (allUrls.length > 0) {
allUrls.sort((a, b) => (parseInt(b.quality, 10) || 0) - (parseInt(a.quality, 10) || 0));
return {
url: allUrls[0].url,
headers: allUrls[0].headers,
allUrls,
};
}
const qualityMatches = [...html.matchAll(/id=["']item-([0-9a-zA-Z]+)["']/gi)].map(m => m[1]);
const qualitiesToProcess = qualityMatches.length > 0 ? qualityMatches : ['1080p', '720p', '360p'];
if (Array.isArray(mediaData?.direct)) {
await Promise.all(
mediaData.direct.map(async (item) => {
if (!item?.path) return;
const streamUrl = await resolveDirect(item.path, HEADERS);
qualitiesToProcess.map(async (quality) => {
let streamUrl = await fetchDownloadUrl(type, id, s, e, quality);
if (!streamUrl) {
try {
const startRes = await fetch(getStartConversionEndpoint(type, id, s, e, quality), {
method: 'POST',
headers: HEADERS,
signal: AbortSignal.timeout(6000),
});
if (startRes.ok) {
const startData = await startRes.json().catch(() => null);
if (startData?.jobId === 'existing') {
streamUrl = await fetchDownloadUrl(type, id, s, e, quality);
} else if (startData?.jobId) {
const isReady = await pollJob(startData.jobId);
if (isReady) {
streamUrl = await fetchDownloadUrl(type, id, s, e, quality);
}
}
}
} catch { }
}
if (streamUrl && !seen.has(streamUrl)) {
seen.add(streamUrl);
const format = item.format || (streamUrl.includes('.mkv') ? 'mkv' : 'mp4');
allUrls.push({
url: streamUrl,
server: `VidVault - Direct ${format.toUpperCase()} (${item.quality || item.size || 'Auto'})`,
quality: item.quality || 'Auto',
type: format,
headers: withCookies(HEADERS),
server: `VidVault - ${quality.toUpperCase()}`,
quality,
type: 'mkv',
headers: PLAYBACK_HEADERS,
});
}
})
);
if (!allUrls.length) return null;
allUrls.sort((a, b) => (parseInt(b.quality, 10) || 0) - (parseInt(a.quality, 10) || 0));
return {
url: allUrls[0].url,
headers: allUrls[0].headers,
allUrls,
};
} catch {
return null;
}
const token = tokenData?.t;
if (token) {
const payload = isTv
? { type: 'tv', tmdbId: String(id), season: String(s), episode: String(e) }
: { type: 'movie', tmdbId: String(id) };
const proxyData = await fetchJson(`${BASE_URL}/api/download-proxy`, {
method: 'POST',
headers: withCookies({
...HEADERS,
'Content-Type': 'application/json',
'x-request-token': token,
}),
body: JSON.stringify(payload),
}).catch(() => null);
const candidates = [
{ data: proxyData?.mp4Data, type: 'mp4', label: 'MP4' },
{ data: proxyData?.mkvData, type: 'mkv', label: 'MKV' },
{ data: proxyData?.mkvV2Data, type: 'mkv', label: 'MKV V2' },
];
for (const { data, type, label } of candidates) {
if (data?.url && !seen.has(data.url)) {
seen.add(data.url);
allUrls.push({
url: data.url,
server: `VidVault - ${label} (${data.quality || data.size || 'Auto'})`,
quality: data.quality || 'Auto',
type,
headers: withCookies(HEADERS),
});
}
}
}
if (!allUrls.length) return null;
return {
url: allUrls[0].url,
headers: allUrls[0].headers,
allUrls,
};
}