mirror of
https://github.com/Inside4ndroid/TMDB-Embed-API.git
synced 2026-10-22 09:35:11 +00:00
Add new providers for streaming services and utility functions
- Implement StreamFlix provider for fetching movie and episode streams. - Add VaPlayer provider to retrieve streaming URLs for movies and TV shows. - Introduce ZXCStreams provider for accessing various streaming links. - Create utility functions for fetching episode counts from Cinemeta. - Develop a title matching system for improved search results. - Implement TMDB title to IMDB ID resolution for better integration with TMDB API.
This commit is contained in:
+154
-54
@@ -8,21 +8,91 @@ const VIDEASY_HEADERS = {
|
||||
'Referer': 'https://player.videasy.net/'
|
||||
};
|
||||
|
||||
// Each server maps a name to its API URL. moviesOnly:true skips TV requests.
|
||||
const VIDEASY_API = 'https://api.speedracelight.com';
|
||||
|
||||
// Each server maps a name to its API route. moviesOnly:true skips TV requests.
|
||||
const SERVERS = {
|
||||
'Neon': { url: 'https://api.videasy.net/myflixerzupcloud/sources-with-title' },
|
||||
'Yoru': { url: 'https://api.videasy.net/cdn/sources-with-title', moviesOnly: true },
|
||||
'Cypher': { url: 'https://api.videasy.net/moviebox/sources-with-title' },
|
||||
'Reyna': { url: 'https://api.videasy.net/primewire/sources-with-title' },
|
||||
'Omen': { url: 'https://api.videasy.net/onionplay/sources-with-title' },
|
||||
'Breach': { url: 'https://api.videasy.net/m4uhd/sources-with-title' },
|
||||
'Ghost': { url: 'https://api.videasy.net/primesrcme/sources-with-title' },
|
||||
'Sage': { url: 'https://api.videasy.net/1movies/sources-with-title' },
|
||||
'Vyse': { url: 'https://api.videasy.net/hdmovie/sources-with-title' },
|
||||
'Raze': { url: 'https://api.videasy.net/superflix/sources-with-title' }
|
||||
'CDN': { url: `${VIDEASY_API}/cdn/sources-with-title` },
|
||||
'LaMovie': { url: `${VIDEASY_API}/lamovie/sources-with-title` },
|
||||
'Meine': { url: `${VIDEASY_API}/meine/sources-with-title`, moviesOnly: true }
|
||||
};
|
||||
|
||||
const DECRYPT_URL = 'https://enc-dec.app/api/dec-videasy';
|
||||
// --- Seed-based response decryption (mirrors player.videasy.net) ---
|
||||
const MAGIC = [109, 118, 109, 49]; // "mvm1"
|
||||
const HASH_TABLE = [1116352408, 1899447441, 3049323471, 3921009573, 961987163, 1508970993, 2453635748, 2870763221, 3624381080, 310598401, 607225278, 1426881987, 1925078388, 2162078206, 2614888103, 3248222580];
|
||||
|
||||
function u32x(x) { return x >>> 0; }
|
||||
function mul32(a, b) { return Math.imul(a, b) >>> 0; }
|
||||
function rotl32(x, n) {
|
||||
x >>>= 0;
|
||||
n &= 31;
|
||||
return n === 0 ? x : ((x << n) | (x >>> (32 - n))) >>> 0;
|
||||
}
|
||||
function hash32(x) {
|
||||
x = u32x(x);
|
||||
x ^= x >>> 16;
|
||||
x = mul32(x, 2246822507);
|
||||
x ^= x >>> 13;
|
||||
x = mul32(x, 3266489909);
|
||||
x ^= x >>> 16;
|
||||
return u32x(x);
|
||||
}
|
||||
function fnv1a(str) {
|
||||
let h = 2166136261;
|
||||
for (let i = 0; i < str.length; i++) h = mul32(h ^ str.charCodeAt(i), 16777619);
|
||||
return hash32(h);
|
||||
}
|
||||
function initStream(seed, secondKey) {
|
||||
const S = new Array(61);
|
||||
let a = u32x(hash32(fnv1a(seed) ^ hash32(u32x((secondKey >>> 0) ^ 2654435769))));
|
||||
for (let i = 0; i < 8; i++) {
|
||||
if ((i * (i + 1) & 1) === 0) {
|
||||
const idx = a % 61;
|
||||
a = rotl32(a + u32x(2654435769), 7 + (7 & i));
|
||||
S[idx] = u32x(a ^ hash32(a));
|
||||
a = hash32(u32x(a + idx));
|
||||
} else {
|
||||
S[i] = HASH_TABLE[15 & i];
|
||||
}
|
||||
}
|
||||
return { S, acc: u32x(hash32(2779096485 ^ a)) };
|
||||
}
|
||||
function nextByte(st, ctr) {
|
||||
const r = st.S;
|
||||
const o = st.acc;
|
||||
const n = o % 61;
|
||||
const inSet = 0 - Number(n in r);
|
||||
const d = r[n] >>> 0;
|
||||
const x = u32x(d ^ mul32(2654435769, ctr + 1));
|
||||
const y = u32x((o ^ x) | (o & x & inSet));
|
||||
const no = hash32(u32x(rotl32(u32x(y + o), 31 & n) ^ rotl32(o, 31 & Math.imul(n, 7))) + 2654435769);
|
||||
r[n] = no >>> 0;
|
||||
st.acc = no;
|
||||
return no >>> 0;
|
||||
}
|
||||
function keystream(seed, secondKey, len) {
|
||||
const st = initStream(seed, secondKey);
|
||||
const out = new Uint8Array(len);
|
||||
let ctr = 0;
|
||||
for (let i = 0; i < len;) {
|
||||
const b = nextByte(st, ctr++);
|
||||
out[i++] = 255 & b;
|
||||
if (i < len) out[i++] = (b >>> 8) & 255;
|
||||
if (i < len) out[i++] = (b >>> 16) & 255;
|
||||
if (i < len) out[i++] = (b >>> 24) & 255;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
function decryptPayload(payload, seed, secondKey) {
|
||||
const b64 = payload.replace(/-/g, '+').replace(/_/g, '/').padEnd(4 * Math.ceil(payload.length / 4), '=');
|
||||
const data = Buffer.from(b64, 'base64');
|
||||
const ks = keystream(seed, secondKey, data.length);
|
||||
for (let i = 0; i < data.length; i++) data[i] ^= ks[i];
|
||||
for (let i = 0; i < MAGIC.length; i++) {
|
||||
if (data[i] !== MAGIC[i]) throw new Error('Invalid encrypted payload');
|
||||
}
|
||||
return data.subarray(MAGIC.length).toString('utf8');
|
||||
}
|
||||
|
||||
async function getVideasyStreams(tmdbId, mediaType = 'movie', seasonNum = null, episodeNum = null) {
|
||||
console.log(`[Videasy] Fetching streams for TMDB ID: ${tmdbId}, Type: ${mediaType}`);
|
||||
@@ -60,57 +130,87 @@ async function getVideasyStreams(tmdbId, mediaType = 'movie', seasonNum = null,
|
||||
const allStreams = [];
|
||||
const seen = new Set();
|
||||
|
||||
// Step 2: Query each server in parallel
|
||||
await Promise.all(Object.entries(SERVERS).map(async ([name, server]) => {
|
||||
if (server.moviesOnly && mediaType === 'tv') return;
|
||||
// Step 2: Fetch a decryption seed (cached server-side per mediaId, short TTL)
|
||||
let seed;
|
||||
try {
|
||||
const seedRes = await axios.get(`${VIDEASY_API}/seed?mediaId=${tmdbId}`, {
|
||||
headers: VIDEASY_HEADERS,
|
||||
timeout: 8000
|
||||
});
|
||||
seed = seedRes.data && seedRes.data.seed;
|
||||
} catch (err) {
|
||||
console.error(`[Videasy] Seed fetch failed: ${err.message}`);
|
||||
return [];
|
||||
}
|
||||
if (!seed) {
|
||||
console.error('[Videasy] No seed returned.');
|
||||
return [];
|
||||
}
|
||||
|
||||
let apiUrl = `${server.url}?title=${encodeURIComponent(details.title)}`
|
||||
+ `&mediaType=${details.type}&year=${details.year}`
|
||||
+ `&tmdbId=${tmdbId}&imdbId=${details.imdbId || ''}`;
|
||||
if (mediaType === 'tv') apiUrl += `&seasonId=${seasonNum}&episodeId=${episodeNum}`;
|
||||
const queryServers = async (activeSeed) => {
|
||||
await Promise.all(Object.entries(SERVERS).map(async ([name, server]) => {
|
||||
if (server.moviesOnly && mediaType === 'tv') return;
|
||||
|
||||
try {
|
||||
const encRes = await axios.get(apiUrl, {
|
||||
headers: VIDEASY_HEADERS,
|
||||
timeout: 8000,
|
||||
responseType: 'text'
|
||||
});
|
||||
let apiUrl = `${server.url}?title=${encodeURIComponent(details.title)}`
|
||||
+ `&mediaType=${details.type === 'tv' ? 'TV Series' : 'Movie'}&year=${details.year}`
|
||||
+ `&tmdbId=${tmdbId}&imdbId=${details.imdbId || ''}`;
|
||||
if (mediaType === 'tv') apiUrl += `&seasonId=${seasonNum}&episodeId=${episodeNum}`;
|
||||
apiUrl += `&enc=2&seed=${activeSeed}`;
|
||||
|
||||
const encryptedText = typeof encRes.data === 'string' ? encRes.data : JSON.stringify(encRes.data);
|
||||
if (!encryptedText || encryptedText.length < 20 || encryptedText.startsWith('<')) return;
|
||||
|
||||
// Step 3: Decrypt via enc-dec.app
|
||||
const decRes = await axios.post(DECRYPT_URL,
|
||||
{ text: encryptedText, id: String(tmdbId) },
|
||||
{ headers: { 'Content-Type': 'application/json' }, timeout: 8000 }
|
||||
);
|
||||
|
||||
const resData = (decRes.data && decRes.data.result) || decRes.data;
|
||||
if (!resData || !Array.isArray(resData.sources)) return;
|
||||
|
||||
for (const s of resData.sources) {
|
||||
if (!s.url || seen.has(s.url)) continue;
|
||||
seen.add(s.url);
|
||||
allStreams.push({
|
||||
name: `Videasy ${name}`,
|
||||
title: `Videasy ${name} - ${s.quality || 'Auto'}`,
|
||||
url: s.url,
|
||||
quality: s.quality || 'Auto',
|
||||
provider: 'Videasy',
|
||||
headers: {
|
||||
'Referer': 'https://player.videasy.net/',
|
||||
'Origin': 'https://player.videasy.net'
|
||||
}
|
||||
try {
|
||||
const encRes = await axios.get(apiUrl, {
|
||||
headers: VIDEASY_HEADERS,
|
||||
timeout: 8000,
|
||||
responseType: 'text'
|
||||
});
|
||||
|
||||
const encryptedText = typeof encRes.data === 'string' ? encRes.data : JSON.stringify(encRes.data);
|
||||
if (!encryptedText || encryptedText.length < 20 || encryptedText.startsWith('<')) return;
|
||||
|
||||
// Step 3: Decrypt payload locally with the seed cipher
|
||||
const plain = decryptPayload(encryptedText, activeSeed, String(tmdbId));
|
||||
const resData = JSON.parse(plain);
|
||||
if (!resData || !Array.isArray(resData.sources)) return;
|
||||
|
||||
for (const s of resData.sources) {
|
||||
if (!s.url || seen.has(s.url)) continue;
|
||||
seen.add(s.url);
|
||||
allStreams.push({
|
||||
name: `Videasy ${name}`,
|
||||
title: `Videasy ${name} - ${s.quality || 'Auto'}`,
|
||||
url: s.url,
|
||||
quality: s.quality || 'Auto',
|
||||
provider: 'Videasy',
|
||||
headers: {
|
||||
'Referer': 'https://player.videasy.net/',
|
||||
'Origin': 'https://player.videasy.net'
|
||||
}
|
||||
});
|
||||
}
|
||||
console.log(`[Videasy] Server ${name}: ${resData.sources.length} source(s)`);
|
||||
} catch {
|
||||
// server unreachable or returned no data — skip silently
|
||||
}
|
||||
console.log(`[Videasy] Server ${name}: ${resData.sources.length} source(s)`);
|
||||
}));
|
||||
};
|
||||
|
||||
// Step 4: Query each server in parallel; retry once with a fresh seed if nothing matched
|
||||
await queryServers(seed);
|
||||
if (allStreams.length === 0) {
|
||||
try {
|
||||
const seedRes = await axios.get(`${VIDEASY_API}/seed?mediaId=${tmdbId}`, {
|
||||
headers: VIDEASY_HEADERS,
|
||||
timeout: 8000
|
||||
});
|
||||
const freshSeed = seedRes.data && seedRes.data.seed;
|
||||
if (freshSeed && freshSeed !== seed) await queryServers(freshSeed);
|
||||
} catch {
|
||||
// server unreachable or returned no data — skip silently
|
||||
// ignore retry failure
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
console.log(`[Videasy] Total streams: ${allStreams.length}`);
|
||||
return allStreams;
|
||||
}
|
||||
|
||||
module.exports = { getVideasyStreams };
|
||||
module.exports = { getVideasyStreams };
|
||||
Reference in New Issue
Block a user