| 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' }) |
| } |
|
|
| |
| const tmdbId = String(movieData.id) |
| const freshStreams = await streamDb.getFreshStreams(tmdbId, null, null) |
|
|
| if (freshStreams.length > 0) { |
| return res.json({ streams: freshStreams }) |
| } |
|
|
| |
| 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, |
| ) |
|
|
| |
| 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) |
| } |
| } |
|
|
| |
| 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) |
|
|
| |
| const freshStreams = await streamDb.getFreshStreams(tmdbId, season, episode) |
|
|
| if (freshStreams.length > 0) { |
| const limited = limitByLanguage(freshStreams) |
| return res.json({ streams: limited }) |
| } |
|
|
| |
| 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, |
| ) |
|
|
| |
| 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) |
| } |
| } |
|
|
| |
| 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 } |
|
|