added mapple, mysterious and vidqueen

This commit is contained in:
2026-11-11 19:40:25 -08:00
parent e15341092f
commit 6b51a76ed4
4 changed files with 738 additions and 3 deletions

View File

@@ -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,
},
{

430
src/sources/mapple.js Normal file
View File

@@ -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
};
}

113
src/sources/mysterious.js Normal file
View File

@@ -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;
}
}

138
src/sources/vidqueen.js Normal file
View File

@@ -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,
};
}