From 60785220b69e7af70ac870d7123c2b7cdc501e95 Mon Sep 17 00:00:00 2001 From: alwaysmesmerizingyou Date: Tue, 10 Nov 2026 13:57:22 -0800 Subject: [PATCH] fixed vidvault --- src/sources/vidvault.js | 274 +++++++++++++++++++++------------------- 1 file changed, 142 insertions(+), 132 deletions(-) diff --git a/src/sources/vidvault.js b/src/sources/vidvault.js index 11bf3ce..c3265b3 100644 --- a/src/sources/vidvault.js +++ b/src/sources/vidvault.js @@ -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 = /]*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>/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, - }; } \ No newline at end of file