diff --git a/src/config.js b/src/config.js index 0821f5d..02d544b 100644 --- a/src/config.js +++ b/src/config.js @@ -230,6 +230,32 @@ export const SOURCES = [ }, }, + { + key: 'luna-sub', + label: 'Luna (Sub)', + sourceFile: 'luna', + proxyParam: 'lusub', + subtype: 'sub', + timeout: 25000, + jitter: 500, + retries: 2, + multiUrl: true, + skipProxy: true, + }, + + { + key: 'luna-dub', + label: 'Luna (Dub)', + sourceFile: 'luna', + proxyParam: 'ludub', + subtype: 'dub', + timeout: 25000, + jitter: 500, + retries: 2, + multiUrl: true, + skipProxy: true, + }, + { key: 'mapple', label: 'Mapple', @@ -288,6 +314,17 @@ export const SOURCES = [ skipProxy: true }, + { + key: 'rivestream', + label: 'RiveStream', + sourceFile: 'rivestream', + proxyParam: 'rs', + timeout: 30000, + jitter: 500, + retries: 2, + multiUrl: true, + }, + { key: 'vidapi', label: 'VidAPI', @@ -478,7 +515,6 @@ export const SOURCES = [ Referer: 'https://player.vidzee.wtf', Origin: 'https://player.vidzee.wtf', }, - disabled: true // Disabled temporarily due to decryption issues }, { diff --git a/src/sources/luna.js b/src/sources/luna.js new file mode 100644 index 0000000..64238ec --- /dev/null +++ b/src/sources/luna.js @@ -0,0 +1,125 @@ +import { USER_AGENT, fetchText, getTmdbInfo, tmdbToAnilist } from '../utils/helpers.js'; + +const ACTION_FETCH_SOURCES = 'afb0491c5516f9fff5fcb464d627638df76062f8'; +const WATCH_URL = 'https://luna-stream.me/anime/watch/21/gogoanime/1'; + +const PROVIDERS = [ + { id: 'megaplay', name: 'Helios' }, + { id: 'anibd', name: 'Nova' }, + { id: 'pahe', name: 'Polaris' }, + { id: 'gogoanime', name: 'Quasar' }, + { id: 'zoro', name: 'Zenith' }, + { id: 'animepahe', name: 'Vega' }, + { id: 'animekai', name: 'Cosmos' }, + { id: 'crysoline', name: 'CrysOline' }, + { id: 'orion', name: 'Orion' }, + { id: 'pulsar', name: 'Pulsar' } +]; + +function parseRscResponse(text) { + for (const line of text.split('\n')) { + if (line.startsWith('1:')) { + try { + return JSON.parse(line.slice(2)); + } catch { } + } + } + return null; +} + +function cleanUrl(rawUrl) { + if (!rawUrl) return ''; + return rawUrl.replace(/^https:\/\/api\.luna-stream\.mehttps:\/\/api\.luna-stream\.me/, 'https://api.luna-stream.me'); +} + +async function fetchProviderStreams(anilistId, epNum, provider, subtype) { + try { + const text = await fetchText(WATCH_URL, { + method: 'POST', + headers: { + 'User-Agent': USER_AGENT, + 'Next-Action': ACTION_FETCH_SOURCES, + 'Content-Type': 'text/plain;charset=UTF-8', + 'Accept': 'text/x-component', + 'Referer': WATCH_URL, + 'Origin': 'https://luna-stream.me' + }, + body: JSON.stringify([anilistId, provider.id, String(epNum), epNum, subtype, null]), + signal: AbortSignal.timeout(10000) + }); + + if (!text) return []; + + const parsed = parseRscResponse(text); + if (!parsed || !Array.isArray(parsed.sources)) return []; + + const streams = []; + for (const s of parsed.sources) { + if (!s || !s.url) continue; + + const url = cleanUrl(s.url); + const format = String(s.type || '').toLowerCase(); + const isM3U8 = format === 'hls' || format === 'm3u8' || url.includes('.m3u8') || url.includes('.txt'); + + streams.push({ + url, + server: `Luna (${provider.name})`, + quality: String(s.quality || 'Auto'), + type: isM3U8 ? 'hls' : 'mp4', + headers: { + 'User-Agent': USER_AGENT, + 'Referer': 'https://luna-stream.me/' + } + }); + } + + return streams; + } catch { + return []; + } +} + +export async function getStream(args) { + const { id, s, e, ep, audio, server, tmdbApiKey } = args; + const isTv = s != null && e != null; + const mediaType = isTv ? 'tv' : 'movie'; + + let anilistId = null; + try { + const info = await getTmdbInfo(tmdbApiKey, id, mediaType, s); + anilistId = await tmdbToAnilist(id, mediaType, s, info?.titles || [], info?.year); + } catch { } + + if (!anilistId) { + anilistId = Number(id); + } + + if (!anilistId || isNaN(anilistId)) return null; + + const epNum = Number(e || ep || 1); + const resolvedSubtype = audio === 'dub' ? 'dub' : 'sub'; + + let targets = PROVIDERS; + if (server && server !== 'all') { + const clean = server.toLowerCase().replace('luna (', '').replace(')', '').replace('luna', '').trim(); + targets = PROVIDERS.filter(p => p.id.toLowerCase() === clean || p.name.toLowerCase().includes(clean)); + if (!targets.length) targets = PROVIDERS; + } + + const settled = await Promise.allSettled( + targets.map(p => fetchProviderStreams(anilistId, epNum, p, resolvedSubtype)) + ); + + const allUrls = []; + for (const r of settled) { + if (r.status === 'fulfilled' && Array.isArray(r.value)) { + allUrls.push(...r.value); + } + } + + return allUrls.length ? { allUrls } : null; +} + +export async function getSources() { + return PROVIDERS.map(p => `Luna (${p.name})`); +} \ No newline at end of file diff --git a/src/sources/rivestream.js b/src/sources/rivestream.js new file mode 100644 index 0000000..d4ebf25 --- /dev/null +++ b/src/sources/rivestream.js @@ -0,0 +1,179 @@ +// im being salty + +import { USER_AGENT, fetchJson } from '../utils/helpers.js'; + +const C_ARRAY = [ + '4Z7lUo', 'gwIVSMD', 'PLmz2elE2v', 'Z4OFV0', 'SZ6RZq6Zc', 'zhJEFYxrz8', 'FOm7b0', 'axHS3q4KDq', 'o9zuXQ', '4Aebt', + 'wgjjWwKKx', 'rY4VIxqSN', 'kfjbnSo', '2DyrFA1M', 'YUixDM9B', 'JQvgEj0', 'mcuFx6JIek', 'eoTKe26gL', 'qaI9EVO1rB', '0xl33btZL', + '1fszuAU', 'a7jnHzst6P', 'wQuJkX', 'cBNhTJlEOf', 'KNcFWhDvgT', 'XipDGjST', 'PCZJlbHoyt', '2AYnMZkqd', 'HIpJh', 'KH0C3iztrG', + 'W81hjts92', 'rJhAT', 'NON7LKoMQ', 'NMdY3nsKzI', 't4En5v', 'Qq5cOQ9H', 'Y9nwrp', 'VX5FYVfsf', 'cE5SJG', 'x1vj1', + 'HegbLe', 'zJ3nmt4OA', 'gt7rxW57dq', 'clIE9b', 'jyJ9g', 'B5jXjMCSx', 'cOzZBZTV', 'FTXGy', 'Dfh1q1', 'ny9jqZ2POI', + 'X2NnMn', 'MBtoyD', 'qz4Ilys7wB', '68lbOMye', '3YUJnmxp', '1fv5Imona', 'PlfvvXD7mA', 'ZarKfHCaPR', 'owORnX', 'dQP1YU', + 'dVdkx', 'qgiK0E', 'cx9wQ', '5F9bGa', '7UjkKrp', 'Yvhrj', 'wYXez5Dg3', 'pG4GMU', 'MwMAu', 'rFRD5wlM' +]; + +const DEFAULT_SERVICES = [ + 'apex', 'pulse', 'solstice', 'quasar', 'horizon', 'primevids', + 'flowcast', 'asiacloud', 'citadel', 'hindicast', 'guru' +]; + +function generateRiveSecretKey(e) { + if (e === undefined || e === null) return 'rive'; + try { + let t, n; + const r = String(e); + if (isNaN(Number(e))) { + const sum = r.split('').reduce((acc, ch) => acc + ch.charCodeAt(0), 0); + t = C_ARRAY[sum % C_ARRAY.length] || Buffer.from(r).toString('base64'); + n = Math.floor((sum % r.length) / 2); + } else { + const i = Number(e); + t = C_ARRAY[i % C_ARRAY.length] || Buffer.from(r).toString('base64'); + n = Math.floor((i % r.length) / 2); + } + const i = r.slice(0, n) + t + r.slice(n); + const o = (function (str) { + const t = String(str); + let n = 3735928559 ^ t.length; + for (let e = 0; e < t.length; e++) { + const r = t.charCodeAt(e); + const rXor = (131 * e + 89 ^ (r << (e % 5))) & 255; + const r2 = r ^ rXor; + n = (((n << 7) | (n >>> 25)) >>> 0) ^ r2; + const idx = (65535 & n) * 60205; + const o2 = ((n >>> 16) * 60205) << 16; + n = (idx + o2) >>> 0; + n ^= n >>> 11; + } + n ^= n >>> 15; + n = ((65535 & n) * 49842 + (((n >>> 16) * 49842) << 16)) >>> 0; + n ^= n >>> 13; + n = ((65535 & n) * 40503 + (((n >>> 16) * 40503) << 16)) >>> 0; + n ^= n >>> 16; + n = ((65535 & n) * 10196 + (((n >>> 16) * 10196) << 16)) >>> 0; + return ((n ^= n >>> 15)).toString(16).padStart(8, '0'); + })( + (function (str) { + const s = String(str); + let t = 0; + for (let n = 0; n < s.length; n++) { + const r = s.charCodeAt(n); + const i = (((t = (r + (t << 6) + (t << 16) - t) >>> 0) << (n % 5)) | (t >>> (32 - (n % 5)))) >>> 0; + t ^= (i ^ ((r << (n % 7)) | (r >>> (8 - (n % 7))))) >>> 0; + t = (t + ((t >>> 11) ^ (t << 3))) >>> 0; + } + t ^= t >>> 15; + t = ((65535 & t) * 49842 + ((((t >>> 16) * 49842) & 65535) << 16)) >>> 0; + t ^= t >>> 13; + t = ((65535 & t) * 40503 + ((((t >>> 16) * 40503) & 65535) << 16)) >>> 0; + return ((t ^= t >>> 16)).toString(16).padStart(8, '0'); + })(i) + ); + return Buffer.from(o, 'utf-8').toString('base64'); + } catch { + return 'topSecret'; + } +} + +async function fetchRiveServices() { + try { + const secretKey = generateRiveSecretKey(); + const url = `https://www.rivestream.app/api/backendfetch?requestID=VideoProviderServices&secretKey=${encodeURIComponent(secretKey)}`; + const res = await fetchJson(url, { + headers: { + 'User-Agent': USER_AGENT, + 'Referer': 'https://www.rivestream.app/', + 'Accept': 'application/json, text/plain, */*' + }, + signal: AbortSignal.timeout(6000) + }); + return Array.isArray(res?.data) && res.data.length ? res.data : DEFAULT_SERVICES; + } catch { + return DEFAULT_SERVICES; + } +} + +async function fetchServiceStreams(service, isMovie, id, s, e, secretKey, referer) { + try { + const cb = (service === 'primevids' || service === 'citadel') + ? `&cb=${Math.floor(Date.now() / 3e6)}` + : ''; + + const reqID = isMovie ? 'movieVideoProvider' : 'tvVideoProvider'; + const params = isMovie + ? `requestID=${reqID}&id=${encodeURIComponent(id)}&service=${service}${cb}&secretKey=${encodeURIComponent(secretKey)}` + : `requestID=${reqID}&id=${encodeURIComponent(id)}&season=${encodeURIComponent(s)}&episode=${encodeURIComponent(e)}&service=${service}${cb}&secretKey=${encodeURIComponent(secretKey)}`; + + const url = `https://www.rivestream.app/api/backendfetch?${params}`; + const res = await fetchJson(url, { + headers: { + 'User-Agent': USER_AGENT, + 'Referer': referer, + 'Accept': 'application/json, text/plain, */*' + }, + signal: AbortSignal.timeout(10000) + }); + + if (!res?.data?.sources || !Array.isArray(res.data.sources)) return []; + + const streams = []; + for (const item of res.data.sources) { + if (!item || !item.url) continue; + + const format = String(item.format || '').toLowerCase(); + const urlStr = String(item.url || '').toLowerCase(); + const isM3U8 = format === 'hls' || format === 'm3u8' || urlStr.includes('.m3u8') || urlStr.includes('.txt'); + + streams.push({ + url: item.url, + server: `Rive (${item.source || service})`, + quality: String(item.quality || 'Auto'), + type: isM3U8 ? 'hls' : 'mp4', + headers: { + 'User-Agent': USER_AGENT, + 'Referer': referer + } + }); + } + return streams; + } catch { + return []; + } +} + +export async function getStream(args) { + const { id, s, e, server } = args; + const isTv = s != null && e != null; + const isMovie = !isTv; + + const secretKey = generateRiveSecretKey(id); + const referer = isMovie + ? `https://www.rivestream.app/watch?type=movie&id=${id}` + : `https://www.rivestream.app/watch?type=tv&id=${id}&season=${s}&episode=${e}`; + + let services = await fetchRiveServices(); + + if (server && server !== 'all') { + const clean = server.toLowerCase().replace('rive (', '').replace(')', '').replace('rive', '').trim(); + services = services.filter(srv => srv.toLowerCase().includes(clean)); + if (!services.length) services = await fetchRiveServices(); + } + + const settled = await Promise.allSettled( + services.map(srv => fetchServiceStreams(srv, isMovie, id, s, e, secretKey, referer)) + ); + + const allUrls = []; + for (const r of settled) { + if (r.status === 'fulfilled' && Array.isArray(r.value)) { + allUrls.push(...r.value); + } + } + + return allUrls.length ? { allUrls } : null; +} + +export async function getSources() { + const services = await fetchRiveServices(); + return services.map(s => `Rive (${s})`); +} \ No newline at end of file diff --git a/src/sources/vidzee.js b/src/sources/vidzee.js index 8116940..d133e3a 100644 --- a/src/sources/vidzee.js +++ b/src/sources/vidzee.js @@ -1,106 +1,184 @@ -// i can't fix this, i'll wait for someone else to fix this lol +import { USER_AGENT, fetchJson } from '../utils/helpers.js'; -import crypto from 'node:crypto'; -import { fetchJson, USER_AGENT } from '../utils/helpers.js'; - -const CORE_URL = 'https://core.vidzee.wtf'; +const BASE_URL = 'https://core.vidzee.wtf'; const PLAYER_URL = 'https://player.vidzee.wtf'; -const KEY = 'c4a8f1d7e2b9a6c3d0f5e8a1b7c4d9e2'; +const SERVICES = ['dcloud', 'ipcloud', 'tik', 'v6:Hindi', '3AHindi']; +const WASM_B64 = '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'; -function decryptAes(encryptedBase64) { - const keyBuf = Buffer.from(KEY, 'utf8'); - const ciphertext = Buffer.from(encryptedBase64, 'base64'); +let wasmInstanceCache = null; +async function getWasmInstance() { + if (wasmInstanceCache) return wasmInstanceCache; try { - const decipher = crypto.createDecipheriv('aes-256-ecb', keyBuf, null); - decipher.setAutoPadding(true); - let dec = decipher.update(ciphertext); - dec = Buffer.concat([dec, decipher.final()]); - const text = dec.toString('utf8'); - if (text.startsWith('http') || text.includes('{')) return text; - } catch { } + const wasmBuffer = Buffer.from(WASM_B64, 'base64'); + const wasmModule = await WebAssembly.compile(wasmBuffer); + let memory; + let exports; + + const env = { + abort() { + throw new Error('WASM abort'); + } + }; + + const res = await WebAssembly.instantiate(wasmModule, { env }); + exports = res.exports; + memory = exports.memory; + + const refMap = new Map(); + function pin(ptr) { + if (ptr) { + const count = refMap.get(ptr); + if (count) refMap.set(ptr, count + 1); + else refMap.set(exports.__pin(ptr), 1); + } + return ptr; + } + function unpin(ptr) { + if (ptr) { + const count = refMap.get(ptr); + if (count === 1) { + exports.__unpin(ptr); + refMap.delete(ptr); + } else if (count) { + refMap.set(ptr, count - 1); + } + } + } + + wasmInstanceCache = { + decrypt(encryptedBytes, keyStr) { + let dataPtr = 0; + let keyPtr = 0; + try { + const len = encryptedBytes.length; + const pinPtr = exports.__pin(exports.__new(len, 1)) >>> 0; + dataPtr = exports.__new(12, 6) >>> 0; + + const view = new DataView(memory.buffer); + view.setUint32(dataPtr, pinPtr, true); + view.setUint32(dataPtr + 4, pinPtr, true); + view.setUint32(dataPtr + 8, len, true); + new Uint8Array(memory.buffer, pinPtr, len).set(encryptedBytes); + exports.__unpin(pinPtr); + + pin(dataPtr); + + const kLen = keyStr.length; + keyPtr = exports.__new(kLen << 1, 2) >>> 0; + const u16View = new Uint16Array(memory.buffer); + for (let i = 0; i < kLen; i++) { + u16View[(keyPtr >>> 1) + i] = keyStr.charCodeAt(i); + } + + const resPtr = exports.decrypt(dataPtr, keyPtr) >>> 0; + if (!resPtr) return new Uint8Array(0); + + const resView = new DataView(memory.buffer); + const outPtr = resView.getUint32(resPtr + 4, true); + const outLen = resView.getUint32(resPtr + 8, true); + return new Uint8Array(memory.buffer, outPtr, outLen).slice(); + } finally { + unpin(dataPtr); + } + } + }; + return wasmInstanceCache; + } catch { + return null; + } +} + +async function requestEndpoint(url, headers) { try { - const iv = Buffer.alloc(16, 0); - const decipher = crypto.createDecipheriv('aes-256-cbc', keyBuf, iv); - decipher.setAutoPadding(true); - let dec = decipher.update(ciphertext); - dec = Buffer.concat([dec, decipher.final()]); - const text = dec.toString('utf8'); - if (text.startsWith('http') || text.includes('{')) return text; - } catch { } + return await fetchJson(url, { + headers, + signal: AbortSignal.timeout(6000) + }); + } catch { + return null; + } +} - try { - const iv = ciphertext.subarray(0, 16); - const data = ciphertext.subarray(16); - const decipher = crypto.createDecipheriv('aes-256-cbc', keyBuf, iv); - decipher.setAutoPadding(true); - let dec = decipher.update(data); - dec = Buffer.concat([dec, decipher.final()]); - const text = dec.toString('utf8'); - if (text.startsWith('http') || text.includes('{')) return text; - } catch { } +async function resolveServiceStream(service, isMovie, id, s, e, headers) { + const directEndpoint = isMovie + ? `${BASE_URL}/streams/movie/${id}?s=${service}` + : `${BASE_URL}/streams/tv/${id}/${s || 1}/${e || 1}?s=${service}`; + let res = await requestEndpoint(directEndpoint, headers); + + if (!res || !res.url) { + const encEndpoint = `${directEndpoint}&e=1`; + const encRes = await requestEndpoint(encEndpoint, headers); + + if (encRes && encRes.c) { + const instance = await getWasmInstance(); + if (instance) { + const encryptedBytes = new Uint8Array(Buffer.from(encRes.c, 'base64')); + const decryptedBytes = instance.decrypt(encryptedBytes, 'player.vidzee.wtf'); + if (decryptedBytes.length > 0) { + const str = new TextDecoder().decode(decryptedBytes); + try { + res = JSON.parse(str); + } catch { } + } + } + } + } + + if (res && res.url) { + const format = res.url.includes('.m3u8') ? 'hls' : 'mp4'; + return { + url: res.url, + server: `VidZee (${service})`, + quality: res.language || 'Auto', + type: format, + headers: { + 'User-Agent': USER_AGENT, + 'Referer': `${PLAYER_URL}/`, + 'Origin': PLAYER_URL, + ...(res.headers || {}) + } + }; + } return null; } -export async function getStream({ id, s, e, clientIP }) { +export async function getStream(args) { + const { id, s, e, server, clientIP } = args; + const isTv = s != null && e != null; + const isMovie = !isTv; + const headers = { 'User-Agent': USER_AGENT, - 'Accept': 'application/json', - 'Referer': PLAYER_URL, + 'Accept': 'application/json, text/plain, */*', + 'Referer': `${PLAYER_URL}/`, 'Origin': PLAYER_URL, ...(clientIP && { 'X-Forwarded-For': clientIP }) }; - try { - const isTv = s != null && e != null; - const servers = ['tik', 'ipcloud', 'v6:Hindi']; - const results = []; - - for (const sr of servers) { - try { - const url = isTv - ? `${CORE_URL}/streams/tv/${id}/${s}/${e}?s=${encodeURIComponent(sr)}&e=${e}` - : `${CORE_URL}/streams/movie/${id}?s=${encodeURIComponent(sr)}&e=1`; - - const data = await fetchJson(url, { headers, signal: AbortSignal.timeout(6000) }); - - if (data && data.c) { - const decryptedText = decryptAes(data.c); - if (decryptedText) { - let streamUrl = decryptedText; - let extraHeaders = {}; - - if (decryptedText.startsWith('{')) { - try { - const parsed = JSON.parse(decryptedText); - streamUrl = parsed.url || parsed.link || parsed.file; - if (parsed.headers) extraHeaders = parsed.headers; - } catch { } - } - - if (streamUrl && streamUrl.startsWith('http')) { - results.push({ - url: streamUrl, - server: `VidZee - ${sr}`, - skipHlsCheck: true, - headers: { - 'User-Agent': USER_AGENT, - 'Referer': PLAYER_URL, - 'Origin': PLAYER_URL, - ...extraHeaders - } - }); - } - } - } - } catch (err) { - } - } - - return results.length ? { allUrls: results } : null; - } catch { - return null; + let targets = SERVICES; + if (server && server !== 'all') { + const clean = server.toLowerCase().replace('vidzee (', '').replace(')', '').replace('vidzee', '').trim(); + targets = SERVICES.filter(srv => srv.toLowerCase().includes(clean)); + if (!targets.length) targets = SERVICES; } + + const settled = await Promise.allSettled( + targets.map(service => resolveServiceStream(service, isMovie, id, s, e, headers)) + ); + + const allUrls = []; + for (const r of settled) { + if (r.status === 'fulfilled' && r.value) { + allUrls.push(r.value); + } + } + + return allUrls.length ? { allUrls } : null; +} + +export async function getSources() { + return SERVICES.map(s => `VidZee (${s})`); } \ No newline at end of file