| const express = require('express'); |
| const { spawn } = require('child_process'); |
| const { MongoClient } = require('mongodb'); |
| const path = require('path'); |
| const app = express(); |
| const http = require('http').createServer(app); |
| const io = require('socket.io')(http, { |
| cors: { |
| origin: "*", |
| methods: ["GET", "POST"] |
| } |
| }); |
| const crypto = require('crypto'); |
| const fs = require('fs'); |
|
|
| |
| const MONGO_URI = process.env.MONGO_URI || "mongodb://localhost:27017"; |
| let db; |
|
|
| async function connectDB() { |
| try { |
| const client = new MongoClient(MONGO_URI); |
| await client.connect(); |
| db = client.db('whatsapp-bots'); |
| console.log("β
Connected to MongoDB"); |
| |
| |
| const collections = ['users', 'bots', 'sessions']; |
| for (const colName of collections) { |
| if (!(await db.listCollections({ name: colName }).hasNext())) { |
| await db.createCollection(colName); |
| console.log(`Created collection: ${colName}`); |
| } |
| } |
| |
| |
| const adminExists = await db.collection('users').findOne({ username: 'admin' }); |
| if (!adminExists) { |
| await db.collection('users').insertOne({ |
| username: 'admin', |
| password: hashPassword('admin123'), |
| isAdmin: true, |
| createdAt: new Date() |
| }); |
| console.log("π Created admin user (password: admin123)"); |
| } |
| } catch (err) { |
| console.error("β MongoDB connection error:", err); |
| process.exit(1); |
| } |
| } |
|
|
| function hashPassword(password) { |
| return crypto |
| .createHash('sha256') |
| .update(password + (process.env.PEPPER || 'defaultPepper')) |
| .digest('hex'); |
| } |
|
|
| |
| io.on('connection', (socket) => { |
| console.log(`New connection: ${socket.id}`); |
| |
| |
| socket.emit('terminal-init', { |
| status: 'ready', |
| timestamp: Date.now() |
| }); |
| |
| |
| socket.on('terminal-command', async (data) => { |
| try { |
| const { command, userId } = data; |
| console.log(`Command from ${userId}: ${command}`); |
| |
| |
| const userDir = `/persistent/storage/${userId}`; |
| const child = spawn(command.split(' ')[0], command.split(' ').slice(1), { |
| cwd: userDir |
| }); |
| |
| child.stdout.on('data', (data) => { |
| socket.emit('terminal-output', data.toString()); |
| }); |
| |
| child.stderr.on('data', (data) => { |
| socket.emit('terminal-output', `ERROR: ${data.toString()}`); |
| }); |
| |
| child.on('close', (code) => { |
| socket.emit('terminal-output', `Process exited with code ${code}\n`); |
| }); |
| } catch (err) { |
| socket.emit('terminal-output', `ERROR: ${err.message}\n`); |
| } |
| }); |
| |
| |
| socket.on('login', async (data) => { |
| try { |
| const { username, password } = data; |
| const user = await db.collection('users').findOne({ |
| username, |
| password: hashPassword(password) |
| }); |
| |
| if (user) { |
| currentUser = user._id.toString(); |
| socket.emit('login-success', { |
| userId: currentUser, |
| isAdmin: user.isAdmin |
| }); |
| |
| |
| const userDir = `/persistent/storage/${currentUser}`; |
| if (!fs.existsSync(userDir)) { |
| fs.mkdirSync(userDir, { recursive: true }); |
| } |
| } else { |
| socket.emit('login-error', 'Invalid credentials'); |
| } |
| } catch (err) { |
| socket.emit('login-error', 'Authentication failed'); |
| } |
| }); |
| }); |
|
|
| |
| app.use(express.static('public')); |
| app.use(express.json()); |
|
|
| |
| app.get('/health', (req, res) => { |
| res.status(200).json({ |
| status: 'healthy', |
| timestamp: Date.now() |
| }); |
| }); |
|
|
| |
| app.use((err, req, res, next) => { |
| console.error('Global error:', err); |
| io.emit('terminal-output', `SYSTEM ERROR: ${err.message}\n`); |
| res.status(500).json({ error: err.message }); |
| }); |
|
|
| |
| const PORT = process.env.PORT || 7860; |
| connectDB().then(() => { |
| http.listen(PORT, () => { |
| console.log(`π Server running on port ${PORT}`); |
| |
| |
| db.collection('bots').find({ status: 'running' }).forEach(bot => { |
| console.log(`Restarting bot for user ${bot.userId}`); |
| startBotProcess(bot.userId, bot.repoUrl, bot.entryFile); |
| }); |
| }); |
| }); |
|
|
| |
| function startBotProcess(userId, repoUrl, entryFile) { |
| const botDir = `/persistent/storage/${userId}`; |
| |
| |
| if (!fs.existsSync(botDir)) { |
| const clone = spawn('git', ['clone', repoUrl, botDir]); |
| clone.on('close', (code) => { |
| if (code === 0) installDependencies(botDir, userId, repoUrl, entryFile); |
| }); |
| } else { |
| installDependencies(botDir, userId, repoUrl, entryFile); |
| } |
| } |
|
|
| function installDependencies(botDir, userId, repoUrl, entryFile) { |
| const install = spawn('npm', ['install'], { cwd: botDir }); |
| install.on('close', (code) => { |
| if (code === 0) runBot(botDir, userId, repoUrl, entryFile); |
| }); |
| } |
|
|
| function runBot(botDir, userId, repoUrl, entryFile) { |
| const botProcess = spawn('node', [entryFile], { cwd: botDir }); |
| |
| |
| db.collection('bots').updateOne( |
| { userId }, |
| { $set: { |
| status: 'running', |
| repoUrl, |
| entryFile, |
| pid: botProcess.pid, |
| lastStarted: new Date() |
| }}, |
| { upsert: true } |
| ); |
| |
| |
| botProcess.on('exit', (code) => { |
| db.collection('bots').updateOne( |
| { userId }, |
| { $set: { status: 'stopped', exitCode: code } } |
| ); |
| }); |
| } |