From ec9f259c4a9cdf3ad6cd44de851b82b5ca82d36e Mon Sep 17 00:00:00 2001 From: alwaysmesmerizingyou Date: Tue, 20 Oct 2026 14:38:36 -0700 Subject: [PATCH] added cinecat, cinesrc, cinezo, hexa, hyper, and yastream --- src/config.js | 104 +++++++++++ src/sources/cinecat.js | 19 ++ src/sources/cinesrc.js | 398 ++++++++++++++++++++++++++++++++++++++++ src/sources/cinezo.js | 55 ++++++ src/sources/hexa.js | 54 ++++++ src/sources/hyper.js | 36 ++++ src/sources/yastream.js | 30 +++ test/index.js | 10 +- 8 files changed, 701 insertions(+), 5 deletions(-) create mode 100644 src/sources/cinecat.js create mode 100644 src/sources/cinesrc.js create mode 100644 src/sources/cinezo.js create mode 100644 src/sources/hexa.js create mode 100644 src/sources/hyper.js create mode 100644 src/sources/yastream.js diff --git a/src/config.js b/src/config.js index 5f7f4bf..ec10999 100644 --- a/src/config.js +++ b/src/config.js @@ -118,6 +118,25 @@ export const SOURCES = [ }], }, + { + key: 'cinecat', + label: 'Cinecat', + sourceFile: 'cinecat', + proxyParam: 'ccat', + timeout: 25000, + jitter: 500, + retries: 2, + multiUrl: false, + skipProxy: true, + cdnHeaders: [{ + pattern: /bluevelvet\.space/i, + headers: { + Referer: 'https://zcdn.bluevelvet.space/', + Origin: 'https://zcdn.bluevelvet.space', + }, + }], + }, + { key: 'cinemaarmy', label: 'CinemaArmy', @@ -137,6 +156,43 @@ export const SOURCES = [ }], }, + { + key: 'cinesrc', + label: 'CineSrc', + sourceFile: 'cinesrc', + proxyParam: 'cs', + timeout: 35000, + jitter: 500, + retries: 2, + multiUrl: true, + skipProxy: true, + cdnHeaders: [{ + pattern: /cinesrc\.st/i, + headers: { + Referer: 'https://cinesrc.st/', + Origin: 'https://cinesrc.st', + }, + }], + }, + + { + key: 'cinezo', + label: 'Cinezo', + sourceFile: 'cinezo', + proxyParam: 'czo', + timeout: 25000, + jitter: 500, + retries: 2, + multiUrl: true, + cdnHeaders: [{ + pattern: /yenime\.net|flikhub\.net|hakunaymatata\.com/i, + headers: { + Referer: 'https://player.cinezo.live/', + Origin: 'https://player.cinezo.live', + }, + }], + }, + { key: 'dulo', label: 'Dulo', @@ -187,6 +243,43 @@ export const SOURCES = [ skipProxy: true, }, + { + key: 'hexa', + label: 'Hexa', + sourceFile: 'hexa', + proxyParam: 'hx', + timeout: 30000, + jitter: 500, + retries: 2, + multiUrl: true, + skipProxy: true, + cdnHeaders: [{ + pattern: /hexa\.su|flixer\.su/i, + headers: { + Referer: 'https://hexa.su/', + Origin: 'https://hexa.su', + }, + }], + }, + + { + key: 'hyper', + label: 'Hyper', + sourceFile: 'hyper', + proxyParam: 'hyp', + timeout: 25000, + jitter: 500, + retries: 2, + multiUrl: true, + cdnHeaders: [{ + pattern: /hakunaymatata\.com/i, + headers: { + Referer: 'https://mzfi.me/', + Origin: 'https://mzfi.me', + }, + }], + }, + { key: 'kisskh', label: 'KissKH', @@ -588,6 +681,17 @@ export const SOURCES = [ }], }, + { + key: 'yastream', + label: 'YaStream', + sourceFile: 'yastream', + proxyParam: 'ya', + timeout: 25000, + jitter: 500, + retries: 2, + multiUrl: true, + }, + ]; export const HEALTH_PROBE_ID = '155'; diff --git a/src/sources/cinecat.js b/src/sources/cinecat.js new file mode 100644 index 0000000..29b48b8 --- /dev/null +++ b/src/sources/cinecat.js @@ -0,0 +1,19 @@ +import { fetchJson } from '../utils/helpers.js'; + +export async function getStream({ id, s, e }) { + const isTv = s != null && e != null; + const url = isTv + ? `https://zcdn.bluevelvet.space/cdn?tmdb=${id}&type=tv&season=${s}&episode=${e}` + : `https://zcdn.bluevelvet.space/cdn?tmdb=${id}&type=movie`; + + const res = await fetchJson(url); + if (!res || !res.ready || !res.url) return null; + + return { + url: res.url, + headers: { + Referer: 'https://zcdn.bluevelvet.space/', + Origin: 'https://zcdn.bluevelvet.space', + } + }; +} \ No newline at end of file diff --git a/src/sources/cinesrc.js b/src/sources/cinesrc.js new file mode 100644 index 0000000..8b0ad5a --- /dev/null +++ b/src/sources/cinesrc.js @@ -0,0 +1,398 @@ +import { fetchJson, fetchText } from '../utils/helpers.js'; + +const API = "https://enc-dec.app/api"; + +async function solveStage1(challenge1, seed) { + const K = [ + 0x46C6C721, 0x4230C311, 0x18223A1D, 0x56389BA7, + 0xD4A87103, 0x07CB9E2F, 0x73085F9D, 0xD2396A1F, + 0x23AE5ADD, 0x4646C48F, 0x0F4E0903, 0x808F5F31, + 0x953D4E07, 0x7E238FFF, 0x634595DF, 0xCB5E4301, + ]; + + const SCHEDULES = [ + [0, 7, 6, 3, 2, 1, 5, 4, 0, 2, 4, 6, 5, 1, 7, 3], + [5, 2, 1, 3, 0, 4, 6, 7, 4, 1, 0, 7, 3, 6, 5, 2], + [4, 0, 2, 1, 5, 7, 6, 3, 2, 6, 0, 7, 4, 3, 5, 1], + [5, 4, 7, 0, 2, 3, 1, 6, 5, 1, 4, 0, 7, 2, 3, 6], + [4, 7, 0, 2, 1, 6, 5, 3, 6, 1, 3, 2, 0, 7, 4, 5], + [0, 7, 2, 6, 3, 4, 5, 1, 6, 5, 0, 2, 7, 1, 4, 3], + [0, 4, 5, 3, 1, 7, 2, 6, 3, 7, 5, 6, 1, 0, 2, 4], + [7, 6, 0, 4, 2, 5, 1, 3, 7, 5, 3, 4, 2, 6, 0, 1], + ]; + + const PERMUTATIONS = [ + [3, 5, 1, 0, 2, 4], + [2, 0, 4, 5, 3, 1], + [1, 4, 5, 3, 0, 2], + [2, 0, 1, 5, 4, 3], + [4, 2, 1, 0, 3, 5], + [5, 0, 2, 1, 3, 4], + [4, 5, 1, 0, 3, 2], + [4, 0, 3, 5, 1, 2], + ]; + + const u32 = x => x >>> 0; + const rotl = (x, n) => u32((x << (n & 31)) | (x >>> (32 - (n & 31)))); + const imul = (a, b) => { + const ah = (a >>> 16) & 0xffff; + const al = a & 0xffff; + const bh = (b >>> 16) & 0xffff; + const bl = b & 0xffff; + return u32((ah * bl + al * bh) << 16) + al * bl; + }; + + const b64dec = s => { + const bin = atob(s.replace(/-/g, '+').replace(/_/g, '/')); + const bytes = new Uint8Array(bin.length); + for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i); + return bytes; + }; + + const c = b64dec(challenge1.w); + const view = new DataView(c.buffer); + const mode = c[21]; + const diffVal = c[22]; + const var_ = c[23]; + const rnd = c[24]; + const m_b = c[25]; + const s_i = c[26]; + const p_i = c[27]; + + const w0 = view.getUint32(0, true); + const w1 = view.getUint32(4, true); + const w2 = view.getUint32(8, true); + const w3 = view.getUint32(12, true); + const w = [w0, w1, w2, w3]; + + const size = 1 << m_b; + const s = new Uint32Array(size); + let x = u32(w[var_ & 3] ^ K[s_i] ^ imul(K[8 + s_i], var_ + 1)); + let y = u32(w[(var_ + 1) & 3] ^ K[15 - s_i]); + + for (let i = 0; i < size; i++) { + x = u32(w[(i + s_i) & 3] + x + imul(K[(i + var_) & 15], i + 1)); + x = u32(x ^ (x << 13)); + x = u32(x ^ (x >>> 17)); + x = u32(x ^ (x << 5)); + y = rotl(imul(K[(i + 9) & 15], i + 3) ^ y ^ x, i + var_ + s_i); + s[i] = u32(imul(K[(i + 5) & 15], i ^ var_) ^ rotl(w[(i + var_) & 3], i + s_i) ^ x ^ y); + } + + const seedBytes = new Uint8Array(seed.match(/.{1,2}/g).map(byte => parseInt(byte, 16))); + const msg = new Uint8Array(88); + msg.set(c.slice(0, 16), 24); + + const cfg = m_b ^ 0x6B29; + msg[48] = 4; + msg[49] = mode; + msg[50] = var_; + msg[51] = s_i; + msg[52] = p_i; + msg[53] = rnd; + msg[54] = cfg & 0xff; + msg[55] = cfg >> 8; + msg.set(seedBytes, 56); + + const mask = size - 1; + const sch = SCHEDULES[s_i]; + const perm = PERMUTATIONS[p_i]; + + let n = 0n; + while (true) { + const lo = Number(n & 0xffffffffn); + const hi = Number(n >> 32n); + + let ma = u32(w[0] ^ K[var_] ^ lo); + let mb = u32(rotl(lo, var_ + 7) ^ w[2]); + let mc = u32(w[1] ^ K[8 + s_i] ^ hi); + let md = u32(rotl(lo ^ hi, s_i + 11) ^ w[3]); + let me = u32(K[s_i + var_] ^ lo ^ hi); + let mf = u32(K[15 - var_] ^ w[s_i & 3]); + + for (let r = 0; r < rnd; r++) { + const i1 = u32(rotl(mb, r + 1) ^ imul(K[r & 15], mf) ^ me ^ ma) & mask; + const v1 = s[i1]; + const i2 = u32(rotl(me, var_ + r + 3) ^ imul(K[(r + 7) & 15], md) ^ mc ^ v1) & mask; + const op = sch[r]; + + if (op === 0) { + ma = rotl(u32(ma + md + v1), 5); + mc = u32(imul(mc ^ mf ^ ma, 1187432225) + mb); + } else if (op === 1) { + mb = rotl(mc ^ me ^ mb ^ v1, 11); + md = u32(imul(mb ^ ma, 1110491921) + md); + } else if (op === 2) { + mc = rotl(u32(mc + mb + v1), 17); + ma = u32(imul(md ^ mf ^ mc, 404896285) ^ ma); + } else if (op === 3) { + md = rotl(ma ^ md ^ v1, 23); + mb = u32(imul(mc ^ me ^ md, 1446550439) + mb); + } else if (op === 4) { + ma = u32(imul(ma ^ me ^ v1, -727158525) + rotl(mb, 7)); + md = u32(rotl(u32(mc + mf + ma), 13) ^ md); + } else if (op === 5) { + mc = u32(imul(u32(mc + mf + v1), 130784815) ^ rotl(md, 9)); + mb = u32(rotl(ma ^ me ^ mc, 19) + mb); + } else if (op === 6) { + mb = u32(imul(mb ^ mf ^ v1, 1929928605) + rotl(ma, 3)); + mc = u32(rotl(u32(md + me + mb), 15) ^ mc); + } else { + md = u32(imul(u32(md + me + v1), -767989217) ^ rotl(mc, 21)); + ma = u32(rotl(mb ^ mf ^ md, 27) + ma); + } + + const v2 = s[i2]; + if (((mf ^ r ^ v1) & 1) === 0) { + mf = rotl(u32(v2 + mf + ma), r + 5); + me = u32(imul(K[8 + op], v2 ^ me ^ mf) + md); + } else { + me = rotl(v2 ^ me ^ mb, r + 9); + mf = u32(imul(K[(op + 11) & 15], u32(v2 + mf + me)) ^ mc); + } + } + + const mArr = [ma, mc, mb, md, mf, me]; + const v = perm.map(idx => mArr[idx]); + + const msgView = new DataView(msg.buffer); + msgView.setUint32(0, v[0], true); + msgView.setUint32(4, v[1], true); + msgView.setUint32(8, v[2], true); + msgView.setUint32(12, v[3], true); + msgView.setUint32(16, v[4], true); + msgView.setUint32(20, v[5], true); + msgView.setBigUint64(40, n, true); + + const hashBuf = await crypto.subtle.digest('SHA-256', msg); + const hashBytes = new Uint8Array(hashBuf); + + const b = Math.floor(diffVal / 8); + const rBits = diffVal % 8; + let ok = true; + for (let i = 0; i < b; i++) { + if (hashBytes[i] !== 0) { + ok = false; + break; + } + } + if (ok && (rBits === 0 || (hashBytes[b] >> (8 - rBits)) === 0)) { + return `m4.${n.toString(16)}`; + } + n++; + } +} + +async function solveStage2(data) { + const target = data.pack[0].split('').reverse().join(''); + const salt = data.pack[3].split('').reverse().join(''); + const r = data.pack[4].split('').reverse().join(''); + + const b64decStr = s => decodeURIComponent(escape(atob(s.replace(/-/g, '+').replace(/_/g, '/')))); + const body = b64decStr(r.split('.')[1]); + const payload = b64decStr(body.split('.')[1]); + const difficulty = JSON.parse(payload).d; + + const width = Math.floor((difficulty + 3) / 4); + const limit = 1 << difficulty; + + for (let counter = 0; counter < limit; counter++) { + const key = counter.toString(16).padStart(width, '0'); + const msgUint8 = new TextEncoder().encode(salt + key); + const hashBuffer = await crypto.subtle.digest('SHA-256', msgUint8); + const hashArray = Array.from(new Uint8Array(hashBuffer)); + const digest = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); + + if (digest === target) { + return key; + } + } + throw new Error('no solution found'); +} + +export async function getStream({ id, s, e }) { + const isTv = s != null && e != null; + const type = isTv ? 'tv' : 'movie'; + const url = isTv + ? `https://cinesrc.st/embed/tv/${id}?s=${s}&e=${e}` + : `https://cinesrc.st/embed/movie/${id}`; + + const headers = { + "Origin": "https://cinesrc.st", + "Referer": "https://cinesrc.st/", + "Content-Type": "text/plain;charset=UTF-8", + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.37 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.37" + }; + + const fields = [type, id, s ? String(s) : null, e ? String(e) : null]; + const encoded = btoa(JSON.stringify(fields).replace(/ /g, '')).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); + + const bootstrapHeaders = { ...headers, "x-cs-q": encoded }; + const bootstrap = await fetchJson("https://cinesrc.st/api/c/bootstrap", { + method: "POST", + headers: bootstrapHeaders, + body: "{}" + }); + + const cookies = { + "x-cs-b": "20261014-a", + "x-cs-q": encoded, + "x-cs-r": bootstrap.r, + "x-cs-p": bootstrap.p, + }; + + const challenge1 = await fetchJson("https://cinesrc.st/api/c/issue", { + headers: { ...headers, ...cookies } + }); + + const payload1 = { + data: { + url, + user_agent: headers["User-Agent"], + challenge_data: challenge1 + }, + type: "seed" + }; + + const seedRes = await fetchJson(`${API}/enc-cinesrc`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(payload1) + }); + + const seedData = seedRes.result; + const solution1 = await solveStage1(challenge1, seedData.seed); + + const challenge2 = await fetchJson("https://cinesrc.st/api/c/stage2/issue", { + headers: { ...headers, ...cookies } + }); + const solution2 = await solveStage2(challenge2); + + const payload2 = { + data: { + url, + user_agent: headers["User-Agent"], + challenge_data: { + stage1: { challenge: challenge1, solution: solution1 }, + stage2: { challenge: challenge2, solution: solution2 } + }, + state: seedData.state + }, + type: "encrypt" + }; + + const encryptRes = await fetchJson(`${API}/enc-cinesrc`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(payload2) + }); + + const encData = encryptRes.result; + const token = `${encData.token}::c3::${cookies["x-cs-r"]}`; + const nextActions = encData.headers; + + const providersText = await fetchText(url, { + method: "POST", + headers: { ...headers, ...cookies, "Next-Action": nextActions.getProviderList }, + body: JSON.stringify([]) + }); + + const lines = providersText.split('\n'); + let providers = null; + for (const rawLine of lines) { + const colonIndex = rawLine.indexOf(':'); + if (colonIndex !== -1) { + const candidate = rawLine.slice(colonIndex + 1).trim(); + if (candidate.startsWith('[') && candidate.endsWith(']')) { + try { + providers = JSON.parse(candidate); + break; + } catch { } + } + } + } + + if (!Array.isArray(providers) || providers.length === 0) return null; + + const allUrls = []; + + for (const providerObj of providers) { + const provider = providerObj.id; + const streamPayload = [ + id, + isTv ? "show" : type, + isTv ? String(s) : "$undefined", + isTv ? String(e) : "$undefined", + token, + provider + ]; + + let streamRes; + try { + streamRes = await fetchText(url, { + method: "POST", + headers: { ...headers, ...cookies, "Next-Action": nextActions.getStream }, + body: JSON.stringify(streamPayload) + }); + } catch { + continue; + } + + if (!streamRes || streamRes.includes('"status":500') || streamRes.includes('invalid_challenge') || streamRes.length < 10) continue; + + let decryptRes; + try { + decryptRes = await fetchJson(`${API}/dec-cinesrc`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ text: streamRes, state: encData.state }) + }); + } catch { + continue; + } + + if (decryptRes && decryptRes.status === 200 && decryptRes.result) { + let resultData = decryptRes.result; + if (typeof resultData === 'string') { + try { + resultData = JSON.parse(resultData); + } catch { } + } + + let streamUrl = null; + if (Array.isArray(resultData?.url)) { + streamUrl = resultData.url[0]?.url; + } else if (typeof resultData?.url === 'string') { + streamUrl = resultData.url; + } else if (resultData?.file) { + streamUrl = resultData.file; + } else if (Array.isArray(resultData?.sources)) { + streamUrl = resultData.sources[0]?.url; + } + + if (streamUrl) { + if (streamUrl.startsWith('/')) { + streamUrl = `https://cinesrc.st${streamUrl}`; + } + + allUrls.push({ + url: streamUrl, + server: `CineSrc - ${providerObj.name || provider}`, + headers: resultData.headers || { + Referer: "https://cinesrc.st/", + Origin: "https://cinesrc.st" + } + }); + break; + } + } + } + + if (!allUrls.length) return null; + + return { + url: allUrls[0].url, + headers: allUrls[0].headers, + allUrls + }; +} \ No newline at end of file diff --git a/src/sources/cinezo.js b/src/sources/cinezo.js new file mode 100644 index 0000000..f67ee83 --- /dev/null +++ b/src/sources/cinezo.js @@ -0,0 +1,55 @@ +import { fetchJson } from '../utils/helpers.js'; + +export async function getStream({ id, s, e }) { + const isTv = s != null && e != null; + const referer = isTv + ? `https://player.cinezo.live/embed/tv/${id}?s=${s}&e=${e}` + : `https://player.cinezo.live/embed/movie/${id}`; + + const headers = { + 'Accept': '*/*', + 'Origin': 'https://player.cinezo.live', + 'Referer': referer, + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/155.0.0.0 Safari/537.36 Edg/155.0.0.0' + }; + + const hosts = [ + 'https://proxy.yenime.net', + 'https://proxy1.flikhub.net' + ]; + + const sources = ['zendaya', 'berlin', 'jennifer', 'scarlet']; + const allUrls = []; + + for (const host of hosts) { + for (const source of sources) { + const url = isTv + ? `${host}/tv?id=${id}&season=${s}&episode=${e}&mode=json&sources=${source}&hevc=1` + : `${host}/movie?id=${id}&mode=json&sources=${source}&hevc=1`; + + try { + const res = await fetchJson(url, { headers }); + const streamUrl = res?.source?.url; + if (streamUrl) { + allUrls.push({ + url: streamUrl, + server: `Cinezo - ${res.source.label || source}`, + headers: { + Referer: 'https://player.cinezo.live/', + Origin: 'https://player.cinezo.live' + } + }); + } + } catch { } + } + if (allUrls.length) break; + } + + if (!allUrls.length) return null; + + return { + url: allUrls[0].url, + headers: allUrls[0].headers, + allUrls + }; +} \ No newline at end of file diff --git a/src/sources/hexa.js b/src/sources/hexa.js new file mode 100644 index 0000000..fbbc7b1 --- /dev/null +++ b/src/sources/hexa.js @@ -0,0 +1,54 @@ +import { fetchJson, fetchText } from '../utils/helpers.js'; + +const API = "https://enc-dec.app/api"; + +function getRandomHex(size = 32) { + const array = new Uint8Array(size); + crypto.getRandomValues(array); + return Array.from(array).map(b => b.toString(16).padStart(2, '0')).join(''); +} + +export async function getStream({ id, s, e }) { + const isTv = s != null && e != null; + const key = getRandomHex(32); + + const headers = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36", + "Referer": "https://hexa.su/", + "Accept": "text/plain", + "X-Fingerprint-Lite": "e9136c41504646444", + "X-Api-Key": key + }; + + const encHexaRes = await fetchJson(`${API}/enc-hexa`); + if (!encHexaRes || encHexaRes.status !== 200) return null; + headers["X-Cap-Token"] = encHexaRes.result.token; + + const url = isTv + ? `https://theemoviedb.hexa.su/api/tmdb/tv/${id}/season/${s}/episode/${e}/images` + : `https://theemoviedb.hexa.su/api/tmdb/movie/${id}/images`; + + const encrypted = await fetchText(url, { headers }); + if (!encrypted) return null; + + const decRes = await fetchJson(`${API}/dec-hexa`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ text: encrypted, key }) + }); + + if (!decRes || decRes.status !== 200 || !decRes.result) return null; + + const data = decRes.result; + const streamUrl = data.url || data.file || (Array.isArray(data.sources) ? data.sources[0]?.url : null); + + if (!streamUrl) return null; + + return { + url: streamUrl, + headers: { + Referer: "https://hexa.su/", + Origin: "https://hexa.su" + } + }; +} \ No newline at end of file diff --git a/src/sources/hyper.js b/src/sources/hyper.js new file mode 100644 index 0000000..f62033e --- /dev/null +++ b/src/sources/hyper.js @@ -0,0 +1,36 @@ +import { fetchJson } from '../utils/helpers.js'; + +export async function getStream({ id, s, e }) { + const isTv = s != null && e != null; + const path = isTv + ? `/local/stream/series/tmdb:${id}:${s}:${e}.json` + : `/local/stream/movie/tmdb:${id}.json`; + + const url = `https://hyper.stravo.site${path}`; + const res = await fetchJson(url); + if (!res || !Array.isArray(res.streams)) return null; + + const validStreams = res.streams.filter(item => { + if (!item.url || item.externalUrl) return false; + if (typeof item.title === 'string' && item.title.includes('🔒')) return false; + return true; + }); + + if (!validStreams.length) return null; + + const allUrls = validStreams.map(item => ({ + url: item.url, + server: `Hyper - ${item.title?.replace(/[^\x20-\x7E]+/g, '').trim() || 'Server'}`, + headers: item.behaviorHints?.proxyHeaders?.request || { + Referer: 'https://mzfi.me/', + Accept: '*/*' + }, + skipProxy: item.behaviorHints?.notWebReady === false + })); + + return { + url: allUrls[0].url, + headers: allUrls[0].headers, + allUrls + }; +} \ No newline at end of file diff --git a/src/sources/yastream.js b/src/sources/yastream.js new file mode 100644 index 0000000..dbade2a --- /dev/null +++ b/src/sources/yastream.js @@ -0,0 +1,30 @@ +import { fetchJson } from '../utils/helpers.js'; + +export async function getStream({ id, s, e }) { + const isTv = s != null && e != null; + const path = isTv + ? `/stream/series/tmdb:${id}:${s}:${e}.json` + : `/stream/movie/tmdb:${id}.json`; + + const url = `https://yastream.tamthai.de${path}`; + const res = await fetchJson(url); + if (!res || !Array.isArray(res.streams) || !res.streams.length) return null; + + const allUrls = res.streams.map(item => { + if (!item.url) return null; + return { + url: item.url, + server: `YaStream - ${item.name?.replace(/\n/g, ' ') || 'Server'}`, + headers: item.behaviorHints?.proxyHeaders?.request || undefined, + skipProxy: item.behaviorHints?.notWebReady === false + }; + }).filter(Boolean); + + if (!allUrls.length) return null; + + return { + url: allUrls[0].url, + headers: allUrls[0].headers, + allUrls + }; +} \ No newline at end of file diff --git a/test/index.js b/test/index.js index 22a28ef..6d17b5a 100644 --- a/test/index.js +++ b/test/index.js @@ -1,7 +1,7 @@ // usage: npm test // Configure the variables below to customize your testing environment -import fs from "fs"; +import fs, { truncate } from "fs"; import path from "path"; import VylaSDK from "../src/sdk.js"; import dotenv from "dotenv"; @@ -13,13 +13,13 @@ const CONFIG = { TV_ID: "37854", // TV TMDB ID to test, for anime I recommend using a random ID like 37854 TV_SEASON: "1", // TV Season to test TV_EPISODE: "1", // TV Episode to test - STREAM_SOURCE: "atsu", // Source key to test + STREAM_SOURCE: "cinezo", // Source key to test TEST_SUBTITLE: false, TEST_DOWNLOAD: false, - TEST_MOVIE: false, - TEST_TV: false, - TEST_MANGA: true, + TEST_MOVIE: true, + TEST_TV: true, + TEST_MANGA: false, MANGA_QUERY: "Steel Ball Run", EXTRA_SDK_PARAMS: {