diff --git a/README.md b/README.md index 3b91420..fa7e0c2 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ This project is developed and maintained by [Vyla Entertainment](https://github. Original work and core authorship: -[@endoverdosing](https://github.com/EndOverdosing) +[@alwaysmesmerizingyou](https://gitlab.com/alwaysmesmerizingyou) [@GavinGoGaming](https://github.com/GavinGoGaming) diff --git a/package.json b/package.json index 0af25cc..464bb7b 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,8 @@ "email": "gavin@klash.dev" }, { - "name": "EndOverdosing" + "name": "alwaysmesmerizingyou", + "email": "alwaysmesmerizingyou@gmail.com" } ], "license": "CC-BY-NC-4.0", diff --git a/src/config.js b/src/config.js index 5580c20..72a5504 100644 --- a/src/config.js +++ b/src/config.js @@ -6,17 +6,6 @@ // Fshare/Fsonic Tested: 155 ( they don't have Michael ) export const SOURCES = [ - { - key: '1embed', - label: '1Embed', - sourceFile: '1embed', - proxyParam: '1e', - timeout: 20000, - jitter: 500, - retries: 2, - multiUrl: true, - }, - { key: '123anime', label: '123Anime', @@ -145,6 +134,18 @@ export const SOURCES = [ },], }, + { + key: 'frame', + label: 'Frame', + sourceFile: 'frame', + proxyParam: 'fr', + timeout: 25000, + jitter: 500, + retries: 2, + multiUrl: true, + skipProxy: true, + }, + { key: 'fsharetv', label: 'FShareTV', @@ -252,30 +253,15 @@ export const SOURCES = [ }, { - key: 'opstream', - label: 'OpStream', - sourceFile: 'opstream', - proxyParam: 'op', - timeout: 35000, + key: 'peestream', + label: 'PeeStream', + sourceFile: 'peestream', + proxyParam: 'pee', + timeout: 25000, jitter: 500, retries: 2, multiUrl: true, skipProxy: true, - verifyHeaders: { - Referer: 'https://opstream.fun/', - Origin: 'https://opstream.fun', - }, - }, - - { - key: 'peachify', - label: 'Peachify', - sourceFile: 'peachify', - proxyParam: 'pf', - timeout: 30000, - jitter: 500, - retries: 1, - multiUrl: true }, { @@ -408,7 +394,8 @@ export const SOURCES = [ jitter: 500, retries: 2, multiUrl: true, - skipProxy: true + skipProxy: true, + disabled: true, // Temporarily disabled because their site is down, embed website does not load }, { diff --git a/src/sources/1embed.js b/src/sources/1embed.js deleted file mode 100644 index 835533e..0000000 --- a/src/sources/1embed.js +++ /dev/null @@ -1,115 +0,0 @@ -import { USER_AGENT } from '../utils/helpers.js'; - -const BASE_URL = "https://1embed.cc"; - -const PROVIDERS = [ - { id: "BUKE", name: "1Embed - BUKE" }, - { id: "YOTA", name: "1Embed - YOTA" }, - { id: "NEPT", name: "1Embed - NEPT" }, - { id: "AZURE", name: "1Embed - AZURE" }, - { id: "YORU", name: "1Embed - YORU" } -]; - -let cachedToken = null; -let tokenExpiresAt = 0; - -async function getToken() { - const now = Math.floor(Date.now() / 1000); - if (cachedToken && tokenExpiresAt > now + 10) return cachedToken; - try { - const res = await fetch(`${BASE_URL}/api/token`, { headers: { "Referer": `${BASE_URL}/`, "User-Agent": USER_AGENT, "Accept": "application/json" }, signal: AbortSignal.timeout(10000) }); - if (!res.ok) return null; - const data = await res.json(); - cachedToken = data.token; - tokenExpiresAt = data.expiresAt; - return cachedToken; - } catch { return null; } -} - -function decodeBsUrl(url) { - if (!url) return url; - let target = url; - if (url.includes('d=')) { - try { - const parts = url.split(/[?&]/); - for (const part of parts) { - if (part.startsWith('d=')) { - target = decodeURIComponent(part.substring(2)); - break; - } - } - } catch { } - } - if (target.startsWith('bs_')) { - try { - const decoded = Buffer.from(target.slice(3), 'base64').toString('utf-8'); - const reversed = decoded.split('').reverse().join(''); - if (reversed.startsWith('http')) return reversed; - } catch { } - } - return url; -} - -async function fetchProviderStream(provider, type, id, s, e, token) { - try { - const referer = type === "movie" ? `${BASE_URL}/embed/movie/${id}` : `${BASE_URL}/embed/tv/${id}/${s}/${e}`; - const payload = { type, id: String(id), provider: provider.id }; - if (type === "tv") { payload.season = Number(s); payload.episode = Number(e); } - const res = await fetch(`${BASE_URL}/api/sources`, { - method: "POST", - headers: { "User-Agent": USER_AGENT, "Referer": referer, "Origin": BASE_URL, "Accept": "application/json", "Content-Type": "application/json", "x-bcine-key": token, "X-Requested-With": "XMLHttpRequest" }, - body: JSON.stringify(payload), - signal: AbortSignal.timeout(15000) - }); - if (!res.ok) return null; - const data = await res.json(); - - let rawSources = []; - if (Array.isArray(data.servers)) rawSources.push(...data.servers); - if (data.providerResults?.[provider.id]?.sources) rawSources.push(...data.providerResults[provider.id].sources); - if (data.providerResults?.[provider.id] && Array.isArray(data.providerResults[provider.id])) rawSources.push(...data.providerResults[provider.id]); - - if (!rawSources.length) return null; - const results = []; - for (const src of rawSources) { - if (typeof src !== 'object' || !src) continue; - let rawUrl = src.url || src.file || src.link; - if (typeof rawUrl !== 'string') continue; - let streamUrl = decodeBsUrl(rawUrl); - if (streamUrl.startsWith('/')) streamUrl = `${BASE_URL}${streamUrl}`; - const isWorker = streamUrl.includes('omena-puu') || streamUrl.includes('nocach') || streamUrl.includes('?p='); - const isHls = src.type === 'hls' || src.type === 'm3u8' || streamUrl.includes('m3u8') || isWorker; - if (isHls && !streamUrl.includes('m3u8')) streamUrl += streamUrl.includes('?') ? '&format=.m3u8' : '?format=.m3u8'; - const srcHeaders = (src.headers && typeof src.headers === 'object') ? src.headers : {}; - results.push({ - url: streamUrl, - server: provider.name, - quality: src.quality || src.label || src.title || "Auto", - type: isHls ? "hls" : "mp4", - headers: { "User-Agent": USER_AGENT, "Referer": referer, "Origin": BASE_URL, ...srcHeaders }, - }); - } - return results; - } catch { - return null; - } -} - -export async function getStream(args) { - const { id, s, e, server } = args; - const type = s != null && e != null ? "tv" : "movie"; - const token = await getToken(); - if (!token) return null; - let targets = PROVIDERS; - if (server && server !== 'all') { - const cleanName = server.replace('1Embed - ', '').toLowerCase(); - targets = PROVIDERS.filter(p => p.id.toLowerCase() === cleanName || p.name.toLowerCase().includes(cleanName)); - if (!targets.length) targets = PROVIDERS; - } - const settled = await Promise.allSettled(targets.map(p => fetchProviderStream(p, type, id, s, e, token))); - 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 PROVIDERS.map(p => p.name); } \ No newline at end of file diff --git a/src/sources/flaxmovies.js b/src/sources/flaxmovies.js index 6d89602..39da95a 100644 --- a/src/sources/flaxmovies.js +++ b/src/sources/flaxmovies.js @@ -1,26 +1,118 @@ -import { fetchJson } from '../utils/helpers.js'; +import { USER_AGENT, fetchJson } from '../utils/helpers.js'; -export async function getStream({ id, s, e }) { +const BASE_URL = 'https://flaxmovies.xyz'; + +async function getWorkerUrls(embedUrl) { + const foundUrls = []; try { - const url = s && e - ? `https://sparkling-mode-a1ed.fillatame.workers.dev/?tmdb_id=${id}&season=${s}&episode=${e}` - : `https://sparkling-mode-a1ed.fillatame.workers.dev/?tmdb_id=${id}`; - const data = await fetchJson(url, { signal: AbortSignal.timeout(15000) }); - if (!data || !Array.isArray(data.streams)) return null; - const allUrls = []; - for (const stream of data.streams) { - const streamUrl = stream.url || stream.link; - if (!streamUrl) continue; - const isHls = stream.format === 'HLS' || stream.isM3U8 === true || streamUrl.includes('m3u8'); - allUrls.push({ - url: streamUrl, - server: stream.provider || 'FlaxMovies', - quality: stream.resolution || 'Auto', - type: isHls ? 'hls' : 'mp4' - }); + const res = await fetch(embedUrl, { + headers: { + 'User-Agent': USER_AGENT, + 'Referer': `${BASE_URL}/`, + 'Origin': BASE_URL + }, + signal: AbortSignal.timeout(8000) + }); + if (res.ok) { + const html = await res.text(); + const directMatch = html.match(/WORKER_URL\s*[:=]\s*["'](https?:\/\/[^"']+)["']/i); + if (directMatch && directMatch[1]) { + foundUrls.push(directMatch[1].replace(/\/+$/, '')); + } + + const scriptMatches = [...html.matchAll(/]+src=["']([^"']+)["']/gi)]; + for (const sm of scriptMatches) { + let scriptUrl = sm[1]; + if (scriptUrl.startsWith('//')) scriptUrl = `https:${scriptUrl}`; + else if (scriptUrl.startsWith('/')) scriptUrl = `${BASE_URL}${scriptUrl}`; + else if (!scriptUrl.startsWith('http')) scriptUrl = `${BASE_URL}/${scriptUrl}`; + + if (scriptUrl.includes('jsdelivr') || scriptUrl.includes('google') || scriptUrl.includes('cloudflare')) continue; + + try { + const sRes = await fetch(scriptUrl, { + headers: { 'User-Agent': USER_AGENT, 'Referer': embedUrl }, + signal: AbortSignal.timeout(6000) + }); + if (sRes.ok) { + const sCode = await sRes.text(); + const m = sCode.match(/WORKER_URL\s*[:=]\s*["'](https?:\/\/[^"']+)["']/i); + if (m && m[1]) { + const wUrl = m[1].replace(/\/+$/, ''); + if (!foundUrls.includes(wUrl)) foundUrls.push(wUrl); + } + } + } catch { } + } } - return allUrls.length ? { allUrls } : null; - } catch { - return null; + } catch { } + + const defaults = ['https://freakyniki.elaxo.lol', 'https://vidlove.nabilekson.workers.dev']; + for (const d of defaults) { + if (!foundUrls.includes(d)) foundUrls.push(d); } + return foundUrls; +} + +export async function getStream(args) { + const { id, s, e, server } = args; + const isTv = s != null && e != null; + const embedUrl = isTv + ? `${BASE_URL}/embed/tv/${id}/${s}/${e}` + : `${BASE_URL}/embed/movie/${id}`; + + const workerUrls = await getWorkerUrls(embedUrl); + const query = isTv + ? `tmdb_id=${id}&tmdbId=${id}&season=${s}&episode=${e}` + : `tmdb_id=${id}&tmdbId=${id}`; + + for (const worker of workerUrls) { + try { + const url = `${worker}/?${query}`; + const data = await fetchJson(url, { + headers: { + 'User-Agent': USER_AGENT, + 'Referer': `${BASE_URL}/`, + 'Origin': BASE_URL, + 'Accept': 'application/json, text/plain, */*' + }, + signal: AbortSignal.timeout(12000) + }); + + if (data && Array.isArray(data.streams) && data.streams.length > 0) { + let allUrls = data.streams + .filter(st => st && typeof st.url === 'string') + .map(st => { + const isHls = (st.format || '').toLowerCase().includes('hls') || st.url.includes('.m3u8'); + return { + url: st.url, + server: `FlaxMovies - ${st.provider || 'Default'}`, + quality: st.resolution || 'Auto', + type: isHls ? 'hls' : 'mp4', + headers: { + 'User-Agent': USER_AGENT, + 'Referer': `${BASE_URL}/`, + 'Origin': BASE_URL + } + }; + }); + + if (server && server !== 'all') { + const clean = server.toLowerCase().replace('flaxmovies - ', ''); + const filtered = allUrls.filter(u => u.server.toLowerCase().includes(clean)); + if (filtered.length) allUrls = filtered; + } + + if (allUrls.length) { + return { allUrls }; + } + } + } catch { } + } + + return null; +} + +export async function getSources() { + return ['FlaxMovies - Airflix', 'FlaxMovies - Xpass-VIP 1']; } \ No newline at end of file diff --git a/src/sources/frame.js b/src/sources/frame.js new file mode 100644 index 0000000..71320ee --- /dev/null +++ b/src/sources/frame.js @@ -0,0 +1,88 @@ +import { USER_AGENT, fetchJson } from '../utils/helpers.js'; + +const BASE_URL = 'https://api.peestream.in'; + +const PROVIDERS = [ + { id: 'vaplayer', name: 'FRAME Zephyr' }, + { id: 'castle', name: 'FRAME Atlas' }, + { id: 'hera', name: 'FRAME Luna' }, + { id: 'multivid', name: 'FRAME Volt' }, + { id: 'netmirror', name: 'FRAME Echo' }, + { id: 'vidsuper-castle', name: 'FRAME Rift' }, + { id: 'vidsuper-vixsrc', name: 'FRAME Quill' } +]; + +async function fetchProviderStreams(provider, type, id, s, e) { + try { + const queryParams = new URLSearchParams({ + q: String(id), + type, + provider: provider.id + }); + if (type === 'tv') { + queryParams.set('season', String(s)); + queryParams.set('episode', String(e)); + } + + const url = `${BASE_URL}/api/search?${queryParams.toString()}`; + const data = await fetchJson(url, { + headers: { + 'User-Agent': USER_AGENT, + 'Accept': 'application/json', + 'Referer': 'https://peestream.in/' + }, + signal: AbortSignal.timeout(12000) + }); + + if (!data || !Array.isArray(data.results) || !data.results.length) return []; + + const streams = []; + for (const result of data.results) { + for (const stream of result.streams || []) { + if (!stream || !stream.url) continue; + streams.push({ + url: stream.url, + server: provider.name, + quality: stream.quality || 'Auto', + type: stream.type === 'm3u8' || stream.url.includes('.m3u8') ? 'hls' : 'mp4', + headers: stream.headers || { + 'User-Agent': USER_AGENT + } + }); + } + } + return streams; + } catch { + return []; + } +} + +export async function getStream(args) { + const { id, s, e, server } = args; + const isTv = s != null && e != null; + const type = isTv ? 'tv' : 'movie'; + + let targets = PROVIDERS; + if (server && server !== 'all') { + const clean = server.toLowerCase().replace('frame ', '').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(p, type, id, s, e)) + ); + + 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 => p.name); +} \ No newline at end of file diff --git a/src/sources/mapple.js b/src/sources/mapple.js index 607d2eb..88185ca 100644 --- a/src/sources/mapple.js +++ b/src/sources/mapple.js @@ -76,13 +76,29 @@ export async function getStream(args) { const reqMatch = html.match(/window\.__REQUEST_TOKEN__\s*=\s*"([^"]+)"/); if (!reqMatch) return null; const requestToken = reqMatch[1]; - const initRes = await fetch(`${BASE_URL}/api/playback-init`, { method: "POST", headers: { "Content-Type": "application/json", ...PROXY_HEADERS, Referer: pageUrl }, body: JSON.stringify({ mediaId: Number(id), mediaType, requestToken }), signal: AbortSignal.timeout(10000) }); + const cookies = pageRes.headers.getSetCookie(); + const cookieHeader = cookies.map(c => c.split(';')[0]).join('; '); + + const initHeaders = { "Content-Type": "application/json", ...PROXY_HEADERS, Referer: pageUrl }; + if (cookieHeader) initHeaders.Cookie = cookieHeader; + + const initRes = await fetch(`${BASE_URL}/api/playback-init`, { + method: "POST", + headers: initHeaders, + body: JSON.stringify({ mediaId: Number(id), mediaType, requestToken }), + signal: AbortSignal.timeout(10000) + }); const initData = await initRes.json(); let streamToken = initData.token; if (initData.requiresPow && initData.pow) { const powRes = solvePoW(initData.pow.challenge, initData.pow.difficulty); if (powRes.found) { - const solveRes = await fetch(`${BASE_URL}/api/playback-init`, { method: "POST", headers: { "Content-Type": "application/json", ...PROXY_HEADERS, Referer: pageUrl }, body: JSON.stringify({ mediaId: Number(id), mediaType, requestToken, pow: { challengeId: initData.pow.challengeId, nonce: powRes.nonce } }), signal: AbortSignal.timeout(10000) }); + const solveRes = await fetch(`${BASE_URL}/api/playback-init`, { + method: "POST", + headers: initHeaders, + body: JSON.stringify({ mediaId: Number(id), mediaType, requestToken, pow: { challengeId: initData.pow.challengeId, nonce: powRes.nonce } }), + signal: AbortSignal.timeout(10000) + }); streamToken = (await solveRes.json()).token; } } @@ -96,8 +112,28 @@ export async function getStream(args) { const allUrls = []; for (const srv of targets) { try { - const streamUrl = `${BASE_URL}/api/stream?mediaId=${id}&mediaType=${mediaType}&tv_slug=${tvSlug}&source=${srv.id}&apikey=mptv_sk_a8f29c4e7b3d1f&requestToken=${requestToken}&token=${streamToken}`; - const streamRes = await fetch(streamUrl, { headers: { ...PROXY_HEADERS, Referer: pageUrl }, signal: AbortSignal.timeout(8000) }); + const encryptRes = await fetch(`${BASE_URL}/api/encrypt`, { + method: "POST", + headers: initHeaders, + body: JSON.stringify({ + data: { + mediaId: Number(id), + mediaType, + tv_slug: tvSlug, + source: srv.id, + apikey: "mptv_sk_a8f29c4e7b3d1f" + } + }), + signal: AbortSignal.timeout(8000) + }); + const encryptData = await encryptRes.json(); + if (!encryptData.encrypted) continue; + + const streamUrl = `${BASE_URL}/api/stream-encrypted?data=${encodeURIComponent(encryptData.encrypted)}&requestToken=${encodeURIComponent(requestToken)}&token=${encodeURIComponent(streamToken)}`; + const streamRes = await fetch(streamUrl, { + headers: { ...PROXY_HEADERS, Referer: pageUrl, ...(cookieHeader ? { Cookie: cookieHeader } : {}) }, + signal: AbortSignal.timeout(8000) + }); const streamData = await streamRes.json(); if (streamData.success && streamData.data?.stream_url) { let fileUrl = streamData.data.stream_url; diff --git a/src/sources/meowtv.js b/src/sources/meowtv.js index 36d0369..cc92a27 100644 --- a/src/sources/meowtv.js +++ b/src/sources/meowtv.js @@ -13,15 +13,7 @@ const HEADERS = { }; const SERVERS = [ - 'pseudo', - 'lynx', - 'tik', - 'ipcloud', - 'v4:English', - 'turkce', - 'v5:Hindi', - 'v4:Hindi', - 'v6:Hindi' + 'hindiv3' ]; const TIMEOUT_MS = 15000; diff --git a/src/sources/nekowatch.js b/src/sources/nekowatch.js deleted file mode 100644 index 29f321c..0000000 --- a/src/sources/nekowatch.js +++ /dev/null @@ -1,57 +0,0 @@ -import { fetchJson, fetchText, getTmdbInfo, tmdbToAnilist } from '../utils/helpers.js'; - -const BASE_URL = "https://nekowatch.xyz"; -const ANIME_JS_URL = `${BASE_URL}/js/anime.js?v=v263_fast_player_start`; -const FALLBACK_ORDER = ['kiwi', 'ally', 'bee', 'arc', 'anikoto', 'jet', 'bonk', 'moo', 'hop', 'pulsar', 'pahe']; - -let providerOrderCache = null, providerOrderTs = 0; - -async function getProviderOrder() { - const now = Date.now(); - if (providerOrderCache && now - providerOrderTs < 1800000) return providerOrderCache; - try { - const js = await fetchText(ANIME_JS_URL, { signal: AbortSignal.timeout(6000) }); - const match = js.match(/PROVIDER_ORDER\s*=\s*\[([\s\S]*?)\]/); - if (match) { - const order = [...match[1].matchAll(/['"]([a-z0-9_]+)['"]/gi)].map(m => m[1]); - if (order.length) { providerOrderCache = order; providerOrderTs = now; return order; } - } - } catch { } - return providerOrderCache || FALLBACK_ORDER; -} - -export async function getStream({ id, s, e, audio }) { - try { - const info = await getTmdbInfo(id, s ? 'tv' : 'movie', s); - if (!info || !info.isAnime) return null; - const anilistId = await tmdbToAnilist(id, s ? 'tv' : 'movie', s, info.titles, info.year); - if (!anilistId) return null; - const ep = e || 1; - const audiosToTry = audio === "all" ? ["sub", "dub"] : (audio === "dub" ? ["dub", "sub"] : ["sub", "dub"]); - const providerOrder = await getProviderOrder(); - for (const aud of audiosToTry) { - const results = await Promise.allSettled(providerOrder.map(p => fetchJson(`${BASE_URL}/api/anime/watch/${p}/${anilistId}/${aud}/${p}-${ep}`, { signal: AbortSignal.timeout(8000) }))); - const allUrls = [], seenActual = new Set(); - for (const r of results) { - if (r.status !== 'fulfilled' || !r.value) continue; - const data = r.value; - let streams = [], actual = data.attempted?.actualProvider || data.provider; - if (Array.isArray(data.streams)) streams = data.streams; - else { - for (const key of Object.keys(data)) if (data[key] && Array.isArray(data[key].streams)) { streams.push(...data[key].streams); if (!actual) actual = data[key].provider || key; } - } - if (!streams.length) continue; - if (actual) { if (seenActual.has(actual)) continue; seenActual.add(actual); } - const valid = streams.filter(st => st.type === "hls" && st.url).sort((a, b) => (b.priority || 0) - (a.priority || 0)); - for (const st of valid) allUrls.push({ url: st.url, type: "hls", audio: aud, server: st.server ? `NekoWatch-${st.server}` : "NekoWatch", headers: st.referer ? { Referer: st.referer } : undefined, skipProxy: /\.workers\.dev(\/|$)/i.test(st.url) }); - } - if (allUrls.length) return { allUrls }; - } - return null; - } catch { return null; } -} - -export async function getSources(args) { - const stream = await getStream(args); - return stream?.allUrls ? [...new Set(stream.allUrls.map(u => u.server))] : []; -} \ No newline at end of file diff --git a/src/sources/opstream.js b/src/sources/opstream.js deleted file mode 100644 index 3103546..0000000 --- a/src/sources/opstream.js +++ /dev/null @@ -1,123 +0,0 @@ -import { getTmdbInfo, USER_AGENT } from '../utils/helpers.js'; - -const BASE_URL = 'https://opstream.fun'; -const API_BASE = `${BASE_URL}/api/resolve`; - -const API_HEADERS = { - 'User-Agent': USER_AGENT, - 'Accept': 'application/x-ndjson; charset=utf-8', - 'Referer': `${BASE_URL}/`, - 'Origin': BASE_URL, -}; - -const STREAM_HEADERS = { - 'User-Agent': USER_AGENT, - 'Referer': `${BASE_URL}/`, - 'Origin': BASE_URL, -}; - -export async function getStream({ id, s, e }) { - try { - const isTv = s != null && e != null; - const info = await getTmdbInfo(id, isTv ? 'tv' : 'movie'); - - if (!info?.titles?.length || !info.imdbId) return null; - - const params = new URLSearchParams({ - type: isTv ? 'tv' : 'movie', - tmdbId: id, - title: info.titles[0], - year: info.year || '', - imdbId: info.imdbId, - progress: '1' - }); - - if (isTv) { - params.append('season', s); - params.append('episode', e); - } - - const res = await fetch(`${API_BASE}?${params}`, { - headers: API_HEADERS, - signal: AbortSignal.timeout(15000) - }); - - if (!res.ok) return null; - - const text = await res.text(); - const lines = text.split('\n'); - - let streamData = null; - for (const line of lines) { - const trimmed = line.trim(); - if (!trimmed) continue; - try { - const json = JSON.parse(trimmed); - if (json.t === 'done' && json.data) { - streamData = json.data; - break; - } - } catch { continue; } - } - - if (!streamData) return null; - - const subtitles = (streamData.captions || []).map(c => ({ - url: c.url.startsWith('http') ? c.url : `${BASE_URL}${c.url}`, - lang: c.label || 'Unknown' - })); - - const allUrls = []; - - if (streamData.qualities && streamData.qualities.length > 0) { - for (const q of streamData.qualities) { - let finalUrl = q.url; - if (!finalUrl.startsWith('http')) { - finalUrl = `${BASE_URL}${finalUrl}`; - } - - const isDash = finalUrl.includes('.mpd'); - const isHls = finalUrl.includes('.m3u8'); - - allUrls.push({ - url: finalUrl, - server: `OpStream - ${q.label}`, - type: isDash ? 'dash' : (isHls ? 'hls' : 'mp4'), - headers: STREAM_HEADERS, - subtitles, - skipProxy: true - }); - } - } else if (streamData.url) { - let finalUrl = streamData.url; - if (finalUrl.includes('?u=')) { - try { - const uParam = new URL(finalUrl, BASE_URL).searchParams.get('u'); - if (uParam) finalUrl = Buffer.from(uParam, 'base64').toString('utf8'); - } catch {} - } else if (!finalUrl.startsWith('http')) { - finalUrl = `${BASE_URL}${finalUrl}`; - } - - const isDash = streamData.kind === 'dash' || finalUrl.includes('.mpd'); - const isHls = streamData.kind === 'hls' || finalUrl.includes('.m3u8'); - - allUrls.push({ - url: finalUrl, - server: 'OpStream', - type: isDash ? 'dash' : (isHls ? 'hls' : 'mp4'), - headers: STREAM_HEADERS, - subtitles, - skipProxy: true - }); - } - - if (allUrls.length === 0) return null; - - return { allUrls }; - } catch { return null; } -} - -export async function getSources() { - return ['OpStream']; -} \ No newline at end of file diff --git a/src/sources/peachify.js b/src/sources/peachify.js deleted file mode 100644 index 1bc201e..0000000 --- a/src/sources/peachify.js +++ /dev/null @@ -1,78 +0,0 @@ -import { fetchJson, USER_AGENT } from '../utils/helpers.js'; - -const DEC_API = "https://enc-dec.app/api/dec-peachify"; -const ORIGIN = "https://peachify.top"; -const HEADERS = { "User-Agent": USER_AGENT, "Origin": ORIGIN, "Referer": `${ORIGIN}/` }; - -const SERVERS = [ - { label: "Wolf", path: "air", api: "https://usa.eat-peach.sbs" }, - { label: "Spider", path: "holly", api: "https://usa.eat-peach.sbs" }, - { label: "Iron", path: "moviebox", api: "https://uwu.eat-peach.sbs" }, - { label: "Multi", path: "multi", api: "https://usa.eat-peach.sbs" }, - { label: "Dark", path: "net", api: "https://uwu.eat-peach.sbs" }, -]; - -export async function getStream({ id, s, e, server }) { - try { - const isTv = s != null && e != null; - const segment = isTv ? `tv/${id}/${s}/${e}` : `movie/${id}`; - - let targets = SERVERS; - if (server && server !== 'all') { - const cleanName = server.replace('Peachify - ', ''); - targets = SERVERS.filter(srv => srv.label === cleanName); - if (!targets.length) targets = SERVERS; - } - - const settled = await Promise.allSettled(targets.map(async srv => { - const url = `${srv.api}/${srv.path}/${segment}`; - const rawData = await fetchJson(url, { headers: HEADERS, signal: AbortSignal.timeout(10000) }); - - if (!rawData?.data) throw new Error(); - - const decJson = await fetchJson(DEC_API, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ text: rawData.data }), - signal: AbortSignal.timeout(10000) - }); - - if (decJson.status !== 200 || !decJson.result) throw new Error(); - const res = decJson.result; - - const sources = Array.isArray(res) ? res : (res.sources || [res]); - const subs = res.subtitles || []; - - return sources.map(source => { - const streamUrl = source.url || source.file || source.link; - if (!streamUrl) return null; - - return { - url: streamUrl, - server: `Peachify - ${srv.label}`, - quality: source.quality || "Auto", - type: streamUrl.includes('.m3u8') ? 'hls' : 'mp4', - headers: { ...HEADERS, "Origin": ORIGIN }, - subtitles: subs.map(sub => ({ - url: sub.url || sub.file, - lang: sub.language || sub.lang || sub.label || 'Unknown' - })), - skipProxy: false, - skipVerify: true, - skipHlsCheck: true - }; - }).filter(Boolean); - })); - - const allUrls = []; - for (const r of settled) { - if (r.status === 'fulfilled' && r.value) allUrls.push(...r.value); - } - - return allUrls.length ? { allUrls } : null; - } catch { return null; } -} - -export async function getSources() { - return SERVERS.map(s => `Peachify - ${s.label}`); -} \ No newline at end of file diff --git a/src/sources/peestream.js b/src/sources/peestream.js new file mode 100644 index 0000000..643e477 --- /dev/null +++ b/src/sources/peestream.js @@ -0,0 +1,122 @@ +import { USER_AGENT, fetchJson, getTmdbInfo } from '../utils/helpers.js'; + +const BASE_URL = 'https://providers.peestream.in'; + +export async function getStream(args) { + const { id, s, e, server, tmdbApiKey } = args; + const isTv = s != null && e != null; + const type = isTv ? 'tv' : 'movie'; + + let title = args.title || ''; + let releaseYear = args.year || ''; + let imdbId = args.imdbId || ''; + + if (!title) { + try { + const info = await getTmdbInfo(id, type, s, tmdbApiKey); + if (info) { + title = info.title || ''; + if (info.year) releaseYear = info.year; + if (info.imdbId) imdbId = info.imdbId; + } + } catch { } + } + + const queryParams = new URLSearchParams({ + type, + tmdbId: String(id), + title: title || String(id) + }); + if (releaseYear) queryParams.set('releaseYear', String(releaseYear)); + if (imdbId) queryParams.set('imdbId', String(imdbId)); + if (isTv) { + queryParams.set('season', String(s)); + queryParams.set('episode', String(e)); + } + + const allUrls = []; + + try { + const scrapeUrl = `${BASE_URL}/scrape?${queryParams.toString()}`; + const res = await fetch(scrapeUrl, { + headers: { + 'User-Agent': USER_AGENT, + 'Accept': 'text/event-stream', + 'Referer': `${BASE_URL}/` + }, + signal: AbortSignal.timeout(15000) + }); + + if (res.ok) { + const text = await res.text(); + const events = text.split('\n\n'); + for (const ev of events) { + if (ev.includes('event: completed')) { + const dataMatch = ev.match(/data:\s*(.+)/); + if (dataMatch) { + try { + const parsed = JSON.parse(dataMatch[1].trim()); + const stream = parsed.stream; + const streamUrl = stream?.playlist || stream?.url || stream?.file; + if (streamUrl) { + allUrls.push({ + url: streamUrl, + server: `PeeStream - ${parsed.sourceId || 'Poseidon'}`, + quality: 'Auto', + type: stream.type === 'hls' || streamUrl.includes('.m3u8') ? 'hls' : 'mp4', + headers: stream.headers || { + 'User-Agent': USER_AGENT + } + }); + } + } catch { } + } + } + } + } + } catch { } + + if (!allUrls.length) { + try { + const searchUrl = `${BASE_URL}/api/search?q=${encodeURIComponent(title || id)}&type=${type}&tmdbId=${id}${isTv ? `&season=${s}&episode=${e}` : ''}`; + const data = await fetchJson(searchUrl, { + headers: { + 'User-Agent': USER_AGENT, + 'Accept': 'application/json', + 'Referer': `${BASE_URL}/` + }, + signal: AbortSignal.timeout(10000) + }); + + if (data && Array.isArray(data.results)) { + for (const result of data.results) { + const pName = result.providerName || result.provider || 'PeeStream'; + for (const stream of result.streams || []) { + if (!stream || !stream.url) continue; + allUrls.push({ + url: stream.url, + server: `PeeStream - ${stream.name || pName}`, + quality: stream.quality || 'Auto', + type: stream.type === 'm3u8' || stream.url.includes('.m3u8') ? 'hls' : 'mp4', + headers: stream.headers || { + 'User-Agent': USER_AGENT + } + }); + } + } + } + } catch { } + } + + if (server && server !== 'all') { + const clean = server.toLowerCase().replace('peestream - ', ''); + const filtered = allUrls.filter(u => u.server.toLowerCase().includes(clean)); + if (filtered.length) return { allUrls: filtered }; + } + + return allUrls.length ? { allUrls } : null; +} + +export async function getSources() { + return ['PeeStream - vaplayer']; +} \ No newline at end of file diff --git a/test/index.js b/test/index.js index ed48125..debf4da 100644 --- a/test/index.js +++ b/test/index.js @@ -18,7 +18,7 @@ const CONFIG = { TEST_SUBTITLE: false, // Toggle subtitle testing TEST_DOWNLOAD: false, // Toggle download testing - TEST_MOVIE: false, // Toggle movie testing + TEST_MOVIE: true, // Toggle movie testing TEST_TV: true // Toggle TV testing };