|
|
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 } } |
|
|
); |
|
|
}); |
|
|
} |