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] !link X2NMAY 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("oui"); // 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(); const hasActiveCodes = await hasActiveLinkCodes(); if (hasActiveCodes && !ws && !isConnecting) { console.log('🔌 Codes de liaison actifs détectés, connexion au WebSocket...'); const serverId = process.env.PTERODACTYL_SERVER_ID; const pterodactylToken = process.env.PTERODACTYL_API_TOKEN; await connectWebSocket(pterodactylToken, serverId); } else if (!hasActiveCodes && ws) { console.log('🔌 Aucun code actif, fermeture du WebSocket...'); stopWebSocketOnly(); } } catch (error) { console.error('Erreur lors de la vérification des codes actifs:', 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); } console.log(message); 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); setTimeout(() => checkAndManageWebSocket(), 2000); } 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) const steamIdMatch = linkData.userId.match(/gdk_(\d+)/); if (steamIdMatch) { const steamId = steamIdMatch[1]; try { await updateLastConnection(steamId); console.log(`✅ Dernière connexion mise à jour pour ${linkData.playerName} (${steamId})`); } 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; } }); } 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 };