blockmine 1.0.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/README.md +107 -0
- package/backend/cli.js +40 -0
- package/backend/nodemon.json +12 -0
- package/backend/package-lock.json +2539 -0
- package/backend/package.json +34 -0
- package/backend/prisma/migrations/20250614085849_add_bot_note/migration.sql +126 -0
- package/backend/prisma/migrations/20250614153037_add_plugin_installed_date/migration.sql +27 -0
- package/backend/prisma/migrations/migration_lock.toml +3 -0
- package/backend/prisma/schema.prisma +138 -0
- package/backend/prisma/seed.js +60 -0
- package/backend/src/api/routes/bots.js +777 -0
- package/backend/src/api/routes/permissions.js +79 -0
- package/backend/src/api/routes/plugins.js +110 -0
- package/backend/src/api/routes/servers.js +50 -0
- package/backend/src/api/routes/settings.js +40 -0
- package/backend/src/core/BotManager.js +264 -0
- package/backend/src/core/BotProcess.js +233 -0
- package/backend/src/core/DependencyService.js +93 -0
- package/backend/src/core/MessageQueue.js +126 -0
- package/backend/src/core/PermissionManager.js +97 -0
- package/backend/src/core/PluginLoader.js +64 -0
- package/backend/src/core/PluginManager.js +161 -0
- package/backend/src/core/PluginService.js +57 -0
- package/backend/src/core/UserService.js +181 -0
- package/backend/src/core/commands/ping.js +35 -0
- package/backend/src/core/commands/warn.js +40 -0
- package/backend/src/core/ipc/PermissionManager.stub.js +24 -0
- package/backend/src/core/ipc/UserService.stub.js +31 -0
- package/backend/src/core/system/Command.js +53 -0
- package/backend/src/core/system/CommandHandler.js +98 -0
- package/backend/src/core/system/CommandManager.js +59 -0
- package/backend/src/core/system/CommandRegistry.js +21 -0
- package/backend/src/core/system/parseArguments.js +43 -0
- package/backend/src/real-time/socketHandler.js +31 -0
- package/backend/src/server.js +66 -0
- package/frontend/dist/apple-touch-icon.png +0 -0
- package/frontend/dist/assets/index-B83SHIXE.css +1 -0
- package/frontend/dist/assets/index-Dh-PcVh1.js +8179 -0
- package/frontend/dist/favicon-96x96.png +0 -0
- package/frontend/dist/favicon.ico +0 -0
- package/frontend/dist/favicon.svg +3 -0
- package/frontend/dist/index.html +51 -0
- package/frontend/dist/logo.png +0 -0
- package/frontend/dist/logo.svg +178 -0
- package/frontend/dist/monacoeditorwork/css.worker.bundle.js +53462 -0
- package/frontend/dist/monacoeditorwork/editor.worker.bundle.js +13519 -0
- package/frontend/dist/monacoeditorwork/html.worker.bundle.js +29662 -0
- package/frontend/dist/monacoeditorwork/json.worker.bundle.js +21320 -0
- package/frontend/dist/monacoeditorwork/ts.worker.bundle.js +256353 -0
- package/frontend/dist/site.webmanifest +21 -0
- package/frontend/dist/vite.svg +1 -0
- package/frontend/dist/web-app-manifest-192x192.png +0 -0
- package/frontend/dist/web-app-manifest-512x512.png +0 -0
- package/frontend/package.json +65 -0
- package/image/1.png +0 -0
- package/image/2.png +0 -0
- package/image/3.png +0 -0
- package/image/logo.png +0 -0
- package/package.json +27 -0
- package/tailwind.config.js +0 -0
- package/vite.config.js +0 -0
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
|
|
2
|
+
const mineflayer = require('mineflayer');
|
|
3
|
+
const { SocksClient } = require('socks');
|
|
4
|
+
const EventEmitter = require('events');
|
|
5
|
+
const { loadCommands } = require('./system/CommandRegistry');
|
|
6
|
+
const { initializePlugins } = require('./PluginLoader');
|
|
7
|
+
const MessageQueue = require('./MessageQueue');
|
|
8
|
+
const Command = require('./system/Command');
|
|
9
|
+
const { parseArguments } = require('./system/parseArguments');
|
|
10
|
+
|
|
11
|
+
const UserService = require('./ipc/UserService.stub.js');
|
|
12
|
+
const PermissionManager = require('./ipc/PermissionManager.stub.js');
|
|
13
|
+
|
|
14
|
+
let bot = null;
|
|
15
|
+
|
|
16
|
+
function sendLog(content) {
|
|
17
|
+
if (process.send) {
|
|
18
|
+
process.send({ type: 'log', content });
|
|
19
|
+
} else {
|
|
20
|
+
console.log(`[ChildProcess Log] ${content}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Анализирует входящее сообщение и отправляет запрос на валидацию команды в главный процесс.
|
|
26
|
+
*/
|
|
27
|
+
async function handleIncomingChat(type, username, message) {
|
|
28
|
+
if (!message.startsWith(bot.config.prefix || '@')) return;
|
|
29
|
+
|
|
30
|
+
const rawMessage = message.slice((bot.config.prefix || '@').length).trim();
|
|
31
|
+
const commandParts = rawMessage.split(/ +/);
|
|
32
|
+
const commandName = commandParts.shift().toLowerCase();
|
|
33
|
+
const restOfMessage = commandParts.join(' ');
|
|
34
|
+
|
|
35
|
+
const commandInstance = bot.commands.get(commandName) ||
|
|
36
|
+
Array.from(bot.commands.values()).find(cmd => cmd.aliases.includes(commandName));
|
|
37
|
+
|
|
38
|
+
if (!commandInstance) return;
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
const args = parseArguments(restOfMessage);
|
|
42
|
+
if (process.send) {
|
|
43
|
+
process.send({
|
|
44
|
+
type: 'validate_and_run_command',
|
|
45
|
+
commandName: commandInstance.name,
|
|
46
|
+
username,
|
|
47
|
+
args,
|
|
48
|
+
typeChat: type
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
} catch (e) {
|
|
52
|
+
sendLog(`[BotProcess] Ошибка парсинга аргументов: ${e.message}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
process.on('message', async (message) => {
|
|
57
|
+
if (message.type === 'start') {
|
|
58
|
+
const config = message.config;
|
|
59
|
+
sendLog(`[System] Получена команда на запуск бота ${config.username}...`);
|
|
60
|
+
try {
|
|
61
|
+
const botOptions = {
|
|
62
|
+
host: config.server.host,
|
|
63
|
+
port: config.server.port,
|
|
64
|
+
username: config.username,
|
|
65
|
+
password: config.password,
|
|
66
|
+
version: config.server.version,
|
|
67
|
+
auth: 'offline',
|
|
68
|
+
hideErrors: false,
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
if (config.proxyHost && config.proxyPort) {
|
|
72
|
+
sendLog(`[System] Используется прокси: ${config.proxyHost}:${config.proxyPort}`);
|
|
73
|
+
botOptions.connect = (client) => {
|
|
74
|
+
SocksClient.createConnection({
|
|
75
|
+
proxy: {
|
|
76
|
+
host: config.proxyHost,
|
|
77
|
+
port: config.proxyPort,
|
|
78
|
+
type: 5,
|
|
79
|
+
userId: config.proxyUsername,
|
|
80
|
+
password: config.proxyPassword
|
|
81
|
+
},
|
|
82
|
+
command: 'connect',
|
|
83
|
+
destination: {
|
|
84
|
+
host: config.server.host,
|
|
85
|
+
port: config.server.port
|
|
86
|
+
}
|
|
87
|
+
}).then(info => {
|
|
88
|
+
client.setSocket(info.socket);
|
|
89
|
+
client.emit('connect');
|
|
90
|
+
}).catch(err => {
|
|
91
|
+
sendLog(`[Proxy Error] ${err.message}`);
|
|
92
|
+
client.emit('error', err);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
} else {
|
|
96
|
+
sendLog(`[System] Прокси не настроен, используется прямое подключение.`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
bot = mineflayer.createBot(botOptions);
|
|
100
|
+
|
|
101
|
+
bot.events = new EventEmitter();
|
|
102
|
+
bot.events.setMaxListeners(30);
|
|
103
|
+
bot.config = config;
|
|
104
|
+
bot.sendLog = sendLog;
|
|
105
|
+
bot.messageQueue = new MessageQueue(bot);
|
|
106
|
+
|
|
107
|
+
const installedPluginNames = config.plugins.map(p => p.name);
|
|
108
|
+
|
|
109
|
+
bot.api = {
|
|
110
|
+
Command: Command,
|
|
111
|
+
events: bot.events,
|
|
112
|
+
sendMessage: (type, message, username) => bot.messageQueue.enqueue(type, message, username),
|
|
113
|
+
sendMessageAndWaitForReply: (command, patterns, timeout) => bot.messageQueue.enqueueAndWait(command, patterns, timeout),
|
|
114
|
+
getUser: (username) => UserService.getUser(username, bot.config.id, bot.config),
|
|
115
|
+
registerPermissions: (permissions) => PermissionManager.registerPermissions(bot.config.id, permissions),
|
|
116
|
+
registerGroup: (groupConfig) => PermissionManager.registerGroup(bot.config.id, groupConfig),
|
|
117
|
+
addPermissionsToGroup: (groupName, permissionNames) => PermissionManager.addPermissionsToGroup(bot.config.id, groupName, permissionNames),
|
|
118
|
+
installedPlugins: installedPluginNames,
|
|
119
|
+
registerCommand: (commandInstance) => {
|
|
120
|
+
if (!(commandInstance instanceof Command)) {
|
|
121
|
+
throw new Error('registerCommand ожидает экземпляр класса Command.');
|
|
122
|
+
}
|
|
123
|
+
bot.commands.set(commandInstance.name, commandInstance);
|
|
124
|
+
if (process.send) {
|
|
125
|
+
process.send({
|
|
126
|
+
type: 'register_command',
|
|
127
|
+
commandConfig: {
|
|
128
|
+
name: commandInstance.name,
|
|
129
|
+
description: commandInstance.description,
|
|
130
|
+
aliases: commandInstance.aliases,
|
|
131
|
+
owner: commandInstance.owner,
|
|
132
|
+
permissions: commandInstance.permissions,
|
|
133
|
+
cooldown: commandInstance.cooldown,
|
|
134
|
+
allowedChatTypes: commandInstance.allowedChatTypes,
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
sendLog(`[API] Команда "${commandInstance.name}" от плагина "${commandInstance.owner}" зарегистрирована в процессе.`);
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
bot.commands = await loadCommands();
|
|
143
|
+
await initializePlugins(bot, config.plugins);
|
|
144
|
+
sendLog('[System] Все системы инициализированы.');
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
let messageHandledByCustomParser = false;
|
|
148
|
+
|
|
149
|
+
bot.events.on('chat:message', (data) => {
|
|
150
|
+
if (data && data.username && data.message) {
|
|
151
|
+
messageHandledByCustomParser = true;
|
|
152
|
+
handleIncomingChat(data.type, data.username, data.message);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
bot.on('message', (jsonMsg) => {
|
|
157
|
+
const ansiMessage = jsonMsg.toAnsi();
|
|
158
|
+
if (ansiMessage.trim()) {
|
|
159
|
+
sendLog(ansiMessage);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
messageHandledByCustomParser = false;
|
|
163
|
+
const rawMessageText = jsonMsg.toString();
|
|
164
|
+
bot.events.emit('core:raw_message', rawMessageText, jsonMsg);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
bot.on('chat', (username, message) => {
|
|
168
|
+
if (messageHandledByCustomParser) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
handleIncomingChat('chat', username, message);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
bot.on('login', () => {
|
|
176
|
+
sendLog('[Event: login] Успешно залогинился!');
|
|
177
|
+
if (process.send) {
|
|
178
|
+
process.send({ type: 'status', status: 'running' });
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
bot.on('spawn', () => {
|
|
183
|
+
sendLog('[Event: spawn] Бот заспавнился в мире. Полностью готов к работе!');
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
bot.on('kicked', (reason) => {
|
|
187
|
+
let reasonText;
|
|
188
|
+
try { reasonText = JSON.parse(reason).text || reason; } catch (e) { reasonText = reason; }
|
|
189
|
+
sendLog(`[Event: kicked] Меня кикнули. Причина: ${reasonText}.`);
|
|
190
|
+
process.exit(0);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
bot.on('error', (err) => sendLog(`[Event: error] Произошла ошибка: ${err.stack || err.message}`));
|
|
194
|
+
|
|
195
|
+
bot.on('end', (reason) => {
|
|
196
|
+
sendLog(`[Event: end] Отключен от сервера. Причина: ${reason}`);
|
|
197
|
+
process.exit(0);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
} catch (err) {
|
|
201
|
+
sendLog(`[CRITICAL] Критическая ошибка при создании бота: ${err.stack}`);
|
|
202
|
+
process.exit(1);
|
|
203
|
+
}
|
|
204
|
+
} else if (message.type === 'stop') {
|
|
205
|
+
if (bot) bot.quit();
|
|
206
|
+
else process.exit(0);
|
|
207
|
+
} 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') {
|
|
213
|
+
const { commandName, username, args, typeChat } = message;
|
|
214
|
+
const commandInstance = bot.commands.get(commandName);
|
|
215
|
+
if (commandInstance) {
|
|
216
|
+
const fakeUser = { username };
|
|
217
|
+
commandInstance.handler(bot, typeChat, fakeUser, args).catch(e => {
|
|
218
|
+
sendLog(`[Handler Error] Ошибка в handler-е команды ${commandName}: ${e.message}`);
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
} else if (message.type === 'invalidate_user_cache') {
|
|
222
|
+
if (message.username && bot && bot.config) {
|
|
223
|
+
UserService.clearCache(message.username, bot.config.id);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
229
|
+
const errorMsg = `[FATAL] Необработанная ошибка процесса: ${reason?.stack || reason}`;
|
|
230
|
+
sendLog(errorMsg);
|
|
231
|
+
console.error(errorMsg, promise);
|
|
232
|
+
process.exit(1);
|
|
233
|
+
});
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
|
|
2
|
+
const semver = require('semver');
|
|
3
|
+
|
|
4
|
+
class DependencyService {
|
|
5
|
+
/**
|
|
6
|
+
* Анализирует плагины, разрешает зависимости и возвращает отсортированный список и информацию о проблемах.
|
|
7
|
+
* @param {Array} enabledPlugins - Массив включенных плагинов (записи из БД).
|
|
8
|
+
* @param {Array} allBotPlugins - Массив всех плагинов, установленных для бота.
|
|
9
|
+
* @returns {{sortedPlugins: Array, pluginInfo: Object, hasCriticalIssues: boolean}}
|
|
10
|
+
*/
|
|
11
|
+
resolveDependencies(enabledPlugins, allBotPlugins) {
|
|
12
|
+
const pluginMap = new Map(allBotPlugins.map(p => [p.name, p]));
|
|
13
|
+
const enabledPluginSet = new Set(enabledPlugins.map(p => p.name));
|
|
14
|
+
const pluginInfo = {};
|
|
15
|
+
let hasCriticalIssues = false;
|
|
16
|
+
|
|
17
|
+
for (const plugin of allBotPlugins) {
|
|
18
|
+
const manifest = plugin.manifest ? JSON.parse(plugin.manifest) : {};
|
|
19
|
+
pluginInfo[plugin.id] = {
|
|
20
|
+
...plugin,
|
|
21
|
+
isEnabled: enabledPluginSet.has(plugin.name),
|
|
22
|
+
issues: [],
|
|
23
|
+
dependencies: manifest.dependencies ? Object.entries(manifest.dependencies) : [],
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
for (const plugin of enabledPlugins) {
|
|
28
|
+
const info = pluginInfo[plugin.id];
|
|
29
|
+
|
|
30
|
+
for (const [depName, depVersion] of info.dependencies) {
|
|
31
|
+
const dependency = pluginMap.get(depName);
|
|
32
|
+
let issue = null;
|
|
33
|
+
|
|
34
|
+
if (!dependency) {
|
|
35
|
+
issue = { type: 'missing_dependency', message: `Требуемый плагин "${depName}" не найден.` };
|
|
36
|
+
hasCriticalIssues = true;
|
|
37
|
+
} else if (!enabledPluginSet.has(depName)) {
|
|
38
|
+
issue = { type: 'disabled_dependency', message: `Требуемый плагин "${depName}" отключен.` };
|
|
39
|
+
} else if (!semver.satisfies(dependency.version, depVersion)) {
|
|
40
|
+
issue = { type: 'version_mismatch', message: `Требуется версия "${depVersion}" для "${depName}", но найдена v${dependency.version}.` };
|
|
41
|
+
hasCriticalIssues = true;
|
|
42
|
+
}
|
|
43
|
+
if (issue) info.issues.push(issue);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const sorted = [];
|
|
48
|
+
const visited = new Set();
|
|
49
|
+
const visiting = new Set();
|
|
50
|
+
|
|
51
|
+
function visit(pluginName) {
|
|
52
|
+
if (visited.has(pluginName)) return;
|
|
53
|
+
if (visiting.has(pluginName)) {
|
|
54
|
+
hasCriticalIssues = true;
|
|
55
|
+
const cyclePath = Array.from(visiting).join(' -> ') + ` -> ${pluginName}`;
|
|
56
|
+
for (const p of enabledPlugins) {
|
|
57
|
+
if (visiting.has(p.name)) {
|
|
58
|
+
pluginInfo[p.id].issues.push({ type: 'circular_dependency', message: `Обнаружена циклическая зависимость: ${cyclePath}` });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
visiting.add(pluginName);
|
|
65
|
+
|
|
66
|
+
const plugin = pluginMap.get(pluginName);
|
|
67
|
+
if (plugin) {
|
|
68
|
+
const manifest = plugin.manifest ? JSON.parse(plugin.manifest) : {};
|
|
69
|
+
if (manifest.dependencies) {
|
|
70
|
+
for (const depName in manifest.dependencies) {
|
|
71
|
+
if (enabledPluginSet.has(depName)) {
|
|
72
|
+
visit(depName);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
visiting.delete(pluginName);
|
|
79
|
+
visited.add(pluginName);
|
|
80
|
+
if (plugin) {
|
|
81
|
+
sorted.push(plugin);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
for (const plugin of enabledPlugins) {
|
|
86
|
+
visit(plugin.name);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return { sortedPlugins: sorted, pluginInfo, hasCriticalIssues };
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
module.exports = new DependencyService();
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
class MessageQueue {
|
|
2
|
+
constructor(bot) {
|
|
3
|
+
this.bot = bot;
|
|
4
|
+
this.queue = [];
|
|
5
|
+
this.isProcessing = false;
|
|
6
|
+
|
|
7
|
+
this.lastGlobalSendTime = 0;
|
|
8
|
+
this.minGlobalDelay = 350;
|
|
9
|
+
|
|
10
|
+
this.lastSendTimes = {};
|
|
11
|
+
this.chatTypes = {
|
|
12
|
+
command: { prefix: '', delay: 400 },
|
|
13
|
+
chat: { prefix: '', delay: 1000 },
|
|
14
|
+
private: { prefix: '/msg ', delay: 1000 },
|
|
15
|
+
};
|
|
16
|
+
Object.keys(this.chatTypes).forEach(type => {
|
|
17
|
+
this.lastSendTimes[type] = 0;
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
registerChatType(typeName, config) {
|
|
22
|
+
this.chatTypes[typeName] = config;
|
|
23
|
+
this.lastSendTimes[typeName] = 0;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
_enqueue(task) {
|
|
27
|
+
this.queue.push(task);
|
|
28
|
+
if (!this.isProcessing) {
|
|
29
|
+
this._processNext();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
enqueue(chatType, message, username = null) {
|
|
34
|
+
const typeConfig = this.chatTypes[chatType];
|
|
35
|
+
if (!typeConfig) return;
|
|
36
|
+
this._enqueue({ type: 'simple', chatType, ...typeConfig, message, username });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
enqueueAndWait(command, patterns, timeout) {
|
|
40
|
+
return new Promise((resolve, reject) => {
|
|
41
|
+
const typeConfig = this.chatTypes['command'];
|
|
42
|
+
this._enqueue({
|
|
43
|
+
type: 'waitable', chatType: 'command', ...typeConfig,
|
|
44
|
+
message: command, patterns, timeout, resolve, reject,
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async _processNext() {
|
|
50
|
+
if (this.queue.length === 0) {
|
|
51
|
+
this.isProcessing = false;
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
this.isProcessing = true;
|
|
56
|
+
const task = this.queue.shift();
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
const now = Date.now();
|
|
60
|
+
|
|
61
|
+
const timeSinceGlobal = now - this.lastGlobalSendTime;
|
|
62
|
+
const globalDelayNeeded = this.minGlobalDelay - timeSinceGlobal;
|
|
63
|
+
|
|
64
|
+
const lastSent = this.lastSendTimes[task.chatType] || 0;
|
|
65
|
+
const timeSinceType = now - lastSent;
|
|
66
|
+
const typeDelayNeeded = (task.delay || 0) - timeSinceType;
|
|
67
|
+
|
|
68
|
+
const finalDelay = Math.max(globalDelayNeeded, typeDelayNeeded);
|
|
69
|
+
|
|
70
|
+
if (finalDelay > 0) {
|
|
71
|
+
await new Promise(resolve => setTimeout(resolve, finalDelay));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const sendTime = Date.now();
|
|
75
|
+
this.lastGlobalSendTime = sendTime;
|
|
76
|
+
this.lastSendTimes[task.chatType] = sendTime;
|
|
77
|
+
|
|
78
|
+
if (task.type === 'waitable') {
|
|
79
|
+
await this._handleWaitableTask(task);
|
|
80
|
+
} else {
|
|
81
|
+
let fullMessage;
|
|
82
|
+
if (task.chatType === 'private' && task.username) {
|
|
83
|
+
fullMessage = `/msg ${task.username} ${task.message}`;
|
|
84
|
+
} else if (task.chatType === 'clan') {
|
|
85
|
+
fullMessage = `/cc ${task.message}`;
|
|
86
|
+
} else if (task.chatType === 'global') {
|
|
87
|
+
fullMessage = `!${task.message}`;
|
|
88
|
+
} else {
|
|
89
|
+
fullMessage = task.message;
|
|
90
|
+
}
|
|
91
|
+
this.bot.chat(fullMessage);
|
|
92
|
+
}
|
|
93
|
+
} catch (error) {
|
|
94
|
+
if (task.type === 'waitable' && task.reject) task.reject(error);
|
|
95
|
+
} finally {
|
|
96
|
+
this._processNext();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
_handleWaitableTask(task) {
|
|
101
|
+
return new Promise((resolve, reject) => {
|
|
102
|
+
let timeoutId;
|
|
103
|
+
const listener = (rawMessageText) => {
|
|
104
|
+
for (const pattern of task.patterns) {
|
|
105
|
+
const match = rawMessageText.match(pattern);
|
|
106
|
+
if (match) {
|
|
107
|
+
this.bot.events.removeListener('core:raw_message', listener);
|
|
108
|
+
clearTimeout(timeoutId);
|
|
109
|
+
task.resolve(match);
|
|
110
|
+
resolve();
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
this.bot.events.on('core:raw_message', listener);
|
|
116
|
+
this.bot.chat(task.message);
|
|
117
|
+
timeoutId = setTimeout(() => {
|
|
118
|
+
this.bot.events.removeListener('core:raw_message', listener);
|
|
119
|
+
task.reject(new Error(`Timeout`));
|
|
120
|
+
resolve();
|
|
121
|
+
}, task.timeout);
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
module.exports = MessageQueue;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
const { PrismaClient } = require('@prisma/client');
|
|
2
|
+
const prisma = new PrismaClient();
|
|
3
|
+
|
|
4
|
+
class PermissionManager {
|
|
5
|
+
/**
|
|
6
|
+
* Регистрирует одно или несколько прав для КОНКРЕТНОГО бота.
|
|
7
|
+
* @param {number} botId - ID бота, для которого регистрируются права.
|
|
8
|
+
* @param {Array<object>} permissions - Массив объектов прав.
|
|
9
|
+
*/
|
|
10
|
+
async registerPermissions(botId, permissions) {
|
|
11
|
+
for (const perm of permissions) {
|
|
12
|
+
if (!perm.name || !perm.owner) {
|
|
13
|
+
console.warn('[PermissionManager] Пропущено право без имени или владельца:', perm);
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
await prisma.permission.upsert({
|
|
17
|
+
where: { botId_name: { botId, name: perm.name } },
|
|
18
|
+
update: { description: perm.description },
|
|
19
|
+
create: {
|
|
20
|
+
botId,
|
|
21
|
+
name: perm.name,
|
|
22
|
+
description: perm.description || '',
|
|
23
|
+
owner: perm.owner,
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Добавляет права к уже существующей группе.
|
|
32
|
+
* @param {number} botId - ID бота.
|
|
33
|
+
* @param {string} groupName - Имя группы, которую нужно обновить.
|
|
34
|
+
* @param {Array<string>} permissionNames - Массив имен прав для добавления.
|
|
35
|
+
*/
|
|
36
|
+
async addPermissionsToGroup(botId, groupName, permissionNames) {
|
|
37
|
+
const group = await prisma.group.findUnique({
|
|
38
|
+
where: { botId_name: { botId, name: groupName } }
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
if (!group) {
|
|
42
|
+
console.warn(`[PermissionManager] Попытка добавить права в несуществующую группу "${groupName}" для бота ID ${botId}.`);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
for (const permName of permissionNames) {
|
|
47
|
+
const permission = await prisma.permission.findUnique({
|
|
48
|
+
where: { botId_name: { botId, name: permName } }
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
if (permission) {
|
|
52
|
+
await prisma.groupPermission.upsert({
|
|
53
|
+
where: { groupId_permissionId: { groupId: group.id, permissionId: permission.id } },
|
|
54
|
+
update: {},
|
|
55
|
+
create: { groupId: group.id, permissionId: permission.id },
|
|
56
|
+
});
|
|
57
|
+
} else {
|
|
58
|
+
console.warn(`[PermissionManager] Право "${permName}" не найдено для бота ID ${botId} при добавлении в группу "${groupName}".`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Регистрирует группу для КОНКРЕТНОГО бота и привязывает к ней права.
|
|
65
|
+
* @param {number} botId - ID бота, для которого регистрируется группа.
|
|
66
|
+
* @param {object} groupConfig - Конфигурация группы.
|
|
67
|
+
*/
|
|
68
|
+
async registerGroup(botId, groupConfig) {
|
|
69
|
+
if (!groupConfig.name || !groupConfig.owner) {
|
|
70
|
+
console.warn('[PermissionManager] Пропущена группа без имени или владельца:', groupConfig);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const group = await prisma.group.upsert({
|
|
75
|
+
where: { botId_name: { botId, name: groupConfig.name } },
|
|
76
|
+
update: {},
|
|
77
|
+
create: { botId, name: groupConfig.name, owner: groupConfig.owner },
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
if (groupConfig.permissions && groupConfig.permissions.length > 0) {
|
|
81
|
+
for (const permName of groupConfig.permissions) {
|
|
82
|
+
const permission = await prisma.permission.findUnique({ where: { botId_name: { botId, name: permName } } });
|
|
83
|
+
if (permission) {
|
|
84
|
+
await prisma.groupPermission.upsert({
|
|
85
|
+
where: { groupId_permissionId: { groupId: group.id, permissionId: permission.id } },
|
|
86
|
+
update: {},
|
|
87
|
+
create: { groupId: group.id, permissionId: permission.id },
|
|
88
|
+
});
|
|
89
|
+
} else {
|
|
90
|
+
console.warn(`[PermissionManager] Право "${permName}" не найдено для бота ID ${botId} при привязке к группе "${groupConfig.name}".`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
module.exports = new PermissionManager();
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const fs = require('fs/promises');
|
|
4
|
+
|
|
5
|
+
async function initializePlugins(bot, installedPlugins = []) {
|
|
6
|
+
if (!installedPlugins || installedPlugins.length === 0) return;
|
|
7
|
+
|
|
8
|
+
const sendLog = bot.sendLog || console.log;
|
|
9
|
+
sendLog(`[PluginLoader] Загрузка ${installedPlugins.length} плагинов...`);
|
|
10
|
+
|
|
11
|
+
for (const plugin of installedPlugins) {
|
|
12
|
+
if (plugin && plugin.path) {
|
|
13
|
+
try {
|
|
14
|
+
const manifest = plugin.manifest ? JSON.parse(plugin.manifest) : {};
|
|
15
|
+
const savedSettings = plugin.settings ? JSON.parse(plugin.settings) : {};
|
|
16
|
+
const defaultSettings = {};
|
|
17
|
+
|
|
18
|
+
if (manifest.settings) {
|
|
19
|
+
for (const key in manifest.settings) {
|
|
20
|
+
const config = manifest.settings[key];
|
|
21
|
+
if (config.type === 'json_file' && config.defaultPath) {
|
|
22
|
+
const configFilePath = path.join(plugin.path, config.defaultPath);
|
|
23
|
+
try {
|
|
24
|
+
const fileContent = await fs.readFile(configFilePath, 'utf-8');
|
|
25
|
+
defaultSettings[key] = JSON.parse(fileContent);
|
|
26
|
+
} catch (e) {
|
|
27
|
+
sendLog(`[PluginLoader] WARN: Не удалось прочитать defaultPath '${config.defaultPath}' для плагина ${plugin.name}.`);
|
|
28
|
+
defaultSettings[key] = (config.type === 'string[]' || config.type === 'json_file') ? [] : {};
|
|
29
|
+
}
|
|
30
|
+
} else {
|
|
31
|
+
try {
|
|
32
|
+
defaultSettings[key] = JSON.parse(config.default || 'null');
|
|
33
|
+
} catch {
|
|
34
|
+
defaultSettings[key] = config.default;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const finalSettings = { ...defaultSettings, ...savedSettings };
|
|
41
|
+
|
|
42
|
+
const mainFile = manifest.main || 'index.js';
|
|
43
|
+
const entryPointPath = path.join(plugin.path, mainFile);
|
|
44
|
+
const normalizedPath = entryPointPath.replace(/\\/g, '/');
|
|
45
|
+
|
|
46
|
+
sendLog(`[PluginLoader] Загрузка: ${plugin.name} (v${plugin.version}) из ${normalizedPath}`);
|
|
47
|
+
const pluginModule = require(normalizedPath);
|
|
48
|
+
|
|
49
|
+
if (typeof pluginModule === 'function') {
|
|
50
|
+
pluginModule(bot, { settings: finalSettings });
|
|
51
|
+
} else if (pluginModule && typeof pluginModule.onLoad === 'function') {
|
|
52
|
+
pluginModule.onLoad(bot, { settings: finalSettings });
|
|
53
|
+
} else {
|
|
54
|
+
sendLog(`[PluginLoader] [ERROR] ${plugin.name} не экспортирует функцию или объект с методом onLoad.`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
} catch (error) {
|
|
58
|
+
sendLog(`[PluginLoader] [FATAL] Не удалось загрузить плагин ${plugin.name}: ${error.stack}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
module.exports = { initializePlugins };
|