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