Files
TV/.build/sdk/sources/xdownloader.js

129 lines
4.3 KiB
JavaScript

const BASE_URL = 'https://www.films365.org';
const HEADERS = {
Authorization: 'Bearer 79a02956be35835728a044b11e2ae793149d45fb2c89cb6d029ec01aac19bfdb',
'Content-Type': 'application/json',
'User-Agent': 'MovieDownloader/1.0',
};
async function resolveTitle(sdk, id, isTv) {
const apiKey = sdk?.tmdbApiKey || process.env.TMDB_API_KEY;
if (!apiKey) return null;
const type = isTv ? 'tv' : 'movie';
try {
const res = await fetch(`https://api.themoviedb.org/3/${type}/${id}?api_key=${apiKey}`, {
signal: AbortSignal.timeout(6000),
});
if (!res.ok) return null;
const data = await res.json();
return isTv ? data.name : data.title;
} catch {
return null;
}
}
export async function getStream({ id, s, e, sdk }) {
const isTv = Boolean(s && e);
const targetType = isTv ? 'tv' : 'movie';
const title = await resolveTitle(sdk, id, isTv);
const query = title || String(id);
const searchRes = await fetch(`${BASE_URL}/api/mobile/search`, {
method: 'POST',
headers: HEADERS,
body: JSON.stringify({ query }),
signal: AbortSignal.timeout(8000),
});
if (!searchRes.ok) return null;
const searchJson = await searchRes.json();
const resultsObj = searchJson?.results;
if (!resultsObj) return null;
const items = resultsObj.all || (targetType === 'tv' ? resultsObj.tvs : resultsObj.movies) || [];
if (!Array.isArray(items) || items.length === 0) return null;
const cleanSearchTitle = query.toLowerCase().replace(/[^a-z0-9]/g, '');
let matchedItem = null;
for (const item of items) {
const itemType = String(item?.type || '').toLowerCase();
if (itemType && itemType !== targetType) continue;
const itemTitle = String(item?.title || '');
const cleanItemTitle = itemTitle.toLowerCase().replace(/[^a-z0-9]/g, '');
if (cleanItemTitle === cleanSearchTitle || cleanItemTitle.includes(cleanSearchTitle)) {
matchedItem = item;
break;
}
}
if (!matchedItem) {
matchedItem = items.find(i => String(i?.type || '').toLowerCase() === targetType) || items[0];
}
const itemId = matchedItem?.id || matchedItem?.tmdbId;
if (!itemId) return null;
const detailsRes = await fetch(`${BASE_URL}/api/mobile/details?id=${itemId}&type=${targetType}`, {
headers: HEADERS,
signal: AbortSignal.timeout(8000),
});
if (!detailsRes.ok) return null;
const detailsJson = await detailsRes.json();
const data = detailsJson?.data;
if (!data) return null;
const spoken = Array.isArray(data.spokenLanguages)
? data.spokenLanguages.map(l => String(l).trim()).filter(Boolean)
: [];
const langSuffix = spoken.length > 0 ? ` · ${spoken.join(', ')}` : '';
const allUrls = [];
if (!isTv) {
const streamUrl = data.downloadUrl || data.videoUrl;
if (streamUrl && typeof streamUrl === 'string') {
allUrls.push({
url: streamUrl.trim(),
label: `X-Downloader${langSuffix}`,
headers: {
'User-Agent': HEADERS['User-Agent'],
},
});
}
} else {
const seasonNum = Number(s);
const episodeNum = Number(e);
const seasons = Array.isArray(data.seasons) ? data.seasons : [];
const targetSeason = seasons.find(sn => Number(sn.seasonNumber) === seasonNum);
if (targetSeason) {
const episodes = Array.isArray(targetSeason.episodes) ? targetSeason.episodes : [];
const targetEp = episodes.find(ep => Number(ep.episodeNumber) === episodeNum);
if (targetEp) {
const streamUrl = targetEp.downloadUrl || targetEp.videoUrl;
if (streamUrl && typeof streamUrl === 'string') {
allUrls.push({
url: streamUrl.trim(),
label: `X-Downloader (S${seasonNum}E${episodeNum})${langSuffix}`,
headers: {
'User-Agent': HEADERS['User-Agent'],
},
});
}
}
}
}
if (allUrls.length === 0) return null;
return {
url: allUrls[0].url,
allUrls,
headers: allUrls[0].headers,
};
}