mirror of
https://gitlab.com/vyla-entertainment/sdk.git
synced 2026-11-09 19:57:23 -05:00
removed a lot of sources, added one
This commit is contained in:
237
src/config.js
237
src/config.js
@@ -6,18 +6,6 @@
|
||||
// Fshare/Fsonic Tested: 155 ( they don't have Michael )
|
||||
|
||||
export const SOURCES = [
|
||||
{
|
||||
key: '4khdhub',
|
||||
label: '4KHDHub',
|
||||
sourceFile: '4khdhub',
|
||||
proxyParam: 'khd',
|
||||
timeout: 35000,
|
||||
jitter: 500,
|
||||
retries: 1,
|
||||
multiUrl: true,
|
||||
skipProxy: true,
|
||||
},
|
||||
|
||||
{
|
||||
key: '123anime',
|
||||
label: '123Anime',
|
||||
@@ -119,72 +107,20 @@ export const SOURCES = [
|
||||
},
|
||||
|
||||
{
|
||||
key: 'cinejoy',
|
||||
label: 'Cinejoy',
|
||||
sourceFile: 'cinejoy',
|
||||
proxyParam: 'cj',
|
||||
timeout: 30000,
|
||||
jitter: 500,
|
||||
retries: 1,
|
||||
multiUrl: true,
|
||||
skipProxy: true,
|
||||
},
|
||||
|
||||
{
|
||||
key: 'cinesrc',
|
||||
label: 'CineSrc',
|
||||
sourceFile: 'cinesrc',
|
||||
proxyParam: 'csr',
|
||||
key: 'dulo',
|
||||
label: 'Dulo',
|
||||
sourceFile: 'dulo',
|
||||
proxyParam: 'dl',
|
||||
timeout: 25000,
|
||||
jitter: 500,
|
||||
retries: 2,
|
||||
multiUrl: true,
|
||||
skipProxy: true,
|
||||
skipVerify: true,
|
||||
cdnHeaders: [{
|
||||
pattern: /glendale-plumbing\.com|1embed\.cc|bright\d+\.online/i,
|
||||
pattern: /dulo\.mov/i,
|
||||
headers: {
|
||||
Referer: 'https://cinesrc.st/',
|
||||
Origin: 'https://cinesrc.st',
|
||||
},
|
||||
}],
|
||||
},
|
||||
|
||||
{
|
||||
key: 'cinesu',
|
||||
label: 'CineSu',
|
||||
sourceFile: 'cinesu',
|
||||
proxyParam: 'cs',
|
||||
timeout: 25000,
|
||||
jitter: 500,
|
||||
retries: 2,
|
||||
multiUrl: true,
|
||||
skipProxy: true,
|
||||
skipVerify: true,
|
||||
cdnHeaders: [{
|
||||
pattern: /glendale-plumbing\.com/i,
|
||||
headers: {
|
||||
Referer: 'https://cine.su/',
|
||||
Origin: 'https://cine.su',
|
||||
},
|
||||
}],
|
||||
},
|
||||
|
||||
{
|
||||
key: 'downloadeverything',
|
||||
label: 'DownloadEverything',
|
||||
sourceFile: 'downloadeverything',
|
||||
proxyParam: 'de',
|
||||
timeout: 35000,
|
||||
jitter: 500,
|
||||
retries: 1,
|
||||
multiUrl: true,
|
||||
skipProxy: true,
|
||||
cdnHeaders: [{
|
||||
pattern: /cloudflarestorage\.com|pixeldrain\.com|hakunaymatata\.com/i,
|
||||
headers: {
|
||||
Referer: 'https://downloadeverythingfromeverywhere.com/',
|
||||
Origin: 'https://downloadeverythingfromeverywhere.com',
|
||||
Referer: 'https://dulo.mov/',
|
||||
Origin: 'https://dulo.mov',
|
||||
},
|
||||
}],
|
||||
},
|
||||
@@ -208,45 +144,6 @@ export const SOURCES = [
|
||||
},],
|
||||
},
|
||||
|
||||
{
|
||||
key: 'frame',
|
||||
label: 'Frame',
|
||||
sourceFile: 'frame',
|
||||
proxyParam: 'fr',
|
||||
timeout: 25000,
|
||||
jitter: 500,
|
||||
retries: 2,
|
||||
multiUrl: true,
|
||||
skipProxy: true,
|
||||
},
|
||||
|
||||
{
|
||||
key: 'fsharetv',
|
||||
label: 'FShareTV',
|
||||
sourceFile: 'fsharetv',
|
||||
proxyParam: 'fs',
|
||||
timeout: 25000,
|
||||
jitter: 600,
|
||||
retries: 2,
|
||||
multiUrl: false,
|
||||
verifyHeaders: {
|
||||
Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.8',
|
||||
'Accept-Language': 'en-US,en;q=0.9',
|
||||
Referer: 'https://fsharetv.cc',
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
key: 'fsonic',
|
||||
label: 'Fsonic',
|
||||
sourceFile: 'fsonic',
|
||||
proxyParam: 'fn',
|
||||
timeout: 35000,
|
||||
jitter: 600,
|
||||
retries: 1,
|
||||
multiUrl: true,
|
||||
},
|
||||
|
||||
{
|
||||
key: 'fsonline',
|
||||
label: 'FSOnline',
|
||||
@@ -259,16 +156,6 @@ export const SOURCES = [
|
||||
skipProxy: true,
|
||||
},
|
||||
|
||||
{
|
||||
key: 'hexa',
|
||||
label: 'Hexa',
|
||||
sourceFile: 'hexa',
|
||||
proxyParam: 'hx',
|
||||
timeout: 20000,
|
||||
jitter: 500,
|
||||
retries: 2,
|
||||
},
|
||||
|
||||
{
|
||||
key: 'kisskh',
|
||||
label: 'KissKH',
|
||||
@@ -304,44 +191,6 @@ export const SOURCES = [
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
key: 'luna-sub',
|
||||
label: 'Luna (Sub)',
|
||||
sourceFile: 'luna',
|
||||
proxyParam: 'lusub',
|
||||
subtype: 'sub',
|
||||
timeout: 25000,
|
||||
jitter: 500,
|
||||
retries: 2,
|
||||
multiUrl: true,
|
||||
skipProxy: true,
|
||||
},
|
||||
|
||||
{
|
||||
key: 'luna-dub',
|
||||
label: 'Luna (Dub)',
|
||||
sourceFile: 'luna',
|
||||
proxyParam: 'ludub',
|
||||
subtype: 'dub',
|
||||
timeout: 25000,
|
||||
jitter: 500,
|
||||
retries: 2,
|
||||
multiUrl: true,
|
||||
skipProxy: true,
|
||||
},
|
||||
|
||||
{
|
||||
key: 'meowtv',
|
||||
label: 'MeowTV',
|
||||
sourceFile: 'meowtv',
|
||||
proxyParam: 'mt',
|
||||
timeout: 20000,
|
||||
jitter: 500,
|
||||
retries: 2,
|
||||
multiUrl: true,
|
||||
skipProxy: true
|
||||
},
|
||||
|
||||
{
|
||||
key: 'movienight',
|
||||
label: 'MovieNight',
|
||||
@@ -380,18 +229,6 @@ export const SOURCES = [
|
||||
}],
|
||||
},
|
||||
|
||||
{
|
||||
key: 'peestream',
|
||||
label: 'PeeStream',
|
||||
sourceFile: 'peestream',
|
||||
proxyParam: 'pee',
|
||||
timeout: 25000,
|
||||
jitter: 500,
|
||||
retries: 2,
|
||||
multiUrl: true,
|
||||
skipProxy: true,
|
||||
},
|
||||
|
||||
{
|
||||
key: 'pengu',
|
||||
label: 'Pengu',
|
||||
@@ -456,26 +293,6 @@ export const SOURCES = [
|
||||
},
|
||||
}],
|
||||
},
|
||||
|
||||
{
|
||||
key: 'vadapav',
|
||||
label: 'Vadapav',
|
||||
sourceFile: 'vadapav',
|
||||
proxyParam: 'vp',
|
||||
timeout: 25000,
|
||||
jitter: 500,
|
||||
retries: 2,
|
||||
multiUrl: true,
|
||||
skipProxy: true,
|
||||
cdnHeaders: [{
|
||||
pattern: /vadapav\.mov/i,
|
||||
headers: {
|
||||
Referer: 'https://stremio.vadapav.mov/',
|
||||
Origin: 'https://stremio.vadapav.mov',
|
||||
},
|
||||
}],
|
||||
},
|
||||
|
||||
{
|
||||
key: 'vidapi',
|
||||
label: 'VidAPI',
|
||||
@@ -487,27 +304,6 @@ export const SOURCES = [
|
||||
multiUrl: true
|
||||
},
|
||||
|
||||
{
|
||||
key: 'vidcore',
|
||||
sourceFile: 'vidcore',
|
||||
label: 'Vidcore',
|
||||
proxyParam: 'vc',
|
||||
timeout: 30000,
|
||||
jitter: 500,
|
||||
retries: 2,
|
||||
sourcesTimeout: 10000,
|
||||
},
|
||||
|
||||
{
|
||||
key: 'vidfast',
|
||||
label: 'Vidfast',
|
||||
sourceFile: 'vidfast',
|
||||
proxyParam: 'vf',
|
||||
timeout: 35000,
|
||||
jitter: 500,
|
||||
retries: 1,
|
||||
},
|
||||
|
||||
{
|
||||
key: 'vidgod',
|
||||
label: 'VidGod',
|
||||
@@ -644,25 +440,6 @@ export const SOURCES = [
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
key: 'vuflix',
|
||||
label: 'Vuflix',
|
||||
sourceFile: 'vuflix',
|
||||
proxyParam: 'vfx',
|
||||
timeout: 25000,
|
||||
jitter: 500,
|
||||
retries: 2,
|
||||
multiUrl: true,
|
||||
skipProxy: true,
|
||||
cdnHeaders: [{
|
||||
pattern: /vuflix\.co/i,
|
||||
headers: {
|
||||
Referer: 'https://vuflix.co/',
|
||||
Origin: 'https://vuflix.co',
|
||||
},
|
||||
}],
|
||||
},
|
||||
|
||||
{
|
||||
key: 'xdownloader',
|
||||
label: 'XDownloader',
|
||||
|
||||
@@ -1,324 +0,0 @@
|
||||
import { getUA } from '../utils/helpers.js';
|
||||
|
||||
const INITIAL_BASE_URL = 'https://4khdhub.one';
|
||||
let cachedBaseUrl = null;
|
||||
let cachedBaseUrlExpiry = 0;
|
||||
|
||||
function rot13(str) {
|
||||
return str.replace(/[a-zA-Z]/g, (c) => {
|
||||
const code = c.charCodeAt(0);
|
||||
if (code >= 65 && code <= 90) {
|
||||
return String.fromCharCode(((code - 65 + 13) % 26) + 65);
|
||||
}
|
||||
return String.fromCharCode(((code - 97 + 13) % 26) + 97);
|
||||
});
|
||||
}
|
||||
|
||||
function levenshtein(a, b) {
|
||||
if (a === b) return 0;
|
||||
if (!a.length) return b.length;
|
||||
if (!b.length) return a.length;
|
||||
const la = a.length;
|
||||
const lb = b.length;
|
||||
const d = Array.from({ length: la + 1 }, () => new Array(lb + 1).fill(0));
|
||||
for (let i = 0; i <= la; i++) d[i][0] = i;
|
||||
for (let j = 0; j <= lb; j++) d[0][j] = j;
|
||||
for (let i = 1; i <= la; i++) {
|
||||
for (let j = 1; j <= lb; j++) {
|
||||
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
||||
d[i][j] = Math.min(d[i - 1][j] + 1, d[i][j - 1] + 1, d[i - 1][j - 1] + cost);
|
||||
}
|
||||
}
|
||||
return d[la][lb];
|
||||
}
|
||||
|
||||
async function getBaseUrl(ua) {
|
||||
if (cachedBaseUrl && Date.now() < cachedBaseUrlExpiry) {
|
||||
return cachedBaseUrl;
|
||||
}
|
||||
|
||||
let currentUrl = INITIAL_BASE_URL;
|
||||
for (let i = 0; i < 10; i++) {
|
||||
try {
|
||||
const res = await fetch(currentUrl, {
|
||||
method: 'HEAD',
|
||||
headers: { 'User-Agent': ua },
|
||||
redirect: 'manual',
|
||||
signal: AbortSignal.timeout(10000),
|
||||
});
|
||||
|
||||
if (res.status >= 300 && res.status < 400) {
|
||||
const location = res.headers.get('location');
|
||||
if (location) {
|
||||
currentUrl = new URL(location, currentUrl).toString();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
break;
|
||||
} catch {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const parsed = new URL(currentUrl);
|
||||
cachedBaseUrl = `${parsed.protocol}//${parsed.host}`;
|
||||
cachedBaseUrlExpiry = Date.now() + 3600000;
|
||||
return cachedBaseUrl;
|
||||
}
|
||||
|
||||
async function resolveMetadata(sdk, id, isTv) {
|
||||
const apiKey = sdk?.tmdbApiKey || process.env.TMDB_API_KEY;
|
||||
if (!apiKey) return null;
|
||||
const type = isTv ? 'tv' : 'movie';
|
||||
try {
|
||||
const res = await fetch(`https://api.themoviedb.org/3/${type}/${id}?api_key=${apiKey}`, {
|
||||
signal: AbortSignal.timeout(8000),
|
||||
});
|
||||
if (!res.ok) return null;
|
||||
const data = await res.json();
|
||||
const title = isTv ? data.name : data.title;
|
||||
const releaseDate = isTv ? data.first_air_date : data.release_date;
|
||||
const year = releaseDate ? parseInt(releaseDate.split('-')[0], 10) : null;
|
||||
return { title, year };
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function sanitizeStreamUrl(rawUrl) {
|
||||
try {
|
||||
const parsed = new URL(rawUrl);
|
||||
parsed.pathname = parsed.pathname.replaceAll('::', '%3A%3A');
|
||||
return parsed.toString();
|
||||
} catch {
|
||||
return rawUrl.replaceAll('::', '%3A%3A');
|
||||
}
|
||||
}
|
||||
|
||||
async function validateStreamUrl(url, ua) {
|
||||
try {
|
||||
const res = await fetch(url, {
|
||||
headers: {
|
||||
'User-Agent': ua,
|
||||
Range: 'bytes=0-1024',
|
||||
},
|
||||
signal: AbortSignal.timeout(4000),
|
||||
});
|
||||
|
||||
const contentType = (res.headers.get('content-type') || '').toLowerCase();
|
||||
const isSuccess = (res.status >= 200 && res.status < 300) || res.status === 206;
|
||||
const isHtmlOrJsonError = contentType.includes('text/html') || contentType.includes('application/json');
|
||||
|
||||
if (!isSuccess || isHtmlOrJsonError) return false;
|
||||
return true;
|
||||
} catch {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
async function resolveRedirectUrl(redirectUrl, ua) {
|
||||
try {
|
||||
const res = await fetch(redirectUrl, {
|
||||
headers: { 'User-Agent': ua },
|
||||
signal: AbortSignal.timeout(15000),
|
||||
});
|
||||
if (!res.ok) return null;
|
||||
const html = await res.text();
|
||||
|
||||
let nextUrl = '';
|
||||
const downloadMatch = html.match(/<a id="download" href="(.*?)"/);
|
||||
if (downloadMatch) {
|
||||
nextUrl = downloadMatch[1];
|
||||
} else {
|
||||
const varMatch = html.match(/var url = '(.*?)';/);
|
||||
if (varMatch) {
|
||||
nextUrl = varMatch[1];
|
||||
}
|
||||
}
|
||||
|
||||
if (!nextUrl) {
|
||||
const match = html.match(/'o','(.*?)'/);
|
||||
if (!match) return null;
|
||||
const rawData = match[1];
|
||||
const step1 = Buffer.from(rawData, 'base64').toString('utf8');
|
||||
const step2 = Buffer.from(step1, 'base64').toString('utf8');
|
||||
const step3 = rot13(step2);
|
||||
const step4 = Buffer.from(step3, 'base64').toString('utf8');
|
||||
const data = JSON.parse(step4);
|
||||
return Buffer.from(data.o, 'base64').toString('utf8');
|
||||
}
|
||||
|
||||
const interRes = await fetch(nextUrl, {
|
||||
headers: { 'User-Agent': ua },
|
||||
signal: AbortSignal.timeout(15000),
|
||||
});
|
||||
if (!interRes.ok) return null;
|
||||
const interHtml = await interRes.text();
|
||||
|
||||
const finalMatch = interHtml.match(/<a href="([^"]+)"[^>]*class="[^"]*btn-success/);
|
||||
if (finalMatch) {
|
||||
return finalMatch[1];
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function extractSourceResults(itemHtml, ua) {
|
||||
const sizeMatch = itemHtml.match(/([\d.]+ ?[GM]B)/);
|
||||
const sizeStr = sizeMatch ? sizeMatch[1] : null;
|
||||
|
||||
const heightMatch = itemHtml.match(/\d{3,}p/);
|
||||
const heightStr = heightMatch ? heightMatch[0] : null;
|
||||
|
||||
const fileTitleMatch = itemHtml.match(/class="(?:file-title|episode-file-title)"[^>]*>([^<]+)/);
|
||||
const fileTitle = fileTitleMatch ? fileTitleMatch[1].trim() : 'Unknown';
|
||||
|
||||
const displayParts = [fileTitle];
|
||||
if (heightStr) displayParts.push(heightStr);
|
||||
if (sizeStr) displayParts.push(sizeStr);
|
||||
|
||||
let hubCloudUrl = null;
|
||||
let hubDriveUrl = null;
|
||||
|
||||
const linkRegex = /<a\s+[^>]*href="([^"]+)"[^>]*>([\s\S]*?)<\/a>/gi;
|
||||
let match;
|
||||
while ((match = linkRegex.exec(itemHtml)) !== null) {
|
||||
const href = match[1];
|
||||
const text = match[2];
|
||||
if (text.includes('HubCloud') && !hubCloudUrl) {
|
||||
hubCloudUrl = href;
|
||||
} else if (text.includes('HubDrive') && !hubDriveUrl) {
|
||||
hubDriveUrl = href;
|
||||
}
|
||||
}
|
||||
|
||||
const redirectUrl = hubCloudUrl || hubDriveUrl;
|
||||
if (!redirectUrl) return null;
|
||||
|
||||
const resolvedUrl = await resolveRedirectUrl(redirectUrl, ua);
|
||||
if (!resolvedUrl) return null;
|
||||
|
||||
const safeUrl = sanitizeStreamUrl(resolvedUrl);
|
||||
const isValid = await validateStreamUrl(safeUrl, ua);
|
||||
if (!isValid) return null;
|
||||
|
||||
return {
|
||||
url: safeUrl,
|
||||
label: displayParts.join(' · '),
|
||||
headers: {
|
||||
'User-Agent': ua,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export async function getStream({ id, s, e, sdk }) {
|
||||
const isTv = Boolean(s && e);
|
||||
const ua = getUA();
|
||||
|
||||
const meta = await resolveMetadata(sdk, id, isTv);
|
||||
if (!meta?.title) return null;
|
||||
|
||||
const baseUrl = await getBaseUrl(ua);
|
||||
const searchUrl = `${baseUrl}/?s=${encodeURIComponent(meta.title)}`;
|
||||
|
||||
const searchRes = await fetch(searchUrl, {
|
||||
headers: { 'User-Agent': ua },
|
||||
signal: AbortSignal.timeout(15000),
|
||||
});
|
||||
if (!searchRes.ok) return null;
|
||||
const searchHtml = await searchRes.text();
|
||||
|
||||
const cardRegex = /<a\s+([^>]*\bclass="[^"]*movie-card[^"]*"[^>]*)>([\s\S]*?)<\/a>/gi;
|
||||
const formatFilter = isTv ? 'Series' : 'Movies';
|
||||
let targetPageUrl = null;
|
||||
|
||||
let cardMatch;
|
||||
while ((cardMatch = cardRegex.exec(searchHtml)) !== null) {
|
||||
const attributes = cardMatch[1];
|
||||
const inner = cardMatch[2];
|
||||
|
||||
const hrefMatch = attributes.match(/href="([^"]+)"/);
|
||||
if (!hrefMatch) continue;
|
||||
const href = hrefMatch[1];
|
||||
|
||||
const formatMatches = [...inner.matchAll(/class="movie-card-format"[^>]*>([\s\S]*?)<\//gi)];
|
||||
const formatTexts = formatMatches.map(m => m[1].trim());
|
||||
if (!formatTexts.some(t => t.includes(formatFilter))) continue;
|
||||
|
||||
if (meta.year != null) {
|
||||
const metaMatch = inner.match(/class="movie-card-meta"[^>]*>([\s\S]*?)<\//i);
|
||||
const yearMatch = metaMatch ? metaMatch[1].match(/\d{4}/) : null;
|
||||
if (yearMatch) {
|
||||
const cardYear = parseInt(yearMatch[0], 10);
|
||||
if (Math.abs(cardYear - meta.year) > 1) continue;
|
||||
}
|
||||
}
|
||||
|
||||
const titleMatch = inner.match(/class="movie-card-title"[^>]*>([\s\S]*?)<\//i);
|
||||
let cardTitle = titleMatch ? titleMatch[1] : '';
|
||||
cardTitle = cardTitle.replace(/\[.*?\]/g, '').trim();
|
||||
|
||||
const diff = levenshtein(cardTitle.toLowerCase(), meta.title.toLowerCase());
|
||||
const isMatch = diff < 5 || (cardTitle.toLowerCase().includes(meta.title.toLowerCase()) && diff < 16);
|
||||
if (!isMatch) continue;
|
||||
|
||||
targetPageUrl = new URL(href, baseUrl).toString();
|
||||
break;
|
||||
}
|
||||
|
||||
if (!targetPageUrl) return null;
|
||||
|
||||
const pageRes = await fetch(targetPageUrl, {
|
||||
headers: { 'User-Agent': ua },
|
||||
signal: AbortSignal.timeout(15000),
|
||||
});
|
||||
if (!pageRes.ok) return null;
|
||||
const pageHtml = await pageRes.text();
|
||||
|
||||
const matchedHtmlBlocks = [];
|
||||
|
||||
if (isTv) {
|
||||
const seasonStr = String(s).padStart(2, '0');
|
||||
const episodeStr = String(e).padStart(2, '0');
|
||||
|
||||
const epItemRegex = /class="[^"]*episode-item[^"]*"[^>]*>([\s\S]*?)(?=(?:class="[^"]*episode-item"|<\/main>|$))/gi;
|
||||
let epMatch;
|
||||
while ((epMatch = epItemRegex.exec(pageHtml)) !== null) {
|
||||
const epBlock = epMatch[1];
|
||||
const titleMatch = epBlock.match(/class="episode-title"[^>]*>([\s\S]*?)<\//i);
|
||||
const titleText = titleMatch ? titleMatch[1] : '';
|
||||
if (!titleText.includes(`S${seasonStr}`)) continue;
|
||||
|
||||
const dlItemRegex = /class="[^"]*episode-download-item[^"]*"[^>]*>([\s\S]*?)(?=(?:class="[^"]*episode-download-item"|<\/div>\s*<\/div>|$))/gi;
|
||||
let dlMatch;
|
||||
while ((dlMatch = dlItemRegex.exec(epBlock)) !== null) {
|
||||
const dlBlock = dlMatch[1];
|
||||
if (dlBlock.includes(`Episode-${episodeStr}`)) {
|
||||
matchedHtmlBlocks.push(dlBlock);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const dlItemRegex = /class="[^"]*download-item[^"]*"[^>]*>([\s\S]*?)(?=(?:class="[^"]*download-item"|<\/main>|$))/gi;
|
||||
let dlMatch;
|
||||
while ((dlMatch = dlItemRegex.exec(pageHtml)) !== null) {
|
||||
matchedHtmlBlocks.push(dlMatch[1]);
|
||||
}
|
||||
}
|
||||
|
||||
if (matchedHtmlBlocks.length === 0) return null;
|
||||
|
||||
const results = await Promise.all(matchedHtmlBlocks.map((block) => extractSourceResults(block, ua)));
|
||||
const allUrls = results.filter(Boolean);
|
||||
|
||||
if (allUrls.length === 0) return null;
|
||||
|
||||
return {
|
||||
url: allUrls[0].url,
|
||||
allUrls,
|
||||
headers: allUrls[0].headers,
|
||||
};
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -1,212 +0,0 @@
|
||||
import { USER_AGENT } from '../utils/helpers.js';
|
||||
|
||||
const _sInit = [23, 27, 30, 31, 86, 28, 19, 86, 9, 18, 19, 21, 26, 27, 86, 120, 114, 120, 116, 86, 9, 18, 19, 21, 26, 27, 122, 89, 25, 23];
|
||||
const _vParam = `_v=${(_sInit[15] ^ 0x5a)}${(_sInit[16] ^ 0x5a)}${(_sInit[17] ^ 0x5a)}${(_sInit[18] ^ 0x5a)}`;
|
||||
|
||||
const nD = '4860ac8bfddb';
|
||||
const aD = '224eff10e662e9635c9f671cf46351dcd69af42b1edd56f5e5fa21751f44b9c8';
|
||||
const Ls = [17, 91, 203, 44, 8, 177, 62, 239, 119, 3, 154, 81, 28, 210, 101, 7];
|
||||
const wa = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
|
||||
|
||||
const SERVERS = [
|
||||
{ id: 'NIGHT', endpoint: '/server/night' },
|
||||
{ id: 'EMP', endpoint: '/server/emp' },
|
||||
{ id: 'MAIN', endpoint: '/server/vidsrc' }
|
||||
];
|
||||
|
||||
let cachedToken = null;
|
||||
let tokenExpiry = 0;
|
||||
|
||||
function ab(e) {
|
||||
let t = e >>> 0;
|
||||
t ^= t >>> 16;
|
||||
t = Math.imul(t, 2146121005) >>> 0;
|
||||
t ^= t >>> 15;
|
||||
t = Math.imul(t, 2221713035) >>> 0;
|
||||
return (t ^ (t >>> 16)) >>> 0;
|
||||
}
|
||||
|
||||
function sD(e) {
|
||||
const t = new TextEncoder().encode(aD);
|
||||
const r = Math.max(32, Math.min(128, e + 17));
|
||||
const n = new Uint8Array(r);
|
||||
let a = 2166136261;
|
||||
for (let s = 0; s < r; s += 1) {
|
||||
a ^= t[s % t.length] ?? s;
|
||||
a = ab((a + Ls[s % Ls.length] + ((2654435761 * s) >>> 0)) >>> 0);
|
||||
n[s] = a & 255;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
function iD(e) {
|
||||
let t = '';
|
||||
for (let r = 0; r < e.length; r += 3) {
|
||||
const n = e[r];
|
||||
const a = e[r + 1];
|
||||
const s = e[r + 2];
|
||||
t += wa[n >>> 2];
|
||||
t += wa[((3 & n) << 4) | ((a ?? 0) >>> 4)];
|
||||
if (a === undefined) break;
|
||||
t += wa[((15 & a) << 2) | ((s ?? 0) >>> 6)];
|
||||
if (s === undefined) break;
|
||||
t += wa[63 & s];
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
function generateCinesrcDirectHlsUrl(id, s, e) {
|
||||
const isTv = s != null && e != null;
|
||||
const tmdbId = Math.floor(Number(id));
|
||||
const season = isTv ? Math.floor(Number(s || 1)) : 0;
|
||||
const episode = isTv ? Math.floor(Number(e || 1)) : 0;
|
||||
|
||||
const str = `${nD}:${isTv ? 's' : 'm'}:${tmdbId}:${season}:${episode}`;
|
||||
const a = new TextEncoder().encode(str);
|
||||
const sArr = sD(a.length);
|
||||
const i = new Uint8Array(a.length + 2);
|
||||
i[0] = a.length & 255;
|
||||
i[1] = (a.length >>> 8) & 255;
|
||||
let o = (2654435769 ^ a.length) >>> 0;
|
||||
for (let l = 0; l < a.length; l += 1) {
|
||||
o = ab((o + sArr[l % sArr.length] + Ls[l % Ls.length] + l) >>> 0);
|
||||
i[l + 2] = (a[l] ^ (255 & o)) ^ sArr[(7 * l + 3) % sArr.length];
|
||||
}
|
||||
|
||||
return `https://glendale-plumbing.com/c/v1/${iD(i)}/master.m3u8`;
|
||||
}
|
||||
|
||||
async function fetchBright67Subtitles(id, season, episode) {
|
||||
let url = `https://subs.bright67.online/search?id=${id}`;
|
||||
if (season != null && episode != null) {
|
||||
url += `&season=${season}&episode=${episode}`;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch(url, {
|
||||
headers: {
|
||||
'User-Agent': USER_AGENT,
|
||||
'Referer': 'https://cinesrc.st/'
|
||||
},
|
||||
signal: AbortSignal.timeout(4000)
|
||||
});
|
||||
|
||||
if (!res.ok) return [];
|
||||
const data = await res.json();
|
||||
if (!Array.isArray(data)) return [];
|
||||
|
||||
return data.slice(0, 15).map(s => ({
|
||||
label: s.display || s.languageName || s.language || 'English',
|
||||
language: s.language || 'en',
|
||||
file: s.url || s.r2Url,
|
||||
type: (s.url || s.r2Url || '').endsWith('.vtt') ? 'vtt' : 'srt'
|
||||
}));
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchInternalToken() {
|
||||
if (cachedToken && Date.now() < tokenExpiry) return cachedToken;
|
||||
try {
|
||||
const res = await fetch('https://1embed.cc/api/token', {
|
||||
headers: {
|
||||
'User-Agent': USER_AGENT,
|
||||
'Referer': 'https://1embed.cc/',
|
||||
'Accept': 'application/json, text/plain, */*'
|
||||
},
|
||||
signal: AbortSignal.timeout(4000)
|
||||
});
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
if (data?.token) {
|
||||
cachedToken = data.token;
|
||||
tokenExpiry = Date.now() + 1800 * 1000;
|
||||
return cachedToken;
|
||||
}
|
||||
}
|
||||
} catch { }
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
export async function getStream(args) {
|
||||
const { id, s, e, clientIP } = args;
|
||||
const isTv = s != null && e != null;
|
||||
const isMovie = !isTv;
|
||||
const allUrls = [];
|
||||
|
||||
const [subtitles, directHlsUrl] = await Promise.all([
|
||||
fetchBright67Subtitles(id, isMovie ? undefined : s, isMovie ? undefined : e),
|
||||
Promise.resolve(generateCinesrcDirectHlsUrl(id, s, e))
|
||||
]);
|
||||
|
||||
const masterUrlWithMark = `${directHlsUrl}?${_vParam}`;
|
||||
|
||||
allUrls.push({
|
||||
url: masterUrlWithMark,
|
||||
server: 'CineSrc (Direct Master)',
|
||||
quality: 'Auto',
|
||||
type: 'hls',
|
||||
headers: {
|
||||
'User-Agent': USER_AGENT,
|
||||
'Referer': 'https://cinesrc.st/',
|
||||
'Origin': 'https://cinesrc.st',
|
||||
...(clientIP && { 'X-Forwarded-For': clientIP })
|
||||
},
|
||||
subtitles: subtitles.length ? subtitles : undefined
|
||||
});
|
||||
|
||||
const token = await fetchInternalToken();
|
||||
|
||||
await Promise.allSettled(
|
||||
SERVERS.map(async (server) => {
|
||||
const query = isTv
|
||||
? `id=${id}?type=tv&s=${s || 1}&e=${e || 1}`
|
||||
: `id=${id}?type=movie`;
|
||||
|
||||
const stParam = token ? `&_st=${token}` : '';
|
||||
const targetUrl = `https://1embed.cc${server.endpoint}/${query}${stParam}`;
|
||||
|
||||
const headers = {
|
||||
'User-Agent': USER_AGENT,
|
||||
'Referer': 'https://cinesrc.st/',
|
||||
'Accept': 'application/json, text/plain, */*',
|
||||
...(token && { 'X-Stream-Token': token }),
|
||||
...(clientIP && { 'X-Forwarded-For': clientIP })
|
||||
};
|
||||
|
||||
const res = await fetch(targetUrl, { headers, signal: AbortSignal.timeout(5000) }).catch(() => null);
|
||||
|
||||
if (res && res.ok) {
|
||||
try {
|
||||
const data = await res.json();
|
||||
if (data && data.success !== false) {
|
||||
const streamUrl = data.streams?.proxy_m3u8 || data.streams?.raw_m3u8 || data.streams?.m3u8 || data.streamUrl;
|
||||
if (streamUrl && typeof streamUrl === 'string') {
|
||||
const urlWithMark = streamUrl.includes('?') ? `${streamUrl}&${_vParam}` : `${streamUrl}?${_vParam}`;
|
||||
allUrls.push({
|
||||
url: urlWithMark,
|
||||
server: `CineSrc (${data.selectedSource || data.provider || server.id})`,
|
||||
quality: 'Auto',
|
||||
type: streamUrl.includes('.m3u8') ? 'hls' : 'mp4',
|
||||
headers: {
|
||||
'User-Agent': USER_AGENT,
|
||||
'Referer': 'https://cinesrc.st/',
|
||||
'Origin': 'https://cinesrc.st'
|
||||
},
|
||||
subtitles: subtitles.length ? subtitles : undefined
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch { }
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
return allUrls.length ? { allUrls } : null;
|
||||
}
|
||||
|
||||
export async function getSources() {
|
||||
return ['CineSrc (Direct Master)', 'CineSrc (NIGHT)', 'CineSrc (EMP)', 'CineSrc (MAIN)'];
|
||||
}
|
||||
@@ -1,148 +0,0 @@
|
||||
import { USER_AGENT } from '../utils/helpers.js';
|
||||
|
||||
const BASE_DOMAIN = 'https://glendale-plumbing.com';
|
||||
const REFERER = 'https://cine.su/';
|
||||
|
||||
const nD = '4860ac8bfddb';
|
||||
const aD = '224eff10e662e9635c9f671cf46351dcd69af42b1edd56f5e5fa21751f44b9c8';
|
||||
const Ls = [17, 91, 203, 44, 8, 177, 62, 239, 119, 3, 154, 81, 28, 210, 101, 7];
|
||||
const wa = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
|
||||
|
||||
function ab(e) {
|
||||
let t = e >>> 0;
|
||||
t ^= t >>> 16;
|
||||
t = Math.imul(t, 2146121005) >>> 0;
|
||||
t ^= t >>> 15;
|
||||
t = Math.imul(t, 2221713035) >>> 0;
|
||||
return (t ^ (t >>> 16)) >>> 0;
|
||||
}
|
||||
|
||||
function sD(e) {
|
||||
const t = new TextEncoder().encode(aD);
|
||||
const r = Math.max(32, Math.min(128, e + 17));
|
||||
const n = new Uint8Array(r);
|
||||
let a = 2166136261;
|
||||
for (let s = 0; s < r; s += 1) {
|
||||
a ^= t[s % t.length] ?? s;
|
||||
a = ab((a + Ls[s % Ls.length] + ((2654435761 * s) >>> 0)) >>> 0);
|
||||
n[s] = a & 255;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
function iD(e) {
|
||||
let t = '';
|
||||
for (let r = 0; r < e.length; r += 3) {
|
||||
const n = e[r];
|
||||
const a = e[r + 1];
|
||||
const s = e[r + 2];
|
||||
t += wa[n >>> 2];
|
||||
t += wa[((3 & n) << 4) | ((a ?? 0) >>> 4)];
|
||||
if (a === undefined) break;
|
||||
t += wa[((15 & a) << 2) | ((s ?? 0) >>> 6)];
|
||||
if (s === undefined) break;
|
||||
t += wa[63 & s];
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
function generateMasterUrl(id, s, e) {
|
||||
const isTv = s != null && e != null;
|
||||
const tmdbId = Math.floor(Number(id));
|
||||
const season = isTv ? Math.floor(Number(s || 1)) : 0;
|
||||
const episode = isTv ? Math.floor(Number(e || 1)) : 0;
|
||||
|
||||
const str = `${nD}:${isTv ? 's' : 'm'}:${tmdbId}:${season}:${episode}`;
|
||||
const a = new TextEncoder().encode(str);
|
||||
const sArr = sD(a.length);
|
||||
const i = new Uint8Array(a.length + 2);
|
||||
i[0] = a.length & 255;
|
||||
i[1] = (a.length >>> 8) & 255;
|
||||
let o = (2654435769 ^ a.length) >>> 0;
|
||||
for (let l = 0; l < a.length; l += 1) {
|
||||
o = ab((o + sArr[l % sArr.length] + Ls[l % Ls.length] + l) >>> 0);
|
||||
i[l + 2] = (a[l] ^ (255 & o)) ^ sArr[(7 * l + 3) % sArr.length];
|
||||
}
|
||||
|
||||
return `${BASE_DOMAIN}/c/v1/${iD(i)}/master.m3u8`;
|
||||
}
|
||||
|
||||
function parsePlaylist(m3u8Text) {
|
||||
const qualities = [];
|
||||
const subtitles = [];
|
||||
|
||||
const subRegex = /#EXT-X-MEDIA:TYPE=SUBTITLES,[^\n]*NAME="([^"]*)"[^\n]*LANGUAGE="([^"]*)"[^\n]*URI="([^"]*)"/g;
|
||||
let match;
|
||||
while ((match = subRegex.exec(m3u8Text)) !== null) {
|
||||
let uri = match[3];
|
||||
if (uri.startsWith('/')) uri = `${BASE_DOMAIN}${uri}`;
|
||||
subtitles.push({ label: match[1], language: match[2], file: uri, type: 'vtt' });
|
||||
}
|
||||
|
||||
const streamRegex = /#EXT-X-STREAM-INF:[^\n]*RESOLUTION=\d+x(\d+)[^\n]*\r?\n([^\n#][^\n]*)/g;
|
||||
while ((match = streamRegex.exec(m3u8Text)) !== null) {
|
||||
let streamPath = match[2].trim();
|
||||
if (streamPath.startsWith('/')) streamPath = `${BASE_DOMAIN}${streamPath}`;
|
||||
qualities.push({ quality: `${match[1]}p`, url: streamPath });
|
||||
}
|
||||
|
||||
return { qualities, subtitles };
|
||||
}
|
||||
|
||||
export async function getStream(args) {
|
||||
const { id, s, e, clientIP } = args;
|
||||
const rawMasterUrl = generateMasterUrl(id, s, e);
|
||||
|
||||
const headers = {
|
||||
'User-Agent': USER_AGENT,
|
||||
'Referer': REFERER,
|
||||
'Origin': 'https://cine.su',
|
||||
...(clientIP && { 'X-Forwarded-For': clientIP })
|
||||
};
|
||||
|
||||
try {
|
||||
const res = await fetch(rawMasterUrl, {
|
||||
headers,
|
||||
signal: AbortSignal.timeout(5000)
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
const text = await res.text();
|
||||
const { qualities, subtitles } = parsePlaylist(text);
|
||||
|
||||
if (qualities.length) {
|
||||
const allUrls = [{
|
||||
url: rawMasterUrl,
|
||||
server: 'CineSu (Auto)',
|
||||
quality: 'Auto',
|
||||
type: 'hls',
|
||||
headers,
|
||||
subtitles: subtitles.length ? subtitles : undefined
|
||||
}];
|
||||
|
||||
for (const q of qualities) {
|
||||
allUrls.push({
|
||||
url: q.url,
|
||||
server: `CineSu (${q.quality})`,
|
||||
quality: q.quality,
|
||||
type: 'hls',
|
||||
headers,
|
||||
subtitles: subtitles.length ? subtitles : undefined
|
||||
});
|
||||
}
|
||||
|
||||
return { allUrls };
|
||||
}
|
||||
}
|
||||
} catch { }
|
||||
|
||||
return {
|
||||
allUrls: [{
|
||||
url: rawMasterUrl,
|
||||
server: 'CineSu',
|
||||
quality: 'Auto',
|
||||
type: 'hls',
|
||||
headers
|
||||
}]
|
||||
};
|
||||
}
|
||||
@@ -1,338 +0,0 @@
|
||||
import { getUA } from '../utils/helpers.js';
|
||||
|
||||
const SLAVE_URL = 'https://slave.downloadeverythingfromeverywhere.com/';
|
||||
const REFERER = 'https://downloadeverythingfromeverywhere.com/';
|
||||
const ORIGIN = 'https://downloadeverythingfromeverywhere.com';
|
||||
const USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36';
|
||||
|
||||
function extractJsonObjects(text) {
|
||||
const objects = [];
|
||||
let depth = 0;
|
||||
let start = -1;
|
||||
let inString = false;
|
||||
let escape = false;
|
||||
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
const char = text[i];
|
||||
if (escape) {
|
||||
escape = false;
|
||||
continue;
|
||||
}
|
||||
if (char === '\\') {
|
||||
escape = true;
|
||||
continue;
|
||||
}
|
||||
if (char === '"') {
|
||||
inString = !inString;
|
||||
continue;
|
||||
}
|
||||
if (!inString) {
|
||||
if (char === '{') {
|
||||
if (depth === 0) start = i;
|
||||
depth++;
|
||||
} else if (char === '}') {
|
||||
depth--;
|
||||
if (depth === 0 && start !== -1) {
|
||||
const jsonStr = text.substring(start, i + 1);
|
||||
try {
|
||||
objects.push(JSON.parse(jsonStr));
|
||||
} catch { }
|
||||
start = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return objects;
|
||||
}
|
||||
|
||||
async function resolveDetails(sdk, id, isTv) {
|
||||
const apiKey = sdk?.tmdbApiKey || process.env.TMDB_API_KEY;
|
||||
if (!apiKey) return null;
|
||||
const type = isTv ? 'tv' : 'movie';
|
||||
try {
|
||||
const res = await fetch(`https://api.themoviedb.org/3/${type}/${id}?api_key=${apiKey}&append_to_response=external_ids`, {
|
||||
signal: AbortSignal.timeout(6000),
|
||||
});
|
||||
if (!res.ok) return null;
|
||||
const data = await res.json();
|
||||
const title = isTv ? data.name : data.title;
|
||||
const releaseDate = isTv ? data.first_air_date : data.release_date;
|
||||
const year = releaseDate ? parseInt(releaseDate.split('-')[0], 10) : null;
|
||||
const imdbId = data.imdb_id || data.external_ids?.imdb_id || null;
|
||||
return { title, year, imdbId };
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function resolveHubCloud(hubUrl, ua) {
|
||||
try {
|
||||
const res1 = await fetch(hubUrl, {
|
||||
headers: {
|
||||
'User-Agent': ua,
|
||||
Referer: REFERER,
|
||||
},
|
||||
signal: AbortSignal.timeout(8000),
|
||||
});
|
||||
|
||||
const html = await res1.text();
|
||||
const hubPhpMatch = html.match(/https?:\/\/[^\s"<>]+\/hubcloud\.php\?[^\s"<>]+/i);
|
||||
if (!hubPhpMatch) return null;
|
||||
|
||||
const phpUrl = hubPhpMatch[0];
|
||||
const phpRes = await fetch(phpUrl, {
|
||||
headers: {
|
||||
'User-Agent': ua,
|
||||
Referer: hubUrl,
|
||||
},
|
||||
signal: AbortSignal.timeout(8000),
|
||||
});
|
||||
|
||||
const phpBody = await phpRes.text();
|
||||
|
||||
const r2Match = phpBody.match(/https?:\/\/[a-zA-Z0-9.\-_]+\.r2\.cloudflarestorage\.com\/[^\s"<>]+/i);
|
||||
if (r2Match) {
|
||||
return r2Match[0].replaceAll('&', '&');
|
||||
}
|
||||
|
||||
const pixelMatch = phpBody.match(/https?:\/\/pixel\.hubcloud\.[a-z]+\/\?id=[^\s"<>]+/i);
|
||||
if (pixelMatch) {
|
||||
return pixelMatch[0];
|
||||
}
|
||||
} catch { }
|
||||
return null;
|
||||
}
|
||||
|
||||
async function resolveClicknUpload(clicknUrl, ua) {
|
||||
try {
|
||||
const res1 = await fetch(clicknUrl, {
|
||||
headers: {
|
||||
'User-Agent': ua,
|
||||
Referer: REFERER,
|
||||
},
|
||||
signal: AbortSignal.timeout(8000),
|
||||
});
|
||||
|
||||
const body1 = await res1.text();
|
||||
const formMatch1 = body1.match(/<form[^>]+method=["']POST["'][^>]*>([\s\S]*?)<\/form>/i);
|
||||
if (!formMatch1) return null;
|
||||
|
||||
const inputMatches1 = [...formMatch1[1].matchAll(/<input[^>]+name=["']([^"']+)["'][^>]+value=["']([^"']*)["']/gi)];
|
||||
const params1 = new URLSearchParams();
|
||||
for (const m of inputMatches1) {
|
||||
params1.set(m[1], m[2]);
|
||||
}
|
||||
params1.set('method_free', 'Slow Download');
|
||||
|
||||
const rawCookies = res1.headers.getSetCookie?.() || [res1.headers.get('set-cookie')].filter(Boolean);
|
||||
const cookieHeader = rawCookies.join('; ');
|
||||
|
||||
const res2 = await fetch(clicknUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'User-Agent': ua,
|
||||
Referer: clicknUrl,
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
...(cookieHeader ? { Cookie: cookieHeader } : {}),
|
||||
},
|
||||
body: params1.toString(),
|
||||
signal: AbortSignal.timeout(8000),
|
||||
});
|
||||
|
||||
const body2 = await res2.text();
|
||||
const formMatch2 = body2.match(/<form[^>]+method=["']POST["'][^>]*>([\s\S]*?)<\/form>/i);
|
||||
if (!formMatch2) return null;
|
||||
|
||||
const inputMatches2 = [...formMatch2[1].matchAll(/<input[^>]+name=["']([^"']+)["'][^>]+value=["']([^"']*)["']/gi)];
|
||||
const params2 = new URLSearchParams();
|
||||
for (const m of inputMatches2) {
|
||||
params2.set(m[1], m[2]);
|
||||
}
|
||||
params2.set('down_script', '1');
|
||||
|
||||
await new Promise(r => setTimeout(r, 4500));
|
||||
|
||||
const res3 = await fetch(clicknUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'User-Agent': ua,
|
||||
Referer: clicknUrl,
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
...(cookieHeader ? { Cookie: cookieHeader } : {}),
|
||||
},
|
||||
body: params2.toString(),
|
||||
signal: AbortSignal.timeout(8000),
|
||||
});
|
||||
|
||||
const body3 = await res3.text();
|
||||
const directMatch = body3.match(/https?:\/\/[a-zA-Z0-9.\-_:]+\/d\/[a-zA-Z0-9_\-\/]+/i) ||
|
||||
body3.match(/window\.open\(["'](https?:\/\/[^"']+)["']\)/i);
|
||||
|
||||
if (directMatch) {
|
||||
const found = directMatch[1] || directMatch[0];
|
||||
if (found.includes('clicknupload.') && !found.includes('/d/')) {
|
||||
return null;
|
||||
}
|
||||
return found;
|
||||
}
|
||||
} catch { }
|
||||
return null;
|
||||
}
|
||||
|
||||
async function resolveItem(item, fallbackTitle, ua) {
|
||||
const rawUrl = String(item.url || '').trim();
|
||||
if (!rawUrl) return null;
|
||||
|
||||
if (
|
||||
rawUrl.includes('111477.xyz') ||
|
||||
rawUrl.includes('vadapav.mov') ||
|
||||
rawUrl.includes('driveseed.org') ||
|
||||
rawUrl.includes('new3.gdflix.io') ||
|
||||
rawUrl.includes('rapidrar.cr') ||
|
||||
rawUrl.includes('megaup.net') ||
|
||||
rawUrl.includes('telegram.dog') ||
|
||||
rawUrl.includes('t.me')
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let directStreamUrl = null;
|
||||
let provider = item.site || 'DownloadEverything';
|
||||
let streamHeaders = { 'User-Agent': ua };
|
||||
|
||||
try {
|
||||
if (rawUrl.includes('hakunaymatata.com')) {
|
||||
directStreamUrl = rawUrl;
|
||||
provider = 'Moviebox';
|
||||
streamHeaders = { 'User-Agent': 'Lavf/60.16.100' };
|
||||
} else if (rawUrl.includes('pixeldrain.dev') || rawUrl.includes('pixeldrain.com')) {
|
||||
const m = rawUrl.match(/pixeldrain\.(?:dev|com)\/(?:u|l)\/([a-zA-Z0-9_-]+)/);
|
||||
if (m) {
|
||||
directStreamUrl = `https://pixeldrain.com/api/file/${m[1]}`;
|
||||
provider = 'Pixeldrain';
|
||||
}
|
||||
} else if (rawUrl.includes('hubcloud.') || rawUrl.includes('vcloud.zip')) {
|
||||
directStreamUrl = await resolveHubCloud(rawUrl, ua);
|
||||
if (directStreamUrl) {
|
||||
provider = 'HubCloud';
|
||||
}
|
||||
} else if (rawUrl.includes('clicknupload.')) {
|
||||
directStreamUrl = await resolveClicknUpload(rawUrl, ua);
|
||||
if (directStreamUrl) {
|
||||
provider = 'ClicknUpload';
|
||||
}
|
||||
} else if (
|
||||
/\.(?:mp4|mkv)(?:\?|$)/i.test(rawUrl) &&
|
||||
!rawUrl.includes('111477.xyz') &&
|
||||
!rawUrl.includes('vadapav.mov') &&
|
||||
!rawUrl.includes('.cyou/res/')
|
||||
) {
|
||||
try {
|
||||
const check = await fetch(rawUrl, {
|
||||
method: 'HEAD',
|
||||
headers: { 'User-Agent': ua },
|
||||
signal: AbortSignal.timeout(2000),
|
||||
});
|
||||
if (check.status === 200 || check.status === 206 || (check.status >= 300 && check.status < 400)) {
|
||||
directStreamUrl = rawUrl;
|
||||
provider = item.site || 'DirectStream';
|
||||
}
|
||||
} catch { }
|
||||
}
|
||||
} catch { }
|
||||
|
||||
if (!directStreamUrl) return null;
|
||||
|
||||
const tags = Array.isArray(item.tags) ? item.tags.map(String) : [];
|
||||
const qualityMatch = tags.find(t => /2160p|4k|1080p|720p|480p/i.test(t)) || '1080p';
|
||||
const rawTitle = item.name || item.release || fallbackTitle;
|
||||
|
||||
return {
|
||||
url: directStreamUrl,
|
||||
label: `[${provider}] ${rawTitle} (${qualityMatch})`,
|
||||
headers: streamHeaders,
|
||||
};
|
||||
}
|
||||
|
||||
export async function getStream({ id, s, e, sdk }) {
|
||||
const isTv = Boolean(s && e);
|
||||
const mediaType = isTv ? 'series' : 'movie';
|
||||
const ua = USER_AGENT;
|
||||
|
||||
const details = await resolveDetails(sdk, id, isTv);
|
||||
const title = details?.title || String(id);
|
||||
const tmdbId = parseInt(id, 10);
|
||||
|
||||
const payload = {
|
||||
mode: mediaType,
|
||||
title,
|
||||
...(details?.year ? { year: String(details.year) } : {}),
|
||||
...(tmdbId ? { tmdb_id: tmdbId } : {}),
|
||||
...(details?.imdbId ? { imdb_id: details.imdbId } : {}),
|
||||
...(isTv && s != null ? { season: Number(s) } : {}),
|
||||
...(isTv && e != null ? { episode: Number(e) } : {}),
|
||||
};
|
||||
|
||||
const res = await fetch(SLAVE_URL, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'User-Agent': ua,
|
||||
Origin: ORIGIN,
|
||||
Referer: REFERER,
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/x-ndjson',
|
||||
'x-defe-manual': '1',
|
||||
'sec-ch-ua': '"Microsoft Edge";v="153", "Not_A Brand";v="8", "Chromium";v="153"',
|
||||
'sec-ch-ua-mobile': '?0',
|
||||
'sec-ch-ua-platform': '"Windows"',
|
||||
'sec-fetch-dest': 'empty',
|
||||
'sec-fetch-mode': 'cors',
|
||||
'sec-fetch-site': 'same-site',
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
signal: AbortSignal.timeout(16000),
|
||||
});
|
||||
|
||||
if (!res.ok) return null;
|
||||
|
||||
let fullText = '';
|
||||
if (res.body?.getReader) {
|
||||
const reader = res.body.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
fullText += decoder.decode(value, { stream: true });
|
||||
}
|
||||
} catch { }
|
||||
} else {
|
||||
fullText = await res.text();
|
||||
}
|
||||
|
||||
const objects = extractJsonObjects(fullText);
|
||||
const candidateItems = [];
|
||||
|
||||
for (const parsed of objects) {
|
||||
if (parsed?.t === 'hit' && Array.isArray(parsed.links)) {
|
||||
const site = parsed.site || 'DownloadEverything';
|
||||
for (const l of parsed.links) {
|
||||
if (l && typeof l === 'object') {
|
||||
candidateItems.push({ site, ...l });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const resolved = await Promise.all(candidateItems.map(item => resolveItem(item, title, ua)));
|
||||
const allUrls = resolved.filter(Boolean);
|
||||
|
||||
if (allUrls.length === 0) return null;
|
||||
|
||||
return {
|
||||
url: allUrls[0].url,
|
||||
allUrls,
|
||||
headers: allUrls[0].headers,
|
||||
};
|
||||
}
|
||||
150
src/sources/dulo.js
Normal file
150
src/sources/dulo.js
Normal file
@@ -0,0 +1,150 @@
|
||||
import { getUA } 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;
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchSource(url, headers) {
|
||||
try {
|
||||
const res = await fetch(url, {
|
||||
headers,
|
||||
signal: AbortSignal.timeout(8000),
|
||||
});
|
||||
if (!res.ok) return null;
|
||||
return await res.json();
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getStream({ id, s, e, sdk }) {
|
||||
const isTv = Boolean(s && e);
|
||||
const mediaType = isTv ? 'tv' : 'movie';
|
||||
const ua = getUA();
|
||||
|
||||
const details = await resolveDetails(sdk, id, isTv);
|
||||
const title = details?.title || '';
|
||||
const year = details?.year || '';
|
||||
const runtime = details?.runtime || '';
|
||||
|
||||
const referer = isTv
|
||||
? `${BASE_URL}/watch/tv/${id}/${s}/${e}`
|
||||
: `${BASE_URL}/watch/movie/${id}`;
|
||||
|
||||
const defaultHeaders = {
|
||||
'User-Agent': ua,
|
||||
Referer: referer,
|
||||
Origin: BASE_URL,
|
||||
Accept: '*/*',
|
||||
'Sec-Fetch-Dest': 'empty',
|
||||
'Sec-Fetch-Mode': 'cors',
|
||||
'Sec-Fetch-Site': 'same-origin',
|
||||
};
|
||||
|
||||
const baseParams = new URLSearchParams({
|
||||
tmdbId: String(id),
|
||||
type: mediaType,
|
||||
});
|
||||
|
||||
const additionalParams = new URLSearchParams({
|
||||
tmdbId: String(id),
|
||||
type: mediaType,
|
||||
progressive: 'true',
|
||||
});
|
||||
|
||||
if (title) additionalParams.set('title', title);
|
||||
if (year) additionalParams.set('year', String(year));
|
||||
if (runtime) additionalParams.set('runtime', String(runtime));
|
||||
|
||||
if (isTv) {
|
||||
baseParams.set('season', String(s));
|
||||
baseParams.set('episode', String(e));
|
||||
additionalParams.set('season', String(s));
|
||||
additionalParams.set('episode', String(e));
|
||||
}
|
||||
|
||||
const endpoints = [
|
||||
`${BASE_URL}/api/sources?${baseParams.toString()}`,
|
||||
`${BASE_URL}/api/sources/willow?${baseParams.toString()}`,
|
||||
`${BASE_URL}/api/sources/additional?${additionalParams.toString()}`,
|
||||
];
|
||||
|
||||
const seenUrls = new Set();
|
||||
const allUrls = [];
|
||||
|
||||
let pendingUrls = [...endpoints];
|
||||
const maxPollTime = Date.now() + 16000;
|
||||
|
||||
while (pendingUrls.length > 0 && Date.now() < maxPollTime) {
|
||||
const results = await Promise.all(
|
||||
pendingUrls.map((url) => fetchSource(url, defaultHeaders))
|
||||
);
|
||||
|
||||
const nextPending = [];
|
||||
|
||||
results.forEach((data, index) => {
|
||||
if (!data) return;
|
||||
|
||||
if (Array.isArray(data.sources) && data.sources.length > 0) {
|
||||
for (const src of data.sources) {
|
||||
let streamUrl = String(src?.url || '').trim();
|
||||
if (!streamUrl) continue;
|
||||
|
||||
if (streamUrl.startsWith('/')) {
|
||||
streamUrl = `${BASE_URL}${streamUrl}`;
|
||||
}
|
||||
|
||||
if (!streamUrl.startsWith('http') || seenUrls.has(streamUrl)) continue;
|
||||
seenUrls.add(streamUrl);
|
||||
|
||||
allUrls.push({
|
||||
url: streamUrl,
|
||||
label: src.label || 'Dulo',
|
||||
headers: {
|
||||
'User-Agent': ua,
|
||||
Referer: referer,
|
||||
},
|
||||
captions: Array.isArray(src.captions) ? src.captions : [],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (data.pending) {
|
||||
nextPending.push(pendingUrls[index]);
|
||||
}
|
||||
});
|
||||
|
||||
pendingUrls = nextPending;
|
||||
if (pendingUrls.length > 0) {
|
||||
await sleep(1500);
|
||||
}
|
||||
}
|
||||
|
||||
if (allUrls.length === 0) return null;
|
||||
|
||||
return {
|
||||
url: allUrls[0].url,
|
||||
allUrls,
|
||||
headers: allUrls[0].headers,
|
||||
};
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
import { USER_AGENT, fetchJson } from '../utils/helpers.js';
|
||||
|
||||
const BASE_URL = 'https://api.peestream.in';
|
||||
|
||||
const PROVIDERS = [
|
||||
{ id: 'vaplayer', name: 'FRAME Zephyr' },
|
||||
{ id: 'castle', name: 'FRAME Atlas' },
|
||||
{ id: 'hera', name: 'FRAME Luna' },
|
||||
{ id: 'multivid', name: 'FRAME Volt' },
|
||||
{ id: 'netmirror', name: 'FRAME Echo' },
|
||||
{ id: 'vidsuper-castle', name: 'FRAME Rift' },
|
||||
{ id: 'vidsuper-vixsrc', name: 'FRAME Quill' }
|
||||
];
|
||||
|
||||
async function fetchProviderStreams(provider, type, id, s, e) {
|
||||
try {
|
||||
const queryParams = new URLSearchParams({
|
||||
q: String(id),
|
||||
type,
|
||||
provider: provider.id
|
||||
});
|
||||
if (type === 'tv') {
|
||||
queryParams.set('season', String(s));
|
||||
queryParams.set('episode', String(e));
|
||||
}
|
||||
|
||||
const url = `${BASE_URL}/api/search?${queryParams.toString()}`;
|
||||
const data = await fetchJson(url, {
|
||||
headers: {
|
||||
'User-Agent': USER_AGENT,
|
||||
'Accept': 'application/json',
|
||||
'Referer': 'https://peestream.in/'
|
||||
},
|
||||
signal: AbortSignal.timeout(12000)
|
||||
});
|
||||
|
||||
if (!data || !Array.isArray(data.results) || !data.results.length) return [];
|
||||
|
||||
const streams = [];
|
||||
for (const result of data.results) {
|
||||
for (const stream of result.streams || []) {
|
||||
if (!stream || !stream.url) continue;
|
||||
streams.push({
|
||||
url: stream.url,
|
||||
server: provider.name,
|
||||
quality: stream.quality || 'Auto',
|
||||
type: stream.type === 'm3u8' || stream.url.includes('.m3u8') ? 'hls' : 'mp4',
|
||||
headers: stream.headers || {
|
||||
'User-Agent': USER_AGENT
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
return streams;
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export async function getStream(args) {
|
||||
const { id, s, e, server } = args;
|
||||
const isTv = s != null && e != null;
|
||||
const type = isTv ? 'tv' : 'movie';
|
||||
|
||||
let targets = PROVIDERS;
|
||||
if (server && server !== 'all') {
|
||||
const clean = server.toLowerCase().replace('frame ', '').trim();
|
||||
targets = PROVIDERS.filter(p => p.id.toLowerCase() === clean || p.name.toLowerCase().includes(clean));
|
||||
if (!targets.length) targets = PROVIDERS;
|
||||
}
|
||||
|
||||
const settled = await Promise.allSettled(
|
||||
targets.map(p => fetchProviderStreams(p, type, id, s, e))
|
||||
);
|
||||
|
||||
const allUrls = [];
|
||||
for (const r of settled) {
|
||||
if (r.status === 'fulfilled' && Array.isArray(r.value)) {
|
||||
allUrls.push(...r.value);
|
||||
}
|
||||
}
|
||||
|
||||
return allUrls.length ? { allUrls } : null;
|
||||
}
|
||||
|
||||
export async function getSources() {
|
||||
return PROVIDERS.map(p => p.name);
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
import { fetchText, fetchJson, USER_AGENT, getTmdbInfo } from '../utils/helpers.js';
|
||||
|
||||
const BASE_URL = 'https://fsharetv.cc';
|
||||
const HEADERS = { 'User-Agent': USER_AGENT, 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Accept-Language': 'en-US,en;q=0.9', 'Referer': `${BASE_URL}/` };
|
||||
const API_HEADERS = { ...HEADERS, 'Accept': 'application/json, */*; q=0.01', 'X-Requested-With': 'XMLHttpRequest' };
|
||||
|
||||
export async function getStream({ id, s }) {
|
||||
if (s != null) return null;
|
||||
try {
|
||||
const info = await getTmdbInfo(id, 'movie');
|
||||
if (!info?.imdbId) return null;
|
||||
const html = await fetchText(`${BASE_URL}/movie/${info.imdbId}`, { headers: HEADERS, signal: AbortSignal.timeout(8000) });
|
||||
let watchPath = null;
|
||||
const match = html.match(/href="(\/w\/[^"]+)"/);
|
||||
if (match) watchPath = match[1];
|
||||
if (!watchPath) return null;
|
||||
const watchHtml = await fetchText(`${BASE_URL}${watchPath}`, { headers: HEADERS, signal: AbortSignal.timeout(8000) });
|
||||
let sourceId = null;
|
||||
const sidMatch = watchHtml.match(/(?:source_id|file_id|setSource)[\s=:\('"]+([^'"\)]+)/i);
|
||||
if (sidMatch) sourceId = sidMatch[1];
|
||||
if (!sourceId) return null;
|
||||
const json = await fetchJson(`${BASE_URL}/api/file/${sourceId}/source?trailer=Png81APqcxU&type=watch`, { headers: { ...API_HEADERS, 'Referer': `${BASE_URL}${watchPath}` }, signal: AbortSignal.timeout(8000) });
|
||||
if (json.status !== 'ok') return null;
|
||||
const groups = [];
|
||||
if (Array.isArray(json.data?.file?.sources)) groups.push(json.data.file.sources);
|
||||
if (Array.isArray(json.data?.file?.alternatives)) json.data.file.alternatives.forEach(g => Array.isArray(g) && groups.push(g));
|
||||
const out = [], seen = new Set();
|
||||
for (const group of groups) {
|
||||
for (const item of group.filter(st => st?.src).sort((a, b) => parseInt(b.quality) - parseInt(a.quality))) {
|
||||
const url = item.src.startsWith('http') ? item.src : `${BASE_URL}${item.src}`;
|
||||
if (seen.has(url)) continue;
|
||||
seen.add(url);
|
||||
out.push({ url, quality: parseInt(item.quality) || 0, label: item.label || null });
|
||||
}
|
||||
}
|
||||
out.sort((a, b) => b.quality - a.quality);
|
||||
return out.length ? { allUrls: out.map(u => ({ url: u.url, headers: HEADERS, skipProxy: false })) } : null;
|
||||
} catch { return null; }
|
||||
}
|
||||
|
||||
export async function getSources(args) {
|
||||
const stream = await getStream(args);
|
||||
return stream?.allUrls ? stream.allUrls.map(u => u.url) : [];
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
import { fetchText, fetchJson, USER_AGENT, getTmdbInfo } from '../utils/helpers.js';
|
||||
|
||||
const BASE_URL = 'https://www.fsonic.net';
|
||||
const FSHARE_BASE = 'https://fsharetv.co';
|
||||
const HEADERS = { 'User-Agent': USER_AGENT, 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Accept-Language': 'en-US,en;q=0.9' };
|
||||
|
||||
export async function getStream({ id, s, sdk }) {
|
||||
const tmdbApiKey = sdk?.tmdbApiKey || null;
|
||||
if (!tmdbApiKey) return null;
|
||||
if (s != null) return null;
|
||||
try {
|
||||
const info = await getTmdbInfo(tmdbApiKey, id, 'movie');
|
||||
if (!info?.titles?.length) return null;
|
||||
let watchSlug = null;
|
||||
for (const title of info.titles) {
|
||||
try {
|
||||
const html = await fetchText(`${BASE_URL}/movie/search/${encodeURIComponent(title)}`, { headers: HEADERS, signal: AbortSignal.timeout(8000) });
|
||||
let idx = 0, found = null;
|
||||
const yearStr = String(info.year);
|
||||
while ((idx = html.indexOf('href="/watch/', idx)) !== -1) {
|
||||
const start = idx + 6;
|
||||
const end = html.indexOf('"', start);
|
||||
const link = html.slice(start, end);
|
||||
if (!found) found = link;
|
||||
if (link.includes(yearStr)) { found = link; break; }
|
||||
idx = end;
|
||||
}
|
||||
if (found) { watchSlug = found; break; }
|
||||
} catch { }
|
||||
}
|
||||
if (!watchSlug) return null;
|
||||
const wHtml = await fetchText(`${BASE_URL}${watchSlug}`, { headers: HEADERS, signal: AbortSignal.timeout(8000) });
|
||||
let token = null, trailer = null;
|
||||
const match = wHtml.match(/init\('([^']+)',\s*(?:'[^']*',\s*)?'([^']+)'/);
|
||||
if (match) { token = match[1]; trailer = match[2]; }
|
||||
if (!token) return null;
|
||||
const json = await fetchJson(`${BASE_URL}/api/source/${token}?trailer=${trailer}&type=watch`, { headers: { ...HEADERS, 'Accept': 'application/json, text/plain, */*', 'Referer': `${BASE_URL}${watchSlug}` }, signal: AbortSignal.timeout(8000) });
|
||||
if (json.status !== 'ok') return null;
|
||||
const allGroups = [];
|
||||
if (json.data?.file?.sources?.length) allGroups.push(json.data.file.sources);
|
||||
if (json.data?.file?.alternatives) for (const g of json.data.file.alternatives) if (g?.length) allGroups.push(g);
|
||||
const urls = new Set();
|
||||
for (const group of allGroups) {
|
||||
const best = [...group].filter(st => st?.src).sort((a, b) => parseInt(b.quality) - parseInt(a.quality))[0];
|
||||
if (best) urls.add(best.src.startsWith('http') ? best.src : `${FSHARE_BASE}${best.src}`);
|
||||
}
|
||||
return urls.size ? { allUrls: [...urls].map(url => ({ url, headers: { ...HEADERS, 'Referer': `${FSHARE_BASE}/` }, skipProxy: false })) } : null;
|
||||
} catch { return null; }
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
import crypto from 'node:crypto';
|
||||
import { fetchJson, fetchText, USER_AGENT } from '../utils/helpers.js';
|
||||
|
||||
const API_BASE = 'https://enc-dec.app/api';
|
||||
const DOMAINS = ['hexa.su', 'flixer.su'];
|
||||
|
||||
async function getChallengeToken() {
|
||||
try {
|
||||
const data = await fetchJson(`${API_BASE}/enc-hexa`, { signal: AbortSignal.timeout(6000) });
|
||||
return data?.status === 200 ? data.result.token : null;
|
||||
} catch { return null; }
|
||||
}
|
||||
|
||||
export async function getStream({ id, s, e, server }) {
|
||||
try {
|
||||
const apiKey = crypto.randomBytes(32).toString('hex');
|
||||
const capToken = await getChallengeToken();
|
||||
if (!capToken) return null;
|
||||
|
||||
const isTv = s != null && e != null;
|
||||
let decrypted = null;
|
||||
|
||||
for (const domain of DOMAINS) {
|
||||
try {
|
||||
const url = isTv
|
||||
? `https://theemoviedb.${domain}/api/tmdb/tv/${id}/season/${s}/episode/${e}/images`
|
||||
: `https://theemoviedb.${domain}/api/tmdb/movie/${id}/images`;
|
||||
|
||||
const encrypted = await fetchText(url, {
|
||||
headers: {
|
||||
'User-Agent': USER_AGENT,
|
||||
'Referer': `https://${domain}/`,
|
||||
'Accept': 'text/plain',
|
||||
'X-Fingerprint-Lite': 'e9136c41504646444',
|
||||
'X-Api-Key': apiKey,
|
||||
'X-Cap-Token': capToken
|
||||
},
|
||||
signal: AbortSignal.timeout(10000)
|
||||
});
|
||||
|
||||
if (!encrypted) continue;
|
||||
|
||||
const decData = await fetchJson(`${API_BASE}/dec-hexa`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ text: encrypted, key: apiKey }),
|
||||
signal: AbortSignal.timeout(8000)
|
||||
});
|
||||
|
||||
if (decData?.status === 200 && decData.result?.sources?.length) {
|
||||
decrypted = decData.result;
|
||||
break;
|
||||
}
|
||||
} catch { continue; }
|
||||
}
|
||||
|
||||
if (!decrypted?.sources?.length) return null;
|
||||
|
||||
let sources = decrypted.sources.map(src => ({
|
||||
url: src.url,
|
||||
server: `Hexa - ${src.name || src.server || 'Server'}`,
|
||||
type: src.url.includes('.m3u8') ? 'hls' : 'mp4',
|
||||
quality: src.quality || 'Auto',
|
||||
subtitles: (decrypted.subtitles || []).map(sub => ({
|
||||
url: sub.url,
|
||||
lang: sub.label || sub.lang || 'Unknown'
|
||||
})),
|
||||
skipProxy: false
|
||||
}));
|
||||
|
||||
if (server && server !== 'all') {
|
||||
const cleanName = server.replace('Hexa - ', '');
|
||||
sources = sources.filter(u => u.server.includes(cleanName));
|
||||
}
|
||||
|
||||
return sources.length ? { allUrls: sources } : null;
|
||||
} catch { return null; }
|
||||
}
|
||||
|
||||
export async function getSources() {
|
||||
return ['Hexa'];
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
import { USER_AGENT, fetchText, getTmdbInfo, tmdbToAnilist } from '../utils/helpers.js';
|
||||
|
||||
const ACTION_FETCH_SOURCES = 'afb0491c5516f9fff5fcb464d627638df76062f8';
|
||||
const WATCH_URL = 'https://luna-stream.me/anime/watch/21/gogoanime/1';
|
||||
|
||||
const PROVIDERS = [
|
||||
{ id: 'megaplay', name: 'Helios' },
|
||||
{ id: 'anibd', name: 'Nova' },
|
||||
{ id: 'pahe', name: 'Polaris' },
|
||||
{ id: 'gogoanime', name: 'Quasar' },
|
||||
{ id: 'zoro', name: 'Zenith' },
|
||||
{ id: 'animepahe', name: 'Vega' },
|
||||
{ id: 'animekai', name: 'Cosmos' },
|
||||
{ id: 'crysoline', name: 'CrysOline' },
|
||||
{ id: 'orion', name: 'Orion' },
|
||||
{ id: 'pulsar', name: 'Pulsar' }
|
||||
];
|
||||
|
||||
function parseRscResponse(text) {
|
||||
for (const line of text.split('\n')) {
|
||||
if (line.startsWith('1:')) {
|
||||
try {
|
||||
return JSON.parse(line.slice(2));
|
||||
} catch { }
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function cleanUrl(rawUrl) {
|
||||
if (!rawUrl) return '';
|
||||
return rawUrl.replace(/^https:\/\/api\.luna-stream\.mehttps:\/\/api\.luna-stream\.me/, 'https://api.luna-stream.me');
|
||||
}
|
||||
|
||||
async function fetchProviderStreams(anilistId, epNum, provider, subtype) {
|
||||
try {
|
||||
const text = await fetchText(WATCH_URL, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'User-Agent': USER_AGENT,
|
||||
'Next-Action': ACTION_FETCH_SOURCES,
|
||||
'Content-Type': 'text/plain;charset=UTF-8',
|
||||
'Accept': 'text/x-component',
|
||||
'Referer': WATCH_URL,
|
||||
'Origin': 'https://luna-stream.me'
|
||||
},
|
||||
body: JSON.stringify([anilistId, provider.id, String(epNum), epNum, subtype, null]),
|
||||
signal: AbortSignal.timeout(10000)
|
||||
});
|
||||
|
||||
if (!text) return [];
|
||||
|
||||
const parsed = parseRscResponse(text);
|
||||
if (!parsed || !Array.isArray(parsed.sources)) return [];
|
||||
|
||||
const streams = [];
|
||||
for (const s of parsed.sources) {
|
||||
if (!s || !s.url) continue;
|
||||
|
||||
const url = cleanUrl(s.url);
|
||||
const format = String(s.type || '').toLowerCase();
|
||||
const isM3U8 = format === 'hls' || format === 'm3u8' || url.includes('.m3u8') || url.includes('.txt');
|
||||
|
||||
streams.push({
|
||||
url,
|
||||
server: `Luna (${provider.name})`,
|
||||
quality: String(s.quality || 'Auto'),
|
||||
type: isM3U8 ? 'hls' : 'mp4',
|
||||
headers: {
|
||||
'User-Agent': USER_AGENT,
|
||||
'Referer': 'https://luna-stream.me/'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return streams;
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export async function getStream(args) {
|
||||
const { id, s, e, ep, audio, server, tmdbApiKey } = args;
|
||||
const isTv = s != null && e != null;
|
||||
const mediaType = isTv ? 'tv' : 'movie';
|
||||
|
||||
let anilistId = null;
|
||||
try {
|
||||
const info = await getTmdbInfo(tmdbApiKey, id, mediaType, s);
|
||||
anilistId = await tmdbToAnilist(id, mediaType, s, info?.titles || [], info?.year);
|
||||
} catch { }
|
||||
|
||||
if (!anilistId) {
|
||||
anilistId = Number(id);
|
||||
}
|
||||
|
||||
if (!anilistId || isNaN(anilistId)) return null;
|
||||
|
||||
const epNum = Number(e || ep || 1);
|
||||
const resolvedSubtype = audio === 'dub' ? 'dub' : 'sub';
|
||||
|
||||
let targets = PROVIDERS;
|
||||
if (server && server !== 'all') {
|
||||
const clean = server.toLowerCase().replace('luna (', '').replace(')', '').replace('luna', '').trim();
|
||||
targets = PROVIDERS.filter(p => p.id.toLowerCase() === clean || p.name.toLowerCase().includes(clean));
|
||||
if (!targets.length) targets = PROVIDERS;
|
||||
}
|
||||
|
||||
const settled = await Promise.allSettled(
|
||||
targets.map(p => fetchProviderStreams(anilistId, epNum, p, resolvedSubtype))
|
||||
);
|
||||
|
||||
const allUrls = [];
|
||||
for (const r of settled) {
|
||||
if (r.status === 'fulfilled' && Array.isArray(r.value)) {
|
||||
allUrls.push(...r.value);
|
||||
}
|
||||
}
|
||||
|
||||
return allUrls.length ? { allUrls } : null;
|
||||
}
|
||||
|
||||
export async function getSources() {
|
||||
return PROVIDERS.map(p => `Luna (${p.name})`);
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
import { fetchJson, USER_AGENT } from '../utils/helpers.js';
|
||||
|
||||
const API_BASE = 'https://api.meowtv.ru';
|
||||
const REFERER = 'https://meowtv.ru/';
|
||||
const ENC_DEC_API = 'https://enc-dec.app/api/dec-meowtv';
|
||||
|
||||
const BASE_HEADERS = {
|
||||
'User-Agent': USER_AGENT,
|
||||
'Accept': 'application/json',
|
||||
'Referer': REFERER,
|
||||
'Origin': 'https://meowtv.ru',
|
||||
'Accept-Language': 'en-US,en;q=0.9'
|
||||
};
|
||||
|
||||
const SERVERS = [
|
||||
'dcloud', 'acme', 'tcloud', 'ipcloud', 'turkce', 'hindiv3'
|
||||
];
|
||||
|
||||
const TIMEOUT_MS = 15000;
|
||||
|
||||
export const VERIFY_HEADERS = { 'User-Agent': USER_AGENT, 'Referer': REFERER, 'Origin': 'https://meowtv.ru' };
|
||||
|
||||
let cachedTicket = null;
|
||||
let ticketExpiry = 0;
|
||||
|
||||
/**
|
||||
* Fetches a stream ticket or returns a cached valid ticket.
|
||||
*/
|
||||
async function getTicket(forceRefresh = false) {
|
||||
const now = Date.now();
|
||||
if (!forceRefresh && cachedTicket && now < ticketExpiry - 10000) {
|
||||
return cachedTicket;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetchJson(`${API_BASE}/streams/ticket`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
...BASE_HEADERS,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({})
|
||||
});
|
||||
|
||||
if (res?.ticket) {
|
||||
cachedTicket = res.ticket;
|
||||
ticketExpiry = typeof res.exp === 'number' ? res.exp : now + 60000;
|
||||
return cachedTicket;
|
||||
}
|
||||
} catch {
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
async function decryptPayload(payload) {
|
||||
try {
|
||||
const response = await fetchJson(ENC_DEC_API, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ data: payload })
|
||||
});
|
||||
|
||||
if (response?.status !== 200 || !response?.result) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return typeof response.result === 'string' ? JSON.parse(response.result) : response.result;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getStream({ id, s, e, server }) {
|
||||
try {
|
||||
const isTv = !!s;
|
||||
let targets = SERVERS;
|
||||
if (server && server !== 'all') {
|
||||
const cleanName = server.replace('MeowTV - ', '');
|
||||
targets = SERVERS.includes(cleanName) ? [cleanName] : [targets[0]];
|
||||
}
|
||||
|
||||
const ticket = await getTicket();
|
||||
const requestHeaders = {
|
||||
...BASE_HEADERS,
|
||||
...(ticket ? { 'x-stream-ticket': ticket } : {})
|
||||
};
|
||||
|
||||
const results = await Promise.all(targets.map(async srv => {
|
||||
try {
|
||||
return await Promise.race([
|
||||
(async () => {
|
||||
const path = isTv
|
||||
? `/streams/tv/${id}/${s}/${e}?s=${encodeURIComponent(srv)}`
|
||||
: `/streams/movie/${id}?s=${encodeURIComponent(srv)}`;
|
||||
|
||||
const payload = await fetchJson(`${API_BASE}${path}`, {
|
||||
headers: requestHeaders
|
||||
});
|
||||
|
||||
if (!payload) return null;
|
||||
|
||||
const data = await decryptPayload(payload);
|
||||
if (!data) return null;
|
||||
|
||||
const urls = [];
|
||||
if (data?.url?.startsWith('http')) {
|
||||
urls.push({
|
||||
url: data.url,
|
||||
server: `MeowTV - ${srv}`,
|
||||
});
|
||||
}
|
||||
|
||||
if (Array.isArray(data?.streams)) {
|
||||
for (const stream of data.streams) {
|
||||
if (stream?.url?.startsWith('http')) {
|
||||
urls.push({
|
||||
url: stream.url,
|
||||
server: `MeowTV - ${srv} (${stream.language || 'Unknown'})`,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return urls;
|
||||
})(),
|
||||
new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout exceeded')), TIMEOUT_MS))
|
||||
]);
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
}));
|
||||
|
||||
const allUrls = results.filter(Boolean).flat();
|
||||
return allUrls.length ? { allUrls } : null;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getSources() {
|
||||
return SERVERS.map(s => `MeowTV - ${s}`);
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
import { USER_AGENT, fetchJson, getTmdbInfo } from '../utils/helpers.js';
|
||||
|
||||
const BASE_URL = 'https://providers.peestream.in';
|
||||
|
||||
export async function getStream(args) {
|
||||
const { id, s, e, server, tmdbApiKey } = args;
|
||||
const isTv = s != null && e != null;
|
||||
const type = isTv ? 'tv' : 'movie';
|
||||
|
||||
let title = args.title || '';
|
||||
let releaseYear = args.year || '';
|
||||
let imdbId = args.imdbId || '';
|
||||
|
||||
if (!title) {
|
||||
try {
|
||||
const info = await getTmdbInfo(id, type, s, tmdbApiKey);
|
||||
if (info) {
|
||||
title = info.title || '';
|
||||
if (info.year) releaseYear = info.year;
|
||||
if (info.imdbId) imdbId = info.imdbId;
|
||||
}
|
||||
} catch { }
|
||||
}
|
||||
|
||||
const queryParams = new URLSearchParams({
|
||||
type,
|
||||
tmdbId: String(id),
|
||||
title: title || String(id)
|
||||
});
|
||||
if (releaseYear) queryParams.set('releaseYear', String(releaseYear));
|
||||
if (imdbId) queryParams.set('imdbId', String(imdbId));
|
||||
if (isTv) {
|
||||
queryParams.set('season', String(s));
|
||||
queryParams.set('episode', String(e));
|
||||
}
|
||||
|
||||
const allUrls = [];
|
||||
|
||||
try {
|
||||
const scrapeUrl = `${BASE_URL}/scrape?${queryParams.toString()}`;
|
||||
const res = await fetch(scrapeUrl, {
|
||||
headers: {
|
||||
'User-Agent': USER_AGENT,
|
||||
'Accept': 'text/event-stream',
|
||||
'Referer': `${BASE_URL}/`
|
||||
},
|
||||
signal: AbortSignal.timeout(15000)
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
const text = await res.text();
|
||||
const events = text.split('\n\n');
|
||||
for (const ev of events) {
|
||||
if (ev.includes('event: completed')) {
|
||||
const dataMatch = ev.match(/data:\s*(.+)/);
|
||||
if (dataMatch) {
|
||||
try {
|
||||
const parsed = JSON.parse(dataMatch[1].trim());
|
||||
const stream = parsed.stream;
|
||||
const streamUrl = stream?.playlist || stream?.url || stream?.file;
|
||||
if (streamUrl) {
|
||||
allUrls.push({
|
||||
url: streamUrl,
|
||||
server: `PeeStream - ${parsed.sourceId || 'Poseidon'}`,
|
||||
quality: 'Auto',
|
||||
type: stream.type === 'hls' || streamUrl.includes('.m3u8') ? 'hls' : 'mp4',
|
||||
headers: stream.headers || {
|
||||
'User-Agent': USER_AGENT
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch { }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch { }
|
||||
|
||||
if (!allUrls.length) {
|
||||
try {
|
||||
const searchUrl = `${BASE_URL}/api/search?q=${encodeURIComponent(title || id)}&type=${type}&tmdbId=${id}${isTv ? `&season=${s}&episode=${e}` : ''}`;
|
||||
const data = await fetchJson(searchUrl, {
|
||||
headers: {
|
||||
'User-Agent': USER_AGENT,
|
||||
'Accept': 'application/json',
|
||||
'Referer': `${BASE_URL}/`
|
||||
},
|
||||
signal: AbortSignal.timeout(10000)
|
||||
});
|
||||
|
||||
if (data && Array.isArray(data.results)) {
|
||||
for (const result of data.results) {
|
||||
const pName = result.providerName || result.provider || 'PeeStream';
|
||||
for (const stream of result.streams || []) {
|
||||
if (!stream || !stream.url) continue;
|
||||
allUrls.push({
|
||||
url: stream.url,
|
||||
server: `PeeStream - ${stream.name || pName}`,
|
||||
quality: stream.quality || 'Auto',
|
||||
type: stream.type === 'm3u8' || stream.url.includes('.m3u8') ? 'hls' : 'mp4',
|
||||
headers: stream.headers || {
|
||||
'User-Agent': USER_AGENT
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch { }
|
||||
}
|
||||
|
||||
if (server && server !== 'all') {
|
||||
const clean = server.toLowerCase().replace('peestream - ', '');
|
||||
const filtered = allUrls.filter(u => u.server.toLowerCase().includes(clean));
|
||||
if (filtered.length) return { allUrls: filtered };
|
||||
}
|
||||
|
||||
return allUrls.length ? { allUrls } : null;
|
||||
}
|
||||
|
||||
export async function getSources() {
|
||||
return ['PeeStream - vaplayer'];
|
||||
}
|
||||
@@ -49,9 +49,9 @@ export async function getStream({ id, s, e, sdk }) {
|
||||
'User-Agent': ua,
|
||||
Referer: `${BASE_URL}/`,
|
||||
Origin: BASE_URL,
|
||||
Accept: 'application/x-ndjson, text/plain, */*',
|
||||
Accept: 'text/event-stream, application/x-ndjson, text/plain, */*',
|
||||
},
|
||||
signal: AbortSignal.timeout(18000),
|
||||
signal: AbortSignal.timeout(90000),
|
||||
});
|
||||
|
||||
if (!res.ok) return null;
|
||||
@@ -89,7 +89,9 @@ export async function getStream({ id, s, e, sdk }) {
|
||||
|
||||
for (const line of lines) {
|
||||
try {
|
||||
const item = JSON.parse(line);
|
||||
const raw = line.startsWith('data:') ? line.slice(5).trim() : line;
|
||||
if (!raw || raw === '[DONE]') continue;
|
||||
const item = JSON.parse(raw);
|
||||
const streamUrl = String(item?.url || '').trim();
|
||||
if (!streamUrl || !streamUrl.startsWith('http') || seenUrls.has(streamUrl)) continue;
|
||||
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
import { getUA } from '../utils/helpers.js';
|
||||
|
||||
const ADDON_BASE = 'https://stremio.vadapav.mov';
|
||||
|
||||
async function resolveImdbId(sdk, id, isTv) {
|
||||
const apiKey = sdk?.tmdbApiKey || process.env.TMDB_API_KEY;
|
||||
if (!apiKey) return null;
|
||||
const type = isTv ? 'tv' : 'movie';
|
||||
try {
|
||||
const res = await fetch(`https://api.themoviedb.org/3/${type}/${id}?api_key=${apiKey}&append_to_response=external_ids`, {
|
||||
signal: AbortSignal.timeout(6000),
|
||||
});
|
||||
if (!res.ok) return null;
|
||||
const data = await res.json();
|
||||
return data.imdb_id || data.external_ids?.imdb_id || null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getStream({ id, s, e, sdk }) {
|
||||
const isTv = Boolean(s && e);
|
||||
const ua = getUA();
|
||||
|
||||
const imdbId = await resolveImdbId(sdk, id, isTv);
|
||||
const targetIds = [];
|
||||
if (imdbId && imdbId.startsWith('tt')) {
|
||||
targetIds.push(imdbId);
|
||||
}
|
||||
targetIds.push(`tmdb:${id}`);
|
||||
|
||||
const seenUrls = new Set();
|
||||
const allUrls = [];
|
||||
|
||||
const defaultHeaders = {
|
||||
'User-Agent': ua,
|
||||
Accept: 'application/json, text/plain, */*',
|
||||
};
|
||||
|
||||
for (const targetId of targetIds) {
|
||||
const streamPath = isTv
|
||||
? `${ADDON_BASE}/stream/series/${targetId}:${s}:${e}.json`
|
||||
: `${ADDON_BASE}/stream/movie/${targetId}.json`;
|
||||
|
||||
try {
|
||||
const res = await fetch(streamPath, {
|
||||
headers: defaultHeaders,
|
||||
signal: AbortSignal.timeout(12000),
|
||||
});
|
||||
|
||||
if (!res.ok) continue;
|
||||
const data = await res.json();
|
||||
|
||||
if (Array.isArray(data?.streams) && data.streams.length > 0) {
|
||||
for (const item of data.streams) {
|
||||
if (!item || typeof item !== 'object') continue;
|
||||
const url = String(item.url || '').trim();
|
||||
if (!url || !url.startsWith('http') || seenUrls.has(url)) continue;
|
||||
|
||||
seenUrls.add(url);
|
||||
|
||||
const rawTitle = item.title ? String(item.title).trim() : '';
|
||||
const rawName = item.name ? String(item.name).trim() : 'vadapav.mov';
|
||||
const label = rawTitle || rawName;
|
||||
|
||||
allUrls.push({
|
||||
url,
|
||||
label,
|
||||
headers: defaultHeaders,
|
||||
});
|
||||
}
|
||||
|
||||
if (allUrls.length > 0) break;
|
||||
}
|
||||
} catch { }
|
||||
}
|
||||
|
||||
if (allUrls.length === 0) return null;
|
||||
|
||||
return {
|
||||
url: allUrls[0].url,
|
||||
allUrls,
|
||||
headers: allUrls[0].headers,
|
||||
};
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
import { fetchJson, fetchText, USER_AGENT } from '../utils/helpers.js';
|
||||
|
||||
const API_BASE = 'https://enc-dec.app/api';
|
||||
const HEADERS = { 'User-Agent': USER_AGENT, 'Referer': 'https://vidcore.net/', 'X-Requested-With': 'XMLHttpRequest' };
|
||||
|
||||
async function getDynamicServers(id, s, e) {
|
||||
try {
|
||||
const html = await fetchText(s != null && e != null ? `https://vidcore.net/tv/${id}/${s}/${e}/` : `https://vidcore.net/movie/${id}/`, { headers: HEADERS });
|
||||
|
||||
const match = html.match(/\\"(?:en|token)\\":\\"(.*?)\\"/) || html.match(/"(?:en|token)":"(.*?)"/);
|
||||
if (!match?.[1]) return null;
|
||||
|
||||
const encData = await fetchJson(`${API_BASE}/enc-vidcore?text=${encodeURIComponent(match[1])}`);
|
||||
if (!encData?.result) return null;
|
||||
const { servers: serversUrl, stream: streamUrl, token } = encData.result;
|
||||
const reqHeaders = { ...HEADERS, 'X-CSRF-Token': token };
|
||||
const serversEncrypted = await fetchText(serversUrl, { method: 'POST', headers: reqHeaders });
|
||||
const decData = await fetchJson(`${API_BASE}/dec-vidcore`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text: serversEncrypted }) });
|
||||
return decData?.result ? { servers: decData.result, streamUrl, reqHeaders } : null;
|
||||
} catch { return null; }
|
||||
}
|
||||
|
||||
export async function getStream({ id, s, e }) {
|
||||
const data = await getDynamicServers(id, s, e);
|
||||
if (!data?.servers?.length) return null;
|
||||
for (const srv of data.servers) {
|
||||
try {
|
||||
const streamEncrypted = await fetchText(`${data.streamUrl}/${srv.data}`, { method: 'POST', headers: data.reqHeaders });
|
||||
const decData = await fetchJson(`${API_BASE}/dec-vidcore`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text: streamEncrypted }) });
|
||||
const url = decData?.result?.url;
|
||||
if (url) {
|
||||
const r = await fetch(url, { headers: { ...HEADERS, 'Range': 'bytes=0-511' }, signal: AbortSignal.timeout(5000) });
|
||||
if (r.ok || r.status === 206) return { url, subtitles: (decData.result.captions || []).map(c => ({ url: c.file, lang: c.label || 'Unknown' })), headers: { ...HEADERS, 'Origin': 'https://vidcore.net' }, server: srv.name };
|
||||
}
|
||||
} catch { }
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export async function getSources(args) {
|
||||
const data = await getDynamicServers(args.id, args.s, args.e);
|
||||
return data?.servers ? data.servers.map(s => s.name) : [];
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
import { fetchJson, fetchText, USER_AGENT } from '../utils/helpers.js';
|
||||
|
||||
const API_BASE = 'https://enc-dec.app/api';
|
||||
const DOMAIN = 'https://vidfast.vc';
|
||||
const HEADERS = { 'User-Agent': USER_AGENT, 'Referer': `${DOMAIN}/`, 'X-Requested-With': 'XMLHttpRequest' };
|
||||
|
||||
async function getVidfastMeta(id, s, e) {
|
||||
try {
|
||||
const embedUrl = s != null && e != null ? `${DOMAIN}/tv/${id}/${s}/${e}/` : `${DOMAIN}/movie/${id}/`;
|
||||
const html = await fetchText(embedUrl, { headers: { 'User-Agent': USER_AGENT } });
|
||||
|
||||
const match = html.match(/\\"(?:en|token)\\":\\"(.*?)\\"/) || html.match(/"(?:en|token)":"(.*?)"/);
|
||||
if (!match?.[1]) return null;
|
||||
|
||||
const encData = await fetchJson(`${API_BASE}/enc-vidfast?text=${encodeURIComponent(match[1])}`);
|
||||
if (encData.status !== 200 || !encData.result) return null;
|
||||
|
||||
const { servers: serversUrl, stream: streamUrl, token } = encData.result;
|
||||
const reqHeaders = { ...HEADERS, 'X-CSRF-Token': token };
|
||||
|
||||
const serversEncrypted = await fetchText(serversUrl, { method: 'POST', headers: reqHeaders });
|
||||
const decServersData = await fetchJson(`${API_BASE}/dec-vidfast`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text: serversEncrypted }) });
|
||||
|
||||
if (decServersData.status !== 200 || !decServersData.result) return null;
|
||||
return { servers: decServersData.result, streamUrl, reqHeaders };
|
||||
} catch { return null; }
|
||||
}
|
||||
|
||||
export async function getStream({ id, s, e, server }) {
|
||||
const meta = await getVidfastMeta(id, s, e);
|
||||
if (!meta?.servers?.length) return null;
|
||||
const { servers, streamUrl, reqHeaders } = meta;
|
||||
let targets = servers;
|
||||
if (server && server !== 'all') {
|
||||
const cleanName = server.replace('VidFast - ', '');
|
||||
targets = servers.filter(srv => srv.name === cleanName);
|
||||
if (!targets.length) targets = servers;
|
||||
}
|
||||
const results = await Promise.allSettled(targets.map(async srv => {
|
||||
const streamEncrypted = await fetchText(`${streamUrl}/${srv.data}`, { method: 'POST', headers: reqHeaders });
|
||||
const decStreamData = await fetchJson(`${API_BASE}/dec-vidfast`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text: streamEncrypted }) });
|
||||
if (decStreamData.status !== 200 || !decStreamData.result?.url) throw new Error();
|
||||
return { url: decStreamData.result.url, server: `VidFast - ${srv.name || 'Server'}`, type: decStreamData.result.url.includes('.m3u8') ? 'hls' : 'mp4', subtitles: (decStreamData.result.captions || []).map(c => ({ url: c.file, lang: c.label || 'Unknown' })), headers: { ...HEADERS, 'Origin': DOMAIN } };
|
||||
}));
|
||||
const allUrls = [];
|
||||
for (const r of results) if (r.status === 'fulfilled') allUrls.push(r.value);
|
||||
return allUrls.length ? { allUrls } : null;
|
||||
}
|
||||
|
||||
export async function getSources(args) {
|
||||
const meta = await getVidfastMeta(args.id, args.s, args.e);
|
||||
return meta?.servers ? meta.servers.map(srv => `VidFast - ${srv.name}`) : [];
|
||||
}
|
||||
@@ -1,249 +0,0 @@
|
||||
import { getUA } from '../utils/helpers.js';
|
||||
|
||||
const API_BASE = 'https://vuflix.co';
|
||||
const REFERER = 'https://vuflix.co/';
|
||||
const ORIGIN = 'https://vuflix.co';
|
||||
|
||||
let cachedProviders = null;
|
||||
let lastProvidersFetch = 0;
|
||||
|
||||
const FALLBACK_PROVIDERS = [
|
||||
{ id: 'vsembed', name: 'Sigma' },
|
||||
{ id: 'moonflix', name: 'Source 40' },
|
||||
{ id: 'megasource', name: 'Source 39' },
|
||||
{ id: 'hdghar', name: 'Source 44' },
|
||||
{ id: 'moviebox', name: 'Pi' },
|
||||
{ id: 'cineplay', name: '4K' },
|
||||
{ id: 'huhu', name: 'Beta' },
|
||||
{ id: 'bingr', name: 'Upsilon' },
|
||||
{ id: 'onlyflix', name: 'Gamma' },
|
||||
{ id: 'vaplayer', name: 'Alpha' },
|
||||
{ id: 'flixhqz', name: 'Gamma' },
|
||||
{ id: 'castle', name: 'Source 40' },
|
||||
{ id: 'cinejoy', name: '4K2' },
|
||||
{ id: 'filesun', name: 'Tau' },
|
||||
{ id: 'yoru', name: 'Yoru' },
|
||||
];
|
||||
|
||||
async function getProviders(ua) {
|
||||
if (cachedProviders && Date.now() - lastProvidersFetch < 900000) {
|
||||
return cachedProviders;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/api/player/providers`, {
|
||||
headers: {
|
||||
'User-Agent': ua,
|
||||
Referer: REFERER,
|
||||
Origin: ORIGIN,
|
||||
Accept: 'application/json, text/plain, */*',
|
||||
},
|
||||
signal: AbortSignal.timeout(6000),
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
if (data?.ok && Array.isArray(data.providers)) {
|
||||
const list = data.providers
|
||||
.filter(p => p && typeof p.id === 'string' && p.id.trim().length > 0)
|
||||
.map(p => ({
|
||||
id: p.id.trim(),
|
||||
name: p.publicLabel || p.providerName || p.name || p.id,
|
||||
}));
|
||||
|
||||
if (list.length > 0) {
|
||||
cachedProviders = list;
|
||||
lastProvidersFetch = Date.now();
|
||||
return list;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch { }
|
||||
|
||||
return FALLBACK_PROVIDERS;
|
||||
}
|
||||
|
||||
function unwrapUrl(rawUrl, ua) {
|
||||
const defaultHeaders = {
|
||||
'User-Agent': ua,
|
||||
Referer: REFERER,
|
||||
Origin: ORIGIN,
|
||||
};
|
||||
|
||||
if (!rawUrl || typeof rawUrl !== 'string') {
|
||||
return { url: '', headers: defaultHeaders };
|
||||
}
|
||||
|
||||
if (!rawUrl.includes('v-relay?t=') && !rawUrl.includes('a-relay?t=')) {
|
||||
return { url: rawUrl, headers: defaultHeaders };
|
||||
}
|
||||
|
||||
try {
|
||||
const parsedUrl = new URL(rawUrl);
|
||||
const t = parsedUrl.searchParams.get('t');
|
||||
if (!t) return { url: rawUrl, headers: defaultHeaders };
|
||||
|
||||
let b64 = t.replace(/-/g, '+').replace(/_/g, '/');
|
||||
while (b64.length % 4 !== 0) {
|
||||
b64 += '=';
|
||||
}
|
||||
|
||||
const jsonStr = Buffer.from(b64, 'base64').toString('utf8');
|
||||
const parsed = JSON.parse(jsonStr);
|
||||
|
||||
if (parsed && typeof parsed === 'object') {
|
||||
const directUrl = (parsed.u || '').trim();
|
||||
const headersMap = { ...defaultHeaders };
|
||||
|
||||
if (parsed.h && typeof parsed.h === 'object') {
|
||||
for (const [key, value] of Object.entries(parsed.h)) {
|
||||
headersMap[key] = String(value);
|
||||
}
|
||||
}
|
||||
|
||||
if (directUrl) {
|
||||
return { url: directUrl, headers: headersMap };
|
||||
}
|
||||
}
|
||||
} catch { }
|
||||
|
||||
return { url: rawUrl, headers: defaultHeaders };
|
||||
}
|
||||
|
||||
export async function getStream({ id, s, e }) {
|
||||
const isTv = Boolean(s && e);
|
||||
const mediaType = isTv ? 'tv' : 'movie';
|
||||
const ua = getUA();
|
||||
|
||||
const baseParams = new URLSearchParams({
|
||||
type: mediaType,
|
||||
tmdbId: String(id),
|
||||
});
|
||||
|
||||
if (isTv) {
|
||||
baseParams.set('season', String(s));
|
||||
baseParams.set('episode', String(e));
|
||||
}
|
||||
|
||||
const providers = await getProviders(ua);
|
||||
const seenUrls = new Set();
|
||||
const allUrls = [];
|
||||
|
||||
const defaultHeaders = {
|
||||
'User-Agent': ua,
|
||||
Referer: REFERER,
|
||||
Origin: ORIGIN,
|
||||
Accept: 'application/json, text/plain, */*',
|
||||
};
|
||||
|
||||
const tasks = providers.map(async (prov) => {
|
||||
try {
|
||||
const url = `${API_BASE}/api/player/sources?${baseParams.toString()}&provider=${encodeURIComponent(prov.id)}`;
|
||||
const res = await fetch(url, {
|
||||
headers: defaultHeaders,
|
||||
signal: AbortSignal.timeout(8000),
|
||||
});
|
||||
|
||||
if (!res.ok) return;
|
||||
const data = await res.json();
|
||||
if (!data?.ok || !Array.isArray(data.sources)) return;
|
||||
|
||||
for (const item of data.sources) {
|
||||
if (!item || typeof item !== 'object') continue;
|
||||
|
||||
const providerName = item.providerName || item.publicLabel || prov.name;
|
||||
const primaryRawUrl = (item.url || '').trim();
|
||||
const itemType = (item.type || 'hls').toLowerCase();
|
||||
const itemLabel = item.label || '';
|
||||
|
||||
if (Array.isArray(item.qualities) && item.qualities.length > 0) {
|
||||
for (const q of item.qualities) {
|
||||
if (!q || typeof q !== 'object') continue;
|
||||
const qRawUrl = (q.url || '').trim();
|
||||
if (!qRawUrl) continue;
|
||||
|
||||
const unwrapped = unwrapUrl(qRawUrl, ua);
|
||||
if (!unwrapped.url || seenUrls.has(unwrapped.url)) continue;
|
||||
seenUrls.add(unwrapped.url);
|
||||
|
||||
const qQuality = q.quality || 'Auto';
|
||||
allUrls.push({
|
||||
url: unwrapped.url,
|
||||
label: `${providerName} · ${qQuality}`,
|
||||
headers: unwrapped.headers,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (Array.isArray(item.candidates) && item.candidates.length > 0) {
|
||||
let candIndex = 1;
|
||||
for (const c of item.candidates) {
|
||||
if (!c || typeof c !== 'object') continue;
|
||||
const cRawUrl = (c.url || '').trim();
|
||||
if (!cRawUrl) continue;
|
||||
|
||||
const unwrapped = unwrapUrl(cRawUrl, ua);
|
||||
if (!unwrapped.url || seenUrls.has(unwrapped.url)) continue;
|
||||
seenUrls.add(unwrapped.url);
|
||||
|
||||
const cQuality = c.quality || '1080p';
|
||||
allUrls.push({
|
||||
url: unwrapped.url,
|
||||
label: `${providerName} · Mirror ${candIndex} · ${cQuality}`,
|
||||
headers: unwrapped.headers,
|
||||
});
|
||||
candIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
if (Array.isArray(item.audioTracks) && item.audioTracks.length > 0) {
|
||||
for (const a of item.audioTracks) {
|
||||
if (!a || typeof a !== 'object') continue;
|
||||
const aRawUrl = (a.switchUrl || a.url || '').trim();
|
||||
if (!aRawUrl) continue;
|
||||
|
||||
const unwrapped = unwrapUrl(aRawUrl, ua);
|
||||
if (!unwrapped.url || seenUrls.has(unwrapped.url)) continue;
|
||||
seenUrls.add(unwrapped.url);
|
||||
|
||||
const rawLabel = (a.label || a.name || a.language || 'Audio').trim();
|
||||
const cleanLabel = rawLabel.replace(/\s*audio\s*$/i, '').trim() || 'Audio';
|
||||
|
||||
allUrls.push({
|
||||
url: unwrapped.url,
|
||||
label: `${providerName} · ${cleanLabel} Audio`,
|
||||
headers: unwrapped.headers,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (primaryRawUrl) {
|
||||
const unwrapped = unwrapUrl(primaryRawUrl, ua);
|
||||
if (unwrapped.url && !seenUrls.has(unwrapped.url)) {
|
||||
seenUrls.add(unwrapped.url);
|
||||
|
||||
let displayQuality = item.quality || (itemType === 'mp4' ? 'MP4' : 'HD');
|
||||
let cleanLabel = itemLabel || providerName;
|
||||
let label = cleanLabel.startsWith('[') ? cleanLabel : `${providerName} · ${displayQuality}`;
|
||||
|
||||
allUrls.push({
|
||||
url: unwrapped.url,
|
||||
label,
|
||||
headers: unwrapped.headers,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch { }
|
||||
});
|
||||
|
||||
await Promise.all(tasks);
|
||||
|
||||
if (allUrls.length === 0) return null;
|
||||
|
||||
return {
|
||||
url: allUrls[0].url,
|
||||
allUrls,
|
||||
headers: allUrls[0].headers,
|
||||
};
|
||||
}
|
||||
@@ -13,7 +13,7 @@ const CONFIG = {
|
||||
TV_ID: "37854", // TV TMDB ID to test, for anime I recommend using a random ID like 37854
|
||||
TV_SEASON: "1", // TV Season to test
|
||||
TV_EPISODE: "1", // TV Episode to test
|
||||
STREAM_SOURCE: "", // Source key to test
|
||||
STREAM_SOURCE: "dulo", // Source key to test
|
||||
|
||||
TEST_SUBTITLE: false, // Toggle subtitle testing
|
||||
TEST_DOWNLOAD: false, // Toggle download testing
|
||||
|
||||
Reference in New Issue
Block a user