Download index.js from wpms/mcbot: direct link, hf CLI and curl.
- Browser
- Download file 17 kB
-
https://huggingface.co/spaces/wpms/mcbot/resolve/main/index.js
- Command line
-
hf download hf://spaces/wpms/mcbot/index.js
-
curl -L -o index.js https://huggingface.co/spaces/wpms/mcbot/resolve/main/index.js
17 kB
| 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"] | |
| }, | |
| pingTimeout: 60000, | |
| pingInterval: 25000 | |
| }); | |
| 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 || 'unknown', | |
| 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; | |
| let reconnectTimer = null; | |
| let reconnectAttempts = 0; | |
| const MAX_RECONNECT_ATTEMPTS = 5; | |
| const RECONNECT_DELAY = 5000; | |
| let botConfig = null; | |
| let isIntentionalDisconnect = false; | |
| // 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; | |
| } | |
| // Save config for reconnect | |
| botConfig = { username, host, port, version }; | |
| isIntentionalDisconnect = false; | |
| reconnectAttempts = 0; | |
| // Disconnect existing bot for this socket | |
| if (currentBot) { | |
| isIntentionalDisconnect = true; | |
| currentBot.quit(); | |
| activeBots.delete(botId); | |
| } | |
| // Clear any pending reconnect | |
| if (reconnectTimer) { | |
| clearTimeout(reconnectTimer); | |
| reconnectTimer = null; | |
| } | |
| botId = `${socket.id}_${Date.now()}`; | |
| createBotConnection(config, socket); | |
| }); | |
| function createBotConnection(config, socket) { | |
| const { username, host, port = 25565, version } = config; | |
| try { | |
| console.log(`Creating bot ${username} for ${host}:${port}...`); | |
| const bot = mineflayer.createBot({ | |
| host: host, | |
| port: parseInt(port), | |
| username: username, | |
| version: version || false, | |
| auth: 'offline', | |
| hideErrors: false, | |
| checkTimeoutInterval: 30000, | |
| closeTimeout: 10000, | |
| keepAlive: true, | |
| // Add some delay to avoid rate limiting | |
| connectTimeout: 30000 | |
| }); | |
| currentBot = bot; | |
| const botData = { | |
| bot: bot, | |
| socket: socket, | |
| host: host, | |
| port: port, | |
| connected: false, | |
| players: [], | |
| chatHistory: [], | |
| spawnTimeout: null, | |
| keepAliveInterval: null | |
| }; | |
| activeBots.set(botId, botData); | |
| // Bot Events | |
| bot.on('login', () => { | |
| console.log(`Bot ${username} logged in to ${host}:${port}`); | |
| botData.connected = true; | |
| reconnectAttempts = 0; | |
| socket.emit('bot-status', { | |
| status: 'online', | |
| username: bot.username, | |
| host: host, | |
| port: port | |
| }); | |
| }); | |
| bot.on('spawn', () => { | |
| console.log(`Bot ${username} spawned`); | |
| socket.emit('bot-spawned', { | |
| position: bot.entity?.position, | |
| health: bot.health, | |
| food: bot.food | |
| }); | |
| updatePlayersList(bot, socket); | |
| // Start keep-alive mechanism | |
| startKeepAlive(bot, botData); | |
| // Clear spawn timeout | |
| if (botData.spawnTimeout) { | |
| clearTimeout(botData.spawnTimeout); | |
| botData.spawnTimeout = null; | |
| } | |
| }); | |
| 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) => { | |
| console.log(`Bot ${username} kicked:`, reason); | |
| const reasonStr = reason.toString ? reason.toString() : JSON.stringify(reason); | |
| socket.emit('bot-kicked', { | |
| reason: reasonStr, | |
| timestamp: new Date().toISOString() | |
| }); | |
| // Try to reconnect if not intentional | |
| if (!isIntentionalDisconnect) { | |
| attemptReconnect(socket, `Kicked: ${reasonStr}`); | |
| } | |
| }); | |
| bot.on('error', (err) => { | |
| console.error(`Bot ${username} error:`, err.message); | |
| socket.emit('bot-error', { | |
| message: err.message, | |
| timestamp: new Date().toISOString() | |
| }); | |
| // Only attempt reconnect for connection errors, not auth errors | |
| if (err.message && ( | |
| err.message.includes('ETIMEDOUT') || | |
| err.message.includes('ECONNRESET') || | |
| err.message.includes('ECONNREFUSED') || | |
| err.message.includes('timeout') || | |
| err.message.includes('disconnected') | |
| )) { | |
| if (!isIntentionalDisconnect) { | |
| attemptReconnect(socket, `Error: ${err.message}`); | |
| } | |
| } | |
| }); | |
| bot.on('end', (reason) => { | |
| console.log(`Bot ${username} disconnected. Reason:`, reason, 'Intentional:', isIntentionalDisconnect); | |
| // Stop keep-alive | |
| stopKeepAlive(botData); | |
| socket.emit('bot-disconnected', { | |
| reason: reason || 'Connection closed', | |
| timestamp: new Date().toISOString() | |
| }); | |
| // Cleanup but keep the bot data for potential reconnect | |
| cleanupBotListeners(botId); | |
| // Attempt reconnect if not intentional disconnect | |
| if (!isIntentionalDisconnect) { | |
| attemptReconnect(socket, reason || 'Connection closed'); | |
| } | |
| }); | |
| // Spawn timeout - if bot doesn't spawn within 30 seconds, reconnect | |
| botData.spawnTimeout = setTimeout(() => { | |
| if (!botData.connected) { | |
| console.log(`Bot ${username} spawn timeout, reconnecting...`); | |
| socket.emit('bot-error', { | |
| message: 'Spawn timeout, reconnecting...', | |
| timestamp: new Date().toISOString() | |
| }); | |
| if (!isIntentionalDisconnect) { | |
| bot.quit(); | |
| attemptReconnect(socket, 'Spawn timeout'); | |
| } | |
| } | |
| }, 30000); | |
| } catch (error) { | |
| console.error('Failed to create bot:', error); | |
| socket.emit('error', { message: error.message }); | |
| if (!isIntentionalDisconnect) { | |
| attemptReconnect(socket, `Create error: ${error.message}`); | |
| } | |
| } | |
| } | |
| function attemptReconnect(socket, reason) { | |
| if (reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) { | |
| console.log(`Max reconnect attempts reached for ${botConfig?.username}`); | |
| socket.emit('bot-error', { | |
| message: `Max reconnect attempts reached. Please check server details or try again later.`, | |
| timestamp: new Date().toISOString() | |
| }); | |
| return; | |
| } | |
| reconnectAttempts++; | |
| console.log(`Reconnect attempt ${reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS} for ${botConfig?.username} in ${RECONNECT_DELAY}ms`); | |
| socket.emit('bot-reconnecting', { | |
| attempt: reconnectAttempts, | |
| maxAttempts: MAX_RECONNECT_ATTEMPTS, | |
| delay: RECONNECT_DELAY, | |
| reason: reason, | |
| timestamp: new Date().toISOString() | |
| }); | |
| reconnectTimer = setTimeout(() => { | |
| if (botConfig && !isIntentionalDisconnect) { | |
| console.log(`Executing reconnect for ${botConfig.username}...`); | |
| createBotConnection(botConfig, socket); | |
| } | |
| }, RECONNECT_DELAY); | |
| } | |
| function startKeepAlive(bot, botData) { | |
| // Send periodic position updates to keep connection alive | |
| botData.keepAliveInterval = setInterval(() => { | |
| if (bot && bot.entity) { | |
| // Small random movement to prevent AFK kick | |
| try { | |
| const random = Math.random(); | |
| if (random < 0.1) { | |
| // 10% chance to look around | |
| const yaw = Math.random() * Math.PI * 2; | |
| bot.look(yaw, 0, false); | |
| } | |
| } catch (e) { | |
| // Ignore look errors | |
| } | |
| } | |
| }, 15000); // Every 15 seconds | |
| } | |
| function stopKeepAlive(botData) { | |
| if (botData.keepAliveInterval) { | |
| clearInterval(botData.keepAliveInterval); | |
| botData.keepAliveInterval = null; | |
| } | |
| if (botData.spawnTimeout) { | |
| clearTimeout(botData.spawnTimeout); | |
| botData.spawnTimeout = null; | |
| } | |
| } | |
| // Send chat message | |
| socket.on('send-chat', (message) => { | |
| if (currentBot && currentBot.chat && botConfig) { | |
| try { | |
| currentBot.chat(message); | |
| socket.emit('chat-sent', { | |
| username: currentBot.username, | |
| message: message, | |
| timestamp: new Date().toISOString() | |
| }); | |
| } catch (e) { | |
| socket.emit('error', { message: 'Failed to send chat: ' + e.message }); | |
| } | |
| } | |
| }); | |
| // Send command | |
| socket.on('send-command', (command) => { | |
| if (currentBot && currentBot.chat && botConfig) { | |
| try { | |
| currentBot.chat(command); | |
| } catch (e) { | |
| socket.emit('error', { message: 'Failed to send command: ' + e.message }); | |
| } | |
| } | |
| }); | |
| // Bot actions | |
| socket.on('bot-action', (action) => { | |
| if (!currentBot || !botConfig) return; | |
| try { | |
| 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; | |
| } | |
| } catch (e) { | |
| socket.emit('error', { message: 'Action failed: ' + e.message }); | |
| } | |
| }); | |
| // Disconnect bot | |
| socket.on('disconnect-bot', () => { | |
| isIntentionalDisconnect = true; | |
| if (reconnectTimer) { | |
| clearTimeout(reconnectTimer); | |
| reconnectTimer = null; | |
| } | |
| if (currentBot) { | |
| try { | |
| currentBot.quit(); | |
| } catch (e) {} | |
| cleanupBot(botId); | |
| currentBot = null; | |
| botId = null; | |
| } | |
| socket.emit('bot-disconnected', { | |
| reason: 'Disconnected by user', | |
| timestamp: new Date().toISOString() | |
| }); | |
| }); | |
| // Get players list | |
| socket.on('get-players', () => { | |
| if (currentBot) { | |
| updatePlayersList(currentBot, socket); | |
| } | |
| }); | |
| // Disconnect handler | |
| socket.on('disconnect', () => { | |
| console.log('Client disconnected:', socket.id); | |
| isIntentionalDisconnect = true; | |
| if (reconnectTimer) { | |
| clearTimeout(reconnectTimer); | |
| reconnectTimer = null; | |
| } | |
| if (currentBot) { | |
| try { | |
| currentBot.quit(); | |
| } catch (e) {} | |
| 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 cleanupBotListeners(id) { | |
| if (activeBots.has(id)) { | |
| const data = activeBots.get(id); | |
| stopKeepAlive(data); | |
| if (data.bot) { | |
| try { | |
| data.bot.removeAllListeners(); | |
| } catch (e) {} | |
| } | |
| activeBots.delete(id); | |
| } | |
| } | |
| function cleanupBot(id) { | |
| if (activeBots.has(id)) { | |
| const data = activeBots.get(id); | |
| stopKeepAlive(data); | |
| if (data.bot) { | |
| try { | |
| data.bot.quit(); | |
| data.bot.removeAllListeners(); | |
| } catch (e) {} | |
| } | |
| activeBots.delete(id); | |
| } | |
| } | |
| function stopKeepAlive(botData) { | |
| if (botData) { | |
| if (botData.keepAliveInterval) { | |
| clearInterval(botData.keepAliveInterval); | |
| botData.keepAliveInterval = null; | |
| } | |
| if (botData.spawnTimeout) { | |
| clearTimeout(botData.spawnTimeout); | |
| botData.spawnTimeout = null; | |
| } | |
| } | |
| } | |
| // 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); | |
| }); | |
| }); |