config.js modified, downloads.js modified, trendimovies.js removed, sdk.js modified, anipm.js removed, mapple.js removed, megasource.js removed, meowtv.js modified, nova.js removed, purstream.js modified, videasy.js removed, vidvault.js modified, xpass.js removed, zxcstream.js removed, subtitles.js modified, test/index.js modified, 111477.js added, 4khdhub.js added, downloadeverything.js added, movienight.js added, movy.js added, streamaggregator.js added, vadapav.js added, vuflix.js added, xdownloader.js added

This commit is contained in:
2026-09-13 18:18:33 -07:00
parent 9e3431b5f7
commit 2e04c64f66
26 changed files with 2171 additions and 865 deletions

View File

@@ -1,4 +1,4 @@
// Currently active sources reported on 9:22:41 PM PST 7/30/2026
// Currently active sources reported on 6:18:22 PM PST 9/13/2026
// None Anime Tested: 936075
// Anime Tested: 37854
@@ -6,6 +6,18 @@
// Fshare/Fsonic Tested: 155 ( they don't have Michael )
export const SOURCES = [
{
key: '4khdhub',
label: '4KHDHub',
sourceFile: '4khdhub',
proxyParam: 'khd',
timeout: 35000,
jitter: 500,
retries: 1,
multiUrl: true,
skipProxy: true,
},
{
key: '123anime',
label: '123Anime',
@@ -25,6 +37,25 @@ export const SOURCES = [
},],
},
{
key: '111477',
label: '111477',
sourceFile: '111477',
proxyParam: 'a11',
timeout: 25000,
jitter: 500,
retries: 2,
multiUrl: true,
skipProxy: true,
cdnHeaders: [{
pattern: /111477\.xyz/i,
headers: {
Referer: 'https://st.111477.xyz/',
Origin: 'https://st.111477.xyz',
},
}],
},
{
key: 'anihq-sub',
label: 'AniHQ (Sub)',
@@ -69,40 +100,6 @@ export const SOURCES = [
retries: 2
},
{
key: 'anipm-sub',
sourceFile: 'anipm',
label: 'AniPM (Sub)',
proxyParam: 'apsub',
timeout: 25000,
jitter: 500,
retries: 2,
skipProxy: true,
skipVerify: true,
multiUrl: true,
verifyHeaders: {
Referer: 'https://ani.pm/',
Origin: 'https://ani.pm',
},
},
{
key: 'anipm-dub',
sourceFile: 'anipm',
label: 'AniPM (Dub)',
proxyParam: 'apdub',
timeout: 25000,
jitter: 500,
retries: 2,
skipProxy: true,
skipVerify: true,
multiUrl: true,
verifyHeaders: {
Referer: 'https://ani.pm/',
Origin: 'https://ani.pm',
},
},
{
key: 'bcine',
label: 'Bcine',
@@ -173,6 +170,25 @@ export const SOURCES = [
}],
},
{
key: 'downloadeverything',
label: 'DownloadEverything',
sourceFile: 'downloadeverything',
proxyParam: 'de',
timeout: 35000,
jitter: 500,
retries: 1,
multiUrl: true,
skipProxy: true,
cdnHeaders: [{
pattern: /cloudflarestorage\.com|pixeldrain\.com|hakunaymatata\.com/i,
headers: {
Referer: 'https://downloadeverythingfromeverywhere.com/',
Origin: 'https://downloadeverythingfromeverywhere.com',
},
}],
},
{
key: 'flaxmovies',
label: 'FlaxMovies',
@@ -314,16 +330,6 @@ export const SOURCES = [
skipProxy: true,
},
{
key: 'mapple',
label: 'Mapple',
sourceFile: 'mapple',
proxyParam: 'mp',
timeout: 15000,
retries: 1,
jitter: 0
},
{
key: 'meowtv',
label: 'MeowTV',
@@ -336,6 +342,44 @@ export const SOURCES = [
skipProxy: true
},
{
key: 'movienight',
label: 'MovieNight',
sourceFile: 'movienight',
proxyParam: 'mn',
timeout: 20000,
jitter: 500,
retries: 2,
multiUrl: true,
skipProxy: true,
cdnHeaders: [{
pattern: /movienig\.ht/i,
headers: {
Referer: 'https://movienig.ht/',
Origin: 'https://movienig.ht',
},
}],
},
{
key: 'movy',
label: 'Movy',
sourceFile: 'movy',
proxyParam: 'mv',
timeout: 25000,
jitter: 500,
retries: 2,
multiUrl: true,
skipProxy: true,
cdnHeaders: [{
pattern: /wecollege\.net|movy\.bz/i,
headers: {
Referer: 'https://www.movy.bz/',
Origin: 'https://www.movy.bz',
},
}],
},
{
key: 'peestream',
label: 'PeeStream',
@@ -372,28 +416,6 @@ export const SOURCES = [
multiUrl: true,
},
{
key: 'nova',
label: 'Nova',
sourceFile: 'nova',
proxyParam: 'nv',
timeout: 25000,
jitter: 500,
retries: 1,
multiUrl: true,
},
{
key: 'megasource',
label: 'MegaSource',
sourceFile: 'megasource',
proxyParam: 'mg',
timeout: 25000,
jitter: 500,
retries: 1,
multiUrl: true,
},
{
key: 'purstream',
sourceFile: 'purstream',
@@ -416,6 +438,44 @@ export const SOURCES = [
multiUrl: true,
},
{
key: 'streamaggregator',
label: 'StreamAggregator',
sourceFile: 'streamaggregator',
proxyParam: 'sa',
timeout: 25000,
jitter: 500,
retries: 2,
multiUrl: true,
skipProxy: true,
cdnHeaders: [{
pattern: /streamaggregator\.in/i,
headers: {
Referer: 'https://streamaggregator.in/',
Origin: 'https://streamaggregator.in',
},
}],
},
{
key: 'vadapav',
label: 'Vadapav',
sourceFile: 'vadapav',
proxyParam: 'vp',
timeout: 25000,
jitter: 500,
retries: 2,
multiUrl: true,
skipProxy: true,
cdnHeaders: [{
pattern: /vadapav\.mov/i,
headers: {
Referer: 'https://stremio.vadapav.mov/',
Origin: 'https://stremio.vadapav.mov',
},
}],
},
{
key: 'vidapi',
label: 'VidAPI',
@@ -438,23 +498,6 @@ export const SOURCES = [
sourcesTimeout: 10000,
},
{
key: 'videasy',
label: 'Videasy',
sourceFile: 'videasy',
proxyParam: 'vy',
timeout: 40000,
sourcesTimeout: 10000,
jitter: 900,
retries: 3,
multiUrl: true,
verifyHeaders: {
Accept: 'application/json, /; q=0.01',
Referer: 'https://player.videasy.net/',
Origin: 'https://player.videasy.net',
},
},
{
key: 'vidfast',
label: 'Vidfast',
@@ -487,7 +530,6 @@ export const SOURCES = [
retries: 2,
skipProxy: true,
multiUrl: true,
disabled: true, // Temporarily disabled because their site is down, all streams are returning 403's
},
{
@@ -603,25 +645,41 @@ export const SOURCES = [
},
{
key: 'xpass',
sourceFile: 'xpass',
label: 'XPass',
proxyParam: 'xp',
timeout: 20000,
jitter: 500,
retries: 2,
skipProxy: true,
},
{
key: 'zxcstream',
label: 'ZxcStream',
sourceFile: 'zxcstream',
proxyParam: 'zxc',
key: 'vuflix',
label: 'Vuflix',
sourceFile: 'vuflix',
proxyParam: 'vfx',
timeout: 25000,
jitter: 500,
retries: 2,
multiUrl: true,
skipProxy: true,
cdnHeaders: [{
pattern: /vuflix\.co/i,
headers: {
Referer: 'https://vuflix.co/',
Origin: 'https://vuflix.co',
},
}],
},
{
key: 'xdownloader',
label: 'XDownloader',
sourceFile: 'xdownloader',
proxyParam: 'xd',
timeout: 25000,
jitter: 500,
retries: 2,
multiUrl: true,
skipProxy: true,
cdnHeaders: [{
pattern: /films365\.org/i,
headers: {
Referer: 'https://www.films365.org/',
Origin: 'https://www.films365.org',
},
}],
},
];

View File

@@ -1,12 +1,53 @@
import { getDownloads, getDownloadsTv } from "./sources/trendimovies.js";
import SOURCES from "../config.js";
export async function fetchDownloads(id, s = null, e = null) {
try {
const res = s != null && e != null
? await getDownloadsTv(id, s, e)
: await getDownloads(id);
return res || [];
} catch {
return [];
export async function fetchDownloads(sdkOrId, idOrS = null, sOrE = null, maybeE = null) {
let sdk = null;
let id = sdkOrId;
let s = idOrS;
let e = sOrE;
if (sdkOrId && typeof sdkOrId === 'object' && typeof sdkOrId.getStream === 'function') {
sdk = sdkOrId;
id = idOrS;
s = sOrE;
e = maybeE;
}
if (!sdk || !id) return [];
const activeSources = SOURCES.filter(cfg => !cfg.disabled && cfg.skipProxy);
const seenUrls = new Set();
const downloads = [];
const results = await Promise.allSettled(
activeSources.map(async (cfg) => {
try {
const res = await sdk.getStream(cfg.key, id, s, e);
if (!res) return;
const items = Array.isArray(res.allUrls) && res.allUrls.length > 0
? res.allUrls
: (res.url ? [{ url: res.url, label: cfg.label }] : []);
for (const item of items) {
if (!item?.url || typeof item.url !== 'string') continue;
const url = item.url.trim();
if (!url.startsWith('http') || url.includes('.m3u8')) continue;
if (!/\.mp4(\?|$)/i.test(url) && !url.includes('.mp4')) continue;
if (seenUrls.has(url)) continue;
seenUrls.add(url);
downloads.push({
source: cfg.key,
label: item.label || cfg.label,
url,
});
}
} catch { }
})
);
return downloads;
}

View File

@@ -1,63 +0,0 @@
const BASE = "https://trendimovies.com";
const HEADERS = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.9",
"Referer": "https://trendimovies.com/",
};
async function fetchPage(path) {
const res = await fetch(`${BASE}${path}`, { headers: HEADERS });
if (!res.ok) throw new Error(`trendimovies ${res.status}`);
return res.text();
}
function parseLinks(html) {
const m = html.match(/DownloadSection[^>]*props="([^"]+)"/s) ||
html.match(/props="([^"]+)"[^>]*ssr[^>]*client="load"[^>]*opts="[^"]*DownloadSection/s);
if (!m) {
const alt = html.match(/"DownloadSection"/);
if (!alt) return [];
const propsMatch = html.match(/props="({[^"]*DownloadSection[^"]*})"/s);
if (!propsMatch) return [];
return extractLinks(propsMatch[1].replace(/"/g, '"').replace(/'/g, "'"));
}
return extractLinks(m[1].replace(/"/g, '"').replace(/'/g, "'"));
}
function extractLinks(propsJson) {
try {
const parsed = JSON.parse(propsJson);
const rawLinks = parsed?.links?.[1];
if (!Array.isArray(rawLinks)) return [];
return rawLinks
.map(item => item?.[1])
.filter(l => l && typeof l === "object")
.map(l => ({
url: unwrap(l.url),
quality: unwrap(l.quality) || "HD",
size: unwrap(l.file_size) || null,
type: "mkv",
active: unwrap(l.is_active) !== false,
}))
.filter(l => l.url && l.active);
} catch {
return [];
}
}
function unwrap(v) {
if (Array.isArray(v) && v.length === 2 && (v[0] === 0 || v[0] === 1)) return v[1];
return v;
}
export async function getDownloads(tmdbId) {
const html = await fetchPage(`/movie/${tmdbId}`);
return parseLinks(html);
}
export async function getDownloadsTv(tmdbId, season, episode) {
const html = await fetchPage(`/tv/${tmdbId}/season/${season}/episode/${episode}`);
return parseLinks(html);
}

1
src/sdk.d.ts vendored
View File

@@ -59,7 +59,6 @@ export interface VylaSDKOptions {
tmdbApiKey?: string;
}
// Descriptions written with Claude.
export default class VylaSDK {
tmdbApiKey?: string;

View File

@@ -63,6 +63,6 @@ export default class VylaSDK {
return await fetchSubtitles(paths);
}
async getDownloads(id, s = null, e = null) {
return await fetchDownloads(id, s, e);
return await fetchDownloads(this, id, s, e);
}
}

101
src/sources/111477.js Normal file
View File

@@ -0,0 +1,101 @@
import { getUA } from '../utils/helpers.js';
const SERVICE_ORIGIN = 'https://st.111477.xyz';
function generateManifestBaseUrl(tmdbKey) {
let config = 'https://a.111477.xyz/::sort=file-desc::limit=3';
if (tmdbKey) {
config += `::tmdb=${tmdbKey}`;
}
const b64 = Buffer.from(config, 'utf8')
.toString('base64')
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=+$/, '');
return `${SERVICE_ORIGIN}/config/${b64}`;
}
async function resolveImdbId(sdk, id, isTv) {
const apiKey = sdk?.tmdbApiKey || process.env.TMDB_API_KEY;
if (!apiKey) return null;
const type = isTv ? 'tv' : 'movie';
try {
const res = await fetch(`https://api.themoviedb.org/3/${type}/${id}?api_key=${apiKey}&append_to_response=external_ids`, {
signal: AbortSignal.timeout(6000),
});
if (!res.ok) return null;
const data = await res.json();
return data.imdb_id || data.external_ids?.imdb_id || null;
} catch {
return null;
}
}
export async function getStream({ id, s, e, sdk }) {
const isTv = Boolean(s && e);
const ua = getUA();
const apiKey = sdk?.tmdbApiKey || process.env.TMDB_API_KEY;
const imdbId = await resolveImdbId(sdk, id, isTv);
const targetIds = [];
if (imdbId && imdbId.startsWith('tt')) {
targetIds.push(imdbId);
}
targetIds.push(`tmdb:${id}`);
const addonBase = generateManifestBaseUrl(apiKey);
const seenUrls = new Set();
const allUrls = [];
const defaultHeaders = {
'User-Agent': ua,
Accept: 'application/json, text/plain, */*',
};
for (const targetId of targetIds) {
const endpoint = isTv
? `${addonBase}/stream/series/${targetId}:${s || 1}:${e || 1}.json`
: `${addonBase}/stream/movie/${targetId}.json`;
try {
const res = await fetch(endpoint, {
headers: defaultHeaders,
signal: AbortSignal.timeout(12000),
});
if (!res.ok) continue;
const data = await res.json();
if (Array.isArray(data?.streams) && data.streams.length > 0) {
for (const item of data.streams) {
if (!item || typeof item !== 'object') continue;
const url = String(item.url || '').trim();
if (!url || !url.startsWith('http') || seenUrls.has(url)) continue;
seenUrls.add(url);
const rawTitle = item.title ? String(item.title).trim() : '';
const rawName = item.name ? String(item.name).trim() : '111477';
const label = rawTitle || rawName;
allUrls.push({
url,
label,
headers: defaultHeaders,
});
}
if (allUrls.length > 0) break;
}
} catch { }
}
if (allUrls.length === 0) return null;
return {
url: allUrls[0].url,
allUrls,
headers: allUrls[0].headers,
};
}

324
src/sources/4khdhub.js Normal file
View File

@@ -0,0 +1,324 @@
import { getUA } from '../utils/helpers.js';
const INITIAL_BASE_URL = 'https://4khdhub.one';
let cachedBaseUrl = null;
let cachedBaseUrlExpiry = 0;
function rot13(str) {
return str.replace(/[a-zA-Z]/g, (c) => {
const code = c.charCodeAt(0);
if (code >= 65 && code <= 90) {
return String.fromCharCode(((code - 65 + 13) % 26) + 65);
}
return String.fromCharCode(((code - 97 + 13) % 26) + 97);
});
}
function levenshtein(a, b) {
if (a === b) return 0;
if (!a.length) return b.length;
if (!b.length) return a.length;
const la = a.length;
const lb = b.length;
const d = Array.from({ length: la + 1 }, () => new Array(lb + 1).fill(0));
for (let i = 0; i <= la; i++) d[i][0] = i;
for (let j = 0; j <= lb; j++) d[0][j] = j;
for (let i = 1; i <= la; i++) {
for (let j = 1; j <= lb; j++) {
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
d[i][j] = Math.min(d[i - 1][j] + 1, d[i][j - 1] + 1, d[i - 1][j - 1] + cost);
}
}
return d[la][lb];
}
async function getBaseUrl(ua) {
if (cachedBaseUrl && Date.now() < cachedBaseUrlExpiry) {
return cachedBaseUrl;
}
let currentUrl = INITIAL_BASE_URL;
for (let i = 0; i < 10; i++) {
try {
const res = await fetch(currentUrl, {
method: 'HEAD',
headers: { 'User-Agent': ua },
redirect: 'manual',
signal: AbortSignal.timeout(10000),
});
if (res.status >= 300 && res.status < 400) {
const location = res.headers.get('location');
if (location) {
currentUrl = new URL(location, currentUrl).toString();
continue;
}
}
break;
} catch {
break;
}
}
const parsed = new URL(currentUrl);
cachedBaseUrl = `${parsed.protocol}//${parsed.host}`;
cachedBaseUrlExpiry = Date.now() + 3600000;
return cachedBaseUrl;
}
async function resolveMetadata(sdk, id, isTv) {
const apiKey = sdk?.tmdbApiKey || process.env.TMDB_API_KEY;
if (!apiKey) return null;
const type = isTv ? 'tv' : 'movie';
try {
const res = await fetch(`https://api.themoviedb.org/3/${type}/${id}?api_key=${apiKey}`, {
signal: AbortSignal.timeout(8000),
});
if (!res.ok) return null;
const data = await res.json();
const title = isTv ? data.name : data.title;
const releaseDate = isTv ? data.first_air_date : data.release_date;
const year = releaseDate ? parseInt(releaseDate.split('-')[0], 10) : null;
return { title, year };
} catch {
return null;
}
}
function sanitizeStreamUrl(rawUrl) {
try {
const parsed = new URL(rawUrl);
parsed.pathname = parsed.pathname.replaceAll('::', '%3A%3A');
return parsed.toString();
} catch {
return rawUrl.replaceAll('::', '%3A%3A');
}
}
async function validateStreamUrl(url, ua) {
try {
const res = await fetch(url, {
headers: {
'User-Agent': ua,
Range: 'bytes=0-1024',
},
signal: AbortSignal.timeout(4000),
});
const contentType = (res.headers.get('content-type') || '').toLowerCase();
const isSuccess = (res.status >= 200 && res.status < 300) || res.status === 206;
const isHtmlOrJsonError = contentType.includes('text/html') || contentType.includes('application/json');
if (!isSuccess || isHtmlOrJsonError) return false;
return true;
} catch {
return true;
}
}
async function resolveRedirectUrl(redirectUrl, ua) {
try {
const res = await fetch(redirectUrl, {
headers: { 'User-Agent': ua },
signal: AbortSignal.timeout(15000),
});
if (!res.ok) return null;
const html = await res.text();
let nextUrl = '';
const downloadMatch = html.match(/<a id="download" href="(.*?)"/);
if (downloadMatch) {
nextUrl = downloadMatch[1];
} else {
const varMatch = html.match(/var url = '(.*?)';/);
if (varMatch) {
nextUrl = varMatch[1];
}
}
if (!nextUrl) {
const match = html.match(/'o','(.*?)'/);
if (!match) return null;
const rawData = match[1];
const step1 = Buffer.from(rawData, 'base64').toString('utf8');
const step2 = Buffer.from(step1, 'base64').toString('utf8');
const step3 = rot13(step2);
const step4 = Buffer.from(step3, 'base64').toString('utf8');
const data = JSON.parse(step4);
return Buffer.from(data.o, 'base64').toString('utf8');
}
const interRes = await fetch(nextUrl, {
headers: { 'User-Agent': ua },
signal: AbortSignal.timeout(15000),
});
if (!interRes.ok) return null;
const interHtml = await interRes.text();
const finalMatch = interHtml.match(/<a href="([^"]+)"[^>]*class="[^"]*btn-success/);
if (finalMatch) {
return finalMatch[1];
}
return null;
} catch {
return null;
}
}
async function extractSourceResults(itemHtml, ua) {
const sizeMatch = itemHtml.match(/([\d.]+ ?[GM]B)/);
const sizeStr = sizeMatch ? sizeMatch[1] : null;
const heightMatch = itemHtml.match(/\d{3,}p/);
const heightStr = heightMatch ? heightMatch[0] : null;
const fileTitleMatch = itemHtml.match(/class="(?:file-title|episode-file-title)"[^>]*>([^<]+)/);
const fileTitle = fileTitleMatch ? fileTitleMatch[1].trim() : 'Unknown';
const displayParts = [fileTitle];
if (heightStr) displayParts.push(heightStr);
if (sizeStr) displayParts.push(sizeStr);
let hubCloudUrl = null;
let hubDriveUrl = null;
const linkRegex = /<a\s+[^>]*href="([^"]+)"[^>]*>([\s\S]*?)<\/a>/gi;
let match;
while ((match = linkRegex.exec(itemHtml)) !== null) {
const href = match[1];
const text = match[2];
if (text.includes('HubCloud') && !hubCloudUrl) {
hubCloudUrl = href;
} else if (text.includes('HubDrive') && !hubDriveUrl) {
hubDriveUrl = href;
}
}
const redirectUrl = hubCloudUrl || hubDriveUrl;
if (!redirectUrl) return null;
const resolvedUrl = await resolveRedirectUrl(redirectUrl, ua);
if (!resolvedUrl) return null;
const safeUrl = sanitizeStreamUrl(resolvedUrl);
const isValid = await validateStreamUrl(safeUrl, ua);
if (!isValid) return null;
return {
url: safeUrl,
label: displayParts.join(' · '),
headers: {
'User-Agent': ua,
},
};
}
export async function getStream({ id, s, e, sdk }) {
const isTv = Boolean(s && e);
const ua = getUA();
const meta = await resolveMetadata(sdk, id, isTv);
if (!meta?.title) return null;
const baseUrl = await getBaseUrl(ua);
const searchUrl = `${baseUrl}/?s=${encodeURIComponent(meta.title)}`;
const searchRes = await fetch(searchUrl, {
headers: { 'User-Agent': ua },
signal: AbortSignal.timeout(15000),
});
if (!searchRes.ok) return null;
const searchHtml = await searchRes.text();
const cardRegex = /<a\s+([^>]*\bclass="[^"]*movie-card[^"]*"[^>]*)>([\s\S]*?)<\/a>/gi;
const formatFilter = isTv ? 'Series' : 'Movies';
let targetPageUrl = null;
let cardMatch;
while ((cardMatch = cardRegex.exec(searchHtml)) !== null) {
const attributes = cardMatch[1];
const inner = cardMatch[2];
const hrefMatch = attributes.match(/href="([^"]+)"/);
if (!hrefMatch) continue;
const href = hrefMatch[1];
const formatMatches = [...inner.matchAll(/class="movie-card-format"[^>]*>([\s\S]*?)<\//gi)];
const formatTexts = formatMatches.map(m => m[1].trim());
if (!formatTexts.some(t => t.includes(formatFilter))) continue;
if (meta.year != null) {
const metaMatch = inner.match(/class="movie-card-meta"[^>]*>([\s\S]*?)<\//i);
const yearMatch = metaMatch ? metaMatch[1].match(/\d{4}/) : null;
if (yearMatch) {
const cardYear = parseInt(yearMatch[0], 10);
if (Math.abs(cardYear - meta.year) > 1) continue;
}
}
const titleMatch = inner.match(/class="movie-card-title"[^>]*>([\s\S]*?)<\//i);
let cardTitle = titleMatch ? titleMatch[1] : '';
cardTitle = cardTitle.replace(/\[.*?\]/g, '').trim();
const diff = levenshtein(cardTitle.toLowerCase(), meta.title.toLowerCase());
const isMatch = diff < 5 || (cardTitle.toLowerCase().includes(meta.title.toLowerCase()) && diff < 16);
if (!isMatch) continue;
targetPageUrl = new URL(href, baseUrl).toString();
break;
}
if (!targetPageUrl) return null;
const pageRes = await fetch(targetPageUrl, {
headers: { 'User-Agent': ua },
signal: AbortSignal.timeout(15000),
});
if (!pageRes.ok) return null;
const pageHtml = await pageRes.text();
const matchedHtmlBlocks = [];
if (isTv) {
const seasonStr = String(s).padStart(2, '0');
const episodeStr = String(e).padStart(2, '0');
const epItemRegex = /class="[^"]*episode-item[^"]*"[^>]*>([\s\S]*?)(?=(?:class="[^"]*episode-item"|<\/main>|$))/gi;
let epMatch;
while ((epMatch = epItemRegex.exec(pageHtml)) !== null) {
const epBlock = epMatch[1];
const titleMatch = epBlock.match(/class="episode-title"[^>]*>([\s\S]*?)<\//i);
const titleText = titleMatch ? titleMatch[1] : '';
if (!titleText.includes(`S${seasonStr}`)) continue;
const dlItemRegex = /class="[^"]*episode-download-item[^"]*"[^>]*>([\s\S]*?)(?=(?:class="[^"]*episode-download-item"|<\/div>\s*<\/div>|$))/gi;
let dlMatch;
while ((dlMatch = dlItemRegex.exec(epBlock)) !== null) {
const dlBlock = dlMatch[1];
if (dlBlock.includes(`Episode-${episodeStr}`)) {
matchedHtmlBlocks.push(dlBlock);
}
}
}
} else {
const dlItemRegex = /class="[^"]*download-item[^"]*"[^>]*>([\s\S]*?)(?=(?:class="[^"]*download-item"|<\/main>|$))/gi;
let dlMatch;
while ((dlMatch = dlItemRegex.exec(pageHtml)) !== null) {
matchedHtmlBlocks.push(dlMatch[1]);
}
}
if (matchedHtmlBlocks.length === 0) return null;
const results = await Promise.all(matchedHtmlBlocks.map((block) => extractSourceResults(block, ua)));
const allUrls = results.filter(Boolean);
if (allUrls.length === 0) return null;
return {
url: allUrls[0].url,
allUrls,
headers: allUrls[0].headers,
};
}

View File

@@ -1,70 +0,0 @@
import { getTmdbInfo, fetchJson } from '../utils/helpers.js';
const BASE_URL = "https://ani.pm";
function cleanTitle(t) { return t ? t.toLowerCase().replace(/[^a-z0-9]/g, '') : ''; }
async function searchCatalog(query) {
try {
const data = await fetchJson(`${BASE_URL}/api/anime/catalog?q=${encodeURIComponent(query)}&page=1`, { signal: AbortSignal.timeout(6000) });
return Array.isArray(data?.items) ? data.items : [];
} catch { return []; }
}
async function resolveEntry(titles, year) {
const orderedTitles = titles.length > 1 ? [...titles].reverse() : titles;
let best = null, bestScore = -1;
for (const title of orderedTitles) {
const items = await searchCatalog(title);
const targetClean = cleanTitle(title);
for (const item of items) {
const itemTitles = [item.title, item.native].filter(Boolean).map(cleanTitle);
let score = 0;
if (itemTitles.some(t => t === targetClean)) score += 5;
else if (itemTitles.some(t => t.includes(targetClean) || targetClean.includes(t))) score += 3;
else continue;
if (item.type === 'TV') score += 4;
else if (item.type === 'Movie' || item.type === 'Special' || item.type === 'ONA') score -= 2;
if (year && item.year) {
const diff = Math.abs(item.year - year);
if (diff === 0) score += 3;
else if (diff === 1) score += 1;
else if (diff > 2) score -= 3;
}
if (score > bestScore) { bestScore = score; best = item; }
}
if (bestScore >= 9) break;
}
return best;
}
export async function getStream({ id, s, e, audio }) {
try {
const info = await getTmdbInfo(id, s ? 'tv' : 'movie', s);
if (!info || !info.isAnime) return null;
const entry = await resolveEntry(info.titles, info.year);
if (!entry) return null;
const episodeNum = e || 1;
const params = new URLSearchParams({ title: entry.title || '', ep: String(episodeNum) });
if (entry.year) params.set('year', entry.year);
if (entry.anilistId) params.set('anilistId', entry.anilistId);
if (entry.malId) params.set('malId', entry.malId);
const servers = await fetchJson(`${BASE_URL}/api/anime/src/servers?${params}`, { signal: AbortSignal.timeout(10000) });
if (!servers) return null;
const audiosToTry = audio === "all" ? ["sub", "dub"] : (audio === "dub" ? ["dub", "sub"] : ["sub", "dub"]);
for (const aud of audiosToTry) {
const list = servers[aud];
if (!Array.isArray(list) || !list.length) continue;
const allUrls = list.filter(srv => (srv.kind === "hls" || srv.kind === "file") && srv.url)
.sort((a, b) => (b.priority || 0) - (a.priority || 0))
.map(srv => ({ url: srv.url.startsWith('/') ? `${BASE_URL}${srv.url}` : srv.url, type: srv.kind === "hls" ? "hls" : "mp4", audio: aud, server: srv.provider ? `AniPM-${srv.provider}` : "AniPM", headers: { Referer: `${BASE_URL}/`, Origin: BASE_URL }, skipProxy: false }));
if (allUrls.length) return { allUrls };
}
return null;
} 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

@@ -0,0 +1,338 @@
import { getUA } from '../utils/helpers.js';
const SLAVE_URL = 'https://slave.downloadeverythingfromeverywhere.com/';
const REFERER = 'https://downloadeverythingfromeverywhere.com/';
const ORIGIN = 'https://downloadeverythingfromeverywhere.com';
const USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36';
function extractJsonObjects(text) {
const objects = [];
let depth = 0;
let start = -1;
let inString = false;
let escape = false;
for (let i = 0; i < text.length; i++) {
const char = text[i];
if (escape) {
escape = false;
continue;
}
if (char === '\\') {
escape = true;
continue;
}
if (char === '"') {
inString = !inString;
continue;
}
if (!inString) {
if (char === '{') {
if (depth === 0) start = i;
depth++;
} else if (char === '}') {
depth--;
if (depth === 0 && start !== -1) {
const jsonStr = text.substring(start, i + 1);
try {
objects.push(JSON.parse(jsonStr));
} catch { }
start = -1;
}
}
}
}
return objects;
}
async function resolveDetails(sdk, id, isTv) {
const apiKey = sdk?.tmdbApiKey || process.env.TMDB_API_KEY;
if (!apiKey) return null;
const type = isTv ? 'tv' : 'movie';
try {
const res = await fetch(`https://api.themoviedb.org/3/${type}/${id}?api_key=${apiKey}&append_to_response=external_ids`, {
signal: AbortSignal.timeout(6000),
});
if (!res.ok) return null;
const data = await res.json();
const title = isTv ? data.name : data.title;
const releaseDate = isTv ? data.first_air_date : data.release_date;
const year = releaseDate ? parseInt(releaseDate.split('-')[0], 10) : null;
const imdbId = data.imdb_id || data.external_ids?.imdb_id || null;
return { title, year, imdbId };
} catch {
return null;
}
}
async function resolveHubCloud(hubUrl, ua) {
try {
const res1 = await fetch(hubUrl, {
headers: {
'User-Agent': ua,
Referer: REFERER,
},
signal: AbortSignal.timeout(8000),
});
const html = await res1.text();
const hubPhpMatch = html.match(/https?:\/\/[^\s"<>]+\/hubcloud\.php\?[^\s"<>]+/i);
if (!hubPhpMatch) return null;
const phpUrl = hubPhpMatch[0];
const phpRes = await fetch(phpUrl, {
headers: {
'User-Agent': ua,
Referer: hubUrl,
},
signal: AbortSignal.timeout(8000),
});
const phpBody = await phpRes.text();
const r2Match = phpBody.match(/https?:\/\/[a-zA-Z0-9.\-_]+\.r2\.cloudflarestorage\.com\/[^\s"<>]+/i);
if (r2Match) {
return r2Match[0].replaceAll('&amp;', '&');
}
const pixelMatch = phpBody.match(/https?:\/\/pixel\.hubcloud\.[a-z]+\/\?id=[^\s"<>]+/i);
if (pixelMatch) {
return pixelMatch[0];
}
} catch { }
return null;
}
async function resolveClicknUpload(clicknUrl, ua) {
try {
const res1 = await fetch(clicknUrl, {
headers: {
'User-Agent': ua,
Referer: REFERER,
},
signal: AbortSignal.timeout(8000),
});
const body1 = await res1.text();
const formMatch1 = body1.match(/<form[^>]+method=["']POST["'][^>]*>([\s\S]*?)<\/form>/i);
if (!formMatch1) return null;
const inputMatches1 = [...formMatch1[1].matchAll(/<input[^>]+name=["']([^"']+)["'][^>]+value=["']([^"']*)["']/gi)];
const params1 = new URLSearchParams();
for (const m of inputMatches1) {
params1.set(m[1], m[2]);
}
params1.set('method_free', 'Slow Download');
const rawCookies = res1.headers.getSetCookie?.() || [res1.headers.get('set-cookie')].filter(Boolean);
const cookieHeader = rawCookies.join('; ');
const res2 = await fetch(clicknUrl, {
method: 'POST',
headers: {
'User-Agent': ua,
Referer: clicknUrl,
'Content-Type': 'application/x-www-form-urlencoded',
...(cookieHeader ? { Cookie: cookieHeader } : {}),
},
body: params1.toString(),
signal: AbortSignal.timeout(8000),
});
const body2 = await res2.text();
const formMatch2 = body2.match(/<form[^>]+method=["']POST["'][^>]*>([\s\S]*?)<\/form>/i);
if (!formMatch2) return null;
const inputMatches2 = [...formMatch2[1].matchAll(/<input[^>]+name=["']([^"']+)["'][^>]+value=["']([^"']*)["']/gi)];
const params2 = new URLSearchParams();
for (const m of inputMatches2) {
params2.set(m[1], m[2]);
}
params2.set('down_script', '1');
await new Promise(r => setTimeout(r, 4500));
const res3 = await fetch(clicknUrl, {
method: 'POST',
headers: {
'User-Agent': ua,
Referer: clicknUrl,
'Content-Type': 'application/x-www-form-urlencoded',
...(cookieHeader ? { Cookie: cookieHeader } : {}),
},
body: params2.toString(),
signal: AbortSignal.timeout(8000),
});
const body3 = await res3.text();
const directMatch = body3.match(/https?:\/\/[a-zA-Z0-9.\-_:]+\/d\/[a-zA-Z0-9_\-\/]+/i) ||
body3.match(/window\.open\(["'](https?:\/\/[^"']+)["']\)/i);
if (directMatch) {
const found = directMatch[1] || directMatch[0];
if (found.includes('clicknupload.') && !found.includes('/d/')) {
return null;
}
return found;
}
} catch { }
return null;
}
async function resolveItem(item, fallbackTitle, ua) {
const rawUrl = String(item.url || '').trim();
if (!rawUrl) return null;
if (
rawUrl.includes('111477.xyz') ||
rawUrl.includes('vadapav.mov') ||
rawUrl.includes('driveseed.org') ||
rawUrl.includes('new3.gdflix.io') ||
rawUrl.includes('rapidrar.cr') ||
rawUrl.includes('megaup.net') ||
rawUrl.includes('telegram.dog') ||
rawUrl.includes('t.me')
) {
return null;
}
let directStreamUrl = null;
let provider = item.site || 'DownloadEverything';
let streamHeaders = { 'User-Agent': ua };
try {
if (rawUrl.includes('hakunaymatata.com')) {
directStreamUrl = rawUrl;
provider = 'Moviebox';
streamHeaders = { 'User-Agent': 'Lavf/60.16.100' };
} else if (rawUrl.includes('pixeldrain.dev') || rawUrl.includes('pixeldrain.com')) {
const m = rawUrl.match(/pixeldrain\.(?:dev|com)\/(?:u|l)\/([a-zA-Z0-9_-]+)/);
if (m) {
directStreamUrl = `https://pixeldrain.com/api/file/${m[1]}`;
provider = 'Pixeldrain';
}
} else if (rawUrl.includes('hubcloud.') || rawUrl.includes('vcloud.zip')) {
directStreamUrl = await resolveHubCloud(rawUrl, ua);
if (directStreamUrl) {
provider = 'HubCloud';
}
} else if (rawUrl.includes('clicknupload.')) {
directStreamUrl = await resolveClicknUpload(rawUrl, ua);
if (directStreamUrl) {
provider = 'ClicknUpload';
}
} else if (
/\.(?:mp4|mkv)(?:\?|$)/i.test(rawUrl) &&
!rawUrl.includes('111477.xyz') &&
!rawUrl.includes('vadapav.mov') &&
!rawUrl.includes('.cyou/res/')
) {
try {
const check = await fetch(rawUrl, {
method: 'HEAD',
headers: { 'User-Agent': ua },
signal: AbortSignal.timeout(2000),
});
if (check.status === 200 || check.status === 206 || (check.status >= 300 && check.status < 400)) {
directStreamUrl = rawUrl;
provider = item.site || 'DirectStream';
}
} catch { }
}
} catch { }
if (!directStreamUrl) return null;
const tags = Array.isArray(item.tags) ? item.tags.map(String) : [];
const qualityMatch = tags.find(t => /2160p|4k|1080p|720p|480p/i.test(t)) || '1080p';
const rawTitle = item.name || item.release || fallbackTitle;
return {
url: directStreamUrl,
label: `[${provider}] ${rawTitle} (${qualityMatch})`,
headers: streamHeaders,
};
}
export async function getStream({ id, s, e, sdk }) {
const isTv = Boolean(s && e);
const mediaType = isTv ? 'series' : 'movie';
const ua = USER_AGENT;
const details = await resolveDetails(sdk, id, isTv);
const title = details?.title || String(id);
const tmdbId = parseInt(id, 10);
const payload = {
mode: mediaType,
title,
...(details?.year ? { year: String(details.year) } : {}),
...(tmdbId ? { tmdb_id: tmdbId } : {}),
...(details?.imdbId ? { imdb_id: details.imdbId } : {}),
...(isTv && s != null ? { season: Number(s) } : {}),
...(isTv && e != null ? { episode: Number(e) } : {}),
};
const res = await fetch(SLAVE_URL, {
method: 'POST',
headers: {
'User-Agent': ua,
Origin: ORIGIN,
Referer: REFERER,
'Content-Type': 'application/json',
Accept: 'application/x-ndjson',
'x-defe-manual': '1',
'sec-ch-ua': '"Microsoft Edge";v="153", "Not_A Brand";v="8", "Chromium";v="153"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"Windows"',
'sec-fetch-dest': 'empty',
'sec-fetch-mode': 'cors',
'sec-fetch-site': 'same-site',
},
body: JSON.stringify(payload),
signal: AbortSignal.timeout(16000),
});
if (!res.ok) return null;
let fullText = '';
if (res.body?.getReader) {
const reader = res.body.getReader();
const decoder = new TextDecoder();
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
fullText += decoder.decode(value, { stream: true });
}
} catch { }
} else {
fullText = await res.text();
}
const objects = extractJsonObjects(fullText);
const candidateItems = [];
for (const parsed of objects) {
if (parsed?.t === 'hit' && Array.isArray(parsed.links)) {
const site = parsed.site || 'DownloadEverything';
for (const l of parsed.links) {
if (l && typeof l === 'object') {
candidateItems.push({ site, ...l });
}
}
}
}
const resolved = await Promise.all(candidateItems.map(item => resolveItem(item, title, ua)));
const allUrls = resolved.filter(Boolean);
if (allUrls.length === 0) return null;
return {
url: allUrls[0].url,
allUrls,
headers: allUrls[0].headers,
};
}

View File

@@ -1,151 +0,0 @@
import { USER_AGENT } from '../utils/helpers.js';
const BASE_URL = "https://mapple.club";
const PROXY_HEADERS = { Referer: `${BASE_URL}/`, Origin: BASE_URL, "User-Agent": USER_AGENT };
const K = new Uint32Array([0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2]);
const W_BUF = new Uint32Array(64);
function sha256Into(buf, total, out) {
let h0 = 0x6a09e667, h1 = 0xbb67ae85, h2 = 0x3c6ef372, h3 = 0xa54ff53a, h4 = 0x510e527f, h5 = 0x9b05688c, h6 = 0x1f83d9ab, h7 = 0x5be0cd19;
const dv = new DataView(buf.buffer, buf.byteOffset, total);
for (let off = 0; off < total; off += 64) {
for (let i = 0; i < 16; i++) W_BUF[i] = dv.getUint32(off + i * 4, false);
for (let i = 16; i < 64; i++) {
const a = W_BUF[i - 15], b = W_BUF[i - 2];
const s0 = ((a >>> 7) | (a << 25)) ^ ((a >>> 18) | (a << 14)) ^ (a >>> 3);
const s1 = ((b >>> 17) | (b << 15)) ^ ((b >>> 19) | (b << 13)) ^ (b >>> 10);
W_BUF[i] = (W_BUF[i - 16] + s0 + W_BUF[i - 7] + s1) | 0;
}
let a = h0, b = h1, c = h2, d = h3, e = h4, f = h5, g = h6, h = h7;
for (let i = 0; i < 64; i++) {
const S1 = ((e >>> 6) | (e << 26)) ^ ((e >>> 11) | (e << 21)) ^ ((e >>> 25) | (e << 7));
const ch = (e & f) ^ (~e & g);
const t1 = (h + S1 + ch + K[i] + W_BUF[i]) | 0;
const S0 = ((a >>> 2) | (a << 30)) ^ ((a >>> 13) | (a << 19)) ^ ((a >>> 22) | (a << 10));
const maj = (a & b) ^ (a & c) ^ (b & c);
const t2 = (S0 + maj) | 0;
h = g; g = f; f = e; e = (d + t1) | 0; d = c; c = b; b = a; a = (t1 + t2) | 0;
}
h0 = (h0 + a) | 0; h1 = (h1 + b) | 0; h2 = (h2 + c) | 0; h3 = (h3 + d) | 0; h4 = (h4 + e) | 0; h5 = (h5 + f) | 0; h6 = (h6 + g) | 0; h7 = (h7 + h) | 0;
}
const odv = new DataView(out.buffer, out.byteOffset, 32);
odv.setUint32(0, h0 >>> 0, false); odv.setUint32(4, h1 >>> 0, false); odv.setUint32(8, h2 >>> 0, false); odv.setUint32(12, h3 >>> 0, false); odv.setUint32(16, h4 >>> 0, false); odv.setUint32(20, h5 >>> 0, false); odv.setUint32(24, h6 >>> 0, false); odv.setUint32(28, h7 >>> 0, false);
}
function solvePoW(challenge, difficulty) {
const encoder = new TextEncoder();
const cBuf = encoder.encode(challenge);
const maxLen = cBuf.length + 16;
const total = (maxLen + 1 + 8 + 63) & ~63;
const buf = new Uint8Array(total);
buf.set(cBuf);
const hash = new Uint8Array(32);
const maskBytes = Math.floor(difficulty / 8);
const maskBits = difficulty % 8;
const finalMask = maskBits > 0 ? (0xff << (8 - maskBits)) & 0xff : 0;
const dv = new DataView(buf.buffer);
for (let nonce = 0; nonce < 10000000; nonce++) {
const str = nonce.toString();
const len = cBuf.length + str.length;
for (let i = 0; i < str.length; i++) buf[cBuf.length + i] = str.charCodeAt(i);
buf[len] = 0x80;
for (let i = len + 1; i < total - 8; i++) buf[i] = 0;
dv.setUint32(total - 4, len * 8, false);
dv.setUint32(total - 8, 0, false);
sha256Into(buf, total, hash);
let ok = true;
for (let i = 0; i < maskBytes; i++) if (hash[i] !== 0) { ok = false; break; }
if (ok && (finalMask === 0 || (hash[maskBytes] & finalMask) === 0)) return { found: true, nonce: str };
}
return { found: false };
}
const SERVER_LIST = [{ id: "mapple", name: "Mapple" }, { id: "s1", name: "Nexus" }, { id: "s2", name: "Cipher" }, { id: "s3", name: "Pulse" }, { id: "s4", name: "Vertex" }, { id: "s10", name: "Chimp" }];
export async function getStream(args) {
const { id, s, e, server } = args;
const isTv = s != null && e != null;
const mediaType = isTv ? "tv" : "movie";
const tvSlug = isTv ? `${s}-${e}` : "";
const pageUrl = isTv ? `${BASE_URL}/watch/tv/${id}/${s}/${e}` : `${BASE_URL}/watch/movie/${id}`;
try {
const pageRes = await fetch(pageUrl, { headers: PROXY_HEADERS, signal: AbortSignal.timeout(10000) });
if (!pageRes.ok) return null;
const html = await pageRes.text();
const reqMatch = html.match(/window\.__REQUEST_TOKEN__\s*=\s*"([^"]+)"/);
if (!reqMatch) return null;
const requestToken = reqMatch[1];
const cookies = pageRes.headers.getSetCookie();
const cookieHeader = cookies.map(c => c.split(';')[0]).join('; ');
const initHeaders = { "Content-Type": "application/json", ...PROXY_HEADERS, Referer: pageUrl };
if (cookieHeader) initHeaders.Cookie = cookieHeader;
const initRes = await fetch(`${BASE_URL}/api/playback-init`, {
method: "POST",
headers: initHeaders,
body: JSON.stringify({ mediaId: Number(id), mediaType, requestToken }),
signal: AbortSignal.timeout(10000)
});
const initData = await initRes.json();
let streamToken = initData.token;
if (initData.requiresPow && initData.pow) {
const powRes = solvePoW(initData.pow.challenge, initData.pow.difficulty);
if (powRes.found) {
const solveRes = await fetch(`${BASE_URL}/api/playback-init`, {
method: "POST",
headers: initHeaders,
body: JSON.stringify({ mediaId: Number(id), mediaType, requestToken, pow: { challengeId: initData.pow.challengeId, nonce: powRes.nonce } }),
signal: AbortSignal.timeout(10000)
});
streamToken = (await solveRes.json()).token;
}
}
if (!streamToken) return null;
let targets = SERVER_LIST;
if (server && server !== "all") {
const clean = server.replace("Mapple - ", "");
targets = SERVER_LIST.filter(sv => sv.name === clean);
if (!targets.length) targets = SERVER_LIST;
}
const allUrls = [];
for (const srv of targets) {
try {
const encryptRes = await fetch(`${BASE_URL}/api/encrypt`, {
method: "POST",
headers: initHeaders,
body: JSON.stringify({
data: {
mediaId: Number(id),
mediaType,
tv_slug: tvSlug,
source: srv.id,
apikey: "mptv_sk_a8f29c4e7b3d1f"
}
}),
signal: AbortSignal.timeout(8000)
});
const encryptData = await encryptRes.json();
if (!encryptData.encrypted) continue;
const streamUrl = `${BASE_URL}/api/stream-encrypted?data=${encodeURIComponent(encryptData.encrypted)}&requestToken=${encodeURIComponent(requestToken)}&token=${encodeURIComponent(streamToken)}`;
const streamRes = await fetch(streamUrl, {
headers: { ...PROXY_HEADERS, Referer: pageUrl, ...(cookieHeader ? { Cookie: cookieHeader } : {}) },
signal: AbortSignal.timeout(8000)
});
const streamData = await streamRes.json();
if (streamData.success && streamData.data?.stream_url) {
let fileUrl = streamData.data.stream_url;
if (fileUrl.includes('omena-puu') || fileUrl.includes('nocach')) fileUrl += fileUrl.includes('?') ? '&format=.m3u8' : '?format=.m3u8';
allUrls.push({ url: fileUrl, server: `Mapple - ${srv.name}`, headers: PROXY_HEADERS, skipProxy: false, skipVerify: true, skipHlsCheck: true });
}
} catch { }
}
return allUrls.length ? { allUrls } : null;
} catch { return null; }
}
export async function getSources() { return SERVER_LIST.map(s => `Mapple - ${s.name}`); }
export const SKIP_VERIFY = true;
export const MULTI_URL = true;

View File

@@ -1,47 +0,0 @@
import { fetchJson } from '../utils/helpers.js';
const CONFIGURATION = 'W3siaWQiOiJkZWZhdWx0IiwibmFtZSI6Ik1lZ2FTb3VyY2UgZGVmYXVsdCIsInVybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS96b3JldS9tZWdhc291cmNlX3NjcmFwZXJzL3Jhdy9yZWZzL2hlYWRzL21haW4vZGVmYXVsdF9zY3JhcGVyLnB5IiwiZGVzY3JpcHRpb24iOiJEZWZhdWx0IHNjcmFwZXIgaG9zdGVkIG9uIEdpdEh1Yi4ifV0';
const BASE_URL = `https://megasource.wasmer.app/${CONFIGURATION}`;
async function getImdbId(tmdbApiKey, tmdbId, s, e) {
const key = tmdbApiKey;
if (!key) return null;
try {
const url = s != null && e != null
? `https://api.themoviedb.org/3/tv/${tmdbId}/external_ids?api_key=${key}`
: `https://api.themoviedb.org/3/movie/${tmdbId}?api_key=${key}`;
const res = await fetchJson(url);
return res?.imdb_id || null;
} catch { return null; }
}
export async function getStream({ id, s, e, sdk }) {
const tmdbApiKey = sdk?.tmdbApiKey || null;
if (!tmdbApiKey) return null;
try {
const imdbId = await getImdbId(tmdbApiKey, id, s, e);
if (!imdbId) return null;
const path = s != null && e != null
? `/stream/series/${imdbId}:${s}:${e}.json`
: `/stream/movie/${imdbId}.json`;
const res = await fetchJson(`${BASE_URL}${path}`);
if (!res?.streams?.length) return null;
const allUrls = res.streams.map(stream => {
if (!stream.url) return null;
const headers = stream.behaviorHints?.proxyHeaders?.request || {};
return {
url: stream.url,
server: `MegaSource - ${stream.title ? ` ${stream.title}` : ''}`,
headers: Object.keys(headers).length > 0 ? headers : undefined,
skipProxy: Object.keys(headers).length > 0 && stream.behaviorHints?.notWebReady === false
};
}).filter(Boolean);
return allUrls.length ? { allUrls } : null;
} catch {
return null;
}
}

View File

@@ -4,7 +4,7 @@ const API_BASE = 'https://api.meowtv.ru';
const REFERER = 'https://meowtv.ru/';
const ENC_DEC_API = 'https://enc-dec.app/api/dec-meowtv';
const HEADERS = {
const BASE_HEADERS = {
'User-Agent': USER_AGENT,
'Accept': 'application/json',
'Referer': REFERER,
@@ -13,13 +13,46 @@ const HEADERS = {
};
const SERVERS = [
'hindiv3', 'tcloud'
'dcloud', 'acme', 'tcloud', 'ipcloud', 'turkce', 'hindiv3'
];
const TIMEOUT_MS = 15000;
export const VERIFY_HEADERS = { 'User-Agent': USER_AGENT, 'Referer': REFERER, 'Origin': 'https://meowtv.ru' };
let cachedTicket = null;
let ticketExpiry = 0;
/**
* Fetches a stream ticket or returns a cached valid ticket.
*/
async function getTicket(forceRefresh = false) {
const now = Date.now();
if (!forceRefresh && cachedTicket && now < ticketExpiry - 10000) {
return cachedTicket;
}
try {
const res = await fetchJson(`${API_BASE}/streams/ticket`, {
method: 'POST',
headers: {
...BASE_HEADERS,
'Content-Type': 'application/json'
},
body: JSON.stringify({})
});
if (res?.ticket) {
cachedTicket = res.ticket;
ticketExpiry = typeof res.exp === 'number' ? res.exp : now + 60000;
return cachedTicket;
}
} catch {
}
return null;
}
async function decryptPayload(payload) {
try {
const response = await fetchJson(ENC_DEC_API, {
@@ -47,6 +80,12 @@ export async function getStream({ id, s, e, server }) {
targets = SERVERS.includes(cleanName) ? [cleanName] : [targets[0]];
}
const ticket = await getTicket();
const requestHeaders = {
...BASE_HEADERS,
...(ticket ? { 'x-stream-ticket': ticket } : {})
};
const results = await Promise.all(targets.map(async srv => {
try {
return await Promise.race([
@@ -56,7 +95,7 @@ export async function getStream({ id, s, e, server }) {
: `/streams/movie/${id}?s=${encodeURIComponent(srv)}`;
const payload = await fetchJson(`${API_BASE}${path}`, {
headers: HEADERS
headers: requestHeaders
});
if (!payload) return null;

117
src/sources/movienight.js Normal file
View File

@@ -0,0 +1,117 @@
import { getUA } from '../utils/helpers.js';
const BASE_URL = 'https://movienig.ht';
const PRIORITY_SERVERS = [
{ id: 'dallas', label: 'Dallas 4K' },
{ id: 'austin', label: 'Austin' },
{ id: 'helena', label: 'Helena' },
{ id: 'seattle', label: 'Seattle 4K' },
{ id: 'vixsrc-1', label: 'Newport Beach' },
{ id: 'tucson', label: 'Tucson' },
{ id: 'salem', label: 'Salem' },
];
async function resolveDetails(sdk, id, isTv) {
const apiKey = sdk?.tmdbApiKey || process.env.TMDB_API_KEY;
if (!apiKey) return null;
const type = isTv ? 'tv' : 'movie';
try {
const res = await fetch(`https://api.themoviedb.org/3/${type}/${id}?api_key=${apiKey}&append_to_response=external_ids`, {
signal: AbortSignal.timeout(6000),
});
if (!res.ok) return null;
const data = await res.json();
const title = isTv ? data.name : data.title;
const releaseDate = isTv ? data.first_air_date : data.release_date;
const year = releaseDate ? parseInt(releaseDate.split('-')[0], 10) : null;
const imdbId = data.imdb_id || data.external_ids?.imdb_id || null;
return { title, year, imdbId };
} catch {
return null;
}
}
export async function getStream({ id, s, e, sdk }) {
const isTv = Boolean(s && e);
const ua = getUA();
const details = await resolveDetails(sdk, id, isTv);
const title = details?.title || String(id);
const year = details?.year;
const imdbId = details?.imdbId;
const encTitle = encodeURIComponent(title);
const yearQuery = year ? `&year=${year}` : '';
const imdbQuery = imdbId ? `&imdbId=${imdbId}` : '';
const seenUrls = new Set();
const allUrls = [];
const defaultHeaders = {
'User-Agent': ua,
Referer: `${BASE_URL}/`,
Origin: BASE_URL,
Accept: 'text/event-stream',
};
const tasks = PRIORITY_SERVERS.map(async (server) => {
try {
const url = isTv
? `${BASE_URL}/api/stream/v1/tv/${id}/${s}/${e}?title=${encTitle}${yearQuery}${imdbQuery}&server=${server.id}&only=1`
: `${BASE_URL}/api/stream/v1/movie/${id}?title=${encTitle}${yearQuery}${imdbQuery}&server=${server.id}&only=1`;
const res = await fetch(url, {
headers: defaultHeaders,
signal: AbortSignal.timeout(6000),
});
if (!res.ok) return;
const bodyStr = await res.text();
if (bodyStr.includes('event: done')) {
const doneIdx = bodyStr.indexOf('event: done');
const dataIdx = bodyStr.indexOf('data: ', doneIdx);
if (dataIdx !== -1) {
const jsonStart = dataIdx + 6;
const jsonEnd = bodyStr.indexOf('\n', jsonStart);
const jsonText = (jsonEnd !== -1 ? bodyStr.substring(jsonStart, jsonEnd) : bodyStr.substring(jsonStart)).trim();
const data = JSON.parse(jsonText);
if (Array.isArray(data?.sources)) {
for (const src of data.sources) {
if (!src || typeof src !== 'object') continue;
const rawUrl = String(src.url || '').trim();
if (!rawUrl || seenUrls.has(rawUrl)) continue;
seenUrls.add(rawUrl);
const quality = src.quality ? String(src.quality) : 'Auto';
const titleQuality = quality !== 'Auto' ? ` (${quality})` : '';
allUrls.push({
url: rawUrl,
label: `MovieNight ${server.label}${titleQuality}`,
headers: {
'User-Agent': ua,
Referer: `${BASE_URL}/`,
},
});
}
}
}
}
} catch { }
});
await Promise.all(tasks);
if (allUrls.length === 0) return null;
return {
url: allUrls[0].url,
allUrls,
headers: allUrls[0].headers,
};
}

282
src/sources/movy.js Normal file
View File

@@ -0,0 +1,282 @@
import { getUA } from '../utils/helpers.js';
const API_BASE = 'https://api.wecollege.net';
const REFERER = 'https://www.movy.bz/';
const ORIGIN = 'https://www.movy.bz';
const MAGIC = [109, 118, 109, 49];
const SERVERS = [
{ endpoint: 'miami', name: 'Miami', note: 'Original audio (Up to 4K)' },
{ endpoint: 'seattle', name: 'Seattle', note: 'Original audio' },
{ endpoint: 'denver', name: 'Denver', note: 'Original audio' },
{ endpoint: 'chicago', name: 'Chicago', note: 'Original audio' },
{ endpoint: 'dallas', name: 'Dallas', note: 'Original audio' },
{ endpoint: 'atlanta', name: 'Atlanta', note: 'Original audio' },
{ endpoint: 'houston', name: 'Houston', note: 'Original audio' },
{ endpoint: 'austin', name: 'Austin', note: 'Original audio' },
{ endpoint: 'boston', name: 'Boston', note: 'Original audio' },
{ endpoint: 'munich', name: 'Munich', note: 'German audio', extra: 'language=german' },
{ endpoint: 'berlin', name: 'Berlin', note: 'German audio' },
{ endpoint: 'paris', name: 'Paris', note: 'French audio' },
{ endpoint: 'delhi', name: 'Delhi', note: 'Hindi audio' },
{ endpoint: 'cancun', name: 'Cancun', note: 'Spanish audio' },
];
const seedCache = new Map();
function _l(e) {
let v = e >>> 0;
v = (v ^ (v >>> 16)) >>> 0;
v = Math.imul(v, 0x85ebca6b) >>> 0;
v = (v ^ (v >>> 13)) >>> 0;
v = Math.imul(v, 0xc2b2ae35) >>> 0;
return (v ^ (v >>> 16)) >>> 0;
}
function _u(e, t) {
const shift = t & 31;
if (shift === 0) return e >>> 0;
return (((e << shift) >>> 0) | (e >>> (32 - shift))) >>> 0;
}
function _fnv1a(str) {
let t = 0x811c9dc5 >>> 0;
for (let i = 0; i < str.length; i++) {
const code = str.charCodeAt(i);
t = Math.imul((t ^ code) >>> 0, 0x1000193) >>> 0;
}
return _l(t);
}
function _initKeyState(seed, tmdbId) {
const s = new Array(61).fill(0);
const isSet = new Array(61).fill(false);
const idVal = ((tmdbId >>> 0) ^ 0x9e3779b9) >>> 0;
let r = _l((_fnv1a(seed) ^ _l(idVal)) >>> 0);
for (let e = 0; e < 8; e++) {
const t = r % 61;
r = _u((r + 0x9e3779b9) >>> 0, 7 + (7 & e));
s[t] = (r ^ _l(r)) >>> 0;
isSet[t] = true;
r = _l((r + t) >>> 0);
}
const acc = _l((0xa5a5a5a5 ^ r) >>> 0);
return { s, isSet, acc };
}
function _nextKeystreamWord(state, t) {
const r = state.s;
let nState = state.acc;
const i = nState % 61;
const d = state.isSet[i] ? r[i] : 0;
const c = Math.imul((t + 1) >>> 0, 0x9e3779b9) >>> 0;
const a = nState;
const sVal = (d ^ c) >>> 0;
const h = state.isSet[i] ? ((a | sVal) >>> 0) : ((a ^ sVal) >>> 0);
const term1 = _u((h + nState) >>> 0, 31 & i);
const term2 = _u(nState, 31 & (i * 7));
nState = _l((((term1 ^ term2) >>> 0) + 0x9e3779b9) >>> 0);
r[i] = nState;
state.isSet[i] = true;
state.acc = nState;
return nState >>> 0;
}
function _generateKeyStream(seed, tmdbId, len) {
const state = _initKeyState(seed, tmdbId);
const out = new Uint8Array(len);
let wordIdx = 0;
let byteIdx = 0;
while (byteIdx < len) {
const word = _nextKeystreamWord(state, wordIdx++);
out[byteIdx++] = word & 0xFF;
if (byteIdx < len) out[byteIdx++] = (word >>> 8) & 0xFF;
if (byteIdx < len) out[byteIdx++] = (word >>> 16) & 0xFF;
if (byteIdx < len) out[byteIdx++] = (word >>> 24) & 0xFF;
}
return out;
}
function decrypt(cipherB64, seed, tmdbId) {
try {
let normalized = cipherB64.replace(/-/g, '+').replace(/_/g, '/');
while (normalized.length % 4 !== 0) normalized += '=';
const cipherBytes = Buffer.from(normalized, 'base64');
if (cipherBytes.length <= MAGIC.length) return null;
const ks = _generateKeyStream(seed, tmdbId, cipherBytes.length);
const decrypted = new Uint8Array(cipherBytes.length);
for (let i = 0; i < cipherBytes.length; i++) {
decrypted[i] = cipherBytes[i] ^ ks[i];
}
for (let k = 0; k < MAGIC.length; k++) {
if (decrypted[k] !== MAGIC[k]) return null;
}
return Buffer.from(decrypted.subarray(MAGIC.length)).toString('utf8');
} catch {
return null;
}
}
async function getSeed(tmdbId, ua) {
const now = Date.now();
const cached = seedCache.get(tmdbId);
if (cached && cached.expiresAt > now + 5000) {
return cached.seed;
}
try {
const res = await fetch(`${API_BASE}/seed?mediaId=${tmdbId}`, {
headers: {
'User-Agent': ua,
Referer: REFERER,
Origin: ORIGIN,
},
signal: AbortSignal.timeout(8000),
});
if (res.ok) {
const data = await res.json();
const seed = data?.seed ? String(data.seed) : '';
const ttlMs = typeof data?.ttlMs === 'number' ? data.ttlMs : 30000;
if (seed) {
seedCache.set(tmdbId, { seed, expiresAt: now + ttlMs });
return seed;
}
}
} catch { }
return null;
}
async function resolveDetails(sdk, id, isTv) {
const apiKey = sdk?.tmdbApiKey || process.env.TMDB_API_KEY;
if (!apiKey) return null;
const type = isTv ? 'tv' : 'movie';
try {
const res = await fetch(`https://api.themoviedb.org/3/${type}/${id}?api_key=${apiKey}&append_to_response=external_ids`, {
signal: AbortSignal.timeout(6000),
});
if (!res.ok) return null;
const data = await res.json();
const title = isTv ? data.name : data.title;
const releaseDate = isTv ? data.first_air_date : data.release_date;
const year = releaseDate ? parseInt(releaseDate.split('-')[0], 10) : null;
const imdbId = data.imdb_id || data.external_ids?.imdb_id || null;
return { title, year, imdbId };
} catch {
return null;
}
}
function formatQuality(raw) {
const lower = String(raw).toLowerCase();
if (lower.includes('2160') || lower.includes('4k')) return '4K';
if (lower.includes('1080')) return '1080p';
if (lower.includes('720')) return '720p';
if (lower.includes('480')) return '480p';
if (lower.includes('360')) return '360p';
if (raw) return String(raw);
return 'Auto';
}
export async function getStream({ id, s, e, sdk }) {
const isTv = Boolean(s && e);
const mediaType = isTv ? 'tv' : 'movie';
const tmdbId = parseInt(id, 10);
const ua = getUA();
const details = await resolveDetails(sdk, id, isTv);
const title = details?.title || String(id);
const year = details?.year;
const imdbId = details?.imdbId;
const seed = await getSeed(tmdbId, ua);
if (!seed) return null;
const params = new URLSearchParams({
title,
mediaType,
tmdbId: String(tmdbId),
enc: '2',
seed,
});
if (year) params.set('year', String(year));
if (isTv) {
if (s) params.set('seasonId', String(s));
if (e) params.set('episodeId', String(e));
}
if (imdbId) params.set('imdbId', imdbId);
const baseQuery = params.toString();
const seenUrls = new Set();
const allUrls = [];
const defaultHeaders = {
'User-Agent': ua,
Referer: REFERER,
Origin: ORIGIN,
};
const tasks = SERVERS.map(async (server) => {
let fullUrl = `${API_BASE}/${server.endpoint}/sources?${baseQuery}`;
if (server.extra) fullUrl += `&${server.extra}`;
try {
const res = await fetch(fullUrl, {
headers: defaultHeaders,
signal: AbortSignal.timeout(8000),
});
if (!res.ok) return;
const encText = (await res.text()).trim();
if (!encText || encText.startsWith('<')) return;
const decJsonStr = decrypt(encText, seed, tmdbId);
if (!decJsonStr) return;
const parsed = JSON.parse(decJsonStr);
if (!Array.isArray(parsed?.sources)) return;
for (const src of parsed.sources) {
if (!src || typeof src !== 'object') continue;
const streamUrl = String(src.url || '').trim();
if (!streamUrl || seenUrls.has(streamUrl)) continue;
seenUrls.add(streamUrl);
const cleanQuality = formatQuality(src.quality || 'Auto');
const isSpecificAudio = server.note.toLowerCase().includes('audio') && !server.note.toLowerCase().includes('original');
const langLabel = isSpecificAudio ? server.note.split(' ')[0] : '';
const label = isSpecificAudio
? `${server.name} (${langLabel}) · ${cleanQuality}`
: `${server.name} · ${cleanQuality}`;
allUrls.push({
url: streamUrl,
label,
headers: {
'User-Agent': ua,
Referer: REFERER,
},
});
}
} catch { }
});
await Promise.all(tasks);
if (allUrls.length === 0) return null;
return {
url: allUrls[0].url,
allUrls,
headers: allUrls[0].headers,
};
}

View File

@@ -1,46 +0,0 @@
import { fetchJson } from '../utils/helpers.js';
const BASE_URL = `https://nova-streamz.vercel.app`;
async function getImdbId(tmdbApiKey, tmdbId, s, e) {
const key = tmdbApiKey;
if (!key) return null;
try {
const url = s != null && e != null
? `https://api.themoviedb.org/3/tv/${tmdbId}/external_ids?api_key=${key}`
: `https://api.themoviedb.org/3/movie/${tmdbId}?api_key=${key}`;
const res = await fetchJson(url);
return res?.imdb_id || null;
} catch { return null; }
}
export async function getStream({ id, s, e, sdk }) {
const tmdbApiKey = sdk?.tmdbApiKey || null;
if (!tmdbApiKey) return null;
try {
const imdbId = await getImdbId(tmdbApiKey, id, s, e);
if (!imdbId) return null;
const path = s != null && e != null
? `/stream/series/${imdbId}:${s}:${e}.json`
: `/stream/movie/${imdbId}.json`;
const res = await fetchJson(`${BASE_URL}${path}`);
if (!res?.streams?.length) return null;
const allUrls = res.streams.map(stream => {
if (!stream.url) return null;
const headers = stream.behaviorHints?.proxyHeaders?.request || {};
return {
url: stream.url,
server: `Nova - ${stream.name.replace('Nova ', '') || 'Server'}${stream.title ? ` ${stream.title.split(' | ')[0] || ''}` : ''}`,
headers: Object.keys(headers).length > 0 ? headers : undefined,
skipProxy: Object.keys(headers).length > 0 && stream.behaviorHints?.notWebReady === false
};
}).filter(Boolean);
return allUrls.length ? { allUrls } : null;
} catch {
return null;
}
}

View File

@@ -1,7 +1,7 @@
import { fetchJson, USER_AGENT, getTmdbInfo } from '../utils/helpers.js';
const DOMAIN = 'https://purstream.club';
const API_BASE = 'https://api.purstream.club/api/v1';
const DOMAIN = 'https://purstream.ad';
const API_BASE = 'https://api.purstream.ad/api/v1';
const HEADERS = {
'User-Agent': USER_AGENT,

View File

@@ -0,0 +1,127 @@
import { getUA } from '../utils/helpers.js';
const BASE_URL = 'https://streamaggregator.in';
async function resolveDetails(sdk, id, isTv) {
const apiKey = sdk?.tmdbApiKey || process.env.TMDB_API_KEY;
if (!apiKey) return null;
const type = isTv ? 'tv' : 'movie';
try {
const res = await fetch(`https://api.themoviedb.org/3/${type}/${id}?api_key=${apiKey}`, {
signal: AbortSignal.timeout(6000),
});
if (!res.ok) return null;
const data = await res.json();
const title = isTv ? data.name : data.title;
const releaseDate = isTv ? data.first_air_date : data.release_date;
const year = releaseDate ? releaseDate.split('-')[0] : null;
return { title, year };
} catch {
return null;
}
}
export async function getStream({ id, s, e, sdk }) {
const isTv = Boolean(s && e);
const mediaType = isTv ? 'tv' : 'movie';
const ua = getUA();
const details = await resolveDetails(sdk, id, isTv);
const title = details?.title || String(id);
const year = details?.year || '';
const formattedTitle = encodeURIComponent(title).replaceAll('%20', '+');
const params = new URLSearchParams({
type: mediaType,
stream: 'true',
});
if (year) params.set('year', String(year));
if (isTv) {
params.set('season', String(s));
params.set('episode', String(e));
}
const scrapeUrl = `${BASE_URL}/api/scrape?title=${formattedTitle}&${params.toString()}`;
const res = await fetch(scrapeUrl, {
headers: {
'User-Agent': ua,
Referer: `${BASE_URL}/`,
Origin: BASE_URL,
Accept: 'application/x-ndjson, text/plain, */*',
},
signal: AbortSignal.timeout(18000),
});
if (!res.ok) return null;
const lines = [];
if (res.body?.getReader) {
const reader = res.body.getReader();
const decoder = new TextDecoder('utf-8');
let buffer = '';
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const parts = buffer.split('\n');
buffer = parts.pop() || '';
for (const part of parts) {
const trimmed = part.trim();
if (trimmed) lines.push(trimmed);
}
}
} catch { }
if (buffer.trim()) lines.push(buffer.trim());
} else {
const text = await res.text();
for (const line of text.split('\n')) {
const trimmed = line.trim();
if (trimmed) lines.push(trimmed);
}
}
const seenUrls = new Set();
const allUrls = [];
for (const line of lines) {
try {
const item = JSON.parse(line);
const streamUrl = String(item?.url || '').trim();
if (!streamUrl || !streamUrl.startsWith('http') || seenUrls.has(streamUrl)) continue;
seenUrls.add(streamUrl);
const serverName = item.server || item.providerKey || 'Stream';
const quality = item.quality ? ` · ${item.quality}` : '';
const size = item.size ? ` (${item.size})` : '';
const label = `${serverName}${quality}${size}`;
const headers = {
'User-Agent': ua,
...(item.headers && typeof item.headers === 'object' ? item.headers : {}),
};
if (item.referer) {
headers.Referer = item.referer;
}
allUrls.push({
url: streamUrl,
label,
headers,
});
} catch { }
}
if (allUrls.length === 0) return null;
return {
url: allUrls[0].url,
allUrls,
headers: allUrls[0].headers,
};
}

85
src/sources/vadapav.js Normal file
View File

@@ -0,0 +1,85 @@
import { getUA } from '../utils/helpers.js';
const ADDON_BASE = 'https://stremio.vadapav.mov';
async function resolveImdbId(sdk, id, isTv) {
const apiKey = sdk?.tmdbApiKey || process.env.TMDB_API_KEY;
if (!apiKey) return null;
const type = isTv ? 'tv' : 'movie';
try {
const res = await fetch(`https://api.themoviedb.org/3/${type}/${id}?api_key=${apiKey}&append_to_response=external_ids`, {
signal: AbortSignal.timeout(6000),
});
if (!res.ok) return null;
const data = await res.json();
return data.imdb_id || data.external_ids?.imdb_id || null;
} catch {
return null;
}
}
export async function getStream({ id, s, e, sdk }) {
const isTv = Boolean(s && e);
const ua = getUA();
const imdbId = await resolveImdbId(sdk, id, isTv);
const targetIds = [];
if (imdbId && imdbId.startsWith('tt')) {
targetIds.push(imdbId);
}
targetIds.push(`tmdb:${id}`);
const seenUrls = new Set();
const allUrls = [];
const defaultHeaders = {
'User-Agent': ua,
Accept: 'application/json, text/plain, */*',
};
for (const targetId of targetIds) {
const streamPath = isTv
? `${ADDON_BASE}/stream/series/${targetId}:${s}:${e}.json`
: `${ADDON_BASE}/stream/movie/${targetId}.json`;
try {
const res = await fetch(streamPath, {
headers: defaultHeaders,
signal: AbortSignal.timeout(12000),
});
if (!res.ok) continue;
const data = await res.json();
if (Array.isArray(data?.streams) && data.streams.length > 0) {
for (const item of data.streams) {
if (!item || typeof item !== 'object') continue;
const url = String(item.url || '').trim();
if (!url || !url.startsWith('http') || seenUrls.has(url)) continue;
seenUrls.add(url);
const rawTitle = item.title ? String(item.title).trim() : '';
const rawName = item.name ? String(item.name).trim() : 'vadapav.mov';
const label = rawTitle || rawName;
allUrls.push({
url,
label,
headers: defaultHeaders,
});
}
if (allUrls.length > 0) break;
}
} catch { }
}
if (allUrls.length === 0) return null;
return {
url: allUrls[0].url,
allUrls,
headers: allUrls[0].headers,
};
}

View File

@@ -1,94 +0,0 @@
import { getTmdbInfo, fetchJson, fetchText, USER_AGENT } from '../utils/helpers.js';
const SPEEDRACE_BASE = "https://api.speedracelight.com";
const HEADERS = { "Accept": "*/*", "Origin": "https://player.videasy.to", "Referer": "https://player.videasy.to/", "User-Agent": USER_AGENT };
const SERVERS = [
{ id: 'cdn', name: 'Yoru' },
{ id: 'm4uhd', name: 'Breach' },
{ id: 'vsrc', name: 'Neon' },
{ id: 'hdmovie', name: 'Vyse' },
{ id: 'meine', name: 'Killjoy' },
{ id: 'lamovie', name: 'Omen' },
{ id: 'superflix', name: 'Raze' }
];
export async function getStream({ id, s, e, server, sdk }) {
const tmdbApiKey = sdk?.tmdbApiKey || null;
if (!tmdbApiKey) return null;
try {
const isTv = s != null && e != null;
const info = await getTmdbInfo(tmdbApiKey, id, isTv ? 'tv' : 'movie');
if (!info?.titles?.length) return null;
const seedData = await fetchJson(`${SPEEDRACE_BASE}/seed?mediaId=${id}`, { headers: HEADERS, signal: AbortSignal.timeout(5000) });
const seed = seedData?.seed;
if (!seed) return null;
const encTitle = encodeURIComponent(encodeURIComponent(info.titles[0]));
let targets = SERVERS;
if (server && server !== 'all') {
const cleanName = server.replace('VidEasy - ', '');
targets = SERVERS.filter(sv => sv.name === cleanName);
if (!targets.length) targets = SERVERS;
}
const settled = await Promise.allSettled(targets.map(async srv => {
let url = `${SPEEDRACE_BASE}/${srv.id}/sources-with-title?title=${encTitle}&mediaType=${isTv ? 'tv' : 'movie'}&year=${info.year || ''}&tmdbId=${id}&imdbId=${info.imdbId || "tt0000000"}&enc=2&seed=${seed}`;
if (isTv) url += `&episodeId=${e}&seasonId=${s}`;
const encText = await fetchText(url, { headers: HEADERS, signal: AbortSignal.timeout(10000) });
const decJson = await fetchJson("https://enc-dec.app/api/dec-videasy", {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text: encText, id: String(id), seed }),
signal: AbortSignal.timeout(10000)
});
if (decJson.status !== 200 || !decJson.result) throw new Error();
let sourcesArray = [];
let subsArray = [];
if (Array.isArray(decJson.result)) {
sourcesArray = decJson.result;
} else if (decJson.result.sources && Array.isArray(decJson.result.sources)) {
sourcesArray = decJson.result.sources;
if (decJson.result.subtitles && Array.isArray(decJson.result.subtitles)) {
subsArray = decJson.result.subtitles.map(sub => ({
url: sub.url || sub.file,
lang: sub.language || sub.lang || sub.label || 'Unknown'
})).filter(sub => sub.url);
}
} else {
sourcesArray = [decJson.result];
}
return sourcesArray.map(res => {
const streamUrl = res.url || res.file || res.link || res.playlist || res.stream;
return streamUrl ? {
url: streamUrl,
quality: res.quality || "Auto",
server: `VidEasy - ${srv.name}`,
type: streamUrl.includes('.m3u8') ? 'hls' : 'mp4',
headers: HEADERS,
subtitles: subsArray.length > 0 ? subsArray : undefined,
skipProxy: false,
skipVerify: true,
skipHlsCheck: true
} : null;
}).filter(Boolean);
}));
const allUrls = [];
for (const r of settled) {
if (r.status === 'fulfilled' && r.value) allUrls.push(...r.value);
}
return allUrls.length ? { allUrls } : null;
} catch { return null; }
}
export async function getSources() {
return SERVERS.map(s => `VidEasy - ${s.name}`);
}

View File

@@ -1,113 +1,187 @@
import { USER_AGENT } from '../utils/helpers.js';
const BASE_URL = "https://vidvault.ru";
const BASE_URL = 'https://vidvault.to';
const API_BASE = `${BASE_URL}/api`;
const DL_PROXY_BASE = 'https://dl.gemlelispe.workers.dev';
const SUB_PROXY_BASE = 'https://sub.k5s7sjozpn.workers.dev';
const HEADERS = {
"Referer": `${BASE_URL}/`,
"Origin": BASE_URL,
"User-Agent": USER_AGENT,
'Referer': `${BASE_URL}/`,
'Origin': BASE_URL,
'User-Agent': USER_AGENT,
'Accept': 'application/json, text/plain, */*',
'Accept-Language': 'en-US,en;q=0.9',
};
function formatBytes(bytes, decimals = 2) {
if (!bytes || bytes <= 0) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
const i = Math.min(Math.floor(Math.log(bytes) / Math.log(k)), sizes.length - 1);
return `${(bytes / Math.pow(k, i)).toFixed(decimals)} ${sizes[i]}`;
}
async function getToken() {
const res = await fetch(`${BASE_URL}/api/get-token`, {
method: "GET",
headers: HEADERS
const res = await fetch(`${API_BASE}/get-token`, {
method: 'GET',
headers: HEADERS,
signal: AbortSignal.timeout(10000),
});
if (!res.ok) throw new Error(`Token fetch failed: ${res.status}`);
const data = await res.json();
return data.t;
return data?.t;
}
function getCaptionType(url) {
return url.toLowerCase().includes(".vtt") ? "vtt" : "srt";
return String(url).toLowerCase().includes('.vtt') ? 'vtt' : 'srt';
}
export async function getStream(args) {
const { id, s, e } = args;
const type = (s != null && e != null) ? "tv" : "movie";
const isTv = s != null && e != null;
const type = isTv ? 'tv' : 'movie';
try {
const token = await getToken();
if (!token) return null;
const payload = { type, tmdbId: String(id) };
if (type === "tv") {
payload.season = String(s);
payload.episode = String(e);
const payload = {
type,
tmdbId: String(id),
};
if (isTv) {
payload.season = Number(s);
payload.episode = Number(e);
}
const res = await fetch(`${BASE_URL}/api/download-proxy`, {
method: "POST",
const res = await fetch(`${API_BASE}/download-proxy`, {
method: 'POST',
headers: {
...HEADERS,
"Content-Type": "application/json",
"x-request-token": token
'Content-Type': 'application/json',
'x-request-token': token,
},
body: JSON.stringify(payload)
body: JSON.stringify(payload),
signal: AbortSignal.timeout(15000),
});
if (!res.ok) return null;
const data = await res.json();
const allUrls = [];
const subtitles = [];
const titleTag = encodeURIComponent(isTv ? `Show_${id}_S${s}E${e}` : `Movie_${id}`);
const mp4 = data?.mp4Data;
if (mp4) {
const country = mp4.country || "";
const lanName = mp4.lanName || "";
const detailPath = mp4.detailPath || "";
const downloadData = mp4?.downloadInfo?.data || mp4?.data?.data || mp4?.data || mp4;
if (downloadData) {
const streams = downloadData.streams || downloadData.downloads || [];
for (const item of streams) {
if (!item?.url) continue;
const res = item.resolutions || item.resolution || 'Auto';
const sizeStr = item.size ? ` (${typeof item.size === 'number' ? formatBytes(item.size) : item.size})` : '';
const proxiedUrl = `${DL_PROXY_BASE}/${encodeURIComponent(item.url)}?n=${titleTag}`;
const downloads = mp4.downloadInfo?.data?.downloads || [];
for (const d of downloads) {
if (!d?.url) continue;
allUrls.push({
quality: `${d.resolution}p ${detailPath} ${country} ${lanName}`.trim(),
url: d.url,
type: "mp4",
headers: HEADERS
url: proxiedUrl,
server: `VidVault - MP4 ${res}p${sizeStr}`.trim(),
quality: typeof res === 'number' ? `${res}p` : res,
type: 'mp4',
headers: HEADERS,
});
}
const srtCaptions = mp4.downloadInfo?.data?.captions || [];
for (const c of srtCaptions) {
if (!c?.url) continue;
const captions = downloadData.captions || [];
for (const cap of captions) {
if (!cap?.url) continue;
const subUrl = `${SUB_PROXY_BASE}/?url=${encodeURIComponent(cap.url)}&title=${titleTag}`;
subtitles.push({
label: c.lanName || c.lan || "Unknown",
type: getCaptionType(c.url),
language: c.lan || "",
url: c.url
label: cap.lanName || cap.lan || 'Unknown',
type: getCaptionType(cap.url),
language: cap.lan || '',
url: subUrl,
});
}
}
const mkvFiles = data?.mkvData?.files || [];
for (const f of mkvFiles) {
if (!f?.url) continue;
for (const file of mkvFiles) {
if (!file?.url) continue;
const sizeStr = file.size ? ` (${typeof file.size === 'number' ? formatBytes(file.size) : file.size})` : '';
allUrls.push({
quality: `MKV ${f.size || ""}`.trim(),
url: f.url,
type: "mp4",
headers: HEADERS
url: file.url,
server: `VidVault - MKV 480p${sizeStr}`.trim(),
quality: '480p',
type: 'mp4',
headers: HEADERS,
});
}
const mkvV2 = data?.mkvV2Data;
if (mkvV2?.url) {
const mkvV2List = Array.isArray(mkvV2)
? mkvV2
: Array.isArray(mkvV2?.files)
? mkvV2.files
: (mkvV2?.url ? [mkvV2] : []);
for (const file of mkvV2List) {
if (!file?.url) continue;
const quality = file.quality ? String(file.quality).replace(/p$/i, '') : 'Auto';
const sizeStr = file.size ? ` (${typeof file.size === 'number' ? formatBytes(file.size) : file.size})` : '';
const meta = [file.country, file.language].filter(Boolean).join(' • ');
const metaStr = meta ? ` [${meta}]` : '';
allUrls.push({
quality: `${mkvV2.quality || ""} ${mkvV2.language || ""} ${mkvV2.country || ""} (${mkvV2.size || ""})`.trim(),
url: mkvV2.url,
type: "mp4",
headers: HEADERS
url: file.url,
server: `VidVault - MKV v2 ${quality}p${sizeStr}${metaStr}`.trim(),
quality: `${quality}p`,
type: 'mp4',
headers: HEADERS,
});
}
const mkvV3 = data?.mkvV3Data;
if (mkvV3?.url) {
allUrls.push({
quality: `${mkvV3.quality || ""} ${mkvV3.language || ""} ${mkvV3.country || ""} (${mkvV3.size || ""})`.trim(),
url: mkvV3.url,
type: "mp4",
headers: HEADERS
});
if (mkvV3?.downloads && Array.isArray(mkvV3.downloads)) {
const country = mkvV3.country || '';
const language = mkvV3.language || '';
for (const down of mkvV3.downloads) {
if (Array.isArray(down?.qualities)) {
for (const qObj of down.qualities) {
for (const ep of qObj?.episodes || []) {
if (!ep?.url) continue;
const quality = qObj.quality ? String(qObj.quality).replace(/p$/i, '') : 'Auto';
const sizeStr = ep.size ? ` (${typeof ep.size === 'number' ? formatBytes(ep.size) : ep.size})` : '';
const meta = [ep.country || country, ep.language || language].filter(Boolean).join(' • ');
const metaStr = meta ? ` [${meta}]` : '';
allUrls.push({
url: ep.url,
server: `VidVault - MKV v3 ${quality}p${sizeStr}${metaStr}`.trim(),
quality: `${quality}p`,
type: 'mp4',
headers: HEADERS,
});
}
}
} else if (down?.url) {
const quality = down.quality ? String(down.quality).replace(/p$/i, '') : 'Auto';
const sizeStr = down.size ? ` (${typeof down.size === 'number' ? formatBytes(down.size) : down.size})` : '';
allUrls.push({
url: down.url,
server: `VidVault - MKV v3 ${quality}p${sizeStr}`.trim(),
quality: `${quality}p`,
type: 'mp4',
headers: HEADERS,
});
}
}
}
return allUrls.length ? { allUrls, subtitles } : null;
return allUrls.length ? { allUrls, subtitles: subtitles.length ? subtitles : undefined } : null;
} catch (err) {
return null;
}

249
src/sources/vuflix.js Normal file
View File

@@ -0,0 +1,249 @@
import { getUA } from '../utils/helpers.js';
const API_BASE = 'https://vuflix.co';
const REFERER = 'https://vuflix.co/';
const ORIGIN = 'https://vuflix.co';
let cachedProviders = null;
let lastProvidersFetch = 0;
const FALLBACK_PROVIDERS = [
{ id: 'vsembed', name: 'Sigma' },
{ id: 'moonflix', name: 'Source 40' },
{ id: 'megasource', name: 'Source 39' },
{ id: 'hdghar', name: 'Source 44' },
{ id: 'moviebox', name: 'Pi' },
{ id: 'cineplay', name: '4K' },
{ id: 'huhu', name: 'Beta' },
{ id: 'bingr', name: 'Upsilon' },
{ id: 'onlyflix', name: 'Gamma' },
{ id: 'vaplayer', name: 'Alpha' },
{ id: 'flixhqz', name: 'Gamma' },
{ id: 'castle', name: 'Source 40' },
{ id: 'cinejoy', name: '4K2' },
{ id: 'filesun', name: 'Tau' },
{ id: 'yoru', name: 'Yoru' },
];
async function getProviders(ua) {
if (cachedProviders && Date.now() - lastProvidersFetch < 900000) {
return cachedProviders;
}
try {
const res = await fetch(`${API_BASE}/api/player/providers`, {
headers: {
'User-Agent': ua,
Referer: REFERER,
Origin: ORIGIN,
Accept: 'application/json, text/plain, */*',
},
signal: AbortSignal.timeout(6000),
});
if (res.ok) {
const data = await res.json();
if (data?.ok && Array.isArray(data.providers)) {
const list = data.providers
.filter(p => p && typeof p.id === 'string' && p.id.trim().length > 0)
.map(p => ({
id: p.id.trim(),
name: p.publicLabel || p.providerName || p.name || p.id,
}));
if (list.length > 0) {
cachedProviders = list;
lastProvidersFetch = Date.now();
return list;
}
}
}
} catch { }
return FALLBACK_PROVIDERS;
}
function unwrapUrl(rawUrl, ua) {
const defaultHeaders = {
'User-Agent': ua,
Referer: REFERER,
Origin: ORIGIN,
};
if (!rawUrl || typeof rawUrl !== 'string') {
return { url: '', headers: defaultHeaders };
}
if (!rawUrl.includes('v-relay?t=') && !rawUrl.includes('a-relay?t=')) {
return { url: rawUrl, headers: defaultHeaders };
}
try {
const parsedUrl = new URL(rawUrl);
const t = parsedUrl.searchParams.get('t');
if (!t) return { url: rawUrl, headers: defaultHeaders };
let b64 = t.replace(/-/g, '+').replace(/_/g, '/');
while (b64.length % 4 !== 0) {
b64 += '=';
}
const jsonStr = Buffer.from(b64, 'base64').toString('utf8');
const parsed = JSON.parse(jsonStr);
if (parsed && typeof parsed === 'object') {
const directUrl = (parsed.u || '').trim();
const headersMap = { ...defaultHeaders };
if (parsed.h && typeof parsed.h === 'object') {
for (const [key, value] of Object.entries(parsed.h)) {
headersMap[key] = String(value);
}
}
if (directUrl) {
return { url: directUrl, headers: headersMap };
}
}
} catch { }
return { url: rawUrl, headers: defaultHeaders };
}
export async function getStream({ id, s, e }) {
const isTv = Boolean(s && e);
const mediaType = isTv ? 'tv' : 'movie';
const ua = getUA();
const baseParams = new URLSearchParams({
type: mediaType,
tmdbId: String(id),
});
if (isTv) {
baseParams.set('season', String(s));
baseParams.set('episode', String(e));
}
const providers = await getProviders(ua);
const seenUrls = new Set();
const allUrls = [];
const defaultHeaders = {
'User-Agent': ua,
Referer: REFERER,
Origin: ORIGIN,
Accept: 'application/json, text/plain, */*',
};
const tasks = providers.map(async (prov) => {
try {
const url = `${API_BASE}/api/player/sources?${baseParams.toString()}&provider=${encodeURIComponent(prov.id)}`;
const res = await fetch(url, {
headers: defaultHeaders,
signal: AbortSignal.timeout(8000),
});
if (!res.ok) return;
const data = await res.json();
if (!data?.ok || !Array.isArray(data.sources)) return;
for (const item of data.sources) {
if (!item || typeof item !== 'object') continue;
const providerName = item.providerName || item.publicLabel || prov.name;
const primaryRawUrl = (item.url || '').trim();
const itemType = (item.type || 'hls').toLowerCase();
const itemLabel = item.label || '';
if (Array.isArray(item.qualities) && item.qualities.length > 0) {
for (const q of item.qualities) {
if (!q || typeof q !== 'object') continue;
const qRawUrl = (q.url || '').trim();
if (!qRawUrl) continue;
const unwrapped = unwrapUrl(qRawUrl, ua);
if (!unwrapped.url || seenUrls.has(unwrapped.url)) continue;
seenUrls.add(unwrapped.url);
const qQuality = q.quality || 'Auto';
allUrls.push({
url: unwrapped.url,
label: `${providerName} · ${qQuality}`,
headers: unwrapped.headers,
});
}
}
if (Array.isArray(item.candidates) && item.candidates.length > 0) {
let candIndex = 1;
for (const c of item.candidates) {
if (!c || typeof c !== 'object') continue;
const cRawUrl = (c.url || '').trim();
if (!cRawUrl) continue;
const unwrapped = unwrapUrl(cRawUrl, ua);
if (!unwrapped.url || seenUrls.has(unwrapped.url)) continue;
seenUrls.add(unwrapped.url);
const cQuality = c.quality || '1080p';
allUrls.push({
url: unwrapped.url,
label: `${providerName} · Mirror ${candIndex} · ${cQuality}`,
headers: unwrapped.headers,
});
candIndex++;
}
}
if (Array.isArray(item.audioTracks) && item.audioTracks.length > 0) {
for (const a of item.audioTracks) {
if (!a || typeof a !== 'object') continue;
const aRawUrl = (a.switchUrl || a.url || '').trim();
if (!aRawUrl) continue;
const unwrapped = unwrapUrl(aRawUrl, ua);
if (!unwrapped.url || seenUrls.has(unwrapped.url)) continue;
seenUrls.add(unwrapped.url);
const rawLabel = (a.label || a.name || a.language || 'Audio').trim();
const cleanLabel = rawLabel.replace(/\s*audio\s*$/i, '').trim() || 'Audio';
allUrls.push({
url: unwrapped.url,
label: `${providerName} · ${cleanLabel} Audio`,
headers: unwrapped.headers,
});
}
}
if (primaryRawUrl) {
const unwrapped = unwrapUrl(primaryRawUrl, ua);
if (unwrapped.url && !seenUrls.has(unwrapped.url)) {
seenUrls.add(unwrapped.url);
let displayQuality = item.quality || (itemType === 'mp4' ? 'MP4' : 'HD');
let cleanLabel = itemLabel || providerName;
let label = cleanLabel.startsWith('[') ? cleanLabel : `${providerName} · ${displayQuality}`;
allUrls.push({
url: unwrapped.url,
label,
headers: unwrapped.headers,
});
}
}
}
} catch { }
});
await Promise.all(tasks);
if (allUrls.length === 0) return null;
return {
url: allUrls[0].url,
allUrls,
headers: allUrls[0].headers,
};
}

129
src/sources/xdownloader.js Normal file
View File

@@ -0,0 +1,129 @@
const BASE_URL = 'https://www.films365.org';
const HEADERS = {
Authorization: 'Bearer 79a02956be35835728a044b11e2ae793149d45fb2c89cb6d029ec01aac19bfdb',
'Content-Type': 'application/json',
'User-Agent': 'MovieDownloader/1.0',
};
async function resolveTitle(sdk, id, isTv) {
const apiKey = sdk?.tmdbApiKey || process.env.TMDB_API_KEY;
if (!apiKey) return null;
const type = isTv ? 'tv' : 'movie';
try {
const res = await fetch(`https://api.themoviedb.org/3/${type}/${id}?api_key=${apiKey}`, {
signal: AbortSignal.timeout(6000),
});
if (!res.ok) return null;
const data = await res.json();
return isTv ? data.name : data.title;
} catch {
return null;
}
}
export async function getStream({ id, s, e, sdk }) {
const isTv = Boolean(s && e);
const targetType = isTv ? 'tv' : 'movie';
const title = await resolveTitle(sdk, id, isTv);
const query = title || String(id);
const searchRes = await fetch(`${BASE_URL}/api/mobile/search`, {
method: 'POST',
headers: HEADERS,
body: JSON.stringify({ query }),
signal: AbortSignal.timeout(8000),
});
if (!searchRes.ok) return null;
const searchJson = await searchRes.json();
const resultsObj = searchJson?.results;
if (!resultsObj) return null;
const items = resultsObj.all || (targetType === 'tv' ? resultsObj.tvs : resultsObj.movies) || [];
if (!Array.isArray(items) || items.length === 0) return null;
const cleanSearchTitle = query.toLowerCase().replace(/[^a-z0-9]/g, '');
let matchedItem = null;
for (const item of items) {
const itemType = String(item?.type || '').toLowerCase();
if (itemType && itemType !== targetType) continue;
const itemTitle = String(item?.title || '');
const cleanItemTitle = itemTitle.toLowerCase().replace(/[^a-z0-9]/g, '');
if (cleanItemTitle === cleanSearchTitle || cleanItemTitle.includes(cleanSearchTitle)) {
matchedItem = item;
break;
}
}
if (!matchedItem) {
matchedItem = items.find(i => String(i?.type || '').toLowerCase() === targetType) || items[0];
}
const itemId = matchedItem?.id || matchedItem?.tmdbId;
if (!itemId) return null;
const detailsRes = await fetch(`${BASE_URL}/api/mobile/details?id=${itemId}&type=${targetType}`, {
headers: HEADERS,
signal: AbortSignal.timeout(8000),
});
if (!detailsRes.ok) return null;
const detailsJson = await detailsRes.json();
const data = detailsJson?.data;
if (!data) return null;
const spoken = Array.isArray(data.spokenLanguages)
? data.spokenLanguages.map(l => String(l).trim()).filter(Boolean)
: [];
const langSuffix = spoken.length > 0 ? ` · ${spoken.join(', ')}` : '';
const allUrls = [];
if (!isTv) {
const streamUrl = data.downloadUrl || data.videoUrl;
if (streamUrl && typeof streamUrl === 'string') {
allUrls.push({
url: streamUrl.trim(),
label: `X-Downloader${langSuffix}`,
headers: {
'User-Agent': HEADERS['User-Agent'],
},
});
}
} else {
const seasonNum = Number(s);
const episodeNum = Number(e);
const seasons = Array.isArray(data.seasons) ? data.seasons : [];
const targetSeason = seasons.find(sn => Number(sn.seasonNumber) === seasonNum);
if (targetSeason) {
const episodes = Array.isArray(targetSeason.episodes) ? targetSeason.episodes : [];
const targetEp = episodes.find(ep => Number(ep.episodeNumber) === episodeNum);
if (targetEp) {
const streamUrl = targetEp.downloadUrl || targetEp.videoUrl;
if (streamUrl && typeof streamUrl === 'string') {
allUrls.push({
url: streamUrl.trim(),
label: `X-Downloader (S${seasonNum}E${episodeNum})${langSuffix}`,
headers: {
'User-Agent': HEADERS['User-Agent'],
},
});
}
}
}
}
if (allUrls.length === 0) return null;
return {
url: allUrls[0].url,
allUrls,
headers: allUrls[0].headers,
};
}

View File

@@ -1,40 +0,0 @@
import { fetchJson, fetchText, USER_AGENT } from '../utils/helpers.js';
const BASE = "https://play.xpass.top";
const HEADERS = { "accept": "*/*", "accept-language": "en-US,en;q=0.9", "cache-control": "no-cache", "pragma": "no-cache", "User-Agent": USER_AGENT, "Origin": BASE, "Referrer": `${BASE}/`, "Cookie": "auth_token=de21073d24bca9b50f189b402ac870734cf945f2085cb7e1a4fc453fcfe4f57e" };
export async function getStream({ id, s, e, server }) {
const isTv = s && e && s !== 'null' && e !== 'null';
const hdrs = { ...HEADERS, referer: `${BASE}/e/${isTv ? 'tv' : 'movie'}/${id}?autostart=true` };
try {
let sources = [];
if (!isTv) {
const text = await fetchText(`${BASE}/e/movie/${id}?autostart=true`, { headers: hdrs, signal: AbortSignal.timeout(7000) });
const match = text.match(/var backups=(\[[\s\S]*?\])/);
if (match) sources = JSON.parse(match[1]);
} else {
sources = await fetchJson(`${BASE}/data/tv/${id}/${s}/${e}?autostart=true&force=true`, { headers: hdrs, signal: AbortSignal.timeout(7000) });
}
if (!sources?.length) return null;
if (server && server !== 'all') {
const exact = sources.find(src => src.name === server.replace('XPass - ', ''));
if (exact) sources = [exact]; else return null;
}
const allUrls = [];
for (const source of sources) {
if (!source.url) continue;
try {
const mdata = await fetchJson(`${BASE}${source.url}`, { headers: hdrs, signal: AbortSignal.timeout(5000) });
if (!mdata?.playlist?.[0]?.sources) continue;
const targetStream = mdata.playlist[0].sources.find(st => st.type === 'hls') || mdata.playlist[0].sources[0];
if (targetStream?.file) allUrls.push({ server: `XPass - ${source.name}`, type: targetStream.type === 'hls' ? 'hls' : 'mp4', url: targetStream.file, headers: { "Origin": HEADERS.Origin, "Referer": HEADERS.Referrer, "User-Agent": HEADERS["User-Agent"] } });
} catch { }
}
return allUrls.length ? { allUrls } : null;
} catch { return null; }
}
export async function getSources(args) {
const stream = await getStream(args);
return stream?.allUrls ? stream.allUrls.map(u => u.server) : [];
}

View File

@@ -1,141 +0,0 @@
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

@@ -3,36 +3,24 @@ import { getUA } from './utils/helpers.js';
const SUBTITLE_BASES = [
'https://sub.vdrk.site/v1',
'https://sub.vdrk.site/v2',
'https://fed-subs.pstream.mov'
];
const VYLIAN_MESSAGES = [
"Thanks for using Vyla!"
'https://opensubtitles-v3.strem.io'
];
function generateVylianVtt() {
let vttContent = 'WEBVTT\n\n';
let currentTime = 1;
while (currentTime <= 7200) {
const duration = Math.floor(Math.random() * 4) + 1;
const message = VYLIAN_MESSAGES[Math.floor(Math.random() * VYLIAN_MESSAGES.length)];
const startTime = formatVttTime(currentTime);
const endTime = formatVttTime(currentTime + duration);
vttContent += `${startTime} --> ${endTime}\n${message}\n\n`;
currentTime += duration + Math.floor(Math.random() * 60) + 30;
async function resolveImdbId(id, isTv) {
if (typeof id === 'string' && id.startsWith('tt')) return id;
const apiKey = process.env.TMDB_API_KEY;
if (!apiKey) return `tt${id}`;
const type = isTv ? 'tv' : 'movie';
try {
const res = await fetch(`https://api.themoviedb.org/3/${type}/${id}?api_key=${apiKey}&append_to_response=external_ids`, {
signal: AbortSignal.timeout(5000),
});
if (!res.ok) return `tt${id}`;
const data = await res.json();
return data.imdb_id || data.external_ids?.imdb_id || `tt${id}`;
} catch {
return `tt${id}`;
}
return vttContent;
}
function formatVttTime(seconds) {
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const secs = seconds % 60;
return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${String(secs).padStart(2, '0')}.000`;
}
export async function fetchSubtitles(paths = []) {
@@ -42,7 +30,7 @@ export async function fetchSubtitles(paths = []) {
try {
const res = await fetch(`${base}${path}`, {
headers: { 'User-Agent': getUA() },
signal: AbortSignal.timeout(5000),
signal: AbortSignal.timeout(6000),
});
if (!res.ok) {
@@ -63,6 +51,20 @@ export async function fetchSubtitles(paths = []) {
: [];
}
if (base.includes('opensubtitles-v3.strem.io')) {
if (!Array.isArray(data?.subtitles)) return [];
return data.subtitles.map(sub => {
if (!sub?.url) return null;
const isSrt = sub.url.toLowerCase().endsWith('.srt');
return {
label: sub.lang ? sub.lang.toUpperCase() : 'OpenSubtitles',
file: sub.url,
type: isSrt ? 'srt' : 'vtt',
source: 'opensubtitles'
};
}).filter(Boolean);
}
if (base.includes('fed-subs.pstream.mov')) {
if (!data?.subtitles || typeof data.subtitles !== 'object') return [];
@@ -93,34 +95,27 @@ export async function fetchSubtitles(paths = []) {
})
);
const val = results.flat();
val.push({
label: 'Vylian',
file: 'data:text/vtt;base64,' + Buffer.from(generateVylianVtt()).toString('base64'),
type: 'vtt',
source: 'easter-egg'
});
return val;
return results.flat();
} catch {
return [];
}
}
export async function getSubPathsMovie(id) {
const imdbId = await resolveImdbId(id, false);
return [
{ base: SUBTITLE_BASES[0], path: `/movie/${id}` },
{ base: SUBTITLE_BASES[1], path: `/movie/${id}` },
{ base: SUBTITLE_BASES[2], path: `/movie/tt${id}` }
{ base: SUBTITLE_BASES[2], path: `/subtitles/movie/${imdbId}.json` }
];
}
export async function getSubPathsTv(id, season, episode) {
const imdbId = await resolveImdbId(id, true);
return [
{ base: SUBTITLE_BASES[0], path: `/tv/${id}/${season}/${episode}` },
{ base: SUBTITLE_BASES[1], path: `/tv/${id}/${season}/${episode}` },
{ base: SUBTITLE_BASES[2], path: `/tv/tt${id}/${season}/${episode}` }
{ base: SUBTITLE_BASES[2], path: `/subtitles/series/${imdbId}:${season}:${episode}.json` }
];
}

View File

@@ -9,7 +9,7 @@ import dotenv from "dotenv";
const CONFIG = {
DEBUG: true, // Set to true to print outgoing requests, headers, formats, and responses
MOVIE_ID: "1084242", // Movie TMDB ID to test
MOVIE_ID: "936075", // Movie TMDB ID to test
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