mirror of
https://gitlab.com/vyla-entertainment/sdk.git
synced 2026-11-13 13:47:25 -05:00
removed dulo, and zenox, added fmovies, torrentio, vidbolt, and willow
This commit is contained in:
114
src/config.js
114
src/config.js
@@ -182,25 +182,6 @@ export const SOURCES = [
|
||||
}],
|
||||
},
|
||||
|
||||
{
|
||||
key: 'dulo',
|
||||
label: 'Dulo',
|
||||
sourceFile: 'dulo',
|
||||
proxyParam: 'dl',
|
||||
timeout: 25000,
|
||||
jitter: 500,
|
||||
retries: 2,
|
||||
multiUrl: true,
|
||||
skipProxy: true,
|
||||
cdnHeaders: [{
|
||||
pattern: /dulo\.mov/i,
|
||||
headers: {
|
||||
Referer: 'https://dulo.mov/',
|
||||
Origin: 'https://dulo.mov',
|
||||
},
|
||||
}],
|
||||
},
|
||||
|
||||
{
|
||||
key: 'flaxmovies',
|
||||
label: 'FlaxMovies',
|
||||
@@ -220,6 +201,25 @@ export const SOURCES = [
|
||||
}],
|
||||
},
|
||||
|
||||
{
|
||||
key: 'fmovies4u',
|
||||
label: 'Fmovies4U',
|
||||
sourceFile: 'fmovies4u',
|
||||
proxyParam: 'fm4u',
|
||||
timeout: 25000,
|
||||
jitter: 500,
|
||||
retries: 2,
|
||||
multiUrl: true,
|
||||
skipProxy: true,
|
||||
cdnHeaders: [{
|
||||
pattern: /fmfau\.com/i,
|
||||
headers: {
|
||||
Referer: 'https://fmfau.com/',
|
||||
Origin: 'https://fmfau.com',
|
||||
},
|
||||
}],
|
||||
},
|
||||
|
||||
{
|
||||
key: 'framextv',
|
||||
label: 'FrameXTV',
|
||||
@@ -536,6 +536,25 @@ export const SOURCES = [
|
||||
}],
|
||||
},
|
||||
|
||||
{
|
||||
key: 'torrentio',
|
||||
label: 'Torrentio',
|
||||
sourceFile: 'torrentio',
|
||||
proxyParam: 'tio',
|
||||
timeout: 30000,
|
||||
jitter: 500,
|
||||
retries: 1,
|
||||
multiUrl: true,
|
||||
skipProxy: true,
|
||||
cdnHeaders: [{
|
||||
pattern: /torrentio\.to|fcxmb\.com/i,
|
||||
headers: {
|
||||
Referer: 'https://stream.vidplay.to/',
|
||||
Origin: 'https://stream.vidplay.to',
|
||||
},
|
||||
}],
|
||||
},
|
||||
|
||||
{
|
||||
key: 'vidapi',
|
||||
label: 'VidAPI',
|
||||
@@ -547,6 +566,25 @@ export const SOURCES = [
|
||||
multiUrl: true
|
||||
},
|
||||
|
||||
{
|
||||
key: 'vidbolt',
|
||||
label: 'VidBolt',
|
||||
sourceFile: 'vidbolt',
|
||||
proxyParam: 'vb',
|
||||
timeout: 25000,
|
||||
jitter: 500,
|
||||
retries: 2,
|
||||
multiUrl: true,
|
||||
skipProxy: true,
|
||||
cdnHeaders: [{
|
||||
pattern: /rousav\.tech|vidbolt\.xyz/i,
|
||||
headers: {
|
||||
Referer: 'https://vidbolt.xyz/',
|
||||
Origin: 'https://vidbolt.xyz',
|
||||
},
|
||||
}],
|
||||
},
|
||||
|
||||
{
|
||||
key: 'vidcore',
|
||||
label: 'VidCore',
|
||||
@@ -695,6 +733,25 @@ export const SOURCES = [
|
||||
skipProxy: true,
|
||||
},
|
||||
|
||||
{
|
||||
key: 'willow',
|
||||
label: 'Willow',
|
||||
sourceFile: 'willow',
|
||||
proxyParam: 'wl',
|
||||
timeout: 25000,
|
||||
jitter: 500,
|
||||
retries: 2,
|
||||
multiUrl: true,
|
||||
skipProxy: true,
|
||||
cdnHeaders: [{
|
||||
pattern: /hscow\.com|downloadwella\.com|willowmovies\.com/i,
|
||||
headers: {
|
||||
Referer: 'https://willowmovies.com/',
|
||||
Origin: 'https://willowmovies.com',
|
||||
},
|
||||
}],
|
||||
},
|
||||
|
||||
{
|
||||
key: 'xdownloader',
|
||||
label: 'XDownloader',
|
||||
@@ -744,25 +801,6 @@ export const SOURCES = [
|
||||
multiUrl: true,
|
||||
},
|
||||
|
||||
{
|
||||
key: 'zenox',
|
||||
label: 'Zenox',
|
||||
sourceFile: 'zenox',
|
||||
proxyParam: 'zx',
|
||||
timeout: 25000,
|
||||
jitter: 500,
|
||||
retries: 2,
|
||||
multiUrl: true,
|
||||
skipProxy: true,
|
||||
cdnHeaders: [{
|
||||
pattern: /zenox\.lol|hls-m3u8\.lol/i,
|
||||
headers: {
|
||||
Referer: 'https://zenox.lol/',
|
||||
Origin: 'https://zenox.lol',
|
||||
},
|
||||
}],
|
||||
},
|
||||
|
||||
];
|
||||
|
||||
export const HEALTH_PROBE_ID = '155';
|
||||
|
||||
@@ -1,135 +0,0 @@
|
||||
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 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(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 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}`
|
||||
: `${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,
|
||||
server: src.label || 'Dulo',
|
||||
label: src.label || 'Dulo',
|
||||
quality: 'Auto',
|
||||
type: streamUrl.includes('.m3u8') ? 'hls' : 'mp4',
|
||||
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,
|
||||
headers: allUrls[0].headers,
|
||||
allUrls,
|
||||
};
|
||||
}
|
||||
143
src/sources/fmovies4u.js
Normal file
143
src/sources/fmovies4u.js
Normal file
@@ -0,0 +1,143 @@
|
||||
const BASE_URL = 'https://fmfau.com';
|
||||
const UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36';
|
||||
|
||||
const HEADERS = {
|
||||
'User-Agent': UA,
|
||||
Referer: `${BASE_URL}/`,
|
||||
Origin: BASE_URL,
|
||||
Accept: 'application/json, text/plain, */*',
|
||||
'Accept-Language': 'en-US,en;q=0.9',
|
||||
};
|
||||
|
||||
const PLAYBACK_HEADERS = {
|
||||
'User-Agent': UA,
|
||||
Referer: `${BASE_URL}/`,
|
||||
Origin: BASE_URL,
|
||||
};
|
||||
|
||||
const PROVIDERS = [
|
||||
'cinetaro',
|
||||
'filmu',
|
||||
'streamingunity',
|
||||
'vaultplayer',
|
||||
'ernax',
|
||||
'phantomflix',
|
||||
'moovie',
|
||||
'watchflux',
|
||||
];
|
||||
|
||||
function extractStreams(data, providerName) {
|
||||
const list = [];
|
||||
if (!data) return list;
|
||||
|
||||
const rawList = Array.isArray(data)
|
||||
? data
|
||||
: Array.isArray(data.sources)
|
||||
? data.sources
|
||||
: Array.isArray(data.streams)
|
||||
? data.streams
|
||||
: Array.isArray(data.data?.sources)
|
||||
? data.data.sources
|
||||
: Array.isArray(data.result?.sources)
|
||||
? data.result.sources
|
||||
: [];
|
||||
|
||||
for (const item of rawList) {
|
||||
const streamUrl = item?.url || item?.file || item?.src || (typeof item === 'string' ? item : null);
|
||||
if (!streamUrl || typeof streamUrl !== 'string' || !streamUrl.startsWith('http')) continue;
|
||||
|
||||
let quality = item.quality || item.label || 'Auto';
|
||||
if (/^\d+$/i.test(quality)) quality = `${quality}p`;
|
||||
|
||||
const type = item.type || item.format || (streamUrl.includes('.m3u8')
|
||||
? 'hls'
|
||||
: streamUrl.includes('.mpd')
|
||||
? 'dash'
|
||||
: 'mp4');
|
||||
|
||||
const server = item.name || item.server || providerName;
|
||||
|
||||
list.push({
|
||||
url: streamUrl,
|
||||
server: `Fmovies4U - ${server}`,
|
||||
quality,
|
||||
type,
|
||||
headers: PLAYBACK_HEADERS,
|
||||
});
|
||||
}
|
||||
|
||||
const singleUrl = data.url || data.file || data.src || data.stream?.url || data.stream;
|
||||
if (typeof singleUrl === 'string' && singleUrl.startsWith('http')) {
|
||||
const type = data.type || (singleUrl.includes('.m3u8')
|
||||
? 'hls'
|
||||
: singleUrl.includes('.mpd')
|
||||
? 'dash'
|
||||
: 'mp4');
|
||||
|
||||
list.push({
|
||||
url: singleUrl,
|
||||
server: `Fmovies4U - ${providerName}`,
|
||||
quality: data.quality || 'Auto',
|
||||
type,
|
||||
headers: PLAYBACK_HEADERS,
|
||||
});
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
export async function getStream(args) {
|
||||
const { id, s, e, title, year, imdbId, audio } = args || {};
|
||||
if (!id) return null;
|
||||
|
||||
const isTv = s != null && e != null;
|
||||
const baseParams = new URLSearchParams({
|
||||
tmdbId: String(id),
|
||||
mediaType: isTv ? 'tv' : 'movie',
|
||||
title: String(title || ''),
|
||||
year: String(year || ''),
|
||||
imdbId: String(imdbId || ''),
|
||||
audioPreference: audio || 'sub',
|
||||
});
|
||||
|
||||
if (isTv) {
|
||||
baseParams.set('season', String(s));
|
||||
baseParams.set('episode', String(e));
|
||||
baseParams.set('seasonTitle', `Season ${s}`);
|
||||
}
|
||||
|
||||
const responses = await Promise.allSettled(
|
||||
PROVIDERS.map(async provider => {
|
||||
const res = await fetch(`${BASE_URL}/api/sources/${provider}?${baseParams.toString()}`, {
|
||||
headers: HEADERS,
|
||||
signal: AbortSignal.timeout(10000),
|
||||
});
|
||||
if (!res.ok) return null;
|
||||
const data = await res.json().catch(() => null);
|
||||
return { provider, data };
|
||||
})
|
||||
);
|
||||
|
||||
const allUrls = [];
|
||||
const seen = new Set();
|
||||
|
||||
for (const r of responses) {
|
||||
if (r.status !== 'fulfilled' || !r.value?.data) continue;
|
||||
const provName = r.value.provider.charAt(0).toUpperCase() + r.value.provider.slice(1);
|
||||
const extracted = extractStreams(r.value.data, provName);
|
||||
|
||||
for (const item of extracted) {
|
||||
if (!item?.url || seen.has(item.url)) continue;
|
||||
seen.add(item.url);
|
||||
allUrls.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
if (!allUrls.length) return null;
|
||||
|
||||
return {
|
||||
url: allUrls[0].url,
|
||||
headers: allUrls[0].headers,
|
||||
allUrls,
|
||||
};
|
||||
}
|
||||
128
src/sources/torrentio.js
Normal file
128
src/sources/torrentio.js
Normal file
@@ -0,0 +1,128 @@
|
||||
const BASE_URL = 'https://s.torrentio.to';
|
||||
const UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36';
|
||||
|
||||
const HEADERS = {
|
||||
'User-Agent': UA,
|
||||
Referer: 'https://stream.vidplay.to/',
|
||||
Origin: 'https://stream.vidplay.to',
|
||||
Accept: 'text/event-stream, */*',
|
||||
'Accept-Language': 'en-US,en;q=0.9',
|
||||
};
|
||||
|
||||
const PLAYBACK_HEADERS = {
|
||||
'User-Agent': UA,
|
||||
Referer: 'https://stream.vidplay.to/',
|
||||
Origin: 'https://stream.vidplay.to',
|
||||
};
|
||||
|
||||
export async function getStream(args) {
|
||||
const { id, s, e } = args || {};
|
||||
if (!id) return null;
|
||||
|
||||
const isTv = s != null && e != null;
|
||||
const targetUrl = isTv
|
||||
? `${BASE_URL}/v1/sources/stream?type=tv&id=${id}&s=${s}&e=${e}`
|
||||
: `${BASE_URL}/v1/sources/stream?type=movie&id=${id}`;
|
||||
|
||||
try {
|
||||
const res = await fetch(targetUrl, {
|
||||
headers: HEADERS,
|
||||
signal: AbortSignal.timeout(args?.config?.timeout || 25000),
|
||||
});
|
||||
|
||||
if (!res.ok || !res.body) return null;
|
||||
|
||||
const reader = res.body.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
let buffer = '';
|
||||
const allUrls = [];
|
||||
const seen = new Set();
|
||||
|
||||
outer: while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
|
||||
buffer += decoder.decode(value, { stream: true });
|
||||
const lines = buffer.split('\n');
|
||||
buffer = lines.pop() || '';
|
||||
|
||||
for (const line of lines) {
|
||||
const trimmed = line.trim();
|
||||
if (trimmed === 'event: done') {
|
||||
try { await reader.cancel(); } catch { }
|
||||
break outer;
|
||||
}
|
||||
|
||||
if (trimmed.startsWith('data: ')) {
|
||||
try {
|
||||
const data = JSON.parse(trimmed.slice(6));
|
||||
if (Array.isArray(data?.sources)) {
|
||||
for (const src of data.sources) {
|
||||
if (!src?.url || seen.has(src.url)) continue;
|
||||
seen.add(src.url);
|
||||
|
||||
const provName = src.provider?.name || data.provider?.name || 'Server';
|
||||
const rawQ = src.quality ? String(src.quality).trim() : 'Auto';
|
||||
const quality = /^\d+$/i.test(rawQ)
|
||||
? `${rawQ}p`
|
||||
: /^\d+p$/i.test(rawQ)
|
||||
? rawQ.toLowerCase()
|
||||
: rawQ;
|
||||
|
||||
const type = src.type || (src.url.includes('.m3u8') ? 'hls' : (src.url.includes('.mpd') ? 'dash' : 'mp4'));
|
||||
|
||||
allUrls.push({
|
||||
url: src.url,
|
||||
server: `Torrentio - ${provName}`,
|
||||
quality,
|
||||
type,
|
||||
headers: PLAYBACK_HEADERS,
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (buffer.trim().startsWith('data: ')) {
|
||||
try {
|
||||
const data = JSON.parse(buffer.trim().slice(6));
|
||||
if (Array.isArray(data?.sources)) {
|
||||
for (const src of data.sources) {
|
||||
if (!src?.url || seen.has(src.url)) continue;
|
||||
seen.add(src.url);
|
||||
|
||||
const provName = src.provider?.name || data.provider?.name || 'Server';
|
||||
const rawQ = src.quality ? String(src.quality).trim() : 'Auto';
|
||||
const quality = /^\d+$/i.test(rawQ)
|
||||
? `${rawQ}p`
|
||||
: /^\d+p$/i.test(rawQ)
|
||||
? rawQ.toLowerCase()
|
||||
: rawQ;
|
||||
|
||||
const type = src.type || (src.url.includes('.m3u8') ? 'hls' : (src.url.includes('.mpd') ? 'dash' : 'mp4'));
|
||||
|
||||
allUrls.push({
|
||||
url: src.url,
|
||||
server: `Torrentio - ${provName}`,
|
||||
quality,
|
||||
type,
|
||||
headers: PLAYBACK_HEADERS,
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch { }
|
||||
}
|
||||
|
||||
if (!allUrls.length) return null;
|
||||
|
||||
return {
|
||||
url: allUrls[0].url,
|
||||
headers: allUrls[0].headers,
|
||||
allUrls,
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
95
src/sources/vidbolt.js
Normal file
95
src/sources/vidbolt.js
Normal file
@@ -0,0 +1,95 @@
|
||||
const BASE_URL = 'https://vidbolt.xyz';
|
||||
const UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36';
|
||||
|
||||
const HEADERS = {
|
||||
'User-Agent': UA,
|
||||
Referer: `${BASE_URL}/`,
|
||||
Origin: BASE_URL,
|
||||
Accept: 'application/json, text/plain, */*',
|
||||
'Accept-Language': 'en-US,en;q=0.9',
|
||||
};
|
||||
|
||||
const PLAYBACK_HEADERS = {
|
||||
'User-Agent': UA,
|
||||
Referer: `${BASE_URL}/`,
|
||||
Origin: BASE_URL,
|
||||
};
|
||||
|
||||
export async function getStream(args) {
|
||||
const { id, s, e } = args || {};
|
||||
if (!id) return null;
|
||||
|
||||
const isTv = s != null && e != null;
|
||||
const targetUrl = isTv
|
||||
? `${BASE_URL}/api/resolve?type=tv&id=${encodeURIComponent(id)}&season=${encodeURIComponent(s)}&episode=${encodeURIComponent(e)}`
|
||||
: `${BASE_URL}/api/resolve?type=movie&id=${encodeURIComponent(id)}`;
|
||||
|
||||
try {
|
||||
const res = await fetch(targetUrl, {
|
||||
headers: HEADERS,
|
||||
signal: AbortSignal.timeout(args?.config?.timeout || 25000),
|
||||
});
|
||||
|
||||
if (!res.ok) return null;
|
||||
const data = await res.json().catch(() => null);
|
||||
if (!data?.stream) return null;
|
||||
|
||||
const serverName = data.stream.server
|
||||
? data.stream.server.charAt(0).toUpperCase() + data.stream.server.slice(1)
|
||||
: 'Direct';
|
||||
|
||||
const allUrls = [];
|
||||
const seen = new Set();
|
||||
|
||||
if (Array.isArray(data.stream.sources)) {
|
||||
for (const item of data.stream.sources) {
|
||||
if (!item?.url || seen.has(item.url)) continue;
|
||||
seen.add(item.url);
|
||||
|
||||
let quality = item.quality ? String(item.quality).trim() : 'Auto';
|
||||
if (/^\d+$/i.test(quality)) quality = `${quality}p`;
|
||||
|
||||
const type = item.kind || (item.url.includes('.m3u8')
|
||||
? 'hls'
|
||||
: item.url.includes('.mpd')
|
||||
? 'dash'
|
||||
: 'mp4');
|
||||
|
||||
allUrls.push({
|
||||
url: item.url,
|
||||
server: `VidBolt - ${serverName}`,
|
||||
quality,
|
||||
type,
|
||||
headers: PLAYBACK_HEADERS,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (data.stream.url && !seen.has(data.stream.url)) {
|
||||
seen.add(data.stream.url);
|
||||
const type = data.stream.kind || (data.stream.url.includes('.m3u8')
|
||||
? 'hls'
|
||||
: data.stream.url.includes('.mpd')
|
||||
? 'dash'
|
||||
: 'mp4');
|
||||
|
||||
allUrls.push({
|
||||
url: data.stream.url,
|
||||
server: `VidBolt - ${serverName}`,
|
||||
quality: data.stream.quality || 'Auto',
|
||||
type,
|
||||
headers: PLAYBACK_HEADERS,
|
||||
});
|
||||
}
|
||||
|
||||
if (!allUrls.length) return null;
|
||||
|
||||
return {
|
||||
url: allUrls[0].url,
|
||||
headers: allUrls[0].headers,
|
||||
allUrls,
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
139
src/sources/willow.js
Normal file
139
src/sources/willow.js
Normal file
@@ -0,0 +1,139 @@
|
||||
const BASE_URL = 'https://willowmovies.com';
|
||||
const UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36';
|
||||
|
||||
const HEADERS = {
|
||||
'User-Agent': UA,
|
||||
Referer: `${BASE_URL}/`,
|
||||
Origin: BASE_URL,
|
||||
Accept: 'application/json, text/plain, */*',
|
||||
'Accept-Language': 'en-US,en;q=0.9',
|
||||
};
|
||||
|
||||
const PLAYBACK_HEADERS = {
|
||||
'User-Agent': UA,
|
||||
Referer: `${BASE_URL}/`,
|
||||
Origin: BASE_URL,
|
||||
};
|
||||
|
||||
function extractStreams(data, fallbackName) {
|
||||
const list = [];
|
||||
if (!data) return list;
|
||||
|
||||
const serverName = data.source?.name || data.source?.id || fallbackName || 'Default';
|
||||
|
||||
if (Array.isArray(data.qualities) && data.qualities.length > 0) {
|
||||
for (const item of data.qualities) {
|
||||
if (!item?.url) continue;
|
||||
|
||||
let quality = item.quality ? String(item.quality).trim() : 'Auto';
|
||||
if (quality === '0p') quality = 'Auto';
|
||||
else if (/^\d+$/i.test(quality)) quality = `${quality}p`;
|
||||
|
||||
const type = item.type || (item.url.includes('.m3u8')
|
||||
? 'hls'
|
||||
: item.url.includes('.mpd')
|
||||
? 'dash'
|
||||
: item.url.includes('.mkv')
|
||||
? 'mkv'
|
||||
: 'mp4');
|
||||
|
||||
list.push({
|
||||
url: item.url,
|
||||
server: `Willow - ${serverName}`,
|
||||
quality,
|
||||
type,
|
||||
headers: PLAYBACK_HEADERS,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (data.src) {
|
||||
const type = data.type || (data.src.includes('.m3u8')
|
||||
? 'hls'
|
||||
: data.src.includes('.mpd')
|
||||
? 'dash'
|
||||
: data.src.includes('.mkv')
|
||||
? 'mkv'
|
||||
: 'mp4');
|
||||
|
||||
list.push({
|
||||
url: data.src,
|
||||
server: `Willow - ${serverName}`,
|
||||
quality: 'Auto',
|
||||
type,
|
||||
headers: PLAYBACK_HEADERS,
|
||||
});
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
export async function getStream(args) {
|
||||
const { id, s, e } = args || {};
|
||||
if (!id) return null;
|
||||
|
||||
const isTv = s != null && e != null;
|
||||
const baseQuery = isTv
|
||||
? `id=${encodeURIComponent(id)}&type=tv&s=${encodeURIComponent(s)}&e=${encodeURIComponent(e)}`
|
||||
: `id=${encodeURIComponent(id)}&type=movie`;
|
||||
|
||||
try {
|
||||
const initialRes = await fetch(`${BASE_URL}/api/stream?${baseQuery}`, {
|
||||
headers: HEADERS,
|
||||
signal: AbortSignal.timeout(10000),
|
||||
});
|
||||
|
||||
if (!initialRes.ok) return null;
|
||||
const initialData = await initialRes.json().catch(() => null);
|
||||
if (!initialData) return null;
|
||||
|
||||
const allUrls = [];
|
||||
const seen = new Set();
|
||||
|
||||
const initialStreams = extractStreams(initialData);
|
||||
for (const item of initialStreams) {
|
||||
if (!item?.url || seen.has(item.url)) continue;
|
||||
seen.add(item.url);
|
||||
allUrls.push(item);
|
||||
}
|
||||
|
||||
const currentSourceId = initialData.source?.id;
|
||||
const extraSources = Array.isArray(initialData.sources)
|
||||
? initialData.sources.filter(src => src?.id && src.id !== currentSourceId)
|
||||
: [];
|
||||
|
||||
if (extraSources.length > 0) {
|
||||
const extraResults = await Promise.allSettled(
|
||||
extraSources.map(async src => {
|
||||
const res = await fetch(`${BASE_URL}/api/stream?${baseQuery}&source=${encodeURIComponent(src.id)}`, {
|
||||
headers: HEADERS,
|
||||
signal: AbortSignal.timeout(8000),
|
||||
});
|
||||
if (!res.ok) return null;
|
||||
const data = await res.json().catch(() => null);
|
||||
return { src, data };
|
||||
})
|
||||
);
|
||||
|
||||
for (const r of extraResults) {
|
||||
if (r.status !== 'fulfilled' || !r.value?.data) continue;
|
||||
const streams = extractStreams(r.value.data, r.value.src.name || r.value.src.id);
|
||||
for (const item of streams) {
|
||||
if (!item?.url || seen.has(item.url)) continue;
|
||||
seen.add(item.url);
|
||||
allUrls.push(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!allUrls.length) return null;
|
||||
|
||||
return {
|
||||
url: allUrls[0].url,
|
||||
headers: allUrls[0].headers,
|
||||
allUrls,
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
const BASE_URL = 'https://zenox.lol';
|
||||
const API_URL = `${BASE_URL}/api/v1/playback/session`;
|
||||
const UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36';
|
||||
|
||||
const HEADERS = {
|
||||
'Content-Type': 'application/json',
|
||||
Origin: BASE_URL,
|
||||
Referer: `${BASE_URL}/`,
|
||||
'User-Agent': UA,
|
||||
};
|
||||
|
||||
const PLAYBACK_HEADERS = {
|
||||
Origin: BASE_URL,
|
||||
Referer: `${BASE_URL}/`,
|
||||
};
|
||||
|
||||
async function fetchServerStream(payload, srv) {
|
||||
try {
|
||||
const res = await fetch(API_URL, {
|
||||
method: 'POST',
|
||||
headers: HEADERS,
|
||||
body: JSON.stringify({
|
||||
...payload,
|
||||
serverIndex: srv.index,
|
||||
serverName: srv.name,
|
||||
}),
|
||||
signal: AbortSignal.timeout(6000),
|
||||
});
|
||||
if (!res.ok) return null;
|
||||
const data = await res.json();
|
||||
if (
|
||||
!data?.success ||
|
||||
!data?.streamUrl ||
|
||||
data.streamUrl.includes('/primary/') ||
|
||||
data.serverName?.toLowerCase() === 'primary'
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const streamUrl = data.streamUrl.startsWith('http')
|
||||
? data.streamUrl
|
||||
: `${BASE_URL}${data.streamUrl}`;
|
||||
|
||||
return {
|
||||
url: streamUrl,
|
||||
server: `Zenox - ${data.serverName || srv.name}`,
|
||||
quality: 'Auto',
|
||||
type: streamUrl.includes('.m3u8') ? 'hls' : (streamUrl.includes('.mpd') ? 'dash' : 'mp4'),
|
||||
headers: PLAYBACK_HEADERS,
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getStream(args) {
|
||||
const { id, s, e, title, year, imdbId } = args || {};
|
||||
const idNum = parseInt(id, 10);
|
||||
if (!idNum) return null;
|
||||
|
||||
const isTv = s != null && e != null;
|
||||
const payload = {
|
||||
mediaId: idNum,
|
||||
mediaType: isTv ? 'tv' : 'movie',
|
||||
season: isTv ? (parseInt(s, 10) || 1) : 1,
|
||||
episode: isTv ? (parseInt(e, 10) || 1) : 1,
|
||||
title: title || '',
|
||||
year: String(year || ''),
|
||||
imdbId: imdbId || '',
|
||||
autoUseCommunity: false,
|
||||
isManualSwitch: true,
|
||||
};
|
||||
|
||||
const activeServers = [
|
||||
{ name: 'Vbot', index: 1 },
|
||||
{ name: 'Temporary', index: 1 },
|
||||
];
|
||||
|
||||
const results = await Promise.allSettled(
|
||||
activeServers.map(srv => fetchServerStream(payload, srv))
|
||||
);
|
||||
|
||||
const allUrls = [];
|
||||
const seen = new Set();
|
||||
|
||||
for (const r of results) {
|
||||
if (r.status === 'fulfilled' && r.value?.url && !seen.has(r.value.url)) {
|
||||
seen.add(r.value.url);
|
||||
allUrls.push(r.value);
|
||||
}
|
||||
}
|
||||
|
||||
if (!allUrls.length) {
|
||||
const delta = await fetchServerStream(payload, { name: 'Delta', index: 0 });
|
||||
if (delta?.url && !seen.has(delta.url)) {
|
||||
seen.add(delta.url);
|
||||
allUrls.push(delta);
|
||||
}
|
||||
}
|
||||
|
||||
if (!allUrls.length) return null;
|
||||
|
||||
return {
|
||||
url: allUrls[0].url,
|
||||
headers: allUrls[0].headers,
|
||||
allUrls,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user