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
@@ -0,0 +1,36 @@
1
+ const express = require('express');
2
+ const { authenticateUniversal, authorize } = require('../middleware/auth');
3
+ const { botManager } = require('../../core/services');
4
+
5
+ const router = express.Router();
6
+
7
+ router.get('/:id/status', authenticateUniversal, authorize('bot:list'), async (req, res) => {
8
+ try {
9
+ const botId = parseInt(req.params.id, 10);
10
+
11
+ const botProcess = botManager.bots.get(botId);
12
+
13
+ if (!botProcess) {
14
+ return res.json({
15
+ online: false,
16
+ connected: false,
17
+ status: 'offline'
18
+ });
19
+ }
20
+
21
+ const status = botProcess.status || 'unknown';
22
+ const isOnline = status === 'running' || status === 'online';
23
+ const isConnected = botProcess.bot?.isConnected?.() || false;
24
+
25
+ res.json({
26
+ online: isOnline,
27
+ connected: isConnected,
28
+ status: status
29
+ });
30
+ } catch (error) {
31
+ console.error('[API Error] GET /bots/:id/status:', error);
32
+ res.status(500).json({ error: 'Не удалось получить статус бота' });
33
+ }
34
+ });
35
+
36
+ module.exports = router;
@@ -0,0 +1,162 @@
1
+ const express = require('express');
2
+ const prisma = require('../../lib/prisma');
3
+ const { authenticateUniversal, authorize } = require('../middleware/auth');
4
+ const { botManager } = require('../../core/services');
5
+ const UserService = require('../../core/UserService');
6
+
7
+ const router = express.Router();
8
+
9
+ router.get('/:botId/users/:username', authenticateUniversal, authorize('management:view'), async (req, res) => {
10
+ console.log('[botUsers] GET /:botId/users/:username called', req.params);
11
+ try {
12
+ const botId = parseInt(req.params.botId, 10);
13
+ const { username } = req.params;
14
+
15
+ const user = await prisma.user.findFirst({
16
+ where: { botId, username },
17
+ include: { groups: { include: { group: true } } }
18
+ });
19
+
20
+ if (!user) {
21
+ return res.status(404).json({ error: 'Пользователь не найден' });
22
+ }
23
+
24
+ res.json(user);
25
+ } catch (error) {
26
+ console.error('[API Error] GET /bots/:botId/users/:username:', error);
27
+ res.status(500).json({ error: 'Не удалось получить пользователя' });
28
+ }
29
+ });
30
+
31
+ router.get('/:botId/users', authenticateUniversal, authorize('management:view'), async (req, res) => {
32
+ try {
33
+ const botId = parseInt(req.params.botId, 10);
34
+ const { page = 1, pageSize = 100, search } = req.query;
35
+
36
+ const skip = (parseInt(page) - 1) * parseInt(pageSize);
37
+ const take = parseInt(pageSize);
38
+
39
+ const where = { botId };
40
+ if (search) {
41
+ where.username = { contains: search };
42
+ }
43
+
44
+ const [users, total] = await Promise.all([
45
+ prisma.user.findMany({
46
+ where,
47
+ include: { groups: { include: { group: true } } },
48
+ orderBy: { username: 'asc' },
49
+ skip,
50
+ take
51
+ }),
52
+ prisma.user.count({ where })
53
+ ]);
54
+
55
+ res.json({
56
+ items: users,
57
+ total,
58
+ page: parseInt(page),
59
+ pageSize: take
60
+ });
61
+ } catch (error) {
62
+ console.error('[API Error] GET /bots/:botId/users:', error);
63
+ res.status(500).json({ error: 'Не удалось получить список пользователей' });
64
+ }
65
+ });
66
+
67
+ router.put('/:botId/users/:username', authenticateUniversal, authorize('management:edit'), async (req, res) => {
68
+ try {
69
+ const botId = parseInt(req.params.botId, 10);
70
+ const { username } = req.params;
71
+ const { isBlacklisted, groupIds } = req.body;
72
+
73
+ let user = await prisma.user.findFirst({
74
+ where: { botId, username }
75
+ });
76
+
77
+ if (!user) {
78
+ user = await prisma.user.create({
79
+ data: {
80
+ botId,
81
+ username,
82
+ isBlacklisted: isBlacklisted || false
83
+ }
84
+ });
85
+ }
86
+
87
+ const updateData = {};
88
+ if (typeof isBlacklisted === 'boolean') {
89
+ updateData.isBlacklisted = isBlacklisted;
90
+ }
91
+
92
+ if (Array.isArray(groupIds)) {
93
+ await prisma.userGroup.deleteMany({ where: { userId: user.id } });
94
+ updateData.groups = {
95
+ create: groupIds.map(gid => ({ groupId: gid }))
96
+ };
97
+ }
98
+
99
+ const updatedUser = await prisma.user.update({
100
+ where: { id: user.id },
101
+ data: updateData,
102
+ include: { groups: { include: { group: true } } }
103
+ });
104
+
105
+ botManager.invalidateUserCache(botId, username);
106
+ UserService.clearCache(username, botId);
107
+
108
+ res.json(updatedUser);
109
+ } catch (error) {
110
+ console.error('[API Error] PUT /bots/:botId/users/:username:', error);
111
+ res.status(500).json({ error: 'Не удалось обновить пользователя' });
112
+ }
113
+ });
114
+
115
+ router.delete('/:botId/users/:username', authenticateUniversal, authorize('management:edit'), async (req, res) => {
116
+ try {
117
+ const botId = parseInt(req.params.botId, 10);
118
+ const { username } = req.params;
119
+
120
+ const user = await prisma.user.findFirst({
121
+ where: { botId, username }
122
+ });
123
+
124
+ if (!user) {
125
+ return res.status(404).json({ error: 'Пользователь не найден' });
126
+ }
127
+
128
+ await prisma.userGroup.deleteMany({ where: { userId: user.id } });
129
+ await prisma.user.delete({ where: { id: user.id } });
130
+
131
+ botManager.invalidateUserCache(botId, username);
132
+ UserService.clearCache(username, botId);
133
+
134
+ res.json({ success: true });
135
+ } catch (error) {
136
+ console.error('[API Error] DELETE /bots/:botId/users/:username:', error);
137
+ res.status(500).json({ error: 'Не удалось удалить пользователя' });
138
+ }
139
+ });
140
+
141
+ router.get('/:botId/users/:username/groups', authenticateUniversal, authorize('management:view'), async (req, res) => {
142
+ try {
143
+ const botId = parseInt(req.params.botId, 10);
144
+ const { username } = req.params;
145
+
146
+ const user = await prisma.user.findFirst({
147
+ where: { botId, username },
148
+ include: { groups: true }
149
+ });
150
+
151
+ if (!user) {
152
+ return res.status(404).json({ error: 'Пользователь не найден' });
153
+ }
154
+
155
+ res.json({ groupIds: user.groups.map(g => g.groupId) });
156
+ } catch (error) {
157
+ console.error('[API Error] GET /bots/:botId/users/:username/groups:', error);
158
+ res.status(500).json({ error: 'Не удалось получить группы пользователя' });
159
+ }
160
+ });
161
+
162
+ module.exports = router;
@@ -7,7 +7,7 @@ const { botManager, pluginManager } = require('../../core/services');
7
7
  const UserService = require('../../core/UserService');
8
8
  const commandManager = require('../../core/system/CommandManager');
9
9
  const NodeRegistry = require('../../core/NodeRegistry');
10
- const { authenticate, authorize } = require('../middleware/auth');
10
+ const { authenticate, authenticateUniversal, authorize } = require('../middleware/auth');
11
11
  const { encrypt } = require('../../core/utils/crypto');
12
12
  const { randomUUID } = require('crypto');
13
13
  const eventGraphsRouter = require('./eventGraphs');
@@ -26,15 +26,13 @@ const upload = multer({ storage: multer.memoryStorage() });
26
26
 
27
27
  const router = express.Router();
28
28
 
29
- router.use('/:botId(\\d+)/*', authenticate, (req, res, next) => checkBotAccess(req, res, next));
30
-
31
29
  const conditionalRestartAuth = (req, res, next) => {
32
30
  if (process.env.DEBUG === 'true' || process.env.NODE_ENV === 'development') {
33
31
  console.log('[Debug] Роут перезапуска бота доступен без проверки прав');
34
32
  return next();
35
33
  }
36
-
37
- return authenticate(req, res, (err) => {
34
+
35
+ return authenticateUniversal(req, res, (err) => {
38
36
  if (err) return next(err);
39
37
  return authorize('bot:start_stop')(req, res, next);
40
38
  });
@@ -45,8 +43,8 @@ const conditionalChatAuth = (req, res, next) => {
45
43
  console.log('[Debug] Роут отправки сообщения боту доступен без проверки прав');
46
44
  return next();
47
45
  }
48
-
49
- return authenticate(req, res, (err) => {
46
+
47
+ return authenticateUniversal(req, res, (err) => {
50
48
  if (err) return next(err);
51
49
  return authorize('bot:interact')(req, res, next);
52
50
  });
@@ -57,29 +55,33 @@ const conditionalStartStopAuth = (req, res, next) => {
57
55
  console.log('[Debug] Роут запуска/остановки бота доступен без проверки прав');
58
56
  return next();
59
57
  }
60
-
61
- return authenticate(req, res, (err) => {
58
+
59
+ return authenticateUniversal(req, res, (err) => {
62
60
  if (err) return next(err);
63
61
  return authorize('bot:start_stop')(req, res, next);
64
62
  });
65
63
  };
66
64
 
67
65
  const conditionalListAuth = (req, res, next) => {
68
- return authenticate(req, res, (err) => {
66
+ console.log('[conditionalListAuth] START, env:', process.env.NODE_ENV, 'debug:', process.env.DEBUG);
67
+ return authenticateUniversal(req, res, (err) => {
68
+ console.log('[conditionalListAuth] After auth, err:', err, 'user:', req.user?.username);
69
69
  if (err) return next(err);
70
70
  if (process.env.DEBUG === 'true' || process.env.NODE_ENV === 'development') {
71
+ console.log('[conditionalListAuth] DEBUG mode, skipping authorize');
71
72
  return next();
72
73
  }
74
+ console.log('[conditionalListAuth] Calling authorize(bot:list)');
73
75
  return authorize('bot:list')(req, res, next);
74
76
  });
75
77
  };
76
78
 
77
- router.post('/:id/restart', conditionalRestartAuth, authenticate, checkBotAccess, async (req, res) => {
79
+ router.post('/:id/restart', conditionalRestartAuth, authenticateUniversal, checkBotAccess, async (req, res) => {
78
80
  try {
79
81
  const botId = parseInt(req.params.id, 10);
80
82
  botManager.stopBot(botId);
81
83
  setTimeout(async () => {
82
- const botConfig = await prisma.bot.findUnique({ where: { id: botId }, include: { server: true } });
84
+ const botConfig = await prisma.bot.findUnique({ where: { id: botId }, include: { server: true, proxy: true } });
83
85
  if (!botConfig) {
84
86
  return res.status(404).json({ success: false, message: 'Бот не найден' });
85
87
  }
@@ -92,7 +94,7 @@ router.post('/:id/restart', conditionalRestartAuth, authenticate, checkBotAccess
92
94
  }
93
95
  });
94
96
 
95
- router.post('/:id/chat', conditionalChatAuth, authenticate, checkBotAccess, (req, res) => {
97
+ router.post('/:id/chat', conditionalChatAuth, authenticateUniversal, checkBotAccess, (req, res) => {
96
98
  try {
97
99
  const botId = parseInt(req.params.id, 10);
98
100
  const { message } = req.body;
@@ -103,10 +105,10 @@ router.post('/:id/chat', conditionalChatAuth, authenticate, checkBotAccess, (req
103
105
  } catch (error) { res.status(500).json({ error: 'Внутренняя ошибка сервера: ' + error.message }); }
104
106
  });
105
107
 
106
- router.post('/:id/start', conditionalStartStopAuth, authenticate, checkBotAccess, async (req, res) => {
108
+ router.post('/:id/start', conditionalStartStopAuth, authenticateUniversal, checkBotAccess, async (req, res) => {
107
109
  try {
108
110
  const botId = parseInt(req.params.id, 10);
109
- const botConfig = await prisma.bot.findUnique({ where: { id: botId }, include: { server: true } });
111
+ const botConfig = await prisma.bot.findUnique({ where: { id: botId }, include: { server: true, proxy: true } });
110
112
  if (!botConfig) {
111
113
  return res.status(404).json({ success: false, message: 'Бот не найден' });
112
114
  }
@@ -118,7 +120,7 @@ router.post('/:id/start', conditionalStartStopAuth, authenticate, checkBotAccess
118
120
  }
119
121
  });
120
122
 
121
- router.post('/:id/stop', conditionalStartStopAuth, authenticate, checkBotAccess, (req, res) => {
123
+ router.post('/:id/stop', conditionalStartStopAuth, authenticateUniversal, checkBotAccess, (req, res) => {
122
124
  try {
123
125
  const botId = parseInt(req.params.id, 10);
124
126
  botManager.stopBot(botId);
@@ -129,7 +131,15 @@ router.post('/:id/stop', conditionalStartStopAuth, authenticate, checkBotAccess,
129
131
  }
130
132
  });
131
133
 
134
+ router.get('/state', conditionalListAuth, (req, res) => {
135
+ try {
136
+ const state = botManager.getFullState();
137
+ res.json(state);
138
+ } catch (error) { res.status(500).json({ error: 'Не удалось получить состояние ботов' }); }
139
+ });
140
+
132
141
  router.get('/', conditionalListAuth, async (req, res) => {
142
+ console.log('[API /api/bots GET] Запрос получен, user:', req.user?.username, 'permissions:', req.user?.permissions?.slice(0, 3));
133
143
  try {
134
144
  const botsWithoutSortOrder = await prisma.bot.findMany({
135
145
  where: { sortOrder: null },
@@ -167,26 +177,40 @@ router.get('/', conditionalListAuth, async (req, res) => {
167
177
  }
168
178
  }
169
179
 
170
- const bots = await prisma.bot.findMany({
180
+ const bots = await prisma.bot.findMany({
171
181
  where: whereFilter,
172
- include: { server: true },
173
- orderBy: { sortOrder: 'asc' }
182
+ include: { server: true },
183
+ orderBy: { sortOrder: 'asc' }
174
184
  });
185
+ console.log(`[API /api/bots GET] Отправляем ${bots.length} ботов, status: 200`);
175
186
  res.json(bots);
176
- } catch (error) {
187
+ } catch (error) {
177
188
  console.error("[API /api/bots] Ошибка получения списка ботов:", error);
178
- res.status(500).json({ error: 'Не удалось получить список ботов' });
189
+ res.status(500).json({ error: 'Не удалось получить список ботов' });
179
190
  }
180
191
  });
181
192
 
182
- router.get('/state', conditionalListAuth, (req, res) => {
193
+ router.get('/:id', conditionalListAuth, async (req, res) => {
183
194
  try {
184
- const state = botManager.getFullState();
185
- res.json(state);
186
- } catch (error) { res.status(500).json({ error: 'Не удалось получить состояние ботов' }); }
195
+ const botId = parseInt(req.params.id, 10);
196
+
197
+ const bot = await prisma.bot.findUnique({
198
+ where: { id: botId },
199
+ include: { server: true, proxy: true }
200
+ });
201
+
202
+ if (!bot) {
203
+ return res.status(404).json({ error: 'Бот не найден' });
204
+ }
205
+
206
+ res.json(bot);
207
+ } catch (error) {
208
+ console.error(`[API /api/bots/:id] Ошибка получения бота:`, error);
209
+ res.status(500).json({ error: 'Не удалось получить бота' });
210
+ }
187
211
  });
188
212
 
189
- router.put('/bulk-proxy-update', authenticate, authorize('bot:update'), async (req, res) => {
213
+ router.put('/bulk-proxy-update', authenticateUniversal, authorize('bot:update'), async (req, res) => {
190
214
  try {
191
215
  const { botIds, proxySettings } = req.body;
192
216
 
@@ -298,7 +322,7 @@ router.put('/bulk-proxy-update', authenticate, authorize('bot:update'), async (r
298
322
  }
299
323
  });
300
324
 
301
- router.get('/:id/logs', conditionalListAuth, authenticate, checkBotAccess, (req, res) => {
325
+ router.get('/:id/logs', conditionalListAuth, authenticateUniversal, checkBotAccess, (req, res) => {
302
326
  try {
303
327
  const botId = parseInt(req.params.id, 10);
304
328
  const { limit = 50, offset = 0 } = req.query;
@@ -372,26 +396,31 @@ async function setupDefaultPermissionsForBot(botId, prismaClient = prisma) {
372
396
 
373
397
  router.post('/', authorize('bot:create'), async (req, res) => {
374
398
  try {
375
- const { username, password, prefix, serverId, note } = req.body;
399
+ const { username, password, prefix, serverId, note, proxyId, proxyHost, proxyPort, proxyUsername, proxyPassword } = req.body;
376
400
  if (!username || !serverId) return res.status(400).json({ error: 'Имя и сервер обязательны' });
377
-
401
+
378
402
  const maxSortOrder = await prisma.bot.aggregate({
379
403
  _max: { sortOrder: true }
380
404
  });
381
405
  const nextSortOrder = (maxSortOrder._max.sortOrder || 0) + 1;
382
-
383
- const data = {
384
- username,
385
- prefix,
386
- note,
406
+
407
+ const data = {
408
+ username,
409
+ prefix,
410
+ note,
387
411
  serverId: parseInt(serverId, 10),
388
412
  password: password ? encrypt(password) : null,
413
+ proxyId: proxyId ? parseInt(proxyId, 10) : null,
414
+ proxyHost: proxyHost || null,
415
+ proxyPort: proxyPort ? parseInt(proxyPort, 10) : null,
416
+ proxyUsername: proxyUsername || null,
417
+ proxyPassword: proxyPassword ? encrypt(proxyPassword) : null,
389
418
  sortOrder: nextSortOrder
390
419
  };
391
420
 
392
421
  const newBot = await prisma.bot.create({
393
422
  data: data,
394
- include: { server: true }
423
+ include: { server: true, proxy: true }
395
424
  });
396
425
  await setupDefaultPermissionsForBot(newBot.id);
397
426
  res.status(201).json(newBot);
@@ -402,11 +431,11 @@ router.post('/', authorize('bot:create'), async (req, res) => {
402
431
  }
403
432
  });
404
433
 
405
- router.put('/:id', authenticate, checkBotAccess, authorize('bot:update'), async (req, res) => {
434
+ router.put('/:id', authenticateUniversal, checkBotAccess, authorize('bot:update'), async (req, res) => {
406
435
  try {
407
- const {
436
+ const {
408
437
  username, password, prefix, serverId, note, owners,
409
- proxyHost, proxyPort, proxyUsername, proxyPassword
438
+ proxyId, proxyHost, proxyPort, proxyUsername, proxyPassword
410
439
  } = req.body;
411
440
 
412
441
  let dataToUpdate = {
@@ -414,6 +443,7 @@ router.put('/:id', authenticate, checkBotAccess, authorize('bot:update'), async
414
443
  prefix,
415
444
  note,
416
445
  owners,
446
+ proxyId: proxyId ? parseInt(proxyId, 10) : null,
417
447
  proxyHost,
418
448
  proxyPort: proxyPort ? parseInt(proxyPort, 10) : null,
419
449
  proxyUsername,
@@ -441,7 +471,17 @@ router.put('/:id', authenticate, checkBotAccess, authorize('bot:update'), async
441
471
  delete dataToUpdate.serverId;
442
472
  dataToUpdate.server = { connect: { id: serverIdValue } };
443
473
  }
444
-
474
+
475
+ if (dataToUpdate.proxyId !== undefined) {
476
+ const proxyIdValue = dataToUpdate.proxyId;
477
+ delete dataToUpdate.proxyId;
478
+ if (proxyIdValue) {
479
+ dataToUpdate.proxy = { connect: { id: proxyIdValue } };
480
+ } else {
481
+ dataToUpdate.proxy = { disconnect: true };
482
+ }
483
+ }
484
+
445
485
  const botId = parseInt(req.params.id, 10);
446
486
  if (isNaN(botId)) {
447
487
  return res.status(400).json({ message: 'Неверный ID бота.' });
@@ -465,9 +505,16 @@ router.put('/:id', authenticate, checkBotAccess, authorize('bot:update'), async
465
505
  const updatedBot = await prisma.bot.update({
466
506
  where: { id: botId },
467
507
  data: dataToUpdate,
468
- include: { server: true }
508
+ include: { server: true, proxy: true }
469
509
  });
470
510
 
511
+ botManager.reloadBotConfigInRealTime(botId);
512
+
513
+ // Отложенная очистка кеша пользователей, чтобы BotProcess успел обновить config
514
+ setTimeout(() => {
515
+ botManager.invalidateAllUserCache(botId);
516
+ }, 500);
517
+
471
518
  res.json(updatedBot);
472
519
  } catch (error) {
473
520
  console.error("[API Error] /bots PUT:", error);
@@ -475,7 +522,7 @@ router.put('/:id', authenticate, checkBotAccess, authorize('bot:update'), async
475
522
  }
476
523
  });
477
524
 
478
- router.put('/:id/sort-order', authenticate, checkBotAccess, authorize('bot:update'), async (req, res) => {
525
+ router.put('/:id/sort-order', authenticateUniversal, checkBotAccess, authorize('bot:update'), async (req, res) => {
479
526
  try {
480
527
  const { newPosition, oldIndex, newIndex } = req.body;
481
528
  const botId = parseInt(req.params.id, 10);
@@ -534,7 +581,7 @@ router.put('/:id/sort-order', authenticate, checkBotAccess, authorize('bot:updat
534
581
  }
535
582
  });
536
583
 
537
- router.delete('/:id', authenticate, checkBotAccess, authorize('bot:delete'), async (req, res) => {
584
+ router.delete('/:id', authenticateUniversal, checkBotAccess, authorize('bot:delete'), async (req, res) => {
538
585
  try {
539
586
  const botId = parseInt(req.params.id, 10);
540
587
  if (botManager.bots.has(botId)) return res.status(400).json({ error: 'Нельзя удалить запущенного бота' });
@@ -553,7 +600,7 @@ router.get('/servers', authorize('bot:list'), async (req, res) => {
553
600
  }
554
601
  });
555
602
 
556
- router.get('/:botId/plugins', authenticate, checkBotAccess, authorize('plugin:list'), async (req, res) => {
603
+ router.get('/:botId/plugins', authenticateUniversal, checkBotAccess, authorize('plugin:list'), async (req, res) => {
557
604
  try {
558
605
  const botId = parseInt(req.params.botId);
559
606
  const plugins = await prisma.installedPlugin.findMany({ where: { botId } });
@@ -561,7 +608,7 @@ router.get('/:botId/plugins', authenticate, checkBotAccess, authorize('plugin:li
561
608
  } catch (error) { res.status(500).json({ error: 'Не удалось получить плагины бота' }); }
562
609
  });
563
610
 
564
- router.post('/:botId/plugins/install/github', authenticate, checkBotAccess, authorize('plugin:install'), async (req, res) => {
611
+ router.post('/:botId/plugins/install/github', authenticateUniversal, checkBotAccess, authorize('plugin:install'), async (req, res) => {
565
612
  const { botId } = req.params;
566
613
  const { repoUrl } = req.body;
567
614
  try {
@@ -572,7 +619,7 @@ router.post('/:botId/plugins/install/github', authenticate, checkBotAccess, auth
572
619
  }
573
620
  });
574
621
 
575
- router.post('/:botId/plugins/install/local', authenticate, checkBotAccess, authorize('plugin:install'), async (req, res) => {
622
+ router.post('/:botId/plugins/install/local', authenticateUniversal, checkBotAccess, authorize('plugin:install'), async (req, res) => {
576
623
  const { botId } = req.params;
577
624
  const { path } = req.body;
578
625
  try {
@@ -583,7 +630,7 @@ router.post('/:botId/plugins/install/local', authenticate, checkBotAccess, autho
583
630
  }
584
631
  });
585
632
 
586
- router.delete('/:botId/plugins/:pluginId', authenticate, checkBotAccess, authorize('plugin:delete'), async (req, res) => {
633
+ router.delete('/:botId/plugins/:pluginId', authenticateUniversal, checkBotAccess, authorize('plugin:delete'), async (req, res) => {
587
634
  const { pluginId } = req.params;
588
635
  try {
589
636
  await pluginManager.deletePlugin(parseInt(pluginId));
@@ -593,7 +640,7 @@ router.delete('/:botId/plugins/:pluginId', authenticate, checkBotAccess, authori
593
640
  }
594
641
  });
595
642
 
596
- router.get('/:botId/plugins/:pluginId/settings', authenticate, checkBotAccess, authorize('plugin:settings:view'), async (req, res) => {
643
+ router.get('/:botId/plugins/:pluginId/settings', authenticateUniversal, checkBotAccess, authorize('plugin:settings:view'), async (req, res) => {
597
644
  try {
598
645
  const pluginId = parseInt(req.params.pluginId);
599
646
  const plugin = await prisma.installedPlugin.findUnique({ where: { id: pluginId } });
@@ -654,7 +701,7 @@ router.get('/:botId/plugins/:pluginId/settings', authenticate, checkBotAccess, a
654
701
  }
655
702
  });
656
703
 
657
- router.get('/:botId/plugins/:pluginId/data', authenticate, checkBotAccess, authorize('plugin:settings:view'), async (req, res) => {
704
+ router.get('/:botId/plugins/:pluginId/data', authenticateUniversal, checkBotAccess, authorize('plugin:settings:view'), async (req, res) => {
658
705
  try {
659
706
  const pluginId = parseInt(req.params.pluginId);
660
707
  const plugin = await prisma.installedPlugin.findUnique({ where: { id: pluginId } });
@@ -674,7 +721,7 @@ router.get('/:botId/plugins/:pluginId/data', authenticate, checkBotAccess, autho
674
721
  } catch (error) { res.status(500).json({ error: 'Не удалось получить данные плагина' }); }
675
722
  });
676
723
 
677
- router.get('/:botId/plugins/:pluginId/data/:key', authenticate, checkBotAccess, authorize('plugin:settings:view'), async (req, res) => {
724
+ router.get('/:botId/plugins/:pluginId/data/:key', authenticateUniversal, checkBotAccess, authorize('plugin:settings:view'), async (req, res) => {
678
725
  try {
679
726
  const pluginId = parseInt(req.params.pluginId);
680
727
  const { key } = req.params;
@@ -696,7 +743,7 @@ router.get('/:botId/plugins/:pluginId/data/:key', authenticate, checkBotAccess,
696
743
  } catch (error) { res.status(500).json({ error: 'Не удалось получить значение по ключу' }); }
697
744
  });
698
745
 
699
- router.put('/:botId/plugins/:pluginId/data/:key', authenticate, checkBotAccess, authorize('plugin:settings:edit'), async (req, res) => {
746
+ router.put('/:botId/plugins/:pluginId/data/:key', authenticateUniversal, checkBotAccess, authorize('plugin:settings:edit'), async (req, res) => {
700
747
  try {
701
748
  const pluginId = parseInt(req.params.pluginId);
702
749
  const { key } = req.params;
@@ -721,7 +768,7 @@ router.put('/:botId/plugins/:pluginId/data/:key', authenticate, checkBotAccess,
721
768
  } catch (error) { res.status(500).json({ error: 'Не удалось сохранить значение' }); }
722
769
  });
723
770
 
724
- router.delete('/:botId/plugins/:pluginId/data/:key', authenticate, checkBotAccess, authorize('plugin:settings:edit'), async (req, res) => {
771
+ router.delete('/:botId/plugins/:pluginId/data/:key', authenticateUniversal, checkBotAccess, authorize('plugin:settings:edit'), async (req, res) => {
725
772
  try {
726
773
  const pluginId = parseInt(req.params.pluginId);
727
774
  const { key } = req.params;
@@ -741,7 +788,7 @@ router.delete('/:botId/plugins/:pluginId/data/:key', authenticate, checkBotAcces
741
788
  } catch (error) { res.status(500).json({ error: 'Не удалось удалить значение' }); }
742
789
  });
743
790
 
744
- router.put('/:botId/plugins/:pluginId', authenticate, checkBotAccess, authorize('plugin:settings:edit'), async (req, res) => {
791
+ router.put('/:botId/plugins/:pluginId', authenticateUniversal, checkBotAccess, authorize('plugin:settings:edit'), async (req, res) => {
745
792
  try {
746
793
  const pluginId = parseInt(req.params.pluginId);
747
794
  const { isEnabled, settings } = req.body;
@@ -782,7 +829,7 @@ router.put('/:botId/plugins/:pluginId', authenticate, checkBotAccess, authorize(
782
829
  }
783
830
  });
784
831
 
785
- router.get('/:botId/management-data', authenticate, checkBotAccess, authorize('management:view'), async (req, res) => {
832
+ router.get('/:botId/management-data', authenticateUniversal, checkBotAccess, authorize('management:view'), async (req, res) => {
786
833
  try {
787
834
  const botId = parseInt(req.params.botId, 10);
788
835
  if (isNaN(botId)) return res.status(400).json({ error: 'Неверный ID бота' });
@@ -1114,7 +1161,7 @@ router.put('/:botId/users/:userId', authorize('management:edit'), async (req, re
1114
1161
  router.post('/start-all', authorize('bot:start_stop'), async (req, res) => {
1115
1162
  try {
1116
1163
  console.log('[API] Получен запрос на запуск всех ботов.');
1117
- const allBots = await prisma.bot.findMany({ include: { server: true } });
1164
+ const allBots = await prisma.bot.findMany({ include: { server: true, proxy: true } });
1118
1165
  let startedCount = 0;
1119
1166
  for (const botConfig of allBots) {
1120
1167
  if (!botManager.bots.has(botConfig.id)) {
@@ -1145,7 +1192,7 @@ router.post('/stop-all', authorize('bot:start_stop'), (req, res) => {
1145
1192
  }
1146
1193
  });
1147
1194
 
1148
- router.get('/:id/settings/all', authenticate, checkBotAccess, authorize('bot:update'), async (req, res) => {
1195
+ router.get('/:id/settings/all', authenticateUniversal, checkBotAccess, authorize('bot:update'), async (req, res) => {
1149
1196
  try {
1150
1197
  const botId = parseInt(req.params.id, 10);
1151
1198
 
@@ -1153,6 +1200,7 @@ router.get('/:id/settings/all', authenticate, checkBotAccess, authorize('bot:upd
1153
1200
  where: { id: botId },
1154
1201
  include: {
1155
1202
  server: true,
1203
+ proxy: true,
1156
1204
  installedPlugins: {
1157
1205
  orderBy: { name: 'asc' }
1158
1206
  }
@@ -1171,6 +1219,7 @@ router.get('/:id/settings/all', authenticate, checkBotAccess, authorize('bot:upd
1171
1219
  note: bot.note,
1172
1220
  owners: bot.owners,
1173
1221
  serverId: bot.serverId,
1222
+ proxyId: bot.proxyId,
1174
1223
  proxyHost: bot.proxyHost,
1175
1224
  proxyPort: bot.proxyPort,
1176
1225
  proxyUsername: bot.proxyUsername,
@@ -1232,7 +1281,7 @@ router.get('/:id/settings/all', authenticate, checkBotAccess, authorize('bot:upd
1232
1281
 
1233
1282
  const nodeRegistry = require('../../core/NodeRegistry');
1234
1283
 
1235
- router.get('/:botId/visual-editor/nodes', authenticate, checkBotAccess, authorize('management:view'), (req, res) => {
1284
+ router.get('/:botId/visual-editor/nodes', authenticateUniversal, checkBotAccess, authorize('management:view'), (req, res) => {
1236
1285
  try {
1237
1286
  const { graphType } = req.query;
1238
1287
  const nodesByCategory = nodeRegistry.getNodesByCategory(graphType);
@@ -1243,7 +1292,7 @@ router.get('/:botId/visual-editor/nodes', authenticate, checkBotAccess, authoriz
1243
1292
  }
1244
1293
  });
1245
1294
 
1246
- router.get('/:botId/visual-editor/node-config', authenticate, checkBotAccess, authorize('management:view'), (req, res) => {
1295
+ router.get('/:botId/visual-editor/node-config', authenticateUniversal, checkBotAccess, authorize('management:view'), (req, res) => {
1247
1296
  try {
1248
1297
  const { types } = req.query;
1249
1298
  if (!types) {
@@ -1258,7 +1307,7 @@ router.get('/:botId/visual-editor/node-config', authenticate, checkBotAccess, au
1258
1307
  }
1259
1308
  });
1260
1309
 
1261
- router.get('/:botId/visual-editor/permissions', authenticate, checkBotAccess, authorize('management:view'), async (req, res) => {
1310
+ router.get('/:botId/visual-editor/permissions', authenticateUniversal, checkBotAccess, authorize('management:view'), async (req, res) => {
1262
1311
  try {
1263
1312
  const botId = parseInt(req.params.botId, 10);
1264
1313
  const permissions = await prisma.permission.findMany({
@@ -1879,7 +1928,7 @@ router.post('/:botId/plugins/:pluginName/action', authorize('plugin:list'), asyn
1879
1928
  });
1880
1929
 
1881
1930
 
1882
- router.get('/:botId/export', authenticate, checkBotAccess, authorize('bot:export'), async (req, res) => {
1931
+ router.get('/:botId/export', authenticateUniversal, checkBotAccess, authorize('bot:export'), async (req, res) => {
1883
1932
  try {
1884
1933
  const botId = parseInt(req.params.botId, 10);
1885
1934
  const {