blockmine 1.0.8 → 1.1.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/backend/cli.js CHANGED
@@ -1,26 +1,18 @@
1
-
1
+ #!/usr/bin/env node
2
2
  const fs = require('fs');
3
3
  const os = require('os');
4
4
  const path = require('path');
5
5
  const { execSync } = require('child_process');
6
6
  const { startServer } = require('./src/server.js');
7
7
 
8
-
9
8
  const DATA_DIR = path.join(os.homedir(), '.blockmine');
10
- if (!fs.existsSync(DATA_DIR)) {
11
- console.log(`[BlockMine] Создание папки для данных: ${DATA_DIR}`);
12
- fs.mkdirSync(DATA_DIR, { recursive: true });
13
- }
14
-
15
- process.env.DATABASE_URL = `file:${path.join(DATA_DIR, 'blockmine.db')}`;
16
-
17
9
  const prismaSchemaPath = path.join(__dirname, 'prisma', 'schema.prisma');
18
10
 
19
-
20
11
  function runCommand(command) {
21
12
  try {
22
13
  console.log(`> ${command}`);
23
- execSync(command, { stdio: 'inherit', cwd: __dirname });
14
+ const shell = process.platform === 'win32' ? 'cmd.exe' : '/bin/sh';
15
+ execSync(command, { stdio: 'inherit', cwd: __dirname, shell: shell });
24
16
  } catch (e) {
25
17
  console.error(`Команда "${command}" не удалась:`, e);
26
18
  process.exit(1);
@@ -29,12 +21,17 @@ function runCommand(command) {
29
21
 
30
22
  async function main() {
31
23
  console.log('Запуск панели управления BlockMine...');
32
- console.log(`[BlockMine] Хранилище данных: ${DATA_DIR}`);
33
24
 
34
- const migrationsPath = path.join(__dirname, 'prisma', 'migrations');
35
- if (!fs.existsSync(path.join(DATA_DIR, 'blockmine.db'))) {
25
+ const dbPath = path.join(DATA_DIR, 'blockmine.db');
26
+
27
+ if (!fs.existsSync(dbPath)) {
36
28
  console.log('База данных не найдена. Создаем и применяем все миграции...');
37
29
  runCommand(`npx prisma migrate deploy --schema=${prismaSchemaPath}`);
30
+
31
+ console.log('Заполнение базы данных начальными данными (серверами)...');
32
+ const seedScriptPath = path.join(__dirname, 'prisma', 'seed.js');
33
+ runCommand(`node ${seedScriptPath}`);
34
+
38
35
  console.log('Первоначальная настройка базы данных завершена.');
39
36
  } else {
40
37
  console.log('Проверка и применение обновлений базы данных...');
@@ -55,7 +55,10 @@ router.get('/', async (req, res) => {
55
55
  try {
56
56
  const bots = await prisma.bot.findMany({ include: { server: true }, orderBy: { createdAt: 'asc' } });
57
57
  res.json(bots);
58
- } catch (error) { res.status(500).json({ error: 'Не удалось получить список ботов' }); }
58
+ } catch (error) {
59
+ console.error("[API /api/bots] Ошибка получения списка ботов:", error);
60
+ res.status(500).json({ error: 'Не удалось получить список ботов' });
61
+ }
59
62
  });
60
63
 
61
64
  router.get('/state', (req, res) => {
@@ -8,6 +8,7 @@ router.get('/', async (req, res) => {
8
8
  const servers = await prisma.server.findMany({ orderBy: { name: 'asc' } });
9
9
  res.json(servers);
10
10
  } catch (error) {
11
+ console.error("[API /api/servers] Ошибка получения списка серверов:", error);
11
12
  res.status(500).json({ error: 'Не удалось получить список серверов' });
12
13
  }
13
14
  });
@@ -111,27 +111,20 @@ class BotManager {
111
111
 
112
112
  const enabledPlugins = allPluginsForBot.filter(p => p.isEnabled);
113
113
 
114
- const { sortedPlugins, pluginInfo } = DependencyService.resolveDependencies(enabledPlugins, allPluginsForBot);
114
+ const { sortedPlugins, pluginInfo, hasCriticalIssues } = DependencyService.resolveDependencies(enabledPlugins, allPluginsForBot);
115
115
 
116
- let canStart = true;
117
- if (Object.values(pluginInfo).some(info => info.issues.length > 0)) {
118
- this.appendLog(botConfig.id, '[DependencyManager] Обнаружены проблемы с зависимостями:');
116
+ if (hasCriticalIssues) {
117
+ this.appendLog(botConfig.id, '[DependencyManager] Обнаружены критические проблемы с зависимостями:');
119
118
  for (const plugin of Object.values(pluginInfo)) {
120
119
  if (plugin.issues.length > 0) {
121
120
  this.appendLog(botConfig.id, ` - Плагин "${plugin.name}":`);
122
121
  for (const issue of plugin.issues) {
123
122
  const logMessage = ` - [${issue.type.toUpperCase()}] ${issue.message}`;
124
123
  this.appendLog(botConfig.id, logMessage);
125
- if (['missing_dependency', 'version_mismatch', 'circular_dependency'].includes(issue.type)) {
126
- canStart = false;
127
- }
128
124
  }
129
125
  }
130
126
  }
131
- }
132
-
133
- if (!canStart) {
134
- this.appendLog(botConfig.id, '[DependencyManager] [FATAL] Запуск отменен из-за критических ошибок в зависимостях.');
127
+ this.appendLog(botConfig.id, '[DependencyManager] [FATAL] Запуск отменен.');
135
128
  this.emitStatusUpdate(botConfig.id, 'stopped', 'Ошибка зависимостей плагинов.');
136
129
  return { success: false, message: 'Критические ошибки в зависимостях плагинов.' };
137
130
  }
@@ -156,6 +149,8 @@ class BotManager {
156
149
  this.emitStatusUpdate(botConfig.id, message.status);
157
150
  } else if (message.type === 'validate_and_run_command') {
158
151
  await this.handleCommandValidation(botConfig, message);
152
+ } else if (message.type === 'register_command') {
153
+ await this.handleCommandRegistration(botConfig.id, message.commandConfig);
159
154
  }
160
155
  });
161
156
 
@@ -180,41 +175,39 @@ class BotManager {
180
175
  try {
181
176
  const user = await RealUserService.getUser(username, botConfig.id, botConfig);
182
177
 
183
- if (user.isBlacklisted) {
184
- return;
185
- }
178
+ if (user.isBlacklisted) return;
186
179
 
187
- const dbCommand = await prisma.command.findUnique({ where: { botId_name: { botId: botConfig.id, name: commandName } } });
180
+ const dbCommand = await prisma.command.findFirst({
181
+ where: {
182
+ botId: botConfig.id,
183
+ OR: [ { name: commandName }, { aliases: { contains: `"${commandName}"` } } ]
184
+ }
185
+ });
188
186
 
189
- if (!dbCommand || (!dbCommand.isEnabled && !user.isOwner)) {
190
- return;
191
- }
187
+ if (!dbCommand || (!dbCommand.isEnabled && !user.isOwner)) return;
192
188
 
193
189
  const allowedTypes = JSON.parse(dbCommand.allowedChatTypes || '[]');
194
190
  if (!allowedTypes.includes(typeChat) && !user.isOwner) {
195
- const replyMessage = `Команду ${commandName} нельзя использовать в этом типе чата.`;
196
- this.sendMessageToBot(botConfig.id, replyMessage, 'private', username);
191
+ this.sendMessageToBot(botConfig.id, `Команду ${dbCommand.name} нельзя использовать в этом типе чата.`, 'private', username);
197
192
  return;
198
193
  }
199
194
 
200
195
  const permission = dbCommand.permissionId ? await prisma.permission.findUnique({ where: { id: dbCommand.permissionId } }) : null;
201
196
  if (permission && !user.hasPermission(permission.name)) {
202
- const replyMessage = `У вас нет прав для выполнения команды ${commandName}.`;
203
- this.sendMessageToBot(botConfig.id, replyMessage, typeChat, username);
197
+ this.sendMessageToBot(botConfig.id, `У вас нет прав для выполнения команды ${dbCommand.name}.`, 'private', username);
204
198
  return;
205
199
  }
206
200
 
207
201
  const domain = (permission?.name || '').split('.')[0] || 'user';
208
202
  const bypassCooldownPermission = `${domain}.cooldown.bypass`;
209
203
  if (dbCommand.cooldown > 0 && !user.isOwner && !user.hasPermission(bypassCooldownPermission)) {
210
- const cooldownKey = `${botConfig.id}:${commandName}:${user.id}`;
204
+ const cooldownKey = `${botConfig.id}:${dbCommand.name}:${user.id}`;
211
205
  const now = Date.now();
212
206
  const lastUsed = cooldowns.get(cooldownKey);
213
207
 
214
208
  if (lastUsed && (now - lastUsed < dbCommand.cooldown * 1000)) {
215
209
  const timeLeft = Math.ceil((dbCommand.cooldown * 1000 - (now - lastUsed)) / 1000);
216
- const replyMessage = `Команду ${commandName} можно будет использовать через ${timeLeft} сек.`;
217
- this.sendMessageToBot(botConfig.id, replyMessage, typeChat, username);
210
+ this.sendMessageToBot(botConfig.id, `Команду ${dbCommand.name} можно будет использовать через ${timeLeft} сек.`, typeChat, username);
218
211
  return;
219
212
  }
220
213
  cooldowns.set(cooldownKey, now);
@@ -222,13 +215,54 @@ class BotManager {
222
215
 
223
216
  const child = this.bots.get(botConfig.id);
224
217
  if (child) {
225
- child.send({ type: 'execute_handler', commandName, username, args, typeChat });
218
+ child.send({ type: 'execute_handler', commandName: dbCommand.name, username, args, typeChat });
226
219
  }
227
220
 
228
221
  } catch (error) {
229
222
  console.error(`[BotManager] Ошибка валидации команды ${commandName}:`, error);
230
- const replyMessage = `Произошла внутренняя ошибка при выполнении команды.`;
231
- this.sendMessageToBot(botConfig.id, replyMessage, 'private', username);
223
+ this.sendMessageToBot(botConfig.id, `Произошла внутренняя ошибка при выполнении команды.`, 'private', username);
224
+ }
225
+ }
226
+
227
+ async handleCommandRegistration(botId, commandConfig) {
228
+ try {
229
+ let permissionId = null;
230
+ if (commandConfig.permissions) {
231
+ let permission = await prisma.permission.findUnique({
232
+ where: { botId_name: { botId, name: commandConfig.permissions } }
233
+ });
234
+ if (!permission) {
235
+ permission = await prisma.permission.create({
236
+ data: {
237
+ botId,
238
+ name: commandConfig.permissions,
239
+ description: `Авто-создано для команды ${commandConfig.name}`,
240
+ owner: commandConfig.owner,
241
+ }
242
+ });
243
+ }
244
+ permissionId = permission.id;
245
+ }
246
+
247
+ await prisma.command.upsert({
248
+ where: { botId_name: { botId, name: commandConfig.name } },
249
+ update: {
250
+ description: commandConfig.description,
251
+ aliases: JSON.stringify(commandConfig.aliases || []),
252
+ owner: commandConfig.owner,
253
+ permissionId: permissionId,
254
+ allowedChatTypes: JSON.stringify(commandConfig.allowedChatTypes || ['chat', 'private']),
255
+ },
256
+ create: {
257
+ ...commandConfig,
258
+ botId,
259
+ aliases: JSON.stringify(commandConfig.aliases || []),
260
+ allowedChatTypes: JSON.stringify(commandConfig.allowedChatTypes || ['chat', 'private']),
261
+ permissionId: permissionId,
262
+ }
263
+ });
264
+ } catch (error) {
265
+ console.error(`[BotManager] Ошибка при регистрации команды '${commandConfig.name}' от плагина:`, error);
232
266
  }
233
267
  }
234
268
 
@@ -244,7 +278,6 @@ class BotManager {
244
278
  sendMessageToBot(botId, message, chatType = 'command', username = null) {
245
279
  const child = this.bots.get(botId);
246
280
  if (child) {
247
- // Отправляем объект с полной информацией
248
281
  child.send({ type: 'chat', payload: { message, chatType, username } });
249
282
  return { success: true };
250
283
  }
@@ -21,10 +21,7 @@ function sendLog(content) {
21
21
  }
22
22
  }
23
23
 
24
- /**
25
- * Анализирует входящее сообщение и отправляет запрос на валидацию команды в главный процесс.
26
- */
27
- async function handleIncomingChat(type, username, message) {
24
+ function handleIncomingCommand(type, username, message) {
28
25
  if (!message.startsWith(bot.config.prefix || '@')) return;
29
26
 
30
27
  const rawMessage = message.slice((bot.config.prefix || '@').length).trim();
@@ -38,13 +35,47 @@ async function handleIncomingChat(type, username, message) {
38
35
  if (!commandInstance) return;
39
36
 
40
37
  try {
41
- const args = parseArguments(restOfMessage);
38
+ const processedArgs = {};
39
+ const parsedArgs = parseArguments(restOfMessage);
40
+ let currentArgIndex = 0;
41
+
42
+ for (const argDef of commandInstance.args) {
43
+ if (argDef.type === 'greedy_string') {
44
+ if (currentArgIndex < parsedArgs.length) {
45
+ processedArgs[argDef.name] = parsedArgs.slice(currentArgIndex).join(' ');
46
+ currentArgIndex = parsedArgs.length;
47
+ }
48
+ } else if (currentArgIndex < parsedArgs.length) {
49
+ let value = parsedArgs[currentArgIndex];
50
+ if (argDef.type === 'number') {
51
+ const numValue = parseFloat(value);
52
+ if (isNaN(numValue)) {
53
+ bot.api.sendMessage(type, `Ошибка: Неверный тип аргумента "${argDef.description}". Ожидалось число.`, username);
54
+ return;
55
+ }
56
+ value = numValue;
57
+ }
58
+ processedArgs[argDef.name] = value;
59
+ currentArgIndex++;
60
+ }
61
+
62
+ if (processedArgs[argDef.name] === undefined) {
63
+ if (argDef.required) {
64
+ bot.api.sendMessage(type, `Ошибка: Необходимо указать: ${argDef.description}`, username);
65
+ return;
66
+ }
67
+ if (argDef.default !== undefined) {
68
+ processedArgs[argDef.name] = argDef.default;
69
+ }
70
+ }
71
+ }
72
+
42
73
  if (process.send) {
43
74
  process.send({
44
75
  type: 'validate_and_run_command',
45
76
  commandName: commandInstance.name,
46
77
  username,
47
- args,
78
+ args: processedArgs,
48
79
  typeChat: type
49
80
  });
50
81
  }
@@ -147,12 +178,13 @@ process.on('message', async (message) => {
147
178
  let messageHandledByCustomParser = false;
148
179
 
149
180
  bot.events.on('chat:message', (data) => {
150
- if (data && data.username && data.message) {
181
+ const { type, username, message } = data;
182
+ if (username && message) {
151
183
  messageHandledByCustomParser = true;
152
- handleIncomingChat(data.type, data.username, data.message);
184
+ handleIncomingCommand(type, username, message);
153
185
  }
154
186
  });
155
-
187
+
156
188
  bot.on('message', (jsonMsg) => {
157
189
  const ansiMessage = jsonMsg.toAnsi();
158
190
  if (ansiMessage.trim()) {
@@ -168,8 +200,7 @@ process.on('message', async (message) => {
168
200
  if (messageHandledByCustomParser) {
169
201
  return;
170
202
  }
171
-
172
- handleIncomingChat('chat', username, message);
203
+ handleIncomingCommand('chat', username, message);
173
204
  });
174
205
 
175
206
  bot.on('login', () => {
@@ -205,11 +236,11 @@ process.on('message', async (message) => {
205
236
  if (bot) bot.quit();
206
237
  else process.exit(0);
207
238
  } else if (message.type === 'chat') {
208
- if (bot && bot.entity) {
209
- const { message: msg, chatType, username } = message.payload;
210
- bot.messageQueue.enqueue(chatType, msg, username);
211
- }
212
- } else if (message.type === 'execute_handler') {
239
+ if (bot && bot.entity) {
240
+ const { message: msg, chatType, username } = message.payload;
241
+ bot.messageQueue.enqueue(chatType, msg, username);
242
+ }
243
+ } else if (message.type === 'execute_handler') {
213
244
  const { commandName, username, args, typeChat } = message;
214
245
  const commandInstance = bot.commands.get(commandName);
215
246
  if (commandInstance) {
@@ -2,7 +2,18 @@
2
2
  const express = require('express');
3
3
  const http = require('http');
4
4
  const path = require('path');
5
- const fs = require('fs/promises');
5
+ const fs = require('fs');
6
+ const os = require('os');
7
+
8
+ const DATA_DIR = path.join(os.homedir(), '.blockmine');
9
+ if (!fs.existsSync(DATA_DIR)) {
10
+ console.log(`[Server] Создание папки для данных: ${DATA_DIR}`);
11
+ fs.mkdirSync(DATA_DIR, { recursive: true });
12
+ }
13
+
14
+ process.env.DATABASE_URL = `file:${path.join(DATA_DIR, 'blockmine.db')}`;
15
+
16
+
6
17
  const { initializeSocket } = require('./real-time/socketHandler');
7
18
  const botRoutes = require('./api/routes/bots');
8
19
  const pluginRoutes = require('./api/routes/plugins');
@@ -24,7 +35,7 @@ const rootPath = path.join(__dirname, '..', '..');
24
35
  app.get('/api/version', async (req, res) => {
25
36
  try {
26
37
  const packageJsonPath = path.join(rootPath, 'package.json');
27
- const packageJsonData = await fs.readFile(packageJsonPath, 'utf-8');
38
+ const packageJsonData = await fs.promises.readFile(packageJsonPath, 'utf-8');
28
39
  const { version } = JSON.parse(packageJsonData);
29
40
  res.json({ version });
30
41
  } catch (error) {
@@ -38,7 +49,6 @@ app.use('/api/plugins', pluginRoutes);
38
49
  app.use('/api/servers', serverRoutes);
39
50
  app.use('/api/permissions', permissionsRoutes);
40
51
 
41
-
42
52
  app.use(express.static(frontendPath));
43
53
 
44
54
  app.get(/^(?!\/api).*/, (req, res) => {
@@ -50,7 +60,6 @@ app.get(/^(?!\/api).*/, (req, res) => {
50
60
  });
51
61
  });
52
62
 
53
-
54
63
  async function startServer() {
55
64
  return new Promise((resolve) => {
56
65
  server.listen(PORT, () => {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
 
2
2
  {
3
3
  "name": "blockmine",
4
- "version": "1.0.8",
4
+ "version": "1.1.0",
5
5
  "description": "Мощная панель управления ботами для Майнкрафта.",
6
6
  "author": "merka",
7
7
  "license": "MIT",
@@ -1,98 +0,0 @@
1
- const { PrismaClient } = require('@prisma/client');
2
- const prisma = new PrismaClient();
3
- const User = require('../UserService');
4
- const { parseArguments } = require('./parseArguments');
5
-
6
- const cooldowns = new Map();
7
-
8
- async function commandHandler(bot, typeChat, username, message) {
9
- if (!message.startsWith(bot.config.prefix || '@')) return;
10
-
11
- const rawMessage = message.slice((bot.config.prefix || '@').length).trim();
12
-
13
- const commandParts = rawMessage.split(/ +/);
14
- const commandName = commandParts.shift().toLowerCase();
15
- const restOfMessage = commandParts.join(' ');
16
-
17
- const commandInstance = bot.commands.get(commandName);
18
-
19
- if (!commandInstance) return;
20
-
21
- try {
22
- const user = await User.getUser(username, bot.config.id, bot.config);
23
-
24
- if (user.isBlacklisted) {
25
- return commandInstance.onBlacklisted(bot, typeChat, user);
26
- }
27
-
28
- const dbCommand = await prisma.command.findUnique({ where: { botId_name: { botId: bot.config.id, name: commandInstance.name } } });
29
-
30
- if (!dbCommand || (!dbCommand.isEnabled && !user.isOwner)) {
31
- return;
32
- }
33
-
34
- const allowedTypes = JSON.parse(dbCommand.allowedChatTypes || '[]');
35
- if (!allowedTypes.includes(typeChat) && !user.isOwner) {
36
- return commandInstance.onWrongChatType(bot, typeChat, user);
37
- }
38
-
39
- if (!user.hasPermission(commandInstance.permissions)) {
40
- return commandInstance.onInsufficientPermissions(bot, typeChat, user);
41
- }
42
-
43
- const processedArgs = {};
44
- const parsedArgs = parseArguments(restOfMessage);
45
- let currentArgIndex = 0;
46
-
47
- for (const argDef of commandInstance.args) {
48
- if (argDef.type === 'greedy_string') {
49
- if (currentArgIndex < parsedArgs.length) {
50
- processedArgs[argDef.name] = parsedArgs.slice(currentArgIndex).join(' ');
51
- currentArgIndex = parsedArgs.length;
52
- }
53
- } else if (currentArgIndex < parsedArgs.length) {
54
- let value = parsedArgs[currentArgIndex];
55
- if (argDef.type === 'number') {
56
- const numValue = parseFloat(value);
57
- if (isNaN(numValue)) {
58
- return commandInstance.onInvalidArguments(bot, typeChat, user, { message: `Неверный тип аргумента "${argDef.description}". Ожидалось число.` });
59
- }
60
- value = numValue;
61
- }
62
- processedArgs[argDef.name] = value;
63
- currentArgIndex++;
64
- }
65
-
66
- if (processedArgs[argDef.name] === undefined) {
67
- if (argDef.required) {
68
- return commandInstance.onInvalidArguments(bot, typeChat, user, { message: `Необходимо указать: ${argDef.description}` });
69
- }
70
- if (argDef.default !== undefined) {
71
- processedArgs[argDef.name] = argDef.default;
72
- }
73
- }
74
- }
75
-
76
- const domain = (commandInstance.permissions || '').split('.')[0] || 'user';
77
- const bypassCooldownPermission = `${domain}.cooldown.bypass`;
78
- if (dbCommand.cooldown > 0 && !user.isOwner && !user.hasPermission(bypassCooldownPermission)) {
79
- const cooldownKey = `${bot.config.id}:${commandName}:${user.id}`;
80
- const now = Date.now();
81
- const lastUsed = cooldowns.get(cooldownKey);
82
-
83
- if (lastUsed && (now - lastUsed < dbCommand.cooldown * 1000)) {
84
- const timeLeft = Math.ceil((dbCommand.cooldown * 1000 - (now - lastUsed)) / 1000);
85
- return commandInstance.onCooldown(bot, typeChat, user, timeLeft);
86
- }
87
- cooldowns.set(cooldownKey, now);
88
- }
89
-
90
- await commandInstance.handler(bot, typeChat, user, processedArgs);
91
-
92
- } catch (error) {
93
- console.error(`[CommandHandler] Ошибка выполнения команды ${commandName}:`, error);
94
- bot.api.sendMessage('private', `Произошла ошибка при выполнении команды.`, username);
95
- }
96
- }
97
-
98
- module.exports = { commandHandler };