blockmine 1.5.7 → 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.
@@ -1,1081 +1,1088 @@
1
- const express = require('express');
2
- const { PrismaClient } = require('@prisma/client');
3
- const path = require('path');
4
- const fs = require('fs/promises');
5
- const { botManager, pluginManager } = require('../../core/services');
6
- const UserService = require('../../core/UserService');
7
- const commandManager = require('../../core/system/CommandManager');
8
- const NodeRegistry = require('../../core/NodeRegistry');
9
- const { authenticate, authorize } = require('../middleware/auth');
10
- const { encrypt } = require('../../core/utils/crypto');
11
- const { randomUUID } = require('crypto');
12
- const eventGraphsRouter = require('./eventGraphs');
13
- const pluginIdeRouter = require('./pluginIde');
14
-
15
- const multer = require('multer');
16
- const archiver = require('archiver');
17
- const AdmZip = require('adm-zip');
18
-
19
- const prisma = new PrismaClient();
20
- const upload = multer({ storage: multer.memoryStorage() });
21
-
22
- const router = express.Router();
23
-
24
- router.use(authenticate);
25
- router.use('/:botId/event-graphs', eventGraphsRouter);
26
- router.use('/:botId/plugins/ide', pluginIdeRouter);
27
-
28
- async function setupDefaultPermissionsForBot(botId, prismaClient = prisma) {
29
- const initialData = {
30
- groups: ["User", "Admin"],
31
- permissions: [
32
- { name: "admin.*", description: "Все права администратора" },
33
- { name: "admin.cooldown.bypass", description: "Обход кулдауна для админ-команд" },
34
- { name: "user.*", description: "Все права обычного пользователя" },
35
- { name: "user.say", description: "Доступ к простым командам" },
36
- { name: "user.cooldown.bypass", description: "Обход кулдауна для юзер-команд" },
37
- ],
38
- groupPermissions: {
39
- "User": ["user.say"],
40
- "Admin": ["admin.*", "admin.cooldown.bypass", "user.cooldown.bypass", "user.*"]
41
- },
42
- };
43
-
44
- for (const perm of initialData.permissions) {
45
- await prismaClient.permission.upsert({ where: { botId_name: { botId, name: perm.name } }, update: { description: perm.description }, create: { ...perm, botId, owner: 'system' } });
46
- }
47
- for (const groupName of initialData.groups) {
48
- await prismaClient.group.upsert({ where: { botId_name: { botId, name: groupName } }, update: {}, create: { name: groupName, botId, owner: 'system' } });
49
- }
50
- for (const [groupName, permNames] of Object.entries(initialData.groupPermissions)) {
51
- const group = await prismaClient.group.findUnique({ where: { botId_name: { botId, name: groupName } } });
52
- if (group) {
53
- for (const permName of permNames) {
54
- const permission = await prismaClient.permission.findUnique({ where: { botId_name: { botId, name: permName } } });
55
- if (permission) {
56
- await prismaClient.groupPermission.upsert({ where: { groupId_permissionId: { groupId: group.id, permissionId: permission.id } }, update: {}, create: { groupId: group.id, permissionId: permission.id } });
57
- }
58
- }
59
- }
60
- }
61
- console.log(`[Setup] Для бота ID ${botId} созданы группы и права по умолчанию.`);
62
- }
63
-
64
- router.get('/', authorize('bot:list'), async (req, res) => {
65
- try {
66
- const bots = await prisma.bot.findMany({ include: { server: true }, orderBy: { createdAt: 'asc' } });
67
- res.json(bots);
68
- } catch (error) {
69
- console.error("[API /api/bots] Ошибка получения списка ботов:", error);
70
- res.status(500).json({ error: 'Не удалось получить список ботов' });
71
- }
72
- });
73
-
74
- router.get('/state', authorize('bot:list'), (req, res) => {
75
- try {
76
- const state = botManager.getFullState();
77
- res.json(state);
78
- } catch (error) { res.status(500).json({ error: 'Не удалось получить состояние ботов' }); }
79
- });
80
-
81
- router.post('/', authorize('bot:create'), async (req, res) => {
82
- try {
83
- const { username, password, prefix, serverId, note } = req.body;
84
- if (!username || !serverId) return res.status(400).json({ error: 'Имя и сервер обязательны' });
85
-
86
- const data = {
87
- username,
88
- prefix,
89
- note,
90
- serverId: parseInt(serverId, 10),
91
- password: password ? encrypt(password) : null
92
- };
93
-
94
- const newBot = await prisma.bot.create({
95
- data: data,
96
- include: { server: true }
97
- });
98
- await setupDefaultPermissionsForBot(newBot.id);
99
- res.status(201).json(newBot);
100
- } catch (error) {
101
- if (error.code === 'P2002') return res.status(409).json({ error: 'Бот с таким именем уже существует' });
102
- console.error("[API Error] /bots POST:", error);
103
- res.status(500).json({ error: 'Не удалось создать бота' });
104
- }
105
- });
106
-
107
- router.put('/:id', authorize('bot:update'), async (req, res) => {
108
- try {
109
- const {
110
- username, password, prefix, serverId, note, owners,
111
- proxyHost, proxyPort, proxyUsername, proxyPassword
112
- } = req.body;
113
-
114
- let dataToUpdate = {
115
- username,
116
- prefix,
117
- note,
118
- owners,
119
- proxyHost,
120
- proxyPort: proxyPort ? parseInt(proxyPort, 10) : null,
121
- proxyUsername,
122
- };
123
-
124
- if (password) {
125
- dataToUpdate.password = encrypt(password);
126
- }
127
- if (proxyPassword) {
128
- dataToUpdate.proxyPassword = encrypt(proxyPassword);
129
- }
130
-
131
- if (serverId) {
132
- dataToUpdate.serverId = parseInt(serverId, 10);
133
- }
134
-
135
- if (dataToUpdate.serverId) {
136
- const { serverId: sId, ...rest } = dataToUpdate;
137
- dataToUpdate = { ...rest, server: { connect: { id: sId } } };
138
- }
139
-
140
- const botId = parseInt(req.params.id, 10);
141
- if (isNaN(botId)) {
142
- return res.status(400).json({ message: 'Неверный ID бота.' });
143
- }
144
-
145
- const updatedBot = await prisma.bot.update({
146
- where: { id: botId },
147
- data: dataToUpdate,
148
- include: {
149
- server: true
150
- }
151
- });
152
-
153
- const botManager = req.app.get('botManager');
154
- botManager.reloadBotConfigInRealTime(botId);
155
-
156
- res.json(updatedBot);
157
- } catch (error) {
158
- console.error('Error updating bot:', error);
159
- res.status(500).json({ message: `Не удалось обновить бота: ${error.message}` });
160
- }
161
- });
162
-
163
- router.delete('/:id', authorize('bot:delete'), async (req, res) => {
164
- try {
165
- const botId = parseInt(req.params.id, 10);
166
- if (botManager.bots.has(botId)) return res.status(400).json({ error: 'Нельзя удалить запущенного бота' });
167
- await prisma.bot.delete({ where: { id: botId } });
168
- res.status(204).send();
169
- } catch (error) { res.status(500).json({ error: 'Не удалось удалить бота' }); }
170
- });
171
-
172
- router.post('/:id/start', authorize('bot:start_stop'), async (req, res) => {
173
- try {
174
- const botId = parseInt(req.params.id, 10);
175
- const botConfig = await prisma.bot.findUnique({ where: { id: botId }, include: { server: true } });
176
- if (!botConfig) return res.status(404).json({ error: 'Бот не найден' });
177
- const result = await botManager.startBot(botConfig);
178
- res.json(result);
179
- } catch (error) { res.status(500).json({ error: 'Ошибка при запуске бота: ' + error.message }); }
180
- });
181
-
182
- router.post('/:id/stop', authorize('bot:start_stop'), (req, res) => {
183
- const botId = parseInt(req.params.id, 10);
184
- const result = botManager.stopBot(botId);
185
- res.json(result);
186
- });
187
-
188
- router.post('/:id/chat', authorize('bot:interact'), (req, res) => {
189
- try {
190
- const botId = parseInt(req.params.id, 10);
191
- const { message } = req.body;
192
- if (!message) return res.status(400).json({ error: 'Сообщение не может быть пустым' });
193
- const result = botManager.sendMessageToBot(botId, message);
194
- if (result.success) res.json({ success: true });
195
- else res.status(404).json(result);
196
- } catch (error) { res.status(500).json({ error: 'Внутренняя ошибка сервера: ' + error.message }); }
197
- });
198
-
199
-
200
- router.get('/servers', authorize('bot:list'), async (req, res) => {
201
- try {
202
- const servers = await prisma.server.findMany();
203
- res.json(servers);
204
- } catch (error) {
205
- console.error("[API /api/bots] Ошибка получения списка серверов:", error);
206
- res.status(500).json({ error: 'Не удалось получить список серверов' });
207
- }
208
- });
209
-
210
- router.get('/:botId/plugins', authorize('plugin:list'), async (req, res) => {
211
- try {
212
- const botId = parseInt(req.params.botId);
213
- const plugins = await prisma.installedPlugin.findMany({ where: { botId } });
214
- res.json(plugins);
215
- } catch (error) { res.status(500).json({ error: 'Не удалось получить плагины бота' }); }
216
- });
217
-
218
- router.post('/:botId/plugins/install/github', authorize('plugin:install'), async (req, res) => {
219
- const { botId } = req.params;
220
- const { repoUrl } = req.body;
221
- try {
222
- const newPlugin = await pluginManager.installFromGithub(parseInt(botId), repoUrl);
223
- res.status(201).json(newPlugin);
224
- } catch (error) {
225
- res.status(500).json({ message: error.message });
226
- }
227
- });
228
-
229
- router.post('/:botId/plugins/install/local', authorize('plugin:install'), async (req, res) => {
230
- const { botId } = req.params;
231
- const { path } = req.body;
232
- try {
233
- const newPlugin = await pluginManager.installFromLocalPath(parseInt(botId), path);
234
- res.status(201).json(newPlugin);
235
- } catch (error) {
236
- res.status(500).json({ message: error.message });
237
- }
238
- });
239
-
240
- router.delete('/:botId/plugins/:pluginId', authorize('plugin:delete'), async (req, res) => {
241
- const { pluginId } = req.params;
242
- try {
243
- await pluginManager.deletePlugin(parseInt(pluginId));
244
- res.status(204).send();
245
- } catch (error) {
246
- res.status(500).json({ message: error.message });
247
- }
248
- });
249
-
250
- router.get('/:botId/plugins/:pluginId/settings', authorize('plugin:settings:view'), async (req, res) => {
251
- try {
252
- const pluginId = parseInt(req.params.pluginId);
253
- const plugin = await prisma.installedPlugin.findUnique({ where: { id: pluginId } });
254
- if (!plugin) return res.status(404).json({ error: 'Установленный плагин не найден' });
255
-
256
- const savedSettings = plugin.settings ? JSON.parse(plugin.settings) : {};
257
- let defaultSettings = {};
258
- const manifest = plugin.manifest ? JSON.parse(plugin.manifest) : {};
259
-
260
- if (manifest.settings) {
261
- for (const key in manifest.settings) {
262
- const config = manifest.settings[key];
263
- if (config.type === 'json_file' && config.defaultPath) {
264
- const configFilePath = path.join(plugin.path, config.defaultPath);
265
- try {
266
- const fileContent = await fs.readFile(configFilePath, 'utf-8');
267
- defaultSettings[key] = JSON.parse(fileContent);
268
- } catch (e) { defaultSettings[key] = {}; }
269
- } else {
270
- try { defaultSettings[key] = JSON.parse(config.default || 'null'); }
271
- catch { defaultSettings[key] = config.default; }
272
- }
273
- }
274
- }
275
- const finalSettings = { ...defaultSettings, ...savedSettings };
276
- res.json(finalSettings);
277
- } catch (error) {
278
- console.error("[API Error] /settings GET:", error);
279
- res.status(500).json({ error: 'Не удалось получить настройки плагина' });
280
- }
281
- });
282
-
283
- router.put('/:botId/plugins/:pluginId', authorize('plugin:settings:edit'), async (req, res) => {
284
- try {
285
- const pluginId = parseInt(req.params.pluginId);
286
- const { isEnabled, settings } = req.body;
287
- const dataToUpdate = {};
288
- if (typeof isEnabled === 'boolean') dataToUpdate.isEnabled = isEnabled;
289
- if (settings) dataToUpdate.settings = JSON.stringify(settings);
290
- if (Object.keys(dataToUpdate).length === 0) return res.status(400).json({ error: "Нет данных для обновления" });
291
- const updated = await prisma.installedPlugin.update({ where: { id: pluginId }, data: dataToUpdate });
292
- res.json(updated);
293
- } catch (error) { res.status(500).json({ error: 'Не удалось обновить плагин' }); }
294
- });
295
-
296
- router.get('/:botId/management-data', authorize('management:view'), async (req, res) => {
297
- try {
298
- const botId = parseInt(req.params.botId, 10);
299
- if (isNaN(botId)) return res.status(400).json({ error: 'Неверный ID бота' });
300
-
301
- const page = parseInt(req.query.page) || 1;
302
- const pageSize = parseInt(req.query.pageSize) || 100;
303
- const searchQuery = req.query.search || '';
304
-
305
- const userSkip = (page - 1) * pageSize;
306
-
307
- const whereClause = {
308
- botId,
309
- };
310
-
311
- if (searchQuery) {
312
- whereClause.username = {
313
- contains: searchQuery,
314
- };
315
- }
316
-
317
- const [dbCommands, groups, allPermissions] = await Promise.all([
318
- prisma.command.findMany({ where: { botId }, orderBy: [{ owner: 'asc' }, { name: 'asc' }] }),
319
- prisma.group.findMany({ where: { botId }, include: { permissions: { include: { permission: true } } }, orderBy: { name: 'asc' } }),
320
- prisma.permission.findMany({ where: { botId }, orderBy: { name: 'asc' } })
321
- ]);
322
-
323
- const [users, usersCount] = await Promise.all([
324
- prisma.user.findMany({
325
- where: whereClause,
326
- include: { groups: { include: { group: true } } },
327
- orderBy: { username: 'asc' },
328
- take: pageSize,
329
- skip: userSkip,
330
- }),
331
- prisma.user.count({ where: whereClause })
332
- ]);
333
-
334
- const templatesMap = new Map(commandManager.getCommandTemplates().map(t => [t.name, t]));
335
- let dbCommandsFromDb = await prisma.command.findMany({ where: { botId } });
336
-
337
- const commandsToCreate = [];
338
- for (const template of templatesMap.values()) {
339
- if (!dbCommandsFromDb.some(cmd => cmd.name === template.name)) {
340
- let permissionId = null;
341
- if (template.permissions) {
342
- const permission = await prisma.permission.upsert({
343
- where: { botId_name: { botId, name: template.permissions } },
344
- update: { description: `Авто-создано для команды ${template.name}` },
345
- create: {
346
- botId,
347
- name: template.permissions,
348
- description: `Авто-создано для команды ${template.name}`,
349
- owner: template.owner || 'system',
350
- }
351
- });
352
- permissionId = permission.id;
353
- }
354
-
355
- commandsToCreate.push({
356
- botId,
357
- name: template.name,
358
- isEnabled: template.isActive,
359
- cooldown: template.cooldown,
360
- aliases: JSON.stringify(template.aliases),
361
- description: template.description,
362
- owner: template.owner,
363
- permissionId: permissionId,
364
- allowedChatTypes: JSON.stringify(template.allowedChatTypes),
365
- });
366
- }
367
- }
368
-
369
- if (commandsToCreate.length > 0) {
370
- await prisma.command.createMany({ data: commandsToCreate });
371
- dbCommandsFromDb = await prisma.command.findMany({ where: { botId }, orderBy: [{ owner: 'asc' }, { name: 'asc' }] });
372
- }
373
-
374
- const finalCommands = dbCommandsFromDb.map(cmd => {
375
- const template = templatesMap.get(cmd.name);
376
- let args = [];
377
-
378
- if (cmd.isVisual) {
379
- try {
380
- args = JSON.parse(cmd.argumentsJson || '[]');
381
- } catch (e) {
382
- console.error(`Error parsing argumentsJson for visual command ${cmd.name} (ID: ${cmd.id}):`, e);
383
- args = [];
384
- }
385
- } else {
386
- if (template && template.args && template.args.length > 0) {
387
- args = template.args;
388
- } else {
389
- try {
390
- args = JSON.parse(cmd.argumentsJson || '[]');
391
- } catch (e) {
392
- args = [];
393
- }
394
- }
395
- }
396
-
397
- return {
398
- ...cmd,
399
- args: args,
400
- aliases: JSON.parse(cmd.aliases || '[]'),
401
- allowedChatTypes: JSON.parse(cmd.allowedChatTypes || '[]'),
402
- };
403
- })
404
-
405
- res.json({
406
- groups,
407
- permissions: allPermissions,
408
- users: {
409
- items: users,
410
- total: usersCount,
411
- page,
412
- pageSize,
413
- totalPages: Math.ceil(usersCount / pageSize),
414
- },
415
- commands: finalCommands
416
- });
417
-
418
- } catch (error) {
419
- console.error(`[API Error] /management-data for bot ${req.params.botId}:`, error);
420
- res.status(500).json({ error: 'Не удалось загрузить данные управления' });
421
- }
422
- });
423
-
424
- router.put('/:botId/commands/:commandId', authorize('management:edit'), async (req, res) => {
425
- try {
426
- const commandId = parseInt(req.params.commandId, 10);
427
- const { name, description, cooldown, aliases, permissionId, allowedChatTypes, isEnabled, argumentsJson, graphJson } = req.body;
428
-
429
- const dataToUpdate = {};
430
- if (name !== undefined) dataToUpdate.name = name;
431
- if (description !== undefined) dataToUpdate.description = description;
432
- if (cooldown !== undefined) dataToUpdate.cooldown = parseInt(cooldown, 10);
433
- if (aliases !== undefined) dataToUpdate.aliases = Array.isArray(aliases) ? JSON.stringify(aliases) : aliases;
434
- if (permissionId !== undefined) dataToUpdate.permissionId = permissionId ? parseInt(permissionId, 10) : null;
435
- if (allowedChatTypes !== undefined) dataToUpdate.allowedChatTypes = Array.isArray(allowedChatTypes) ? JSON.stringify(allowedChatTypes) : allowedChatTypes;
436
- if (isEnabled !== undefined) dataToUpdate.isEnabled = isEnabled;
437
- if (argumentsJson !== undefined) dataToUpdate.argumentsJson = Array.isArray(argumentsJson) ? JSON.stringify(argumentsJson) : argumentsJson;
438
- if (graphJson !== undefined) dataToUpdate.graphJson = graphJson;
439
-
440
- const updatedCommand = await prisma.command.update({
441
- where: { id: commandId },
442
- data: dataToUpdate,
443
- });
444
-
445
- res.json(updatedCommand);
446
- } catch (error) {
447
- console.error(`[API Error] /commands/:commandId PUT:`, error);
448
- res.status(500).json({ error: 'Failed to update command' });
449
- }
450
- });
451
-
452
- router.post('/:botId/groups', authorize('management:edit'), async (req, res) => {
453
- try {
454
- const botId = parseInt(req.params.botId);
455
- const { name, permissionIds } = req.body;
456
- if (!name) return res.status(400).json({ error: "Имя группы обязательно" });
457
-
458
- const newGroup = await prisma.group.create({
459
- data: {
460
- name,
461
- botId,
462
- owner: 'admin',
463
- permissions: { create: (permissionIds || []).map(id => ({ permissionId: id })) }
464
- }
465
- });
466
-
467
- botManager.reloadBotConfigInRealTime(botId);
468
-
469
- res.status(201).json(newGroup);
470
- } catch (error) {
471
- if (error.code === 'P2002') return res.status(409).json({ error: 'Группа с таким именем уже существует для этого бота.' });
472
- res.status(500).json({ error: 'Не удалось создать группу.' });
473
- }
474
- });
475
-
476
- router.put('/:botId/groups/:groupId', authorize('management:edit'), async (req, res) => {
477
- try {
478
- const botId = parseInt(req.params.botId, 10);
479
- const groupId = parseInt(req.params.groupId);
480
- const { name, permissionIds } = req.body;
481
- if (!name) return res.status(400).json({ error: "Имя группы обязательно" });
482
-
483
- const usersInGroup = await prisma.user.findMany({
484
- where: { botId, groups: { some: { groupId } } },
485
- select: { username: true }
486
- });
487
-
488
- await prisma.$transaction(async (tx) => {
489
- await tx.group.update({ where: { id: groupId }, data: { name } });
490
- await tx.groupPermission.deleteMany({ where: { groupId } });
491
- if (permissionIds && permissionIds.length > 0) {
492
- await tx.groupPermission.createMany({
493
- data: permissionIds.map(pid => ({ groupId, permissionId: pid })),
494
- });
495
- }
496
- });
497
-
498
- for (const user of usersInGroup) {
499
- botManager.invalidateUserCache(botId, user.username);
500
- }
501
-
502
- botManager.reloadBotConfigInRealTime(botId);
503
-
504
- res.status(200).send();
505
- } catch (error) {
506
- if (error.code === 'P2002') return res.status(409).json({ error: 'Группа с таким именем уже существует для этого бота.' });
507
- res.status(500).json({ error: 'Не удалось обновить группу.' });
508
- }
509
- });
510
-
511
- router.delete('/:botId/groups/:groupId', authorize('management:edit'), async (req, res) => {
512
- try {
513
- const botId = parseInt(req.params.botId, 10);
514
- const groupId = parseInt(req.params.groupId);
515
- const group = await prisma.group.findUnique({ where: { id: groupId } });
516
- if (group && group.owner !== 'admin') {
517
- return res.status(403).json({ error: `Нельзя удалить группу с источником "${group.owner}".` });
518
- }
519
- await prisma.group.delete({ where: { id: groupId } });
520
- botManager.reloadBotConfigInRealTime(botId);
521
-
522
- res.status(204).send();
523
- } catch (error) { res.status(500).json({ error: 'Не удалось удалить группу.' }); }
524
- });
525
-
526
- router.post('/:botId/permissions', authorize('management:edit'), async (req, res) => {
527
- try {
528
- const botId = parseInt(req.params.botId);
529
- const { name, description } = req.body;
530
- if (!name) return res.status(400).json({ error: 'Имя права обязательно' });
531
- const newPermission = await prisma.permission.create({
532
- data: { name, description, botId, owner: 'admin' }
533
- });
534
-
535
- botManager.reloadBotConfigInRealTime(botId);
536
-
537
- res.status(201).json(newPermission);
538
- } catch (error) {
539
- if (error.code === 'P2002') return res.status(409).json({ error: 'Право с таким именем уже существует для этого бота.' });
540
- res.status(500).json({ error: 'Не удалось создать право.' });
541
- }
542
- });
543
-
544
- router.put('/:botId/users/:userId', authorize('management:edit'), async (req, res) => {
545
- try {
546
- const botId = parseInt(req.params.botId, 10);
547
- const userId = parseInt(req.params.userId, 10);
548
- const { isBlacklisted, groupIds } = req.body;
549
-
550
- const updateData = {};
551
- if (typeof isBlacklisted === 'boolean') {
552
- updateData.isBlacklisted = isBlacklisted;
553
- }
554
-
555
- if (Array.isArray(groupIds)) {
556
- await prisma.userGroup.deleteMany({ where: { userId } });
557
- updateData.groups = {
558
- create: groupIds.map(gid => ({ groupId: gid })),
559
- };
560
- }
561
-
562
- const updatedUser = await prisma.user.update({
563
- where: { id: userId },
564
- data: updateData,
565
- include: { groups: true }
566
- });
567
-
568
- botManager.invalidateUserCache(botId, updatedUser.username);
569
-
570
- UserService.clearCache(updatedUser.username, botId);
571
-
572
- res.json(updatedUser);
573
-
574
- } catch (error) {
575
- console.error(`[API Error] /users/:userId PUT:`, error);
576
- res.status(500).json({ error: 'Не удалось обновить пользователя' });
577
- }
578
- });
579
-
580
- router.post('/start-all', authorize('bot:start_stop'), async (req, res) => {
581
- try {
582
- console.log('[API] Получен запрос на запуск всех ботов.');
583
- const allBots = await prisma.bot.findMany({ include: { server: true } });
584
- let startedCount = 0;
585
- for (const botConfig of allBots) {
586
- if (!botManager.bots.has(botConfig.id)) {
587
- await botManager.startBot(botConfig);
588
- startedCount++;
589
- }
590
- }
591
- res.json({ success: true, message: `Запущено ${startedCount} ботов.` });
592
- } catch (error) {
593
- console.error('[API Error] /start-all:', error);
594
- res.status(500).json({ error: 'Произошла ошибка при массовом запуске ботов.' });
595
- }
596
- });
597
-
598
- router.post('/stop-all', authorize('bot:start_stop'), (req, res) => {
599
- try {
600
- console.log('[API] Получен запрос на остановку всех ботов.');
601
- const botIds = Array.from(botManager.bots.keys());
602
- let stoppedCount = 0;
603
- for (const botId of botIds) {
604
- botManager.stopBot(botId);
605
- stoppedCount++;
606
- }
607
- res.json({ success: true, message: `Остановлено ${stoppedCount} ботов.` });
608
- } catch (error) {
609
- console.error('[API Error] /stop-all:', error);
610
- res.status(500).json({ error: 'Произошла ошибка при массовой остановке ботов.' });
611
- }
612
- });
613
-
614
- router.get('/:id/settings/all', authorize('bot:update'), async (req, res) => {
615
- try {
616
- const botId = parseInt(req.params.id, 10);
617
-
618
- const bot = await prisma.bot.findUnique({
619
- where: { id: botId },
620
- include: {
621
- server: true,
622
- installedPlugins: {
623
- orderBy: { name: 'asc' }
624
- }
625
- }
626
- });
627
-
628
- if (!bot) {
629
- return res.status(404).json({ error: 'Бот не найден' });
630
- }
631
-
632
- const allSettings = {
633
- bot: {
634
- id: bot.id,
635
- username: bot.username,
636
- prefix: bot.prefix,
637
- note: bot.note,
638
- owners: bot.owners,
639
- serverId: bot.serverId,
640
- proxyHost: bot.proxyHost,
641
- proxyPort: bot.proxyPort,
642
- proxyUsername: bot.proxyUsername,
643
- },
644
- plugins: []
645
- };
646
-
647
- const pluginSettingsPromises = bot.installedPlugins.map(async (plugin) => {
648
- const manifest = plugin.manifest ? JSON.parse(plugin.manifest) : {};
649
-
650
- if (!manifest.settings || Object.keys(manifest.settings).length === 0) {
651
- return null;
652
- }
653
-
654
- const savedSettings = plugin.settings ? JSON.parse(plugin.settings) : {};
655
- let defaultSettings = {};
656
-
657
- for (const key in manifest.settings) {
658
- const config = manifest.settings[key];
659
- if (config.type === 'json_file' && config.defaultPath) {
660
- const configFilePath = path.join(plugin.path, config.defaultPath);
661
- try {
662
- const fileContent = await fs.readFile(configFilePath, 'utf-8');
663
- defaultSettings[key] = JSON.parse(fileContent);
664
- } catch (e) { defaultSettings[key] = {}; }
665
- } else {
666
- try { defaultSettings[key] = JSON.parse(config.default || 'null'); }
667
- catch { defaultSettings[key] = config.default; }
668
- }
669
- }
670
-
671
- return {
672
- id: plugin.id,
673
- name: plugin.name,
674
- description: plugin.description,
675
- isEnabled: plugin.isEnabled,
676
- manifest: manifest,
677
- settings: { ...defaultSettings, ...savedSettings }
678
- };
679
- });
680
-
681
- allSettings.plugins = (await Promise.all(pluginSettingsPromises)).filter(Boolean);
682
-
683
- res.json(allSettings);
684
-
685
- } catch (error) {
686
- console.error("[API Error] /settings/all GET:", error);
687
- res.status(500).json({ error: 'Не удалось загрузить все настройки' });
688
- }
689
- });
690
-
691
- const nodeRegistry = require('../../core/NodeRegistry');
692
-
693
- router.get('/:botId/visual-editor/nodes', authorize('management:view'), (req, res) => {
694
- try {
695
- const { graphType } = req.query;
696
- const nodesByCategory = nodeRegistry.getNodesByCategory(graphType);
697
- res.json(nodesByCategory);
698
- } catch (error) {
699
- console.error('[API Error] /visual-editor/nodes GET:', error);
700
- res.status(500).json({ error: 'Failed to get available nodes' });
701
- }
702
- });
703
-
704
- router.get('/:botId/visual-editor/node-config', authorize('management:view'), (req, res) => {
705
- try {
706
- const { types } = req.query;
707
- if (!types) {
708
- return res.status(400).json({ error: 'Node types must be provided' });
709
- }
710
- const typeArray = Array.isArray(types) ? types : [types];
711
- const config = nodeRegistry.getNodesByTypes(typeArray);
712
- res.json(config);
713
- } catch (error) {
714
- console.error('[API Error] /visual-editor/node-config GET:', error);
715
- res.status(500).json({ error: 'Failed to get node configuration' });
716
- }
717
- });
718
-
719
- router.get('/:botId/visual-editor/permissions', authorize('management:view'), async (req, res) => {
720
- try {
721
- const botId = parseInt(req.params.botId, 10);
722
- const permissions = await prisma.permission.findMany({
723
- where: { botId },
724
- orderBy: { name: 'asc' }
725
- });
726
- res.json(permissions);
727
- } catch (error) {
728
- console.error('[API Error] /visual-editor/permissions GET:', error);
729
- res.status(500).json({ error: 'Failed to get permissions' });
730
- }
731
- });
732
-
733
- router.post('/:botId/commands/visual', authorize('management:edit'), async (req, res) => {
734
- try {
735
- const botId = parseInt(req.params.botId, 10);
736
- const {
737
- name,
738
- description,
739
- aliases = [],
740
- permissionId,
741
- cooldown = 0,
742
- allowedChatTypes = ['chat', 'private'],
743
- argumentsJson = '[]',
744
- graphJson = 'null'
745
- } = req.body;
746
-
747
- if (!name) {
748
- return res.status(400).json({ error: 'Command name is required' });
749
- }
750
-
751
- const newCommand = await prisma.command.create({
752
- data: {
753
- botId,
754
- name,
755
- description,
756
- aliases: JSON.stringify(aliases),
757
- permissionId: permissionId || null,
758
- cooldown,
759
- allowedChatTypes: JSON.stringify(allowedChatTypes),
760
- isVisual: true,
761
- argumentsJson,
762
- graphJson
763
- }
764
- });
765
-
766
- botManager.reloadBotConfigInRealTime(botId);
767
- res.status(201).json(newCommand);
768
- } catch (error) {
769
- if (error.code === 'P2002') {
770
- return res.status(409).json({ error: 'Command with this name already exists' });
771
- }
772
- console.error('[API Error] /commands/visual POST:', error);
773
- res.status(500).json({ error: 'Failed to create visual command' });
774
- }
775
- });
776
-
777
- router.put('/:botId/commands/:commandId/visual', authorize('management:edit'), async (req, res) => {
778
- try {
779
- const botId = parseInt(req.params.botId, 10);
780
- const commandId = parseInt(req.params.commandId, 10);
781
- const {
782
- name,
783
- description,
784
- aliases,
785
- permissionId,
786
- cooldown,
787
- allowedChatTypes,
788
- argumentsJson,
789
- graphJson
790
- } = req.body;
791
-
792
- const dataToUpdate = { isVisual: true };
793
-
794
- if (name) dataToUpdate.name = name;
795
- if (description !== undefined) dataToUpdate.description = description;
796
- if (Array.isArray(aliases)) dataToUpdate.aliases = JSON.stringify(aliases);
797
- if (permissionId !== undefined) dataToUpdate.permissionId = permissionId || null;
798
- if (typeof cooldown === 'number') dataToUpdate.cooldown = cooldown;
799
- if (Array.isArray(allowedChatTypes)) dataToUpdate.allowedChatTypes = JSON.stringify(allowedChatTypes);
800
- if (argumentsJson !== undefined) dataToUpdate.argumentsJson = argumentsJson;
801
- if (graphJson !== undefined) dataToUpdate.graphJson = graphJson;
802
-
803
- const updatedCommand = await prisma.command.update({
804
- where: { id: commandId, botId },
805
- data: dataToUpdate
806
- });
807
-
808
- botManager.reloadBotConfigInRealTime(botId);
809
- res.json(updatedCommand);
810
- } catch (error) {
811
- if (error.code === 'P2002') {
812
- return res.status(409).json({ error: 'Command with this name already exists' });
813
- }
814
- console.error('[API Error] /commands/:commandId/visual PUT:', error);
815
- res.status(500).json({ error: 'Failed to update visual command' });
816
- }
817
- });
818
-
819
- router.get('/:botId/commands/:commandId/export', authorize('management:view'), async (req, res) => {
820
- try {
821
- const botId = parseInt(req.params.botId, 10);
822
- const commandId = parseInt(req.params.commandId, 10);
823
-
824
- const command = await prisma.command.findUnique({
825
- where: { id: commandId, botId: botId },
826
- });
827
-
828
- if (!command) {
829
- return res.status(404).json({ error: 'Command not found' });
830
- }
831
-
832
- const exportData = {
833
- version: '1.0',
834
- type: 'command',
835
- ...command
836
- };
837
-
838
- delete exportData.id;
839
- delete exportData.botId;
840
-
841
- res.json(exportData);
842
- } catch (error) {
843
- console.error('Failed to export command:', error);
844
- res.status(500).json({ error: 'Failed to export command' });
845
- }
846
- });
847
-
848
- router.post('/:botId/commands/import', authorize('management:edit'), async (req, res) => {
849
- try {
850
- const botId = parseInt(req.params.botId, 10);
851
- const importData = req.body;
852
-
853
- if (importData.type !== 'command') {
854
- return res.status(400).json({ error: 'Invalid file type. Expected "command".' });
855
- }
856
-
857
- let commandName = importData.name;
858
- let counter = 1;
859
-
860
- while (await prisma.command.findFirst({ where: { botId, name: commandName } })) {
861
- commandName = `${importData.name}_imported_${counter}`;
862
- counter++;
863
- }
864
-
865
- let finalGraphJson = importData.graphJson;
866
-
867
- if (finalGraphJson && finalGraphJson !== 'null') {
868
- const graph = JSON.parse(finalGraphJson);
869
- const nodeIdMap = new Map();
870
-
871
- // Рандомизация ID узлов
872
- if (graph.nodes) {
873
- graph.nodes.forEach(node => {
874
- const oldId = node.id;
875
- const newId = `${node.type}-${randomUUID()}`;
876
- nodeIdMap.set(oldId, newId);
877
- node.id = newId;
878
- });
879
- }
880
-
881
- // Обновление и рандомизация ID связей
882
- if (graph.connections) {
883
- graph.connections.forEach(conn => {
884
- conn.id = `edge-${randomUUID()}`;
885
- conn.sourceNodeId = nodeIdMap.get(conn.sourceNodeId) || conn.sourceNodeId;
886
- conn.targetNodeId = nodeIdMap.get(conn.targetNodeId) || conn.targetNodeId;
887
- });
888
- }
889
-
890
- finalGraphJson = JSON.stringify(graph);
891
- }
892
-
893
- const newCommand = await prisma.command.create({
894
- data: {
895
- botId: botId,
896
- name: commandName,
897
- description: importData.description,
898
- aliases: importData.aliases,
899
- permissionId: null,
900
- cooldown: importData.cooldown,
901
- allowedChatTypes: importData.allowedChatTypes,
902
- isVisual: importData.isVisual,
903
- isEnabled: importData.isEnabled,
904
- argumentsJson: importData.argumentsJson,
905
- graphJson: finalGraphJson,
906
- owner: 'visual_editor',
907
- }
908
- });
909
-
910
- botManager.reloadBotConfigInRealTime(botId);
911
- res.status(201).json(newCommand);
912
- } catch (error) {
913
- console.error("Failed to import command:", error);
914
- res.status(500).json({ error: 'Failed to import command' });
915
- }
916
- });
917
-
918
- router.post('/:botId/commands', authorize('management:edit'), async (req, res) => {
919
- try {
920
- const botId = parseInt(req.params.botId, 10);
921
- const {
922
- name,
923
- description,
924
- aliases = [],
925
- permissionId,
926
- cooldown = 0,
927
- allowedChatTypes = ['chat', 'private'],
928
- isVisual = false,
929
- argumentsJson = '[]',
930
- graphJson = 'null'
931
- } = req.body;
932
-
933
- if (!name) {
934
- return res.status(400).json({ error: 'Command name is required' });
935
- }
936
-
937
- const newCommand = await prisma.command.create({
938
- data: {
939
- botId,
940
- name,
941
- description,
942
- aliases: JSON.stringify(aliases),
943
- permissionId: permissionId || null,
944
- cooldown,
945
- allowedChatTypes: JSON.stringify(allowedChatTypes),
946
- isVisual,
947
- argumentsJson,
948
- graphJson,
949
- owner: isVisual ? 'visual_editor' : 'manual',
950
- }
951
- });
952
-
953
- botManager.reloadBotConfigInRealTime(botId);
954
- res.status(201).json(newCommand);
955
- } catch (error) {
956
- if (error.code === 'P2002') {
957
- return res.status(409).json({ error: 'Command with this name already exists' });
958
- }
959
- console.error('[API Error] /commands POST:', error);
960
- res.status(500).json({ error: 'Failed to create command' });
961
- }
962
- });
963
-
964
- router.delete('/:botId/commands/:commandId', authorize('management:edit'), async (req, res) => {
965
- try {
966
- const botId = parseInt(req.params.botId, 10);
967
- const commandId = parseInt(req.params.commandId, 10);
968
-
969
- await prisma.command.delete({
970
- where: { id: commandId, botId: botId },
971
- });
972
-
973
- botManager.reloadBotConfigInRealTime(botId);
974
- res.status(204).send();
975
- } catch (error) {
976
- console.error(`[API Error] /commands/:commandId DELETE:`, error);
977
- res.status(500).json({ error: 'Failed to delete command' });
978
- }
979
- });
980
-
981
- router.get('/:botId/event-graphs/:graphId', authorize('management:view'), async (req, res) => {
982
- try {
983
- const botId = parseInt(req.params.botId, 10);
984
- const graphId = parseInt(req.params.graphId, 10);
985
-
986
- const eventGraph = await prisma.eventGraph.findUnique({
987
- where: { id: graphId, botId },
988
- include: { triggers: true },
989
- });
990
-
991
- if (!eventGraph) {
992
- return res.status(404).json({ error: 'Граф события не найден' });
993
- }
994
-
995
- res.json(eventGraph);
996
- } catch (error) {
997
- console.error(`[API Error] /event-graphs/:graphId GET:`, error);
998
- res.status(500).json({ error: 'Не удалось получить граф события' });
999
- }
1000
- });
1001
-
1002
- router.post('/:botId/event-graphs', authorize('management:edit'), async (req, res) => {
1003
- try {
1004
- const botId = parseInt(req.params.botId, 10);
1005
- const { name } = req.body;
1006
-
1007
- if (!name) {
1008
- return res.status(400).json({ error: 'Имя графа обязательно' });
1009
- }
1010
-
1011
- const newEventGraph = await prisma.eventGraph.create({
1012
- data: {
1013
- botId,
1014
- name,
1015
- isEnabled: true,
1016
- graphJson: 'null',
1017
- },
1018
- });
1019
-
1020
- res.status(201).json(newEventGraph);
1021
- } catch (error) {
1022
- if (error.code === 'P2002') {
1023
- return res.status(409).json({ error: 'Граф событий с таким именем уже существует' });
1024
- }
1025
- console.error(`[API Error] /event-graphs POST:`, error);
1026
- res.status(500).json({ error: 'Не удалось создать граф событий' });
1027
- }
1028
- });
1029
-
1030
- router.delete('/:botId/event-graphs/:graphId', authorize('management:edit'), async (req, res) => {
1031
- try {
1032
- const botId = parseInt(req.params.botId, 10);
1033
- const graphId = parseInt(req.params.graphId, 10);
1034
-
1035
- await prisma.eventGraph.delete({
1036
- where: { id: graphId, botId: botId },
1037
- });
1038
-
1039
- res.status(204).send();
1040
- } catch (error) {
1041
- console.error(`[API Error] /event-graphs/:graphId DELETE:`, error);
1042
- res.status(500).json({ error: 'Не удалось удалить граф событий' });
1043
- }
1044
- });
1045
-
1046
- router.put('/:botId/event-graphs/:graphId', authorize('management:edit'), async (req, res) => {
1047
- const { botId, graphId } = req.params;
1048
- const { name, isEnabled, graphJson, eventType } = req.body;
1049
-
1050
- if (!name || !eventType || !graphJson) {
1051
- return res.status(400).json({ error: 'Поля name, eventType и graphJson обязательны.' });
1052
- }
1053
-
1054
- if (typeof isEnabled !== 'boolean') {
1055
- return res.status(400).json({ error: 'Поле isEnabled должно быть true или false.' });
1056
- }
1057
-
1058
- try {
1059
- const updatedGraph = await prisma.eventGraph.update({
1060
- where: { id: parseInt(graphId), botId: parseInt(botId) },
1061
- data: {
1062
- name,
1063
- isEnabled,
1064
- graphJson,
1065
- eventType,
1066
- }
1067
- });
1068
-
1069
- botManager.eventGraphManager.updateGraph(parseInt(botId), updatedGraph);
1070
-
1071
- res.json(updatedGraph);
1072
- } catch (error) {
1073
- console.error(`[API Error] /event-graphs/:graphId PUT:`, error);
1074
- res.status(500).json({ error: 'Ошибка при обновлении графа событий.' });
1075
- }
1076
- });
1077
-
1078
- router.post('/:botId/visual-editor/save', authorize('management:edit'), async (req, res) => {
1079
- });
1080
-
1081
- module.exports = router;
1
+ const express = require('express');
2
+ const prisma = require('../../lib/prisma');
3
+ const path = require('path');
4
+ const fs = require('fs/promises');
5
+ const { botManager, pluginManager } = require('../../core/services');
6
+ const UserService = require('../../core/UserService');
7
+ const commandManager = require('../../core/system/CommandManager');
8
+ const NodeRegistry = require('../../core/NodeRegistry');
9
+ const { authenticate, authorize } = require('../middleware/auth');
10
+ const { encrypt } = require('../../core/utils/crypto');
11
+ const { randomUUID } = require('crypto');
12
+ const eventGraphsRouter = require('./eventGraphs');
13
+ const pluginIdeRouter = require('./pluginIde');
14
+
15
+ const multer = require('multer');
16
+ const archiver = require('archiver');
17
+ const AdmZip = require('adm-zip');
18
+
19
+ const upload = multer({ storage: multer.memoryStorage() });
20
+
21
+ const router = express.Router();
22
+
23
+ router.use(authenticate);
24
+ router.use('/:botId/event-graphs', eventGraphsRouter);
25
+ router.use('/:botId/plugins/ide', pluginIdeRouter);
26
+
27
+ async function setupDefaultPermissionsForBot(botId, prismaClient = prisma) {
28
+ const initialData = {
29
+ groups: ["User", "Admin"],
30
+ permissions: [
31
+ { name: "admin.*", description: "Все права администратора" },
32
+ { name: "admin.cooldown.bypass", description: "Обход кулдауна для админ-команд" },
33
+ { name: "user.*", description: "Все права обычного пользователя" },
34
+ { name: "user.say", description: "Доступ к простым командам" },
35
+ { name: "user.cooldown.bypass", description: "Обход кулдауна для юзер-команд" },
36
+ ],
37
+ groupPermissions: {
38
+ "User": ["user.say"],
39
+ "Admin": ["admin.*", "admin.cooldown.bypass", "user.cooldown.bypass", "user.*"]
40
+ },
41
+ };
42
+
43
+ for (const perm of initialData.permissions) {
44
+ await prismaClient.permission.upsert({ where: { botId_name: { botId, name: perm.name } }, update: { description: perm.description }, create: { ...perm, botId, owner: 'system' } });
45
+ }
46
+ for (const groupName of initialData.groups) {
47
+ await prismaClient.group.upsert({ where: { botId_name: { botId, name: groupName } }, update: {}, create: { name: groupName, botId, owner: 'system' } });
48
+ }
49
+ for (const [groupName, permNames] of Object.entries(initialData.groupPermissions)) {
50
+ const group = await prismaClient.group.findUnique({ where: { botId_name: { botId, name: groupName } } });
51
+ if (group) {
52
+ for (const permName of permNames) {
53
+ const permission = await prismaClient.permission.findUnique({ where: { botId_name: { botId, name: permName } } });
54
+ if (permission) {
55
+ await prismaClient.groupPermission.upsert({ where: { groupId_permissionId: { groupId: group.id, permissionId: permission.id } }, update: {}, create: { groupId: group.id, permissionId: permission.id } });
56
+ }
57
+ }
58
+ }
59
+ }
60
+ console.log(`[Setup] Для бота ID ${botId} созданы группы и права по умолчанию.`);
61
+ }
62
+
63
+ router.get('/', authorize('bot:list'), async (req, res) => {
64
+ try {
65
+ const bots = await prisma.bot.findMany({ include: { server: true }, orderBy: { createdAt: 'asc' } });
66
+ res.json(bots);
67
+ } catch (error) {
68
+ console.error("[API /api/bots] Ошибка получения списка ботов:", error);
69
+ res.status(500).json({ error: 'Не удалось получить список ботов' });
70
+ }
71
+ });
72
+
73
+ router.get('/state', authorize('bot:list'), (req, res) => {
74
+ try {
75
+ const state = botManager.getFullState();
76
+ res.json(state);
77
+ } catch (error) { res.status(500).json({ error: 'Не удалось получить состояние ботов' }); }
78
+ });
79
+
80
+ router.post('/', authorize('bot:create'), async (req, res) => {
81
+ try {
82
+ const { username, password, prefix, serverId, note } = req.body;
83
+ if (!username || !serverId) return res.status(400).json({ error: 'Имя и сервер обязательны' });
84
+
85
+ const data = {
86
+ username,
87
+ prefix,
88
+ note,
89
+ serverId: parseInt(serverId, 10),
90
+ password: password ? encrypt(password) : null
91
+ };
92
+
93
+ const newBot = await prisma.bot.create({
94
+ data: data,
95
+ include: { server: true }
96
+ });
97
+ await setupDefaultPermissionsForBot(newBot.id);
98
+ res.status(201).json(newBot);
99
+ } catch (error) {
100
+ if (error.code === 'P2002') return res.status(409).json({ error: 'Бот с таким именем уже существует' });
101
+ console.error("[API Error] /bots POST:", error);
102
+ res.status(500).json({ error: 'Не удалось создать бота' });
103
+ }
104
+ });
105
+
106
+ router.put('/:id', authorize('bot:update'), async (req, res) => {
107
+ try {
108
+ const {
109
+ username, password, prefix, serverId, note, owners,
110
+ proxyHost, proxyPort, proxyUsername, proxyPassword
111
+ } = req.body;
112
+
113
+ let dataToUpdate = {
114
+ username,
115
+ prefix,
116
+ note,
117
+ owners,
118
+ proxyHost,
119
+ proxyPort: proxyPort ? parseInt(proxyPort, 10) : null,
120
+ proxyUsername,
121
+ };
122
+
123
+ if (password) {
124
+ dataToUpdate.password = encrypt(password);
125
+ }
126
+ if (proxyPassword) {
127
+ dataToUpdate.proxyPassword = encrypt(proxyPassword);
128
+ }
129
+
130
+ if (serverId) {
131
+ dataToUpdate.serverId = parseInt(serverId, 10);
132
+ }
133
+
134
+ if (dataToUpdate.serverId) {
135
+ const { serverId: sId, ...rest } = dataToUpdate;
136
+ dataToUpdate = { ...rest, server: { connect: { id: sId } } };
137
+ }
138
+
139
+ const botId = parseInt(req.params.id, 10);
140
+ if (isNaN(botId)) {
141
+ return res.status(400).json({ message: 'Неверный ID бота.' });
142
+ }
143
+
144
+ const updatedBot = await prisma.bot.update({
145
+ where: { id: botId },
146
+ data: dataToUpdate,
147
+ include: {
148
+ server: true
149
+ }
150
+ });
151
+
152
+ const botManager = req.app.get('botManager');
153
+ botManager.reloadBotConfigInRealTime(botId);
154
+
155
+ res.json(updatedBot);
156
+ } catch (error) {
157
+ console.error('Error updating bot:', error);
158
+ res.status(500).json({ message: `Не удалось обновить бота: ${error.message}` });
159
+ }
160
+ });
161
+
162
+ router.delete('/:id', authorize('bot:delete'), async (req, res) => {
163
+ try {
164
+ const botId = parseInt(req.params.id, 10);
165
+ if (botManager.bots.has(botId)) return res.status(400).json({ error: 'Нельзя удалить запущенного бота' });
166
+ await prisma.bot.delete({ where: { id: botId } });
167
+ res.status(204).send();
168
+ } catch (error) { res.status(500).json({ error: 'Не удалось удалить бота' }); }
169
+ });
170
+
171
+ router.post('/:id/start', authorize('bot:start_stop'), async (req, res) => {
172
+ try {
173
+ const botId = parseInt(req.params.id, 10);
174
+ const botConfig = await prisma.bot.findUnique({ where: { id: botId }, include: { server: true } });
175
+ if (!botConfig) {
176
+ return res.status(404).json({ success: false, message: 'Бот не найден' });
177
+ }
178
+ botManager.startBot(botConfig);
179
+ res.status(202).json({ success: true, message: 'Команда на запуск отправлена.' });
180
+ } catch (error) {
181
+ console.error(`[API] Ошибка запуска бота ${req.params.id}:`, error);
182
+ res.status(500).json({ success: false, message: 'Ошибка при запуске бота: ' + error.message });
183
+ }
184
+ });
185
+
186
+ router.post('/:id/stop', authorize('bot:start_stop'), (req, res) => {
187
+ try {
188
+ const botId = parseInt(req.params.id, 10);
189
+ botManager.stopBot(botId);
190
+ res.status(202).json({ success: true, message: 'Команда на остановку отправлена.' });
191
+ } catch (error) {
192
+ console.error(`[API] Ошибка остановки бота ${req.params.id}:`, error);
193
+ res.status(500).json({ success: false, message: 'Ошибка при остановке бота: ' + error.message });
194
+ }
195
+ });
196
+
197
+ router.post('/:id/chat', authorize('bot:interact'), (req, res) => {
198
+ try {
199
+ const botId = parseInt(req.params.id, 10);
200
+ const { message } = req.body;
201
+ if (!message) return res.status(400).json({ error: 'Сообщение не может быть пустым' });
202
+ const result = botManager.sendMessageToBot(botId, message);
203
+ if (result.success) res.json({ success: true });
204
+ else res.status(404).json(result);
205
+ } catch (error) { res.status(500).json({ error: 'Внутренняя ошибка сервера: ' + error.message }); }
206
+ });
207
+
208
+
209
+ router.get('/servers', authorize('bot:list'), async (req, res) => {
210
+ try {
211
+ const servers = await prisma.server.findMany();
212
+ res.json(servers);
213
+ } catch (error) {
214
+ console.error("[API /api/bots] Ошибка получения списка серверов:", error);
215
+ res.status(500).json({ error: 'Не удалось получить список серверов' });
216
+ }
217
+ });
218
+
219
+ router.get('/:botId/plugins', authorize('plugin:list'), async (req, res) => {
220
+ try {
221
+ const botId = parseInt(req.params.botId);
222
+ const plugins = await prisma.installedPlugin.findMany({ where: { botId } });
223
+ res.json(plugins);
224
+ } catch (error) { res.status(500).json({ error: 'Не удалось получить плагины бота' }); }
225
+ });
226
+
227
+ router.post('/:botId/plugins/install/github', authorize('plugin:install'), async (req, res) => {
228
+ const { botId } = req.params;
229
+ const { repoUrl } = req.body;
230
+ try {
231
+ const newPlugin = await pluginManager.installFromGithub(parseInt(botId), repoUrl);
232
+ res.status(201).json(newPlugin);
233
+ } catch (error) {
234
+ res.status(500).json({ message: error.message });
235
+ }
236
+ });
237
+
238
+ router.post('/:botId/plugins/install/local', authorize('plugin:install'), async (req, res) => {
239
+ const { botId } = req.params;
240
+ const { path } = req.body;
241
+ try {
242
+ const newPlugin = await pluginManager.installFromLocalPath(parseInt(botId), path);
243
+ res.status(201).json(newPlugin);
244
+ } catch (error) {
245
+ res.status(500).json({ message: error.message });
246
+ }
247
+ });
248
+
249
+ router.delete('/:botId/plugins/:pluginId', authorize('plugin:delete'), async (req, res) => {
250
+ const { pluginId } = req.params;
251
+ try {
252
+ await pluginManager.deletePlugin(parseInt(pluginId));
253
+ res.status(204).send();
254
+ } catch (error) {
255
+ res.status(500).json({ message: error.message });
256
+ }
257
+ });
258
+
259
+ router.get('/:botId/plugins/:pluginId/settings', authorize('plugin:settings:view'), async (req, res) => {
260
+ try {
261
+ const pluginId = parseInt(req.params.pluginId);
262
+ const plugin = await prisma.installedPlugin.findUnique({ where: { id: pluginId } });
263
+ if (!plugin) return res.status(404).json({ error: 'Установленный плагин не найден' });
264
+
265
+ const savedSettings = plugin.settings ? JSON.parse(plugin.settings) : {};
266
+ let defaultSettings = {};
267
+ const manifest = plugin.manifest ? JSON.parse(plugin.manifest) : {};
268
+
269
+ if (manifest.settings) {
270
+ for (const key in manifest.settings) {
271
+ const config = manifest.settings[key];
272
+ if (config.type === 'json_file' && config.defaultPath) {
273
+ const configFilePath = path.join(plugin.path, config.defaultPath);
274
+ try {
275
+ const fileContent = await fs.readFile(configFilePath, 'utf-8');
276
+ defaultSettings[key] = JSON.parse(fileContent);
277
+ } catch (e) { defaultSettings[key] = {}; }
278
+ } else {
279
+ try { defaultSettings[key] = JSON.parse(config.default || 'null'); }
280
+ catch { defaultSettings[key] = config.default; }
281
+ }
282
+ }
283
+ }
284
+ const finalSettings = { ...defaultSettings, ...savedSettings };
285
+ res.json(finalSettings);
286
+ } catch (error) {
287
+ console.error("[API Error] /settings GET:", error);
288
+ res.status(500).json({ error: 'Не удалось получить настройки плагина' });
289
+ }
290
+ });
291
+
292
+ router.put('/:botId/plugins/:pluginId', authorize('plugin:settings:edit'), async (req, res) => {
293
+ try {
294
+ const pluginId = parseInt(req.params.pluginId);
295
+ const { isEnabled, settings } = req.body;
296
+ const dataToUpdate = {};
297
+ if (typeof isEnabled === 'boolean') dataToUpdate.isEnabled = isEnabled;
298
+ if (settings) dataToUpdate.settings = JSON.stringify(settings);
299
+ if (Object.keys(dataToUpdate).length === 0) return res.status(400).json({ error: "Нет данных для обновления" });
300
+ const updated = await prisma.installedPlugin.update({ where: { id: pluginId }, data: dataToUpdate });
301
+ res.json(updated);
302
+ } catch (error) { res.status(500).json({ error: 'Не удалось обновить плагин' }); }
303
+ });
304
+
305
+ router.get('/:botId/management-data', authorize('management:view'), async (req, res) => {
306
+ try {
307
+ const botId = parseInt(req.params.botId, 10);
308
+ if (isNaN(botId)) return res.status(400).json({ error: 'Неверный ID бота' });
309
+
310
+ const page = parseInt(req.query.page) || 1;
311
+ const pageSize = parseInt(req.query.pageSize) || 100;
312
+ const searchQuery = req.query.search || '';
313
+
314
+ const userSkip = (page - 1) * pageSize;
315
+
316
+ const whereClause = {
317
+ botId,
318
+ };
319
+
320
+ if (searchQuery) {
321
+ whereClause.username = {
322
+ contains: searchQuery,
323
+ };
324
+ }
325
+
326
+ const [dbCommands, groups, allPermissions] = await Promise.all([
327
+ prisma.command.findMany({ where: { botId }, orderBy: [{ owner: 'asc' }, { name: 'asc' }] }),
328
+ prisma.group.findMany({ where: { botId }, include: { permissions: { include: { permission: true } } }, orderBy: { name: 'asc' } }),
329
+ prisma.permission.findMany({ where: { botId }, orderBy: { name: 'asc' } })
330
+ ]);
331
+
332
+ const [users, usersCount] = await Promise.all([
333
+ prisma.user.findMany({
334
+ where: whereClause,
335
+ include: { groups: { include: { group: true } } },
336
+ orderBy: { username: 'asc' },
337
+ take: pageSize,
338
+ skip: userSkip,
339
+ }),
340
+ prisma.user.count({ where: whereClause })
341
+ ]);
342
+
343
+ const templatesMap = new Map(commandManager.getCommandTemplates().map(t => [t.name, t]));
344
+ let dbCommandsFromDb = await prisma.command.findMany({ where: { botId } });
345
+
346
+ const commandsToCreate = [];
347
+ for (const template of templatesMap.values()) {
348
+ if (!dbCommandsFromDb.some(cmd => cmd.name === template.name)) {
349
+ let permissionId = null;
350
+ if (template.permissions) {
351
+ const permission = await prisma.permission.upsert({
352
+ where: { botId_name: { botId, name: template.permissions } },
353
+ update: { description: `Авто-создано для команды ${template.name}` },
354
+ create: {
355
+ botId,
356
+ name: template.permissions,
357
+ description: `Авто-создано для команды ${template.name}`,
358
+ owner: template.owner || 'system',
359
+ }
360
+ });
361
+ permissionId = permission.id;
362
+ }
363
+
364
+ commandsToCreate.push({
365
+ botId,
366
+ name: template.name,
367
+ isEnabled: template.isActive,
368
+ cooldown: template.cooldown,
369
+ aliases: JSON.stringify(template.aliases),
370
+ description: template.description,
371
+ owner: template.owner,
372
+ permissionId: permissionId,
373
+ allowedChatTypes: JSON.stringify(template.allowedChatTypes),
374
+ });
375
+ }
376
+ }
377
+
378
+ if (commandsToCreate.length > 0) {
379
+ await prisma.command.createMany({ data: commandsToCreate });
380
+ dbCommandsFromDb = await prisma.command.findMany({ where: { botId }, orderBy: [{ owner: 'asc' }, { name: 'asc' }] });
381
+ }
382
+
383
+ const finalCommands = dbCommandsFromDb.map(cmd => {
384
+ const template = templatesMap.get(cmd.name);
385
+ let args = [];
386
+
387
+ if (cmd.isVisual) {
388
+ try {
389
+ args = JSON.parse(cmd.argumentsJson || '[]');
390
+ } catch (e) {
391
+ console.error(`Error parsing argumentsJson for visual command ${cmd.name} (ID: ${cmd.id}):`, e);
392
+ args = [];
393
+ }
394
+ } else {
395
+ if (template && template.args && template.args.length > 0) {
396
+ args = template.args;
397
+ } else {
398
+ try {
399
+ args = JSON.parse(cmd.argumentsJson || '[]');
400
+ } catch (e) {
401
+ args = [];
402
+ }
403
+ }
404
+ }
405
+
406
+ return {
407
+ ...cmd,
408
+ args: args,
409
+ aliases: JSON.parse(cmd.aliases || '[]'),
410
+ allowedChatTypes: JSON.parse(cmd.allowedChatTypes || '[]'),
411
+ };
412
+ })
413
+
414
+ res.json({
415
+ groups,
416
+ permissions: allPermissions,
417
+ users: {
418
+ items: users,
419
+ total: usersCount,
420
+ page,
421
+ pageSize,
422
+ totalPages: Math.ceil(usersCount / pageSize),
423
+ },
424
+ commands: finalCommands
425
+ });
426
+
427
+ } catch (error) {
428
+ console.error(`[API Error] /management-data for bot ${req.params.botId}:`, error);
429
+ res.status(500).json({ error: 'Не удалось загрузить данные управления' });
430
+ }
431
+ });
432
+
433
+ router.put('/:botId/commands/:commandId', authorize('management:edit'), async (req, res) => {
434
+ try {
435
+ const commandId = parseInt(req.params.commandId, 10);
436
+ const { name, description, cooldown, aliases, permissionId, allowedChatTypes, isEnabled, argumentsJson, graphJson } = req.body;
437
+
438
+ const dataToUpdate = {};
439
+ if (name !== undefined) dataToUpdate.name = name;
440
+ if (description !== undefined) dataToUpdate.description = description;
441
+ if (cooldown !== undefined) dataToUpdate.cooldown = parseInt(cooldown, 10);
442
+ if (aliases !== undefined) dataToUpdate.aliases = Array.isArray(aliases) ? JSON.stringify(aliases) : aliases;
443
+ if (permissionId !== undefined) dataToUpdate.permissionId = permissionId ? parseInt(permissionId, 10) : null;
444
+ if (allowedChatTypes !== undefined) dataToUpdate.allowedChatTypes = Array.isArray(allowedChatTypes) ? JSON.stringify(allowedChatTypes) : allowedChatTypes;
445
+ if (isEnabled !== undefined) dataToUpdate.isEnabled = isEnabled;
446
+ if (argumentsJson !== undefined) dataToUpdate.argumentsJson = Array.isArray(argumentsJson) ? JSON.stringify(argumentsJson) : argumentsJson;
447
+ if (graphJson !== undefined) dataToUpdate.graphJson = graphJson;
448
+
449
+ const updatedCommand = await prisma.command.update({
450
+ where: { id: commandId },
451
+ data: dataToUpdate,
452
+ });
453
+
454
+ res.json(updatedCommand);
455
+ } catch (error) {
456
+ console.error(`[API Error] /commands/:commandId PUT:`, error);
457
+ res.status(500).json({ error: 'Failed to update command' });
458
+ }
459
+ });
460
+
461
+ router.post('/:botId/groups', authorize('management:edit'), async (req, res) => {
462
+ try {
463
+ const botId = parseInt(req.params.botId);
464
+ const { name, permissionIds } = req.body;
465
+ if (!name) return res.status(400).json({ error: "Имя группы обязательно" });
466
+
467
+ const newGroup = await prisma.group.create({
468
+ data: {
469
+ name,
470
+ botId,
471
+ owner: 'admin',
472
+ permissions: { create: (permissionIds || []).map(id => ({ permissionId: id })) }
473
+ }
474
+ });
475
+
476
+ botManager.reloadBotConfigInRealTime(botId);
477
+
478
+ res.status(201).json(newGroup);
479
+ } catch (error) {
480
+ if (error.code === 'P2002') return res.status(409).json({ error: 'Группа с таким именем уже существует для этого бота.' });
481
+ res.status(500).json({ error: 'Не удалось создать группу.' });
482
+ }
483
+ });
484
+
485
+ router.put('/:botId/groups/:groupId', authorize('management:edit'), async (req, res) => {
486
+ try {
487
+ const botId = parseInt(req.params.botId, 10);
488
+ const groupId = parseInt(req.params.groupId);
489
+ const { name, permissionIds } = req.body;
490
+ if (!name) return res.status(400).json({ error: "Имя группы обязательно" });
491
+
492
+ const usersInGroup = await prisma.user.findMany({
493
+ where: { botId, groups: { some: { groupId } } },
494
+ select: { username: true }
495
+ });
496
+
497
+ await prisma.$transaction(async (tx) => {
498
+ await tx.group.update({ where: { id: groupId }, data: { name } });
499
+ await tx.groupPermission.deleteMany({ where: { groupId } });
500
+ if (permissionIds && permissionIds.length > 0) {
501
+ await tx.groupPermission.createMany({
502
+ data: permissionIds.map(pid => ({ groupId, permissionId: pid })),
503
+ });
504
+ }
505
+ });
506
+
507
+ for (const user of usersInGroup) {
508
+ botManager.invalidateUserCache(botId, user.username);
509
+ }
510
+
511
+ botManager.reloadBotConfigInRealTime(botId);
512
+
513
+ res.status(200).send();
514
+ } catch (error) {
515
+ if (error.code === 'P2002') return res.status(409).json({ error: 'Группа с таким именем уже существует для этого бота.' });
516
+ res.status(500).json({ error: 'Не удалось обновить группу.' });
517
+ }
518
+ });
519
+
520
+ router.delete('/:botId/groups/:groupId', authorize('management:edit'), async (req, res) => {
521
+ try {
522
+ const botId = parseInt(req.params.botId, 10);
523
+ const groupId = parseInt(req.params.groupId);
524
+ const group = await prisma.group.findUnique({ where: { id: groupId } });
525
+ if (group && group.owner !== 'admin') {
526
+ return res.status(403).json({ error: `Нельзя удалить группу с источником "${group.owner}".` });
527
+ }
528
+ await prisma.group.delete({ where: { id: groupId } });
529
+ botManager.reloadBotConfigInRealTime(botId);
530
+
531
+ res.status(204).send();
532
+ } catch (error) { res.status(500).json({ error: 'Не удалось удалить группу.' }); }
533
+ });
534
+
535
+ router.post('/:botId/permissions', authorize('management:edit'), async (req, res) => {
536
+ try {
537
+ const botId = parseInt(req.params.botId);
538
+ const { name, description } = req.body;
539
+ if (!name) return res.status(400).json({ error: 'Имя права обязательно' });
540
+ const newPermission = await prisma.permission.create({
541
+ data: { name, description, botId, owner: 'admin' }
542
+ });
543
+
544
+ botManager.reloadBotConfigInRealTime(botId);
545
+
546
+ res.status(201).json(newPermission);
547
+ } catch (error) {
548
+ if (error.code === 'P2002') return res.status(409).json({ error: 'Право с таким именем уже существует для этого бота.' });
549
+ res.status(500).json({ error: 'Не удалось создать право.' });
550
+ }
551
+ });
552
+
553
+ router.put('/:botId/users/:userId', authorize('management:edit'), async (req, res) => {
554
+ try {
555
+ const botId = parseInt(req.params.botId, 10);
556
+ const userId = parseInt(req.params.userId, 10);
557
+ const { isBlacklisted, groupIds } = req.body;
558
+
559
+ const updateData = {};
560
+ if (typeof isBlacklisted === 'boolean') {
561
+ updateData.isBlacklisted = isBlacklisted;
562
+ }
563
+
564
+ if (Array.isArray(groupIds)) {
565
+ await prisma.userGroup.deleteMany({ where: { userId } });
566
+ updateData.groups = {
567
+ create: groupIds.map(gid => ({ groupId: gid })),
568
+ };
569
+ }
570
+
571
+ const updatedUser = await prisma.user.update({
572
+ where: { id: userId },
573
+ data: updateData,
574
+ include: { groups: true }
575
+ });
576
+
577
+ botManager.invalidateUserCache(botId, updatedUser.username);
578
+
579
+ UserService.clearCache(updatedUser.username, botId);
580
+
581
+ res.json(updatedUser);
582
+
583
+ } catch (error) {
584
+ console.error(`[API Error] /users/:userId PUT:`, error);
585
+ res.status(500).json({ error: 'Не удалось обновить пользователя' });
586
+ }
587
+ });
588
+
589
+ router.post('/start-all', authorize('bot:start_stop'), async (req, res) => {
590
+ try {
591
+ console.log('[API] Получен запрос на запуск всех ботов.');
592
+ const allBots = await prisma.bot.findMany({ include: { server: true } });
593
+ let startedCount = 0;
594
+ for (const botConfig of allBots) {
595
+ if (!botManager.bots.has(botConfig.id)) {
596
+ await botManager.startBot(botConfig);
597
+ startedCount++;
598
+ }
599
+ }
600
+ res.json({ success: true, message: `Запущено ${startedCount} ботов.` });
601
+ } catch (error) {
602
+ console.error('[API Error] /start-all:', error);
603
+ res.status(500).json({ error: 'Произошла ошибка при массовом запуске ботов.' });
604
+ }
605
+ });
606
+
607
+ router.post('/stop-all', authorize('bot:start_stop'), (req, res) => {
608
+ try {
609
+ console.log('[API] Получен запрос на остановку всех ботов.');
610
+ const botIds = Array.from(botManager.bots.keys());
611
+ let stoppedCount = 0;
612
+ for (const botId of botIds) {
613
+ botManager.stopBot(botId);
614
+ stoppedCount++;
615
+ }
616
+ res.json({ success: true, message: `Остановлено ${stoppedCount} ботов.` });
617
+ } catch (error) {
618
+ console.error('[API Error] /stop-all:', error);
619
+ res.status(500).json({ error: 'Произошла ошибка при массовой остановке ботов.' });
620
+ }
621
+ });
622
+
623
+ router.get('/:id/settings/all', authorize('bot:update'), async (req, res) => {
624
+ try {
625
+ const botId = parseInt(req.params.id, 10);
626
+
627
+ const bot = await prisma.bot.findUnique({
628
+ where: { id: botId },
629
+ include: {
630
+ server: true,
631
+ installedPlugins: {
632
+ orderBy: { name: 'asc' }
633
+ }
634
+ }
635
+ });
636
+
637
+ if (!bot) {
638
+ return res.status(404).json({ error: 'Бот не найден' });
639
+ }
640
+
641
+ const allSettings = {
642
+ bot: {
643
+ id: bot.id,
644
+ username: bot.username,
645
+ prefix: bot.prefix,
646
+ note: bot.note,
647
+ owners: bot.owners,
648
+ serverId: bot.serverId,
649
+ proxyHost: bot.proxyHost,
650
+ proxyPort: bot.proxyPort,
651
+ proxyUsername: bot.proxyUsername,
652
+ },
653
+ plugins: []
654
+ };
655
+
656
+ const pluginSettingsPromises = bot.installedPlugins.map(async (plugin) => {
657
+ const manifest = plugin.manifest ? JSON.parse(plugin.manifest) : {};
658
+
659
+ if (!manifest.settings || Object.keys(manifest.settings).length === 0) {
660
+ return null;
661
+ }
662
+
663
+ const savedSettings = plugin.settings ? JSON.parse(plugin.settings) : {};
664
+ let defaultSettings = {};
665
+
666
+ for (const key in manifest.settings) {
667
+ const config = manifest.settings[key];
668
+ if (config.type === 'json_file' && config.defaultPath) {
669
+ const configFilePath = path.join(plugin.path, config.defaultPath);
670
+ try {
671
+ const fileContent = await fs.readFile(configFilePath, 'utf-8');
672
+ defaultSettings[key] = JSON.parse(fileContent);
673
+ } catch (e) { defaultSettings[key] = {}; }
674
+ } else {
675
+ try { defaultSettings[key] = JSON.parse(config.default || 'null'); }
676
+ catch { defaultSettings[key] = config.default; }
677
+ }
678
+ }
679
+
680
+ return {
681
+ id: plugin.id,
682
+ name: plugin.name,
683
+ description: plugin.description,
684
+ isEnabled: plugin.isEnabled,
685
+ manifest: manifest,
686
+ settings: { ...defaultSettings, ...savedSettings }
687
+ };
688
+ });
689
+
690
+ allSettings.plugins = (await Promise.all(pluginSettingsPromises)).filter(Boolean);
691
+
692
+ res.json(allSettings);
693
+
694
+ } catch (error) {
695
+ console.error("[API Error] /settings/all GET:", error);
696
+ res.status(500).json({ error: 'Не удалось загрузить все настройки' });
697
+ }
698
+ });
699
+
700
+ const nodeRegistry = require('../../core/NodeRegistry');
701
+
702
+ router.get('/:botId/visual-editor/nodes', authorize('management:view'), (req, res) => {
703
+ try {
704
+ const { graphType } = req.query;
705
+ const nodesByCategory = nodeRegistry.getNodesByCategory(graphType);
706
+ res.json(nodesByCategory);
707
+ } catch (error) {
708
+ console.error('[API Error] /visual-editor/nodes GET:', error);
709
+ res.status(500).json({ error: 'Failed to get available nodes' });
710
+ }
711
+ });
712
+
713
+ router.get('/:botId/visual-editor/node-config', authorize('management:view'), (req, res) => {
714
+ try {
715
+ const { types } = req.query;
716
+ if (!types) {
717
+ return res.status(400).json({ error: 'Node types must be provided' });
718
+ }
719
+ const typeArray = Array.isArray(types) ? types : [types];
720
+ const config = nodeRegistry.getNodesByTypes(typeArray);
721
+ res.json(config);
722
+ } catch (error) {
723
+ console.error('[API Error] /visual-editor/node-config GET:', error);
724
+ res.status(500).json({ error: 'Failed to get node configuration' });
725
+ }
726
+ });
727
+
728
+ router.get('/:botId/visual-editor/permissions', authorize('management:view'), async (req, res) => {
729
+ try {
730
+ const botId = parseInt(req.params.botId, 10);
731
+ const permissions = await prisma.permission.findMany({
732
+ where: { botId },
733
+ orderBy: { name: 'asc' }
734
+ });
735
+ res.json(permissions);
736
+ } catch (error) {
737
+ console.error('[API Error] /visual-editor/permissions GET:', error);
738
+ res.status(500).json({ error: 'Failed to get permissions' });
739
+ }
740
+ });
741
+
742
+ router.post('/:botId/commands/visual', authorize('management:edit'), async (req, res) => {
743
+ try {
744
+ const botId = parseInt(req.params.botId, 10);
745
+ const {
746
+ name,
747
+ description,
748
+ aliases = [],
749
+ permissionId,
750
+ cooldown = 0,
751
+ allowedChatTypes = ['chat', 'private'],
752
+ argumentsJson = '[]',
753
+ graphJson = 'null'
754
+ } = req.body;
755
+
756
+ if (!name) {
757
+ return res.status(400).json({ error: 'Command name is required' });
758
+ }
759
+
760
+ const newCommand = await prisma.command.create({
761
+ data: {
762
+ botId,
763
+ name,
764
+ description,
765
+ aliases: JSON.stringify(aliases),
766
+ permissionId: permissionId || null,
767
+ cooldown,
768
+ allowedChatTypes: JSON.stringify(allowedChatTypes),
769
+ isVisual: true,
770
+ argumentsJson,
771
+ graphJson
772
+ }
773
+ });
774
+
775
+ botManager.reloadBotConfigInRealTime(botId);
776
+ res.status(201).json(newCommand);
777
+ } catch (error) {
778
+ if (error.code === 'P2002') {
779
+ return res.status(409).json({ error: 'Command with this name already exists' });
780
+ }
781
+ console.error('[API Error] /commands/visual POST:', error);
782
+ res.status(500).json({ error: 'Failed to create visual command' });
783
+ }
784
+ });
785
+
786
+ router.put('/:botId/commands/:commandId/visual', authorize('management:edit'), async (req, res) => {
787
+ try {
788
+ const botId = parseInt(req.params.botId, 10);
789
+ const commandId = parseInt(req.params.commandId, 10);
790
+ const {
791
+ name,
792
+ description,
793
+ aliases,
794
+ permissionId,
795
+ cooldown,
796
+ allowedChatTypes,
797
+ argumentsJson,
798
+ graphJson
799
+ } = req.body;
800
+
801
+ const dataToUpdate = { isVisual: true };
802
+
803
+ if (name) dataToUpdate.name = name;
804
+ if (description !== undefined) dataToUpdate.description = description;
805
+ if (Array.isArray(aliases)) dataToUpdate.aliases = JSON.stringify(aliases);
806
+ if (permissionId !== undefined) dataToUpdate.permissionId = permissionId || null;
807
+ if (typeof cooldown === 'number') dataToUpdate.cooldown = cooldown;
808
+ if (Array.isArray(allowedChatTypes)) dataToUpdate.allowedChatTypes = JSON.stringify(allowedChatTypes);
809
+ if (argumentsJson !== undefined) dataToUpdate.argumentsJson = argumentsJson;
810
+ if (graphJson !== undefined) dataToUpdate.graphJson = graphJson;
811
+
812
+ const updatedCommand = await prisma.command.update({
813
+ where: { id: commandId, botId },
814
+ data: dataToUpdate
815
+ });
816
+
817
+ botManager.reloadBotConfigInRealTime(botId);
818
+ res.json(updatedCommand);
819
+ } catch (error) {
820
+ if (error.code === 'P2002') {
821
+ return res.status(409).json({ error: 'Command with this name already exists' });
822
+ }
823
+ console.error('[API Error] /commands/:commandId/visual PUT:', error);
824
+ res.status(500).json({ error: 'Failed to update visual command' });
825
+ }
826
+ });
827
+
828
+ router.get('/:botId/commands/:commandId/export', authorize('management:view'), async (req, res) => {
829
+ try {
830
+ const botId = parseInt(req.params.botId, 10);
831
+ const commandId = parseInt(req.params.commandId, 10);
832
+
833
+ const command = await prisma.command.findUnique({
834
+ where: { id: commandId, botId: botId },
835
+ });
836
+
837
+ if (!command) {
838
+ return res.status(404).json({ error: 'Command not found' });
839
+ }
840
+
841
+ const exportData = {
842
+ version: '1.0',
843
+ type: 'command',
844
+ ...command
845
+ };
846
+
847
+ delete exportData.id;
848
+ delete exportData.botId;
849
+
850
+ res.json(exportData);
851
+ } catch (error) {
852
+ console.error('Failed to export command:', error);
853
+ res.status(500).json({ error: 'Failed to export command' });
854
+ }
855
+ });
856
+
857
+ router.post('/:botId/commands/import', authorize('management:edit'), async (req, res) => {
858
+ try {
859
+ const botId = parseInt(req.params.botId, 10);
860
+ const importData = req.body;
861
+
862
+ if (importData.type !== 'command') {
863
+ return res.status(400).json({ error: 'Invalid file type. Expected "command".' });
864
+ }
865
+
866
+ let commandName = importData.name;
867
+ let counter = 1;
868
+
869
+ while (await prisma.command.findFirst({ where: { botId, name: commandName } })) {
870
+ commandName = `${importData.name}_imported_${counter}`;
871
+ counter++;
872
+ }
873
+
874
+ let finalGraphJson = importData.graphJson;
875
+
876
+ if (finalGraphJson && finalGraphJson !== 'null') {
877
+ const graph = JSON.parse(finalGraphJson);
878
+ const nodeIdMap = new Map();
879
+
880
+ if (graph.nodes) {
881
+ graph.nodes.forEach(node => {
882
+ const oldId = node.id;
883
+ const newId = `${node.type}-${randomUUID()}`;
884
+ nodeIdMap.set(oldId, newId);
885
+ node.id = newId;
886
+ });
887
+ }
888
+
889
+ if (graph.connections) {
890
+ graph.connections.forEach(conn => {
891
+ conn.id = `edge-${randomUUID()}`;
892
+ conn.sourceNodeId = nodeIdMap.get(conn.sourceNodeId) || conn.sourceNodeId;
893
+ conn.targetNodeId = nodeIdMap.get(conn.targetNodeId) || conn.targetNodeId;
894
+ });
895
+ }
896
+
897
+ finalGraphJson = JSON.stringify(graph);
898
+ }
899
+
900
+ const newCommand = await prisma.command.create({
901
+ data: {
902
+ botId: botId,
903
+ name: commandName,
904
+ description: importData.description,
905
+ aliases: importData.aliases,
906
+ permissionId: null,
907
+ cooldown: importData.cooldown,
908
+ allowedChatTypes: importData.allowedChatTypes,
909
+ isVisual: importData.isVisual,
910
+ isEnabled: importData.isEnabled,
911
+ argumentsJson: importData.argumentsJson,
912
+ graphJson: finalGraphJson,
913
+ owner: 'visual_editor',
914
+ }
915
+ });
916
+
917
+ botManager.reloadBotConfigInRealTime(botId);
918
+ res.status(201).json(newCommand);
919
+ } catch (error) {
920
+ console.error("Failed to import command:", error);
921
+ res.status(500).json({ error: 'Failed to import command' });
922
+ }
923
+ });
924
+
925
+ router.post('/:botId/commands', authorize('management:edit'), async (req, res) => {
926
+ try {
927
+ const botId = parseInt(req.params.botId, 10);
928
+ const {
929
+ name,
930
+ description,
931
+ aliases = [],
932
+ permissionId,
933
+ cooldown = 0,
934
+ allowedChatTypes = ['chat', 'private'],
935
+ isVisual = false,
936
+ argumentsJson = '[]',
937
+ graphJson = 'null'
938
+ } = req.body;
939
+
940
+ if (!name) {
941
+ return res.status(400).json({ error: 'Command name is required' });
942
+ }
943
+
944
+ const newCommand = await prisma.command.create({
945
+ data: {
946
+ botId,
947
+ name,
948
+ description,
949
+ aliases: JSON.stringify(aliases),
950
+ permissionId: permissionId || null,
951
+ cooldown,
952
+ allowedChatTypes: JSON.stringify(allowedChatTypes),
953
+ isVisual,
954
+ argumentsJson,
955
+ graphJson,
956
+ owner: isVisual ? 'visual_editor' : 'manual',
957
+ }
958
+ });
959
+
960
+ botManager.reloadBotConfigInRealTime(botId);
961
+ res.status(201).json(newCommand);
962
+ } catch (error) {
963
+ if (error.code === 'P2002') {
964
+ return res.status(409).json({ error: 'Command with this name already exists' });
965
+ }
966
+ console.error('[API Error] /commands POST:', error);
967
+ res.status(500).json({ error: 'Failed to create command' });
968
+ }
969
+ });
970
+
971
+ router.delete('/:botId/commands/:commandId', authorize('management:edit'), async (req, res) => {
972
+ try {
973
+ const botId = parseInt(req.params.botId, 10);
974
+ const commandId = parseInt(req.params.commandId, 10);
975
+
976
+ await prisma.command.delete({
977
+ where: { id: commandId, botId: botId },
978
+ });
979
+
980
+ botManager.reloadBotConfigInRealTime(botId);
981
+ res.status(204).send();
982
+ } catch (error) {
983
+ console.error(`[API Error] /commands/:commandId DELETE:`, error);
984
+ res.status(500).json({ error: 'Failed to delete command' });
985
+ }
986
+ });
987
+
988
+ router.get('/:botId/event-graphs/:graphId', authorize('management:view'), async (req, res) => {
989
+ try {
990
+ const botId = parseInt(req.params.botId, 10);
991
+ const graphId = parseInt(req.params.graphId, 10);
992
+
993
+ const eventGraph = await prisma.eventGraph.findUnique({
994
+ where: { id: graphId, botId },
995
+ include: { triggers: true },
996
+ });
997
+
998
+ if (!eventGraph) {
999
+ return res.status(404).json({ error: 'Граф события не найден' });
1000
+ }
1001
+
1002
+ res.json(eventGraph);
1003
+ } catch (error) {
1004
+ console.error(`[API Error] /event-graphs/:graphId GET:`, error);
1005
+ res.status(500).json({ error: 'Не удалось получить граф события' });
1006
+ }
1007
+ });
1008
+
1009
+ router.post('/:botId/event-graphs', authorize('management:edit'), async (req, res) => {
1010
+ try {
1011
+ const botId = parseInt(req.params.botId, 10);
1012
+ const { name } = req.body;
1013
+
1014
+ if (!name) {
1015
+ return res.status(400).json({ error: 'Имя графа обязательно' });
1016
+ }
1017
+
1018
+ const newEventGraph = await prisma.eventGraph.create({
1019
+ data: {
1020
+ botId,
1021
+ name,
1022
+ isEnabled: true,
1023
+ graphJson: 'null',
1024
+ },
1025
+ });
1026
+
1027
+ res.status(201).json(newEventGraph);
1028
+ } catch (error) {
1029
+ if (error.code === 'P2002') {
1030
+ return res.status(409).json({ error: 'Граф событий с таким именем уже существует' });
1031
+ }
1032
+ console.error(`[API Error] /event-graphs POST:`, error);
1033
+ res.status(500).json({ error: 'Не удалось создать граф событий' });
1034
+ }
1035
+ });
1036
+
1037
+ router.delete('/:botId/event-graphs/:graphId', authorize('management:edit'), async (req, res) => {
1038
+ try {
1039
+ const botId = parseInt(req.params.botId, 10);
1040
+ const graphId = parseInt(req.params.graphId, 10);
1041
+
1042
+ await prisma.eventGraph.delete({
1043
+ where: { id: graphId, botId: botId },
1044
+ });
1045
+
1046
+ res.status(204).send();
1047
+ } catch (error) {
1048
+ console.error(`[API Error] /event-graphs/:graphId DELETE:`, error);
1049
+ res.status(500).json({ error: 'Не удалось удалить граф событий' });
1050
+ }
1051
+ });
1052
+
1053
+ router.put('/:botId/event-graphs/:graphId', authorize('management:edit'), async (req, res) => {
1054
+ const { botId, graphId } = req.params;
1055
+ const { name, isEnabled, graphJson, eventType } = req.body;
1056
+
1057
+ if (!name || !eventType || !graphJson) {
1058
+ return res.status(400).json({ error: 'Поля name, eventType и graphJson обязательны.' });
1059
+ }
1060
+
1061
+ if (typeof isEnabled !== 'boolean') {
1062
+ return res.status(400).json({ error: 'Поле isEnabled должно быть true или false.' });
1063
+ }
1064
+
1065
+ try {
1066
+ const updatedGraph = await prisma.eventGraph.update({
1067
+ where: { id: parseInt(graphId), botId: parseInt(botId) },
1068
+ data: {
1069
+ name,
1070
+ isEnabled,
1071
+ graphJson,
1072
+ eventType,
1073
+ }
1074
+ });
1075
+
1076
+ botManager.eventGraphManager.updateGraph(parseInt(botId), updatedGraph);
1077
+
1078
+ res.json(updatedGraph);
1079
+ } catch (error) {
1080
+ console.error(`[API Error] /event-graphs/:graphId PUT:`, error);
1081
+ res.status(500).json({ error: 'Ошибка при обновлении графа событий.' });
1082
+ }
1083
+ });
1084
+
1085
+ router.post('/:botId/visual-editor/save', authorize('management:edit'), async (req, res) => {
1086
+ });
1087
+
1088
+ module.exports = router;