const { getIdType } = require('../utils/idType') const { getHandlersForId } = require('../handlers/index') const { getProvidersByType, getExtraLocalesForType } = require('../providers') const { runProvidersCached } = require('../utils/providerRunner') const streamDb = require('../utils/streamDb') const { refreshInBackground } = require('../utils/streamValidator') const { classifyRarity } = require('../utils/rarityClassifier') function buildProviderKey(providers) { return providers.map(p => p.name).join(',') } async function getStreams(req, res) { try { const id = req.params.id const type = 'movie' const handlers = getHandlersForId(getIdType(id)) if (handlers.length === 0) { return res.status(400).json({ error: 'Tipo de ID no soportado' }) } const extraLocales = getExtraLocalesForType(type) let movieData = null for (const handler of handlers) { movieData = handler.name === 'tmdb' ? await handler.run(id, type, extraLocales) : await handler.run(id, extraLocales) if (movieData) break } if (!movieData) { return res.status(404).json({ error: 'Película no encontrada' }) } // 1. Intentar servir de la DB const tmdbId = String(movieData.id) const freshStreams = await streamDb.getFreshStreams(tmdbId, null, null) if (freshStreams.length > 0) { return res.json({ streams: freshStreams }) } // 2. No hay frescos → scrapear providers const providers = getProvidersByType(type) const cacheKey = [ 'stremio', type, movieData.id, movieData.type, buildProviderKey(providers), ].join(':') const scrapedStreams = await runProvidersCached( cacheKey, providers, movieData, {}, movieData.release_date, ) // 3. Logear a DB const providersUsed = [...new Set(scrapedStreams.map(s => s.provider).filter(Boolean))] for (const providerName of providersUsed) { const providerStreams = scrapedStreams.filter(s => s.provider === providerName) if (providerStreams.length > 0) { await streamDb.logStreams(tmdbId, providerName, type, providerStreams, movieData.original_title, null, null) } } // 4. Background refresh si había streams viejos en DB if (freshStreams.length === 0) { const allOld = await streamDb.getAllStreams(tmdbId, null, null) if (allOld.length > 0) { for (const providerName of providersUsed) { const providerStreams = scrapedStreams.filter(s => s.provider === providerName) refreshInBackground(tmdbId, allOld, providerStreams, providerName, type) } } } res.json({ streams: scrapedStreams }) } catch (error) { console.error(error.message) res.status(500).json({ error: 'Error interno del servidor' }) } } async function getSerieStreams(req, res) { try { const decodedId = decodeURIComponent(req.params.id) const [id, seasonStr, episodeStr] = decodedId.split(':') const season = parseInt(seasonStr, 10) || 1 const episode = parseInt(episodeStr, 10) || 1 const handlers = getHandlersForId(getIdType(id)) if (handlers.length === 0) return res.status(400).json({ error: 'Tipo de ID no soportado' }) const extraLocalesSerie = getExtraLocalesForType('serie') const extraLocalesAnime = getExtraLocalesForType('anime') const extraLocales = [...new Set([...extraLocalesSerie, ...extraLocalesAnime])] let serieData = null for (const handler of handlers) { serieData = handler.name === 'tmdb' ? await handler.run(id, 'series', extraLocales) : await handler.run(id, extraLocales) if (serieData?.type === 'tv') break } if (!serieData || serieData.type !== 'tv') { return res.status(404).json({ error: 'Serie no encontrada o tipo incorrecto' }) } const providerType = serieData.isAnime ? 'anime' : 'serie' const rarity = serieData.isAnime ? classifyRarity(serieData.popularity, serieData.vote_count) : null const tmdbId = String(serieData.id) // 1. Intentar servir de la DB const freshStreams = await streamDb.getFreshStreams(tmdbId, season, episode) if (freshStreams.length > 0) { const limited = limitByLanguage(freshStreams) return res.json({ streams: limited }) } // 2. Scrapear providers const providers = getProvidersByType(providerType, { rarity }) const cacheKey = [ 'stremio', providerType, serieData.id, season, episode, buildProviderKey(providers), ].join(':') const scrapedStreams = await runProvidersCached( cacheKey, providers, serieData, { season, episode }, serieData.release_date, ) // 3. Logear a DB const providersUsed = [...new Set(scrapedStreams.map(s => s.provider).filter(Boolean))] for (const providerName of providersUsed) { const providerStreams = scrapedStreams.filter(s => s.provider === providerName) if (providerStreams.length > 0) { await streamDb.logStreams(tmdbId, providerName, providerType, providerStreams, serieData.original_title, season, episode) } } // 4. Background refresh si había streams viejos en DB const allOld = await streamDb.getAllStreams(tmdbId, season, episode) if (allOld.length > 0) { for (const providerName of providersUsed) { const providerStreams = scrapedStreams.filter(s => s.provider === providerName) refreshInBackground(tmdbId, allOld, providerStreams, providerName, providerType) } } const limited = limitByLanguage(scrapedStreams) res.json({ streams: limited }) } catch (error) { console.trace(error) console.error('Error interno en getSerieStreams:', error.message) res.status(500).json({ error: 'Error interno del servidor' }) } } function limitByLanguage(streams) { const grouped = {} for (const s of streams) { const lang = s.language || s.lang || 'unknown' if (!grouped[lang]) grouped[lang] = [] if (grouped[lang].length < 3) grouped[lang].push(s) } return Object.values(grouped).flat() } module.exports = { getStreams, getSerieStreams }