organized/fixed streams

This commit is contained in:
2026-10-31 12:37:24 -07:00
parent 95f4991914
commit 7473c0fa15
51 changed files with 2920 additions and 3059 deletions

View File

@@ -2,7 +2,8 @@
"name": "@vyla-entertainment/sdk",
"version": "1.20.0",
"scripts": {
"test": "node test/index.js"
"test": "node test/index.js",
"test:all": "node test/index.js all"
},
"author": "vyla-entertainment",
"contributors": [

View File

@@ -94,24 +94,6 @@ export const SOURCES = [
}],
},
{
key: 'cinezo',
label: 'Cinezo',
sourceFile: 'cinezo',
proxyParam: 'czo',
timeout: 25000,
jitter: 500,
retries: 2,
multiUrl: true,
cdnHeaders: [{
pattern: /yenime\.net|flikhub\.net|hakunaymatata\.com/i,
headers: {
Referer: 'https://player.cinezo.live/',
Origin: 'https://player.cinezo.live',
},
}],
},
{
key: 'cno',
label: 'Cno',
@@ -355,7 +337,7 @@ export const SOURCES = [
retries: 1,
multiUrl: true,
skipProxy: true,
disabled: false, // Enabled, but only returns streams when a manifest is provided
disabled: false,
},
{
@@ -499,26 +481,6 @@ export const SOURCES = [
multiUrl: true,
},
{
key: 'vidrock',
label: 'VidRock',
sourceFile: 'vidrock',
proxyParam: 'vr',
timeout: 20000,
jitter: 800,
retries: 3,
multiUrl: true,
cdnHeaders: [{
pattern: /./,
headers: {
Accept: '/',
'Accept-Language': 'en-US,en;q=0.9',
Referer: 'https://vidrock.ru/',
Origin: 'https://vidrock.ru',
},
},],
},
{
key: 'vidup',
label: 'VidUp',

33
src/sdk.d.ts vendored
View File

@@ -20,10 +20,12 @@ export interface SourceConfig {
disabled?: boolean;
sourcesTimeout?: number;
}
export interface ProbeResult {
ok: boolean;
ms: number | null;
}
export interface StreamResult {
url: string;
headers: {
@@ -46,6 +48,7 @@ export interface DownloadResult {
type: "mkv";
active: boolean;
}
export interface SubtitleResult {
label: string;
file: string;
@@ -64,30 +67,12 @@ export default class VylaSDK {
constructor(options?: VylaSDKOptions);
/**
* Get the list of configured sources.
* @param excludeDisabled If true, filters out disabled sources.
*/
getSources(excludeDisabled?: boolean): SourceConfig[];
/**
* Probe all active (non-disabled) sources for availability.
*/
probeAllSources(): Promise<ProbeAllResult>;
/**
* Probe a single source by key.
*/
probeSource(key: string): Promise<ProbeResult>;
/**
* Get a playable stream for a given source/media id.
* @param key Source key
* @param id Media id (e.g. TMDB id)
* @param s Season number (for TV), or null for movies
* @param e Episode number (for TV), or null for movies
* @param clientIP Optional client IP for source-specific logic
*/
getStream(
key: string,
id: string | number,
@@ -96,24 +81,12 @@ export default class VylaSDK {
clientIP?: string | null
): Promise<StreamResult | { allUrls: StreamResult[] }>;
/**
* Get subtitles for a movie or TV episode.
* @param id Media id
* @param s Season number (for TV), or null for movies
* @param e Episode number (for TV), or null for movies
*/
getSubtitles(
id: string | number,
s?: number | null,
e?: number | null
): Promise<SubtitleResult[]>;
/**
* Get download links for a movie or TV episode.
* @param id Media id
* @param s Season number (for TV), or null for movies
* @param e Episode number (for TV), or null for movies
*/
getDownloads(
id: string | number,
s?: number | null,

View File

@@ -1,78 +1,82 @@
import SOURCES, { SOURCE_MAP } from "./config.js";
import { fetchDownloads } from "./downloads/downloads.js";
import { probeSource } from "./health.js";
import { getChapterPages, getMangaDetails, searchManga } from "./manga/atsu.js";
import { fetchSubtitles, getSubPathsMovie, getSubPathsTv } from "./subtitles.js";
import { createStreamArgs } from "./utils/helpers.js";
const ALL_SOURCE_MODULES = Object.fromEntries(
await Promise.all(SOURCES.map(async cfg => [cfg.key, await import(`./sources/${cfg.sourceFile}.js`)]))
);
const SOURCE_MODULES = Object.fromEntries(
Object.entries(ALL_SOURCE_MODULES).filter(([key]) => !SOURCE_MAP[key]?.disabled)
);
export default class VylaSDK {
tmdbApiKey = null;
penguManifest = null;
constructor({ tmdbApiKey, penguManifest } = {}) {
this.tmdbApiKey = tmdbApiKey || null;
this.penguManifest = penguManifest || null;
}
getSources(excludeDisabled = false) {
if (excludeDisabled) {
return SOURCES.filter(cfg => !cfg.disabled);
}
return SOURCES;
}
async probeAllSources() {
const active = SOURCES.filter(cfg => !cfg.disabled);
const results = await Promise.allSettled(
active.map(async cfg => await probeSource(cfg, SOURCE_MODULES[cfg.key]))
);
const sources = Object.fromEntries(
active.map((cfg, i) => [
cfg.key,
results[i].status === 'fulfilled' ? results[i].value : { ok: false, ms: null },
])
);
return sources;
}
async probeSource(key) {
const cfg = SOURCES.find(cfg => cfg.key === key);
if (!cfg) throw new Error(`Source with key "${key}" not found`);
const mod = SOURCE_MODULES[key];
if (!mod) throw new Error(`Source module for key "${key}" not found`);
return await probeSource(this, cfg, mod);
}
async getStream(key, id, s = null, e = null, clientIP = null) {
const cfg = SOURCES.find(cfg => cfg.key === key);
if (!cfg) throw new Error(`Source with key "${key}" not found`);
const mod = SOURCE_MODULES[key];
if (!mod) throw new Error(`Source module for key "${key}" not found`);
const streamArgs = await createStreamArgs(cfg, this, id, s, e, clientIP);
return await mod.getStream(streamArgs);
}
async getSubtitles(id, s = null, e = null) {
const paths = s != null && e != null
? await getSubPathsTv(id, s, e)
: await getSubPathsMovie(id);
return await fetchSubtitles(paths);
}
async getDownloads(id, s = null, e = null) {
return await fetchDownloads(this, id, s, e);
}
async searchManga(query, page = 1, perPage = 12) {
return await searchManga(query, page, perPage);
}
async getManga(id) {
return await getMangaDetails(id);
}
async getMangaChapter(mangaId, chapterId) {
return await getChapterPages(mangaId, chapterId);
}
}
import SOURCES, { SOURCE_MAP } from "./config.js";
import { fetchDownloads } from "./downloads/downloads.js";
import { probeSource } from "./health.js";
import { getChapterPages, getMangaDetails, searchManga } from "./manga/atsu.js";
import { fetchSubtitles, getSubPathsMovie, getSubPathsTv } from "./subtitles.js";
import { createStreamArgs } from "./utils/helpers.js";
const ALL_SOURCE_MODULES = Object.fromEntries(
await Promise.all(SOURCES.map(async cfg => [cfg.key, await import(`./sources/${cfg.sourceFile}.js`)]))
);
const SOURCE_MODULES = Object.fromEntries(
Object.entries(ALL_SOURCE_MODULES).filter(([key]) => !SOURCE_MAP[key]?.disabled)
);
export default class VylaSDK {
tmdbApiKey = null;
penguManifest = null;
constructor({ tmdbApiKey, penguManifest } = {}) {
this.tmdbApiKey = tmdbApiKey || null;
this.penguManifest = penguManifest || null;
}
getSources(excludeDisabled = false) {
if (excludeDisabled) {
return SOURCES.filter(cfg => !cfg.disabled);
}
return SOURCES;
}
async probeAllSources() {
const active = SOURCES.filter(cfg => !cfg.disabled);
const results = await Promise.allSettled(
active.map(async cfg => await probeSource(this, cfg, SOURCE_MODULES[cfg.key]))
);
const sources = Object.fromEntries(
active.map((cfg, i) => [
cfg.key,
results[i].status === 'fulfilled' ? results[i].value : { ok: false, ms: null },
])
);
return sources;
}
async probeSource(key) {
const cfg = SOURCES.find(cfg => cfg.key === key);
if (!cfg) throw new Error(`Source with key "${key}" not found`);
const mod = SOURCE_MODULES[key];
if (!mod) throw new Error(`Source module for key "${key}" not found`);
return await probeSource(this, cfg, mod);
}
async getStream(key, id, s = null, e = null, clientIP = null) {
const cfg = SOURCES.find(cfg => cfg.key === key);
if (!cfg) throw new Error(`Source with key "${key}" not found`);
const mod = SOURCE_MODULES[key];
if (!mod) throw new Error(`Source module for key "${key}" not found`);
const streamArgs = await createStreamArgs(cfg, this, id, s, e, clientIP);
const result = await mod.getStream(streamArgs);
if (result && typeof result === 'object') {
delete result.subtitles;
}
return result;
}
async getSubtitles(id, s = null, e = null) {
const paths = s != null && e != null
? await getSubPathsTv(id, s, e)
: await getSubPathsMovie(id);
return await fetchSubtitles(paths);
}
async getDownloads(id, s = null, e = null) {
return await fetchDownloads(this, id, s, e);
}
async searchManga(query, page = 1, perPage = 12) {
return await searchManga(query, page, perPage);
}
async getManga(id) {
return await getMangaDetails(id);
}
async getMangaChapter(mangaId, chapterId) {
return await getChapterPages(mangaId, chapterId);
}
}

View File

@@ -1,11 +1,11 @@
import { getUA } from '../utils/helpers.js';
import { getUA, resolveImdbId, resolveTmdbKey } from '../utils/helpers.js';
const SERVICE_ORIGIN = 'https://st.111477.xyz';
function generateManifestBaseUrl(tmdbKey) {
function generateManifestBaseUrl(apiKey) {
let config = 'https://a.111477.xyz/::sort=file-desc::limit=3';
if (tmdbKey) {
config += `::tmdb=${tmdbKey}`;
if (apiKey) {
config += `::tmdb=${apiKey}`;
}
const b64 = Buffer.from(config, 'utf8')
.toString('base64')
@@ -15,28 +15,13 @@ function generateManifestBaseUrl(tmdbKey) {
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);
export async function getStream(args) {
const { id, s, e, sdk } = args || {};
const isTv = s != null && e != null;
const apiKey = resolveTmdbKey(args, sdk);
const ua = getUA();
const apiKey = sdk?.tmdbApiKey || process.env.TMDB_API_KEY;
const imdbId = await resolveImdbId(sdk, id, isTv);
const imdbId = await resolveImdbId(id, isTv, apiKey);
const targetIds = [];
if (imdbId && imdbId.startsWith('tt')) {
@@ -48,7 +33,7 @@ export async function getStream({ id, s, e, sdk }) {
const seenUrls = new Set();
const allUrls = [];
const defaultHeaders = {
const headers = {
'User-Agent': ua,
Accept: 'application/json, text/plain, */*',
};
@@ -60,7 +45,7 @@ export async function getStream({ id, s, e, sdk }) {
try {
const res = await fetch(endpoint, {
headers: defaultHeaders,
headers,
signal: AbortSignal.timeout(12000),
});
@@ -70,19 +55,22 @@ export async function getStream({ id, s, e, sdk }) {
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;
const streamUrl = String(item.url || '').trim();
if (!streamUrl || !streamUrl.startsWith('http') || seenUrls.has(streamUrl)) continue;
seenUrls.add(url);
seenUrls.add(streamUrl);
const rawTitle = item.title ? String(item.title).trim() : '';
const rawName = item.name ? String(item.name).trim() : '111477';
const label = rawTitle || rawName;
allUrls.push({
url,
url: streamUrl,
label,
headers: defaultHeaders,
server: label,
quality: 'Auto',
type: streamUrl.includes('.m3u8') ? 'hls' : 'mp4',
headers,
});
}
@@ -95,7 +83,7 @@ export async function getStream({ id, s, e, sdk }) {
return {
url: allUrls[0].url,
allUrls,
headers: allUrls[0].headers,
allUrls,
};
}

View File

@@ -1,57 +1,121 @@
import { getTmdbInfo, fetchJson, USER_AGENT } from '../utils/helpers.js';
async function resolveSlug(id, s, e, info) {
const titles = info?.titles || [];
const keywords = [];
for (const title of titles) {
const t = title.toLowerCase().replace(/[^a-z0-9\s]/g, ' ').replace(/\s+/g, ' ').trim();
if (!keywords.includes(t)) keywords.push(t);
const words = title.split(/[\s:]+/).filter(w => w.length > 2);
if (words.length >= 2) {
const short = words.slice(0, 3).join(' ').toLowerCase();
if (!keywords.includes(short)) keywords.push(short);
}
}
const slugCandidates = [];
for (const keyword of keywords) {
try {
const data = await fetchJson(`https://123animehub.cc/ajax/film/search?keyword=${encodeURIComponent(keyword)}&_=${Date.now()}`, { headers: { 'X-Requested-With': 'XMLHttpRequest', 'Referer': 'https://123animehub.cc', 'Accept': 'application/json', 'User-Agent': USER_AGENT }, signal: AbortSignal.timeout(8000) });
let html = data.html || '', idx = 0;
while ((idx = html.indexOf('href="/anime/', idx)) !== -1) {
const start = idx + 13;
const end = html.indexOf('"', start);
const slug = html.slice(start, end);
if (!slugCandidates.includes(slug)) slugCandidates.push(slug);
idx = end;
}
} catch { }
if (slugCandidates.length) break;
}
for (const slug of slugCandidates) {
try {
const data = await fetchJson(`https://123animehub.cc/ajax/episode/info?epr=${encodeURIComponent(s ? `${slug}/${s}/${e}` : `${slug}/1`)}&ts=1&_=${Date.now()}`, { headers: { 'Referer': `https://123animehub.cc/anime/${slug}`, 'X-Requested-With': 'XMLHttpRequest', 'Accept': 'application/json, text/javascript, */*; q=0.01', 'User-Agent': USER_AGENT }, signal: AbortSignal.timeout(10000) });
if (data?.target) return data.target;
} catch { }
}
return null;
}
export async function getStream({ id, s, e }) {
if (!s) return null;
const info = await getTmdbInfo(id, 'tv', s);
if (!info.isAnime) return null;
const target = await resolveSlug(id, s, e, info);
if (!target) return null;
const embedMatch = target.match(/\/embed-[^/]+\/([A-Za-z0-9+/=]+)$/);
if (!embedMatch) return null;
const origin = new URL(target).origin;
try {
const embedRes = await fetch(target, { headers: { 'User-Agent': USER_AGENT }, signal: AbortSignal.timeout(10000) });
if (!embedRes.ok) return null;
const setCookie = embedRes.headers.get('set-cookie');
const data = await fetchJson(`${origin}/hs/getSources?id=${embedMatch[1]}`, { headers: { 'Referer': target, 'Accept': '*/*', 'User-Agent': USER_AGENT, ...(setCookie ? { 'Cookie': setCookie.split(';')[0].trim() } : {}) }, signal: AbortSignal.timeout(10000) });
const src = typeof data.sources === 'string' && data.sources.length ? data.sources : (Array.isArray(data.sources) && data.sources.length ? (data.sources[0].file || data.sources[0].src || data.sources[0].url) : null);
if (src) return { url: src, headers: { 'Referer': 'https://play2.echovideo.ru/', 'Origin': 'https://play2.echovideo.ru' } };
} catch { }
return null;
import { fetchJson, getTmdbMetadata, resolveTmdbKey, USER_AGENT } from '../utils/helpers.js';
const BASE_URL = 'https://123animehub.cc';
const CDN_ORIGIN = 'https://play2.echovideo.ru';
async function resolveSlug(id, s, e, titles) {
const keywords = [];
for (const title of titles) {
const t = title.toLowerCase().replace(/[^a-z0-9\s]/g, ' ').replace(/\s+/g, ' ').trim();
if (!keywords.includes(t)) keywords.push(t);
const words = title.split(/[\s:]+/).filter(w => w.length > 2);
if (words.length >= 2) {
const short = words.slice(0, 3).join(' ').toLowerCase();
if (!keywords.includes(short)) keywords.push(short);
}
}
const slugCandidates = [];
for (const keyword of keywords) {
try {
const data = await fetchJson(`${BASE_URL}/ajax/film/search?keyword=${encodeURIComponent(keyword)}&_=${Date.now()}`, {
headers: {
'X-Requested-With': 'XMLHttpRequest',
Referer: `${BASE_URL}/`,
Accept: 'application/json',
'User-Agent': USER_AGENT,
},
signal: AbortSignal.timeout(8000),
});
let html = data.html || '';
let idx = 0;
while ((idx = html.indexOf('href="/anime/', idx)) !== -1) {
const start = idx + 13;
const end = html.indexOf('"', start);
const slug = html.slice(start, end);
if (!slugCandidates.includes(slug)) slugCandidates.push(slug);
idx = end;
}
} catch { }
if (slugCandidates.length) break;
}
for (const slug of slugCandidates) {
try {
const data = await fetchJson(`${BASE_URL}/ajax/episode/info?epr=${encodeURIComponent(s ? `${slug}/${s}/${e}` : `${slug}/1`)}&ts=1&_=${Date.now()}`, {
headers: {
Referer: `${BASE_URL}/anime/${slug}`,
'X-Requested-With': 'XMLHttpRequest',
Accept: 'application/json, text/javascript, */*; q=0.01',
'User-Agent': USER_AGENT,
},
signal: AbortSignal.timeout(10000),
});
if (data?.target) return data.target;
} catch { }
}
return null;
}
export async function getStream(args) {
const { id, s, e, sdk } = args || {};
const isTv = s != null && e != null;
if (!s) return null;
const apiKey = resolveTmdbKey(args, sdk);
const meta = await getTmdbMetadata(id, isTv, apiKey);
if (!meta?.isAnime) return null;
const target = await resolveSlug(id, s, e, meta.titles || []);
if (!target) return null;
const embedMatch = target.match(/\/embed-[^/]+\/([A-Za-z0-9+/=]+)$/);
if (!embedMatch) return null;
const origin = new URL(target).origin;
try {
const embedRes = await fetch(target, {
headers: { 'User-Agent': USER_AGENT },
signal: AbortSignal.timeout(10000),
});
if (!embedRes.ok) return null;
const setCookie = embedRes.headers.get('set-cookie');
const data = await fetchJson(`${origin}/hs/getSources?id=${embedMatch[1]}`, {
headers: {
Referer: target,
Accept: '*/*',
'User-Agent': USER_AGENT,
...(setCookie ? { Cookie: setCookie.split(';')[0].trim() } : {}),
},
signal: AbortSignal.timeout(10000),
});
const streamUrl = typeof data.sources === 'string' && data.sources.length
? data.sources
: (Array.isArray(data.sources) && data.sources.length ? (data.sources[0].file || data.sources[0].src || data.sources[0].url) : null);
if (!streamUrl) return null;
const headers = {
Referer: `${CDN_ORIGIN}/`,
Origin: CDN_ORIGIN,
};
const allUrls = [{
url: streamUrl,
server: '123Anime',
quality: 'Auto',
type: streamUrl.includes('.m3u8') ? 'hls' : 'mp4',
headers,
}];
return {
url: streamUrl,
headers,
allUrls,
};
} catch {
return null;
}
}

View File

@@ -1,101 +1,45 @@
import { getUA } from "../utils/helpers.js";
import { getTmdbMetadata, getUA, resolveTmdbKey, slugify } from '../utils/helpers.js';
const BASE_URL = "https://3eyedraven.watch";
const API_BASE = "https://3eyedraven.watch/api";
function getHeaders() {
return {
"User-Agent": getUA(),
Accept: "application/json",
Referer: `${BASE_URL}/`,
Origin: BASE_URL,
};
}
function resolveApiKey(args) {
return (
args?.tmdbApiKey ||
args?.sdk?.tmdbApiKey ||
process.env.TMDB_API_KEY ||
null
);
}
async function getTitle(id, isTv, apiKey) {
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(5000) }
);
if (!res.ok) return null;
const data = await res.json();
return isTv ? data.name : data.title;
} catch {
return null;
}
}
function slugify(text) {
return text
.toLowerCase()
.normalize("NFKD")
.replace(/[\u0300-\u036f]/g, "")
.replace(/[^a-z0-9]+/g, "-")
.replace(/^-|-$/g, "");
}
const BASE_URL = 'https://3eyedraven.watch';
const API_BASE = 'https://3eyedraven.watch/api';
function absolute(url) {
if (!url) return "";
if (!url) return '';
if (/^https?:\/\//.test(url)) return url;
return `${BASE_URL}${url.startsWith("/") ? "" : "/"}${url}`;
return `${BASE_URL}${url.startsWith('/') ? '' : '/'}${url}`;
}
export async function getStream(args) {
const { id, s = null, e = null } = args;
const { id, s, e, sdk } = args || {};
const isTv = s != null && e != null;
const apiKey = resolveTmdbKey(args, sdk);
const apiKey = resolveApiKey(args);
const title = await getTitle(id, isTv, apiKey);
if (!title) {
if (process.env.DEBUG_CAUSE) {
console.error(
`[3eyedraven] Could not resolve title for ${isTv ? "tv" : "movie"} ${id} ` +
`(tmdbApiKey ${apiKey ? "present" : "MISSING"})`
);
}
return null;
}
const meta = await getTmdbMetadata(id, isTv, apiKey);
const title = meta?.title;
if (!title) return null;
const identifier = `${slugify(title)}-${id}`;
const url = isTv
? `${API_BASE}/stream/${identifier}?season=${s}&episode=${e}`
: `${API_BASE}/stream/${identifier}`;
const headers = {
'User-Agent': getUA(),
Accept: 'application/json',
Referer: `${BASE_URL}/`,
Origin: BASE_URL,
};
try {
const res = await fetch(url, {
headers: getHeaders(),
headers,
signal: AbortSignal.timeout(15000),
});
if (!res.ok) {
if (process.env.DEBUG_CAUSE) {
console.error(`[3eyedraven] HTTP ${res.status} for ${url}`);
}
return null;
}
if (!res.ok) return null;
const data = await res.json();
const stream = data?.stream;
if (!stream?.url) {
if (process.env.DEBUG_CAUSE) {
console.error(`[3eyedraven] No stream.url in response for ${url}`);
}
return null;
}
if (!stream?.url) return null;
const streamUrl = absolute(stream.url);
if (!/^https?:\/\//.test(streamUrl)) return null;
@@ -105,32 +49,35 @@ export async function getStream(args) {
const subUrl = absolute(sub.url);
if (!subUrl) return null;
return {
label: sub.label || sub.language || "Subtitle",
label: sub.label || sub.language || 'Subtitle',
file: subUrl,
type: "vtt",
source: "3eyedraven",
type: 'vtt',
source: '3eyedraven',
};
})
.filter(Boolean);
return {
const allUrls = [{
url: streamUrl,
headers: {
"User-Agent": getUA(),
Referer: `${BASE_URL}/`,
Origin: BASE_URL,
},
server: "3eyedraven",
quality: "1080p",
type: stream.format === "hls" ? "hls" : "mp4",
headers,
server: '3eyedraven',
quality: '1080p',
type: stream.format === 'hls' ? 'hls' : 'mp4',
subtitles,
skipProxy: true,
}];
return {
url: streamUrl,
headers,
server: '3eyedraven',
quality: '1080p',
type: stream.format === 'hls' ? 'hls' : 'mp4',
subtitles,
skipProxy: true,
allUrls,
};
} catch (err) {
if (process.env.DEBUG_CAUSE) {
console.error(`[3eyedraven] fetch error for ${url}:`, err.message);
if (err.cause) console.error(" cause:", err.cause.code, err.cause.message);
}
} catch {
return null;
}
}

View File

@@ -1,19 +1,38 @@
import { fetchJson } from '../utils/helpers.js';
export async function getStream({ id, s, e }) {
const BASE_URL = 'https://zcdn.bluevelvet.space';
export async function getStream(args) {
const { id, s, e } = args || {};
const isTv = s != null && e != null;
const url = isTv
? `https://zcdn.bluevelvet.space/cdn?tmdb=${id}&type=tv&season=${s}&episode=${e}`
: `https://zcdn.bluevelvet.space/cdn?tmdb=${id}&type=movie`;
? `${BASE_URL}/cdn?tmdb=${id}&type=tv&season=${s}&episode=${e}`
: `${BASE_URL}/cdn?tmdb=${id}&type=movie`;
const res = await fetchJson(url);
if (!res || !res.ready || !res.url) return null;
try {
const res = await fetchJson(url);
if (!res || !res.ready || !res.url) return null;
return {
url: res.url,
headers: {
Referer: 'https://zcdn.bluevelvet.space/',
Origin: 'https://zcdn.bluevelvet.space',
}
};
const headers = {
Referer: `${BASE_URL}/`,
Origin: BASE_URL,
};
const allUrls = [{
url: res.url,
server: 'CineCat',
quality: 'Auto',
type: res.url.includes('.m3u8') ? 'hls' : 'mp4',
headers,
}];
return {
url: res.url,
headers,
allUrls,
};
} catch {
return null;
}
}

View File

@@ -1,19 +1,18 @@
import crypto from "crypto";
import { Session, ClientIdentifier, initTLS, destroyTLS } from "node-tls-client";
import crypto from 'node:crypto';
import { ClientIdentifier, destroyTLS, initTLS, Session } from 'node-tls-client';
const BASE_URL = "https://cinema.army";
const API_BASE = "https://cinema.army/api";
const XOR_KEY = "f16a6b92fcd6eed3e9476297f893f2505c32f79bc6efef9b6c95496aa5914548";
const HMAC_KEY = "a2cfc9eaa2a3690b5d4f6b5f958dbdeae52a097283d375bd253ee7c7062524f5";
const UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36";
const BASE_URL = 'https://cinema.army';
const API_BASE = `${BASE_URL}/api`;
const HMAC_KEY = 'a2cfc9eaa2a3690b5d4f6b5f958dbdeae52a097283d375bd253ee7c7062524f5';
const XOR_KEY = 'f16a6b92fcd6eed3e9476297f893f2505c32f79bc6efef9b6c95496aa5914548';
const UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36';
const SERVERS = [
{ key: "cookiebakers", method: "POST", label: "CookieBakers" },
{ key: "tcloud", method: "GET", label: "TCloud" },
{ key: "ipcloud", method: "GET", label: "IPcloud" },
{ key: "dcloud", method: "GET", label: "Dcloud" },
{ key: 'cookiebakers', label: 'CookieBakers', method: 'POST' },
{ key: 'tcloud', label: 'TCloud', method: 'POST' },
{ key: 'ipcloud', label: 'IPCloud', method: 'POST' },
{ key: 'dcloud', label: 'DCloud', method: 'POST' },
{ key: 'tidb', label: 'TIDB', method: 'POST' },
];
let tlsInitialized = false;
@@ -40,22 +39,22 @@ function getHeaders(token, id, isTv, s, e) {
: `${BASE_URL}/watch?id=${id}&type=movie`;
return {
"User-Agent": UA,
"Accept": "application/json",
"Accept-Language": "en-US,en;q=0.9",
"Accept-Encoding": "gzip, deflate, br",
"Cache-Control": "no-cache",
"Pragma": "no-cache",
"Referer": referer,
"Origin": BASE_URL,
"Sec-Ch-Ua": '"Chromium";v="131", "Not_A Brand";v="24"',
"Sec-Ch-Ua-Mobile": "?0",
"Sec-Ch-Ua-Platform": '"Windows"',
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "same-origin",
"Priority": "u=1, i",
"X-Stream-Token": token,
'User-Agent': UA,
Accept: 'application/json',
'Accept-Language': 'en-US,en;q=0.9',
'Accept-Encoding': 'gzip, deflate, br',
'Cache-Control': 'no-cache',
Pragma: 'no-cache',
Referer: referer,
Origin: BASE_URL,
'Sec-Ch-Ua': '"Chromium";v="131", "Not_A Brand";v="24"',
'Sec-Ch-Ua-Mobile': '?0',
'Sec-Ch-Ua-Platform': '"Windows"',
'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'same-origin',
Priority: 'u=1, i',
'X-Stream-Token': token,
};
}
@@ -63,20 +62,20 @@ function generateToken(id) {
try {
const minute = Math.floor(Date.now() / 1000 / 60);
return crypto
.createHmac("sha256", HMAC_KEY)
.createHmac('sha256', HMAC_KEY)
.update(`${id}:${minute}`)
.digest("hex");
.digest('hex');
} catch {
return "";
return '';
}
}
function decryptStreamUrl(encoded) {
if (!encoded) return "";
if (encoded.startsWith("http://") || encoded.startsWith("https://")) return encoded;
if (!encoded) return '';
if (encoded.startsWith('http://') || encoded.startsWith('https://')) return encoded;
try {
const decoded = Buffer.from(encoded, "base64").toString("binary");
let out = "";
const decoded = Buffer.from(encoded, 'base64').toString('binary');
let out = '';
for (let i = 0; i < decoded.length; i++) {
out += String.fromCharCode(
decoded.charCodeAt(i) ^ XOR_KEY.charCodeAt(i % XOR_KEY.length)
@@ -89,7 +88,7 @@ function decryptStreamUrl(encoded) {
}
export async function getStream(args) {
const { id, s = null, e = null } = args;
const { id, s, e } = args || {};
const isTv = s != null && e != null;
const token = generateToken(id);
@@ -107,62 +106,62 @@ export async function getStream(args) {
const results = await Promise.allSettled(
requests.map(async ({ url, server }) => {
try {
const res = server.method === "POST"
? await sess.post(url, { headers })
const res = server.method === 'POST'
? await sess.post(url, { headers, body: '' })
: await sess.get(url, { headers });
if (res.status !== 200) {
if (process.env.DEBUG_CAUSE) {
console.error(`[CinemaArmy ${server.key}] HTTP ${res.status}`);
}
return null;
}
if (res?.status !== 200) return null;
const rawBody = typeof res.text === 'function' ? await res.text() : res.body;
const text = await res.text();
let data;
try {
data = JSON.parse(text);
} catch {
data = typeof rawBody === 'object' && rawBody !== null ? rawBody : JSON.parse(rawBody);
} catch (err) {
return null;
}
const encrypted = data?.stream?.url;
if (!encrypted) return null;
if (!encrypted) {
return null;
}
const streamUrl = decryptStreamUrl(encrypted);
if (!streamUrl || !/^https?:\/\//.test(streamUrl)) return null;
if (!streamUrl || !/^https?:\/\//.test(streamUrl)) {
return null;
}
return {
url: streamUrl,
headers: {
"User-Agent": UA,
'User-Agent': UA,
Referer: `${BASE_URL}/`,
Origin: BASE_URL,
},
server: `CinemaArmy (${server.label})`,
quality: "1080p",
type: streamUrl.includes(".m3u8") ? "hls" : "mp4",
quality: '1080p',
type: streamUrl.includes('.m3u8') ? 'hls' : 'mp4',
};
} catch (err) {
if (process.env.DEBUG_CAUSE) {
console.error(`[CinemaArmy ${server.key}] ${err.message}`);
if (err.cause) {
console.error(` cause.code: ${err.cause.code || "n/a"}`);
console.error(` cause.message: ${err.cause.message || err.cause}`);
}
}
return null;
}
})
);
const allUrls = results
.filter(r => r.status === "fulfilled" && r.value)
.filter(r => r.status === 'fulfilled' && r.value)
.map(r => r.value);
if (!allUrls.length) return null;
if (!allUrls.length) {
return null;
}
return { allUrls };
return {
url: allUrls[0].url,
headers: allUrls[0].headers,
allUrls,
};
}
export async function cleanup() {

View File

@@ -1,6 +1,6 @@
import { fetchJson, fetchText } from '../utils/helpers.js';
const API = "https://enc-dec.app/api";
const API = 'https://enc-dec.app/api';
async function solveStage1(challenge1, seed) {
const K = [
@@ -210,91 +210,93 @@ async function solveStage2(data) {
throw new Error('no solution found');
}
export async function getStream({ id, s, e }) {
export async function getStream(args) {
const { id, s, e } = args || {};
const isTv = s != null && e != null;
const mediaType = isTv ? 'tv' : 'movie';
const type = isTv ? 'tv' : 'movie';
const url = isTv
? `https://cinesrc.st/embed/tv/${id}?s=${s}&e=${e}`
: `https://cinesrc.st/embed/movie/${id}`;
const headers = {
"Origin": "https://cinesrc.st",
"Referer": "https://cinesrc.st/",
"Content-Type": "text/plain;charset=UTF-8",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.37 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.37"
Origin: 'https://cinesrc.st',
Referer: 'https://cinesrc.st/',
'Content-Type': 'text/plain;charset=UTF-8',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.37 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.37',
};
const fields = [type, id, s ? String(s) : null, e ? String(e) : null];
const encoded = btoa(JSON.stringify(fields).replace(/ /g, '')).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
const bootstrapHeaders = { ...headers, "x-cs-q": encoded };
const bootstrap = await fetchJson("https://cinesrc.st/api/c/bootstrap", {
method: "POST",
const bootstrapHeaders = { ...headers, 'x-cs-q': encoded };
const bootstrap = await fetchJson('https://cinesrc.st/api/c/bootstrap', {
method: 'POST',
headers: bootstrapHeaders,
body: "{}"
body: '{}',
});
const cookies = {
"x-cs-b": "20261014-a",
"x-cs-q": encoded,
"x-cs-r": bootstrap.r,
"x-cs-p": bootstrap.p,
'x-cs-b': '20261014-a',
'x-cs-q': encoded,
'x-cs-r': bootstrap.r,
'x-cs-p': bootstrap.p,
};
const challenge1 = await fetchJson("https://cinesrc.st/api/c/issue", {
headers: { ...headers, ...cookies }
const challenge1 = await fetchJson('https://cinesrc.st/api/c/issue', {
headers: { ...headers, ...cookies },
});
const payload1 = {
data: {
url,
user_agent: headers["User-Agent"],
challenge_data: challenge1
user_agent: headers['User-Agent'],
challenge_data: challenge1,
},
type: "seed"
type: 'seed',
};
const seedRes = await fetchJson(`${API}/enc-cinesrc`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload1)
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload1),
});
const seedData = seedRes.result;
const solution1 = await solveStage1(challenge1, seedData.seed);
const challenge2 = await fetchJson("https://cinesrc.st/api/c/stage2/issue", {
headers: { ...headers, ...cookies }
const challenge2 = await fetchJson('https://cinesrc.st/api/c/stage2/issue', {
headers: { ...headers, ...cookies },
});
const solution2 = await solveStage2(challenge2);
const payload2 = {
data: {
url,
user_agent: headers["User-Agent"],
user_agent: headers['User-Agent'],
challenge_data: {
stage1: { challenge: challenge1, solution: solution1 },
stage2: { challenge: challenge2, solution: solution2 }
stage2: { challenge: challenge2, solution: solution2 },
},
state: seedData.state
state: seedData.state,
},
type: "encrypt"
type: 'encrypt',
};
const encryptRes = await fetchJson(`${API}/enc-cinesrc`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload2)
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload2),
});
const encData = encryptRes.result;
const token = `${encData.token}::c3::${cookies["x-cs-r"]}`;
const token = `${encData.token}::c3::${cookies['x-cs-r']}`;
const nextActions = encData.headers;
const providersText = await fetchText(url, {
method: "POST",
headers: { ...headers, ...cookies, "Next-Action": nextActions.getProviderList },
body: JSON.stringify([])
method: 'POST',
headers: { ...headers, ...cookies, 'Next-Action': nextActions.getProviderList },
body: JSON.stringify([]),
});
const lines = providersText.split('\n');
@@ -320,19 +322,19 @@ export async function getStream({ id, s, e }) {
const provider = providerObj.id;
const streamPayload = [
id,
isTv ? "show" : type,
isTv ? String(s) : "$undefined",
isTv ? String(e) : "$undefined",
isTv ? 'show' : type,
isTv ? String(s) : '$undefined',
isTv ? String(e) : '$undefined',
token,
provider
provider,
];
let streamRes;
try {
streamRes = await fetchText(url, {
method: "POST",
headers: { ...headers, ...cookies, "Next-Action": nextActions.getStream },
body: JSON.stringify(streamPayload)
method: 'POST',
headers: { ...headers, ...cookies, 'Next-Action': nextActions.getStream },
body: JSON.stringify(streamPayload),
});
} catch {
continue;
@@ -343,9 +345,9 @@ export async function getStream({ id, s, e }) {
let decryptRes;
try {
decryptRes = await fetchJson(`${API}/dec-cinesrc`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ text: streamRes, state: encData.state })
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text: streamRes, state: encData.state }),
});
} catch {
continue;
@@ -378,10 +380,12 @@ export async function getStream({ id, s, e }) {
allUrls.push({
url: streamUrl,
server: `CineSrc - ${providerObj.name || provider}`,
quality: 'Auto',
type: streamUrl.includes('.m3u8') ? 'hls' : 'mp4',
headers: resultData.headers || {
Referer: "https://cinesrc.st/",
Origin: "https://cinesrc.st"
}
Referer: 'https://cinesrc.st/',
Origin: 'https://cinesrc.st',
},
});
break;
}
@@ -393,6 +397,6 @@ export async function getStream({ id, s, e }) {
return {
url: allUrls[0].url,
headers: allUrls[0].headers,
allUrls
allUrls,
};
}

View File

@@ -1,55 +0,0 @@
import { fetchJson } from '../utils/helpers.js';
export async function getStream({ id, s, e }) {
const isTv = s != null && e != null;
const referer = isTv
? `https://player.cinezo.live/embed/tv/${id}?s=${s}&e=${e}`
: `https://player.cinezo.live/embed/movie/${id}`;
const headers = {
'Accept': '*/*',
'Origin': 'https://player.cinezo.live',
'Referer': referer,
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/155.0.0.0 Safari/537.36 Edg/155.0.0.0'
};
const hosts = [
'https://proxy.yenime.net',
'https://proxy1.flikhub.net'
];
const sources = ['zendaya', 'berlin', 'jennifer', 'scarlet'];
const allUrls = [];
for (const host of hosts) {
for (const source of sources) {
const url = isTv
? `${host}/tv?id=${id}&season=${s}&episode=${e}&mode=json&sources=${source}&hevc=1`
: `${host}/movie?id=${id}&mode=json&sources=${source}&hevc=1`;
try {
const res = await fetchJson(url, { headers });
const streamUrl = res?.source?.url;
if (streamUrl) {
allUrls.push({
url: streamUrl,
server: `Cinezo - ${res.source.label || source}`,
headers: {
Referer: 'https://player.cinezo.live/',
Origin: 'https://player.cinezo.live'
}
});
}
} catch { }
}
if (allUrls.length) break;
}
if (!allUrls.length) return null;
return {
url: allUrls[0].url,
headers: allUrls[0].headers,
allUrls
};
}

View File

@@ -1,20 +1,23 @@
import { fetchText } from '../utils/helpers.js';
export async function getStream({ id, s, e }) {
const BASE_URL = 'https://cno.to';
export async function getStream(args) {
const { id, s, e } = args || {};
const isTv = s != null && e != null;
const serverIndices = Array.from({ length: 50 }, (_, i) => i + 1);
const headers = {
'Accept': 'application/json, text/plain, */*',
'Referer': 'https://cno.to/',
'Origin': 'https://cno.to',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36'
Accept: 'application/json, text/plain, */*',
Referer: `${BASE_URL}/`,
Origin: BASE_URL,
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36',
};
const promises = serverIndices.map(async (serverNum) => {
const url = isTv
? `https://cno.to/api/s/${serverNum}/tv/${id}/${s}/${e}`
: `https://cno.to/api/s/${serverNum}/movie/${id}?v=3`;
? `${BASE_URL}/api/s/${serverNum}/tv/${id}/${s}/${e}`
: `${BASE_URL}/api/s/${serverNum}/movie/${id}?v=3`;
try {
const raw = await fetchText(url, { headers });
@@ -50,16 +53,18 @@ export async function getStream({ id, s, e }) {
if (streamUrl) {
if (streamUrl.startsWith('/')) {
streamUrl = `https://cno.to${streamUrl}`;
streamUrl = `${BASE_URL}${streamUrl}`;
}
return {
url: streamUrl,
server: `Cno - Server ${serverNum} (${quality})`,
quality,
type: 'hls',
headers: {
Referer: 'https://cno.to/',
Origin: 'https://cno.to'
}
Referer: `${BASE_URL}/`,
Origin: BASE_URL,
},
};
}
}
@@ -71,10 +76,12 @@ export async function getStream({ id, s, e }) {
return {
url: jsonUrl,
server: `Cno - Server ${serverNum} (${json.quality || 'Auto'})`,
quality: json.quality || 'Auto',
type: jsonUrl.includes('.m3u8') ? 'hls' : 'mp4',
headers: {
Referer: 'https://cno.to/',
Origin: 'https://cno.to'
}
Referer: `${BASE_URL}/`,
Origin: BASE_URL,
},
};
}
} catch { }
@@ -90,6 +97,6 @@ export async function getStream({ id, s, e }) {
return {
url: allUrls[0].url,
headers: allUrls[0].headers,
allUrls
allUrls,
};
}

View File

@@ -1,28 +1,8 @@
import { getUA } from '../utils/helpers.js';
import { getTmdbMetadata, getUA, resolveTmdbKey } from '../utils/helpers.js';
const BASE_URL = 'https://dulo.mov';
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
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] : '';
const runtime = isTv ? (data.episode_run_time?.[0] || 24) : (data.runtime || 90);
return { title, year, runtime };
} catch {
return null;
}
}
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
async function fetchSource(url, headers) {
try {
@@ -37,15 +17,17 @@ async function fetchSource(url, headers) {
}
}
export async function getStream({ id, s, e, sdk }) {
const isTv = Boolean(s && e);
export async function getStream(args) {
const { id, s, e, sdk } = args || {};
const isTv = s != null && e != null;
const mediaType = isTv ? 'tv' : 'movie';
const apiKey = resolveTmdbKey(args, sdk);
const ua = getUA();
const details = await resolveDetails(sdk, id, isTv);
const title = details?.title || '';
const year = details?.year || '';
const runtime = details?.runtime || '';
const meta = await getTmdbMetadata(id, isTv, apiKey);
const title = meta?.title || '';
const year = meta?.year || '';
const runtime = meta?.runtime || '';
const referer = isTv
? `${BASE_URL}/watch/tv/${id}/${s}/${e}`
@@ -97,7 +79,7 @@ export async function getStream({ id, s, e, sdk }) {
while (pendingUrls.length > 0 && Date.now() < maxPollTime) {
const results = await Promise.all(
pendingUrls.map((url) => fetchSource(url, defaultHeaders))
pendingUrls.map(url => fetchSource(url, defaultHeaders))
);
const nextPending = [];
@@ -119,7 +101,10 @@ export async function getStream({ id, s, e, sdk }) {
allUrls.push({
url: streamUrl,
server: src.label || 'Dulo',
label: src.label || 'Dulo',
quality: 'Auto',
type: streamUrl.includes('.m3u8') ? 'hls' : 'mp4',
headers: {
'User-Agent': ua,
Referer: referer,
@@ -144,7 +129,7 @@ export async function getStream({ id, s, e, sdk }) {
return {
url: allUrls[0].url,
allUrls,
headers: allUrls[0].headers,
allUrls,
};
}

View File

@@ -1,106 +1,115 @@
import { fetchJson } from '../utils/helpers.js';
export async function getStream({ id, s, e }) {
const isTv = s != null && e != null;
const allUrls = [];
const endpoints = [
async () => {
const url = isTv
? `https://themoviebox.uk/api/resolve?type=tv&id=${id}&season=${s}&episode=${e}`
: `https://themoviebox.uk/api/resolve?type=movie&id=${id}`;
const res = await fetchJson(url);
if (res?.success && Array.isArray(res.sources)) {
for (const item of res.sources) {
const streamUrl = item.url || item.original_url;
if (streamUrl) {
allUrls.push({
url: streamUrl,
server: `TheMovieBox - ${item.server || item.provider || 'Server'}`,
headers: {
Referer: 'https://themoviebox.uk/',
Origin: 'https://themoviebox.uk'
}
});
}
}
}
},
async () => {
const url = isTv
? `https://pro2.nabilekson.workers.dev/tv/${id}/${s}/${e}`
: `https://pro2.nabilekson.workers.dev/movie/${id}`;
const res = await fetchJson(url);
if (res?.success && Array.isArray(res.primarySources)) {
for (const item of res.primarySources) {
const streamUrl = item.url || item.originalUrl;
if (streamUrl) {
allUrls.push({
url: streamUrl,
server: `CineBros - ${item.name || item.quality || 'Server'}`,
headers: item.headers || {
Referer: 'https://pro2.nabilekson.workers.dev/',
Origin: 'https://pro2.nabilekson.workers.dev'
}
});
}
}
}
},
async () => {
const url = isTv
? `https://praxi.nabilekson.workers.dev/tv/${id}/${s}/${e}`
: `https://praxi.nabilekson.workers.dev/movie/${id}`;
const res = await fetchJson(url);
if (res?.success && Array.isArray(res.primarySources)) {
for (const item of res.primarySources) {
const streamUrl = item.url || item.originalUrl;
if (streamUrl) {
allUrls.push({
url: streamUrl,
server: `Praxi - ${item.name || item.providerName || 'Server'}`,
headers: item.headers || {
Referer: 'https://praxi.nabilekson.workers.dev/',
Origin: 'https://praxi.nabilekson.workers.dev'
}
});
}
}
}
},
async () => {
const url = isTv
? `https://freakyniki.elaxo.lol/?tmdb_id=${id}&season=${s}&episode=${e}`
: `https://freakyniki.elaxo.lol/?tmdb_id=${id}`;
const res = await fetchJson(url);
if (Array.isArray(res?.streams)) {
for (const item of res.streams) {
const streamUrl = item.url || item.raw_url;
if (streamUrl) {
allUrls.push({
url: streamUrl,
server: `FreakyNiki - ${item.server || item.provider || 'Server'}`,
headers: {
Referer: 'https://freakyniki.elaxo.lol/',
Origin: 'https://freakyniki.elaxo.lol'
}
});
}
}
}
}
];
await Promise.allSettled(endpoints.map(fn => fn().catch(() => { })));
if (!allUrls.length) return null;
return {
url: allUrls[0].url,
headers: allUrls[0].headers,
allUrls
};
import { fetchJson } from '../utils/helpers.js';
export async function getStream(args) {
const { id, s, e } = args || {};
const isTv = s != null && e != null;
const allUrls = [];
const endpoints = [
async () => {
const url = isTv
? `https://themoviebox.uk/api/resolve?type=tv&id=${id}&season=${s}&episode=${e}`
: `https://themoviebox.uk/api/resolve?type=movie&id=${id}`;
const res = await fetchJson(url);
if (res?.success && Array.isArray(res.sources)) {
for (const item of res.sources) {
const streamUrl = item.url || item.original_url;
if (streamUrl) {
allUrls.push({
url: streamUrl,
server: `TheMovieBox - ${item.server || item.provider || 'Server'}`,
quality: 'Auto',
type: streamUrl.includes('.m3u8') ? 'hls' : 'mp4',
headers: {
Referer: 'https://themoviebox.uk/',
Origin: 'https://themoviebox.uk',
},
});
}
}
}
},
async () => {
const url = isTv
? `https://pro2.nabilekson.workers.dev/tv/${id}/${s}/${e}`
: `https://pro2.nabilekson.workers.dev/movie/${id}`;
const res = await fetchJson(url);
if (res?.success && Array.isArray(res.primarySources)) {
for (const item of res.primarySources) {
const streamUrl = item.url || item.originalUrl;
if (streamUrl) {
allUrls.push({
url: streamUrl,
server: `CineBros - ${item.name || item.quality || 'Server'}`,
quality: item.quality || 'Auto',
type: streamUrl.includes('.m3u8') ? 'hls' : 'mp4',
headers: item.headers || {
Referer: 'https://pro2.nabilekson.workers.dev/',
Origin: 'https://pro2.nabilekson.workers.dev',
},
});
}
}
}
},
async () => {
const url = isTv
? `https://praxi.nabilekson.workers.dev/tv/${id}/${s}/${e}`
: `https://praxi.nabilekson.workers.dev/movie/${id}`;
const res = await fetchJson(url);
if (res?.success && Array.isArray(res.primarySources)) {
for (const item of res.primarySources) {
const streamUrl = item.url || item.originalUrl;
if (streamUrl) {
allUrls.push({
url: streamUrl,
server: `Praxi - ${item.name || item.providerName || 'Server'}`,
quality: 'Auto',
type: streamUrl.includes('.m3u8') ? 'hls' : 'mp4',
headers: item.headers || {
Referer: 'https://praxi.nabilekson.workers.dev/',
Origin: 'https://praxi.nabilekson.workers.dev',
},
});
}
}
}
},
async () => {
const url = isTv
? `https://freakyniki.elaxo.lol/?tmdb_id=${id}&season=${s}&episode=${e}`
: `https://freakyniki.elaxo.lol/?tmdb_id=${id}`;
const res = await fetchJson(url);
if (Array.isArray(res?.streams)) {
for (const item of res.streams) {
const streamUrl = item.url || item.raw_url;
if (streamUrl) {
allUrls.push({
url: streamUrl,
server: `FreakyNiki - ${item.server || item.provider || 'Server'}`,
quality: 'Auto',
type: streamUrl.includes('.m3u8') ? 'hls' : 'mp4',
headers: {
Referer: 'https://freakyniki.elaxo.lol/',
Origin: 'https://freakyniki.elaxo.lol',
},
});
}
}
}
},
];
await Promise.allSettled(endpoints.map(fn => fn().catch(() => { })));
if (!allUrls.length) return null;
return {
url: allUrls[0].url,
headers: allUrls[0].headers,
allUrls,
};
}

View File

@@ -1,30 +1,39 @@
import { fetchJson, resolveImdbId } from '../utils/helpers.js';
import { fetchJson, resolveImdbId, resolveTmdbKey } from '../utils/helpers.js';
export async function getStream({ id, s, e, tmdbApiKey, sdk }) {
const BASE_URL = 'https://froststream.cloutteam.com';
export async function getStream(args) {
const { id, s, e, sdk } = args || {};
const isTv = s != null && e != null;
const type = isTv ? 'series' : 'movie';
const imdb = await resolveImdbId(id, isTv, tmdbApiKey || sdk?.tmdbApiKey);
const apiKey = resolveTmdbKey(args, sdk);
const imdb = await resolveImdbId(id, isTv, apiKey);
if (!imdb) return null;
const url = isTv
? `https://froststream.cloutteam.com/stream/series/${imdb}:${s}:${e}.json`
: `https://froststream.cloutteam.com/stream/movie/${imdb}.json`;
? `${BASE_URL}/stream/series/${imdb}:${s}:${e}.json`
: `${BASE_URL}/stream/movie/${imdb}.json`;
const res = await fetchJson(url).catch(() => null);
if (!res || !Array.isArray(res.streams) || !res.streams.length) return null;
const allUrls = res.streams.filter(st => st.url).map(st => ({
url: st.url,
server: `Frost - ${st.name || 'Server'} ${st.title ? `- ${st.title}` : ''}`,
headers: st.behaviorHints?.proxyHeaders?.request || undefined,
skipProxy: st.behaviorHints?.notWebReady === false
}));
const allUrls = res.streams
.filter(st => st.url)
.map(st => ({
url: st.url,
server: `Frost - ${st.name || 'Server'}${st.title ? ` - ${st.title}` : ''}`,
quality: 'Auto',
type: st.url.includes('.m3u8') ? 'hls' : 'mp4',
headers: st.behaviorHints?.proxyHeaders?.request || undefined,
skipProxy: st.behaviorHints?.notWebReady === false,
}));
if (!allUrls.length) return null;
return {
url: allUrls[0].url,
headers: allUrls[0].headers,
allUrls
allUrls,
};
}

View File

@@ -1,48 +1,96 @@
import { fetchText, USER_AGENT, getTmdbInfo } from '../utils/helpers.js';
const ORIGIN = 'https://www3.fsonline.app';
const AJAX_URL = `${ORIGIN}/wp-admin/admin-ajax.php`;
const HEADERS = { 'User-Agent': USER_AGENT, 'Origin': ORIGIN, 'Referer': `${ORIGIN}/` };
export async function getStream({ id, s, e, 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 searchHtml = await fetchText(`${ORIGIN}/?s=${encodeURIComponent(info.titles[0] + (info.year ? ' ' + info.year : ''))}`, { headers: HEADERS });
const typeFolder = isTv ? 'seriale' : 'film';
const linkMatch = searchHtml.match(new RegExp(`href=["'](https?://www3\\.fsonline\\.app/${typeFolder}/([^"'/]+)/)["']`, 'i'));
if (!linkMatch) return null;
const targetPageUrl = isTv ? `${ORIGIN}/episoade/${linkMatch[2].replace(/-\d{4}$/, '')}-sezonul-${s}-episodul-${e}/` : linkMatch[1];
const pageHtml = await fetchText(targetPageUrl, { headers: HEADERS });
const movieId = (pageHtml.match(/movie-id=['"]([^'"]+)['"]/) || pageHtml.match(/movie-id=([^ >]+)/))?.[1];
if (!movieId) return null;
const ajaxHtml = await fetchText(AJAX_URL, { method: 'POST', headers: { ...HEADERS, 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', 'X-Requested-With': 'XMLHttpRequest', 'Referer': targetPageUrl }, body: `action=lazy_player&movieID=${movieId}` });
const results = [];
let idx = 0;
while ((idx = ajaxHtml.indexOf('data-vs="', idx)) !== -1) {
const embedStart = idx + 9;
const embedEnd = ajaxHtml.indexOf('"', embedStart);
const embedUrl = ajaxHtml.slice(embedStart, embedEnd);
const spanStart = ajaxHtml.indexOf('<span>', embedEnd);
const spanEnd = ajaxHtml.indexOf('</span>', spanStart);
const serverLabel = ajaxHtml.slice(spanStart + 6, spanEnd).trim().toLowerCase();
if (serverLabel.includes('filesun')) {
try {
const rHtml = await fetchText(embedUrl, { headers: { 'Referer': ORIGIN, 'User-Agent': USER_AGENT } });
const m3u8Match = rHtml.match(/file:\s*["'](https?:\/\/[^"']+\.m3u8[^"']*)["']/) || rHtml.match(/["']?file["']?:\s*["'](https?:\/\/[^"']+)["']/);
if (m3u8Match) results.push({ url: m3u8Match[1].replace(/\\\//g, '/'), server: 'FSOnline - FileSuN', headers: { 'Referer': embedUrl, 'Origin': 'https://player.fsonline.app', 'User-Agent': USER_AGENT } });
} catch { }
}
idx = spanEnd;
}
return results.length ? { allUrls: results } : null;
} catch { return null; }
}
export async function getSources(args) {
const res = await getStream(args);
return res?.allUrls ? [...new Set(res.allUrls.map(u => u.server))] : [];
import { fetchText, getTmdbMetadata, resolveTmdbKey, USER_AGENT } from '../utils/helpers.js';
const ORIGIN = 'https://www3.fsonline.app';
const AJAX_URL = `${ORIGIN}/wp-admin/admin-ajax.php`;
const HEADERS = {
'User-Agent': USER_AGENT,
Origin: ORIGIN,
Referer: `${ORIGIN}/`,
};
export async function getStream(args) {
const { id, s, e, sdk } = args || {};
const isTv = s != null && e != null;
const apiKey = resolveTmdbKey(args, sdk);
if (!apiKey) return null;
try {
const meta = await getTmdbMetadata(id, isTv, apiKey);
if (!meta?.titles?.length) return null;
const query = meta.titles[0] + (meta.year ? ` ${meta.year}` : '');
const searchHtml = await fetchText(`${ORIGIN}/?s=${encodeURIComponent(query)}`, { headers: HEADERS });
const typeFolder = isTv ? 'seriale' : 'film';
const linkMatch = searchHtml.match(new RegExp(`href=["'](https?://www3\\.fsonline\\.app/${typeFolder}/([^"'/]+)/)["']`, 'i'));
if (!linkMatch) return null;
const targetPageUrl = isTv
? `${ORIGIN}/episoade/${linkMatch[2].replace(/-\d{4}$/, '')}-sezonul-${s}-episodul-${e}/`
: linkMatch[1];
const pageHtml = await fetchText(targetPageUrl, { headers: HEADERS });
const movieId = (pageHtml.match(/movie-id=['"]([^'"]+)['"]/) || pageHtml.match(/movie-id=([^ >]+)/))?.[1];
if (!movieId) return null;
const ajaxHtml = await fetchText(AJAX_URL, {
method: 'POST',
headers: {
...HEADERS,
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
'X-Requested-With': 'XMLHttpRequest',
Referer: targetPageUrl,
},
body: `action=lazy_player&movieID=${movieId}`,
});
const allUrls = [];
let idx = 0;
while ((idx = ajaxHtml.indexOf('data-vs="', idx)) !== -1) {
const embedStart = idx + 9;
const embedEnd = ajaxHtml.indexOf('"', embedStart);
const embedUrl = ajaxHtml.slice(embedStart, embedEnd);
const spanStart = ajaxHtml.indexOf('<span>', embedEnd);
const spanEnd = ajaxHtml.indexOf('</span>', spanStart);
const serverLabel = ajaxHtml.slice(spanStart + 6, spanEnd).trim().toLowerCase();
if (serverLabel.includes('filesun')) {
try {
const rHtml = await fetchText(embedUrl, {
headers: { Referer: ORIGIN, 'User-Agent': USER_AGENT },
});
const m3u8Match = rHtml.match(/file:\s*["'](https?:\/\/[^"']+\.m3u8[^"']*)["']/) || rHtml.match(/["']?file["']?:\s*["'](https?:\/\/[^"']+)["']/);
if (m3u8Match) {
const streamUrl = m3u8Match[1].replace(/\\\//g, '/');
allUrls.push({
url: streamUrl,
server: 'FSOnline - FileSuN',
quality: 'Auto',
type: 'hls',
headers: {
Referer: embedUrl,
Origin: 'https://player.fsonline.app',
'User-Agent': USER_AGENT,
},
});
}
} catch { }
}
idx = spanEnd;
}
if (!allUrls.length) return null;
return {
url: allUrls[0].url,
headers: allUrls[0].headers,
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

@@ -1,13 +1,18 @@
import { fetchJson, resolveImdbId } from '../utils/helpers.js';
import { fetchJson, resolveImdbId, resolveTmdbKey } from '../utils/helpers.js';
export async function getStream({ id, s, e, tmdbApiKey, sdk }) {
const BASE_URL = 'https://hdhub.thevolecitor.qzz.io';
export async function getStream(args) {
const { id, s, e, sdk } = args || {};
const isTv = s != null && e != null;
const type = isTv ? 'series' : 'movie';
const imdb = await resolveImdbId(id, isTv, tmdbApiKey || sdk?.tmdbApiKey);
const apiKey = resolveTmdbKey(args, sdk);
const imdb = await resolveImdbId(id, isTv, apiKey);
if (!imdb) return null;
const CONFIG = btoa(JSON.stringify({ "torbox": "unset", "qualities": "2160p,1080p,720p", "sort": "desc", "catalogs": "featured,popular" }));
const url = `https://hdhub.thevolecitor.qzz.io/${CONFIG}/stream/${type}/${imdb}${isTv ? `:${s}:${e}` : ""}.json`;
const config = btoa(JSON.stringify({ torbox: 'unset', qualities: '2160p,1080p,720p', sort: 'desc', catalogs: 'featured,popular' }));
const url = `${BASE_URL}/${config}/stream/${type}/${imdb}${isTv ? `:${s}:${e}` : ''}.json`;
const res = await fetchJson(url).catch(() => null);
if (!res || !Array.isArray(res.streams) || !res.streams.length) return null;
@@ -24,13 +29,15 @@ export async function getStream({ id, s, e, tmdbApiKey, sdk }) {
const allUrls = validStreams.map(st => ({
url: st.url,
server: `HDHub - ${st.name || 'Server'}`,
quality: 'Auto',
type: st.url.includes('.m3u8') ? 'hls' : 'mp4',
headers: st.behaviorHints?.proxyHeaders?.request || undefined,
skipProxy: st.behaviorHints?.notWebReady === false
skipProxy: st.behaviorHints?.notWebReady === false,
}));
return {
url: allUrls[0].url,
headers: allUrls[0].headers,
allUrls
allUrls,
};
}
}

View File

@@ -1,54 +1,61 @@
import { fetchJson, fetchText } from '../utils/helpers.js';
import { fetchJson, fetchText, getRandomHex } from '../utils/helpers.js';
const API = "https://enc-dec.app/api";
const API_BASE = 'https://enc-dec.app/api';
const BASE_URL = 'https://hexa.su';
const TMDB_PROXY_BASE = 'https://theemoviedb.hexa.su';
function getRandomHex(size = 32) {
const array = new Uint8Array(size);
crypto.getRandomValues(array);
return Array.from(array).map(b => b.toString(16).padStart(2, '0')).join('');
}
export async function getStream({ id, s, e }) {
export async function getStream(args) {
const { id, s, e } = args || {};
const isTv = s != null && e != null;
const key = getRandomHex(32);
const headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36",
"Referer": "https://hexa.su/",
"Accept": "text/plain",
"X-Fingerprint-Lite": "e9136c41504646444",
"X-Api-Key": key
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36',
Referer: `${BASE_URL}/`,
Accept: 'text/plain',
'X-Fingerprint-Lite': 'e9136c41504646444',
'X-Api-Key': key,
};
const encHexaRes = await fetchJson(`${API}/enc-hexa`);
const encHexaRes = await fetchJson(`${API_BASE}/enc-hexa`);
if (!encHexaRes || encHexaRes.status !== 200) return null;
headers["X-Cap-Token"] = encHexaRes.result.token;
headers['X-Cap-Token'] = encHexaRes.result.token;
const url = isTv
? `https://theemoviedb.hexa.su/api/tmdb/tv/${id}/season/${s}/episode/${e}/images`
: `https://theemoviedb.hexa.su/api/tmdb/movie/${id}/images`;
? `${TMDB_PROXY_BASE}/api/tmdb/tv/${id}/season/${s}/episode/${e}/images`
: `${TMDB_PROXY_BASE}/api/tmdb/movie/${id}/images`;
const encrypted = await fetchText(url, { headers });
if (!encrypted) return null;
const decRes = await fetchJson(`${API}/dec-hexa`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ text: encrypted, key })
const decRes = await fetchJson(`${API_BASE}/dec-hexa`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text: encrypted, key }),
});
if (!decRes || decRes.status !== 200 || !decRes.result) return null;
const data = decRes.result;
const streamUrl = data.url || data.file || (Array.isArray(data.sources) ? data.sources[0]?.url : null);
if (!streamUrl) return null;
const streamHeaders = {
Referer: `${BASE_URL}/`,
Origin: BASE_URL,
};
const allUrls = [{
url: streamUrl,
server: 'Hexa',
quality: 'Auto',
type: streamUrl.includes('.m3u8') ? 'hls' : 'mp4',
headers: streamHeaders,
}];
return {
url: streamUrl,
headers: {
Referer: "https://hexa.su/",
Origin: "https://hexa.su"
}
headers: streamHeaders,
allUrls,
};
}

View File

@@ -1,13 +1,18 @@
import { fetchJson } from '../utils/helpers.js';
export async function getStream({ id, s, e }) {
const BASE_URL = 'https://hyper.stravo.site';
const DEFAULT_ORIGIN = 'https://mzfi.me';
export async function getStream(args) {
const { id, s, e } = args || {};
const isTv = s != null && e != null;
const path = isTv
? `/local/stream/series/tmdb:${id}:${s}:${e}.json`
: `/local/stream/movie/tmdb:${id}.json`;
const url = `https://hyper.stravo.site${path}`;
const res = await fetchJson(url);
const url = `${BASE_URL}${path}`;
const res = await fetchJson(url).catch(() => null);
if (!res || !Array.isArray(res.streams)) return null;
const validStreams = res.streams.filter(item => {
@@ -21,16 +26,18 @@ export async function getStream({ id, s, e }) {
const allUrls = validStreams.map(item => ({
url: item.url,
server: `Hyper - ${item.title?.replace(/[^\x20-\x7E]+/g, '').trim() || 'Server'}`,
quality: 'Auto',
type: item.url.includes('.m3u8') ? 'hls' : 'mp4',
headers: item.behaviorHints?.proxyHeaders?.request || {
Referer: 'https://mzfi.me/',
Accept: '*/*'
Referer: `${DEFAULT_ORIGIN}/`,
Accept: '*/*',
},
skipProxy: item.behaviorHints?.notWebReady === false
skipProxy: item.behaviorHints?.notWebReady === false,
}));
return {
url: allUrls[0].url,
headers: allUrls[0].headers,
allUrls
allUrls,
};
}

View File

@@ -1,24 +1,24 @@
import { fetchJson, USER_AGENT } from '../utils/helpers.js';
import { fetchJson, getTmdbMetadata, resolveTmdbKey, USER_AGENT } from '../utils/helpers.js';
const BASE = "https://kaa.lt";
const HLS_BASE = "https://hls.krussdomi.com/manifest";
const HEADERS = { "User-Agent": USER_AGENT, Accept: "application/json" };
const BASE_URL = 'https://kaa.lt';
const HLS_BASE = 'https://hls.krussdomi.com/manifest';
const HEADERS = { 'User-Agent': USER_AGENT, Accept: 'application/json' };
async function kaaSearch(query) {
const res = await fetchJson(`${BASE}/api/fsearch`, {
method: "POST",
headers: { ...HEADERS, "Content-Type": "application/json" },
const res = await fetchJson(`${BASE_URL}/api/fsearch`, {
method: 'POST',
headers: { ...HEADERS, 'Content-Type': 'application/json' },
body: JSON.stringify({ page: 1, query }),
}).catch(() => null);
return Array.isArray(res?.result) ? res.result : [];
}
async function kaaShowInfo(showSlug) {
return fetchJson(`${BASE}/api/show/${showSlug}`, { headers: HEADERS }).catch(() => null);
return fetchJson(`${BASE_URL}/api/show/${showSlug}`, { headers: HEADERS }).catch(() => null);
}
async function kaaEpisodePage(showSlug, ep) {
return fetchJson(`${BASE}/api/show/${showSlug}/episodes?ep=${ep}&lang=ja-JP`, { headers: HEADERS }).catch(() => null);
return fetchJson(`${BASE_URL}/api/show/${showSlug}/episodes?ep=${ep}&lang=ja-JP`, { headers: HEADERS }).catch(() => null);
}
async function kaaAllEpisodes(showSlug) {
@@ -29,7 +29,7 @@ async function kaaAllEpisodes(showSlug) {
if (pages.length > 1) {
const rest = await Promise.all(
pages.slice(1).map(async (pg) => {
pages.slice(1).map(async pg => {
const startEp = pg.eps?.[0];
if (!startEp) return [];
const d = await kaaEpisodePage(showSlug, startEp);
@@ -43,35 +43,18 @@ async function kaaAllEpisodes(showSlug) {
}
async function kaaEpisodeServers(showSlug, fullEpSlug) {
return fetchJson(`${BASE}/api/show/${showSlug}/episode/${fullEpSlug}`, { headers: HEADERS }).catch(() => null);
return fetchJson(`${BASE_URL}/api/show/${showSlug}/episode/${fullEpSlug}`, { headers: HEADERS }).catch(() => null);
}
export async function getStream({ id, s, e, tmdbApiKey, sdk }) {
export async function getStream(args) {
const { id, s, e, sdk } = args || {};
const isTv = s != null && e != null;
const apiKey = resolveTmdbKey(args, sdk);
const epNum = e ? Number(e) : 1;
try {
const apiKey = tmdbApiKey || sdk?.tmdbApiKey || process.env.TMDB_API_KEY || '45dbdd5e714da212424bda0336167447';
const isTv = !!s;
const mediaType = isTv ? 'tv' : 'movie';
const epNum = e ? Number(e) : 1;
let titles = [];
try {
const tmdbRes = await fetchJson(`https://api.themoviedb.org/3/${mediaType}/${id}?api_key=${apiKey}&append_to_response=alternative_titles`);
if (tmdbRes) {
const primary = tmdbRes.title || tmdbRes.name;
const original = tmdbRes.original_title || tmdbRes.original_name;
if (primary) titles.push(primary);
if (original && !titles.includes(original)) titles.push(original);
const alts = tmdbRes.alternative_titles?.titles || tmdbRes.alternative_titles?.results || [];
for (const alt of alts) {
const candidate = alt.title || alt.name;
if (candidate && !titles.includes(candidate)) {
titles.push(candidate);
}
}
}
} catch { }
const meta = await getTmdbMetadata(id, isTv, apiKey);
const titles = meta?.titles || [];
if (!titles.length) return null;
let showSlug = null;
@@ -88,12 +71,12 @@ export async function getStream({ id, s, e, tmdbApiKey, sdk }) {
const showInfo = await kaaShowInfo(showSlug);
let fullEpSlug = null;
if (showInfo?.type === "movie") {
const m = (showInfo.watch_uri || "").match(/\/(ep-(\d+)-([a-f0-9]+))$/i);
if (showInfo?.type === 'movie') {
const m = (showInfo.watch_uri || '').match(/\/(ep-(\d+)-([a-f0-9]+))$/i);
if (m) fullEpSlug = m[1];
} else {
const episodes = await kaaAllEpisodes(showSlug);
const epItem = episodes.find((item) => item.episode_number === epNum);
const epItem = episodes.find(item => item.episode_number === epNum);
if (epItem) fullEpSlug = `ep-${epItem.episode_number}-${epItem.slug}`;
}
@@ -102,24 +85,34 @@ export async function getStream({ id, s, e, tmdbApiKey, sdk }) {
const episodeData = await kaaEpisodeServers(showSlug, fullEpSlug);
const servers = Array.isArray(episodeData?.servers) ? episodeData.servers : [];
const urls = [];
const allUrls = [];
for (const srv of servers) {
if (!srv.src) continue;
const m = srv.src.match(/[?&]id=([^&]+)/);
if (m) {
urls.push({
allUrls.push({
url: `${HLS_BASE}/${m[1]}/master.m3u8`,
server: `KickAssAnime (${srv.name || 'Server'})`,
quality: 'Auto',
type: 'hls',
headers: HEADERS,
});
}
}
return urls.length ? { allUrls: urls } : null;
} catch (err) {
if (!allUrls.length) return null;
return {
url: allUrls[0].url,
headers: allUrls[0].headers,
allUrls,
};
} catch {
return null;
}
}
export async function getSources() {
return ['KickAssAnime'];
export async function getSources(args) {
const stream = await getStream(args);
return stream?.allUrls ? [...new Set(stream.allUrls.map(u => u.server))] : ['KickAssAnime'];
}

View File

@@ -1,60 +1,115 @@
import { fetchJson, USER_AGENT, getTmdbInfo } from '../utils/helpers.js';
export const SKIP_VERIFY = true;
export const MULTI_URL = false;
const ENC_API = "https://enc-dec.app/api";
const BASE = "https://kisskh.do";
const HEADERS = { 'User-Agent': USER_AGENT, Accept: 'application/json' };
function cleanTitle(t) { return t ? t.toLowerCase().replace(/\(\d{4}\)/g, '').replace(/[^a-z0-9]/g, '') : ''; }
async function encKey(episodeId, type) {
try {
const data = await fetchJson(`${ENC_API}/enc-kisskh?text=${episodeId}&type=${type}`, { signal: AbortSignal.timeout(6000) });
return data?.status === 200 ? data.result : null;
} catch { return null; }
}
export async function getStream({ id, s, e }) {
try {
const info = await getTmdbInfo(id, s ? 'tv' : 'movie', s);
if (!info) return null;
let drama = null, bestScore = -1;
for (const title of (info.titles.length > 1 ? [...info.titles].reverse() : info.titles)) {
const results = await fetchJson(`${BASE}/api/DramaList/Search?q=${encodeURIComponent(title)}`, { headers: HEADERS, signal: AbortSignal.timeout(6000) }).catch(() => []);
const targetClean = cleanTitle(title);
for (const item of results) {
const parts = (item.title || '').split(/\s*-\s*/).map(cleanTitle);
let score = 0;
if (parts.some(p => p === targetClean)) score += 5;
else if (parts.some(p => p.includes(targetClean) || targetClean.includes(p))) score += 3;
else continue;
if (score > bestScore) { bestScore = score; drama = item; }
}
if (drama && bestScore >= 5) break;
}
if (!drama) return null;
const detail = await fetchJson(`${BASE}/api/DramaList/Drama/${drama.id}`, { headers: HEADERS, signal: AbortSignal.timeout(8000) });
const episodeId = detail?.episodes?.find(ep => Math.floor(ep.number) === Number(e || 1))?.id;
if (!episodeId) return null;
const vidKey = await encKey(episodeId, 'vid');
if (!vidKey) return null;
const videoData = await fetchJson(`${BASE}/api/DramaList/Episode/${episodeId}.png?err=false&ts=&time=&kkey=${vidKey}`, { headers: HEADERS, signal: AbortSignal.timeout(10000) });
if (!videoData?.Video) return null;
let subtitles = [];
const subKey = await encKey(episodeId, 'sub');
if (subKey) {
try {
const subList = await fetchJson(`${BASE}/api/Sub/${episodeId}?kkey=${subKey}`, { headers: HEADERS, signal: AbortSignal.timeout(8000) });
if (Array.isArray(subList)) subtitles = subList.map(sub => ({ url: sub.src, label: sub.label, lang: sub.land, default: !!sub.default }));
} catch { }
}
return { allUrls: [{ url: videoData.Video, type: videoData.Video.includes('.m3u8') ? 'hls' : 'mp4', audio: 'sub', server: 'KissKH', skipProxy: false, subtitles }] };
} catch { return null; }
}
export async function getSources(args) {
const stream = await getStream(args);
return stream?.allUrls ? [...new Set(stream.allUrls.map(u => u.server))] : [];
import { cleanTitle, fetchJson, getTmdbMetadata, resolveTmdbKey, USER_AGENT } from '../utils/helpers.js';
const ENC_API = 'https://enc-dec.app/api';
const BASE_URL = 'https://kisskh.do';
const HEADERS = { 'User-Agent': USER_AGENT, Accept: 'application/json' };
async function encKey(episodeId, type) {
try {
const data = await fetchJson(`${ENC_API}/enc-kisskh?text=${episodeId}&type=${type}`, { signal: AbortSignal.timeout(6000) });
return data?.status === 200 ? data.result : null;
} catch {
return null;
}
}
export async function getStream(args) {
const { id, s, e, sdk } = args || {};
const isTv = s != null && e != null;
const apiKey = resolveTmdbKey(args, sdk);
try {
const meta = await getTmdbMetadata(id, isTv, apiKey);
if (!meta?.titles?.length) return null;
let drama = null;
let bestScore = -1;
const searchTitles = meta.titles.length > 1 ? [...meta.titles].reverse() : meta.titles;
for (const title of searchTitles) {
const results = await fetchJson(`${BASE_URL}/api/DramaList/Search?q=${encodeURIComponent(title)}`, {
headers: HEADERS,
signal: AbortSignal.timeout(6000),
}).catch(() => []);
const targetClean = cleanTitle(title);
for (const item of results) {
const parts = (item.title || '').split(/\s*-\s*/).map(cleanTitle);
let score = 0;
if (parts.some(p => p === targetClean)) score += 5;
else if (parts.some(p => p.includes(targetClean) || targetClean.includes(p))) score += 3;
else continue;
if (score > bestScore) {
bestScore = score;
drama = item;
}
}
if (drama && bestScore >= 5) break;
}
if (!drama) return null;
const detail = await fetchJson(`${BASE_URL}/api/DramaList/Drama/${drama.id}`, {
headers: HEADERS,
signal: AbortSignal.timeout(8000),
});
const episodeId = detail?.episodes?.find(ep => Math.floor(ep.number) === Number(e || 1))?.id;
if (!episodeId) return null;
const vidKey = await encKey(episodeId, 'vid');
if (!vidKey) return null;
const videoData = await fetchJson(`${BASE_URL}/api/DramaList/Episode/${episodeId}.png?err=false&ts=&time=&kkey=${vidKey}`, {
headers: HEADERS,
signal: AbortSignal.timeout(10000),
});
if (!videoData?.Video) return null;
let subtitles = [];
const subKey = await encKey(episodeId, 'sub');
if (subKey) {
try {
const subList = await fetchJson(`${BASE_URL}/api/Sub/${episodeId}?kkey=${subKey}`, {
headers: HEADERS,
signal: AbortSignal.timeout(8000),
});
if (Array.isArray(subList)) {
subtitles = subList.map(sub => ({
url: sub.src,
label: sub.label,
lang: sub.land,
default: Boolean(sub.default),
}));
}
} catch { }
}
const streamUrl = videoData.Video;
const allUrls = [{
url: streamUrl,
type: streamUrl.includes('.m3u8') ? 'hls' : 'mp4',
audio: 'sub',
server: 'KissKH',
quality: 'Auto',
headers: HEADERS,
skipProxy: false,
subtitles,
}];
return {
url: streamUrl,
headers: HEADERS,
allUrls,
subtitles,
};
} catch {
return null;
}
}
export async function getSources(args) {
const stream = await getStream(args);
return stream?.allUrls ? [...new Set(stream.allUrls.map(u => u.server))] : ['KissKH'];
}

View File

@@ -1,25 +1,25 @@
import { getTmdbInfo } from '../utils/helpers.js';
import { fetchJson, getTmdbMetadata, resolveTmdbKey, USER_AGENT } from '../utils/helpers.js';
const BASE_URL = 'https://lmscript.xyz';
const REFERER = 'https://lookmovie2.to/';
export async function getStream(args) {
const { id, s, e, tmdbApiKey, sdk } = args;
const type = (s != null && e != null) ? 'tv' : 'movie';
const apiKey = tmdbApiKey || sdk?.tmdbApiKey || process.env.TMDB_API_KEY || '45dbdd5e714da212424bda0336167447';
const { id, s, e, sdk } = args || {};
const isTv = s != null && e != null;
if (isTv) return null;
const apiKey = resolveTmdbKey(args, sdk);
try {
if (type === 'tv') {
return null;
}
const tmdbInfo = await getTmdbInfo(apiKey, id, type, s);
const titles = tmdbInfo?.titles || [];
const meta = await getTmdbMetadata(id, isTv, apiKey);
const titles = meta?.titles || [];
if (!titles.length) return null;
let movie = null;
for (const title of titles) {
const url = `https://lmscript.xyz/v1/movies?filters[q]=${encodeURIComponent(title)}&expand=streams`;
const response = await fetch(url);
if (!response.ok) continue;
const data = await response.json();
const url = `${BASE_URL}/v1/movies?filters[q]=${encodeURIComponent(title)}&expand=streams`;
const data = await fetchJson(url).catch(() => null);
if (!data) continue;
const found = data.items?.find(item => {
const tmdbPrefixMatch = `${item.tmdb_prefix}` === `${id}`;
@@ -37,33 +37,35 @@ export async function getStream(args) {
if (!movie) return null;
const streams = movie.streams || {};
const SOURCE_ORDER = ["1080p", "720p", "480p", "360p"];
const sourceOrder = ['1080p', '720p', '480p', '360p'];
const sortedStreams = Object.keys(streams).sort((a, b) => {
const indexA = SOURCE_ORDER.indexOf(a);
const indexB = SOURCE_ORDER.indexOf(b);
return (indexA === -1 ? 99 : indexA) - (indexB === -1 ? 99 : indexB);
const idxA = sourceOrder.indexOf(a);
const idxB = sourceOrder.indexOf(b);
if (idxA !== -1 && idxB !== -1) return idxA - idxB;
if (idxA !== -1) return -1;
if (idxB !== -1) return 1;
return 0;
});
if (!sortedStreams.length) return null;
const headers = {
Referer: REFERER,
'User-Agent': USER_AGENT,
};
const allUrls = sortedStreams.map(quality => {
const streamUrl = streams[quality];
return {
url: streamUrl,
quality,
server: `LMScript - ${quality}`,
type: streamUrl.includes('.m3u8') ? 'hls' : 'mp4',
headers: {
Referer: 'https://lookmovie2.to/',
Origin: 'https://lookmovie2.to'
}
};
});
const allUrls = sortedStreams.map(res => ({
url: streams[res],
quality: res,
server: `LookMovie - ${res}`,
type: streams[res].includes('.m3u8') ? 'hls' : 'mp4',
headers,
}));
if (!allUrls.length) return null;
return {
url: allUrls[0].url,
headers: allUrls[0].headers,
allUrls
allUrls,
};
} catch {
return null;

View File

@@ -1,80 +1,81 @@
import { USER_AGENT, fetchText, fetchJson } from '../utils/helpers.js';
const TMDB_BASE = 'https://api.themoviedb.org/3';
const LM_DOMAINS = ['https://www.lookmovie2.to', 'https://lookmovie2.to', 'https://lookmovie.foundation'];
const HEADERS_BASE = { 'User-Agent': USER_AGENT, 'Accept-Language': 'en-US,en;q=0.9' };
async function searchLookMovie(type, title, year, clientIP) {
for (const base of LM_DOMAINS) {
try {
const headers = { ...HEADERS_BASE, 'Accept': 'application/json', 'Referer': `${base}/`, 'X-Requested-With': 'XMLHttpRequest', ...(clientIP && { 'X-Forwarded-For': clientIP }) };
const data = await fetchJson(`${base}/api/v1/${type}/do-search/?q=${encodeURIComponent(title)}`, { headers, signal: AbortSignal.timeout(4000) });
const results = data?.result;
if (results?.length) {
const match = results.find(r => String(r.year) === String(year)) ?? results.find(r => r.title?.toLowerCase() === title.toLowerCase()) ?? results[0];
if (match) return { match, base };
}
} catch { }
}
return null;
}
const STORAGE_RE = /window\[['"](?:movie|show)_storage['"]\]\s*=\s*\{([^}]+)\}/s;
const ID_MOVIE_RE = /['"]?(?:id_movie|movieId)['"]?\s*[:=]\s*['"]?(\d+)['"]?/i;
const SEASONS_RE = /seasons\s*:\s*(\[[\s\S]+?\])\s*[,}]/;
function getEpisodeIdFast(html, s, e) {
const sMatch = html.match(STORAGE_RE);
if (sMatch) {
const block = sMatch[1], sm = block.match(SEASONS_RE);
if (sm) {
try {
const seasons = JSON.parse(sm[1]);
const season = seasons.find(x => String(x?.season ?? x?.meta?.season) === String(s));
if (season) {
const ep = Array.isArray(season.episodes) ? season.episodes.find(x => String(x.episode) === String(e)) : (season.episodes?.[String(e)] || Object.values(season.episodes || {}).find(x => String(x.episode) === String(e)));
if (ep) return String(ep.id_episode ?? ep.id);
}
} catch { }
}
}
const parts = html.split(/id_episode['"]?\s*[:=]\s*['"]?(\d+)['"]?/i);
for (let i = 1; i < parts.length; i += 2) {
const context = parts[i - 1].slice(-300) + parts[i + 1].slice(0, 300);
if (context.match(new RegExp(`['"]?episode['"]?\\s*[:=]\\s*['"]?${e}['"]?`, 'i')) && context.match(new RegExp(`['"]?season['"]?\\s*[:=]\\s*['"]?${s}['"]?`, 'i'))) return parts[i];
}
const am = html.match(new RegExp(`data-season=["']${s}["'][^>]*?data-episode=["']${e}["'][^>]*?data-id=["'](\\d+)["']`, 'i')) || html.match(new RegExp(`data-episode=["']${e}["'][^>]*?data-season=["']${s}["'][^>]*?data-id=["'](\\d+)["']`, 'i'));
return am ? am[1] : null;
}
export async function getStream({ id, s, e, sdk, clientIP }) {
const tmdbApiKey = sdk?.tmdbApiKey || null;
if (!tmdbApiKey) return null;
try {
const tmdbKey = tmdbApiKey;
if (!tmdbKey) return null;
const isTV = s != null && e != null;
const typeStr = isTV ? 'shows' : 'movies';
const tmdbData = await fetchJson(`${TMDB_BASE}/${isTV ? 'tv' : 'movie'}/${id}?api_key=${tmdbKey}`, { signal: AbortSignal.timeout(3000) });
const title = tmdbData?.title || tmdbData?.name;
const year = (tmdbData?.first_air_date || tmdbData?.release_date || '').slice(0, 4);
if (!title) return null;
const searchRes = await searchLookMovie(typeStr, title, year, clientIP);
if (!searchRes?.match?.slug) return null;
const { match, base } = searchRes;
const headers = { ...HEADERS_BASE, 'Accept': 'text/html', 'Referer': `${base}/`, ...(clientIP && { 'X-Forwarded-For': clientIP }) };
const html = await fetchText(`${base}/${typeStr}/play/${match.slug}`, { headers, signal: AbortSignal.timeout(8000) });
const storageMatch = html.match(STORAGE_RE);
if (!storageMatch) return null;
const hashMatch = storageMatch[1].match(/hash\s*:\s*['"]([^'"]+)['"]/);
const expiresMatch = storageMatch[1].match(/expires\s*:\s*(\d+)/);
if (!hashMatch || !expiresMatch) return null;
const streamId = isTV ? getEpisodeIdFast(html, s, e) : (match.id_movie || match.id || html.match(ID_MOVIE_RE)?.[1]);
if (!streamId) return null;
const accessHeaders = { ...HEADERS_BASE, 'Accept': 'application/json', 'Referer': `${base}/`, 'X-Requested-With': 'XMLHttpRequest', ...(clientIP && { 'X-Forwarded-For': clientIP }) };
const data = await fetchJson(`${base}/api/v1/security/${isTV ? 'episode' : 'movie'}-access?id_${isTV ? 'episode' : 'movie'}=${streamId}&hash=${hashMatch[1]}&expires=${expiresMatch[1]}`, { headers: accessHeaders, signal: AbortSignal.timeout(8000) });
const streams = data?.streams ?? data?.result?.streams ?? data?.data?.streams ?? data;
const allUrls = Object.entries(streams || {}).filter(([, v]) => typeof v === 'string' && v.includes('.m3u8')).map(([quality, url]) => ({ url, quality }));
return allUrls.length ? { allUrls } : null;
} catch { return null; }
import { USER_AGENT, fetchText, fetchJson } from '../utils/helpers.js';
const TMDB_BASE = 'https://api.themoviedb.org/3';
const LM_DOMAINS = ['https://www.lookmovie2.to', 'https://lookmovie2.to', 'https://lookmovie.foundation'];
const HEADERS_BASE = { 'User-Agent': USER_AGENT, 'Accept-Language': 'en-US,en;q=0.9' };
async function searchLookMovie(type, title, year, clientIP) {
for (const base of LM_DOMAINS) {
try {
const headers = { ...HEADERS_BASE, 'Accept': 'application/json', 'Referer': `${base}/`, 'X-Requested-With': 'XMLHttpRequest', ...(clientIP && { 'X-Forwarded-For': clientIP }) };
const data = await fetchJson(`${base}/api/v1/${type}/do-search/?q=${encodeURIComponent(title)}`, { headers, signal: AbortSignal.timeout(4000) });
const results = data?.result;
if (results?.length) {
const match = results.find(r => String(r.year) === String(year)) ?? results.find(r => r.title?.toLowerCase() === title.toLowerCase()) ?? results[0];
if (match) return { match, base };
}
} catch { }
}
return null;
}
const STORAGE_RE = /window\[['"](?:movie|show)_storage['"]\]\s*=\s*\{([^}]+)\}/s;
const ID_MOVIE_RE = /['"]?(?:id_movie|movieId)['"]?\s*[:=]\s*['"]?(\d+)['"]?/i;
const SEASONS_RE = /seasons\s*:\s*(\[[\s\S]+?\])\s*[,}]/;
function getEpisodeIdFast(html, s, e) {
const sMatch = html.match(STORAGE_RE);
if (sMatch) {
const block = sMatch[1], sm = block.match(SEASONS_RE);
if (sm) {
try {
const seasons = JSON.parse(sm[1]);
const season = seasons.find(x => String(x?.season ?? x?.meta?.season) === String(s));
if (season) {
const ep = Array.isArray(season.episodes) ? season.episodes.find(x => String(x.episode) === String(e)) : (season.episodes?.[String(e)] || Object.values(season.episodes || {}).find(x => String(x.episode) === String(e)));
if (ep) return String(ep.id_episode ?? ep.id);
}
} catch { }
}
}
const parts = html.split(/id_episode['"]?\s*[:=]\s*['"]?(\d+)['"]?/i);
for (let i = 1; i < parts.length; i += 2) {
const context = parts[i - 1].slice(-300) + parts[i + 1].slice(0, 300);
if (context.match(new RegExp(`['"]?episode['"]?\\s*[:=]\\s*['"]?${e}['"]?`, 'i')) && context.match(new RegExp(`['"]?season['"]?\\s*[:=]\\s*['"]?${s}['"]?`, 'i'))) return parts[i];
}
const am = html.match(new RegExp(`data-season=["']${s}["'][^>]*?data-episode=["']${e}["'][^>]*?data-id=["'](\\d+)["']`, 'i')) || html.match(new RegExp(`data-episode=["']${e}["'][^>]*?data-season=["']${s}["'][^>]*?data-id=["'](\\d+)["']`, 'i'));
return am ? am[1] : null;
}
export async function getStream({ id, s, e, sdk, clientIP }) {
const tmdbApiKey = sdk?.tmdbApiKey || null;
if (!tmdbApiKey) return null;
try {
const tmdbKey = tmdbApiKey;
if (!tmdbKey) return null;
const isTV = s != null && e != null;
const typeStr = isTV ? 'shows' : 'movies';
const tmdbData = await fetchJson(`${TMDB_BASE}/${isTV ? 'tv' : 'movie'}/${id}?api_key=${tmdbKey}`, { signal: AbortSignal.timeout(3000) });
const title = tmdbData?.title || tmdbData?.name;
const year = (tmdbData?.first_air_date || tmdbData?.release_date || '').slice(0, 4);
if (!title) return null;
const searchRes = await searchLookMovie(typeStr, title, year, clientIP);
if (!searchRes?.match?.slug) return null;
const { match, base } = searchRes;
const headers = { ...HEADERS_BASE, 'Accept': 'text/html', 'Referer': `${base}/`, ...(clientIP && { 'X-Forwarded-For': clientIP }) };
const html = await fetchText(`${base}/${typeStr}/play/${match.slug}`, { headers, signal: AbortSignal.timeout(8000) });
const storageMatch = html.match(STORAGE_RE);
if (!storageMatch) return null;
const hashMatch = storageMatch[1].match(/hash\s*:\s*['"]([^'"]+)['"]/);
const expiresMatch = storageMatch[1].match(/expires\s*:\s*(\d+)/);
if (!hashMatch || !expiresMatch) return null;
const streamId = isTV ? getEpisodeIdFast(html, s, e) : (match.id_movie || match.id || html.match(ID_MOVIE_RE)?.[1]);
if (!streamId) return null;
const accessHeaders = { ...HEADERS_BASE, 'Accept': 'application/json', 'Referer': `${base}/`, 'X-Requested-With': 'XMLHttpRequest', ...(clientIP && { 'X-Forwarded-For': clientIP }) };
const data = await fetchJson(`${base}/api/v1/security/${isTV ? 'episode' : 'movie'}-access?id_${isTV ? 'episode' : 'movie'}=${streamId}&hash=${hashMatch[1]}&expires=${expiresMatch[1]}`, { headers: accessHeaders, signal: AbortSignal.timeout(8000) });
const streams = data?.streams ?? data?.result?.streams ?? data?.data?.streams ?? data;
const allUrls = Object.entries(streams || {}).filter(([, v]) => typeof v === 'string' && v.includes('.m3u8')).map(([quality, url]) => ({ url, quality }));
return allUrls.length ? { allUrls } : null;
} catch { return null; }
}

View File

@@ -1,125 +1,75 @@
import { getUA } from "../utils/helpers.js";
import { getTmdbMetadata, getUA, resolveTmdbKey } from '../utils/helpers.js';
const API_BASE = "https://api.movenerds.space/api";
function getHeaders() {
return {
"User-Agent": getUA(),
Accept: "application/json",
Referer: "https://movenerds.space/",
Origin: "https://movenerds.space",
};
}
function resolveApiKey(args) {
return (
args?.tmdbApiKey ||
args?.sdk?.tmdbApiKey ||
process.env.TMDB_API_KEY ||
null
);
}
async function getMeta(id, isTv, apiKey) {
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(5000) }
);
if (!res.ok) return null;
const data = await res.json();
const title = isTv ? data.name : data.title;
const date = isTv ? data.first_air_date : data.release_date;
const year = date ? parseInt(date.slice(0, 4), 10) : null;
return { title, year };
} catch {
return null;
}
}
const BASE_URL = 'https://movenerds.space';
const API_BASE = 'https://api.movenerds.space/api';
function mapSource(source) {
if (!source || !source.url) return null;
if (source.type !== "hls" && source.type !== "mp4") return null;
if (source.type !== 'hls' && source.type !== 'mp4') return null;
if (!/^https?:\/\//.test(source.url)) return null;
return {
url: source.url,
headers: {
"User-Agent": source.headers?.["User-Agent"] || getUA(),
Referer: source.headers?.Referer || "https://movenerds.space/",
Origin: source.headers?.Origin || "https://movenerds.space",
'User-Agent': source.headers?.['User-Agent'] || getUA(),
Referer: source.headers?.Referer || `${BASE_URL}/`,
Origin: source.headers?.Origin || BASE_URL,
},
server: source.name || source.providerName || "MoverNerds",
quality: source.quality || "1080p",
server: source.name || source.providerName || 'MoverNerds',
quality: source.quality || '1080p',
type: source.type,
skipProxy: true,
};
}
export async function getStream(args) {
const { id, s = null, e = null } = args;
const { id, s, e, sdk } = args || {};
const isTv = s != null && e != null;
const apiKey = resolveTmdbKey(args, sdk);
const apiKey = resolveApiKey(args);
const meta = await getMeta(id, isTv, apiKey);
if (!meta?.title) {
if (process.env.DEBUG_CAUSE) {
console.error(
`[movenerds] Could not resolve title for ${isTv ? "tv" : "movie"} ${id} ` +
`(tmdbApiKey ${apiKey ? "present" : "MISSING"})`
);
}
return null;
}
const meta = await getTmdbMetadata(id, isTv, apiKey);
if (!meta?.title) return null;
const params = new URLSearchParams({
tmdbId: String(id),
type: isTv ? "tv" : "movie",
type: isTv ? 'tv' : 'movie',
title: meta.title,
});
if (meta.year) params.set("year", String(meta.year));
if (meta.year) params.set('year', String(meta.year));
if (isTv) {
params.set("season", String(s));
params.set("episode", String(e));
params.set("language", "en");
params.set('season', String(s));
params.set('episode', String(e));
params.set('language', 'en');
}
const url = `${API_BASE}/stream?${params.toString()}`;
const headers = {
'User-Agent': getUA(),
Accept: 'application/json',
Referer: `${BASE_URL}/`,
Origin: BASE_URL,
};
try {
const res = await fetch(url, {
headers: getHeaders(),
headers,
signal: AbortSignal.timeout(20000),
});
if (!res.ok) {
if (process.env.DEBUG_CAUSE) {
console.error(`[movenerds] HTTP ${res.status} for ${url}`);
}
return null;
}
if (!res.ok) return null;
const data = await res.json();
const primary = Array.isArray(data?.primarySources) ? data.primarySources : [];
if (!primary.length) {
if (process.env.DEBUG_CAUSE) {
console.error(`[movenerds] No primarySources in response for ${url}`);
}
return null;
}
if (!primary.length) return null;
const allUrls = primary.map(mapSource).filter(Boolean);
if (!allUrls.length) return null;
return { allUrls };
} catch (err) {
if (process.env.DEBUG_CAUSE) {
console.error(`[movenerds] fetch error for ${url}:`, err.message);
if (err.cause) console.error(" cause:", err.cause.code, err.cause.message);
}
return {
url: allUrls[0].url,
headers: allUrls[0].headers,
allUrls,
};
} catch {
return null;
}
}

View File

@@ -1,10 +1,10 @@
import { fetchJson, fetchText, getTmdbInfo } from '../utils/helpers.js';
import { fetchJson, fetchText, getTmdbMetadata, resolveTmdbKey } from '../utils/helpers.js';
const K = [
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174
0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
];
const MAGIC = [109, 118, 109, 49];
@@ -62,7 +62,7 @@ function initCipherState(keyStr, seedStr) {
}
return {
S,
acc: scramble(0xa5a5a5a5 ^ n) >>> 0
acc: scramble(0xa5a5a5a5 ^ n) >>> 0,
};
}
@@ -82,7 +82,7 @@ function nextKeystreamWord(state, step) {
}
function decodeBase64Url(str) {
const padded = str.replace(/-/g, "+").replace(/_/g, "/").padEnd(4 * Math.ceil(str.length / 4), "=");
const padded = str.replace(/-/g, '+').replace(/_/g, '/').padEnd(4 * Math.ceil(str.length / 4), '=');
const bin = atob(padded);
const bytes = new Uint8Array(bin.length);
for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
@@ -111,45 +111,31 @@ function decryptPayload(ciphertext, seed, mediaId) {
for (let i = 0; i < MAGIC.length; i++) {
if (rawBytes[i] !== MAGIC[i]) {
throw new Error("decrypt failed: bad seed or tampered payload");
throw new Error('decrypt failed: bad seed or tampered payload');
}
}
const payload = rawBytes.subarray(MAGIC.length);
return new TextDecoder("utf-8").decode(payload);
return new TextDecoder('utf-8').decode(payload);
}
export async function getStream({ id, s, e, tmdbApiKey, sdk }) {
export async function getStream(args) {
const { id, s, e, sdk } = args || {};
const isTv = s != null && e != null;
const type = isTv ? 'tv' : 'movie';
const apiKey = tmdbApiKey || sdk?.tmdbApiKey || process.env.TMDB_API_KEY || '45dbdd5e714da212424bda0336167447';
const apiKey = resolveTmdbKey(args, sdk);
const headers = {
'Accept': '*/*',
'Origin': 'https://www.movy.sx',
'Referer': 'https://www.movy.sx/',
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1'
Accept: '*/*',
Origin: 'https://www.movy.sx',
Referer: 'https://www.movy.sx/',
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1',
};
let title = '';
let year = '';
let imdbId = '';
try {
const info = await getTmdbInfo(apiKey, id, type, s);
title = info?.titles?.[0] || '';
year = info?.year || '';
imdbId = info?.imdbId || '';
} catch { }
if (!title) {
try {
const tmdbRes = await fetchJson(`https://api.themoviedb.org/3/${type}/${id}?api_key=${apiKey}&append_to_response=external_ids`);
title = tmdbRes?.title || tmdbRes?.name || '';
year = (tmdbRes?.release_date || tmdbRes?.first_air_date || '').split('-')[0] || '';
imdbId = tmdbRes?.imdb_id || tmdbRes?.external_ids?.imdb_id || '';
} catch { }
}
const meta = await getTmdbMetadata(id, isTv, apiKey);
const title = meta?.title || '';
const year = meta?.year ? String(meta.year) : '';
const imdbId = meta?.imdbId || '';
if (!title) return null;
@@ -170,7 +156,7 @@ export async function getStream({ id, s, e, tmdbApiKey, sdk }) {
tmdbId: String(id),
imdbId: String(imdbId || ''),
enc: '2',
seed
seed,
});
const url = `https://api.wecollege.net/${srv}/sources?${params.toString()}`;
@@ -187,10 +173,12 @@ export async function getStream({ id, s, e, tmdbApiKey, sdk }) {
allUrls.push({
url: src.url,
server: `Movy - ${srv.toUpperCase()} (${src.quality || 'Auto'})`,
quality: src.quality || 'Auto',
type: src.url.includes('.m3u8') ? 'hls' : 'mp4',
headers: {
Referer: 'https://www.movy.sx/',
Origin: 'https://www.movy.sx'
}
Origin: 'https://www.movy.sx',
},
});
}
}
@@ -205,6 +193,6 @@ export async function getStream({ id, s, e, tmdbApiKey, sdk }) {
return {
url: allUrls[0].url,
headers: allUrls[0].headers,
allUrls
allUrls,
};
}

View File

@@ -1,16 +1,20 @@
import { fetchJson } from '../utils/helpers.js';
export async function getStream({ id, s, e }) {
const BASE_URL = 'https://net79.cc';
export async function getStream(args) {
const { id, s, e } = args || {};
const isTv = s != null && e != null;
const url = isTv
? `https://net79.cc/api/embed-tmdb/${id}?type=tv&se=${s}&ep=${e}`
: `https://net79.cc/api/embed-tmdb/${id}?type=movie`;
? `${BASE_URL}/api/embed-tmdb/${id}?type=tv&se=${s}&ep=${e}`
: `${BASE_URL}/api/embed-tmdb/${id}?type=movie`;
const headers = {
'Accept': 'application/json',
'Referer': 'https://net79.cc/',
'Origin': 'https://net79.cc',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36'
Accept: 'application/json',
Referer: `${BASE_URL}/`,
Origin: BASE_URL,
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36',
};
const res = await fetchJson(url, { headers }).catch(() => null);
@@ -27,9 +31,9 @@ export async function getStream({ id, s, e }) {
server: `Net79 - ${stream.resolution ? `${stream.resolution}p` : 'MP4'}`,
type: stream.url.includes('.m3u8') ? 'hls' : 'mp4',
headers: {
Referer: 'https://net79.cc/',
Origin: 'https://net79.cc'
}
Referer: `${BASE_URL}/`,
Origin: BASE_URL,
},
});
}
}
@@ -40,9 +44,9 @@ export async function getStream({ id, s, e }) {
server: `Net79 - ${res.resolution ? `${res.resolution}p` : 'MP4'}`,
type: res.mp4.includes('.m3u8') ? 'hls' : 'mp4',
headers: {
Referer: 'https://net79.cc/',
Origin: 'https://net79.cc'
}
Referer: `${BASE_URL}/`,
Origin: BASE_URL,
},
});
}
@@ -51,6 +55,6 @@ export async function getStream({ id, s, e }) {
return {
url: allUrls[0].url,
headers: allUrls[0].headers,
allUrls
allUrls,
};
}

View File

@@ -1,4 +1,4 @@
import { USER_AGENT, fetchJson, getTmdbInfo } from '../utils/helpers.js';
import { fetchJson, getTmdbMetadata, resolveTmdbKey, USER_AGENT } from '../utils/helpers.js';
const BASE_URL = 'https://providers.peestream.in';
@@ -6,58 +6,40 @@ const PROVIDERS = {
purstream: 'Athena',
vidrock: 'Thor',
vaplayer: 'Poseidon',
netmirror: 'Ra'
netmirror: 'Ra',
};
export async function getStream(args) {
const { id, s, e, server, tmdbApiKey } = args;
const { id, s, e, server, sdk } = args || {};
const isTv = s != null && e != null;
const type = isTv ? 'tv' : 'movie';
const apiKey = resolveTmdbKey(args, sdk);
let title = args.title || '';
let releaseYear = args.year || '';
let imdbId = args.imdbId || '';
let seasonTmdbId = args.seasonTmdbId || '';
let episodeTmdbId = args.episodeTmdbId || '';
let title = args?.title || '';
let releaseYear = args?.year || '';
let imdbId = args?.imdbId || '';
let seasonTmdbId = args?.seasonTmdbId || '';
let episodeTmdbId = args?.episodeTmdbId || '';
try {
const info = await getTmdbInfo(id, type, s, tmdbApiKey);
if (info) {
title = title || info.name || info.title || '';
if (!releaseYear) {
const date = info.release_date || info.first_air_date || '';
releaseYear = date ? date.split('-')[0] : (info.year || '');
}
if (!imdbId && info.external_ids?.imdb_id) {
imdbId = info.external_ids.imdb_id;
} else if (!imdbId && info.imdb_id) {
imdbId = info.imdb_id;
}
const meta = await getTmdbMetadata(id, isTv, apiKey);
if (meta) {
title = title || meta.title || '';
if (!releaseYear && meta.year) releaseYear = String(meta.year);
if (!imdbId && meta.imdbId) imdbId = meta.imdbId;
if (isTv) {
if (!seasonTmdbId && Array.isArray(info.seasons)) {
const matchedSeason = info.seasons.find(
item => item.season_number === Number(s)
);
if (matchedSeason) {
seasonTmdbId = String(matchedSeason.id);
}
if (!seasonTmdbId && Array.isArray(meta.seasons)) {
const matchedSeason = meta.seasons.find(item => item.season_number === Number(s));
if (matchedSeason) seasonTmdbId = String(matchedSeason.id);
}
if (!episodeTmdbId && tmdbApiKey) {
if (!episodeTmdbId && apiKey) {
try {
const seasonData = await fetchJson(
`https://api.themoviedb.org/3/tv/${id}/season/${s}?api_key=${tmdbApiKey}`
);
const seasonData = await fetchJson(`https://api.themoviedb.org/3/tv/${id}/season/${s}?api_key=${apiKey}`);
if (seasonData?.episodes) {
const matchedEp = seasonData.episodes.find(
ep => ep.episode_number === Number(e)
);
if (matchedEp) {
episodeTmdbId = String(matchedEp.id);
}
const matchedEp = seasonData.episodes.find(ep => ep.episode_number === Number(e));
if (matchedEp) episodeTmdbId = String(matchedEp.id);
}
} catch { }
}
@@ -68,7 +50,7 @@ export async function getStream(args) {
const queryParams = new URLSearchParams({
type,
tmdbId: String(id),
title: title || String(id)
title: title || String(id),
});
if (releaseYear) queryParams.set('releaseYear', String(releaseYear));
@@ -88,10 +70,10 @@ export async function getStream(args) {
const res = await fetch(scrapeUrl, {
headers: {
'User-Agent': USER_AGENT,
'Accept': 'text/event-stream',
'Referer': `${BASE_URL}/`
Accept: 'text/event-stream',
Referer: `${BASE_URL}/`,
},
signal: AbortSignal.timeout(20000)
signal: AbortSignal.timeout(20000),
});
if (res.ok) {
@@ -114,8 +96,8 @@ export async function getStream(args) {
quality: 'Auto',
type: stream.type === 'hls' || streamUrl.includes('.m3u8') ? 'hls' : 'mp4',
headers: stream.headers || {
'User-Agent': USER_AGENT
}
'User-Agent': USER_AGENT,
},
});
}
} catch { }
@@ -128,10 +110,16 @@ export async function getStream(args) {
if (server && server !== 'all') {
const clean = server.toLowerCase().replace('peestream - ', '').trim();
const filtered = allUrls.filter(u => u.server.toLowerCase().includes(clean));
if (filtered.length) return { allUrls: filtered };
if (filtered.length) return { url: filtered[0].url, headers: filtered[0].headers, allUrls: filtered };
}
return allUrls.length ? { allUrls } : null;
if (!allUrls.length) return null;
return {
url: allUrls[0].url,
headers: allUrls[0].headers,
allUrls,
};
}
export async function getSources() {

View File

@@ -1,50 +1,47 @@
import { fetchJson } from '../utils/helpers.js';
// const PENGU_CONFIG = '%7B%22source_4khdhub%22%3A%22on%22%2C%22source_moviebox%22%3A%22on%22%2C%22source_moviesdrives%22%3A%22on%22%2C%22source_vaplayer%22%3A%22on%22%2C%22source_hdghartv%22%3A%22on%22%2C%22res_2160%22%3A%22on%22%2C%22res_1080%22%3A%22on%22%2C%22res_720%22%3A%22on%22%2C%22disable_direct%22%3A%22on%22%7D';
// const BASE_URL = `https://pengu.uk/${PENGU_CONFIG}`;
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;
const manifest = sdk?.penguManifest || null;
if (!tmdbApiKey || !manifest || (manifest && !manifest.endsWith('manifest.json'))) return null;
const BASE_URL = manifest ? manifest.split('/manifest.json')[0] : null;
if (!BASE_URL) 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: `Pengu - ${stream.name || 'Server'}`,
headers: Object.keys(headers).length > 0 ? headers : undefined,
skipProxy: stream.behaviorHints?.notWebReady === false
};
}).filter(Boolean);
return allUrls.length ? { allUrls } : null;
} catch {
return null;
}
import { fetchJson, resolveImdbId, resolveTmdbKey } from '../utils/helpers.js';
export async function getStream(args) {
const { id, s, e, sdk } = args || {};
const isTv = s != null && e != null;
const apiKey = resolveTmdbKey(args, sdk);
const manifest = sdk?.penguManifest || null;
if (!apiKey || !manifest || !manifest.endsWith('manifest.json')) return null;
const baseUrl = manifest.split('/manifest.json')[0];
if (!baseUrl) return null;
try {
const imdbId = await resolveImdbId(id, isTv, apiKey);
if (!imdbId) return null;
const path = isTv
? `/stream/series/${imdbId}:${s}:${e}.json`
: `/stream/movie/${imdbId}.json`;
const res = await fetchJson(`${baseUrl}${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: `Pengu - ${stream.name || 'Server'}`,
quality: 'Auto',
type: stream.url.includes('.m3u8') ? 'hls' : 'mp4',
headers: Object.keys(headers).length > 0 ? headers : undefined,
skipProxy: stream.behaviorHints?.notWebReady === false,
};
}).filter(Boolean);
if (!allUrls.length) return null;
return {
url: allUrls[0].url,
headers: allUrls[0].headers,
allUrls,
};
} catch {
return null;
}
}

View File

@@ -9,7 +9,7 @@ async function safeImport(moduleName) {
}
export async function getStream(args) {
const { id, s, e, clientIP, sdk } = args;
const { id, s, e, clientIP, sdk } = args || {};
const isTv = s != null && e != null;
const type = isTv ? 'tv' : 'movie';
const allUrls = [];
@@ -64,5 +64,11 @@ export async function getStream(args) {
})
);
return allUrls.length ? { allUrls } : null;
if (!allUrls.length) return null;
return {
url: allUrls[0].url,
headers: allUrls[0].headers,
allUrls,
};
}

View File

@@ -1,104 +1,96 @@
import { fetchJson, USER_AGENT, getTmdbInfo } from '../utils/helpers.js';
const DOMAIN = 'https://purstream.ad';
const API_BASE = 'https://api.purstream.ad/api/v1';
const HEADERS = {
'User-Agent': USER_AGENT,
'Accept': 'application/json, text/plain, */*',
'Referer': `${DOMAIN}/`,
'Origin': DOMAIN,
'X-Requested-With': 'XMLHttpRequest',
'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'same-site'
};
export async function getStream({ id, s, e }) {
try {
const isTv = s != null;
const info = await getTmdbInfo(id, isTv ? 'tv' : 'movie');
if (!info?.titles?.length) {
return null;
}
const title = info.titles[0];
const searchUrl = `${API_BASE}/search-bar/search/${encodeURIComponent(title)}`;
const searchData = await fetchJson(searchUrl, {
headers: HEADERS,
signal: AbortSignal.timeout(10000)
});
const items = searchData?.data?.items?.movies?.items || [];
const type = isTv ? 'tv' : 'movie';
const lowerTitle = title.toLowerCase();
let match = items.find(item =>
item.type === type &&
item.title?.toLowerCase() === lowerTitle &&
(!info.year || item.release_date?.startsWith(String(info.year)))
);
if (!match) {
match = items.find(item => item.type === type);
}
if (!match?.id) {
return null;
}
const streamUrl = isTv
? `${API_BASE}/stream/${match.id}/episode?season=${s}&episode=${e}`
: `${API_BASE}/stream/${match.id}`;
const json = await fetchJson(streamUrl, {
headers: HEADERS,
signal: AbortSignal.timeout(10000)
});
const sources = json?.data?.items?.sources;
if (json?.type !== 'success' || !Array.isArray(sources) || !sources.length) {
return null;
}
const chosen = sources.find(src => src.stream_url);
if (!chosen?.stream_url) {
return null;
}
const result = {
url: chosen.stream_url,
headers: {
'User-Agent': USER_AGENT,
'Referer': `${DOMAIN}/`
},
server: 'purstream',
quality: chosen.source_name || 'Auto',
type: chosen.format || 'hls',
skipProxy: true
};
return result;
} catch (err) {
return null;
}
}
export async function getSources(args) {
const stream = await getStream(args);
return stream ? [stream] : [];
}
export const VERIFY_HEADERS = {
...HEADERS
};
import { fetchJson, getTmdbMetadata, resolveTmdbKey, USER_AGENT } from '../utils/helpers.js';
const DOMAIN = 'https://purstream.ad';
const API_BASE = 'https://api.purstream.ad/api/v1';
const HEADERS = {
'User-Agent': USER_AGENT,
Accept: 'application/json, text/plain, */*',
Referer: `${DOMAIN}/`,
Origin: DOMAIN,
'X-Requested-With': 'XMLHttpRequest',
'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'same-site',
};
export async function getStream(args) {
const { id, s, e, sdk } = args || {};
const isTv = s != null && e != null;
const type = isTv ? 'tv' : 'movie';
const apiKey = resolveTmdbKey(args, sdk);
try {
const meta = await getTmdbMetadata(id, isTv, apiKey);
if (!meta?.titles?.length) return null;
const title = meta.titles[0];
const searchUrl = `${API_BASE}/search-bar/search/${encodeURIComponent(title)}`;
const searchData = await fetchJson(searchUrl, {
headers: HEADERS,
signal: AbortSignal.timeout(10000),
});
const items = searchData?.data?.items?.movies?.items || [];
const lowerTitle = title.toLowerCase();
let match = items.find(item =>
item.type === type &&
item.title?.toLowerCase() === lowerTitle &&
(!meta.year || item.release_date?.startsWith(String(meta.year)))
);
if (!match) {
match = items.find(item => item.type === type);
}
if (!match?.id) return null;
const streamUrl = isTv
? `${API_BASE}/stream/${match.id}/episode?season=${s}&episode=${e}`
: `${API_BASE}/stream/${match.id}`;
const json = await fetchJson(streamUrl, {
headers: HEADERS,
signal: AbortSignal.timeout(10000),
});
const sources = json?.data?.items?.sources;
if (json?.type !== 'success' || !Array.isArray(sources) || !sources.length) return null;
const chosen = sources.find(src => src.stream_url);
if (!chosen?.stream_url) return null;
const streamHeaders = {
'User-Agent': USER_AGENT,
Referer: `${DOMAIN}/`,
};
const allUrls = [{
url: chosen.stream_url,
headers: streamHeaders,
server: 'purstream',
quality: chosen.source_name || 'Auto',
type: chosen.format || 'hls',
skipProxy: true,
}];
return {
url: chosen.stream_url,
headers: streamHeaders,
server: 'purstream',
quality: chosen.source_name || 'Auto',
type: chosen.format || 'hls',
skipProxy: true,
allUrls,
};
} catch {
return null;
}
}
export async function getSources(args) {
const stream = await getStream(args);
return stream?.allUrls ? stream.allUrls.map(u => u.server) : ['purstream'];
}

View File

@@ -1,4 +1,4 @@
import { USER_AGENT, fetchJson } from '../utils/helpers.js';
import { fetchJson, USER_AGENT } from '../utils/helpers.js';
const C_ARRAY = [
'4Z7lUo', 'gwIVSMD', 'PLmz2elE2v', 'Z4OFV0', 'SZ6RZq6Zc', 'zhJEFYxrz8', 'FOm7b0', 'axHS3q4KDq', 'o9zuXQ', '4Aebt',
@@ -7,12 +7,12 @@ const C_ARRAY = [
'W81hjts92', 'rJhAT', 'NON7LKoMQ', 'NMdY3nsKzI', 't4En5v', 'Qq5cOQ9H', 'Y9nwrp', 'VX5FYVfsf', 'cE5SJG', 'x1vj1',
'HegbLe', 'zJ3nmt4OA', 'gt7rxW57dq', 'clIE9b', 'jyJ9g', 'B5jXjMCSx', 'cOzZBZTV', 'FTXGy', 'Dfh1q1', 'ny9jqZ2POI',
'X2NnMn', 'MBtoyD', 'qz4Ilys7wB', '68lbOMye', '3YUJnmxp', '1fv5Imona', 'PlfvvXD7mA', 'ZarKfHCaPR', 'owORnX', 'dQP1YU',
'dVdkx', 'qgiK0E', 'cx9wQ', '5F9bGa', '7UjkKrp', 'Yvhrj', 'wYXez5Dg3', 'pG4GMU', 'MwMAu', 'rFRD5wlM'
'dVdkx', 'qgiK0E', 'cx9wQ', '5F9bGa', '7UjkKrp', 'Yvhrj', 'wYXez5Dg3', 'pG4GMU', 'MwMAu', 'rFRD5wlM',
];
const DEFAULT_SERVICES = [
'apex', 'pulse', 'solstice', 'quasar', 'horizon', 'primevids',
'flowcast', 'asiacloud', 'citadel', 'hindicast', 'guru'
'flowcast', 'asiacloud', 'citadel', 'hindicast', 'guru',
];
function generateRiveSecretKey(e) {
@@ -80,10 +80,10 @@ async function fetchRiveServices() {
const res = await fetchJson(url, {
headers: {
'User-Agent': USER_AGENT,
'Referer': 'https://www.rivestream.app/',
'Accept': 'application/json, text/plain, */*'
Referer: 'https://www.rivestream.app/',
Accept: 'application/json, text/plain, */*',
},
signal: AbortSignal.timeout(6000)
signal: AbortSignal.timeout(6000),
});
return Array.isArray(res?.data) && res.data.length ? res.data : DEFAULT_SERVICES;
} catch {
@@ -106,10 +106,10 @@ async function fetchServiceStreams(service, isMovie, id, s, e, secretKey, refere
const res = await fetchJson(url, {
headers: {
'User-Agent': USER_AGENT,
'Referer': referer,
'Accept': 'application/json, text/plain, */*'
Referer: referer,
Accept: 'application/json, text/plain, */*',
},
signal: AbortSignal.timeout(10000)
signal: AbortSignal.timeout(10000),
});
if (!res?.data?.sources || !Array.isArray(res.data.sources)) return [];
@@ -129,8 +129,8 @@ async function fetchServiceStreams(service, isMovie, id, s, e, secretKey, refere
type: isM3U8 ? 'hls' : 'mp4',
headers: {
'User-Agent': USER_AGENT,
'Referer': referer
}
Referer: referer,
},
});
}
return streams;
@@ -140,7 +140,7 @@ async function fetchServiceStreams(service, isMovie, id, s, e, secretKey, refere
}
export async function getStream(args) {
const { id, s, e, server } = args;
const { id, s, e, server } = args || {};
const isTv = s != null && e != null;
const isMovie = !isTv;
@@ -168,7 +168,13 @@ export async function getStream(args) {
}
}
return allUrls.length ? { allUrls } : null;
if (!allUrls.length) return null;
return {
url: allUrls[0].url,
headers: allUrls[0].headers,
allUrls,
};
}
export async function getSources() {

View File

@@ -1,23 +1,26 @@
import { fetchJson } from '../utils/helpers.js';
export async function getStream({ id, s, e }) {
const BASE_URL = 'https://f.rivo.st';
export async function getStream(args) {
const { id, s, e } = args || {};
const isTv = s != null && e != null;
const separator = isTv ? '&' : '?';
const basePath = isTv
? `https://f.rivo.st/api/source/tv/${id}?s=${s}&e=${e}`
: `https://f.rivo.st/api/source/movie/${id}`;
? `${BASE_URL}/api/source/tv/${id}?s=${s}&e=${e}`
: `${BASE_URL}/api/source/movie/${id}`;
const headers = {
'Accept': '*/*',
'Referer': 'https://f.rivo.st/',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36'
Accept: '*/*',
Referer: `${BASE_URL}/`,
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36',
};
const serverIndices = Array.from({ length: 26 }, (_, i) => i + 1);
const delimiter = basePath.includes('?') ? '&' : '?';
const responses = await Promise.allSettled(
serverIndices.map(serverNum =>
fetchJson(`${basePath}&server=${serverNum}`, { headers })
fetchJson(`${basePath}${delimiter}server=${serverNum}`, { headers })
.catch(() => null)
)
);
@@ -34,10 +37,12 @@ export async function getStream({ id, s, e }) {
allUrls.push({
url: src.url,
server: `Rivo - ${data.server || src.subProvider || 'Server'}`,
quality: src.quality || 'Auto',
type: src.kind || (src.url.includes('.m3u8') ? 'hls' : 'mp4'),
headers: {
Referer: 'https://f.rivo.st/',
Origin: 'https://f.rivo.st'
}
Referer: `${BASE_URL}/`,
Origin: BASE_URL,
},
});
}
}
@@ -49,6 +54,6 @@ export async function getStream({ id, s, e }) {
return {
url: allUrls[0].url,
headers: allUrls[0].headers,
allUrls
allUrls,
};
}

View File

@@ -1,34 +1,17 @@
import { getUA } from '../utils/helpers.js';
import { getTmdbMetadata, getUA, resolveTmdbKey } 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);
export async function getStream(args) {
const { id, s, e, sdk } = args || {};
const isTv = s != null && e != null;
const mediaType = isTv ? 'tv' : 'movie';
const apiKey = resolveTmdbKey(args, sdk);
const ua = getUA();
const details = await resolveDetails(sdk, id, isTv);
const title = details?.title || String(id);
const year = details?.year || '';
const meta = await getTmdbMetadata(id, isTv, apiKey);
const title = meta?.title || String(id);
const year = meta?.year ? String(meta.year) : '';
const formattedTitle = encodeURIComponent(title).replaceAll('%20', '+');
const params = new URLSearchParams({
@@ -114,6 +97,9 @@ export async function getStream({ id, s, e, sdk }) {
allUrls.push({
url: streamUrl,
label,
server: serverName,
quality: item.quality || 'Auto',
type: streamUrl.includes('.m3u8') ? 'hls' : 'mp4',
headers,
});
} catch { }
@@ -123,7 +109,7 @@ export async function getStream({ id, s, e, sdk }) {
return {
url: allUrls[0].url,
allUrls,
headers: allUrls[0].headers,
allUrls,
};
}

View File

@@ -1,15 +1,19 @@
import { fetchJson } from '../utils/helpers.js';
export async function getStream({ id, s, e }) {
const BASE_URL = 'https://thugtv.online';
const API_BASE = 'https://api.thugtv.online/api';
export async function getStream(args) {
const { id, s, e } = args || {};
const isTv = s != null && e != null;
const path = isTv ? `/stream/tv/${id}/${s}/${e}` : `/stream/movie/${id}`;
const mainUrl = `https://api.thugtv.online/api${path}`;
const mainUrl = `${API_BASE}${path}`;
const headers = {
'Accept': 'application/json',
'Referer': 'https://thugtv.online/',
'Origin': 'https://thugtv.online',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36'
Accept: 'application/json',
Referer: `${BASE_URL}/`,
Origin: BASE_URL,
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36',
};
const res = await fetchJson(mainUrl, { headers }).catch(() => null);
@@ -24,17 +28,19 @@ export async function getStream({ id, s, e }) {
allUrls.push({
url: src.url,
server: `ThugTV - ${src.name || src.label || 'Direct'} (${src.quality || 'Auto'})`,
quality: src.quality || 'Auto',
type: src.url.includes('.m3u8') ? 'hls' : 'mp4',
headers: {
Referer: 'https://thugtv.online/',
Origin: 'https://thugtv.online'
}
Referer: `${BASE_URL}/`,
Origin: BASE_URL,
},
});
continue;
}
if (src.resolvePath) {
const cleanPath = src.resolvePath.startsWith('/') ? src.resolvePath : `/${src.resolvePath}`;
const resolveUrl = `https://thugtv.online/api${cleanPath}`;
const resolveUrl = `${BASE_URL}/api${cleanPath}`;
try {
const resolved = await fetchJson(resolveUrl, { headers });
@@ -43,10 +49,12 @@ export async function getStream({ id, s, e }) {
allUrls.push({
url: directUrl,
server: `ThugTV - ${resolved.source.name || resolved.source.label || src.name || 'Direct'}`,
quality: resolved.source.quality || src.quality || 'Auto',
type: directUrl.includes('.m3u8') ? 'hls' : 'mp4',
headers: {
Referer: 'https://thugtv.online/',
Origin: 'https://thugtv.online'
}
Referer: `${BASE_URL}/`,
Origin: BASE_URL,
},
});
}
} catch { }
@@ -58,6 +66,6 @@ export async function getStream({ id, s, e }) {
return {
url: allUrls[0].url,
headers: allUrls[0].headers,
allUrls
allUrls,
};
}

View File

@@ -1,115 +1,123 @@
import { fetchJson, USER_AGENT } from '../utils/helpers.js';
const API_BASE = 'https://streamdata.vaplayer.ru/api.php';
const EMBED_ORIGIN = 'https://nextgencloudfabric.com';
export const VERIFY_HEADERS = {
'User-Agent': USER_AGENT,
'Referer': `${EMBED_ORIGIN}/`,
'Origin': EMBED_ORIGIN
};
function extractStreamUrls(obj, urls = new Set()) {
if (!obj) return urls;
if (typeof obj === 'string') {
if (obj.startsWith('http') && (obj.includes('.m3u8') || obj.includes('.mp4') || obj.includes('.mpd'))) {
urls.add(obj);
}
} else if (Array.isArray(obj)) {
for (const item of obj) extractStreamUrls(item, urls);
} else if (typeof obj === 'object') {
for (const val of Object.values(obj)) {
extractStreamUrls(val, urls);
}
}
return urls;
}
export async function getStream({ id, s, e }) {
try {
const isTv = s != null && e != null;
const type = isTv ? 'tv' : 'movie';
const url = `${API_BASE}?tmdb=${id}&type=${type}${isTv ? `&season=${s}&episode=${e}` : ''}`;
const data = await fetchJson(url, {
headers: {
'Accept': '*/*',
'Origin': EMBED_ORIGIN,
'Referer': `${EMBED_ORIGIN}/`,
'User-Agent': USER_AGENT
},
signal: AbortSignal.timeout(10000)
});
const streamUrls = Array.from(extractStreamUrls(data));
if (!streamUrls.length) return null;
let subs = [];
if (data.subtitles && Array.isArray(data.subtitles)) {
subs = data.subtitles;
} else if (data.captions && Array.isArray(data.captions)) {
subs = data.captions;
}
const subtitles = subs.map(sub => ({
url: sub.url || sub.file || sub.src,
lang: sub.display || sub.label || sub.language || sub.lang || 'Unknown'
})).filter(s => s.url);
const allUrls = streamUrls.map(streamUrl => {
let type = 'mp4';
if (streamUrl.includes('.mpd')) type = 'dash';
else if (streamUrl.includes('.m3u8')) type = 'hls';
return {
url: streamUrl,
server: 'VidAPI',
quality: 'Auto',
type,
headers: VERIFY_HEADERS,
subtitles: subtitles.length > 0 ? subtitles : undefined,
skipProxy: false
};
});
return allUrls.length ? { allUrls } : null;
} catch {
return null;
}
}
export async function getSources() {
return ['VidAPI'];
}
export async function proxyStream({ url, res, proxyUtils: { fetchUpstream, rewriteM3u8 } }) {
try {
const upstream = await fetchUpstream(url, 0, VERIFY_HEADERS);
if (!upstream) return res.end();
const ct = (upstream.headers?.['content-type'] || '').toLowerCase();
const isM3u8 = ct.includes('mpegurl') || ct.includes('m3u8') || /\.m3u8?(\?|$)/i.test(url);
if (isM3u8) {
const chunks = [];
for await (const c of upstream) chunks.push(c);
let body = Buffer.concat(chunks).toString('utf8');
res.setHeader('Content-Type', 'application/vnd.apple.mpegurl');
res.setHeader('Access-Control-Allow-Origin', '*');
return res.end(rewriteM3u8(body, url, '&va=1&tt=1'));
}
res.setHeader('Content-Type', ct || 'application/octet-stream');
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Cache-Control', 'public, max-age=3600');
upstream.pipe(res);
} catch {
if (!res.headersSent) {
res.statusCode = 502;
res.end();
}
}
import { fetchJson, USER_AGENT } from '../utils/helpers.js';
const API_BASE = 'https://streamdata.vaplayer.ru/api.php';
const EMBED_ORIGIN = 'https://nextgencloudfabric.com';
export const VERIFY_HEADERS = {
'User-Agent': USER_AGENT,
Referer: `${EMBED_ORIGIN}/`,
Origin: EMBED_ORIGIN,
};
function extractStreamUrls(obj, urls = new Set()) {
if (!obj) return urls;
if (typeof obj === 'string') {
if (obj.startsWith('http') && (obj.includes('.m3u8') || obj.includes('.mp4') || obj.includes('.mpd'))) {
urls.add(obj);
}
} else if (Array.isArray(obj)) {
for (const item of obj) extractStreamUrls(item, urls);
} else if (typeof obj === 'object') {
for (const val of Object.values(obj)) {
extractStreamUrls(val, urls);
}
}
return urls;
}
export async function getStream(args) {
const { id, s, e } = args || {};
const isTv = s != null && e != null;
const type = isTv ? 'tv' : 'movie';
const url = `${API_BASE}?tmdb=${id}&type=${type}${isTv ? `&season=${s}&episode=${e}` : ''}`;
try {
const data = await fetchJson(url, {
headers: {
Accept: '*/*',
Origin: EMBED_ORIGIN,
Referer: `${EMBED_ORIGIN}/`,
'User-Agent': USER_AGENT,
},
signal: AbortSignal.timeout(10000),
});
const streamUrls = Array.from(extractStreamUrls(data));
if (!streamUrls.length) return null;
let subs = [];
if (data.subtitles && Array.isArray(data.subtitles)) {
subs = data.subtitles;
} else if (data.captions && Array.isArray(data.captions)) {
subs = data.captions;
}
const subtitles = subs.map(sub => ({
url: sub.url || sub.file || sub.src,
lang: sub.display || sub.label || sub.language || sub.lang || 'Unknown',
})).filter(subItem => subItem.url);
const allUrls = streamUrls.map(streamUrl => {
let type = 'mp4';
if (streamUrl.includes('.mpd')) type = 'dash';
else if (streamUrl.includes('.m3u8')) type = 'hls';
return {
url: streamUrl,
server: 'VidAPI',
quality: 'Auto',
type,
headers: VERIFY_HEADERS,
subtitles: subtitles.length > 0 ? subtitles : undefined,
skipProxy: false,
};
});
if (!allUrls.length) return null;
return {
url: allUrls[0].url,
headers: allUrls[0].headers,
allUrls,
...(subtitles.length > 0 ? { subtitles } : {}),
};
} catch {
return null;
}
}
export async function getSources() {
return ['VidAPI'];
}
export async function proxyStream({ url, res, proxyUtils: { fetchUpstream, rewriteM3u8 } }) {
try {
const upstream = await fetchUpstream(url, 0, VERIFY_HEADERS);
if (!upstream) return res.end();
const ct = (upstream.headers?.['content-type'] || '').toLowerCase();
const isM3u8 = ct.includes('mpegurl') || ct.includes('m3u8') || /\.m3u8?(\?|$)/i.test(url);
if (isM3u8) {
const chunks = [];
for await (const c of upstream) chunks.push(c);
let body = Buffer.concat(chunks).toString('utf8');
res.setHeader('Content-Type', 'application/vnd.apple.mpegurl');
res.setHeader('Access-Control-Allow-Origin', '*');
return res.end(rewriteM3u8(body, url, '&va=1&tt=1'));
}
res.setHeader('Content-Type', ct || 'application/octet-stream');
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Cache-Control', 'public, max-age=3600');
upstream.pipe(res);
} catch {
if (!res.headersSent) {
res.statusCode = 502;
res.end();
}
}
}

View File

@@ -1,123 +1,15 @@
import { getUA } from '../utils/helpers.js';
import { scrapeVidcoreFamily } from '../utils/helpers.js';
const BASE_URL = 'https://vidcore.io';
const API = 'https://enc-dec.app/api';
export async function getStream({ id, s, e }) {
const isTv = Boolean(s && e);
const targetUrl = isTv
? `${BASE_URL}/tv/${id}/${s}/${e}/`
: `${BASE_URL}/movie/${id}`;
const ua = getUA();
const baseHeaders = {
'User-Agent': ua,
'Referer': `${BASE_URL}/`,
'X-Requested-With': 'XMLHttpRequest',
};
try {
const pageRes = await fetch(targetUrl, {
headers: { 'User-Agent': ua },
signal: AbortSignal.timeout(10000),
});
if (!pageRes.ok) return null;
const html = await pageRes.text();
const match = html.match(/\\"(?:en|token)\\":\\"(.*?)\\"/);
if (!match || !match[1]) return null;
const enToken = match[1];
const s1Res = await fetch(`${API}/enc-vidcore?text=${encodeURIComponent(enToken)}&stage=1`, {
signal: AbortSignal.timeout(8000),
});
const s1Data = await s1Res.json();
if (s1Data?.status !== 200 || !s1Data?.result?.stage1) return null;
const token1 = s1Data.result.token;
const stage1Url = s1Data.result.stage1;
const post1Res = await fetch(stage1Url, {
method: 'POST',
headers: { ...baseHeaders, 'X-CSRF-Token': token1 },
signal: AbortSignal.timeout(10000),
});
if (!post1Res.ok) return null;
const stage1Text = await post1Res.text();
const s2Res = await fetch(`${API}/enc-vidcore?text=${encodeURIComponent(stage1Text)}&stage=2`, {
signal: AbortSignal.timeout(8000),
});
const s2Data = await s2Res.json();
if (s2Data?.status !== 200 || !s2Data?.result?.servers) return null;
const { servers: serversUrl, stream: streamBaseUrl, token: token2 } = s2Data.result;
const currentHeaders = { ...baseHeaders, 'X-CSRF-Token': token2 };
const serversRes = await fetch(serversUrl, {
method: 'POST',
headers: currentHeaders,
signal: AbortSignal.timeout(10000),
});
if (!serversRes.ok) return null;
const serversEncrypted = await serversRes.text();
const decServersRes = await fetch(`${API}/dec-vidcore`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text: serversEncrypted }),
signal: AbortSignal.timeout(8000),
});
const decServersData = await decServersRes.json();
if (decServersData?.status !== 200 || !Array.isArray(decServersData.result)) return null;
const allUrls = [];
for (const server of decServersData.result) {
if (!server?.data) continue;
try {
const streamUrl = `${streamBaseUrl}/${server.data}`;
const streamEncRes = await fetch(streamUrl, {
method: 'POST',
headers: currentHeaders,
signal: AbortSignal.timeout(8000),
});
if (!streamEncRes.ok) continue;
const streamEncText = await streamEncRes.text();
const decStreamRes = await fetch(`${API}/dec-vidcore`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text: streamEncText }),
signal: AbortSignal.timeout(8000),
});
const decStreamData = await decStreamRes.json();
if (decStreamData?.status !== 200 || !decStreamData.result) continue;
const streamResult = decStreamData.result;
const resultUrl = typeof streamResult === 'string' ? streamResult : streamResult?.url || streamResult?.file;
if (typeof resultUrl === 'string' && resultUrl.startsWith('http')) {
allUrls.push({
url: resultUrl,
label: server.name || server.title || 'VidCore',
headers: {
'User-Agent': ua,
'Referer': `${BASE_URL}/`,
},
});
}
} catch { }
}
if (allUrls.length === 0) return null;
return {
url: allUrls[0].url,
allUrls,
headers: allUrls[0].headers,
};
} catch {
return null;
}
export async function getStream(args) {
const { id, s, e } = args || {};
return await scrapeVidcoreFamily({
id,
s,
e,
baseUrl: BASE_URL,
apiName: 'vidcore',
serverLabel: 'VidCore',
});
}

View File

@@ -1,123 +1,15 @@
import { getUA } from '../utils/helpers.js';
import { scrapeVidcoreFamily } from '../utils/helpers.js';
const BASE_URL = 'https://vidfast.vc';
const API = 'https://enc-dec.app/api';
export async function getStream({ id, s, e }) {
const isTv = Boolean(s && e);
const targetUrl = isTv
? `${BASE_URL}/tv/${id}/${s}/${e}/`
: `${BASE_URL}/movie/${id}`;
const ua = getUA();
const baseHeaders = {
'User-Agent': ua,
'Referer': `${BASE_URL}/`,
'X-Requested-With': 'XMLHttpRequest',
};
try {
const pageRes = await fetch(targetUrl, {
headers: { 'User-Agent': ua },
signal: AbortSignal.timeout(10000),
});
if (!pageRes.ok) return null;
const html = await pageRes.text();
const match = html.match(/\\"(?:en|token)\\":\\"(.*?)\\"/);
if (!match || !match[1]) return null;
const enToken = match[1];
const s1Res = await fetch(`${API}/enc-vidfast?text=${encodeURIComponent(enToken)}&stage=1`, {
signal: AbortSignal.timeout(8000),
});
const s1Data = await s1Res.json();
if (s1Data?.status !== 200 || !s1Data?.result?.stage1) return null;
const token1 = s1Data.result.token;
const stage1Url = s1Data.result.stage1;
const post1Res = await fetch(stage1Url, {
method: 'POST',
headers: { ...baseHeaders, 'X-CSRF-Token': token1 },
signal: AbortSignal.timeout(10000),
});
if (!post1Res.ok) return null;
const stage1Text = await post1Res.text();
const s2Res = await fetch(`${API}/enc-vidfast?text=${encodeURIComponent(stage1Text)}&stage=2`, {
signal: AbortSignal.timeout(8000),
});
const s2Data = await s2Res.json();
if (s2Data?.status !== 200 || !s2Data?.result?.servers) return null;
const { servers: serversUrl, stream: streamBaseUrl, token: token2 } = s2Data.result;
const currentHeaders = { ...baseHeaders, 'X-CSRF-Token': token2 };
const serversRes = await fetch(serversUrl, {
method: 'POST',
headers: currentHeaders,
signal: AbortSignal.timeout(10000),
});
if (!serversRes.ok) return null;
const serversEncrypted = await serversRes.text();
const decServersRes = await fetch(`${API}/dec-vidfast`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text: serversEncrypted }),
signal: AbortSignal.timeout(8000),
});
const decServersData = await decServersRes.json();
if (decServersData?.status !== 200 || !Array.isArray(decServersData.result)) return null;
const allUrls = [];
for (const server of decServersData.result) {
if (!server?.data) continue;
try {
const streamUrl = `${streamBaseUrl}/${server.data}`;
const streamEncRes = await fetch(streamUrl, {
method: 'POST',
headers: currentHeaders,
signal: AbortSignal.timeout(8000),
});
if (!streamEncRes.ok) continue;
const streamEncText = await streamEncRes.text();
const decStreamRes = await fetch(`${API}/dec-vidfast`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text: streamEncText }),
signal: AbortSignal.timeout(8000),
});
const decStreamData = await decStreamRes.json();
if (decStreamData?.status !== 200 || !decStreamData.result) continue;
const streamResult = decStreamData.result;
const resultUrl = typeof streamResult === 'string' ? streamResult : streamResult?.url || streamResult?.file;
if (typeof resultUrl === 'string' && resultUrl.startsWith('http')) {
allUrls.push({
url: resultUrl,
label: server.name || server.title || 'VidFast',
headers: {
'User-Agent': ua,
'Referer': `${BASE_URL}/`,
},
});
}
} catch { }
}
if (allUrls.length === 0) return null;
return {
url: allUrls[0].url,
allUrls,
headers: allUrls[0].headers,
};
} catch {
return null;
}
export async function getStream(args) {
const { id, s, e } = args || {};
return await scrapeVidcoreFamily({
id,
s,
e,
baseUrl: BASE_URL,
apiName: 'vidfast',
serverLabel: 'VidFast',
});
}

View File

@@ -1,76 +1,79 @@
import { fetchJson, USER_AGENT } from '../utils/helpers.js';
const BASE = "https://vidlink.pro";
const HEADERS = { 'User-Agent': USER_AGENT, Origin: BASE, Referer: `${BASE}/` };
export async function getStream({ id, s, e }) {
try {
const encData = await fetchJson(`https://enc-dec.app/api/enc-vidlink?text=${id}`, { signal: AbortSignal.timeout(6000) });
if (encData?.status !== 200 || !encData?.result) return null;
const basePath = s ? `${BASE}/api/b/tv/${encData.result}/${s}/${e || 1}` : `${BASE}/api/b/movie/${encData.result}`;
const responses = await Promise.allSettled([
fetchJson(basePath, { headers: HEADERS, signal: AbortSignal.timeout(10000) }),
fetchJson(`${basePath}?multiLang=0`, { headers: HEADERS, signal: AbortSignal.timeout(10000) })
]);
const streamList = [];
const seenUrls = new Set();
const seenSubs = new Set();
for (const res of responses) {
if (res.status !== 'fulfilled' || !res.value?.stream) continue;
const stream = res.value.stream;
if (stream.playlist && !seenUrls.has(stream.playlist)) {
seenUrls.add(stream.playlist);
const isDash = stream.type === 'dash' || stream.deliveryType === 'dash' || stream.playlist.includes('.mpd');
streamList.push({
url: stream.playlist,
quality: stream.playbackMetadata?.resolutions?.[0] ? `${stream.playbackMetadata.resolutions[0]}p` : 'Auto',
label: `Vidlink ${isDash ? 'DASH' : 'HLS'}`,
name: `Vidlink ${isDash ? 'DASH' : 'HLS'}`,
type: isDash ? 'dash' : 'hls',
headers: stream.playlistHeaders || {},
server: 'Vidlink',
});
}
if (stream.qualities && typeof stream.qualities === 'object') {
const qualityOrder = ['1080', '720', '480', '360', ...Object.keys(stream.qualities)];
const seenQualities = new Set();
for (const q of qualityOrder) {
if (seenQualities.has(q)) continue;
seenQualities.add(q);
const qData = stream.qualities[q];
if (!qData?.url || seenUrls.has(qData.url)) continue;
seenUrls.add(qData.url);
streamList.push({
url: qData.url,
quality: `${q}p`,
label: `Vidlink ${q}p`,
name: `Vidlink ${q}p`,
type: qData.type || 'mp4',
headers: qData.headers || {},
server: 'Vidlink',
});
}
}
}
if (!streamList.length) return null;
return {
allUrls: streamList,
streams: streamList,
};
} catch {
return null;
}
}
export async function getSources(args) {
const stream = await getStream(args);
return stream?.allUrls ? [...new Set(stream.allUrls.map(u => u.server || 'Vidlink'))] : [];
import { fetchJson, USER_AGENT } from '../utils/helpers.js';
const BASE_URL = 'https://vidlink.pro';
const HEADERS = { 'User-Agent': USER_AGENT, Origin: BASE_URL, Referer: `${BASE_URL}/` };
export async function getStream(args) {
const { id, s, e } = args || {};
try {
const encData = await fetchJson(`https://enc-dec.app/api/enc-vidlink?text=${id}`, { signal: AbortSignal.timeout(6000) });
if (encData?.status !== 200 || !encData?.result) return null;
const basePath = s ? `${BASE_URL}/api/b/tv/${encData.result}/${s}/${e || 1}` : `${BASE_URL}/api/b/movie/${encData.result}`;
const responses = await Promise.allSettled([
fetchJson(basePath, { headers: HEADERS, signal: AbortSignal.timeout(10000) }),
fetchJson(`${basePath}?multiLang=0`, { headers: HEADERS, signal: AbortSignal.timeout(10000) }),
]);
const streamList = [];
const seenUrls = new Set();
for (const res of responses) {
if (res.status !== 'fulfilled' || !res.value?.stream) continue;
const stream = res.value.stream;
if (stream.playlist && !seenUrls.has(stream.playlist)) {
seenUrls.add(stream.playlist);
const isDash = stream.type === 'dash' || stream.deliveryType === 'dash' || stream.playlist.includes('.mpd');
streamList.push({
url: stream.playlist,
quality: stream.playbackMetadata?.resolutions?.[0] ? `${stream.playbackMetadata.resolutions[0]}p` : 'Auto',
label: `Vidlink ${isDash ? 'DASH' : 'HLS'}`,
name: `Vidlink ${isDash ? 'DASH' : 'HLS'}`,
server: 'Vidlink',
type: isDash ? 'dash' : 'hls',
headers: stream.playlistHeaders || {},
});
}
if (stream.qualities && typeof stream.qualities === 'object') {
const qualityOrder = ['1080', '720', '480', '360', ...Object.keys(stream.qualities)];
const seenQualities = new Set();
for (const q of qualityOrder) {
if (seenQualities.has(q)) continue;
seenQualities.add(q);
const qData = stream.qualities[q];
if (!qData?.url || seenUrls.has(qData.url)) continue;
seenUrls.add(qData.url);
streamList.push({
url: qData.url,
quality: `${q}p`,
label: `Vidlink ${q}p`,
name: `Vidlink ${q}p`,
server: 'Vidlink',
type: qData.type || 'mp4',
headers: qData.headers || {},
});
}
}
}
if (!streamList.length) return null;
return {
url: streamList[0].url,
headers: streamList[0].headers,
allUrls: streamList,
streams: streamList,
};
} catch {
return null;
}
}
export async function getSources(args) {
const stream = await getStream(args);
return stream?.allUrls ? [...new Set(stream.allUrls.map(u => u.server || 'Vidlink'))] : [];
}

View File

@@ -1,145 +0,0 @@
import { webcrypto } from 'node:crypto';
import { fetchJson, USER_AGENT } from '../utils/helpers.js';
const GCM_HEX_KEY = '7f3e9c2a8b5d1f4e6a9c3b7d2e5f8a1c4b6d9e2f5a8c1b4d7e9f2a5c8b1d4e7f';
const BASE_URL = 'https://vidrock.ru/';
const PROXY_PREFIX = 'https://proxy.vidrock.store/';
let cryptoKey = null;
function hexToBytes(hex) {
const bytes = new Uint8Array(hex.length / 2);
for (let i = 0; i < bytes.length; i++) {
bytes[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
}
return bytes;
}
function base64UrlToBytes(value) {
let base64 = value.replace(/-/g, '+').replace(/_/g, '/');
const padding = base64.length % 4;
if (padding === 2) {
base64 += '==';
} else if (padding === 3) {
base64 += '=';
} else if (padding === 1) {
throw new Error('Invalid base64url');
}
return new Uint8Array(Buffer.from(base64, 'base64'));
}
async function getKey() {
if (cryptoKey) return cryptoKey;
const keyBytes = hexToBytes(GCM_HEX_KEY);
cryptoKey = await webcrypto.subtle.importKey(
'raw',
keyBytes,
'AES-GCM',
false,
['decrypt']
);
return cryptoKey;
}
async function decryptStreamUrl(value) {
try {
const data = base64UrlToBytes(value);
if (data.length < 28) {
throw new Error('Ciphertext too short');
}
const iv = data.slice(0, 12);
const ciphertext = data.slice(12);
const key = await getKey();
const decrypted = await webcrypto.subtle.decrypt(
{
name: 'AES-GCM',
iv
},
key,
ciphertext
);
return new TextDecoder().decode(decrypted);
} catch (error) {
return null;
}
}
export async function getStream({ id, s, e }) {
try {
const path = s
? `tv/${id}/${s}/${e || 1}`
: `movie/${id}`;
const apiData = await fetchJson(`${BASE_URL}api/${path}`, {
headers: {
'User-Agent': USER_AGENT,
Referer: BASE_URL,
Origin: BASE_URL
}
});
if (!apiData || typeof apiData !== 'object') {
return null;
}
const streams = [];
for (const [provider, info] of Object.entries(apiData)) {
if (!info?.url) {
continue;
}
const decryptedUrl = await decryptStreamUrl(info.url);
if (!decryptedUrl) {
continue;
}
let url = decryptedUrl;
if (url.startsWith(PROXY_PREFIX)) {
url = decodeURIComponent(
url.slice(PROXY_PREFIX.length).replace(/^\/+/, '')
);
}
const isXpass = url.includes('xpass.top');
streams.push({
name: provider,
url,
file: url,
stream: url,
src: url,
quality: info.type || 'auto',
type: info.type || 'hls',
headers: {
'User-Agent': USER_AGENT,
Referer: isXpass
? 'https://play.xpass.top/'
: BASE_URL,
Origin: isXpass
? 'https://play.xpass.top'
: 'https://vidrock.ru'
}
});
}
return streams.length ? { streams } : null;
} catch (error) {
return null;
}
}

View File

@@ -1,123 +1,15 @@
import { getUA } from '../utils/helpers.js';
const BASE_URL = 'https://vidup.to';
const API = 'https://enc-dec.app/api';
export async function getStream({ id, s, e }) {
const isTv = Boolean(s && e);
const targetUrl = isTv
? `${BASE_URL}/tv/${id}/${s}/${e}/`
: `${BASE_URL}/movie/${id}`;
const ua = getUA();
const baseHeaders = {
'User-Agent': ua,
'Referer': `${BASE_URL}/`,
'X-Requested-With': 'XMLHttpRequest',
};
try {
const pageRes = await fetch(targetUrl, {
headers: { 'User-Agent': ua },
signal: AbortSignal.timeout(10000),
});
if (!pageRes.ok) return null;
const html = await pageRes.text();
const match = html.match(/\\"(?:en|token)\\":\\"(.*?)\\"/);
if (!match || !match[1]) return null;
const enToken = match[1];
const s1Res = await fetch(`${API}/enc-vidup?text=${encodeURIComponent(enToken)}&stage=1`, {
signal: AbortSignal.timeout(8000),
});
const s1Data = await s1Res.json();
if (s1Data?.status !== 200 || !s1Data?.result?.stage1) return null;
const token1 = s1Data.result.token;
const stage1Url = s1Data.result.stage1;
const post1Res = await fetch(stage1Url, {
method: 'POST',
headers: { ...baseHeaders, 'X-CSRF-Token': token1 },
signal: AbortSignal.timeout(10000),
});
if (!post1Res.ok) return null;
const stage1Text = await post1Res.text();
const s2Res = await fetch(`${API}/enc-vidup?text=${encodeURIComponent(stage1Text)}&stage=2`, {
signal: AbortSignal.timeout(8000),
});
const s2Data = await s2Res.json();
if (s2Data?.status !== 200 || !s2Data?.result?.servers) return null;
const { servers: serversUrl, stream: streamBaseUrl, token: token2 } = s2Data.result;
const currentHeaders = { ...baseHeaders, 'X-CSRF-Token': token2 };
const serversRes = await fetch(serversUrl, {
method: 'POST',
headers: currentHeaders,
signal: AbortSignal.timeout(10000),
});
if (!serversRes.ok) return null;
const serversEncrypted = await serversRes.text();
const decServersRes = await fetch(`${API}/dec-vidup`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text: serversEncrypted }),
signal: AbortSignal.timeout(8000),
});
const decServersData = await decServersRes.json();
if (decServersData?.status !== 200 || !Array.isArray(decServersData.result)) return null;
const allUrls = [];
for (const server of decServersData.result) {
if (!server?.data) continue;
try {
const streamUrl = `${streamBaseUrl}/${server.data}`;
const streamEncRes = await fetch(streamUrl, {
method: 'POST',
headers: currentHeaders,
signal: AbortSignal.timeout(8000),
});
if (!streamEncRes.ok) continue;
const streamEncText = await streamEncRes.text();
const decStreamRes = await fetch(`${API}/dec-vidup`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text: streamEncText }),
signal: AbortSignal.timeout(8000),
});
const decStreamData = await decStreamRes.json();
if (decStreamData?.status !== 200 || !decStreamData.result) continue;
const streamResult = decStreamData.result;
const resultUrl = typeof streamResult === 'string' ? streamResult : streamResult?.url || streamResult?.file;
if (typeof resultUrl === 'string' && resultUrl.startsWith('http')) {
allUrls.push({
url: resultUrl,
label: server.name || server.title || 'VidUp',
headers: {
'User-Agent': ua,
'Referer': `${BASE_URL}/`,
},
});
}
} catch { }
}
if (allUrls.length === 0) return null;
return {
url: allUrls[0].url,
allUrls,
headers: allUrls[0].headers,
};
} catch {
return null;
}
import { scrapeVidcoreFamily } from '../utils/helpers.js';
const BASE_URL = 'https://vidup.to';
export async function getStream(args) {
const { id, s, e } = args || {};
return await scrapeVidcoreFamily({
id,
s,
e,
baseUrl: BASE_URL,
apiName: 'vidup',
serverLabel: 'VidUp',
});
}

View File

@@ -1,4 +1,4 @@
import { USER_AGENT } from '../utils/helpers.js';
import { formatBytes, USER_AGENT } from '../utils/helpers.js';
const BASE_URL = 'https://vidvault.to';
const API_BASE = `${BASE_URL}/api`;
@@ -6,21 +6,13 @@ 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,
Referer: `${BASE_URL}/`,
Origin: BASE_URL,
'User-Agent': USER_AGENT,
'Accept': 'application/json, text/plain, */*',
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(`${API_BASE}/get-token`, {
method: 'GET',
@@ -36,26 +28,73 @@ function getCaptionType(url) {
return String(url).toLowerCase().includes('.vtt') ? 'vtt' : 'srt';
}
export async function getStream(args) {
const { id, s, e } = args;
const isTv = s != null && e != null;
const type = isTv ? 'tv' : 'movie';
async function fetchJson(url, options = {}) {
try {
const token = await getToken();
if (!token) return null;
const res = await fetch(url, options);
if (!res.ok) return null;
return await res.json();
} catch {
return null;
}
}
const payload = {
type,
tmdbId: String(id),
};
async function resolveDirect(directPath, headers) {
const fullUrl = directPath.startsWith('http') ? directPath : `${BASE_URL}${directPath}`;
try {
const res = await fetch(fullUrl, {
method: 'HEAD',
redirect: 'manual',
headers,
});
const loc = res.headers.get('location');
if (loc) return loc;
} catch { }
return fullUrl;
}
if (isTv) {
payload.season = Number(s);
payload.episode = Number(e);
}
export async function getStream(args) {
const { id, s, e } = args || {};
const isTv = s != null && e != null;
const res = await fetch(`${API_BASE}/download-proxy`, {
const mediaPath = isTv
? `${BASE_URL}/media/tv/${id}/${s}/${e}`
: `${BASE_URL}/media/movie/${id}`;
const [mediaData, tokenData] = await Promise.all([
fetchJson(mediaPath, { headers: HEADERS }).catch(() => null),
fetchJson(`${BASE_URL}/api/get-token`, { headers: HEADERS }).catch(() => null),
]);
const allUrls = [];
const seen = new Set();
if (Array.isArray(mediaData?.direct)) {
await Promise.all(
mediaData.direct.map(async (item) => {
if (!item?.path) return;
const streamUrl = await resolveDirect(item.path, HEADERS);
if (streamUrl && !seen.has(streamUrl)) {
seen.add(streamUrl);
const format = item.format || (streamUrl.includes('.mkv') ? 'mkv' : 'mp4');
allUrls.push({
url: streamUrl,
server: `VidVault - Direct ${format.toUpperCase()} (${item.quality || item.size || 'Auto'})`,
quality: item.quality || 'Auto',
type: format,
headers: HEADERS,
});
}
})
);
}
const token = tokenData?.t;
if (token) {
const payload = isTv
? { type: 'tv', tmdbId: String(id), season: String(s), episode: String(e) }
: { type: 'movie', tmdbId: String(id) };
const proxyData = await fetchJson(`${BASE_URL}/api/download-proxy`, {
method: 'POST',
headers: {
...HEADERS,
@@ -63,126 +102,33 @@ export async function getStream(args) {
'x-request-token': token,
},
body: JSON.stringify(payload),
signal: AbortSignal.timeout(15000),
});
}).catch(() => null);
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;
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 candidates = [
{ data: proxyData?.mp4Data, type: 'mp4', label: 'MP4' },
{ data: proxyData?.mkvData, type: 'mkv', label: 'MKV' },
{ data: proxyData?.mkvV2Data, type: 'mkv', label: 'MKV V2' },
];
for (const { data, type, label } of candidates) {
if (data?.url && !seen.has(data.url)) {
seen.add(data.url);
allUrls.push({
url: proxiedUrl,
server: `VidVault - MP4 ${res}p${sizeStr}`.trim(),
quality: typeof res === 'number' ? `${res}p` : res,
type: 'mp4',
url: data.url,
server: `VidVault - ${label} (${data.quality || data.size || 'Auto'})`,
quality: data.quality || 'Auto',
type,
headers: HEADERS,
});
}
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: cap.lanName || cap.lan || 'Unknown',
type: getCaptionType(cap.url),
language: cap.lan || '',
url: subUrl,
});
}
}
const mkvFiles = data?.mkvData?.files || [];
for (const file of mkvFiles) {
if (!file?.url) continue;
const sizeStr = file.size ? ` (${typeof file.size === 'number' ? formatBytes(file.size) : file.size})` : '';
allUrls.push({
url: file.url,
server: `VidVault - MKV 480p${sizeStr}`.trim(),
quality: '480p',
type: 'mp4',
headers: HEADERS,
});
}
const mkvV2 = data?.mkvV2Data;
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({
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?.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: subtitles.length ? subtitles : undefined } : null;
} catch (err) {
return null;
}
if (!allUrls.length) return null;
return {
url: allUrls[0].url,
headers: allUrls[0].headers,
allUrls,
};
}

View File

@@ -1,7 +1,7 @@
import { getUA } from "../utils/helpers.js";
import { getUA } from '../utils/helpers.js';
const API_URL = "https://api.wecollege.net";
const SERVERS = ["miami", "atlanta", "seattle", "denver", "dallas", "tampa"];
const API_URL = 'https://api.wecollege.net';
const SERVERS = ['miami', 'atlanta', 'seattle', 'denver', 'dallas', 'tampa'];
const MAGIC = [109, 118, 109, 49];
function imul(a, b) {
@@ -26,10 +26,10 @@ function gFn(e, t) {
}
function decryptVidy(ciphertext, seedStr, mediaId) {
let padded = ciphertext.replace(/-/g, "+").replace(/_/g, "/");
while (padded.length % 4) padded += "=";
let padded = ciphertext.replace(/-/g, '+').replace(/_/g, '/');
while (padded.length % 4) padded += '=';
const raw = new Uint8Array(Buffer.from(padded, "base64"));
const raw = new Uint8Array(Buffer.from(padded, 'base64'));
const length = raw.length;
let tFnv = 2166136261;
@@ -85,7 +85,7 @@ function decryptVidy(ciphertext, seedStr, mediaId) {
for (let i = 0; i < MAGIC.length; i++) {
if (raw[i] !== MAGIC[i]) {
throw new Error("Vidy decrypt failed: magic header mismatch");
throw new Error('Vidy decrypt failed: magic header mismatch');
}
}
@@ -93,19 +93,19 @@ function decryptVidy(ciphertext, seedStr, mediaId) {
}
export async function getStream(args) {
const { id, s = null, e = null } = args;
const { id, s, e } = args || {};
const isTv = s != null && e != null;
const mediaId = Number(id);
const headers = {
"User-Agent": getUA(),
Referer: "https://www.vidy.st/",
Origin: "https://www.vidy.st",
'User-Agent': getUA(),
Referer: 'https://www.vidy.st/',
Origin: 'https://www.vidy.st',
};
try {
const seedUrl = new URL(`${API_URL}/seed`);
seedUrl.searchParams.set("mediaId", String(id));
seedUrl.searchParams.set('mediaId', String(id));
const seedRes = await fetch(seedUrl.toString(), {
headers,
@@ -120,13 +120,13 @@ export async function getStream(args) {
for (const server of SERVERS) {
try {
const srcUrl = new URL(`${API_URL}/${server}/sources`);
srcUrl.searchParams.set("mediaType", isTv ? "tv" : "movie");
srcUrl.searchParams.set("tmdbId", String(id));
srcUrl.searchParams.set("enc", "2");
srcUrl.searchParams.set("seed", seed);
srcUrl.searchParams.set('mediaType', isTv ? 'tv' : 'movie');
srcUrl.searchParams.set('tmdbId', String(id));
srcUrl.searchParams.set('enc', '2');
srcUrl.searchParams.set('seed', seed);
if (isTv) {
srcUrl.searchParams.set("seasonId", String(s));
srcUrl.searchParams.set("episodeId", String(e));
srcUrl.searchParams.set('seasonId', String(s));
srcUrl.searchParams.set('episodeId', String(e));
}
const res = await fetch(srcUrl.toString(), {
@@ -145,35 +145,40 @@ export async function getStream(args) {
}
const sources = data?.sources || [];
const playlist = data?.playlist || "";
const playlist = data?.playlist || '';
if (!sources.length && !playlist) continue;
let streamUrl = playlist;
let quality = "1080p";
let quality = '1080p';
for (const src of sources) {
if (src?.quality === "1080p" && src?.url) {
if (src?.quality === '1080p' && src?.url) {
streamUrl = src.url;
quality = "1080p";
quality = '1080p';
break;
}
}
if (!streamUrl && sources.length) {
streamUrl = sources[0]?.url || "";
quality = sources[0]?.quality || "";
streamUrl = sources[0]?.url || '';
quality = sources[0]?.quality || '';
}
if (!streamUrl) continue;
const allUrls = [{
url: streamUrl,
headers,
server: 'Vidy',
quality,
type: streamUrl.includes('.m3u8') ? 'hls' : 'mp4',
}];
return {
url: streamUrl,
headers: {
"User-Agent": getUA(),
Referer: "https://www.vidy.st/",
Origin: "https://www.vidy.st",
},
server: "Vidy",
headers,
server: 'Vidy',
quality,
type: streamUrl.includes(".m3u8") ? "hls" : "mp4",
type: streamUrl.includes('.m3u8') ? 'hls' : 'mp4',
allUrls,
};
} catch {
continue;
@@ -184,4 +189,4 @@ export async function getStream(args) {
} catch {
return null;
}
}
}

View File

@@ -1,184 +1,190 @@
import { USER_AGENT, fetchJson } from '../utils/helpers.js';
const BASE_URL = 'https://core.vidzee.wtf';
const PLAYER_URL = 'https://player.vidzee.wtf';
const SERVICES = ['dcloud', 'ipcloud', 'tik', 'v6:Hindi', '3AHindi'];
const WASM_B64 = 'AGFzbQEAAAABPgtgAX8Bf2ACf38Bf2ABfwBgAn9/AGADf39/AGAAAGAEf39/fwBgBH9/f38Bf2ADf39/AX9gA39/fgBgAAF/Ag0BA2VudgVhYm9ydAAGAygnAgEAAAQBAAEDBAADBQAAAgMHAAEAAAQIAwEJAgIBBQAAAgEABQoBBQMBAAEGJgd/AUEAC38BQQALfwFBAAt/AUEAC38BQQALfwFBAAt/AEGA2wALB0gHB2RlY3J5cHQAJwVfX25ldwAGBV9fcGluACQHX191bnBpbgAiCV9fY29sbGVjdAAlC19fcnR0aV9iYXNlAwYGbWVtb3J5AgAIAR8MAqkBCogkJyUAIABFBEAPCyAAQRRrIgAQByMCRgRAIAAQECAAIwUjAkUQCgsLCgAgACABai0AAAsKACAAKAIEQXxxCw0AIABBFGsoAhBBAXYLDAAgACABaiACOgAAC1MBAX8gAEHs////A0sEQEHwDEGwDUH9AEEeEAAACyMARQRAEA0LIwAgAEEQahAaIgIgATYCDCACIAA2AhAgAiMBIwIQCiACEAsjA2okAyACQRRqCwoAIAAoAgRBA3ELKQAgACAAIAFBCHZqLQAAQQV0aiABQf8BcUEDdmotAAAgAUEHcXZBAXELwwEBBH8gASgCAEF8cSIDQYACSQR/IANBBHYFQR9B/P///wMgAyADQfz///8DTxsiA2drIgRBB2shAiADIARBBGt2QRBzCyEEIAEoAgghBSABKAIEIgMEQCADIAU2AggLIAUEQCAFIAM2AgQLIAEgACACQQR0IARqQQJ0aiIBKAJgRgRAIAEgBTYCYCAFRQRAIAAgAkECdGoiASgCBEF+IAR3cSEDIAEgAzYCBCADRQRAIAAgACgCAEF+IAJ3cTYCAAsLCwspAQF/IAEoAgghAyAAIAEgAnI2AgQgACADNgIIIAMgABARIAEgADYCCAsNACAAKAIAQXxxQQRqC7wCAQV/IAEoAgAhAyABQQRqIAEoAgBBfHFqIgQoAgAiAkEBcQRAIAAgBBAJIAEgA0EEaiACQXxxaiIDNgIAIAFBBGogASgCAEF8cWoiBCgCACECCyADQQJxBEAgAUEEaygCACIBKAIAIQYgACABEAkgASAGQQRqIANBfHFqIgM2AgALIAQgAkECcjYCACAEQQRrIAE2AgAgACADQXxxIgJBgAJJBH8gAkEEdgVBH0H8////AyACIAJB/P///wNPGyICZ2siA0EHayEFIAIgA0EEa3ZBEHMLIgIgBUEEdGpBAnRqKAJgIQMgAUEANgIEIAEgAzYCCCADBEAgAyABNgIECyAAIAVBBHQgAmpBAnRqIAE2AmAgACAAKAIAQQEgBXRyNgIAIAAgBUECdGoiACAAKAIEQQEgAnRyNgIEC5cBAQJ/PwAiAEEATAR/QQEgAGtAAEEASAVBAAsEQAALQbDbAEEANgIAQdDnAEEANgIAA0AgAUEXSQRAIAFBAnRBsNsAakEANgIEQQAhAANAIABBEEkEQCABQQR0IABqQQJ0QbDbAGkBADYCYCAAQQFqIQAMAQsLIAFBAWohAQwBCwtBsNsAQdTnAD8ArEIQhhAbQbDbACQACyYBAX8gAEEEayEBIABBD3FBASAAGwR/QQEFIAEoAgBBAXELGiABCxIAIAAgADYCBCAAIAA2AgggAAsnAQF/IAAQAyIBRQRAIAAoAggaDwsgASAAKAIIIgA2AgggACABEBELEgAgACABIAAoAgRBA3FyNgIEC0kBAX8gACABQQF0aiEBA0AgAyIAQQFrIQMgAARAIAEvAQAiACACLwEAIgRHBEAgACAEaw8LIAFBAmohASACQQJqIQIMAQsLQQALbgECf0EMQQYQBiIBRQRAQQxBAxAGIQELIAFBADYCACABQQA2AgQgAUEANgIIIABB/P///wNLBEBB4NcAQZDYAEETQTkQAAALIABBARAGIgJBACAA/AsAIAEgAjYCACABIAI2AgQgASAANgIIIAELjgEBAn8gAUGAAkkEfyABQQR2BUEfIAEQFSIBZ2siA0EHayECIAEgA0EEa3ZBEHMLIgEgACACQQJ0aigCBEF/IAF0cSIBBH8gACABaCACQQR0akECdGooAmAFIAAoAgBBfyACQQFqdHEiAQR/IAAgACABaCIAQQJ0aigCBGggAEEEdGpBAnRqKAJgBUEACwsLHQAgAEEBQRsgAGdrdGpBAWsgACAAQf7///8BSRsLLwAgAEH8////A0sEQEHwDEHwDUHNA0EdEAAAC0EMIABBE2pBcHFBBGsgAEEMTRsLZwECfyABKAIAIgNBfHEgAmsiBEEQTwRAIAEgAiADQQJxcjYCACABQQRqIAJqIgEgBEEEa0EBcjYCACAAIAEQDAUgASADQX5xNgIAIAFBBGogASgCAEF8cWoiACAAKAIAQX1xNgIACwswACAAIAIQGiICQQRqIAFBBGogASgCAEF8cfwKAAAgAUGk2wBPBEAgACABEBkLIAILFQAgASABKAIAQQFyNgIAIAAgARAMC48BAQJ/IAAgARAWIgIQFCIBRQRAQQQgACgCoAw/ACIBQRB0QQRrR3QgAhAVIAIgAkGAAk8bakH//wNqQYCAfHFBEHYhAyABIAMgASADShtAAEEASARAIANAAEEASARAAAsLIAAgAUEQdD8ArEIQhhAbIAAgAhAUIQELIAEoAgAaIAAgARAJIAAgASACEBcgAQuGAQEDfyABQRNqQXBxQQRrIQEgACgCoAwiAwRAIAFBEGsiBSADRgRAIAMoAgAhBCAFIQELCyACp0FwcSABayIDQRRJBEAPCyABIARBAnEgA0EIayIDQQFycjYCACABQQA2AgQgAUEANgIIIAFBBGogA2oiA0ECNgIAIAAgAzYCoAwgACABEAwLhQEBA38CQAJAAkACQAJAAkACQAJAAkAgAEEIaygCAA4IAAECAwQFBgcICw8LDwsPCyAAEB0PCyAAKAIAEAEPCyAAKAIEIgEgACgCDEECdGohAgNAIAEgAkkEQCABKAIAIgMEQCADEAELIAFBBGohAQwBCwsgACgCABABDwsgABAdDwsPCwALCQAgACgCABABC0MAIAEgACgCDE8EQEHw1QBBsNYAQfIAQSoQAAALIAAoAgQgAUECdGooAgAiAEUEQEHg1gBBsNYAQfYAQSgQAAALIAALGQBBoA4QDyQBQYDaABAPJARB4NoAEA8kBQv9AQEGfyAAIgFBCHYiAkHMzgBqLQAAIAJBlDJqLQAAQdYAbEGUMmogAEH/AXEiA0EDbmotAAAgA0EDcEECdEGAxwBqKAIAbEELdkEGcGpBAnRBjbrowserigCACICQf8BcSEAIAJBCHUhAgJAIABBAkkNACACQf8BcSEAIAJBCHYhAgNAIAAEQCAAQQF2IgYgAmpBAXRBzNIAaiIELQAAIgUgA0YEfyAELQABQQJ0QYzHAGooAgAiAkH/AXEhACACQQh1IQIgAEECSQ0DIAFBAWoPBSADIAVJBH8gBgUgAiAGaiECIAAgBmsLCyEADAELCyABDwsgASACQQAgAGtxag==';
let wasmInstanceCache = null;
async function getWasmInstance() {
if (wasmInstanceCache) return wasmInstanceCache;
try {
const wasmBuffer = Buffer.from(WASM_B64, 'base64');
const wasmModule = await WebAssembly.compile(wasmBuffer);
let memory;
let exports;
const env = {
abort() {
throw new Error('WASM abort');
}
};
const res = await WebAssembly.instantiate(wasmModule, { env });
exports = res.exports;
memory = exports.memory;
const refMap = new Map();
function pin(ptr) {
if (ptr) {
const count = refMap.get(ptr);
if (count) refMap.set(ptr, count + 1);
else refMap.set(exports.__pin(ptr), 1);
}
return ptr;
}
function unpin(ptr) {
if (ptr) {
const count = refMap.get(ptr);
if (count === 1) {
exports.__unpin(ptr);
refMap.delete(ptr);
} else if (count) {
refMap.set(ptr, count - 1);
}
}
}
wasmInstanceCache = {
decrypt(encryptedBytes, keyStr) {
let dataPtr = 0;
let keyPtr = 0;
try {
const len = encryptedBytes.length;
const pinPtr = exports.__pin(exports.__new(len, 1)) >>> 0;
dataPtr = exports.__new(12, 6) >>> 0;
const view = new DataView(memory.buffer);
view.setUint32(dataPtr, pinPtr, true);
view.setUint32(dataPtr + 4, pinPtr, true);
view.setUint32(dataPtr + 8, len, true);
new Uint8Array(memory.buffer, pinPtr, len).set(encryptedBytes);
exports.__unpin(pinPtr);
pin(dataPtr);
const kLen = keyStr.length;
keyPtr = exports.__new(kLen << 1, 2) >>> 0;
const u16View = new Uint16Array(memory.buffer);
for (let i = 0; i < kLen; i++) {
u16View[(keyPtr >>> 1) + i] = keyStr.charCodeAt(i);
}
const resPtr = exports.decrypt(dataPtr, keyPtr) >>> 0;
if (!resPtr) return new Uint8Array(0);
const resView = new DataView(memory.buffer);
const outPtr = resView.getUint32(resPtr + 4, true);
const outLen = resView.getUint32(resPtr + 8, true);
return new Uint8Array(memory.buffer, outPtr, outLen).slice();
} finally {
unpin(dataPtr);
}
}
};
return wasmInstanceCache;
} catch {
return null;
}
}
async function requestEndpoint(url, headers) {
try {
return await fetchJson(url, {
headers,
signal: AbortSignal.timeout(6000)
});
} catch {
return null;
}
}
async function resolveServiceStream(service, isMovie, id, s, e, headers) {
const directEndpoint = isMovie
? `${BASE_URL}/streams/movie/${id}?s=${service}`
: `${BASE_URL}/streams/tv/${id}/${s || 1}/${e || 1}?s=${service}`;
let res = await requestEndpoint(directEndpoint, headers);
if (!res || !res.url) {
const encEndpoint = `${directEndpoint}&e=1`;
const encRes = await requestEndpoint(encEndpoint, headers);
if (encRes && encRes.c) {
const instance = await getWasmInstance();
if (instance) {
const encryptedBytes = new Uint8Array(Buffer.from(encRes.c, 'base64'));
const decryptedBytes = instance.decrypt(encryptedBytes, 'player.vidzee.wtf');
if (decryptedBytes.length > 0) {
const str = new TextDecoder().decode(decryptedBytes);
try {
res = JSON.parse(str);
} catch { }
}
}
}
}
if (res && res.url) {
const format = res.url.includes('.m3u8') ? 'hls' : 'mp4';
return {
url: res.url,
server: `VidZee (${service})`,
quality: res.language || 'Auto',
type: format,
headers: {
'User-Agent': USER_AGENT,
'Referer': `${PLAYER_URL}/`,
'Origin': PLAYER_URL,
...(res.headers || {})
}
};
}
return null;
}
export async function getStream(args) {
const { id, s, e, server, clientIP } = args;
const isTv = s != null && e != null;
const isMovie = !isTv;
const headers = {
'User-Agent': USER_AGENT,
'Accept': 'application/json, text/plain, */*',
'Referer': `${PLAYER_URL}/`,
'Origin': PLAYER_URL,
...(clientIP && { 'X-Forwarded-For': clientIP })
};
let targets = SERVICES;
if (server && server !== 'all') {
const clean = server.toLowerCase().replace('vidzee (', '').replace(')', '').replace('vidzee', '').trim();
targets = SERVICES.filter(srv => srv.toLowerCase().includes(clean));
if (!targets.length) targets = SERVICES;
}
const settled = await Promise.allSettled(
targets.map(service => resolveServiceStream(service, isMovie, id, s, e, headers))
);
const allUrls = [];
for (const r of settled) {
if (r.status === 'fulfilled' && r.value) {
allUrls.push(r.value);
}
}
return allUrls.length ? { allUrls } : null;
}
export async function getSources() {
return SERVICES.map(s => `VidZee (${s})`);
import { fetchJson, USER_AGENT } from '../utils/helpers.js';
const BASE_URL = 'https://core.vidzee.wtf';
const PLAYER_URL = 'https://player.vidzee.wtf';
const SERVICES = ['dcloud', 'ipcloud', 'tik', 'v6:Hindi', '3AHindi'];
const WASM_B64 = 'AGFzbQEAAAABPgtgAX8Bf2ACf38Bf2ABfwBgAn9/AGADf39/AGAAAGAEf39/fwBgBH9/f38Bf2ADf39/AX9gA39/fgBgAAF/Ag0BA2VudgVhYm9ydAAGAygnAgEAAAQBAAEDBAADBQAAAgMHAAEAAAQIAwEJAgIBBQAAAgEABQoBBQMBAAEGJgd/AUEAC38BQQALfwFBAAt/AUEAC38BQQALfwFBAAt/AEGA2wALB0gHB2RlY3J5cHQAJwVfX25ldwAGBV9fcGluACQHX191bnBpbgAiCV9fY29sbGVjdAAlC19fcnR0aV9iYXNlAwYGbWVtb3J5AgAIAR8MAqkBCogkJyUAIABFBEAPCyAAQRRrIgAQByMCRgRAIAAQECAAIwUjAkUQCgsLCgAgACABai0AAAsKACAAKAIEQXxxCw0AIABBFGsoAhBBAXYLDAAgACABaiACOgAAC1MBAX8gAEHs////A0sEQEHwDEGwDUH9AEEeEAAACyMARQRAEA0LIwAgAEEQahAaIgIgATYCDCACIAA2AhAgAiMBIwIQCiACEAsjA2okAyACQRRqCwoAIAAoAgRBA3ELKQAgACAAIAFBCHZqLQAAQQV0aiABQf8BcUEDdmotAAAgAUEHcXZBAXELwwEBBH8gASgCAEF8cSIDQYACSQR/IANBBHYFQR9B/P///wMgAyADQfz///8DTxsiA2drIgRBB2shAiADIARBBGt2QRBzCyEEIAEoAgghBSABKAIEIgMEQCADIAU2AggLIAUEQCAFIAM2AgQLIAEgACACQQR0IARqQQJ0aiIBKAJgRgRAIAEgBTYCYCAFRQRAIAAgAkECdGoiASgCBEF+IAR3cSEDIAEgAzYCBCADRQRAIAAgACgCAEF+IAJ3cTYCAAsLCwspAQF/IAEoAgghAyAAIAEgAnI2AgQgACADNgIIIAMgABARIAEgADYCCAsNACAAKAIAQXxxQQRqC7wCAQV/IAEoAgAhAyABQQRqIAEoAgBBfHFqIgQoAgAiAkEBcQRAIAAgBBAJIAEgA0EEaiACQXxxaiIDNgIAIAFBBGogASgCAEF8cWoiBCgCACECCyADQQJxBEAgAUEEaygCACIBKAIAIQYgACABEAkgASAGQQRqIANBfHFqIgM2AgALIAQgAkECcjYCACAEQQRrIAE2AgAgACADQXxxIgJBgAJJBH8gAkEEdgVBH0H8////AyACIAJB/P///wNPGyICZ2siA0EHayEFIAIgA0EEa3ZBEHMLIgIgBUEEdGpBAnRqKAJgIQMgAUEANgIEIAEgAzYCCCADBEAgAyABNgIECyAAIAVBBHQgAmpBAnRqIAE2AmAgACAAKAIAQQEgBXRyNgIAIAAgBUECdGoiACAAKAIEQQEgAnRyNgIEC5cBAQJ/PwAiAEEATAR/QQEgAGtAAEEASAVBAAsEQAALQbDbAEEANgIAQdDnAEEANgIAA0AgAUEXSQRAIAFBAnRBsNsAakEANgIEQQAhAANAIABBEEkEQCABQQR0IABqQQJ0QbDbAGkBADYCYCAAQQFqIQAMAQsLIAFBAWohAQwBCwtBsNsAQdTnAD8ArEIQhhAbQbDbACQACyYBAX8gAEEEayEBIABBD3FBASAAGwR/QQEFIAEoAgBBAXELGiABCxIAIAAgADYCBCAAIAA2AgggAAsnAQF/IAAQAyIBRQRAIAAoAggaDwsgASAAKAIIIgA2AgggACABEBELEgAgACABIAAoAgRBA3FyNgIEC0kBAX8gACABQQF0aiEBA0AgAyIAQQFrIQMgAARAIAEvAQAiACACLwEAIgRHBEAgACAEaw8LIAFBAmohASACQQJqIQIMAQsLQQALbgECf0EMQQYQBiIBRQRAQQxBAxAGIQELIAFBADYCACABQQA2AgQgAUEANgIIIABB/P///wNLBEBB4NcAQZDYAEETQTkQAAALIABBARAGIgJBACAA/AsAIAEgAjYCACABIAI2AgQgASAANgIIIAELjgEBAn8gAUGAAkkEfyABQQR2BUEfIAEQFSIBZ2siA0EHayECIAEgA0EEa3ZBEHMLIgEgACACQQJ0aigCBEF/IAF0cSIBBH8gACABaCACQQR0akECdGooAmAFIAAoAgBBfyACQQFqdHEiAQR/IAAgACABaCIAQQJ0aigCBGggAEEEdGpBAnRqKAJgBUEACwsLHQAgAEEBQRsgAGdrdGpBAWsgACAAQf7///8BSRsLLwAgAEH8////A0sEQEHwDEHwDUHNA0EdEAAAC0EMIABBE2pBcHFBBGsgAEEMTRsLZwECfyABKAIAIgNBfHEgAmsiBEEQTwRAIAEgAiADQQJxcjYCACABQQRqIAJqIgEgBEEEa0EBcjYCACAAIAEQDAUgASADQX5xNgIAIAFBBGogASgCAEF8cWoiACAAKAIAQX1xNgIACwswACAAIAIQGiICQQRqIAFBBGogASgCAEF8cfwKAAAgAUGk2wBPBEAgACABEBkLIAILFQAgASABKAIAQQFyNgIAIAAgARAMC48BAQJ/IAAgARAWIgIQFCIBRQRAQQQgACgCoAw/ACIBQRB0QQRrR3QgAhAVIAIgAkGAAk8bakH//wNqQYCAfHFBEHYhAyABIAMgASADShtAAEEASARAIANAAEEASARAAAsLIAAgAUEQdD8ArEIQhhAbIAAgAhAUIQELIAEoAgAaIAAgARAJIAAgASACEBcgAQuGAQEDfyABQRNqQXBxQQRrIQEgACgCoAwiAwRAIAFBEGsiBSADRgRAIAMoAgAhBCAFIQELCyACp0FwcSABayIDQRRJBEAPCyABIARBAnEgA0EIayIDQQFycjYCACABQQA2AgQgAUEANgIIIAFBBGogA2oiA0ECNgIAIAAgAzYCoAwgACABEAwLhQEBA38CQAJAAkACQAJAAkACQAJAAkAgAEEIaygCAA4IAAECAwQFBgcICw8LDwsPCyAAEB0PCyAAKAIAEAEPCyAAKAIEIgEgACgCDEECdGohAgNAIAEgAkkEQCABKAIAIgMEQCADEAELIAFBBGohAQwBCwsgACgCABABDwsgABAdDwsPCwALCQAgACgCABABC0MAIAEgACgCDE8EQEHw1QBBsNYAQfIAQSoQAAALIAAoAgQgAUECdGooAgAiAEUEQEHg1gBBsNYAQfYAQSgQAAALIAALGQBBoA4QDyQBQYDaABAPJARB4NoAEA8kBQv9AQEGfyAAIgFBCHYiAkHMzgBqLQAAIAJBlDJqLQAAQdYAbEGUMmogAEH/AXEiA0EDbmotAAAgA0EDcEECdEGAxwBqKAIAbEELdkEGcGpBAnRBjbrowserigCACICQf8BcSEAIAJBCHUhAgJAIABBAkkNACACQf8BcSEAIAJBCHYhAgNAIAAEQCAAQQF2IgYgAmpBAXRBzNIAaiIELQAAIgUgA0YEfyAELQABQQJ0QYzHAGooAgAiAkH/AXEhACACQQh1IQIgAEECSQ0DIAFBAWoPBSADIAVJBH8gBgUgAiAGaiECIAAgBmsLCyEADAELCyABDwsgASACQQAgAGtxag==';
let wasmInstanceCache = null;
async function getWasmInstance() {
if (wasmInstanceCache) return wasmInstanceCache;
try {
const wasmBuffer = Buffer.from(WASM_B64, 'base64');
const wasmModule = await WebAssembly.compile(wasmBuffer);
let memory;
let exports;
const env = {
abort() {
throw new Error('WASM abort');
},
};
const res = await WebAssembly.instantiate(wasmModule, { env });
exports = res.exports;
memory = exports.memory;
const refMap = new Map();
function pin(ptr) {
if (ptr) {
const count = refMap.get(ptr);
if (count) refMap.set(ptr, count + 1);
else refMap.set(exports.__pin(ptr), 1);
}
return ptr;
}
function unpin(ptr) {
if (ptr) {
const count = refMap.get(ptr);
if (count === 1) {
refMap.delete(ptr);
exports.__unpin(ptr);
} else if (count > 1) {
refMap.set(ptr, count - 1);
}
}
}
wasmInstanceCache = {
decrypt(encryptedBytes, keyStr) {
let dataPtr = 0;
let keyPtr = 0;
try {
const len = encryptedBytes.length;
const pinPtr = exports.__pin(exports.__new(len, 1)) >>> 0;
dataPtr = exports.__new(12, 6) >>> 0;
const view = new DataView(memory.buffer);
view.setUint32(dataPtr, pinPtr, true);
view.setUint32(dataPtr + 4, pinPtr, true);
view.setUint32(dataPtr + 8, len, true);
new Uint8Array(memory.buffer, pinPtr, len).set(encryptedBytes);
exports.__unpin(pinPtr);
pin(dataPtr);
const kLen = keyStr.length;
keyPtr = exports.__new(kLen << 1, 2) >>> 0;
const u16View = new Uint16Array(memory.buffer);
for (let i = 0; i < kLen; i++) {
u16View[(keyPtr >>> 1) + i] = keyStr.charCodeAt(i);
}
const resPtr = exports.decrypt(dataPtr, keyPtr) >>> 0;
if (!resPtr) return new Uint8Array(0);
const resView = new DataView(memory.buffer);
const outPtr = resView.getUint32(resPtr + 4, true);
const outLen = resView.getUint32(resPtr + 8, true);
return new Uint8Array(memory.buffer, outPtr, outLen).slice();
} finally {
unpin(dataPtr);
}
},
};
return wasmInstanceCache;
} catch {
return null;
}
}
async function requestEndpoint(url, headers) {
try {
return await fetchJson(url, {
headers,
signal: AbortSignal.timeout(6000),
});
} catch {
return null;
}
}
async function resolveServiceStream(service, isMovie, id, s, e, headers) {
const directEndpoint = isMovie
? `${BASE_URL}/streams/movie/${id}?s=${service}`
: `${BASE_URL}/streams/tv/${id}/${s || 1}/${e || 1}?s=${service}`;
let res = await requestEndpoint(directEndpoint, headers);
if (!res || !res.url) {
const encEndpoint = `${directEndpoint}&e=1`;
const encRes = await requestEndpoint(encEndpoint, headers);
if (encRes && encRes.c) {
const instance = await getWasmInstance();
if (instance) {
const encryptedBytes = new Uint8Array(Buffer.from(encRes.c, 'base64'));
const decryptedBytes = instance.decrypt(encryptedBytes, 'player.vidzee.wtf');
if (decryptedBytes.length > 0) {
const str = new TextDecoder().decode(decryptedBytes);
try {
res = JSON.parse(str);
} catch { }
}
}
}
}
if (res && res.url) {
const format = res.url.includes('.m3u8') ? 'hls' : 'mp4';
return {
url: res.url,
server: `VidZee (${service})`,
quality: res.language || 'Auto',
type: format,
headers: {
'User-Agent': USER_AGENT,
Referer: `${PLAYER_URL}/`,
Origin: PLAYER_URL,
...(res.headers || {}),
},
};
}
return null;
}
export async function getStream(args) {
const { id, s, e, server, clientIP } = args || {};
const isTv = s != null && e != null;
const isMovie = !isTv;
const headers = {
'User-Agent': USER_AGENT,
Accept: 'application/json, text/plain, */*',
Referer: `${PLAYER_URL}/`,
Origin: PLAYER_URL,
...(clientIP && { 'X-Forwarded-For': clientIP }),
};
let targets = SERVICES;
if (server && server !== 'all') {
const clean = server.toLowerCase().replace('vidzee (', '').replace(')', '').replace('vidzee', '').trim();
targets = SERVICES.filter(srv => srv.toLowerCase().includes(clean));
if (!targets.length) targets = SERVICES;
}
const settled = await Promise.allSettled(
targets.map(service => resolveServiceStream(service, isMovie, id, s, e, headers))
);
const allUrls = [];
for (const r of settled) {
if (r.status === 'fulfilled' && r.value) {
allUrls.push(r.value);
}
}
if (!allUrls.length) return null;
return {
url: allUrls[0].url,
headers: allUrls[0].headers,
allUrls,
};
}
export async function getSources() {
return SERVICES.map(s => `VidZee (${s})`);
}

View File

@@ -1,114 +1,124 @@
import { fetchText, USER_AGENT } from '../utils/helpers.js';
const BASE_URL = 'https://vixsrc.to';
const HEADERS = {
'User-Agent': USER_AGENT,
'Accept': 'application/json, text/javascript, */*; q=0.01',
'Accept-Language': 'en-US,en;q=0.9',
'Referer': `${BASE_URL}/`,
'Origin': BASE_URL
};
function extract(regex, text) {
const m = text?.match(regex);
if (!m) return null;
let val = m[1];
val = val.replace(/\\u0026/gi, '&');
val = val.replace(/\\\//g, '/');
val = val.replace(/\\/g, '');
return val;
}
export async function getStream({ id, s, e, absoluteBase }) {
try {
const apiUrl = s && e
? `${BASE_URL}/api/tv/${id}/${s}/${e}`
: `${BASE_URL}/api/movie/${id}`;
const apiProxy = absoluteBase && absoluteBase !== '/'
? `${absoluteBase.replace(/^https:\/\/(localhost|127\.0\.0\.1)/, 'http://$1')}/api?url=${encodeURIComponent(apiUrl)}&proxyHeaders=${encodeURIComponent(JSON.stringify(HEADERS))}`
: apiUrl;
const apiText = await fetchText(apiProxy, { headers: HEADERS }).catch(() => null);
const apiData = JSON.parse(apiText || '{}');
if (!apiData.src) return null;
const embedUrl = apiData.src.startsWith('http') ? apiData.src : `${BASE_URL}${apiData.src}`;
const embedProxy = absoluteBase && absoluteBase !== '/'
? `${absoluteBase.replace(/^https:\/\/(localhost|127\.0\.0\.1)/, 'http://$1')}/api?url=${encodeURIComponent(embedUrl)}&proxyHeaders=${encodeURIComponent(JSON.stringify(HEADERS))}`
: embedUrl;
const html = await fetchText(embedProxy, { headers: HEADERS }).catch(() => null);
if (!html) return null;
const token = extract(/token["']\s*:\s*["']([^"']+)/, html);
const expires = extract(/expires["']\s*:\s*["']([^"']+)/, html);
let playlist = extract(/url["']\s*:\s*["']([^"']+)/, html);
if (!token || !expires || !playlist) return null;
if (playlist.startsWith('/')) {
playlist = `${BASE_URL}${playlist}`;
}
const defaultLang = extract(/lang(?:uage)?["']\s*:\s*["']([a-z]{2,5})/i, html) || 'en';
const rawMasterUrl = `${playlist}${playlist.includes('?') ? '&' : '?'}token=${token}&expires=${expires}&h=1&lang=${defaultLang}`;
const masterProxy = absoluteBase && absoluteBase !== '/'
? `${absoluteBase.replace(/^https:\/\/(localhost|127\.0\.0\.1)/, 'http://$1')}/api?url=${encodeURIComponent(rawMasterUrl)}&proxyHeaders=${encodeURIComponent(JSON.stringify({ ...HEADERS, Referer: embedUrl }))}`
: rawMasterUrl;
const playlistText = await fetchText(masterProxy, { headers: { ...HEADERS, 'Referer': embedUrl } }).catch(() => null);
if (!playlistText?.includes('#EXTM3U')) return null;
const lines = playlistText.split('\n');
const audioTracks = [];
const seen = new Set();
for (const line of lines) {
if (line.startsWith('#EXT-X-MEDIA') && line.includes('TYPE=AUDIO')) {
const name = line.match(/NAME=["']([^"']+)["']/)?.[1] || 'Default';
const rendition = line.match(/rendition=([a-zA-Z0-9_-]+)/)?.[1]
|| line.match(/LANGUAGE=["']([^"']+)["']/)?.[1]
|| name;
const key = rendition.toLowerCase();
if (!seen.has(key)) {
seen.add(key);
audioTracks.push({ name, lang: rendition });
}
}
}
const allUrls = [];
if (audioTracks.length > 0) {
for (const track of audioTracks) {
const streamUrl = `${playlist}${playlist.includes('?') ? '&' : '?'}token=${token}&expires=${expires}&h=1&lang=${track.lang}`;
allUrls.push({
url: streamUrl,
server: `VixSrc (${track.name})`,
skipProxy: true,
headers: {
...HEADERS,
'Referer': `${BASE_URL}/`
}
});
}
} else {
allUrls.push({
url: rawMasterUrl,
server: 'VixSrc',
skipProxy: true,
headers: {
...HEADERS,
'Referer': `${BASE_URL}/`
}
});
}
return { allUrls };
} catch (err) {
return null;
}
import { fetchText, USER_AGENT } from '../utils/helpers.js';
const BASE_URL = 'https://vixsrc.to';
const HEADERS = {
'User-Agent': USER_AGENT,
Accept: 'application/json, text/javascript, */*; q=0.01',
'Accept-Language': 'en-US,en;q=0.9',
Referer: `${BASE_URL}/`,
Origin: BASE_URL,
};
function extract(regex, text) {
const m = text?.match(regex);
if (!m) return null;
let val = m[1];
val = val.replace(/\\u0026/gi, '&');
val = val.replace(/\\\//g, '/');
val = val.replace(/\\/g, '');
return val;
}
export async function getStream(args) {
const { id, s, e, absoluteBase } = args || {};
const isTv = s != null && e != null;
try {
const apiUrl = isTv
? `${BASE_URL}/api/tv/${id}/${s}/${e}`
: `${BASE_URL}/api/movie/${id}`;
const apiProxy = absoluteBase && absoluteBase !== '/'
? `${absoluteBase.replace(/^https:\/\/(localhost|127\.0\.0\.1)/, 'http://$1')}/api?url=${encodeURIComponent(apiUrl)}&proxyHeaders=${encodeURIComponent(JSON.stringify(HEADERS))}`
: apiUrl;
const apiText = await fetchText(apiProxy, { headers: HEADERS }).catch(() => null);
const apiData = JSON.parse(apiText || '{}');
if (!apiData.src) return null;
const embedUrl = apiData.src.startsWith('http') ? apiData.src : `${BASE_URL}${apiData.src}`;
const embedProxy = absoluteBase && absoluteBase !== '/'
? `${absoluteBase.replace(/^https:\/\/(localhost|127\.0\.0\.1)/, 'http://$1')}/api?url=${encodeURIComponent(embedUrl)}&proxyHeaders=${encodeURIComponent(JSON.stringify(HEADERS))}`
: embedUrl;
const html = await fetchText(embedProxy, { headers: HEADERS }).catch(() => null);
if (!html) return null;
const token = extract(/token["']\s*:\s*["']([^"']+)/, html);
const expires = extract(/expires["']\s*:\s*["']([^"']+)/, html);
let playlist = extract(/url["']\s*:\s*["']([^"']+)/, html);
if (!token || !expires || !playlist) return null;
if (playlist.startsWith('/')) {
playlist = `${BASE_URL}${playlist}`;
}
const defaultLang = extract(/lang(?:uage)?["']\s*:\s*["']([a-z]{2,5})/i, html) || 'en';
const rawMasterUrl = `${playlist}${playlist.includes('?') ? '&' : '?'}token=${token}&expires=${expires}&h=1&lang=${defaultLang}`;
const masterProxy = absoluteBase && absoluteBase !== '/'
? `${absoluteBase.replace(/^https:\/\/(localhost|127\.0\.0\.1)/, 'http://$1')}/api?url=${encodeURIComponent(rawMasterUrl)}&proxyHeaders=${encodeURIComponent(JSON.stringify({ ...HEADERS, Referer: embedUrl }))}`
: rawMasterUrl;
const playlistText = await fetchText(masterProxy, { headers: { ...HEADERS, Referer: embedUrl } }).catch(() => null);
if (!playlistText?.includes('#EXTM3U')) return null;
const lines = playlistText.split('\n');
const audioTracks = [];
const seen = new Set();
for (const line of lines) {
if (line.startsWith('#EXT-X-MEDIA') && line.includes('TYPE=AUDIO')) {
const name = line.match(/NAME=["']([^"']+)["']/)?.[1] || 'Default';
const rendition = line.match(/rendition=([a-zA-Z0-9_-]+)/)?.[1]
|| line.match(/LANGUAGE=["']([^"']+)["']/)?.[1]
|| name;
const key = rendition.toLowerCase();
if (!seen.has(key)) {
seen.add(key);
audioTracks.push({ name, lang: rendition });
}
}
}
const allUrls = [];
if (audioTracks.length > 0) {
for (const track of audioTracks) {
const streamUrl = `${playlist}${playlist.includes('?') ? '&' : '?'}token=${token}&expires=${expires}&h=1&lang=${track.lang}`;
allUrls.push({
url: streamUrl,
server: `VixSrc (${track.name})`,
quality: 'Auto',
type: 'hls',
skipProxy: true,
headers: {
...HEADERS,
Referer: `${BASE_URL}/`,
},
});
}
} else {
allUrls.push({
url: rawMasterUrl,
server: 'VixSrc',
quality: 'Auto',
type: 'hls',
skipProxy: true,
headers: {
...HEADERS,
Referer: `${BASE_URL}/`,
},
});
}
return {
url: allUrls[0].url,
headers: allUrls[0].headers,
allUrls,
};
} catch {
return null;
}
}

View File

@@ -1,8 +1,13 @@
import { fetchJson, fetchText } from '../utils/helpers.js';
const API_URL = "https://raw.githubusercontent.com/Watchout2025/api/refs/heads/main/hls/";
const API_URL = 'https://raw.githubusercontent.com/Watchout2025/api/refs/heads/main/hls/';
const HEADERS = {
Origin: 'https://vidout.pages.dev',
Referer: 'https://vidout.pages.dev',
};
export async function getStream({ id, s, e }) {
export async function getStream(args) {
const { id, s, e } = args || {};
const isTv = s != null && e != null;
const type = isTv ? 'tv' : 'movie';
@@ -10,37 +15,46 @@ export async function getStream({ id, s, e }) {
? `${API_URL}tv/${id}/S${s}.json`
: `${API_URL}movie/${id}`;
const headers = {
'Origin': 'https://vidout.pages.dev',
'Referer': 'https://vidout.pages.dev'
};
if (type === 'movie') {
const data = await fetchText(targetUrl).catch(() => null);
if (!data || !data.includes('://')) return null;
return {
url: data.trim(),
headers,
allUrls: [{
url: data.trim(),
server: 'Watchout - Direct',
headers
}]
};
} else {
const data = await fetchJson(targetUrl).catch(() => null);
const streamUrl = data?.[String(e)] || null;
if (!streamUrl) return null;
const streamUrl = data.trim();
const isHls = streamUrl.includes('.m3u8') || streamUrl.includes('.txt') || streamUrl.includes('master') || !streamUrl.includes('.mp4');
const allUrls = [{
url: streamUrl,
server: 'Watchout - Direct',
quality: 'Auto',
type: isHls ? 'hls' : 'mp4',
headers: HEADERS,
}];
return {
url: streamUrl,
headers,
allUrls: [{
url: streamUrl,
server: 'Watchout - Direct',
headers
}]
headers: HEADERS,
allUrls,
};
} else {
let data = await fetchJson(targetUrl).catch(() => null);
if (!data) {
data = await fetchJson(`${API_URL}tv/${id}/s${s}.json`).catch(() => null);
}
const streamUrl = data?.[String(e)] || null;
if (!streamUrl) return null;
const isHls = streamUrl.includes('.m3u8') || streamUrl.includes('.txt') || streamUrl.includes('master') || !streamUrl.includes('.mp4');
const allUrls = [{
url: streamUrl,
server: 'Watchout - Direct',
quality: 'Auto',
type: isHls ? 'hls' : 'mp4',
headers: HEADERS,
}];
return {
url: streamUrl,
headers: HEADERS,
allUrls,
};
}
}
}

View File

@@ -1,3 +1,5 @@
import { getTmdbMetadata, resolveTmdbKey } from '../utils/helpers.js';
const BASE_URL = 'https://www.films365.org';
const HEADERS = {
Authorization: 'Bearer 79a02956be35835728a044b11e2ae793149d45fb2c89cb6d029ec01aac19bfdb',
@@ -5,125 +7,122 @@ const HEADERS = {
'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);
export async function getStream(args) {
const { id, s, e, sdk } = args || {};
const isTv = s != null && e != null;
const targetType = isTv ? 'tv' : 'movie';
const apiKey = resolveTmdbKey(args, sdk);
const title = await resolveTitle(sdk, id, isTv);
const meta = await getTmdbMetadata(id, isTv, apiKey);
const title = meta?.title;
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),
});
try {
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;
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 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;
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;
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, '');
const itemTitle = String(item?.title || '');
const cleanItemTitle = itemTitle.toLowerCase().replace(/[^a-z0-9]/g, '');
if (cleanItemTitle === cleanSearchTitle || cleanItemTitle.includes(cleanSearchTitle)) {
matchedItem = item;
break;
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'],
},
});
if (!matchedItem) {
matchedItem = items.find(i => String(i?.type || '').toLowerCase() === targetType) || items[0];
}
} 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);
const itemId = matchedItem?.id || matchedItem?.tmdbId;
if (!itemId) return null;
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'],
},
});
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}`,
server: `X-Downloader${langSuffix}`,
quality: 'Auto',
type: streamUrl.includes('.m3u8') ? 'hls' : 'mp4',
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}`,
server: `X-Downloader (S${seasonNum}E${episodeNum})${langSuffix}`,
quality: 'Auto',
type: streamUrl.includes('.m3u8') ? 'hls' : 'mp4',
headers: {
'User-Agent': HEADERS['User-Agent'],
},
});
}
}
}
}
if (allUrls.length === 0) return null;
return {
url: allUrls[0].url,
headers: allUrls[0].headers,
allUrls,
};
} catch {
return null;
}
if (allUrls.length === 0) return null;
return {
url: allUrls[0].url,
allUrls,
headers: allUrls[0].headers,
};
}

View File

@@ -1,17 +1,22 @@
import { fetchJson } from '../utils/helpers.js';
export async function getStream({ id, s, e }) {
const BASE_URL = 'https://yapgrid.com';
const EMBED_BASE = 'https://embedwave.cc';
export async function getStream(args) {
const { id, s, e } = args || {};
const isTv = s != null && e != null;
const targetEmbedUrl = isTv
? `https://embedwave.cc/embed/tv/${id}/${s}/${e}`
: `https://embedwave.cc/embed/movie/${id}`;
? `${EMBED_BASE}/embed/tv/${id}/${s}/${e}`
: `${EMBED_BASE}/embed/movie/${id}`;
const headers = {
'Accept': 'application/json',
Accept: 'application/json',
'Content-Type': 'application/json',
'Origin': 'https://yapgrid.com',
'Referer': isTv ? `https://yapgrid.com/embed/tv/${id}/${s}/${e}` : `https://yapgrid.com/embed/movie/${id}`,
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36'
Origin: BASE_URL,
Referer: isTv ? `${BASE_URL}/embed/tv/${id}/${s}/${e}` : `${BASE_URL}/embed/movie/${id}`,
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36',
};
const payload = {
@@ -20,13 +25,13 @@ export async function getStream({ id, s, e }) {
strictSource: true,
waitMs: 5000,
nativeHls: false,
cacheOnly: true
cacheOnly: true,
};
const res = await fetchJson('https://yapgrid.com/player/api/extract', {
const res = await fetchJson(`${BASE_URL}/player/api/extract`, {
method: 'POST',
headers,
body: JSON.stringify(payload)
body: JSON.stringify(payload),
}).catch(() => null);
if (!res?.success || !Array.isArray(res.found) || !res.found.length) return null;
@@ -38,18 +43,19 @@ export async function getStream({ id, s, e }) {
let streamUrl = item.url;
if (streamUrl.startsWith('/')) {
streamUrl = `https://yapgrid.com${streamUrl}`;
streamUrl = `${BASE_URL}${streamUrl}`;
}
allUrls.push({
url: streamUrl,
server: `YapGrid - ${item.nxshaProvider || item.provider || 'Server'}`,
quality: 'Auto',
type: item.type === 'master' ? 'hls' : (streamUrl.includes('.m3u8') ? 'hls' : 'mp4'),
headers: {
Referer: 'https://yapgrid.com/',
Origin: 'https://yapgrid.com',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36'
}
Referer: `${BASE_URL}/`,
Origin: BASE_URL,
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
},
});
}
@@ -58,6 +64,6 @@ export async function getStream({ id, s, e }) {
return {
url: allUrls[0].url,
headers: allUrls[0].headers,
allUrls
allUrls,
};
}

View File

@@ -1,13 +1,17 @@
import { fetchJson } from '../utils/helpers.js';
export async function getStream({ id, s, e }) {
const BASE_URL = 'https://yastream.tamthai.de';
export async function getStream(args) {
const { id, s, e } = args || {};
const isTv = s != null && e != null;
const path = isTv
? `/stream/series/tmdb:${id}:${s}:${e}.json`
: `/stream/movie/tmdb:${id}.json`;
const url = `https://yastream.tamthai.de${path}`;
const res = await fetchJson(url);
const url = `${BASE_URL}${path}`;
const res = await fetchJson(url).catch(() => null);
if (!res || !Array.isArray(res.streams) || !res.streams.length) return null;
const allUrls = res.streams.map(item => {
@@ -15,8 +19,10 @@ export async function getStream({ id, s, e }) {
return {
url: item.url,
server: `YaStream - ${item.name?.replace(/\n/g, ' ') || 'Server'}`,
quality: 'Auto',
type: item.url.includes('.m3u8') ? 'hls' : 'mp4',
headers: item.behaviorHints?.proxyHeaders?.request || undefined,
skipProxy: item.behaviorHints?.notWebReady === false
skipProxy: item.behaviorHints?.notWebReady === false,
};
}).filter(Boolean);
@@ -25,6 +31,6 @@ export async function getStream({ id, s, e }) {
return {
url: allUrls[0].url,
headers: allUrls[0].headers,
allUrls
allUrls,
};
}

View File

@@ -1,122 +1,105 @@
import { getUA } from './utils/helpers.js';
const SUBTITLE_BASES = [
'https://sub.vdrk.site/v1',
'https://sub.vdrk.site/v2',
'https://opensubtitles-v3.strem.io'
];
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}`;
}
}
export async function fetchSubtitles(paths = []) {
try {
const results = await Promise.all(
paths.map(async ({ base, path }) => {
try {
const res = await fetch(`${base}${path}`, {
headers: { 'User-Agent': getUA() },
signal: AbortSignal.timeout(6000),
});
if (!res.ok) {
res.body?.cancel();
return [];
}
const data = await res.json();
if (base.includes('/v2')) {
return Array.isArray(data)
? data.map(x => ({
label: x.label,
file: x.file || x.url,
type: 'vtt',
source: 'v2'
}))
: [];
}
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 [];
return Object.entries(data.subtitles)
.map(([language, sub]) => {
if (!sub?.subtitle_link) return null;
const ext = sub.subtitle_link.split('.').pop()?.toLowerCase();
return {
label: sub.subtitle_name || language,
file: sub.subtitle_link,
type: ext === 'vtt' ? 'vtt' : 'srt',
source: 'febbox'
};
})
.filter(Boolean);
}
const v1 = Array.isArray(data) ? data : [];
return v1.map(x => ({
label: x.label,
file: x.file || x.url,
type: 'vtt',
source: 'v1'
}));
} catch {
return [];
}
})
);
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: `/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: `/subtitles/series/${imdbId}:${season}:${episode}.json` }
];
}
export { SUBTITLE_BASES };
import { getUA, resolveImdbId } from './utils/helpers.js';
const SUBTITLE_BASES = [
'https://sub.vdrk.site/v1',
'https://sub.vdrk.site/v2',
'https://opensubtitles-v3.strem.io'
];
export async function fetchSubtitles(paths = []) {
try {
const results = await Promise.all(
paths.map(async ({ base, path }) => {
try {
const res = await fetch(`${base}${path}`, {
headers: { 'User-Agent': getUA() },
signal: AbortSignal.timeout(6000),
});
if (!res.ok) {
res.body?.cancel();
return [];
}
const data = await res.json();
if (base.includes('/v2')) {
return Array.isArray(data)
? data.map(x => ({
label: x.label,
file: x.file || x.url,
type: 'vtt',
source: 'v2'
}))
: [];
}
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 [];
return Object.entries(data.subtitles)
.map(([language, sub]) => {
if (!sub?.subtitle_link) return null;
const ext = sub.subtitle_link.split('.').pop()?.toLowerCase();
return {
label: sub.subtitle_name || language,
file: sub.subtitle_link,
type: ext === 'vtt' ? 'vtt' : 'srt',
source: 'febbox'
};
})
.filter(Boolean);
}
const v1 = Array.isArray(data) ? data : [];
return v1.map(x => ({
label: x.label,
file: x.file || x.url,
type: 'vtt',
source: 'v1'
}));
} catch {
return [];
}
})
);
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: `/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: `/subtitles/series/${imdbId}:${season}:${episode}.json` }
];
}
export { SUBTITLE_BASES };

View File

@@ -1,247 +1,586 @@
import crypto from 'node:crypto';
export const UA_LIST = [
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15',
'Mozilla/5.0 (X11; Linux x86_64; rv:125.0) Gecko/20100101 Firefox/125.0',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0',
];
export const getUA = () => UA_LIST[Math.floor(Math.random() * UA_LIST.length)];
export const USER_AGENT = UA_LIST[0];
let sharedDcKey = null;
export function decryptPartner(responseText, keyEnv) {
try {
const sep = responseText.indexOf(':');
if (sep === -1) return null;
const iv = Buffer.from(responseText.slice(0, sep), 'base64');
const encrypted = responseText.slice(sep + 1);
if (!sharedDcKey) sharedDcKey = crypto.createHash('sha256').update(keyEnv || '').digest();
const decipher = crypto.createDecipheriv('aes-256-cbc', sharedDcKey, iv);
return JSON.parse(decipher.update(encrypted, 'base64', 'utf8') + decipher.final('utf8'));
} catch {
return null;
}
}
export async function fetchJson(url, options) {
const res = await fetch(url, options);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
}
export async function fetchText(url, options) {
const res = await fetch(url, options);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.text();
}
const tmdbInfoCache = new Map();
const anilistCache = new Map();
const tmdbValidationCache = new Map();
function pruneHelperCache(cache) {
const now = Date.now();
for (const [k, v] of cache) if (now - v.ts >= v.ttl) cache.delete(k);
if (cache.size > 3000) {
const overflow = cache.size - 3000;
const it = cache.keys();
for (let i = 0; i < overflow; i++) cache.delete(it.next().value);
}
}
export async function resolveImdbId(id, isTv, tmdbApiKey) {
if (typeof id === 'string' && id.startsWith('tt')) return id;
const apiKey = tmdbApiKey || 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}`;
}
}
setInterval(() => {
pruneHelperCache(tmdbValidationCache);
pruneHelperCache(tmdbInfoCache);
pruneHelperCache(anilistCache);
}, 60000).unref();
function cacheGet(cache, key) {
const entry = cache.get(key);
if (!entry) return undefined;
if (Date.now() - entry.ts >= entry.ttl) { cache.delete(key); return undefined; }
return entry.val;
}
function cacheSet(cache, key, val, ttl) { cache.set(key, { val, ts: Date.now(), ttl }); }
export async function createStreamArgs(source, sdk, id, s, e, clientIP = null) {
const audio = /dub$/.test(source.key) ? 'dub' : 'sub';
return {
id: id,
s: s,
e: e,
clientIP: clientIP,
absoluteBase: '/',
audio,
sdk,
config: source,
}
}
export async function validateTmdbId(tmdbApiKey, tmdbId, mediaType = 'movie') {
let apiKey = tmdbApiKey;
let id = tmdbId;
let type = mediaType;
if (id === 'movie' || id === 'tv' || !id) {
if (apiKey && apiKey !== 'movie' && apiKey !== 'tv') {
type = id || 'movie';
id = apiKey;
apiKey = process.env.TMDB_API_KEY;
}
}
if (!apiKey) {
apiKey = process.env.TMDB_API_KEY;
}
const key = `${id}-${type}`;
const cached = cacheGet(tmdbValidationCache, key);
if (cached !== undefined) return cached;
if (!apiKey) return { valid: false, error: 'TMDB API key not configured' };
try {
const res = await fetch(`https://api.themoviedb.org/3/${type}/${id}?api_key=${apiKey}`, { signal: AbortSignal.timeout(5000) });
if (res.ok) {
const data = await res.json();
const isValid = !!(data?.id && (data?.title || data?.name));
const result = { valid: isValid, error: isValid ? null : 'Invalid TMDB ID' };
cacheSet(tmdbValidationCache, key, result, 300000);
return result;
}
const result = { valid: false, error: `TMDB API error: ${res.status}` };
cacheSet(tmdbValidationCache, key, result, 60000);
return result;
} catch (error) {
const result = { valid: false, error: `Failed to validate TMDB ID: ${error.message}` };
cacheSet(tmdbValidationCache, key, result, 60000);
return result;
}
}
export async function getTmdbInfo(tmdbApiKey, tmdbId, mediaType, season) {
let apiKey = tmdbApiKey;
let id = tmdbId;
let type = mediaType;
let s = season;
if (apiKey === 'movie' || apiKey === 'tv') {
type = apiKey;
id = tmdbId;
apiKey = process.env.TMDB_API_KEY;
} else if (id === 'movie' || id === 'tv') {
type = id;
id = apiKey;
apiKey = process.env.TMDB_API_KEY;
}
if (!apiKey) {
apiKey = process.env.TMDB_API_KEY;
}
const key = `${id}-${type}-${s || ''}`;
const cached = cacheGet(tmdbInfoCache, key);
if (cached !== undefined) return cached;
if (!apiKey) return { isAnime: false, titles: [], year: null, imdbId: null };
try {
const [mainRes, seasonRes] = await Promise.all([
fetch(`https://api.themoviedb.org/3/${type}/${id}?api_key=${apiKey}&append_to_response=external_ids`, { signal: AbortSignal.timeout(5000) }),
s && type === 'tv' ? fetch(`https://api.themoviedb.org/3/tv/${id}/season/${s}?api_key=${apiKey}`, { signal: AbortSignal.timeout(5000) }) : Promise.resolve(null),
]);
let mainData = null, seasonData = null;
if (mainRes.ok) mainData = await mainRes.json(); else mainRes.body?.cancel();
if (seasonRes) { if (seasonRes.ok) seasonData = await seasonRes.json(); else seasonRes.body?.cancel(); }
const genres = mainData?.genres || [];
const isAnime = genres.some(g => g.id === 16) && ((mainData?.origin_country || []).includes('JP') || mainData?.original_language === 'ja');
const titles = [];
if (seasonData?.name) titles.push(seasonData.name);
const titleText = mainData?.title || mainData?.name || '';
const ot = mainData?.original_title || mainData?.original_name || '';
if (titleText) titles.push(titleText);
if (ot && ot !== titleText) titles.push(ot);
const dateStr = seasonData?.air_date || mainData?.release_date || mainData?.first_air_date || '';
const result = { isAnime, titles: [...new Set(titles.filter(Boolean))], year: dateStr ? parseInt(dateStr.slice(0, 4), 10) : null, imdbId: mainData?.imdb_id || mainData?.external_ids?.imdb_id || null };
cacheSet(tmdbInfoCache, key, result, 600000);
return result;
} catch { return { isAnime: false, titles: [], year: null, imdbId: null }; }
}
export async function tmdbToAnilist(tmdbId, mediaType, season, titles = [], year = null) {
const key = `${tmdbId}-${mediaType}-${season || ''}`;
const cached = cacheGet(anilistCache, key);
if (cached !== undefined) return cached;
try {
const res = await fetch(`https://api.ani.zip/mappings?tmdb_id=${tmdbId}&type=${mediaType}&season=${season || 1}`, { signal: AbortSignal.timeout(6000) });
if (res.ok) {
const id = (await res.json())?.mappings?.[0]?.anilist_id;
if (id) { cacheSet(anilistCache, key, id, 600000); return id; }
} else res.body?.cancel();
} catch { }
if (!titles.length) return null;
const query = `query ($s: String) { Page(page:1,perPage:10) { media(search:$s,type:ANIME) { id title { romaji english native } startDate { year } format } } }`;
let bestId = null, bestScore = -1;
for (const searchTitle of titles) {
try {
const res = await fetch('https://graphql.anilist.co', { method: 'POST', headers: { 'Content-Type': 'application/json', Accept: 'application/json' }, body: JSON.stringify({ query, variables: { s: searchTitle } }), signal: AbortSignal.timeout(6000) });
if (!res.ok) { res.body?.cancel(); continue; }
for (const entry of (await res.json())?.data?.Page?.media || []) {
const entryTitles = [entry.title?.romaji, entry.title?.english, entry.title?.native].filter(Boolean).map(t => t.toLowerCase());
const sl = searchTitle.toLowerCase();
let score = 0;
if (entryTitles.some(t => t === sl)) score += 5;
else if (entryTitles.some(t => t.includes(sl) || sl.includes(t))) score += 3;
else if (entryTitles.some(t => { const w = sl.split(/\s+/).filter(x => x.length > 3); return w.length > 0 && w.every(x => t.includes(x)); })) score += 2;
else continue;
if (year && entry.startDate?.year) {
const diff = Math.abs(entry.startDate.year - year);
if (diff === 0) score += 3; else if (diff === 1) score += 1; else if (diff > 2) score -= 3;
}
if (mediaType === 'tv' && ['TV', 'TV_SHORT', 'ONA', 'OVA'].includes(entry.format)) score += 1;
if (mediaType === 'movie' && entry.format === 'MOVIE') score += 1;
if (score > bestScore) { bestScore = score; bestId = entry.id; }
}
if (bestScore >= 8) break;
} catch { }
}
if (bestId) cacheSet(anilistCache, key, bestId, 600000);
return bestId;
}
export async function imdbToTmdb(tmdbApiKey, imdbId, mediaType = 'movie') {
let apiKey = tmdbApiKey;
let id = imdbId;
let type = mediaType;
if (id === 'movie' || id === 'tv') {
type = id;
id = apiKey;
apiKey = process.env.TMDB_API_KEY;
}
if (!apiKey) {
apiKey = process.env.TMDB_API_KEY;
}
const key = `imdb-${id}`;
const cached = cacheGet(tmdbInfoCache, key);
if (cached !== undefined) return cached;
if (!apiKey) return null;
try {
const data = await fetchJson(`https://api.themoviedb.org/3/find/${id}?api_key=${apiKey}&external_source=imdb_id`, { signal: AbortSignal.timeout(5000) });
let result = null;
if (type === 'movie' && data.movie_results?.length) result = data.movie_results[0].id;
else if (type === 'tv' && data.tv_results?.length) result = data.tv_results[0].id;
if (result) cacheSet(tmdbInfoCache, key, result, 600000);
return result;
} catch { return null; }
}
import crypto from 'node:crypto';
export const UA_LIST = [
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15',
'Mozilla/5.0 (X11; Linux x86_64; rv:125.0) Gecko/20100101 Firefox/125.0',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0',
];
export const getUA = () => UA_LIST[Math.floor(Math.random() * UA_LIST.length)];
export const USER_AGENT = UA_LIST[0];
const DEFAULT_TMDB_KEY = '45dbdd5e714da212424bda0336167447';
let sharedDcKey = null;
const tmdbInfoCache = new Map();
const anilistCache = new Map();
const tmdbValidationCache = new Map();
const tmdbMetaCache = new Map();
function pruneCache(cache) {
const now = Date.now();
for (const [k, v] of cache) {
if (now - v.ts >= v.ttl) cache.delete(k);
}
if (cache.size > 3000) {
const overflow = cache.size - 3000;
const it = cache.keys();
for (let i = 0; i < overflow; i++) cache.delete(it.next().value);
}
}
setInterval(() => {
pruneCache(tmdbValidationCache);
pruneCache(tmdbInfoCache);
pruneCache(anilistCache);
pruneCache(tmdbMetaCache);
}, 60000).unref();
function cacheGet(cache, key) {
const entry = cache.get(key);
if (!entry) return undefined;
if (Date.now() - entry.ts >= entry.ttl) {
cache.delete(key);
return undefined;
}
return entry.val;
}
function cacheSet(cache, key, val, ttl) {
cache.set(key, { val, ts: Date.now(), ttl });
}
export function resolveTmdbKey(args = {}, sdk = null) {
return (
args?.tmdbApiKey ||
args?.sdk?.tmdbApiKey ||
sdk?.tmdbApiKey ||
process.env.TMDB_API_KEY ||
DEFAULT_TMDB_KEY
);
}
export function slugify(text) {
if (!text) return '';
return String(text)
.toLowerCase()
.normalize('NFKD')
.replace(/[\u0300-\u036f]/g, '')
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-|-$/g, '');
}
export function cleanTitle(text) {
if (!text) return '';
return String(text)
.toLowerCase()
.replace(/\(\d{4}\)/g, '')
.replace(/[^a-z0-9]/g, '')
.trim();
}
export 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]}`;
}
export function getRandomHex(size = 32) {
return crypto.randomBytes(size).toString('hex');
}
export async function fetchJson(url, options = {}) {
const res = await fetch(url, options);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
}
export async function fetchText(url, options = {}) {
const res = await fetch(url, options);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.text();
}
export function decryptPartner(responseText, keyEnv) {
try {
const sep = responseText.indexOf(':');
if (sep === -1) return null;
const iv = Buffer.from(responseText.slice(0, sep), 'base64');
const encrypted = responseText.slice(sep + 1);
if (!sharedDcKey) sharedDcKey = crypto.createHash('sha256').update(keyEnv || '').digest();
const decipher = crypto.createDecipheriv('aes-256-cbc', sharedDcKey, iv);
return JSON.parse(decipher.update(encrypted, 'base64', 'utf8') + decipher.final('utf8'));
} catch {
return null;
}
}
export async function getTmdbMetadata(id, isTv, apiKey = null) {
const key = apiKey || process.env.TMDB_API_KEY || DEFAULT_TMDB_KEY;
const type = isTv ? 'tv' : 'movie';
const cacheKey = `${id}-${type}-${key}`;
const cached = cacheGet(tmdbMetaCache, cacheKey);
if (cached !== undefined) return cached;
const fallback = {
title: '',
originalTitle: '',
titles: [],
year: null,
releaseDate: '',
imdbId: null,
isAnime: false,
runtime: 0,
seasons: [],
};
try {
const url = `https://api.themoviedb.org/3/${type}/${id}?api_key=${key}&append_to_response=external_ids,alternative_titles`;
const res = await fetch(url, { signal: AbortSignal.timeout(6000) });
if (!res.ok) {
cacheSet(tmdbMetaCache, cacheKey, fallback, 60000);
return fallback;
}
const data = await res.json();
const title = data.name || data.title || '';
const originalTitle = data.original_name || data.original_title || '';
const releaseDate = data.first_air_date || data.release_date || '';
const year = releaseDate ? parseInt(releaseDate.slice(0, 4), 10) : null;
const imdbId = data.imdb_id || data.external_ids?.imdb_id || null;
const runtime = isTv ? (data.episode_run_time?.[0] || 24) : (data.runtime || 90);
const titles = [];
if (title) titles.push(title);
if (originalTitle && !titles.includes(originalTitle)) titles.push(originalTitle);
const alts = data.alternative_titles?.titles || data.alternative_titles?.results || [];
for (const alt of alts) {
const altTitle = alt.title || alt.name;
if (altTitle && !titles.includes(altTitle)) titles.push(altTitle);
}
const genres = data.genres || [];
const isAnime = genres.some(g => g.id === 16) && ((data.origin_country || []).includes('JP') || data.original_language === 'ja');
const result = {
title,
originalTitle,
titles,
year,
releaseDate,
imdbId,
isAnime,
runtime,
seasons: Array.isArray(data.seasons) ? data.seasons : [],
};
cacheSet(tmdbMetaCache, cacheKey, result, 600000);
return result;
} catch {
cacheSet(tmdbMetaCache, cacheKey, fallback, 60000);
return fallback;
}
}
export async function resolveImdbId(id, isTv, tmdbApiKey = null) {
if (typeof id === 'string' && id.startsWith('tt')) return id;
const meta = await getTmdbMetadata(id, isTv, tmdbApiKey);
return meta?.imdbId || `tt${id}`;
}
export async function getTmdbInfo(arg1, arg2, arg3, arg4) {
let apiKey = null;
let id = null;
let type = null;
let season = null;
if (arg1 === 'movie' || arg1 === 'tv') {
type = arg1;
id = arg2;
season = arg3;
apiKey = arg4 || process.env.TMDB_API_KEY || DEFAULT_TMDB_KEY;
} else if (arg2 === 'movie' || arg2 === 'tv') {
if (typeof arg1 === 'string' && arg1.length > 20 && !/^\d+$/.test(arg1)) {
apiKey = arg1;
id = arg3;
type = arg2;
season = arg4;
} else {
id = arg1;
type = arg2;
season = arg3;
apiKey = arg4 || process.env.TMDB_API_KEY || DEFAULT_TMDB_KEY;
}
} else if (arg3 === 'movie' || arg3 === 'tv') {
apiKey = arg1;
id = arg2;
type = arg3;
season = arg4;
} else {
id = arg1;
type = arg2 || 'movie';
season = arg3;
apiKey = arg4 || process.env.TMDB_API_KEY || DEFAULT_TMDB_KEY;
}
if (!apiKey) apiKey = process.env.TMDB_API_KEY || DEFAULT_TMDB_KEY;
const isTv = type === 'tv';
const key = `${id}-${type}-${season || ''}-${apiKey}`;
const cached = cacheGet(tmdbInfoCache, key);
if (cached !== undefined) return cached;
try {
const meta = await getTmdbMetadata(id, isTv, apiKey);
let seasonTitle = null;
if (season && isTv) {
try {
const seasonRes = await fetch(`https://api.themoviedb.org/3/tv/${id}/season/${season}?api_key=${apiKey}`, { signal: AbortSignal.timeout(5000) });
if (seasonRes.ok) {
const seasonData = await seasonRes.json();
if (seasonData?.name) seasonTitle = seasonData.name;
}
} catch { }
}
const titles = seasonTitle ? [seasonTitle, ...meta.titles] : meta.titles;
const result = {
name: meta.title,
title: meta.title,
isAnime: meta.isAnime,
titles: [...new Set(titles.filter(Boolean))],
year: meta.year,
release_date: meta.releaseDate,
first_air_date: meta.releaseDate,
imdbId: meta.imdbId,
imdb_id: meta.imdbId,
external_ids: { imdb_id: meta.imdbId },
seasons: meta.seasons,
runtime: meta.runtime,
};
cacheSet(tmdbInfoCache, key, result, 600000);
return result;
} catch {
const fallback = {
name: '',
title: '',
isAnime: false,
titles: [],
year: null,
release_date: '',
first_air_date: '',
imdbId: null,
imdb_id: null,
external_ids: { imdb_id: null },
seasons: [],
runtime: 0,
};
return fallback;
}
}
export async function validateTmdbId(tmdbApiKey, tmdbId, mediaType = 'movie') {
let apiKey = tmdbApiKey;
let id = tmdbId;
let type = mediaType;
if (id === 'movie' || id === 'tv' || !id) {
if (apiKey && apiKey !== 'movie' && apiKey !== 'tv') {
type = id || 'movie';
id = apiKey;
apiKey = process.env.TMDB_API_KEY || DEFAULT_TMDB_KEY;
}
}
if (!apiKey) apiKey = process.env.TMDB_API_KEY || DEFAULT_TMDB_KEY;
const key = `${id}-${type}`;
const cached = cacheGet(tmdbValidationCache, key);
if (cached !== undefined) return cached;
try {
const res = await fetch(`https://api.themoviedb.org/3/${type}/${id}?api_key=${apiKey}`, { signal: AbortSignal.timeout(5000) });
if (res.ok) {
const data = await res.json();
const isValid = Boolean(data?.id && (data?.title || data?.name));
const result = { valid: isValid, error: isValid ? null : 'Invalid TMDB ID' };
cacheSet(tmdbValidationCache, key, result, 300000);
return result;
}
const result = { valid: false, error: `TMDB API error: ${res.status}` };
cacheSet(tmdbValidationCache, key, result, 60000);
return result;
} catch (error) {
const result = { valid: false, error: `Failed to validate TMDB ID: ${error.message}` };
cacheSet(tmdbValidationCache, key, result, 60000);
return result;
}
}
export async function imdbToTmdb(tmdbApiKey, imdbId, mediaType = 'movie') {
let apiKey = tmdbApiKey;
let id = imdbId;
let type = mediaType;
if (id === 'movie' || id === 'tv') {
type = id;
id = apiKey;
apiKey = process.env.TMDB_API_KEY || DEFAULT_TMDB_KEY;
}
if (!apiKey) apiKey = process.env.TMDB_API_KEY || DEFAULT_TMDB_KEY;
const key = `imdb-${id}`;
const cached = cacheGet(tmdbInfoCache, key);
if (cached !== undefined) return cached;
try {
const data = await fetchJson(`https://api.themoviedb.org/3/find/${id}?api_key=${apiKey}&external_source=imdb_id`, { signal: AbortSignal.timeout(5000) });
let result = null;
if (type === 'movie' && data.movie_results?.length) result = data.movie_results[0].id;
else if (type === 'tv' && data.tv_results?.length) result = data.tv_results[0].id;
if (result) cacheSet(tmdbInfoCache, key, result, 600000);
return result;
} catch {
return null;
}
}
export async function tmdbToAnilist(tmdbId, mediaType, season, titles = [], year = null) {
const key = `${tmdbId}-${mediaType}-${season || ''}`;
const cached = cacheGet(anilistCache, key);
if (cached !== undefined) return cached;
try {
const res = await fetch(`https://api.ani.zip/mappings?tmdb_id=${tmdbId}&type=${mediaType}&season=${season || 1}`, { signal: AbortSignal.timeout(6000) });
if (res.ok) {
const data = await res.json();
const id = data?.mappings?.[0]?.anilist_id;
if (id) {
cacheSet(anilistCache, key, id, 600000);
return id;
}
}
} catch { }
if (!titles.length) return null;
const query = `query ($s: String) { Page(page:1,perPage:10) { media(search:$s,type:ANIME) { id title { romaji english native } startDate { year } format } } }`;
let bestId = null;
let bestScore = -1;
for (const searchTitle of titles) {
try {
const res = await fetch('https://graphql.anilist.co', {
method: 'POST',
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
body: JSON.stringify({ query, variables: { s: searchTitle } }),
signal: AbortSignal.timeout(6000)
});
if (!res.ok) continue;
const resJson = await res.json();
for (const entry of resJson?.data?.Page?.media || []) {
const entryTitles = [entry.title?.romaji, entry.title?.english, entry.title?.native].filter(Boolean).map(t => t.toLowerCase());
const sl = searchTitle.toLowerCase();
let score = 0;
if (entryTitles.some(t => t === sl)) score += 5;
else if (entryTitles.some(t => t.includes(sl) || sl.includes(t))) score += 3;
else if (entryTitles.some(t => {
const w = sl.split(/\s+/).filter(x => x.length > 3);
return w.length > 0 && w.every(x => t.includes(x));
})) score += 2;
else continue;
if (year && entry.startDate?.year) {
const diff = Math.abs(entry.startDate.year - year);
if (diff === 0) score += 3;
else if (diff === 1) score += 1;
else if (diff > 2) score -= 3;
}
if (mediaType === 'tv' && ['TV', 'TV_SHORT', 'ONA', 'OVA'].includes(entry.format)) score += 1;
if (mediaType === 'movie' && entry.format === 'MOVIE') score += 1;
if (score > bestScore) {
bestScore = score;
bestId = entry.id;
}
}
if (bestScore >= 8) break;
} catch { }
}
if (bestId) cacheSet(anilistCache, key, bestId, 600000);
return bestId;
}
export async function createStreamArgs(source, sdk, id, s, e, clientIP = null) {
const audio = /dub$/.test(source.key) ? 'dub' : 'sub';
return {
id,
s,
e,
clientIP,
absoluteBase: '/',
audio,
sdk,
config: source,
tmdbApiKey: resolveTmdbKey(null, sdk),
};
}
export function formatStreamResult(streams, defaultHeaders = {}, extra = {}) {
if (!streams) return null;
const list = Array.isArray(streams) ? streams.filter(Boolean) : [streams];
if (!list.length) return null;
const allUrls = list.map(item => {
const itemUrl = item.url || item.file || item.stream || item.src;
if (!itemUrl) return null;
return {
url: itemUrl,
server: item.server || item.label || item.name || 'Server',
quality: item.quality || 'Auto',
type: item.type || (itemUrl.includes('.m3u8') ? 'hls' : (itemUrl.includes('.mpd') ? 'dash' : 'mp4')),
headers: item.headers || defaultHeaders,
...(item.subtitles ? { subtitles: item.subtitles } : {}),
...(item.skipProxy !== undefined ? { skipProxy: item.skipProxy } : {}),
...(item.audio ? { audio: item.audio } : {}),
...(item.label ? { label: item.label } : {}),
};
}).filter(Boolean);
if (!allUrls.length) return null;
return {
url: allUrls[0].url,
headers: allUrls[0].headers,
server: allUrls[0].server,
quality: allUrls[0].quality,
type: allUrls[0].type,
allUrls,
...extra,
};
}
export async function scrapeVidcoreFamily({ id, s, e, baseUrl, apiName, serverLabel }) {
const isTv = Boolean(s && e);
const targetUrl = isTv
? `${baseUrl}/tv/${id}/${s}/${e}/`
: `${baseUrl}/movie/${id}`;
const ua = getUA();
const baseHeaders = {
'User-Agent': ua,
Referer: `${baseUrl}/`,
'X-Requested-With': 'XMLHttpRequest',
};
try {
const pageRes = await fetch(targetUrl, {
headers: { 'User-Agent': ua },
signal: AbortSignal.timeout(10000),
});
if (!pageRes.ok) return null;
const html = await pageRes.text();
const match = html.match(/\\"(?:en|token)\\":\\"(.*?)\\"/);
if (!match || !match[1]) return null;
const enToken = match[1];
const s1Res = await fetch(`https://enc-dec.app/api/enc-${apiName}?text=${encodeURIComponent(enToken)}&stage=1`, {
signal: AbortSignal.timeout(8000),
});
const s1Data = await s1Res.json();
if (s1Data?.status !== 200 || !s1Data?.result?.stage1) return null;
const token1 = s1Data.result.token;
const stage1Url = s1Data.result.stage1;
const post1Res = await fetch(stage1Url, {
method: 'POST',
headers: { ...baseHeaders, 'X-CSRF-Token': token1 },
signal: AbortSignal.timeout(10000),
});
if (!post1Res.ok) return null;
const stage1Text = await post1Res.text();
const s2Res = await fetch(`https://enc-dec.app/api/enc-${apiName}?text=${encodeURIComponent(stage1Text)}&stage=2`, {
signal: AbortSignal.timeout(8000),
});
const s2Data = await s2Res.json();
if (s2Data?.status !== 200 || !s2Data?.result?.servers) return null;
const { servers: serversUrl, stream: streamBaseUrl, token: token2 } = s2Data.result;
const currentHeaders = { ...baseHeaders, 'X-CSRF-Token': token2 };
const serversRes = await fetch(serversUrl, {
method: 'POST',
headers: currentHeaders,
signal: AbortSignal.timeout(10000),
});
if (!serversRes.ok) return null;
const serversEncrypted = await serversRes.text();
const decServersRes = await fetch(`https://enc-dec.app/api/dec-${apiName}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text: serversEncrypted }),
signal: AbortSignal.timeout(8000),
});
const decServersData = await decServersRes.json();
if (decServersData?.status !== 200 || !Array.isArray(decServersData.result)) return null;
const allUrls = [];
for (const server of decServersData.result) {
if (!server?.data) continue;
try {
const streamUrl = `${streamBaseUrl}/${server.data}`;
const streamEncRes = await fetch(streamUrl, {
method: 'POST',
headers: currentHeaders,
signal: AbortSignal.timeout(8000),
});
if (!streamEncRes.ok) continue;
const streamEncText = await streamEncRes.text();
const decStreamRes = await fetch(`https://enc-dec.app/api/dec-${apiName}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text: streamEncText }),
signal: AbortSignal.timeout(8000),
});
const decStreamData = await decStreamRes.json();
if (decStreamData?.status !== 200 || !decStreamData.result) continue;
const streamResult = decStreamData.result;
const resultUrl = typeof streamResult === 'string' ? streamResult : streamResult?.url || streamResult?.file;
if (typeof resultUrl === 'string' && resultUrl.startsWith('http')) {
allUrls.push({
url: resultUrl,
server: `${serverLabel} - ${server.name || server.title || 'Server'}`,
label: server.name || server.title || serverLabel,
type: resultUrl.includes('.m3u8') ? 'hls' : 'mp4',
headers: {
'User-Agent': ua,
Referer: `${baseUrl}/`,
},
});
}
} catch { }
}
return formatStreamResult(allUrls);
} catch {
return null;
}
}

View File

@@ -18,7 +18,7 @@ const CONFIG = {
TEST_SUBTITLE: false,
TEST_DOWNLOAD: false,
TEST_MOVIE: true,
TEST_TV: true,
TEST_TV: false,
TEST_MANGA: false,
MANGA_QUERY: "Steel Ball Run",
@@ -32,7 +32,7 @@ dotenv.config({
});
// debug
if (CONFIG.DEBUG) {
if (CONFIG.DEBUG && !process.argv.includes("all")) {
const originalFetch = globalThis.fetch;
const debugFile = path.resolve("test/debug-output.json");
@@ -118,6 +118,45 @@ const sdk = new VylaSDK({
...CONFIG.EXTRA_SDK_PARAMS
});
if (process.argv.includes("all")) {
const sources = sdk.getSources(true);
console.log(`Testing ${sources.length} sources in parallel...\n`);
const results = {};
const failed = [];
await Promise.all(
sources.map(async (cfg) => {
try {
const res = await sdk.probeSource(cfg.key);
results[cfg.key] = res;
if (res?.ok) {
console.log(`[PASS] ${cfg.key} (${res.ms}ms)`);
} else {
failed.push(cfg.key);
console.log(`[FAIL] ${cfg.key} (${res?.ms ?? 0}ms)`);
}
} catch (err) {
results[cfg.key] = { ok: false, ms: null, error: err.message };
failed.push(cfg.key);
console.log(`[FAIL] ${cfg.key} (${err.message})`);
}
})
);
console.log(`\nFinished testing. Total: ${sources.length}, Passed: ${sources.length - failed.length}, Failed: ${failed.length}`);
console.log("Sources that returned nothing:", failed);
const debugFile = path.resolve("test/debug-output.json");
fs.mkdirSync(path.dirname(debugFile), { recursive: true });
fs.writeFileSync(debugFile, JSON.stringify({
sourcesThatReturnedNothing: failed,
results
}, null, 2));
process.exit(0);
}
console.log("Starting...");
if (CONFIG.TEST_SUBTITLE && CONFIG.TEST_TV) {