mirror of
https://gitlab.com/vyla-entertainment/sdk.git
synced 2026-11-13 21:57:27 -05:00
removed all skipproxies, fixed hexa, and nexa
This commit is contained in:
@@ -8,7 +8,6 @@ export const SOURCES = [
|
||||
jitter: 500,
|
||||
retries: 2,
|
||||
multiUrl: false,
|
||||
skipProxy: true,
|
||||
cdnHeaders: [{
|
||||
pattern: /3eyedraven\.watch/i,
|
||||
headers: {
|
||||
@@ -27,7 +26,6 @@ export const SOURCES = [
|
||||
jitter: 500,
|
||||
retries: 2,
|
||||
multiUrl: true,
|
||||
skipProxy: true,
|
||||
cdnHeaders: [{
|
||||
pattern: /4movie\.in|aapanel\.devcorp\.me/i,
|
||||
headers: {
|
||||
@@ -46,7 +44,6 @@ export const SOURCES = [
|
||||
jitter: 500,
|
||||
retries: 2,
|
||||
multiUrl: false,
|
||||
skipProxy: true,
|
||||
cdnHeaders: [{
|
||||
pattern: /hlsx\d+cdn\.|burntburst\d+\.store|echovideo\.ru/i,
|
||||
headers: {
|
||||
@@ -65,7 +62,6 @@ export const SOURCES = [
|
||||
jitter: 500,
|
||||
retries: 2,
|
||||
multiUrl: true,
|
||||
skipProxy: true,
|
||||
cdnHeaders: [{
|
||||
pattern: /111477\.xyz/i,
|
||||
headers: {
|
||||
@@ -84,7 +80,6 @@ export const SOURCES = [
|
||||
jitter: 500,
|
||||
retries: 2,
|
||||
multiUrl: true,
|
||||
skipProxy: true,
|
||||
cdnHeaders: [{
|
||||
pattern: /raccoonunbutteredbagel\.zip|1x2\.space|kaxate\.fun|mechanicalserenity\.xyz|reelvault\.click|visionaryfounderslab\.site|goldenkite\.top|aether\.ist/i,
|
||||
headers: {
|
||||
@@ -103,7 +98,6 @@ export const SOURCES = [
|
||||
jitter: 500,
|
||||
retries: 2,
|
||||
multiUrl: true,
|
||||
skipProxy: true,
|
||||
cdnHeaders: [{
|
||||
pattern: /braflix\.dev/i,
|
||||
headers: {
|
||||
@@ -122,7 +116,6 @@ export const SOURCES = [
|
||||
jitter: 500,
|
||||
retries: 2,
|
||||
multiUrl: true,
|
||||
skipProxy: true,
|
||||
},
|
||||
|
||||
{
|
||||
@@ -134,7 +127,6 @@ export const SOURCES = [
|
||||
jitter: 500,
|
||||
retries: 2,
|
||||
multiUrl: true,
|
||||
skipProxy: true,
|
||||
cdnHeaders: [{
|
||||
pattern: /cinema\.army/i,
|
||||
headers: {
|
||||
@@ -153,7 +145,6 @@ export const SOURCES = [
|
||||
jitter: 500,
|
||||
retries: 2,
|
||||
multiUrl: true,
|
||||
skipProxy: true,
|
||||
cdnHeaders: [{
|
||||
pattern: /cinesrc\.st/i,
|
||||
headers: {
|
||||
@@ -172,7 +163,6 @@ export const SOURCES = [
|
||||
jitter: 300,
|
||||
retries: 1,
|
||||
multiUrl: true,
|
||||
skipProxy: true,
|
||||
cdnHeaders: [{
|
||||
pattern: /cno\.to/i,
|
||||
headers: {
|
||||
@@ -191,7 +181,6 @@ export const SOURCES = [
|
||||
jitter: 500,
|
||||
retries: 2,
|
||||
multiUrl: true,
|
||||
skipProxy: true,
|
||||
cdnHeaders: [{
|
||||
pattern: /themoviebox\.uk|vidrock\.|orbitingdreams\.xyz|fastcloud\.live|deton444eik\.com/i,
|
||||
headers: {
|
||||
@@ -210,7 +199,6 @@ export const SOURCES = [
|
||||
jitter: 500,
|
||||
retries: 2,
|
||||
multiUrl: true,
|
||||
skipProxy: true,
|
||||
cdnHeaders: [{
|
||||
pattern: /fmfau\.com/i,
|
||||
headers: {
|
||||
@@ -229,7 +217,6 @@ export const SOURCES = [
|
||||
jitter: 500,
|
||||
retries: 2,
|
||||
multiUrl: true,
|
||||
skipProxy: true,
|
||||
cdnHeaders: [{
|
||||
pattern: /framextv\.cc|streamos\.top|workers\.dev|films365\.org/i,
|
||||
headers: {
|
||||
@@ -248,7 +235,6 @@ export const SOURCES = [
|
||||
jitter: 500,
|
||||
retries: 2,
|
||||
multiUrl: true,
|
||||
skipProxy: true,
|
||||
},
|
||||
|
||||
{
|
||||
@@ -260,7 +246,6 @@ export const SOURCES = [
|
||||
retries: 1,
|
||||
jitter: 0,
|
||||
multiUrl: true,
|
||||
skipProxy: true,
|
||||
},
|
||||
|
||||
{
|
||||
@@ -272,7 +257,6 @@ export const SOURCES = [
|
||||
jitter: 500,
|
||||
retries: 2,
|
||||
multiUrl: true,
|
||||
skipProxy: true,
|
||||
},
|
||||
|
||||
{
|
||||
@@ -284,7 +268,6 @@ export const SOURCES = [
|
||||
jitter: 500,
|
||||
retries: 2,
|
||||
multiUrl: true,
|
||||
skipProxy: true,
|
||||
cdnHeaders: [{
|
||||
pattern: /hexa\.su|flixer\.su/i,
|
||||
headers: {
|
||||
@@ -303,7 +286,6 @@ export const SOURCES = [
|
||||
jitter: 500,
|
||||
retries: 2,
|
||||
multiUrl: true,
|
||||
skipProxy: true,
|
||||
cdnHeaders: [{
|
||||
pattern: /krussdomi\.com/i,
|
||||
headers: {
|
||||
@@ -375,7 +357,6 @@ export const SOURCES = [
|
||||
jitter: 500,
|
||||
retries: 2,
|
||||
multiUrl: true,
|
||||
skipProxy: true,
|
||||
cdnHeaders: [{
|
||||
pattern: /meowtv\.ru/i,
|
||||
headers: {
|
||||
@@ -394,7 +375,6 @@ export const SOURCES = [
|
||||
jitter: 500,
|
||||
retries: 2,
|
||||
multiUrl: true,
|
||||
skipProxy: true,
|
||||
cdnHeaders: [{
|
||||
pattern: /movenerds\.space/i,
|
||||
headers: {
|
||||
@@ -413,7 +393,6 @@ export const SOURCES = [
|
||||
jitter: 500,
|
||||
retries: 2,
|
||||
multiUrl: true,
|
||||
skipProxy: true,
|
||||
cdnHeaders: [{
|
||||
pattern: /wecollege\.net|movy\.sx|goldenkite\.top/i,
|
||||
headers: {
|
||||
@@ -434,6 +413,24 @@ export const SOURCES = [
|
||||
multiUrl: true,
|
||||
},
|
||||
|
||||
{
|
||||
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',
|
||||
@@ -443,7 +440,6 @@ export const SOURCES = [
|
||||
jitter: 500,
|
||||
retries: 1,
|
||||
multiUrl: true,
|
||||
skipProxy: true,
|
||||
disabled: false,
|
||||
},
|
||||
|
||||
@@ -507,7 +503,6 @@ export const SOURCES = [
|
||||
jitter: 500,
|
||||
retries: 2,
|
||||
multiUrl: true,
|
||||
skipProxy: true,
|
||||
cdnHeaders: [{
|
||||
pattern: /streamaggregator\.in/i,
|
||||
headers: {
|
||||
@@ -526,7 +521,6 @@ export const SOURCES = [
|
||||
jitter: 500,
|
||||
retries: 2,
|
||||
multiUrl: true,
|
||||
skipProxy: true,
|
||||
cdnHeaders: [{
|
||||
pattern: /thugtv\.online|fcxmb\.com|hscow\.com/i,
|
||||
headers: {
|
||||
@@ -545,7 +539,6 @@ export const SOURCES = [
|
||||
jitter: 500,
|
||||
retries: 1,
|
||||
multiUrl: true,
|
||||
skipProxy: true,
|
||||
cdnHeaders: [{
|
||||
pattern: /torrentio\.to|fcxmb\.com/i,
|
||||
headers: {
|
||||
@@ -575,7 +568,6 @@ export const SOURCES = [
|
||||
jitter: 500,
|
||||
retries: 2,
|
||||
multiUrl: true,
|
||||
skipProxy: true,
|
||||
cdnHeaders: [{
|
||||
pattern: /rousav\.tech|vidbolt\.xyz/i,
|
||||
headers: {
|
||||
@@ -594,7 +586,6 @@ export const SOURCES = [
|
||||
jitter: 500,
|
||||
retries: 1,
|
||||
multiUrl: true,
|
||||
skipProxy: true,
|
||||
cdnHeaders: [{
|
||||
pattern: /vidcore\.io/i,
|
||||
headers: {
|
||||
@@ -635,7 +626,6 @@ export const SOURCES = [
|
||||
jitter: 500,
|
||||
retries: 2,
|
||||
multiUrl: true,
|
||||
skipProxy: true,
|
||||
cdnHeaders: [{
|
||||
pattern: /vidoraplayer\.duckdns\.org/i,
|
||||
headers: {
|
||||
@@ -676,7 +666,6 @@ export const SOURCES = [
|
||||
jitter: 500,
|
||||
retries: 2,
|
||||
multiUrl: true,
|
||||
skipProxy: true,
|
||||
cdnHeaders: [{
|
||||
pattern: /wecollege\.net|vidy\.st/i,
|
||||
headers: {
|
||||
@@ -712,7 +701,6 @@ export const SOURCES = [
|
||||
jitter: 0,
|
||||
retries: 2,
|
||||
multiUrl: false,
|
||||
skipProxy: true,
|
||||
verifyHeaders: {
|
||||
Accept: 'application/json, text/javascript, /; q=0.01',
|
||||
'Accept-Language': 'en-US,en;q=0.9',
|
||||
@@ -730,7 +718,6 @@ export const SOURCES = [
|
||||
jitter: 500,
|
||||
retries: 2,
|
||||
multiUrl: true,
|
||||
skipProxy: true,
|
||||
},
|
||||
|
||||
{
|
||||
@@ -742,7 +729,6 @@ export const SOURCES = [
|
||||
jitter: 500,
|
||||
retries: 2,
|
||||
multiUrl: true,
|
||||
skipProxy: true,
|
||||
cdnHeaders: [{
|
||||
pattern: /hscow\.com|downloadwella\.com|willowmovies\.com/i,
|
||||
headers: {
|
||||
@@ -761,7 +747,6 @@ export const SOURCES = [
|
||||
jitter: 500,
|
||||
retries: 2,
|
||||
multiUrl: true,
|
||||
skipProxy: true,
|
||||
cdnHeaders: [{
|
||||
pattern: /films365\.org/i,
|
||||
headers: {
|
||||
@@ -780,7 +765,6 @@ export const SOURCES = [
|
||||
jitter: 500,
|
||||
retries: 2,
|
||||
multiUrl: true,
|
||||
skipProxy: true,
|
||||
cdnHeaders: [{
|
||||
pattern: /yapgrid\.com|embedwave\.cc/i,
|
||||
headers: {
|
||||
|
||||
@@ -1,61 +1,205 @@
|
||||
import { fetchJson, fetchText, getRandomHex } from '../utils/helpers.js';
|
||||
import crypto from 'node:crypto';
|
||||
|
||||
const API_BASE = 'https://enc-dec.app/api';
|
||||
const BASE_URL = 'https://hexa.su';
|
||||
const TMDB_PROXY_BASE = 'https://theemoviedb.hexa.su';
|
||||
const BASE_URL = 'https://theemoviedb.hexa.su';
|
||||
const CAP_URL = 'https://cap.hexa.su/15d2cf0395';
|
||||
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 CHALLENGE_HEADERS = {
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json',
|
||||
'Accept-Language': 'en-US,en;q=0.9,ru;q=0.8,fr;q=0.7,bg;q=0.6,pl;q=0.5,id;q=0.4,zh-CN;q=0.3,zh;q=0.2,ja;q=0.1,es;q=0.1',
|
||||
Origin: 'https://hexa.su',
|
||||
Referer: 'https://hexa.su/',
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36 OPR/129.0.0.0 (Edition std-2)',
|
||||
};
|
||||
|
||||
const PLAYBACK_HEADERS = {
|
||||
'User-Agent': UA,
|
||||
Referer: 'https://hexa.su/',
|
||||
Origin: 'https://hexa.su',
|
||||
};
|
||||
|
||||
let cachedCapToken = null;
|
||||
let tokenUseCount = 0;
|
||||
|
||||
async function fetchCapToken() {
|
||||
if (cachedCapToken && tokenUseCount < 18) {
|
||||
tokenUseCount++;
|
||||
return cachedCapToken;
|
||||
}
|
||||
|
||||
try {
|
||||
const chalRes = await fetch(`${CAP_URL}/challenge`, {
|
||||
method: 'POST',
|
||||
headers: CHALLENGE_HEADERS,
|
||||
signal: AbortSignal.timeout(8000),
|
||||
});
|
||||
if (!chalRes.ok) return null;
|
||||
const challengeData = await chalRes.json();
|
||||
|
||||
const encRes = await fetch(`${DEC_API}/enc-hexa`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'User-Agent': UA,
|
||||
},
|
||||
body: JSON.stringify({ challenge_data: challengeData }),
|
||||
signal: AbortSignal.timeout(8000),
|
||||
});
|
||||
if (!encRes.ok) return null;
|
||||
const encJson = await encRes.json();
|
||||
if (encJson?.status !== 200 || !encJson.result) return null;
|
||||
|
||||
const redeemRes = await fetch(`${CAP_URL}/redeem`, {
|
||||
method: 'POST',
|
||||
headers: CHALLENGE_HEADERS,
|
||||
body: JSON.stringify(encJson.result),
|
||||
signal: AbortSignal.timeout(8000),
|
||||
});
|
||||
if (!redeemRes.ok) return null;
|
||||
const redeemJson = await redeemRes.json();
|
||||
|
||||
if (redeemJson?.token) {
|
||||
cachedCapToken = redeemJson.token;
|
||||
tokenUseCount = 1;
|
||||
return cachedCapToken;
|
||||
}
|
||||
} catch {
|
||||
cachedCapToken = null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function extractStreams(result) {
|
||||
const list = [];
|
||||
if (!result) return list;
|
||||
|
||||
const data = typeof result === 'string' && (result.startsWith('{') || result.startsWith('['))
|
||||
? JSON.parse(result)
|
||||
: result;
|
||||
|
||||
if (typeof data === 'string' && /^https?:\/\//.test(data)) {
|
||||
list.push({
|
||||
url: data,
|
||||
server: 'Hexa - Direct',
|
||||
quality: 'Auto',
|
||||
type: data.includes('.m3u8') ? 'hls' : (data.includes('.mpd') ? 'dash' : 'mp4'),
|
||||
headers: PLAYBACK_HEADERS,
|
||||
});
|
||||
return list;
|
||||
}
|
||||
|
||||
const rawSources = Array.isArray(data)
|
||||
? data
|
||||
: Array.isArray(data?.sources)
|
||||
? data.sources
|
||||
: Array.isArray(data?.streams)
|
||||
? data.streams
|
||||
: [];
|
||||
|
||||
for (const item of rawSources) {
|
||||
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`;
|
||||
|
||||
list.push({
|
||||
url: streamUrl,
|
||||
server: `Hexa - ${item.server || item.name || 'Direct'}`,
|
||||
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')) {
|
||||
list.push({
|
||||
url: data.url,
|
||||
server: `Hexa - ${data.server || 'Direct'}`,
|
||||
quality: data.quality || 'Auto',
|
||||
type: data.type || (data.url.includes('.m3u8') ? 'hls' : (data.url.includes('.mpd') ? 'dash' : 'mp4')),
|
||||
headers: PLAYBACK_HEADERS,
|
||||
});
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
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 key = getRandomHex(32);
|
||||
const path = isTv
|
||||
? `/api/tmdb/tv/${idNum}/season/${s}/episode/${e}/images`
|
||||
: `/api/tmdb/movie/${idNum}/images`;
|
||||
|
||||
const capToken = await fetchCapToken();
|
||||
if (!capToken) return null;
|
||||
|
||||
const key = crypto.randomBytes(32).toString('hex');
|
||||
|
||||
const headers = {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36',
|
||||
Referer: `${BASE_URL}/`,
|
||||
'User-Agent': UA,
|
||||
Referer: 'https://hexa.su/',
|
||||
Origin: 'https://hexa.su',
|
||||
Accept: 'text/plain',
|
||||
'X-Fingerprint-Lite': 'e9136c41504646444',
|
||||
'X-Cap-Token': capToken,
|
||||
'X-Api-Key': key,
|
||||
};
|
||||
|
||||
const encHexaRes = await fetchJson(`${API_BASE}/enc-hexa`);
|
||||
if (!encHexaRes || encHexaRes.status !== 200) return null;
|
||||
headers['X-Cap-Token'] = encHexaRes.result.token;
|
||||
try {
|
||||
const res = await fetch(`${BASE_URL}${path}`, {
|
||||
headers,
|
||||
signal: AbortSignal.timeout(12000),
|
||||
});
|
||||
|
||||
const url = isTv
|
||||
? `${TMDB_PROXY_BASE}/api/tmdb/tv/${id}/season/${s}/episode/${e}/images`
|
||||
: `${TMDB_PROXY_BASE}/api/tmdb/movie/${id}/images`;
|
||||
if (!res.ok) {
|
||||
cachedCapToken = null;
|
||||
return null;
|
||||
}
|
||||
|
||||
const encrypted = await fetchText(url, { headers });
|
||||
if (!encrypted) return null;
|
||||
const encryptedText = (await res.text()).trim();
|
||||
if (!encryptedText) return null;
|
||||
|
||||
const decRes = await fetchJson(`${API_BASE}/dec-hexa`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ text: encrypted, key }),
|
||||
});
|
||||
const decRes = await fetch(`${DEC_API}/dec-hexa`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'User-Agent': UA,
|
||||
},
|
||||
body: JSON.stringify({ text: encryptedText, key }),
|
||||
signal: AbortSignal.timeout(10000),
|
||||
});
|
||||
|
||||
if (!decRes || decRes.status !== 200 || !decRes.result) return null;
|
||||
if (!decRes.ok) return null;
|
||||
const decJson = await decRes.json();
|
||||
if (decJson?.status !== 200 || !decJson.result) return null;
|
||||
|
||||
const data = decRes.result;
|
||||
const streamUrl = data.url || data.file || (Array.isArray(data.sources) ? data.sources[0]?.url : null);
|
||||
if (!streamUrl) return null;
|
||||
const streams = extractStreams(decJson.result);
|
||||
if (!streams.length) return null;
|
||||
|
||||
const streamHeaders = {
|
||||
Referer: `${BASE_URL}/`,
|
||||
Origin: BASE_URL,
|
||||
};
|
||||
const allUrls = [];
|
||||
const seen = new Set();
|
||||
|
||||
const allUrls = [{
|
||||
url: streamUrl,
|
||||
server: 'Hexa',
|
||||
quality: 'Auto',
|
||||
type: streamUrl.includes('.m3u8') ? 'hls' : 'mp4',
|
||||
headers: streamHeaders,
|
||||
}];
|
||||
for (const item of streams) {
|
||||
if (!item?.url || seen.has(item.url)) continue;
|
||||
seen.add(item.url);
|
||||
allUrls.push(item);
|
||||
}
|
||||
|
||||
return {
|
||||
url: streamUrl,
|
||||
headers: streamHeaders,
|
||||
allUrls,
|
||||
};
|
||||
if (!allUrls.length) return null;
|
||||
|
||||
return {
|
||||
url: allUrls[0].url,
|
||||
headers: allUrls[0].headers,
|
||||
allUrls,
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
179
src/sources/nexa.js
Normal file
179
src/sources/nexa.js
Normal file
@@ -0,0 +1,179 @@
|
||||
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,
|
||||
};
|
||||
}
|
||||
@@ -13,7 +13,7 @@ const CONFIG = {
|
||||
TV_ID: "37854", // TV TMDB ID to test, for anime I recommend using a random ID like 37854
|
||||
TV_SEASON: "1", // TV Season to test
|
||||
TV_EPISODE: "1", // TV Episode to test
|
||||
STREAM_SOURCE: "", // Source key to test
|
||||
STREAM_SOURCE: "hexa", // Source key to test
|
||||
|
||||
TEST_SUBTITLE: false,
|
||||
TEST_DOWNLOAD: false,
|
||||
|
||||
Reference in New Issue
Block a user