diff --git a/src/config.js b/src/config.js index 1ce50e0..10fac1a 100644 --- a/src/config.js +++ b/src/config.js @@ -956,6 +956,17 @@ export const SOURCES = [ multiUrl: true, }, + { + key: 'youplex', + label: 'YouPlex', + sourceFile: 'youplex', + proxyParam: 'ypx', + timeout: 30000, + jitter: 500, + retries: 1, + multiUrl: true, + }, + ]; export const HEALTH_PROBE_ID = '155'; diff --git a/src/sources/youplex.js b/src/sources/youplex.js new file mode 100644 index 0000000..f5758f2 --- /dev/null +++ b/src/sources/youplex.js @@ -0,0 +1,103 @@ +import { USER_AGENT } from '../utils/helpers.js'; + +const API_BASE = 'https://api.youplex.site'; + +const PROVIDERS = [ + 'taroflix', + 'cinemaos', + 'cno', + 'cinejoy', + 'oneshows', + 'cinecat', + 'pressplayz', + 'arrowtv', + 'rivestream', + 'kaizoku', + 'chillflix', + 'zetmoon', + 'vidcore', +]; + +const PLAYBACK_HEADERS = { + 'User-Agent': USER_AGENT, +}; + +function buildQuery(id, isTv, s, e, provider) { + const q = new URLSearchParams({ + id: String(id), + type: isTv ? 'tv' : 'movie', + provider, + }); + if (isTv) { + q.set('s', String(s)); + q.set('e', String(e)); + } + return q; +} + +function normalizeQuality(q) { + if (!q || q === 'Auto') return 'Auto'; + const str = String(q).trim(); + return /^\d+$/.test(str) ? `${str}p` : str; +} + +export async function getStream(args) { + const { id, s, e, config } = args || {}; + if (!id) return null; + + const isTv = s != null && e != null; + const providers = config?.provider ? [config.provider, ...PROVIDERS.filter(p => p !== config.provider)] : PROVIDERS; + + for (const provider of providers) { + try { + const url = `${API_BASE}/scrape?${buildQuery(id, isTv, s, e, provider).toString()}`; + const res = await fetch(url, { + headers: { Accept: 'application/json', 'User-Agent': USER_AGENT }, + signal: AbortSignal.timeout(config?.timeout || 25000), + }); + + if (!res.ok) continue; + + const data = await res.json(); + if (!data || data.success !== true) continue; + + const allUrls = []; + const seen = new Set(); + + if (Array.isArray(data.sources) && data.sources.length > 0) { + for (const src of data.sources) { + if (!src?.url || seen.has(src.url)) continue; + seen.add(src.url); + allUrls.push({ + url: src.url, + server: `YouPlex - ${src.name || provider}`, + quality: normalizeQuality(src.quality), + type: src.url.includes('.m3u8') ? 'hls' : src.url.includes('.mpd') ? 'dash' : 'mp4', + headers: PLAYBACK_HEADERS, + }); + } + } else if (typeof data.url === 'string' && data.url.startsWith('http')) { + seen.add(data.url); + allUrls.push({ + url: data.url, + server: `YouPlex - ${provider}`, + quality: 'Auto', + type: data.url.includes('.m3u8') ? 'hls' : data.url.includes('.mpd') ? 'dash' : 'mp4', + headers: PLAYBACK_HEADERS, + }); + } + + if (!allUrls.length) continue; + + return { + url: allUrls[0].url, + headers: allUrls[0].headers, + allUrls, + }; + } catch { + continue; + } + } + + return null; +} \ No newline at end of file