blockmine 1.6.3 → 1.13.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/.husky/commit-msg +1 -0
- package/.husky/pre-commit +1 -0
- package/.versionrc.json +17 -0
- package/CHANGELOG.md +36 -0
- package/README.md +1 -1
- package/backend/package.json +1 -0
- package/backend/prisma/migrations/20250718181335_add_plugin_data_store/migration.sql +14 -0
- package/backend/prisma/schema.prisma +17 -2
- package/backend/src/api/routes/auth.js +140 -0
- package/backend/src/api/routes/bots.js +176 -0
- package/backend/src/api/routes/changelog.js +16 -0
- package/backend/src/api/routes/eventGraphs.js +11 -1
- package/backend/src/api/routes/plugins.js +11 -0
- package/backend/src/core/BotManager.js +92 -40
- package/backend/src/core/BotProcess.js +44 -24
- package/backend/src/core/EventGraphManager.js +29 -5
- package/backend/src/core/GraphExecutionEngine.js +54 -12
- package/backend/src/core/MessageQueue.js +10 -1
- package/backend/src/core/NodeRegistry.js +2 -1
- package/backend/src/core/PluginLoader.js +72 -8
- package/backend/src/core/PluginManager.js +19 -0
- package/backend/src/plugins/PluginStore.js +87 -0
- package/backend/src/real-time/socketHandler.js +11 -3
- package/backend/src/server.js +2 -0
- package/backend/temp_migration.sql +0 -0
- package/commitlint.config.js +3 -0
- package/frontend/dist/assets/index-CHwi1QN9.js +8331 -0
- package/frontend/dist/assets/index-DhU2u6V0.css +1 -0
- package/frontend/dist/index.html +2 -2
- package/frontend/package.json +6 -0
- package/package.json +20 -4
- package/frontend/dist/assets/index-CIDmlKtb.js +0 -8203
- package/frontend/dist/assets/index-DF3i-W3m.css +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
npx --no -- commitlint --edit $1
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# npm test
|
package/.versionrc.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"header": "# История версий\n\n",
|
|
3
|
+
"types": [
|
|
4
|
+
{ "type": "feat", "section": "✨ Новые возможности" },
|
|
5
|
+
{ "type": "fix", "section": "🐛 Исправления" },
|
|
6
|
+
{ "type": "perf", "section": "⚡ Производительность" },
|
|
7
|
+
{ "type": "refactor", "section": "🛠 Рефакторинг" },
|
|
8
|
+
{ "type": "build", "section": "🏗 Сборка" },
|
|
9
|
+
{ "type": "chore", "hidden": true },
|
|
10
|
+
{ "type": "docs", "hidden": true },
|
|
11
|
+
{ "type": "style", "hidden": true },
|
|
12
|
+
{ "type": "test", "hidden": true }
|
|
13
|
+
],
|
|
14
|
+
"writerOpts": {
|
|
15
|
+
"commitPartial": "* {{#if scope}}**{{scope}}:** {{/if}}{{#if subject}}{{subject}}{{/if}} ([{{shortHash}}](https://github.com/blockmineJS/blockmine/commit/{{hash}})) - _{{authorName}}_\n"
|
|
16
|
+
}
|
|
17
|
+
}
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# История версий
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
## [1.13.0](https://github.com/blockmineJS/blockmine/compare/v1.12.0...v1.13.0) (2025-07-18)
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
### ✨ Новые возможности
|
|
8
|
+
|
|
9
|
+
* возможность сбрасывать пароль рут аккаунта ([e23181d](https://github.com/blockmineJS/blockmine/commit/e23181d29dbe730ff882d654b8ec0e80a1f007bc))
|
|
10
|
+
|
|
11
|
+
## [1.12.0](https://github.com/blockmineJS/blockmine/compare/v1.11.5...v1.12.0) (2025-07-18)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
### ✨ Новые возможности
|
|
15
|
+
|
|
16
|
+
* плагины теперь могу делать свои странички что бы делать какие либо действия в панели ([2664c3c](https://github.com/blockmineJS/blockmine/commit/2664c3c1a02db4e650f8a5be7e96ebbcfe3ab0bb))
|
|
17
|
+
* плагины теперь могуть хранить ингформацию в базе данных ([edb12fd](https://github.com/blockmineJS/blockmine/commit/edb12fd365603bc72c82ad159ffd734cec265dcb))
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### 🛠 Рефакторинг
|
|
21
|
+
|
|
22
|
+
* очередь сообщений. теперь может принимать в себя массив и задержкой отправляет сообщения ([4f193c9](https://github.com/blockmineJS/blockmine/commit/4f193c9c1c76a53ff655e63f520aa83e16c35126))
|
|
23
|
+
|
|
24
|
+
### [1.11.1](https://github.com/blockmineJS/blockmine/compare/v1.11.0...v1.11.1) (2025-07-17)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
### 🐛 Исправления
|
|
28
|
+
|
|
29
|
+
* test2 ([3ada981](https://github.com/blockmineJS/blockmine/commit/3ada981363de10b9d38cf34f5eb3a00ef527d6b2))
|
|
30
|
+
|
|
31
|
+
## 1.11.0 (2025-07-17)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
### ✨ Новые возможности
|
|
35
|
+
|
|
36
|
+
* **commands:** Редизайн интерфейса управления командами ([f520049](https://github.com/blockmineJS/blockmine/commit/f520049196dad133ea7957398d512c0334e85917))
|
package/README.md
CHANGED
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
<td align="center">
|
|
34
34
|
<p><strong>Управление командами</strong></p>
|
|
35
35
|
<img src="./image/3.png" alt="Скриншот страницы управления" width="100%">
|
|
36
|
-
<em>Настраивайте
|
|
36
|
+
<em>Настраивайте права алиасы и кулдауны для каждой команды.</em>
|
|
37
37
|
</td>
|
|
38
38
|
</tr>
|
|
39
39
|
</table>
|
package/backend/package.json
CHANGED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
-- CreateTable
|
|
2
|
+
CREATE TABLE "PluginDataStore" (
|
|
3
|
+
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
|
4
|
+
"pluginName" TEXT NOT NULL,
|
|
5
|
+
"botId" INTEGER NOT NULL,
|
|
6
|
+
"key" TEXT NOT NULL,
|
|
7
|
+
"value" TEXT NOT NULL,
|
|
8
|
+
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
9
|
+
"updatedAt" DATETIME NOT NULL,
|
|
10
|
+
CONSTRAINT "PluginDataStore_botId_fkey" FOREIGN KEY ("botId") REFERENCES "Bot" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
-- CreateIndex
|
|
14
|
+
CREATE UNIQUE INDEX "PluginDataStore_pluginName_botId_key_key" ON "PluginDataStore"("pluginName", "botId", "key");
|
|
@@ -38,6 +38,7 @@ model Bot {
|
|
|
38
38
|
permissions Permission[]
|
|
39
39
|
commands Command[]
|
|
40
40
|
eventGraphs EventGraph[]
|
|
41
|
+
pluginData PluginDataStore[]
|
|
41
42
|
|
|
42
43
|
createdAt DateTime @default(now())
|
|
43
44
|
updatedAt DateTime @updatedAt
|
|
@@ -198,7 +199,21 @@ model PanelRole {
|
|
|
198
199
|
name String @unique
|
|
199
200
|
|
|
200
201
|
// Храним права как джсон строку. SQLite не поддерживает массивы.
|
|
201
|
-
// Пример: '["bot:create", "bot:delete", "user:manage"]'
|
|
202
|
+
// Пример: '[\"bot:create\", \"bot:delete\", \"user:manage\"]'
|
|
202
203
|
permissions String @default("[]")
|
|
203
204
|
users PanelUser[]
|
|
204
|
-
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
model PluginDataStore {
|
|
208
|
+
id Int @id @default(autoincrement())
|
|
209
|
+
pluginName String
|
|
210
|
+
botId Int
|
|
211
|
+
key String
|
|
212
|
+
value String // в виде json
|
|
213
|
+
createdAt DateTime @default(now())
|
|
214
|
+
updatedAt DateTime @updatedAt
|
|
215
|
+
|
|
216
|
+
bot Bot @relation(fields: [botId], references: [id], onDelete: Cascade)
|
|
217
|
+
|
|
218
|
+
@@unique([pluginName, botId, key])
|
|
219
|
+
}
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
const express = require('express');
|
|
2
2
|
const bcrypt = require('bcryptjs');
|
|
3
3
|
const jwt = require('jsonwebtoken');
|
|
4
|
+
const crypto = require('crypto');
|
|
4
5
|
const { PrismaClient } = require('@prisma/client');
|
|
5
6
|
const config = require('../../config');
|
|
6
7
|
const { authenticate, authorize } = require('../middleware/auth');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const os = require('os');
|
|
7
10
|
|
|
8
11
|
const router = express.Router();
|
|
9
12
|
const prisma = new PrismaClient();
|
|
@@ -11,6 +14,8 @@ const prisma = new PrismaClient();
|
|
|
11
14
|
const JWT_SECRET = config.security.jwtSecret;
|
|
12
15
|
const JWT_EXPIRES_IN = '7d';
|
|
13
16
|
|
|
17
|
+
const activeResetTokens = new Map();
|
|
18
|
+
|
|
14
19
|
/**
|
|
15
20
|
* @route GET /api/auth/status
|
|
16
21
|
* @desc Проверяет, была ли произведена первоначальная настройка (создан админ)
|
|
@@ -26,6 +31,16 @@ router.get('/status', async (req, res) => {
|
|
|
26
31
|
}
|
|
27
32
|
});
|
|
28
33
|
|
|
34
|
+
/**
|
|
35
|
+
* @route GET /api/auth/config-path
|
|
36
|
+
* @desc Получить путь к конфигурационному файлу
|
|
37
|
+
* @access Public
|
|
38
|
+
*/
|
|
39
|
+
router.get('/config-path', (req, res) => {
|
|
40
|
+
const configPath = path.join(os.homedir(), '.blockmine', 'config.json');
|
|
41
|
+
res.json({ configPath });
|
|
42
|
+
});
|
|
43
|
+
|
|
29
44
|
/**
|
|
30
45
|
* @route POST /api/auth/setup
|
|
31
46
|
* @desc Создает первого пользователя с ролью администратора
|
|
@@ -100,6 +115,131 @@ router.post('/setup', async (req, res) => {
|
|
|
100
115
|
}
|
|
101
116
|
});
|
|
102
117
|
|
|
118
|
+
/**
|
|
119
|
+
* @route POST /api/auth/recovery/verify
|
|
120
|
+
* @desc Проверка кода восстановления
|
|
121
|
+
* @access Public
|
|
122
|
+
*/
|
|
123
|
+
router.post('/recovery/verify', async (req, res) => {
|
|
124
|
+
try {
|
|
125
|
+
const { recoveryCode } = req.body;
|
|
126
|
+
|
|
127
|
+
if (!recoveryCode) {
|
|
128
|
+
return res.status(400).json({ error: 'Код восстановления обязателен' });
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (recoveryCode !== config.security.adminRecoveryCode) {
|
|
132
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
133
|
+
return res.status(401).json({ error: 'Неверный код восстановления' });
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const rootUser = await prisma.panelUser.findFirst({
|
|
137
|
+
orderBy: { id: 'asc' },
|
|
138
|
+
include: { role: true }
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
if (!rootUser) {
|
|
142
|
+
return res.status(404).json({ error: 'В системе нет ни одного пользователя. Выполните первоначальную настройку.' });
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const tokenId = crypto.randomBytes(16).toString('hex');
|
|
146
|
+
const resetToken = jwt.sign(
|
|
147
|
+
{
|
|
148
|
+
userId: rootUser.id,
|
|
149
|
+
type: 'password-reset',
|
|
150
|
+
tokenId: tokenId,
|
|
151
|
+
timestamp: Date.now()
|
|
152
|
+
},
|
|
153
|
+
JWT_SECRET,
|
|
154
|
+
{ expiresIn: '5m' }
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
activeResetTokens.set(tokenId, {
|
|
158
|
+
userId: rootUser.id,
|
|
159
|
+
createdAt: Date.now(),
|
|
160
|
+
used: false
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
setTimeout(() => {
|
|
164
|
+
activeResetTokens.delete(tokenId);
|
|
165
|
+
}, 5 * 60 * 1000);
|
|
166
|
+
|
|
167
|
+
res.json({
|
|
168
|
+
success: true,
|
|
169
|
+
username: rootUser.username,
|
|
170
|
+
resetToken
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
} catch (error) {
|
|
174
|
+
console.error('[Recovery Verify Error]', error);
|
|
175
|
+
res.status(500).json({ error: 'Ошибка при проверке кода восстановления' });
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* @route POST /api/auth/recovery/reset
|
|
181
|
+
* @desc Сброс пароля с использованием токена
|
|
182
|
+
* @access Public (с валидным токеном сброса)
|
|
183
|
+
*/
|
|
184
|
+
router.post('/recovery/reset', async (req, res) => {
|
|
185
|
+
try {
|
|
186
|
+
const { resetToken, newPassword } = req.body;
|
|
187
|
+
|
|
188
|
+
if (!resetToken || !newPassword) {
|
|
189
|
+
return res.status(400).json({ error: 'Токен и новый пароль обязательны' });
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (newPassword.length < 4) {
|
|
193
|
+
return res.status(400).json({ error: 'Пароль должен быть не менее 4 символов' });
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
let decoded;
|
|
197
|
+
try {
|
|
198
|
+
decoded = jwt.verify(resetToken, JWT_SECRET);
|
|
199
|
+
if (decoded.type !== 'password-reset') {
|
|
200
|
+
throw new Error('Invalid token type');
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const tokenInfo = activeResetTokens.get(decoded.tokenId);
|
|
204
|
+
if (!tokenInfo) {
|
|
205
|
+
throw new Error('Token not found in active tokens');
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (tokenInfo.used) {
|
|
209
|
+
throw new Error('Token already used');
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (tokenInfo.userId !== decoded.userId) {
|
|
213
|
+
throw new Error('Token userId mismatch');
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
} catch (err) {
|
|
217
|
+
return res.status(401).json({ error: 'Недействительный или истекший токен' });
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const hashedPassword = await bcrypt.hash(newPassword, 12);
|
|
221
|
+
const updatedUser = await prisma.panelUser.update({
|
|
222
|
+
where: { id: decoded.userId },
|
|
223
|
+
data: { passwordHash: hashedPassword },
|
|
224
|
+
select: { username: true }
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
const tokenInfo = activeResetTokens.get(decoded.tokenId);
|
|
228
|
+
tokenInfo.used = true;
|
|
229
|
+
|
|
230
|
+
activeResetTokens.delete(decoded.tokenId);
|
|
231
|
+
|
|
232
|
+
res.json({
|
|
233
|
+
message: 'Пароль успешно сброшен',
|
|
234
|
+
username: updatedUser.username
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
} catch (error) {
|
|
238
|
+
console.error('[Recovery Reset Error]', error);
|
|
239
|
+
res.status(500).json({ error: 'Ошибка при сбросе пароля' });
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
|
|
103
243
|
/**
|
|
104
244
|
* @route POST /api/auth/login
|
|
105
245
|
* @desc Аутентифицирует пользователя и возвращает токен
|
|
@@ -1085,4 +1085,180 @@ router.put('/:botId/event-graphs/:graphId', authorize('management:edit'), async
|
|
|
1085
1085
|
router.post('/:botId/visual-editor/save', authorize('management:edit'), async (req, res) => {
|
|
1086
1086
|
});
|
|
1087
1087
|
|
|
1088
|
+
router.get('/:botId/ui-extensions', authorize('plugin:list'), async (req, res) => {
|
|
1089
|
+
try {
|
|
1090
|
+
const botId = parseInt(req.params.botId, 10);
|
|
1091
|
+
const enabledPlugins = await prisma.installedPlugin.findMany({
|
|
1092
|
+
where: { botId: botId, isEnabled: true }
|
|
1093
|
+
});
|
|
1094
|
+
|
|
1095
|
+
const extensions = [];
|
|
1096
|
+
for (const plugin of enabledPlugins) {
|
|
1097
|
+
if (plugin.manifest) {
|
|
1098
|
+
try {
|
|
1099
|
+
const manifest = JSON.parse(plugin.manifest);
|
|
1100
|
+
if (manifest.uiExtensions && Array.isArray(manifest.uiExtensions)) {
|
|
1101
|
+
manifest.uiExtensions.forEach(ext => {
|
|
1102
|
+
extensions.push({
|
|
1103
|
+
pluginName: plugin.name,
|
|
1104
|
+
...ext
|
|
1105
|
+
});
|
|
1106
|
+
});
|
|
1107
|
+
}
|
|
1108
|
+
} catch (e) {
|
|
1109
|
+
console.error(`Ошибка парсинга манифеста для плагина ${plugin.name}:`, e);
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
res.json(extensions);
|
|
1114
|
+
} catch (error) {
|
|
1115
|
+
res.status(500).json({ error: 'Не удалось получить расширения интерфейса' });
|
|
1116
|
+
}
|
|
1117
|
+
});
|
|
1118
|
+
|
|
1119
|
+
router.get('/:botId/plugins/:pluginName/ui-content/:path', authorize('plugin:list'), async (req, res) => {
|
|
1120
|
+
const { botId, pluginName, path: uiPath } = req.params;
|
|
1121
|
+
const numericBotId = parseInt(botId, 10);
|
|
1122
|
+
|
|
1123
|
+
try {
|
|
1124
|
+
const plugin = await prisma.installedPlugin.findFirst({
|
|
1125
|
+
where: { botId: numericBotId, name: pluginName, isEnabled: true }
|
|
1126
|
+
});
|
|
1127
|
+
|
|
1128
|
+
if (!plugin) {
|
|
1129
|
+
return res.status(404).json({ error: `Активный плагин "${pluginName}" не найден для этого бота.` });
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
const manifest = plugin.manifest ? JSON.parse(plugin.manifest) : {};
|
|
1133
|
+
const savedSettings = plugin.settings ? JSON.parse(plugin.settings) : {};
|
|
1134
|
+
const defaultSettings = {};
|
|
1135
|
+
|
|
1136
|
+
if (manifest.settings) {
|
|
1137
|
+
for (const key in manifest.settings) {
|
|
1138
|
+
const config = manifest.settings[key];
|
|
1139
|
+
if (config.type === 'json_file' && config.defaultPath) {
|
|
1140
|
+
const configFilePath = path.join(plugin.path, config.defaultPath);
|
|
1141
|
+
try {
|
|
1142
|
+
const fileContent = await fs.readFile(configFilePath, 'utf-8');
|
|
1143
|
+
defaultSettings[key] = JSON.parse(fileContent);
|
|
1144
|
+
} catch (e) { defaultSettings[key] = {}; }
|
|
1145
|
+
} else {
|
|
1146
|
+
try { defaultSettings[key] = JSON.parse(config.default || 'null'); }
|
|
1147
|
+
catch { defaultSettings[key] = config.default; }
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
const finalSettings = { ...defaultSettings, ...savedSettings };
|
|
1152
|
+
|
|
1153
|
+
const mainFilePath = manifest.main || 'index.js';
|
|
1154
|
+
const pluginEntryPoint = path.join(plugin.path, mainFilePath);
|
|
1155
|
+
|
|
1156
|
+
delete require.cache[require.resolve(pluginEntryPoint)];
|
|
1157
|
+
const pluginModule = require(pluginEntryPoint);
|
|
1158
|
+
|
|
1159
|
+
if (typeof pluginModule.getUiPageContent !== 'function') {
|
|
1160
|
+
return res.status(501).json({ error: `Плагин "${pluginName}" не предоставляет кастомный UI контент.` });
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
const botProcess = botManager.bots.get(numericBotId);
|
|
1164
|
+
const botApi = botProcess ? botProcess.api : null;
|
|
1165
|
+
|
|
1166
|
+
const content = await pluginModule.getUiPageContent({
|
|
1167
|
+
path: uiPath,
|
|
1168
|
+
bot: botApi,
|
|
1169
|
+
botId: numericBotId,
|
|
1170
|
+
settings: finalSettings
|
|
1171
|
+
});
|
|
1172
|
+
|
|
1173
|
+
if (content === null) {
|
|
1174
|
+
return res.status(404).json({ error: `Для пути "${uiPath}" не найдено содержимого в плагине "${pluginName}".` });
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
res.json(content);
|
|
1178
|
+
|
|
1179
|
+
} catch (error) {
|
|
1180
|
+
console.error(`[UI Content] Ошибка при получении контента для плагина "${pluginName}":`, error);
|
|
1181
|
+
res.status(500).json({ error: error.message || 'Внутренняя ошибка сервера.' });
|
|
1182
|
+
}
|
|
1183
|
+
});
|
|
1184
|
+
|
|
1185
|
+
|
|
1186
|
+
router.post('/:botId/plugins/:pluginName/action', authorize('plugin:list'), async (req, res) => {
|
|
1187
|
+
const { botId, pluginName } = req.params;
|
|
1188
|
+
const { actionName, payload } = req.body;
|
|
1189
|
+
const numericBotId = parseInt(botId, 10);
|
|
1190
|
+
|
|
1191
|
+
if (!actionName) {
|
|
1192
|
+
return res.status(400).json({ error: 'Необходимо указать "actionName".' });
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
try {
|
|
1196
|
+
const botProcess = botManager.bots.get(numericBotId);
|
|
1197
|
+
|
|
1198
|
+
if (!botProcess) {
|
|
1199
|
+
return res.status(404).json({ error: 'Бот не найден или не запущен.' });
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
const plugin = await prisma.installedPlugin.findFirst({
|
|
1203
|
+
where: { botId: numericBotId, name: pluginName, isEnabled: true }
|
|
1204
|
+
});
|
|
1205
|
+
|
|
1206
|
+
if (!plugin) {
|
|
1207
|
+
return res.status(404).json({ error: `Активный плагин с таким именем "${pluginName}" не найден.` });
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
const manifest = plugin.manifest ? JSON.parse(plugin.manifest) : {};
|
|
1211
|
+
const savedSettings = plugin.settings ? JSON.parse(plugin.settings) : {};
|
|
1212
|
+
const defaultSettings = {};
|
|
1213
|
+
|
|
1214
|
+
if (manifest.settings) {
|
|
1215
|
+
for (const key in manifest.settings) {
|
|
1216
|
+
const config = manifest.settings[key];
|
|
1217
|
+
if (config.type === 'json_file' && config.defaultPath) {
|
|
1218
|
+
const configFilePath = path.join(plugin.path, config.defaultPath);
|
|
1219
|
+
try {
|
|
1220
|
+
const fileContent = await fs.readFile(configFilePath, 'utf-8');
|
|
1221
|
+
defaultSettings[key] = JSON.parse(fileContent);
|
|
1222
|
+
} catch (e) {
|
|
1223
|
+
console.error(`[Action] Не удалось прочитать defaultPath для ${pluginName}: ${e.message}`);
|
|
1224
|
+
defaultSettings[key] = {};
|
|
1225
|
+
}
|
|
1226
|
+
} else {
|
|
1227
|
+
try {
|
|
1228
|
+
defaultSettings[key] = JSON.parse(config.default || 'null');
|
|
1229
|
+
} catch {
|
|
1230
|
+
defaultSettings[key] = config.default;
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
const finalSettings = { ...defaultSettings, ...savedSettings };
|
|
1236
|
+
|
|
1237
|
+
const mainFilePath = manifest.main || 'index.js';
|
|
1238
|
+
const pluginPath = path.join(plugin.path, mainFilePath);
|
|
1239
|
+
|
|
1240
|
+
delete require.cache[require.resolve(pluginPath)];
|
|
1241
|
+
const pluginModule = require(pluginPath);
|
|
1242
|
+
|
|
1243
|
+
if (typeof pluginModule.handleAction !== 'function') {
|
|
1244
|
+
return res.status(501).json({ error: `Плагин "${pluginName}" не поддерживает обработку действий.` });
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
const result = await pluginModule.handleAction({
|
|
1248
|
+
botProcess: botProcess,
|
|
1249
|
+
botId: numericBotId,
|
|
1250
|
+
action: actionName,
|
|
1251
|
+
payload: payload,
|
|
1252
|
+
settings: finalSettings
|
|
1253
|
+
});
|
|
1254
|
+
|
|
1255
|
+
res.json({ success: true, message: 'Действие выполнено.', result: result || null });
|
|
1256
|
+
|
|
1257
|
+
} catch (error) {
|
|
1258
|
+
console.error(`Ошибка выполнения действия "${actionName}" для плагина "${pluginName}":`, error);
|
|
1259
|
+
res.status(500).json({ error: error.message || 'Внутренняя ошибка сервера.' });
|
|
1260
|
+
}
|
|
1261
|
+
});
|
|
1262
|
+
|
|
1263
|
+
|
|
1088
1264
|
module.exports = router;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const router = express.Router();
|
|
5
|
+
|
|
6
|
+
router.get('/', async (req, res) => {
|
|
7
|
+
try {
|
|
8
|
+
const file = path.resolve(__dirname, '../../../../CHANGELOG.md');
|
|
9
|
+
const data = await fs.promises.readFile(file, 'utf8');
|
|
10
|
+
res.type('text/markdown').send(data);
|
|
11
|
+
} catch (e) {
|
|
12
|
+
res.status(500).json({ error: 'changelog_not_found' });
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
module.exports = router;
|
|
@@ -121,7 +121,17 @@ router.put('/:graphId',
|
|
|
121
121
|
|
|
122
122
|
const parsedGraph = JSON.parse(graphJson);
|
|
123
123
|
const eventNodes = parsedGraph.nodes.filter(node => node.type.startsWith('event:'));
|
|
124
|
-
const eventTypes = eventNodes.map(node => node.type.split(':')[1]);
|
|
124
|
+
const eventTypes = [...new Set(eventNodes.map(node => node.type.split(':')[1]))];
|
|
125
|
+
|
|
126
|
+
const existingGraph = await prisma.eventGraph.findUnique({
|
|
127
|
+
where: { id: parseInt(graphId) },
|
|
128
|
+
include: { triggers: true }
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
const existingEventTypes = existingGraph?.triggers?.map(t => t.eventType) || [];
|
|
132
|
+
|
|
133
|
+
const eventTypesToDelete = existingEventTypes.filter(et => !eventTypes.includes(et));
|
|
134
|
+
const eventTypesToCreate = eventTypes.filter(et => !existingEventTypes.includes(et));
|
|
125
135
|
|
|
126
136
|
dataToUpdate.triggers = {
|
|
127
137
|
deleteMany: {},
|
|
@@ -53,6 +53,17 @@ router.post('/update/:pluginId', authenticate, authorize('plugin:update'), async
|
|
|
53
53
|
}
|
|
54
54
|
});
|
|
55
55
|
|
|
56
|
+
router.post('/:id/clear-data', authenticate, authorize('plugin:settings:edit'), async (req, res) => {
|
|
57
|
+
try {
|
|
58
|
+
const pluginId = parseInt(req.params.id);
|
|
59
|
+
await pluginManager.clearPluginData(pluginId);
|
|
60
|
+
res.status(200).json({ message: 'Данные плагина успешно очищены.' });
|
|
61
|
+
} catch (error) {
|
|
62
|
+
console.error(`[API Error] /plugins/:id/clear-data:`, error);
|
|
63
|
+
res.status(500).json({ error: error.message || 'Не удалось очистить данные плагина.' });
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
56
67
|
router.get('/catalog/:name', async (req, res) => {
|
|
57
68
|
try {
|
|
58
69
|
const pluginName = req.params.name;
|