blockmine 1.22.0 → 1.23.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/.claude/settings.json +5 -1
- package/.claude/settings.local.json +10 -1
- package/CHANGELOG.md +27 -3
- package/CLAUDE.md +284 -0
- package/README.md +302 -152
- package/backend/package-lock.json +681 -9
- package/backend/package.json +8 -0
- package/backend/prisma/migrations/20251116111851_add_execution_trace/migration.sql +22 -0
- package/backend/prisma/migrations/20251120154914_add_panel_api_keys/migration.sql +21 -0
- package/backend/prisma/migrations/20251121110241_add_proxy_table/migration.sql +45 -0
- package/backend/prisma/migrations/migration_lock.toml +2 -2
- package/backend/prisma/schema.prisma +70 -1
- package/backend/src/__tests__/services/BotLifecycleService.test.js +9 -4
- package/backend/src/ai/plugin-assistant-system-prompt.md +788 -0
- package/backend/src/api/middleware/auth.js +27 -0
- package/backend/src/api/middleware/botAccess.js +7 -3
- package/backend/src/api/middleware/panelApiAuth.js +135 -0
- package/backend/src/api/routes/aiAssistant.js +995 -0
- package/backend/src/api/routes/auth.js +669 -633
- package/backend/src/api/routes/botCommands.js +107 -0
- package/backend/src/api/routes/botGroups.js +165 -0
- package/backend/src/api/routes/botHistory.js +108 -0
- package/backend/src/api/routes/botPermissions.js +99 -0
- package/backend/src/api/routes/botStatus.js +36 -0
- package/backend/src/api/routes/botUsers.js +162 -0
- package/backend/src/api/routes/bots.js +2451 -2402
- package/backend/src/api/routes/eventGraphs.js +4 -1
- package/backend/src/api/routes/logs.js +13 -3
- package/backend/src/api/routes/panel.js +66 -66
- package/backend/src/api/routes/panelApiKeys.js +179 -0
- package/backend/src/api/routes/pluginIde.js +1715 -135
- package/backend/src/api/routes/plugins.js +376 -219
- package/backend/src/api/routes/proxies.js +130 -0
- package/backend/src/api/routes/search.js +4 -0
- package/backend/src/api/routes/servers.js +20 -3
- package/backend/src/api/routes/settings.js +5 -0
- package/backend/src/api/routes/system.js +174 -174
- package/backend/src/api/routes/traces.js +131 -0
- package/backend/src/config/debug.config.js +36 -0
- package/backend/src/core/BotHistoryStore.js +180 -0
- package/backend/src/core/BotManager.js +14 -4
- package/backend/src/core/BotProcess.js +1517 -1092
- package/backend/src/core/EventGraphManager.js +37 -123
- package/backend/src/core/GraphExecutionEngine.js +977 -321
- package/backend/src/core/MessageQueue.js +12 -6
- package/backend/src/core/PluginLoader.js +99 -5
- package/backend/src/core/PluginManager.js +74 -13
- package/backend/src/core/TaskScheduler.js +1 -1
- package/backend/src/core/commands/whois.js +1 -1
- package/backend/src/core/node-registries/actions.js +70 -0
- package/backend/src/core/node-registries/arrays.js +18 -0
- package/backend/src/core/node-registries/data.js +1 -1
- package/backend/src/core/node-registries/events.js +14 -0
- package/backend/src/core/node-registries/logic.js +17 -0
- package/backend/src/core/node-registries/strings.js +34 -0
- package/backend/src/core/node-registries/type.js +25 -0
- package/backend/src/core/nodes/actions/bot_look_at.js +1 -1
- package/backend/src/core/nodes/actions/create_command.js +189 -0
- package/backend/src/core/nodes/actions/delete_command.js +92 -0
- package/backend/src/core/nodes/actions/update_command.js +133 -0
- package/backend/src/core/nodes/arrays/join.js +28 -0
- package/backend/src/core/nodes/data/cast.js +2 -1
- package/backend/src/core/nodes/logic/not.js +22 -0
- package/backend/src/core/nodes/strings/starts_with.js +1 -1
- package/backend/src/core/nodes/strings/to_lower.js +22 -0
- package/backend/src/core/nodes/strings/to_upper.js +22 -0
- package/backend/src/core/nodes/type/to_string.js +32 -0
- package/backend/src/core/services/BotLifecycleService.js +255 -16
- package/backend/src/core/services/CommandExecutionService.js +430 -351
- package/backend/src/core/services/DebugSessionManager.js +347 -0
- package/backend/src/core/services/GraphCollaborationManager.js +501 -0
- package/backend/src/core/services/MinecraftBotManager.js +259 -0
- package/backend/src/core/services/MinecraftViewerService.js +216 -0
- package/backend/src/core/services/TraceCollectorService.js +545 -0
- package/backend/src/core/system/RuntimeCommandRegistry.js +116 -0
- package/backend/src/core/system/Transport.js +0 -4
- package/backend/src/core/validation/nodeSchemas.js +6 -6
- package/backend/src/real-time/botApi/handlers/graphHandlers.js +2 -2
- package/backend/src/real-time/botApi/handlers/graphWebSocketHandlers.js +1 -1
- package/backend/src/real-time/botApi/utils.js +11 -0
- package/backend/src/real-time/panelNamespace.js +387 -0
- package/backend/src/real-time/presence.js +7 -2
- package/backend/src/real-time/socketHandler.js +395 -4
- package/backend/src/server.js +18 -0
- package/frontend/dist/assets/index-B1serztM.js +11210 -0
- package/frontend/dist/assets/index-t6K1u4OV.css +32 -0
- package/frontend/dist/index.html +2 -2
- package/frontend/package-lock.json +9437 -0
- package/frontend/package.json +8 -0
- package/package.json +2 -2
- package/screen/console.png +0 -0
- package/screen/dashboard.png +0 -0
- package/screen/graph_collabe.png +0 -0
- package/screen/graph_live_debug.png +0 -0
- package/screen/management_command.png +0 -0
- package/screen/node_debug_trace.png +0 -0
- package/screen/plugin_/320/276/320/261/320/267/320/276/321/200.png +0 -0
- package/screen/websocket.png +0 -0
- package/screen//320/275/320/260/321/201/321/202/321/200/320/276/320/271/320/272/320/270_/320/276/321/202/320/264/320/265/320/273/321/214/320/275/321/213/321/205_/320/272/320/276/320/274/320/260/320/275/320/264_/320/272/320/260/320/266/320/264/321/203_/320/272/320/276/320/274/320/260/320/275/320/273/320/264/321/203_/320/274/320/276/320/266/320/275/320/276_/320/275/320/260/321/201/321/202/321/200/320/260/320/270/320/262/320/260/321/202/321/214.png +0 -0
- package/screen//320/277/320/273/320/260/320/275/320/270/321/200/320/276/320/262/321/211/320/270/320/272_/320/274/320/276/320/266/320/275/320/276_/320/267/320/260/320/264/320/260/320/262/320/260/321/202/321/214_/320/264/320/265/320/271/321/201/321/202/320/262/320/270/321/217_/320/277/320/276_/320/262/321/200/320/265/320/274/320/265/320/275/320/270.png +0 -0
- package/frontend/dist/assets/index-CfTo92bP.css +0 -1
- package/frontend/dist/assets/index-CiFD5X9Z.js +0 -8344
|
@@ -7,8 +7,19 @@
|
|
|
7
7
|
*/
|
|
8
8
|
function broadcastToApiClients(io, botId, eventName, data) {
|
|
9
9
|
try {
|
|
10
|
+
// Broadcast to bot-api namespace
|
|
10
11
|
const apiNamespace = io.of('/bot-api');
|
|
11
12
|
apiNamespace.to(`bot_${botId}`).emit(eventName, data);
|
|
13
|
+
|
|
14
|
+
// Also broadcast to Panel namespace subscribers
|
|
15
|
+
const panelNamespace = io.of('/panel');
|
|
16
|
+
const panelChannel = `bots:${botId}:events`;
|
|
17
|
+
panelNamespace.to(panelChannel).emit(panelChannel, {
|
|
18
|
+
botId,
|
|
19
|
+
eventType: eventName,
|
|
20
|
+
data,
|
|
21
|
+
timestamp: new Date().toISOString()
|
|
22
|
+
});
|
|
12
23
|
} catch (error) {
|
|
13
24
|
console.error('[Bot API] Ошибка broadcast:', error);
|
|
14
25
|
}
|
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
const bcrypt = require('bcryptjs');
|
|
2
|
+
const prisma = require('../lib/prisma');
|
|
3
|
+
const { botManager } = require('../core/services');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Аутентификация Panel API Key для WebSocket
|
|
7
|
+
*/
|
|
8
|
+
async function authenticateSocket(socket, next) {
|
|
9
|
+
const token = socket.handshake.auth?.token;
|
|
10
|
+
|
|
11
|
+
if (!token) {
|
|
12
|
+
return next(new Error('API ключ не предоставлен'));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
const allKeys = await prisma.panelApiKey.findMany({
|
|
17
|
+
where: {
|
|
18
|
+
isActive: true,
|
|
19
|
+
OR: [
|
|
20
|
+
{ expiresAt: null },
|
|
21
|
+
{ expiresAt: { gt: new Date() } }
|
|
22
|
+
]
|
|
23
|
+
},
|
|
24
|
+
include: {
|
|
25
|
+
user: {
|
|
26
|
+
include: {
|
|
27
|
+
role: true,
|
|
28
|
+
botAccess: {
|
|
29
|
+
include: {
|
|
30
|
+
bot: true
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
let matchedKey = null;
|
|
39
|
+
for (const keyRecord of allKeys) {
|
|
40
|
+
if (await bcrypt.compare(token, keyRecord.keyHash)) {
|
|
41
|
+
matchedKey = keyRecord;
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!matchedKey) {
|
|
47
|
+
return next(new Error('Неверный API ключ'));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
await prisma.panelApiKey.update({
|
|
51
|
+
where: { id: matchedKey.id },
|
|
52
|
+
data: { lastUsedAt: new Date() }
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
let permissions;
|
|
56
|
+
try {
|
|
57
|
+
if (matchedKey.customScopes) {
|
|
58
|
+
permissions = JSON.parse(matchedKey.customScopes);
|
|
59
|
+
} else {
|
|
60
|
+
permissions = JSON.parse(matchedKey.user.role.permissions);
|
|
61
|
+
}
|
|
62
|
+
} catch (parseError) {
|
|
63
|
+
console.error('Ошибка парсинга прав доступа:', parseError);
|
|
64
|
+
return next(new Error('Ошибка обработки прав доступа'));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
let availableBots;
|
|
68
|
+
if (matchedKey.user.allBots) {
|
|
69
|
+
availableBots = await prisma.bot.findMany({
|
|
70
|
+
select: { id: true, username: true }
|
|
71
|
+
});
|
|
72
|
+
} else {
|
|
73
|
+
availableBots = matchedKey.user.botAccess.map(access => ({
|
|
74
|
+
id: access.bot.id,
|
|
75
|
+
username: access.bot.username
|
|
76
|
+
}));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
socket.user = {
|
|
80
|
+
id: matchedKey.user.id,
|
|
81
|
+
userId: matchedKey.user.id,
|
|
82
|
+
uuid: matchedKey.user.uuid,
|
|
83
|
+
username: matchedKey.user.username,
|
|
84
|
+
roleId: matchedKey.user.roleId,
|
|
85
|
+
roleName: matchedKey.user.role.name,
|
|
86
|
+
permissions,
|
|
87
|
+
allBots: matchedKey.user.allBots,
|
|
88
|
+
availableBotIds: availableBots.map(b => b.id)
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
socket.apiKey = {
|
|
92
|
+
id: matchedKey.id,
|
|
93
|
+
name: matchedKey.name,
|
|
94
|
+
prefix: matchedKey.prefix
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
next();
|
|
98
|
+
} catch (err) {
|
|
99
|
+
console.error('Ошибка аутентификации Panel WebSocket:', err);
|
|
100
|
+
next(new Error('Ошибка аутентификации'));
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Проверка доступа к боту
|
|
106
|
+
*/
|
|
107
|
+
function canAccessBot(socket, botId) {
|
|
108
|
+
if (socket.user.allBots) return true;
|
|
109
|
+
return socket.user.availableBotIds.includes(botId);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Проверка прав доступа
|
|
114
|
+
*/
|
|
115
|
+
function hasPermission(socket, permission) {
|
|
116
|
+
if (!socket.user || !Array.isArray(socket.user.permissions)) {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
return socket.user.permissions.includes('*') || socket.user.permissions.includes(permission);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Инициализация Panel namespace
|
|
124
|
+
*/
|
|
125
|
+
function initializePanelNamespace(io) {
|
|
126
|
+
const panelNamespace = io.of('/panel');
|
|
127
|
+
|
|
128
|
+
panelNamespace.use(authenticateSocket);
|
|
129
|
+
|
|
130
|
+
panelNamespace.on('connection', (socket) => {
|
|
131
|
+
console.log(`[Panel WS] Connected: ${socket.user.username} (${socket.apiKey.name})`);
|
|
132
|
+
|
|
133
|
+
socket.emit('authenticated', {
|
|
134
|
+
user: {
|
|
135
|
+
id: socket.user.id,
|
|
136
|
+
username: socket.user.username,
|
|
137
|
+
role: socket.user.roleName,
|
|
138
|
+
permissions: socket.user.permissions
|
|
139
|
+
},
|
|
140
|
+
apiKey: {
|
|
141
|
+
id: socket.apiKey.id,
|
|
142
|
+
name: socket.apiKey.name,
|
|
143
|
+
prefix: socket.apiKey.prefix
|
|
144
|
+
},
|
|
145
|
+
availableBots: socket.user.availableBotIds
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// ========== SUBSCRIPTIONS ==========
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Подписка на каналы событий
|
|
152
|
+
*/
|
|
153
|
+
socket.on('subscribe', async ({ channel }, callback) => {
|
|
154
|
+
try {
|
|
155
|
+
// Парсим канал: "bots:status" или "bots:1:events"
|
|
156
|
+
const parts = channel.split(':');
|
|
157
|
+
const resource = parts[0];
|
|
158
|
+
|
|
159
|
+
if (resource === 'bots') {
|
|
160
|
+
if (parts.length === 2 && parts[1] === 'status') {
|
|
161
|
+
socket.join('bots:status');
|
|
162
|
+
if (callback) callback({ success: true, channel });
|
|
163
|
+
} else if (parts.length >= 3) {
|
|
164
|
+
const botId = parseInt(parts[1]);
|
|
165
|
+
const eventType = parts[2];
|
|
166
|
+
|
|
167
|
+
if (!canAccessBot(socket, botId)) {
|
|
168
|
+
if (callback) callback({
|
|
169
|
+
success: false,
|
|
170
|
+
error: 'Нет доступа к этому боту'
|
|
171
|
+
});
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
socket.join(channel);
|
|
176
|
+
if (callback) callback({ success: true, channel });
|
|
177
|
+
}
|
|
178
|
+
} else if (resource === 'system') {
|
|
179
|
+
socket.join(channel);
|
|
180
|
+
if (callback) callback({ success: true, channel });
|
|
181
|
+
} else {
|
|
182
|
+
if (callback) callback({
|
|
183
|
+
success: false,
|
|
184
|
+
error: 'Неизвестный канал'
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
} catch (error) {
|
|
188
|
+
console.error('[Panel WS] Ошибка подписки:', error);
|
|
189
|
+
if (callback) callback({
|
|
190
|
+
success: false,
|
|
191
|
+
error: error.message
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Отписка от каналов
|
|
198
|
+
*/
|
|
199
|
+
socket.on('unsubscribe', ({ channel }, callback) => {
|
|
200
|
+
socket.leave(channel);
|
|
201
|
+
if (callback) callback({ success: true, channel });
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
// ========== ACTIONS ==========
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Отправка сообщения от бота
|
|
208
|
+
*/
|
|
209
|
+
socket.on('action:bot:send_message', async ({ botId, message, chatType, recipient }, callback) => {
|
|
210
|
+
try {
|
|
211
|
+
if (!canAccessBot(socket, botId)) {
|
|
212
|
+
if (callback) callback({ success: false, error: 'Нет доступа к этому боту' });
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (!hasPermission(socket, 'bot:interact')) {
|
|
217
|
+
if (callback) callback({ success: false, error: 'Нет прав: bot:interact' });
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const bot = botManager.getBotInstance(botId);
|
|
222
|
+
if (!bot) {
|
|
223
|
+
if (callback) callback({ success: false, error: 'Бот не найден или оффлайн' });
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const payload = {
|
|
228
|
+
chatType: chatType || 'chat',
|
|
229
|
+
message,
|
|
230
|
+
...(recipient && { recipient })
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
await bot.sendMessage(payload);
|
|
234
|
+
|
|
235
|
+
if (callback) callback({ success: true });
|
|
236
|
+
} catch (error) {
|
|
237
|
+
console.error('[Panel WS] Ошибка отправки сообщения:', error);
|
|
238
|
+
if (callback) callback({ success: false, error: error.message });
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Выполнение команды
|
|
244
|
+
*/
|
|
245
|
+
socket.on('action:bot:execute_command', async ({ botId, username, command, args }, callback) => {
|
|
246
|
+
try {
|
|
247
|
+
if (!canAccessBot(socket, botId)) {
|
|
248
|
+
if (callback) callback({ success: false, error: 'Нет доступа к этому боту' });
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (!hasPermission(socket, 'bot:interact')) {
|
|
253
|
+
if (callback) callback({ success: false, error: 'Нет прав: bot:interact' });
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const bot = botManager.getBotInstance(botId);
|
|
258
|
+
if (!bot) {
|
|
259
|
+
if (callback) callback({ success: false, error: 'Бот не найден или оффлайн' });
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const result = await bot.executeCommand(username, command, args || {});
|
|
264
|
+
|
|
265
|
+
if (callback) callback({ success: true, result });
|
|
266
|
+
} catch (error) {
|
|
267
|
+
console.error('[Panel WS] Ошибка выполнения команды:', error);
|
|
268
|
+
if (callback) callback({ success: false, error: error.message });
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Запуск бота
|
|
274
|
+
*/
|
|
275
|
+
socket.on('action:bot:start', async ({ botId }, callback) => {
|
|
276
|
+
try {
|
|
277
|
+
if (!canAccessBot(socket, botId)) {
|
|
278
|
+
if (callback) callback({ success: false, error: 'Нет доступа к этому боту' });
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (!hasPermission(socket, 'bot:start_stop')) {
|
|
283
|
+
if (callback) callback({ success: false, error: 'Нет прав: bot:start_stop' });
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
await botManager.startBot(botId);
|
|
288
|
+
|
|
289
|
+
if (callback) callback({ success: true, message: 'Бот запущен' });
|
|
290
|
+
} catch (error) {
|
|
291
|
+
console.error('[Panel WS] Ошибка запуска бота:', error);
|
|
292
|
+
if (callback) callback({ success: false, error: error.message });
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Остановка бота
|
|
298
|
+
*/
|
|
299
|
+
socket.on('action:bot:stop', async ({ botId }, callback) => {
|
|
300
|
+
try {
|
|
301
|
+
if (!canAccessBot(socket, botId)) {
|
|
302
|
+
if (callback) callback({ success: false, error: 'Нет доступа к этому боту' });
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (!hasPermission(socket, 'bot:start_stop')) {
|
|
307
|
+
if (callback) callback({ success: false, error: 'Нет прав: bot:start_stop' });
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
await botManager.stopBot(botId);
|
|
312
|
+
|
|
313
|
+
if (callback) callback({ success: true, message: 'Бот остановлен' });
|
|
314
|
+
} catch (error) {
|
|
315
|
+
console.error('[Panel WS] Ошибка остановки бота:', error);
|
|
316
|
+
if (callback) callback({ success: false, error: error.message });
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Перезапуск бота
|
|
322
|
+
*/
|
|
323
|
+
socket.on('action:bot:restart', async ({ botId }, callback) => {
|
|
324
|
+
try {
|
|
325
|
+
if (!canAccessBot(socket, botId)) {
|
|
326
|
+
if (callback) callback({ success: false, error: 'Нет доступа к этому боту' });
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (!hasPermission(socket, 'bot:start_stop')) {
|
|
331
|
+
if (callback) callback({ success: false, error: 'Нет прав: bot:start_stop' });
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
await botManager.restartBot(botId);
|
|
336
|
+
|
|
337
|
+
if (callback) callback({ success: true, message: 'Бот перезапущен' });
|
|
338
|
+
} catch (error) {
|
|
339
|
+
console.error('[Panel WS] Ошибка перезапуска бота:', error);
|
|
340
|
+
if (callback) callback({ success: false, error: error.message });
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Получение статуса бота
|
|
346
|
+
*/
|
|
347
|
+
socket.on('action:bot:get_status', async ({ botId }, callback) => {
|
|
348
|
+
try {
|
|
349
|
+
if (!canAccessBot(socket, botId)) {
|
|
350
|
+
if (callback) callback({ success: false, error: 'Нет доступа к этому боту' });
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const status = botManager.getBotStatus(botId);
|
|
355
|
+
|
|
356
|
+
if (callback) callback({
|
|
357
|
+
success: true,
|
|
358
|
+
status: status || { online: false, connected: false, status: 'offline' }
|
|
359
|
+
});
|
|
360
|
+
} catch (error) {
|
|
361
|
+
console.error('[Panel WS] Ошибка получения статуса:', error);
|
|
362
|
+
if (callback) callback({ success: false, error: error.message });
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
// ========== DISCONNECT ==========
|
|
367
|
+
|
|
368
|
+
socket.on('disconnect', () => {
|
|
369
|
+
console.log(`[Panel WS] Disconnected: ${socket.user.username}`);
|
|
370
|
+
});
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
return panelNamespace;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Broadcast события в Panel namespace
|
|
378
|
+
*/
|
|
379
|
+
function broadcastToPanelNamespace(io, channel, data) {
|
|
380
|
+
const panelNamespace = io.of('/panel');
|
|
381
|
+
panelNamespace.to(channel).emit(channel, data);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
module.exports = {
|
|
385
|
+
initializePanelNamespace,
|
|
386
|
+
broadcastToPanelNamespace
|
|
387
|
+
};
|
|
@@ -27,6 +27,7 @@ function broadcast(io) {
|
|
|
27
27
|
username: info.username,
|
|
28
28
|
path: info.path || '/',
|
|
29
29
|
lastSeen: info.lastSeen,
|
|
30
|
+
metadata: info.metadata || {},
|
|
30
31
|
}));
|
|
31
32
|
io.emit('presence:list', list);
|
|
32
33
|
}
|
|
@@ -38,7 +39,9 @@ function handleConnection(io, socket) {
|
|
|
38
39
|
return socket.disconnect(true);
|
|
39
40
|
}
|
|
40
41
|
const { userId, username } = decoded;
|
|
41
|
-
|
|
42
|
+
const initialPath = socket.handshake?.query?.initialPath || '/';
|
|
43
|
+
console.log(`[Presence] User ${username} (${userId}) connected from ${initialPath}`);
|
|
44
|
+
presenceMap.set(userId, { username, socketId: socket.id, lastSeen: Date.now(), path: initialPath });
|
|
42
45
|
broadcast(io);
|
|
43
46
|
|
|
44
47
|
socket.on('presence:heartbeat', () => {
|
|
@@ -50,11 +53,13 @@ function handleConnection(io, socket) {
|
|
|
50
53
|
}
|
|
51
54
|
});
|
|
52
55
|
|
|
53
|
-
socket.on('presence:update', ({ path }) => {
|
|
56
|
+
socket.on('presence:update', ({ path, metadata }) => {
|
|
54
57
|
const info = presenceMap.get(userId) || { username, socketId: socket.id };
|
|
55
58
|
info.lastSeen = Date.now();
|
|
56
59
|
info.path = typeof path === 'string' ? path : '/';
|
|
60
|
+
info.metadata = metadata || {};
|
|
57
61
|
presenceMap.set(userId, info);
|
|
62
|
+
console.log(`[Presence] User ${username} updated location: ${info.path}`, metadata);
|
|
58
63
|
broadcast(io);
|
|
59
64
|
});
|
|
60
65
|
|