const express = require('express'); const router = express.Router(); const axios = require('axios'); const MovieBoxAPI = require('./api'); const { DOWNLOAD_REQUEST_HEADERS, HOST_PROTOCOL, SELECTED_HOST } = require('./constants'); router.get('/home', async (req, res) => { try { const api = new MovieBoxAPI(); const data = await api.getHomepage(); res.json(data); } catch (error) { res.status(500).json({ error: error.message }); } }); router.get('/search', async (req, res) => { try { const { keyword, page = 1, perPage = 24, subjectType = 0 } = req.query; if (!keyword) { return res.status(400).json({ error: "Keyword is required" }); } const api = new MovieBoxAPI(); const data = await api.search(keyword, parseInt(page), parseInt(perPage), parseInt(subjectType)); res.json(data); } catch (error) { res.status(500).json({ error: error.message }); } }); router.get('/trending', async (req, res) => { try { const { page = 0, perPage = 18 } = req.query; const api = new MovieBoxAPI(); const data = await api.getTrending(parseInt(page), parseInt(perPage)); res.json(data); } catch (error) { res.status(500).json({ error: error.message }); } }); router.get('/rank', async (req, res) => { try { const api = new MovieBoxAPI(); const data = await api.getHotMovesAndSeries(); res.json(data); } catch (error) { res.status(500).json({ error: error.message }); } }); router.get('/recommend', async (req, res) => { try { const { subjectId, page = 1, perPage = 24 } = req.query; if (!subjectId) { return res.status(400).json({ error: "subjectId is required" }); } const api = new MovieBoxAPI(); const data = await api.getRecommendation(subjectId, parseInt(page), parseInt(perPage)); res.json(data); } catch (error) { res.status(500).json({ error: error.message }); } }); router.get('/detail', async (req, res) => { try { const { subjectId } = req.query; if (!subjectId) { return res.status(400).json({ error: "subjectId is required" }); } const api = new MovieBoxAPI(); const data = await api.getSubjectDetails(subjectId); res.json(data); } catch (error) { res.status(500).json({ error: error.message }); } }); router.get('/stream', async (req, res) => { try { const { subjectId, detailPath, season = 0, episode = 0 } = req.query; if (!subjectId || !detailPath) { return res.status(400).json({ error: "subjectId and detailPath are required" }); } const api = new MovieBoxAPI(); const data = await api.getStreamUrls(subjectId, detailPath, parseInt(season), parseInt(episode)); res.json(data); } catch (error) { res.status(500).json({ error: error.message }); } }); router.get('/download', async (req, res) => { try { const { subjectId, season = 1, episode = 1 } = req.query; if (!subjectId) { return res.status(400).json({ error: "subjectId is required" }); } const api = new MovieBoxAPI(); // 1. Fetch Detail to get the correct detailPath (Slug) and warm up the session let detailPath = "video"; let subjectType = 1; try { const apiDetail = await api.getSubjectDetails(subjectId); if (apiDetail && apiDetail.subject && apiDetail.subject.detailPath) { detailPath = apiDetail.subject.detailPath; } else if (apiDetail && apiDetail.detailPath) { detailPath = apiDetail.detailPath; } if (apiDetail && apiDetail.subject && apiDetail.subject.subjectType) { subjectType = apiDetail.subject.subjectType; } // Construct the web URL for warming up the session - discovered all used /movies/ path const webUrl = `/movies/${detailPath}`; await api.getItemDetails(webUrl); } catch (e) { console.warn("[Download] Session warm-up failed:", e.message); } // 2. Determine correct season/episode defaults let defaultVal = (parseInt(subjectType) === 1) ? 0 : 1; let finalSeason = req.query.season !== undefined ? parseInt(req.query.season) : defaultVal; let finalEpisode = req.query.episode !== undefined ? parseInt(req.query.episode) : defaultVal; // 3. Try fetching Download URLs let data = await api.getDownloadUrls(subjectId, detailPath, finalSeason, finalEpisode, subjectType); // 4. Fallback: If downloads empty, try Stream URLs if (!data.downloads || data.downloads.length === 0) { const streamData = await api.getStreamUrls(subjectId, detailPath, finalSeason, finalEpisode, subjectType); const list = streamData.list || streamData.streams || []; if (streamData && list.length > 0) { data.downloads = list.map(item => ({ ...item, url: item.url || item.path, resolution: item.quality || item.resolution || '720' })); if ((!data.captions || data.captions.length === 0) && streamData.captions) { data.captions = streamData.captions; } } } // Transform for user requirement data.processedSources = []; if (data.downloads) { data.processedSources = data.downloads.map(item => ({ id: item.id || `src_${Math.random()}`, quality: item.resolution || 'unknown', directUrl: item.url || "", size: item.size || "0", format: "mp4" })); } res.json(data); } catch (error) { res.status(500).json({ error: error.message }); } }); router.get('/generate-stream-link', async (req, res) => { try { const { url } = req.query; if (!url) { return res.status(400).json({ success: false, message: "URL is required" }); } // Get file info using HEAD request const headRes = await axios.head(url, { headers: DOWNLOAD_REQUEST_HEADERS }); const size = headRes.headers['content-length']; const contentType = headRes.headers['content-type']; const sizeFormatted = size ? (parseInt(size) / (1024 * 1024)).toFixed(2) + " MB" : "Unknown"; // Construct proxy URL // Assuming the server is running on req.get('host') or configured host const protocol = req.protocol; const host = req.get('host'); const proxyUrl = `${protocol}://${host}/api/proxy-stream?url=${encodeURIComponent(url)}`; res.json({ success: true, message: "URL successfully validated! Open the link below to stream.", instruction: "Copy streamUrl and open in browser/player", streamUrl: proxyUrl, fileInfo: { size: parseInt(size) || 0, sizeFormatted: sizeFormatted, contentType: contentType } }); } catch (error) { res.status(500).json({ success: false, message: error.message }); } }); router.get('/sources', async (req, res) => { try { const { subjectId, season = 1, episode = 1 } = req.query; if (!subjectId) { return res.status(400).json({ error: "subjectId is required" }); } const api = new MovieBoxAPI(); // 1. Fetch Detail to get the correct detailPath (Slug) and warm up the session let detailPath = "video"; let subjectType = 1; try { const apiDetail = await api.getSubjectDetails(subjectId); if (apiDetail && apiDetail.subject && apiDetail.subject.detailPath) { detailPath = apiDetail.subject.detailPath; } else if (apiDetail && apiDetail.detailPath) { detailPath = apiDetail.detailPath; } if (apiDetail && apiDetail.subject && apiDetail.subject.subjectType) { subjectType = apiDetail.subject.subjectType; } // Construct the web URL for warming up the session - discovered all used /movies/ path const webUrl = `/movies/${detailPath}`; await api.getItemDetails(webUrl); } catch (e) { console.warn("[Sources] Session warm-up failed:", e.message); } // 2. Determine correct season/episode defaults let defaultVal = (parseInt(subjectType) === 1) ? 0 : 1; let finalSeason = req.query.season !== undefined ? parseInt(req.query.season) : defaultVal; let finalEpisode = req.query.episode !== undefined ? parseInt(req.query.episode) : defaultVal; // 3. Try fetching Download URLs let data = await api.getDownloadUrls(subjectId, detailPath, finalSeason, finalEpisode, subjectType); // 3. Fallback: If downloads empty, try Stream URLs if (!data.downloads || data.downloads.length === 0) { const streamData = await api.getStreamUrls(subjectId, detailPath, finalSeason, finalEpisode, subjectType); // Map stream list (usually data.list or data.data.list) to downloads structure // API getStreamUrls returns data.data which has 'list' or similar? // verifying api.js: getStreamUrls returns data.data. // Let's assume streamData has 'list' or we map 'resources' if present // Note: streamData structure might differ slightly (list vs downloads) // But usually keys are: { list: [], ... } or { items: [] } // Let's inspect streamData keys in previous log: ['downloads', 'captions'...] - actually NO, stream calls /play // /play response often has `list` const list = streamData.list || streamData.streams || []; if (streamData && list.length > 0) { // Map stream list to downloads format to unify processing data.downloads = list.map(item => ({ ...item, // Stream items might have 'path' or 'url' url: item.url || item.path, resolution: item.quality || item.resolution || '720' // fallback })); // Merge captions if download didn't have them if ((!data.captions || data.captions.length === 0) && streamData.captions) { data.captions = streamData.captions; } } } // Transform for user requirement data.processedSources = []; if (data.downloads) { data.processedSources = data.downloads.map(item => ({ id: item.id || `src_${Math.random()}`, quality: item.resolution || 'unknown', directUrl: item.url || "", size: item.size || "0", format: "mp4" // valid assumption for array of mp4s })); } res.json(data); } catch (error) { res.status(500).json({ error: error.message }); } }); router.get('/proxy-stream', async (req, res) => { try { const { url } = req.query; if (!url) { return res.status(400).send("URL is required"); } const range = req.headers.range; const headers = { ...DOWNLOAD_REQUEST_HEADERS }; if (range) { headers['Range'] = range; } const response = await axios({ method: 'GET', url: url, headers: headers, responseType: 'stream', validateStatus: null // Capture all statuses (200, 206, etc) }); // Forward headers res.status(response.status); if (response.headers['content-range']) { res.setHeader('Content-Range', response.headers['content-range']); } if (response.headers['content-length']) { res.setHeader('Content-Length', response.headers['content-length']); } if (response.headers['content-type']) { res.setHeader('Content-Type', response.headers['content-type']); } if (response.headers['accept-ranges']) { res.setHeader('Accept-Ranges', response.headers['accept-ranges']); } response.data.pipe(res); } catch (error) { console.error("Proxy Error:", error.message); if (!res.headersSent) { res.status(500).send("Proxy Error"); } } }); router.get('/apicheck', async (req, res) => { const startTime = Date.now(); try { // Ping upstream to verify connectivity const api = new MovieBoxAPI(); await api.getHomepage(); const latency = Date.now() - startTime; res.json({ status: "Success", code: 200, message: "API is running smoothly", latency: `${latency}ms`, upstream: "OK" }); } catch (error) { const latency = Date.now() - startTime; res.status(500).json({ status: "Error", code: 500, message: "Upstream unreachable or Error", error: error.message, latency: `${latency}ms` }); } }); module.exports = router;