blockmine 1.22.0 → 1.23.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.
Files changed (108) hide show
  1. package/.claude/agents/code-architect.md +34 -0
  2. package/.claude/agents/code-explorer.md +51 -0
  3. package/.claude/agents/code-reviewer.md +46 -0
  4. package/.claude/commands/feature-dev.md +125 -0
  5. package/.claude/settings.json +5 -1
  6. package/.claude/settings.local.json +12 -1
  7. package/.claude/skills/frontend-design/SKILL.md +42 -0
  8. package/CHANGELOG.md +32 -1
  9. package/README.md +302 -152
  10. package/backend/package-lock.json +681 -9
  11. package/backend/package.json +8 -0
  12. package/backend/prisma/migrations/20251116111851_add_execution_trace/migration.sql +22 -0
  13. package/backend/prisma/migrations/20251120154914_add_panel_api_keys/migration.sql +21 -0
  14. package/backend/prisma/migrations/20251121110241_add_proxy_table/migration.sql +45 -0
  15. package/backend/prisma/schema.prisma +70 -1
  16. package/backend/src/__tests__/services/BotLifecycleService.test.js +9 -4
  17. package/backend/src/ai/plugin-assistant-system-prompt.md +788 -0
  18. package/backend/src/api/middleware/auth.js +27 -0
  19. package/backend/src/api/middleware/botAccess.js +7 -3
  20. package/backend/src/api/middleware/panelApiAuth.js +135 -0
  21. package/backend/src/api/routes/aiAssistant.js +995 -0
  22. package/backend/src/api/routes/auth.js +90 -54
  23. package/backend/src/api/routes/botCommands.js +107 -0
  24. package/backend/src/api/routes/botGroups.js +165 -0
  25. package/backend/src/api/routes/botHistory.js +108 -0
  26. package/backend/src/api/routes/botPermissions.js +99 -0
  27. package/backend/src/api/routes/botStatus.js +36 -0
  28. package/backend/src/api/routes/botUsers.js +162 -0
  29. package/backend/src/api/routes/bots.js +108 -59
  30. package/backend/src/api/routes/eventGraphs.js +4 -1
  31. package/backend/src/api/routes/logs.js +13 -3
  32. package/backend/src/api/routes/panel.js +3 -3
  33. package/backend/src/api/routes/panelApiKeys.js +179 -0
  34. package/backend/src/api/routes/pluginIde.js +1715 -135
  35. package/backend/src/api/routes/plugins.js +170 -13
  36. package/backend/src/api/routes/proxies.js +130 -0
  37. package/backend/src/api/routes/search.js +4 -0
  38. package/backend/src/api/routes/servers.js +20 -3
  39. package/backend/src/api/routes/settings.js +5 -0
  40. package/backend/src/api/routes/system.js +3 -3
  41. package/backend/src/api/routes/traces.js +131 -0
  42. package/backend/src/config/debug.config.js +36 -0
  43. package/backend/src/core/BotHistoryStore.js +180 -0
  44. package/backend/src/core/BotManager.js +14 -4
  45. package/backend/src/core/BotProcess.js +1517 -1092
  46. package/backend/src/core/EventGraphManager.js +194 -280
  47. package/backend/src/core/GraphExecutionEngine.js +1004 -321
  48. package/backend/src/core/MessageQueue.js +12 -6
  49. package/backend/src/core/PluginLoader.js +99 -5
  50. package/backend/src/core/PluginManager.js +74 -13
  51. package/backend/src/core/TaskScheduler.js +1 -1
  52. package/backend/src/core/commands/whois.js +1 -1
  53. package/backend/src/core/node-registries/actions.js +72 -2
  54. package/backend/src/core/node-registries/arrays.js +18 -0
  55. package/backend/src/core/node-registries/data.js +1 -1
  56. package/backend/src/core/node-registries/events.js +14 -0
  57. package/backend/src/core/node-registries/logic.js +17 -0
  58. package/backend/src/core/node-registries/strings.js +34 -0
  59. package/backend/src/core/node-registries/type.js +25 -0
  60. package/backend/src/core/nodes/actions/bot_look_at.js +1 -1
  61. package/backend/src/core/nodes/actions/create_command.js +189 -0
  62. package/backend/src/core/nodes/actions/delete_command.js +92 -0
  63. package/backend/src/core/nodes/actions/http_request.js +23 -4
  64. package/backend/src/core/nodes/actions/send_message.js +2 -12
  65. package/backend/src/core/nodes/actions/update_command.js +133 -0
  66. package/backend/src/core/nodes/arrays/join.js +28 -0
  67. package/backend/src/core/nodes/data/cast.js +2 -1
  68. package/backend/src/core/nodes/data/string_literal.js +2 -13
  69. package/backend/src/core/nodes/logic/not.js +22 -0
  70. package/backend/src/core/nodes/strings/starts_with.js +1 -1
  71. package/backend/src/core/nodes/strings/to_lower.js +22 -0
  72. package/backend/src/core/nodes/strings/to_upper.js +22 -0
  73. package/backend/src/core/nodes/type/to_string.js +32 -0
  74. package/backend/src/core/services/BotLifecycleService.js +835 -596
  75. package/backend/src/core/services/CommandExecutionService.js +430 -351
  76. package/backend/src/core/services/DebugSessionManager.js +347 -0
  77. package/backend/src/core/services/GraphCollaborationManager.js +501 -0
  78. package/backend/src/core/services/MinecraftBotManager.js +259 -0
  79. package/backend/src/core/services/MinecraftViewerService.js +216 -0
  80. package/backend/src/core/services/TraceCollectorService.js +545 -0
  81. package/backend/src/core/system/RuntimeCommandRegistry.js +116 -0
  82. package/backend/src/core/system/Transport.js +0 -4
  83. package/backend/src/core/validation/nodeSchemas.js +6 -6
  84. package/backend/src/real-time/botApi/handlers/graphHandlers.js +2 -2
  85. package/backend/src/real-time/botApi/handlers/graphWebSocketHandlers.js +1 -1
  86. package/backend/src/real-time/botApi/utils.js +11 -0
  87. package/backend/src/real-time/panelNamespace.js +387 -0
  88. package/backend/src/real-time/presence.js +7 -2
  89. package/backend/src/real-time/socketHandler.js +395 -4
  90. package/backend/src/server.js +18 -0
  91. package/frontend/dist/assets/index-DqzDkFsP.js +11210 -0
  92. package/frontend/dist/assets/index-t6K1u4OV.css +32 -0
  93. package/frontend/dist/index.html +2 -2
  94. package/frontend/package-lock.json +9437 -0
  95. package/frontend/package.json +8 -0
  96. package/package.json +2 -2
  97. package/screen/console.png +0 -0
  98. package/screen/dashboard.png +0 -0
  99. package/screen/graph_collabe.png +0 -0
  100. package/screen/graph_live_debug.png +0 -0
  101. package/screen/management_command.png +0 -0
  102. package/screen/node_debug_trace.png +0 -0
  103. package/screen/plugin_/320/276/320/261/320/267/320/276/321/200.png +0 -0
  104. package/screen/websocket.png +0 -0
  105. package/screen//320/275/320/260/321/201/321/202/321/200/320/276/320/271/320/272/320/270_/320/276/321/202/320/264/320/265/320/273/321/214/320/275/321/213/321/205_/320/272/320/276/320/274/320/260/320/275/320/264_/320/272/320/260/320/266/320/264/321/203_/320/272/320/276/320/274/320/260/320/275/320/273/320/264/321/203_/320/274/320/276/320/266/320/275/320/276_/320/275/320/260/321/201/321/202/321/200/320/260/320/270/320/262/320/260/321/202/321/214.png +0 -0
  106. package/screen//320/277/320/273/320/260/320/275/320/270/321/200/320/276/320/262/321/211/320/270/320/272_/320/274/320/276/320/266/320/275/320/276_/320/267/320/260/320/264/320/260/320/262/320/260/321/202/321/214_/320/264/320/265/320/271/321/201/321/202/320/262/320/270/321/217_/320/277/320/276_/320/262/321/200/320/265/320/274/320/265/320/275/320/270.png +0 -0
  107. package/frontend/dist/assets/index-CfTo92bP.css +0 -1
  108. package/frontend/dist/assets/index-CiFD5X9Z.js +0 -8344
@@ -1,351 +1,430 @@
1
- const { v4: uuidv4 } = require('uuid');
2
- const UserService = require('../UserService');
3
-
4
- // Кулдауны и предупреждения - глобальные для всех инстансов
5
- const cooldowns = new Map();
6
- const warningCache = new Map();
7
- const WARNING_COOLDOWN = 10 * 1000;
8
-
9
- class CommandExecutionService {
10
- constructor({
11
- botProcessManager,
12
- cacheManager,
13
- eventGraphManager,
14
- commandRepository,
15
- permissionRepository,
16
- groupRepository,
17
- logger
18
- }) {
19
- this.processManager = botProcessManager;
20
- this.cache = cacheManager;
21
- this.eventGraphManager = eventGraphManager;
22
- this.commandRepository = commandRepository;
23
- this.permissionRepository = permissionRepository;
24
- this.groupRepository = groupRepository;
25
- this.logger = logger;
26
- }
27
-
28
- async handleCommandValidation(botConfig, message) {
29
- const { commandName, username, args, typeChat } = message;
30
- const botId = botConfig.id;
31
-
32
- try {
33
- // Получаем конфигурацию из кеша или загружаем из БД
34
- const botConfigCache = await this.cache.getOrLoadBotConfig(botId);
35
-
36
- const user = await UserService.getUser(username, botId, botConfig);
37
-
38
- const child = this.processManager.getProcess(botId);
39
- if (!child) return;
40
-
41
- if (user.isBlacklisted) {
42
- child.send({
43
- type: 'handle_blacklist',
44
- commandName,
45
- username,
46
- typeChat
47
- });
48
- return;
49
- }
50
-
51
- const mainCommandName = botConfigCache.commandAliases.get(commandName) || commandName;
52
- const dbCommand = botConfigCache.commands.get(mainCommandName);
53
-
54
- if (!dbCommand || (!dbCommand.isEnabled && !user.isOwner)) {
55
- return;
56
- }
57
-
58
- const allowedTypes = JSON.parse(dbCommand.allowedChatTypes || '[]');
59
- if (!allowedTypes.includes(typeChat) && !user.isOwner) {
60
- if (typeChat === 'global') return;
61
- child.send({
62
- type: 'handle_wrong_chat',
63
- commandName: dbCommand.name,
64
- username,
65
- typeChat
66
- });
67
- return;
68
- }
69
-
70
- const permission = dbCommand.permissionId ? botConfigCache.permissionsById.get(dbCommand.permissionId) : null;
71
- if (permission && !user.hasPermission(permission.name) && !user.isOwner) {
72
- child.send({
73
- type: 'handle_permission_error',
74
- commandName: dbCommand.name,
75
- username,
76
- typeChat
77
- });
78
- return;
79
- }
80
-
81
- const domain = (permission?.name || '').split('.')[0] || 'user';
82
- const bypassCooldownPermission = `${domain}.cooldown.bypass`;
83
-
84
- if (dbCommand.cooldown > 0 && !user.isOwner && !user.hasPermission(bypassCooldownPermission)) {
85
- const cooldownKey = `${botId}:${dbCommand.name}:${user.id}`;
86
- const now = Date.now();
87
- const lastUsed = cooldowns.get(cooldownKey);
88
-
89
- if (lastUsed && (now - lastUsed < dbCommand.cooldown * 1000)) {
90
- const timeLeft = Math.ceil((dbCommand.cooldown * 1000 - (now - lastUsed)) / 1000);
91
- child.send({
92
- type: 'handle_cooldown',
93
- commandName: dbCommand.name,
94
- username,
95
- typeChat,
96
- timeLeft
97
- });
98
- return;
99
- }
100
- cooldowns.set(cooldownKey, now);
101
- }
102
-
103
- if (this.eventGraphManager) {
104
- this.eventGraphManager.handleEvent(botId, 'command', {
105
- commandName: dbCommand.name,
106
- user: { username },
107
- args,
108
- typeChat
109
- });
110
- }
111
-
112
- child.send({ type: 'execute_handler', commandName: dbCommand.name, username, args, typeChat });
113
-
114
- } catch (error) {
115
- this.logger.error({ botId, command: commandName, username, error }, 'Ошибка валидации команды');
116
- this.sendMessageToBot(botId, `Произошла внутренняя ошибка при выполнении команды.`, 'private', username);
117
- }
118
- }
119
-
120
- async validateAndExecuteCommandForApi(botId, username, commandName, args) {
121
- const botConfig = this.processManager.getProcess(botId)?.botConfig;
122
- if (!botConfig) {
123
- throw new Error('Bot configuration not found.');
124
- }
125
-
126
- const typeChat = 'websocket';
127
-
128
- let botConfigCache = this.cache.getBotConfig(botId);
129
- if (!botConfigCache) {
130
- throw new Error('Bot configuration cache not loaded.');
131
- }
132
-
133
- const user = await UserService.getUser(username, botId, botConfig);
134
-
135
- if (user.isBlacklisted) {
136
- throw new Error(`User '${username}' is blacklisted.`);
137
- }
138
-
139
- const mainCommandName = botConfigCache.commandAliases.get(commandName) || commandName;
140
- const dbCommand = botConfigCache.commands.get(mainCommandName);
141
-
142
- if (!dbCommand || (!dbCommand.isEnabled && !user.isOwner)) {
143
- throw new Error(`Command '${commandName}' not found or is disabled.`);
144
- }
145
-
146
- // WebSocket - универсальный транспорт
147
- if (typeChat !== 'websocket') {
148
- const allowedTypes = JSON.parse(dbCommand.allowedChatTypes || '[]');
149
- if (!allowedTypes.includes(typeChat) && !user.isOwner) {
150
- throw new Error(`Command '${commandName}' cannot be used in this chat type.`);
151
- }
152
- }
153
-
154
- const permission = dbCommand.permissionId ? botConfigCache.permissionsById.get(dbCommand.permissionId) : null;
155
- if (permission && !user.hasPermission(permission.name) && !user.isOwner) {
156
- throw new Error(`User '${username}' has insufficient permissions.`);
157
- }
158
-
159
- const domain = (permission?.name || '').split('.')[0] || 'user';
160
- const bypassCooldownPermission = `${domain}.cooldown.bypass`;
161
-
162
- if (dbCommand.cooldown > 0 && !user.isOwner && !user.hasPermission(bypassCooldownPermission)) {
163
- const cooldownKey = `${botId}:${dbCommand.name}:${user.id}`;
164
- const now = Date.now();
165
- const lastUsed = cooldowns.get(cooldownKey);
166
-
167
- if (lastUsed && (now - lastUsed < dbCommand.cooldown * 1000)) {
168
- const timeLeft = Math.ceil((dbCommand.cooldown * 1000 - (now - lastUsed)) / 1000);
169
- throw new Error(`Command on cooldown for user '${username}'. Please wait ${timeLeft} seconds.`);
170
- }
171
- cooldowns.set(cooldownKey, now);
172
- }
173
-
174
- return this._executeCommandInProcess(botId, dbCommand.name, args, user, typeChat);
175
- }
176
-
177
- async _executeCommandInProcess(botId, commandName, args, user, typeChat) {
178
- return new Promise((resolve, reject) => {
179
- const child = this.processManager.getProcess(botId);
180
- if (!child || child.killed) {
181
- return reject(new Error('Bot is not running'));
182
- }
183
-
184
- const requestId = uuidv4();
185
-
186
- // Таймаут на выполнение команды
187
- const timeout = setTimeout(() => {
188
- reject(new Error('Command execution timed out.'));
189
- }, 10000);
190
-
191
- this.processManager.addCommandRequest(requestId, {
192
- resolve: (result) => {
193
- clearTimeout(timeout);
194
- resolve(result);
195
- },
196
- reject: (error) => {
197
- clearTimeout(timeout);
198
- reject(error);
199
- }
200
- });
201
-
202
- child.send({
203
- type: 'execute_command_request',
204
- requestId,
205
- payload: {
206
- commandName,
207
- args: args || {},
208
- username: user.username,
209
- typeChat
210
- }
211
- });
212
- });
213
- }
214
-
215
- sendMessageToBot(botId, message, chatType = 'command', username = null) {
216
- const child = this.processManager.getProcess(botId);
217
- if (child && child.api) {
218
- child.api.sendMessage(chatType, message, username);
219
- return { success: true };
220
- }
221
- return { success: false, message: 'Бот не найден или не запущен' };
222
- }
223
-
224
- async handleCommandRegistration(botId, commandConfig) {
225
- try {
226
- let permissionId = null;
227
-
228
- if (commandConfig.permissions) {
229
- let permission = await this.permissionRepository.findByName(botId, commandConfig.permissions);
230
-
231
- if (!permission) {
232
- permission = await this.permissionRepository.create({
233
- botId,
234
- name: commandConfig.permissions,
235
- description: `Автоматически создано для команды ${commandConfig.name}`,
236
- owner: commandConfig.owner,
237
- });
238
- }
239
- permissionId = permission.id;
240
- }
241
-
242
- const createData = {
243
- botId,
244
- name: commandConfig.name,
245
- description: commandConfig.description,
246
- aliases: JSON.stringify(commandConfig.aliases || []),
247
- owner: commandConfig.owner,
248
- permissionId: permissionId,
249
- allowedChatTypes: JSON.stringify(commandConfig.allowedChatTypes || []),
250
- cooldown: commandConfig.cooldown || 0,
251
- };
252
-
253
- const updateData = {
254
- description: commandConfig.description,
255
- owner: commandConfig.owner,
256
- permissionId: permissionId,
257
- aliases: JSON.stringify(commandConfig.aliases || []),
258
- allowedChatTypes: JSON.stringify(commandConfig.allowedChatTypes || []),
259
- cooldown: commandConfig.cooldown || 0,
260
- };
261
-
262
- const existingCommand = await this.commandRepository.findByName(botId, commandConfig.name);
263
- if (existingCommand) {
264
- await this.commandRepository.update(existingCommand.id, updateData);
265
- } else {
266
- await this.commandRepository.create(createData);
267
- }
268
-
269
- this.cache.deleteBotConfig(botId);
270
- } catch (error) {
271
- this.logger.error({ botId, commandName: commandConfig.name, error }, 'Ошибка регистрации команды');
272
- }
273
- }
274
-
275
- async handleGroupRegistration(botId, groupConfig) {
276
- try {
277
- if (!groupConfig.name || !groupConfig.owner) {
278
- this.logger.warn({ botId, groupConfig }, 'Пропущена группа без имени или владельца');
279
- return;
280
- }
281
-
282
- await this.groupRepository.upsertGroup(botId, groupConfig.name, {
283
- owner: groupConfig.owner,
284
- description: groupConfig.description || ''
285
- });
286
-
287
- this.logger.debug({ botId, groupName: groupConfig.name }, 'Группа зарегистрирована');
288
- this.cache.deleteBotConfig(botId);
289
- } catch (error) {
290
- this.logger.error({ botId, groupName: groupConfig.name, error }, 'Ошибка регистрации группы');
291
- }
292
- }
293
-
294
- async handlePermissionsRegistration(botId, permissions) {
295
- try {
296
- for (const perm of permissions) {
297
- if (!perm.name || !perm.owner) {
298
- this.logger.warn({ botId, perm }, 'Пропущено право без имени или владельца');
299
- continue;
300
- }
301
-
302
- const existing = await this.permissionRepository.findByName(botId, perm.name);
303
- if (existing) {
304
- await this.permissionRepository.update(existing.id, { description: perm.description });
305
- } else {
306
- await this.permissionRepository.create({
307
- botId,
308
- name: perm.name,
309
- description: perm.description || '',
310
- owner: perm.owner,
311
- });
312
- }
313
- }
314
-
315
- this.cache.deleteBotConfig(botId);
316
- } catch (error) {
317
- this.logger.error({ botId, error }, 'Ошибка регистрации прав');
318
- }
319
- }
320
-
321
- async handleAddPermissionsToGroup(botId, message) {
322
- try {
323
- const { groupName, permissionNames } = message;
324
-
325
- // Находим группу
326
- const group = await this.groupRepository.findByName(botId, groupName);
327
- if (!group) {
328
- this.logger.warn({ botId, groupName }, 'Группа не найдена');
329
- return;
330
- }
331
-
332
- // Добавляем каждое право в группу
333
- for (const permName of permissionNames) {
334
- const permission = await this.permissionRepository.findByName(botId, permName);
335
- if (!permission) {
336
- this.logger.warn({ botId, groupName, permName }, 'Право не найдено');
337
- continue;
338
- }
339
-
340
- await this.groupRepository.addPermissionToGroup(group.id, permission.id, this.permissionRepository.prisma);
341
- this.logger.debug({ botId, groupName, permName }, 'Право добавлено в группу');
342
- }
343
-
344
- this.cache.deleteBotConfig(botId);
345
- } catch (error) {
346
- this.logger.error({ botId, groupName: message.groupName, error }, 'Ошибка добавления прав в группу');
347
- }
348
- }
349
- }
350
-
351
- module.exports = CommandExecutionService;
1
+ const { v4: uuidv4 } = require('uuid');
2
+ const UserService = require('../UserService');
3
+ const { getRuntimeCommandRegistry } = require('../system/RuntimeCommandRegistry');
4
+ const botHistoryStore = require('../BotHistoryStore');
5
+
6
+ // Кулдауны и предупреждения - глобальные для всех инстансов
7
+ const cooldowns = new Map();
8
+ const warningCache = new Map();
9
+ const WARNING_COOLDOWN = 10 * 1000;
10
+
11
+ class CommandExecutionService {
12
+ constructor({
13
+ botProcessManager,
14
+ cacheManager,
15
+ eventGraphManager,
16
+ commandRepository,
17
+ permissionRepository,
18
+ groupRepository,
19
+ logger
20
+ }) {
21
+ this.processManager = botProcessManager;
22
+ this.cache = cacheManager;
23
+ this.eventGraphManager = eventGraphManager;
24
+ this.commandRepository = commandRepository;
25
+ this.permissionRepository = permissionRepository;
26
+ this.groupRepository = groupRepository;
27
+ this.logger = logger;
28
+ }
29
+
30
+ async handleCommandValidation(botConfig, message) {
31
+ const { commandName, username, args, typeChat } = message;
32
+ const botId = botConfig.id;
33
+
34
+ try {
35
+ // Получаем конфигурацию из кеша или загружаем из БД
36
+ const botConfigCache = await this.cache.getOrLoadBotConfig(botId);
37
+
38
+ const user = await UserService.getUser(username, botId, botConfig);
39
+
40
+ const child = this.processManager.getProcess(botId);
41
+ if (!child) return;
42
+
43
+ if (user.isBlacklisted) {
44
+ child.send({
45
+ type: 'handle_blacklist',
46
+ commandName,
47
+ username,
48
+ typeChat
49
+ });
50
+ return;
51
+ }
52
+
53
+ const mainCommandName = botConfigCache.commandAliases.get(commandName) || commandName;
54
+ let dbCommand = botConfigCache.commands.get(mainCommandName);
55
+
56
+ // Если команда не найдена в БД, проверяем runtime registry (временные команды)
57
+ if (!dbCommand) {
58
+ const runtimeRegistry = getRuntimeCommandRegistry();
59
+ const tempCommand = runtimeRegistry.get(botId, mainCommandName);
60
+
61
+ if (tempCommand) {
62
+ // Преобразуем временную команду в формат dbCommand
63
+ dbCommand = {
64
+ name: tempCommand.name,
65
+ isEnabled: true,
66
+ allowedChatTypes: JSON.stringify(tempCommand.allowedChatTypes || ['chat', 'private']),
67
+ permissionId: tempCommand.permissionId || null,
68
+ cooldown: tempCommand.cooldown || 0,
69
+ isTemporary: true
70
+ };
71
+ }
72
+ }
73
+
74
+ if (!dbCommand || (!dbCommand.isEnabled && !user.isOwner)) {
75
+ return;
76
+ }
77
+
78
+ const allowedTypes = JSON.parse(dbCommand.allowedChatTypes || '[]');
79
+ if (!allowedTypes.includes(typeChat) && !user.isOwner) {
80
+ // Тип чата не разрешен для обычного пользователя - просто молча игнорируем
81
+ // Никаких сообщений об ошибке не отправляем
82
+ return;
83
+ }
84
+
85
+ // Проверяем required аргументы ПОСЛЕ проверки типа чата
86
+ // чтобы обычные пользователи не видели ошибки в неразрешенных чатах
87
+ if (message.commandArgs) {
88
+ for (const argDef of message.commandArgs) {
89
+ if (argDef.required && (args[argDef.name] === undefined || args[argDef.name] === null)) {
90
+ const usage = message.commandArgs.map(arg => {
91
+ return arg.required ? `<${arg.description || arg.name}>` : `[${arg.description || arg.name}]`;
92
+ }).join(' ');
93
+
94
+ child.send({
95
+ type: 'send_message',
96
+ typeChat,
97
+ message: `Ошибка: Необходимо указать: ${argDef.description || argDef.name}`,
98
+ username
99
+ });
100
+ child.send({
101
+ type: 'send_message',
102
+ typeChat,
103
+ message: `Использование: ${botConfig.prefix || '@'}${dbCommand.name} ${usage}`,
104
+ username
105
+ });
106
+ return;
107
+ }
108
+ }
109
+ }
110
+
111
+ const permission = dbCommand.permissionId ? botConfigCache.permissionsById.get(dbCommand.permissionId) : null;
112
+ if (permission && !user.hasPermission(permission.name) && !user.isOwner) {
113
+ child.send({
114
+ type: 'handle_permission_error',
115
+ commandName: dbCommand.name,
116
+ username,
117
+ typeChat
118
+ });
119
+ return;
120
+ }
121
+
122
+ const domain = (permission?.name || '').split('.')[0] || 'user';
123
+ const bypassCooldownPermission = `${domain}.cooldown.bypass`;
124
+
125
+ if (dbCommand.cooldown > 0 && !user.isOwner && !user.hasPermission(bypassCooldownPermission)) {
126
+ const cooldownKey = `${botId}:${dbCommand.name}:${user.id}`;
127
+ const now = Date.now();
128
+ const lastUsed = cooldowns.get(cooldownKey);
129
+
130
+ if (lastUsed && (now - lastUsed < dbCommand.cooldown * 1000)) {
131
+ const timeLeft = Math.ceil((dbCommand.cooldown * 1000 - (now - lastUsed)) / 1000);
132
+ child.send({
133
+ type: 'handle_cooldown',
134
+ commandName: dbCommand.name,
135
+ username,
136
+ typeChat,
137
+ timeLeft
138
+ });
139
+ return;
140
+ }
141
+ cooldowns.set(cooldownKey, now);
142
+ }
143
+
144
+ if (this.eventGraphManager) {
145
+ this.eventGraphManager.handleEvent(botId, 'command', {
146
+ commandName: dbCommand.name,
147
+ user: { username },
148
+ args,
149
+ typeChat
150
+ });
151
+ }
152
+
153
+ child.send({ type: 'execute_handler', commandName: dbCommand.name, username, args, typeChat });
154
+
155
+ } catch (error) {
156
+ this.logger.error({ botId, command: commandName, username, error }, 'Ошибка валидации команды');
157
+ this.sendMessageToBot(botId, `Произошла внутренняя ошибка при выполнении команды.`, 'private', username);
158
+ }
159
+ }
160
+
161
+ async validateAndExecuteCommandForApi(botId, username, commandName, args) {
162
+ const botConfig = this.processManager.getProcess(botId)?.botConfig;
163
+ if (!botConfig) {
164
+ throw new Error('Bot configuration not found.');
165
+ }
166
+
167
+ const typeChat = 'websocket';
168
+
169
+ let botConfigCache = this.cache.getBotConfig(botId);
170
+ if (!botConfigCache) {
171
+ throw new Error('Bot configuration cache not loaded.');
172
+ }
173
+
174
+ const user = await UserService.getUser(username, botId, botConfig);
175
+
176
+ if (user.isBlacklisted) {
177
+ throw new Error(`User '${username}' is blacklisted.`);
178
+ }
179
+
180
+ const mainCommandName = botConfigCache.commandAliases.get(commandName) || commandName;
181
+ let dbCommand = botConfigCache.commands.get(mainCommandName);
182
+
183
+ // Если команда не найдена в БД, проверяем runtime registry (временные команды)
184
+ if (!dbCommand) {
185
+ const runtimeRegistry = getRuntimeCommandRegistry();
186
+ const tempCommand = runtimeRegistry.get(botId, mainCommandName);
187
+
188
+ if (tempCommand) {
189
+ // Преобразуем временную команду в формат dbCommand
190
+ dbCommand = {
191
+ name: tempCommand.name,
192
+ isEnabled: true,
193
+ allowedChatTypes: JSON.stringify(tempCommand.allowedChatTypes || ['chat', 'private']),
194
+ permissionId: tempCommand.permissionId || null,
195
+ cooldown: tempCommand.cooldown || 0,
196
+ isTemporary: true
197
+ };
198
+ }
199
+ }
200
+
201
+ if (!dbCommand || (!dbCommand.isEnabled && !user.isOwner)) {
202
+ throw new Error(`Command '${commandName}' not found or is disabled.`);
203
+ }
204
+
205
+ // WebSocket - универсальный транспорт
206
+ if (typeChat !== 'websocket') {
207
+ const allowedTypes = JSON.parse(dbCommand.allowedChatTypes || '[]');
208
+ if (!allowedTypes.includes(typeChat) && !user.isOwner) {
209
+ throw new Error(`Command '${commandName}' cannot be used in this chat type.`);
210
+ }
211
+ }
212
+
213
+ const permission = dbCommand.permissionId ? botConfigCache.permissionsById.get(dbCommand.permissionId) : null;
214
+ if (permission && !user.hasPermission(permission.name) && !user.isOwner) {
215
+ throw new Error(`User '${username}' has insufficient permissions.`);
216
+ }
217
+
218
+ const domain = (permission?.name || '').split('.')[0] || 'user';
219
+ const bypassCooldownPermission = `${domain}.cooldown.bypass`;
220
+
221
+ if (dbCommand.cooldown > 0 && !user.isOwner && !user.hasPermission(bypassCooldownPermission)) {
222
+ const cooldownKey = `${botId}:${dbCommand.name}:${user.id}`;
223
+ const now = Date.now();
224
+ const lastUsed = cooldowns.get(cooldownKey);
225
+
226
+ if (lastUsed && (now - lastUsed < dbCommand.cooldown * 1000)) {
227
+ const timeLeft = Math.ceil((dbCommand.cooldown * 1000 - (now - lastUsed)) / 1000);
228
+ throw new Error(`Command on cooldown for user '${username}'. Please wait ${timeLeft} seconds.`);
229
+ }
230
+ cooldowns.set(cooldownKey, now);
231
+ }
232
+
233
+ return this._executeCommandInProcess(botId, dbCommand.name, args, user, typeChat);
234
+ }
235
+
236
+ async _executeCommandInProcess(botId, commandName, args, user, typeChat) {
237
+ return new Promise((resolve, reject) => {
238
+ const child = this.processManager.getProcess(botId);
239
+ if (!child || child.killed) {
240
+ return reject(new Error('Bot is not running'));
241
+ }
242
+
243
+ const requestId = uuidv4();
244
+
245
+ // Таймаут на выполнение команды
246
+ const timeout = setTimeout(() => {
247
+ reject(new Error('Command execution timed out.'));
248
+ }, 10000);
249
+
250
+ this.processManager.addCommandRequest(requestId, {
251
+ resolve: (result) => {
252
+ clearTimeout(timeout);
253
+
254
+ botHistoryStore.addCommandLog(botId, {
255
+ username: user.username,
256
+ command: commandName,
257
+ args: args || {},
258
+ success: true
259
+ });
260
+
261
+ resolve(result);
262
+ },
263
+ reject: (error) => {
264
+ clearTimeout(timeout);
265
+
266
+ botHistoryStore.addCommandLog(botId, {
267
+ username: user.username,
268
+ command: commandName,
269
+ args: args || {},
270
+ success: false,
271
+ error: error.message || String(error)
272
+ });
273
+
274
+ reject(error);
275
+ }
276
+ });
277
+
278
+ child.send({
279
+ type: 'execute_command_request',
280
+ requestId,
281
+ payload: {
282
+ commandName,
283
+ args: args || {},
284
+ username: user.username,
285
+ typeChat
286
+ }
287
+ });
288
+ });
289
+ }
290
+
291
+ sendMessageToBot(botId, message, chatType = 'command', username = null) {
292
+ const child = this.processManager.getProcess(botId);
293
+ if (child && child.api) {
294
+ child.api.sendMessage(chatType, message, username);
295
+ return { success: true };
296
+ }
297
+ return { success: false, message: 'Бот не найден или не запущен' };
298
+ }
299
+
300
+ async handleCommandRegistration(botId, commandConfig) {
301
+ try {
302
+ let permissionId = null;
303
+
304
+ if (commandConfig.permissions) {
305
+ let permission = await this.permissionRepository.findByName(botId, commandConfig.permissions);
306
+
307
+ if (!permission) {
308
+ permission = await this.permissionRepository.create({
309
+ botId,
310
+ name: commandConfig.permissions,
311
+ description: `Автоматически создано для команды ${commandConfig.name}`,
312
+ owner: commandConfig.owner,
313
+ });
314
+ }
315
+ permissionId = permission.id;
316
+ }
317
+
318
+ const createData = {
319
+ botId,
320
+ name: commandConfig.name,
321
+ description: commandConfig.description,
322
+ aliases: JSON.stringify(commandConfig.aliases || []),
323
+ owner: commandConfig.owner,
324
+ permissionId: permissionId,
325
+ allowedChatTypes: JSON.stringify(commandConfig.allowedChatTypes || []),
326
+ cooldown: commandConfig.cooldown || 0,
327
+ };
328
+
329
+ const updateData = {
330
+ description: commandConfig.description,
331
+ owner: commandConfig.owner,
332
+ aliases: JSON.stringify(commandConfig.aliases || []),
333
+ allowedChatTypes: JSON.stringify(commandConfig.allowedChatTypes || []),
334
+ cooldown: commandConfig.cooldown || 0,
335
+ };
336
+
337
+ const existingCommand = await this.commandRepository.findByName(botId, commandConfig.name);
338
+ if (existingCommand) {
339
+ // Обновляем permissionId только если он null (не был установлен пользователем)
340
+ if (existingCommand.permissionId === null && permissionId !== null) {
341
+ updateData.permissionId = permissionId;
342
+ }
343
+ await this.commandRepository.update(existingCommand.id, updateData);
344
+ } else {
345
+ await this.commandRepository.create(createData);
346
+ }
347
+
348
+ this.cache.deleteBotConfig(botId);
349
+ } catch (error) {
350
+ this.logger.error({ botId, commandName: commandConfig.name, error }, 'Ошибка регистрации команды');
351
+ }
352
+ }
353
+
354
+ async handleGroupRegistration(botId, groupConfig) {
355
+ try {
356
+ if (!groupConfig.name || !groupConfig.owner) {
357
+ this.logger.warn({ botId, groupConfig }, 'Пропущена группа без имени или владельца');
358
+ return;
359
+ }
360
+
361
+ await this.groupRepository.upsertGroup(botId, groupConfig.name, {
362
+ owner: groupConfig.owner,
363
+ description: groupConfig.description || ''
364
+ });
365
+
366
+ this.logger.debug({ botId, groupName: groupConfig.name }, 'Группа зарегистрирована');
367
+ this.cache.deleteBotConfig(botId);
368
+ } catch (error) {
369
+ this.logger.error({ botId, groupName: groupConfig.name, error }, 'Ошибка регистрации группы');
370
+ }
371
+ }
372
+
373
+ async handlePermissionsRegistration(botId, permissions) {
374
+ try {
375
+ for (const perm of permissions) {
376
+ if (!perm.name || !perm.owner) {
377
+ this.logger.warn({ botId, perm }, 'Пропущено право без имени или владельца');
378
+ continue;
379
+ }
380
+
381
+ const existing = await this.permissionRepository.findByName(botId, perm.name);
382
+ if (existing) {
383
+ await this.permissionRepository.update(existing.id, { description: perm.description });
384
+ } else {
385
+ await this.permissionRepository.create({
386
+ botId,
387
+ name: perm.name,
388
+ description: perm.description || '',
389
+ owner: perm.owner,
390
+ });
391
+ }
392
+ }
393
+
394
+ this.cache.deleteBotConfig(botId);
395
+ } catch (error) {
396
+ this.logger.error({ botId, error }, 'Ошибка регистрации прав');
397
+ }
398
+ }
399
+
400
+ async handleAddPermissionsToGroup(botId, message) {
401
+ try {
402
+ const { groupName, permissionNames } = message;
403
+
404
+ // Находим группу
405
+ const group = await this.groupRepository.findByName(botId, groupName);
406
+ if (!group) {
407
+ this.logger.warn({ botId, groupName }, 'Группа не найдена');
408
+ return;
409
+ }
410
+
411
+ // Добавляем каждое право в группу
412
+ for (const permName of permissionNames) {
413
+ const permission = await this.permissionRepository.findByName(botId, permName);
414
+ if (!permission) {
415
+ this.logger.warn({ botId, groupName, permName }, 'Право не найдено');
416
+ continue;
417
+ }
418
+
419
+ await this.groupRepository.addPermissionToGroup(group.id, permission.id, this.permissionRepository.prisma);
420
+ this.logger.debug({ botId, groupName, permName }, 'Право добавлено в группу');
421
+ }
422
+
423
+ this.cache.deleteBotConfig(botId);
424
+ } catch (error) {
425
+ this.logger.error({ botId, groupName: message.groupName, error }, 'Ошибка добавления прав в группу');
426
+ }
427
+ }
428
+ }
429
+
430
+ module.exports = CommandExecutionService;