const express = require('express'); const http = require('http'); const { Server } = require('socket.io'); const mineflayer = require('mineflayer'); const path = require('path'); const app = express(); const server = http.createServer(app); const io = new Server(server, { cors: { origin: "*", methods: ["GET", "POST"] } }); const PORT = process.env.PORT || 7860; // Store active bots const activeBots = new Map(); // Middleware app.use(express.json()); app.use(express.static('public')); // Health check endpoint app.get('/health', (req, res) => { res.json({ status: true, uptime: process.uptime(), bots: activeBots.size }); }); // API Routes app.get('/api/bots', (req, res) => { const bots = Array.from(activeBots.entries()).map(([id, data]) => ({ id, username: data.bot.username, host: data.host, port: data.port, connected: data.connected, players: data.players || [] })); res.json(bots); }); // Socket.IO Connection io.on('connection', (socket) => { console.log('Client connected:', socket.id); let currentBot = null; let botId = null; // Connect bot to Minecraft server socket.on('connect-bot', (config) => { const { username, host, port = 25565, version } = config; if (!username || !host) { socket.emit('error', { message: 'Username and host are required' }); return; } // Disconnect existing bot for this socket if (currentBot) { currentBot.quit(); activeBots.delete(botId); } botId = `${socket.id}_${Date.now()}`; try { const bot = mineflayer.createBot({ host: host, port: parseInt(port), username: username, version: version || false, auth: 'offline', hideErrors: true }); currentBot = bot; const botData = { bot: bot, socket: socket, host: host, port: port, connected: false, players: [], chatHistory: [] }; activeBots.set(botId, botData); // Bot Events bot.on('login', () => { botData.connected = true; socket.emit('bot-status', { status: 'online', username: bot.username, host: host, port: port }); console.log(`Bot ${username} connected to ${host}:${port}`); }); bot.on('spawn', () => { socket.emit('bot-spawned', { position: bot.entity?.position, health: bot.health, food: bot.food }); updatePlayersList(bot, socket); }); bot.on('chat', (username, message) => { if (username === bot.username) return; const chatData = { username: username, message: message, timestamp: new Date().toISOString(), type: 'chat' }; botData.chatHistory.push(chatData); socket.emit('chat-message', chatData); }); bot.on('message', (jsonMsg) => { const text = jsonMsg.toString(); if (text && !text.includes(bot.username)) { socket.emit('system-message', { message: text, timestamp: new Date().toISOString() }); } }); bot.on('playerJoined', (player) => { socket.emit('player-joined', { username: player.username, timestamp: new Date().toISOString() }); updatePlayersList(bot, socket); }); bot.on('playerLeft', (player) => { socket.emit('player-left', { username: player.username, timestamp: new Date().toISOString() }); updatePlayersList(bot, socket); }); bot.on('health', () => { socket.emit('bot-health', { health: bot.health, food: bot.food }); }); bot.on('death', () => { socket.emit('bot-death', { message: 'Bot died!', timestamp: new Date().toISOString() }); }); bot.on('kicked', (reason) => { socket.emit('bot-kicked', { reason: reason.toString(), timestamp: new Date().toISOString() }); cleanupBot(botId); }); bot.on('error', (err) => { console.error('Bot error:', err.message); socket.emit('bot-error', { message: err.message, timestamp: new Date().toISOString() }); }); bot.on('end', (reason) => { socket.emit('bot-disconnected', { reason: reason || 'Connection closed', timestamp: new Date().toISOString() }); cleanupBot(botId); }); } catch (error) { socket.emit('error', { message: error.message }); } }); // Send chat message socket.on('send-chat', (message) => { if (currentBot && currentBot.chat) { currentBot.chat(message); socket.emit('chat-sent', { username: currentBot.username, message: message, timestamp: new Date().toISOString() }); } }); // Send command socket.on('send-command', (command) => { if (currentBot && currentBot.chat) { currentBot.chat(command); } }); // Bot actions socket.on('bot-action', (action) => { if (!currentBot) return; switch(action.type) { case 'jump': currentBot.setControlState('jump', true); setTimeout(() => currentBot.setControlState('jump', false), 500); break; case 'sneak': currentBot.setControlState('sneak', true); setTimeout(() => currentBot.setControlState('sneak', false), 1000); break; case 'look': if (action.x !== undefined && action.y !== undefined && action.z !== undefined) { const Vec3 = require('vec3'); currentBot.lookAt(new Vec3(action.x, action.y, action.z)); } break; case 'move': if (action.direction) { currentBot.setControlState(action.direction, true); setTimeout(() => currentBot.setControlState(action.direction, false), 500); } break; } }); // Disconnect bot socket.on('disconnect-bot', () => { if (currentBot) { currentBot.quit(); cleanupBot(botId); currentBot = null; botId = null; } }); // Get players list socket.on('get-players', () => { if (currentBot) { updatePlayersList(currentBot, socket); } }); // Disconnect handler socket.on('disconnect', () => { console.log('Client disconnected:', socket.id); if (currentBot) { currentBot.quit(); cleanupBot(botId); } }); }); function updatePlayersList(bot, socket) { if (!bot.players) return; const players = Object.values(bot.players).map(p => ({ username: p.username, ping: p.ping, uuid: p.uuid })); socket.emit('players-list', players); } function cleanupBot(id) { if (activeBots.has(id)) { const data = activeBots.get(id); if (data.bot) { try { data.bot.quit(); data.bot.removeAllListeners(); } catch (e) {} } activeBots.delete(id); } } // Start server server.listen(PORT, () => { console.log(`🎮 Minecraft Bot Controller running on port ${PORT}`); console.log(`🌐 Access the web interface at http://localhost:${PORT}`); }); // Graceful shutdown process.on('SIGTERM', () => { console.log('SIGTERM received, shutting down gracefully'); activeBots.forEach((data, id) => { cleanupBot(id); }); server.close(() => { process.exit(0); }); }); process.on('SIGINT', () => { console.log('SIGINT received, shutting down gracefully'); activeBots.forEach((data, id) => { cleanupBot(id); }); server.close(() => { process.exit(0); }); });