mirror of
https://gitlab.com/vyla-entertainment/sdk.git
synced 2026-11-13 13:47:25 -05:00
fixed vidvault
This commit is contained in:
@@ -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,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user