Files
TMDB-Embed-API/providers/VidZee.js
T

202 lines
7.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const crypto = require('crypto');
const axios = require('axios');
const BASE_URL = 'https://core.vidzee.wtf';
const PLAYER_URL = 'https://player.vidzee.wtf';
const VIDZEE_HEADERS = {
'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.7051.98 Safari/537.36',
'Accept': 'application/json, text/javascript, */*; q=0.01',
'Accept-Language': 'en-US,en;q=0.9',
'Referer': PLAYER_URL,
'Origin': PLAYER_URL
};
// Pad/truncate key string to 32 bytes (UTF-8, mirrors CryptoJS behaviour)
function getKeyBytes(key) {
const encoded = Buffer.from(key, 'utf8');
const result = Buffer.alloc(32);
encoded.copy(result, 0, 0, Math.min(encoded.length, 32));
return result;
}
// AES-256-CBC decrypt a VidZee stream link.
// encryptedData is outer-base64 of "ivBase64:cipherBase64"
async function decryptLink(encryptedData, decryptionKey) {
try {
if (!encryptedData || !decryptionKey) return '';
const decoded = Buffer.from(encryptedData, 'base64').toString('utf8');
const colonIdx = decoded.indexOf(':');
if (colonIdx === -1) return '';
const ivBase64 = decoded.slice(0, colonIdx);
const cipherBase64 = decoded.slice(colonIdx + 1);
if (!ivBase64 || !cipherBase64) return '';
const iv = Buffer.from(ivBase64, 'base64');
const cipherBytes = Buffer.from(cipherBase64, 'base64');
const keyBytes = getKeyBytes(decryptionKey);
const decipher = crypto.createDecipheriv('aes-256-cbc', keyBytes, iv);
const decrypted = Buffer.concat([decipher.update(cipherBytes), decipher.final()]);
return decrypted.toString('utf8');
} catch {
return '';
}
}
// Derive the real AES-CBC decryption key from VidZee's API-key blob via AES-GCM.
// Mirrors the TypeScript deriveKey() in the reference implementation.
async function deriveKey(e) {
try {
if (!e) return '';
const t = Buffer.from(e.replace(/\s+/g, ''), 'base64');
if (t.length <= 28) return '';
const n = t.slice(0, 12); // AES-GCM IV
const r = t.slice(12, 28);
const a = t.slice(28);
// i = a || r (reference: i.set(a, 0); i.set(r, a.length))
const i = Buffer.concat([a, r]);
// AES-256-GCM key = SHA-256 of fixed salt string
const l = crypto.createHash('sha256')
.update('4f2a9c7d1e8b3a6f0d5c2e9a7b1f4d8c', 'utf8')
.digest();
// WebCrypto passes ciphertext+tag as one buffer; tag is last 16 bytes
const ciphertext = i.slice(0, i.length - 16);
const authTag = i.slice(i.length - 16);
const decipher = crypto.createDecipheriv('aes-256-gcm', l, n);
decipher.setAuthTag(authTag);
const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
return decrypted.toString('utf8');
} catch {
return '';
}
}
// Fetch and derive the current decryption key from VidZee's API
async function fetchDecryptionKey() {
try {
const response = await axios.get(`${BASE_URL}/api-key`, {
headers: VIDZEE_HEADERS,
responseType: 'text',
timeout: 10000
});
if (response.status === 200 && response.data) {
return await deriveKey(response.data);
}
return null;
} catch {
return null;
}
}
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 [];
}
}
// Fetch and derive the decryption key before hitting servers
const decKey = await fetchDecryptionKey();
if (!decKey) {
console.error('[VidZee] Failed to fetch/derive decryption key.');
return [];
}
console.log('[VidZee] Decryption key derived successfully.');
// Servers 0–13 (matches reference implementation)
const servers = Array.from({ length: 14 }, (_, i) => i);
const streamPromises = servers.map(async (sr) => {
let apiUrl = `${PLAYER_URL}/api/server?id=${tmdbId}&sr=${sr}`;
if (mediaType === 'tv') {
apiUrl += `&ss=${seasonNum}&ep=${episodeNum}`;
}
try {
const response = await axios.get(apiUrl, {
headers: VIDZEE_HEADERS,
timeout: 7000
});
const responseData = response.data;
if (!responseData || typeof responseData !== 'object') return [];
let apiSources = [];
if (responseData.url && Array.isArray(responseData.url)) {
apiSources = responseData.url;
} else if (responseData.link && typeof responseData.link === 'string') {
apiSources = [responseData];
}
if (apiSources.length === 0) return [];
const streams = await Promise.all(apiSources.map(async (sourceItem) => {
const label = sourceItem.name || sourceItem.type || 'VidZee';
let quality = String(label).match(/^\d+$/) ? `${label}p` : label;
if (!/\d{3,4}p/.test(quality)) quality = '720p';
const language = sourceItem.language || sourceItem.lang || 'Unknown';
let rawLink = sourceItem.link;
if (rawLink && !/^https?:\/\//i.test(rawLink)) {
const decoded = await decryptLink(rawLink, decKey);
if (decoded && /^https?:\/\//i.test(decoded)) {
rawLink = decoded;
} else {
console.log(`[VidZee S${sr}] Decryption yielded non-URL, skipping.`);
return null;
}
}
if (!rawLink || !/^https?:\/\//i.test(rawLink)) return null;
return {
name: `VidZee Server${sr} - ${quality} - ${language}`,
title: `VidZee Server${sr} - ${quality} - ${language}`,
url: rawLink,
quality,
provider: 'VidZee',
headers: { 'Referer': `${BASE_URL}/` }
};
}));
const valid = streams.filter(Boolean);
if (valid.length > 0) {
console.log(`[VidZee S${sr}] ${valid.length} stream(s) extracted.`);
}
return valid;
} catch (error) {
console.error(`[VidZee S${sr}] Error: ${error.message}`);
return [];
}
});
const allStreams = (await Promise.all(streamPromises)).flat();
console.log(`[VidZee] Total streams: ${allStreams.length}`);
return allStreams;
};
module.exports = { getVidZeeStreams };