blockmine 1.23.0 → 1.23.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/agents/code-architect.md +34 -0
- package/.claude/agents/code-explorer.md +51 -0
- package/.claude/agents/code-reviewer.md +46 -0
- package/.claude/commands/feature-dev.md +125 -0
- package/.claude/settings.json +1 -1
- package/.claude/settings.local.json +3 -1
- package/.claude/skills/frontend-design/SKILL.md +42 -0
- package/CHANGELOG.md +31 -16
- package/backend/prisma/migrations/20251116111851_add_execution_trace/migration.sql +22 -22
- package/backend/prisma/migrations/20251120154914_add_panel_api_keys/migration.sql +21 -21
- package/backend/prisma/migrations/20251121110241_add_proxy_table/migration.sql +45 -45
- package/backend/prisma/migrations/migration_lock.toml +2 -2
- package/backend/src/api/routes/auth.js +669 -669
- package/backend/src/api/routes/bots.js +2451 -2451
- package/backend/src/api/routes/panel.js +66 -66
- package/backend/src/api/routes/panelApiKeys.js +179 -179
- package/backend/src/api/routes/plugins.js +376 -376
- package/backend/src/api/routes/system.js +174 -174
- package/backend/src/core/EventGraphManager.js +194 -194
- package/backend/src/core/GraphExecutionEngine.js +28 -1
- package/backend/src/core/node-registries/actions.js +2 -2
- package/backend/src/core/nodes/actions/http_request.js +23 -4
- package/backend/src/core/nodes/actions/send_message.js +2 -12
- package/backend/src/core/nodes/data/string_literal.js +2 -13
- package/backend/src/core/services/BotLifecycleService.js +835 -835
- package/frontend/dist/assets/{index-B1serztM.js → index-DqzDkFsP.js} +185 -185
- package/frontend/dist/index.html +1 -1
- package/package.json +2 -1
- package/CLAUDE.md +0 -284
|
@@ -1,2451 +1,2451 @@
|
|
|
1
|
-
const express = require('express');
|
|
2
|
-
const prisma = require('../../lib/prisma');
|
|
3
|
-
const path = require('path');
|
|
4
|
-
const fs = require('fs/promises');
|
|
5
|
-
const fse = require('fs-extra');
|
|
6
|
-
const { botManager, pluginManager } = require('../../core/services');
|
|
7
|
-
const UserService = require('../../core/UserService');
|
|
8
|
-
const commandManager = require('../../core/system/CommandManager');
|
|
9
|
-
const NodeRegistry = require('../../core/NodeRegistry');
|
|
10
|
-
const { authenticate, authenticateUniversal, authorize } = require('../middleware/auth');
|
|
11
|
-
const { encrypt } = require('../../core/utils/crypto');
|
|
12
|
-
const { randomUUID } = require('crypto');
|
|
13
|
-
const eventGraphsRouter = require('./eventGraphs');
|
|
14
|
-
const pluginIdeRouter = require('./pluginIde');
|
|
15
|
-
const apiKeysRouter = require('./apiKeys');
|
|
16
|
-
const { deepMergeSettings } = require('../../core/utils/settingsMerger');
|
|
17
|
-
const { checkBotAccess } = require('../middleware/botAccess');
|
|
18
|
-
const { filterSecretSettings, prepareSettingsForSave, isGroupedSettings } = require('../../core/utils/secretsFilter');
|
|
19
|
-
|
|
20
|
-
const multer = require('multer');
|
|
21
|
-
const archiver = require('archiver');
|
|
22
|
-
const AdmZip = require('adm-zip');
|
|
23
|
-
const os = require('os');
|
|
24
|
-
|
|
25
|
-
const upload = multer({ storage: multer.memoryStorage() });
|
|
26
|
-
|
|
27
|
-
const router = express.Router();
|
|
28
|
-
|
|
29
|
-
const conditionalRestartAuth = (req, res, next) => {
|
|
30
|
-
if (process.env.DEBUG === 'true' || process.env.NODE_ENV === 'development') {
|
|
31
|
-
console.log('[Debug] Роут перезапуска бота доступен без проверки прав');
|
|
32
|
-
return next();
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
return authenticateUniversal(req, res, (err) => {
|
|
36
|
-
if (err) return next(err);
|
|
37
|
-
return authorize('bot:start_stop')(req, res, next);
|
|
38
|
-
});
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
const conditionalChatAuth = (req, res, next) => {
|
|
42
|
-
if (process.env.DEBUG === 'true' || process.env.NODE_ENV === 'development') {
|
|
43
|
-
console.log('[Debug] Роут отправки сообщения боту доступен без проверки прав');
|
|
44
|
-
return next();
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return authenticateUniversal(req, res, (err) => {
|
|
48
|
-
if (err) return next(err);
|
|
49
|
-
return authorize('bot:interact')(req, res, next);
|
|
50
|
-
});
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
const conditionalStartStopAuth = (req, res, next) => {
|
|
54
|
-
if (process.env.DEBUG === 'true' || process.env.NODE_ENV === 'development') {
|
|
55
|
-
console.log('[Debug] Роут запуска/остановки бота доступен без проверки прав');
|
|
56
|
-
return next();
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return authenticateUniversal(req, res, (err) => {
|
|
60
|
-
if (err) return next(err);
|
|
61
|
-
return authorize('bot:start_stop')(req, res, next);
|
|
62
|
-
});
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
const conditionalListAuth = (req, res, next) => {
|
|
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
|
-
if (err) return next(err);
|
|
70
|
-
if (process.env.DEBUG === 'true' || process.env.NODE_ENV === 'development') {
|
|
71
|
-
console.log('[conditionalListAuth] DEBUG mode, skipping authorize');
|
|
72
|
-
return next();
|
|
73
|
-
}
|
|
74
|
-
console.log('[conditionalListAuth] Calling authorize(bot:list)');
|
|
75
|
-
return authorize('bot:list')(req, res, next);
|
|
76
|
-
});
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
router.post('/:id/restart', conditionalRestartAuth, authenticateUniversal, checkBotAccess, async (req, res) => {
|
|
80
|
-
try {
|
|
81
|
-
const botId = parseInt(req.params.id, 10);
|
|
82
|
-
botManager.stopBot(botId);
|
|
83
|
-
setTimeout(async () => {
|
|
84
|
-
const botConfig = await prisma.bot.findUnique({ where: { id: botId }, include: { server: true, proxy: true } });
|
|
85
|
-
if (!botConfig) {
|
|
86
|
-
return res.status(404).json({ success: false, message: 'Бот не найден' });
|
|
87
|
-
}
|
|
88
|
-
botManager.startBot(botConfig);
|
|
89
|
-
res.status(202).json({ success: true, message: 'Команда на перезапуск отправлена.' });
|
|
90
|
-
}, 1000);
|
|
91
|
-
} catch (error) {
|
|
92
|
-
console.error(`[API] Ошибка перезапуска бота ${req.params.id}:`, error);
|
|
93
|
-
res.status(500).json({ success: false, message: 'Ошибка при перезапуске бота: ' + error.message });
|
|
94
|
-
}
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
router.post('/:id/chat', conditionalChatAuth, authenticateUniversal, checkBotAccess, (req, res) => {
|
|
98
|
-
try {
|
|
99
|
-
const botId = parseInt(req.params.id, 10);
|
|
100
|
-
const { message } = req.body;
|
|
101
|
-
if (!message) return res.status(400).json({ error: 'Сообщение не может быть пустым' });
|
|
102
|
-
const result = botManager.sendMessageToBot(botId, message);
|
|
103
|
-
if (result.success) res.json({ success: true });
|
|
104
|
-
else res.status(404).json(result);
|
|
105
|
-
} catch (error) { res.status(500).json({ error: 'Внутренняя ошибка сервера: ' + error.message }); }
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
router.post('/:id/start', conditionalStartStopAuth, authenticateUniversal, checkBotAccess, async (req, res) => {
|
|
109
|
-
try {
|
|
110
|
-
const botId = parseInt(req.params.id, 10);
|
|
111
|
-
const botConfig = await prisma.bot.findUnique({ where: { id: botId }, include: { server: true, proxy: true } });
|
|
112
|
-
if (!botConfig) {
|
|
113
|
-
return res.status(404).json({ success: false, message: 'Бот не найден' });
|
|
114
|
-
}
|
|
115
|
-
botManager.startBot(botConfig);
|
|
116
|
-
res.status(202).json({ success: true, message: 'Команда на запуск отправлена.' });
|
|
117
|
-
} catch (error) {
|
|
118
|
-
console.error(`[API] Ошибка запуска бота ${req.params.id}:`, error);
|
|
119
|
-
res.status(500).json({ success: false, message: 'Ошибка при запуске бота: ' + error.message });
|
|
120
|
-
}
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
router.post('/:id/stop', conditionalStartStopAuth, authenticateUniversal, checkBotAccess, (req, res) => {
|
|
124
|
-
try {
|
|
125
|
-
const botId = parseInt(req.params.id, 10);
|
|
126
|
-
botManager.stopBot(botId);
|
|
127
|
-
res.status(202).json({ success: true, message: 'Команда на остановку отправлена.' });
|
|
128
|
-
} catch (error) {
|
|
129
|
-
console.error(`[API] Ошибка остановки бота ${req.params.id}:`, error);
|
|
130
|
-
res.status(500).json({ success: false, message: 'Ошибка при остановке бота: ' + error.message });
|
|
131
|
-
}
|
|
132
|
-
});
|
|
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
|
-
|
|
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));
|
|
143
|
-
try {
|
|
144
|
-
const botsWithoutSortOrder = await prisma.bot.findMany({
|
|
145
|
-
where: { sortOrder: null },
|
|
146
|
-
select: { id: true }
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
if (botsWithoutSortOrder.length > 0) {
|
|
150
|
-
console.log(`[API] Обновляем sortOrder для ${botsWithoutSortOrder.length} ботов`);
|
|
151
|
-
|
|
152
|
-
const maxSortOrder = await prisma.bot.aggregate({
|
|
153
|
-
_max: { sortOrder: true }
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
let nextSortOrder = (maxSortOrder._max.sortOrder || 0) + 1;
|
|
157
|
-
|
|
158
|
-
for (const bot of botsWithoutSortOrder) {
|
|
159
|
-
await prisma.bot.update({
|
|
160
|
-
where: { id: bot.id },
|
|
161
|
-
data: { sortOrder: nextSortOrder }
|
|
162
|
-
});
|
|
163
|
-
console.log(`[API] Установлен sortOrder ${nextSortOrder} для бота ${bot.id}`);
|
|
164
|
-
nextSortOrder++;
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
let whereFilter = {};
|
|
169
|
-
if (req.user && typeof req.user.userId === 'number') {
|
|
170
|
-
const panelUser = await prisma.panelUser.findUnique({
|
|
171
|
-
where: { id: req.user.userId },
|
|
172
|
-
include: { botAccess: { select: { botId: true } } }
|
|
173
|
-
});
|
|
174
|
-
if (panelUser && panelUser.allBots === false) {
|
|
175
|
-
const allowedIds = panelUser.botAccess.map(a => a.botId);
|
|
176
|
-
whereFilter = { id: { in: allowedIds.length ? allowedIds : [-1] } };
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
const bots = await prisma.bot.findMany({
|
|
181
|
-
where: whereFilter,
|
|
182
|
-
include: { server: true },
|
|
183
|
-
orderBy: { sortOrder: 'asc' }
|
|
184
|
-
});
|
|
185
|
-
console.log(`[API /api/bots GET] Отправляем ${bots.length} ботов, status: 200`);
|
|
186
|
-
res.json(bots);
|
|
187
|
-
} catch (error) {
|
|
188
|
-
console.error("[API /api/bots] Ошибка получения списка ботов:", error);
|
|
189
|
-
res.status(500).json({ error: 'Не удалось получить список ботов' });
|
|
190
|
-
}
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
router.get('/:id', conditionalListAuth, async (req, res) => {
|
|
194
|
-
try {
|
|
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
|
-
}
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
router.put('/bulk-proxy-update', authenticateUniversal, authorize('bot:update'), async (req, res) => {
|
|
214
|
-
try {
|
|
215
|
-
const { botIds, proxySettings } = req.body;
|
|
216
|
-
|
|
217
|
-
if (!Array.isArray(botIds) || botIds.length === 0) {
|
|
218
|
-
return res.status(400).json({ error: 'Bot IDs array is required and cannot be empty' });
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
if (!proxySettings || !proxySettings.proxyHost || !proxySettings.proxyPort) {
|
|
222
|
-
return res.status(400).json({ error: 'Proxy host and port are required' });
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
if (proxySettings.proxyPort < 1 || proxySettings.proxyPort > 65535) {
|
|
226
|
-
return res.status(400).json({ error: 'Proxy port must be between 1 and 65535' });
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
const accessibleBots = [];
|
|
230
|
-
const inaccessibleBots = [];
|
|
231
|
-
|
|
232
|
-
for (const botId of botIds) {
|
|
233
|
-
try {
|
|
234
|
-
const userId = req.user?.userId;
|
|
235
|
-
if (!userId) {
|
|
236
|
-
inaccessibleBots.push(botId);
|
|
237
|
-
continue;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
const botIdInt = parseInt(botId, 10);
|
|
241
|
-
if (isNaN(botIdInt)) {
|
|
242
|
-
inaccessibleBots.push(botId);
|
|
243
|
-
continue;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
const user = await prisma.panelUser.findUnique({
|
|
247
|
-
where: { id: userId },
|
|
248
|
-
include: { botAccess: { select: { botId: true } } }
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
if (!user) {
|
|
252
|
-
inaccessibleBots.push(botId);
|
|
253
|
-
continue;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
if (user.allBots !== false || user.botAccess.some((a) => a.botId === botIdInt)) {
|
|
257
|
-
accessibleBots.push(botIdInt);
|
|
258
|
-
} else {
|
|
259
|
-
inaccessibleBots.push(botId);
|
|
260
|
-
}
|
|
261
|
-
} catch (error) {
|
|
262
|
-
console.error(`Error checking access for bot ${botId}:`, error);
|
|
263
|
-
inaccessibleBots.push(botId);
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
if (accessibleBots.length === 0) {
|
|
268
|
-
return res.status(403).json({ error: 'No accessible bots in the provided list' });
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
const encryptedSettings = {
|
|
272
|
-
proxyHost: proxySettings.proxyHost.trim(),
|
|
273
|
-
proxyPort: parseInt(proxySettings.proxyPort),
|
|
274
|
-
proxyUsername: proxySettings.proxyUsername ? proxySettings.proxyUsername.trim() : null,
|
|
275
|
-
proxyPassword: proxySettings.proxyPassword ? encrypt(proxySettings.proxyPassword) : null
|
|
276
|
-
};
|
|
277
|
-
|
|
278
|
-
const updatedBots = await prisma.$transaction(
|
|
279
|
-
accessibleBots.map(botId =>
|
|
280
|
-
prisma.bot.update({
|
|
281
|
-
where: { id: parseInt(botId) },
|
|
282
|
-
data: encryptedSettings,
|
|
283
|
-
include: {
|
|
284
|
-
server: {
|
|
285
|
-
select: {
|
|
286
|
-
id: true,
|
|
287
|
-
name: true,
|
|
288
|
-
host: true,
|
|
289
|
-
port: true,
|
|
290
|
-
version: true
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
})
|
|
295
|
-
)
|
|
296
|
-
);
|
|
297
|
-
|
|
298
|
-
if (req.io) {
|
|
299
|
-
req.io.emit('bots-updated', updatedBots);
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
res.json({
|
|
303
|
-
success: true,
|
|
304
|
-
message: `Proxy settings updated for ${updatedBots.length} bot(s)`,
|
|
305
|
-
updatedBots: updatedBots.map(bot => ({
|
|
306
|
-
id: bot.id,
|
|
307
|
-
username: bot.username,
|
|
308
|
-
proxyHost: bot.proxyHost,
|
|
309
|
-
proxyPort: bot.proxyPort,
|
|
310
|
-
server: bot.server
|
|
311
|
-
})),
|
|
312
|
-
inaccessibleBots: inaccessibleBots,
|
|
313
|
-
errors: inaccessibleBots.length > 0 ? [`Access denied to ${inaccessibleBots.length} bot(s)`] : []
|
|
314
|
-
});
|
|
315
|
-
|
|
316
|
-
} catch (error) {
|
|
317
|
-
console.error('Bulk proxy update error:', error);
|
|
318
|
-
res.status(500).json({
|
|
319
|
-
error: 'Failed to update proxy settings',
|
|
320
|
-
details: error.message
|
|
321
|
-
});
|
|
322
|
-
}
|
|
323
|
-
});
|
|
324
|
-
|
|
325
|
-
router.get('/:id/logs', conditionalListAuth, authenticateUniversal, checkBotAccess, (req, res) => {
|
|
326
|
-
try {
|
|
327
|
-
const botId = parseInt(req.params.id, 10);
|
|
328
|
-
const { limit = 50, offset = 0 } = req.query;
|
|
329
|
-
|
|
330
|
-
const logs = botManager.getBotLogs(botId);
|
|
331
|
-
|
|
332
|
-
const startIndex = parseInt(offset);
|
|
333
|
-
const endIndex = startIndex + parseInt(limit);
|
|
334
|
-
const paginatedLogs = logs.slice(startIndex, endIndex);
|
|
335
|
-
|
|
336
|
-
res.json({
|
|
337
|
-
success: true,
|
|
338
|
-
data: {
|
|
339
|
-
logs: paginatedLogs,
|
|
340
|
-
pagination: {
|
|
341
|
-
total: logs.length,
|
|
342
|
-
limit: parseInt(limit),
|
|
343
|
-
offset: startIndex,
|
|
344
|
-
hasMore: endIndex < logs.length
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
});
|
|
348
|
-
} catch (error) {
|
|
349
|
-
console.error(`[API] Ошибка получения логов бота ${req.params.id}:`, error);
|
|
350
|
-
res.status(500).json({ error: 'Не удалось получить логи бота' });
|
|
351
|
-
}
|
|
352
|
-
});
|
|
353
|
-
|
|
354
|
-
router.use(authenticate);
|
|
355
|
-
router.use('/:botId/event-graphs', eventGraphsRouter);
|
|
356
|
-
router.use('/:botId/plugins/ide', pluginIdeRouter);
|
|
357
|
-
router.use('/:botId/api-keys', apiKeysRouter);
|
|
358
|
-
|
|
359
|
-
async function setupDefaultPermissionsForBot(botId, prismaClient = prisma) {
|
|
360
|
-
const initialData = {
|
|
361
|
-
groups: ["User", "Admin"],
|
|
362
|
-
permissions: [
|
|
363
|
-
{ name: "admin.*", description: "Все права администратора" },
|
|
364
|
-
{ name: "admin.cooldown.bypass", description: "Обход кулдауна для админ-команд" },
|
|
365
|
-
{ name: "user.*", description: "Все права обычного пользователя" },
|
|
366
|
-
{ name: "user.say", description: "Доступ к простым командам" },
|
|
367
|
-
{ name: "user.cooldown.bypass", description: "Обход кулдауна для юзер-команд" },
|
|
368
|
-
],
|
|
369
|
-
groupPermissions: {
|
|
370
|
-
"User": ["user.say"],
|
|
371
|
-
"Admin": ["admin.*", "admin.cooldown.bypass", "user.cooldown.bypass", "user.*"]
|
|
372
|
-
},
|
|
373
|
-
};
|
|
374
|
-
|
|
375
|
-
for (const perm of initialData.permissions) {
|
|
376
|
-
await prismaClient.permission.upsert({ where: { botId_name: { botId, name: perm.name } }, update: { description: perm.description }, create: { ...perm, botId, owner: 'system' } });
|
|
377
|
-
}
|
|
378
|
-
for (const groupName of initialData.groups) {
|
|
379
|
-
await prismaClient.group.upsert({ where: { botId_name: { botId, name: groupName } }, update: {}, create: { name: groupName, botId, owner: 'system' } });
|
|
380
|
-
}
|
|
381
|
-
for (const [groupName, permNames] of Object.entries(initialData.groupPermissions)) {
|
|
382
|
-
const group = await prismaClient.group.findUnique({ where: { botId_name: { botId, name: groupName } } });
|
|
383
|
-
if (group) {
|
|
384
|
-
for (const permName of permNames) {
|
|
385
|
-
const permission = await prismaClient.permission.findUnique({ where: { botId_name: { botId, name: permName } } });
|
|
386
|
-
if (permission) {
|
|
387
|
-
await prismaClient.groupPermission.upsert({ where: { groupId_permissionId: { groupId: group.id, permissionId: permission.id } }, update: {}, create: { groupId: group.id, permissionId: permission.id } });
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
console.log(`[Setup] Для бота ID ${botId} созданы группы и права по умолчанию.`);
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
router.post('/', authorize('bot:create'), async (req, res) => {
|
|
398
|
-
try {
|
|
399
|
-
const { username, password, prefix, serverId, note, proxyId, proxyHost, proxyPort, proxyUsername, proxyPassword } = req.body;
|
|
400
|
-
if (!username || !serverId) return res.status(400).json({ error: 'Имя и сервер обязательны' });
|
|
401
|
-
|
|
402
|
-
const maxSortOrder = await prisma.bot.aggregate({
|
|
403
|
-
_max: { sortOrder: true }
|
|
404
|
-
});
|
|
405
|
-
const nextSortOrder = (maxSortOrder._max.sortOrder || 0) + 1;
|
|
406
|
-
|
|
407
|
-
const data = {
|
|
408
|
-
username,
|
|
409
|
-
prefix,
|
|
410
|
-
note,
|
|
411
|
-
serverId: parseInt(serverId, 10),
|
|
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,
|
|
418
|
-
sortOrder: nextSortOrder
|
|
419
|
-
};
|
|
420
|
-
|
|
421
|
-
const newBot = await prisma.bot.create({
|
|
422
|
-
data: data,
|
|
423
|
-
include: { server: true, proxy: true }
|
|
424
|
-
});
|
|
425
|
-
await setupDefaultPermissionsForBot(newBot.id);
|
|
426
|
-
res.status(201).json(newBot);
|
|
427
|
-
} catch (error) {
|
|
428
|
-
if (error.code === 'P2002') return res.status(409).json({ error: 'Бот с таким именем уже существует' });
|
|
429
|
-
console.error("[API Error] /bots POST:", error);
|
|
430
|
-
res.status(500).json({ error: 'Не удалось создать бота' });
|
|
431
|
-
}
|
|
432
|
-
});
|
|
433
|
-
|
|
434
|
-
router.put('/:id', authenticateUniversal, checkBotAccess, authorize('bot:update'), async (req, res) => {
|
|
435
|
-
try {
|
|
436
|
-
const {
|
|
437
|
-
username, password, prefix, serverId, note, owners,
|
|
438
|
-
proxyId, proxyHost, proxyPort, proxyUsername, proxyPassword
|
|
439
|
-
} = req.body;
|
|
440
|
-
|
|
441
|
-
let dataToUpdate = {
|
|
442
|
-
username,
|
|
443
|
-
prefix,
|
|
444
|
-
note,
|
|
445
|
-
owners,
|
|
446
|
-
proxyId: proxyId ? parseInt(proxyId, 10) : null,
|
|
447
|
-
proxyHost,
|
|
448
|
-
proxyPort: proxyPort ? parseInt(proxyPort, 10) : null,
|
|
449
|
-
proxyUsername,
|
|
450
|
-
};
|
|
451
|
-
|
|
452
|
-
if (password) {
|
|
453
|
-
dataToUpdate.password = encrypt(password);
|
|
454
|
-
}
|
|
455
|
-
if (proxyPassword) {
|
|
456
|
-
dataToUpdate.proxyPassword = encrypt(proxyPassword);
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
if (serverId !== undefined && serverId !== '') {
|
|
460
|
-
dataToUpdate.serverId = parseInt(serverId, 10);
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
Object.keys(dataToUpdate).forEach(key => {
|
|
464
|
-
if (dataToUpdate[key] === undefined) {
|
|
465
|
-
delete dataToUpdate[key];
|
|
466
|
-
}
|
|
467
|
-
});
|
|
468
|
-
|
|
469
|
-
if (dataToUpdate.serverId) {
|
|
470
|
-
const serverIdValue = dataToUpdate.serverId;
|
|
471
|
-
delete dataToUpdate.serverId;
|
|
472
|
-
dataToUpdate.server = { connect: { id: serverIdValue } };
|
|
473
|
-
}
|
|
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
|
-
|
|
485
|
-
const botId = parseInt(req.params.id, 10);
|
|
486
|
-
if (isNaN(botId)) {
|
|
487
|
-
return res.status(400).json({ message: 'Неверный ID бота.' });
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
if (dataToUpdate.username) {
|
|
491
|
-
const existingBot = await prisma.bot.findFirst({
|
|
492
|
-
where: {
|
|
493
|
-
username: dataToUpdate.username,
|
|
494
|
-
id: { not: botId }
|
|
495
|
-
}
|
|
496
|
-
});
|
|
497
|
-
|
|
498
|
-
if (existingBot) {
|
|
499
|
-
return res.status(400).json({
|
|
500
|
-
message: `Бот с именем "${dataToUpdate.username}" уже существует.`
|
|
501
|
-
});
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
const updatedBot = await prisma.bot.update({
|
|
506
|
-
where: { id: botId },
|
|
507
|
-
data: dataToUpdate,
|
|
508
|
-
include: { server: true, proxy: true }
|
|
509
|
-
});
|
|
510
|
-
|
|
511
|
-
botManager.reloadBotConfigInRealTime(botId);
|
|
512
|
-
|
|
513
|
-
// Отложенная очистка кеша пользователей, чтобы BotProcess успел обновить config
|
|
514
|
-
setTimeout(() => {
|
|
515
|
-
botManager.invalidateAllUserCache(botId);
|
|
516
|
-
}, 500);
|
|
517
|
-
|
|
518
|
-
res.json(updatedBot);
|
|
519
|
-
} catch (error) {
|
|
520
|
-
console.error("[API Error] /bots PUT:", error);
|
|
521
|
-
res.status(500).json({ error: 'Не удалось обновить бота' });
|
|
522
|
-
}
|
|
523
|
-
});
|
|
524
|
-
|
|
525
|
-
router.put('/:id/sort-order', authenticateUniversal, checkBotAccess, authorize('bot:update'), async (req, res) => {
|
|
526
|
-
try {
|
|
527
|
-
const { newPosition, oldIndex, newIndex } = req.body;
|
|
528
|
-
const botId = parseInt(req.params.id, 10);
|
|
529
|
-
|
|
530
|
-
console.log(`[API] Запрос на изменение порядка бота ${botId}: oldIndex=${oldIndex}, newIndex=${newIndex}, newPosition=${newPosition}`);
|
|
531
|
-
|
|
532
|
-
if (isNaN(botId)) {
|
|
533
|
-
console.log(`[API] Неверный botId: ${botId}`);
|
|
534
|
-
return res.status(400).json({ error: 'Неверный ID бота' });
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
const allBots = await prisma.bot.findMany({
|
|
538
|
-
orderBy: { sortOrder: 'asc' },
|
|
539
|
-
select: { id: true, sortOrder: true }
|
|
540
|
-
});
|
|
541
|
-
|
|
542
|
-
console.log(`[API] Всего ботов: ${allBots.length}`);
|
|
543
|
-
const currentBotIndex = allBots.findIndex(bot => bot.id === botId);
|
|
544
|
-
if (currentBotIndex === -1) {
|
|
545
|
-
console.log(`[API] Бот ${botId} не найден`);
|
|
546
|
-
return res.status(404).json({ error: 'Бот не найден' });
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
if (newIndex < 0 || newIndex >= allBots.length) {
|
|
550
|
-
console.log(`[API] Неверная новая позиция: ${newIndex}`);
|
|
551
|
-
return res.status(400).json({ error: 'Неверная позиция' });
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
if (currentBotIndex === newIndex) {
|
|
555
|
-
console.log(`[API] Позиция не изменилась для бота ${botId}`);
|
|
556
|
-
return res.json({ success: true, message: 'Позиция не изменилась' });
|
|
557
|
-
}
|
|
558
|
-
const reorderedBots = [...allBots];
|
|
559
|
-
const [movedBot] = reorderedBots.splice(currentBotIndex, 1);
|
|
560
|
-
reorderedBots.splice(newIndex, 0, movedBot);
|
|
561
|
-
|
|
562
|
-
console.log(`[API] Обновляем порядок для всех ботов`);
|
|
563
|
-
for (let i = 0; i < reorderedBots.length; i++) {
|
|
564
|
-
const bot = reorderedBots[i];
|
|
565
|
-
const newSortOrder = i + 1; // 1-based позиции
|
|
566
|
-
|
|
567
|
-
if (bot.sortOrder !== newSortOrder) {
|
|
568
|
-
await prisma.bot.update({
|
|
569
|
-
where: { id: bot.id },
|
|
570
|
-
data: { sortOrder: newSortOrder }
|
|
571
|
-
});
|
|
572
|
-
console.log(`[API] Обновлен бот ${bot.id}: sortOrder ${bot.sortOrder} -> ${newSortOrder}`);
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
console.log(`[API] Успешно обновлен порядок бота ${botId}`);
|
|
577
|
-
res.json({ success: true, message: 'Порядок ботов обновлен' });
|
|
578
|
-
} catch (error) {
|
|
579
|
-
console.error("[API Error] /bots sort-order PUT:", error);
|
|
580
|
-
res.status(500).json({ error: 'Не удалось обновить порядок ботов' });
|
|
581
|
-
}
|
|
582
|
-
});
|
|
583
|
-
|
|
584
|
-
router.delete('/:id', authenticateUniversal, checkBotAccess, authorize('bot:delete'), async (req, res) => {
|
|
585
|
-
try {
|
|
586
|
-
const botId = parseInt(req.params.id, 10);
|
|
587
|
-
if (botManager.bots.has(botId)) return res.status(400).json({ error: 'Нельзя удалить запущенного бота' });
|
|
588
|
-
await prisma.bot.delete({ where: { id: botId } });
|
|
589
|
-
res.status(204).send();
|
|
590
|
-
} catch (error) { res.status(500).json({ error: 'Не удалось удалить бота' }); }
|
|
591
|
-
});
|
|
592
|
-
|
|
593
|
-
router.get('/servers', authorize('bot:list'), async (req, res) => {
|
|
594
|
-
try {
|
|
595
|
-
const servers = await prisma.server.findMany();
|
|
596
|
-
res.json(servers);
|
|
597
|
-
} catch (error) {
|
|
598
|
-
console.error("[API /api/bots] Ошибка получения списка серверов:", error);
|
|
599
|
-
res.status(500).json({ error: 'Не удалось получить список серверов' });
|
|
600
|
-
}
|
|
601
|
-
});
|
|
602
|
-
|
|
603
|
-
router.get('/:botId/plugins', authenticateUniversal, checkBotAccess, authorize('plugin:list'), async (req, res) => {
|
|
604
|
-
try {
|
|
605
|
-
const botId = parseInt(req.params.botId);
|
|
606
|
-
const plugins = await prisma.installedPlugin.findMany({ where: { botId } });
|
|
607
|
-
res.json(plugins);
|
|
608
|
-
} catch (error) { res.status(500).json({ error: 'Не удалось получить плагины бота' }); }
|
|
609
|
-
});
|
|
610
|
-
|
|
611
|
-
router.post('/:botId/plugins/install/github', authenticateUniversal, checkBotAccess, authorize('plugin:install'), async (req, res) => {
|
|
612
|
-
const { botId } = req.params;
|
|
613
|
-
const { repoUrl } = req.body;
|
|
614
|
-
try {
|
|
615
|
-
const newPlugin = await pluginManager.installFromGithub(parseInt(botId), repoUrl);
|
|
616
|
-
res.status(201).json(newPlugin);
|
|
617
|
-
} catch (error) {
|
|
618
|
-
res.status(500).json({ message: error.message });
|
|
619
|
-
}
|
|
620
|
-
});
|
|
621
|
-
|
|
622
|
-
router.post('/:botId/plugins/install/local', authenticateUniversal, checkBotAccess, authorize('plugin:install'), async (req, res) => {
|
|
623
|
-
const { botId } = req.params;
|
|
624
|
-
const { path } = req.body;
|
|
625
|
-
try {
|
|
626
|
-
const newPlugin = await pluginManager.installFromLocalPath(parseInt(botId), path);
|
|
627
|
-
res.status(201).json(newPlugin);
|
|
628
|
-
} catch (error) {
|
|
629
|
-
res.status(500).json({ message: error.message });
|
|
630
|
-
}
|
|
631
|
-
});
|
|
632
|
-
|
|
633
|
-
router.delete('/:botId/plugins/:pluginId', authenticateUniversal, checkBotAccess, authorize('plugin:delete'), async (req, res) => {
|
|
634
|
-
const { pluginId } = req.params;
|
|
635
|
-
try {
|
|
636
|
-
await pluginManager.deletePlugin(parseInt(pluginId));
|
|
637
|
-
res.status(204).send();
|
|
638
|
-
} catch (error) {
|
|
639
|
-
res.status(500).json({ message: error.message });
|
|
640
|
-
}
|
|
641
|
-
});
|
|
642
|
-
|
|
643
|
-
router.get('/:botId/plugins/:pluginId/settings', authenticateUniversal, checkBotAccess, authorize('plugin:settings:view'), async (req, res) => {
|
|
644
|
-
try {
|
|
645
|
-
const pluginId = parseInt(req.params.pluginId);
|
|
646
|
-
const plugin = await prisma.installedPlugin.findUnique({ where: { id: pluginId } });
|
|
647
|
-
if (!plugin) return res.status(404).json({ error: 'Установленный плагин не найден' });
|
|
648
|
-
|
|
649
|
-
const savedSettings = plugin.settings ? JSON.parse(plugin.settings) : {};
|
|
650
|
-
const defaultSettings = {};
|
|
651
|
-
const manifest = plugin.manifest ? JSON.parse(plugin.manifest) : {};
|
|
652
|
-
const manifestSettings = manifest.settings || {};
|
|
653
|
-
|
|
654
|
-
// Определяем тип настроек (обычные или группированные)
|
|
655
|
-
const isGrouped = isGroupedSettings(manifestSettings);
|
|
656
|
-
|
|
657
|
-
const processSetting = async (settingKey, config) => {
|
|
658
|
-
if (!config || !config.type) return;
|
|
659
|
-
|
|
660
|
-
if (config.type === 'json_file' && config.defaultPath) {
|
|
661
|
-
const configFilePath = path.join(plugin.path, config.defaultPath);
|
|
662
|
-
try {
|
|
663
|
-
const fileContent = await fs.readFile(configFilePath, 'utf-8');
|
|
664
|
-
defaultSettings[settingKey] = JSON.parse(fileContent);
|
|
665
|
-
} catch (e) {
|
|
666
|
-
console.error(`[API Settings] Не удалось прочитать defaultPath ${config.defaultPath} для плагина ${plugin.name}: ${e.message}`);
|
|
667
|
-
defaultSettings[settingKey] = {};
|
|
668
|
-
}
|
|
669
|
-
} else if (config.default !== undefined) {
|
|
670
|
-
try {
|
|
671
|
-
defaultSettings[settingKey] = JSON.parse(config.default);
|
|
672
|
-
} catch {
|
|
673
|
-
defaultSettings[settingKey] = config.default;
|
|
674
|
-
}
|
|
675
|
-
}
|
|
676
|
-
};
|
|
677
|
-
|
|
678
|
-
if (isGrouped) {
|
|
679
|
-
for (const categoryKey in manifestSettings) {
|
|
680
|
-
const categoryConfig = manifestSettings[categoryKey];
|
|
681
|
-
for (const settingKey in categoryConfig) {
|
|
682
|
-
if (settingKey === 'label') continue;
|
|
683
|
-
await processSetting(settingKey, categoryConfig[settingKey]);
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
} else {
|
|
687
|
-
for (const settingKey in manifestSettings) {
|
|
688
|
-
await processSetting(settingKey, manifestSettings[settingKey]);
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
const finalSettings = deepMergeSettings(defaultSettings, savedSettings);
|
|
693
|
-
|
|
694
|
-
// Фильтруем секретные значения перед отправкой на фронтенд
|
|
695
|
-
const filteredSettings = filterSecretSettings(finalSettings, manifestSettings, isGrouped);
|
|
696
|
-
|
|
697
|
-
res.json(filteredSettings);
|
|
698
|
-
} catch (error) {
|
|
699
|
-
console.error("[API Error] /settings GET:", error);
|
|
700
|
-
res.status(500).json({ error: 'Не удалось получить настройки плагина' });
|
|
701
|
-
}
|
|
702
|
-
});
|
|
703
|
-
|
|
704
|
-
router.get('/:botId/plugins/:pluginId/data', authenticateUniversal, checkBotAccess, authorize('plugin:settings:view'), async (req, res) => {
|
|
705
|
-
try {
|
|
706
|
-
const pluginId = parseInt(req.params.pluginId);
|
|
707
|
-
const plugin = await prisma.installedPlugin.findUnique({ where: { id: pluginId } });
|
|
708
|
-
if (!plugin) return res.status(404).json({ error: 'Установленный плагин не найден' });
|
|
709
|
-
|
|
710
|
-
const rows = await prisma.pluginDataStore.findMany({
|
|
711
|
-
where: { botId: plugin.botId, pluginName: plugin.name },
|
|
712
|
-
orderBy: { updatedAt: 'desc' }
|
|
713
|
-
});
|
|
714
|
-
|
|
715
|
-
const result = rows.map(r => {
|
|
716
|
-
let value;
|
|
717
|
-
try { value = JSON.parse(r.value); } catch { value = r.value; }
|
|
718
|
-
return { key: r.key, value, createdAt: r.createdAt, updatedAt: r.updatedAt };
|
|
719
|
-
});
|
|
720
|
-
res.json(result);
|
|
721
|
-
} catch (error) { res.status(500).json({ error: 'Не удалось получить данные плагина' }); }
|
|
722
|
-
});
|
|
723
|
-
|
|
724
|
-
router.get('/:botId/plugins/:pluginId/data/:key', authenticateUniversal, checkBotAccess, authorize('plugin:settings:view'), async (req, res) => {
|
|
725
|
-
try {
|
|
726
|
-
const pluginId = parseInt(req.params.pluginId);
|
|
727
|
-
const { key } = req.params;
|
|
728
|
-
const plugin = await prisma.installedPlugin.findUnique({ where: { id: pluginId } });
|
|
729
|
-
if (!plugin) return res.status(404).json({ error: 'Установленный плагин не найден' });
|
|
730
|
-
|
|
731
|
-
const row = await prisma.pluginDataStore.findUnique({
|
|
732
|
-
where: {
|
|
733
|
-
pluginName_botId_key: {
|
|
734
|
-
pluginName: plugin.name,
|
|
735
|
-
botId: plugin.botId,
|
|
736
|
-
key
|
|
737
|
-
}
|
|
738
|
-
}
|
|
739
|
-
});
|
|
740
|
-
if (!row) return res.status(404).json({ error: 'Ключ не найден' });
|
|
741
|
-
let value; try { value = JSON.parse(row.value); } catch { value = row.value; }
|
|
742
|
-
res.json({ key: row.key, value, createdAt: row.createdAt, updatedAt: row.updatedAt });
|
|
743
|
-
} catch (error) { res.status(500).json({ error: 'Не удалось получить значение по ключу' }); }
|
|
744
|
-
});
|
|
745
|
-
|
|
746
|
-
router.put('/:botId/plugins/:pluginId/data/:key', authenticateUniversal, checkBotAccess, authorize('plugin:settings:edit'), async (req, res) => {
|
|
747
|
-
try {
|
|
748
|
-
const pluginId = parseInt(req.params.pluginId);
|
|
749
|
-
const { key } = req.params;
|
|
750
|
-
const { value } = req.body;
|
|
751
|
-
const plugin = await prisma.installedPlugin.findUnique({ where: { id: pluginId } });
|
|
752
|
-
if (!plugin) return res.status(404).json({ error: 'Установленный плагин не найден' });
|
|
753
|
-
|
|
754
|
-
const jsonValue = JSON.stringify(value ?? null);
|
|
755
|
-
const upserted = await prisma.pluginDataStore.upsert({
|
|
756
|
-
where: {
|
|
757
|
-
pluginName_botId_key: {
|
|
758
|
-
pluginName: plugin.name,
|
|
759
|
-
botId: plugin.botId,
|
|
760
|
-
key
|
|
761
|
-
}
|
|
762
|
-
},
|
|
763
|
-
update: { value: jsonValue },
|
|
764
|
-
create: { pluginName: plugin.name, botId: plugin.botId, key, value: jsonValue }
|
|
765
|
-
});
|
|
766
|
-
let parsed; try { parsed = JSON.parse(upserted.value); } catch { parsed = upserted.value; }
|
|
767
|
-
res.json({ key: upserted.key, value: parsed, createdAt: upserted.createdAt, updatedAt: upserted.updatedAt });
|
|
768
|
-
} catch (error) { res.status(500).json({ error: 'Не удалось сохранить значение' }); }
|
|
769
|
-
});
|
|
770
|
-
|
|
771
|
-
router.delete('/:botId/plugins/:pluginId/data/:key', authenticateUniversal, checkBotAccess, authorize('plugin:settings:edit'), async (req, res) => {
|
|
772
|
-
try {
|
|
773
|
-
const pluginId = parseInt(req.params.pluginId);
|
|
774
|
-
const { key } = req.params;
|
|
775
|
-
const plugin = await prisma.installedPlugin.findUnique({ where: { id: pluginId } });
|
|
776
|
-
if (!plugin) return res.status(404).json({ error: 'Установленный плагин не найден' });
|
|
777
|
-
|
|
778
|
-
await prisma.pluginDataStore.delete({
|
|
779
|
-
where: {
|
|
780
|
-
pluginName_botId_key: {
|
|
781
|
-
pluginName: plugin.name,
|
|
782
|
-
botId: plugin.botId,
|
|
783
|
-
key
|
|
784
|
-
}
|
|
785
|
-
}
|
|
786
|
-
});
|
|
787
|
-
res.status(204).send();
|
|
788
|
-
} catch (error) { res.status(500).json({ error: 'Не удалось удалить значение' }); }
|
|
789
|
-
});
|
|
790
|
-
|
|
791
|
-
router.put('/:botId/plugins/:pluginId', authenticateUniversal, checkBotAccess, authorize('plugin:settings:edit'), async (req, res) => {
|
|
792
|
-
try {
|
|
793
|
-
const pluginId = parseInt(req.params.pluginId);
|
|
794
|
-
const { isEnabled, settings } = req.body;
|
|
795
|
-
const dataToUpdate = {};
|
|
796
|
-
|
|
797
|
-
if (typeof isEnabled === 'boolean') dataToUpdate.isEnabled = isEnabled;
|
|
798
|
-
|
|
799
|
-
if (settings) {
|
|
800
|
-
// Получаем существующий плагин для обработки секретных значений
|
|
801
|
-
const plugin = await prisma.installedPlugin.findUnique({ where: { id: pluginId } });
|
|
802
|
-
if (!plugin) return res.status(404).json({ error: 'Плагин не найден' });
|
|
803
|
-
|
|
804
|
-
const manifest = plugin.manifest ? JSON.parse(plugin.manifest) : {};
|
|
805
|
-
const existingSettings = plugin.settings ? JSON.parse(plugin.settings) : {};
|
|
806
|
-
const manifestSettings = manifest.settings || {};
|
|
807
|
-
|
|
808
|
-
// Определяем тип настроек (обычные или группированные)
|
|
809
|
-
const isGrouped = isGroupedSettings(manifestSettings);
|
|
810
|
-
|
|
811
|
-
// Подготавливаем настройки для сохранения (сохраняем существующие значения для замаскированных секретов)
|
|
812
|
-
const settingsToSave = prepareSettingsForSave(settings, existingSettings, manifestSettings, isGrouped);
|
|
813
|
-
|
|
814
|
-
// Валидация структуры settingsToSave
|
|
815
|
-
if (!settingsToSave || typeof settingsToSave !== 'object' || Array.isArray(settingsToSave)) {
|
|
816
|
-
console.error("[Validation Error] prepareSettingsForSave вернул некорректную структуру:", settingsToSave);
|
|
817
|
-
return res.status(400).json({ error: "Некорректная структура настроек для сохранения" });
|
|
818
|
-
}
|
|
819
|
-
|
|
820
|
-
dataToUpdate.settings = JSON.stringify(settingsToSave);
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
if (Object.keys(dataToUpdate).length === 0) return res.status(400).json({ error: "Нет данных для обновления" });
|
|
824
|
-
const updated = await prisma.installedPlugin.update({ where: { id: pluginId }, data: dataToUpdate });
|
|
825
|
-
res.json(updated);
|
|
826
|
-
} catch (error) {
|
|
827
|
-
console.error("[API Error] /plugins/:pluginId PUT:", error);
|
|
828
|
-
res.status(500).json({ error: 'Не удалось обновить плагин' });
|
|
829
|
-
}
|
|
830
|
-
});
|
|
831
|
-
|
|
832
|
-
router.get('/:botId/management-data', authenticateUniversal, checkBotAccess, authorize('management:view'), async (req, res) => {
|
|
833
|
-
try {
|
|
834
|
-
const botId = parseInt(req.params.botId, 10);
|
|
835
|
-
if (isNaN(botId)) return res.status(400).json({ error: 'Неверный ID бота' });
|
|
836
|
-
|
|
837
|
-
const page = parseInt(req.query.page) || 1;
|
|
838
|
-
const pageSize = parseInt(req.query.pageSize) || 100;
|
|
839
|
-
const searchQuery = req.query.search || '';
|
|
840
|
-
|
|
841
|
-
const userSkip = (page - 1) * pageSize;
|
|
842
|
-
|
|
843
|
-
const whereClause = {
|
|
844
|
-
botId,
|
|
845
|
-
};
|
|
846
|
-
|
|
847
|
-
if (searchQuery) {
|
|
848
|
-
whereClause.username = {
|
|
849
|
-
contains: searchQuery,
|
|
850
|
-
};
|
|
851
|
-
}
|
|
852
|
-
|
|
853
|
-
const [groups, allPermissions] = await Promise.all([
|
|
854
|
-
prisma.group.findMany({ where: { botId }, include: { permissions: { include: { permission: true } } }, orderBy: { name: 'asc' } }),
|
|
855
|
-
prisma.permission.findMany({ where: { botId }, orderBy: { name: 'asc' } })
|
|
856
|
-
]);
|
|
857
|
-
|
|
858
|
-
const [users, usersCount] = await Promise.all([
|
|
859
|
-
prisma.user.findMany({
|
|
860
|
-
where: whereClause,
|
|
861
|
-
include: { groups: { include: { group: true } } },
|
|
862
|
-
orderBy: { username: 'asc' },
|
|
863
|
-
take: pageSize,
|
|
864
|
-
skip: userSkip,
|
|
865
|
-
}),
|
|
866
|
-
prisma.user.count({ where: whereClause })
|
|
867
|
-
]);
|
|
868
|
-
|
|
869
|
-
const templatesMap = new Map(commandManager.getCommandTemplates().map(t => [t.name, t]));
|
|
870
|
-
let dbCommandsFromDb = await prisma.command.findMany({
|
|
871
|
-
where: { botId },
|
|
872
|
-
include: {
|
|
873
|
-
pluginOwner: {
|
|
874
|
-
select: {
|
|
875
|
-
id: true,
|
|
876
|
-
name: true,
|
|
877
|
-
version: true,
|
|
878
|
-
sourceType: true
|
|
879
|
-
}
|
|
880
|
-
}
|
|
881
|
-
},
|
|
882
|
-
orderBy: [{ owner: 'asc' }, { name: 'asc' }]
|
|
883
|
-
});
|
|
884
|
-
|
|
885
|
-
const commandsToCreate = [];
|
|
886
|
-
for (const template of templatesMap.values()) {
|
|
887
|
-
if (!dbCommandsFromDb.some(cmd => cmd.name === template.name)) {
|
|
888
|
-
let permissionId = null;
|
|
889
|
-
if (template.permissions) {
|
|
890
|
-
const permission = await prisma.permission.upsert({
|
|
891
|
-
where: { botId_name: { botId, name: template.permissions } },
|
|
892
|
-
update: { description: `Авто-создано для команды ${template.name}` },
|
|
893
|
-
create: {
|
|
894
|
-
botId,
|
|
895
|
-
name: template.permissions,
|
|
896
|
-
description: `Авто-создано для команды ${template.name}`,
|
|
897
|
-
owner: template.owner || 'system',
|
|
898
|
-
}
|
|
899
|
-
});
|
|
900
|
-
permissionId = permission.id;
|
|
901
|
-
}
|
|
902
|
-
|
|
903
|
-
commandsToCreate.push({
|
|
904
|
-
botId,
|
|
905
|
-
name: template.name,
|
|
906
|
-
isEnabled: template.isActive,
|
|
907
|
-
cooldown: template.cooldown,
|
|
908
|
-
aliases: JSON.stringify(template.aliases),
|
|
909
|
-
description: template.description,
|
|
910
|
-
owner: template.owner,
|
|
911
|
-
permissionId: permissionId,
|
|
912
|
-
allowedChatTypes: JSON.stringify(template.allowedChatTypes),
|
|
913
|
-
});
|
|
914
|
-
}
|
|
915
|
-
}
|
|
916
|
-
|
|
917
|
-
if (commandsToCreate.length > 0) {
|
|
918
|
-
await prisma.command.createMany({ data: commandsToCreate });
|
|
919
|
-
dbCommandsFromDb = await prisma.command.findMany({
|
|
920
|
-
where: { botId },
|
|
921
|
-
include: {
|
|
922
|
-
pluginOwner: {
|
|
923
|
-
select: {
|
|
924
|
-
id: true,
|
|
925
|
-
name: true,
|
|
926
|
-
version: true,
|
|
927
|
-
sourceType: true
|
|
928
|
-
}
|
|
929
|
-
}
|
|
930
|
-
},
|
|
931
|
-
orderBy: [{ owner: 'asc' }, { name: 'asc' }]
|
|
932
|
-
});
|
|
933
|
-
}
|
|
934
|
-
|
|
935
|
-
const finalCommands = dbCommandsFromDb.map(cmd => {
|
|
936
|
-
const template = templatesMap.get(cmd.name);
|
|
937
|
-
let args = [];
|
|
938
|
-
|
|
939
|
-
if (cmd.isVisual) {
|
|
940
|
-
try {
|
|
941
|
-
args = JSON.parse(cmd.argumentsJson || '[]');
|
|
942
|
-
} catch (e) {
|
|
943
|
-
console.error(`Error parsing argumentsJson for visual command ${cmd.name} (ID: ${cmd.id}):`, e);
|
|
944
|
-
args = [];
|
|
945
|
-
}
|
|
946
|
-
} else {
|
|
947
|
-
if (template && template.args && template.args.length > 0) {
|
|
948
|
-
args = template.args;
|
|
949
|
-
} else {
|
|
950
|
-
try {
|
|
951
|
-
args = JSON.parse(cmd.argumentsJson || '[]');
|
|
952
|
-
} catch (e) {
|
|
953
|
-
args = [];
|
|
954
|
-
}
|
|
955
|
-
}
|
|
956
|
-
}
|
|
957
|
-
|
|
958
|
-
return {
|
|
959
|
-
...cmd,
|
|
960
|
-
args: args,
|
|
961
|
-
aliases: JSON.parse(cmd.aliases || '[]'),
|
|
962
|
-
allowedChatTypes: JSON.parse(cmd.allowedChatTypes || '[]'),
|
|
963
|
-
};
|
|
964
|
-
})
|
|
965
|
-
|
|
966
|
-
res.json({
|
|
967
|
-
groups,
|
|
968
|
-
permissions: allPermissions,
|
|
969
|
-
users: {
|
|
970
|
-
items: users,
|
|
971
|
-
total: usersCount,
|
|
972
|
-
page,
|
|
973
|
-
pageSize,
|
|
974
|
-
totalPages: Math.ceil(usersCount / pageSize),
|
|
975
|
-
},
|
|
976
|
-
commands: finalCommands
|
|
977
|
-
});
|
|
978
|
-
|
|
979
|
-
} catch (error) {
|
|
980
|
-
console.error(`[API Error] /management-data for bot ${req.params.botId}:`, error);
|
|
981
|
-
res.status(500).json({ error: 'Не удалось загрузить данные управления' });
|
|
982
|
-
}
|
|
983
|
-
});
|
|
984
|
-
|
|
985
|
-
router.put('/:botId/commands/:commandId', authorize('management:edit'), async (req, res) => {
|
|
986
|
-
try {
|
|
987
|
-
const commandId = parseInt(req.params.commandId, 10);
|
|
988
|
-
const { name, description, cooldown, aliases, permissionId, allowedChatTypes, isEnabled, argumentsJson, graphJson, pluginOwnerId } = req.body;
|
|
989
|
-
|
|
990
|
-
const dataToUpdate = {};
|
|
991
|
-
if (name !== undefined) dataToUpdate.name = name;
|
|
992
|
-
if (description !== undefined) dataToUpdate.description = description;
|
|
993
|
-
if (cooldown !== undefined) dataToUpdate.cooldown = parseInt(cooldown, 10);
|
|
994
|
-
if (aliases !== undefined) dataToUpdate.aliases = Array.isArray(aliases) ? JSON.stringify(aliases) : aliases;
|
|
995
|
-
if (permissionId !== undefined) dataToUpdate.permissionId = permissionId ? parseInt(permissionId, 10) : null;
|
|
996
|
-
if (allowedChatTypes !== undefined) dataToUpdate.allowedChatTypes = Array.isArray(allowedChatTypes) ? JSON.stringify(allowedChatTypes) : allowedChatTypes;
|
|
997
|
-
if (isEnabled !== undefined) dataToUpdate.isEnabled = isEnabled;
|
|
998
|
-
if (argumentsJson !== undefined) dataToUpdate.argumentsJson = Array.isArray(argumentsJson) ? JSON.stringify(argumentsJson) : argumentsJson;
|
|
999
|
-
if (graphJson !== undefined) dataToUpdate.graphJson = graphJson;
|
|
1000
|
-
if (pluginOwnerId !== undefined) dataToUpdate.pluginOwnerId = pluginOwnerId;
|
|
1001
|
-
|
|
1002
|
-
const updatedCommand = await prisma.command.update({
|
|
1003
|
-
where: { id: commandId },
|
|
1004
|
-
data: dataToUpdate,
|
|
1005
|
-
});
|
|
1006
|
-
|
|
1007
|
-
if (graphJson && updatedCommand.pluginOwnerId) {
|
|
1008
|
-
try {
|
|
1009
|
-
const plugin = await prisma.installedPlugin.findUnique({
|
|
1010
|
-
where: { id: updatedCommand.pluginOwnerId }
|
|
1011
|
-
});
|
|
1012
|
-
|
|
1013
|
-
if (plugin) {
|
|
1014
|
-
const graphDir = path.join(plugin.path, 'graph');
|
|
1015
|
-
await fse.mkdir(graphDir, { recursive: true });
|
|
1016
|
-
|
|
1017
|
-
const graphFile = path.join(graphDir, `${updatedCommand.name}.json`);
|
|
1018
|
-
await fse.writeJson(graphFile, JSON.parse(graphJson), { spaces: 2 });
|
|
1019
|
-
console.log(`[API] Граф команды ${updatedCommand.name} сохранен в ${graphFile}`);
|
|
1020
|
-
}
|
|
1021
|
-
} catch (error) {
|
|
1022
|
-
console.error(`[API] Ошибка сохранения графа в папку плагина:`, error);
|
|
1023
|
-
}
|
|
1024
|
-
}
|
|
1025
|
-
|
|
1026
|
-
res.json(updatedCommand);
|
|
1027
|
-
} catch (error) {
|
|
1028
|
-
console.error(`[API Error] /commands/:commandId PUT:`, error);
|
|
1029
|
-
res.status(500).json({ error: 'Failed to update command' });
|
|
1030
|
-
}
|
|
1031
|
-
});
|
|
1032
|
-
|
|
1033
|
-
router.post('/:botId/groups', authorize('management:edit'), async (req, res) => {
|
|
1034
|
-
try {
|
|
1035
|
-
const botId = parseInt(req.params.botId);
|
|
1036
|
-
const { name, permissionIds } = req.body;
|
|
1037
|
-
if (!name) return res.status(400).json({ error: "Имя группы обязательно" });
|
|
1038
|
-
|
|
1039
|
-
const newGroup = await prisma.group.create({
|
|
1040
|
-
data: {
|
|
1041
|
-
name,
|
|
1042
|
-
botId,
|
|
1043
|
-
owner: 'admin',
|
|
1044
|
-
permissions: { create: (permissionIds || []).map(id => ({ permissionId: id })) }
|
|
1045
|
-
}
|
|
1046
|
-
});
|
|
1047
|
-
|
|
1048
|
-
botManager.reloadBotConfigInRealTime(botId);
|
|
1049
|
-
|
|
1050
|
-
res.status(201).json(newGroup);
|
|
1051
|
-
} catch (error) {
|
|
1052
|
-
if (error.code === 'P2002') return res.status(409).json({ error: 'Группа с таким именем уже существует для этого бота.' });
|
|
1053
|
-
res.status(500).json({ error: 'Не удалось создать группу.' });
|
|
1054
|
-
}
|
|
1055
|
-
});
|
|
1056
|
-
|
|
1057
|
-
router.put('/:botId/groups/:groupId', authorize('management:edit'), async (req, res) => {
|
|
1058
|
-
try {
|
|
1059
|
-
const botId = parseInt(req.params.botId, 10);
|
|
1060
|
-
const groupId = parseInt(req.params.groupId);
|
|
1061
|
-
const { name, permissionIds } = req.body;
|
|
1062
|
-
if (!name) return res.status(400).json({ error: "Имя группы обязательно" });
|
|
1063
|
-
|
|
1064
|
-
const usersInGroup = await prisma.user.findMany({
|
|
1065
|
-
where: { botId, groups: { some: { groupId } } },
|
|
1066
|
-
select: { username: true }
|
|
1067
|
-
});
|
|
1068
|
-
|
|
1069
|
-
await prisma.$transaction(async (tx) => {
|
|
1070
|
-
await tx.group.update({ where: { id: groupId }, data: { name } });
|
|
1071
|
-
await tx.groupPermission.deleteMany({ where: { groupId } });
|
|
1072
|
-
if (permissionIds && permissionIds.length > 0) {
|
|
1073
|
-
await tx.groupPermission.createMany({
|
|
1074
|
-
data: permissionIds.map(pid => ({ groupId, permissionId: pid })),
|
|
1075
|
-
});
|
|
1076
|
-
}
|
|
1077
|
-
});
|
|
1078
|
-
|
|
1079
|
-
for (const user of usersInGroup) {
|
|
1080
|
-
botManager.invalidateUserCache(botId, user.username);
|
|
1081
|
-
}
|
|
1082
|
-
|
|
1083
|
-
botManager.reloadBotConfigInRealTime(botId);
|
|
1084
|
-
|
|
1085
|
-
res.status(200).send();
|
|
1086
|
-
} catch (error) {
|
|
1087
|
-
if (error.code === 'P2002') return res.status(409).json({ error: 'Группа с таким именем уже существует для этого бота.' });
|
|
1088
|
-
res.status(500).json({ error: 'Не удалось обновить группу.' });
|
|
1089
|
-
}
|
|
1090
|
-
});
|
|
1091
|
-
|
|
1092
|
-
router.delete('/:botId/groups/:groupId', authorize('management:edit'), async (req, res) => {
|
|
1093
|
-
try {
|
|
1094
|
-
const botId = parseInt(req.params.botId, 10);
|
|
1095
|
-
const groupId = parseInt(req.params.groupId);
|
|
1096
|
-
const group = await prisma.group.findUnique({ where: { id: groupId } });
|
|
1097
|
-
if (group && group.owner !== 'admin') {
|
|
1098
|
-
return res.status(403).json({ error: `Нельзя удалить группу с источником "${group.owner}".` });
|
|
1099
|
-
}
|
|
1100
|
-
await prisma.group.delete({ where: { id: groupId } });
|
|
1101
|
-
botManager.reloadBotConfigInRealTime(botId);
|
|
1102
|
-
|
|
1103
|
-
res.status(204).send();
|
|
1104
|
-
} catch (error) { res.status(500).json({ error: 'Не удалось удалить группу.' }); }
|
|
1105
|
-
});
|
|
1106
|
-
|
|
1107
|
-
router.post('/:botId/permissions', authorize('management:edit'), async (req, res) => {
|
|
1108
|
-
try {
|
|
1109
|
-
const botId = parseInt(req.params.botId);
|
|
1110
|
-
const { name, description } = req.body;
|
|
1111
|
-
if (!name) return res.status(400).json({ error: 'Имя права обязательно' });
|
|
1112
|
-
const newPermission = await prisma.permission.create({
|
|
1113
|
-
data: { name, description, botId, owner: 'admin' }
|
|
1114
|
-
});
|
|
1115
|
-
|
|
1116
|
-
botManager.reloadBotConfigInRealTime(botId);
|
|
1117
|
-
|
|
1118
|
-
res.status(201).json(newPermission);
|
|
1119
|
-
} catch (error) {
|
|
1120
|
-
if (error.code === 'P2002') return res.status(409).json({ error: 'Право с таким именем уже существует для этого бота.' });
|
|
1121
|
-
res.status(500).json({ error: 'Не удалось создать право.' });
|
|
1122
|
-
}
|
|
1123
|
-
});
|
|
1124
|
-
|
|
1125
|
-
router.put('/:botId/users/:userId', authorize('management:edit'), async (req, res) => {
|
|
1126
|
-
try {
|
|
1127
|
-
const botId = parseInt(req.params.botId, 10);
|
|
1128
|
-
const userId = parseInt(req.params.userId, 10);
|
|
1129
|
-
const { isBlacklisted, groupIds } = req.body;
|
|
1130
|
-
|
|
1131
|
-
const updateData = {};
|
|
1132
|
-
if (typeof isBlacklisted === 'boolean') {
|
|
1133
|
-
updateData.isBlacklisted = isBlacklisted;
|
|
1134
|
-
}
|
|
1135
|
-
|
|
1136
|
-
if (Array.isArray(groupIds)) {
|
|
1137
|
-
await prisma.userGroup.deleteMany({ where: { userId } });
|
|
1138
|
-
updateData.groups = {
|
|
1139
|
-
create: groupIds.map(gid => ({ groupId: gid })),
|
|
1140
|
-
};
|
|
1141
|
-
}
|
|
1142
|
-
|
|
1143
|
-
const updatedUser = await prisma.user.update({
|
|
1144
|
-
where: { id: userId },
|
|
1145
|
-
data: updateData,
|
|
1146
|
-
include: { groups: true }
|
|
1147
|
-
});
|
|
1148
|
-
|
|
1149
|
-
botManager.invalidateUserCache(botId, updatedUser.username);
|
|
1150
|
-
|
|
1151
|
-
UserService.clearCache(updatedUser.username, botId);
|
|
1152
|
-
|
|
1153
|
-
res.json(updatedUser);
|
|
1154
|
-
|
|
1155
|
-
} catch (error) {
|
|
1156
|
-
console.error(`[API Error] /users/:userId PUT:`, error);
|
|
1157
|
-
res.status(500).json({ error: 'Не удалось обновить пользователя' });
|
|
1158
|
-
}
|
|
1159
|
-
});
|
|
1160
|
-
|
|
1161
|
-
router.post('/start-all', authorize('bot:start_stop'), async (req, res) => {
|
|
1162
|
-
try {
|
|
1163
|
-
console.log('[API] Получен запрос на запуск всех ботов.');
|
|
1164
|
-
const allBots = await prisma.bot.findMany({ include: { server: true, proxy: true } });
|
|
1165
|
-
let startedCount = 0;
|
|
1166
|
-
for (const botConfig of allBots) {
|
|
1167
|
-
if (!botManager.bots.has(botConfig.id)) {
|
|
1168
|
-
await botManager.startBot(botConfig);
|
|
1169
|
-
startedCount++;
|
|
1170
|
-
}
|
|
1171
|
-
}
|
|
1172
|
-
res.json({ success: true, message: `Запущено ${startedCount} ботов.` });
|
|
1173
|
-
} catch (error) {
|
|
1174
|
-
console.error('[API Error] /start-all:', error);
|
|
1175
|
-
res.status(500).json({ error: 'Произошла ошибка при массовом запуске ботов.' });
|
|
1176
|
-
}
|
|
1177
|
-
});
|
|
1178
|
-
|
|
1179
|
-
router.post('/stop-all', authorize('bot:start_stop'), (req, res) => {
|
|
1180
|
-
try {
|
|
1181
|
-
console.log('[API] Получен запрос на остановку всех ботов.');
|
|
1182
|
-
const botIds = Array.from(botManager.bots.keys());
|
|
1183
|
-
let stoppedCount = 0;
|
|
1184
|
-
for (const botId of botIds) {
|
|
1185
|
-
botManager.stopBot(botId);
|
|
1186
|
-
stoppedCount++;
|
|
1187
|
-
}
|
|
1188
|
-
res.json({ success: true, message: `Остановлено ${stoppedCount} ботов.` });
|
|
1189
|
-
} catch (error) {
|
|
1190
|
-
console.error('[API Error] /stop-all:', error);
|
|
1191
|
-
res.status(500).json({ error: 'Произошла ошибка при массовой остановке ботов.' });
|
|
1192
|
-
}
|
|
1193
|
-
});
|
|
1194
|
-
|
|
1195
|
-
router.get('/:id/settings/all', authenticateUniversal, checkBotAccess, authorize('bot:update'), async (req, res) => {
|
|
1196
|
-
try {
|
|
1197
|
-
const botId = parseInt(req.params.id, 10);
|
|
1198
|
-
|
|
1199
|
-
const bot = await prisma.bot.findUnique({
|
|
1200
|
-
where: { id: botId },
|
|
1201
|
-
include: {
|
|
1202
|
-
server: true,
|
|
1203
|
-
proxy: true,
|
|
1204
|
-
installedPlugins: {
|
|
1205
|
-
orderBy: { name: 'asc' }
|
|
1206
|
-
}
|
|
1207
|
-
}
|
|
1208
|
-
});
|
|
1209
|
-
|
|
1210
|
-
if (!bot) {
|
|
1211
|
-
return res.status(404).json({ error: 'Бот не найден' });
|
|
1212
|
-
}
|
|
1213
|
-
|
|
1214
|
-
const allSettings = {
|
|
1215
|
-
bot: {
|
|
1216
|
-
id: bot.id,
|
|
1217
|
-
username: bot.username,
|
|
1218
|
-
prefix: bot.prefix,
|
|
1219
|
-
note: bot.note,
|
|
1220
|
-
owners: bot.owners,
|
|
1221
|
-
serverId: bot.serverId,
|
|
1222
|
-
proxyId: bot.proxyId,
|
|
1223
|
-
proxyHost: bot.proxyHost,
|
|
1224
|
-
proxyPort: bot.proxyPort,
|
|
1225
|
-
proxyUsername: bot.proxyUsername,
|
|
1226
|
-
},
|
|
1227
|
-
plugins: []
|
|
1228
|
-
};
|
|
1229
|
-
|
|
1230
|
-
const pluginSettingsPromises = bot.installedPlugins.map(async (plugin) => {
|
|
1231
|
-
const manifest = plugin.manifest ? JSON.parse(plugin.manifest) : {};
|
|
1232
|
-
|
|
1233
|
-
if (!manifest.settings || Object.keys(manifest.settings).length === 0) {
|
|
1234
|
-
return null;
|
|
1235
|
-
}
|
|
1236
|
-
|
|
1237
|
-
const savedSettings = plugin.settings ? JSON.parse(plugin.settings) : {};
|
|
1238
|
-
let defaultSettings = {};
|
|
1239
|
-
|
|
1240
|
-
for (const key in manifest.settings) {
|
|
1241
|
-
const config = manifest.settings[key];
|
|
1242
|
-
if (config.type === 'json_file' && config.defaultPath) {
|
|
1243
|
-
const configFilePath = path.join(plugin.path, config.defaultPath);
|
|
1244
|
-
try {
|
|
1245
|
-
const fileContent = await fs.readFile(configFilePath, 'utf-8');
|
|
1246
|
-
defaultSettings[key] = JSON.parse(fileContent);
|
|
1247
|
-
} catch (e) { defaultSettings[key] = {}; }
|
|
1248
|
-
} else {
|
|
1249
|
-
try { defaultSettings[key] = JSON.parse(config.default || 'null'); }
|
|
1250
|
-
catch { defaultSettings[key] = config.default; }
|
|
1251
|
-
}
|
|
1252
|
-
}
|
|
1253
|
-
|
|
1254
|
-
const mergedSettings = deepMergeSettings(defaultSettings, savedSettings);
|
|
1255
|
-
|
|
1256
|
-
// Определяем тип настроек (обычные или группированные)
|
|
1257
|
-
const isGrouped = isGroupedSettings(manifest.settings);
|
|
1258
|
-
|
|
1259
|
-
// Фильтруем секретные значения
|
|
1260
|
-
const filteredSettings = filterSecretSettings(mergedSettings, manifest.settings, isGrouped);
|
|
1261
|
-
|
|
1262
|
-
return {
|
|
1263
|
-
id: plugin.id,
|
|
1264
|
-
name: plugin.name,
|
|
1265
|
-
description: plugin.description,
|
|
1266
|
-
isEnabled: plugin.isEnabled,
|
|
1267
|
-
manifest: manifest,
|
|
1268
|
-
settings: filteredSettings
|
|
1269
|
-
};
|
|
1270
|
-
});
|
|
1271
|
-
|
|
1272
|
-
allSettings.plugins = (await Promise.all(pluginSettingsPromises)).filter(Boolean);
|
|
1273
|
-
|
|
1274
|
-
res.json(allSettings);
|
|
1275
|
-
|
|
1276
|
-
} catch (error) {
|
|
1277
|
-
console.error("[API Error] /settings/all GET:", error);
|
|
1278
|
-
res.status(500).json({ error: 'Не удалось загрузить все настройки' });
|
|
1279
|
-
}
|
|
1280
|
-
});
|
|
1281
|
-
|
|
1282
|
-
const nodeRegistry = require('../../core/NodeRegistry');
|
|
1283
|
-
|
|
1284
|
-
router.get('/:botId/visual-editor/nodes', authenticateUniversal, checkBotAccess, authorize('management:view'), (req, res) => {
|
|
1285
|
-
try {
|
|
1286
|
-
const { graphType } = req.query;
|
|
1287
|
-
const nodesByCategory = nodeRegistry.getNodesByCategory(graphType);
|
|
1288
|
-
res.json(nodesByCategory);
|
|
1289
|
-
} catch (error) {
|
|
1290
|
-
console.error('[API Error] /visual-editor/nodes GET:', error);
|
|
1291
|
-
res.status(500).json({ error: 'Failed to get available nodes' });
|
|
1292
|
-
}
|
|
1293
|
-
});
|
|
1294
|
-
|
|
1295
|
-
router.get('/:botId/visual-editor/node-config', authenticateUniversal, checkBotAccess, authorize('management:view'), (req, res) => {
|
|
1296
|
-
try {
|
|
1297
|
-
const { types } = req.query;
|
|
1298
|
-
if (!types) {
|
|
1299
|
-
return res.status(400).json({ error: 'Node types must be provided' });
|
|
1300
|
-
}
|
|
1301
|
-
const typeArray = Array.isArray(types) ? types : [types];
|
|
1302
|
-
const config = nodeRegistry.getNodesByTypes(typeArray);
|
|
1303
|
-
res.json(config);
|
|
1304
|
-
} catch (error) {
|
|
1305
|
-
console.error('[API Error] /visual-editor/node-config GET:', error);
|
|
1306
|
-
res.status(500).json({ error: 'Failed to get node configuration' });
|
|
1307
|
-
}
|
|
1308
|
-
});
|
|
1309
|
-
|
|
1310
|
-
router.get('/:botId/visual-editor/permissions', authenticateUniversal, checkBotAccess, authorize('management:view'), async (req, res) => {
|
|
1311
|
-
try {
|
|
1312
|
-
const botId = parseInt(req.params.botId, 10);
|
|
1313
|
-
const permissions = await prisma.permission.findMany({
|
|
1314
|
-
where: { botId },
|
|
1315
|
-
orderBy: { name: 'asc' }
|
|
1316
|
-
});
|
|
1317
|
-
res.json(permissions);
|
|
1318
|
-
} catch (error) {
|
|
1319
|
-
console.error('[API Error] /visual-editor/permissions GET:', error);
|
|
1320
|
-
res.status(500).json({ error: 'Failed to get permissions' });
|
|
1321
|
-
}
|
|
1322
|
-
});
|
|
1323
|
-
|
|
1324
|
-
router.post('/:botId/commands/visual', authorize('management:edit'), async (req, res) => {
|
|
1325
|
-
try {
|
|
1326
|
-
const botId = parseInt(req.params.botId, 10);
|
|
1327
|
-
const {
|
|
1328
|
-
name,
|
|
1329
|
-
description,
|
|
1330
|
-
aliases = [],
|
|
1331
|
-
permissionId,
|
|
1332
|
-
cooldown = 0,
|
|
1333
|
-
allowedChatTypes = ['chat', 'private'],
|
|
1334
|
-
argumentsJson = '[]',
|
|
1335
|
-
graphJson = 'null'
|
|
1336
|
-
} = req.body;
|
|
1337
|
-
|
|
1338
|
-
if (!name) {
|
|
1339
|
-
return res.status(400).json({ error: 'Command name is required' });
|
|
1340
|
-
}
|
|
1341
|
-
|
|
1342
|
-
const newCommand = await prisma.command.create({
|
|
1343
|
-
data: {
|
|
1344
|
-
botId,
|
|
1345
|
-
name,
|
|
1346
|
-
description,
|
|
1347
|
-
aliases: JSON.stringify(aliases),
|
|
1348
|
-
permissionId: permissionId || null,
|
|
1349
|
-
cooldown,
|
|
1350
|
-
allowedChatTypes: JSON.stringify(allowedChatTypes),
|
|
1351
|
-
isVisual: true,
|
|
1352
|
-
argumentsJson,
|
|
1353
|
-
graphJson,
|
|
1354
|
-
pluginOwnerId: null
|
|
1355
|
-
}
|
|
1356
|
-
});
|
|
1357
|
-
|
|
1358
|
-
botManager.reloadBotConfigInRealTime(botId);
|
|
1359
|
-
res.status(201).json(newCommand);
|
|
1360
|
-
} catch (error) {
|
|
1361
|
-
if (error.code === 'P2002') {
|
|
1362
|
-
return res.status(409).json({ error: 'Command with this name already exists' });
|
|
1363
|
-
}
|
|
1364
|
-
console.error('[API Error] /commands/visual POST:', error);
|
|
1365
|
-
res.status(500).json({ error: 'Failed to create visual command' });
|
|
1366
|
-
}
|
|
1367
|
-
});
|
|
1368
|
-
|
|
1369
|
-
router.put('/:botId/commands/:commandId/visual', authorize('management:edit'), async (req, res) => {
|
|
1370
|
-
try {
|
|
1371
|
-
const botId = parseInt(req.params.botId, 10);
|
|
1372
|
-
const commandId = parseInt(req.params.commandId, 10);
|
|
1373
|
-
const {
|
|
1374
|
-
name,
|
|
1375
|
-
description,
|
|
1376
|
-
aliases,
|
|
1377
|
-
permissionId,
|
|
1378
|
-
cooldown,
|
|
1379
|
-
allowedChatTypes,
|
|
1380
|
-
argumentsJson,
|
|
1381
|
-
graphJson
|
|
1382
|
-
} = req.body;
|
|
1383
|
-
|
|
1384
|
-
const dataToUpdate = { isVisual: true };
|
|
1385
|
-
|
|
1386
|
-
if (name) dataToUpdate.name = name;
|
|
1387
|
-
if (description !== undefined) dataToUpdate.description = description;
|
|
1388
|
-
if (Array.isArray(aliases)) dataToUpdate.aliases = JSON.stringify(aliases);
|
|
1389
|
-
if (permissionId !== undefined) dataToUpdate.permissionId = permissionId || null;
|
|
1390
|
-
if (typeof cooldown === 'number') dataToUpdate.cooldown = cooldown;
|
|
1391
|
-
if (Array.isArray(allowedChatTypes)) dataToUpdate.allowedChatTypes = JSON.stringify(allowedChatTypes);
|
|
1392
|
-
if (argumentsJson !== undefined) dataToUpdate.argumentsJson = argumentsJson;
|
|
1393
|
-
if (graphJson !== undefined) dataToUpdate.graphJson = graphJson;
|
|
1394
|
-
|
|
1395
|
-
const updatedCommand = await prisma.command.update({
|
|
1396
|
-
where: { id: commandId, botId },
|
|
1397
|
-
data: dataToUpdate
|
|
1398
|
-
});
|
|
1399
|
-
|
|
1400
|
-
if (graphJson && updatedCommand.pluginOwnerId) {
|
|
1401
|
-
try {
|
|
1402
|
-
const plugin = await prisma.installedPlugin.findUnique({
|
|
1403
|
-
where: { id: updatedCommand.pluginOwnerId }
|
|
1404
|
-
});
|
|
1405
|
-
|
|
1406
|
-
if (plugin) {
|
|
1407
|
-
const graphDir = path.join(plugin.path, 'graph');
|
|
1408
|
-
await fse.mkdir(graphDir, { recursive: true });
|
|
1409
|
-
|
|
1410
|
-
const graphFile = path.join(graphDir, `${updatedCommand.name}.json`);
|
|
1411
|
-
await fse.writeJson(graphFile, JSON.parse(graphJson), { spaces: 2 });
|
|
1412
|
-
console.log(`[API] Граф команды ${updatedCommand.name} сохранен в ${graphFile}`);
|
|
1413
|
-
}
|
|
1414
|
-
} catch (error) {
|
|
1415
|
-
console.error(`[API] Ошибка сохранения графа в папку плагина:`, error);
|
|
1416
|
-
}
|
|
1417
|
-
}
|
|
1418
|
-
|
|
1419
|
-
botManager.reloadBotConfigInRealTime(botId);
|
|
1420
|
-
res.json(updatedCommand);
|
|
1421
|
-
} catch (error) {
|
|
1422
|
-
if (error.code === 'P2002') {
|
|
1423
|
-
return res.status(409).json({ error: 'Command with this name already exists' });
|
|
1424
|
-
}
|
|
1425
|
-
console.error('[API Error] /commands/:commandId/visual PUT:', error);
|
|
1426
|
-
res.status(500).json({ error: 'Failed to update visual command' });
|
|
1427
|
-
}
|
|
1428
|
-
});
|
|
1429
|
-
|
|
1430
|
-
router.get('/:botId/commands/:commandId/export', authorize('management:view'), async (req, res) => {
|
|
1431
|
-
try {
|
|
1432
|
-
const botId = parseInt(req.params.botId, 10);
|
|
1433
|
-
const commandId = parseInt(req.params.commandId, 10);
|
|
1434
|
-
|
|
1435
|
-
const command = await prisma.command.findUnique({
|
|
1436
|
-
where: { id: commandId, botId: botId },
|
|
1437
|
-
});
|
|
1438
|
-
|
|
1439
|
-
if (!command) {
|
|
1440
|
-
return res.status(404).json({ error: 'Command not found' });
|
|
1441
|
-
}
|
|
1442
|
-
|
|
1443
|
-
const exportData = {
|
|
1444
|
-
version: '1.0',
|
|
1445
|
-
type: 'command',
|
|
1446
|
-
...command
|
|
1447
|
-
};
|
|
1448
|
-
|
|
1449
|
-
delete exportData.id;
|
|
1450
|
-
delete exportData.botId;
|
|
1451
|
-
|
|
1452
|
-
res.json(exportData);
|
|
1453
|
-
} catch (error) {
|
|
1454
|
-
console.error('Failed to export command:', error);
|
|
1455
|
-
res.status(500).json({ error: 'Failed to export command' });
|
|
1456
|
-
}
|
|
1457
|
-
});
|
|
1458
|
-
|
|
1459
|
-
router.post('/:botId/commands/import', authorize('management:edit'), async (req, res) => {
|
|
1460
|
-
try {
|
|
1461
|
-
const botId = parseInt(req.params.botId, 10);
|
|
1462
|
-
const importData = req.body;
|
|
1463
|
-
|
|
1464
|
-
if (importData.type !== 'command') {
|
|
1465
|
-
return res.status(400).json({ error: 'Invalid file type. Expected "command".' });
|
|
1466
|
-
}
|
|
1467
|
-
|
|
1468
|
-
let commandName = importData.name;
|
|
1469
|
-
let counter = 1;
|
|
1470
|
-
|
|
1471
|
-
while (await prisma.command.findFirst({ where: { botId, name: commandName } })) {
|
|
1472
|
-
commandName = `${importData.name}_imported_${counter}`;
|
|
1473
|
-
counter++;
|
|
1474
|
-
}
|
|
1475
|
-
|
|
1476
|
-
let finalGraphJson = importData.graphJson;
|
|
1477
|
-
|
|
1478
|
-
if (finalGraphJson && finalGraphJson !== 'null') {
|
|
1479
|
-
const graph = JSON.parse(finalGraphJson);
|
|
1480
|
-
const nodeIdMap = new Map();
|
|
1481
|
-
|
|
1482
|
-
if (graph.nodes) {
|
|
1483
|
-
graph.nodes.forEach(node => {
|
|
1484
|
-
const oldId = node.id;
|
|
1485
|
-
const newId = `${node.type}-${randomUUID()}`;
|
|
1486
|
-
nodeIdMap.set(oldId, newId);
|
|
1487
|
-
node.id = newId;
|
|
1488
|
-
});
|
|
1489
|
-
}
|
|
1490
|
-
|
|
1491
|
-
if (graph.connections) {
|
|
1492
|
-
graph.connections.forEach(conn => {
|
|
1493
|
-
conn.id = `edge-${randomUUID()}`;
|
|
1494
|
-
conn.sourceNodeId = nodeIdMap.get(conn.sourceNodeId) || conn.sourceNodeId;
|
|
1495
|
-
conn.targetNodeId = nodeIdMap.get(conn.targetNodeId) || conn.targetNodeId;
|
|
1496
|
-
});
|
|
1497
|
-
}
|
|
1498
|
-
|
|
1499
|
-
finalGraphJson = JSON.stringify(graph);
|
|
1500
|
-
}
|
|
1501
|
-
|
|
1502
|
-
const newCommand = await prisma.command.create({
|
|
1503
|
-
data: {
|
|
1504
|
-
botId: botId,
|
|
1505
|
-
name: commandName,
|
|
1506
|
-
description: importData.description,
|
|
1507
|
-
aliases: importData.aliases,
|
|
1508
|
-
permissionId: null,
|
|
1509
|
-
cooldown: importData.cooldown,
|
|
1510
|
-
allowedChatTypes: importData.allowedChatTypes,
|
|
1511
|
-
isVisual: importData.isVisual,
|
|
1512
|
-
isEnabled: importData.isEnabled,
|
|
1513
|
-
argumentsJson: importData.argumentsJson,
|
|
1514
|
-
graphJson: finalGraphJson,
|
|
1515
|
-
owner: 'visual_editor',
|
|
1516
|
-
}
|
|
1517
|
-
});
|
|
1518
|
-
|
|
1519
|
-
botManager.reloadBotConfigInRealTime(botId);
|
|
1520
|
-
res.status(201).json(newCommand);
|
|
1521
|
-
} catch (error) {
|
|
1522
|
-
console.error("Failed to import command:", error);
|
|
1523
|
-
res.status(500).json({ error: 'Failed to import command' });
|
|
1524
|
-
}
|
|
1525
|
-
});
|
|
1526
|
-
|
|
1527
|
-
router.post('/:botId/commands', authorize('management:edit'), async (req, res) => {
|
|
1528
|
-
try {
|
|
1529
|
-
const botId = parseInt(req.params.botId, 10);
|
|
1530
|
-
const {
|
|
1531
|
-
name,
|
|
1532
|
-
description,
|
|
1533
|
-
aliases = [],
|
|
1534
|
-
permissionId,
|
|
1535
|
-
cooldown = 0,
|
|
1536
|
-
allowedChatTypes = ['chat', 'private'],
|
|
1537
|
-
isVisual = false,
|
|
1538
|
-
argumentsJson = '[]',
|
|
1539
|
-
graphJson = 'null'
|
|
1540
|
-
} = req.body;
|
|
1541
|
-
|
|
1542
|
-
if (!name) {
|
|
1543
|
-
return res.status(400).json({ error: 'Command name is required' });
|
|
1544
|
-
}
|
|
1545
|
-
|
|
1546
|
-
const newCommand = await prisma.command.create({
|
|
1547
|
-
data: {
|
|
1548
|
-
botId,
|
|
1549
|
-
name,
|
|
1550
|
-
description,
|
|
1551
|
-
aliases: JSON.stringify(aliases),
|
|
1552
|
-
permissionId: permissionId || null,
|
|
1553
|
-
cooldown,
|
|
1554
|
-
allowedChatTypes: JSON.stringify(allowedChatTypes),
|
|
1555
|
-
isVisual,
|
|
1556
|
-
argumentsJson,
|
|
1557
|
-
graphJson,
|
|
1558
|
-
owner: isVisual ? 'visual_editor' : 'manual',
|
|
1559
|
-
pluginOwnerId: null
|
|
1560
|
-
}
|
|
1561
|
-
});
|
|
1562
|
-
|
|
1563
|
-
if (graphJson && graphJson !== 'null' && req.body.pluginOwnerId) {
|
|
1564
|
-
try {
|
|
1565
|
-
const plugin = await prisma.installedPlugin.findUnique({
|
|
1566
|
-
where: { id: req.body.pluginOwnerId }
|
|
1567
|
-
});
|
|
1568
|
-
|
|
1569
|
-
if (plugin) {
|
|
1570
|
-
const graphDir = path.join(plugin.path, 'graph');
|
|
1571
|
-
await fse.mkdir(graphDir, { recursive: true });
|
|
1572
|
-
|
|
1573
|
-
const graphFile = path.join(graphDir, `${name}.json`);
|
|
1574
|
-
await fse.writeJson(graphFile, JSON.parse(graphJson), { spaces: 2 });
|
|
1575
|
-
console.log(`[API] Граф команды ${name} сохранен в ${graphFile}`);
|
|
1576
|
-
}
|
|
1577
|
-
} catch (error) {
|
|
1578
|
-
console.error(`[API] Ошибка сохранения графа в папку плагина:`, error);
|
|
1579
|
-
}
|
|
1580
|
-
}
|
|
1581
|
-
|
|
1582
|
-
botManager.reloadBotConfigInRealTime(botId);
|
|
1583
|
-
res.status(201).json(newCommand);
|
|
1584
|
-
} catch (error) {
|
|
1585
|
-
if (error.code === 'P2002') {
|
|
1586
|
-
return res.status(409).json({ error: 'Command with this name already exists' });
|
|
1587
|
-
}
|
|
1588
|
-
console.error('[API Error] /commands POST:', error);
|
|
1589
|
-
res.status(500).json({ error: 'Failed to create command' });
|
|
1590
|
-
}
|
|
1591
|
-
});
|
|
1592
|
-
|
|
1593
|
-
router.delete('/:botId/commands/:commandId', authorize('management:edit'), async (req, res) => {
|
|
1594
|
-
try {
|
|
1595
|
-
const botId = parseInt(req.params.botId, 10);
|
|
1596
|
-
const commandId = parseInt(req.params.commandId, 10);
|
|
1597
|
-
|
|
1598
|
-
await prisma.command.delete({
|
|
1599
|
-
where: { id: commandId, botId: botId },
|
|
1600
|
-
});
|
|
1601
|
-
|
|
1602
|
-
botManager.reloadBotConfigInRealTime(botId);
|
|
1603
|
-
res.status(204).send();
|
|
1604
|
-
} catch (error) {
|
|
1605
|
-
console.error(`[API Error] /commands/:commandId DELETE:`, error);
|
|
1606
|
-
res.status(500).json({ error: 'Failed to delete command' });
|
|
1607
|
-
}
|
|
1608
|
-
});
|
|
1609
|
-
|
|
1610
|
-
router.get('/:botId/event-graphs/:graphId', authorize('management:view'), async (req, res) => {
|
|
1611
|
-
try {
|
|
1612
|
-
const botId = parseInt(req.params.botId, 10);
|
|
1613
|
-
const graphId = parseInt(req.params.graphId, 10);
|
|
1614
|
-
|
|
1615
|
-
const eventGraph = await prisma.eventGraph.findUnique({
|
|
1616
|
-
where: { id: graphId, botId },
|
|
1617
|
-
include: { triggers: true },
|
|
1618
|
-
});
|
|
1619
|
-
|
|
1620
|
-
if (!eventGraph) {
|
|
1621
|
-
return res.status(404).json({ error: 'Граф события не найден' });
|
|
1622
|
-
}
|
|
1623
|
-
|
|
1624
|
-
res.json(eventGraph);
|
|
1625
|
-
} catch (error) {
|
|
1626
|
-
console.error(`[API Error] /event-graphs/:graphId GET:`, error);
|
|
1627
|
-
res.status(500).json({ error: 'Не удалось получить граф события' });
|
|
1628
|
-
}
|
|
1629
|
-
});
|
|
1630
|
-
|
|
1631
|
-
router.post('/:botId/event-graphs', authorize('management:edit'), async (req, res) => {
|
|
1632
|
-
try {
|
|
1633
|
-
const botId = parseInt(req.params.botId, 10);
|
|
1634
|
-
const { name, description, graphJson, variables, eventType, isEnabled = true } = req.body;
|
|
1635
|
-
|
|
1636
|
-
if (!name || typeof name !== 'string' || name.trim() === '') {
|
|
1637
|
-
return res.status(400).json({ error: 'Имя графа обязательно и должно быть непустой строкой' });
|
|
1638
|
-
}
|
|
1639
|
-
|
|
1640
|
-
let graphJsonString;
|
|
1641
|
-
if (graphJson) {
|
|
1642
|
-
if (typeof graphJson === 'string') {
|
|
1643
|
-
graphJsonString = graphJson;
|
|
1644
|
-
} else {
|
|
1645
|
-
graphJsonString = JSON.stringify(graphJson);
|
|
1646
|
-
}
|
|
1647
|
-
} else {
|
|
1648
|
-
graphJsonString = JSON.stringify({
|
|
1649
|
-
nodes: [],
|
|
1650
|
-
connections: []
|
|
1651
|
-
});
|
|
1652
|
-
}
|
|
1653
|
-
|
|
1654
|
-
console.log('[API] Final graphJsonString:', graphJsonString);
|
|
1655
|
-
|
|
1656
|
-
let eventTypes = [];
|
|
1657
|
-
try {
|
|
1658
|
-
const parsedGraph = JSON.parse(graphJsonString);
|
|
1659
|
-
if (parsedGraph.nodes && Array.isArray(parsedGraph.nodes)) {
|
|
1660
|
-
const eventNodes = parsedGraph.nodes.filter(node => node.type && node.type.startsWith('event:'));
|
|
1661
|
-
eventTypes = [...new Set(eventNodes.map(node => node.type.split(':')[1]))];
|
|
1662
|
-
}
|
|
1663
|
-
} catch (error) {
|
|
1664
|
-
console.warn('[API] Не удалось извлечь типы событий из графа:', error.message);
|
|
1665
|
-
}
|
|
1666
|
-
|
|
1667
|
-
const newEventGraph = await prisma.eventGraph.create({
|
|
1668
|
-
data: {
|
|
1669
|
-
botId,
|
|
1670
|
-
name: name.trim(),
|
|
1671
|
-
description: description || '',
|
|
1672
|
-
isEnabled: isEnabled,
|
|
1673
|
-
graphJson: graphJsonString,
|
|
1674
|
-
variables: variables || '[]',
|
|
1675
|
-
eventType: eventType || 'custom',
|
|
1676
|
-
triggers: {
|
|
1677
|
-
create: eventTypes.map(eventType => ({ eventType }))
|
|
1678
|
-
}
|
|
1679
|
-
},
|
|
1680
|
-
include: { triggers: true }
|
|
1681
|
-
});
|
|
1682
|
-
|
|
1683
|
-
console.log('[API] Created event graph:', newEventGraph);
|
|
1684
|
-
res.status(201).json(newEventGraph);
|
|
1685
|
-
} catch (error) {
|
|
1686
|
-
if (error.code === 'P2002') {
|
|
1687
|
-
return res.status(409).json({ error: 'Граф событий с таким именем уже существует' });
|
|
1688
|
-
}
|
|
1689
|
-
console.error(`[API Error] /event-graphs POST:`, error);
|
|
1690
|
-
res.status(500).json({ error: 'Не удалось создать граф событий' });
|
|
1691
|
-
}
|
|
1692
|
-
});
|
|
1693
|
-
|
|
1694
|
-
router.delete('/:botId/event-graphs/:graphId', authorize('management:edit'), async (req, res) => {
|
|
1695
|
-
try {
|
|
1696
|
-
const botId = parseInt(req.params.botId, 10);
|
|
1697
|
-
const graphId = parseInt(req.params.graphId, 10);
|
|
1698
|
-
|
|
1699
|
-
await prisma.eventGraph.delete({
|
|
1700
|
-
where: { id: graphId, botId: botId },
|
|
1701
|
-
});
|
|
1702
|
-
|
|
1703
|
-
res.status(204).send();
|
|
1704
|
-
} catch (error) {
|
|
1705
|
-
console.error(`[API Error] /event-graphs/:graphId DELETE:`, error);
|
|
1706
|
-
res.status(500).json({ error: 'Не удалось удалить граф событий' });
|
|
1707
|
-
}
|
|
1708
|
-
});
|
|
1709
|
-
|
|
1710
|
-
router.put('/:botId/event-graphs/:graphId', authorize('management:edit'), async (req, res) => {
|
|
1711
|
-
const { botId, graphId } = req.params;
|
|
1712
|
-
const { name, isEnabled, graphJson, variables, pluginOwnerId } = req.body;
|
|
1713
|
-
|
|
1714
|
-
if (!name || typeof name !== 'string' || name.trim() === '') {
|
|
1715
|
-
return res.status(400).json({ error: 'Поле name обязательно и должно быть непустой строкой.' });
|
|
1716
|
-
}
|
|
1717
|
-
|
|
1718
|
-
if (typeof isEnabled !== 'boolean') {
|
|
1719
|
-
return res.status(400).json({ error: 'Поле isEnabled должно быть true или false.' });
|
|
1720
|
-
}
|
|
1721
|
-
|
|
1722
|
-
try {
|
|
1723
|
-
const dataToUpdate = {
|
|
1724
|
-
name: name.trim(),
|
|
1725
|
-
isEnabled,
|
|
1726
|
-
};
|
|
1727
|
-
|
|
1728
|
-
if (graphJson !== undefined) {
|
|
1729
|
-
dataToUpdate.graphJson = graphJson;
|
|
1730
|
-
}
|
|
1731
|
-
|
|
1732
|
-
if (variables !== undefined) {
|
|
1733
|
-
dataToUpdate.variables = Array.isArray(variables) ? JSON.stringify(variables) : variables;
|
|
1734
|
-
}
|
|
1735
|
-
|
|
1736
|
-
if (pluginOwnerId !== undefined) {
|
|
1737
|
-
dataToUpdate.pluginOwnerId = pluginOwnerId;
|
|
1738
|
-
}
|
|
1739
|
-
|
|
1740
|
-
const updatedGraph = await prisma.eventGraph.update({
|
|
1741
|
-
where: { id: parseInt(graphId), botId: parseInt(botId) },
|
|
1742
|
-
data: dataToUpdate
|
|
1743
|
-
});
|
|
1744
|
-
|
|
1745
|
-
res.json(updatedGraph);
|
|
1746
|
-
} catch (error) {
|
|
1747
|
-
console.error(`[API Error] /event-graphs/:graphId PUT:`, error);
|
|
1748
|
-
res.status(500).json({ error: 'Ошибка при обновлении графа событий.' });
|
|
1749
|
-
}
|
|
1750
|
-
});
|
|
1751
|
-
|
|
1752
|
-
router.post('/:botId/visual-editor/save', authorize('management:edit'), async (req, res) => {
|
|
1753
|
-
});
|
|
1754
|
-
|
|
1755
|
-
router.get('/:botId/ui-extensions', authorize('plugin:list'), async (req, res) => {
|
|
1756
|
-
try {
|
|
1757
|
-
const botId = parseInt(req.params.botId, 10);
|
|
1758
|
-
const enabledPlugins = await prisma.installedPlugin.findMany({
|
|
1759
|
-
where: { botId: botId, isEnabled: true }
|
|
1760
|
-
});
|
|
1761
|
-
|
|
1762
|
-
const extensions = [];
|
|
1763
|
-
for (const plugin of enabledPlugins) {
|
|
1764
|
-
if (plugin.manifest) {
|
|
1765
|
-
try {
|
|
1766
|
-
const manifest = JSON.parse(plugin.manifest);
|
|
1767
|
-
if (manifest.uiExtensions && Array.isArray(manifest.uiExtensions)) {
|
|
1768
|
-
manifest.uiExtensions.forEach(ext => {
|
|
1769
|
-
extensions.push({
|
|
1770
|
-
pluginName: plugin.name,
|
|
1771
|
-
...ext
|
|
1772
|
-
});
|
|
1773
|
-
});
|
|
1774
|
-
}
|
|
1775
|
-
} catch (e) {
|
|
1776
|
-
console.error(`Ошибка парсинга манифеста для плагина ${plugin.name}:`, e);
|
|
1777
|
-
}
|
|
1778
|
-
}
|
|
1779
|
-
}
|
|
1780
|
-
res.json(extensions);
|
|
1781
|
-
} catch (error) {
|
|
1782
|
-
res.status(500).json({ error: 'Не удалось получить расширения интерфейса' });
|
|
1783
|
-
}
|
|
1784
|
-
});
|
|
1785
|
-
|
|
1786
|
-
router.get('/:botId/plugins/:pluginName/ui-content/:path', authorize('plugin:list'), async (req, res) => {
|
|
1787
|
-
const { botId, pluginName, path: uiPath } = req.params;
|
|
1788
|
-
const numericBotId = parseInt(botId, 10);
|
|
1789
|
-
|
|
1790
|
-
try {
|
|
1791
|
-
const plugin = await prisma.installedPlugin.findFirst({
|
|
1792
|
-
where: { botId: numericBotId, name: pluginName, isEnabled: true }
|
|
1793
|
-
});
|
|
1794
|
-
|
|
1795
|
-
if (!plugin) {
|
|
1796
|
-
return res.status(404).json({ error: `Активный плагин "${pluginName}" не найден для этого бота.` });
|
|
1797
|
-
}
|
|
1798
|
-
|
|
1799
|
-
const manifest = plugin.manifest ? JSON.parse(plugin.manifest) : {};
|
|
1800
|
-
const savedSettings = plugin.settings ? JSON.parse(plugin.settings) : {};
|
|
1801
|
-
const defaultSettings = {};
|
|
1802
|
-
|
|
1803
|
-
if (manifest.settings) {
|
|
1804
|
-
for (const key in manifest.settings) {
|
|
1805
|
-
const config = manifest.settings[key];
|
|
1806
|
-
if (config.type === 'json_file' && config.defaultPath) {
|
|
1807
|
-
const configFilePath = path.join(plugin.path, config.defaultPath);
|
|
1808
|
-
try {
|
|
1809
|
-
const fileContent = await fs.readFile(configFilePath, 'utf-8');
|
|
1810
|
-
defaultSettings[key] = JSON.parse(fileContent);
|
|
1811
|
-
} catch (e) { defaultSettings[key] = {}; }
|
|
1812
|
-
} else {
|
|
1813
|
-
try { defaultSettings[key] = JSON.parse(config.default || 'null'); }
|
|
1814
|
-
catch { defaultSettings[key] = config.default; }
|
|
1815
|
-
}
|
|
1816
|
-
}
|
|
1817
|
-
}
|
|
1818
|
-
const finalSettings = { ...defaultSettings, ...savedSettings };
|
|
1819
|
-
|
|
1820
|
-
const mainFilePath = manifest.main || 'index.js';
|
|
1821
|
-
const pluginEntryPoint = path.join(plugin.path, mainFilePath);
|
|
1822
|
-
|
|
1823
|
-
delete require.cache[require.resolve(pluginEntryPoint)];
|
|
1824
|
-
const pluginModule = require(pluginEntryPoint);
|
|
1825
|
-
|
|
1826
|
-
if (typeof pluginModule.getUiPageContent !== 'function') {
|
|
1827
|
-
return res.status(501).json({ error: `Плагин "${pluginName}" не предоставляет кастомный UI контент.` });
|
|
1828
|
-
}
|
|
1829
|
-
|
|
1830
|
-
const botProcess = botManager.bots.get(numericBotId);
|
|
1831
|
-
const botApi = botProcess ? botProcess.api : null;
|
|
1832
|
-
|
|
1833
|
-
const content = await pluginModule.getUiPageContent({
|
|
1834
|
-
path: uiPath,
|
|
1835
|
-
bot: botApi,
|
|
1836
|
-
botId: numericBotId,
|
|
1837
|
-
settings: finalSettings
|
|
1838
|
-
});
|
|
1839
|
-
|
|
1840
|
-
if (content === null) {
|
|
1841
|
-
return res.status(404).json({ error: `Для пути "${uiPath}" не найдено содержимого в плагине "${pluginName}".` });
|
|
1842
|
-
}
|
|
1843
|
-
|
|
1844
|
-
res.json(content);
|
|
1845
|
-
|
|
1846
|
-
} catch (error) {
|
|
1847
|
-
console.error(`[UI Content] Ошибка при получении контента для плагина "${pluginName}":`, error);
|
|
1848
|
-
res.status(500).json({ error: error.message || 'Внутренняя ошибка сервера.' });
|
|
1849
|
-
}
|
|
1850
|
-
});
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
router.post('/:botId/plugins/:pluginName/action', authorize('plugin:list'), async (req, res) => {
|
|
1854
|
-
const { botId, pluginName } = req.params;
|
|
1855
|
-
const { actionName, payload } = req.body;
|
|
1856
|
-
const numericBotId = parseInt(botId, 10);
|
|
1857
|
-
|
|
1858
|
-
if (!actionName) {
|
|
1859
|
-
return res.status(400).json({ error: 'Необходимо указать "actionName".' });
|
|
1860
|
-
}
|
|
1861
|
-
|
|
1862
|
-
try {
|
|
1863
|
-
const botProcess = botManager.bots.get(numericBotId);
|
|
1864
|
-
|
|
1865
|
-
if (!botProcess) {
|
|
1866
|
-
return res.status(404).json({ error: 'Бот не найден или не запущен.' });
|
|
1867
|
-
}
|
|
1868
|
-
|
|
1869
|
-
const plugin = await prisma.installedPlugin.findFirst({
|
|
1870
|
-
where: { botId: numericBotId, name: pluginName, isEnabled: true }
|
|
1871
|
-
});
|
|
1872
|
-
|
|
1873
|
-
if (!plugin) {
|
|
1874
|
-
return res.status(404).json({ error: `Активный плагин с таким именем "${pluginName}" не найден.` });
|
|
1875
|
-
}
|
|
1876
|
-
|
|
1877
|
-
const manifest = plugin.manifest ? JSON.parse(plugin.manifest) : {};
|
|
1878
|
-
const savedSettings = plugin.settings ? JSON.parse(plugin.settings) : {};
|
|
1879
|
-
const defaultSettings = {};
|
|
1880
|
-
|
|
1881
|
-
if (manifest.settings) {
|
|
1882
|
-
for (const key in manifest.settings) {
|
|
1883
|
-
const config = manifest.settings[key];
|
|
1884
|
-
if (config.type === 'json_file' && config.defaultPath) {
|
|
1885
|
-
const configFilePath = path.join(plugin.path, config.defaultPath);
|
|
1886
|
-
try {
|
|
1887
|
-
const fileContent = await fs.readFile(configFilePath, 'utf-8');
|
|
1888
|
-
defaultSettings[key] = JSON.parse(fileContent);
|
|
1889
|
-
} catch (e) {
|
|
1890
|
-
console.error(`[Action] Не удалось прочитать defaultPath для ${pluginName}: ${e.message}`);
|
|
1891
|
-
defaultSettings[key] = {};
|
|
1892
|
-
}
|
|
1893
|
-
} else {
|
|
1894
|
-
try {
|
|
1895
|
-
defaultSettings[key] = JSON.parse(config.default || 'null');
|
|
1896
|
-
} catch {
|
|
1897
|
-
defaultSettings[key] = config.default;
|
|
1898
|
-
}
|
|
1899
|
-
}
|
|
1900
|
-
}
|
|
1901
|
-
}
|
|
1902
|
-
const finalSettings = { ...defaultSettings, ...savedSettings };
|
|
1903
|
-
|
|
1904
|
-
const mainFilePath = manifest.main || 'index.js';
|
|
1905
|
-
const pluginPath = path.join(plugin.path, mainFilePath);
|
|
1906
|
-
|
|
1907
|
-
delete require.cache[require.resolve(pluginPath)];
|
|
1908
|
-
const pluginModule = require(pluginPath);
|
|
1909
|
-
|
|
1910
|
-
if (typeof pluginModule.handleAction !== 'function') {
|
|
1911
|
-
return res.status(501).json({ error: `Плагин "${pluginName}" не поддерживает обработку действий.` });
|
|
1912
|
-
}
|
|
1913
|
-
|
|
1914
|
-
const result = await pluginModule.handleAction({
|
|
1915
|
-
botProcess: botProcess,
|
|
1916
|
-
botId: numericBotId,
|
|
1917
|
-
action: actionName,
|
|
1918
|
-
payload: payload,
|
|
1919
|
-
settings: finalSettings
|
|
1920
|
-
});
|
|
1921
|
-
|
|
1922
|
-
res.json({ success: true, message: 'Действие выполнено.', result: result || null });
|
|
1923
|
-
|
|
1924
|
-
} catch (error) {
|
|
1925
|
-
console.error(`Ошибка выполнения действия "${actionName}" для плагина "${pluginName}":`, error);
|
|
1926
|
-
res.status(500).json({ error: error.message || 'Внутренняя ошибка сервера.' });
|
|
1927
|
-
}
|
|
1928
|
-
});
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
router.get('/:botId/export', authenticateUniversal, checkBotAccess, authorize('bot:export'), async (req, res) => {
|
|
1932
|
-
try {
|
|
1933
|
-
const botId = parseInt(req.params.botId, 10);
|
|
1934
|
-
const {
|
|
1935
|
-
includeCommands,
|
|
1936
|
-
includePermissions,
|
|
1937
|
-
includePluginFiles,
|
|
1938
|
-
includePluginDataStore,
|
|
1939
|
-
includeEventGraphs,
|
|
1940
|
-
} = req.query;
|
|
1941
|
-
|
|
1942
|
-
const bot = await prisma.bot.findUnique({ where: { id: botId } });
|
|
1943
|
-
if (!bot) {
|
|
1944
|
-
return res.status(404).json({ error: 'Bot not found' });
|
|
1945
|
-
}
|
|
1946
|
-
|
|
1947
|
-
const archive = archiver('zip', { zlib: { level: 9 } });
|
|
1948
|
-
res.attachment(`bot_${bot.username}_export_${new Date().toISOString()}.zip`);
|
|
1949
|
-
archive.pipe(res);
|
|
1950
|
-
|
|
1951
|
-
const botData = { ...bot };
|
|
1952
|
-
delete botData.password;
|
|
1953
|
-
delete botData.proxyPassword;
|
|
1954
|
-
archive.append(JSON.stringify(botData, null, 2), { name: 'bot.json' });
|
|
1955
|
-
|
|
1956
|
-
if (includeCommands === 'true') {
|
|
1957
|
-
const commands = await prisma.command.findMany({ where: { botId } });
|
|
1958
|
-
archive.append(JSON.stringify(commands, null, 2), { name: 'commands.json' });
|
|
1959
|
-
}
|
|
1960
|
-
|
|
1961
|
-
if (includePermissions === 'true') {
|
|
1962
|
-
const users = await prisma.user.findMany({ where: { botId }, include: { groups: { include: { group: true } } } });
|
|
1963
|
-
const groups = await prisma.group.findMany({ where: { botId }, include: { permissions: { include: { permission: true } } } });
|
|
1964
|
-
const permissions = await prisma.permission.findMany({ where: { botId } });
|
|
1965
|
-
const permissionsData = { users, groups, permissions };
|
|
1966
|
-
archive.append(JSON.stringify(permissionsData, null, 2), { name: 'permissions.json' });
|
|
1967
|
-
}
|
|
1968
|
-
|
|
1969
|
-
if (includeEventGraphs === 'true') {
|
|
1970
|
-
const eventGraphs = await prisma.eventGraph.findMany({ where: { botId } });
|
|
1971
|
-
archive.append(JSON.stringify(eventGraphs, null, 2), { name: 'event_graphs.json' });
|
|
1972
|
-
}
|
|
1973
|
-
|
|
1974
|
-
if (includePluginFiles === 'true' || includePluginDataStore === 'true') {
|
|
1975
|
-
const installedPlugins = await prisma.installedPlugin.findMany({ where: { botId } });
|
|
1976
|
-
archive.append(JSON.stringify(installedPlugins, null, 2), { name: 'plugins.json' });
|
|
1977
|
-
|
|
1978
|
-
try {
|
|
1979
|
-
const installedPlugins = await prisma.installedPlugin.findMany({ where: { botId } });
|
|
1980
|
-
const pluginSettings = installedPlugins
|
|
1981
|
-
.filter(plugin => plugin.settings && plugin.settings !== '{}')
|
|
1982
|
-
.map(plugin => ({
|
|
1983
|
-
pluginName: plugin.name,
|
|
1984
|
-
settings: plugin.settings
|
|
1985
|
-
}));
|
|
1986
|
-
|
|
1987
|
-
if (pluginSettings.length > 0) {
|
|
1988
|
-
console.log(`[Export] Экспорт настроек плагинов для бота ${botId}: ${pluginSettings.length} настроек`);
|
|
1989
|
-
archive.append(JSON.stringify(pluginSettings, null, 2), { name: 'settings.json' });
|
|
1990
|
-
} else {
|
|
1991
|
-
console.log(`[Export] Нет настроек плагинов для экспорта`);
|
|
1992
|
-
}
|
|
1993
|
-
} catch (error) {
|
|
1994
|
-
console.warn(`[Export] Ошибка при экспорте настроек плагинов:`, error.message);
|
|
1995
|
-
}
|
|
1996
|
-
|
|
1997
|
-
if (includePluginFiles === 'true') {
|
|
1998
|
-
for (const plugin of installedPlugins) {
|
|
1999
|
-
const pluginPath = plugin.path;
|
|
2000
|
-
if (await fs.stat(pluginPath).then(s => s.isDirectory()).catch(() => false)) {
|
|
2001
|
-
archive.directory(pluginPath, `plugins/${plugin.name}`);
|
|
2002
|
-
}
|
|
2003
|
-
}
|
|
2004
|
-
}
|
|
2005
|
-
if (includePluginDataStore === 'true') {
|
|
2006
|
-
console.log(`[Export] Экспорт PluginDataStore для бота ${botId}`);
|
|
2007
|
-
const pluginDataStore = await prisma.pluginDataStore.findMany({
|
|
2008
|
-
where: { botId: parseInt(botId) }
|
|
2009
|
-
});
|
|
2010
|
-
console.log(`[Export] Найдено записей PluginDataStore: ${pluginDataStore.length}`);
|
|
2011
|
-
if (pluginDataStore.length > 0) {
|
|
2012
|
-
archive.append(JSON.stringify(pluginDataStore, null, 2), { name: 'plugin_data_store.json' });
|
|
2013
|
-
console.log(`[Export] Данные PluginDataStore добавлены в архив`);
|
|
2014
|
-
} else {
|
|
2015
|
-
console.log(`[Export] Нет данных PluginDataStore для экспорта`);
|
|
2016
|
-
}
|
|
2017
|
-
}
|
|
2018
|
-
}
|
|
2019
|
-
|
|
2020
|
-
await archive.finalize();
|
|
2021
|
-
|
|
2022
|
-
} catch (error) {
|
|
2023
|
-
console.error('Failed to export bot:', error);
|
|
2024
|
-
if (!res.headersSent) {
|
|
2025
|
-
res.status(500).json({ error: `Failed to export bot: ${error.message}` });
|
|
2026
|
-
}
|
|
2027
|
-
}
|
|
2028
|
-
});
|
|
2029
|
-
|
|
2030
|
-
router.post('/import', authorize('bot:create'), upload.single('file'), async (req, res) => {
|
|
2031
|
-
if (!req.file) {
|
|
2032
|
-
return res.status(400).json({ error: 'No file uploaded.' });
|
|
2033
|
-
}
|
|
2034
|
-
|
|
2035
|
-
const botIdMap = new Map();
|
|
2036
|
-
|
|
2037
|
-
try {
|
|
2038
|
-
const zip = new AdmZip(req.file.buffer);
|
|
2039
|
-
const zipEntries = zip.getEntries();
|
|
2040
|
-
|
|
2041
|
-
const botDataEntry = zipEntries.find(e => e.entryName === 'bot.json');
|
|
2042
|
-
if (!botDataEntry) {
|
|
2043
|
-
return res.status(400).json({ error: 'Archive missing bot.json' });
|
|
2044
|
-
}
|
|
2045
|
-
const botData = JSON.parse(botDataEntry.getData().toString('utf8'));
|
|
2046
|
-
|
|
2047
|
-
const server = await prisma.server.findFirst();
|
|
2048
|
-
if (!server) {
|
|
2049
|
-
return res.status(500).json({ error: 'No servers configured in the target system.' });
|
|
2050
|
-
}
|
|
2051
|
-
|
|
2052
|
-
let newBotName = botData.username;
|
|
2053
|
-
let counter = 1;
|
|
2054
|
-
while (await prisma.bot.findFirst({ where: { username: newBotName } })) {
|
|
2055
|
-
newBotName = `${botData.username}_imported_${counter}`;
|
|
2056
|
-
counter++;
|
|
2057
|
-
}
|
|
2058
|
-
|
|
2059
|
-
const newBot = await prisma.bot.create({
|
|
2060
|
-
data: {
|
|
2061
|
-
...botData,
|
|
2062
|
-
id: undefined,
|
|
2063
|
-
username: newBotName,
|
|
2064
|
-
serverId: server.id,
|
|
2065
|
-
password: null,
|
|
2066
|
-
proxyPassword: null
|
|
2067
|
-
},
|
|
2068
|
-
include: { server: true }
|
|
2069
|
-
});
|
|
2070
|
-
|
|
2071
|
-
botIdMap.set(botData.id, newBot.id);
|
|
2072
|
-
|
|
2073
|
-
const permissionsEntry = zipEntries.find(e => e.entryName === 'permissions.json');
|
|
2074
|
-
let pMap = new Map();
|
|
2075
|
-
|
|
2076
|
-
if (permissionsEntry) {
|
|
2077
|
-
const { users, groups, permissions } = JSON.parse(permissionsEntry.getData().toString('utf8'));
|
|
2078
|
-
|
|
2079
|
-
await setupDefaultPermissionsForBot(newBot.id, prisma);
|
|
2080
|
-
|
|
2081
|
-
for(let p of permissions.filter(p=>p.owner === 'system')) {
|
|
2082
|
-
const existingPermission = await prisma.permission.findFirst({
|
|
2083
|
-
where: {
|
|
2084
|
-
botId: newBot.id,
|
|
2085
|
-
name: p.name,
|
|
2086
|
-
owner: 'system'
|
|
2087
|
-
}
|
|
2088
|
-
});
|
|
2089
|
-
if (existingPermission) {
|
|
2090
|
-
pMap.set(p.id, existingPermission.id);
|
|
2091
|
-
}
|
|
2092
|
-
}
|
|
2093
|
-
|
|
2094
|
-
for(let p of permissions.filter(p=>p.owner !== 'system')) {
|
|
2095
|
-
const newP = await prisma.permission.create({ data: { ...p, id: undefined, botId: newBot.id }});
|
|
2096
|
-
pMap.set(p.id, newP.id);
|
|
2097
|
-
}
|
|
2098
|
-
|
|
2099
|
-
const gMap = new Map();
|
|
2100
|
-
for(let g of groups.filter(g=>g.owner !== 'system')) {
|
|
2101
|
-
const newG = await prisma.group.create({ data: { ...g, id: undefined, botId: newBot.id, permissions: {
|
|
2102
|
-
create: g.permissions.map(gp => ({ permissionId: pMap.get(gp.permissionId) })).filter(p=>p.permissionId)
|
|
2103
|
-
}}});
|
|
2104
|
-
gMap.set(g.id, newG.id);
|
|
2105
|
-
}
|
|
2106
|
-
|
|
2107
|
-
for(let u of users) {
|
|
2108
|
-
await prisma.user.create({ data: { ...u, id: undefined, botId: newBot.id, groups: {
|
|
2109
|
-
create: u.groups.map(ug => ({ groupId: gMap.get(ug.groupId) })).filter(g=>g.groupId)
|
|
2110
|
-
}}});
|
|
2111
|
-
}
|
|
2112
|
-
}
|
|
2113
|
-
|
|
2114
|
-
const pluginDataStoreEntry = zipEntries.find(e => e.entryName === 'plugin_data_store.json');
|
|
2115
|
-
if (pluginDataStoreEntry) {
|
|
2116
|
-
console.log(`[Import] Импорт PluginDataStore для бота ${newBot.id}`);
|
|
2117
|
-
const pluginDataStore = JSON.parse(pluginDataStoreEntry.getData().toString('utf8'));
|
|
2118
|
-
console.log(`[Import] Найдено записей PluginDataStore: ${pluginDataStore.length}`);
|
|
2119
|
-
|
|
2120
|
-
for (let dataRecord of pluginDataStore) {
|
|
2121
|
-
delete dataRecord.id;
|
|
2122
|
-
dataRecord.botId = newBot.id;
|
|
2123
|
-
await prisma.pluginDataStore.create({ data: dataRecord });
|
|
2124
|
-
}
|
|
2125
|
-
console.log(`[Import] PluginDataStore успешно импортирован`);
|
|
2126
|
-
}
|
|
2127
|
-
|
|
2128
|
-
const pluginsEntry = zipEntries.find(e => e.entryName === 'plugins.json');
|
|
2129
|
-
let pluginMap = new Map();
|
|
2130
|
-
|
|
2131
|
-
if (pluginsEntry) {
|
|
2132
|
-
const plugins = JSON.parse(pluginsEntry.getData().toString('utf8'));
|
|
2133
|
-
const pluginsDir = path.join(os.homedir(), '.blockmine', 'storage', 'plugins');
|
|
2134
|
-
const botPluginsDir = path.join(pluginsDir, newBot.username);
|
|
2135
|
-
await fs.mkdir(botPluginsDir, { recursive: true });
|
|
2136
|
-
|
|
2137
|
-
for (let pluginData of plugins) {
|
|
2138
|
-
const oldPath = pluginData.path;
|
|
2139
|
-
const pluginName = pluginData.name;
|
|
2140
|
-
const newPluginPath = path.join(botPluginsDir, pluginName);
|
|
2141
|
-
|
|
2142
|
-
const oldPluginId = pluginData.id;
|
|
2143
|
-
delete pluginData.id;
|
|
2144
|
-
pluginData.botId = newBot.id;
|
|
2145
|
-
pluginData.path = path.resolve(newPluginPath);
|
|
2146
|
-
|
|
2147
|
-
for (const entry of zipEntries) {
|
|
2148
|
-
if (entry.entryName.startsWith(`plugins/${pluginName}/`)) {
|
|
2149
|
-
const relativePath = entry.entryName.replace(`plugins/${pluginName}/`, '');
|
|
2150
|
-
if (relativePath) {
|
|
2151
|
-
const destPath = path.join(newPluginPath, relativePath);
|
|
2152
|
-
const destDir = path.dirname(destPath);
|
|
2153
|
-
await fs.mkdir(destDir, { recursive: true });
|
|
2154
|
-
|
|
2155
|
-
if (!entry.isDirectory) {
|
|
2156
|
-
await fs.writeFile(destPath, entry.getData());
|
|
2157
|
-
}
|
|
2158
|
-
}
|
|
2159
|
-
}
|
|
2160
|
-
}
|
|
2161
|
-
|
|
2162
|
-
try {
|
|
2163
|
-
await pluginManager._installDependencies(newPluginPath);
|
|
2164
|
-
} catch (e) {
|
|
2165
|
-
console.warn(`[Import] Не удалось установить зависимости для плагина ${pluginName}: ${e.message}`);
|
|
2166
|
-
}
|
|
2167
|
-
|
|
2168
|
-
let newPlugin;
|
|
2169
|
-
try {
|
|
2170
|
-
newPlugin = await pluginManager.registerPlugin(newBot.id, newPluginPath, 'LOCAL', newPluginPath);
|
|
2171
|
-
} catch (e) {
|
|
2172
|
-
newPlugin = await prisma.installedPlugin.create({ data: pluginData });
|
|
2173
|
-
}
|
|
2174
|
-
pluginMap.set(oldPluginId, newPlugin.id);
|
|
2175
|
-
}
|
|
2176
|
-
}
|
|
2177
|
-
|
|
2178
|
-
const commandsEntry = zipEntries.find(e => e.entryName === 'commands.json');
|
|
2179
|
-
if (commandsEntry) {
|
|
2180
|
-
const commands = JSON.parse(commandsEntry.getData().toString('utf8'));
|
|
2181
|
-
for (let command of commands) {
|
|
2182
|
-
delete command.id;
|
|
2183
|
-
command.botId = newBot.id;
|
|
2184
|
-
|
|
2185
|
-
if (command.permissionId && pMap.has(command.permissionId)) {
|
|
2186
|
-
command.permissionId = pMap.get(command.permissionId);
|
|
2187
|
-
} else {
|
|
2188
|
-
command.permissionId = null;
|
|
2189
|
-
}
|
|
2190
|
-
|
|
2191
|
-
if (command.pluginOwnerId && pluginMap.has(command.pluginOwnerId)) {
|
|
2192
|
-
command.pluginOwnerId = pluginMap.get(command.pluginOwnerId);
|
|
2193
|
-
} else {
|
|
2194
|
-
command.pluginOwnerId = null;
|
|
2195
|
-
}
|
|
2196
|
-
|
|
2197
|
-
try {
|
|
2198
|
-
await prisma.command.create({ data: command });
|
|
2199
|
-
} catch (error) {
|
|
2200
|
-
console.warn(`[Import] Пропущена команда ${command.name}: ${error.message}`);
|
|
2201
|
-
}
|
|
2202
|
-
}
|
|
2203
|
-
}
|
|
2204
|
-
|
|
2205
|
-
const eventGraphsEntry = zipEntries.find(e => e.entryName === 'event_graphs.json');
|
|
2206
|
-
if (eventGraphsEntry) {
|
|
2207
|
-
const eventGraphs = JSON.parse(eventGraphsEntry.getData().toString('utf8'));
|
|
2208
|
-
for (let graph of eventGraphs) {
|
|
2209
|
-
delete graph.id;
|
|
2210
|
-
graph.botId = newBot.id;
|
|
2211
|
-
|
|
2212
|
-
if (graph.pluginOwnerId && pluginMap.has(graph.pluginOwnerId)) {
|
|
2213
|
-
graph.pluginOwnerId = pluginMap.get(graph.pluginOwnerId);
|
|
2214
|
-
} else {
|
|
2215
|
-
graph.pluginOwnerId = null;
|
|
2216
|
-
}
|
|
2217
|
-
|
|
2218
|
-
try {
|
|
2219
|
-
await prisma.eventGraph.create({ data: graph });
|
|
2220
|
-
} catch (error) {
|
|
2221
|
-
console.warn(`[Import] Пропущен граф ${graph.name}: ${error.message}`);
|
|
2222
|
-
}
|
|
2223
|
-
}
|
|
2224
|
-
}
|
|
2225
|
-
|
|
2226
|
-
res.status(201).json(newBot);
|
|
2227
|
-
|
|
2228
|
-
} catch (error) {
|
|
2229
|
-
console.error('Failed to import bot:', error);
|
|
2230
|
-
res.status(500).json({ error: `Failed to import bot: ${error.message}` });
|
|
2231
|
-
}
|
|
2232
|
-
});
|
|
2233
|
-
|
|
2234
|
-
router.post('/import/preview', authorize('bot:create'), upload.single('file'), async (req, res) => {
|
|
2235
|
-
try {
|
|
2236
|
-
if (!req.file) {
|
|
2237
|
-
return res.status(400).json({ error: 'Файл не загружен' });
|
|
2238
|
-
}
|
|
2239
|
-
|
|
2240
|
-
const tempDir = path.join(os.tmpdir(), `import-${Date.now()}`);
|
|
2241
|
-
await fse.ensureDir(tempDir);
|
|
2242
|
-
|
|
2243
|
-
try {
|
|
2244
|
-
const zip = new AdmZip(req.file.buffer);
|
|
2245
|
-
zip.extractAllTo(tempDir, true);
|
|
2246
|
-
|
|
2247
|
-
console.log('[Import] Файлы в архиве:', zip.getEntries().map(entry => entry.entryName));
|
|
2248
|
-
|
|
2249
|
-
const importData = {
|
|
2250
|
-
plugins: [],
|
|
2251
|
-
commands: [],
|
|
2252
|
-
eventGraphs: [],
|
|
2253
|
-
settings: null,
|
|
2254
|
-
bot: null
|
|
2255
|
-
};
|
|
2256
|
-
|
|
2257
|
-
const botConfigPath = path.join(tempDir, 'bot.json');
|
|
2258
|
-
if (await fse.pathExists(botConfigPath)) {
|
|
2259
|
-
console.log('[Import] Найден bot.json');
|
|
2260
|
-
const botConfig = JSON.parse(await fse.readFile(botConfigPath, 'utf8'));
|
|
2261
|
-
delete botConfig.password;
|
|
2262
|
-
delete botConfig.proxyPassword;
|
|
2263
|
-
delete botConfig.id;
|
|
2264
|
-
delete botConfig.createdAt;
|
|
2265
|
-
delete botConfig.updatedAt;
|
|
2266
|
-
importData.bot = botConfig;
|
|
2267
|
-
} else {
|
|
2268
|
-
console.log('[Import] bot.json не найден');
|
|
2269
|
-
}
|
|
2270
|
-
|
|
2271
|
-
const pluginsPath = path.join(tempDir, 'plugins.json');
|
|
2272
|
-
if (await fse.pathExists(pluginsPath)) {
|
|
2273
|
-
console.log('[Import] Найден plugins.json');
|
|
2274
|
-
importData.plugins = JSON.parse(await fse.readFile(pluginsPath, 'utf8'));
|
|
2275
|
-
console.log('[Import] Плагинов:', importData.plugins.length);
|
|
2276
|
-
} else {
|
|
2277
|
-
console.log('[Import] plugins.json не найден');
|
|
2278
|
-
}
|
|
2279
|
-
|
|
2280
|
-
const commandsPath = path.join(tempDir, 'commands.json');
|
|
2281
|
-
if (await fse.pathExists(commandsPath)) {
|
|
2282
|
-
console.log('[Import] Найден commands.json');
|
|
2283
|
-
importData.commands = JSON.parse(await fse.readFile(commandsPath, 'utf8'));
|
|
2284
|
-
console.log('[Import] Команд:', importData.commands.length);
|
|
2285
|
-
} else {
|
|
2286
|
-
console.log('[Import] commands.json не найден');
|
|
2287
|
-
}
|
|
2288
|
-
|
|
2289
|
-
const eventGraphsPath = path.join(tempDir, 'event_graphs.json');
|
|
2290
|
-
if (await fse.pathExists(eventGraphsPath)) {
|
|
2291
|
-
console.log('[Import] Найден event_graphs.json');
|
|
2292
|
-
importData.eventGraphs = JSON.parse(await fse.readFile(eventGraphsPath, 'utf8'));
|
|
2293
|
-
console.log('[Import] Графов событий:', importData.eventGraphs.length);
|
|
2294
|
-
} else {
|
|
2295
|
-
console.log('[Import] event_graphs.json не найден');
|
|
2296
|
-
const eventGraphsPathAlt = path.join(tempDir, 'event-graphs.json');
|
|
2297
|
-
if (await fse.pathExists(eventGraphsPathAlt)) {
|
|
2298
|
-
console.log('[Import] Найден event-graphs.json');
|
|
2299
|
-
importData.eventGraphs = JSON.parse(await fse.readFile(eventGraphsPathAlt, 'utf8'));
|
|
2300
|
-
console.log('[Import] Графов событий:', importData.eventGraphs.length);
|
|
2301
|
-
} else {
|
|
2302
|
-
console.log('[Import] event-graphs.json тоже не найден');
|
|
2303
|
-
}
|
|
2304
|
-
}
|
|
2305
|
-
|
|
2306
|
-
const settingsPath = path.join(tempDir, 'settings.json');
|
|
2307
|
-
if (await fse.pathExists(settingsPath)) {
|
|
2308
|
-
console.log('[Import] Найден settings.json');
|
|
2309
|
-
importData.settings = JSON.parse(await fse.readFile(settingsPath, 'utf8'));
|
|
2310
|
-
} else {
|
|
2311
|
-
console.log('[Import] settings.json не найден');
|
|
2312
|
-
}
|
|
2313
|
-
|
|
2314
|
-
console.log('[Import] Итоговые данные:', {
|
|
2315
|
-
plugins: importData.plugins.length,
|
|
2316
|
-
commands: importData.commands.length,
|
|
2317
|
-
eventGraphs: importData.eventGraphs.length,
|
|
2318
|
-
hasSettings: !!importData.settings,
|
|
2319
|
-
hasBot: !!importData.bot
|
|
2320
|
-
});
|
|
2321
|
-
|
|
2322
|
-
res.json(importData);
|
|
2323
|
-
|
|
2324
|
-
} finally {
|
|
2325
|
-
await fse.remove(tempDir);
|
|
2326
|
-
}
|
|
2327
|
-
|
|
2328
|
-
} catch (error) {
|
|
2329
|
-
console.error('[API Error] /bots/import/preview:', error);
|
|
2330
|
-
res.status(500).json({ error: 'Не удалось обработать архив импорта' });
|
|
2331
|
-
}
|
|
2332
|
-
});
|
|
2333
|
-
|
|
2334
|
-
router.post('/import/create', authorize('bot:create'), async (req, res) => {
|
|
2335
|
-
try {
|
|
2336
|
-
const { username, password, prefix, serverId, note, owners, proxyHost, proxyPort, proxyUsername, proxyPassword, importData } = req.body;
|
|
2337
|
-
|
|
2338
|
-
if (!username || !serverId) {
|
|
2339
|
-
return res.status(400).json({ error: 'Имя и сервер обязательны' });
|
|
2340
|
-
}
|
|
2341
|
-
|
|
2342
|
-
const botData = {
|
|
2343
|
-
username,
|
|
2344
|
-
prefix,
|
|
2345
|
-
note,
|
|
2346
|
-
serverId: parseInt(serverId, 10),
|
|
2347
|
-
password: password ? encrypt(password) : null,
|
|
2348
|
-
owners: owners || '',
|
|
2349
|
-
proxyHost: proxyHost || null,
|
|
2350
|
-
proxyPort: proxyPort ? parseInt(proxyPort, 10) : null,
|
|
2351
|
-
proxyUsername: proxyUsername || null,
|
|
2352
|
-
proxyPassword: proxyPassword ? encrypt(proxyPassword) : null
|
|
2353
|
-
};
|
|
2354
|
-
|
|
2355
|
-
const newBot = await prisma.bot.create({
|
|
2356
|
-
data: botData,
|
|
2357
|
-
include: { server: true }
|
|
2358
|
-
});
|
|
2359
|
-
|
|
2360
|
-
await setupDefaultPermissionsForBot(newBot.id);
|
|
2361
|
-
|
|
2362
|
-
if (importData) {
|
|
2363
|
-
try {
|
|
2364
|
-
if (importData.plugins && Array.isArray(importData.plugins)) {
|
|
2365
|
-
for (const plugin of importData.plugins) {
|
|
2366
|
-
try {
|
|
2367
|
-
await prisma.installedPlugin.create({
|
|
2368
|
-
data: {
|
|
2369
|
-
...plugin,
|
|
2370
|
-
botId: newBot.id,
|
|
2371
|
-
id: undefined
|
|
2372
|
-
}
|
|
2373
|
-
});
|
|
2374
|
-
console.log(`[Import] Импортирован плагин ${plugin.name}`);
|
|
2375
|
-
} catch (error) {
|
|
2376
|
-
console.warn(`[Import] Не удалось импортировать плагин ${plugin.name}:`, error.message);
|
|
2377
|
-
}
|
|
2378
|
-
}
|
|
2379
|
-
}
|
|
2380
|
-
|
|
2381
|
-
if (importData.commands && Array.isArray(importData.commands)) {
|
|
2382
|
-
for (const command of importData.commands) {
|
|
2383
|
-
try {
|
|
2384
|
-
await prisma.command.create({
|
|
2385
|
-
data: {
|
|
2386
|
-
...command,
|
|
2387
|
-
botId: newBot.id,
|
|
2388
|
-
id: undefined
|
|
2389
|
-
}
|
|
2390
|
-
});
|
|
2391
|
-
} catch (error) {
|
|
2392
|
-
console.warn(`[Import] Не удалось импортировать команду ${command.name}:`, error.message);
|
|
2393
|
-
}
|
|
2394
|
-
}
|
|
2395
|
-
}
|
|
2396
|
-
|
|
2397
|
-
if (importData.eventGraphs && Array.isArray(importData.eventGraphs)) {
|
|
2398
|
-
for (const graph of importData.eventGraphs) {
|
|
2399
|
-
try {
|
|
2400
|
-
await prisma.eventGraph.create({
|
|
2401
|
-
data: {
|
|
2402
|
-
...graph,
|
|
2403
|
-
botId: newBot.id,
|
|
2404
|
-
id: undefined
|
|
2405
|
-
}
|
|
2406
|
-
});
|
|
2407
|
-
} catch (error) {
|
|
2408
|
-
console.warn(`[Import] Не удалось импортировать граф событий ${graph.name}:`, error.message);
|
|
2409
|
-
}
|
|
2410
|
-
}
|
|
2411
|
-
}
|
|
2412
|
-
|
|
2413
|
-
if (importData.settings && Array.isArray(importData.settings)) {
|
|
2414
|
-
for (const setting of importData.settings) {
|
|
2415
|
-
try {
|
|
2416
|
-
const updated = await prisma.installedPlugin.updateMany({
|
|
2417
|
-
where: {
|
|
2418
|
-
botId: newBot.id,
|
|
2419
|
-
name: setting.pluginName
|
|
2420
|
-
},
|
|
2421
|
-
data: {
|
|
2422
|
-
settings: setting.settings
|
|
2423
|
-
}
|
|
2424
|
-
});
|
|
2425
|
-
if (updated.count > 0) {
|
|
2426
|
-
console.log(`[Import] Импортированы настройки плагина ${setting.pluginName}`);
|
|
2427
|
-
} else {
|
|
2428
|
-
console.warn(`[Import] Плагин ${setting.pluginName} не найден для применения настроек`);
|
|
2429
|
-
}
|
|
2430
|
-
} catch (error) {
|
|
2431
|
-
console.warn(`[Import] Не удалось импортировать настройки плагина ${setting.pluginName}:`, error.message);
|
|
2432
|
-
}
|
|
2433
|
-
}
|
|
2434
|
-
}
|
|
2435
|
-
|
|
2436
|
-
} catch (error) {
|
|
2437
|
-
console.error('[Import] Ошибка при импорте данных:', error);
|
|
2438
|
-
}
|
|
2439
|
-
}
|
|
2440
|
-
|
|
2441
|
-
res.status(201).json(newBot);
|
|
2442
|
-
} catch (error) {
|
|
2443
|
-
if (error.code === 'P2002') {
|
|
2444
|
-
return res.status(409).json({ error: 'Бот с таким именем уже существует' });
|
|
2445
|
-
}
|
|
2446
|
-
console.error("[API Error] /bots/import/create:", error);
|
|
2447
|
-
res.status(500).json({ error: 'Не удалось создать бота с импортированными данными' });
|
|
2448
|
-
}
|
|
2449
|
-
});
|
|
2450
|
-
|
|
2451
|
-
module.exports = router;
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const prisma = require('../../lib/prisma');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const fs = require('fs/promises');
|
|
5
|
+
const fse = require('fs-extra');
|
|
6
|
+
const { botManager, pluginManager } = require('../../core/services');
|
|
7
|
+
const UserService = require('../../core/UserService');
|
|
8
|
+
const commandManager = require('../../core/system/CommandManager');
|
|
9
|
+
const NodeRegistry = require('../../core/NodeRegistry');
|
|
10
|
+
const { authenticate, authenticateUniversal, authorize } = require('../middleware/auth');
|
|
11
|
+
const { encrypt } = require('../../core/utils/crypto');
|
|
12
|
+
const { randomUUID } = require('crypto');
|
|
13
|
+
const eventGraphsRouter = require('./eventGraphs');
|
|
14
|
+
const pluginIdeRouter = require('./pluginIde');
|
|
15
|
+
const apiKeysRouter = require('./apiKeys');
|
|
16
|
+
const { deepMergeSettings } = require('../../core/utils/settingsMerger');
|
|
17
|
+
const { checkBotAccess } = require('../middleware/botAccess');
|
|
18
|
+
const { filterSecretSettings, prepareSettingsForSave, isGroupedSettings } = require('../../core/utils/secretsFilter');
|
|
19
|
+
|
|
20
|
+
const multer = require('multer');
|
|
21
|
+
const archiver = require('archiver');
|
|
22
|
+
const AdmZip = require('adm-zip');
|
|
23
|
+
const os = require('os');
|
|
24
|
+
|
|
25
|
+
const upload = multer({ storage: multer.memoryStorage() });
|
|
26
|
+
|
|
27
|
+
const router = express.Router();
|
|
28
|
+
|
|
29
|
+
const conditionalRestartAuth = (req, res, next) => {
|
|
30
|
+
if (process.env.DEBUG === 'true' || process.env.NODE_ENV === 'development') {
|
|
31
|
+
console.log('[Debug] Роут перезапуска бота доступен без проверки прав');
|
|
32
|
+
return next();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return authenticateUniversal(req, res, (err) => {
|
|
36
|
+
if (err) return next(err);
|
|
37
|
+
return authorize('bot:start_stop')(req, res, next);
|
|
38
|
+
});
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const conditionalChatAuth = (req, res, next) => {
|
|
42
|
+
if (process.env.DEBUG === 'true' || process.env.NODE_ENV === 'development') {
|
|
43
|
+
console.log('[Debug] Роут отправки сообщения боту доступен без проверки прав');
|
|
44
|
+
return next();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return authenticateUniversal(req, res, (err) => {
|
|
48
|
+
if (err) return next(err);
|
|
49
|
+
return authorize('bot:interact')(req, res, next);
|
|
50
|
+
});
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const conditionalStartStopAuth = (req, res, next) => {
|
|
54
|
+
if (process.env.DEBUG === 'true' || process.env.NODE_ENV === 'development') {
|
|
55
|
+
console.log('[Debug] Роут запуска/остановки бота доступен без проверки прав');
|
|
56
|
+
return next();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return authenticateUniversal(req, res, (err) => {
|
|
60
|
+
if (err) return next(err);
|
|
61
|
+
return authorize('bot:start_stop')(req, res, next);
|
|
62
|
+
});
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const conditionalListAuth = (req, res, next) => {
|
|
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
|
+
if (err) return next(err);
|
|
70
|
+
if (process.env.DEBUG === 'true' || process.env.NODE_ENV === 'development') {
|
|
71
|
+
console.log('[conditionalListAuth] DEBUG mode, skipping authorize');
|
|
72
|
+
return next();
|
|
73
|
+
}
|
|
74
|
+
console.log('[conditionalListAuth] Calling authorize(bot:list)');
|
|
75
|
+
return authorize('bot:list')(req, res, next);
|
|
76
|
+
});
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
router.post('/:id/restart', conditionalRestartAuth, authenticateUniversal, checkBotAccess, async (req, res) => {
|
|
80
|
+
try {
|
|
81
|
+
const botId = parseInt(req.params.id, 10);
|
|
82
|
+
botManager.stopBot(botId);
|
|
83
|
+
setTimeout(async () => {
|
|
84
|
+
const botConfig = await prisma.bot.findUnique({ where: { id: botId }, include: { server: true, proxy: true } });
|
|
85
|
+
if (!botConfig) {
|
|
86
|
+
return res.status(404).json({ success: false, message: 'Бот не найден' });
|
|
87
|
+
}
|
|
88
|
+
botManager.startBot(botConfig);
|
|
89
|
+
res.status(202).json({ success: true, message: 'Команда на перезапуск отправлена.' });
|
|
90
|
+
}, 1000);
|
|
91
|
+
} catch (error) {
|
|
92
|
+
console.error(`[API] Ошибка перезапуска бота ${req.params.id}:`, error);
|
|
93
|
+
res.status(500).json({ success: false, message: 'Ошибка при перезапуске бота: ' + error.message });
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
router.post('/:id/chat', conditionalChatAuth, authenticateUniversal, checkBotAccess, (req, res) => {
|
|
98
|
+
try {
|
|
99
|
+
const botId = parseInt(req.params.id, 10);
|
|
100
|
+
const { message } = req.body;
|
|
101
|
+
if (!message) return res.status(400).json({ error: 'Сообщение не может быть пустым' });
|
|
102
|
+
const result = botManager.sendMessageToBot(botId, message);
|
|
103
|
+
if (result.success) res.json({ success: true });
|
|
104
|
+
else res.status(404).json(result);
|
|
105
|
+
} catch (error) { res.status(500).json({ error: 'Внутренняя ошибка сервера: ' + error.message }); }
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
router.post('/:id/start', conditionalStartStopAuth, authenticateUniversal, checkBotAccess, async (req, res) => {
|
|
109
|
+
try {
|
|
110
|
+
const botId = parseInt(req.params.id, 10);
|
|
111
|
+
const botConfig = await prisma.bot.findUnique({ where: { id: botId }, include: { server: true, proxy: true } });
|
|
112
|
+
if (!botConfig) {
|
|
113
|
+
return res.status(404).json({ success: false, message: 'Бот не найден' });
|
|
114
|
+
}
|
|
115
|
+
botManager.startBot(botConfig);
|
|
116
|
+
res.status(202).json({ success: true, message: 'Команда на запуск отправлена.' });
|
|
117
|
+
} catch (error) {
|
|
118
|
+
console.error(`[API] Ошибка запуска бота ${req.params.id}:`, error);
|
|
119
|
+
res.status(500).json({ success: false, message: 'Ошибка при запуске бота: ' + error.message });
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
router.post('/:id/stop', conditionalStartStopAuth, authenticateUniversal, checkBotAccess, (req, res) => {
|
|
124
|
+
try {
|
|
125
|
+
const botId = parseInt(req.params.id, 10);
|
|
126
|
+
botManager.stopBot(botId);
|
|
127
|
+
res.status(202).json({ success: true, message: 'Команда на остановку отправлена.' });
|
|
128
|
+
} catch (error) {
|
|
129
|
+
console.error(`[API] Ошибка остановки бота ${req.params.id}:`, error);
|
|
130
|
+
res.status(500).json({ success: false, message: 'Ошибка при остановке бота: ' + error.message });
|
|
131
|
+
}
|
|
132
|
+
});
|
|
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
|
+
|
|
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));
|
|
143
|
+
try {
|
|
144
|
+
const botsWithoutSortOrder = await prisma.bot.findMany({
|
|
145
|
+
where: { sortOrder: null },
|
|
146
|
+
select: { id: true }
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
if (botsWithoutSortOrder.length > 0) {
|
|
150
|
+
console.log(`[API] Обновляем sortOrder для ${botsWithoutSortOrder.length} ботов`);
|
|
151
|
+
|
|
152
|
+
const maxSortOrder = await prisma.bot.aggregate({
|
|
153
|
+
_max: { sortOrder: true }
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
let nextSortOrder = (maxSortOrder._max.sortOrder || 0) + 1;
|
|
157
|
+
|
|
158
|
+
for (const bot of botsWithoutSortOrder) {
|
|
159
|
+
await prisma.bot.update({
|
|
160
|
+
where: { id: bot.id },
|
|
161
|
+
data: { sortOrder: nextSortOrder }
|
|
162
|
+
});
|
|
163
|
+
console.log(`[API] Установлен sortOrder ${nextSortOrder} для бота ${bot.id}`);
|
|
164
|
+
nextSortOrder++;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
let whereFilter = {};
|
|
169
|
+
if (req.user && typeof req.user.userId === 'number') {
|
|
170
|
+
const panelUser = await prisma.panelUser.findUnique({
|
|
171
|
+
where: { id: req.user.userId },
|
|
172
|
+
include: { botAccess: { select: { botId: true } } }
|
|
173
|
+
});
|
|
174
|
+
if (panelUser && panelUser.allBots === false) {
|
|
175
|
+
const allowedIds = panelUser.botAccess.map(a => a.botId);
|
|
176
|
+
whereFilter = { id: { in: allowedIds.length ? allowedIds : [-1] } };
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const bots = await prisma.bot.findMany({
|
|
181
|
+
where: whereFilter,
|
|
182
|
+
include: { server: true },
|
|
183
|
+
orderBy: { sortOrder: 'asc' }
|
|
184
|
+
});
|
|
185
|
+
console.log(`[API /api/bots GET] Отправляем ${bots.length} ботов, status: 200`);
|
|
186
|
+
res.json(bots);
|
|
187
|
+
} catch (error) {
|
|
188
|
+
console.error("[API /api/bots] Ошибка получения списка ботов:", error);
|
|
189
|
+
res.status(500).json({ error: 'Не удалось получить список ботов' });
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
router.get('/:id', conditionalListAuth, async (req, res) => {
|
|
194
|
+
try {
|
|
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
|
+
}
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
router.put('/bulk-proxy-update', authenticateUniversal, authorize('bot:update'), async (req, res) => {
|
|
214
|
+
try {
|
|
215
|
+
const { botIds, proxySettings } = req.body;
|
|
216
|
+
|
|
217
|
+
if (!Array.isArray(botIds) || botIds.length === 0) {
|
|
218
|
+
return res.status(400).json({ error: 'Bot IDs array is required and cannot be empty' });
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (!proxySettings || !proxySettings.proxyHost || !proxySettings.proxyPort) {
|
|
222
|
+
return res.status(400).json({ error: 'Proxy host and port are required' });
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (proxySettings.proxyPort < 1 || proxySettings.proxyPort > 65535) {
|
|
226
|
+
return res.status(400).json({ error: 'Proxy port must be between 1 and 65535' });
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const accessibleBots = [];
|
|
230
|
+
const inaccessibleBots = [];
|
|
231
|
+
|
|
232
|
+
for (const botId of botIds) {
|
|
233
|
+
try {
|
|
234
|
+
const userId = req.user?.userId;
|
|
235
|
+
if (!userId) {
|
|
236
|
+
inaccessibleBots.push(botId);
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const botIdInt = parseInt(botId, 10);
|
|
241
|
+
if (isNaN(botIdInt)) {
|
|
242
|
+
inaccessibleBots.push(botId);
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const user = await prisma.panelUser.findUnique({
|
|
247
|
+
where: { id: userId },
|
|
248
|
+
include: { botAccess: { select: { botId: true } } }
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
if (!user) {
|
|
252
|
+
inaccessibleBots.push(botId);
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (user.allBots !== false || user.botAccess.some((a) => a.botId === botIdInt)) {
|
|
257
|
+
accessibleBots.push(botIdInt);
|
|
258
|
+
} else {
|
|
259
|
+
inaccessibleBots.push(botId);
|
|
260
|
+
}
|
|
261
|
+
} catch (error) {
|
|
262
|
+
console.error(`Error checking access for bot ${botId}:`, error);
|
|
263
|
+
inaccessibleBots.push(botId);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (accessibleBots.length === 0) {
|
|
268
|
+
return res.status(403).json({ error: 'No accessible bots in the provided list' });
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const encryptedSettings = {
|
|
272
|
+
proxyHost: proxySettings.proxyHost.trim(),
|
|
273
|
+
proxyPort: parseInt(proxySettings.proxyPort),
|
|
274
|
+
proxyUsername: proxySettings.proxyUsername ? proxySettings.proxyUsername.trim() : null,
|
|
275
|
+
proxyPassword: proxySettings.proxyPassword ? encrypt(proxySettings.proxyPassword) : null
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
const updatedBots = await prisma.$transaction(
|
|
279
|
+
accessibleBots.map(botId =>
|
|
280
|
+
prisma.bot.update({
|
|
281
|
+
where: { id: parseInt(botId) },
|
|
282
|
+
data: encryptedSettings,
|
|
283
|
+
include: {
|
|
284
|
+
server: {
|
|
285
|
+
select: {
|
|
286
|
+
id: true,
|
|
287
|
+
name: true,
|
|
288
|
+
host: true,
|
|
289
|
+
port: true,
|
|
290
|
+
version: true
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
})
|
|
295
|
+
)
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
if (req.io) {
|
|
299
|
+
req.io.emit('bots-updated', updatedBots);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
res.json({
|
|
303
|
+
success: true,
|
|
304
|
+
message: `Proxy settings updated for ${updatedBots.length} bot(s)`,
|
|
305
|
+
updatedBots: updatedBots.map(bot => ({
|
|
306
|
+
id: bot.id,
|
|
307
|
+
username: bot.username,
|
|
308
|
+
proxyHost: bot.proxyHost,
|
|
309
|
+
proxyPort: bot.proxyPort,
|
|
310
|
+
server: bot.server
|
|
311
|
+
})),
|
|
312
|
+
inaccessibleBots: inaccessibleBots,
|
|
313
|
+
errors: inaccessibleBots.length > 0 ? [`Access denied to ${inaccessibleBots.length} bot(s)`] : []
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
} catch (error) {
|
|
317
|
+
console.error('Bulk proxy update error:', error);
|
|
318
|
+
res.status(500).json({
|
|
319
|
+
error: 'Failed to update proxy settings',
|
|
320
|
+
details: error.message
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
router.get('/:id/logs', conditionalListAuth, authenticateUniversal, checkBotAccess, (req, res) => {
|
|
326
|
+
try {
|
|
327
|
+
const botId = parseInt(req.params.id, 10);
|
|
328
|
+
const { limit = 50, offset = 0 } = req.query;
|
|
329
|
+
|
|
330
|
+
const logs = botManager.getBotLogs(botId);
|
|
331
|
+
|
|
332
|
+
const startIndex = parseInt(offset);
|
|
333
|
+
const endIndex = startIndex + parseInt(limit);
|
|
334
|
+
const paginatedLogs = logs.slice(startIndex, endIndex);
|
|
335
|
+
|
|
336
|
+
res.json({
|
|
337
|
+
success: true,
|
|
338
|
+
data: {
|
|
339
|
+
logs: paginatedLogs,
|
|
340
|
+
pagination: {
|
|
341
|
+
total: logs.length,
|
|
342
|
+
limit: parseInt(limit),
|
|
343
|
+
offset: startIndex,
|
|
344
|
+
hasMore: endIndex < logs.length
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
} catch (error) {
|
|
349
|
+
console.error(`[API] Ошибка получения логов бота ${req.params.id}:`, error);
|
|
350
|
+
res.status(500).json({ error: 'Не удалось получить логи бота' });
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
router.use(authenticate);
|
|
355
|
+
router.use('/:botId/event-graphs', eventGraphsRouter);
|
|
356
|
+
router.use('/:botId/plugins/ide', pluginIdeRouter);
|
|
357
|
+
router.use('/:botId/api-keys', apiKeysRouter);
|
|
358
|
+
|
|
359
|
+
async function setupDefaultPermissionsForBot(botId, prismaClient = prisma) {
|
|
360
|
+
const initialData = {
|
|
361
|
+
groups: ["User", "Admin"],
|
|
362
|
+
permissions: [
|
|
363
|
+
{ name: "admin.*", description: "Все права администратора" },
|
|
364
|
+
{ name: "admin.cooldown.bypass", description: "Обход кулдауна для админ-команд" },
|
|
365
|
+
{ name: "user.*", description: "Все права обычного пользователя" },
|
|
366
|
+
{ name: "user.say", description: "Доступ к простым командам" },
|
|
367
|
+
{ name: "user.cooldown.bypass", description: "Обход кулдауна для юзер-команд" },
|
|
368
|
+
],
|
|
369
|
+
groupPermissions: {
|
|
370
|
+
"User": ["user.say"],
|
|
371
|
+
"Admin": ["admin.*", "admin.cooldown.bypass", "user.cooldown.bypass", "user.*"]
|
|
372
|
+
},
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
for (const perm of initialData.permissions) {
|
|
376
|
+
await prismaClient.permission.upsert({ where: { botId_name: { botId, name: perm.name } }, update: { description: perm.description }, create: { ...perm, botId, owner: 'system' } });
|
|
377
|
+
}
|
|
378
|
+
for (const groupName of initialData.groups) {
|
|
379
|
+
await prismaClient.group.upsert({ where: { botId_name: { botId, name: groupName } }, update: {}, create: { name: groupName, botId, owner: 'system' } });
|
|
380
|
+
}
|
|
381
|
+
for (const [groupName, permNames] of Object.entries(initialData.groupPermissions)) {
|
|
382
|
+
const group = await prismaClient.group.findUnique({ where: { botId_name: { botId, name: groupName } } });
|
|
383
|
+
if (group) {
|
|
384
|
+
for (const permName of permNames) {
|
|
385
|
+
const permission = await prismaClient.permission.findUnique({ where: { botId_name: { botId, name: permName } } });
|
|
386
|
+
if (permission) {
|
|
387
|
+
await prismaClient.groupPermission.upsert({ where: { groupId_permissionId: { groupId: group.id, permissionId: permission.id } }, update: {}, create: { groupId: group.id, permissionId: permission.id } });
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
console.log(`[Setup] Для бота ID ${botId} созданы группы и права по умолчанию.`);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
router.post('/', authorize('bot:create'), async (req, res) => {
|
|
398
|
+
try {
|
|
399
|
+
const { username, password, prefix, serverId, note, proxyId, proxyHost, proxyPort, proxyUsername, proxyPassword } = req.body;
|
|
400
|
+
if (!username || !serverId) return res.status(400).json({ error: 'Имя и сервер обязательны' });
|
|
401
|
+
|
|
402
|
+
const maxSortOrder = await prisma.bot.aggregate({
|
|
403
|
+
_max: { sortOrder: true }
|
|
404
|
+
});
|
|
405
|
+
const nextSortOrder = (maxSortOrder._max.sortOrder || 0) + 1;
|
|
406
|
+
|
|
407
|
+
const data = {
|
|
408
|
+
username,
|
|
409
|
+
prefix,
|
|
410
|
+
note,
|
|
411
|
+
serverId: parseInt(serverId, 10),
|
|
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,
|
|
418
|
+
sortOrder: nextSortOrder
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
const newBot = await prisma.bot.create({
|
|
422
|
+
data: data,
|
|
423
|
+
include: { server: true, proxy: true }
|
|
424
|
+
});
|
|
425
|
+
await setupDefaultPermissionsForBot(newBot.id);
|
|
426
|
+
res.status(201).json(newBot);
|
|
427
|
+
} catch (error) {
|
|
428
|
+
if (error.code === 'P2002') return res.status(409).json({ error: 'Бот с таким именем уже существует' });
|
|
429
|
+
console.error("[API Error] /bots POST:", error);
|
|
430
|
+
res.status(500).json({ error: 'Не удалось создать бота' });
|
|
431
|
+
}
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
router.put('/:id', authenticateUniversal, checkBotAccess, authorize('bot:update'), async (req, res) => {
|
|
435
|
+
try {
|
|
436
|
+
const {
|
|
437
|
+
username, password, prefix, serverId, note, owners,
|
|
438
|
+
proxyId, proxyHost, proxyPort, proxyUsername, proxyPassword
|
|
439
|
+
} = req.body;
|
|
440
|
+
|
|
441
|
+
let dataToUpdate = {
|
|
442
|
+
username,
|
|
443
|
+
prefix,
|
|
444
|
+
note,
|
|
445
|
+
owners,
|
|
446
|
+
proxyId: proxyId ? parseInt(proxyId, 10) : null,
|
|
447
|
+
proxyHost,
|
|
448
|
+
proxyPort: proxyPort ? parseInt(proxyPort, 10) : null,
|
|
449
|
+
proxyUsername,
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
if (password) {
|
|
453
|
+
dataToUpdate.password = encrypt(password);
|
|
454
|
+
}
|
|
455
|
+
if (proxyPassword) {
|
|
456
|
+
dataToUpdate.proxyPassword = encrypt(proxyPassword);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
if (serverId !== undefined && serverId !== '') {
|
|
460
|
+
dataToUpdate.serverId = parseInt(serverId, 10);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
Object.keys(dataToUpdate).forEach(key => {
|
|
464
|
+
if (dataToUpdate[key] === undefined) {
|
|
465
|
+
delete dataToUpdate[key];
|
|
466
|
+
}
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
if (dataToUpdate.serverId) {
|
|
470
|
+
const serverIdValue = dataToUpdate.serverId;
|
|
471
|
+
delete dataToUpdate.serverId;
|
|
472
|
+
dataToUpdate.server = { connect: { id: serverIdValue } };
|
|
473
|
+
}
|
|
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
|
+
|
|
485
|
+
const botId = parseInt(req.params.id, 10);
|
|
486
|
+
if (isNaN(botId)) {
|
|
487
|
+
return res.status(400).json({ message: 'Неверный ID бота.' });
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
if (dataToUpdate.username) {
|
|
491
|
+
const existingBot = await prisma.bot.findFirst({
|
|
492
|
+
where: {
|
|
493
|
+
username: dataToUpdate.username,
|
|
494
|
+
id: { not: botId }
|
|
495
|
+
}
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
if (existingBot) {
|
|
499
|
+
return res.status(400).json({
|
|
500
|
+
message: `Бот с именем "${dataToUpdate.username}" уже существует.`
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
const updatedBot = await prisma.bot.update({
|
|
506
|
+
where: { id: botId },
|
|
507
|
+
data: dataToUpdate,
|
|
508
|
+
include: { server: true, proxy: true }
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
botManager.reloadBotConfigInRealTime(botId);
|
|
512
|
+
|
|
513
|
+
// Отложенная очистка кеша пользователей, чтобы BotProcess успел обновить config
|
|
514
|
+
setTimeout(() => {
|
|
515
|
+
botManager.invalidateAllUserCache(botId);
|
|
516
|
+
}, 500);
|
|
517
|
+
|
|
518
|
+
res.json(updatedBot);
|
|
519
|
+
} catch (error) {
|
|
520
|
+
console.error("[API Error] /bots PUT:", error);
|
|
521
|
+
res.status(500).json({ error: 'Не удалось обновить бота' });
|
|
522
|
+
}
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
router.put('/:id/sort-order', authenticateUniversal, checkBotAccess, authorize('bot:update'), async (req, res) => {
|
|
526
|
+
try {
|
|
527
|
+
const { newPosition, oldIndex, newIndex } = req.body;
|
|
528
|
+
const botId = parseInt(req.params.id, 10);
|
|
529
|
+
|
|
530
|
+
console.log(`[API] Запрос на изменение порядка бота ${botId}: oldIndex=${oldIndex}, newIndex=${newIndex}, newPosition=${newPosition}`);
|
|
531
|
+
|
|
532
|
+
if (isNaN(botId)) {
|
|
533
|
+
console.log(`[API] Неверный botId: ${botId}`);
|
|
534
|
+
return res.status(400).json({ error: 'Неверный ID бота' });
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
const allBots = await prisma.bot.findMany({
|
|
538
|
+
orderBy: { sortOrder: 'asc' },
|
|
539
|
+
select: { id: true, sortOrder: true }
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
console.log(`[API] Всего ботов: ${allBots.length}`);
|
|
543
|
+
const currentBotIndex = allBots.findIndex(bot => bot.id === botId);
|
|
544
|
+
if (currentBotIndex === -1) {
|
|
545
|
+
console.log(`[API] Бот ${botId} не найден`);
|
|
546
|
+
return res.status(404).json({ error: 'Бот не найден' });
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
if (newIndex < 0 || newIndex >= allBots.length) {
|
|
550
|
+
console.log(`[API] Неверная новая позиция: ${newIndex}`);
|
|
551
|
+
return res.status(400).json({ error: 'Неверная позиция' });
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
if (currentBotIndex === newIndex) {
|
|
555
|
+
console.log(`[API] Позиция не изменилась для бота ${botId}`);
|
|
556
|
+
return res.json({ success: true, message: 'Позиция не изменилась' });
|
|
557
|
+
}
|
|
558
|
+
const reorderedBots = [...allBots];
|
|
559
|
+
const [movedBot] = reorderedBots.splice(currentBotIndex, 1);
|
|
560
|
+
reorderedBots.splice(newIndex, 0, movedBot);
|
|
561
|
+
|
|
562
|
+
console.log(`[API] Обновляем порядок для всех ботов`);
|
|
563
|
+
for (let i = 0; i < reorderedBots.length; i++) {
|
|
564
|
+
const bot = reorderedBots[i];
|
|
565
|
+
const newSortOrder = i + 1; // 1-based позиции
|
|
566
|
+
|
|
567
|
+
if (bot.sortOrder !== newSortOrder) {
|
|
568
|
+
await prisma.bot.update({
|
|
569
|
+
where: { id: bot.id },
|
|
570
|
+
data: { sortOrder: newSortOrder }
|
|
571
|
+
});
|
|
572
|
+
console.log(`[API] Обновлен бот ${bot.id}: sortOrder ${bot.sortOrder} -> ${newSortOrder}`);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
console.log(`[API] Успешно обновлен порядок бота ${botId}`);
|
|
577
|
+
res.json({ success: true, message: 'Порядок ботов обновлен' });
|
|
578
|
+
} catch (error) {
|
|
579
|
+
console.error("[API Error] /bots sort-order PUT:", error);
|
|
580
|
+
res.status(500).json({ error: 'Не удалось обновить порядок ботов' });
|
|
581
|
+
}
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
router.delete('/:id', authenticateUniversal, checkBotAccess, authorize('bot:delete'), async (req, res) => {
|
|
585
|
+
try {
|
|
586
|
+
const botId = parseInt(req.params.id, 10);
|
|
587
|
+
if (botManager.bots.has(botId)) return res.status(400).json({ error: 'Нельзя удалить запущенного бота' });
|
|
588
|
+
await prisma.bot.delete({ where: { id: botId } });
|
|
589
|
+
res.status(204).send();
|
|
590
|
+
} catch (error) { res.status(500).json({ error: 'Не удалось удалить бота' }); }
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
router.get('/servers', authorize('bot:list'), async (req, res) => {
|
|
594
|
+
try {
|
|
595
|
+
const servers = await prisma.server.findMany();
|
|
596
|
+
res.json(servers);
|
|
597
|
+
} catch (error) {
|
|
598
|
+
console.error("[API /api/bots] Ошибка получения списка серверов:", error);
|
|
599
|
+
res.status(500).json({ error: 'Не удалось получить список серверов' });
|
|
600
|
+
}
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
router.get('/:botId/plugins', authenticateUniversal, checkBotAccess, authorize('plugin:list'), async (req, res) => {
|
|
604
|
+
try {
|
|
605
|
+
const botId = parseInt(req.params.botId);
|
|
606
|
+
const plugins = await prisma.installedPlugin.findMany({ where: { botId } });
|
|
607
|
+
res.json(plugins);
|
|
608
|
+
} catch (error) { res.status(500).json({ error: 'Не удалось получить плагины бота' }); }
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
router.post('/:botId/plugins/install/github', authenticateUniversal, checkBotAccess, authorize('plugin:install'), async (req, res) => {
|
|
612
|
+
const { botId } = req.params;
|
|
613
|
+
const { repoUrl } = req.body;
|
|
614
|
+
try {
|
|
615
|
+
const newPlugin = await pluginManager.installFromGithub(parseInt(botId), repoUrl);
|
|
616
|
+
res.status(201).json(newPlugin);
|
|
617
|
+
} catch (error) {
|
|
618
|
+
res.status(500).json({ message: error.message });
|
|
619
|
+
}
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
router.post('/:botId/plugins/install/local', authenticateUniversal, checkBotAccess, authorize('plugin:install'), async (req, res) => {
|
|
623
|
+
const { botId } = req.params;
|
|
624
|
+
const { path } = req.body;
|
|
625
|
+
try {
|
|
626
|
+
const newPlugin = await pluginManager.installFromLocalPath(parseInt(botId), path);
|
|
627
|
+
res.status(201).json(newPlugin);
|
|
628
|
+
} catch (error) {
|
|
629
|
+
res.status(500).json({ message: error.message });
|
|
630
|
+
}
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
router.delete('/:botId/plugins/:pluginId', authenticateUniversal, checkBotAccess, authorize('plugin:delete'), async (req, res) => {
|
|
634
|
+
const { pluginId } = req.params;
|
|
635
|
+
try {
|
|
636
|
+
await pluginManager.deletePlugin(parseInt(pluginId));
|
|
637
|
+
res.status(204).send();
|
|
638
|
+
} catch (error) {
|
|
639
|
+
res.status(500).json({ message: error.message });
|
|
640
|
+
}
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
router.get('/:botId/plugins/:pluginId/settings', authenticateUniversal, checkBotAccess, authorize('plugin:settings:view'), async (req, res) => {
|
|
644
|
+
try {
|
|
645
|
+
const pluginId = parseInt(req.params.pluginId);
|
|
646
|
+
const plugin = await prisma.installedPlugin.findUnique({ where: { id: pluginId } });
|
|
647
|
+
if (!plugin) return res.status(404).json({ error: 'Установленный плагин не найден' });
|
|
648
|
+
|
|
649
|
+
const savedSettings = plugin.settings ? JSON.parse(plugin.settings) : {};
|
|
650
|
+
const defaultSettings = {};
|
|
651
|
+
const manifest = plugin.manifest ? JSON.parse(plugin.manifest) : {};
|
|
652
|
+
const manifestSettings = manifest.settings || {};
|
|
653
|
+
|
|
654
|
+
// Определяем тип настроек (обычные или группированные)
|
|
655
|
+
const isGrouped = isGroupedSettings(manifestSettings);
|
|
656
|
+
|
|
657
|
+
const processSetting = async (settingKey, config) => {
|
|
658
|
+
if (!config || !config.type) return;
|
|
659
|
+
|
|
660
|
+
if (config.type === 'json_file' && config.defaultPath) {
|
|
661
|
+
const configFilePath = path.join(plugin.path, config.defaultPath);
|
|
662
|
+
try {
|
|
663
|
+
const fileContent = await fs.readFile(configFilePath, 'utf-8');
|
|
664
|
+
defaultSettings[settingKey] = JSON.parse(fileContent);
|
|
665
|
+
} catch (e) {
|
|
666
|
+
console.error(`[API Settings] Не удалось прочитать defaultPath ${config.defaultPath} для плагина ${plugin.name}: ${e.message}`);
|
|
667
|
+
defaultSettings[settingKey] = {};
|
|
668
|
+
}
|
|
669
|
+
} else if (config.default !== undefined) {
|
|
670
|
+
try {
|
|
671
|
+
defaultSettings[settingKey] = JSON.parse(config.default);
|
|
672
|
+
} catch {
|
|
673
|
+
defaultSettings[settingKey] = config.default;
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
};
|
|
677
|
+
|
|
678
|
+
if (isGrouped) {
|
|
679
|
+
for (const categoryKey in manifestSettings) {
|
|
680
|
+
const categoryConfig = manifestSettings[categoryKey];
|
|
681
|
+
for (const settingKey in categoryConfig) {
|
|
682
|
+
if (settingKey === 'label') continue;
|
|
683
|
+
await processSetting(settingKey, categoryConfig[settingKey]);
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
} else {
|
|
687
|
+
for (const settingKey in manifestSettings) {
|
|
688
|
+
await processSetting(settingKey, manifestSettings[settingKey]);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
const finalSettings = deepMergeSettings(defaultSettings, savedSettings);
|
|
693
|
+
|
|
694
|
+
// Фильтруем секретные значения перед отправкой на фронтенд
|
|
695
|
+
const filteredSettings = filterSecretSettings(finalSettings, manifestSettings, isGrouped);
|
|
696
|
+
|
|
697
|
+
res.json(filteredSettings);
|
|
698
|
+
} catch (error) {
|
|
699
|
+
console.error("[API Error] /settings GET:", error);
|
|
700
|
+
res.status(500).json({ error: 'Не удалось получить настройки плагина' });
|
|
701
|
+
}
|
|
702
|
+
});
|
|
703
|
+
|
|
704
|
+
router.get('/:botId/plugins/:pluginId/data', authenticateUniversal, checkBotAccess, authorize('plugin:settings:view'), async (req, res) => {
|
|
705
|
+
try {
|
|
706
|
+
const pluginId = parseInt(req.params.pluginId);
|
|
707
|
+
const plugin = await prisma.installedPlugin.findUnique({ where: { id: pluginId } });
|
|
708
|
+
if (!plugin) return res.status(404).json({ error: 'Установленный плагин не найден' });
|
|
709
|
+
|
|
710
|
+
const rows = await prisma.pluginDataStore.findMany({
|
|
711
|
+
where: { botId: plugin.botId, pluginName: plugin.name },
|
|
712
|
+
orderBy: { updatedAt: 'desc' }
|
|
713
|
+
});
|
|
714
|
+
|
|
715
|
+
const result = rows.map(r => {
|
|
716
|
+
let value;
|
|
717
|
+
try { value = JSON.parse(r.value); } catch { value = r.value; }
|
|
718
|
+
return { key: r.key, value, createdAt: r.createdAt, updatedAt: r.updatedAt };
|
|
719
|
+
});
|
|
720
|
+
res.json(result);
|
|
721
|
+
} catch (error) { res.status(500).json({ error: 'Не удалось получить данные плагина' }); }
|
|
722
|
+
});
|
|
723
|
+
|
|
724
|
+
router.get('/:botId/plugins/:pluginId/data/:key', authenticateUniversal, checkBotAccess, authorize('plugin:settings:view'), async (req, res) => {
|
|
725
|
+
try {
|
|
726
|
+
const pluginId = parseInt(req.params.pluginId);
|
|
727
|
+
const { key } = req.params;
|
|
728
|
+
const plugin = await prisma.installedPlugin.findUnique({ where: { id: pluginId } });
|
|
729
|
+
if (!plugin) return res.status(404).json({ error: 'Установленный плагин не найден' });
|
|
730
|
+
|
|
731
|
+
const row = await prisma.pluginDataStore.findUnique({
|
|
732
|
+
where: {
|
|
733
|
+
pluginName_botId_key: {
|
|
734
|
+
pluginName: plugin.name,
|
|
735
|
+
botId: plugin.botId,
|
|
736
|
+
key
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
});
|
|
740
|
+
if (!row) return res.status(404).json({ error: 'Ключ не найден' });
|
|
741
|
+
let value; try { value = JSON.parse(row.value); } catch { value = row.value; }
|
|
742
|
+
res.json({ key: row.key, value, createdAt: row.createdAt, updatedAt: row.updatedAt });
|
|
743
|
+
} catch (error) { res.status(500).json({ error: 'Не удалось получить значение по ключу' }); }
|
|
744
|
+
});
|
|
745
|
+
|
|
746
|
+
router.put('/:botId/plugins/:pluginId/data/:key', authenticateUniversal, checkBotAccess, authorize('plugin:settings:edit'), async (req, res) => {
|
|
747
|
+
try {
|
|
748
|
+
const pluginId = parseInt(req.params.pluginId);
|
|
749
|
+
const { key } = req.params;
|
|
750
|
+
const { value } = req.body;
|
|
751
|
+
const plugin = await prisma.installedPlugin.findUnique({ where: { id: pluginId } });
|
|
752
|
+
if (!plugin) return res.status(404).json({ error: 'Установленный плагин не найден' });
|
|
753
|
+
|
|
754
|
+
const jsonValue = JSON.stringify(value ?? null);
|
|
755
|
+
const upserted = await prisma.pluginDataStore.upsert({
|
|
756
|
+
where: {
|
|
757
|
+
pluginName_botId_key: {
|
|
758
|
+
pluginName: plugin.name,
|
|
759
|
+
botId: plugin.botId,
|
|
760
|
+
key
|
|
761
|
+
}
|
|
762
|
+
},
|
|
763
|
+
update: { value: jsonValue },
|
|
764
|
+
create: { pluginName: plugin.name, botId: plugin.botId, key, value: jsonValue }
|
|
765
|
+
});
|
|
766
|
+
let parsed; try { parsed = JSON.parse(upserted.value); } catch { parsed = upserted.value; }
|
|
767
|
+
res.json({ key: upserted.key, value: parsed, createdAt: upserted.createdAt, updatedAt: upserted.updatedAt });
|
|
768
|
+
} catch (error) { res.status(500).json({ error: 'Не удалось сохранить значение' }); }
|
|
769
|
+
});
|
|
770
|
+
|
|
771
|
+
router.delete('/:botId/plugins/:pluginId/data/:key', authenticateUniversal, checkBotAccess, authorize('plugin:settings:edit'), async (req, res) => {
|
|
772
|
+
try {
|
|
773
|
+
const pluginId = parseInt(req.params.pluginId);
|
|
774
|
+
const { key } = req.params;
|
|
775
|
+
const plugin = await prisma.installedPlugin.findUnique({ where: { id: pluginId } });
|
|
776
|
+
if (!plugin) return res.status(404).json({ error: 'Установленный плагин не найден' });
|
|
777
|
+
|
|
778
|
+
await prisma.pluginDataStore.delete({
|
|
779
|
+
where: {
|
|
780
|
+
pluginName_botId_key: {
|
|
781
|
+
pluginName: plugin.name,
|
|
782
|
+
botId: plugin.botId,
|
|
783
|
+
key
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
});
|
|
787
|
+
res.status(204).send();
|
|
788
|
+
} catch (error) { res.status(500).json({ error: 'Не удалось удалить значение' }); }
|
|
789
|
+
});
|
|
790
|
+
|
|
791
|
+
router.put('/:botId/plugins/:pluginId', authenticateUniversal, checkBotAccess, authorize('plugin:settings:edit'), async (req, res) => {
|
|
792
|
+
try {
|
|
793
|
+
const pluginId = parseInt(req.params.pluginId);
|
|
794
|
+
const { isEnabled, settings } = req.body;
|
|
795
|
+
const dataToUpdate = {};
|
|
796
|
+
|
|
797
|
+
if (typeof isEnabled === 'boolean') dataToUpdate.isEnabled = isEnabled;
|
|
798
|
+
|
|
799
|
+
if (settings) {
|
|
800
|
+
// Получаем существующий плагин для обработки секретных значений
|
|
801
|
+
const plugin = await prisma.installedPlugin.findUnique({ where: { id: pluginId } });
|
|
802
|
+
if (!plugin) return res.status(404).json({ error: 'Плагин не найден' });
|
|
803
|
+
|
|
804
|
+
const manifest = plugin.manifest ? JSON.parse(plugin.manifest) : {};
|
|
805
|
+
const existingSettings = plugin.settings ? JSON.parse(plugin.settings) : {};
|
|
806
|
+
const manifestSettings = manifest.settings || {};
|
|
807
|
+
|
|
808
|
+
// Определяем тип настроек (обычные или группированные)
|
|
809
|
+
const isGrouped = isGroupedSettings(manifestSettings);
|
|
810
|
+
|
|
811
|
+
// Подготавливаем настройки для сохранения (сохраняем существующие значения для замаскированных секретов)
|
|
812
|
+
const settingsToSave = prepareSettingsForSave(settings, existingSettings, manifestSettings, isGrouped);
|
|
813
|
+
|
|
814
|
+
// Валидация структуры settingsToSave
|
|
815
|
+
if (!settingsToSave || typeof settingsToSave !== 'object' || Array.isArray(settingsToSave)) {
|
|
816
|
+
console.error("[Validation Error] prepareSettingsForSave вернул некорректную структуру:", settingsToSave);
|
|
817
|
+
return res.status(400).json({ error: "Некорректная структура настроек для сохранения" });
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
dataToUpdate.settings = JSON.stringify(settingsToSave);
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
if (Object.keys(dataToUpdate).length === 0) return res.status(400).json({ error: "Нет данных для обновления" });
|
|
824
|
+
const updated = await prisma.installedPlugin.update({ where: { id: pluginId }, data: dataToUpdate });
|
|
825
|
+
res.json(updated);
|
|
826
|
+
} catch (error) {
|
|
827
|
+
console.error("[API Error] /plugins/:pluginId PUT:", error);
|
|
828
|
+
res.status(500).json({ error: 'Не удалось обновить плагин' });
|
|
829
|
+
}
|
|
830
|
+
});
|
|
831
|
+
|
|
832
|
+
router.get('/:botId/management-data', authenticateUniversal, checkBotAccess, authorize('management:view'), async (req, res) => {
|
|
833
|
+
try {
|
|
834
|
+
const botId = parseInt(req.params.botId, 10);
|
|
835
|
+
if (isNaN(botId)) return res.status(400).json({ error: 'Неверный ID бота' });
|
|
836
|
+
|
|
837
|
+
const page = parseInt(req.query.page) || 1;
|
|
838
|
+
const pageSize = parseInt(req.query.pageSize) || 100;
|
|
839
|
+
const searchQuery = req.query.search || '';
|
|
840
|
+
|
|
841
|
+
const userSkip = (page - 1) * pageSize;
|
|
842
|
+
|
|
843
|
+
const whereClause = {
|
|
844
|
+
botId,
|
|
845
|
+
};
|
|
846
|
+
|
|
847
|
+
if (searchQuery) {
|
|
848
|
+
whereClause.username = {
|
|
849
|
+
contains: searchQuery,
|
|
850
|
+
};
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
const [groups, allPermissions] = await Promise.all([
|
|
854
|
+
prisma.group.findMany({ where: { botId }, include: { permissions: { include: { permission: true } } }, orderBy: { name: 'asc' } }),
|
|
855
|
+
prisma.permission.findMany({ where: { botId }, orderBy: { name: 'asc' } })
|
|
856
|
+
]);
|
|
857
|
+
|
|
858
|
+
const [users, usersCount] = await Promise.all([
|
|
859
|
+
prisma.user.findMany({
|
|
860
|
+
where: whereClause,
|
|
861
|
+
include: { groups: { include: { group: true } } },
|
|
862
|
+
orderBy: { username: 'asc' },
|
|
863
|
+
take: pageSize,
|
|
864
|
+
skip: userSkip,
|
|
865
|
+
}),
|
|
866
|
+
prisma.user.count({ where: whereClause })
|
|
867
|
+
]);
|
|
868
|
+
|
|
869
|
+
const templatesMap = new Map(commandManager.getCommandTemplates().map(t => [t.name, t]));
|
|
870
|
+
let dbCommandsFromDb = await prisma.command.findMany({
|
|
871
|
+
where: { botId },
|
|
872
|
+
include: {
|
|
873
|
+
pluginOwner: {
|
|
874
|
+
select: {
|
|
875
|
+
id: true,
|
|
876
|
+
name: true,
|
|
877
|
+
version: true,
|
|
878
|
+
sourceType: true
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
},
|
|
882
|
+
orderBy: [{ owner: 'asc' }, { name: 'asc' }]
|
|
883
|
+
});
|
|
884
|
+
|
|
885
|
+
const commandsToCreate = [];
|
|
886
|
+
for (const template of templatesMap.values()) {
|
|
887
|
+
if (!dbCommandsFromDb.some(cmd => cmd.name === template.name)) {
|
|
888
|
+
let permissionId = null;
|
|
889
|
+
if (template.permissions) {
|
|
890
|
+
const permission = await prisma.permission.upsert({
|
|
891
|
+
where: { botId_name: { botId, name: template.permissions } },
|
|
892
|
+
update: { description: `Авто-создано для команды ${template.name}` },
|
|
893
|
+
create: {
|
|
894
|
+
botId,
|
|
895
|
+
name: template.permissions,
|
|
896
|
+
description: `Авто-создано для команды ${template.name}`,
|
|
897
|
+
owner: template.owner || 'system',
|
|
898
|
+
}
|
|
899
|
+
});
|
|
900
|
+
permissionId = permission.id;
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
commandsToCreate.push({
|
|
904
|
+
botId,
|
|
905
|
+
name: template.name,
|
|
906
|
+
isEnabled: template.isActive,
|
|
907
|
+
cooldown: template.cooldown,
|
|
908
|
+
aliases: JSON.stringify(template.aliases),
|
|
909
|
+
description: template.description,
|
|
910
|
+
owner: template.owner,
|
|
911
|
+
permissionId: permissionId,
|
|
912
|
+
allowedChatTypes: JSON.stringify(template.allowedChatTypes),
|
|
913
|
+
});
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
if (commandsToCreate.length > 0) {
|
|
918
|
+
await prisma.command.createMany({ data: commandsToCreate });
|
|
919
|
+
dbCommandsFromDb = await prisma.command.findMany({
|
|
920
|
+
where: { botId },
|
|
921
|
+
include: {
|
|
922
|
+
pluginOwner: {
|
|
923
|
+
select: {
|
|
924
|
+
id: true,
|
|
925
|
+
name: true,
|
|
926
|
+
version: true,
|
|
927
|
+
sourceType: true
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
},
|
|
931
|
+
orderBy: [{ owner: 'asc' }, { name: 'asc' }]
|
|
932
|
+
});
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
const finalCommands = dbCommandsFromDb.map(cmd => {
|
|
936
|
+
const template = templatesMap.get(cmd.name);
|
|
937
|
+
let args = [];
|
|
938
|
+
|
|
939
|
+
if (cmd.isVisual) {
|
|
940
|
+
try {
|
|
941
|
+
args = JSON.parse(cmd.argumentsJson || '[]');
|
|
942
|
+
} catch (e) {
|
|
943
|
+
console.error(`Error parsing argumentsJson for visual command ${cmd.name} (ID: ${cmd.id}):`, e);
|
|
944
|
+
args = [];
|
|
945
|
+
}
|
|
946
|
+
} else {
|
|
947
|
+
if (template && template.args && template.args.length > 0) {
|
|
948
|
+
args = template.args;
|
|
949
|
+
} else {
|
|
950
|
+
try {
|
|
951
|
+
args = JSON.parse(cmd.argumentsJson || '[]');
|
|
952
|
+
} catch (e) {
|
|
953
|
+
args = [];
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
return {
|
|
959
|
+
...cmd,
|
|
960
|
+
args: args,
|
|
961
|
+
aliases: JSON.parse(cmd.aliases || '[]'),
|
|
962
|
+
allowedChatTypes: JSON.parse(cmd.allowedChatTypes || '[]'),
|
|
963
|
+
};
|
|
964
|
+
})
|
|
965
|
+
|
|
966
|
+
res.json({
|
|
967
|
+
groups,
|
|
968
|
+
permissions: allPermissions,
|
|
969
|
+
users: {
|
|
970
|
+
items: users,
|
|
971
|
+
total: usersCount,
|
|
972
|
+
page,
|
|
973
|
+
pageSize,
|
|
974
|
+
totalPages: Math.ceil(usersCount / pageSize),
|
|
975
|
+
},
|
|
976
|
+
commands: finalCommands
|
|
977
|
+
});
|
|
978
|
+
|
|
979
|
+
} catch (error) {
|
|
980
|
+
console.error(`[API Error] /management-data for bot ${req.params.botId}:`, error);
|
|
981
|
+
res.status(500).json({ error: 'Не удалось загрузить данные управления' });
|
|
982
|
+
}
|
|
983
|
+
});
|
|
984
|
+
|
|
985
|
+
router.put('/:botId/commands/:commandId', authorize('management:edit'), async (req, res) => {
|
|
986
|
+
try {
|
|
987
|
+
const commandId = parseInt(req.params.commandId, 10);
|
|
988
|
+
const { name, description, cooldown, aliases, permissionId, allowedChatTypes, isEnabled, argumentsJson, graphJson, pluginOwnerId } = req.body;
|
|
989
|
+
|
|
990
|
+
const dataToUpdate = {};
|
|
991
|
+
if (name !== undefined) dataToUpdate.name = name;
|
|
992
|
+
if (description !== undefined) dataToUpdate.description = description;
|
|
993
|
+
if (cooldown !== undefined) dataToUpdate.cooldown = parseInt(cooldown, 10);
|
|
994
|
+
if (aliases !== undefined) dataToUpdate.aliases = Array.isArray(aliases) ? JSON.stringify(aliases) : aliases;
|
|
995
|
+
if (permissionId !== undefined) dataToUpdate.permissionId = permissionId ? parseInt(permissionId, 10) : null;
|
|
996
|
+
if (allowedChatTypes !== undefined) dataToUpdate.allowedChatTypes = Array.isArray(allowedChatTypes) ? JSON.stringify(allowedChatTypes) : allowedChatTypes;
|
|
997
|
+
if (isEnabled !== undefined) dataToUpdate.isEnabled = isEnabled;
|
|
998
|
+
if (argumentsJson !== undefined) dataToUpdate.argumentsJson = Array.isArray(argumentsJson) ? JSON.stringify(argumentsJson) : argumentsJson;
|
|
999
|
+
if (graphJson !== undefined) dataToUpdate.graphJson = graphJson;
|
|
1000
|
+
if (pluginOwnerId !== undefined) dataToUpdate.pluginOwnerId = pluginOwnerId;
|
|
1001
|
+
|
|
1002
|
+
const updatedCommand = await prisma.command.update({
|
|
1003
|
+
where: { id: commandId },
|
|
1004
|
+
data: dataToUpdate,
|
|
1005
|
+
});
|
|
1006
|
+
|
|
1007
|
+
if (graphJson && updatedCommand.pluginOwnerId) {
|
|
1008
|
+
try {
|
|
1009
|
+
const plugin = await prisma.installedPlugin.findUnique({
|
|
1010
|
+
where: { id: updatedCommand.pluginOwnerId }
|
|
1011
|
+
});
|
|
1012
|
+
|
|
1013
|
+
if (plugin) {
|
|
1014
|
+
const graphDir = path.join(plugin.path, 'graph');
|
|
1015
|
+
await fse.mkdir(graphDir, { recursive: true });
|
|
1016
|
+
|
|
1017
|
+
const graphFile = path.join(graphDir, `${updatedCommand.name}.json`);
|
|
1018
|
+
await fse.writeJson(graphFile, JSON.parse(graphJson), { spaces: 2 });
|
|
1019
|
+
console.log(`[API] Граф команды ${updatedCommand.name} сохранен в ${graphFile}`);
|
|
1020
|
+
}
|
|
1021
|
+
} catch (error) {
|
|
1022
|
+
console.error(`[API] Ошибка сохранения графа в папку плагина:`, error);
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
res.json(updatedCommand);
|
|
1027
|
+
} catch (error) {
|
|
1028
|
+
console.error(`[API Error] /commands/:commandId PUT:`, error);
|
|
1029
|
+
res.status(500).json({ error: 'Failed to update command' });
|
|
1030
|
+
}
|
|
1031
|
+
});
|
|
1032
|
+
|
|
1033
|
+
router.post('/:botId/groups', authorize('management:edit'), async (req, res) => {
|
|
1034
|
+
try {
|
|
1035
|
+
const botId = parseInt(req.params.botId);
|
|
1036
|
+
const { name, permissionIds } = req.body;
|
|
1037
|
+
if (!name) return res.status(400).json({ error: "Имя группы обязательно" });
|
|
1038
|
+
|
|
1039
|
+
const newGroup = await prisma.group.create({
|
|
1040
|
+
data: {
|
|
1041
|
+
name,
|
|
1042
|
+
botId,
|
|
1043
|
+
owner: 'admin',
|
|
1044
|
+
permissions: { create: (permissionIds || []).map(id => ({ permissionId: id })) }
|
|
1045
|
+
}
|
|
1046
|
+
});
|
|
1047
|
+
|
|
1048
|
+
botManager.reloadBotConfigInRealTime(botId);
|
|
1049
|
+
|
|
1050
|
+
res.status(201).json(newGroup);
|
|
1051
|
+
} catch (error) {
|
|
1052
|
+
if (error.code === 'P2002') return res.status(409).json({ error: 'Группа с таким именем уже существует для этого бота.' });
|
|
1053
|
+
res.status(500).json({ error: 'Не удалось создать группу.' });
|
|
1054
|
+
}
|
|
1055
|
+
});
|
|
1056
|
+
|
|
1057
|
+
router.put('/:botId/groups/:groupId', authorize('management:edit'), async (req, res) => {
|
|
1058
|
+
try {
|
|
1059
|
+
const botId = parseInt(req.params.botId, 10);
|
|
1060
|
+
const groupId = parseInt(req.params.groupId);
|
|
1061
|
+
const { name, permissionIds } = req.body;
|
|
1062
|
+
if (!name) return res.status(400).json({ error: "Имя группы обязательно" });
|
|
1063
|
+
|
|
1064
|
+
const usersInGroup = await prisma.user.findMany({
|
|
1065
|
+
where: { botId, groups: { some: { groupId } } },
|
|
1066
|
+
select: { username: true }
|
|
1067
|
+
});
|
|
1068
|
+
|
|
1069
|
+
await prisma.$transaction(async (tx) => {
|
|
1070
|
+
await tx.group.update({ where: { id: groupId }, data: { name } });
|
|
1071
|
+
await tx.groupPermission.deleteMany({ where: { groupId } });
|
|
1072
|
+
if (permissionIds && permissionIds.length > 0) {
|
|
1073
|
+
await tx.groupPermission.createMany({
|
|
1074
|
+
data: permissionIds.map(pid => ({ groupId, permissionId: pid })),
|
|
1075
|
+
});
|
|
1076
|
+
}
|
|
1077
|
+
});
|
|
1078
|
+
|
|
1079
|
+
for (const user of usersInGroup) {
|
|
1080
|
+
botManager.invalidateUserCache(botId, user.username);
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
botManager.reloadBotConfigInRealTime(botId);
|
|
1084
|
+
|
|
1085
|
+
res.status(200).send();
|
|
1086
|
+
} catch (error) {
|
|
1087
|
+
if (error.code === 'P2002') return res.status(409).json({ error: 'Группа с таким именем уже существует для этого бота.' });
|
|
1088
|
+
res.status(500).json({ error: 'Не удалось обновить группу.' });
|
|
1089
|
+
}
|
|
1090
|
+
});
|
|
1091
|
+
|
|
1092
|
+
router.delete('/:botId/groups/:groupId', authorize('management:edit'), async (req, res) => {
|
|
1093
|
+
try {
|
|
1094
|
+
const botId = parseInt(req.params.botId, 10);
|
|
1095
|
+
const groupId = parseInt(req.params.groupId);
|
|
1096
|
+
const group = await prisma.group.findUnique({ where: { id: groupId } });
|
|
1097
|
+
if (group && group.owner !== 'admin') {
|
|
1098
|
+
return res.status(403).json({ error: `Нельзя удалить группу с источником "${group.owner}".` });
|
|
1099
|
+
}
|
|
1100
|
+
await prisma.group.delete({ where: { id: groupId } });
|
|
1101
|
+
botManager.reloadBotConfigInRealTime(botId);
|
|
1102
|
+
|
|
1103
|
+
res.status(204).send();
|
|
1104
|
+
} catch (error) { res.status(500).json({ error: 'Не удалось удалить группу.' }); }
|
|
1105
|
+
});
|
|
1106
|
+
|
|
1107
|
+
router.post('/:botId/permissions', authorize('management:edit'), async (req, res) => {
|
|
1108
|
+
try {
|
|
1109
|
+
const botId = parseInt(req.params.botId);
|
|
1110
|
+
const { name, description } = req.body;
|
|
1111
|
+
if (!name) return res.status(400).json({ error: 'Имя права обязательно' });
|
|
1112
|
+
const newPermission = await prisma.permission.create({
|
|
1113
|
+
data: { name, description, botId, owner: 'admin' }
|
|
1114
|
+
});
|
|
1115
|
+
|
|
1116
|
+
botManager.reloadBotConfigInRealTime(botId);
|
|
1117
|
+
|
|
1118
|
+
res.status(201).json(newPermission);
|
|
1119
|
+
} catch (error) {
|
|
1120
|
+
if (error.code === 'P2002') return res.status(409).json({ error: 'Право с таким именем уже существует для этого бота.' });
|
|
1121
|
+
res.status(500).json({ error: 'Не удалось создать право.' });
|
|
1122
|
+
}
|
|
1123
|
+
});
|
|
1124
|
+
|
|
1125
|
+
router.put('/:botId/users/:userId', authorize('management:edit'), async (req, res) => {
|
|
1126
|
+
try {
|
|
1127
|
+
const botId = parseInt(req.params.botId, 10);
|
|
1128
|
+
const userId = parseInt(req.params.userId, 10);
|
|
1129
|
+
const { isBlacklisted, groupIds } = req.body;
|
|
1130
|
+
|
|
1131
|
+
const updateData = {};
|
|
1132
|
+
if (typeof isBlacklisted === 'boolean') {
|
|
1133
|
+
updateData.isBlacklisted = isBlacklisted;
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
if (Array.isArray(groupIds)) {
|
|
1137
|
+
await prisma.userGroup.deleteMany({ where: { userId } });
|
|
1138
|
+
updateData.groups = {
|
|
1139
|
+
create: groupIds.map(gid => ({ groupId: gid })),
|
|
1140
|
+
};
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
const updatedUser = await prisma.user.update({
|
|
1144
|
+
where: { id: userId },
|
|
1145
|
+
data: updateData,
|
|
1146
|
+
include: { groups: true }
|
|
1147
|
+
});
|
|
1148
|
+
|
|
1149
|
+
botManager.invalidateUserCache(botId, updatedUser.username);
|
|
1150
|
+
|
|
1151
|
+
UserService.clearCache(updatedUser.username, botId);
|
|
1152
|
+
|
|
1153
|
+
res.json(updatedUser);
|
|
1154
|
+
|
|
1155
|
+
} catch (error) {
|
|
1156
|
+
console.error(`[API Error] /users/:userId PUT:`, error);
|
|
1157
|
+
res.status(500).json({ error: 'Не удалось обновить пользователя' });
|
|
1158
|
+
}
|
|
1159
|
+
});
|
|
1160
|
+
|
|
1161
|
+
router.post('/start-all', authorize('bot:start_stop'), async (req, res) => {
|
|
1162
|
+
try {
|
|
1163
|
+
console.log('[API] Получен запрос на запуск всех ботов.');
|
|
1164
|
+
const allBots = await prisma.bot.findMany({ include: { server: true, proxy: true } });
|
|
1165
|
+
let startedCount = 0;
|
|
1166
|
+
for (const botConfig of allBots) {
|
|
1167
|
+
if (!botManager.bots.has(botConfig.id)) {
|
|
1168
|
+
await botManager.startBot(botConfig);
|
|
1169
|
+
startedCount++;
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
res.json({ success: true, message: `Запущено ${startedCount} ботов.` });
|
|
1173
|
+
} catch (error) {
|
|
1174
|
+
console.error('[API Error] /start-all:', error);
|
|
1175
|
+
res.status(500).json({ error: 'Произошла ошибка при массовом запуске ботов.' });
|
|
1176
|
+
}
|
|
1177
|
+
});
|
|
1178
|
+
|
|
1179
|
+
router.post('/stop-all', authorize('bot:start_stop'), (req, res) => {
|
|
1180
|
+
try {
|
|
1181
|
+
console.log('[API] Получен запрос на остановку всех ботов.');
|
|
1182
|
+
const botIds = Array.from(botManager.bots.keys());
|
|
1183
|
+
let stoppedCount = 0;
|
|
1184
|
+
for (const botId of botIds) {
|
|
1185
|
+
botManager.stopBot(botId);
|
|
1186
|
+
stoppedCount++;
|
|
1187
|
+
}
|
|
1188
|
+
res.json({ success: true, message: `Остановлено ${stoppedCount} ботов.` });
|
|
1189
|
+
} catch (error) {
|
|
1190
|
+
console.error('[API Error] /stop-all:', error);
|
|
1191
|
+
res.status(500).json({ error: 'Произошла ошибка при массовой остановке ботов.' });
|
|
1192
|
+
}
|
|
1193
|
+
});
|
|
1194
|
+
|
|
1195
|
+
router.get('/:id/settings/all', authenticateUniversal, checkBotAccess, authorize('bot:update'), async (req, res) => {
|
|
1196
|
+
try {
|
|
1197
|
+
const botId = parseInt(req.params.id, 10);
|
|
1198
|
+
|
|
1199
|
+
const bot = await prisma.bot.findUnique({
|
|
1200
|
+
where: { id: botId },
|
|
1201
|
+
include: {
|
|
1202
|
+
server: true,
|
|
1203
|
+
proxy: true,
|
|
1204
|
+
installedPlugins: {
|
|
1205
|
+
orderBy: { name: 'asc' }
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
});
|
|
1209
|
+
|
|
1210
|
+
if (!bot) {
|
|
1211
|
+
return res.status(404).json({ error: 'Бот не найден' });
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
const allSettings = {
|
|
1215
|
+
bot: {
|
|
1216
|
+
id: bot.id,
|
|
1217
|
+
username: bot.username,
|
|
1218
|
+
prefix: bot.prefix,
|
|
1219
|
+
note: bot.note,
|
|
1220
|
+
owners: bot.owners,
|
|
1221
|
+
serverId: bot.serverId,
|
|
1222
|
+
proxyId: bot.proxyId,
|
|
1223
|
+
proxyHost: bot.proxyHost,
|
|
1224
|
+
proxyPort: bot.proxyPort,
|
|
1225
|
+
proxyUsername: bot.proxyUsername,
|
|
1226
|
+
},
|
|
1227
|
+
plugins: []
|
|
1228
|
+
};
|
|
1229
|
+
|
|
1230
|
+
const pluginSettingsPromises = bot.installedPlugins.map(async (plugin) => {
|
|
1231
|
+
const manifest = plugin.manifest ? JSON.parse(plugin.manifest) : {};
|
|
1232
|
+
|
|
1233
|
+
if (!manifest.settings || Object.keys(manifest.settings).length === 0) {
|
|
1234
|
+
return null;
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
const savedSettings = plugin.settings ? JSON.parse(plugin.settings) : {};
|
|
1238
|
+
let defaultSettings = {};
|
|
1239
|
+
|
|
1240
|
+
for (const key in manifest.settings) {
|
|
1241
|
+
const config = manifest.settings[key];
|
|
1242
|
+
if (config.type === 'json_file' && config.defaultPath) {
|
|
1243
|
+
const configFilePath = path.join(plugin.path, config.defaultPath);
|
|
1244
|
+
try {
|
|
1245
|
+
const fileContent = await fs.readFile(configFilePath, 'utf-8');
|
|
1246
|
+
defaultSettings[key] = JSON.parse(fileContent);
|
|
1247
|
+
} catch (e) { defaultSettings[key] = {}; }
|
|
1248
|
+
} else {
|
|
1249
|
+
try { defaultSettings[key] = JSON.parse(config.default || 'null'); }
|
|
1250
|
+
catch { defaultSettings[key] = config.default; }
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
const mergedSettings = deepMergeSettings(defaultSettings, savedSettings);
|
|
1255
|
+
|
|
1256
|
+
// Определяем тип настроек (обычные или группированные)
|
|
1257
|
+
const isGrouped = isGroupedSettings(manifest.settings);
|
|
1258
|
+
|
|
1259
|
+
// Фильтруем секретные значения
|
|
1260
|
+
const filteredSettings = filterSecretSettings(mergedSettings, manifest.settings, isGrouped);
|
|
1261
|
+
|
|
1262
|
+
return {
|
|
1263
|
+
id: plugin.id,
|
|
1264
|
+
name: plugin.name,
|
|
1265
|
+
description: plugin.description,
|
|
1266
|
+
isEnabled: plugin.isEnabled,
|
|
1267
|
+
manifest: manifest,
|
|
1268
|
+
settings: filteredSettings
|
|
1269
|
+
};
|
|
1270
|
+
});
|
|
1271
|
+
|
|
1272
|
+
allSettings.plugins = (await Promise.all(pluginSettingsPromises)).filter(Boolean);
|
|
1273
|
+
|
|
1274
|
+
res.json(allSettings);
|
|
1275
|
+
|
|
1276
|
+
} catch (error) {
|
|
1277
|
+
console.error("[API Error] /settings/all GET:", error);
|
|
1278
|
+
res.status(500).json({ error: 'Не удалось загрузить все настройки' });
|
|
1279
|
+
}
|
|
1280
|
+
});
|
|
1281
|
+
|
|
1282
|
+
const nodeRegistry = require('../../core/NodeRegistry');
|
|
1283
|
+
|
|
1284
|
+
router.get('/:botId/visual-editor/nodes', authenticateUniversal, checkBotAccess, authorize('management:view'), (req, res) => {
|
|
1285
|
+
try {
|
|
1286
|
+
const { graphType } = req.query;
|
|
1287
|
+
const nodesByCategory = nodeRegistry.getNodesByCategory(graphType);
|
|
1288
|
+
res.json(nodesByCategory);
|
|
1289
|
+
} catch (error) {
|
|
1290
|
+
console.error('[API Error] /visual-editor/nodes GET:', error);
|
|
1291
|
+
res.status(500).json({ error: 'Failed to get available nodes' });
|
|
1292
|
+
}
|
|
1293
|
+
});
|
|
1294
|
+
|
|
1295
|
+
router.get('/:botId/visual-editor/node-config', authenticateUniversal, checkBotAccess, authorize('management:view'), (req, res) => {
|
|
1296
|
+
try {
|
|
1297
|
+
const { types } = req.query;
|
|
1298
|
+
if (!types) {
|
|
1299
|
+
return res.status(400).json({ error: 'Node types must be provided' });
|
|
1300
|
+
}
|
|
1301
|
+
const typeArray = Array.isArray(types) ? types : [types];
|
|
1302
|
+
const config = nodeRegistry.getNodesByTypes(typeArray);
|
|
1303
|
+
res.json(config);
|
|
1304
|
+
} catch (error) {
|
|
1305
|
+
console.error('[API Error] /visual-editor/node-config GET:', error);
|
|
1306
|
+
res.status(500).json({ error: 'Failed to get node configuration' });
|
|
1307
|
+
}
|
|
1308
|
+
});
|
|
1309
|
+
|
|
1310
|
+
router.get('/:botId/visual-editor/permissions', authenticateUniversal, checkBotAccess, authorize('management:view'), async (req, res) => {
|
|
1311
|
+
try {
|
|
1312
|
+
const botId = parseInt(req.params.botId, 10);
|
|
1313
|
+
const permissions = await prisma.permission.findMany({
|
|
1314
|
+
where: { botId },
|
|
1315
|
+
orderBy: { name: 'asc' }
|
|
1316
|
+
});
|
|
1317
|
+
res.json(permissions);
|
|
1318
|
+
} catch (error) {
|
|
1319
|
+
console.error('[API Error] /visual-editor/permissions GET:', error);
|
|
1320
|
+
res.status(500).json({ error: 'Failed to get permissions' });
|
|
1321
|
+
}
|
|
1322
|
+
});
|
|
1323
|
+
|
|
1324
|
+
router.post('/:botId/commands/visual', authorize('management:edit'), async (req, res) => {
|
|
1325
|
+
try {
|
|
1326
|
+
const botId = parseInt(req.params.botId, 10);
|
|
1327
|
+
const {
|
|
1328
|
+
name,
|
|
1329
|
+
description,
|
|
1330
|
+
aliases = [],
|
|
1331
|
+
permissionId,
|
|
1332
|
+
cooldown = 0,
|
|
1333
|
+
allowedChatTypes = ['chat', 'private'],
|
|
1334
|
+
argumentsJson = '[]',
|
|
1335
|
+
graphJson = 'null'
|
|
1336
|
+
} = req.body;
|
|
1337
|
+
|
|
1338
|
+
if (!name) {
|
|
1339
|
+
return res.status(400).json({ error: 'Command name is required' });
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
const newCommand = await prisma.command.create({
|
|
1343
|
+
data: {
|
|
1344
|
+
botId,
|
|
1345
|
+
name,
|
|
1346
|
+
description,
|
|
1347
|
+
aliases: JSON.stringify(aliases),
|
|
1348
|
+
permissionId: permissionId || null,
|
|
1349
|
+
cooldown,
|
|
1350
|
+
allowedChatTypes: JSON.stringify(allowedChatTypes),
|
|
1351
|
+
isVisual: true,
|
|
1352
|
+
argumentsJson,
|
|
1353
|
+
graphJson,
|
|
1354
|
+
pluginOwnerId: null
|
|
1355
|
+
}
|
|
1356
|
+
});
|
|
1357
|
+
|
|
1358
|
+
botManager.reloadBotConfigInRealTime(botId);
|
|
1359
|
+
res.status(201).json(newCommand);
|
|
1360
|
+
} catch (error) {
|
|
1361
|
+
if (error.code === 'P2002') {
|
|
1362
|
+
return res.status(409).json({ error: 'Command with this name already exists' });
|
|
1363
|
+
}
|
|
1364
|
+
console.error('[API Error] /commands/visual POST:', error);
|
|
1365
|
+
res.status(500).json({ error: 'Failed to create visual command' });
|
|
1366
|
+
}
|
|
1367
|
+
});
|
|
1368
|
+
|
|
1369
|
+
router.put('/:botId/commands/:commandId/visual', authorize('management:edit'), async (req, res) => {
|
|
1370
|
+
try {
|
|
1371
|
+
const botId = parseInt(req.params.botId, 10);
|
|
1372
|
+
const commandId = parseInt(req.params.commandId, 10);
|
|
1373
|
+
const {
|
|
1374
|
+
name,
|
|
1375
|
+
description,
|
|
1376
|
+
aliases,
|
|
1377
|
+
permissionId,
|
|
1378
|
+
cooldown,
|
|
1379
|
+
allowedChatTypes,
|
|
1380
|
+
argumentsJson,
|
|
1381
|
+
graphJson
|
|
1382
|
+
} = req.body;
|
|
1383
|
+
|
|
1384
|
+
const dataToUpdate = { isVisual: true };
|
|
1385
|
+
|
|
1386
|
+
if (name) dataToUpdate.name = name;
|
|
1387
|
+
if (description !== undefined) dataToUpdate.description = description;
|
|
1388
|
+
if (Array.isArray(aliases)) dataToUpdate.aliases = JSON.stringify(aliases);
|
|
1389
|
+
if (permissionId !== undefined) dataToUpdate.permissionId = permissionId || null;
|
|
1390
|
+
if (typeof cooldown === 'number') dataToUpdate.cooldown = cooldown;
|
|
1391
|
+
if (Array.isArray(allowedChatTypes)) dataToUpdate.allowedChatTypes = JSON.stringify(allowedChatTypes);
|
|
1392
|
+
if (argumentsJson !== undefined) dataToUpdate.argumentsJson = argumentsJson;
|
|
1393
|
+
if (graphJson !== undefined) dataToUpdate.graphJson = graphJson;
|
|
1394
|
+
|
|
1395
|
+
const updatedCommand = await prisma.command.update({
|
|
1396
|
+
where: { id: commandId, botId },
|
|
1397
|
+
data: dataToUpdate
|
|
1398
|
+
});
|
|
1399
|
+
|
|
1400
|
+
if (graphJson && updatedCommand.pluginOwnerId) {
|
|
1401
|
+
try {
|
|
1402
|
+
const plugin = await prisma.installedPlugin.findUnique({
|
|
1403
|
+
where: { id: updatedCommand.pluginOwnerId }
|
|
1404
|
+
});
|
|
1405
|
+
|
|
1406
|
+
if (plugin) {
|
|
1407
|
+
const graphDir = path.join(plugin.path, 'graph');
|
|
1408
|
+
await fse.mkdir(graphDir, { recursive: true });
|
|
1409
|
+
|
|
1410
|
+
const graphFile = path.join(graphDir, `${updatedCommand.name}.json`);
|
|
1411
|
+
await fse.writeJson(graphFile, JSON.parse(graphJson), { spaces: 2 });
|
|
1412
|
+
console.log(`[API] Граф команды ${updatedCommand.name} сохранен в ${graphFile}`);
|
|
1413
|
+
}
|
|
1414
|
+
} catch (error) {
|
|
1415
|
+
console.error(`[API] Ошибка сохранения графа в папку плагина:`, error);
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
botManager.reloadBotConfigInRealTime(botId);
|
|
1420
|
+
res.json(updatedCommand);
|
|
1421
|
+
} catch (error) {
|
|
1422
|
+
if (error.code === 'P2002') {
|
|
1423
|
+
return res.status(409).json({ error: 'Command with this name already exists' });
|
|
1424
|
+
}
|
|
1425
|
+
console.error('[API Error] /commands/:commandId/visual PUT:', error);
|
|
1426
|
+
res.status(500).json({ error: 'Failed to update visual command' });
|
|
1427
|
+
}
|
|
1428
|
+
});
|
|
1429
|
+
|
|
1430
|
+
router.get('/:botId/commands/:commandId/export', authorize('management:view'), async (req, res) => {
|
|
1431
|
+
try {
|
|
1432
|
+
const botId = parseInt(req.params.botId, 10);
|
|
1433
|
+
const commandId = parseInt(req.params.commandId, 10);
|
|
1434
|
+
|
|
1435
|
+
const command = await prisma.command.findUnique({
|
|
1436
|
+
where: { id: commandId, botId: botId },
|
|
1437
|
+
});
|
|
1438
|
+
|
|
1439
|
+
if (!command) {
|
|
1440
|
+
return res.status(404).json({ error: 'Command not found' });
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
const exportData = {
|
|
1444
|
+
version: '1.0',
|
|
1445
|
+
type: 'command',
|
|
1446
|
+
...command
|
|
1447
|
+
};
|
|
1448
|
+
|
|
1449
|
+
delete exportData.id;
|
|
1450
|
+
delete exportData.botId;
|
|
1451
|
+
|
|
1452
|
+
res.json(exportData);
|
|
1453
|
+
} catch (error) {
|
|
1454
|
+
console.error('Failed to export command:', error);
|
|
1455
|
+
res.status(500).json({ error: 'Failed to export command' });
|
|
1456
|
+
}
|
|
1457
|
+
});
|
|
1458
|
+
|
|
1459
|
+
router.post('/:botId/commands/import', authorize('management:edit'), async (req, res) => {
|
|
1460
|
+
try {
|
|
1461
|
+
const botId = parseInt(req.params.botId, 10);
|
|
1462
|
+
const importData = req.body;
|
|
1463
|
+
|
|
1464
|
+
if (importData.type !== 'command') {
|
|
1465
|
+
return res.status(400).json({ error: 'Invalid file type. Expected "command".' });
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
let commandName = importData.name;
|
|
1469
|
+
let counter = 1;
|
|
1470
|
+
|
|
1471
|
+
while (await prisma.command.findFirst({ where: { botId, name: commandName } })) {
|
|
1472
|
+
commandName = `${importData.name}_imported_${counter}`;
|
|
1473
|
+
counter++;
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
let finalGraphJson = importData.graphJson;
|
|
1477
|
+
|
|
1478
|
+
if (finalGraphJson && finalGraphJson !== 'null') {
|
|
1479
|
+
const graph = JSON.parse(finalGraphJson);
|
|
1480
|
+
const nodeIdMap = new Map();
|
|
1481
|
+
|
|
1482
|
+
if (graph.nodes) {
|
|
1483
|
+
graph.nodes.forEach(node => {
|
|
1484
|
+
const oldId = node.id;
|
|
1485
|
+
const newId = `${node.type}-${randomUUID()}`;
|
|
1486
|
+
nodeIdMap.set(oldId, newId);
|
|
1487
|
+
node.id = newId;
|
|
1488
|
+
});
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1491
|
+
if (graph.connections) {
|
|
1492
|
+
graph.connections.forEach(conn => {
|
|
1493
|
+
conn.id = `edge-${randomUUID()}`;
|
|
1494
|
+
conn.sourceNodeId = nodeIdMap.get(conn.sourceNodeId) || conn.sourceNodeId;
|
|
1495
|
+
conn.targetNodeId = nodeIdMap.get(conn.targetNodeId) || conn.targetNodeId;
|
|
1496
|
+
});
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
finalGraphJson = JSON.stringify(graph);
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
const newCommand = await prisma.command.create({
|
|
1503
|
+
data: {
|
|
1504
|
+
botId: botId,
|
|
1505
|
+
name: commandName,
|
|
1506
|
+
description: importData.description,
|
|
1507
|
+
aliases: importData.aliases,
|
|
1508
|
+
permissionId: null,
|
|
1509
|
+
cooldown: importData.cooldown,
|
|
1510
|
+
allowedChatTypes: importData.allowedChatTypes,
|
|
1511
|
+
isVisual: importData.isVisual,
|
|
1512
|
+
isEnabled: importData.isEnabled,
|
|
1513
|
+
argumentsJson: importData.argumentsJson,
|
|
1514
|
+
graphJson: finalGraphJson,
|
|
1515
|
+
owner: 'visual_editor',
|
|
1516
|
+
}
|
|
1517
|
+
});
|
|
1518
|
+
|
|
1519
|
+
botManager.reloadBotConfigInRealTime(botId);
|
|
1520
|
+
res.status(201).json(newCommand);
|
|
1521
|
+
} catch (error) {
|
|
1522
|
+
console.error("Failed to import command:", error);
|
|
1523
|
+
res.status(500).json({ error: 'Failed to import command' });
|
|
1524
|
+
}
|
|
1525
|
+
});
|
|
1526
|
+
|
|
1527
|
+
router.post('/:botId/commands', authorize('management:edit'), async (req, res) => {
|
|
1528
|
+
try {
|
|
1529
|
+
const botId = parseInt(req.params.botId, 10);
|
|
1530
|
+
const {
|
|
1531
|
+
name,
|
|
1532
|
+
description,
|
|
1533
|
+
aliases = [],
|
|
1534
|
+
permissionId,
|
|
1535
|
+
cooldown = 0,
|
|
1536
|
+
allowedChatTypes = ['chat', 'private'],
|
|
1537
|
+
isVisual = false,
|
|
1538
|
+
argumentsJson = '[]',
|
|
1539
|
+
graphJson = 'null'
|
|
1540
|
+
} = req.body;
|
|
1541
|
+
|
|
1542
|
+
if (!name) {
|
|
1543
|
+
return res.status(400).json({ error: 'Command name is required' });
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
const newCommand = await prisma.command.create({
|
|
1547
|
+
data: {
|
|
1548
|
+
botId,
|
|
1549
|
+
name,
|
|
1550
|
+
description,
|
|
1551
|
+
aliases: JSON.stringify(aliases),
|
|
1552
|
+
permissionId: permissionId || null,
|
|
1553
|
+
cooldown,
|
|
1554
|
+
allowedChatTypes: JSON.stringify(allowedChatTypes),
|
|
1555
|
+
isVisual,
|
|
1556
|
+
argumentsJson,
|
|
1557
|
+
graphJson,
|
|
1558
|
+
owner: isVisual ? 'visual_editor' : 'manual',
|
|
1559
|
+
pluginOwnerId: null
|
|
1560
|
+
}
|
|
1561
|
+
});
|
|
1562
|
+
|
|
1563
|
+
if (graphJson && graphJson !== 'null' && req.body.pluginOwnerId) {
|
|
1564
|
+
try {
|
|
1565
|
+
const plugin = await prisma.installedPlugin.findUnique({
|
|
1566
|
+
where: { id: req.body.pluginOwnerId }
|
|
1567
|
+
});
|
|
1568
|
+
|
|
1569
|
+
if (plugin) {
|
|
1570
|
+
const graphDir = path.join(plugin.path, 'graph');
|
|
1571
|
+
await fse.mkdir(graphDir, { recursive: true });
|
|
1572
|
+
|
|
1573
|
+
const graphFile = path.join(graphDir, `${name}.json`);
|
|
1574
|
+
await fse.writeJson(graphFile, JSON.parse(graphJson), { spaces: 2 });
|
|
1575
|
+
console.log(`[API] Граф команды ${name} сохранен в ${graphFile}`);
|
|
1576
|
+
}
|
|
1577
|
+
} catch (error) {
|
|
1578
|
+
console.error(`[API] Ошибка сохранения графа в папку плагина:`, error);
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
botManager.reloadBotConfigInRealTime(botId);
|
|
1583
|
+
res.status(201).json(newCommand);
|
|
1584
|
+
} catch (error) {
|
|
1585
|
+
if (error.code === 'P2002') {
|
|
1586
|
+
return res.status(409).json({ error: 'Command with this name already exists' });
|
|
1587
|
+
}
|
|
1588
|
+
console.error('[API Error] /commands POST:', error);
|
|
1589
|
+
res.status(500).json({ error: 'Failed to create command' });
|
|
1590
|
+
}
|
|
1591
|
+
});
|
|
1592
|
+
|
|
1593
|
+
router.delete('/:botId/commands/:commandId', authorize('management:edit'), async (req, res) => {
|
|
1594
|
+
try {
|
|
1595
|
+
const botId = parseInt(req.params.botId, 10);
|
|
1596
|
+
const commandId = parseInt(req.params.commandId, 10);
|
|
1597
|
+
|
|
1598
|
+
await prisma.command.delete({
|
|
1599
|
+
where: { id: commandId, botId: botId },
|
|
1600
|
+
});
|
|
1601
|
+
|
|
1602
|
+
botManager.reloadBotConfigInRealTime(botId);
|
|
1603
|
+
res.status(204).send();
|
|
1604
|
+
} catch (error) {
|
|
1605
|
+
console.error(`[API Error] /commands/:commandId DELETE:`, error);
|
|
1606
|
+
res.status(500).json({ error: 'Failed to delete command' });
|
|
1607
|
+
}
|
|
1608
|
+
});
|
|
1609
|
+
|
|
1610
|
+
router.get('/:botId/event-graphs/:graphId', authorize('management:view'), async (req, res) => {
|
|
1611
|
+
try {
|
|
1612
|
+
const botId = parseInt(req.params.botId, 10);
|
|
1613
|
+
const graphId = parseInt(req.params.graphId, 10);
|
|
1614
|
+
|
|
1615
|
+
const eventGraph = await prisma.eventGraph.findUnique({
|
|
1616
|
+
where: { id: graphId, botId },
|
|
1617
|
+
include: { triggers: true },
|
|
1618
|
+
});
|
|
1619
|
+
|
|
1620
|
+
if (!eventGraph) {
|
|
1621
|
+
return res.status(404).json({ error: 'Граф события не найден' });
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
res.json(eventGraph);
|
|
1625
|
+
} catch (error) {
|
|
1626
|
+
console.error(`[API Error] /event-graphs/:graphId GET:`, error);
|
|
1627
|
+
res.status(500).json({ error: 'Не удалось получить граф события' });
|
|
1628
|
+
}
|
|
1629
|
+
});
|
|
1630
|
+
|
|
1631
|
+
router.post('/:botId/event-graphs', authorize('management:edit'), async (req, res) => {
|
|
1632
|
+
try {
|
|
1633
|
+
const botId = parseInt(req.params.botId, 10);
|
|
1634
|
+
const { name, description, graphJson, variables, eventType, isEnabled = true } = req.body;
|
|
1635
|
+
|
|
1636
|
+
if (!name || typeof name !== 'string' || name.trim() === '') {
|
|
1637
|
+
return res.status(400).json({ error: 'Имя графа обязательно и должно быть непустой строкой' });
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
let graphJsonString;
|
|
1641
|
+
if (graphJson) {
|
|
1642
|
+
if (typeof graphJson === 'string') {
|
|
1643
|
+
graphJsonString = graphJson;
|
|
1644
|
+
} else {
|
|
1645
|
+
graphJsonString = JSON.stringify(graphJson);
|
|
1646
|
+
}
|
|
1647
|
+
} else {
|
|
1648
|
+
graphJsonString = JSON.stringify({
|
|
1649
|
+
nodes: [],
|
|
1650
|
+
connections: []
|
|
1651
|
+
});
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
console.log('[API] Final graphJsonString:', graphJsonString);
|
|
1655
|
+
|
|
1656
|
+
let eventTypes = [];
|
|
1657
|
+
try {
|
|
1658
|
+
const parsedGraph = JSON.parse(graphJsonString);
|
|
1659
|
+
if (parsedGraph.nodes && Array.isArray(parsedGraph.nodes)) {
|
|
1660
|
+
const eventNodes = parsedGraph.nodes.filter(node => node.type && node.type.startsWith('event:'));
|
|
1661
|
+
eventTypes = [...new Set(eventNodes.map(node => node.type.split(':')[1]))];
|
|
1662
|
+
}
|
|
1663
|
+
} catch (error) {
|
|
1664
|
+
console.warn('[API] Не удалось извлечь типы событий из графа:', error.message);
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
const newEventGraph = await prisma.eventGraph.create({
|
|
1668
|
+
data: {
|
|
1669
|
+
botId,
|
|
1670
|
+
name: name.trim(),
|
|
1671
|
+
description: description || '',
|
|
1672
|
+
isEnabled: isEnabled,
|
|
1673
|
+
graphJson: graphJsonString,
|
|
1674
|
+
variables: variables || '[]',
|
|
1675
|
+
eventType: eventType || 'custom',
|
|
1676
|
+
triggers: {
|
|
1677
|
+
create: eventTypes.map(eventType => ({ eventType }))
|
|
1678
|
+
}
|
|
1679
|
+
},
|
|
1680
|
+
include: { triggers: true }
|
|
1681
|
+
});
|
|
1682
|
+
|
|
1683
|
+
console.log('[API] Created event graph:', newEventGraph);
|
|
1684
|
+
res.status(201).json(newEventGraph);
|
|
1685
|
+
} catch (error) {
|
|
1686
|
+
if (error.code === 'P2002') {
|
|
1687
|
+
return res.status(409).json({ error: 'Граф событий с таким именем уже существует' });
|
|
1688
|
+
}
|
|
1689
|
+
console.error(`[API Error] /event-graphs POST:`, error);
|
|
1690
|
+
res.status(500).json({ error: 'Не удалось создать граф событий' });
|
|
1691
|
+
}
|
|
1692
|
+
});
|
|
1693
|
+
|
|
1694
|
+
router.delete('/:botId/event-graphs/:graphId', authorize('management:edit'), async (req, res) => {
|
|
1695
|
+
try {
|
|
1696
|
+
const botId = parseInt(req.params.botId, 10);
|
|
1697
|
+
const graphId = parseInt(req.params.graphId, 10);
|
|
1698
|
+
|
|
1699
|
+
await prisma.eventGraph.delete({
|
|
1700
|
+
where: { id: graphId, botId: botId },
|
|
1701
|
+
});
|
|
1702
|
+
|
|
1703
|
+
res.status(204).send();
|
|
1704
|
+
} catch (error) {
|
|
1705
|
+
console.error(`[API Error] /event-graphs/:graphId DELETE:`, error);
|
|
1706
|
+
res.status(500).json({ error: 'Не удалось удалить граф событий' });
|
|
1707
|
+
}
|
|
1708
|
+
});
|
|
1709
|
+
|
|
1710
|
+
router.put('/:botId/event-graphs/:graphId', authorize('management:edit'), async (req, res) => {
|
|
1711
|
+
const { botId, graphId } = req.params;
|
|
1712
|
+
const { name, isEnabled, graphJson, variables, pluginOwnerId } = req.body;
|
|
1713
|
+
|
|
1714
|
+
if (!name || typeof name !== 'string' || name.trim() === '') {
|
|
1715
|
+
return res.status(400).json({ error: 'Поле name обязательно и должно быть непустой строкой.' });
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
if (typeof isEnabled !== 'boolean') {
|
|
1719
|
+
return res.status(400).json({ error: 'Поле isEnabled должно быть true или false.' });
|
|
1720
|
+
}
|
|
1721
|
+
|
|
1722
|
+
try {
|
|
1723
|
+
const dataToUpdate = {
|
|
1724
|
+
name: name.trim(),
|
|
1725
|
+
isEnabled,
|
|
1726
|
+
};
|
|
1727
|
+
|
|
1728
|
+
if (graphJson !== undefined) {
|
|
1729
|
+
dataToUpdate.graphJson = graphJson;
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
if (variables !== undefined) {
|
|
1733
|
+
dataToUpdate.variables = Array.isArray(variables) ? JSON.stringify(variables) : variables;
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1736
|
+
if (pluginOwnerId !== undefined) {
|
|
1737
|
+
dataToUpdate.pluginOwnerId = pluginOwnerId;
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1740
|
+
const updatedGraph = await prisma.eventGraph.update({
|
|
1741
|
+
where: { id: parseInt(graphId), botId: parseInt(botId) },
|
|
1742
|
+
data: dataToUpdate
|
|
1743
|
+
});
|
|
1744
|
+
|
|
1745
|
+
res.json(updatedGraph);
|
|
1746
|
+
} catch (error) {
|
|
1747
|
+
console.error(`[API Error] /event-graphs/:graphId PUT:`, error);
|
|
1748
|
+
res.status(500).json({ error: 'Ошибка при обновлении графа событий.' });
|
|
1749
|
+
}
|
|
1750
|
+
});
|
|
1751
|
+
|
|
1752
|
+
router.post('/:botId/visual-editor/save', authorize('management:edit'), async (req, res) => {
|
|
1753
|
+
});
|
|
1754
|
+
|
|
1755
|
+
router.get('/:botId/ui-extensions', authorize('plugin:list'), async (req, res) => {
|
|
1756
|
+
try {
|
|
1757
|
+
const botId = parseInt(req.params.botId, 10);
|
|
1758
|
+
const enabledPlugins = await prisma.installedPlugin.findMany({
|
|
1759
|
+
where: { botId: botId, isEnabled: true }
|
|
1760
|
+
});
|
|
1761
|
+
|
|
1762
|
+
const extensions = [];
|
|
1763
|
+
for (const plugin of enabledPlugins) {
|
|
1764
|
+
if (plugin.manifest) {
|
|
1765
|
+
try {
|
|
1766
|
+
const manifest = JSON.parse(plugin.manifest);
|
|
1767
|
+
if (manifest.uiExtensions && Array.isArray(manifest.uiExtensions)) {
|
|
1768
|
+
manifest.uiExtensions.forEach(ext => {
|
|
1769
|
+
extensions.push({
|
|
1770
|
+
pluginName: plugin.name,
|
|
1771
|
+
...ext
|
|
1772
|
+
});
|
|
1773
|
+
});
|
|
1774
|
+
}
|
|
1775
|
+
} catch (e) {
|
|
1776
|
+
console.error(`Ошибка парсинга манифеста для плагина ${plugin.name}:`, e);
|
|
1777
|
+
}
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
res.json(extensions);
|
|
1781
|
+
} catch (error) {
|
|
1782
|
+
res.status(500).json({ error: 'Не удалось получить расширения интерфейса' });
|
|
1783
|
+
}
|
|
1784
|
+
});
|
|
1785
|
+
|
|
1786
|
+
router.get('/:botId/plugins/:pluginName/ui-content/:path', authorize('plugin:list'), async (req, res) => {
|
|
1787
|
+
const { botId, pluginName, path: uiPath } = req.params;
|
|
1788
|
+
const numericBotId = parseInt(botId, 10);
|
|
1789
|
+
|
|
1790
|
+
try {
|
|
1791
|
+
const plugin = await prisma.installedPlugin.findFirst({
|
|
1792
|
+
where: { botId: numericBotId, name: pluginName, isEnabled: true }
|
|
1793
|
+
});
|
|
1794
|
+
|
|
1795
|
+
if (!plugin) {
|
|
1796
|
+
return res.status(404).json({ error: `Активный плагин "${pluginName}" не найден для этого бота.` });
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1799
|
+
const manifest = plugin.manifest ? JSON.parse(plugin.manifest) : {};
|
|
1800
|
+
const savedSettings = plugin.settings ? JSON.parse(plugin.settings) : {};
|
|
1801
|
+
const defaultSettings = {};
|
|
1802
|
+
|
|
1803
|
+
if (manifest.settings) {
|
|
1804
|
+
for (const key in manifest.settings) {
|
|
1805
|
+
const config = manifest.settings[key];
|
|
1806
|
+
if (config.type === 'json_file' && config.defaultPath) {
|
|
1807
|
+
const configFilePath = path.join(plugin.path, config.defaultPath);
|
|
1808
|
+
try {
|
|
1809
|
+
const fileContent = await fs.readFile(configFilePath, 'utf-8');
|
|
1810
|
+
defaultSettings[key] = JSON.parse(fileContent);
|
|
1811
|
+
} catch (e) { defaultSettings[key] = {}; }
|
|
1812
|
+
} else {
|
|
1813
|
+
try { defaultSettings[key] = JSON.parse(config.default || 'null'); }
|
|
1814
|
+
catch { defaultSettings[key] = config.default; }
|
|
1815
|
+
}
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1818
|
+
const finalSettings = { ...defaultSettings, ...savedSettings };
|
|
1819
|
+
|
|
1820
|
+
const mainFilePath = manifest.main || 'index.js';
|
|
1821
|
+
const pluginEntryPoint = path.join(plugin.path, mainFilePath);
|
|
1822
|
+
|
|
1823
|
+
delete require.cache[require.resolve(pluginEntryPoint)];
|
|
1824
|
+
const pluginModule = require(pluginEntryPoint);
|
|
1825
|
+
|
|
1826
|
+
if (typeof pluginModule.getUiPageContent !== 'function') {
|
|
1827
|
+
return res.status(501).json({ error: `Плагин "${pluginName}" не предоставляет кастомный UI контент.` });
|
|
1828
|
+
}
|
|
1829
|
+
|
|
1830
|
+
const botProcess = botManager.bots.get(numericBotId);
|
|
1831
|
+
const botApi = botProcess ? botProcess.api : null;
|
|
1832
|
+
|
|
1833
|
+
const content = await pluginModule.getUiPageContent({
|
|
1834
|
+
path: uiPath,
|
|
1835
|
+
bot: botApi,
|
|
1836
|
+
botId: numericBotId,
|
|
1837
|
+
settings: finalSettings
|
|
1838
|
+
});
|
|
1839
|
+
|
|
1840
|
+
if (content === null) {
|
|
1841
|
+
return res.status(404).json({ error: `Для пути "${uiPath}" не найдено содержимого в плагине "${pluginName}".` });
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1844
|
+
res.json(content);
|
|
1845
|
+
|
|
1846
|
+
} catch (error) {
|
|
1847
|
+
console.error(`[UI Content] Ошибка при получении контента для плагина "${pluginName}":`, error);
|
|
1848
|
+
res.status(500).json({ error: error.message || 'Внутренняя ошибка сервера.' });
|
|
1849
|
+
}
|
|
1850
|
+
});
|
|
1851
|
+
|
|
1852
|
+
|
|
1853
|
+
router.post('/:botId/plugins/:pluginName/action', authorize('plugin:list'), async (req, res) => {
|
|
1854
|
+
const { botId, pluginName } = req.params;
|
|
1855
|
+
const { actionName, payload } = req.body;
|
|
1856
|
+
const numericBotId = parseInt(botId, 10);
|
|
1857
|
+
|
|
1858
|
+
if (!actionName) {
|
|
1859
|
+
return res.status(400).json({ error: 'Необходимо указать "actionName".' });
|
|
1860
|
+
}
|
|
1861
|
+
|
|
1862
|
+
try {
|
|
1863
|
+
const botProcess = botManager.bots.get(numericBotId);
|
|
1864
|
+
|
|
1865
|
+
if (!botProcess) {
|
|
1866
|
+
return res.status(404).json({ error: 'Бот не найден или не запущен.' });
|
|
1867
|
+
}
|
|
1868
|
+
|
|
1869
|
+
const plugin = await prisma.installedPlugin.findFirst({
|
|
1870
|
+
where: { botId: numericBotId, name: pluginName, isEnabled: true }
|
|
1871
|
+
});
|
|
1872
|
+
|
|
1873
|
+
if (!plugin) {
|
|
1874
|
+
return res.status(404).json({ error: `Активный плагин с таким именем "${pluginName}" не найден.` });
|
|
1875
|
+
}
|
|
1876
|
+
|
|
1877
|
+
const manifest = plugin.manifest ? JSON.parse(plugin.manifest) : {};
|
|
1878
|
+
const savedSettings = plugin.settings ? JSON.parse(plugin.settings) : {};
|
|
1879
|
+
const defaultSettings = {};
|
|
1880
|
+
|
|
1881
|
+
if (manifest.settings) {
|
|
1882
|
+
for (const key in manifest.settings) {
|
|
1883
|
+
const config = manifest.settings[key];
|
|
1884
|
+
if (config.type === 'json_file' && config.defaultPath) {
|
|
1885
|
+
const configFilePath = path.join(plugin.path, config.defaultPath);
|
|
1886
|
+
try {
|
|
1887
|
+
const fileContent = await fs.readFile(configFilePath, 'utf-8');
|
|
1888
|
+
defaultSettings[key] = JSON.parse(fileContent);
|
|
1889
|
+
} catch (e) {
|
|
1890
|
+
console.error(`[Action] Не удалось прочитать defaultPath для ${pluginName}: ${e.message}`);
|
|
1891
|
+
defaultSettings[key] = {};
|
|
1892
|
+
}
|
|
1893
|
+
} else {
|
|
1894
|
+
try {
|
|
1895
|
+
defaultSettings[key] = JSON.parse(config.default || 'null');
|
|
1896
|
+
} catch {
|
|
1897
|
+
defaultSettings[key] = config.default;
|
|
1898
|
+
}
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1902
|
+
const finalSettings = { ...defaultSettings, ...savedSettings };
|
|
1903
|
+
|
|
1904
|
+
const mainFilePath = manifest.main || 'index.js';
|
|
1905
|
+
const pluginPath = path.join(plugin.path, mainFilePath);
|
|
1906
|
+
|
|
1907
|
+
delete require.cache[require.resolve(pluginPath)];
|
|
1908
|
+
const pluginModule = require(pluginPath);
|
|
1909
|
+
|
|
1910
|
+
if (typeof pluginModule.handleAction !== 'function') {
|
|
1911
|
+
return res.status(501).json({ error: `Плагин "${pluginName}" не поддерживает обработку действий.` });
|
|
1912
|
+
}
|
|
1913
|
+
|
|
1914
|
+
const result = await pluginModule.handleAction({
|
|
1915
|
+
botProcess: botProcess,
|
|
1916
|
+
botId: numericBotId,
|
|
1917
|
+
action: actionName,
|
|
1918
|
+
payload: payload,
|
|
1919
|
+
settings: finalSettings
|
|
1920
|
+
});
|
|
1921
|
+
|
|
1922
|
+
res.json({ success: true, message: 'Действие выполнено.', result: result || null });
|
|
1923
|
+
|
|
1924
|
+
} catch (error) {
|
|
1925
|
+
console.error(`Ошибка выполнения действия "${actionName}" для плагина "${pluginName}":`, error);
|
|
1926
|
+
res.status(500).json({ error: error.message || 'Внутренняя ошибка сервера.' });
|
|
1927
|
+
}
|
|
1928
|
+
});
|
|
1929
|
+
|
|
1930
|
+
|
|
1931
|
+
router.get('/:botId/export', authenticateUniversal, checkBotAccess, authorize('bot:export'), async (req, res) => {
|
|
1932
|
+
try {
|
|
1933
|
+
const botId = parseInt(req.params.botId, 10);
|
|
1934
|
+
const {
|
|
1935
|
+
includeCommands,
|
|
1936
|
+
includePermissions,
|
|
1937
|
+
includePluginFiles,
|
|
1938
|
+
includePluginDataStore,
|
|
1939
|
+
includeEventGraphs,
|
|
1940
|
+
} = req.query;
|
|
1941
|
+
|
|
1942
|
+
const bot = await prisma.bot.findUnique({ where: { id: botId } });
|
|
1943
|
+
if (!bot) {
|
|
1944
|
+
return res.status(404).json({ error: 'Bot not found' });
|
|
1945
|
+
}
|
|
1946
|
+
|
|
1947
|
+
const archive = archiver('zip', { zlib: { level: 9 } });
|
|
1948
|
+
res.attachment(`bot_${bot.username}_export_${new Date().toISOString()}.zip`);
|
|
1949
|
+
archive.pipe(res);
|
|
1950
|
+
|
|
1951
|
+
const botData = { ...bot };
|
|
1952
|
+
delete botData.password;
|
|
1953
|
+
delete botData.proxyPassword;
|
|
1954
|
+
archive.append(JSON.stringify(botData, null, 2), { name: 'bot.json' });
|
|
1955
|
+
|
|
1956
|
+
if (includeCommands === 'true') {
|
|
1957
|
+
const commands = await prisma.command.findMany({ where: { botId } });
|
|
1958
|
+
archive.append(JSON.stringify(commands, null, 2), { name: 'commands.json' });
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1961
|
+
if (includePermissions === 'true') {
|
|
1962
|
+
const users = await prisma.user.findMany({ where: { botId }, include: { groups: { include: { group: true } } } });
|
|
1963
|
+
const groups = await prisma.group.findMany({ where: { botId }, include: { permissions: { include: { permission: true } } } });
|
|
1964
|
+
const permissions = await prisma.permission.findMany({ where: { botId } });
|
|
1965
|
+
const permissionsData = { users, groups, permissions };
|
|
1966
|
+
archive.append(JSON.stringify(permissionsData, null, 2), { name: 'permissions.json' });
|
|
1967
|
+
}
|
|
1968
|
+
|
|
1969
|
+
if (includeEventGraphs === 'true') {
|
|
1970
|
+
const eventGraphs = await prisma.eventGraph.findMany({ where: { botId } });
|
|
1971
|
+
archive.append(JSON.stringify(eventGraphs, null, 2), { name: 'event_graphs.json' });
|
|
1972
|
+
}
|
|
1973
|
+
|
|
1974
|
+
if (includePluginFiles === 'true' || includePluginDataStore === 'true') {
|
|
1975
|
+
const installedPlugins = await prisma.installedPlugin.findMany({ where: { botId } });
|
|
1976
|
+
archive.append(JSON.stringify(installedPlugins, null, 2), { name: 'plugins.json' });
|
|
1977
|
+
|
|
1978
|
+
try {
|
|
1979
|
+
const installedPlugins = await prisma.installedPlugin.findMany({ where: { botId } });
|
|
1980
|
+
const pluginSettings = installedPlugins
|
|
1981
|
+
.filter(plugin => plugin.settings && plugin.settings !== '{}')
|
|
1982
|
+
.map(plugin => ({
|
|
1983
|
+
pluginName: plugin.name,
|
|
1984
|
+
settings: plugin.settings
|
|
1985
|
+
}));
|
|
1986
|
+
|
|
1987
|
+
if (pluginSettings.length > 0) {
|
|
1988
|
+
console.log(`[Export] Экспорт настроек плагинов для бота ${botId}: ${pluginSettings.length} настроек`);
|
|
1989
|
+
archive.append(JSON.stringify(pluginSettings, null, 2), { name: 'settings.json' });
|
|
1990
|
+
} else {
|
|
1991
|
+
console.log(`[Export] Нет настроек плагинов для экспорта`);
|
|
1992
|
+
}
|
|
1993
|
+
} catch (error) {
|
|
1994
|
+
console.warn(`[Export] Ошибка при экспорте настроек плагинов:`, error.message);
|
|
1995
|
+
}
|
|
1996
|
+
|
|
1997
|
+
if (includePluginFiles === 'true') {
|
|
1998
|
+
for (const plugin of installedPlugins) {
|
|
1999
|
+
const pluginPath = plugin.path;
|
|
2000
|
+
if (await fs.stat(pluginPath).then(s => s.isDirectory()).catch(() => false)) {
|
|
2001
|
+
archive.directory(pluginPath, `plugins/${plugin.name}`);
|
|
2002
|
+
}
|
|
2003
|
+
}
|
|
2004
|
+
}
|
|
2005
|
+
if (includePluginDataStore === 'true') {
|
|
2006
|
+
console.log(`[Export] Экспорт PluginDataStore для бота ${botId}`);
|
|
2007
|
+
const pluginDataStore = await prisma.pluginDataStore.findMany({
|
|
2008
|
+
where: { botId: parseInt(botId) }
|
|
2009
|
+
});
|
|
2010
|
+
console.log(`[Export] Найдено записей PluginDataStore: ${pluginDataStore.length}`);
|
|
2011
|
+
if (pluginDataStore.length > 0) {
|
|
2012
|
+
archive.append(JSON.stringify(pluginDataStore, null, 2), { name: 'plugin_data_store.json' });
|
|
2013
|
+
console.log(`[Export] Данные PluginDataStore добавлены в архив`);
|
|
2014
|
+
} else {
|
|
2015
|
+
console.log(`[Export] Нет данных PluginDataStore для экспорта`);
|
|
2016
|
+
}
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
|
|
2020
|
+
await archive.finalize();
|
|
2021
|
+
|
|
2022
|
+
} catch (error) {
|
|
2023
|
+
console.error('Failed to export bot:', error);
|
|
2024
|
+
if (!res.headersSent) {
|
|
2025
|
+
res.status(500).json({ error: `Failed to export bot: ${error.message}` });
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
2028
|
+
});
|
|
2029
|
+
|
|
2030
|
+
router.post('/import', authorize('bot:create'), upload.single('file'), async (req, res) => {
|
|
2031
|
+
if (!req.file) {
|
|
2032
|
+
return res.status(400).json({ error: 'No file uploaded.' });
|
|
2033
|
+
}
|
|
2034
|
+
|
|
2035
|
+
const botIdMap = new Map();
|
|
2036
|
+
|
|
2037
|
+
try {
|
|
2038
|
+
const zip = new AdmZip(req.file.buffer);
|
|
2039
|
+
const zipEntries = zip.getEntries();
|
|
2040
|
+
|
|
2041
|
+
const botDataEntry = zipEntries.find(e => e.entryName === 'bot.json');
|
|
2042
|
+
if (!botDataEntry) {
|
|
2043
|
+
return res.status(400).json({ error: 'Archive missing bot.json' });
|
|
2044
|
+
}
|
|
2045
|
+
const botData = JSON.parse(botDataEntry.getData().toString('utf8'));
|
|
2046
|
+
|
|
2047
|
+
const server = await prisma.server.findFirst();
|
|
2048
|
+
if (!server) {
|
|
2049
|
+
return res.status(500).json({ error: 'No servers configured in the target system.' });
|
|
2050
|
+
}
|
|
2051
|
+
|
|
2052
|
+
let newBotName = botData.username;
|
|
2053
|
+
let counter = 1;
|
|
2054
|
+
while (await prisma.bot.findFirst({ where: { username: newBotName } })) {
|
|
2055
|
+
newBotName = `${botData.username}_imported_${counter}`;
|
|
2056
|
+
counter++;
|
|
2057
|
+
}
|
|
2058
|
+
|
|
2059
|
+
const newBot = await prisma.bot.create({
|
|
2060
|
+
data: {
|
|
2061
|
+
...botData,
|
|
2062
|
+
id: undefined,
|
|
2063
|
+
username: newBotName,
|
|
2064
|
+
serverId: server.id,
|
|
2065
|
+
password: null,
|
|
2066
|
+
proxyPassword: null
|
|
2067
|
+
},
|
|
2068
|
+
include: { server: true }
|
|
2069
|
+
});
|
|
2070
|
+
|
|
2071
|
+
botIdMap.set(botData.id, newBot.id);
|
|
2072
|
+
|
|
2073
|
+
const permissionsEntry = zipEntries.find(e => e.entryName === 'permissions.json');
|
|
2074
|
+
let pMap = new Map();
|
|
2075
|
+
|
|
2076
|
+
if (permissionsEntry) {
|
|
2077
|
+
const { users, groups, permissions } = JSON.parse(permissionsEntry.getData().toString('utf8'));
|
|
2078
|
+
|
|
2079
|
+
await setupDefaultPermissionsForBot(newBot.id, prisma);
|
|
2080
|
+
|
|
2081
|
+
for(let p of permissions.filter(p=>p.owner === 'system')) {
|
|
2082
|
+
const existingPermission = await prisma.permission.findFirst({
|
|
2083
|
+
where: {
|
|
2084
|
+
botId: newBot.id,
|
|
2085
|
+
name: p.name,
|
|
2086
|
+
owner: 'system'
|
|
2087
|
+
}
|
|
2088
|
+
});
|
|
2089
|
+
if (existingPermission) {
|
|
2090
|
+
pMap.set(p.id, existingPermission.id);
|
|
2091
|
+
}
|
|
2092
|
+
}
|
|
2093
|
+
|
|
2094
|
+
for(let p of permissions.filter(p=>p.owner !== 'system')) {
|
|
2095
|
+
const newP = await prisma.permission.create({ data: { ...p, id: undefined, botId: newBot.id }});
|
|
2096
|
+
pMap.set(p.id, newP.id);
|
|
2097
|
+
}
|
|
2098
|
+
|
|
2099
|
+
const gMap = new Map();
|
|
2100
|
+
for(let g of groups.filter(g=>g.owner !== 'system')) {
|
|
2101
|
+
const newG = await prisma.group.create({ data: { ...g, id: undefined, botId: newBot.id, permissions: {
|
|
2102
|
+
create: g.permissions.map(gp => ({ permissionId: pMap.get(gp.permissionId) })).filter(p=>p.permissionId)
|
|
2103
|
+
}}});
|
|
2104
|
+
gMap.set(g.id, newG.id);
|
|
2105
|
+
}
|
|
2106
|
+
|
|
2107
|
+
for(let u of users) {
|
|
2108
|
+
await prisma.user.create({ data: { ...u, id: undefined, botId: newBot.id, groups: {
|
|
2109
|
+
create: u.groups.map(ug => ({ groupId: gMap.get(ug.groupId) })).filter(g=>g.groupId)
|
|
2110
|
+
}}});
|
|
2111
|
+
}
|
|
2112
|
+
}
|
|
2113
|
+
|
|
2114
|
+
const pluginDataStoreEntry = zipEntries.find(e => e.entryName === 'plugin_data_store.json');
|
|
2115
|
+
if (pluginDataStoreEntry) {
|
|
2116
|
+
console.log(`[Import] Импорт PluginDataStore для бота ${newBot.id}`);
|
|
2117
|
+
const pluginDataStore = JSON.parse(pluginDataStoreEntry.getData().toString('utf8'));
|
|
2118
|
+
console.log(`[Import] Найдено записей PluginDataStore: ${pluginDataStore.length}`);
|
|
2119
|
+
|
|
2120
|
+
for (let dataRecord of pluginDataStore) {
|
|
2121
|
+
delete dataRecord.id;
|
|
2122
|
+
dataRecord.botId = newBot.id;
|
|
2123
|
+
await prisma.pluginDataStore.create({ data: dataRecord });
|
|
2124
|
+
}
|
|
2125
|
+
console.log(`[Import] PluginDataStore успешно импортирован`);
|
|
2126
|
+
}
|
|
2127
|
+
|
|
2128
|
+
const pluginsEntry = zipEntries.find(e => e.entryName === 'plugins.json');
|
|
2129
|
+
let pluginMap = new Map();
|
|
2130
|
+
|
|
2131
|
+
if (pluginsEntry) {
|
|
2132
|
+
const plugins = JSON.parse(pluginsEntry.getData().toString('utf8'));
|
|
2133
|
+
const pluginsDir = path.join(os.homedir(), '.blockmine', 'storage', 'plugins');
|
|
2134
|
+
const botPluginsDir = path.join(pluginsDir, newBot.username);
|
|
2135
|
+
await fs.mkdir(botPluginsDir, { recursive: true });
|
|
2136
|
+
|
|
2137
|
+
for (let pluginData of plugins) {
|
|
2138
|
+
const oldPath = pluginData.path;
|
|
2139
|
+
const pluginName = pluginData.name;
|
|
2140
|
+
const newPluginPath = path.join(botPluginsDir, pluginName);
|
|
2141
|
+
|
|
2142
|
+
const oldPluginId = pluginData.id;
|
|
2143
|
+
delete pluginData.id;
|
|
2144
|
+
pluginData.botId = newBot.id;
|
|
2145
|
+
pluginData.path = path.resolve(newPluginPath);
|
|
2146
|
+
|
|
2147
|
+
for (const entry of zipEntries) {
|
|
2148
|
+
if (entry.entryName.startsWith(`plugins/${pluginName}/`)) {
|
|
2149
|
+
const relativePath = entry.entryName.replace(`plugins/${pluginName}/`, '');
|
|
2150
|
+
if (relativePath) {
|
|
2151
|
+
const destPath = path.join(newPluginPath, relativePath);
|
|
2152
|
+
const destDir = path.dirname(destPath);
|
|
2153
|
+
await fs.mkdir(destDir, { recursive: true });
|
|
2154
|
+
|
|
2155
|
+
if (!entry.isDirectory) {
|
|
2156
|
+
await fs.writeFile(destPath, entry.getData());
|
|
2157
|
+
}
|
|
2158
|
+
}
|
|
2159
|
+
}
|
|
2160
|
+
}
|
|
2161
|
+
|
|
2162
|
+
try {
|
|
2163
|
+
await pluginManager._installDependencies(newPluginPath);
|
|
2164
|
+
} catch (e) {
|
|
2165
|
+
console.warn(`[Import] Не удалось установить зависимости для плагина ${pluginName}: ${e.message}`);
|
|
2166
|
+
}
|
|
2167
|
+
|
|
2168
|
+
let newPlugin;
|
|
2169
|
+
try {
|
|
2170
|
+
newPlugin = await pluginManager.registerPlugin(newBot.id, newPluginPath, 'LOCAL', newPluginPath);
|
|
2171
|
+
} catch (e) {
|
|
2172
|
+
newPlugin = await prisma.installedPlugin.create({ data: pluginData });
|
|
2173
|
+
}
|
|
2174
|
+
pluginMap.set(oldPluginId, newPlugin.id);
|
|
2175
|
+
}
|
|
2176
|
+
}
|
|
2177
|
+
|
|
2178
|
+
const commandsEntry = zipEntries.find(e => e.entryName === 'commands.json');
|
|
2179
|
+
if (commandsEntry) {
|
|
2180
|
+
const commands = JSON.parse(commandsEntry.getData().toString('utf8'));
|
|
2181
|
+
for (let command of commands) {
|
|
2182
|
+
delete command.id;
|
|
2183
|
+
command.botId = newBot.id;
|
|
2184
|
+
|
|
2185
|
+
if (command.permissionId && pMap.has(command.permissionId)) {
|
|
2186
|
+
command.permissionId = pMap.get(command.permissionId);
|
|
2187
|
+
} else {
|
|
2188
|
+
command.permissionId = null;
|
|
2189
|
+
}
|
|
2190
|
+
|
|
2191
|
+
if (command.pluginOwnerId && pluginMap.has(command.pluginOwnerId)) {
|
|
2192
|
+
command.pluginOwnerId = pluginMap.get(command.pluginOwnerId);
|
|
2193
|
+
} else {
|
|
2194
|
+
command.pluginOwnerId = null;
|
|
2195
|
+
}
|
|
2196
|
+
|
|
2197
|
+
try {
|
|
2198
|
+
await prisma.command.create({ data: command });
|
|
2199
|
+
} catch (error) {
|
|
2200
|
+
console.warn(`[Import] Пропущена команда ${command.name}: ${error.message}`);
|
|
2201
|
+
}
|
|
2202
|
+
}
|
|
2203
|
+
}
|
|
2204
|
+
|
|
2205
|
+
const eventGraphsEntry = zipEntries.find(e => e.entryName === 'event_graphs.json');
|
|
2206
|
+
if (eventGraphsEntry) {
|
|
2207
|
+
const eventGraphs = JSON.parse(eventGraphsEntry.getData().toString('utf8'));
|
|
2208
|
+
for (let graph of eventGraphs) {
|
|
2209
|
+
delete graph.id;
|
|
2210
|
+
graph.botId = newBot.id;
|
|
2211
|
+
|
|
2212
|
+
if (graph.pluginOwnerId && pluginMap.has(graph.pluginOwnerId)) {
|
|
2213
|
+
graph.pluginOwnerId = pluginMap.get(graph.pluginOwnerId);
|
|
2214
|
+
} else {
|
|
2215
|
+
graph.pluginOwnerId = null;
|
|
2216
|
+
}
|
|
2217
|
+
|
|
2218
|
+
try {
|
|
2219
|
+
await prisma.eventGraph.create({ data: graph });
|
|
2220
|
+
} catch (error) {
|
|
2221
|
+
console.warn(`[Import] Пропущен граф ${graph.name}: ${error.message}`);
|
|
2222
|
+
}
|
|
2223
|
+
}
|
|
2224
|
+
}
|
|
2225
|
+
|
|
2226
|
+
res.status(201).json(newBot);
|
|
2227
|
+
|
|
2228
|
+
} catch (error) {
|
|
2229
|
+
console.error('Failed to import bot:', error);
|
|
2230
|
+
res.status(500).json({ error: `Failed to import bot: ${error.message}` });
|
|
2231
|
+
}
|
|
2232
|
+
});
|
|
2233
|
+
|
|
2234
|
+
router.post('/import/preview', authorize('bot:create'), upload.single('file'), async (req, res) => {
|
|
2235
|
+
try {
|
|
2236
|
+
if (!req.file) {
|
|
2237
|
+
return res.status(400).json({ error: 'Файл не загружен' });
|
|
2238
|
+
}
|
|
2239
|
+
|
|
2240
|
+
const tempDir = path.join(os.tmpdir(), `import-${Date.now()}`);
|
|
2241
|
+
await fse.ensureDir(tempDir);
|
|
2242
|
+
|
|
2243
|
+
try {
|
|
2244
|
+
const zip = new AdmZip(req.file.buffer);
|
|
2245
|
+
zip.extractAllTo(tempDir, true);
|
|
2246
|
+
|
|
2247
|
+
console.log('[Import] Файлы в архиве:', zip.getEntries().map(entry => entry.entryName));
|
|
2248
|
+
|
|
2249
|
+
const importData = {
|
|
2250
|
+
plugins: [],
|
|
2251
|
+
commands: [],
|
|
2252
|
+
eventGraphs: [],
|
|
2253
|
+
settings: null,
|
|
2254
|
+
bot: null
|
|
2255
|
+
};
|
|
2256
|
+
|
|
2257
|
+
const botConfigPath = path.join(tempDir, 'bot.json');
|
|
2258
|
+
if (await fse.pathExists(botConfigPath)) {
|
|
2259
|
+
console.log('[Import] Найден bot.json');
|
|
2260
|
+
const botConfig = JSON.parse(await fse.readFile(botConfigPath, 'utf8'));
|
|
2261
|
+
delete botConfig.password;
|
|
2262
|
+
delete botConfig.proxyPassword;
|
|
2263
|
+
delete botConfig.id;
|
|
2264
|
+
delete botConfig.createdAt;
|
|
2265
|
+
delete botConfig.updatedAt;
|
|
2266
|
+
importData.bot = botConfig;
|
|
2267
|
+
} else {
|
|
2268
|
+
console.log('[Import] bot.json не найден');
|
|
2269
|
+
}
|
|
2270
|
+
|
|
2271
|
+
const pluginsPath = path.join(tempDir, 'plugins.json');
|
|
2272
|
+
if (await fse.pathExists(pluginsPath)) {
|
|
2273
|
+
console.log('[Import] Найден plugins.json');
|
|
2274
|
+
importData.plugins = JSON.parse(await fse.readFile(pluginsPath, 'utf8'));
|
|
2275
|
+
console.log('[Import] Плагинов:', importData.plugins.length);
|
|
2276
|
+
} else {
|
|
2277
|
+
console.log('[Import] plugins.json не найден');
|
|
2278
|
+
}
|
|
2279
|
+
|
|
2280
|
+
const commandsPath = path.join(tempDir, 'commands.json');
|
|
2281
|
+
if (await fse.pathExists(commandsPath)) {
|
|
2282
|
+
console.log('[Import] Найден commands.json');
|
|
2283
|
+
importData.commands = JSON.parse(await fse.readFile(commandsPath, 'utf8'));
|
|
2284
|
+
console.log('[Import] Команд:', importData.commands.length);
|
|
2285
|
+
} else {
|
|
2286
|
+
console.log('[Import] commands.json не найден');
|
|
2287
|
+
}
|
|
2288
|
+
|
|
2289
|
+
const eventGraphsPath = path.join(tempDir, 'event_graphs.json');
|
|
2290
|
+
if (await fse.pathExists(eventGraphsPath)) {
|
|
2291
|
+
console.log('[Import] Найден event_graphs.json');
|
|
2292
|
+
importData.eventGraphs = JSON.parse(await fse.readFile(eventGraphsPath, 'utf8'));
|
|
2293
|
+
console.log('[Import] Графов событий:', importData.eventGraphs.length);
|
|
2294
|
+
} else {
|
|
2295
|
+
console.log('[Import] event_graphs.json не найден');
|
|
2296
|
+
const eventGraphsPathAlt = path.join(tempDir, 'event-graphs.json');
|
|
2297
|
+
if (await fse.pathExists(eventGraphsPathAlt)) {
|
|
2298
|
+
console.log('[Import] Найден event-graphs.json');
|
|
2299
|
+
importData.eventGraphs = JSON.parse(await fse.readFile(eventGraphsPathAlt, 'utf8'));
|
|
2300
|
+
console.log('[Import] Графов событий:', importData.eventGraphs.length);
|
|
2301
|
+
} else {
|
|
2302
|
+
console.log('[Import] event-graphs.json тоже не найден');
|
|
2303
|
+
}
|
|
2304
|
+
}
|
|
2305
|
+
|
|
2306
|
+
const settingsPath = path.join(tempDir, 'settings.json');
|
|
2307
|
+
if (await fse.pathExists(settingsPath)) {
|
|
2308
|
+
console.log('[Import] Найден settings.json');
|
|
2309
|
+
importData.settings = JSON.parse(await fse.readFile(settingsPath, 'utf8'));
|
|
2310
|
+
} else {
|
|
2311
|
+
console.log('[Import] settings.json не найден');
|
|
2312
|
+
}
|
|
2313
|
+
|
|
2314
|
+
console.log('[Import] Итоговые данные:', {
|
|
2315
|
+
plugins: importData.plugins.length,
|
|
2316
|
+
commands: importData.commands.length,
|
|
2317
|
+
eventGraphs: importData.eventGraphs.length,
|
|
2318
|
+
hasSettings: !!importData.settings,
|
|
2319
|
+
hasBot: !!importData.bot
|
|
2320
|
+
});
|
|
2321
|
+
|
|
2322
|
+
res.json(importData);
|
|
2323
|
+
|
|
2324
|
+
} finally {
|
|
2325
|
+
await fse.remove(tempDir);
|
|
2326
|
+
}
|
|
2327
|
+
|
|
2328
|
+
} catch (error) {
|
|
2329
|
+
console.error('[API Error] /bots/import/preview:', error);
|
|
2330
|
+
res.status(500).json({ error: 'Не удалось обработать архив импорта' });
|
|
2331
|
+
}
|
|
2332
|
+
});
|
|
2333
|
+
|
|
2334
|
+
router.post('/import/create', authorize('bot:create'), async (req, res) => {
|
|
2335
|
+
try {
|
|
2336
|
+
const { username, password, prefix, serverId, note, owners, proxyHost, proxyPort, proxyUsername, proxyPassword, importData } = req.body;
|
|
2337
|
+
|
|
2338
|
+
if (!username || !serverId) {
|
|
2339
|
+
return res.status(400).json({ error: 'Имя и сервер обязательны' });
|
|
2340
|
+
}
|
|
2341
|
+
|
|
2342
|
+
const botData = {
|
|
2343
|
+
username,
|
|
2344
|
+
prefix,
|
|
2345
|
+
note,
|
|
2346
|
+
serverId: parseInt(serverId, 10),
|
|
2347
|
+
password: password ? encrypt(password) : null,
|
|
2348
|
+
owners: owners || '',
|
|
2349
|
+
proxyHost: proxyHost || null,
|
|
2350
|
+
proxyPort: proxyPort ? parseInt(proxyPort, 10) : null,
|
|
2351
|
+
proxyUsername: proxyUsername || null,
|
|
2352
|
+
proxyPassword: proxyPassword ? encrypt(proxyPassword) : null
|
|
2353
|
+
};
|
|
2354
|
+
|
|
2355
|
+
const newBot = await prisma.bot.create({
|
|
2356
|
+
data: botData,
|
|
2357
|
+
include: { server: true }
|
|
2358
|
+
});
|
|
2359
|
+
|
|
2360
|
+
await setupDefaultPermissionsForBot(newBot.id);
|
|
2361
|
+
|
|
2362
|
+
if (importData) {
|
|
2363
|
+
try {
|
|
2364
|
+
if (importData.plugins && Array.isArray(importData.plugins)) {
|
|
2365
|
+
for (const plugin of importData.plugins) {
|
|
2366
|
+
try {
|
|
2367
|
+
await prisma.installedPlugin.create({
|
|
2368
|
+
data: {
|
|
2369
|
+
...plugin,
|
|
2370
|
+
botId: newBot.id,
|
|
2371
|
+
id: undefined
|
|
2372
|
+
}
|
|
2373
|
+
});
|
|
2374
|
+
console.log(`[Import] Импортирован плагин ${plugin.name}`);
|
|
2375
|
+
} catch (error) {
|
|
2376
|
+
console.warn(`[Import] Не удалось импортировать плагин ${plugin.name}:`, error.message);
|
|
2377
|
+
}
|
|
2378
|
+
}
|
|
2379
|
+
}
|
|
2380
|
+
|
|
2381
|
+
if (importData.commands && Array.isArray(importData.commands)) {
|
|
2382
|
+
for (const command of importData.commands) {
|
|
2383
|
+
try {
|
|
2384
|
+
await prisma.command.create({
|
|
2385
|
+
data: {
|
|
2386
|
+
...command,
|
|
2387
|
+
botId: newBot.id,
|
|
2388
|
+
id: undefined
|
|
2389
|
+
}
|
|
2390
|
+
});
|
|
2391
|
+
} catch (error) {
|
|
2392
|
+
console.warn(`[Import] Не удалось импортировать команду ${command.name}:`, error.message);
|
|
2393
|
+
}
|
|
2394
|
+
}
|
|
2395
|
+
}
|
|
2396
|
+
|
|
2397
|
+
if (importData.eventGraphs && Array.isArray(importData.eventGraphs)) {
|
|
2398
|
+
for (const graph of importData.eventGraphs) {
|
|
2399
|
+
try {
|
|
2400
|
+
await prisma.eventGraph.create({
|
|
2401
|
+
data: {
|
|
2402
|
+
...graph,
|
|
2403
|
+
botId: newBot.id,
|
|
2404
|
+
id: undefined
|
|
2405
|
+
}
|
|
2406
|
+
});
|
|
2407
|
+
} catch (error) {
|
|
2408
|
+
console.warn(`[Import] Не удалось импортировать граф событий ${graph.name}:`, error.message);
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
2411
|
+
}
|
|
2412
|
+
|
|
2413
|
+
if (importData.settings && Array.isArray(importData.settings)) {
|
|
2414
|
+
for (const setting of importData.settings) {
|
|
2415
|
+
try {
|
|
2416
|
+
const updated = await prisma.installedPlugin.updateMany({
|
|
2417
|
+
where: {
|
|
2418
|
+
botId: newBot.id,
|
|
2419
|
+
name: setting.pluginName
|
|
2420
|
+
},
|
|
2421
|
+
data: {
|
|
2422
|
+
settings: setting.settings
|
|
2423
|
+
}
|
|
2424
|
+
});
|
|
2425
|
+
if (updated.count > 0) {
|
|
2426
|
+
console.log(`[Import] Импортированы настройки плагина ${setting.pluginName}`);
|
|
2427
|
+
} else {
|
|
2428
|
+
console.warn(`[Import] Плагин ${setting.pluginName} не найден для применения настроек`);
|
|
2429
|
+
}
|
|
2430
|
+
} catch (error) {
|
|
2431
|
+
console.warn(`[Import] Не удалось импортировать настройки плагина ${setting.pluginName}:`, error.message);
|
|
2432
|
+
}
|
|
2433
|
+
}
|
|
2434
|
+
}
|
|
2435
|
+
|
|
2436
|
+
} catch (error) {
|
|
2437
|
+
console.error('[Import] Ошибка при импорте данных:', error);
|
|
2438
|
+
}
|
|
2439
|
+
}
|
|
2440
|
+
|
|
2441
|
+
res.status(201).json(newBot);
|
|
2442
|
+
} catch (error) {
|
|
2443
|
+
if (error.code === 'P2002') {
|
|
2444
|
+
return res.status(409).json({ error: 'Бот с таким именем уже существует' });
|
|
2445
|
+
}
|
|
2446
|
+
console.error("[API Error] /bots/import/create:", error);
|
|
2447
|
+
res.status(500).json({ error: 'Не удалось создать бота с импортированными данными' });
|
|
2448
|
+
}
|
|
2449
|
+
});
|
|
2450
|
+
|
|
2451
|
+
module.exports = router;
|