diff --git a/src/config.js b/src/config.js index 690ef65..a992b2f 100644 --- a/src/config.js +++ b/src/config.js @@ -115,6 +115,26 @@ export const SOURCES = [ skipProxy: true, }, + { + key: 'cinesu', + label: 'CineSu', + sourceFile: 'cinesu', + proxyParam: 'cs', + timeout: 25000, + jitter: 500, + retries: 2, + multiUrl: true, + skipProxy: true, + skipVerify: true, + cdnHeaders: [{ + pattern: /glendale-plumbing\.com/i, + headers: { + Referer: 'https://cine.su/', + Origin: 'https://cine.su', + }, + }], + }, + { key: 'flaxmovies', label: 'FlaxMovies', @@ -303,6 +323,17 @@ export const SOURCES = [ disabled: false, // Enabled, but only returns streams when a manifest is provided }, + { + key: 'pstream', + label: 'PStream', + sourceFile: 'pstream', + proxyParam: 'ps', + timeout: 35000, + jitter: 500, + retries: 1, + multiUrl: true, + }, + { key: 'nova', label: 'Nova', @@ -456,19 +487,6 @@ export const SOURCES = [ skipProxy: true, }, - { - key: 'vidrift', - label: 'VidRift', - sourceFile: 'vidrift', - proxyParam: 'vr', - timeout: 20000, - jitter: 500, - retries: 2, - multiUrl: true, - skipProxy: true, - disabled: true, // Temporarily disabled because their site is down, embed website does not load - }, - { key: 'vidrock', label: 'VidRock', @@ -489,17 +507,6 @@ export const SOURCES = [ },], }, - { - key: 'vidsync', - label: 'VidSync', - sourceFile: 'vidsync', - proxyParam: 'vs', - timeout: 20000, - retries: 2, - multiUrl: true, - disabled: true, // Temporarily disabled due to enc dec issues - }, - { key: 'vidup', label: 'VidUp', diff --git a/src/sources/cinesu.js b/src/sources/cinesu.js new file mode 100644 index 0000000..d1e2b55 --- /dev/null +++ b/src/sources/cinesu.js @@ -0,0 +1,148 @@ +import { USER_AGENT } from '../utils/helpers.js'; + +const BASE_DOMAIN = 'https://glendale-plumbing.com'; +const REFERER = 'https://cine.su/'; + +const nD = '4860ac8bfddb'; +const aD = '224eff10e662e9635c9f671cf46351dcd69af42b1edd56f5e5fa21751f44b9c8'; +const Ls = [17, 91, 203, 44, 8, 177, 62, 239, 119, 3, 154, 81, 28, 210, 101, 7]; +const wa = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'; + +function ab(e) { + let t = e >>> 0; + t ^= t >>> 16; + t = Math.imul(t, 2146121005) >>> 0; + t ^= t >>> 15; + t = Math.imul(t, 2221713035) >>> 0; + return (t ^ (t >>> 16)) >>> 0; +} + +function sD(e) { + const t = new TextEncoder().encode(aD); + const r = Math.max(32, Math.min(128, e + 17)); + const n = new Uint8Array(r); + let a = 2166136261; + for (let s = 0; s < r; s += 1) { + a ^= t[s % t.length] ?? s; + a = ab((a + Ls[s % Ls.length] + ((2654435761 * s) >>> 0)) >>> 0); + n[s] = a & 255; + } + return n; +} + +function iD(e) { + let t = ''; + for (let r = 0; r < e.length; r += 3) { + const n = e[r]; + const a = e[r + 1]; + const s = e[r + 2]; + t += wa[n >>> 2]; + t += wa[((3 & n) << 4) | ((a ?? 0) >>> 4)]; + if (a === undefined) break; + t += wa[((15 & a) << 2) | ((s ?? 0) >>> 6)]; + if (s === undefined) break; + t += wa[63 & s]; + } + return t; +} + +function generateMasterUrl(id, s, e) { + const isTv = s != null && e != null; + const tmdbId = Math.floor(Number(id)); + const season = isTv ? Math.floor(Number(s || 1)) : 0; + const episode = isTv ? Math.floor(Number(e || 1)) : 0; + + const str = `${nD}:${isTv ? 's' : 'm'}:${tmdbId}:${season}:${episode}`; + const a = new TextEncoder().encode(str); + const sArr = sD(a.length); + const i = new Uint8Array(a.length + 2); + i[0] = a.length & 255; + i[1] = (a.length >>> 8) & 255; + let o = (2654435769 ^ a.length) >>> 0; + for (let l = 0; l < a.length; l += 1) { + o = ab((o + sArr[l % sArr.length] + Ls[l % Ls.length] + l) >>> 0); + i[l + 2] = (a[l] ^ (255 & o)) ^ sArr[(7 * l + 3) % sArr.length]; + } + + return `${BASE_DOMAIN}/c/v1/${iD(i)}/master.m3u8`; +} + +function parsePlaylist(m3u8Text) { + const qualities = []; + const subtitles = []; + + const subRegex = /#EXT-X-MEDIA:TYPE=SUBTITLES,[^\n]*NAME="([^"]*)"[^\n]*LANGUAGE="([^"]*)"[^\n]*URI="([^"]*)"/g; + let match; + while ((match = subRegex.exec(m3u8Text)) !== null) { + let uri = match[3]; + if (uri.startsWith('/')) uri = `${BASE_DOMAIN}${uri}`; + subtitles.push({ label: match[1], language: match[2], file: uri, type: 'vtt' }); + } + + const streamRegex = /#EXT-X-STREAM-INF:[^\n]*RESOLUTION=\d+x(\d+)[^\n]*\r?\n([^\n#][^\n]*)/g; + while ((match = streamRegex.exec(m3u8Text)) !== null) { + let streamPath = match[2].trim(); + if (streamPath.startsWith('/')) streamPath = `${BASE_DOMAIN}${streamPath}`; + qualities.push({ quality: `${match[1]}p`, url: streamPath }); + } + + return { qualities, subtitles }; +} + +export async function getStream(args) { + const { id, s, e, clientIP } = args; + const rawMasterUrl = generateMasterUrl(id, s, e); + + const headers = { + 'User-Agent': USER_AGENT, + 'Referer': REFERER, + 'Origin': 'https://cine.su', + ...(clientIP && { 'X-Forwarded-For': clientIP }) + }; + + try { + const res = await fetch(rawMasterUrl, { + headers, + signal: AbortSignal.timeout(5000) + }); + + if (res.ok) { + const text = await res.text(); + const { qualities, subtitles } = parsePlaylist(text); + + if (qualities.length) { + const allUrls = [{ + url: rawMasterUrl, + server: 'CineSu (Auto)', + quality: 'Auto', + type: 'hls', + headers, + subtitles: subtitles.length ? subtitles : undefined + }]; + + for (const q of qualities) { + allUrls.push({ + url: q.url, + server: `CineSu (${q.quality})`, + quality: q.quality, + type: 'hls', + headers, + subtitles: subtitles.length ? subtitles : undefined + }); + } + + return { allUrls }; + } + } + } catch { } + + return { + allUrls: [{ + url: rawMasterUrl, + server: 'CineSu', + quality: 'Auto', + type: 'hls', + headers + }] + }; +} \ No newline at end of file diff --git a/src/sources/pstream.js b/src/sources/pstream.js new file mode 100644 index 0000000..ecfd76b --- /dev/null +++ b/src/sources/pstream.js @@ -0,0 +1,68 @@ +import { USER_AGENT } from '../utils/helpers.js'; + +async function safeImport(moduleName) { + try { + return await import(`./${moduleName}.js`); + } catch { + return null; + } +} + +export async function getStream(args) { + const { id, s, e, clientIP, sdk } = args; + const isTv = s != null && e != null; + const type = isTv ? 'tv' : 'movie'; + const allUrls = []; + + const providers = [ + { name: 'CineSu', key: 'cinesu', fnName: 'fetchCineSuStreams' }, + { name: 'ZxcStream', key: 'zxcstream', fnName: 'fetchZxcStreams' }, + { name: 'VidFast', key: 'vidfast', fnName: 'fetchVidFastStreams' }, + { name: '1Embed', key: '1embed', fnName: 'fetchOneEmbedStreams' }, + ]; + + await Promise.all( + providers.map(async ({ name, key, fnName }) => { + try { + let streamResult = null; + + if (sdk) { + try { + streamResult = await sdk.getStream(key, id, s, e, clientIP); + } catch { } + } + + if (!streamResult) { + const mod = await safeImport(key); + if (mod) { + if (typeof mod.getStream === 'function') { + streamResult = await mod.getStream(args); + } else if (typeof mod[fnName] === 'function') { + const raw = await mod[fnName]({ type, id, season: s, episode: e }); + if (Array.isArray(raw)) { + streamResult = { allUrls: raw }; + } + } + } + } + + if (!streamResult) return; + + const streams = streamResult.allUrls || (streamResult.url ? [streamResult] : []); + for (const item of streams) { + allUrls.push({ + url: item.url, + server: `FlyStream (${item.source || item.server || name})`, + quality: item.quality || 'Auto', + type: item.type || item.format || (item.url?.includes('.m3u8') ? 'hls' : 'mp4'), + headers: item.headers || { + 'User-Agent': USER_AGENT, + }, + }); + } + } catch { } + }) + ); + + return allUrls.length ? { allUrls } : null; +} \ No newline at end of file diff --git a/src/sources/vidrift.js b/src/sources/vidrift.js deleted file mode 100644 index aebddd3..0000000 --- a/src/sources/vidrift.js +++ /dev/null @@ -1,95 +0,0 @@ -'use strict'; - -const BASE = "https://embed.vidrift.in"; - -const DEFAULT_HEADERS = { - "Accept": "*/*", - "Accept-Language": "en-US,en;q=0.9", - "Cache-Control": "no-cache", - "Pragma": "no-cache", - "Referer": `${BASE}/`, - "Origin": BASE, - "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36 Edg/150.0.0.0" -}; - -function decodeStreams(encodedStr) { - try { - const base64Str = encodedStr.replace(/-/g, '+').replace(/_/g, '/'); - - const binaryStr = typeof atob === 'function' - ? atob(base64Str) - : Buffer.from(base64Str, 'base64').toString('binary'); - - const bytes = new Uint8Array(binaryStr.length); - for (let i = 0; i < binaryStr.length; i++) { - bytes[i] = binaryStr.charCodeAt(i); - } - - const key = bytes[0]; - let decodedStr = ''; - for (let i = 1; i < bytes.length; i++) { - decodedStr += String.fromCharCode(bytes[i] ^ key); - } - - return JSON.parse(decodedStr); - } catch (e) { - return []; - } -} - -export async function getStream(args) { - const { id, s, e, server: serverName } = args; - const type = s != null && e != null ? "tv" : "movie"; - - const endpoint = type === "tv" - ? `${BASE}/embed/tv/${id}/${s}/${e}` - : `${BASE}/embed/movie/${id}`; - - try { - const res = await fetch(endpoint, { - headers: { - ...DEFAULT_HEADERS, - "Accept": "text/html", - }, - signal: AbortSignal.timeout(10000) - }); - - if (!res.ok) return null; - - const html = await res.text(); - - const match = html.match(/var\s+_s\s*=\s*['"]([^'"]+)['"]/); - if (!match) return null; - - const encodedStreams = match[1]; - const streamsData = decodeStreams(encodedStreams); - - if (!Array.isArray(streamsData) || !streamsData.length) { - return null; - } - - const allUrls = streamsData.map((stream, index) => ({ - url: stream.url.startsWith("http") - ? stream.url - : `${BASE}${stream.url}`, - server: stream.label || `VidRift - Server ${index + 1}`, - type: "hls", - headers: DEFAULT_HEADERS - })); - - if (serverName && serverName !== "all") { - const filtered = allUrls.filter(x => x.server === serverName); - return filtered.length ? { allUrls: filtered } : null; - } - - return { allUrls }; - } catch (err) { - return null; - } -} - -export async function getSources(args) { - const res = await getStream(args); - if (!res?.allUrls) return ["VidRift - Server 1"]; - return res.allUrls.map(x => x.server); -} \ No newline at end of file diff --git a/src/sources/vidsync.js b/src/sources/vidsync.js deleted file mode 100644 index affab62..0000000 --- a/src/sources/vidsync.js +++ /dev/null @@ -1,38 +0,0 @@ -import { fetchJson, fetchText, USER_AGENT, getTmdbInfo } from '../utils/helpers.js'; - -const API_BASE = "https://enc-dec.app/api"; -const DOMAIN = "https://vidsync.xyz"; -const HEADERS = { "Accept": "*/*", "Origin": DOMAIN, "Referer": `${DOMAIN}/`, "User-Agent": USER_AGENT, "X-Requested-With": "XMLHttpRequest" }; -const SERVERS = ["cinevault", "cinedub", "cinebox", "cineflix", "cinevip", "cinecloud", "cine4k"]; - -export async function getStream({ id, s, e, server }) { - try { - const isTv = s != null && e != null; - const type = isTv ? 'tv' : 'movie'; - const info = await getTmdbInfo(id, type); - if (!info?.titles?.length) return null; - const turnstileJson = await fetchJson(`${API_BASE}/enc-vidsync`, { signal: AbortSignal.timeout(5000) }); - if (turnstileJson.status !== 200 || !turnstileJson.result?.token) return null; - const turnstileToken = turnstileJson.result.token; - let targets = SERVERS; - if (server && server !== 'all') { - const clean = server.replace('VidSync - ', ''); - targets = SERVERS.includes(clean) ? [clean] : SERVERS; - } - const encTitle = encodeURIComponent(info.titles[0]).replace(/%20/g, '+'); - const results = await Promise.allSettled(targets.map(async srv => { - let url = `${DOMAIN}/api/stream/fetch?title=${encTitle}&type=${type}&releaseYear=${info.year}&mediaId=${id}&serverName=${srv}`; - if (isTv) url += `&season=${s}&episode=${e}`; - const encryptedText = await fetchText(url, { headers: { ...HEADERS, "X-Cf-Turnstile": turnstileToken }, signal: AbortSignal.timeout(10000) }); - const decJson = await fetchJson(`${API_BASE}/dec-vidsync`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text: encryptedText, id: String(id) }), signal: AbortSignal.timeout(10000) }); - if (decJson.status !== 200 || !decJson.result) throw new Error(); - const streams = Array.isArray(decJson.result) ? decJson.result : [decJson.result]; - return streams.map(st => ({ url: st.url || st.file, server: `VidSync - ${srv}`, quality: st.quality || "Auto", type: (st.url || st.file || "").includes(".m3u8") ? "hls" : "mp4", headers: { ...HEADERS, "Origin": DOMAIN }, skipProxy: false, skipVerify: true, skipHlsCheck: true })); - })); - const allUrls = []; - for (const r of results) if (r.status === 'fulfilled') allUrls.push(...r.value); - return allUrls.length ? { allUrls } : null; - } catch { return null; } -} - -export async function getSources() { return SERVERS.map(s => `VidSync - ${s}`); } \ No newline at end of file