blockmine 1.5.9 → 1.6.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/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 +238 -21
- package/backend/src/api/routes/tasks.js +19 -2
- package/backend/src/core/BotManager.js +87 -6
- package/backend/src/core/BotProcess.js +6 -16
- package/backend/src/core/GraphExecutionEngine.js +796 -714
- package/backend/src/core/NodeRegistry.js +1059 -902
- package/backend/src/core/PluginManager.js +281 -274
- 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-kCeS76bv.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;
|
|
@@ -23,20 +21,25 @@ const resolvePluginPath = async (req, res, next) => {
|
|
|
23
21
|
return res.status(400).json({ error: 'Имя плагина обязательно в пути.' });
|
|
24
22
|
}
|
|
25
23
|
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
24
|
+
const plugin = await prisma.installedPlugin.findFirst({
|
|
25
|
+
where: {
|
|
26
|
+
botId: parseInt(botId),
|
|
27
|
+
name: pluginName
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
if (!plugin) {
|
|
32
|
+
return res.status(404).json({ error: 'Плагин не найден в базе данных.' });
|
|
32
33
|
}
|
|
33
34
|
|
|
35
|
+
const pluginPath = plugin.path;
|
|
36
|
+
|
|
34
37
|
if (!await fse.pathExists(pluginPath)) {
|
|
35
|
-
return res.status(404).json({ error: 'Директория плагина не
|
|
38
|
+
return res.status(404).json({ error: 'Директория плагина не найдена в файловой системе.' });
|
|
36
39
|
}
|
|
37
40
|
|
|
38
|
-
// Attach the safe path to the request object
|
|
39
41
|
req.pluginPath = pluginPath;
|
|
42
|
+
req.pluginData = plugin;
|
|
40
43
|
next();
|
|
41
44
|
} catch (error) {
|
|
42
45
|
console.error('[Plugin IDE Middleware Error]', error);
|
|
@@ -52,7 +55,7 @@ router.post('/create', async (req, res) => {
|
|
|
52
55
|
version = '1.0.0',
|
|
53
56
|
description = '',
|
|
54
57
|
author = '',
|
|
55
|
-
template = 'empty'
|
|
58
|
+
template = 'empty'
|
|
56
59
|
} = req.body;
|
|
57
60
|
|
|
58
61
|
if (!name) {
|
|
@@ -297,6 +300,57 @@ router.post('/:pluginName/file', resolvePluginPath, async (req, res) => {
|
|
|
297
300
|
}
|
|
298
301
|
|
|
299
302
|
await fse.writeFile(safePath, content, 'utf-8');
|
|
303
|
+
|
|
304
|
+
if (relativePath === 'package.json' || relativePath.endsWith('/package.json')) {
|
|
305
|
+
try {
|
|
306
|
+
const packageJson = JSON.parse(content);
|
|
307
|
+
|
|
308
|
+
const existingPlugin = await prisma.installedPlugin.findFirst({
|
|
309
|
+
where: {
|
|
310
|
+
botId: parseInt(req.params.botId),
|
|
311
|
+
path: req.pluginPath,
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
if (existingPlugin) {
|
|
316
|
+
const newName = packageJson.name || req.params.pluginName;
|
|
317
|
+
|
|
318
|
+
const conflictingPlugin = await prisma.installedPlugin.findFirst({
|
|
319
|
+
where: {
|
|
320
|
+
botId: parseInt(req.params.botId),
|
|
321
|
+
name: newName,
|
|
322
|
+
id: { not: existingPlugin.id }
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
if (conflictingPlugin) {
|
|
327
|
+
console.warn(`[Plugin IDE] Конфликт имени плагина: ${newName} уже существует для бота ${req.params.botId}`);
|
|
328
|
+
await prisma.installedPlugin.update({
|
|
329
|
+
where: { id: existingPlugin.id },
|
|
330
|
+
data: {
|
|
331
|
+
version: packageJson.version || '1.0.0',
|
|
332
|
+
description: packageJson.description || '',
|
|
333
|
+
manifest: JSON.stringify(packageJson.botpanel || {}),
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
} else {
|
|
337
|
+
await prisma.installedPlugin.update({
|
|
338
|
+
where: { id: existingPlugin.id },
|
|
339
|
+
data: {
|
|
340
|
+
name: newName,
|
|
341
|
+
version: packageJson.version || '1.0.0',
|
|
342
|
+
description: packageJson.description || '',
|
|
343
|
+
manifest: JSON.stringify(packageJson.botpanel || {}),
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
console.log(`[Plugin IDE] Manifest обновлен для плагина ${req.params.pluginName} после сохранения package.json`);
|
|
348
|
+
}
|
|
349
|
+
} catch (manifestError) {
|
|
350
|
+
console.error(`[Plugin IDE] Ошибка обновления manifest для ${req.params.pluginName}:`, manifestError);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
300
354
|
res.status(200).json({ message: 'Файл успешно сохранен.' });
|
|
301
355
|
|
|
302
356
|
} catch (error) {
|
|
@@ -375,7 +429,7 @@ router.post('/:pluginName/manifest', resolvePluginPath, async (req, res) => {
|
|
|
375
429
|
}
|
|
376
430
|
|
|
377
431
|
const currentManifest = await fse.readJson(manifestPath);
|
|
378
|
-
const { name, version, description, author } = req.body;
|
|
432
|
+
const { name, version, description, author, repositoryUrl } = req.body;
|
|
379
433
|
|
|
380
434
|
const newManifest = {
|
|
381
435
|
...currentManifest,
|
|
@@ -384,26 +438,60 @@ router.post('/:pluginName/manifest', resolvePluginPath, async (req, res) => {
|
|
|
384
438
|
description: description,
|
|
385
439
|
author: author,
|
|
386
440
|
};
|
|
441
|
+
|
|
442
|
+
if (repositoryUrl) {
|
|
443
|
+
newManifest.repository = {
|
|
444
|
+
type: 'git',
|
|
445
|
+
url: repositoryUrl
|
|
446
|
+
};
|
|
447
|
+
}
|
|
387
448
|
|
|
388
449
|
await fse.writeJson(manifestPath, newManifest, { spaces: 2 });
|
|
389
450
|
|
|
390
|
-
|
|
391
|
-
await prisma.installedPlugin.updateMany({
|
|
451
|
+
const existingPlugin = await prisma.installedPlugin.findFirst({
|
|
392
452
|
where: {
|
|
393
453
|
botId: parseInt(req.params.botId),
|
|
394
454
|
path: req.pluginPath,
|
|
395
|
-
},
|
|
396
|
-
data: {
|
|
397
|
-
name: newManifest.name,
|
|
398
|
-
version: newManifest.version,
|
|
399
|
-
description: newManifest.description,
|
|
400
455
|
}
|
|
401
456
|
});
|
|
457
|
+
|
|
458
|
+
if (existingPlugin) {
|
|
459
|
+
const conflictingPlugin = await prisma.installedPlugin.findFirst({
|
|
460
|
+
where: {
|
|
461
|
+
botId: parseInt(req.params.botId),
|
|
462
|
+
name: newManifest.name,
|
|
463
|
+
id: { not: existingPlugin.id }
|
|
464
|
+
}
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
if (conflictingPlugin) {
|
|
468
|
+
console.warn(`[Plugin IDE] Конфликт имени плагина: ${newManifest.name} уже существует для бота ${req.params.botId}`);
|
|
469
|
+
await prisma.installedPlugin.update({
|
|
470
|
+
where: { id: existingPlugin.id },
|
|
471
|
+
data: {
|
|
472
|
+
version: newManifest.version,
|
|
473
|
+
description: newManifest.description,
|
|
474
|
+
manifest: JSON.stringify(newManifest.botpanel || {}),
|
|
475
|
+
}
|
|
476
|
+
});
|
|
477
|
+
} else {
|
|
478
|
+
await prisma.installedPlugin.update({
|
|
479
|
+
where: { id: existingPlugin.id },
|
|
480
|
+
data: {
|
|
481
|
+
name: newManifest.name,
|
|
482
|
+
version: newManifest.version,
|
|
483
|
+
description: newManifest.description,
|
|
484
|
+
manifest: JSON.stringify(newManifest.botpanel || {}),
|
|
485
|
+
}
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
}
|
|
402
489
|
|
|
403
490
|
res.status(200).json({ message: 'package.json успешно обновлен.' });
|
|
404
491
|
} catch (error) {
|
|
405
492
|
console.error(`[Plugin IDE Error] /manifest POST for ${req.params.pluginName}:`, error);
|
|
406
|
-
|
|
493
|
+
// Файл уже сохранен, поэтому возвращаем успех даже если есть ошибка с БД
|
|
494
|
+
res.status(200).json({ message: 'package.json обновлен (возможны проблемы с синхронизацией БД).' });
|
|
407
495
|
}
|
|
408
496
|
});
|
|
409
497
|
|
|
@@ -436,6 +524,12 @@ router.post('/:pluginName/fork', resolvePluginPath, async (req, res) => {
|
|
|
436
524
|
|
|
437
525
|
packageJson.name = newName;
|
|
438
526
|
packageJson.description = `(Forked from ${pluginName}) ${packageJson.description || ''}`;
|
|
527
|
+
|
|
528
|
+
packageJson.repository = {
|
|
529
|
+
type: 'git',
|
|
530
|
+
url: currentPlugin.sourceUri
|
|
531
|
+
};
|
|
532
|
+
|
|
439
533
|
await fse.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
440
534
|
|
|
441
535
|
const forkedPlugin = await prisma.installedPlugin.create({
|
|
@@ -448,7 +542,7 @@ router.post('/:pluginName/fork', resolvePluginPath, async (req, res) => {
|
|
|
448
542
|
sourceType: 'LOCAL_IDE',
|
|
449
543
|
sourceUri: newPath,
|
|
450
544
|
manifest: JSON.stringify(packageJson.botpanel || {}),
|
|
451
|
-
isEnabled: false
|
|
545
|
+
isEnabled: false
|
|
452
546
|
}
|
|
453
547
|
});
|
|
454
548
|
|
|
@@ -460,4 +554,127 @@ router.post('/:pluginName/fork', resolvePluginPath, async (req, res) => {
|
|
|
460
554
|
}
|
|
461
555
|
});
|
|
462
556
|
|
|
557
|
+
router.post('/:pluginName/create-pr', resolvePluginPath, async (req, res) => {
|
|
558
|
+
const cp = require('child_process');
|
|
559
|
+
const { branch = 'main', commitMessage = 'Changes from local edit', repositoryUrl } = req.body;
|
|
560
|
+
|
|
561
|
+
if (!branch) {
|
|
562
|
+
return res.status(400).json({ error: 'Название ветки обязательно.' });
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
try {
|
|
566
|
+
cp.execSync('git --version');
|
|
567
|
+
} catch (e) {
|
|
568
|
+
return res.status(400).json({ error: 'Git не установлен на этой системе. Пожалуйста, установите Git для создания PR.' });
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
try {
|
|
572
|
+
const manifestPath = path.join(req.pluginPath, 'package.json');
|
|
573
|
+
const packageJson = await fse.readJson(manifestPath);
|
|
574
|
+
let originalRepo = packageJson.repository?.url;
|
|
575
|
+
|
|
576
|
+
if (repositoryUrl) {
|
|
577
|
+
originalRepo = repositoryUrl;
|
|
578
|
+
|
|
579
|
+
packageJson.repository = {
|
|
580
|
+
type: 'git',
|
|
581
|
+
url: repositoryUrl
|
|
582
|
+
};
|
|
583
|
+
await fse.writeJson(manifestPath, packageJson, { spaces: 2 });
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
if (!originalRepo) {
|
|
587
|
+
return res.status(400).json({ error: 'URL репозитория не указан.' });
|
|
588
|
+
}
|
|
589
|
+
const cleanRepoUrl = originalRepo.replace(/^git\+/, '');
|
|
590
|
+
|
|
591
|
+
const parseRepo = (url) => {
|
|
592
|
+
const match = url.match(/(?:git\+)?https?:\/\/github\.com\/([^\/]+)\/([^\/]+)(?:\.git)?/);
|
|
593
|
+
return match ? { owner: match[1], repo: match[2].replace(/\.git$/, '') } : null;
|
|
594
|
+
};
|
|
595
|
+
|
|
596
|
+
const repoInfo = parseRepo(cleanRepoUrl);
|
|
597
|
+
if (!repoInfo) {
|
|
598
|
+
return res.status(400).json({ error: 'Неверный формат URL репозитория.' });
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
const cwd = req.pluginPath;
|
|
602
|
+
const tempDir = path.join(cwd, '..', `temp-${Date.now()}`);
|
|
603
|
+
|
|
604
|
+
try {
|
|
605
|
+
cp.execSync(`git clone ${cleanRepoUrl} "${tempDir}"`, { stdio: 'inherit' });
|
|
606
|
+
|
|
607
|
+
process.chdir(tempDir);
|
|
608
|
+
|
|
609
|
+
let branchExists = false;
|
|
610
|
+
try {
|
|
611
|
+
cp.execSync(`git ls-remote --heads origin ${branch}`, { stdio: 'pipe' });
|
|
612
|
+
branchExists = true;
|
|
613
|
+
console.log(`[Plugin IDE] Ветка ${branch} уже существует, переключаемся на неё`);
|
|
614
|
+
} catch (e) {
|
|
615
|
+
console.log(`[Plugin IDE] Ветка ${branch} не существует, создаём новую`);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
if (branchExists) {
|
|
619
|
+
cp.execSync(`git checkout -b ${branch} origin/${branch}`);
|
|
620
|
+
cp.execSync(`git pull origin ${branch}`);
|
|
621
|
+
} else {
|
|
622
|
+
cp.execSync(`git checkout -b ${branch}`);
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
const files = await fse.readdir(req.pluginPath);
|
|
626
|
+
for (const file of files) {
|
|
627
|
+
if (file !== '.git') {
|
|
628
|
+
const sourcePath = path.join(req.pluginPath, file);
|
|
629
|
+
const destPath = path.join(tempDir, file);
|
|
630
|
+
await fse.copy(sourcePath, destPath, { overwrite: true });
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
cp.execSync('git add .');
|
|
635
|
+
try {
|
|
636
|
+
cp.execSync(`git commit -m "${commitMessage}"`);
|
|
637
|
+
} catch (e) {
|
|
638
|
+
if (e.message.includes('nothing to commit')) {
|
|
639
|
+
return res.status(400).json({ error: 'Нет изменений для коммита.' });
|
|
640
|
+
}
|
|
641
|
+
throw e;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
|
|
645
|
+
if (branchExists) {
|
|
646
|
+
cp.execSync(`git push origin ${branch} --force`);
|
|
647
|
+
console.log(`[Plugin IDE] Ветка ${branch} обновлена`);
|
|
648
|
+
} else {
|
|
649
|
+
cp.execSync(`git push -u origin ${branch}`);
|
|
650
|
+
console.log(`[Plugin IDE] Новая ветка ${branch} создана`);
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
const prUrl = `https://github.com/${repoInfo.owner}/${repoInfo.repo}/pull/new/${branch}`;
|
|
654
|
+
|
|
655
|
+
const responseData = {
|
|
656
|
+
success: true,
|
|
657
|
+
prUrl: prUrl,
|
|
658
|
+
isUpdate: branchExists,
|
|
659
|
+
message: branchExists ? 'Существующий PR обновлен' : 'Новый PR создан'
|
|
660
|
+
};
|
|
661
|
+
|
|
662
|
+
console.log(`[Plugin IDE] PR ${branchExists ? 'обновлен' : 'создан'} для плагина ${req.params.pluginName}:`, responseData);
|
|
663
|
+
res.json(responseData);
|
|
664
|
+
|
|
665
|
+
} finally {
|
|
666
|
+
try {
|
|
667
|
+
process.chdir(req.pluginPath);
|
|
668
|
+
await fse.remove(tempDir);
|
|
669
|
+
} catch (cleanupError) {
|
|
670
|
+
console.error('Cleanup error:', cleanupError);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
} catch (error) {
|
|
675
|
+
console.error('[Plugin IDE Error] /create-pr:', error);
|
|
676
|
+
res.status(500).json({ error: 'Не удалось создать PR: ' + error.message });
|
|
677
|
+
}
|
|
678
|
+
});
|
|
679
|
+
|
|
463
680
|
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();
|
|
@@ -12,7 +12,7 @@ const { parseArguments } = require('./system/parseArguments');
|
|
|
12
12
|
const GraphExecutionEngine = require('./GraphExecutionEngine');
|
|
13
13
|
const NodeRegistry = require('./NodeRegistry');
|
|
14
14
|
|
|
15
|
-
const UserService = require('./
|
|
15
|
+
const UserService = require('./UserService');
|
|
16
16
|
const PermissionManager = require('./ipc/PermissionManager.stub.js');
|
|
17
17
|
|
|
18
18
|
let bot = null;
|
|
@@ -208,20 +208,7 @@ process.on('message', async (message) => {
|
|
|
208
208
|
sendMessage: (type, message, username) => bot.messageQueue.enqueue(type, message, username),
|
|
209
209
|
sendMessageAndWaitForReply: (command, patterns, timeout) => bot.messageQueue.enqueueAndWait(command, patterns, timeout),
|
|
210
210
|
getUser: async (username) => {
|
|
211
|
-
|
|
212
|
-
if (!userData) return null;
|
|
213
|
-
|
|
214
|
-
return {
|
|
215
|
-
...userData,
|
|
216
|
-
addGroup: (group) => bot.api.performUserAction(username, 'addGroup', { group }),
|
|
217
|
-
removeGroup: (group) => bot.api.performUserAction(username, 'removeGroup', { group }),
|
|
218
|
-
addPermission: (permission) => bot.api.performUserAction(username, 'addPermission', { permission }),
|
|
219
|
-
removePermission: (permission) => bot.api.performUserAction(username, 'removePermission', { permission }),
|
|
220
|
-
getGroups: () => bot.api.performUserAction(username, 'getGroups'),
|
|
221
|
-
getPermissions: () => bot.api.performUserAction(username, 'getPermissions'),
|
|
222
|
-
isBlacklisted: () => bot.api.performUserAction(username, 'isBlacklisted'),
|
|
223
|
-
setBlacklisted: (value) => bot.api.performUserAction(username, 'setBlacklisted', { value }),
|
|
224
|
-
};
|
|
211
|
+
return await UserService.getUser(username, bot.config.id, bot.config);
|
|
225
212
|
},
|
|
226
213
|
registerPermissions: (permissions) => PermissionManager.registerPermissions(bot.config.id, permissions),
|
|
227
214
|
registerGroup: (groupConfig) => PermissionManager.registerGroup(bot.config.id, groupConfig),
|
|
@@ -514,11 +501,13 @@ process.on('message', async (message) => {
|
|
|
514
501
|
});
|
|
515
502
|
|
|
516
503
|
bot.on('entitySpawn', (entity) => {
|
|
504
|
+
if (!isReady) return;
|
|
517
505
|
const serialized = serializeEntity(entity);
|
|
518
506
|
sendEvent('entitySpawn', { entity: serialized });
|
|
519
507
|
});
|
|
520
508
|
|
|
521
509
|
bot.on('entityMoved', (entity) => {
|
|
510
|
+
if (!isReady) return;
|
|
522
511
|
const now = Date.now();
|
|
523
512
|
const lastSent = entityMoveThrottles.get(entity.id);
|
|
524
513
|
if (!lastSent || now - lastSent > 500) {
|
|
@@ -528,6 +517,7 @@ process.on('message', async (message) => {
|
|
|
528
517
|
});
|
|
529
518
|
|
|
530
519
|
bot.on('entityGone', (entity) => {
|
|
520
|
+
if (!isReady) return;
|
|
531
521
|
sendEvent('entityGone', { entity: serializeEntity(entity) });
|
|
532
522
|
entityMoveThrottles.delete(entity.id);
|
|
533
523
|
});
|
|
@@ -536,7 +526,7 @@ process.on('message', async (message) => {
|
|
|
536
526
|
sendLog('[Event: spawn] Бот заспавнился в мире.');
|
|
537
527
|
setTimeout(() => {
|
|
538
528
|
isReady = true;
|
|
539
|
-
sendLog('[BotProcess] Бот готов к приему
|
|
529
|
+
sendLog('[BotProcess] Бот готов к приему событий.');
|
|
540
530
|
}, 3000);
|
|
541
531
|
});
|
|
542
532
|
} catch (err) {
|