Files
sdk/src/sources/dulo.js

135 lines
4.0 KiB
JavaScript

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