blockmine 1.17.1 → 1.18.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/backend/package.json +27 -27
- package/backend/src/api/routes/bots.js +3 -2
- package/backend/src/api/routes/tasks.js +2 -1
- package/backend/src/core/BotManager.js +10 -0
- package/backend/src/core/BotProcess.js +4 -1
- package/backend/src/core/PluginLoader.js +86 -131
- package/backend/src/core/PluginManager.js +427 -402
- package/backend/src/core/TaskScheduler.js +82 -52
- package/backend/src/core/utils/settingsMerger.js +25 -0
- package/frontend/dist/assets/{index-UZUhEwz5.js → index-BYC2UyN-.js} +1667 -1667
- package/frontend/dist/create_issue.png +0 -0
- package/frontend/dist/index.html +1 -1
- package/package.json +1 -1
|
@@ -1,403 +1,428 @@
|
|
|
1
|
-
const path = require('path');
|
|
2
|
-
const fse = require('fs-extra');
|
|
3
|
-
const os = require('os');
|
|
4
|
-
const {
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
this.
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
if (
|
|
129
|
-
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
const plugin
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
}
|
|
402
|
-
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const fse = require('fs-extra');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
const { execSync } = require('child_process');
|
|
5
|
+
const { PrismaClient } = require('@prisma/client');
|
|
6
|
+
const AdmZip = require('adm-zip');
|
|
7
|
+
const semver = require('semver');
|
|
8
|
+
|
|
9
|
+
const prisma = new PrismaClient();
|
|
10
|
+
|
|
11
|
+
const DATA_DIR = path.join(os.homedir(), '.blockmine');
|
|
12
|
+
const PLUGINS_BASE_DIR = path.join(DATA_DIR, 'storage', 'plugins');
|
|
13
|
+
|
|
14
|
+
const TELEMETRY_ENABLED = true;
|
|
15
|
+
const STATS_SERVER_URL = 'http://185.65.200.184:3000';
|
|
16
|
+
|
|
17
|
+
function reportPluginDownload(pluginName) {
|
|
18
|
+
if (!TELEMETRY_ENABLED) return;
|
|
19
|
+
|
|
20
|
+
console.log(`[Telemetry] Попытка отправить статистику по плагину: ${pluginName}`);
|
|
21
|
+
|
|
22
|
+
fetch(`${STATS_SERVER_URL}/api/plugins/download`, {
|
|
23
|
+
method: 'POST',
|
|
24
|
+
headers: { 'Content-Type': 'application/json' },
|
|
25
|
+
body: JSON.stringify({ plugin_name: pluginName })
|
|
26
|
+
})
|
|
27
|
+
.then(res => {
|
|
28
|
+
if (res.ok) {
|
|
29
|
+
console.log(`[Telemetry] Статистика для плагина ${pluginName} успешно отправлена.`);
|
|
30
|
+
} else {
|
|
31
|
+
console.error(`[Telemetry] Сервер статистики вернул ошибку для плагина ${pluginName}: ${res.statusText}`);
|
|
32
|
+
}
|
|
33
|
+
})
|
|
34
|
+
.catch(error => {
|
|
35
|
+
console.error(`[Telemetry] Не удалось отправить статистику по плагину ${pluginName}: ${error.message}`);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class PluginManager {
|
|
41
|
+
constructor(botManager) {
|
|
42
|
+
this.botManager = botManager;
|
|
43
|
+
this.ensureBaseDirExists();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async ensureBaseDirExists() {
|
|
47
|
+
await fse.mkdir(PLUGINS_BASE_DIR, { recursive: true }).catch(console.error);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async _installDependencies(pluginPath) {
|
|
51
|
+
const packageJsonPath = path.join(pluginPath, 'package.json');
|
|
52
|
+
try {
|
|
53
|
+
if (await fse.pathExists(packageJsonPath)) {
|
|
54
|
+
const packageJson = await fse.readJson(packageJsonPath);
|
|
55
|
+
if (packageJson.dependencies && Object.keys(packageJson.dependencies).length > 0) {
|
|
56
|
+
console.log(`[PluginManager] Установка зависимостей для плагина в ${pluginPath}...`);
|
|
57
|
+
// Используем --omit=dev чтобы не ставить dev-зависимости
|
|
58
|
+
execSync('npm install --omit=dev', {
|
|
59
|
+
cwd: pluginPath,
|
|
60
|
+
stdio: 'inherit'
|
|
61
|
+
});
|
|
62
|
+
console.log(`[PluginManager] Зависимости успешно установлены.`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
} catch (error) {
|
|
66
|
+
console.error(`[PluginManager] Ошибка при установке зависимостей в ${pluginPath}:`, error);
|
|
67
|
+
throw new Error('Не удалось установить зависимости плагина.');
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async installFromLocalPath(botId, directoryPath) {
|
|
72
|
+
const newPlugin = await this.registerPlugin(botId, directoryPath, 'LOCAL', directoryPath);
|
|
73
|
+
try {
|
|
74
|
+
const packageJson = JSON.parse(await fse.readFile(path.join(directoryPath, 'package.json'), 'utf-8'));
|
|
75
|
+
reportPluginDownload(packageJson.name);
|
|
76
|
+
} catch(e) {
|
|
77
|
+
console.error('Не удалось прочитать package.json для отправки статистики локального плагина');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
await this._installDependencies(directoryPath);
|
|
81
|
+
await this.loadPluginGraphs(botId, newPlugin.id, directoryPath);
|
|
82
|
+
|
|
83
|
+
if (this.botManager) {
|
|
84
|
+
await this.botManager.reloadPlugins(botId);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return newPlugin;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async installFromGithub(botId, repoUrl, prismaClient = prisma, isUpdate = false) {
|
|
91
|
+
const botPluginsDir = path.join(PLUGINS_BASE_DIR, `bot_${botId}`);
|
|
92
|
+
await fse.mkdir(botPluginsDir, { recursive: true });
|
|
93
|
+
|
|
94
|
+
if (!isUpdate) {
|
|
95
|
+
const existing = await prismaClient.installedPlugin.findFirst({ where: { botId, sourceUri: repoUrl } });
|
|
96
|
+
if (existing) throw new Error(`Плагин из ${repoUrl} уже установлен.`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
const url = new URL(repoUrl);
|
|
101
|
+
const repoPath = url.pathname.replace(/^\/|\.git$/g, '');
|
|
102
|
+
const archiveUrlMain = `https://github.com/${repoPath}/archive/refs/heads/main.zip`;
|
|
103
|
+
const archiveUrlMaster = `https://github.com/${repoPath}/archive/refs/heads/master.zip`;
|
|
104
|
+
|
|
105
|
+
let response = await fetch(archiveUrlMain);
|
|
106
|
+
if (!response.ok) {
|
|
107
|
+
console.log(`[PluginManager] Ветка 'main' не найдена для ${repoUrl}, пробую 'master'...`);
|
|
108
|
+
response = await fetch(archiveUrlMaster);
|
|
109
|
+
if (!response.ok) {
|
|
110
|
+
throw new Error(`Не удалось скачать архив плагина. Статус: ${response.status}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
const buffer = await response.arrayBuffer();
|
|
114
|
+
|
|
115
|
+
const zip = new AdmZip(Buffer.from(buffer));
|
|
116
|
+
const zipEntries = zip.getEntries();
|
|
117
|
+
if (zipEntries.length === 0) {
|
|
118
|
+
throw new Error('Скачанный архив плагина пуст.');
|
|
119
|
+
}
|
|
120
|
+
const rootFolderName = zipEntries[0].entryName.split('/')[0];
|
|
121
|
+
const repoName = path.basename(repoPath);
|
|
122
|
+
const localPath = path.join(botPluginsDir, repoName);
|
|
123
|
+
|
|
124
|
+
if (await fse.pathExists(localPath)) {
|
|
125
|
+
await fse.remove(localPath);
|
|
126
|
+
}
|
|
127
|
+
const tempExtractPath = path.join(botPluginsDir, rootFolderName);
|
|
128
|
+
if (await fse.pathExists(tempExtractPath)) {
|
|
129
|
+
await fse.remove(tempExtractPath);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
zip.extractAllTo(botPluginsDir, true);
|
|
133
|
+
|
|
134
|
+
await fse.move(tempExtractPath, localPath, { overwrite: true });
|
|
135
|
+
|
|
136
|
+
await this._installDependencies(localPath);
|
|
137
|
+
|
|
138
|
+
const newPlugin = await this.registerPlugin(botId, localPath, 'GITHUB', repoUrl, prismaClient);
|
|
139
|
+
|
|
140
|
+
const packageJson = JSON.parse(await fse.readFile(path.join(localPath, 'package.json'), 'utf-8'));
|
|
141
|
+
reportPluginDownload(packageJson.name);
|
|
142
|
+
|
|
143
|
+
await this.loadPluginGraphs(botId, newPlugin.id, localPath);
|
|
144
|
+
|
|
145
|
+
if (this.botManager) {
|
|
146
|
+
await this.botManager.reloadPlugins(botId);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return newPlugin;
|
|
150
|
+
|
|
151
|
+
} catch (error) {
|
|
152
|
+
console.error(`[PluginManager] Ошибка установки с GitHub: ${error.message}`);
|
|
153
|
+
if (error.message.includes('fetch')) {
|
|
154
|
+
throw new Error(`Не удалось подключиться к GitHub или репозиторий не найден. Проверьте ссылку и ваше интернет-соединение.`);
|
|
155
|
+
}
|
|
156
|
+
throw error;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async registerPlugin(botId, directoryPath, sourceType, sourceUri, prismaClient = prisma) {
|
|
161
|
+
const packageJsonPath = path.join(directoryPath, 'package.json');
|
|
162
|
+
let packageJson;
|
|
163
|
+
try {
|
|
164
|
+
packageJson = JSON.parse(await fse.readFile(packageJsonPath, 'utf-8'));
|
|
165
|
+
} catch (e) {
|
|
166
|
+
throw new Error(`Не удалось прочитать или распарсить package.json в плагине по пути: ${directoryPath}`);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (!packageJson.name || !packageJson.version) {
|
|
170
|
+
throw new Error('package.json не содержит обязательных полей name и version');
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const pluginData = {
|
|
174
|
+
botId,
|
|
175
|
+
name: packageJson.name,
|
|
176
|
+
version: packageJson.version,
|
|
177
|
+
description: packageJson.description || '',
|
|
178
|
+
path: directoryPath,
|
|
179
|
+
sourceType,
|
|
180
|
+
sourceUri: sourceUri || directoryPath,
|
|
181
|
+
manifest: JSON.stringify(packageJson.botpanel || {}),
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
return prismaClient.installedPlugin.upsert({
|
|
185
|
+
where: { botId_name: { botId, name: packageJson.name } },
|
|
186
|
+
update: pluginData,
|
|
187
|
+
create: pluginData,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async deletePlugin(pluginId) {
|
|
192
|
+
const plugin = await prisma.installedPlugin.findUnique({ where: { id: pluginId } });
|
|
193
|
+
if (!plugin) throw new Error('Плагин не найден');
|
|
194
|
+
|
|
195
|
+
const pluginOwnerId = `plugin:${plugin.name}`;
|
|
196
|
+
console.log(`[PluginManager] Начало удаления плагина ${plugin.name} (ID: ${plugin.id}) и его ресурсов.`);
|
|
197
|
+
console.log(`[PluginManager] Идентификатор владельца для очистки: ${pluginOwnerId}`);
|
|
198
|
+
|
|
199
|
+
try {
|
|
200
|
+
const manifest = plugin.manifest ? JSON.parse(plugin.manifest) : {};
|
|
201
|
+
const mainFile = manifest.main || 'index.js';
|
|
202
|
+
const entryPointPath = path.join(plugin.path, mainFile);
|
|
203
|
+
|
|
204
|
+
if (await fse.pathExists(entryPointPath)) {
|
|
205
|
+
if (require.cache[require.resolve(entryPointPath)]) {
|
|
206
|
+
delete require.cache[require.resolve(entryPointPath)];
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const pluginModule = require(entryPointPath);
|
|
210
|
+
|
|
211
|
+
if (pluginModule && typeof pluginModule.onUnload === 'function') {
|
|
212
|
+
console.log(`[PluginManager] Вызов хука onUnload для плагина ${plugin.name}...`);
|
|
213
|
+
await pluginModule.onUnload({ botId: plugin.botId, prisma });
|
|
214
|
+
console.log(`[PluginManager] Хук onUnload для ${plugin.name} успешно выполнен.`);
|
|
215
|
+
}
|
|
216
|
+
} else {
|
|
217
|
+
console.warn(`[PluginManager] Главный файл плагина ${entryPointPath} не найден. Хук onUnload пропущен.`);
|
|
218
|
+
}
|
|
219
|
+
} catch (error) {
|
|
220
|
+
console.error(`[PluginManager] Ошибка при выполнении хука onUnload для плагина ${plugin.name}:`, error);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
try {
|
|
224
|
+
await prisma.$transaction(async (tx) => {
|
|
225
|
+
const deletedCommands = await tx.command.deleteMany({
|
|
226
|
+
where: { botId: plugin.botId, pluginOwnerId: plugin.id },
|
|
227
|
+
});
|
|
228
|
+
if (deletedCommands.count > 0) console.log(`[DB Cleanup] Удалено команд: ${deletedCommands.count}`);
|
|
229
|
+
|
|
230
|
+
const deletedEventGraphs = await tx.eventGraph.deleteMany({
|
|
231
|
+
where: { botId: plugin.botId, pluginOwnerId: plugin.id },
|
|
232
|
+
});
|
|
233
|
+
if (deletedEventGraphs.count > 0) console.log(`[DB Cleanup] Удалено графов событий: ${deletedEventGraphs.count}`);
|
|
234
|
+
|
|
235
|
+
const deletedPermissions = await tx.permission.deleteMany({
|
|
236
|
+
where: { botId: plugin.botId, owner: pluginOwnerId },
|
|
237
|
+
});
|
|
238
|
+
if (deletedPermissions.count > 0) console.log(`[DB Cleanup] Удалено прав: ${deletedPermissions.count}`);
|
|
239
|
+
|
|
240
|
+
const deletedGroups = await tx.group.deleteMany({
|
|
241
|
+
where: { botId: plugin.botId, owner: pluginOwnerId },
|
|
242
|
+
});
|
|
243
|
+
if (deletedGroups.count > 0) console.log(`[DB Cleanup] Удалено групп: ${deletedGroups.count}`);
|
|
244
|
+
|
|
245
|
+
await tx.installedPlugin.delete({ where: { id: pluginId } });
|
|
246
|
+
console.log(`[DB Cleanup] Запись о плагине ${plugin.name} удалена.`);
|
|
247
|
+
});
|
|
248
|
+
} catch (dbError) {
|
|
249
|
+
console.error(`[PluginManager] Ошибка при очистке БД для плагина ${plugin.name}:`, dbError);
|
|
250
|
+
throw new Error('Ошибка при удалении данных плагина из БД. Файлы не были удалены.');
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (plugin.path && plugin.path.startsWith(PLUGINS_BASE_DIR)) {
|
|
254
|
+
try {
|
|
255
|
+
if (await fse.pathExists(plugin.path)) {
|
|
256
|
+
await fse.remove(plugin.path);
|
|
257
|
+
console.log(`[PluginManager] Папка плагина ${plugin.path} успешно удалена.`);
|
|
258
|
+
} else {
|
|
259
|
+
console.log(`[PluginManager] Папка плагина ${plugin.path} не найдена, удаление не требуется.`);
|
|
260
|
+
}
|
|
261
|
+
} catch (fileError) {
|
|
262
|
+
console.error(`Не удалось удалить папку плагина ${plugin.path}:`, fileError);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
async checkForUpdates(botId, catalog) {
|
|
268
|
+
const githubPlugins = await prisma.installedPlugin.findMany({
|
|
269
|
+
where: { botId, sourceType: 'GITHUB' }
|
|
270
|
+
});
|
|
271
|
+
const updatesAvailable = [];
|
|
272
|
+
const catalogMap = new Map(catalog.map(item => [item.repoUrl, item]));
|
|
273
|
+
|
|
274
|
+
for (const plugin of githubPlugins) {
|
|
275
|
+
try {
|
|
276
|
+
const catalogInfo = catalogMap.get(plugin.sourceUri);
|
|
277
|
+
if (!catalogInfo || !catalogInfo.latestTag) continue;
|
|
278
|
+
|
|
279
|
+
const localVersion = semver.coerce(plugin.version)?.version || plugin.version;
|
|
280
|
+
const recommendedVersion = semver.coerce(catalogInfo.latestTag)?.version || catalogInfo.latestTag;
|
|
281
|
+
|
|
282
|
+
if (semver.gt(recommendedVersion, localVersion)) {
|
|
283
|
+
updatesAvailable.push({
|
|
284
|
+
id: plugin.id,
|
|
285
|
+
name: plugin.name,
|
|
286
|
+
sourceUri: plugin.sourceUri,
|
|
287
|
+
currentVersion: localVersion,
|
|
288
|
+
recommendedVersion: recommendedVersion,
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
} catch (error) {
|
|
292
|
+
console.error(`[PluginManager] Ошибка проверки обновлений для ${plugin.name}:`, error.message);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
return updatesAvailable;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
async updatePlugin(pluginId) {
|
|
299
|
+
const plugin = await prisma.installedPlugin.findUnique({ where: { id: pluginId } });
|
|
300
|
+
if (!plugin || plugin.sourceType !== 'GITHUB') {
|
|
301
|
+
throw new Error('Плагин не найден или не является GitHub-плагином.');
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
console.log(`[PluginManager] Начало обновления плагина ${plugin.name}...`);
|
|
305
|
+
|
|
306
|
+
const repoUrl = plugin.sourceUri;
|
|
307
|
+
const botId = plugin.botId;
|
|
308
|
+
|
|
309
|
+
await this.deletePlugin(pluginId);
|
|
310
|
+
console.log(`[PluginManager] Старая версия ${plugin.name} удалена, устанавливаем новую...`);
|
|
311
|
+
|
|
312
|
+
return await this.installFromGithub(botId, repoUrl, prisma, true);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
async loadPluginGraphs(botId, pluginId, pluginPath) {
|
|
316
|
+
const plugin = await prisma.installedPlugin.findUnique({
|
|
317
|
+
where: { id: pluginId }
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
if (!plugin) {
|
|
321
|
+
console.error(`[PluginManager] Плагин с ID ${pluginId} не найден`);
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
try {
|
|
325
|
+
const graphDir = path.join(pluginPath, 'graph');
|
|
326
|
+
if (!await fse.pathExists(graphDir)) {
|
|
327
|
+
console.log(`[PluginManager] Папка graph не найдена в плагине: ${graphDir}`);
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const graphFiles = await fse.readdir(graphDir);
|
|
332
|
+
const jsonFiles = graphFiles.filter(file => file.endsWith('.json'));
|
|
333
|
+
|
|
334
|
+
console.log(`[PluginManager] Найдено ${jsonFiles.length} файлов графов в плагине`);
|
|
335
|
+
|
|
336
|
+
for (const fileName of jsonFiles) {
|
|
337
|
+
try {
|
|
338
|
+
const graphName = path.basename(fileName, '.json');
|
|
339
|
+
const graphPath = path.join(graphDir, fileName);
|
|
340
|
+
const graphData = await fse.readJson(graphPath);
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
const hasCommandNode = graphData.nodes?.some(node => node.type === 'event:command');
|
|
345
|
+
const hasEventNode = graphData.nodes?.some(node => node.type?.startsWith('event:') && node.type !== 'event:command');
|
|
346
|
+
|
|
347
|
+
const isCommand = hasCommandNode;
|
|
348
|
+
const isEventGraph = hasEventNode;
|
|
349
|
+
|
|
350
|
+
if (isEventGraph) {
|
|
351
|
+
const existingEventGraph = await prisma.eventGraph.findFirst({
|
|
352
|
+
where: { botId, name: graphName }
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
if (existingEventGraph) {
|
|
356
|
+
console.log(`[PluginManager] Граф события ${graphName} уже существует, пропускаем`);
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const newEventGraph = await prisma.eventGraph.create({
|
|
361
|
+
data: {
|
|
362
|
+
botId,
|
|
363
|
+
name: graphName,
|
|
364
|
+
graphJson: JSON.stringify(graphData),
|
|
365
|
+
isEnabled: true,
|
|
366
|
+
pluginOwnerId: pluginId
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
console.log(`[PluginManager] Загружен граф события: ${graphName} (ID: ${newEventGraph.id})`);
|
|
371
|
+
} else if (isCommand) {
|
|
372
|
+
const existingCommand = await prisma.command.findFirst({
|
|
373
|
+
where: { botId, name: graphName }
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
if (existingCommand) {
|
|
377
|
+
console.log(`[PluginManager] Команда ${graphName} уже существует, пропускаем`);
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const newCommand = await prisma.command.create({
|
|
382
|
+
data: {
|
|
383
|
+
botId,
|
|
384
|
+
name: graphName,
|
|
385
|
+
description: graphData.command || `Команда ${graphName}`,
|
|
386
|
+
graphJson: JSON.stringify(graphData),
|
|
387
|
+
isEnabled: true,
|
|
388
|
+
pluginOwnerId: pluginId,
|
|
389
|
+
owner: `plugin:${plugin.name}`,
|
|
390
|
+
isVisual: true
|
|
391
|
+
}
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
console.log(`[PluginManager] Загружена команда: ${graphName} (ID: ${newCommand.id}) с pluginOwnerId: ${pluginId}`);
|
|
395
|
+
} else {
|
|
396
|
+
console.warn(`[PluginManager] Неизвестный тип графа в файле ${fileName}, пропускаем`);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
} catch (error) {
|
|
400
|
+
console.error(`[PluginManager] Ошибка загрузки графа ${fileName}:`, error);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
} catch (error) {
|
|
404
|
+
console.error(`[PluginManager] Ошибка загрузки графов плагина:`, error);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
async clearPluginData(pluginId) {
|
|
409
|
+
const plugin = await prisma.installedPlugin.findUnique({ where: { id: pluginId } });
|
|
410
|
+
if (!plugin) {
|
|
411
|
+
throw new Error('Плагин не найден.');
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
console.log(`[PluginManager] Очистка данных для плагина ${plugin.name} (Bot ID: ${plugin.botId})`);
|
|
415
|
+
|
|
416
|
+
const { count } = await prisma.pluginDataStore.deleteMany({
|
|
417
|
+
where: {
|
|
418
|
+
pluginName: plugin.name,
|
|
419
|
+
botId: plugin.botId,
|
|
420
|
+
},
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
console.log(`[PluginManager] Удалено ${count} записей из хранилища.`);
|
|
424
|
+
return { count };
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
403
428
|
module.exports = PluginManager;
|