blockmine 1.6.0 → 1.6.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/README.md CHANGED
@@ -131,6 +131,7 @@
131
131
  git clone https://github.com/blockmineJS/blockmine.git
132
132
  cd blockmine
133
133
  npm install
134
+ npm run build
134
135
  ```
135
136
 
136
137
  ### 2. Запуск в режиме разработки
@@ -21,18 +21,25 @@ const resolvePluginPath = async (req, res, next) => {
21
21
  return res.status(400).json({ error: 'Имя плагина обязательно в пути.' });
22
22
  }
23
23
 
24
- const botPluginsDir = path.join(PLUGINS_BASE_DIR, `bot_${botId}`);
25
- const pluginPath = path.resolve(botPluginsDir, pluginName);
26
-
27
- if (!pluginPath.startsWith(botPluginsDir)) {
28
- return res.status(403).json({ error: 'Доступ запрещен: попытка доступа за пределы директории плагина.' });
24
+ const plugin = await prisma.installedPlugin.findFirst({
25
+ where: {
26
+ botId: parseInt(botId),
27
+ name: pluginName
28
+ }
29
+ });
30
+
31
+ if (!plugin) {
32
+ return res.status(404).json({ error: 'Плагин не найден в базе данных.' });
29
33
  }
30
34
 
35
+ const pluginPath = plugin.path;
36
+
31
37
  if (!await fse.pathExists(pluginPath)) {
32
- return res.status(404).json({ error: 'Директория плагина не найдена.' });
38
+ return res.status(404).json({ error: 'Директория плагина не найдена в файловой системе.' });
33
39
  }
34
40
 
35
41
  req.pluginPath = pluginPath;
42
+ req.pluginData = plugin;
36
43
  next();
37
44
  } catch (error) {
38
45
  console.error('[Plugin IDE Middleware Error]', error);
@@ -297,19 +304,48 @@ router.post('/:pluginName/file', resolvePluginPath, async (req, res) => {
297
304
  if (relativePath === 'package.json' || relativePath.endsWith('/package.json')) {
298
305
  try {
299
306
  const packageJson = JSON.parse(content);
300
- await prisma.installedPlugin.updateMany({
307
+
308
+ const existingPlugin = await prisma.installedPlugin.findFirst({
301
309
  where: {
302
310
  botId: parseInt(req.params.botId),
303
311
  path: req.pluginPath,
304
- },
305
- data: {
306
- name: packageJson.name || req.params.pluginName,
307
- version: packageJson.version || '1.0.0',
308
- description: packageJson.description || '',
309
- manifest: JSON.stringify(packageJson.botpanel || {}),
310
312
  }
311
313
  });
312
- console.log(`[Plugin IDE] Manifest обновлен для плагина ${req.params.pluginName} после сохранения package.json`);
314
+
315
+ if (existingPlugin) {
316
+ const newName = packageJson.name || req.params.pluginName;
317
+
318
+ const conflictingPlugin = await prisma.installedPlugin.findFirst({
319
+ where: {
320
+ botId: parseInt(req.params.botId),
321
+ name: newName,
322
+ id: { not: existingPlugin.id }
323
+ }
324
+ });
325
+
326
+ if (conflictingPlugin) {
327
+ console.warn(`[Plugin IDE] Конфликт имени плагина: ${newName} уже существует для бота ${req.params.botId}`);
328
+ await prisma.installedPlugin.update({
329
+ where: { id: existingPlugin.id },
330
+ data: {
331
+ version: packageJson.version || '1.0.0',
332
+ description: packageJson.description || '',
333
+ manifest: JSON.stringify(packageJson.botpanel || {}),
334
+ }
335
+ });
336
+ } else {
337
+ await prisma.installedPlugin.update({
338
+ where: { id: existingPlugin.id },
339
+ data: {
340
+ name: newName,
341
+ version: packageJson.version || '1.0.0',
342
+ description: packageJson.description || '',
343
+ manifest: JSON.stringify(packageJson.botpanel || {}),
344
+ }
345
+ });
346
+ }
347
+ console.log(`[Plugin IDE] Manifest обновлен для плагина ${req.params.pluginName} после сохранения package.json`);
348
+ }
313
349
  } catch (manifestError) {
314
350
  console.error(`[Plugin IDE] Ошибка обновления manifest для ${req.params.pluginName}:`, manifestError);
315
351
  }
@@ -366,6 +402,33 @@ router.post('/:pluginName/fs', resolvePluginPath, async (req, res) => {
366
402
  res.status(200).json({ message: 'Renamed successfully.' });
367
403
  break;
368
404
 
405
+ case 'move':
406
+ if (!newPath) return res.status(400).json({ error: 'New path is required for move operation.' });
407
+ const safeMoveNewPath = path.resolve(req.pluginPath, newPath);
408
+ if (!safeMoveNewPath.startsWith(req.pluginPath)) return res.status(403).json({ error: 'Access denied: New path is outside of plugin directory.' });
409
+ if (!await fse.pathExists(safePath)) return res.status(404).json({ error: 'Source file or folder not found.' });
410
+
411
+ if (safeMoveNewPath.startsWith(safePath + path.sep)) {
412
+ return res.status(400).json({ error: 'Cannot move folder into itself.' });
413
+ }
414
+
415
+ let finalPath = safeMoveNewPath;
416
+ let counter = 1;
417
+ while (await fse.pathExists(finalPath)) {
418
+ const ext = path.extname(safeMoveNewPath);
419
+ const base = path.basename(safeMoveNewPath, ext);
420
+ const dir = path.dirname(safeMoveNewPath);
421
+ finalPath = path.join(dir, `${base} (${counter})${ext}`);
422
+ counter++;
423
+ }
424
+
425
+ await fse.move(safePath, finalPath);
426
+ res.status(200).json({
427
+ message: 'Moved successfully.',
428
+ newPath: path.relative(req.pluginPath, finalPath)
429
+ });
430
+ break;
431
+
369
432
  default:
370
433
  res.status(400).json({ error: 'Invalid operation specified.' });
371
434
  }
@@ -412,23 +475,50 @@ router.post('/:pluginName/manifest', resolvePluginPath, async (req, res) => {
412
475
 
413
476
  await fse.writeJson(manifestPath, newManifest, { spaces: 2 });
414
477
 
415
- await prisma.installedPlugin.updateMany({
478
+ const existingPlugin = await prisma.installedPlugin.findFirst({
416
479
  where: {
417
480
  botId: parseInt(req.params.botId),
418
481
  path: req.pluginPath,
419
- },
420
- data: {
421
- name: newManifest.name,
422
- version: newManifest.version,
423
- description: newManifest.description,
424
- manifest: JSON.stringify(newManifest.botpanel || {}),
425
482
  }
426
483
  });
484
+
485
+ if (existingPlugin) {
486
+ const conflictingPlugin = await prisma.installedPlugin.findFirst({
487
+ where: {
488
+ botId: parseInt(req.params.botId),
489
+ name: newManifest.name,
490
+ id: { not: existingPlugin.id }
491
+ }
492
+ });
493
+
494
+ if (conflictingPlugin) {
495
+ console.warn(`[Plugin IDE] Конфликт имени плагина: ${newManifest.name} уже существует для бота ${req.params.botId}`);
496
+ await prisma.installedPlugin.update({
497
+ where: { id: existingPlugin.id },
498
+ data: {
499
+ version: newManifest.version,
500
+ description: newManifest.description,
501
+ manifest: JSON.stringify(newManifest.botpanel || {}),
502
+ }
503
+ });
504
+ } else {
505
+ await prisma.installedPlugin.update({
506
+ where: { id: existingPlugin.id },
507
+ data: {
508
+ name: newManifest.name,
509
+ version: newManifest.version,
510
+ description: newManifest.description,
511
+ manifest: JSON.stringify(newManifest.botpanel || {}),
512
+ }
513
+ });
514
+ }
515
+ }
427
516
 
428
517
  res.status(200).json({ message: 'package.json успешно обновлен.' });
429
518
  } catch (error) {
430
519
  console.error(`[Plugin IDE Error] /manifest POST for ${req.params.pluginName}:`, error);
431
- res.status(500).json({ error: 'Не удалось обновить package.json.' });
520
+ // Файл уже сохранен, поэтому возвращаем успех даже если есть ошибка с БД
521
+ res.status(200).json({ message: 'package.json обновлен (возможны проблемы с синхронизацией БД).' });
432
522
  }
433
523
  });
434
524
 
@@ -543,7 +633,26 @@ router.post('/:pluginName/create-pr', resolvePluginPath, async (req, res) => {
543
633
 
544
634
  process.chdir(tempDir);
545
635
 
546
- cp.execSync(`git checkout -b ${branch}`);
636
+ let branchExists = false;
637
+ try {
638
+ cp.execSync(`git checkout -b ${branch}`, { stdio: 'pipe' });
639
+ console.log(`[Plugin IDE] Создана новая ветка ${branch}`);
640
+ } catch (e) {
641
+ try {
642
+ cp.execSync(`git checkout ${branch}`, { stdio: 'pipe' });
643
+ console.log(`[Plugin IDE] Переключились на существующую ветку ${branch}`);
644
+ } catch (e2) {
645
+ try {
646
+ cp.execSync(`git fetch origin ${branch}`, { stdio: 'pipe' });
647
+ cp.execSync(`git checkout -b ${branch} origin/${branch}`, { stdio: 'pipe' });
648
+ branchExists = true;
649
+ console.log(`[Plugin IDE] Создана ветка ${branch} из удаленной`);
650
+ } catch (e3) {
651
+ cp.execSync(`git checkout -B ${branch}`, { stdio: 'pipe' });
652
+ console.log(`[Plugin IDE] Принудительно создана ветка ${branch}`);
653
+ }
654
+ }
655
+ }
547
656
 
548
657
  const files = await fse.readdir(req.pluginPath);
549
658
  for (const file of files) {
@@ -564,11 +673,26 @@ router.post('/:pluginName/create-pr', resolvePluginPath, async (req, res) => {
564
673
  throw e;
565
674
  }
566
675
 
567
- cp.execSync(`git push -u origin ${branch}`);
676
+
677
+ if (branchExists) {
678
+ cp.execSync(`git push origin ${branch} --force`);
679
+ console.log(`[Plugin IDE] Ветка ${branch} обновлена`);
680
+ } else {
681
+ cp.execSync(`git push -u origin ${branch}`);
682
+ console.log(`[Plugin IDE] Новая ветка ${branch} создана`);
683
+ }
568
684
 
569
685
  const prUrl = `https://github.com/${repoInfo.owner}/${repoInfo.repo}/pull/new/${branch}`;
570
686
 
571
- res.json({ success: true, prUrl });
687
+ const responseData = {
688
+ success: true,
689
+ prUrl: prUrl,
690
+ isUpdate: branchExists,
691
+ message: branchExists ? 'Существующий PR обновлен' : 'Новый PR создан'
692
+ };
693
+
694
+ console.log(`[Plugin IDE] PR ${branchExists ? 'обновлен' : 'создан'} для плагина ${req.params.pluginName}:`, responseData);
695
+ res.json(responseData);
572
696
 
573
697
  } finally {
574
698
  try {
@@ -12,7 +12,7 @@ const { parseArguments } = require('./system/parseArguments');
12
12
  const GraphExecutionEngine = require('./GraphExecutionEngine');
13
13
  const NodeRegistry = require('./NodeRegistry');
14
14
 
15
- const UserService = require('./ipc/UserService.stub.js');
15
+ const UserService = require('./UserService');
16
16
  const PermissionManager = require('./ipc/PermissionManager.stub.js');
17
17
 
18
18
  let bot = null;
@@ -208,51 +208,7 @@ process.on('message', async (message) => {
208
208
  sendMessage: (type, message, username) => bot.messageQueue.enqueue(type, message, username),
209
209
  sendMessageAndWaitForReply: (command, patterns, timeout) => bot.messageQueue.enqueueAndWait(command, patterns, timeout),
210
210
  getUser: async (username) => {
211
- const userData = await UserService.getUser(username, bot.config.id, bot.config);
212
- if (!userData) return null;
213
-
214
- const permissions = userData.permissionsSet ? Array.from(userData.permissionsSet) : [];
215
-
216
- return {
217
- id: userData.id,
218
- username: userData.username,
219
- isOwner: userData.isOwner,
220
- isBlacklisted: userData.isBlacklisted,
221
- permissions: permissions,
222
- groups: userData.groups,
223
- hasPermission: (permissionName) => {
224
- if (userData.isOwner) return true;
225
- if (!permissionName) return false;
226
-
227
- if (permissions.includes(permissionName)) {
228
- return true;
229
- }
230
-
231
- const permissionParts = permissionName.split('.');
232
- if (permissionParts.length > 1) {
233
- const domain = permissionParts[0];
234
- const wildcard = `${domain}.*`;
235
- if (permissions.includes(wildcard)) {
236
- return true;
237
- }
238
- }
239
-
240
- if (permissions.includes('*')) {
241
- return true;
242
- }
243
-
244
- return false;
245
- },
246
- hasGroup: (groupName) => userData.hasGroup(groupName),
247
- addGroup: (group) => bot.api.performUserAction(username, 'addGroup', { group }),
248
- removeGroup: (group) => bot.api.performUserAction(username, 'removeGroup', { group }),
249
- addPermission: (permission) => bot.api.performUserAction(username, 'addPermission', { permission }),
250
- removePermission: (permission) => bot.api.performUserAction(username, 'removePermission', { permission }),
251
- getGroups: () => bot.api.performUserAction(username, 'getGroups'),
252
- getPermissions: () => bot.api.performUserAction(username, 'getPermissions'),
253
- isBlacklisted: () => bot.api.performUserAction(username, 'isBlacklisted'),
254
- setBlacklisted: (value) => bot.api.performUserAction(username, 'setBlacklisted', { value }),
255
- };
211
+ return await UserService.getUser(username, bot.config.id, bot.config);
256
212
  },
257
213
  registerPermissions: (permissions) => PermissionManager.registerPermissions(bot.config.id, permissions),
258
214
  registerGroup: (groupConfig) => PermissionManager.registerGroup(bot.config.id, groupConfig),
@@ -464,6 +420,10 @@ process.on('message', async (message) => {
464
420
  messageHandledByCustomParser = false;
465
421
  const rawMessageText = jsonMsg.toString();
466
422
  bot.events.emit('core:raw_message', rawMessageText, jsonMsg);
423
+
424
+ sendEvent('raw_message', {
425
+ rawText: rawMessageText
426
+ });
467
427
  });
468
428
 
469
429
  bot.events.on('chat:message', (data) => {
@@ -145,6 +145,9 @@ class EventGraphManager {
145
145
  context.message = args.message;
146
146
  context.chat_type = args.chatType;
147
147
  break;
148
+ case 'raw_message':
149
+ context.rawText = args.rawText;
150
+ break;
148
151
  case 'playerJoined':
149
152
  case 'playerLeft':
150
153
  context.user = args.user;
@@ -379,7 +379,13 @@ class GraphExecutionEngine {
379
379
  else result = this.context[pinId];
380
380
  break;
381
381
  case 'event:chat':
382
- if (pinId === 'user') result = { username: this.context.username };
382
+ if (pinId === 'username') result = this.context.username;
383
+ else if (pinId === 'message') result = this.context.message;
384
+ else if (pinId === 'chatType') result = this.context.chat_type;
385
+ else result = this.context[pinId];
386
+ break;
387
+ case 'event:raw_message':
388
+ if (pinId === 'rawText') result = this.context.rawText;
383
389
  else result = this.context[pinId];
384
390
  break;
385
391
  case 'event:playerJoined':
@@ -129,7 +129,23 @@ class NodeRegistry {
129
129
  { id: 'username', type: 'String', name: 'Игрок' },
130
130
  { id: 'message', type: 'String', name: 'Сообщение' },
131
131
  { id: 'chatType', type: 'String', name: 'Тип чата' },
132
- { id: 'raw', type: 'String', name: 'Raw JSON' },
132
+ ]
133
+ }
134
+ });
135
+
136
+ this.registerNodeType({
137
+ type: 'event:raw_message',
138
+ name: 'Событие: Сырое сообщение',
139
+ label: '📝 Сырое сообщение',
140
+ description: 'Срабатывает при получении любого сообщения в сыром виде (до парсинга).',
141
+ category: 'События',
142
+ graphType: event,
143
+ isEvent: true,
144
+ pins: {
145
+ inputs: [],
146
+ outputs: [
147
+ { id: 'exec', type: 'Exec', name: 'Выполнить' },
148
+ { id: 'rawText', type: 'String', name: 'Сырой текст' },
133
149
  ]
134
150
  }
135
151
  });