diff --git a/src/config.js b/src/config.js index 83ad820..4a7c483 100644 --- a/src/config.js +++ b/src/config.js @@ -94,6 +94,25 @@ export const SOURCES = [ }], }, + { + key: 'braflix', + label: 'Braflix', + sourceFile: 'braflix', + proxyParam: 'bf', + timeout: 25000, + jitter: 500, + retries: 2, + multiUrl: true, + skipProxy: true, + cdnHeaders: [{ + pattern: /braflix\.dev/i, + headers: { + Referer: 'https://braflix.dev/', + Origin: 'https://braflix.dev', + }, + }], + }, + { key: 'castle', label: 'Castle', @@ -725,6 +744,25 @@ export const SOURCES = [ multiUrl: true, }, + { + key: 'zenox', + label: 'Zenox', + sourceFile: 'zenox', + proxyParam: 'zx', + timeout: 25000, + jitter: 500, + retries: 2, + multiUrl: true, + skipProxy: true, + cdnHeaders: [{ + pattern: /zenox\.lol|hls-m3u8\.lol/i, + headers: { + Referer: 'https://zenox.lol/', + Origin: 'https://zenox.lol', + }, + }], + }, + ]; export const HEALTH_PROBE_ID = '155'; diff --git a/src/sources/braflix.js b/src/sources/braflix.js new file mode 100644 index 0000000..9faff58 --- /dev/null +++ b/src/sources/braflix.js @@ -0,0 +1,80 @@ +const BASE_URL = 'https://braflix.dev'; +const UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36'; + +const HEADERS = { + Origin: BASE_URL, + Referer: `${BASE_URL}/`, + 'User-Agent': UA, + Accept: 'application/json, text/plain, */*', +}; + +const PLAYBACK_HEADERS = { + Origin: BASE_URL, + Referer: `${BASE_URL}/`, +}; + +export async function getStream(args) { + const { id, s, e } = args || {}; + const idNum = parseInt(id, 10); + if (!idNum) return null; + + const isTv = s != null && e != null; + const targetSeason = isTv ? (s || 1) : 1; + const targetEpisode = isTv ? (e || 1) : 1; + + const path = isTv + ? `/api/play/tv/${idNum}/${targetSeason}/${targetEpisode}` + : `/api/play/movie/${idNum}`; + + try { + const res = await fetch(`${BASE_URL}${path}`, { + headers: HEADERS, + signal: AbortSignal.timeout(10000), + }); + if (!res.ok) return null; + const data = await res.json(); + + if (data?.status !== 'ready' || !data.result?.file?.stream) { + return null; + } + + const rawStreamPath = data.result.file.stream; + const fullStreamUrl = rawStreamPath.startsWith('http') + ? rawStreamPath + : `${BASE_URL}${rawStreamPath}`; + + const qualityRaw = String(data.result.source?.quality || '1080,720,480'); + const qualityParts = qualityRaw.split(',').map(q => q.trim()).filter(Boolean); + const maxQ = qualityParts.reduce((acc, curr) => { + const num = parseInt(curr, 10); + return num > acc ? num : acc; + }, 0); + + const quality = maxQ > 0 ? `${maxQ}p` : 'Auto'; + const qualitySummary = qualityParts.length > 0 + ? qualityParts.map(q => `${q}p`).join('/') + : 'DASH'; + + const streamType = fullStreamUrl.includes('.mpd') + ? 'dash' + : fullStreamUrl.includes('.m3u8') + ? 'hls' + : 'dash'; + + const allUrls = [{ + url: fullStreamUrl, + server: `Braflix - DASH (${qualitySummary})`, + quality, + type: streamType, + headers: PLAYBACK_HEADERS, + }]; + + return { + url: fullStreamUrl, + headers: PLAYBACK_HEADERS, + allUrls, + }; + } catch { + return null; + } +} \ No newline at end of file diff --git a/src/sources/zenox.js b/src/sources/zenox.js new file mode 100644 index 0000000..7094432 --- /dev/null +++ b/src/sources/zenox.js @@ -0,0 +1,108 @@ +const BASE_URL = 'https://zenox.lol'; +const API_URL = `${BASE_URL}/api/v1/playback/session`; +const UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36'; + +const HEADERS = { + 'Content-Type': 'application/json', + Origin: BASE_URL, + Referer: `${BASE_URL}/`, + 'User-Agent': UA, +}; + +const PLAYBACK_HEADERS = { + Origin: BASE_URL, + Referer: `${BASE_URL}/`, +}; + +async function fetchServerStream(payload, srv) { + try { + const res = await fetch(API_URL, { + method: 'POST', + headers: HEADERS, + body: JSON.stringify({ + ...payload, + serverIndex: srv.index, + serverName: srv.name, + }), + signal: AbortSignal.timeout(6000), + }); + if (!res.ok) return null; + const data = await res.json(); + if ( + !data?.success || + !data?.streamUrl || + data.streamUrl.includes('/primary/') || + data.serverName?.toLowerCase() === 'primary' + ) { + return null; + } + + const streamUrl = data.streamUrl.startsWith('http') + ? data.streamUrl + : `${BASE_URL}${data.streamUrl}`; + + return { + url: streamUrl, + server: `Zenox - ${data.serverName || srv.name}`, + quality: 'Auto', + type: streamUrl.includes('.m3u8') ? 'hls' : (streamUrl.includes('.mpd') ? 'dash' : 'mp4'), + headers: PLAYBACK_HEADERS, + }; + } catch { + return null; + } +} + +export async function getStream(args) { + const { id, s, e, title, year, imdbId } = args || {}; + const idNum = parseInt(id, 10); + if (!idNum) return null; + + const isTv = s != null && e != null; + const payload = { + mediaId: idNum, + mediaType: isTv ? 'tv' : 'movie', + season: isTv ? (parseInt(s, 10) || 1) : 1, + episode: isTv ? (parseInt(e, 10) || 1) : 1, + title: title || '', + year: String(year || ''), + imdbId: imdbId || '', + autoUseCommunity: false, + isManualSwitch: true, + }; + + const activeServers = [ + { name: 'Vbot', index: 1 }, + { name: 'Temporary', index: 1 }, + ]; + + const results = await Promise.allSettled( + activeServers.map(srv => fetchServerStream(payload, srv)) + ); + + const allUrls = []; + const seen = new Set(); + + for (const r of results) { + if (r.status === 'fulfilled' && r.value?.url && !seen.has(r.value.url)) { + seen.add(r.value.url); + allUrls.push(r.value); + } + } + + if (!allUrls.length) { + const delta = await fetchServerStream(payload, { name: 'Delta', index: 0 }); + if (delta?.url && !seen.has(delta.url)) { + seen.add(delta.url); + allUrls.push(delta); + } + } + + if (!allUrls.length) return null; + + return { + url: allUrls[0].url, + headers: allUrls[0].headers, + allUrls, + }; +} \ No newline at end of file