blockmine 1.0.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.
Files changed (61) hide show
  1. package/README.md +107 -0
  2. package/backend/cli.js +40 -0
  3. package/backend/nodemon.json +12 -0
  4. package/backend/package-lock.json +2539 -0
  5. package/backend/package.json +34 -0
  6. package/backend/prisma/migrations/20250614085849_add_bot_note/migration.sql +126 -0
  7. package/backend/prisma/migrations/20250614153037_add_plugin_installed_date/migration.sql +27 -0
  8. package/backend/prisma/migrations/migration_lock.toml +3 -0
  9. package/backend/prisma/schema.prisma +138 -0
  10. package/backend/prisma/seed.js +60 -0
  11. package/backend/src/api/routes/bots.js +777 -0
  12. package/backend/src/api/routes/permissions.js +79 -0
  13. package/backend/src/api/routes/plugins.js +110 -0
  14. package/backend/src/api/routes/servers.js +50 -0
  15. package/backend/src/api/routes/settings.js +40 -0
  16. package/backend/src/core/BotManager.js +264 -0
  17. package/backend/src/core/BotProcess.js +233 -0
  18. package/backend/src/core/DependencyService.js +93 -0
  19. package/backend/src/core/MessageQueue.js +126 -0
  20. package/backend/src/core/PermissionManager.js +97 -0
  21. package/backend/src/core/PluginLoader.js +64 -0
  22. package/backend/src/core/PluginManager.js +161 -0
  23. package/backend/src/core/PluginService.js +57 -0
  24. package/backend/src/core/UserService.js +181 -0
  25. package/backend/src/core/commands/ping.js +35 -0
  26. package/backend/src/core/commands/warn.js +40 -0
  27. package/backend/src/core/ipc/PermissionManager.stub.js +24 -0
  28. package/backend/src/core/ipc/UserService.stub.js +31 -0
  29. package/backend/src/core/system/Command.js +53 -0
  30. package/backend/src/core/system/CommandHandler.js +98 -0
  31. package/backend/src/core/system/CommandManager.js +59 -0
  32. package/backend/src/core/system/CommandRegistry.js +21 -0
  33. package/backend/src/core/system/parseArguments.js +43 -0
  34. package/backend/src/real-time/socketHandler.js +31 -0
  35. package/backend/src/server.js +66 -0
  36. package/frontend/dist/apple-touch-icon.png +0 -0
  37. package/frontend/dist/assets/index-B83SHIXE.css +1 -0
  38. package/frontend/dist/assets/index-Dh-PcVh1.js +8179 -0
  39. package/frontend/dist/favicon-96x96.png +0 -0
  40. package/frontend/dist/favicon.ico +0 -0
  41. package/frontend/dist/favicon.svg +3 -0
  42. package/frontend/dist/index.html +51 -0
  43. package/frontend/dist/logo.png +0 -0
  44. package/frontend/dist/logo.svg +178 -0
  45. package/frontend/dist/monacoeditorwork/css.worker.bundle.js +53462 -0
  46. package/frontend/dist/monacoeditorwork/editor.worker.bundle.js +13519 -0
  47. package/frontend/dist/monacoeditorwork/html.worker.bundle.js +29662 -0
  48. package/frontend/dist/monacoeditorwork/json.worker.bundle.js +21320 -0
  49. package/frontend/dist/monacoeditorwork/ts.worker.bundle.js +256353 -0
  50. package/frontend/dist/site.webmanifest +21 -0
  51. package/frontend/dist/vite.svg +1 -0
  52. package/frontend/dist/web-app-manifest-192x192.png +0 -0
  53. package/frontend/dist/web-app-manifest-512x512.png +0 -0
  54. package/frontend/package.json +65 -0
  55. package/image/1.png +0 -0
  56. package/image/2.png +0 -0
  57. package/image/3.png +0 -0
  58. package/image/logo.png +0 -0
  59. package/package.json +27 -0
  60. package/tailwind.config.js +0 -0
  61. package/vite.config.js +0 -0
@@ -0,0 +1,777 @@
1
+ const express = require('express');
2
+ const router = express.Router();
3
+ const { PrismaClient } = require('@prisma/client');
4
+ const path = require('path');
5
+ const fs = require('fs/promises');
6
+ const BotManager = require('../../core/BotManager');
7
+ const PluginManager = require('../../core/PluginManager');
8
+ const UserService = require('../../core/UserService');
9
+ const commandManager = require('../../core/system/CommandManager');
10
+
11
+ const multer = require('multer');
12
+ const archiver = require('archiver');
13
+ const AdmZip = require('adm-zip');
14
+
15
+ const prisma = new PrismaClient();
16
+ const upload = multer({ storage: multer.memoryStorage() });
17
+
18
+ async function setupDefaultPermissionsForBot(botId, prismaClient = prisma) {
19
+ const initialData = {
20
+ groups: ["User", "Admin"],
21
+ permissions: [
22
+ { name: "admin.*", description: "Все права администратора" },
23
+ { name: "admin.cooldown.bypass", description: "Обход кулдауна для админ-команд" },
24
+ { name: "user.*", description: "Все права обычного пользователя" },
25
+ { name: "user.cooldown.bypass", description: "Обход кулдауна для юзер-команд" },
26
+ ],
27
+ groupPermissions: {
28
+ "User": ["user.*"],
29
+ "Admin": ["admin.*", "admin.cooldown.bypass", "user.cooldown.bypass"]
30
+ },
31
+ };
32
+
33
+ for (const perm of initialData.permissions) {
34
+ await prismaClient.permission.upsert({ where: { botId_name: { botId, name: perm.name } }, update: { description: perm.description }, create: { ...perm, botId, owner: 'system' } });
35
+ }
36
+ for (const groupName of initialData.groups) {
37
+ await prismaClient.group.upsert({ where: { botId_name: { botId, name: groupName } }, update: {}, create: { name: groupName, botId, owner: 'system' } });
38
+ }
39
+ for (const [groupName, permNames] of Object.entries(initialData.groupPermissions)) {
40
+ const group = await prismaClient.group.findUnique({ where: { botId_name: { botId, name: groupName } } });
41
+ if (group) {
42
+ for (const permName of permNames) {
43
+ const permission = await prismaClient.permission.findUnique({ where: { botId_name: { botId, name: permName } } });
44
+ if (permission) {
45
+ await prismaClient.groupPermission.upsert({ where: { groupId_permissionId: { groupId: group.id, permissionId: permission.id } }, update: {}, create: { groupId: group.id, permissionId: permission.id } });
46
+ }
47
+ }
48
+ }
49
+ }
50
+ console.log(`[Setup] Для бота ID ${botId} созданы группы и права по умолчанию.`);
51
+ }
52
+
53
+
54
+ router.get('/', async (req, res) => {
55
+ try {
56
+ const bots = await prisma.bot.findMany({ include: { server: true }, orderBy: { createdAt: 'asc' } });
57
+ res.json(bots);
58
+ } catch (error) { res.status(500).json({ error: 'Не удалось получить список ботов' }); }
59
+ });
60
+
61
+ router.get('/state', (req, res) => {
62
+ try {
63
+ const state = BotManager.getFullState();
64
+ res.json(state);
65
+ } catch (error) { res.status(500).json({ error: 'Не удалось получить состояние ботов' }); }
66
+ });
67
+
68
+ router.post('/', async (req, res) => {
69
+ try {
70
+ const { username, password, prefix, serverId, note } = req.body;
71
+ if (!username || !serverId) return res.status(400).json({ error: 'Имя и сервер обязательны' });
72
+ const newBot = await prisma.bot.create({
73
+ data: { username, password, prefix, note, serverId: parseInt(serverId, 10) },
74
+ include: { server: true }
75
+ });
76
+ await setupDefaultPermissionsForBot(newBot.id);
77
+ res.status(201).json(newBot);
78
+ } catch (error) {
79
+ if (error.code === 'P2002') return res.status(409).json({ error: 'Бот с таким именем уже существует' });
80
+ console.error("[API Error] /bots POST:", error);
81
+ res.status(500).json({ error: 'Не удалось создать бота' });
82
+ }
83
+ });
84
+
85
+ router.put('/:id', async (req, res) => {
86
+ try {
87
+ const botId = parseInt(req.params.id, 10);
88
+
89
+ const {
90
+ username, password, prefix, serverId, note,
91
+ proxyHost, proxyPort, proxyUsername, proxyPassword
92
+ } = req.body;
93
+
94
+ let dataToUpdate = {
95
+ username,
96
+ prefix,
97
+ note,
98
+ proxyHost,
99
+ proxyPort: proxyPort ? parseInt(proxyPort, 10) : null,
100
+ proxyUsername,
101
+ };
102
+
103
+ if (password) {
104
+ dataToUpdate.password = password;
105
+ }
106
+ if (proxyPassword) {
107
+ dataToUpdate.proxyPassword = proxyPassword;
108
+ }
109
+
110
+ if (serverId) {
111
+ dataToUpdate.serverId = parseInt(serverId, 10);
112
+ }
113
+
114
+ if (dataToUpdate.serverId) {
115
+ const { serverId: sId, ...rest } = dataToUpdate;
116
+ dataToUpdate = { ...rest, server: { connect: { id: sId } } };
117
+ }
118
+
119
+ const updatedBot = await prisma.bot.update({
120
+ where: { id: botId },
121
+ data: dataToUpdate,
122
+ include: { server: true }
123
+ });
124
+
125
+ res.json(updatedBot);
126
+ } catch (error) {
127
+ console.error("Update bot error:", error);
128
+ if (error.code === 'P2002') return res.status(409).json({ error: 'Бот с таким именем уже существует' });
129
+ res.status(500).json({ error: 'Не удалось обновить бота' });
130
+ }
131
+ });
132
+
133
+ router.delete('/:id', async (req, res) => {
134
+ try {
135
+ const botId = parseInt(req.params.id, 10);
136
+ if (BotManager.bots.has(botId)) return res.status(400).json({ error: 'Нельзя удалить запущенного бота' });
137
+ await prisma.bot.delete({ where: { id: botId } });
138
+ res.status(204).send();
139
+ } catch (error) { res.status(500).json({ error: 'Не удалось удалить бота' }); }
140
+ });
141
+
142
+
143
+ router.post('/:id/start', async (req, res) => {
144
+ try {
145
+ const botId = parseInt(req.params.id, 10);
146
+ const botConfig = await prisma.bot.findUnique({ where: { id: botId }, include: { server: true } });
147
+ if (!botConfig) return res.status(404).json({ error: 'Бот не найден' });
148
+ const result = await BotManager.startBot(botConfig);
149
+ res.json(result);
150
+ } catch (error) { res.status(500).json({ error: 'Ошибка при запуске бота: ' + error.message }); }
151
+ });
152
+
153
+ router.post('/:id/stop', (req, res) => {
154
+ const botId = parseInt(req.params.id, 10);
155
+ const result = BotManager.stopBot(botId);
156
+ res.json(result);
157
+ });
158
+
159
+ router.post('/:id/chat', (req, res) => {
160
+ try {
161
+ const botId = parseInt(req.params.id, 10);
162
+ const { message } = req.body;
163
+ if (!message) return res.status(400).json({ error: 'Сообщение не может быть пустым' });
164
+ const result = BotManager.sendMessageToBot(botId, message);
165
+ if (result.success) res.json({ success: true });
166
+ else res.status(404).json(result);
167
+ } catch (error) { res.status(500).json({ error: 'Внутренняя ошибка сервера: ' + error.message }); }
168
+ });
169
+
170
+ router.get('/:id/export', async (req, res) => {
171
+ try {
172
+ const botId = parseInt(req.params.id, 10);
173
+ const {
174
+ includeCommands = 'true',
175
+ includePermissions = 'true',
176
+ includePluginFiles = 'true'
177
+ } = req.query;
178
+
179
+ const bot = await prisma.bot.findUnique({
180
+ where: { id: botId },
181
+ include: { server: true, installedPlugins: true },
182
+ });
183
+
184
+ if (!bot) {
185
+ return res.status(404).json({ error: 'Бот не найден' });
186
+ }
187
+
188
+ const archive = archiver('zip', { zlib: { level: 9 } });
189
+ res.setHeader('Content-Disposition', `attachment; filename="bot-${bot.username}-export.zip"`);
190
+ res.setHeader('Content-Type', 'application/zip');
191
+ archive.pipe(res);
192
+
193
+ const exportMetadata = {
194
+ version: '1.2-configurable',
195
+ bot: {
196
+ username: bot.username,
197
+ prefix: bot.prefix,
198
+ note: bot.note,
199
+ server: { host: bot.server.host, port: bot.server.port, version: bot.server.version },
200
+ proxy: { host: bot.proxyHost, port: bot.proxyPort },
201
+ },
202
+ plugins: bot.installedPlugins.map(p => ({
203
+ name: p.name,
204
+ folderName: path.basename(p.path),
205
+ isEnabled: p.isEnabled,
206
+ settings: p.settings,
207
+ sourceUri: p.sourceType === 'GITHUB' ? p.sourceUri : null,
208
+ })),
209
+ };
210
+
211
+ if (includeCommands === 'true') {
212
+ exportMetadata.commands = await prisma.command.findMany({ where: { botId } });
213
+ }
214
+ if (includePermissions === 'true') {
215
+ exportMetadata.permissions = await prisma.permission.findMany({ where: { botId } });
216
+ exportMetadata.groups = await prisma.group.findMany({ where: { botId }, include: { permissions: { select: { permission: { select: { name: true } } } } } });
217
+ exportMetadata.users = await prisma.user.findMany({ where: { botId }, include: { groups: { select: { group: { select: { name: true } } } } } });
218
+ }
219
+
220
+ archive.append(JSON.stringify(exportMetadata, null, 2), { name: 'bot_export.json' });
221
+
222
+ if (includePluginFiles === 'true') {
223
+ for (const plugin of bot.installedPlugins) {
224
+ const pluginFolderName = path.basename(plugin.path);
225
+ try {
226
+ await fs.access(plugin.path);
227
+ archive.directory(plugin.path, `plugins/${pluginFolderName}`);
228
+ } catch (error) {
229
+ console.warn(`[Export] Папка плагина ${plugin.name} по пути ${plugin.path} не найдена, пропускаем.`);
230
+ }
231
+ }
232
+ }
233
+
234
+ await archive.finalize();
235
+
236
+ } catch (error) {
237
+ console.error("[API Error] /export GET:", error);
238
+ res.status(500).json({ error: 'Не удалось экспортировать бота' });
239
+ }
240
+ });
241
+
242
+ router.post('/import', upload.single('botFile'), async (req, res) => {
243
+ if (!req.file) return res.status(400).json({ error: 'Файл не был загружен' });
244
+ if (path.extname(req.file.originalname).toLowerCase() !== '.zip') return res.status(400).json({ error: 'Неверный формат файла. Ожидался ZIP-архив.' });
245
+
246
+ const zip = new AdmZip(req.file.buffer);
247
+ const metadataEntry = zip.getEntry('bot_export.json');
248
+ if (!metadataEntry) return res.status(400).json({ error: 'Файл bot_export.json не найден в архиве.' });
249
+
250
+ const importMetadata = JSON.parse(metadataEntry.getData().toString('utf-8'));
251
+ const { bot: botData, plugins: pluginsInfo, commands, permissions, groups, users } = importMetadata;
252
+
253
+ const existingBot = await prisma.bot.findUnique({ where: { username: botData.username } });
254
+ if (existingBot) return res.status(409).json({ error: `Бот с именем "${botData.username}" уже существует.` });
255
+
256
+ let newBotId;
257
+ let botStorageDir;
258
+ try {
259
+ await prisma.$transaction(async (tx) => {
260
+ let server = await tx.server.findFirst({ where: { host: botData.server.host, port: botData.server.port } });
261
+ if (!server) server = await tx.server.create({ data: { name: botData.server.host, host: botData.server.host, port: botData.server.port, version: botData.server.version } });
262
+ 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 } });
263
+ newBotId = newBot.id;
264
+
265
+ if (permissions && groups && users) {
266
+ const permMap = new Map();
267
+ for (const perm of permissions) {
268
+ const newPerm = await tx.permission.create({ data: { botId: newBotId, name: perm.name, description: perm.description, owner: perm.owner } });
269
+ permMap.set(perm.name, newPerm.id);
270
+ }
271
+
272
+ const groupMap = new Map();
273
+ for (const group of groups) {
274
+ const newGroup = await tx.group.create({
275
+ data: {
276
+ botId: newBotId,
277
+ name: group.name,
278
+ owner: group.owner,
279
+ permissions: { create: group.permissions.map(p => ({ permissionId: permMap.get(p.permission.name) })).filter(p => p.permissionId) }
280
+ }
281
+ });
282
+ groupMap.set(group.name, newGroup.id);
283
+ }
284
+
285
+ for (const user of users) {
286
+ await tx.user.create({
287
+ data: {
288
+ botId: newBotId,
289
+ username: user.username,
290
+ isBlacklisted: user.isBlacklisted,
291
+ groups: { create: user.groups.map(g => ({ groupId: groupMap.get(g.group.name) })).filter(g => g.groupId) }
292
+ }
293
+ });
294
+ }
295
+ } else {
296
+ await setupDefaultPermissionsForBot(newBotId, tx);
297
+ }
298
+
299
+ if (commands) {
300
+ for (const cmd of commands) {
301
+ const permName = permissions?.find(p => p.id === cmd.permissionId)?.name;
302
+ const permId = permName ? (await tx.permission.findUnique({where: {botId_name: {botId: newBotId, name: permName}}}))?.id : null;
303
+
304
+ await tx.command.create({
305
+ data: {
306
+ botId: newBotId,
307
+ name: cmd.name,
308
+ isEnabled: cmd.isEnabled,
309
+ cooldown: cmd.cooldown,
310
+ aliases: cmd.aliases,
311
+ description: cmd.description,
312
+ owner: cmd.owner,
313
+ permissionId: permId,
314
+ allowedChatTypes: cmd.allowedChatTypes,
315
+ }
316
+ });
317
+ }
318
+ }
319
+
320
+ botStorageDir = path.resolve(__dirname, `../../../storage/plugins/bot_${newBotId}`);
321
+ await fs.mkdir(botStorageDir, { recursive: true });
322
+ const pluginsDirInZip = zip.getEntry('plugins/');
323
+
324
+ if (pluginsDirInZip && pluginsDirInZip.isDirectory) {
325
+ zip.extractEntryTo('plugins/', botStorageDir, true, true);
326
+ for (const pInfo of pluginsInfo) {
327
+ const targetPath = path.join(botStorageDir, 'plugins', pInfo.folderName);
328
+ const packageJson = JSON.parse(await fs.readFile(path.join(targetPath, 'package.json'), 'utf-8'));
329
+ await tx.installedPlugin.create({
330
+ 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 }
331
+ });
332
+ }
333
+ } else {
334
+ for (const pInfo of pluginsInfo) {
335
+ if (pInfo.sourceUri) {
336
+ const newPlugin = await PluginManager.installFromGithub(newBotId, pInfo.sourceUri, tx);
337
+ await tx.installedPlugin.update({ where: { id: newPlugin.id }, data: { isEnabled: pInfo.isEnabled, settings: pInfo.settings } });
338
+ }
339
+ }
340
+ }
341
+ });
342
+
343
+ const finalBot = await prisma.bot.findUnique({ where: { id: newBotId }});
344
+ res.status(201).json(finalBot);
345
+
346
+ } catch (error) {
347
+ console.error("[API Error] /import POST:", error);
348
+ if (botStorageDir) {
349
+ await fs.rm(botStorageDir, { recursive: true, force: true }).catch(() => {});
350
+ }
351
+ res.status(500).json({ error: `Не удалось импортировать бота: ${error.message}` });
352
+ }
353
+ });
354
+
355
+
356
+ router.get('/:botId/plugins', async (req, res) => {
357
+ try {
358
+ const botId = parseInt(req.params.botId);
359
+ const plugins = await prisma.installedPlugin.findMany({ where: { botId } });
360
+ res.json(plugins);
361
+ } catch (error) { res.status(500).json({ error: 'Не удалось получить плагины бота' }); }
362
+ });
363
+
364
+ router.post('/:botId/plugins/install/github', async (req, res) => {
365
+ try {
366
+ const botId = parseInt(req.params.botId);
367
+ const { repoUrl } = req.body;
368
+ const newPlugin = await PluginManager.installFromGithub(botId, repoUrl);
369
+ res.status(201).json(newPlugin);
370
+ } catch (error) { res.status(500).json({ error: error.message }); }
371
+ });
372
+
373
+ router.post('/:botId/plugins/register/local', async (req, res) => {
374
+ try {
375
+ const botId = parseInt(req.params.botId);
376
+ const { path } = req.body;
377
+ const newPlugin = await PluginManager.installFromLocalPath(botId, path);
378
+ res.status(201).json(newPlugin);
379
+ } catch (error) { res.status(500).json({ error: error.message }); }
380
+ });
381
+
382
+ router.delete('/:botId/plugins/:pluginId', async (req, res) => {
383
+ try {
384
+ const pluginId = parseInt(req.params.pluginId);
385
+ await PluginManager.deletePlugin(pluginId);
386
+ res.status(204).send();
387
+ } catch (error) { res.status(500).json({ error: error.message }); }
388
+ });
389
+
390
+ router.get('/:botId/plugins/:pluginId/settings', async (req, res) => {
391
+ try {
392
+ const pluginId = parseInt(req.params.pluginId);
393
+ const plugin = await prisma.installedPlugin.findUnique({ where: { id: pluginId } });
394
+ if (!plugin) return res.status(404).json({ error: 'Установленный плагин не найден' });
395
+
396
+ const savedSettings = plugin.settings ? JSON.parse(plugin.settings) : {};
397
+ let defaultSettings = {};
398
+ const manifest = plugin.manifest ? JSON.parse(plugin.manifest) : {};
399
+
400
+ if (manifest.settings) {
401
+ for (const key in manifest.settings) {
402
+ const config = manifest.settings[key];
403
+ if (config.type === 'json_file' && config.defaultPath) {
404
+ const configFilePath = path.join(plugin.path, config.defaultPath);
405
+ try {
406
+ const fileContent = await fs.readFile(configFilePath, 'utf-8');
407
+ defaultSettings[key] = JSON.parse(fileContent);
408
+ } catch (e) { defaultSettings[key] = {}; }
409
+ } else {
410
+ try { defaultSettings[key] = JSON.parse(config.default || 'null'); }
411
+ catch { defaultSettings[key] = config.default; }
412
+ }
413
+ }
414
+ }
415
+ const finalSettings = { ...defaultSettings, ...savedSettings };
416
+ res.json(finalSettings);
417
+ } catch (error) {
418
+ console.error("[API Error] /settings GET:", error);
419
+ res.status(500).json({ error: 'Не удалось получить настройки плагина' });
420
+ }
421
+ });
422
+
423
+ router.put('/:botId/plugins/:pluginId', async (req, res) => {
424
+ try {
425
+ const pluginId = parseInt(req.params.pluginId);
426
+ const { isEnabled, settings } = req.body;
427
+ const dataToUpdate = {};
428
+ if (typeof isEnabled === 'boolean') dataToUpdate.isEnabled = isEnabled;
429
+ if (settings) dataToUpdate.settings = JSON.stringify(settings);
430
+ if (Object.keys(dataToUpdate).length === 0) return res.status(400).json({ error: "Нет данных для обновления" });
431
+ const updated = await prisma.installedPlugin.update({ where: { id: pluginId }, data: dataToUpdate });
432
+ res.json(updated);
433
+ } catch (error) { res.status(500).json({ error: 'Не удалось обновить плагин' }); }
434
+ });
435
+
436
+
437
+ router.get('/:botId/management-data', async (req, res) => {
438
+ try {
439
+ const botId = parseInt(req.params.botId);
440
+
441
+ const commandTemplates = commandManager.getCommandTemplates();
442
+ let permissions = await prisma.permission.findMany({ where: { botId } });
443
+
444
+ for (const template of commandTemplates) {
445
+ const exists = await prisma.command.findUnique({
446
+ where: { botId_name: { botId, name: template.name } }
447
+ });
448
+
449
+ if (!exists) {
450
+ let permissionId = null;
451
+ if (template.permissions) {
452
+ let requiredPermission = permissions.find(p => p.name === template.permissions);
453
+ if (!requiredPermission) {
454
+ requiredPermission = await prisma.permission.create({
455
+ data: {
456
+ botId,
457
+ name: template.permissions,
458
+ description: `Авто-создано для команды ${template.name}`,
459
+ owner: template.owner || 'system',
460
+ }
461
+ });
462
+ permissions.push(requiredPermission);
463
+ }
464
+ permissionId = requiredPermission.id;
465
+ }
466
+
467
+ await prisma.command.create({
468
+ data: {
469
+ botId,
470
+ name: template.name,
471
+ isEnabled: template.isActive !== undefined ? template.isActive : true,
472
+ cooldown: template.cooldown || 0,
473
+ aliases: JSON.stringify(template.aliases || []),
474
+ description: template.description,
475
+ owner: template.owner,
476
+ permissionId: permissionId,
477
+ allowedChatTypes: JSON.stringify(template.allowedChatTypes || ['chat', 'private']),
478
+ }
479
+ });
480
+ }
481
+ }
482
+
483
+ const [groups, users, dbCommands, allPermissions] = await Promise.all([
484
+ prisma.group.findMany({ where: { botId }, include: { permissions: { include: { permission: true } } }, orderBy: { name: 'asc' } }),
485
+ prisma.user.findMany({ where: { botId }, include: { groups: { include: { group: true } } }, orderBy: { username: 'asc' } }),
486
+ prisma.command.findMany({ where: { botId }, orderBy: [{ owner: 'asc' }, { name: 'asc' }] }),
487
+ prisma.permission.findMany({ where: { botId }, orderBy: { name: 'asc' } })
488
+ ]);
489
+
490
+ const finalCommands = dbCommands.map(cmd => {
491
+ try {
492
+ return {
493
+ ...cmd,
494
+ aliases: JSON.parse(cmd.aliases || '[]'),
495
+ allowedChatTypes: JSON.parse(cmd.allowedChatTypes || '[]')
496
+ };
497
+ } catch (e) {
498
+ console.error(`Ошибка парсинга JSON для команды ${cmd.name} (ID: ${cmd.id})`, e);
499
+ return {
500
+ ...cmd,
501
+ aliases: [],
502
+ allowedChatTypes: []
503
+ };
504
+ }
505
+ });
506
+
507
+ res.json({ groups, permissions: allPermissions, users, commands: finalCommands });
508
+
509
+ } catch (error) {
510
+ console.error(`[API Error] /management-data for bot ${req.params.botId}:`, error);
511
+ res.status(500).json({ error: 'Не удалось загрузить данные управления' });
512
+ }
513
+ });
514
+
515
+ router.put('/:botId/commands/:commandId', async (req, res) => {
516
+ try {
517
+ const commandId = parseInt(req.params.commandId, 10);
518
+ const botId = parseInt(req.params.botId, 10);
519
+ const { isEnabled, cooldown, aliases, permissionId, allowedChatTypes } = req.body;
520
+
521
+ const dataToUpdate = {};
522
+ if (typeof isEnabled === 'boolean') dataToUpdate.isEnabled = isEnabled;
523
+ if (typeof cooldown === 'number') dataToUpdate.cooldown = cooldown;
524
+ if (Array.isArray(aliases)) dataToUpdate.aliases = JSON.stringify(aliases);
525
+ if (permissionId !== undefined) {
526
+ dataToUpdate.permissionId = permissionId === null ? null : parseInt(permissionId, 10);
527
+ }
528
+ if (Array.isArray(allowedChatTypes)) {
529
+ dataToUpdate.allowedChatTypes = JSON.stringify(allowedChatTypes);
530
+ }
531
+
532
+ if (Object.keys(dataToUpdate).length === 0) {
533
+ return res.status(400).json({ error: "Нет данных для обновления" });
534
+ }
535
+
536
+ const updatedCommand = await prisma.command.update({
537
+ where: { id: commandId, botId: botId },
538
+ data: dataToUpdate,
539
+ });
540
+
541
+ res.json(updatedCommand);
542
+ } catch (error) {
543
+ console.error(`[API Error] /commands/:commandId PUT:`, error);
544
+ res.status(500).json({ error: 'Не удалось обновить команду' });
545
+ }
546
+ });
547
+
548
+ router.post('/:botId/groups', async (req, res) => {
549
+ try {
550
+ const botId = parseInt(req.params.botId);
551
+ const { name, permissionIds } = req.body;
552
+ if (!name) return res.status(400).json({ error: "Имя группы обязательно" });
553
+
554
+ const newGroup = await prisma.group.create({
555
+ data: {
556
+ name,
557
+ botId,
558
+ owner: 'admin',
559
+ permissions: { create: (permissionIds || []).map(id => ({ permissionId: id })) }
560
+ }
561
+ });
562
+ res.status(201).json(newGroup);
563
+ } catch (error) {
564
+ if (error.code === 'P2002') return res.status(409).json({ error: 'Группа с таким именем уже существует для этого бота.' });
565
+ res.status(500).json({ error: 'Не удалось создать группу.' });
566
+ }
567
+ });
568
+
569
+ router.put('/:botId/groups/:groupId', async (req, res) => {
570
+ try {
571
+ const botId = parseInt(req.params.botId, 10);
572
+ const groupId = parseInt(req.params.groupId);
573
+ const { name, permissionIds } = req.body;
574
+ if (!name) return res.status(400).json({ error: "Имя группы обязательно" });
575
+
576
+ const usersInGroup = await prisma.user.findMany({
577
+ where: { botId, groups: { some: { groupId } } },
578
+ select: { username: true }
579
+ });
580
+
581
+ await prisma.$transaction(async (tx) => {
582
+ await tx.group.update({ where: { id: groupId }, data: { name } });
583
+ await tx.groupPermission.deleteMany({ where: { groupId } });
584
+ if (permissionIds && permissionIds.length > 0) {
585
+ await tx.groupPermission.createMany({
586
+ data: permissionIds.map(pid => ({ groupId, permissionId: pid })),
587
+ });
588
+ }
589
+ });
590
+
591
+ for (const user of usersInGroup) {
592
+ BotManager.invalidateUserCache(botId, user.username);
593
+ }
594
+
595
+ res.status(200).send();
596
+ } catch (error) {
597
+ if (error.code === 'P2002') return res.status(409).json({ error: 'Группа с таким именем уже существует для этого бота.' });
598
+ res.status(500).json({ error: 'Не удалось обновить группу.' });
599
+ }
600
+ });
601
+
602
+ router.delete('/:botId/groups/:groupId', async (req, res) => {
603
+ try {
604
+ const groupId = parseInt(req.params.groupId);
605
+ const group = await prisma.group.findUnique({ where: { id: groupId } });
606
+ if (group && group.owner !== 'admin') {
607
+ return res.status(403).json({ error: `Нельзя удалить группу с источником "${group.owner}".` });
608
+ }
609
+ await prisma.group.delete({ where: { id: groupId } });
610
+ res.status(204).send();
611
+ } catch (error) { res.status(500).json({ error: 'Не удалось удалить группу.' }); }
612
+ });
613
+
614
+ router.post('/:botId/permissions', async (req, res) => {
615
+ try {
616
+ const botId = parseInt(req.params.botId);
617
+ const { name, description } = req.body;
618
+ if (!name) return res.status(400).json({ error: 'Имя права обязательно' });
619
+ const newPermission = await prisma.permission.create({
620
+ data: { name, description, botId, owner: 'admin' }
621
+ });
622
+ res.status(201).json(newPermission);
623
+ } catch (error) {
624
+ if (error.code === 'P2002') return res.status(409).json({ error: 'Право с таким именем уже существует для этого бота.' });
625
+ res.status(500).json({ error: 'Не удалось создать право.' });
626
+ }
627
+ });
628
+
629
+ router.put('/:botId/users/:userId', async (req, res) => {
630
+ try {
631
+ const botId = parseInt(req.params.botId, 10);
632
+ const userId = parseInt(req.params.userId, 10);
633
+ const { isBlacklisted, groupIds } = req.body;
634
+
635
+ const updateData = {};
636
+ if (typeof isBlacklisted === 'boolean') {
637
+ updateData.isBlacklisted = isBlacklisted;
638
+ }
639
+
640
+ if (Array.isArray(groupIds)) {
641
+ await prisma.userGroup.deleteMany({ where: { userId } });
642
+ updateData.groups = {
643
+ create: groupIds.map(gid => ({ groupId: gid })),
644
+ };
645
+ }
646
+
647
+ const updatedUser = await prisma.user.update({
648
+ where: { id: userId },
649
+ data: updateData,
650
+ include: { groups: true }
651
+ });
652
+
653
+ BotManager.invalidateUserCache(botId, updatedUser.username);
654
+
655
+ UserService.clearCache(updatedUser.username, botId);
656
+
657
+ res.json(updatedUser);
658
+
659
+ } catch (error) {
660
+ console.error(`[API Error] /users/:userId PUT:`, error);
661
+ res.status(500).json({ error: 'Не удалось обновить пользователя' });
662
+ }
663
+ });
664
+
665
+
666
+ router.post('/start-all', async (req, res) => {
667
+ try {
668
+ console.log('[API] Получен запрос на запуск всех ботов.');
669
+ const allBots = await prisma.bot.findMany({ include: { server: true } });
670
+ let startedCount = 0;
671
+ for (const botConfig of allBots) {
672
+ if (!BotManager.bots.has(botConfig.id)) {
673
+ await BotManager.startBot(botConfig);
674
+ startedCount++;
675
+ }
676
+ }
677
+ res.json({ success: true, message: `Запущено ${startedCount} ботов.` });
678
+ } catch (error) {
679
+ console.error('[API Error] /start-all:', error);
680
+ res.status(500).json({ error: 'Произошла ошибка при массовом запуске ботов.' });
681
+ }
682
+ });
683
+
684
+ router.post('/stop-all', (req, res) => {
685
+ try {
686
+ console.log('[API] Получен запрос на остановку всех ботов.');
687
+ const botIds = Array.from(BotManager.bots.keys());
688
+ let stoppedCount = 0;
689
+ for (const botId of botIds) {
690
+ BotManager.stopBot(botId);
691
+ stoppedCount++;
692
+ }
693
+ res.json({ success: true, message: `Остановлено ${stoppedCount} ботов.` });
694
+ } catch (error) {
695
+ console.error('[API Error] /stop-all:', error);
696
+ res.status(500).json({ error: 'Произошла ошибка при массовой остановке ботов.' });
697
+ }
698
+ });
699
+
700
+
701
+ router.get('/:id/settings/all', async (req, res) => {
702
+ try {
703
+ const botId = parseInt(req.params.id, 10);
704
+
705
+ const bot = await prisma.bot.findUnique({
706
+ where: { id: botId },
707
+ include: {
708
+ server: true,
709
+ installedPlugins: {
710
+ orderBy: { name: 'asc' }
711
+ }
712
+ }
713
+ });
714
+
715
+ if (!bot) {
716
+ return res.status(404).json({ error: 'Бот не найден' });
717
+ }
718
+
719
+ const allSettings = {
720
+ bot: {
721
+ id: bot.id,
722
+ username: bot.username,
723
+ prefix: bot.prefix,
724
+ note: bot.note,
725
+ serverId: bot.serverId,
726
+ proxyHost: bot.proxyHost,
727
+ proxyPort: bot.proxyPort,
728
+ proxyUsername: bot.proxyUsername,
729
+ },
730
+ plugins: []
731
+ };
732
+
733
+ const pluginSettingsPromises = bot.installedPlugins.map(async (plugin) => {
734
+ const manifest = plugin.manifest ? JSON.parse(plugin.manifest) : {};
735
+
736
+ if (!manifest.settings || Object.keys(manifest.settings).length === 0) {
737
+ return null;
738
+ }
739
+
740
+ const savedSettings = plugin.settings ? JSON.parse(plugin.settings) : {};
741
+ let defaultSettings = {};
742
+
743
+ for (const key in manifest.settings) {
744
+ const config = manifest.settings[key];
745
+ if (config.type === 'json_file' && config.defaultPath) {
746
+ const configFilePath = path.join(plugin.path, config.defaultPath);
747
+ try {
748
+ const fileContent = await fs.readFile(configFilePath, 'utf-8');
749
+ defaultSettings[key] = JSON.parse(fileContent);
750
+ } catch (e) { defaultSettings[key] = {}; }
751
+ } else {
752
+ try { defaultSettings[key] = JSON.parse(config.default || 'null'); }
753
+ catch { defaultSettings[key] = config.default; }
754
+ }
755
+ }
756
+
757
+ return {
758
+ id: plugin.id,
759
+ name: plugin.name,
760
+ description: plugin.description,
761
+ isEnabled: plugin.isEnabled,
762
+ manifest: manifest,
763
+ settings: { ...defaultSettings, ...savedSettings }
764
+ };
765
+ });
766
+
767
+ allSettings.plugins = (await Promise.all(pluginSettingsPromises)).filter(Boolean);
768
+
769
+ res.json(allSettings);
770
+
771
+ } catch (error) {
772
+ console.error("[API Error] /settings/all GET:", error);
773
+ res.status(500).json({ error: 'Не удалось загрузить все настройки' });
774
+ }
775
+ });
776
+
777
+ module.exports = router;