@baipiaodajun/mcbots 1.2.0 → 1.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/package.json +1 -1
- package/server.js +167 -91
package/README.md
CHANGED
|
@@ -57,7 +57,7 @@ docker run -d \
|
|
|
57
57
|
mingli2038/mcbot:latest
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
-
|
|
60
|
+
### 新版本说明
|
|
61
61
|
|
|
62
62
|
- 新版本引入了 **热更新功能:`SERVER_JSON`**
|
|
63
63
|
- 目前该功能处于 **实验状态**,可能会导致一些 **意料之外的问题**
|
|
@@ -65,7 +65,7 @@ docker run -d \
|
|
|
65
65
|
|
|
66
66
|
---
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
### 页面更新
|
|
69
69
|
|
|
70
70
|
在页面底部新增了修复入口:
|
|
71
71
|
**【更新 MC 机器人配置】**
|
|
@@ -74,7 +74,7 @@ docker run -d \
|
|
|
74
74
|
|
|
75
75
|
---
|
|
76
76
|
|
|
77
|
-
|
|
77
|
+
### 密钥设置
|
|
78
78
|
|
|
79
79
|
- 默认密钥可从 [Telegram频道](https://t.me/boost/wanjulaji) 获取
|
|
80
80
|
- 也可以通过环境变量自行设置:
|
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -10,6 +10,13 @@ const fs = require('fs');
|
|
|
10
10
|
const PORT = process.env.SERVER_PORT || process.env.PORT || 3000 ;
|
|
11
11
|
const CHAT = process.env.CHAT || false ;
|
|
12
12
|
const MOVE = process.env.MOVE || false ;
|
|
13
|
+
//debug模式,只有开启时才会有很多日志。
|
|
14
|
+
const DEBUG = (process.env.DEBUG || '').toLowerCase() === 'true';
|
|
15
|
+
function debugPrint(...args) {
|
|
16
|
+
if (DEBUG) {
|
|
17
|
+
console.log(...args);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
13
20
|
// Minecraft服务器配置
|
|
14
21
|
const SERVERS = [
|
|
15
22
|
{
|
|
@@ -124,7 +131,7 @@ const BOT_CONFIG = {
|
|
|
124
131
|
|
|
125
132
|
const SERVER_CONFIG = {
|
|
126
133
|
statusCheckInterval: 30000,
|
|
127
|
-
maxFailedAttempts:
|
|
134
|
+
maxFailedAttempts: 1,
|
|
128
135
|
resetTimeout: 30000
|
|
129
136
|
};
|
|
130
137
|
|
|
@@ -207,6 +214,7 @@ class MinecraftBotManager {
|
|
|
207
214
|
this.version = version || "1.20.1";
|
|
208
215
|
this.currentBots = 0;
|
|
209
216
|
this.activeBots = new Map();
|
|
217
|
+
this.pendingReconnect = new Set(); // 等待重连的机器人名字(已断开但优先重用名字)
|
|
210
218
|
this.failedAttempts = 0;
|
|
211
219
|
this.lastUpdate = Date.now();
|
|
212
220
|
this.status = 'initializing';
|
|
@@ -231,7 +239,54 @@ class MinecraftBotManager {
|
|
|
231
239
|
status: 'initializing'
|
|
232
240
|
});
|
|
233
241
|
}
|
|
234
|
-
|
|
242
|
+
dispose() {
|
|
243
|
+
const key = `${this.host}:${this.port}`;
|
|
244
|
+
console.log(`[${key}] 正在释放 BotManager 所有资源...`);
|
|
245
|
+
|
|
246
|
+
// 1. 停止所有正在运行的机器人(最重要!)
|
|
247
|
+
this.activeBots.forEach((bot, botName) => {
|
|
248
|
+
try {
|
|
249
|
+
if (bot && typeof bot.end === 'function') {
|
|
250
|
+
bot.removeAllListeners(); // 关键!移除所有事件监听,防止回调残留
|
|
251
|
+
bot.end(); // 主动断开连接
|
|
252
|
+
}
|
|
253
|
+
console.log(`[${key}] 已断开机器人: ${botName}`);
|
|
254
|
+
} catch (err) {
|
|
255
|
+
console.warn(`[${key}] 断开 ${botName} 时出错:`, err.message);
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
this.activeBots.clear();
|
|
259
|
+
this.currentBots = 0;
|
|
260
|
+
|
|
261
|
+
// 2. 清理所有定时器(防止残留 setInterval/setTimeout)
|
|
262
|
+
if (this.monitoringInterval) {
|
|
263
|
+
clearInterval(this.monitoringInterval);
|
|
264
|
+
this.monitoringInterval = null;
|
|
265
|
+
console.log(`[${key}] 已清理 monitoringInterval`);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (this.timeout) {
|
|
269
|
+
clearTimeout(this.timeout);
|
|
270
|
+
this.timeout = null;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (this.resetTimer) {
|
|
274
|
+
clearTimeout(this.resetTimer);
|
|
275
|
+
this.resetTimer = null;
|
|
276
|
+
console.log(`[${key}] 已清理 resetTimer`);
|
|
277
|
+
}
|
|
278
|
+
// 4. 从全局状态移除(前端立刻看不到)
|
|
279
|
+
globalServerStatus.servers.delete(key);
|
|
280
|
+
console.log(`[${key}] 已从 globalServerStatus 中移除`);
|
|
281
|
+
|
|
282
|
+
// 5. 清理自身引用,帮助 GC
|
|
283
|
+
this.botNames = null;
|
|
284
|
+
this.activeBots = null;
|
|
285
|
+
this.status = 'disposed';
|
|
286
|
+
this.reachable = false;
|
|
287
|
+
|
|
288
|
+
console.log(`[${key}] BotManager 资源释放完成`);
|
|
289
|
+
}
|
|
235
290
|
// 生成随机机器人名称
|
|
236
291
|
generateBotName() {
|
|
237
292
|
const baseName = this.botNames[Math.floor(Math.random() * this.botNames.length)];
|
|
@@ -253,16 +308,52 @@ class MinecraftBotManager {
|
|
|
253
308
|
this.notice=true;
|
|
254
309
|
}
|
|
255
310
|
}
|
|
311
|
+
async handleBottimeout(bot,botName){
|
|
312
|
+
this.timeout = setTimeout(() => {
|
|
313
|
+
debugPrint(`[${this.host}:${this.port}] ${botName} 连接超时,强制终止`);
|
|
314
|
+
|
|
315
|
+
// 第一步:尝试正常结束(推荐方式)
|
|
316
|
+
if (typeof bot.end === 'function') {
|
|
317
|
+
try {
|
|
318
|
+
bot.end();
|
|
319
|
+
debugPrint(`[${this.host}:${this.port}] ${botName} 调用 bot.end() 成功`);
|
|
320
|
+
return; // 如果正常结束,就不需要进一步操作
|
|
321
|
+
} catch (err) {
|
|
322
|
+
debugPrint(`[${this.host}:${this.port}] ${botName} bot.end() 抛出异常:`, err.message);
|
|
323
|
+
// 继续尝试强杀底层 socket
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// 第二步:bot.end 失败或不存在,强制关闭底层 client socket
|
|
328
|
+
if (bot._client?.socket) {
|
|
329
|
+
try {
|
|
330
|
+
bot._client.socket.destroy(); // 最彻底:直接 destroy socket
|
|
331
|
+
debugPrint(`[${this.host}:${this.port}] ${botName} 强制 destroy 底层 socket`);
|
|
332
|
+
} catch (err) {
|
|
333
|
+
debugPrint(`[${this.host}:${this.port}] ${botName} destroy socket 失败:`, err.message);
|
|
334
|
+
}
|
|
335
|
+
} else if (bot._client?.end) {
|
|
336
|
+
try {
|
|
337
|
+
bot._client.end();
|
|
338
|
+
debugPrint(`[${this.host}:${this.port}] ${botName} 调用 bot._client.end()`);
|
|
339
|
+
} catch (err) {
|
|
340
|
+
debugPrint(`[${this.host}:${this.port}] ${botName} bot._client.end() 失败:`, err.message);
|
|
341
|
+
}
|
|
342
|
+
} else {
|
|
343
|
+
debugPrint(`[${this.host}:${this.port}] ${botName} 无可用 client socket,无法强制关闭`);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// 可选:触发断开处理(确保进入重连逻辑)
|
|
347
|
+
this.handleBotDisconnect(botName);
|
|
348
|
+
|
|
349
|
+
}, BOT_CONFIG.connectTimeout);
|
|
350
|
+
}
|
|
256
351
|
// 创建Minecraft机器人
|
|
257
352
|
async createBot(botName) {
|
|
258
353
|
if (this.currentBots >= this.maxBots) {
|
|
259
354
|
console.log(`[${this.host}:${this.port}] 已达到最大机器人限制: ${this.maxBots}`);
|
|
260
355
|
return null;
|
|
261
356
|
}
|
|
262
|
-
await this.testmc();
|
|
263
|
-
if (!this.reachable) {
|
|
264
|
-
return null;
|
|
265
|
-
}
|
|
266
357
|
if(!botName){
|
|
267
358
|
botName = this.generateBotName();
|
|
268
359
|
}
|
|
@@ -277,12 +368,7 @@ class MinecraftBotManager {
|
|
|
277
368
|
auth: 'offline'
|
|
278
369
|
});
|
|
279
370
|
|
|
280
|
-
this.
|
|
281
|
-
console.error(`[${this.host}:${this.port}] ${botName}连接超时,放弃等待`);
|
|
282
|
-
if (typeof bot.end === 'function') {
|
|
283
|
-
bot.end();
|
|
284
|
-
}
|
|
285
|
-
}, BOT_CONFIG.connectTimeout);
|
|
371
|
+
this.handleBottimeout(bot,botName);
|
|
286
372
|
|
|
287
373
|
// 设置机器人事件处理
|
|
288
374
|
this.setupBotEvents(bot, botName);
|
|
@@ -293,7 +379,7 @@ class MinecraftBotManager {
|
|
|
293
379
|
this.updateStatus();
|
|
294
380
|
return bot;
|
|
295
381
|
} catch (error) {
|
|
296
|
-
|
|
382
|
+
debugPrint(`[${this.host}:${this.port}] 创建机器人 ${botName} 失败:`, error.message);
|
|
297
383
|
this.handleBotFailure(botName);
|
|
298
384
|
return null;
|
|
299
385
|
}
|
|
@@ -307,19 +393,20 @@ class MinecraftBotManager {
|
|
|
307
393
|
clearTimeout(this.timeout);
|
|
308
394
|
this.timeout = null;
|
|
309
395
|
}
|
|
396
|
+
this.failedAttempts = 0;
|
|
397
|
+
this.pendingReconnect.delete(botName);
|
|
310
398
|
this.updateStatus();
|
|
311
399
|
});
|
|
312
400
|
|
|
313
401
|
bot.on('spawn', () => {
|
|
314
|
-
|
|
315
|
-
|
|
402
|
+
debugPrint(`[${this.host}:${this.port}] 机器人 ${botName} 生成在世界中`);
|
|
316
403
|
// 机器人基础行为
|
|
317
404
|
this.setupBotBehavior(bot, botName);
|
|
318
405
|
});
|
|
319
406
|
|
|
320
407
|
bot.on('message', (message) => {
|
|
321
408
|
const text = message.toString();
|
|
322
|
-
|
|
409
|
+
debugPrint(`[${this.host}:${this.port}] ${botName} 收到消息: ${text}`);
|
|
323
410
|
});
|
|
324
411
|
|
|
325
412
|
bot.on('error', (error) => {
|
|
@@ -333,7 +420,7 @@ class MinecraftBotManager {
|
|
|
333
420
|
|
|
334
421
|
bot.on('end', (reason) => {
|
|
335
422
|
console.log(`[${this.host}:${this.port}] 机器人 ${botName} 断开连接:`, reason);
|
|
336
|
-
console.log(`[${this.host}:${this.port}] ${botName}:失败次数${this.failedAttempts}`);
|
|
423
|
+
// console.log(`[${this.host}:${this.port}] ${botName}:失败次数${this.failedAttempts}`);
|
|
337
424
|
this.handleBotDisconnect(botName);
|
|
338
425
|
});
|
|
339
426
|
|
|
@@ -372,27 +459,21 @@ class MinecraftBotManager {
|
|
|
372
459
|
}
|
|
373
460
|
}
|
|
374
461
|
|
|
375
|
-
// 处理机器人断开连接 - 增强版本
|
|
376
462
|
handleBotDisconnect(botName) {
|
|
377
463
|
if (this.activeBots.has(botName)) {
|
|
378
|
-
|
|
464
|
+
debugPrint(`[${this.host}:${this.port}] 从活跃列表中移除机器人: ${botName}`);
|
|
379
465
|
this.activeBots.delete(botName);
|
|
380
466
|
this.currentBots = Math.max(0, this.currentBots - 1);
|
|
381
467
|
this.updateStatus();
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
if(this.failedAttempts > SERVER_CONFIG.maxFailedAttempts){
|
|
387
|
-
console.log(`[${this.host}:${this.port}] 机器人 ${botName} 重连次数太多,跳过`);
|
|
388
|
-
return;
|
|
389
|
-
}
|
|
390
|
-
// 延迟重连,避免频繁重连
|
|
468
|
+
this.pendingReconnect.add(botName);
|
|
469
|
+
debugPrint(`[${this.host}:${this.port}] 当前活跃机器人数量: ${this.currentBots}, 目标: ${this.minBots}`);
|
|
470
|
+
|
|
391
471
|
setTimeout(() => {
|
|
392
472
|
this.reconnect(botName);
|
|
393
473
|
}, BOT_CONFIG.reconnectDelay);
|
|
474
|
+
|
|
394
475
|
} else {
|
|
395
|
-
|
|
476
|
+
debugPrint(`[${this.host}:${this.port}] 机器人 ${botName} 不在活跃列表中,无需处理`);
|
|
396
477
|
}
|
|
397
478
|
}
|
|
398
479
|
|
|
@@ -400,62 +481,63 @@ class MinecraftBotManager {
|
|
|
400
481
|
handleBotFailure(botName) {
|
|
401
482
|
this.failedAttempts++;
|
|
402
483
|
this.updateStatus();
|
|
403
|
-
|
|
404
|
-
console.log(`[${this.host}:${this.port}] 失败次数过多,${SERVER_CONFIG.resetTimeout / 1000}秒后再次尝试`);
|
|
405
|
-
setTimeout(() => {
|
|
406
|
-
this.failedAttempts = 0;
|
|
407
|
-
this.reconnect(botName);
|
|
408
|
-
}, SERVER_CONFIG.resetTimeout);
|
|
409
|
-
return;
|
|
410
|
-
}
|
|
411
|
-
setTimeout(() => {
|
|
412
|
-
this.reconnect(botName);
|
|
413
|
-
}, BOT_CONFIG.reconnectDelay);
|
|
484
|
+
this.reconnect(botName);
|
|
414
485
|
}
|
|
415
486
|
|
|
416
487
|
// 维护机器人数目 - 增强版本
|
|
417
|
-
maintainBots() {
|
|
418
|
-
const neededBots =
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
if (this.
|
|
423
|
-
|
|
424
|
-
this.notice=false;
|
|
425
|
-
}
|
|
426
|
-
for (let i = 0; i < neededBots; i++) {
|
|
427
|
-
setTimeout(() => {
|
|
428
|
-
this.createBot();
|
|
429
|
-
}, i * 8000); // 每隔8秒启动一个
|
|
488
|
+
async maintainBots() {
|
|
489
|
+
const neededBots = this.minBots -this.currentBots;
|
|
490
|
+
try {
|
|
491
|
+
debugPrint(`[${this.host}:${this.port}] 当前机器人: ${this.currentBots}, 需要: ${neededBots}, 失败次数: ${this.failedAttempts}`);
|
|
492
|
+
await this.testmc();
|
|
493
|
+
if (!this.reachable) {
|
|
494
|
+
return null;
|
|
430
495
|
}
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
496
|
+
if (neededBots > 0 && this.failedAttempts < SERVER_CONFIG.maxFailedAttempts) {
|
|
497
|
+
if (this.notice) {
|
|
498
|
+
console.log(`[${this.host}:${this.port}] 需要启动 ${neededBots} 个机器人`);
|
|
499
|
+
this.notice=false;
|
|
500
|
+
}
|
|
501
|
+
for (let i = 0; i < neededBots; i++) {
|
|
502
|
+
setTimeout(() => {
|
|
503
|
+
this.createBot();
|
|
504
|
+
}, i * 8000); // 每隔8秒启动一个
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
this.updateStatus();
|
|
508
|
+
}
|
|
509
|
+
catch (err) {
|
|
510
|
+
console.error(`[${this.host}:${this.port}] maintainBots 异常:`, err);
|
|
511
|
+
}
|
|
436
512
|
}
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
this.failedAttempts++;
|
|
441
|
-
setTimeout(() => {
|
|
442
|
-
this.createBot(botName);
|
|
443
|
-
}, 5000);
|
|
444
|
-
} else{
|
|
445
|
-
// 如果还没有设置过 resetTimer,就设置一次
|
|
513
|
+
|
|
514
|
+
reconnect(botName) {
|
|
515
|
+
if (this.failedAttempts >= SERVER_CONFIG.maxFailedAttempts) {
|
|
446
516
|
if (!this.resetTimer) {
|
|
447
517
|
this.resetTimer = setTimeout(() => {
|
|
448
518
|
this.failedAttempts = 0;
|
|
449
|
-
this.resetTimer = null;
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
);
|
|
519
|
+
this.resetTimer = null;
|
|
520
|
+
debugPrint(`[${this.host}:${this.port}] 冷却期结束,恢复维护`);
|
|
521
|
+
this.maintainBots();
|
|
453
522
|
}, SERVER_CONFIG.resetTimeout);
|
|
523
|
+
debugPrint(`[${this.host}:${this.port}] 失败过多,进入冷却 ${ SERVER_CONFIG.resetTimeout/ 1000}s`);
|
|
454
524
|
}
|
|
455
|
-
|
|
525
|
+
return;
|
|
456
526
|
}
|
|
457
|
-
|
|
527
|
+
|
|
528
|
+
this.failedAttempts++;
|
|
529
|
+
|
|
530
|
+
// 如果传入 botName(来自断开重连),且它还在待重连名单中 → 优先用原名字
|
|
531
|
+
if (botName && this.pendingReconnect.has(botName)) {
|
|
532
|
+
debugPrint(`[${this.host}:${this.port}] 第 ${this.failedAttempts} 次尝试重连原机器人: ${botName}`);
|
|
533
|
+
this.createBot(botName); // 传名字,保持固定
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
// 否则(新补人或名字已被放弃)→ 不传名字,生成新的
|
|
537
|
+
debugPrint(`[${this.host}:${this.port}] 第 ${this.failedAttempts} 次连接失败,触发维护补人`);
|
|
538
|
+
this.maintainBots();
|
|
458
539
|
}
|
|
540
|
+
|
|
459
541
|
// 更新状态
|
|
460
542
|
updateStatus() {
|
|
461
543
|
const serverInfo = globalServerStatus.servers.get(`${this.host}:${this.port}`);
|
|
@@ -1551,29 +1633,14 @@ async function hotUpdateServers() {
|
|
|
1551
1633
|
const key = `${config.host}:${config.port}`;
|
|
1552
1634
|
console.log(`热更新: 新增服务器 ${key} (${config.minBots}-${config.maxBots})`);
|
|
1553
1635
|
toAdd.push(config);
|
|
1554
|
-
|
|
1555
|
-
// 立即在前台状态中注册(前端马上能看到 initializing)
|
|
1556
|
-
globalServerStatus.servers.set(key, {
|
|
1557
|
-
host: config.host,
|
|
1558
|
-
port: config.port,
|
|
1559
|
-
minBots: config.minBots,
|
|
1560
|
-
maxBots: config.maxBots,
|
|
1561
|
-
currentBots: 0,
|
|
1562
|
-
activeBots: [],
|
|
1563
|
-
lastUpdate: Date.now(),
|
|
1564
|
-
status: 'initializing'
|
|
1565
|
-
});
|
|
1566
1636
|
}
|
|
1567
1637
|
|
|
1568
1638
|
// 3. 先关闭 + 移除旧的管理器
|
|
1569
1639
|
for (const manager of toRemove) {
|
|
1570
|
-
manager.
|
|
1571
|
-
if (typeof manager.stopMonitoring === 'function') {
|
|
1572
|
-
manager.stopMonitoring();
|
|
1573
|
-
}
|
|
1640
|
+
manager.dispose(); // 统一释放资源
|
|
1574
1641
|
const idx = botManagers.indexOf(manager);
|
|
1575
1642
|
if (idx !== -1) botManagers.splice(idx, 1);
|
|
1576
|
-
}
|
|
1643
|
+
}
|
|
1577
1644
|
|
|
1578
1645
|
// 4. 创建新的管理器(构造函数里会自动注册到 globalServerStatus)
|
|
1579
1646
|
for (const config of toAdd) {
|
|
@@ -1587,7 +1654,6 @@ async function hotUpdateServers() {
|
|
|
1587
1654
|
newManager.startMonitoring();
|
|
1588
1655
|
botManagers.push(newManager);
|
|
1589
1656
|
}
|
|
1590
|
-
|
|
1591
1657
|
console.log(`热更新完成,当前运行服务器数量: ${botManagers.length},前端状态已同步`);
|
|
1592
1658
|
}
|
|
1593
1659
|
|
|
@@ -1607,6 +1673,16 @@ function shutdown() {
|
|
|
1607
1673
|
|
|
1608
1674
|
// 启动
|
|
1609
1675
|
if (require.main === module) {
|
|
1676
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
1677
|
+
console.error('未处理的 Promise Rejection:', reason);
|
|
1678
|
+
// 不退出进程,但至少能看到问题
|
|
1679
|
+
});
|
|
1680
|
+
|
|
1681
|
+
process.on('uncaughtException', (error) => {
|
|
1682
|
+
console.error('未捕获的异常:', error);
|
|
1683
|
+
// process.exit(1);
|
|
1684
|
+
});
|
|
1685
|
+
|
|
1610
1686
|
initialize().catch(error => {
|
|
1611
1687
|
console.error('初始化失败:', error);
|
|
1612
1688
|
process.exit(1);
|