blockmine 1.16.3 → 1.17.1
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/CHANGELOG.md +27 -0
- package/backend/prisma/migrations/20250723160648_add_bot_sort_order/migration.sql +2 -0
- package/backend/prisma/schema.prisma +229 -228
- package/backend/src/api/routes/bots.js +2144 -1801
- package/backend/src/api/routes/eventGraphs.js +459 -459
- package/backend/src/core/BotManager.js +912 -876
- package/backend/src/core/BotProcess.js +37 -6
- package/backend/src/core/commands/dev.js +20 -0
- package/frontend/dist/assets/index-D3DCCCQP.css +1 -0
- package/frontend/dist/assets/index-UZUhEwz5.js +8347 -0
- package/frontend/dist/index.html +2 -2
- package/frontend/package.json +3 -0
- package/package.json +82 -82
- package/frontend/dist/assets/index-CxCbyhFB.js +0 -8331
- package/frontend/dist/assets/index-_stfadil.css +0 -1
|
@@ -1,876 +1,912 @@
|
|
|
1
|
-
const { fork } = require('child_process');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const prisma = require('../lib/prisma');
|
|
4
|
-
const pidusage = require('pidusage');
|
|
5
|
-
const DependencyService = require('./DependencyService');
|
|
6
|
-
const config = require('../config');
|
|
7
|
-
const fs = require('fs');
|
|
8
|
-
const os = require('os');
|
|
9
|
-
const { v4: uuidv4 } = require('uuid');
|
|
10
|
-
const crypto = require('crypto');
|
|
11
|
-
const { decrypt } = require('./utils/crypto');
|
|
12
|
-
const EventGraphManager = require('./EventGraphManager');
|
|
13
|
-
const nodeRegistry = require('./NodeRegistry');
|
|
14
|
-
const UserService = require('./UserService');
|
|
15
|
-
|
|
16
|
-
const cooldowns = new Map();
|
|
17
|
-
const warningCache = new Map();
|
|
18
|
-
const WARNING_COOLDOWN = 10 * 1000;
|
|
19
|
-
|
|
20
|
-
const STATS_SERVER_URL = 'http://185.65.200.184:3000';
|
|
21
|
-
let instanceId = null;
|
|
22
|
-
const DATA_DIR = path.join(os.homedir(), '.blockmine');
|
|
23
|
-
const INSTANCE_ID_PATH = path.join(DATA_DIR, '.instance_id');
|
|
24
|
-
|
|
25
|
-
function getInstanceId() {
|
|
26
|
-
if (instanceId) return instanceId;
|
|
27
|
-
try {
|
|
28
|
-
if (fs.existsSync(INSTANCE_ID_PATH)) {
|
|
29
|
-
instanceId = fs.readFileSync(INSTANCE_ID_PATH, 'utf-8');
|
|
30
|
-
} else {
|
|
31
|
-
instanceId = uuidv4();
|
|
32
|
-
if (!fs.existsSync(DATA_DIR)) {
|
|
33
|
-
fs.mkdirSync(DATA_DIR, { recursive: true });
|
|
34
|
-
}
|
|
35
|
-
fs.writeFileSync(INSTANCE_ID_PATH, instanceId, 'utf-8');
|
|
36
|
-
}
|
|
37
|
-
} catch (error) {
|
|
38
|
-
console.error('[Telemetry] Ошибка при загрузке/создании Instance ID:', error);
|
|
39
|
-
return null;
|
|
40
|
-
}
|
|
41
|
-
return instanceId;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
class BotManager {
|
|
45
|
-
constructor() {
|
|
46
|
-
this.bots = new Map();
|
|
47
|
-
this.logCache = new Map();
|
|
48
|
-
this.resourceUsage = new Map();
|
|
49
|
-
this.botConfigs = new Map();
|
|
50
|
-
this.nodeRegistry = nodeRegistry;
|
|
51
|
-
this.pendingPlayerListRequests = new Map();
|
|
52
|
-
this.playerListCache = new Map();
|
|
53
|
-
this.eventGraphManager = null;
|
|
54
|
-
this.uiSubscriptions = new Map();
|
|
55
|
-
|
|
56
|
-
getInstanceId();
|
|
57
|
-
setInterval(() => this.updateAllResourceUsage(), 5000);
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
prisma.
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
if (
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
.
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
let
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
{ name: "admin
|
|
221
|
-
{ name: "
|
|
222
|
-
{ name: "user
|
|
223
|
-
{ name: "user.
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
"
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
const
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
const
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
}
|
|
420
|
-
break;
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
break;
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
break;
|
|
436
|
-
case '
|
|
437
|
-
|
|
438
|
-
break;
|
|
439
|
-
case '
|
|
440
|
-
|
|
441
|
-
break;
|
|
442
|
-
case '
|
|
443
|
-
|
|
444
|
-
break;
|
|
445
|
-
case '
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
const
|
|
560
|
-
if (!
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
return;
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
const
|
|
573
|
-
const
|
|
574
|
-
|
|
575
|
-
if (dbCommand
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
const
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
return
|
|
795
|
-
}
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
}
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
}
|
|
875
|
-
|
|
876
|
-
|
|
1
|
+
const { fork } = require('child_process');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const prisma = require('../lib/prisma');
|
|
4
|
+
const pidusage = require('pidusage');
|
|
5
|
+
const DependencyService = require('./DependencyService');
|
|
6
|
+
const config = require('../config');
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const os = require('os');
|
|
9
|
+
const { v4: uuidv4 } = require('uuid');
|
|
10
|
+
const crypto = require('crypto');
|
|
11
|
+
const { decrypt } = require('./utils/crypto');
|
|
12
|
+
const EventGraphManager = require('./EventGraphManager');
|
|
13
|
+
const nodeRegistry = require('./NodeRegistry');
|
|
14
|
+
const UserService = require('./UserService');
|
|
15
|
+
|
|
16
|
+
const cooldowns = new Map();
|
|
17
|
+
const warningCache = new Map();
|
|
18
|
+
const WARNING_COOLDOWN = 10 * 1000;
|
|
19
|
+
|
|
20
|
+
const STATS_SERVER_URL = 'http://185.65.200.184:3000';
|
|
21
|
+
let instanceId = null;
|
|
22
|
+
const DATA_DIR = path.join(os.homedir(), '.blockmine');
|
|
23
|
+
const INSTANCE_ID_PATH = path.join(DATA_DIR, '.instance_id');
|
|
24
|
+
|
|
25
|
+
function getInstanceId() {
|
|
26
|
+
if (instanceId) return instanceId;
|
|
27
|
+
try {
|
|
28
|
+
if (fs.existsSync(INSTANCE_ID_PATH)) {
|
|
29
|
+
instanceId = fs.readFileSync(INSTANCE_ID_PATH, 'utf-8');
|
|
30
|
+
} else {
|
|
31
|
+
instanceId = uuidv4();
|
|
32
|
+
if (!fs.existsSync(DATA_DIR)) {
|
|
33
|
+
fs.mkdirSync(DATA_DIR, { recursive: true });
|
|
34
|
+
}
|
|
35
|
+
fs.writeFileSync(INSTANCE_ID_PATH, instanceId, 'utf-8');
|
|
36
|
+
}
|
|
37
|
+
} catch (error) {
|
|
38
|
+
console.error('[Telemetry] Ошибка при загрузке/создании Instance ID:', error);
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
return instanceId;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
class BotManager {
|
|
45
|
+
constructor() {
|
|
46
|
+
this.bots = new Map();
|
|
47
|
+
this.logCache = new Map();
|
|
48
|
+
this.resourceUsage = new Map();
|
|
49
|
+
this.botConfigs = new Map();
|
|
50
|
+
this.nodeRegistry = nodeRegistry;
|
|
51
|
+
this.pendingPlayerListRequests = new Map();
|
|
52
|
+
this.playerListCache = new Map();
|
|
53
|
+
this.eventGraphManager = null;
|
|
54
|
+
this.uiSubscriptions = new Map();
|
|
55
|
+
|
|
56
|
+
getInstanceId();
|
|
57
|
+
setInterval(() => this.updateAllResourceUsage(), 5000);
|
|
58
|
+
setInterval(() => this.syncBotStatuses(), 10000);
|
|
59
|
+
if (config.telemetry?.enabled) {
|
|
60
|
+
setInterval(() => this.sendHeartbeat(), 5 * 60 * 1000);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
initialize() {
|
|
65
|
+
if (!this.eventGraphManager) {
|
|
66
|
+
this.eventGraphManager = new EventGraphManager(this);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
subscribeToPluginUi(botId, pluginName, socket) {
|
|
71
|
+
if (!this.uiSubscriptions.has(botId)) {
|
|
72
|
+
this.uiSubscriptions.set(botId, new Map());
|
|
73
|
+
}
|
|
74
|
+
const botSubscriptions = this.uiSubscriptions.get(botId);
|
|
75
|
+
|
|
76
|
+
if (!botSubscriptions.has(pluginName)) {
|
|
77
|
+
botSubscriptions.set(pluginName, new Set());
|
|
78
|
+
}
|
|
79
|
+
const pluginSubscribers = botSubscriptions.get(pluginName);
|
|
80
|
+
|
|
81
|
+
pluginSubscribers.add(socket);
|
|
82
|
+
console.log(`[UI Sub] Сокет ${socket.id} подписался на ${pluginName} для бота ${botId}. Всего подписчиков: ${pluginSubscribers.size}`);
|
|
83
|
+
|
|
84
|
+
const botProcess = this.bots.get(botId);
|
|
85
|
+
if (botProcess && !botProcess.killed) {
|
|
86
|
+
botProcess.send({ type: 'plugin:ui:start-updates', pluginName });
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
unsubscribeFromPluginUi(botId, pluginName, socket) {
|
|
91
|
+
const botSubscriptions = this.uiSubscriptions.get(botId);
|
|
92
|
+
if (!botSubscriptions) return;
|
|
93
|
+
|
|
94
|
+
const pluginSubscribers = botSubscriptions.get(pluginName);
|
|
95
|
+
if (!pluginSubscribers) return;
|
|
96
|
+
|
|
97
|
+
pluginSubscribers.delete(socket);
|
|
98
|
+
console.log(`[UI Sub] Сокет ${socket.id} отписался от ${pluginName} для бота ${botId}. Осталось: ${pluginSubscribers.size}`);
|
|
99
|
+
|
|
100
|
+
if (pluginSubscribers.size === 0) {
|
|
101
|
+
const botProcess = this.bots.get(botId);
|
|
102
|
+
if (botProcess && !botProcess.killed) {
|
|
103
|
+
botProcess.send({ type: 'plugin:ui:stop-updates', pluginName });
|
|
104
|
+
}
|
|
105
|
+
botSubscriptions.delete(pluginName);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
handleSocketDisconnect(socket) {
|
|
110
|
+
this.uiSubscriptions.forEach((botSubscriptions, botId) => {
|
|
111
|
+
botSubscriptions.forEach((pluginSubscribers, pluginName) => {
|
|
112
|
+
if (pluginSubscribers.has(socket)) {
|
|
113
|
+
this.unsubscribeFromPluginUi(botId, pluginName, socket);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async loadConfigForBot(botId) {
|
|
120
|
+
console.log(`[BotManager] Caching configuration for bot ID ${botId}...`);
|
|
121
|
+
try {
|
|
122
|
+
const [commands, permissions] = await Promise.all([
|
|
123
|
+
prisma.command.findMany({ where: { botId } }),
|
|
124
|
+
prisma.permission.findMany({ where: { botId } }),
|
|
125
|
+
]);
|
|
126
|
+
const config = {
|
|
127
|
+
commands: new Map(commands.map(cmd => [cmd.name, cmd])),
|
|
128
|
+
permissionsById: new Map(permissions.map(p => [p.id, p])),
|
|
129
|
+
commandAliases: new Map()
|
|
130
|
+
};
|
|
131
|
+
for (const cmd of commands) {
|
|
132
|
+
const aliases = JSON.parse(cmd.aliases || '[]');
|
|
133
|
+
for (const alias of aliases) {
|
|
134
|
+
config.commandAliases.set(alias, cmd.name);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
this.botConfigs.set(botId, config);
|
|
138
|
+
console.log(`[BotManager] Configuration for bot ID ${botId} cached successfully.`);
|
|
139
|
+
return config;
|
|
140
|
+
} catch (error) {
|
|
141
|
+
console.error(`[BotManager] Failed to cache configuration for bot ${botId}:`, error);
|
|
142
|
+
throw new Error(`Failed to load/cache bot configuration for botId ${botId}: ${error.message}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async _ensureDefaultEventGraphs(botId) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
invalidateConfigCache(botId) {
|
|
151
|
+
if (this.botConfigs.has(botId)) {
|
|
152
|
+
this.botConfigs.delete(botId);
|
|
153
|
+
console.log(`[BotManager] Invalidated config cache for bot ID ${botId}. It will be reloaded on next command.`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
reloadBotConfigInRealTime(botId) {
|
|
158
|
+
const { getIO } = require('../real-time/socketHandler');
|
|
159
|
+
this.invalidateConfigCache(botId);
|
|
160
|
+
const child = this.bots.get(botId);
|
|
161
|
+
if (child && !child.killed) {
|
|
162
|
+
child.send({ type: 'config:reload' });
|
|
163
|
+
console.log(`[BotManager] Sent config:reload to bot process ${botId}`);
|
|
164
|
+
getIO().emit('bot:config_reloaded', { botId });
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
triggerHeartbeat() {
|
|
169
|
+
if (!config.telemetry?.enabled) return;
|
|
170
|
+
if (this.heartbeatDebounceTimer) {
|
|
171
|
+
clearTimeout(this.heartbeatDebounceTimer);
|
|
172
|
+
}
|
|
173
|
+
this.heartbeatDebounceTimer = setTimeout(() => {
|
|
174
|
+
this.sendHeartbeat();
|
|
175
|
+
}, 3000);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async sendHeartbeat() {
|
|
179
|
+
if (!config.telemetry?.enabled || !instanceId) return;
|
|
180
|
+
try {
|
|
181
|
+
const runningBots = Array.from(this.bots.values())
|
|
182
|
+
.filter(p => p.botConfig)
|
|
183
|
+
.map(p => ({
|
|
184
|
+
username: p.botConfig.username,
|
|
185
|
+
serverHost: p.botConfig.server.host,
|
|
186
|
+
serverPort: p.botConfig.server.port
|
|
187
|
+
}));
|
|
188
|
+
|
|
189
|
+
if (runningBots.length === 0) return;
|
|
190
|
+
|
|
191
|
+
const challengeRes = await fetch(`${STATS_SERVER_URL}/api/challenge?uuid=${instanceId}`);
|
|
192
|
+
if (!challengeRes.ok) throw new Error(`Challenge server error: ${challengeRes.statusText}`);
|
|
193
|
+
|
|
194
|
+
const { challenge, difficulty, prefix } = await challengeRes.json();
|
|
195
|
+
let nonce = 0;
|
|
196
|
+
let hash = '';
|
|
197
|
+
do {
|
|
198
|
+
nonce++;
|
|
199
|
+
hash = crypto.createHash('sha256').update(prefix + challenge + nonce).digest('hex');
|
|
200
|
+
} while (!hash.startsWith('0'.repeat(difficulty)));
|
|
201
|
+
|
|
202
|
+
const packageJson = require('../../../package.json');
|
|
203
|
+
await fetch(`${STATS_SERVER_URL}/api/heartbeat`, {
|
|
204
|
+
method: 'POST',
|
|
205
|
+
headers: { 'Content-Type': 'application/json' },
|
|
206
|
+
body: JSON.stringify({
|
|
207
|
+
instanceUuid: instanceId,
|
|
208
|
+
appVersion: packageJson.version,
|
|
209
|
+
bots: runningBots,
|
|
210
|
+
nonce: nonce
|
|
211
|
+
})
|
|
212
|
+
});
|
|
213
|
+
} catch (error) {
|
|
214
|
+
console.error(`[Telemetry] Не удалось отправить heartbeat: ${error.message}`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async _syncSystemPermissions(botId) {
|
|
219
|
+
const systemPermissions = [
|
|
220
|
+
{ name: "admin.*", description: "Все права администратора" },
|
|
221
|
+
{ name: "admin.cooldown.bypass", description: "Обход кулдауна для админ-команд" },
|
|
222
|
+
{ name: "user.*", description: "Все права обычного пользователя" },
|
|
223
|
+
{ name: "user.say", description: "Доступ к простым командам" },
|
|
224
|
+
{ name: "user.cooldown.bypass", description: "Обход кулдауна для юзер-команд" },
|
|
225
|
+
];
|
|
226
|
+
const systemGroups = ["User", "Admin"];
|
|
227
|
+
const systemGroupPermissions = {
|
|
228
|
+
"User": ["user.say"],
|
|
229
|
+
"Admin": ["admin.*", "admin.cooldown.bypass", "user.cooldown.bypass", "user.*"]
|
|
230
|
+
};
|
|
231
|
+
console.log(`[Permission Sync] Синхронизация системных прав для бота ID ${botId}...`);
|
|
232
|
+
for (const perm of systemPermissions) {
|
|
233
|
+
await prisma.permission.upsert({
|
|
234
|
+
where: { botId_name: { botId, name: perm.name } },
|
|
235
|
+
update: { description: perm.description },
|
|
236
|
+
create: { ...perm, botId, owner: 'system' }
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
for (const groupName of systemGroups) {
|
|
240
|
+
await prisma.group.upsert({
|
|
241
|
+
where: { botId_name: { botId, name: groupName } },
|
|
242
|
+
update: {},
|
|
243
|
+
create: { name: groupName, botId, owner: 'system' }
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
for (const [groupName, permNames] of Object.entries(systemGroupPermissions)) {
|
|
247
|
+
const group = await prisma.group.findUnique({ where: { botId_name: { botId, name: groupName } } });
|
|
248
|
+
if (group) {
|
|
249
|
+
for (const permName of permNames) {
|
|
250
|
+
const permission = await prisma.permission.findUnique({ where: { botId_name: { botId, name: permName } } });
|
|
251
|
+
if (permission) {
|
|
252
|
+
await prisma.groupPermission.upsert({
|
|
253
|
+
where: { groupId_permissionId: { groupId: group.id, permissionId: permission.id } },
|
|
254
|
+
update: {},
|
|
255
|
+
create: { groupId: group.id, permissionId: permission.id }
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
console.log(`[Permission Sync] Синхронизация для бота ID ${botId} завершена.`);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
async updateAllResourceUsage() {
|
|
265
|
+
const { getIO } = require('../real-time/socketHandler');
|
|
266
|
+
if (this.bots.size === 0) {
|
|
267
|
+
if (this.resourceUsage.size > 0) {
|
|
268
|
+
this.resourceUsage.clear();
|
|
269
|
+
getIO().emit('bots:usage', []);
|
|
270
|
+
}
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
const pids = Array.from(this.bots.values()).map(child => child.pid).filter(Boolean);
|
|
274
|
+
if (pids.length === 0) return;
|
|
275
|
+
try {
|
|
276
|
+
const stats = await pidusage(pids);
|
|
277
|
+
const usageData = [];
|
|
278
|
+
for (const pid in stats) {
|
|
279
|
+
if (!stats[pid]) continue;
|
|
280
|
+
const botId = this.getBotIdByPid(parseInt(pid, 10));
|
|
281
|
+
if (botId) {
|
|
282
|
+
const usage = {
|
|
283
|
+
botId: botId,
|
|
284
|
+
cpu: parseFloat(stats[pid].cpu.toFixed(1)),
|
|
285
|
+
memory: parseFloat((stats[pid].memory / 1024 / 1024).toFixed(1)),
|
|
286
|
+
};
|
|
287
|
+
this.resourceUsage.set(botId, usage);
|
|
288
|
+
usageData.push(usage);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
getIO().emit('bots:usage', usageData);
|
|
292
|
+
} catch (error) {}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
getBotIdByPid(pid) {
|
|
296
|
+
for (const [botId, child] of this.bots.entries()) {
|
|
297
|
+
if (child.pid === pid) {
|
|
298
|
+
return botId;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
return null;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
getFullState() {
|
|
305
|
+
const statuses = {};
|
|
306
|
+
for (const [id, child] of this.bots.entries()) {
|
|
307
|
+
statuses[id] = child.killed ? 'stopped' : 'running';
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const logs = {};
|
|
311
|
+
for (const [botId, logArray] of this.logCache.entries()) {
|
|
312
|
+
logs[botId] = logArray;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return {
|
|
316
|
+
statuses,
|
|
317
|
+
logs,
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
emitStatusUpdate(botId, status, message = null) {
|
|
322
|
+
const { getIO } = require('../real-time/socketHandler');
|
|
323
|
+
if (message) this.appendLog(botId, `[SYSTEM] ${message}`);
|
|
324
|
+
getIO().emit('bot:status', { botId, status, message });
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
syncBotStatuses() {
|
|
328
|
+
for (const [botId, child] of this.bots.entries()) {
|
|
329
|
+
const actualStatus = child.killed ? 'stopped' : 'running';
|
|
330
|
+
const { getIO } = require('../real-time/socketHandler');
|
|
331
|
+
getIO().emit('bot:status', { botId, status: actualStatus });
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
appendLog(botId, logContent) {
|
|
336
|
+
const { getIO } = require('../real-time/socketHandler');
|
|
337
|
+
const logEntry = {
|
|
338
|
+
id: Date.now() + Math.random(),
|
|
339
|
+
content: logContent,
|
|
340
|
+
};
|
|
341
|
+
const currentLogs = this.logCache.get(botId) || [];
|
|
342
|
+
const newLogs = [...currentLogs.slice(-199), logEntry];
|
|
343
|
+
this.logCache.set(botId, newLogs);
|
|
344
|
+
getIO().emit('bot:log', { botId, log: logEntry });
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
getBotLogs(botId) {
|
|
348
|
+
return this.logCache.get(botId) || [];
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
async startBot(botConfig) {
|
|
352
|
+
if (this.bots.has(botConfig.id) && !this.bots.get(botConfig.id).killed) {
|
|
353
|
+
this.appendLog(botConfig.id, `[SYSTEM-ERROR] Попытка повторного запуска. Запуск отменен.`);
|
|
354
|
+
return { success: false, message: 'Бот уже запущен или запускается.' };
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
await this._syncSystemPermissions(botConfig.id);
|
|
358
|
+
await this.loadConfigForBot(botConfig.id);
|
|
359
|
+
this.logCache.set(botConfig.id, []);
|
|
360
|
+
this.emitStatusUpdate(botConfig.id, 'starting', '');
|
|
361
|
+
|
|
362
|
+
const allPluginsForBot = await prisma.installedPlugin.findMany({ where: { botId: botConfig.id, isEnabled: true } });
|
|
363
|
+
const { sortedPlugins, hasCriticalIssues, pluginInfo } = DependencyService.resolveDependencies(allPluginsForBot, allPluginsForBot);
|
|
364
|
+
|
|
365
|
+
if (hasCriticalIssues) {
|
|
366
|
+
this.appendLog(botConfig.id, '[DependencyManager] Обнаружены критические проблемы с зависимостями, запуск отменен.');
|
|
367
|
+
|
|
368
|
+
const criticalIssueTypes = new Set(['missing_dependency', 'version_mismatch', 'circular_dependency']);
|
|
369
|
+
|
|
370
|
+
for (const pluginId in pluginInfo) {
|
|
371
|
+
const info = pluginInfo[pluginId];
|
|
372
|
+
if (info.issues.length === 0) continue;
|
|
373
|
+
|
|
374
|
+
const criticalIssues = info.issues.filter(issue => criticalIssueTypes.has(issue.type));
|
|
375
|
+
|
|
376
|
+
if (criticalIssues.length > 0) {
|
|
377
|
+
this.appendLog(botConfig.id, `* Плагин "${info.name}":`);
|
|
378
|
+
for (const issue of criticalIssues) {
|
|
379
|
+
this.appendLog(botConfig.id, ` - ${issue.message}`);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
this.emitStatusUpdate(botConfig.id, 'stopped', 'Ошибка зависимостей плагинов.');
|
|
385
|
+
return { success: false, message: 'Критические ошибки в зависимостях плагинов.' };
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const decryptedConfig = { ...botConfig };
|
|
389
|
+
if (decryptedConfig.password) decryptedConfig.password = decrypt(decryptedConfig.password);
|
|
390
|
+
if (decryptedConfig.proxyPassword) decryptedConfig.proxyPassword = decrypt(decryptedConfig.proxyPassword);
|
|
391
|
+
|
|
392
|
+
if (decryptedConfig.proxyUsername) decryptedConfig.proxyUsername = decryptedConfig.proxyUsername.trim();
|
|
393
|
+
if (decryptedConfig.proxyPassword) decryptedConfig.proxyPassword = decryptedConfig.proxyPassword.trim();
|
|
394
|
+
|
|
395
|
+
const fullBotConfig = { ...decryptedConfig, plugins: sortedPlugins };
|
|
396
|
+
const botProcessPath = path.resolve(__dirname, 'BotProcess.js');
|
|
397
|
+
const child = fork(botProcessPath, [], { stdio: ['pipe', 'pipe', 'pipe', 'ipc'] });
|
|
398
|
+
|
|
399
|
+
child.botConfig = botConfig;
|
|
400
|
+
|
|
401
|
+
child.api = {
|
|
402
|
+
sendMessage: (type, message, username) => {
|
|
403
|
+
if (!child.killed) {
|
|
404
|
+
child.send({ type: 'chat', payload: { message, chatType: type, username } });
|
|
405
|
+
}
|
|
406
|
+
},
|
|
407
|
+
sendLog: (message) => {
|
|
408
|
+
this.appendLog(botConfig.id, message);
|
|
409
|
+
}
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
child.on('message', async (message) => {
|
|
413
|
+
const botId = botConfig.id;
|
|
414
|
+
try {
|
|
415
|
+
switch (message.type) {
|
|
416
|
+
case 'event':
|
|
417
|
+
if (this.eventGraphManager) {
|
|
418
|
+
this.eventGraphManager.handleEvent(botId, message.eventType, message.args);
|
|
419
|
+
}
|
|
420
|
+
break;
|
|
421
|
+
case 'plugin:data': {
|
|
422
|
+
const { plugin: pluginName, payload } = message;
|
|
423
|
+
const botSubscriptions = this.uiSubscriptions.get(botId);
|
|
424
|
+
if (!botSubscriptions) break;
|
|
425
|
+
|
|
426
|
+
const pluginSubscribers = botSubscriptions.get(pluginName);
|
|
427
|
+
if (pluginSubscribers && pluginSubscribers.size > 0) {
|
|
428
|
+
pluginSubscribers.forEach(socket => {
|
|
429
|
+
socket.emit('plugin:ui:dataUpdate', payload);
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
break;
|
|
433
|
+
}
|
|
434
|
+
case 'plugin:stopped':
|
|
435
|
+
break;
|
|
436
|
+
case 'log':
|
|
437
|
+
this.appendLog(botId, message.content);
|
|
438
|
+
break;
|
|
439
|
+
case 'status':
|
|
440
|
+
this.emitStatusUpdate(botId, message.status);
|
|
441
|
+
break;
|
|
442
|
+
case 'bot_ready':
|
|
443
|
+
this.emitStatusUpdate(botId, 'running', 'Бот успешно подключился к серверу.');
|
|
444
|
+
break;
|
|
445
|
+
case 'validate_and_run_command':
|
|
446
|
+
await this.handleCommandValidation(botConfig, message);
|
|
447
|
+
break;
|
|
448
|
+
case 'register_command':
|
|
449
|
+
await this.handleCommandRegistration(botId, message.commandConfig);
|
|
450
|
+
break;
|
|
451
|
+
case 'register_group':
|
|
452
|
+
await this.handleGroupRegistration(botId, message.groupConfig);
|
|
453
|
+
break;
|
|
454
|
+
case 'register_permissions':
|
|
455
|
+
await this.handlePermissionsRegistration(botId, message);
|
|
456
|
+
break;
|
|
457
|
+
case 'add_permissions_to_group':
|
|
458
|
+
await this.handleAddPermissionsToGroup(botId, message);
|
|
459
|
+
break;
|
|
460
|
+
case 'request_user_action':
|
|
461
|
+
const { requestId, payload } = message;
|
|
462
|
+
const { targetUsername, action, data } = payload;
|
|
463
|
+
|
|
464
|
+
try {
|
|
465
|
+
const user = await UserService.getUser(targetUsername, botConfig.id);
|
|
466
|
+
if (!user) throw new Error(`Пользователь ${targetUsername} не найден.`);
|
|
467
|
+
|
|
468
|
+
let result;
|
|
469
|
+
|
|
470
|
+
switch (action) {
|
|
471
|
+
case 'addGroup':
|
|
472
|
+
result = await user.addGroup(data.group);
|
|
473
|
+
break;
|
|
474
|
+
case 'removeGroup':
|
|
475
|
+
result = await user.removeGroup(data.group);
|
|
476
|
+
break;
|
|
477
|
+
case 'addPermission':
|
|
478
|
+
break;
|
|
479
|
+
case 'removePermission':
|
|
480
|
+
break;
|
|
481
|
+
case 'getGroups':
|
|
482
|
+
result = user.groups ? user.groups.map(g => g.group.name) : [];
|
|
483
|
+
break;
|
|
484
|
+
case 'getPermissions':
|
|
485
|
+
result = Array.from(user.permissionsSet);
|
|
486
|
+
break;
|
|
487
|
+
case 'isBlacklisted':
|
|
488
|
+
result = user.isBlacklisted;
|
|
489
|
+
break;
|
|
490
|
+
case 'setBlacklisted':
|
|
491
|
+
result = await user.setBlacklist(data.value);
|
|
492
|
+
break;
|
|
493
|
+
default:
|
|
494
|
+
throw new Error(`Неизвестное действие: ${action}`);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
child.send({ type: 'user_action_response', requestId, payload: result });
|
|
498
|
+
} catch (error) {
|
|
499
|
+
console.error(`[BotManager] Ошибка выполнения действия '${action}' для пользователя '${targetUsername}':`, error);
|
|
500
|
+
child.send({ type: 'user_action_response', requestId, error: error.message });
|
|
501
|
+
}
|
|
502
|
+
break;
|
|
503
|
+
case 'playerListUpdate':
|
|
504
|
+
break;
|
|
505
|
+
case 'get_player_list_response': {
|
|
506
|
+
const { requestId, payload } = message;
|
|
507
|
+
const request = this.pendingPlayerListRequests.get(requestId);
|
|
508
|
+
if (request) {
|
|
509
|
+
clearTimeout(request.timeout);
|
|
510
|
+
request.resolve(payload.players);
|
|
511
|
+
this.pendingPlayerListRequests.delete(requestId);
|
|
512
|
+
}
|
|
513
|
+
break;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
} catch (error) {
|
|
517
|
+
this.appendLog(botId, `[SYSTEM-ERROR] Критическая ошибка в обработчике сообщений от бота: ${error.stack}`);
|
|
518
|
+
console.error(`[BotManager] Критическая ошибка в обработчике сообщений от бота ${botId}:`, error);
|
|
519
|
+
}
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
child.on('error', (err) => this.appendLog(botConfig.id, `[PROCESS FATAL] ${err.stack}`));
|
|
523
|
+
child.stdout.on('data', (data) => console.log(data.toString()));
|
|
524
|
+
child.stderr.on('data', (data) => this.appendLog(botConfig.id, `[STDERR] ${data.toString()}`));
|
|
525
|
+
|
|
526
|
+
child.on('exit', (code, signal) => {
|
|
527
|
+
const botId = botConfig.id;
|
|
528
|
+
this.bots.delete(botId);
|
|
529
|
+
this.resourceUsage.delete(botId);
|
|
530
|
+
this.botConfigs.delete(botId);
|
|
531
|
+
this.emitStatusUpdate(botId, 'stopped', `Процесс завершился с кодом ${code} (сигнал: ${signal || 'none'}).`);
|
|
532
|
+
this.updateAllResourceUsage();
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
this.bots.set(botConfig.id, child);
|
|
536
|
+
child.send({ type: 'start', config: fullBotConfig });
|
|
537
|
+
|
|
538
|
+
await this.eventGraphManager.loadGraphsForBot(botConfig.id);
|
|
539
|
+
|
|
540
|
+
this.triggerHeartbeat();
|
|
541
|
+
const { getIO } = require('../real-time/socketHandler');
|
|
542
|
+
getIO().emit('bot:status', { botId: botConfig.id, status: 'starting' });
|
|
543
|
+
return child;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
async handleCommandValidation(botConfig, message) {
|
|
547
|
+
const { commandName, username, args, typeChat } = message;
|
|
548
|
+
const botId = botConfig.id;
|
|
549
|
+
|
|
550
|
+
try {
|
|
551
|
+
let botConfigCache = this.botConfigs.get(botId);
|
|
552
|
+
if (!botConfigCache) {
|
|
553
|
+
console.log(`[BotManager] No cache for ${botId}, loading...`);
|
|
554
|
+
botConfigCache = await this.loadConfigForBot(botId);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
const user = await UserService.getUser(username, botId, botConfig);
|
|
558
|
+
|
|
559
|
+
const child = this.bots.get(botId);
|
|
560
|
+
if (!child) return;
|
|
561
|
+
|
|
562
|
+
if (user.isBlacklisted) {
|
|
563
|
+
child.send({
|
|
564
|
+
type: 'handle_blacklist',
|
|
565
|
+
commandName: commandName,
|
|
566
|
+
username,
|
|
567
|
+
typeChat
|
|
568
|
+
});
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
const mainCommandName = botConfigCache.commandAliases.get(commandName) || commandName;
|
|
573
|
+
const dbCommand = botConfigCache.commands.get(mainCommandName);
|
|
574
|
+
|
|
575
|
+
if (!dbCommand || (!dbCommand.isEnabled && !user.isOwner)) {
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
const allowedTypes = JSON.parse(dbCommand.allowedChatTypes || '[]');
|
|
580
|
+
if (!allowedTypes.includes(typeChat) && !user.isOwner) {
|
|
581
|
+
if (typeChat === 'global') return;
|
|
582
|
+
child.send({
|
|
583
|
+
type: 'handle_wrong_chat',
|
|
584
|
+
commandName: dbCommand.name,
|
|
585
|
+
username,
|
|
586
|
+
typeChat
|
|
587
|
+
});
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
const permission = dbCommand.permissionId ? botConfigCache.permissionsById.get(dbCommand.permissionId) : null;
|
|
592
|
+
if (permission && !user.hasPermission(permission.name)) {
|
|
593
|
+
child.send({
|
|
594
|
+
type: 'handle_permission_error',
|
|
595
|
+
commandName: dbCommand.name,
|
|
596
|
+
username,
|
|
597
|
+
typeChat
|
|
598
|
+
});
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
const domain = (permission?.name || '').split('.')[0] || 'user';
|
|
603
|
+
const bypassCooldownPermission = `${domain}.cooldown.bypass`;
|
|
604
|
+
|
|
605
|
+
if (dbCommand.cooldown > 0 && !user.isOwner && !user.hasPermission(bypassCooldownPermission)) {
|
|
606
|
+
const cooldownKey = `${botId}:${dbCommand.name}:${user.id}`;
|
|
607
|
+
const now = Date.now();
|
|
608
|
+
const lastUsed = cooldowns.get(cooldownKey);
|
|
609
|
+
|
|
610
|
+
if (lastUsed && (now - lastUsed < dbCommand.cooldown * 1000)) {
|
|
611
|
+
const timeLeft = Math.ceil((dbCommand.cooldown * 1000 - (now - lastUsed)) / 1000);
|
|
612
|
+
child.send({
|
|
613
|
+
type: 'handle_cooldown',
|
|
614
|
+
commandName: dbCommand.name,
|
|
615
|
+
username,
|
|
616
|
+
typeChat,
|
|
617
|
+
timeLeft
|
|
618
|
+
});
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
cooldowns.set(cooldownKey, now);
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
if (this.eventGraphManager) {
|
|
625
|
+
this.eventGraphManager.handleEvent(botId, 'command', {
|
|
626
|
+
commandName: dbCommand.name,
|
|
627
|
+
user: { username },
|
|
628
|
+
args,
|
|
629
|
+
typeChat
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
child.send({ type: 'execute_handler', commandName: dbCommand.name, username, args, typeChat });
|
|
634
|
+
|
|
635
|
+
} catch (error) {
|
|
636
|
+
console.error(`[BotManager] Command validation error for botId: ${botId}`, {
|
|
637
|
+
command: commandName, user: username, error: error.message, stack: error.stack
|
|
638
|
+
});
|
|
639
|
+
this.sendMessageToBot(botId, `Произошла внутренняя ошибка при выполнении команды.`, 'private', username);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
async handleCommandRegistration(botId, commandConfig) {
|
|
644
|
+
try {
|
|
645
|
+
let permissionId = null;
|
|
646
|
+
if (commandConfig.permissions) {
|
|
647
|
+
let permission = await prisma.permission.findUnique({
|
|
648
|
+
where: { botId_name: { botId, name: commandConfig.permissions } }
|
|
649
|
+
});
|
|
650
|
+
if (!permission) {
|
|
651
|
+
permission = await prisma.permission.create({
|
|
652
|
+
data: {
|
|
653
|
+
botId,
|
|
654
|
+
name: commandConfig.permissions,
|
|
655
|
+
description: `Автоматически создано для команды ${commandConfig.name}`,
|
|
656
|
+
owner: commandConfig.owner,
|
|
657
|
+
}
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
permissionId = permission.id;
|
|
661
|
+
}
|
|
662
|
+
const createData = {
|
|
663
|
+
botId,
|
|
664
|
+
name: commandConfig.name,
|
|
665
|
+
description: commandConfig.description,
|
|
666
|
+
aliases: JSON.stringify(commandConfig.aliases || []),
|
|
667
|
+
owner: commandConfig.owner,
|
|
668
|
+
permissionId: permissionId,
|
|
669
|
+
allowedChatTypes: JSON.stringify(commandConfig.allowedChatTypes || []),
|
|
670
|
+
cooldown: commandConfig.cooldown || 0,
|
|
671
|
+
};
|
|
672
|
+
const updateData = {
|
|
673
|
+
description: commandConfig.description,
|
|
674
|
+
owner: commandConfig.owner,
|
|
675
|
+
};
|
|
676
|
+
await prisma.command.upsert({
|
|
677
|
+
where: { botId_name: { botId, name: commandConfig.name } },
|
|
678
|
+
update: updateData,
|
|
679
|
+
create: createData,
|
|
680
|
+
});
|
|
681
|
+
this.invalidateConfigCache(botId);
|
|
682
|
+
} catch (error) {
|
|
683
|
+
console.error(`[BotManager] Ошибка при регистрации команды '${commandConfig.name}':`, error);
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
async handleGroupRegistration(botId, groupConfig) {
|
|
688
|
+
try {
|
|
689
|
+
await prisma.group.upsert({
|
|
690
|
+
where: { botId_name: { botId, name: groupConfig.name } },
|
|
691
|
+
update: {
|
|
692
|
+
owner: groupConfig.owner,
|
|
693
|
+
},
|
|
694
|
+
create: {
|
|
695
|
+
botId,
|
|
696
|
+
name: groupConfig.name,
|
|
697
|
+
owner: groupConfig.owner,
|
|
698
|
+
},
|
|
699
|
+
});
|
|
700
|
+
this.invalidateConfigCache(botId);
|
|
701
|
+
} catch (error) {
|
|
702
|
+
console.error(`[BotManager] Ошибка при регистрации группы '${groupConfig.name}':`, error);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
async handlePermissionsRegistration(botId, message) {
|
|
707
|
+
try {
|
|
708
|
+
const { permissions } = message;
|
|
709
|
+
for (const perm of permissions) {
|
|
710
|
+
if (!perm.name || !perm.owner) {
|
|
711
|
+
console.warn(`[BotManager] Пропущено право без имени или владельца для бота ${botId}:`, perm);
|
|
712
|
+
continue;
|
|
713
|
+
}
|
|
714
|
+
await prisma.permission.upsert({
|
|
715
|
+
where: { botId_name: { botId, name: perm.name } },
|
|
716
|
+
update: { description: perm.description },
|
|
717
|
+
create: {
|
|
718
|
+
botId,
|
|
719
|
+
name: perm.name,
|
|
720
|
+
description: perm.description || '',
|
|
721
|
+
owner: perm.owner,
|
|
722
|
+
},
|
|
723
|
+
});
|
|
724
|
+
}
|
|
725
|
+
this.invalidateConfigCache(botId);
|
|
726
|
+
} catch (error) {
|
|
727
|
+
console.error(`[BotManager] Ошибка при регистрации прав для бота ${botId}:`, error);
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
async handleAddPermissionsToGroup(botId, message) {
|
|
732
|
+
try {
|
|
733
|
+
const { groupName, permissionNames } = message;
|
|
734
|
+
|
|
735
|
+
const group = await prisma.group.findUnique({
|
|
736
|
+
where: { botId_name: { botId, name: groupName } }
|
|
737
|
+
});
|
|
738
|
+
|
|
739
|
+
if (!group) {
|
|
740
|
+
console.warn(`[BotManager] Попытка добавить права в несуществующую группу "${groupName}" для бота ID ${botId}.`);
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
for (const permName of permissionNames) {
|
|
745
|
+
const permission = await prisma.permission.findUnique({
|
|
746
|
+
where: { botId_name: { botId, name: permName } }
|
|
747
|
+
});
|
|
748
|
+
|
|
749
|
+
if (permission) {
|
|
750
|
+
await prisma.groupPermission.upsert({
|
|
751
|
+
where: { groupId_permissionId: { groupId: group.id, permissionId: permission.id } },
|
|
752
|
+
update: {},
|
|
753
|
+
create: { groupId: group.id, permissionId: permission.id },
|
|
754
|
+
});
|
|
755
|
+
} else {
|
|
756
|
+
console.warn(`[BotManager] Право "${permName}" не найдено для бота ID ${botId} при добавлении в группу "${groupName}".`);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
this.invalidateConfigCache(botId);
|
|
761
|
+
} catch (error) {
|
|
762
|
+
console.error(`[BotManager] Ошибка при добавлении прав в группу "${message.groupName}" для бота ${botId}:`, error);
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
stopBot(botId) {
|
|
767
|
+
const child = this.bots.get(botId);
|
|
768
|
+
if (child) {
|
|
769
|
+
this.eventGraphManager.unloadGraphsForBot(botId);
|
|
770
|
+
|
|
771
|
+
child.send({ type: 'stop' });
|
|
772
|
+
|
|
773
|
+
setTimeout(() => {
|
|
774
|
+
if (!child.killed) {
|
|
775
|
+
console.log(`[BotManager] Принудительное завершение процесса бота ${botId}`);
|
|
776
|
+
try {
|
|
777
|
+
child.kill('SIGKILL');
|
|
778
|
+
} catch (error) {
|
|
779
|
+
console.error(`[BotManager] Ошибка при принудительном завершении бота ${botId}:`, error);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
}, 5000);
|
|
783
|
+
|
|
784
|
+
this.botConfigs.delete(botId);
|
|
785
|
+
return { success: true };
|
|
786
|
+
}
|
|
787
|
+
return { success: false, message: 'Бот не найден или уже остановлен' };
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
sendMessageToBot(botId, message, chatType = 'command', username = null) {
|
|
791
|
+
const child = this.bots.get(botId);
|
|
792
|
+
if (child) {
|
|
793
|
+
child.api.sendMessage(chatType, message, username);
|
|
794
|
+
return { success: true };
|
|
795
|
+
}
|
|
796
|
+
return { success: false, message: 'Бот не найден или не запущен' };
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
invalidateUserCache(botId, username) {
|
|
800
|
+
UserService.clearCache(username, botId);
|
|
801
|
+
const child = this.bots.get(botId);
|
|
802
|
+
if (child) {
|
|
803
|
+
child.send({ type: 'invalidate_user_cache', username });
|
|
804
|
+
}
|
|
805
|
+
return { success: true };
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
invalidateAllUserCache(botId) {
|
|
809
|
+
for (const [cacheKey, user] of UserService.cache.entries()) {
|
|
810
|
+
if (cacheKey.startsWith(`${botId}:`)) {
|
|
811
|
+
UserService.cache.delete(cacheKey);
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
console.log(`[BotManager] Кэш пользователей очищен для бота ${botId}`);
|
|
815
|
+
|
|
816
|
+
const child = this.bots.get(botId);
|
|
817
|
+
if (child && !child.killed) {
|
|
818
|
+
child.send({ type: 'invalidate_all_user_cache' });
|
|
819
|
+
console.log(`[BotManager] Отправлено сообщение об очистке кэша в процесс бота ${botId}`);
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
return { success: true };
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
async getPlayerList(botId) {
|
|
826
|
+
const PLAYER_LIST_CACHE_TTL = 2000;
|
|
827
|
+
|
|
828
|
+
const child = this.bots.get(botId);
|
|
829
|
+
if (!child || child.killed) {
|
|
830
|
+
return [];
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
const cachedEntry = this.playerListCache.get(botId);
|
|
834
|
+
if (cachedEntry && (Date.now() - cachedEntry.timestamp < PLAYER_LIST_CACHE_TTL)) {
|
|
835
|
+
return cachedEntry.promise;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
const newPromise = new Promise((resolve) => {
|
|
839
|
+
const requestId = uuidv4();
|
|
840
|
+
const timeout = setTimeout(() => {
|
|
841
|
+
this.pendingPlayerListRequests.delete(requestId);
|
|
842
|
+
if (this.playerListCache.get(botId)?.promise === newPromise) {
|
|
843
|
+
this.playerListCache.delete(botId);
|
|
844
|
+
}
|
|
845
|
+
resolve([]);
|
|
846
|
+
}, 5000);
|
|
847
|
+
|
|
848
|
+
this.pendingPlayerListRequests.set(requestId, {
|
|
849
|
+
resolve: (playerList) => {
|
|
850
|
+
clearTimeout(timeout);
|
|
851
|
+
this.pendingPlayerListRequests.delete(requestId);
|
|
852
|
+
this.playerListCache.set(botId, {
|
|
853
|
+
promise: Promise.resolve(playerList),
|
|
854
|
+
timestamp: Date.now()
|
|
855
|
+
});
|
|
856
|
+
resolve(playerList);
|
|
857
|
+
},
|
|
858
|
+
reject: (error) => {
|
|
859
|
+
clearTimeout(timeout);
|
|
860
|
+
this.pendingPlayerListRequests.delete(requestId);
|
|
861
|
+
if (this.playerListCache.get(botId)?.promise === newPromise) {
|
|
862
|
+
this.playerListCache.delete(botId);
|
|
863
|
+
}
|
|
864
|
+
resolve([]);
|
|
865
|
+
},
|
|
866
|
+
});
|
|
867
|
+
|
|
868
|
+
child.send({ type: 'system:get_player_list', requestId });
|
|
869
|
+
});
|
|
870
|
+
|
|
871
|
+
this.playerListCache.set(botId, {
|
|
872
|
+
promise: newPromise,
|
|
873
|
+
timestamp: Date.now()
|
|
874
|
+
});
|
|
875
|
+
|
|
876
|
+
return newPromise;
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
setEventGraphManager(manager) {
|
|
880
|
+
this.eventGraphManager = manager;
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
lookAt(botId, position) {
|
|
884
|
+
const botProcess = this.bots.get(botId);
|
|
885
|
+
if (botProcess && !botProcess.killed) {
|
|
886
|
+
botProcess.send({ type: 'action', name: 'lookAt', payload: { position } });
|
|
887
|
+
} else {
|
|
888
|
+
console.error(`[BotManager] Не удалось найти запущенный процесс для бота ${botId}, чтобы выполнить lookAt.`);
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
async reloadPlugins(botId) {
|
|
893
|
+
const child = this.bots.get(botId);
|
|
894
|
+
if (child && !child.killed) {
|
|
895
|
+
child.send({ type: 'plugins:reload' });
|
|
896
|
+
console.log(`[BotManager] Sent plugins:reload to bot process ${botId}`);
|
|
897
|
+
const { getIO } = require('../real-time/socketHandler');
|
|
898
|
+
getIO().emit('bot:plugins_reloaded', { botId });
|
|
899
|
+
return { success: true, message: 'Команда на перезагрузку плагинов отправлена.' };
|
|
900
|
+
}
|
|
901
|
+
return { success: false, message: 'Бот не запущен.' };
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
sendServerCommandToBot(botId, command) {
|
|
905
|
+
const child = this.bots.get(botId);
|
|
906
|
+
if (child) {
|
|
907
|
+
child.send({ type: 'server_command', payload: { command } });
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
module.exports = new BotManager();
|