mirror of
https://github.com/Inside4ndroid/TMDB-Embed-API.git
synced 2026-10-22 09:35:11 +00:00
181 lines
7.0 KiB
JavaScript
181 lines
7.0 KiB
JavaScript
const { config } = require('../utils/config');
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
// Lazy load cache for providers
|
|
const providerCache = new Map();
|
|
|
|
// Function name mappings for existing providers
|
|
const providerFunctionMap = {
|
|
'Showbox.js': 'getStreamsFromTmdbId',
|
|
'4khdhub.js': 'get4KHDHubStreams',
|
|
'moviesmod.js': 'getMoviesModStreams',
|
|
'MP4Hydra.js': 'getMP4HydraStreams',
|
|
'VidZee.js': 'getVidZeeStreams',
|
|
'vixsrc.js': 'getVixsrcStreams',
|
|
'xprime.js': 'getXprimeStreams',
|
|
'uhdmovies.js': 'getUHDMoviesStreams',
|
|
'moviesclub.js': 'getMoviesClubStreams',
|
|
};
|
|
|
|
// Stats for debug endpoint
|
|
let lastCookieStats = { selected: null, index: null, total: 0, remainingMB: null, timestamp: null };
|
|
|
|
async function getEffectiveCookies() {
|
|
return Array.isArray(config.febboxCookies) ? config.febboxCookies : [];
|
|
}
|
|
|
|
// Get all provider files
|
|
function getProviderFiles() {
|
|
const providersDir = path.join(__dirname);
|
|
return fs.readdirSync(providersDir)
|
|
.filter(file => file.endsWith('.js') && file !== 'registry.js' && file !== 'vidsrcextractor.js')
|
|
.map(file => ({
|
|
name: path.parse(file).name.toLowerCase(),
|
|
file: file,
|
|
functionName: providerFunctionMap[file]
|
|
}));
|
|
}
|
|
|
|
// Load a provider module
|
|
function loadProvider(providerFile) {
|
|
if (!providerCache.has(providerFile)) {
|
|
try {
|
|
providerCache.set(providerFile, require(path.join(__dirname, providerFile)));
|
|
} catch (e) {
|
|
console.error(`Failed to load provider ${providerFile}:`, e.message);
|
|
return null;
|
|
}
|
|
}
|
|
return providerCache.get(providerFile);
|
|
}
|
|
|
|
// Create fetch function for a provider
|
|
function createFetchFunction(providerInfo) {
|
|
return async function(ctx) {
|
|
const module = loadProvider(providerInfo.file);
|
|
if (!module) return [];
|
|
|
|
const funcName = providerInfo.functionName;
|
|
if (!module[funcName]) {
|
|
console.error(`Provider ${providerInfo.name} does not export ${funcName}`);
|
|
return [];
|
|
}
|
|
|
|
try {
|
|
const mediaType = ctx.type === 'movie' ? 'movie' : 'tv';
|
|
const t0 = Date.now();
|
|
|
|
let result;
|
|
if (providerInfo.name === 'showbox') {
|
|
// Special case for Showbox with TMDB key and cookies
|
|
const { getTmdbApiKey } = require('../utils/tmdbKey');
|
|
const tmdbApiKey = getTmdbApiKey();
|
|
if (!tmdbApiKey) {
|
|
console.warn('[registry] showbox skipped: TMDB API key missing');
|
|
return [];
|
|
}
|
|
const cookies = await getEffectiveCookies();
|
|
const previousConfig = global.currentRequestConfig;
|
|
global.currentRequestConfig = { ...(previousConfig || {}) };
|
|
let selected = null;
|
|
if (cookies.length > 0) {
|
|
const index = Math.floor(Math.random() * cookies.length);
|
|
selected = cookies[index];
|
|
global.currentRequestConfig.cookie = selected.startsWith('ui=') ? selected : `ui=${selected}`;
|
|
global.currentRequestConfig.cookies = cookies.map(c => c.startsWith('ui=') ? c : `ui=${c}`);
|
|
lastCookieStats = { selected: selected.slice(0, 16) + '...', index, total: cookies.length, remainingMB: null, timestamp: Date.now() };
|
|
console.log(`[registry] Cookie random pick index=${index} total=${cookies.length}`);
|
|
}
|
|
result = await module[funcName](mediaType, ctx.tmdbId, ctx.season || null, ctx.episode || null, null, selected);
|
|
if (global.currentRequestUserCookieRemainingMB != null) {
|
|
lastCookieStats.remainingMB = global.currentRequestUserCookieRemainingMB;
|
|
}
|
|
global.currentRequestConfig = previousConfig || {};
|
|
} else if (providerInfo.name === 'xprime') {
|
|
// Special case for Xprime with TMDB metadata lookup
|
|
const { getTmdbApiKey } = require('../utils/tmdbKey');
|
|
const tmdbApiKey = getTmdbApiKey();
|
|
if (!tmdbApiKey) {
|
|
console.warn('[registry] xprime skipped: TMDB API key missing');
|
|
return [];
|
|
}
|
|
// Fetch minimal TMDB metadata for title + year
|
|
let title = null; let year = null;
|
|
try {
|
|
const axios = require('axios');
|
|
const metaUrl = `https://api.themoviedb.org/3/${mediaType}/${ctx.tmdbId}?api_key=${tmdbApiKey}`;
|
|
const { data: meta } = await axios.get(metaUrl, { timeout: 8000 });
|
|
title = mediaType === 'movie' ? (meta.title || meta.original_title) : (meta.name || meta.original_name);
|
|
const dateStr = mediaType === 'movie' ? meta.release_date : meta.first_air_date;
|
|
if (dateStr) year = dateStr.split('-')[0];
|
|
} catch (mErr) {
|
|
console.warn('[registry] xprime metadata fetch failed:', mErr.message);
|
|
return [];
|
|
}
|
|
if (!title || !year) {
|
|
console.log('[registry] xprime missing title/year after TMDB lookup');
|
|
return [];
|
|
}
|
|
result = await module[funcName](title, year, mediaType, ctx.season || null, ctx.episode || null);
|
|
// Normalize quality labels
|
|
if (Array.isArray(result)) {
|
|
result = result.map(s => {
|
|
let q = s.quality || 'Unknown';
|
|
if (!/(\d{3,4})p/.test(q.toLowerCase())) {
|
|
const qLower = q.toLowerCase();
|
|
if (qLower.includes('fhd') || qLower.includes('full')) q = '1080p';
|
|
else if (qLower === 'hd' || qLower.includes('720')) q = '720p';
|
|
else if (qLower.includes('sd') || qLower.includes('480')) q = '480p';
|
|
else if (qLower.includes('cam')) q = '360p';
|
|
}
|
|
return { ...s, quality: q, name: s.name || 'Xprime', provider: 'xprime' };
|
|
});
|
|
}
|
|
} else {
|
|
// Standard provider call
|
|
result = await module[funcName](ctx.tmdbId, mediaType, ctx.season || null, ctx.episode || null);
|
|
}
|
|
|
|
const durationMs = Date.now() - t0;
|
|
console.log(`[registry] ${providerInfo.name} fetch duration ${durationMs}ms`);
|
|
|
|
if (!Array.isArray(result)) return [];
|
|
|
|
// Add provider name if not present
|
|
return result.map(s => ({ ...s, provider: s.provider || providerInfo.name }));
|
|
|
|
} catch (e) {
|
|
console.error(`[registry] ${providerInfo.name} fetch error:`, e.message);
|
|
return [];
|
|
}
|
|
};
|
|
}
|
|
|
|
// Initialize providers
|
|
const providerFiles = getProviderFiles();
|
|
const providers = [];
|
|
|
|
for (const providerInfo of providerFiles) {
|
|
const enableFlag = `enable${providerInfo.name.charAt(0).toUpperCase() + providerInfo.name.slice(1)}Provider`;
|
|
const enabled = config[enableFlag] !== false;
|
|
|
|
if (enabled) {
|
|
providers.push({
|
|
name: providerInfo.name,
|
|
enabled: true,
|
|
fetch: createFetchFunction(providerInfo)
|
|
});
|
|
console.log(`[registry] ${providerInfo.name} provider enabled`);
|
|
} else {
|
|
console.log(`[registry] ${providerInfo.name} disabled via config`);
|
|
}
|
|
}
|
|
|
|
function listProviders() { return providers.map(p => ({ name: p.name, enabled: p.enabled })); }
|
|
function getProvider(name) { return providers.find(p => p.name === name.toLowerCase()) || null; }
|
|
|
|
function getCookieStats() { return lastCookieStats; }
|
|
|
|
module.exports = { listProviders, getProvider, getCookieStats };
|