Spaces:
Running
Running
| import express, { Request, Response, NextFunction } from 'express'; | |
| import { spawn, execSync, ChildProcess } from 'child_process'; | |
| import fs from 'fs'; | |
| import path from 'path'; | |
| interface BotProcess { | |
| child: ChildProcess; | |
| tempDir: string; | |
| startedAt: number; | |
| } | |
| interface Command { | |
| filename: string; | |
| content: string; | |
| } | |
| interface RunRequestBody { | |
| botId: string; | |
| discordId: string; | |
| envFile: string; | |
| zbrJson: string; | |
| commands: Command[]; | |
| dbBase64: string; | |
| } | |
| interface StopRequestBody { | |
| botId: string; | |
| discordId: string; | |
| } | |
| const app = express(); | |
| app.use(express.json({ limit: '10mb' })); | |
| const ZBR_SECRET = process.env.ZBR_SECRET; | |
| const processes = new Map<string, BotProcess>(); | |
| try { | |
| const version = execSync('/usr/local/bin/zbr --version').toString().trim(); | |
| const size = execSync('wc -c < /usr/local/bin/zbr').toString().trim(); | |
| console.log('ZBR binary version:', version, '| size:', size, 'bytes'); | |
| } catch (e) { | |
| console.error("Failed to execute ZBR binary:", e); | |
| } | |
| const auth = (req: Request, res: Response, next: NextFunction): void | Response => { | |
| if (req.path === '/health') return next(); | |
| if (req.headers['x-zbr-secret'] !== ZBR_SECRET) { | |
| return res.status(401).send('Unauthorized'); | |
| } | |
| next(); | |
| }; | |
| app.use(auth); | |
| app.post('/run', (req: Request<{}, any, RunRequestBody>, res: Response) => { | |
| const { botId, discordId, envFile, zbrJson, commands, dbBase64 } = req.body; | |
| const key = `${discordId}-${botId}`; | |
| const userHasRunningBot = Array.from(processes.keys()).some(k => k.startsWith(`${discordId}-`)); | |
| if (userHasRunningBot) return res.status(400).send('You already have a bot running. Stop it before starting another.'); | |
| if (processes.has(key)) return res.status(400).send('Bot already running'); | |
| const tempDir = `/tmp/zbr-${discordId}-${botId}`; | |
| fs.mkdirSync(tempDir, { recursive: true }); | |
| fs.writeFileSync(path.join(tempDir, '.env'), envFile); | |
| fs.writeFileSync(path.join(tempDir, 'zbr.json'), zbrJson); | |
| const commandsDir = path.join(tempDir, 'commands'); | |
| fs.mkdirSync(commandsDir, { recursive: true }); | |
| commands.forEach(cmd => { | |
| fs.writeFileSync(path.join(commandsDir, cmd.filename), cmd.content); | |
| }); | |
| fs.writeFileSync(path.join(tempDir, 'zbr.db'), Buffer.from(dbBase64, 'base64')); | |
| const child = spawn('/usr/local/bin/zbr', [], { | |
| cwd: tempDir, | |
| stdio: ['ignore', 'pipe', 'pipe'] | |
| }); | |
| if (child.stdout) { | |
| child.stdout.on('data', (data) => console.log(data.toString())); | |
| } | |
| if (child.stderr) { | |
| child.stderr.on('data', (data) => console.error(data.toString())); | |
| } | |
| processes.set(key, { child, tempDir, startedAt: Date.now() }); | |
| setTimeout(() => { | |
| if (processes.has(key)) stopBot(key); | |
| }, 2 * 60 * 60 * 1000); | |
| res.json({ success: true, status: 'running' }); | |
| }); | |
| function stopBot(key: string): boolean { | |
| const proc = processes.get(key); | |
| if (!proc) return false; | |
| proc.child.kill(); | |
| fs.rmSync(proc.tempDir, { recursive: true, force: true }); | |
| processes.delete(key); | |
| return true; | |
| } | |
| app.post('/stop', (req: Request<{}, any, StopRequestBody>, res: Response) => { | |
| const { botId, discordId } = req.body; | |
| stopBot(`${discordId}-${botId}`); | |
| res.json({ success: true, status: 'stopped' }); | |
| }); | |
| app.get('/status/:discordId/:botId', (req: Request, res: Response) => { | |
| const { discordId, botId } = req.params; | |
| const proc = processes.get(`${discordId}-${botId}`); | |
| if (proc) { | |
| res.json({ status: 'running', startedAt: proc.startedAt }); | |
| } else { | |
| res.json({ status: 'stopped' }); | |
| } | |
| }); | |
| app.get('/health', (req: Request, res: Response) => res.json({ ok: true })); | |
| app.listen(7860, () => console.log('Server running on port 7860')); | |