UNPKG

@baipiaodajun/mcbots

Version:

Minecraft bot and status dashboard for multi-server management

1,010 lines (893 loc) 35.3 kB
const mineflayer = require('mineflayer'); const express = require('express'); const net = require('net'); const PORT = process.env.SERVER_PORT || process.env.PORT || 3000 ; const CHAT = process.env.CHAT || false ; const MOVE = process.env.MOVE || false ; // Minecraft服务器配置 const SERVERS = [ { host: "127.0.0.1", port: 25565, minBots: 1, maxBots: 3, version: "1.20.1" } ]; // 从环境变量读取服务器配置 if (process && process.env && process.env.SERVERS_JSON) { try { const envConfig = JSON.parse(process.env.SERVERS_JSON); SERVERS.length = 0; SERVERS.push(...envConfig); console.log('从环境变量加载服务器配置,数量:', SERVERS.length, '项'); } catch (error) { console.error('❌ [错误] 无法解析环境变量 SERVERS_JSON'); console.error('原因:', error.message); console.error('原始内容:\n', process.env.SERVERS_JSON); console.error('请检查 JSON 格式是否正确,例如引号、逗号是否缺失'); console.error('即将退出...'); process.exit(1); } } /** * 测试 IP 和端口是否可达 * @param {string} host - IP 或域名 * @param {number} port - 端口号 * @param {number} timeoutMs - 超时时间(毫秒) * @returns {Promise<boolean>} */ function testConnection(host, port, timeoutMs = 5000) { return new Promise((resolve) => { const socket = new net.Socket(); // 成功连接 socket.once('connect', () => { socket.destroy(); resolve(true); }); // 出错或拒绝连接 socket.once('error', () => { socket.destroy(); resolve(false); }); // 超时 socket.setTimeout(timeoutMs, () => { socket.destroy(); resolve(false); }); // 尝试连接 socket.connect(port, host); }); } // 配置常量 const BOT_CONFIG = { reconnectDelay: 5000, maxReconnectAttempts: 5, healthCheckInterval: 60000, viewDistance: 4, connectTimeout: 15000, chat: CHAT, move: MOVE }; const SERVER_CONFIG = { statusCheckInterval: 30000, maxFailedAttempts: 3, resetTimeout: 30000 }; // 全局状态存储 const globalServerStatus = { servers: new Map() }; function generateUsername() { const adjectives = [ 'Clever', 'Swift', 'Brave', 'Sneaky', 'Happy', 'Crazy', 'Silky', 'Fluffy', 'Shiny', 'Quick', 'Mighty', 'Tiny', 'Wise', 'Lazy' ]; const animals = [ 'Fox', 'Wolf', 'Bear', 'Panda', 'Tiger', 'Eagle', 'Shark', 'Mole', 'Badger', 'Otter', 'Cat', 'Frog', 'Dog' ]; const adjective = adjectives[Math.floor(Math.random() * adjectives.length)]; const animal = animals[Math.floor(Math.random() * animals.length)]; return `${adjective}${animal}`; } // Minecraft机器人管理器 class MinecraftBotManager { constructor(host, port, minBots, maxBots, version) { this.host = host; this.port = port; this.minBots = minBots; this.maxBots = maxBots; this.version = version || "1.20.1"; this.currentBots = 0; this.activeBots = new Map(); this.failedAttempts = 0; this.lastUpdate = Date.now(); this.status = 'initializing'; this.monitoringInterval = null; this.timeout= null; this.reachable=false; this.lastReachable = null; this.resetTimer=null; this.notice=true; // 机器人名称池 this.botNames = Array.from({ length: 20 }, () => generateUsername()); // 注册到全局状态 globalServerStatus.servers.set(`${host}:${port}`, { host: host, port: port, minBots: minBots, maxBots: maxBots, currentBots: 0, activeBots: [], lastUpdate: Date.now(), status: 'initializing' }); } // 生成随机机器人名称 generateBotName() { const baseName = this.botNames[Math.floor(Math.random() * this.botNames.length)]; const randomNum = Math.floor(Math.random() * 1000); return `${baseName}${randomNum}`; } async testmc() { const reachable = await testConnection(this.host, this.port, 3000); this.reachable = reachable; // 只有状态发生变化时才通知 if (this.reachable !== this.lastReachable) { if (!this.reachable) { console.log(`[${this.host}:${this.port}] MC服务器网络不可达`); } else { console.log(`[${this.host}:${this.port}] MC服务器恢复可达`); } this.lastReachable = this.reachable; this.notice=true; } } // 创建Minecraft机器人 async createBot(botName) { if (this.currentBots >= this.maxBots) { console.log(`[${this.host}:${this.port}] 已达到最大机器人限制: ${this.maxBots}`); return null; } await this.testmc(); if (!this.reachable) { return null; } if(!botName){ botName = this.generateBotName(); } try { console.log(`[${this.host}:${this.port}] 创建机器人: ${botName}`); const bot = mineflayer.createBot({ host: this.host, port: this.port, username: botName, version: this.version, viewDistance: BOT_CONFIG.viewDistance, auth: 'offline' }); this.timeout = setTimeout(() => { console.error(`[${this.host}:${this.port}] ${botName}连接超时,放弃等待`); if (typeof bot.end === 'function') { bot.end(); } }, BOT_CONFIG.connectTimeout); // 设置机器人事件处理 this.setupBotEvents(bot, botName); this.activeBots.set(botName, bot); this.currentBots++; // this.failedAttempts = 0; this.updateStatus(); return bot; } catch (error) { console.log(`[${this.host}:${this.port}] 创建机器人 ${botName} 失败:`, error.message); this.handleBotFailure(botName); return null; } } // 设置机器人事件 setupBotEvents(bot, botName) { bot.on('login', () => { console.log(`[${this.host}:${this.port}] 机器人 ${botName} 登录成功`); if (this.timeout) { clearTimeout(this.timeout); this.timeout = null; } this.updateStatus(); }); bot.on('spawn', () => { console.log(`[${this.host}:${this.port}] 机器人 ${botName} 生成在世界中`); // 机器人基础行为 this.setupBotBehavior(bot, botName); }); bot.on('message', (message) => { const text = message.toString(); console.log(`[${this.host}:${this.port}] ${botName} 收到消息: ${text}`); }); bot.on('error', (error) => { console.log(`[${this.host}:${this.port}] 机器人 ${botName} 错误:`, error.message); // if (this.activeBots.has(botName) && this.failedAttempts <= SERVER_CONFIG.maxFailedAttempts) { this.handleBotDisconnect(botName); // } else { // this.handleBotFailure(botName); // } }); bot.on('end', (reason) => { console.log(`[${this.host}:${this.port}] 机器人 ${botName} 断开连接:`, reason); console.log(`[${this.host}:${this.port}] ${botName}:失败次数${this.failedAttempts}`); this.handleBotDisconnect(botName); }); bot.on('kicked', (reason) => { console.log(`[${this.host}:${this.port}] 机器人 ${botName} 被踢出:`, reason); this.handleBotDisconnect(botName); }); } // 设置机器人行为 setupBotBehavior(bot, botName) { // 随机移动 if (BOT_CONFIG.move){ setInterval(() => { if (bot.entity && Math.random() < 0.3) { const yaw = Math.random() * Math.PI * 2; const pitch = Math.random() * Math.PI - Math.PI / 2; bot.look(yaw, pitch, false); if (Math.random() < 0.2) { bot.setControlState('forward', true); setTimeout(() => { bot.setControlState('forward', false); }, 1000); } } }, 5000); } // 随机聊天(如果启用) if (BOT_CONFIG.chat && Math.random() < 0.1) { setInterval(() => { const messages = ['Hello!', 'Nice server!', 'What\'s up?', 'Good game!']; const randomMessage = messages[Math.floor(Math.random() * messages.length)]; bot.chat(randomMessage); }, 30000 + Math.random() * 60000); } } // 处理机器人断开连接 - 增强版本 handleBotDisconnect(botName) { if (this.activeBots.has(botName)) { console.log(`[${this.host}:${this.port}] 从活跃列表中移除机器人: ${botName}`); this.activeBots.delete(botName); this.currentBots = Math.max(0, this.currentBots - 1); this.updateStatus(); // 记录断开连接时间,用于调试 console.log(`[${this.host}:${this.port}] 当前活跃机器人数量: ${this.currentBots}, 目标: ${this.minBots}`); if(this.failedAttempts > SERVER_CONFIG.maxFailedAttempts){ console.log(`[${this.host}:${this.port}] 机器人 ${botName} 重连次数太多,跳过`); return; } // 延迟重连,避免频繁重连 setTimeout(() => { this.reconnect(botName); }, BOT_CONFIG.reconnectDelay); } else { console.log(`[${this.host}:${this.port}] 机器人 ${botName} 不在活跃列表中,无需处理`); } } // 处理机器人失败 handleBotFailure(botName) { this.failedAttempts++; this.updateStatus(); if (this.failedAttempts >= SERVER_CONFIG.maxFailedAttempts) { console.log(`[${this.host}:${this.port}] 失败次数过多,${SERVER_CONFIG.resetTimeout / 1000}秒后再次尝试`); setTimeout(() => { this.failedAttempts = 0; this.reconnect(botName); }, SERVER_CONFIG.resetTimeout); return; } setTimeout(() => { this.reconnect(botName); }, BOT_CONFIG.reconnectDelay); } // 维护机器人数目 - 增强版本 maintainBots() { const neededBots = this.minBots - this.currentBots; // console.log(`[${this.host}:${this.port}] 当前机器人: ${this.currentBots}, 需要: ${neededBots}, 失败次数: ${this.failedAttempts}`); if (neededBots > 0 && this.failedAttempts < SERVER_CONFIG.maxFailedAttempts) { if (this.notice) { console.log(`[${this.host}:${this.port}] 需要启动 ${neededBots} 个机器人`); this.notice=false; } for (let i = 0; i < neededBots; i++) { setTimeout(() => { this.createBot(); }, i * 8000); // 每隔8秒启动一个 } } // else if (neededBots > 0) { // console.log(`[${this.host}:${this.port}] 由于失败次数过多,暂停创建新机器人`); // } this.updateStatus(); } reconnect(botName){ if (this.failedAttempts < SERVER_CONFIG.maxFailedAttempts) { console.log(`[${this.host}:${this.port}] 第${this.failedAttempts}次重连 ${botName} 机器人`); this.failedAttempts++; setTimeout(() => { this.createBot(botName); }, 5000); } else{ // 如果还没有设置过 resetTimer,就设置一次 if (!this.resetTimer) { this.resetTimer = setTimeout(() => { this.failedAttempts = 0; this.resetTimer = null; // 清理标记,允许下次再设置 console.log( `[${this.host}:${this.port}] 失败次数已重置,可以重新尝试创建机器人` ); }, SERVER_CONFIG.resetTimeout); } console.log(`[${this.host}:${this.port}] 由于失败次数过多,暂停创建新机器人`); } this.updateStatus(); } // 更新状态 updateStatus() { const serverInfo = globalServerStatus.servers.get(`${this.host}:${this.port}`); if (serverInfo) { serverInfo.currentBots = this.currentBots; serverInfo.activeBots = Array.from(this.activeBots.keys()); serverInfo.lastUpdate = Date.now(); serverInfo.status = this.currentBots >= this.minBots ? 'healthy' : this.failedAttempts >= SERVER_CONFIG.maxFailedAttempts ? 'failed' : 'degraded'; this.status = serverInfo.status; } } // 启动监控 startMonitoring() { this.maintainBots(); this.monitoringInterval = setInterval(() => { this.maintainBots(); }, SERVER_CONFIG.statusCheckInterval); } // 停止所有机器人 stopAllBots() { this.activeBots.forEach((bot, botName) => { try { bot.quit(); console.log(`[${this.host}:${this.port}] 停止机器人: ${botName}`); } catch (error) { console.log(`[${this.host}:${this.port}] 停止机器人 ${botName} 失败:`, error.message); } }); this.activeBots.clear(); this.currentBots = 0; this.updateStatus(); if (this.monitoringInterval) { clearInterval(this.monitoringInterval); this.monitoringInterval = null; } } // 获取状态信息 getStatus() { return { host: this.host, port: this.port, version: this.version, minBots: this.minBots, maxBots: this.maxBots, currentBots: this.currentBots, activeBots: Array.from(this.activeBots.keys()), failedAttempts: this.failedAttempts, status: this.status, lastUpdate: this.lastUpdate }; } } // 系统监控 class SystemMonitor { constructor() { this.memoryUsage = '未知'; this.uptime = 0; this.monitoringInterval = null; } async getMemoryUsage() { return new Promise((resolve) => { const used = process.memoryUsage(); this.memoryUsage = `RSS: ${Math.round(used.rss / 1024 / 1024)}MB, Heap: ${Math.round(used.heapUsed / 1024 / 1024)}MB`; resolve(this.memoryUsage); }); } startMonitoring() { this.getMemoryUsage(); this.monitoringInterval = setInterval(async () => { await this.getMemoryUsage(); this.uptime = process.uptime(); }, BOT_CONFIG.healthCheckInterval); } stopMonitoring() { if (this.monitoringInterval) { clearInterval(this.monitoringInterval); } } getSystemInfo() { return { memoryUsage: this.memoryUsage, uptime: this.uptime, nodeVersion: process.version, platform: process.platform }; } } // Web状态服务器 class StatusServer { constructor(port = PORT) { this.port = port; this.app = express(); this.server = null; this.setupRoutes(); } setupRoutes() { this.app.get('/', (req, res) => { const serversStatus = Array.from(globalServerStatus.servers.values()); const systemInfo = systemMonitor.getSystemInfo(); const { version } = require('./package.json'); const html = ` <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Minecraft 机器人监控系统</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; padding: 20px; color: #333; } .container { max-width: 1200px; margin: 0 auto; } .header { text-align: center; margin-bottom: 30px; color: white; } .header h1 { font-size: 2.5rem; margin-bottom: 10px; text-shadow: 2px 2px 4px rgba(0,0,0,0.3); } .header p { font-size: 1.1rem; opacity: 0.9; } .dashboard { display: grid; grid-template-columns: 1fr 2fr; gap: 20px; margin-bottom: 20px; } @media (max-width: 768px) { .dashboard { grid-template-columns: 1fr; } } .system-card { background: white; border-radius: 15px; padding: 25px; box-shadow: 0 10px 30px rgba(0,0,0,0.2); backdrop-filter: blur(10px); } .system-card h3 { color: #4a5568; margin-bottom: 20px; font-size: 1.3rem; border-bottom: 2px solid #e2e8f0; padding-bottom: 10px; } .stats-grid { display: grid; gap: 15px; } .stat-item { display: flex; justify-content: space-between; align-items: center; padding: 12px; background: #f7fafc; border-radius: 10px; border-left: 4px solid #4299e1; } .stat-label { font-weight: 600; color: #4a5568; } .stat-value { font-weight: 700; color: #2d3748; } .servers-section { background: white; border-radius: 15px; padding: 25px; box-shadow: 0 10px 30px rgba(0,0,0,0.2); } .servers-section h3 { color: #4a5568; margin-bottom: 20px; font-size: 1.3rem; } .server-grid { display: grid; gap: 20px; } .server-card { border-radius: 12px; padding: 20px; border: 1px solid #e2e8f0; transition: all 0.3s ease; background: white; } .server-card:hover { transform: translateY(-2px); box-shadow: 0 8px 25px rgba(0,0,0,0.1); } .server-card.healthy { border-left: 4px solid #48bb78; } .server-card.degraded { border-left: 4px solid #ed8936; } .server-card.failed { border-left: 4px solid #f56565; } .server-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; } .server-title { font-size: 1.2rem; font-weight: 600; color: #2d3748; } .status-badge { padding: 6px 12px; border-radius: 20px; font-size: 0.85rem; font-weight: 600; text-transform: uppercase; } .status-healthy { background: #c6f6d5; color: #276749; } .status-degraded { background: #fed7d7; color: #c53030; } .status-failed { background: #fed7d7; color: #c53030; } .server-info { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 15px; margin-bottom: 15px; } .info-item { text-align: center; padding: 10px; background: #f7fafc; border-radius: 8px; } .info-label { font-size: 0.85rem; color: #718096; margin-bottom: 5px; } .info-value { font-size: 1.1rem; font-weight: 700; color: #2d3748; } .bots-section { margin-top: 15px; } .bots-title { font-weight: 600; margin-bottom: 10px; color: #4a5568; } .bots-list { display: flex; flex-wrap: wrap; gap: 8px; } .bot-tag { background: #4299e1; color: white; padding: 4px 12px; border-radius: 15px; font-size: 0.8rem; font-weight: 500; } .no-bots { color: #a0aec0; font-style: italic; text-align: center; padding: 10px; } .last-update { text-align: right; font-size: 0.8rem; color: #a0aec0; margin-top: 10px; } .refresh-info { text-align: center; color: white; margin-top: 20px; opacity: 0.8; font-size: 0.9rem; } .refresh-info a { color: #ffeb3b; font-weight: bold; text-decoration: none; margin-left: 8px; } .refresh-info a:hover { text-decoration: underline; color: #fff176; } .refresh-info button { margin-left: 8px; background-color: #ffeb3b; color: #6a0dad; font-weight: bold; border: none; border-radius: 4px; padding: 4px 8px; cursor: pointer; } .refresh-info button:hover { background-color: #fff176; } .progress-bar { width: 100%; height: 8px; background: #e2e8f0; border-radius: 4px; overflow: hidden; margin-top: 5px; } .progress-fill { height: 100%; border-radius: 4px; transition: width 0.3s ease; } .progress-healthy { background: linear-gradient(90deg, #48bb78, #68d391); } .progress-degraded { background: linear-gradient(90deg, #ed8936, #f6ad55); } .progress-failed { background: linear-gradient(90deg, #f56565, #fc8181); } </style> </head> <body> <div class="container"> <div class="header"> <h1>🎮 Minecraft 机器人监控系统</h1> <p>实时监控服务器状态和机器人连接</p> </div> <div class="dashboard"> <div class="system-card"> <h3>📊 系统概览</h3> <div class="stats-grid"> <div class="stat-item"> <span class="stat-label">运行时间</span> <span class="stat-value">${Math.floor(systemInfo.uptime / 60)} 分钟</span> </div> <div class="stat-item"> <span class="stat-label">内存使用</span> <span class="stat-value">${systemInfo.memoryUsage.split(',')[0].replace('RSS: ', '')}</span> </div> <div class="stat-item"> <span class="stat-label">Node.js 版本</span> <span class="stat-value">${systemInfo.nodeVersion}</span> </div> <div class="stat-item"> <span class="stat-label">监控服务器</span> <span class="stat-value">${serversStatus.length} 个</span> </div> </div> </div> <div class="servers-section"> <h3>🖥️ 服务器状态</h3> <div class="server-grid"> ${serversStatus.map(server => { const progress = (server.currentBots / server.maxBots) * 100; const statusClass = server.status === 'healthy' ? 'status-healthy' : server.status === 'degraded' ? 'status-degraded' : 'status-failed'; const progressClass = server.status === 'healthy' ? 'progress-healthy' : server.status === 'degraded' ? 'progress-degraded' : 'progress-failed'; const statusText = server.status === 'healthy' ? '正常' : server.status === 'degraded' ? '降级' : '故障'; return ` <div class="server-card ${server.status}"> <div class="server-header"> <div class="server-title">${server.host}:${server.port}</div> <div class="status-badge ${statusClass}">${statusText}</div> </div> <div class="server-info"> <div class="info-item"> <div class="info-label">机器人数量</div> <div class="info-value">${server.currentBots} / ${server.maxBots}</div> <div class="progress-bar"> <div class="progress-fill ${progressClass}" style="width: ${progress}%"></div> </div> </div> <div class="info-item"> <div class="info-label">最小要求</div> <div class="info-value">${server.minBots}</div> </div> <div class="info-item"> <div class="info-label">连接状态</div> <div class="info-value" style="color: ${ server.status === 'healthy' ? '#48bb78' : server.status === 'degraded' ? '#ed8936' : '#f56565' }">${server.status}</div> </div> </div> <div class="bots-section"> <div class="bots-title">🤖 活跃机器人</div> <div class="bots-list"> ${server.activeBots.length > 0 ? server.activeBots.map(bot => ` <div class="bot-tag">${bot}</div> `).join('') : '<div class="no-bots">暂无活跃机器人</div>' } </div> </div> <div class="last-update"> 最后更新: ${new Date(server.lastUpdate).toLocaleString('zh-CN')} </div> </div> `; }).join('')} </div> </div> </div> <div class="refresh-info"> 页面每30秒自动刷新 • Minecraft 机器人监控系统 v${version} · <a href="https://www.npmjs.com/package/@baipiaodajun/mcbots" target="_blank" rel="noopener noreferrer">NPM主頁</a> · <a href="https://gbjs.hospedagem-gratis.com/mcbot.html" target="_blank" rel="noopener noreferrer">SERVER_JSON生成器</a> · <a href="https://gbjs.hospedagem-gratis.com/mcbot2.html" target="_blank" rel="noopener noreferrer">SERVER_JSON修改器</a> · <button id="copy-config">复制配置</button> </div> <!-- 隐藏元素存放 SERVERS_JSON --> <div id="servers-json" style="display:none;"> ${process.env.SERVERS_JSON} </div> </div> <script> document.getElementById('copy-config').addEventListener('click', () => { // 从隐藏元素中获取内容 const serversJson = document.getElementById('servers-json').textContent.trim(); if (serversJson) { navigator.clipboard.writeText(serversJson) .then(() => alert('配置已复制到剪贴板')) .catch(err => alert('复制失败: ' + err)); } else { alert('未找到配置内容'); } }); // 30秒自动刷新 setTimeout(() => { location.reload(); }, 30000); // 添加一些交互动画 document.addEventListener('DOMContentLoaded', function() { const cards = document.querySelectorAll('.server-card'); cards.forEach(card => { card.addEventListener('mouseenter', function() { this.style.transform = 'translateY(-5px)'; }); card.addEventListener('mouseleave', function() { this.style.transform = 'translateY(0)'; }); }); }); </script> </body> </html>`; res.send(html); }); this.app.get('/api/status', (req, res) => { const serversStatus = Array.from(globalServerStatus.servers.values()); const systemInfo = systemMonitor.getSystemInfo(); res.json({ system: systemInfo, servers: serversStatus, timestamp: Date.now() }); }); } start() { this.server = this.app.listen(this.port, () => { console.log(`状态监控页面: http://localhost:${this.port}`); }); } stop() { if (this.server) { this.server.close(); } } } // 全局实例 const systemMonitor = new SystemMonitor(); const statusServer = new StatusServer(); let botManagers = []; // 主初始化 async function initialize() { console.log('初始化Minecraft机器人管理系统...'); systemMonitor.startMonitoring(); // 创建机器人管理器 botManagers = SERVERS.map(server => new MinecraftBotManager( server.host, server.port, server.minBots, server.maxBots, server.version ) ); // 启动所有管理器 botManagers.forEach(manager => { manager.startMonitoring(); console.log(`启动服务器: ${manager.host}:${manager.port} (${manager.minBots}-${manager.maxBots} 机器人)`); }); statusServer.start(); console.log('Minecraft机器人管理系统初始化完成'); process.on('SIGINT', shutdown); process.on('SIGTERM', shutdown); } function shutdown() { console.log('正在关闭系统...'); botManagers.forEach(manager => { manager.stopAllBots(); }); systemMonitor.stopMonitoring(); statusServer.stop(); console.log('系统已关闭'); process.exit(0); } // 启动 if (require.main === module) { initialize().catch(error => { console.error('初始化失败:', error); process.exit(1); }); } module.exports = { MinecraftBotManager, SystemMonitor, StatusServer, initialize, shutdown };