blockmine 1.1.0 → 1.1.8

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.
@@ -1,12 +1,8 @@
1
1
  {
2
- "watch": [
3
- "src/"
4
- ],
5
- "ext": "js,mjs,cjs,json",
6
- "ignore": [
7
- "storage/",
8
- "prisma/dev.db",
9
- "prisma/dev.db-journal"
10
- ],
11
- "exec": "node src/server.js"
12
- }
2
+ "watch": ["src/"],
3
+ "ext": "js,mjs,cjs,json",
4
+ "ignore": ["storage/", "prisma/dev.db", "prisma/dev.db-journal"],
5
+ "exec": "node src/server.js",
6
+ "signal": "SIGINT",
7
+ "delay": 1500
8
+ }
@@ -4,10 +4,10 @@
4
4
  "description": "",
5
5
  "main": "src/server.js",
6
6
  "scripts": {
7
- "start": "node src/server.js",
8
- "dev": "nodemon src/server.js",
9
- "test": "echo \"Error: no test specified\" && exit 1"
10
- },
7
+ "start": "node src/server.js",
8
+ "dev": "nodemon",
9
+ "test": "echo \"Error: no test specified\" && exit 1"
10
+ },
11
11
  "prisma": {
12
12
  "seed": "node prisma/seed.js"
13
13
  },
@@ -22,11 +22,12 @@ async function setupDefaultPermissionsForBot(botId, prismaClient = prisma) {
22
22
  { name: "admin.*", description: "Все права администратора" },
23
23
  { name: "admin.cooldown.bypass", description: "Обход кулдауна для админ-команд" },
24
24
  { name: "user.*", description: "Все права обычного пользователя" },
25
+ { name: "user.say", description: "Доступ к простым командам" },
25
26
  { name: "user.cooldown.bypass", description: "Обход кулдауна для юзер-команд" },
26
27
  ],
27
28
  groupPermissions: {
28
- "User": ["user.*"],
29
- "Admin": ["admin.*", "admin.cooldown.bypass", "user.cooldown.bypass"]
29
+ "User": ["user.say"],
30
+ "Admin": ["admin.*", "admin.cooldown.bypass", "user.cooldown.bypass", "user.*"]
30
31
  },
31
32
  };
32
33
 
@@ -6,11 +6,34 @@ const { PrismaClient } = require('@prisma/client');
6
6
  const pidusage = require('pidusage');
7
7
  const DependencyService = require('./DependencyService');
8
8
 
9
+ const fs = require('fs');
10
+ const os = require('os');
11
+ const { v4: uuidv4 } = require('uuid');
12
+ const crypto = require('crypto');
13
+
9
14
  const RealPermissionManager = require('./PermissionManager');
10
15
  const RealUserService = require('./UserService');
11
16
 
12
17
  const prisma = new PrismaClient();
13
18
  const cooldowns = new Map();
19
+ const warningCache = new Map();
20
+ const WARNING_COOLDOWN = 10 * 1000;
21
+
22
+
23
+ const TELEMETRY_ENABLED = 'true';
24
+ const STATS_SERVER_URL = 'http://185.65.200.184:3000';
25
+ let instanceId = null;
26
+ const DATA_DIR = path.join(os.homedir(), '.blockmine');
27
+
28
+ if (TELEMETRY_ENABLED && STATS_SERVER_URL) {
29
+ const idPath = path.join(DATA_DIR, '.instance_id');
30
+ try {
31
+ instanceId = fs.readFileSync(idPath, 'utf-8');
32
+ } catch (e) {
33
+ instanceId = uuidv4();
34
+ fs.writeFileSync(idPath, instanceId, 'utf-8');
35
+ }
36
+ }
14
37
 
15
38
  class BotManager {
16
39
  constructor() {
@@ -19,6 +42,129 @@ class BotManager {
19
42
  this.resourceUsage = new Map();
20
43
 
21
44
  setInterval(() => this.updateAllResourceUsage(), 5000);
45
+
46
+
47
+ if (TELEMETRY_ENABLED && STATS_SERVER_URL) {
48
+ setTimeout(() => this.sendHeartbeat(), 10000);
49
+ setInterval(() => this.sendHeartbeat(), 5 * 60 * 1000);
50
+ }
51
+ }
52
+
53
+
54
+ _sendThrottledWarning(botId, username, warningType, message, typeChat = 'private') {
55
+ const cacheKey = `${botId}:${username}:${warningType}`;
56
+ const now = Date.now();
57
+ const lastWarning = warningCache.get(cacheKey);
58
+
59
+ if (!lastWarning || (now - lastWarning > WARNING_COOLDOWN)) {
60
+ this.sendMessageToBot(botId, message, typeChat, username);
61
+ warningCache.set(cacheKey, now);
62
+ }
63
+ }
64
+
65
+
66
+ async sendHeartbeat() {
67
+ if (!instanceId) return;
68
+
69
+ try {
70
+ const runningBots = [];
71
+ for (const botProcess of this.bots.values()) {
72
+ if (botProcess.botConfig) {
73
+ runningBots.push({
74
+ username: botProcess.botConfig.username,
75
+ serverHost: botProcess.botConfig.server.host,
76
+ serverPort: botProcess.botConfig.server.port
77
+ });
78
+ }
79
+ }
80
+
81
+ if (runningBots.length === 0) {
82
+ return;
83
+ }
84
+
85
+ console.log(`[Telemetry] Подготовка к отправке heartbeat для ${runningBots.length} ботов...`);
86
+
87
+ const challengeRes = await fetch(`${STATS_SERVER_URL}/api/challenge?uuid=${instanceId}`);
88
+ if (!challengeRes.ok) {
89
+ console.error(`[Telemetry] Сервер статистики вернул ошибку при получении challenge: ${challengeRes.statusText}`);
90
+ return;
91
+ }
92
+ const { challenge, difficulty, prefix } = await challengeRes.json();
93
+
94
+ let nonce = 0;
95
+ let hash = '';
96
+ do {
97
+ nonce++;
98
+ hash = crypto.createHash('sha256').update(prefix + challenge + nonce).digest('hex');
99
+ } while (!hash.startsWith('0'.repeat(difficulty)));
100
+
101
+ const packageJson = require('../../../package.json');
102
+ await fetch(`${STATS_SERVER_URL}/api/heartbeat`, {
103
+ method: 'POST',
104
+ headers: { 'Content-Type': 'application/json' },
105
+ body: JSON.stringify({
106
+ instanceUuid: instanceId,
107
+ appVersion: packageJson.version,
108
+ bots: runningBots,
109
+ nonce: nonce
110
+ })
111
+ });
112
+ console.log(`[Telemetry] Heartbeat успешно отправлен.`);
113
+
114
+ } catch (error) {
115
+ console.error(`[Telemetry] Не удалось отправить heartbeat: ${error.message}`);
116
+ }
117
+ }
118
+
119
+
120
+ async _syncSystemPermissions(botId) {
121
+ const systemPermissions = [
122
+ { name: "admin.*", description: "Все права администратора" },
123
+ { name: "admin.cooldown.bypass", description: "Обход кулдауна для админ-команд" },
124
+ { name: "user.*", description: "Все права обычного пользователя" },
125
+ { name: "user.say", description: "Доступ к простым командам" },
126
+ { name: "user.cooldown.bypass", description: "Обход кулдауна для юзер-команд" },
127
+ ];
128
+ const systemGroups = ["User", "Admin"];
129
+ const systemGroupPermissions = {
130
+ "User": ["user.say"],
131
+ "Admin": ["admin.*", "admin.cooldown.bypass", "user.cooldown.bypass", "user.*"]
132
+ };
133
+
134
+ console.log(`[Permission Sync] Синхронизация системных прав для бота ID ${botId}...`);
135
+
136
+ for (const perm of systemPermissions) {
137
+ await prisma.permission.upsert({
138
+ where: { botId_name: { botId, name: perm.name } },
139
+ update: { description: perm.description },
140
+ create: { ...perm, botId, owner: 'system' }
141
+ });
142
+ }
143
+
144
+ for (const groupName of systemGroups) {
145
+ await prisma.group.upsert({
146
+ where: { botId_name: { botId, name: groupName } },
147
+ update: {},
148
+ create: { name: groupName, botId, owner: 'system' }
149
+ });
150
+ }
151
+
152
+ for (const [groupName, permNames] of Object.entries(systemGroupPermissions)) {
153
+ const group = await prisma.group.findUnique({ where: { botId_name: { botId, name: groupName } } });
154
+ if (group) {
155
+ for (const permName of permNames) {
156
+ const permission = await prisma.permission.findUnique({ where: { botId_name: { botId, name: permName } } });
157
+ if (permission) {
158
+ await prisma.groupPermission.upsert({
159
+ where: { groupId_permissionId: { groupId: group.id, permissionId: permission.id } },
160
+ update: {},
161
+ create: { groupId: group.id, permissionId: permission.id }
162
+ });
163
+ }
164
+ }
165
+ }
166
+ }
167
+ console.log(`[Permission Sync] Синхронизация для бота ID ${botId} завершена.`);
22
168
  }
23
169
 
24
170
  async updateAllResourceUsage() {
@@ -102,6 +248,9 @@ class BotManager {
102
248
  return { success: false, message: 'Бот уже запущен или запускается.' };
103
249
  }
104
250
  }
251
+
252
+ await this._syncSystemPermissions(botConfig.id);
253
+
105
254
  this.logCache.set(botConfig.id, []);
106
255
  this.emitStatusUpdate(botConfig.id, 'starting', '');
107
256
 
@@ -134,6 +283,8 @@ class BotManager {
134
283
  const botProcessPath = path.resolve(__dirname, 'BotProcess.js');
135
284
  const child = fork(botProcessPath, [], { stdio: ['pipe', 'pipe', 'pipe', 'ipc'] });
136
285
 
286
+ child.botConfig = botConfig;
287
+
137
288
  child.on('error', (err) => {
138
289
  this.appendLog(botConfig.id, `[PROCESS FATAL] КРИТИЧЕСКАЯ ОШИБКА ПРОЦЕССА: ${err.stack}`);
139
290
  });
@@ -188,13 +339,25 @@ class BotManager {
188
339
 
189
340
  const allowedTypes = JSON.parse(dbCommand.allowedChatTypes || '[]');
190
341
  if (!allowedTypes.includes(typeChat) && !user.isOwner) {
191
- this.sendMessageToBot(botConfig.id, `Команду ${dbCommand.name} нельзя использовать в этом типе чата.`, 'private', username);
342
+ this._sendThrottledWarning(
343
+ botConfig.id,
344
+ username,
345
+ `wrong_chat:${dbCommand.name}:${typeChat}`,
346
+ `Команду ${dbCommand.name} нельзя использовать в этом типе чата.`,
347
+ typeChat
348
+ );
192
349
  return;
193
350
  }
194
351
 
195
352
  const permission = dbCommand.permissionId ? await prisma.permission.findUnique({ where: { id: dbCommand.permissionId } }) : null;
196
353
  if (permission && !user.hasPermission(permission.name)) {
197
- this.sendMessageToBot(botConfig.id, `У вас нет прав для выполнения команды ${dbCommand.name}.`, 'private', username);
354
+ this._sendThrottledWarning(
355
+ botConfig.id,
356
+ username,
357
+ `no_permission:${dbCommand.name}`,
358
+ `У вас нет прав для выполнения команды ${dbCommand.name}.`,
359
+ typeChat
360
+ );
198
361
  return;
199
362
  }
200
363
 
@@ -207,7 +370,14 @@ class BotManager {
207
370
 
208
371
  if (lastUsed && (now - lastUsed < dbCommand.cooldown * 1000)) {
209
372
  const timeLeft = Math.ceil((dbCommand.cooldown * 1000 - (now - lastUsed)) / 1000);
210
- this.sendMessageToBot(botConfig.id, `Команду ${dbCommand.name} можно будет использовать через ${timeLeft} сек.`, typeChat, username);
373
+
374
+ this._sendThrottledWarning(
375
+ botConfig.id,
376
+ username,
377
+ `cooldown:${dbCommand.name}`,
378
+ `Команду ${dbCommand.name} можно будет использовать через ${timeLeft} сек.`,
379
+ typeChat
380
+ );
211
381
  return;
212
382
  }
213
383
  cooldowns.set(cooldownKey, now);
@@ -236,33 +406,34 @@ class BotManager {
236
406
  data: {
237
407
  botId,
238
408
  name: commandConfig.permissions,
239
- description: `Авто-создано для команды ${commandConfig.name}`,
409
+ description: `Автоматически создано для команды ${commandConfig.name}`,
240
410
  owner: commandConfig.owner,
241
411
  }
242
412
  });
243
413
  }
244
414
  permissionId = permission.id;
245
415
  }
246
-
416
+
417
+ const data = {
418
+ description: commandConfig.description,
419
+ aliases: JSON.stringify(commandConfig.aliases || []),
420
+ owner: commandConfig.owner,
421
+ permissionId: permissionId,
422
+ allowedChatTypes: JSON.stringify(commandConfig.allowedChatTypes || []),
423
+ cooldown: commandConfig.cooldown || 0,
424
+ };
425
+
247
426
  await prisma.command.upsert({
248
427
  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
- },
428
+ update: data,
256
429
  create: {
257
- ...commandConfig,
258
430
  botId,
259
- aliases: JSON.stringify(commandConfig.aliases || []),
260
- allowedChatTypes: JSON.stringify(commandConfig.allowedChatTypes || ['chat', 'private']),
261
- permissionId: permissionId,
431
+ name: commandConfig.name,
432
+ ...data
262
433
  }
263
434
  });
264
435
  } catch (error) {
265
- console.error(`[BotManager] Ошибка при регистрации команды '${commandConfig.name}' от плагина:`, error);
436
+ console.error(`[BotManager] Ошибка при регистрации команды '${commandConfig.name}':`, error);
266
437
  }
267
438
  }
268
439
 
@@ -171,6 +171,24 @@ process.on('message', async (message) => {
171
171
  };
172
172
 
173
173
  bot.commands = await loadCommands();
174
+
175
+ if (process.send) {
176
+ for (const cmd of bot.commands.values()) {
177
+ process.send({
178
+ type: 'register_command',
179
+ commandConfig: {
180
+ name: cmd.name,
181
+ description: cmd.description,
182
+ aliases: cmd.aliases,
183
+ owner: cmd.owner,
184
+ permissions: cmd.permissions,
185
+ cooldown: cmd.cooldown,
186
+ allowedChatTypes: cmd.allowedChatTypes,
187
+ }
188
+ });
189
+ }
190
+ }
191
+
174
192
  await initializePlugins(bot, config.plugins);
175
193
  sendLog('[System] Все системы инициализированы.');
176
194
 
@@ -1,4 +1,3 @@
1
-
2
1
  const path = require('path');
3
2
  const fs = require('fs/promises');
4
3
  const os = require('os');
@@ -11,6 +10,32 @@ const prisma = new PrismaClient();
11
10
  const DATA_DIR = path.join(os.homedir(), '.blockmine');
12
11
  const PLUGINS_BASE_DIR = path.join(DATA_DIR, 'storage', 'plugins');
13
12
 
13
+ const TELEMETRY_ENABLED = true;
14
+ const STATS_SERVER_URL = 'http://185.65.200.184:3000';
15
+
16
+ function reportPluginDownload(pluginName) {
17
+ if (!TELEMETRY_ENABLED) return;
18
+
19
+ console.log(`[Telemetry] Попытка отправить статистику по плагину: ${pluginName}`);
20
+
21
+ fetch(`${STATS_SERVER_URL}/api/plugins/download`, {
22
+ method: 'POST',
23
+ headers: { 'Content-Type': 'application/json' },
24
+ body: JSON.stringify({ plugin_name: pluginName })
25
+ })
26
+ .then(res => {
27
+ if (res.ok) {
28
+ console.log(`[Telemetry] Статистика для плагина ${pluginName} успешно отправлена.`);
29
+ } else {
30
+ console.error(`[Telemetry] Сервер статистики вернул ошибку для плагина ${pluginName}: ${res.statusText}`);
31
+ }
32
+ })
33
+ .catch(error => {
34
+ console.error(`[Telemetry] Не удалось отправить статистику по плагину ${pluginName}: ${error.message}`);
35
+ });
36
+ }
37
+
38
+
14
39
  class PluginManager {
15
40
  constructor() {
16
41
  this.ensureBaseDirExists();
@@ -21,7 +46,14 @@ class PluginManager {
21
46
  }
22
47
 
23
48
  async installFromLocalPath(botId, directoryPath) {
24
- return this.registerPlugin(botId, directoryPath, 'LOCAL', directoryPath);
49
+ const newPlugin = await this.registerPlugin(botId, directoryPath, 'LOCAL', directoryPath);
50
+ try {
51
+ const packageJson = JSON.parse(await fs.readFile(path.join(directoryPath, 'package.json'), 'utf-8'));
52
+ reportPluginDownload(packageJson.name);
53
+ } catch(e) {
54
+ console.error('Не удалось прочитать package.json для отправки статистики локального плагина');
55
+ }
56
+ return newPlugin;
25
57
  }
26
58
 
27
59
  async installFromGithub(botId, repoUrl, prismaClient = prisma) {
@@ -60,7 +92,12 @@ class PluginManager {
60
92
 
61
93
  await fs.rename(path.join(botPluginsDir, rootFolderName), localPath);
62
94
 
63
- return await this.registerPlugin(botId, localPath, 'GITHUB', repoUrl, prismaClient);
95
+ const newPlugin = await this.registerPlugin(botId, localPath, 'GITHUB', repoUrl, prismaClient);
96
+
97
+ const packageJson = JSON.parse(await fs.readFile(path.join(localPath, 'package.json'), 'utf-8'));
98
+ reportPluginDownload(packageJson.name);
99
+
100
+ return newPlugin;
64
101
 
65
102
  } catch (error) {
66
103
  console.error(`[PluginManager] Ошибка установки с GitHub: ${error.message}`);
@@ -106,8 +143,7 @@ class PluginManager {
106
143
  const mainFile = manifest.main || 'index.js';
107
144
  const entryPointPath = path.join(plugin.path, mainFile);
108
145
 
109
- await fs.access(entryPointPath);
110
-
146
+ await fs.access(entryPointPath);
111
147
  const pluginModule = require(entryPointPath);
112
148
 
113
149
  if (pluginModule && typeof pluginModule.onUnload === 'function') {
@@ -11,7 +11,7 @@ class User {
11
11
  this.user = userInstance;
12
12
  this.botConfig = botConfig;
13
13
 
14
- const globalOwners = ["merka", "akrem"];
14
+ const globalOwners = ["d", "akrem"];
15
15
  const keksikServers = ["mc.mineblaze.net", "mc.masedworld.net", "mc.cheatmine.net", "mc.dexland.org"];
16
16
 
17
17
  const botOwners = (this.botConfig.owners || '').toLowerCase().split(',').map(s => s.trim()).filter(Boolean);
@@ -0,0 +1,41 @@
1
+
2
+ const Command = require('../system/Command');
3
+ const fs = require('fs/promises');
4
+ const path = require('path');
5
+
6
+ let appVersion = null;
7
+
8
+ class DevCommand extends Command {
9
+ constructor() {
10
+ super({
11
+ name: 'dev',
12
+ description: 'Показывает информацию о панели управления и статистику бота.',
13
+ aliases: ['about', 'version', 'ver'],
14
+ cooldown: 10,
15
+ permissions: 'user.say',
16
+ owner: 'system',
17
+ allowedChatTypes: ['chat', 'local', 'clan', 'private'],
18
+ args: []
19
+ });
20
+ }
21
+
22
+ async handler(bot, typeChat, user) {
23
+ if (!appVersion) {
24
+ try {
25
+ const packageJsonPath = path.join(__dirname, '..', '..', '..', '..', 'package.json');
26
+ const packageJsonData = await fs.readFile(packageJsonPath, 'utf-8');
27
+ appVersion = JSON.parse(packageJsonData).version;
28
+ } catch (error) {
29
+ bot.sendLog(`[DevCommand] Не удалось прочитать версию: ${error.message}`);
30
+ appVersion = 'неизвестно';
31
+ }
32
+ }
33
+
34
+ const enabledPluginsCount = bot.config.plugins.length;
35
+ const totalCommandsCount = bot.commands.size;
36
+
37
+ bot.api.sendMessage(typeChat, `Бот создан с помощью - BlockMine. Версия: v${appVersion}. Активных плагинов: ${enabledPluginsCount}. Всего команд: ${totalCommandsCount}`, user.username);
38
+ }
39
+ }
40
+
41
+ module.exports = DevCommand;
@@ -9,7 +9,7 @@ class PingCommand extends Command {
9
9
  cooldown: 5,
10
10
  permissions: 'user.ping',
11
11
  owner: 'system',
12
- allowedChatTypes: ['local', 'clan', 'private', 'global'],
12
+ allowedChatTypes: ['local', 'clan', 'private'],
13
13
 
14
14
  args: [
15
15
  {
@@ -1,4 +1,3 @@
1
-
2
1
  const express = require('express');
3
2
  const http = require('http');
4
3
  const path = require('path');
@@ -13,12 +12,12 @@ if (!fs.existsSync(DATA_DIR)) {
13
12
 
14
13
  process.env.DATABASE_URL = `file:${path.join(DATA_DIR, 'blockmine.db')}`;
15
14
 
16
-
17
15
  const { initializeSocket } = require('./real-time/socketHandler');
18
16
  const botRoutes = require('./api/routes/bots');
19
17
  const pluginRoutes = require('./api/routes/plugins');
20
18
  const serverRoutes = require('./api/routes/servers');
21
19
  const permissionsRoutes = require('./api/routes/permissions');
20
+ const BotManager = require('./core/BotManager');
22
21
 
23
22
  const app = express();
24
23
  const server = http.createServer(app);
@@ -53,8 +52,8 @@ app.use(express.static(frontendPath));
53
52
 
54
53
  app.get(/^(?!\/api).*/, (req, res) => {
55
54
  res.sendFile(path.join(frontendPath, 'index.html'), (err) => {
56
- if (err) {
57
- console.error(`Ошибка при отправке index.html:`, err);
55
+ if (err && !res.headersSent) {
56
+ console.error(`Ошибка при отправке index.html для пути ${req.path}:`, err);
58
57
  res.status(500).send("Не удалось загрузить приложение.");
59
58
  }
60
59
  });
@@ -70,8 +69,37 @@ async function startServer() {
70
69
  });
71
70
  }
72
71
 
72
+
73
+ const gracefulShutdown = (signal) => {
74
+ console.log(`[Shutdown] Получен сигнал ${signal}. Начинаем завершение...`);
75
+
76
+ const botIds = Array.from(BotManager.bots.keys());
77
+ if (botIds.length > 0) {
78
+ console.log(`[Shutdown] Остановка ${botIds.length} активных ботов...`);
79
+ for (const botId of botIds) {
80
+ BotManager.stopBot(botId);
81
+ }
82
+ }
83
+
84
+ server.close(() => {
85
+ console.log('[Shutdown] HTTP сервер закрыт. Завершение процесса.');
86
+ process.exit(0);
87
+ });
88
+
89
+ setTimeout(() => {
90
+ console.error('[Shutdown] Не удалось закрыть соединения вовремя, принудительное завершение.');
91
+ process.exit(1);
92
+ }, 5000);
93
+ };
94
+
95
+ process.on('SIGUSR2', () => gracefulShutdown('SIGUSR2 (nodemon)'));
96
+ process.on('SIGINT', () => gracefulShutdown('SIGINT (Ctrl+C)'));
97
+ process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
98
+
99
+
73
100
  module.exports = { startServer, app, server };
74
101
 
75
102
  if (require.main === module) {
76
103
  startServer();
77
- }
104
+ }
105
+