/** * KiraBot proxy — forwards /api/kirabot/* to KiraStream backend * authenticating on behalf of the current kiratv user. */ const express = require('express'); const router = express.Router(); const { requireAuth } = require('../auth'); const { sources } = require('../db'); const http = require('http'); const KIRASTREAM_BASE = process.env.KIRASTREAM_BASE_URL || 'http://10.10.10.234'; // Token cache: { userId: { token, expires } } const _tokenCache = new Map(); async function _getKirastreamToken(user) { const cached = _tokenCache.get(user.id); if (cached && cached.expires > Date.now()) { return cached.token; } // Find user's xtream source pointing to kirastream let db; try { db = await require('../db').loadDb(); } catch (e) { return null; } const src = db.sources.find( (s) => s.ownerId === user.id && s.type === 'xtream' && s.username && s.password ); if (!src) return null; // POST form to /api/user/auth/token return new Promise((resolve) => { const body = `username=${encodeURIComponent(src.username)}&password=${encodeURIComponent(src.password)}`; const url = new URL(`${KIRASTREAM_BASE}/api/user/auth/token`); const options = { hostname: url.hostname, port: url.port || 80, path: url.pathname, method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Content-Length': Buffer.byteLength(body), }, }; const req = http.request(options, (res) => { let data = ''; res.on('data', (chunk) => { data += chunk; }); res.on('end', () => { try { const json = JSON.parse(data); if (json.access_token) { _tokenCache.set(user.id, { token: json.access_token, expires: Date.now() + 25 * 60 * 1000, // 25 min }); resolve(json.access_token); } else { resolve(null); } } catch (e) { resolve(null); } }); }); req.on('error', () => resolve(null)); req.write(body); req.end(); }); } async function _proxyToKirastream(req, res, path, method, body) { const token = await _getKirastreamToken(req.user); if (!token) { return res.status(401).json({ error: 'No se pudo autenticar con KiraStream' }); } const url = new URL(`${KIRASTREAM_BASE}/api/user/kirabot${path}`); const bodyStr = body ? JSON.stringify(body) : ''; const options = { hostname: url.hostname, port: url.port || 80, path: url.pathname + (url.search || ''), method: method, headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(bodyStr), }, }; return new Promise((resolve) => { const proxyReq = http.request(options, (proxyRes) => { let data = ''; proxyRes.on('data', (chunk) => { data += chunk; }); proxyRes.on('end', () => { try { const json = JSON.parse(data); res.status(proxyRes.statusCode).json(json); } catch (e) { res.status(proxyRes.statusCode).send(data); } resolve(); }); }); proxyReq.on('error', (err) => { res.status(502).json({ error: 'Error de conexión con KiraStream', detail: err.message }); resolve(); }); if (bodyStr) proxyReq.write(bodyStr); proxyReq.end(); }); } router.use(requireAuth); router.post('/chat', async (req, res) => { await _proxyToKirastream(req, res, '/chat', 'POST', req.body); }); router.get('/chat/history', async (req, res) => { await _proxyToKirastream(req, res, '/chat/history', 'GET', null); }); router.delete('/chat/history', async (req, res) => { await _proxyToKirastream(req, res, '/chat/history', 'DELETE', null); }); router.get('/status', async (req, res) => { await _proxyToKirastream(req, res, '/status', 'GET', null); }); router.get('/recordings', async (req, res) => { const qs = req.query.limit ? `?limit=${req.query.limit}` : ''; await _proxyToKirastream(req, res, `/recordings${qs}`, 'GET', null); }); router.post('/recordings', async (req, res) => { await _proxyToKirastream(req, res, '/recordings', 'POST', req.body); }); router.delete('/recordings/:id', async (req, res) => { await _proxyToKirastream(req, res, `/recordings/${req.params.id}`, 'DELETE', null); }); module.exports = router;