added zxstream as a source

This commit is contained in:
2026-08-21 20:22:43 -07:00
parent 5ebd8d902f
commit 280cc0c3dd
3 changed files with 155 additions and 3 deletions

View File

@@ -302,7 +302,7 @@ export const SOURCES = [
skipProxy: true,
disabled: false, // Enabled, but only returns streams when a manifest is provided
},
{
key: 'nova',
label: 'Nova',
@@ -313,7 +313,7 @@ export const SOURCES = [
retries: 1,
multiUrl: true,
},
{
key: 'megasource',
label: 'MegaSource',
@@ -568,6 +568,17 @@ export const SOURCES = [
skipProxy: true,
},
{
key: 'zxcstream',
label: 'ZxcStream',
sourceFile: 'zxcstream',
proxyParam: 'zxc',
timeout: 25000,
jitter: 500,
retries: 2,
multiUrl: true,
},
];
export const HEALTH_PROBE_ID = '155';

141
src/sources/zxcstream.js Normal file
View File

@@ -0,0 +1,141 @@
import crypto from 'crypto';
import { getTmdbInfo, getUA } from '../utils/helpers.js';
const BASE_URL = 'https://player.zxcstream.xyz';
const AES_KEY = '7f4c9e2a81d63b05c4f7a9e8126d3b50e1a8c7f23d9465ab0c6e9f1d4a7b832c';
const SERVERS = ['berkas', 'orion', 'aquarius', 'resshin'];
function decryptCryptoJS(ciphertextB64, passphrase) {
try {
const buf = Buffer.from(ciphertextB64, 'base64');
if (buf.slice(0, 8).toString('utf8') !== 'Salted__') {
const key = crypto.createHash('md5').update(passphrase).digest();
const iv = Buffer.alloc(16, 0);
const decipher = crypto.createDecipheriv('aes-128-cbc', key, iv);
return Buffer.concat([decipher.update(buf), decipher.final()]).toString('utf8');
}
const salt = buf.slice(8, 16);
const cipherBytes = buf.slice(16);
let hash = Buffer.alloc(0);
let keyAndIv = Buffer.alloc(0);
while (keyAndIv.length < 48) {
hash = crypto.createHash('md5').update(Buffer.concat([hash, Buffer.from(passphrase), salt])).digest();
keyAndIv = Buffer.concat([keyAndIv, hash]);
}
const key = keyAndIv.slice(0, 32);
const iv = keyAndIv.slice(32, 48);
const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
return Buffer.concat([decipher.update(cipherBytes), decipher.final()]).toString('utf8');
} catch {
return null;
}
}
export async function getStream({ id, s, e }) {
try {
const isTv = Boolean(s && e);
const mediaType = isTv ? 'tv' : 'movie';
let title = 'Media';
let year = '2024';
try {
const info = await getTmdbInfo(id, mediaType, s);
if (info?.title) title = info.title;
if (info?.year) year = String(info.year);
} catch { }
const headers = {
'User-Agent': getUA(),
Referer: `${BASE_URL}/`,
Origin: BASE_URL,
Accept: 'application/json, text/plain, */*',
};
const serverResults = await Promise.all(
SERVERS.map(async (server) => {
try {
const tokenRes = await fetch(`${BASE_URL}/backend/you-are-gay`, {
method: 'POST',
headers: { ...headers, 'Content-Type': 'application/json' },
body: JSON.stringify({
id,
media_type: mediaType,
path: server,
...(isTv ? { season: Number(s || 1), episode: Number(e || 1) } : {}),
}),
signal: AbortSignal.timeout(8000),
});
if (!tokenRes.ok) return [];
const tokenData = await tokenRes.json();
if (!tokenData?.token) return [];
const params = new URLSearchParams({
id,
b: mediaType,
ts: String(tokenData.ts),
token: tokenData.token,
title,
year,
date: year,
});
if (isTv) {
params.append('season', String(s || 1));
params.append('episode', String(e || 1));
}
const sourcesRes = await fetch(`${BASE_URL}/backend_/sources/${server}?${params.toString()}`, {
headers,
signal: AbortSignal.timeout(8000),
});
if (!sourcesRes.ok) return [];
const sourcesData = await sourcesRes.json();
const links = [];
if (Array.isArray(sourcesData?.links)) {
for (const item of sourcesData.links) {
if (!item.link) continue;
const decryptedUrl = decryptCryptoJS(item.link, AES_KEY);
if (decryptedUrl && decryptedUrl.startsWith('http')) {
links.push({
url: decryptedUrl,
type: decryptedUrl.includes('.m3u8') ? 'hls' : decryptedUrl.includes('.mpd') ? 'dash' : 'mp4',
quality: item.resolution || 'Auto',
server: `ZxcStream-${server.toUpperCase()}`,
headers: {
Referer: `${BASE_URL}/`,
Origin: BASE_URL,
},
skipProxy: true,
});
}
}
}
return links;
} catch {
return [];
}
})
);
const allUrls = serverResults.flat();
if (!allUrls.length) return null;
return { allUrls };
} catch {
return null;
}
}
export async function getSources(args) {
const stream = await getStream(args);
return stream?.allUrls ? [...new Set(stream.allUrls.map(u => u.server))] : [];
}

View File

@@ -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: "pengu", // Source key to test
STREAM_SOURCE: "", // Source key to test
TEST_SUBTITLE: false, // Toggle subtitle testing
TEST_DOWNLOAD: false, // Toggle download testing