mirror of
https://gitlab.com/vyla-entertainment/sdk.git
synced 2026-11-09 19:57:23 -05:00
added chillflix, and trendimovies, removed nexa
This commit is contained in:
@@ -172,6 +172,24 @@ export const SOURCES = [
|
||||
multiUrl: true,
|
||||
},
|
||||
|
||||
{
|
||||
key: 'chillflix',
|
||||
label: 'ChillFlix',
|
||||
sourceFile: 'chillflix',
|
||||
proxyParam: 'cf',
|
||||
timeout: 25000,
|
||||
jitter: 500,
|
||||
retries: 2,
|
||||
multiUrl: true,
|
||||
cdnHeaders: [{
|
||||
pattern: /chillflix\.pw|chillflix\.lol|nextgencloudfabric\.com/i,
|
||||
headers: {
|
||||
Referer: 'https://www.chillflix.pw/',
|
||||
Origin: 'https://www.chillflix.pw',
|
||||
},
|
||||
}],
|
||||
},
|
||||
|
||||
{
|
||||
key: 'cinebros',
|
||||
label: 'CineBros',
|
||||
@@ -531,24 +549,6 @@ export const SOURCES = [
|
||||
}],
|
||||
},
|
||||
|
||||
{
|
||||
key: 'nexa',
|
||||
label: 'Nexa',
|
||||
sourceFile: 'nexa',
|
||||
proxyParam: 'nx',
|
||||
timeout: 30000,
|
||||
jitter: 500,
|
||||
retries: 2,
|
||||
multiUrl: true,
|
||||
cdnHeaders: [{
|
||||
pattern: /nexa\.mov|providers\.nexa\.mov/i,
|
||||
headers: {
|
||||
Referer: 'https://nexa.mov/',
|
||||
Origin: 'https://nexa.mov',
|
||||
},
|
||||
}],
|
||||
},
|
||||
|
||||
{
|
||||
key: 'pengu',
|
||||
label: 'Pengu',
|
||||
@@ -683,6 +683,24 @@ export const SOURCES = [
|
||||
}],
|
||||
},
|
||||
|
||||
{
|
||||
key: 'trendimovies',
|
||||
label: 'TrendiMovies',
|
||||
sourceFile: 'trendimovies',
|
||||
proxyParam: 'tm',
|
||||
timeout: 25000,
|
||||
jitter: 500,
|
||||
retries: 2,
|
||||
multiUrl: true,
|
||||
cdnHeaders: [{
|
||||
pattern: /trendimovies\.com|tgstream/i,
|
||||
headers: {
|
||||
Referer: 'https://trendimovies.com/',
|
||||
Origin: 'https://trendimovies.com',
|
||||
},
|
||||
}],
|
||||
},
|
||||
|
||||
{
|
||||
key: 'vidapi',
|
||||
label: 'VidAPI',
|
||||
|
||||
185
src/sources/chillflix.js
Normal file
185
src/sources/chillflix.js
Normal file
@@ -0,0 +1,185 @@
|
||||
import { USER_AGENT, fetchJson } from '../utils/helpers.js';
|
||||
|
||||
const BASE_URL = 'https://www.chillflix.pw';
|
||||
const VAPLAYER_ORIGIN = 'https://nextgencloudfabric.com';
|
||||
|
||||
const HEADERS = {
|
||||
'User-Agent': USER_AGENT,
|
||||
Accept: 'application/json, text/plain, */*',
|
||||
'Accept-Language': 'en-US,en;q=0.9',
|
||||
Origin: BASE_URL,
|
||||
};
|
||||
|
||||
const PLAYBACK_HEADERS = {
|
||||
'User-Agent': USER_AGENT,
|
||||
Referer: `${BASE_URL}/`,
|
||||
Origin: BASE_URL,
|
||||
};
|
||||
|
||||
const FAST_PROVIDERS = ['novahd', 'cinesu', 'filesun', 'notorrent'];
|
||||
|
||||
function qualityWeight(q) {
|
||||
const s = String(q || '').toLowerCase();
|
||||
if (s.includes('4k') || s.includes('2160') || s.includes('uhd')) return 2160;
|
||||
if (s.includes('1440') || s.includes('2k')) return 1440;
|
||||
if (s.includes('1080') || s.includes('fhd')) return 1080;
|
||||
if (s.includes('720') || s.includes('hd')) return 720;
|
||||
if (s.includes('480') || s.includes('sd')) return 480;
|
||||
const m = s.match(/\d+/);
|
||||
return m ? parseInt(m[0], 10) : 0;
|
||||
}
|
||||
|
||||
function isDecoy(url) {
|
||||
return /\/hm-decoy\/|\/promo\//i.test(url);
|
||||
}
|
||||
|
||||
export async function getStream(args) {
|
||||
const { id, s, e } = args || {};
|
||||
if (!id) return null;
|
||||
|
||||
const isTv = s != null && e != null;
|
||||
const type = isTv ? 'tv' : 'movie';
|
||||
const timeout = args?.config?.timeout || 25000;
|
||||
|
||||
const referer = isTv
|
||||
? `${BASE_URL}/embed/tv/${id}/${s}/${e}?autoplay=true&watchparty=false&title=false&parent_origin=https%3A%2F%2Fwww.chillflix.lol`
|
||||
: `${BASE_URL}/embed/movie/${id}?autoplay=true&watchparty=false&title=false&parent_origin=https%3A%2F%2Fwww.chillflix.lol`;
|
||||
|
||||
const requestHeaders = {
|
||||
...HEADERS,
|
||||
Referer: referer,
|
||||
};
|
||||
|
||||
const baseParams = new URLSearchParams({
|
||||
type,
|
||||
tmdbId: String(id),
|
||||
});
|
||||
|
||||
if (isTv) {
|
||||
baseParams.set('season', String(s));
|
||||
baseParams.set('episode', String(e));
|
||||
}
|
||||
|
||||
const tasks = [
|
||||
async () => {
|
||||
const data = await fetchJson(`${BASE_URL}/api/cinepro/sources?${baseParams.toString()}`, {
|
||||
headers: requestHeaders,
|
||||
signal: AbortSignal.timeout(timeout),
|
||||
});
|
||||
return { type: 'cinepro', data };
|
||||
},
|
||||
|
||||
async () => {
|
||||
const data = await fetchJson(`${BASE_URL}/api/playback/vaplayer-resolve?${baseParams.toString()}`, {
|
||||
headers: requestHeaders,
|
||||
signal: AbortSignal.timeout(12000),
|
||||
});
|
||||
return { type: 'vaplayer', data };
|
||||
},
|
||||
|
||||
async () => {
|
||||
const data = await fetchJson(`${BASE_URL}/api/4k/resolve?${baseParams.toString()}`, {
|
||||
headers: requestHeaders,
|
||||
signal: AbortSignal.timeout(12000),
|
||||
});
|
||||
return { type: '4k', data };
|
||||
},
|
||||
|
||||
...FAST_PROVIDERS.map(prov => async () => {
|
||||
const params = new URLSearchParams(baseParams);
|
||||
params.set('provider', prov);
|
||||
const data = await fetchJson(`${BASE_URL}/api/cinepro/sources?${params.toString()}`, {
|
||||
headers: requestHeaders,
|
||||
signal: AbortSignal.timeout(timeout),
|
||||
});
|
||||
return { type: 'cinepro', data };
|
||||
}),
|
||||
];
|
||||
|
||||
const results = await Promise.allSettled(tasks.map(t => t().catch(() => null)));
|
||||
const allUrls = [];
|
||||
const seen = new Set();
|
||||
|
||||
for (const res of results) {
|
||||
if (res.status !== 'fulfilled' || !res.value?.data) continue;
|
||||
const { type: resType, data } = res.value;
|
||||
|
||||
if (resType === 'cinepro') {
|
||||
const sources = Array.isArray(data.sources) ? data.sources : [];
|
||||
for (const item of sources) {
|
||||
if (!item?.url) continue;
|
||||
|
||||
let streamUrl = item.url;
|
||||
if (streamUrl.startsWith('/')) {
|
||||
streamUrl = `${BASE_URL}${streamUrl}`;
|
||||
}
|
||||
|
||||
if (!streamUrl.startsWith('http') || seen.has(streamUrl) || isDecoy(streamUrl)) continue;
|
||||
seen.add(streamUrl);
|
||||
|
||||
const providerName = item.provider?.name || item.provider?.id || 'Direct';
|
||||
let quality = item.quality ? String(item.quality).trim() : 'Auto';
|
||||
if (/^\d+$/.test(quality)) quality = `${quality}p`;
|
||||
|
||||
const lang = item.audioTracks?.[0]?.language || item.audioTracks?.[0]?.label;
|
||||
const langLabel = lang && !/original/i.test(lang) ? ` (${lang})` : '';
|
||||
|
||||
allUrls.push({
|
||||
url: streamUrl,
|
||||
server: `ChillFlix - ${providerName} ${quality}${langLabel}`,
|
||||
quality,
|
||||
type: item.type || (streamUrl.includes('.m3u8') ? 'hls' : 'mp4'),
|
||||
headers: PLAYBACK_HEADERS,
|
||||
});
|
||||
}
|
||||
} else if (resType === 'vaplayer') {
|
||||
const streams = Array.isArray(data.streams)
|
||||
? data.streams
|
||||
: (Array.isArray(data.urls) ? data.urls : []);
|
||||
|
||||
for (const rawUrl of streams) {
|
||||
const streamUrl = String(rawUrl || '').trim();
|
||||
if (!streamUrl.startsWith('http') || seen.has(streamUrl) || isDecoy(streamUrl)) continue;
|
||||
seen.add(streamUrl);
|
||||
|
||||
allUrls.push({
|
||||
url: streamUrl,
|
||||
server: 'ChillFlix - VAPlayer',
|
||||
quality: 'Auto',
|
||||
type: streamUrl.includes('.m3u8') ? 'hls' : 'mp4',
|
||||
headers: {
|
||||
'User-Agent': USER_AGENT,
|
||||
Referer: data.referer || `${VAPLAYER_ORIGIN}/`,
|
||||
Origin: VAPLAYER_ORIGIN,
|
||||
},
|
||||
});
|
||||
}
|
||||
} else if (resType === '4k') {
|
||||
const streams = Array.isArray(data.streams) ? data.streams : [];
|
||||
for (const item of streams) {
|
||||
const streamUrl = String(item?.url || '').trim();
|
||||
if (!streamUrl.startsWith('http') || seen.has(streamUrl) || isDecoy(streamUrl)) continue;
|
||||
seen.add(streamUrl);
|
||||
|
||||
const quality = item.quality || '4K';
|
||||
allUrls.push({
|
||||
url: streamUrl,
|
||||
server: `ChillFlix - 4K (${quality})`,
|
||||
quality,
|
||||
type: item.type || (streamUrl.includes('.m3u8') ? 'hls' : 'mp4'),
|
||||
headers: PLAYBACK_HEADERS,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!allUrls.length) return null;
|
||||
|
||||
allUrls.sort((a, b) => qualityWeight(b.quality) - qualityWeight(a.quality));
|
||||
|
||||
return {
|
||||
url: allUrls[0].url,
|
||||
headers: allUrls[0].headers,
|
||||
allUrls,
|
||||
};
|
||||
}
|
||||
@@ -1,179 +0,0 @@
|
||||
const BASE_URL = 'https://providers.nexa.mov';
|
||||
const DEC_API = 'https://enc-dec.app/api';
|
||||
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 = {
|
||||
'User-Agent': UA,
|
||||
Referer: 'https://nexa.mov/',
|
||||
Origin: 'https://nexa.mov',
|
||||
};
|
||||
|
||||
const PLAYBACK_HEADERS = {
|
||||
'User-Agent': UA,
|
||||
Referer: 'https://nexa.mov/',
|
||||
Origin: 'https://nexa.mov',
|
||||
};
|
||||
|
||||
const ANIME_SERVERS = new Set(['kyoto', 'tokyo', 'hokkaido', 'fuji']);
|
||||
|
||||
function extractUrls(result, serverName) {
|
||||
const urls = [];
|
||||
if (!result) return urls;
|
||||
|
||||
const data = typeof result === 'string' && (result.startsWith('{') || result.startsWith('['))
|
||||
? JSON.parse(result)
|
||||
: result;
|
||||
|
||||
if (typeof data === 'string' && /^https?:\/\//.test(data)) {
|
||||
urls.push({
|
||||
url: data,
|
||||
server: `Nexa - ${serverName}`,
|
||||
quality: 'Auto',
|
||||
type: data.includes('.m3u8') ? 'hls' : (data.includes('.mpd') ? 'dash' : 'mp4'),
|
||||
headers: PLAYBACK_HEADERS,
|
||||
});
|
||||
return urls;
|
||||
}
|
||||
|
||||
const list = Array.isArray(data)
|
||||
? data
|
||||
: Array.isArray(data?.sources)
|
||||
? data.sources
|
||||
: Array.isArray(data?.streams)
|
||||
? data.streams
|
||||
: Array.isArray(data?.playlist)
|
||||
? data.playlist
|
||||
: [];
|
||||
|
||||
for (const item of list) {
|
||||
const streamUrl = item?.url || item?.file || item?.src || (typeof item === 'string' ? item : null);
|
||||
if (!streamUrl || typeof streamUrl !== 'string' || !streamUrl.startsWith('http')) continue;
|
||||
|
||||
let quality = item.quality || item.label || 'Auto';
|
||||
if (/^\d+$/i.test(quality)) quality = `${quality}p`;
|
||||
|
||||
urls.push({
|
||||
url: streamUrl,
|
||||
server: `Nexa - ${item.server || item.name || serverName}`,
|
||||
quality,
|
||||
type: item.type || (streamUrl.includes('.m3u8') ? 'hls' : (streamUrl.includes('.mpd') ? 'dash' : 'mp4')),
|
||||
headers: PLAYBACK_HEADERS,
|
||||
});
|
||||
}
|
||||
|
||||
if (data?.url && typeof data.url === 'string' && data.url.startsWith('http')) {
|
||||
urls.push({
|
||||
url: data.url,
|
||||
server: `Nexa - ${data.server || serverName}`,
|
||||
quality: data.quality || 'Auto',
|
||||
type: data.type || (data.url.includes('.m3u8') ? 'hls' : (data.url.includes('.mpd') ? 'dash' : 'mp4')),
|
||||
headers: PLAYBACK_HEADERS,
|
||||
});
|
||||
}
|
||||
|
||||
return urls;
|
||||
}
|
||||
|
||||
async function getAvailableServers() {
|
||||
try {
|
||||
const res = await fetch(`${BASE_URL}/v1/status`, {
|
||||
headers: HEADERS,
|
||||
signal: AbortSignal.timeout(6000),
|
||||
});
|
||||
if (!res.ok) return ['charlotte', 'albuquerque', 'denver'];
|
||||
const json = await res.json();
|
||||
const sources = Array.isArray(json?.sources) ? json.sources : [];
|
||||
const active = sources
|
||||
.map(s => s?.codename)
|
||||
.filter(name => name && !ANIME_SERVERS.has(name));
|
||||
return active.length > 0 ? active : ['charlotte', 'albuquerque', 'denver'];
|
||||
} catch {
|
||||
return ['charlotte', 'albuquerque', 'denver'];
|
||||
}
|
||||
}
|
||||
|
||||
async function resolveServer(server, targetUrl) {
|
||||
try {
|
||||
const encRes = await fetch(`${DEC_API}/enc-nexa?url=${encodeURIComponent(targetUrl)}`, {
|
||||
headers: { 'User-Agent': UA },
|
||||
signal: AbortSignal.timeout(8000),
|
||||
});
|
||||
if (!encRes.ok) return null;
|
||||
const encJson = await encRes.json();
|
||||
if (encJson?.status !== 200 || !encJson.result) return null;
|
||||
|
||||
const payloadBuffer = Buffer.from(encJson.result, 'base64');
|
||||
|
||||
const playlistRes = await fetch(`${BASE_URL}/v1/playlist`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
...HEADERS,
|
||||
'Content-Type': 'application/octet-stream',
|
||||
},
|
||||
body: payloadBuffer,
|
||||
signal: AbortSignal.timeout(10000),
|
||||
});
|
||||
if (!playlistRes.ok) return null;
|
||||
|
||||
const playlistBytes = await playlistRes.arrayBuffer();
|
||||
const hexData = Buffer.from(playlistBytes).toString('hex');
|
||||
if (!hexData) return null;
|
||||
|
||||
const decRes = await fetch(`${DEC_API}/dec-nexa`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'User-Agent': UA,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ text: hexData, server }),
|
||||
signal: AbortSignal.timeout(8000),
|
||||
});
|
||||
if (!decRes.ok) return null;
|
||||
const decJson = await decRes.json();
|
||||
if (decJson?.status !== 200 || !decJson.result) return null;
|
||||
|
||||
return { server, result: decJson.result };
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getStream(args) {
|
||||
const { id, s, e } = args || {};
|
||||
if (!id) return null;
|
||||
|
||||
const isTv = s != null && e != null;
|
||||
const servers = await getAvailableServers();
|
||||
const targetServers = servers.slice(0, 3);
|
||||
|
||||
const responses = await Promise.allSettled(
|
||||
targetServers.map(server => {
|
||||
const url = isTv
|
||||
? `https://providers.nexa.mov/?platform=tmdb&id=${id}&server=${server}&season=${s}&episode=${e}`
|
||||
: `https://providers.nexa.mov/?platform=tmdb&id=${id}&server=${server}`;
|
||||
return resolveServer(server, url);
|
||||
})
|
||||
);
|
||||
|
||||
const allUrls = [];
|
||||
const seen = new Set();
|
||||
|
||||
for (const res of responses) {
|
||||
if (res.status !== 'fulfilled' || !res.value?.result) continue;
|
||||
const { server, result } = res.value;
|
||||
const found = extractUrls(result, server);
|
||||
for (const item of found) {
|
||||
if (!item?.url || seen.has(item.url)) continue;
|
||||
seen.add(item.url);
|
||||
allUrls.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
if (!allUrls.length) return null;
|
||||
|
||||
return {
|
||||
url: allUrls[0].url,
|
||||
headers: allUrls[0].headers,
|
||||
allUrls,
|
||||
};
|
||||
}
|
||||
@@ -26,8 +26,6 @@ const PLAYBACK_HEADERS = {
|
||||
Origin: BACKEND_BASE,
|
||||
};
|
||||
|
||||
// Known active servers ranked by reliability
|
||||
// Hemlic [Multi], Siddle, and Tyler yield the workers.dev/v1... stream directly
|
||||
const ACTIVE_SERVERS = [
|
||||
{ name: 'Hemlic [Multi]', responseType: 'vidly' },
|
||||
{ name: 'Siddle', responseType: 'vidly' },
|
||||
@@ -177,7 +175,6 @@ function parseStreamData(data, srv) {
|
||||
});
|
||||
};
|
||||
|
||||
// Vidly: Hemlic [Multi] and Siddle
|
||||
if (type === 'vidly' && Array.isArray(data.streams)) {
|
||||
for (const s of data.streams) {
|
||||
if (s?.url) {
|
||||
@@ -185,7 +182,6 @@ function parseStreamData(data, srv) {
|
||||
}
|
||||
}
|
||||
} else if (type === 'vidlink') {
|
||||
// Tyler combined playlist
|
||||
if (typeof data.combinedPlaylistUrl === 'string' && data.combinedPlaylistUrl) {
|
||||
add(data.combinedPlaylistUrl, 'Auto', true);
|
||||
}
|
||||
@@ -230,7 +226,6 @@ function parseStreamData(data, srv) {
|
||||
|
||||
function scoreStream(item) {
|
||||
let score = 0;
|
||||
// Highest priority for the exact workers.dev / v1 stream
|
||||
if (item.url?.includes('workers.dev')) score += 50000;
|
||||
if (item.url?.includes('/v1.')) score += 30000;
|
||||
if (item.isPrimary) score += 20000;
|
||||
@@ -255,7 +250,6 @@ export async function getStream(args) {
|
||||
Referer: referer,
|
||||
};
|
||||
|
||||
// Race top servers: Hemlic [Multi], Siddle, Tyler, Cipher, Eternal
|
||||
const candidateBatch = ACTIVE_SERVERS.slice(0, 5);
|
||||
|
||||
const settled = await Promise.allSettled(
|
||||
|
||||
187
src/sources/trendimovies.js
Normal file
187
src/sources/trendimovies.js
Normal file
@@ -0,0 +1,187 @@
|
||||
import { USER_AGENT, fetchText } from '../utils/helpers.js';
|
||||
|
||||
const BASE = 'https://trendimovies.com';
|
||||
|
||||
const HEADERS = {
|
||||
'User-Agent': USER_AGENT,
|
||||
Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
||||
'Accept-Language': 'en-US,en;q=0.9',
|
||||
Referer: `${BASE}/`,
|
||||
};
|
||||
|
||||
const PLAYBACK_HEADERS = {
|
||||
'User-Agent': USER_AGENT,
|
||||
Referer: `${BASE}/`,
|
||||
};
|
||||
|
||||
function deepUnwrap(value) {
|
||||
if (Array.isArray(value)) {
|
||||
if (value.length === 2 && (value[0] === 0 || value[0] === 1)) {
|
||||
return deepUnwrap(value[1]);
|
||||
}
|
||||
return value.map(deepUnwrap);
|
||||
}
|
||||
if (value && typeof value === 'object') {
|
||||
return Object.fromEntries(
|
||||
Object.entries(value).map(([k, v]) => [k, deepUnwrap(v)])
|
||||
);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function detectFileType(url, item) {
|
||||
const ext = item?.format || item?.extension || item?.ext || item?.type;
|
||||
if (ext) return String(ext).toLowerCase().replace(/^\./, '');
|
||||
const lower = String(url).toLowerCase();
|
||||
if (lower.includes('.m3u8')) return 'hls';
|
||||
if (lower.includes('.mpd')) return 'dash';
|
||||
if (lower.includes('.mp4')) return 'mp4';
|
||||
if (lower.includes('.avi')) return 'avi';
|
||||
return 'mkv';
|
||||
}
|
||||
|
||||
function qualityWeight(q) {
|
||||
const s = String(q || '').toLowerCase();
|
||||
if (s.includes('4k') || s.includes('2160')) return 2160;
|
||||
const m = s.match(/\d+/);
|
||||
return m ? parseInt(m[0], 10) : 0;
|
||||
}
|
||||
|
||||
function toStreamLinks(raw) {
|
||||
if (!Array.isArray(raw)) return [];
|
||||
const seen = new Set();
|
||||
const links = [];
|
||||
|
||||
for (const item of raw) {
|
||||
const url = item?.url ?? item?.file_url ?? item?.link;
|
||||
if (!url || typeof url !== 'string' || !url.startsWith('http')) continue;
|
||||
if (seen.has(url)) continue;
|
||||
seen.add(url);
|
||||
|
||||
let quality = item.quality ?? item.resolution ?? 'Auto';
|
||||
if (/^\d+$/.test(String(quality).trim())) {
|
||||
quality = `${quality}p`;
|
||||
}
|
||||
|
||||
const size = item.file_size ?? item.size ?? null;
|
||||
const language = item.language ?? null;
|
||||
const type = detectFileType(url, item);
|
||||
|
||||
let serverLabel = `TrendiMovies - ${quality}`;
|
||||
if (language) serverLabel += ` (${language})`;
|
||||
if (size) serverLabel += ` [${size}]`;
|
||||
|
||||
links.push({
|
||||
url,
|
||||
server: serverLabel,
|
||||
quality,
|
||||
type,
|
||||
headers: PLAYBACK_HEADERS,
|
||||
});
|
||||
}
|
||||
|
||||
return links;
|
||||
}
|
||||
|
||||
function parseAstroIslands(html) {
|
||||
const results = [];
|
||||
if (!html) return results;
|
||||
|
||||
const regex = /<astro-island[^>]*props="([^"]+)"/g;
|
||||
let match;
|
||||
while ((match = regex.exec(html)) !== null) {
|
||||
try {
|
||||
const decoded = match[1]
|
||||
.replace(/"/g, '"')
|
||||
.replace(/&/g, '&')
|
||||
.replace(/'/g, "'")
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>');
|
||||
|
||||
const unwrapped = deepUnwrap(JSON.parse(decoded));
|
||||
if (unwrapped) results.push(unwrapped);
|
||||
} catch { }
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
async function fetchMovieStreams(tmdbId, timeout) {
|
||||
try {
|
||||
const html = await fetchText(`${BASE}/movie/${tmdbId}`, {
|
||||
headers: HEADERS,
|
||||
signal: AbortSignal.timeout(timeout),
|
||||
});
|
||||
|
||||
const islands = parseAstroIslands(html);
|
||||
for (const island of islands) {
|
||||
const raw = island?.links ?? island?.movie?.downloads ?? island?.downloads ?? island?.movie?.links;
|
||||
const streams = toStreamLinks(raw);
|
||||
if (streams.length) return streams;
|
||||
}
|
||||
} catch { }
|
||||
return [];
|
||||
}
|
||||
|
||||
async function fetchTvStreams(tmdbId, s, e, timeout) {
|
||||
let html = null;
|
||||
try {
|
||||
html = await fetchText(`${BASE}/tv/${tmdbId}`, {
|
||||
headers: HEADERS,
|
||||
signal: AbortSignal.timeout(timeout),
|
||||
});
|
||||
} catch { }
|
||||
|
||||
if (!html) {
|
||||
try {
|
||||
html = await fetchText(`${BASE}/show/${tmdbId}`, {
|
||||
headers: HEADERS,
|
||||
signal: AbortSignal.timeout(timeout),
|
||||
});
|
||||
} catch { }
|
||||
}
|
||||
|
||||
if (!html) return [];
|
||||
|
||||
const islands = parseAstroIslands(html);
|
||||
let episodesBySeason = null;
|
||||
let downloadsByEpisode = null;
|
||||
|
||||
for (const island of islands) {
|
||||
episodesBySeason = island?.episodesBySeason ?? island?.show?.episodes ?? island?.episodes ?? episodesBySeason;
|
||||
downloadsByEpisode = island?.downloadsByEpisode ?? island?.downloads ?? downloadsByEpisode;
|
||||
if (episodesBySeason && downloadsByEpisode) break;
|
||||
}
|
||||
|
||||
if (!episodesBySeason || !downloadsByEpisode) return [];
|
||||
|
||||
const seasonEpisodes = episodesBySeason[String(s)];
|
||||
if (!Array.isArray(seasonEpisodes)) return [];
|
||||
|
||||
const ep = seasonEpisodes.find(item => String(item.episode_number) === String(e));
|
||||
if (!ep || ep.id == null) return [];
|
||||
|
||||
const raw = downloadsByEpisode[String(ep.id)];
|
||||
return toStreamLinks(raw);
|
||||
}
|
||||
|
||||
export async function getStream(args) {
|
||||
const { id, s, e } = args || {};
|
||||
if (!id) return null;
|
||||
|
||||
const isTv = s != null && e != null;
|
||||
const timeout = args?.config?.timeout || 25000;
|
||||
|
||||
const allUrls = isTv
|
||||
? await fetchTvStreams(id, s, e, timeout)
|
||||
: await fetchMovieStreams(id, timeout);
|
||||
|
||||
if (!allUrls.length) return null;
|
||||
|
||||
allUrls.sort((a, b) => qualityWeight(b.quality) - qualityWeight(a.quality));
|
||||
|
||||
return {
|
||||
url: allUrls[0].url,
|
||||
headers: allUrls[0].headers,
|
||||
allUrls,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user