blockmine 1.5.9 → 1.6.0
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/backend/prisma/migrations/20250709150611_add_run_on_startup_to_tasks/migration.sql +21 -0
- package/backend/prisma/migrations/20250709151124_make_cron_pattern_optional/migration.sql +21 -0
- package/backend/prisma/schema.prisma +2 -1
- package/backend/src/api/routes/bots.js +1088 -1088
- package/backend/src/api/routes/pluginIde.js +133 -8
- package/backend/src/api/routes/tasks.js +19 -2
- package/backend/src/core/BotManager.js +87 -6
- package/backend/src/core/BotProcess.js +36 -2
- package/backend/src/core/GraphExecutionEngine.js +796 -714
- package/backend/src/core/NodeRegistry.js +1059 -902
- package/backend/src/core/PluginManager.js +12 -5
- package/backend/src/core/TaskScheduler.js +27 -15
- package/backend/src/core/UserService.js +1 -1
- package/backend/src/core/services.js +1 -2
- package/backend/src/server.js +1 -3
- package/frontend/dist/assets/index-4mfeN9LH.css +1 -0
- package/frontend/dist/assets/{index-Cb7r5FoV.js → index-CVKjf72r.js} +234 -234
- package/frontend/dist/index.html +2 -2
- package/package.json +1 -1
- package/frontend/dist/assets/index-OIucIMTn.css +0 -1
|
@@ -12,10 +12,8 @@ const router = express.Router({ mergeParams: true });
|
|
|
12
12
|
const DATA_DIR = path.join(os.homedir(), '.blockmine');
|
|
13
13
|
const PLUGINS_BASE_DIR = path.join(DATA_DIR, 'storage', 'plugins');
|
|
14
14
|
|
|
15
|
-
// All routes in this file require plugin development permission
|
|
16
15
|
router.use(authenticate, authorize('plugin:develop'));
|
|
17
16
|
|
|
18
|
-
// Middleware to resolve plugin path and ensure it's safe
|
|
19
17
|
const resolvePluginPath = async (req, res, next) => {
|
|
20
18
|
try {
|
|
21
19
|
const { botId, pluginName } = req.params;
|
|
@@ -26,7 +24,6 @@ const resolvePluginPath = async (req, res, next) => {
|
|
|
26
24
|
const botPluginsDir = path.join(PLUGINS_BASE_DIR, `bot_${botId}`);
|
|
27
25
|
const pluginPath = path.resolve(botPluginsDir, pluginName);
|
|
28
26
|
|
|
29
|
-
// Security check: ensure the resolved path is still within the bot's plugins directory
|
|
30
27
|
if (!pluginPath.startsWith(botPluginsDir)) {
|
|
31
28
|
return res.status(403).json({ error: 'Доступ запрещен: попытка доступа за пределы директории плагина.' });
|
|
32
29
|
}
|
|
@@ -35,7 +32,6 @@ const resolvePluginPath = async (req, res, next) => {
|
|
|
35
32
|
return res.status(404).json({ error: 'Директория плагина не найдена.' });
|
|
36
33
|
}
|
|
37
34
|
|
|
38
|
-
// Attach the safe path to the request object
|
|
39
35
|
req.pluginPath = pluginPath;
|
|
40
36
|
next();
|
|
41
37
|
} catch (error) {
|
|
@@ -52,7 +48,7 @@ router.post('/create', async (req, res) => {
|
|
|
52
48
|
version = '1.0.0',
|
|
53
49
|
description = '',
|
|
54
50
|
author = '',
|
|
55
|
-
template = 'empty'
|
|
51
|
+
template = 'empty'
|
|
56
52
|
} = req.body;
|
|
57
53
|
|
|
58
54
|
if (!name) {
|
|
@@ -297,6 +293,28 @@ router.post('/:pluginName/file', resolvePluginPath, async (req, res) => {
|
|
|
297
293
|
}
|
|
298
294
|
|
|
299
295
|
await fse.writeFile(safePath, content, 'utf-8');
|
|
296
|
+
|
|
297
|
+
if (relativePath === 'package.json' || relativePath.endsWith('/package.json')) {
|
|
298
|
+
try {
|
|
299
|
+
const packageJson = JSON.parse(content);
|
|
300
|
+
await prisma.installedPlugin.updateMany({
|
|
301
|
+
where: {
|
|
302
|
+
botId: parseInt(req.params.botId),
|
|
303
|
+
path: req.pluginPath,
|
|
304
|
+
},
|
|
305
|
+
data: {
|
|
306
|
+
name: packageJson.name || req.params.pluginName,
|
|
307
|
+
version: packageJson.version || '1.0.0',
|
|
308
|
+
description: packageJson.description || '',
|
|
309
|
+
manifest: JSON.stringify(packageJson.botpanel || {}),
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
console.log(`[Plugin IDE] Manifest обновлен для плагина ${req.params.pluginName} после сохранения package.json`);
|
|
313
|
+
} catch (manifestError) {
|
|
314
|
+
console.error(`[Plugin IDE] Ошибка обновления manifest для ${req.params.pluginName}:`, manifestError);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
300
318
|
res.status(200).json({ message: 'Файл успешно сохранен.' });
|
|
301
319
|
|
|
302
320
|
} catch (error) {
|
|
@@ -375,7 +393,7 @@ router.post('/:pluginName/manifest', resolvePluginPath, async (req, res) => {
|
|
|
375
393
|
}
|
|
376
394
|
|
|
377
395
|
const currentManifest = await fse.readJson(manifestPath);
|
|
378
|
-
const { name, version, description, author } = req.body;
|
|
396
|
+
const { name, version, description, author, repositoryUrl } = req.body;
|
|
379
397
|
|
|
380
398
|
const newManifest = {
|
|
381
399
|
...currentManifest,
|
|
@@ -384,10 +402,16 @@ router.post('/:pluginName/manifest', resolvePluginPath, async (req, res) => {
|
|
|
384
402
|
description: description,
|
|
385
403
|
author: author,
|
|
386
404
|
};
|
|
405
|
+
|
|
406
|
+
if (repositoryUrl) {
|
|
407
|
+
newManifest.repository = {
|
|
408
|
+
type: 'git',
|
|
409
|
+
url: repositoryUrl
|
|
410
|
+
};
|
|
411
|
+
}
|
|
387
412
|
|
|
388
413
|
await fse.writeJson(manifestPath, newManifest, { spaces: 2 });
|
|
389
414
|
|
|
390
|
-
// Also update the DB record
|
|
391
415
|
await prisma.installedPlugin.updateMany({
|
|
392
416
|
where: {
|
|
393
417
|
botId: parseInt(req.params.botId),
|
|
@@ -397,6 +421,7 @@ router.post('/:pluginName/manifest', resolvePluginPath, async (req, res) => {
|
|
|
397
421
|
name: newManifest.name,
|
|
398
422
|
version: newManifest.version,
|
|
399
423
|
description: newManifest.description,
|
|
424
|
+
manifest: JSON.stringify(newManifest.botpanel || {}),
|
|
400
425
|
}
|
|
401
426
|
});
|
|
402
427
|
|
|
@@ -436,6 +461,12 @@ router.post('/:pluginName/fork', resolvePluginPath, async (req, res) => {
|
|
|
436
461
|
|
|
437
462
|
packageJson.name = newName;
|
|
438
463
|
packageJson.description = `(Forked from ${pluginName}) ${packageJson.description || ''}`;
|
|
464
|
+
|
|
465
|
+
packageJson.repository = {
|
|
466
|
+
type: 'git',
|
|
467
|
+
url: currentPlugin.sourceUri
|
|
468
|
+
};
|
|
469
|
+
|
|
439
470
|
await fse.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
440
471
|
|
|
441
472
|
const forkedPlugin = await prisma.installedPlugin.create({
|
|
@@ -448,7 +479,7 @@ router.post('/:pluginName/fork', resolvePluginPath, async (req, res) => {
|
|
|
448
479
|
sourceType: 'LOCAL_IDE',
|
|
449
480
|
sourceUri: newPath,
|
|
450
481
|
manifest: JSON.stringify(packageJson.botpanel || {}),
|
|
451
|
-
isEnabled: false
|
|
482
|
+
isEnabled: false
|
|
452
483
|
}
|
|
453
484
|
});
|
|
454
485
|
|
|
@@ -460,4 +491,98 @@ router.post('/:pluginName/fork', resolvePluginPath, async (req, res) => {
|
|
|
460
491
|
}
|
|
461
492
|
});
|
|
462
493
|
|
|
494
|
+
router.post('/:pluginName/create-pr', resolvePluginPath, async (req, res) => {
|
|
495
|
+
const cp = require('child_process');
|
|
496
|
+
const { branch = 'main', commitMessage = 'Changes from local edit', repositoryUrl } = req.body;
|
|
497
|
+
|
|
498
|
+
if (!branch) {
|
|
499
|
+
return res.status(400).json({ error: 'Название ветки обязательно.' });
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
try {
|
|
503
|
+
cp.execSync('git --version');
|
|
504
|
+
} catch (e) {
|
|
505
|
+
return res.status(400).json({ error: 'Git не установлен на этой системе. Пожалуйста, установите Git для создания PR.' });
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
try {
|
|
509
|
+
const manifestPath = path.join(req.pluginPath, 'package.json');
|
|
510
|
+
const packageJson = await fse.readJson(manifestPath);
|
|
511
|
+
let originalRepo = packageJson.repository?.url;
|
|
512
|
+
|
|
513
|
+
if (repositoryUrl) {
|
|
514
|
+
originalRepo = repositoryUrl;
|
|
515
|
+
|
|
516
|
+
packageJson.repository = {
|
|
517
|
+
type: 'git',
|
|
518
|
+
url: repositoryUrl
|
|
519
|
+
};
|
|
520
|
+
await fse.writeJson(manifestPath, packageJson, { spaces: 2 });
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
if (!originalRepo) {
|
|
524
|
+
return res.status(400).json({ error: 'URL репозитория не указан.' });
|
|
525
|
+
}
|
|
526
|
+
const cleanRepoUrl = originalRepo.replace(/^git\+/, '');
|
|
527
|
+
|
|
528
|
+
const parseRepo = (url) => {
|
|
529
|
+
const match = url.match(/(?:git\+)?https?:\/\/github\.com\/([^\/]+)\/([^\/]+)(?:\.git)?/);
|
|
530
|
+
return match ? { owner: match[1], repo: match[2].replace(/\.git$/, '') } : null;
|
|
531
|
+
};
|
|
532
|
+
|
|
533
|
+
const repoInfo = parseRepo(cleanRepoUrl);
|
|
534
|
+
if (!repoInfo) {
|
|
535
|
+
return res.status(400).json({ error: 'Неверный формат URL репозитория.' });
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
const cwd = req.pluginPath;
|
|
539
|
+
const tempDir = path.join(cwd, '..', `temp-${Date.now()}`);
|
|
540
|
+
|
|
541
|
+
try {
|
|
542
|
+
cp.execSync(`git clone ${cleanRepoUrl} "${tempDir}"`, { stdio: 'inherit' });
|
|
543
|
+
|
|
544
|
+
process.chdir(tempDir);
|
|
545
|
+
|
|
546
|
+
cp.execSync(`git checkout -b ${branch}`);
|
|
547
|
+
|
|
548
|
+
const files = await fse.readdir(req.pluginPath);
|
|
549
|
+
for (const file of files) {
|
|
550
|
+
if (file !== '.git') {
|
|
551
|
+
const sourcePath = path.join(req.pluginPath, file);
|
|
552
|
+
const destPath = path.join(tempDir, file);
|
|
553
|
+
await fse.copy(sourcePath, destPath, { overwrite: true });
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
cp.execSync('git add .');
|
|
558
|
+
try {
|
|
559
|
+
cp.execSync(`git commit -m "${commitMessage}"`);
|
|
560
|
+
} catch (e) {
|
|
561
|
+
if (e.message.includes('nothing to commit')) {
|
|
562
|
+
return res.status(400).json({ error: 'Нет изменений для коммита.' });
|
|
563
|
+
}
|
|
564
|
+
throw e;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
cp.execSync(`git push -u origin ${branch}`);
|
|
568
|
+
|
|
569
|
+
const prUrl = `https://github.com/${repoInfo.owner}/${repoInfo.repo}/pull/new/${branch}`;
|
|
570
|
+
|
|
571
|
+
res.json({ success: true, prUrl });
|
|
572
|
+
|
|
573
|
+
} finally {
|
|
574
|
+
try {
|
|
575
|
+
process.chdir(req.pluginPath);
|
|
576
|
+
await fse.remove(tempDir);
|
|
577
|
+
} catch (cleanupError) {
|
|
578
|
+
console.error('Cleanup error:', cleanupError);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
} catch (error) {
|
|
583
|
+
console.error('[Plugin IDE Error] /create-pr:', error);
|
|
584
|
+
res.status(500).json({ error: 'Не удалось создать PR: ' + error.message });
|
|
585
|
+
}
|
|
586
|
+
});
|
|
587
|
+
|
|
463
588
|
module.exports = router;
|
|
@@ -31,9 +31,19 @@ router.get('/', authorize('task:list'), async (req, res) => {
|
|
|
31
31
|
|
|
32
32
|
router.post('/', authorize('task:create'), async (req, res) => {
|
|
33
33
|
try {
|
|
34
|
-
const
|
|
34
|
+
const { runOnStartup, cronPattern, ...restOfBody } = req.body;
|
|
35
|
+
const taskData = { ...restOfBody, runOnStartup: !!runOnStartup };
|
|
36
|
+
|
|
37
|
+
if (runOnStartup) {
|
|
38
|
+
taskData.cronPattern = null;
|
|
39
|
+
} else {
|
|
40
|
+
taskData.cronPattern = normalizeCronPattern(cronPattern);
|
|
41
|
+
}
|
|
42
|
+
|
|
35
43
|
const newTask = await prisma.scheduledTask.create({ data: taskData });
|
|
36
|
-
|
|
44
|
+
if (!runOnStartup) {
|
|
45
|
+
TaskScheduler.scheduleTask(newTask);
|
|
46
|
+
}
|
|
37
47
|
res.status(201).json(newTask);
|
|
38
48
|
} catch (error) {
|
|
39
49
|
console.error("[API /tasks] Ошибка создания задачи:", error);
|
|
@@ -46,6 +56,13 @@ router.put('/:id', authorize('task:edit'), async (req, res) => {
|
|
|
46
56
|
try {
|
|
47
57
|
const { id, createdAt, updatedAt, lastRun, ...dataToUpdate } = req.body;
|
|
48
58
|
|
|
59
|
+
if (typeof dataToUpdate.runOnStartup !== 'undefined') {
|
|
60
|
+
dataToUpdate.runOnStartup = !!dataToUpdate.runOnStartup;
|
|
61
|
+
if (dataToUpdate.runOnStartup) {
|
|
62
|
+
dataToUpdate.cronPattern = null;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
49
66
|
if (dataToUpdate.cronPattern) {
|
|
50
67
|
dataToUpdate.cronPattern = normalizeCronPattern(dataToUpdate.cronPattern);
|
|
51
68
|
}
|
|
@@ -253,9 +253,19 @@ class BotManager {
|
|
|
253
253
|
}
|
|
254
254
|
|
|
255
255
|
getFullState() {
|
|
256
|
+
const statuses = {};
|
|
257
|
+
for (const [id, child] of this.bots.entries()) {
|
|
258
|
+
statuses[id] = child.killed ? 'stopped' : 'running';
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const logs = {};
|
|
262
|
+
for (const [botId, logArray] of this.logCache.entries()) {
|
|
263
|
+
logs[botId] = logArray;
|
|
264
|
+
}
|
|
265
|
+
|
|
256
266
|
return {
|
|
257
|
-
statuses
|
|
258
|
-
logs
|
|
267
|
+
statuses,
|
|
268
|
+
logs,
|
|
259
269
|
};
|
|
260
270
|
}
|
|
261
271
|
|
|
@@ -265,11 +275,15 @@ class BotManager {
|
|
|
265
275
|
getIO().emit('bot:status', { botId, status, message });
|
|
266
276
|
}
|
|
267
277
|
|
|
268
|
-
appendLog(botId,
|
|
278
|
+
appendLog(botId, logContent) {
|
|
279
|
+
const logEntry = {
|
|
280
|
+
id: Date.now() + Math.random(),
|
|
281
|
+
content: logContent,
|
|
282
|
+
};
|
|
269
283
|
const currentLogs = this.logCache.get(botId) || [];
|
|
270
|
-
const newLogs = [...currentLogs.slice(-499),
|
|
284
|
+
const newLogs = [...currentLogs.slice(-499), logEntry];
|
|
271
285
|
this.logCache.set(botId, newLogs);
|
|
272
|
-
getIO().emit('bot:log', { botId, log });
|
|
286
|
+
getIO().emit('bot:log', { botId, log: logEntry });
|
|
273
287
|
}
|
|
274
288
|
|
|
275
289
|
async startBot(botConfig) {
|
|
@@ -343,6 +357,12 @@ class BotManager {
|
|
|
343
357
|
case 'register_group':
|
|
344
358
|
await this.handleGroupRegistration(botId, message.groupConfig);
|
|
345
359
|
break;
|
|
360
|
+
case 'register_permissions':
|
|
361
|
+
await this.handlePermissionsRegistration(botId, message);
|
|
362
|
+
break;
|
|
363
|
+
case 'add_permissions_to_group':
|
|
364
|
+
await this.handleAddPermissionsToGroup(botId, message);
|
|
365
|
+
break;
|
|
346
366
|
case 'request_user_action':
|
|
347
367
|
const { requestId, payload } = message;
|
|
348
368
|
const { targetUsername, action, data } = payload;
|
|
@@ -406,6 +426,7 @@ class BotManager {
|
|
|
406
426
|
});
|
|
407
427
|
|
|
408
428
|
child.on('error', (err) => this.appendLog(botConfig.id, `[PROCESS FATAL] ${err.stack}`));
|
|
429
|
+
child.stdout.on('data', (data) => console.log(data.toString()));
|
|
409
430
|
child.stderr.on('data', (data) => this.appendLog(botConfig.id, `[STDERR] ${data.toString()}`));
|
|
410
431
|
|
|
411
432
|
child.on('exit', (code, signal) => {
|
|
@@ -590,6 +611,66 @@ class BotManager {
|
|
|
590
611
|
}
|
|
591
612
|
}
|
|
592
613
|
|
|
614
|
+
async handlePermissionsRegistration(botId, message) {
|
|
615
|
+
try {
|
|
616
|
+
const { permissions } = message;
|
|
617
|
+
for (const perm of permissions) {
|
|
618
|
+
if (!perm.name || !perm.owner) {
|
|
619
|
+
console.warn(`[BotManager] Пропущено право без имени или владельца для бота ${botId}:`, perm);
|
|
620
|
+
continue;
|
|
621
|
+
}
|
|
622
|
+
await prisma.permission.upsert({
|
|
623
|
+
where: { botId_name: { botId, name: perm.name } },
|
|
624
|
+
update: { description: perm.description },
|
|
625
|
+
create: {
|
|
626
|
+
botId,
|
|
627
|
+
name: perm.name,
|
|
628
|
+
description: perm.description || '',
|
|
629
|
+
owner: perm.owner,
|
|
630
|
+
},
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
this.invalidateConfigCache(botId);
|
|
634
|
+
} catch (error) {
|
|
635
|
+
console.error(`[BotManager] Ошибка при регистрации прав для бота ${botId}:`, error);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
async handleAddPermissionsToGroup(botId, message) {
|
|
640
|
+
try {
|
|
641
|
+
const { groupName, permissionNames } = message;
|
|
642
|
+
|
|
643
|
+
const group = await prisma.group.findUnique({
|
|
644
|
+
where: { botId_name: { botId, name: groupName } }
|
|
645
|
+
});
|
|
646
|
+
|
|
647
|
+
if (!group) {
|
|
648
|
+
console.warn(`[BotManager] Попытка добавить права в несуществующую группу "${groupName}" для бота ID ${botId}.`);
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
for (const permName of permissionNames) {
|
|
653
|
+
const permission = await prisma.permission.findUnique({
|
|
654
|
+
where: { botId_name: { botId, name: permName } }
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
if (permission) {
|
|
658
|
+
await prisma.groupPermission.upsert({
|
|
659
|
+
where: { groupId_permissionId: { groupId: group.id, permissionId: permission.id } },
|
|
660
|
+
update: {},
|
|
661
|
+
create: { groupId: group.id, permissionId: permission.id },
|
|
662
|
+
});
|
|
663
|
+
} else {
|
|
664
|
+
console.warn(`[BotManager] Право "${permName}" не найдено для бота ID ${botId} при добавлении в группу "${groupName}".`);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
this.invalidateConfigCache(botId);
|
|
669
|
+
} catch (error) {
|
|
670
|
+
console.error(`[BotManager] Ошибка при добавлении прав в группу "${message.groupName}" для бота ${botId}:`, error);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
|
|
593
674
|
stopBot(botId) {
|
|
594
675
|
const child = this.bots.get(botId);
|
|
595
676
|
if (child) {
|
|
@@ -705,4 +786,4 @@ class BotManager {
|
|
|
705
786
|
}
|
|
706
787
|
}
|
|
707
788
|
|
|
708
|
-
module.exports = BotManager;
|
|
789
|
+
module.exports = new BotManager();
|
|
@@ -211,8 +211,39 @@ process.on('message', async (message) => {
|
|
|
211
211
|
const userData = await UserService.getUser(username, bot.config.id, bot.config);
|
|
212
212
|
if (!userData) return null;
|
|
213
213
|
|
|
214
|
+
const permissions = userData.permissionsSet ? Array.from(userData.permissionsSet) : [];
|
|
215
|
+
|
|
214
216
|
return {
|
|
215
|
-
|
|
217
|
+
id: userData.id,
|
|
218
|
+
username: userData.username,
|
|
219
|
+
isOwner: userData.isOwner,
|
|
220
|
+
isBlacklisted: userData.isBlacklisted,
|
|
221
|
+
permissions: permissions,
|
|
222
|
+
groups: userData.groups,
|
|
223
|
+
hasPermission: (permissionName) => {
|
|
224
|
+
if (userData.isOwner) return true;
|
|
225
|
+
if (!permissionName) return false;
|
|
226
|
+
|
|
227
|
+
if (permissions.includes(permissionName)) {
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const permissionParts = permissionName.split('.');
|
|
232
|
+
if (permissionParts.length > 1) {
|
|
233
|
+
const domain = permissionParts[0];
|
|
234
|
+
const wildcard = `${domain}.*`;
|
|
235
|
+
if (permissions.includes(wildcard)) {
|
|
236
|
+
return true;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (permissions.includes('*')) {
|
|
241
|
+
return true;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return false;
|
|
245
|
+
},
|
|
246
|
+
hasGroup: (groupName) => userData.hasGroup(groupName),
|
|
216
247
|
addGroup: (group) => bot.api.performUserAction(username, 'addGroup', { group }),
|
|
217
248
|
removeGroup: (group) => bot.api.performUserAction(username, 'removeGroup', { group }),
|
|
218
249
|
addPermission: (permission) => bot.api.performUserAction(username, 'addPermission', { permission }),
|
|
@@ -514,11 +545,13 @@ process.on('message', async (message) => {
|
|
|
514
545
|
});
|
|
515
546
|
|
|
516
547
|
bot.on('entitySpawn', (entity) => {
|
|
548
|
+
if (!isReady) return;
|
|
517
549
|
const serialized = serializeEntity(entity);
|
|
518
550
|
sendEvent('entitySpawn', { entity: serialized });
|
|
519
551
|
});
|
|
520
552
|
|
|
521
553
|
bot.on('entityMoved', (entity) => {
|
|
554
|
+
if (!isReady) return;
|
|
522
555
|
const now = Date.now();
|
|
523
556
|
const lastSent = entityMoveThrottles.get(entity.id);
|
|
524
557
|
if (!lastSent || now - lastSent > 500) {
|
|
@@ -528,6 +561,7 @@ process.on('message', async (message) => {
|
|
|
528
561
|
});
|
|
529
562
|
|
|
530
563
|
bot.on('entityGone', (entity) => {
|
|
564
|
+
if (!isReady) return;
|
|
531
565
|
sendEvent('entityGone', { entity: serializeEntity(entity) });
|
|
532
566
|
entityMoveThrottles.delete(entity.id);
|
|
533
567
|
});
|
|
@@ -536,7 +570,7 @@ process.on('message', async (message) => {
|
|
|
536
570
|
sendLog('[Event: spawn] Бот заспавнился в мире.');
|
|
537
571
|
setTimeout(() => {
|
|
538
572
|
isReady = true;
|
|
539
|
-
sendLog('[BotProcess] Бот готов к приему
|
|
573
|
+
sendLog('[BotProcess] Бот готов к приему событий.');
|
|
540
574
|
}, 3000);
|
|
541
575
|
});
|
|
542
576
|
} catch (err) {
|