couteau_suisse/consoleMonitor.js
2025-12-09 19:37:16 +01:00

346 lines
12 KiB
JavaScript

const axios = require('axios');
const WebSocket = require('ws');
const { verifyLinkCode, updateUserLinkWithUsername, updateLastConnection } = require('./database.js');
let ws = null;
let reconnectTimeout = null;
let client = null;
let heartbeatInterval = null;
let checkInterval = null;
let isMonitoring = false;
let isConnecting = false;
let connectionTimestamp = null;
const parseLogMessage = (log) => {
// Format réel de log Palworld:
// [2025-12-09 13:28:23] [CHAT] <LouisMazin> !link X2NMAY
console.log(log)
const linkRegex = /\[.*?\]\s*\[CHAT\]\s*<(.+?)>\s*!lier\s+([A-Z0-9]{6})/i;
const match = log.match(linkRegex);
if (match) {
const playerName = match[1].trim();
const code = match[2].toUpperCase();
console.log(`✅ Commande !lier détectée: ${playerName} avec le code ${code}`);
return {
type: 'link',
playerName: playerName,
code: code,
needsSteamId: true
};
}
console.log(log);
// Détecter les déconnexions: [2025-12-09 18:55:19] [LOG] Nami left the server. (User id: gdk_2535420062888893)
const disconnectRegex = /\[.*?\]\s*\[LOG\]\s*(.+?)\s+left the server\.\s*\(User id:\s*(.+?)\)/i;
const disconnectMatch = log.match(disconnectRegex);
console.log(disconnectMatch);
if (disconnectMatch) {
const playerName = disconnectMatch[1].trim();
const userId = disconnectMatch[2].trim();
console.log(`👋 Déconnexion détectée: ${playerName} (${userId})`);
return {
type: 'disconnect',
playerName: playerName,
userId: userId
};
}
return null;
};
const getSteamIdFromPlayerName = async (playerName) => {
try {
const response = await axios({
method: 'get',
maxBodyLength: Infinity,
url: 'http://play.louismazin.ovh:8212/v1/api/players',
headers: {
'Accept': 'application/json',
'Authorization': `Basic ${process.env.PALWORLD_API_TOKEN}`
}
});
const players = response.data.players || {};
console.log(`🔍 Recherche du Steam ID pour le joueur: ${playerName}`);
// Chercher le joueur par nom
for (const [steamId, player] of Object.entries(players)) {
if (player.name === playerName) {
return {
steamId: player.userId,
playerId: player.playerId,
name: player.name
};
}
}
return null;
} catch (error) {
console.error('Erreur lors de la récupération du Steam ID:', error.message);
return null;
}
};
const handleLinkCommand = async (playerName, playerData, code) => {
try {
console.log(`🔗 Tentative de liaison détectée: ${playerName} (${playerData.steamId}) avec le code ${code}`);
const result = await verifyLinkCode(code, playerData.steamId, playerData.name, playerData.playerId);
if (result.success) {
console.log(`✅ Liaison réussie pour ${playerName}`);
if (client) {
const user = await client.users.fetch(result.discordId).catch(() => null);
if (user) {
await updateUserLinkWithUsername(result.discordId, user.tag);
await user.send(
`✅ **Liaison réussie !**\n\n` +
`Votre compte Discord a été lié avec succès à votre compte Palworld:\n` +
`🎮 Nom Palworld: **${playerData.name}**\n` +
`🆔 Steam ID: \`${playerData.steamId}\`\n` +
`🎯 Player ID: \`${playerData.playerId}\``
).catch(() => {});
}
}
} else {
console.log(`❌ Échec de la liaison: ${result.message}`);
}
} catch (error) {
console.error('Erreur lors du traitement de la commande !link:', error);
}
};
const getWebSocketCredentials = async (pterodactylToken, serverId) => {
try {
const response = await axios({
method: 'get',
url: `${process.env.PTERODACTYL_API_URL}/api/client/servers/${serverId}/websocket`,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': `Bearer ${pterodactylToken}`
}
});
return response.data.data;
} catch (error) {
console.error('Erreur lors de la récupération des credentials WebSocket:', error.message);
throw error;
}
};
const checkAndManageWebSocket = async () => {
const { hasActiveLinkCodes, cleanExpiredCodes } = require('./database.js');
try {
await cleanExpiredCodes();
// Le WebSocket doit toujours être connecté maintenant (pour les déconnexions)
if (!ws && !isConnecting) {
console.log('🔌 Connexion au WebSocket pour surveillance...');
const serverId = process.env.PTERODACTYL_SERVER_ID;
const pterodactylToken = process.env.PTERODACTYL_API_TOKEN;
await connectWebSocket(pterodactylToken, serverId);
}
} catch (error) {
console.error('Erreur lors de la vérification du WebSocket:', error);
}
};
const connectWebSocket = async (pterodactylToken, serverId) => {
if (ws && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING)) {
console.log('⚠️ WebSocket déjà connecté ou en cours de connexion');
return;
}
if (isConnecting) {
console.log('⚠️ Connexion WebSocket déjà en cours');
return;
}
isConnecting = true;
connectionTimestamp = Date.now();
console.log(`📅 Timestamp de connexion: ${new Date(connectionTimestamp).toISOString()}`);
try {
const credentials = await getWebSocketCredentials(pterodactylToken, serverId);
ws = new WebSocket(credentials.socket, {
origin: process.env.PTERODACTYL_API_URL
});
ws.on('open', () => {
console.log('✅ WebSocket Pterodactyl connecté');
isConnecting = false;
ws.send(JSON.stringify({
event: 'auth',
args: [credentials.token]
}));
});
ws.on('message', async (data) => {
try {
const message = JSON.parse(data.toString());
if (message.event === 'auth success') {
console.log('✅ Authentification WebSocket réussie');
ws.send(JSON.stringify({
event: 'send logs',
args: [null]
}));
if (heartbeatInterval) clearInterval(heartbeatInterval);
heartbeatInterval = setInterval(() => {
if (ws && ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({
event: 'send heartbeat',
args: []
}));
}
}, 30000);
}
if (message.event === 'console output') {
const log = message.args[0];
// Extraire le timestamp du log
const timestampMatch = log.match(/\[(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})\]/);
if (timestampMatch) {
const logTimestamp = new Date(timestampMatch[1]).getTime();
// Ignorer les messages antérieurs à la connexion
if (logTimestamp < connectionTimestamp) {
return;
}
}
const linkData = parseLogMessage(log);
if (linkData) {
if (linkData.type === 'link') {
const playerData = await getSteamIdFromPlayerName(linkData.playerName);
if (playerData) {
await handleLinkCommand(linkData.playerName, playerData, linkData.code);
// Ne plus vérifier si on doit fermer le WebSocket
} else {
console.log(`❌ Impossible de trouver le Steam ID pour ${linkData.playerName}`);
}
} else if (linkData.type === 'disconnect') {
// Extraire le Steam ID du userId (format: gdk_STEAMID)
try {
await updateLastConnection(linkData.userId);
console.log(`✅ Dernière connexion mise à jour pour ${linkData.playerName} (${linkData.userId})`);
} catch (error) {
console.error('Erreur lors de la mise à jour de la dernière connexion:', error);
}
}
}
}
if (message.event === 'status') {
console.log('📊 Statut du serveur:', message.args[0]);
}
} catch (error) {
console.error('Erreur parsing message WebSocket:', error);
}
});
ws.on('error', (error) => {
console.error('❌ Erreur WebSocket:', error.message);
isConnecting = false;
});
ws.on('close', (code, reason) => {
console.log(`⚠️ WebSocket Pterodactyl déconnecté (Code: ${code})`);
ws = null;
isConnecting = false;
connectionTimestamp = null;
if (heartbeatInterval) {
clearInterval(heartbeatInterval);
heartbeatInterval = null;
}
// Tenter une reconnexion après 5 secondes
if (isMonitoring) {
console.log('🔄 Reconnexion dans 5 secondes...');
reconnectTimeout = setTimeout(() => {
checkAndManageWebSocket();
}, 5000);
}
});
} catch (error) {
console.error('Erreur lors de la connexion WebSocket:', error);
isConnecting = false;
connectionTimestamp = null;
}
};
const stopWebSocketOnly = () => {
if (heartbeatInterval) {
clearInterval(heartbeatInterval);
heartbeatInterval = null;
}
if (ws) {
ws.close();
ws = null;
}
isConnecting = false;
};
const startConsoleMonitoring = (discordClient, pterodactylToken) => {
if (isMonitoring) {
console.log('⚠️ Surveillance déjà active');
return;
}
client = discordClient;
isMonitoring = true;
const serverId = process.env.PTERODACTYL_SERVER_ID;
if (!serverId) {
console.error('❌ PTERODACTYL_SERVER_ID non défini dans .env');
return;
}
console.log('🔍 Surveillance de la console Pterodactyl démarrée (mode intelligent)');
// Vérifier immédiatement
checkAndManageWebSocket();
// Vérifier toutes les 30 secondes
checkInterval = setInterval(checkAndManageWebSocket, 30000);
};
const stopConsoleMonitoring = () => {
stopWebSocketOnly();
if (checkInterval) {
clearInterval(checkInterval);
checkInterval = null;
}
if (reconnectTimeout) {
clearTimeout(reconnectTimeout);
reconnectTimeout = null;
}
isMonitoring = false;
console.log('🔌 Surveillance de la console arrêtée');
};
module.exports = { startConsoleMonitoring, stopConsoleMonitoring };