blockmine 1.16.2 → 1.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -0
- package/backend/cli.js +57 -52
- package/backend/package.json +27 -26
- package/backend/prisma/migrations/20250723160648_add_bot_sort_order/migration.sql +2 -0
- package/backend/prisma/schema.prisma +229 -228
- package/backend/src/api/routes/bots.js +526 -101
- package/backend/src/api/routes/eventGraphs.js +459 -459
- package/backend/src/api/routes/logs.js +245 -0
- package/backend/src/core/BotManager.js +901 -855
- package/backend/src/core/BotProcess.js +35 -10
- package/backend/src/core/commands/dev.js +20 -0
- package/backend/src/server.js +2 -0
- package/frontend/dist/assets/index-BpUwmzIs.js +8347 -0
- package/frontend/dist/assets/index-D3DCCCQP.css +1 -0
- package/frontend/dist/index.html +2 -2
- package/frontend/package.json +3 -0
- package/package.json +1 -1
- package/frontend/dist/assets/index-ComgCgjP.js +0 -8331
- package/frontend/dist/assets/index-_stfadil.css +0 -1
|
@@ -22,6 +22,173 @@ const upload = multer({ storage: multer.memoryStorage() });
|
|
|
22
22
|
|
|
23
23
|
const router = express.Router();
|
|
24
24
|
|
|
25
|
+
const conditionalRestartAuth = (req, res, next) => {
|
|
26
|
+
if (process.env.DEBUG === 'true' || process.env.NODE_ENV === 'development') {
|
|
27
|
+
console.log('[Debug] Роут перезапуска бота доступен без проверки прав');
|
|
28
|
+
return next();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return authenticate(req, res, (err) => {
|
|
32
|
+
if (err) return next(err);
|
|
33
|
+
return authorize('bot:start_stop')(req, res, next);
|
|
34
|
+
});
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const conditionalChatAuth = (req, res, next) => {
|
|
38
|
+
if (process.env.DEBUG === 'true' || process.env.NODE_ENV === 'development') {
|
|
39
|
+
console.log('[Debug] Роут отправки сообщения боту доступен без проверки прав');
|
|
40
|
+
return next();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return authenticate(req, res, (err) => {
|
|
44
|
+
if (err) return next(err);
|
|
45
|
+
return authorize('bot:interact')(req, res, next);
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const conditionalStartStopAuth = (req, res, next) => {
|
|
50
|
+
if (process.env.DEBUG === 'true' || process.env.NODE_ENV === 'development') {
|
|
51
|
+
console.log('[Debug] Роут запуска/остановки бота доступен без проверки прав');
|
|
52
|
+
return next();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return authenticate(req, res, (err) => {
|
|
56
|
+
if (err) return next(err);
|
|
57
|
+
return authorize('bot:start_stop')(req, res, next);
|
|
58
|
+
});
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const conditionalListAuth = (req, res, next) => {
|
|
62
|
+
if (process.env.DEBUG === 'true' || process.env.NODE_ENV === 'development') {
|
|
63
|
+
console.log('[Debug] Роут списка ботов/состояния доступен без проверки прав');
|
|
64
|
+
return next();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return authenticate(req, res, (err) => {
|
|
68
|
+
if (err) return next(err);
|
|
69
|
+
return authorize('bot:list')(req, res, next);
|
|
70
|
+
});
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
router.post('/:id/restart', conditionalRestartAuth, async (req, res) => {
|
|
74
|
+
try {
|
|
75
|
+
const botId = parseInt(req.params.id, 10);
|
|
76
|
+
botManager.stopBot(botId);
|
|
77
|
+
setTimeout(async () => {
|
|
78
|
+
const botConfig = await prisma.bot.findUnique({ where: { id: botId }, include: { server: true } });
|
|
79
|
+
if (!botConfig) {
|
|
80
|
+
return res.status(404).json({ success: false, message: 'Бот не найден' });
|
|
81
|
+
}
|
|
82
|
+
botManager.startBot(botConfig);
|
|
83
|
+
res.status(202).json({ success: true, message: 'Команда на перезапуск отправлена.' });
|
|
84
|
+
}, 1000);
|
|
85
|
+
} catch (error) {
|
|
86
|
+
console.error(`[API] Ошибка перезапуска бота ${req.params.id}:`, error);
|
|
87
|
+
res.status(500).json({ success: false, message: 'Ошибка при перезапуске бота: ' + error.message });
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
router.post('/:id/chat', conditionalChatAuth, (req, res) => {
|
|
92
|
+
try {
|
|
93
|
+
const botId = parseInt(req.params.id, 10);
|
|
94
|
+
const { message } = req.body;
|
|
95
|
+
if (!message) return res.status(400).json({ error: 'Сообщение не может быть пустым' });
|
|
96
|
+
const result = botManager.sendMessageToBot(botId, message);
|
|
97
|
+
if (result.success) res.json({ success: true });
|
|
98
|
+
else res.status(404).json(result);
|
|
99
|
+
} catch (error) { res.status(500).json({ error: 'Внутренняя ошибка сервера: ' + error.message }); }
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
router.post('/:id/start', conditionalStartStopAuth, async (req, res) => {
|
|
103
|
+
try {
|
|
104
|
+
const botId = parseInt(req.params.id, 10);
|
|
105
|
+
const botConfig = await prisma.bot.findUnique({ where: { id: botId }, include: { server: true } });
|
|
106
|
+
if (!botConfig) {
|
|
107
|
+
return res.status(404).json({ success: false, message: 'Бот не найден' });
|
|
108
|
+
}
|
|
109
|
+
botManager.startBot(botConfig);
|
|
110
|
+
res.status(202).json({ success: true, message: 'Команда на запуск отправлена.' });
|
|
111
|
+
} catch (error) {
|
|
112
|
+
console.error(`[API] Ошибка запуска бота ${req.params.id}:`, error);
|
|
113
|
+
res.status(500).json({ success: false, message: 'Ошибка при запуске бота: ' + error.message });
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
router.post('/:id/stop', conditionalStartStopAuth, (req, res) => {
|
|
118
|
+
try {
|
|
119
|
+
const botId = parseInt(req.params.id, 10);
|
|
120
|
+
botManager.stopBot(botId);
|
|
121
|
+
res.status(202).json({ success: true, message: 'Команда на остановку отправлена.' });
|
|
122
|
+
} catch (error) {
|
|
123
|
+
console.error(`[API] Ошибка остановки бота ${req.params.id}:`, error);
|
|
124
|
+
res.status(500).json({ success: false, message: 'Ошибка при остановке бота: ' + error.message });
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
router.get('/', conditionalListAuth, async (req, res) => {
|
|
129
|
+
try {
|
|
130
|
+
const botsWithoutSortOrder = await prisma.bot.findMany({
|
|
131
|
+
where: { sortOrder: null },
|
|
132
|
+
select: { id: true }
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
if (botsWithoutSortOrder.length > 0) {
|
|
136
|
+
console.log(`[API] Обновляем sortOrder для ${botsWithoutSortOrder.length} ботов`);
|
|
137
|
+
for (const bot of botsWithoutSortOrder) {
|
|
138
|
+
await prisma.bot.update({
|
|
139
|
+
where: { id: bot.id },
|
|
140
|
+
data: { sortOrder: bot.id }
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const bots = await prisma.bot.findMany({
|
|
146
|
+
include: { server: true },
|
|
147
|
+
orderBy: { sortOrder: 'asc' }
|
|
148
|
+
});
|
|
149
|
+
res.json(bots);
|
|
150
|
+
} catch (error) {
|
|
151
|
+
console.error("[API /api/bots] Ошибка получения списка ботов:", error);
|
|
152
|
+
res.status(500).json({ error: 'Не удалось получить список ботов' });
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
router.get('/state', conditionalListAuth, (req, res) => {
|
|
157
|
+
try {
|
|
158
|
+
const state = botManager.getFullState();
|
|
159
|
+
res.json(state);
|
|
160
|
+
} catch (error) { res.status(500).json({ error: 'Не удалось получить состояние ботов' }); }
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
router.get('/:id/logs', conditionalListAuth, (req, res) => {
|
|
164
|
+
try {
|
|
165
|
+
const botId = parseInt(req.params.id, 10);
|
|
166
|
+
const { limit = 100, offset = 0 } = req.query;
|
|
167
|
+
|
|
168
|
+
const logs = botManager.getBotLogs(botId);
|
|
169
|
+
|
|
170
|
+
const startIndex = parseInt(offset);
|
|
171
|
+
const endIndex = startIndex + parseInt(limit);
|
|
172
|
+
const paginatedLogs = logs.slice(startIndex, endIndex);
|
|
173
|
+
|
|
174
|
+
res.json({
|
|
175
|
+
success: true,
|
|
176
|
+
data: {
|
|
177
|
+
logs: paginatedLogs,
|
|
178
|
+
pagination: {
|
|
179
|
+
total: logs.length,
|
|
180
|
+
limit: parseInt(limit),
|
|
181
|
+
offset: startIndex,
|
|
182
|
+
hasMore: endIndex < logs.length
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
} catch (error) {
|
|
187
|
+
console.error(`[API] Ошибка получения логов бота ${req.params.id}:`, error);
|
|
188
|
+
res.status(500).json({ error: 'Не удалось получить логи бота' });
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
|
|
25
192
|
router.use(authenticate);
|
|
26
193
|
router.use('/:botId/event-graphs', eventGraphsRouter);
|
|
27
194
|
router.use('/:botId/plugins/ide', pluginIdeRouter);
|
|
@@ -62,34 +229,25 @@ async function setupDefaultPermissionsForBot(botId, prismaClient = prisma) {
|
|
|
62
229
|
console.log(`[Setup] Для бота ID ${botId} созданы группы и права по умолчанию.`);
|
|
63
230
|
}
|
|
64
231
|
|
|
65
|
-
router.get('/', authorize('bot:list'), async (req, res) => {
|
|
66
|
-
try {
|
|
67
|
-
const bots = await prisma.bot.findMany({ include: { server: true }, orderBy: { createdAt: 'asc' } });
|
|
68
|
-
res.json(bots);
|
|
69
|
-
} catch (error) {
|
|
70
|
-
console.error("[API /api/bots] Ошибка получения списка ботов:", error);
|
|
71
|
-
res.status(500).json({ error: 'Не удалось получить список ботов' });
|
|
72
|
-
}
|
|
73
|
-
});
|
|
74
232
|
|
|
75
|
-
router.get('/state', authorize('bot:list'), (req, res) => {
|
|
76
|
-
try {
|
|
77
|
-
const state = botManager.getFullState();
|
|
78
|
-
res.json(state);
|
|
79
|
-
} catch (error) { res.status(500).json({ error: 'Не удалось получить состояние ботов' }); }
|
|
80
|
-
});
|
|
81
233
|
|
|
82
234
|
router.post('/', authorize('bot:create'), async (req, res) => {
|
|
83
235
|
try {
|
|
84
236
|
const { username, password, prefix, serverId, note } = req.body;
|
|
85
237
|
if (!username || !serverId) return res.status(400).json({ error: 'Имя и сервер обязательны' });
|
|
86
238
|
|
|
239
|
+
const maxSortOrder = await prisma.bot.aggregate({
|
|
240
|
+
_max: { sortOrder: true }
|
|
241
|
+
});
|
|
242
|
+
const nextSortOrder = (maxSortOrder._max.sortOrder || 0) + 1;
|
|
243
|
+
|
|
87
244
|
const data = {
|
|
88
245
|
username,
|
|
89
246
|
prefix,
|
|
90
247
|
note,
|
|
91
248
|
serverId: parseInt(serverId, 10),
|
|
92
|
-
password: password ? encrypt(password) : null
|
|
249
|
+
password: password ? encrypt(password) : null,
|
|
250
|
+
sortOrder: nextSortOrder
|
|
93
251
|
};
|
|
94
252
|
|
|
95
253
|
const newBot = await prisma.bot.create({
|
|
@@ -168,98 +326,102 @@ router.put('/:id', authorize('bot:update'), async (req, res) => {
|
|
|
168
326
|
const updatedBot = await prisma.bot.update({
|
|
169
327
|
where: { id: botId },
|
|
170
328
|
data: dataToUpdate,
|
|
171
|
-
include: {
|
|
172
|
-
server: true
|
|
173
|
-
}
|
|
329
|
+
include: { server: true }
|
|
174
330
|
});
|
|
175
331
|
|
|
176
|
-
const botManager = req.app.get('botManager');
|
|
177
|
-
botManager.reloadBotConfigInRealTime(botId);
|
|
178
|
-
|
|
179
332
|
res.json(updatedBot);
|
|
180
333
|
} catch (error) {
|
|
181
|
-
console.error(
|
|
182
|
-
|
|
183
|
-
code: error.code,
|
|
184
|
-
meta: error.meta,
|
|
185
|
-
message: error.message
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
if (error.code === 'P2002' && error.meta?.target?.includes('username')) {
|
|
189
|
-
return res.status(400).json({
|
|
190
|
-
message: 'Бот с таким именем уже существует. Выберите другое имя.'
|
|
191
|
-
});
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
res.status(500).json({ message: `Не удалось обновить бота: ${error.message}` });
|
|
334
|
+
console.error("[API Error] /bots PUT:", error);
|
|
335
|
+
res.status(500).json({ error: 'Не удалось обновить бота' });
|
|
195
336
|
}
|
|
196
337
|
});
|
|
197
338
|
|
|
198
|
-
router.
|
|
339
|
+
router.put('/:id/sort-order', authorize('bot:update'), async (req, res) => {
|
|
199
340
|
try {
|
|
341
|
+
const { newPosition } = req.body;
|
|
200
342
|
const botId = parseInt(req.params.id, 10);
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
});
|
|
343
|
+
|
|
344
|
+
console.log(`[API] Запрос на изменение порядка бота ${botId} на позицию ${newPosition}`);
|
|
345
|
+
|
|
346
|
+
if (isNaN(botId) || typeof newPosition !== 'number') {
|
|
347
|
+
console.log(`[API] Неверные параметры: botId=${botId}, newPosition=${newPosition}`);
|
|
348
|
+
return res.status(400).json({ error: 'Неверные параметры' });
|
|
349
|
+
}
|
|
206
350
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
351
|
+
const currentBot = await prisma.bot.findUnique({
|
|
352
|
+
where: { id: botId },
|
|
353
|
+
select: { sortOrder: true }
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
if (!currentBot) {
|
|
357
|
+
console.log(`[API] Бот ${botId} не найден`);
|
|
358
|
+
return res.status(404).json({ error: 'Бот не найден' });
|
|
213
359
|
}
|
|
214
|
-
botManager.startBot(botConfig);
|
|
215
|
-
res.status(202).json({ success: true, message: 'Команда на запуск отправлена.' });
|
|
216
|
-
} catch (error) {
|
|
217
|
-
console.error(`[API] Ошибка запуска бота ${req.params.id}:`, error);
|
|
218
|
-
res.status(500).json({ success: false, message: 'Ошибка при запуске бота: ' + error.message });
|
|
219
|
-
}
|
|
220
|
-
});
|
|
221
360
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
res.status(500).json({ success: false, message: 'Ошибка при остановке бота: ' + error.message });
|
|
230
|
-
}
|
|
231
|
-
});
|
|
361
|
+
const currentPosition = currentBot.sortOrder;
|
|
362
|
+
console.log(`[API] Текущая позиция бота ${botId}: ${currentPosition}, новая позиция: ${newPosition}`);
|
|
363
|
+
|
|
364
|
+
if (newPosition === currentPosition) {
|
|
365
|
+
console.log(`[API] Позиция не изменилась для бота ${botId}`);
|
|
366
|
+
return res.json({ success: true, message: 'Позиция не изменилась' });
|
|
367
|
+
}
|
|
232
368
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
369
|
+
if (newPosition > currentPosition) {
|
|
370
|
+
console.log(`[API] Перемещаем бота ${botId} вниз с позиции ${currentPosition} на ${newPosition}`);
|
|
371
|
+
const updateResult = await prisma.bot.updateMany({
|
|
372
|
+
where: {
|
|
373
|
+
sortOrder: {
|
|
374
|
+
gt: currentPosition,
|
|
375
|
+
lte: newPosition
|
|
376
|
+
}
|
|
377
|
+
},
|
|
378
|
+
data: {
|
|
379
|
+
sortOrder: {
|
|
380
|
+
decrement: 1
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
console.log(`[API] Обновлено ${updateResult.count} ботов при перемещении вниз`);
|
|
385
|
+
} else {
|
|
386
|
+
console.log(`[API] Перемещаем бота ${botId} вверх с позиции ${currentPosition} на ${newPosition}`);
|
|
387
|
+
const updateResult = await prisma.bot.updateMany({
|
|
388
|
+
where: {
|
|
389
|
+
sortOrder: {
|
|
390
|
+
gte: newPosition,
|
|
391
|
+
lt: currentPosition
|
|
392
|
+
}
|
|
393
|
+
},
|
|
394
|
+
data: {
|
|
395
|
+
sortOrder: {
|
|
396
|
+
increment: 1
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
console.log(`[API] Обновлено ${updateResult.count} ботов при перемещении вверх`);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
await prisma.bot.update({
|
|
404
|
+
where: { id: botId },
|
|
405
|
+
data: { sortOrder: newPosition }
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
console.log(`[API] Успешно обновлен порядок бота ${botId} на позицию ${newPosition}`);
|
|
409
|
+
res.json({ success: true, message: 'Порядок ботов обновлен' });
|
|
245
410
|
} catch (error) {
|
|
246
|
-
console.error(
|
|
247
|
-
res.status(500).json({
|
|
411
|
+
console.error("[API Error] /bots sort-order PUT:", error);
|
|
412
|
+
res.status(500).json({ error: 'Не удалось обновить порядок ботов' });
|
|
248
413
|
}
|
|
249
414
|
});
|
|
250
415
|
|
|
251
|
-
router.
|
|
416
|
+
router.delete('/:id', authorize('bot:delete'), async (req, res) => {
|
|
252
417
|
try {
|
|
253
418
|
const botId = parseInt(req.params.id, 10);
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
else res.status(404).json(result);
|
|
259
|
-
} catch (error) { res.status(500).json({ error: 'Внутренняя ошибка сервера: ' + error.message }); }
|
|
419
|
+
if (botManager.bots.has(botId)) return res.status(400).json({ error: 'Нельзя удалить запущенного бота' });
|
|
420
|
+
await prisma.bot.delete({ where: { id: botId } });
|
|
421
|
+
res.status(204).send();
|
|
422
|
+
} catch (error) { res.status(500).json({ error: 'Не удалось удалить бота' }); }
|
|
260
423
|
});
|
|
261
424
|
|
|
262
|
-
|
|
263
425
|
router.get('/servers', authorize('bot:list'), async (req, res) => {
|
|
264
426
|
try {
|
|
265
427
|
const servers = await prisma.server.findMany();
|
|
@@ -317,24 +479,49 @@ router.get('/:botId/plugins/:pluginId/settings', authorize('plugin:settings:view
|
|
|
317
479
|
if (!plugin) return res.status(404).json({ error: 'Установленный плагин не найден' });
|
|
318
480
|
|
|
319
481
|
const savedSettings = plugin.settings ? JSON.parse(plugin.settings) : {};
|
|
320
|
-
|
|
482
|
+
const defaultSettings = {};
|
|
321
483
|
const manifest = plugin.manifest ? JSON.parse(plugin.manifest) : {};
|
|
484
|
+
const manifestSettings = manifest.settings || {};
|
|
322
485
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
486
|
+
|
|
487
|
+
const firstSettingValue = Object.values(manifestSettings)[0];
|
|
488
|
+
const isGrouped = firstSettingValue && typeof firstSettingValue === 'object' && !firstSettingValue.type && firstSettingValue.label;
|
|
489
|
+
|
|
490
|
+
const processSetting = async (settingKey, config) => {
|
|
491
|
+
if (!config || !config.type) return;
|
|
492
|
+
|
|
493
|
+
if (config.type === 'json_file' && config.defaultPath) {
|
|
494
|
+
const configFilePath = path.join(plugin.path, config.defaultPath);
|
|
495
|
+
try {
|
|
496
|
+
const fileContent = await fs.readFile(configFilePath, 'utf-8');
|
|
497
|
+
defaultSettings[settingKey] = JSON.parse(fileContent);
|
|
498
|
+
} catch (e) {
|
|
499
|
+
console.error(`[API Settings] Не удалось прочитать defaultPath ${config.defaultPath} для плагина ${plugin.name}: ${e.message}`);
|
|
500
|
+
defaultSettings[settingKey] = {};
|
|
501
|
+
}
|
|
502
|
+
} else if (config.default !== undefined) {
|
|
503
|
+
try {
|
|
504
|
+
defaultSettings[settingKey] = JSON.parse(config.default);
|
|
505
|
+
} catch {
|
|
506
|
+
defaultSettings[settingKey] = config.default;
|
|
335
507
|
}
|
|
336
508
|
}
|
|
509
|
+
};
|
|
510
|
+
|
|
511
|
+
if (isGrouped) {
|
|
512
|
+
for (const categoryKey in manifestSettings) {
|
|
513
|
+
const categoryConfig = manifestSettings[categoryKey];
|
|
514
|
+
for (const settingKey in categoryConfig) {
|
|
515
|
+
if (settingKey === 'label') continue;
|
|
516
|
+
await processSetting(settingKey, categoryConfig[settingKey]);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
} else {
|
|
520
|
+
for (const settingKey in manifestSettings) {
|
|
521
|
+
await processSetting(settingKey, manifestSettings[settingKey]);
|
|
522
|
+
}
|
|
337
523
|
}
|
|
524
|
+
|
|
338
525
|
const finalSettings = { ...defaultSettings, ...savedSettings };
|
|
339
526
|
res.json(finalSettings);
|
|
340
527
|
} catch (error) {
|
|
@@ -1492,6 +1679,25 @@ router.get('/:botId/export', authorize('bot:export'), async (req, res) => {
|
|
|
1492
1679
|
const installedPlugins = await prisma.installedPlugin.findMany({ where: { botId } });
|
|
1493
1680
|
archive.append(JSON.stringify(installedPlugins, null, 2), { name: 'plugins.json' });
|
|
1494
1681
|
|
|
1682
|
+
try {
|
|
1683
|
+
const installedPlugins = await prisma.installedPlugin.findMany({ where: { botId } });
|
|
1684
|
+
const pluginSettings = installedPlugins
|
|
1685
|
+
.filter(plugin => plugin.settings && plugin.settings !== '{}')
|
|
1686
|
+
.map(plugin => ({
|
|
1687
|
+
pluginName: plugin.name,
|
|
1688
|
+
settings: plugin.settings
|
|
1689
|
+
}));
|
|
1690
|
+
|
|
1691
|
+
if (pluginSettings.length > 0) {
|
|
1692
|
+
console.log(`[Export] Экспорт настроек плагинов для бота ${botId}: ${pluginSettings.length} настроек`);
|
|
1693
|
+
archive.append(JSON.stringify(pluginSettings, null, 2), { name: 'settings.json' });
|
|
1694
|
+
} else {
|
|
1695
|
+
console.log(`[Export] Нет настроек плагинов для экспорта`);
|
|
1696
|
+
}
|
|
1697
|
+
} catch (error) {
|
|
1698
|
+
console.warn(`[Export] Ошибка при экспорте настроек плагинов:`, error.message);
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1495
1701
|
if (includePluginFiles === 'true') {
|
|
1496
1702
|
for (const plugin of installedPlugins) {
|
|
1497
1703
|
const pluginPath = plugin.path;
|
|
@@ -1519,7 +1725,9 @@ router.get('/:botId/export', authorize('bot:export'), async (req, res) => {
|
|
|
1519
1725
|
|
|
1520
1726
|
} catch (error) {
|
|
1521
1727
|
console.error('Failed to export bot:', error);
|
|
1522
|
-
res.
|
|
1728
|
+
if (!res.headersSent) {
|
|
1729
|
+
res.status(500).json({ error: `Failed to export bot: ${error.message}` });
|
|
1730
|
+
}
|
|
1523
1731
|
}
|
|
1524
1732
|
});
|
|
1525
1733
|
|
|
@@ -1716,4 +1924,221 @@ router.post('/import', authorize('bot:create'), upload.single('file'), async (re
|
|
|
1716
1924
|
}
|
|
1717
1925
|
});
|
|
1718
1926
|
|
|
1927
|
+
router.post('/import/preview', authorize('bot:create'), upload.single('file'), async (req, res) => {
|
|
1928
|
+
try {
|
|
1929
|
+
if (!req.file) {
|
|
1930
|
+
return res.status(400).json({ error: 'Файл не загружен' });
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1933
|
+
const tempDir = path.join(os.tmpdir(), `import-${Date.now()}`);
|
|
1934
|
+
await fse.ensureDir(tempDir);
|
|
1935
|
+
|
|
1936
|
+
try {
|
|
1937
|
+
const zip = new AdmZip(req.file.buffer);
|
|
1938
|
+
zip.extractAllTo(tempDir, true);
|
|
1939
|
+
|
|
1940
|
+
console.log('[Import] Файлы в архиве:', zip.getEntries().map(entry => entry.entryName));
|
|
1941
|
+
|
|
1942
|
+
const importData = {
|
|
1943
|
+
plugins: [],
|
|
1944
|
+
commands: [],
|
|
1945
|
+
eventGraphs: [],
|
|
1946
|
+
settings: null,
|
|
1947
|
+
bot: null
|
|
1948
|
+
};
|
|
1949
|
+
|
|
1950
|
+
const botConfigPath = path.join(tempDir, 'bot.json');
|
|
1951
|
+
if (await fse.pathExists(botConfigPath)) {
|
|
1952
|
+
console.log('[Import] Найден bot.json');
|
|
1953
|
+
const botConfig = JSON.parse(await fse.readFile(botConfigPath, 'utf8'));
|
|
1954
|
+
delete botConfig.password;
|
|
1955
|
+
delete botConfig.proxyPassword;
|
|
1956
|
+
delete botConfig.id;
|
|
1957
|
+
delete botConfig.createdAt;
|
|
1958
|
+
delete botConfig.updatedAt;
|
|
1959
|
+
importData.bot = botConfig;
|
|
1960
|
+
} else {
|
|
1961
|
+
console.log('[Import] bot.json не найден');
|
|
1962
|
+
}
|
|
1963
|
+
|
|
1964
|
+
const pluginsPath = path.join(tempDir, 'plugins.json');
|
|
1965
|
+
if (await fse.pathExists(pluginsPath)) {
|
|
1966
|
+
console.log('[Import] Найден plugins.json');
|
|
1967
|
+
importData.plugins = JSON.parse(await fse.readFile(pluginsPath, 'utf8'));
|
|
1968
|
+
console.log('[Import] Плагинов:', importData.plugins.length);
|
|
1969
|
+
} else {
|
|
1970
|
+
console.log('[Import] plugins.json не найден');
|
|
1971
|
+
}
|
|
1972
|
+
|
|
1973
|
+
const commandsPath = path.join(tempDir, 'commands.json');
|
|
1974
|
+
if (await fse.pathExists(commandsPath)) {
|
|
1975
|
+
console.log('[Import] Найден commands.json');
|
|
1976
|
+
importData.commands = JSON.parse(await fse.readFile(commandsPath, 'utf8'));
|
|
1977
|
+
console.log('[Import] Команд:', importData.commands.length);
|
|
1978
|
+
} else {
|
|
1979
|
+
console.log('[Import] commands.json не найден');
|
|
1980
|
+
}
|
|
1981
|
+
|
|
1982
|
+
const eventGraphsPath = path.join(tempDir, 'event_graphs.json');
|
|
1983
|
+
if (await fse.pathExists(eventGraphsPath)) {
|
|
1984
|
+
console.log('[Import] Найден event_graphs.json');
|
|
1985
|
+
importData.eventGraphs = JSON.parse(await fse.readFile(eventGraphsPath, 'utf8'));
|
|
1986
|
+
console.log('[Import] Графов событий:', importData.eventGraphs.length);
|
|
1987
|
+
} else {
|
|
1988
|
+
console.log('[Import] event_graphs.json не найден');
|
|
1989
|
+
const eventGraphsPathAlt = path.join(tempDir, 'event-graphs.json');
|
|
1990
|
+
if (await fse.pathExists(eventGraphsPathAlt)) {
|
|
1991
|
+
console.log('[Import] Найден event-graphs.json');
|
|
1992
|
+
importData.eventGraphs = JSON.parse(await fse.readFile(eventGraphsPathAlt, 'utf8'));
|
|
1993
|
+
console.log('[Import] Графов событий:', importData.eventGraphs.length);
|
|
1994
|
+
} else {
|
|
1995
|
+
console.log('[Import] event-graphs.json тоже не найден');
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1998
|
+
|
|
1999
|
+
const settingsPath = path.join(tempDir, 'settings.json');
|
|
2000
|
+
if (await fse.pathExists(settingsPath)) {
|
|
2001
|
+
console.log('[Import] Найден settings.json');
|
|
2002
|
+
importData.settings = JSON.parse(await fse.readFile(settingsPath, 'utf8'));
|
|
2003
|
+
} else {
|
|
2004
|
+
console.log('[Import] settings.json не найден');
|
|
2005
|
+
}
|
|
2006
|
+
|
|
2007
|
+
console.log('[Import] Итоговые данные:', {
|
|
2008
|
+
plugins: importData.plugins.length,
|
|
2009
|
+
commands: importData.commands.length,
|
|
2010
|
+
eventGraphs: importData.eventGraphs.length,
|
|
2011
|
+
hasSettings: !!importData.settings,
|
|
2012
|
+
hasBot: !!importData.bot
|
|
2013
|
+
});
|
|
2014
|
+
|
|
2015
|
+
res.json(importData);
|
|
2016
|
+
|
|
2017
|
+
} finally {
|
|
2018
|
+
await fse.remove(tempDir);
|
|
2019
|
+
}
|
|
2020
|
+
|
|
2021
|
+
} catch (error) {
|
|
2022
|
+
console.error('[API Error] /bots/import/preview:', error);
|
|
2023
|
+
res.status(500).json({ error: 'Не удалось обработать архив импорта' });
|
|
2024
|
+
}
|
|
2025
|
+
});
|
|
2026
|
+
|
|
2027
|
+
router.post('/import/create', authorize('bot:create'), async (req, res) => {
|
|
2028
|
+
try {
|
|
2029
|
+
const { username, password, prefix, serverId, note, owners, proxyHost, proxyPort, proxyUsername, proxyPassword, importData } = req.body;
|
|
2030
|
+
|
|
2031
|
+
if (!username || !serverId) {
|
|
2032
|
+
return res.status(400).json({ error: 'Имя и сервер обязательны' });
|
|
2033
|
+
}
|
|
2034
|
+
|
|
2035
|
+
const botData = {
|
|
2036
|
+
username,
|
|
2037
|
+
prefix,
|
|
2038
|
+
note,
|
|
2039
|
+
serverId: parseInt(serverId, 10),
|
|
2040
|
+
password: password ? encrypt(password) : null,
|
|
2041
|
+
owners: owners || '',
|
|
2042
|
+
proxyHost: proxyHost || null,
|
|
2043
|
+
proxyPort: proxyPort ? parseInt(proxyPort, 10) : null,
|
|
2044
|
+
proxyUsername: proxyUsername || null,
|
|
2045
|
+
proxyPassword: proxyPassword ? encrypt(proxyPassword) : null
|
|
2046
|
+
};
|
|
2047
|
+
|
|
2048
|
+
const newBot = await prisma.bot.create({
|
|
2049
|
+
data: botData,
|
|
2050
|
+
include: { server: true }
|
|
2051
|
+
});
|
|
2052
|
+
|
|
2053
|
+
await setupDefaultPermissionsForBot(newBot.id);
|
|
2054
|
+
|
|
2055
|
+
if (importData) {
|
|
2056
|
+
try {
|
|
2057
|
+
if (importData.plugins && Array.isArray(importData.plugins)) {
|
|
2058
|
+
for (const plugin of importData.plugins) {
|
|
2059
|
+
try {
|
|
2060
|
+
await prisma.installedPlugin.create({
|
|
2061
|
+
data: {
|
|
2062
|
+
...plugin,
|
|
2063
|
+
botId: newBot.id,
|
|
2064
|
+
id: undefined
|
|
2065
|
+
}
|
|
2066
|
+
});
|
|
2067
|
+
console.log(`[Import] Импортирован плагин ${plugin.name}`);
|
|
2068
|
+
} catch (error) {
|
|
2069
|
+
console.warn(`[Import] Не удалось импортировать плагин ${plugin.name}:`, error.message);
|
|
2070
|
+
}
|
|
2071
|
+
}
|
|
2072
|
+
}
|
|
2073
|
+
|
|
2074
|
+
if (importData.commands && Array.isArray(importData.commands)) {
|
|
2075
|
+
for (const command of importData.commands) {
|
|
2076
|
+
try {
|
|
2077
|
+
await prisma.command.create({
|
|
2078
|
+
data: {
|
|
2079
|
+
...command,
|
|
2080
|
+
botId: newBot.id,
|
|
2081
|
+
id: undefined
|
|
2082
|
+
}
|
|
2083
|
+
});
|
|
2084
|
+
} catch (error) {
|
|
2085
|
+
console.warn(`[Import] Не удалось импортировать команду ${command.name}:`, error.message);
|
|
2086
|
+
}
|
|
2087
|
+
}
|
|
2088
|
+
}
|
|
2089
|
+
|
|
2090
|
+
if (importData.eventGraphs && Array.isArray(importData.eventGraphs)) {
|
|
2091
|
+
for (const graph of importData.eventGraphs) {
|
|
2092
|
+
try {
|
|
2093
|
+
await prisma.eventGraph.create({
|
|
2094
|
+
data: {
|
|
2095
|
+
...graph,
|
|
2096
|
+
botId: newBot.id,
|
|
2097
|
+
id: undefined
|
|
2098
|
+
}
|
|
2099
|
+
});
|
|
2100
|
+
} catch (error) {
|
|
2101
|
+
console.warn(`[Import] Не удалось импортировать граф событий ${graph.name}:`, error.message);
|
|
2102
|
+
}
|
|
2103
|
+
}
|
|
2104
|
+
}
|
|
2105
|
+
|
|
2106
|
+
if (importData.settings && Array.isArray(importData.settings)) {
|
|
2107
|
+
for (const setting of importData.settings) {
|
|
2108
|
+
try {
|
|
2109
|
+
const updated = await prisma.installedPlugin.updateMany({
|
|
2110
|
+
where: {
|
|
2111
|
+
botId: newBot.id,
|
|
2112
|
+
name: setting.pluginName
|
|
2113
|
+
},
|
|
2114
|
+
data: {
|
|
2115
|
+
settings: setting.settings
|
|
2116
|
+
}
|
|
2117
|
+
});
|
|
2118
|
+
if (updated.count > 0) {
|
|
2119
|
+
console.log(`[Import] Импортированы настройки плагина ${setting.pluginName}`);
|
|
2120
|
+
} else {
|
|
2121
|
+
console.warn(`[Import] Плагин ${setting.pluginName} не найден для применения настроек`);
|
|
2122
|
+
}
|
|
2123
|
+
} catch (error) {
|
|
2124
|
+
console.warn(`[Import] Не удалось импортировать настройки плагина ${setting.pluginName}:`, error.message);
|
|
2125
|
+
}
|
|
2126
|
+
}
|
|
2127
|
+
}
|
|
2128
|
+
|
|
2129
|
+
} catch (error) {
|
|
2130
|
+
console.error('[Import] Ошибка при импорте данных:', error);
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2133
|
+
|
|
2134
|
+
res.status(201).json(newBot);
|
|
2135
|
+
} catch (error) {
|
|
2136
|
+
if (error.code === 'P2002') {
|
|
2137
|
+
return res.status(409).json({ error: 'Бот с таким именем уже существует' });
|
|
2138
|
+
}
|
|
2139
|
+
console.error("[API Error] /bots/import/create:", error);
|
|
2140
|
+
res.status(500).json({ error: 'Не удалось создать бота с импортированными данными' });
|
|
2141
|
+
}
|
|
2142
|
+
});
|
|
2143
|
+
|
|
1719
2144
|
module.exports = router;
|