From 6b51a76ed4525123bdab524e5168a106433a8360 Mon Sep 17 00:00:00 2001 From: alwaysmesmerizingyou Date: Wed, 11 Nov 2026 19:40:25 -0800 Subject: [PATCH] added mapple, mysterious and vidqueen --- src/config.js | 60 +++++- src/sources/mapple.js | 430 ++++++++++++++++++++++++++++++++++++++ src/sources/mysterious.js | 113 ++++++++++ src/sources/vidqueen.js | 138 ++++++++++++ 4 files changed, 738 insertions(+), 3 deletions(-) create mode 100644 src/sources/mapple.js create mode 100644 src/sources/mysterious.js create mode 100644 src/sources/vidqueen.js diff --git a/src/config.js b/src/config.js index e80cd47..210bf14 100644 --- a/src/config.js +++ b/src/config.js @@ -50,7 +50,7 @@ export const SOURCES = [ Referer: 'https://play2.echovideo.ru/', Origin: 'https://play2.echovideo.ru', }, - },], + }], }, { @@ -438,6 +438,24 @@ export const SOURCES = [ }, }, + { + key: 'mapple', + label: 'Mapple', + sourceFile: 'mapple', + proxyParam: 'mp', + timeout: 25000, + jitter: 500, + retries: 2, + multiUrl: true, + cdnHeaders: [{ + pattern: /mapple\.fun/i, + headers: { + Referer: 'https://mapple.fun/', + Origin: 'https://mapple.fun', + }, + }], + }, + { key: 'meowtv', label: 'MeowTV', @@ -520,6 +538,24 @@ export const SOURCES = [ }], }, + { + key: 'mysterious', + label: 'Mysterious', + sourceFile: 'mysterious', + proxyParam: 'mys', + timeout: 20000, + jitter: 500, + retries: 2, + multiUrl: true, + cdnHeaders: [{ + pattern: /speedoflight\.buzz|febbox\.com/i, + headers: { + Referer: 'https://atlantic.speedoflight.buzz/', + Origin: 'https://atlantic.speedoflight.buzz', + }, + }], + }, + { key: 'net79', label: 'Net79', @@ -709,7 +745,7 @@ export const SOURCES = [ timeout: 25000, jitter: 500, retries: 1, - multiUrl: true + multiUrl: true, }, { @@ -849,6 +885,24 @@ export const SOURCES = [ }], }, + { + key: 'vidqueen', + label: 'Vidqueen', + sourceFile: 'vidqueen', + proxyParam: 'vq', + timeout: 25000, + jitter: 500, + retries: 2, + multiUrl: true, + cdnHeaders: [{ + pattern: /vidqueen\.st|surrealviews\.com/i, + headers: { + Referer: 'https://vidqueen.st/', + Origin: 'https://vidqueen.st', + }, + }], + }, + { key: 'vidspark', label: 'VidSpark', @@ -875,7 +929,7 @@ export const SOURCES = [ timeout: 35000, jitter: 500, retries: 1, - multiUrl: true + multiUrl: true, }, { diff --git a/src/sources/mapple.js b/src/sources/mapple.js new file mode 100644 index 0000000..a82fd4c --- /dev/null +++ b/src/sources/mapple.js @@ -0,0 +1,430 @@ +const BASE_URL = 'https://mapple.fun'; +const DSKTP_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 MBILE_UA = 'Mozilla/5.0 (Linux; Android 10) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36'; + +const SERVERS = [ + { name: 'Zeus', source: 'mapple' }, + { name: 'Poseidon', source: 's25' }, + { name: 'Aphrodite', source: 's6' }, + { name: 'Hephaestus', source: 's15' }, + { name: 'Prometheus', source: 's10' }, + { name: 'Atlas', source: 's1' } +]; + +const SHA256_K = new Int32Array([ + 1116352408, 1899447441, 3049323471, 3921009573, 961987163, 1508970993, 2453635748, 2870763221, + 3624381080, 310598401, 607225278, 1426881987, 1925078388, 2162078206, 2614888103, 3248222580, + 3835390401, 4022224774, 264347078, 604807628, 770255983, 1249150122, 1555081692, 1996064986, + 2554220882, 2821834349, 2952996808, 3210313671, 3336571891, 3584528711, 113926993, 338241895, + 666307205, 773529912, 1294757372, 1396182291, 1695183700, 1986661051, 2177026350, 2456956037, + 2730485921, 2820302411, 3259730800, 3345764771, 3516065817, 3600352804, 4094571909, 275423344, + 430227734, 506948616, 659060556, 883997877, 958139571, 1322822218, 1537002063, 1747873779, + 1955562222, 2024104815, 2227730452, 2361852424, 2428436474, 2756734187, 3204031479, 3329325298 +]); + +const SHA256_IS = new Int32Array([ + 1779033703, 3144134277, 1013904242, 2773480762, 1359893119, 2600822924, 528734635, 1541459225 +]); + +function sha256Compress(state, schedule, data, offset) { + let i, pos, a, b, c, d, e, f, g, h, temp1, sum0, sum1, choice, majority, w15, w2; + for (i = 0; i < 16; i++) { + pos = offset + i * 4; + schedule[i] = (data[pos] << 24) | (data[pos + 1] << 16) | (data[pos + 2] << 8) | data[pos + 3]; + } + for (i = 16; i < 64; i++) { + w15 = schedule[i - 15]; + w2 = schedule[i - 2]; + schedule[i] = (schedule[i - 16] + (((w15 >>> 7) | (w15 << 25)) ^ ((w15 >>> 18) | (w15 << 14)) ^ (w15 >>> 3)) + + schedule[i - 7] + (((w2 >>> 17) | (w2 << 15)) ^ ((w2 >>> 19) | (w2 << 13)) ^ (w2 >>> 10))) | 0; + } + a = state[0]; b = state[1]; c = state[2]; d = state[3]; + e = state[4]; f = state[5]; g = state[6]; h = state[7]; + for (i = 0; i < 64; i += 8) { + sum1 = (((e >>> 6) | (e << 26)) ^ ((e >>> 11) | (e << 21)) ^ ((e >>> 25) | (e << 7))); + choice = (e & f) ^ (~e & g); + temp1 = (h + sum1 + choice + SHA256_K[i + 0] + schedule[i + 0]) | 0; + sum0 = (((a >>> 2) | (a << 30)) ^ ((a >>> 13) | (a << 19)) ^ ((a >>> 22) | (a << 10))); + majority = (a & b) ^ (a & c) ^ (b & c); + d = (d + temp1) | 0; + h = (temp1 + sum0 + majority) | 0; + + sum1 = (((d >>> 6) | (d << 26)) ^ ((d >>> 11) | (d << 21)) ^ ((d >>> 25) | (d << 7))); + choice = (d & e) ^ (~d & f); + temp1 = (g + sum1 + choice + SHA256_K[i + 1] + schedule[i + 1]) | 0; + sum0 = (((h >>> 2) | (h << 30)) ^ ((h >>> 13) | (h << 19)) ^ ((h >>> 22) | (h << 10))); + majority = (h & a) ^ (h & b) ^ (a & b); + c = (c + temp1) | 0; + g = (temp1 + sum0 + majority) | 0; + + sum1 = (((c >>> 6) | (c << 26)) ^ ((c >>> 11) | (c << 21)) ^ ((c >>> 25) | (c << 7))); + choice = (c & d) ^ (~c & e); + temp1 = (f + sum1 + choice + SHA256_K[i + 2] + schedule[i + 2]) | 0; + sum0 = (((g >>> 2) | (g << 30)) ^ ((g >>> 13) | (g << 19)) ^ ((g >>> 22) | (g << 10))); + majority = (g & h) ^ (g & a) ^ (h & a); + b = (b + temp1) | 0; + f = (temp1 + sum0 + majority) | 0; + + sum1 = (((b >>> 6) | (b << 26)) ^ ((b >>> 11) | (b << 21)) ^ ((b >>> 25) | (b << 7))); + choice = (b & c) ^ (~b & d); + temp1 = (e + sum1 + choice + SHA256_K[i + 3] + schedule[i + 3]) | 0; + sum0 = (((f >>> 2) | (f << 30)) ^ ((f >>> 13) | (f << 19)) ^ ((f >>> 22) | (f << 10))); + majority = (f & g) ^ (f & h) ^ (g & h); + a = (a + temp1) | 0; + e = (temp1 + sum0 + majority) | 0; + + sum1 = (((a >>> 6) | (a << 26)) ^ ((a >>> 11) | (a << 21)) ^ ((a >>> 25) | (a << 7))); + choice = (a & b) ^ (~a & c); + temp1 = (d + sum1 + choice + SHA256_K[i + 4] + schedule[i + 4]) | 0; + sum0 = (((e >>> 2) | (e << 30)) ^ ((e >>> 13) | (e << 19)) ^ ((e >>> 22) | (e << 10))); + majority = (e & f) ^ (e & g) ^ (f & g); + h = (h + temp1) | 0; + d = (temp1 + sum0 + majority) | 0; + + sum1 = (((h >>> 6) | (h << 26)) ^ ((h >>> 11) | (h << 21)) ^ ((h >>> 25) | (h << 7))); + choice = (h & a) ^ (~h & b); + temp1 = (c + sum1 + choice + SHA256_K[i + 5] + schedule[i + 5]) | 0; + sum0 = (((d >>> 2) | (d << 30)) ^ ((d >>> 13) | (d << 19)) ^ ((d >>> 22) | (d << 10))); + majority = (d & e) ^ (d & f) ^ (e & f); + g = (g + temp1) | 0; + c = (temp1 + sum0 + majority) | 0; + + sum1 = (((g >>> 6) | (g << 26)) ^ ((g >>> 11) | (g << 21)) ^ ((g >>> 25) | (g << 7))); + choice = (g & h) ^ (~g & a); + temp1 = (b + sum1 + choice + SHA256_K[i + 6] + schedule[i + 6]) | 0; + sum0 = (((c >>> 2) | (c << 30)) ^ ((c >>> 13) | (c << 19)) ^ ((c >>> 22) | (c << 10))); + majority = (c & d) ^ (c & e) ^ (d & e); + f = (f + temp1) | 0; + b = (temp1 + sum0 + majority) | 0; + + sum1 = (((f >>> 6) | (f << 26)) ^ ((f >>> 11) | (f << 21)) ^ ((f >>> 25) | (f << 7))); + choice = (f & g) ^ (~f & h); + temp1 = (a + sum1 + choice + SHA256_K[i + 7] + schedule[i + 7]) | 0; + sum0 = (((b >>> 2) | (b << 30)) ^ ((b >>> 13) | (b << 19)) ^ ((b >>> 22) | (b << 10))); + majority = (b & c) ^ (b & d) ^ (c & d); + e = (e + temp1) | 0; + a = (temp1 + sum0 + majority) | 0; + } + state[0] = (state[0] + a) | 0; state[1] = (state[1] + b) | 0; + state[2] = (state[2] + c) | 0; state[3] = (state[3] + d) | 0; + state[4] = (state[4] + e) | 0; state[5] = (state[5] + f) | 0; + state[6] = (state[6] + g) | 0; state[7] = (state[7] + h) | 0; +} + +function hasLeadingZeroBits(state, requiredBits) { + let remaining = requiredBits; + for (let i = 0; i < 8; i++) { + if (remaining <= 0) return true; + if (state[i] === 0) { + remaining -= 32; + continue; + } + return Math.clz32(state[i]) >= remaining; + } + return true; +} + +function solveProofOfWork(challenge, difficulty) { + const prefix = new TextEncoder().encode(String(challenge)); + const prefixLength = prefix.length; + const state = new Int32Array(8); + const midState = new Int32Array(8); + const schedule = new Int32Array(64); + const firstDynamicBlock = Math.floor(prefixLength / 64); + + for (let digits = 1; digits <= 8; digits++) { + const rangeStart = digits === 1 ? 0 : Math.pow(10, digits - 1); + const rangeEnd = Math.min(Math.pow(10, digits), 20000000); + const messageLength = prefixLength + digits; + const paddedLength = Math.ceil((messageLength + 9) / 64) * 64; + const buffer = new Uint8Array(paddedLength); + buffer.set(prefix, 0); + buffer[messageLength] = 128; + const bitLength = messageLength * 8; + buffer[paddedLength - 4] = (bitLength >>> 24) & 255; + buffer[paddedLength - 3] = (bitLength >>> 16) & 255; + buffer[paddedLength - 2] = (bitLength >>> 8) & 255; + buffer[paddedLength - 1] = bitLength & 255; + + midState.set(SHA256_IS); + for (let block = 0; block < firstDynamicBlock; block++) { + sha256Compress(midState, schedule, buffer, block * 64); + } + + for (let nonce = rangeStart; nonce < rangeEnd; nonce++) { + let remainder = nonce; + for (let pos = messageLength - 1; pos >= prefixLength; pos--) { + buffer[pos] = 48 + (remainder % 10); + remainder = (remainder / 10) | 0; + } + state.set(midState); + for (let offset = firstDynamicBlock * 64; offset < paddedLength; offset += 64) { + sha256Compress(state, schedule, buffer, offset); + } + if (hasLeadingZeroBits(state, difficulty)) return String(nonce); + } + } + return null; +} + +function createCookieJar() { + const cookies = {}; + return { + update(response) { + let rawCookies = []; + try { + if (response && response.headers && typeof response.headers.getSetCookie === 'function') { + rawCookies = response.headers.getSetCookie() || []; + } else if (response && response.headers) { + const header = response.headers.get('set-cookie'); + if (header) rawCookies = [header]; + } + } catch { } + rawCookies.forEach((cookie) => { + const pair = cookie.split(';')[0].split('='); + if (pair.length >= 2) cookies[pair[0].trim()] = pair.slice(1).join('=').trim(); + }); + }, + serialize() { + return Object.keys(cookies).map((name) => `${name}=${cookies[name]}`).join('; '); + } + }; +} + +function resolvePlaylistUrl(entry, playlistUrl) { + if (/^https?:\/\//i.test(entry)) return entry; + const cleanUrl = playlistUrl.split('#')[0].split('?')[0]; + if (entry.charAt(0) === '/') { + const origin = cleanUrl.match(/^(https?:\/\/[^\/]+)/i); + return `${origin ? origin[1] : ''}${entry}`; + } + return `${cleanUrl.slice(0, cleanUrl.lastIndexOf('/') + 1)}${entry}`; +} + +const PLAYBACK_HEADERS = { + 'User-Agent': DSKTP_UA, + Origin: BASE_URL, + Referer: `${BASE_URL}/` +}; + +async function createSession(tmdbId, mediaType, tvSlug, referer, timeout = 25000) { + try { + const jar = createCookieJar(); + const homeRes = await fetch(`${BASE_URL}/`, { + headers: { 'User-Agent': MBILE_UA, Accept: 'text/html' }, + signal: AbortSignal.timeout(timeout) + }); + jar.update(homeRes); + + const tokenRes = await fetch(`${BASE_URL}/api/ui/session`, { + method: 'POST', + headers: { + 'User-Agent': MBILE_UA, + Accept: '*/*', + Origin: BASE_URL, + Referer: referer, + 'Content-Type': 'application/json', + Cookie: jar.serialize() + }, + body: '', + signal: AbortSignal.timeout(timeout) + }); + jar.update(tokenRes); + + const tokenData = await tokenRes.json().catch(() => null); + const requestToken = tokenData?.token; + if (!requestToken) return null; + + const payload = { + mediaId: Number(tmdbId), + mediaType, + tv_slug: tvSlug, + requestToken + }; + + const initPlayback = async (body) => { + const res = await fetch(`${BASE_URL}/api/playback-init`, { + method: 'POST', + headers: { + 'User-Agent': MBILE_UA, + Accept: '*/*', + Origin: BASE_URL, + Referer: referer, + 'Content-Type': 'application/json', + Cookie: jar.serialize() + }, + body: JSON.stringify(body), + signal: AbortSignal.timeout(timeout) + }); + jar.update(res); + return await res.json().catch(() => null); + }; + + let result = await initPlayback(payload); + if (result?.success && result.token) { + return { requestToken, playbackToken: result.token, jar, referer }; + } + + if (result?.requiresPow && result.pow) { + const nonce = solveProofOfWork(result.pow.challenge, result.pow.difficulty || 18); + if (nonce === null) return null; + + result = await initPlayback({ + ...payload, + pow: { challengeId: result.pow.challengeId, nonce } + }); + + if (result?.token) { + return { requestToken, playbackToken: result.token, jar, referer }; + } + } + + return null; + } catch { + return null; + } +} + +async function parseMasterPlaylist(playlistUrl, headers) { + try { + const res = await fetch(playlistUrl, { + headers, + signal: AbortSignal.timeout(5000) + }); + if (!res.ok) return null; + const text = await res.text(); + if (!text.includes('#EXT-X-STREAM-INF')) return null; + + const lines = text.split('\n'); + const variants = []; + let currentQuality = '1080p'; + + for (let i = 0; i < lines.length; i++) { + const line = lines[i].trim(); + if (line.startsWith('#EXT-X-STREAM-INF')) { + const resMatch = line.match(/RESOLUTION=\d+x(\d+)/i); + if (resMatch) { + currentQuality = `${resMatch[1]}p`; + } + } else if (line && !line.startsWith('#')) { + const streamUrl = resolvePlaylistUrl(line, playlistUrl); + variants.push({ + url: streamUrl, + quality: currentQuality + }); + currentQuality = '1080p'; + } + } + + return variants.length ? variants : null; + } catch { + return null; + } +} + +async function fetchServerStreams(server, tmdbId, mediaType, tvSlug, session, timeout = 25000) { + const sourceKey = server.source || server.key; + try { + const encryptRes = await fetch(`${BASE_URL}/api/encrypt`, { + method: 'POST', + headers: { + 'User-Agent': MBILE_UA, + Accept: '*/*', + Origin: BASE_URL, + Referer: session.referer, + 'Content-Type': 'application/json', + Cookie: session.jar.serialize() + }, + body: JSON.stringify({ + data: { + mediaId: Number(tmdbId), + mediaType, + tv_slug: tvSlug, + source: sourceKey + }, + endpoint: 'stream-encrypted', + requestToken: session.requestToken + }), + signal: AbortSignal.timeout(timeout) + }); + + const encrypted = await encryptRes.json().catch(() => null); + if (!encrypted?.url) return []; + + const streamEndpoint = `${BASE_URL}${encrypted.url}&requestToken=${encodeURIComponent(session.requestToken)}&token=${encodeURIComponent(session.playbackToken)}`; + const streamRes = await fetch(streamEndpoint, { + headers: { + 'User-Agent': MBILE_UA, + Accept: '*/*', + Origin: BASE_URL, + Referer: session.referer, + Cookie: session.jar.serialize() + }, + signal: AbortSignal.timeout(timeout) + }); + + const streamData = await streamRes.json().catch(() => null); + const streamUrl = streamData?.data?.stream_url; + if (!streamUrl || String(streamUrl).includes('playback-unavailable')) return []; + + const variants = await parseMasterPlaylist(streamUrl, PLAYBACK_HEADERS); + if (variants && variants.length > 0) { + return variants.map(v => ({ + url: v.url, + quality: v.quality, + serverName: server.name + })); + } + + return [{ url: streamUrl, quality: 'Auto', serverName: server.name }]; + } catch { + return []; + } +} + +export async function getStream(args) { + const { id, s, e } = args || {}; + if (!id || !/^\d+$/.test(String(id))) return null; + + const isTv = s != null && e != null; + const type = isTv ? 'tv' : 'movie'; + const tvSlug = isTv ? `${Number(s || 1)}-${Number(e || 1)}` : ''; + const referer = isTv + ? `${BASE_URL}/tv/${id}/${Number(s || 1)}/${Number(e || 1)}` + : `${BASE_URL}/movie/${id}`; + + const session = await createSession(id, type, tvSlug, referer, args?.config?.timeout); + if (!session) return null; + + const serverPromises = SERVERS.map((server) => + fetchServerStreams(server, id, type, tvSlug, session, args?.config?.timeout) + ); + + const settled = await Promise.allSettled(serverPromises); + const allUrls = []; + const seenUrls = new Set(); + + for (const res of settled) { + if (res.status !== 'fulfilled' || !Array.isArray(res.value)) continue; + for (const item of res.value) { + if (!item.url || seenUrls.has(item.url)) continue; + seenUrls.add(item.url); + + allUrls.push({ + url: item.url, + server: `Mapple - ${item.serverName}`, + quality: item.quality || 'Auto', + type: item.url.includes('.m3u8') ? 'hls' : 'mp4', + headers: PLAYBACK_HEADERS + }); + } + } + + 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/mysterious.js b/src/sources/mysterious.js new file mode 100644 index 0000000..a2d5983 --- /dev/null +++ b/src/sources/mysterious.js @@ -0,0 +1,113 @@ +import { fetchJson } from '../utils/helpers.js'; + +const BASE_URL = 'https://atlantic.speedoflight.buzz'; +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'; + +function normalizeQuality(q) { + if (!q) return '1080p'; + const str = String(q).trim(); + return /^\d+$/.test(str) ? `${str}p` : str; +} + +export async function getStream(args) { + const { id, s, e } = args || {}; + if (!id) return null; + + const isTv = s != null && e != null; + const season = s || 1; + const episode = e || 1; + + const targetUrl = isTv + ? `${BASE_URL}/api/embed?tmdb_id=${encodeURIComponent(id)}&type=series&season=${encodeURIComponent(season)}&episode=${encodeURIComponent(episode)}` + : `${BASE_URL}/api/embed?tmdb_id=${encodeURIComponent(id)}`; + + try { + const payload = await fetchJson(targetUrl, { + headers: { + 'User-Agent': UA, + Accept: 'application/json', + }, + signal: AbortSignal.timeout(args?.config?.timeout || 10000), + }); + + if (!payload || (!Array.isArray(payload.qualities) && !payload.download_url && !payload.url)) { + return null; + } + + const allUrls = []; + const seenUrls = new Set(); + + if (Array.isArray(payload.qualities)) { + for (const item of payload.qualities) { + if (!item || !item.url || seenUrls.has(item.url)) continue; + + const qualLabel = String(item.quality || '').trim(); + if (qualLabel.toUpperCase() === 'ORG') continue; + + seenUrls.add(item.url); + const quality = normalizeQuality(qualLabel || '1080p'); + const sizeText = item.sizeText ? ` (${item.sizeText})` : ''; + + const ext = item.url.split('?')[0].split('.').pop()?.toLowerCase(); + const type = ext === 'm3u8' || item.url.includes('.m3u8') + ? 'hls' + : (ext === 'mpd' || item.url.includes('.mpd') ? 'dash' : (ext === 'mkv' ? 'mkv' : 'mp4')); + + allUrls.push({ + url: item.url, + server: `Mysterious - Direct ${quality}${sizeText}`, + quality, + type, + skipProxy: true, + }); + } + } + + const directUrl = payload.download_url || payload.url; + if (directUrl && !seenUrls.has(directUrl)) { + seenUrls.add(directUrl); + const qMatch = (payload.file_name || '').match(/\b(2160p|1080p|720p|480p|360p|4k)\b/i); + const quality = qMatch ? qMatch[1].toLowerCase() : 'Auto'; + const sizeText = payload.file_size ? ` (${payload.file_size})` : ''; + const ext = directUrl.split('?')[0].split('.').pop()?.toLowerCase(); + const type = ext === 'm3u8' || directUrl.includes('.m3u8') + ? 'hls' + : (ext === 'mpd' || directUrl.includes('.mpd') ? 'dash' : (ext === 'mkv' || (payload.file_name && payload.file_name.endsWith('.mkv')) ? 'mkv' : 'mp4')); + + allUrls.push({ + url: directUrl, + server: `Mysterious - Direct ${quality}${sizeText}`, + quality, + type, + skipProxy: true, + }); + } + + if (payload.download_url && !seenUrls.has(payload.download_url)) { + seenUrls.add(payload.download_url); + const qMatch = (payload.file_name || '').match(/\b(2160p|1080p|720p|480p|360p|4k)\b/i); + const quality = qMatch ? qMatch[1].toLowerCase() : 'Auto'; + const sizeText = payload.file_size ? ` (${payload.file_size})` : ''; + const ext = payload.download_url.split('?')[0].split('.').pop()?.toLowerCase(); + const type = ext === 'm3u8' ? 'hls' : (ext === 'mpd' ? 'dash' : (ext === 'mkv' || (payload.file_name && payload.file_name.endsWith('.mkv')) ? 'mkv' : 'mp4')); + + allUrls.push({ + url: payload.download_url, + server: `Mysterious - Direct ${quality}${sizeText}`, + quality, + type, + skipProxy: true, + }); + } + + if (!allUrls.length) return null; + + return { + url: allUrls[0].url, + headers: allUrls[0].headers, + allUrls, + }; + } catch { + return null; + } +} \ No newline at end of file diff --git a/src/sources/vidqueen.js b/src/sources/vidqueen.js new file mode 100644 index 0000000..10c1ae6 --- /dev/null +++ b/src/sources/vidqueen.js @@ -0,0 +1,138 @@ +const BASE_URL = 'https://api.vidqueen.st'; +const ORIGIN = 'https://vidqueen.st'; + +const HEADERS = { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/157.0.0.0 Safari/537.36', + Accept: 'application/json', + 'Accept-Language': 'en-US,en;q=0.9', + Origin: ORIGIN, + Referer: `${ORIGIN}/`, + 'Sec-Fetch-Site': 'same-site', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Dest': 'empty', + 'sec-ch-ua': '"Not-A.Brand";v="99", "Google Chrome";v="157", "Chromium";v="157"', + 'sec-ch-ua-mobile': '?0', + 'sec-ch-ua-platform': '"Windows"', +}; + +const PLAYBACK_HEADERS = { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/157.0.0.0 Safari/537.36', + Origin: ORIGIN, + Referer: `${ORIGIN}/`, +}; + +const SERVERS = [ + { key: 's1', label: 'Bloodstone', params: 'sources=s1&hevc=1' }, + { key: 's2', label: 'Garnet', params: 'sources=s2&hevc=1&vh=1' }, + { key: 's3', label: 'Ruby', params: 'sources=s3&hevc=1&vh=1' }, + { key: 's4', label: 'Obsidian', params: 'sources=s4&sw=1' }, + { key: 's5', label: 'Jet', params: 'sources=s5&hevc=1&vh=1' }, +]; + +function normalizeQuality(q) { + if (!q) return '1080p'; + const str = String(q).trim(); + return /^\d+$/.test(str) ? `${str}p` : str; +} + +export async function getStream(args) { + const { id, s, e } = args || {}; + if (!id) return null; + + const isTv = s != null && e != null; + const prefix = isTv + ? `tv?id=${id}&season=${s || 1}&episode=${e || 1}&mode=json` + : `movie?id=${id}&mode=json`; + + const responses = await Promise.allSettled( + SERVERS.map(async (cfg) => { + const url = `${BASE_URL}/${prefix}&${cfg.params}`; + const res = await fetch(url, { + headers: HEADERS, + signal: AbortSignal.timeout(args?.config?.timeout || 10000), + }).catch(() => null); + + if (!res || !res.ok) return null; + const data = await res.json().catch(() => null); + if (!data?.source) return null; + + return { cfg, data }; + }) + ); + + const allUrls = []; + const seenUrls = new Set(); + + for (const r of responses) { + if (r.status !== 'fulfilled' || !r.value?.data?.source) continue; + const { cfg, data } = r.value; + const src = data.source; + const serverLabel = src.label || cfg.label; + + if (src.manifest && typeof src.manifest === 'string') { + const lines = src.manifest.split('\n').map(l => l.trim()); + for (let i = 0; i < lines.length; i++) { + if (lines[i].startsWith('#EXT-X-STREAM-INF')) { + const inf = lines[i]; + const streamUrl = lines[i + 1]; + if (streamUrl && streamUrl.startsWith('http') && !seenUrls.has(streamUrl)) { + seenUrls.add(streamUrl); + let quality = '1080p'; + const resMatch = inf.match(/RESOLUTION=\d+x(\d+)/i); + if (resMatch) { + quality = `${resMatch[1]}p`; + } + allUrls.push({ + url: streamUrl, + server: `Vidqueen - ${serverLabel}`, + quality, + type: 'hls', + headers: PLAYBACK_HEADERS, + }); + } + } + } + } + + if (Array.isArray(src.qualities) && src.qualities.some(q => q.type === 'mp4')) { + for (const q of src.qualities) { + if (!q?.url || seenUrls.has(q.url)) continue; + seenUrls.add(q.url); + allUrls.push({ + url: q.url, + server: `Vidqueen - ${serverLabel}`, + quality: normalizeQuality(q.quality), + type: 'mp4', + headers: PLAYBACK_HEADERS, + }); + } + } + + if (src.url && (!src.qualities || !src.qualities.some(q => q.type === 'mp4')) && !src.manifest) { + if (!seenUrls.has(src.url)) { + seenUrls.add(src.url); + let rawQuality = '1080p'; + if (src.h) { + rawQuality = `${src.h}p`; + } else if (src.qualities?.[0]?.quality && src.qualities[0].quality !== 'Auto') { + rawQuality = src.qualities[0].quality; + } + allUrls.push({ + url: src.url, + server: `Vidqueen - ${serverLabel}`, + quality: normalizeQuality(rawQuality), + type: src.url.includes('.m3u8') ? 'hls' : 'mp4', + headers: PLAYBACK_HEADERS, + }); + } + } + } + + if (!allUrls.length) return null; + + return { + url: allUrls[0].url, + headers: allUrls[0].headers, + allUrls, + }; +} \ No newline at end of file