|
require('dotenv').config(); |
|
const { getRouter } = require('stremio-addon-sdk'); |
|
const addonInterface = require('./addon'); |
|
const express = require('express'); |
|
const path = require('path'); |
|
const http = require('http'); |
|
const winston = require('winston'); |
|
|
|
|
|
const logger = winston.createLogger({ |
|
level: 'info', |
|
format: winston.format.combine( |
|
winston.format.timestamp(), |
|
winston.format.printf(({ level, message, timestamp }) => { |
|
return `${timestamp} ${level}: ${message}`; |
|
}) |
|
), |
|
transports: [ |
|
new winston.transports.Console(), |
|
new winston.transports.File({ filename: 'error.log', level: 'error' }), |
|
new winston.transports.File({ filename: 'combined.log' }) |
|
] |
|
}); |
|
|
|
const app = express(); |
|
|
|
|
|
app.use(express.static(path.join(__dirname, 'public'))); |
|
|
|
|
|
app.use((err, req, res, next) => { |
|
logger.error('Unhandled error:', err); |
|
res.status(500).json({ error: 'Internal server error' }); |
|
}); |
|
|
|
|
|
app.get('/health', (req, res) => { |
|
res.json({ status: 'ok' }); |
|
}); |
|
|
|
|
|
app.get('/', (req, res) => { |
|
res.sendFile(path.join(__dirname, 'public', 'index.html')); |
|
}); |
|
|
|
let addonRouter = null; |
|
|
|
|
|
app.use('/:configuration?', (req, res, next) => { |
|
try { |
|
if (!addonRouter) { |
|
const configurationStr = req.params.configuration; |
|
if (configurationStr) { |
|
let configuration; |
|
try { |
|
const decodedStr = Buffer.from(configurationStr, 'base64').toString('utf-8'); |
|
configuration = JSON.parse(decodedStr); |
|
logger.debug('Decoded configuration:', { |
|
...configuration, |
|
username: configuration.username ? '[REDACTED]' : undefined, |
|
password: configuration.password ? '[REDACTED]' : undefined |
|
}); |
|
} catch (e) { |
|
logger.error('Failed to decode configuration:', e); |
|
return res.status(400).json({ error: 'Invalid configuration format' }); |
|
} |
|
|
|
const { username, password } = configuration; |
|
if (!username || !password) { |
|
logger.warn('Missing required configuration parameters'); |
|
return res.status(400).json({ error: 'Missing username or password' }); |
|
} |
|
|
|
try { |
|
const addonWithConfig = addonInterface.setConfiguration({ username, password }); |
|
addonRouter = getRouter(addonWithConfig); |
|
logger.info('Addon router created with configuration'); |
|
} catch (e) { |
|
logger.error('Failed to create addon router:', e); |
|
return res.status(500).json({ error: 'Failed to initialize addon' }); |
|
} |
|
} else { |
|
|
|
addonRouter = getRouter(addonInterface); |
|
logger.info('Addon router created with default configuration'); |
|
} |
|
} |
|
|
|
addonRouter(req, res, next); |
|
} catch (error) { |
|
logger.error('Error in addon middleware:', error); |
|
next(error); |
|
} |
|
}); |
|
|
|
|
|
app.use((req, res) => { |
|
res.status(404).json({ error: 'Not found' }); |
|
}); |
|
|
|
function startServer(port) { |
|
return new Promise((resolve, reject) => { |
|
const server = http.createServer(app); |
|
|
|
|
|
server.on('error', (error) => { |
|
if (error.code === 'EADDRINUSE') { |
|
logger.warn(`Port ${port} is in use, trying ${port + 1}`); |
|
resolve(startServer(port + 1)); |
|
} else { |
|
logger.error('Server error:', error); |
|
reject(error); |
|
} |
|
}); |
|
|
|
|
|
process.on('uncaughtException', (error) => { |
|
logger.error('Uncaught exception:', error); |
|
process.exit(1); |
|
}); |
|
|
|
|
|
process.on('unhandledRejection', (reason, promise) => { |
|
logger.error('Unhandled Rejection at:', promise, 'reason:', reason); |
|
}); |
|
|
|
|
|
server.listen(port, () => { |
|
logger.info(`Addon running on http://127.0.0.1:${port}`); |
|
resolve(server); |
|
}); |
|
}); |
|
} |
|
|
|
|
|
startServer(3000).catch(err => { |
|
logger.error('Failed to start server:', err); |
|
process.exit(1); |
|
}); |
|
|
|
|
|
module.exports = { app, startServer }; |
|
|