removed dulo, and zenox, added fmovies, torrentio, vidbolt, and willow

This commit is contained in:
2026-11-03 13:35:10 -08:00
parent 928171055e
commit bf48ccdb05
7 changed files with 581 additions and 281 deletions

View File

@@ -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';

View File

@@ -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
View 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
View 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
View 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
View 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;
}
}

View File

@@ -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,
};
}