mirror of
https://github.com/Inside4ndroid/TMDB-Embed-API.git
synced 2026-10-22 09:35:11 +00:00
161 lines
6.3 KiB
JavaScript
161 lines
6.3 KiB
JavaScript
const axios = require('axios');
|
|
|
|
// Confirmed AES-CBC decoder for VidZee encrypted link format.
|
|
// Format (after initial atob in site script): ivBase64:cipherBase64
|
|
// ivBase64 -> CryptoJS Base64 parsed to WordArray (IV)
|
|
// Key: UTF-8 bytes of "qrincywincyspider" padded with nulls to 32 bytes (AES-256-CBC)
|
|
// Cipher: base64 ciphertext, PKCS7 padding, mode CBC.
|
|
let cryptoJs; // lazy load to avoid cost if not needed
|
|
function decodeVidZeeToken(token, debug) {
|
|
try {
|
|
if (typeof token !== 'string') return null;
|
|
if (/^https?:\/\//i.test(token)) return null; // already a URL
|
|
// Expect pattern base64:base64 (iv:cipher)
|
|
const raw = Buffer.from(token, 'base64').toString('utf8');
|
|
if (!raw.includes(':')) return null;
|
|
const [ivB64, cipherB64] = raw.split(':');
|
|
if (!ivB64 || !cipherB64) return null;
|
|
if (!cryptoJs) cryptoJs = require('crypto-js');
|
|
const iv = cryptoJs.enc.Base64.parse(ivB64.trim());
|
|
const keyStr = 'qrincywincyspider';
|
|
// Pad key with nulls to 32 bytes
|
|
const keyUtf8 = cryptoJs.enc.Utf8.parse(keyStr.padEnd(32, '\0'));
|
|
const decrypted = cryptoJs.AES.decrypt(cipherB64.trim(), keyUtf8, {
|
|
iv,
|
|
mode: cryptoJs.mode.CBC,
|
|
padding: cryptoJs.pad.Pkcs7
|
|
}).toString(cryptoJs.enc.Utf8);
|
|
if (!decrypted) return null;
|
|
if (!/^https?:\/\//i.test(decrypted)) {
|
|
if (debug) console.log('[VidZee] decrypted but not a URL', decrypted.slice(0,80));
|
|
return null;
|
|
}
|
|
if (debug) console.log('[VidZee] AES decoded token', { tokenSnippet: token.slice(0, 24)+'...', url: decrypted.slice(0,120) });
|
|
return decrypted.trim();
|
|
} catch (e) {
|
|
if (debug) console.log('[VidZee] AES decode error', e.message);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// Removed unused parseArgs helper
|
|
|
|
const getVidZeeStreams = async (tmdbId, mediaType, seasonNum, episodeNum) => {
|
|
if (!tmdbId) {
|
|
console.error('[VidZee] Error: TMDB ID (tmdbId) is required.');
|
|
return [];
|
|
}
|
|
|
|
if (!mediaType || (mediaType !== 'movie' && mediaType !== 'tv')) {
|
|
console.error('[VidZee] Error: mediaType is required and must be either "movie" or "tv".');
|
|
return [];
|
|
}
|
|
|
|
if (mediaType === 'tv') {
|
|
if (!seasonNum) {
|
|
console.error('[VidZee] Error: Season (seasonNum) is required for TV shows.');
|
|
return [];
|
|
}
|
|
if (!episodeNum) {
|
|
console.error('[VidZee] Error: Episode (episodeNum) is required for TV shows.');
|
|
return [];
|
|
}
|
|
}
|
|
|
|
const servers = [1,2,3,4,5,6,7,8,9,10]; // VidZee server identifiers
|
|
|
|
const streamPromises = servers.map(async (sr) => {
|
|
let targetApiUrl = `https://player.vidzee.wtf/api/server?id=${tmdbId}&sr=${sr}`;
|
|
|
|
if (mediaType === 'tv') {
|
|
targetApiUrl += `&ss=${seasonNum}&ep=${episodeNum}`;
|
|
}
|
|
|
|
let finalApiUrl;
|
|
const headers = {
|
|
'Referer': `https://player.vidzee.wtf/embed/movie/${tmdbId}`
|
|
};
|
|
const timeout = 7000; // Reduced timeout
|
|
|
|
finalApiUrl = targetApiUrl;
|
|
|
|
console.log(`[VidZee] Fetching from server ${sr}: ${targetApiUrl}`);
|
|
|
|
try {
|
|
const response = await axios.get(finalApiUrl, {
|
|
headers: headers,
|
|
timeout: timeout
|
|
});
|
|
|
|
const responseData = response.data;
|
|
|
|
if (!responseData || typeof responseData !== 'object') {
|
|
console.error(`[VidZee S${sr}] Error: Invalid response data from API.`);
|
|
return [];
|
|
}
|
|
|
|
if (responseData.tracks) {
|
|
delete responseData.tracks;
|
|
}
|
|
|
|
let apiSources = [];
|
|
if (responseData.url && Array.isArray(responseData.url)) {
|
|
apiSources = responseData.url;
|
|
} else if (responseData.link && typeof responseData.link === 'string') {
|
|
apiSources = [responseData];
|
|
}
|
|
|
|
if (!apiSources || apiSources.length === 0) {
|
|
console.log(`[VidZee S${sr}] No stream sources found in API response.`);
|
|
return [];
|
|
}
|
|
|
|
const streams = apiSources.map(sourceItem => {
|
|
const label = sourceItem.name || sourceItem.type || 'VidZee';
|
|
let quality = String(label).match(/^\d+$/) ? `${label}p` : label;
|
|
if (!/(\d{3,4})p/.test(quality.toLowerCase())) {
|
|
quality = '720p';
|
|
}
|
|
const language = sourceItem.language || sourceItem.lang;
|
|
let rawLink = sourceItem.link;
|
|
const debug = process.env.VIDZEE_DEBUG === '1';
|
|
const decoded = decodeVidZeeToken(rawLink, debug);
|
|
if (decoded && /^https?:\/\//i.test(decoded)) {
|
|
if (debug) console.log('[VidZee] decoded link', { beforeSample: rawLink.slice(0,40)+'...', after: decoded.slice(0,80) });
|
|
rawLink = decoded;
|
|
}
|
|
return {
|
|
name: `VidZee Server${sr} - ${quality} - ${language}`,
|
|
title: `VidZee Server${sr} - ${quality} - ${language}`,
|
|
url: rawLink,
|
|
quality: quality,
|
|
provider: "VidZee",
|
|
headers: {
|
|
'Referer': 'https://core.vidzee.wtf/'
|
|
}
|
|
};
|
|
}).filter(stream => stream.url);
|
|
|
|
console.log(`[VidZee S${sr}] Successfully extracted ${streams.length} streams. Qualities: ${streams.map(s=>s.quality).join(', ')}`);
|
|
return streams;
|
|
|
|
} catch (error) {
|
|
if (error.response) {
|
|
console.error(`[VidZee S${sr}] Error fetching: ${error.response.status} ${error.response.statusText}`);
|
|
} else if (error.request) {
|
|
console.error(`[VidZee S${sr}] Error fetching: No response received.`);
|
|
} else {
|
|
console.error(`[VidZee S${sr}] Error fetching:`, error.message);
|
|
}
|
|
return [];
|
|
}
|
|
});
|
|
|
|
const allStreamsNested = await Promise.all(streamPromises);
|
|
const allStreams = allStreamsNested.flat();
|
|
|
|
console.log(`[VidZee] Found a total of ${allStreams.length} streams from servers ${servers.join(', ')}.`);
|
|
return allStreams;
|
|
};
|
|
|
|
module.exports = { getVidZeeStreams };
|