const axios = require('axios'); const WebSocket = require('ws'); const { verifyLinkCode, updateUserLinkWithUsername } = require('./database.js'); let ws = null; let reconnectTimeout = null; let client = null; let heartbeatInterval = null; let checkInterval = null; let isMonitoring = false; const parseLogMessage = (log) => { // Format réel de log Palworld: // [2025-12-08 21:47:17] [CHAT] !link ABC123 const linkRegex = /\[.*?\]\s*\[CHAT\]\s*<(.+?)>\s*!link\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 !link détectée: ${playerName} avec le code ${code}`); // Le Steam ID n'est pas dans les logs de chat // On va devoir le récupérer via l'API des joueurs connectés return { playerName: playerName, code: code, needsSteamId: true }; } return null; }; const getSteamIdFromPlayerName = async (playerName) => { try { const response = await axios({ method: 'get', 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}`); console.log(players); // 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 { // Nettoyer les codes expirés await cleanExpiredCodes(); const hasActiveCodes = await hasActiveLinkCodes(); if (hasActiveCodes && !ws) { // Il y a des codes actifs mais pas de connexion WebSocket 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) { // Pas de codes actifs mais WebSocket connecté console.log('🔌 Aucun code actif, fermeture du WebSocket...'); stopConsoleMonitoring(); } } catch (error) { console.error('Erreur lors de la vérification des codes actifs:', error); } }; const connectWebSocket = async (pterodactylToken, serverId) => { // Éviter les connexions multiples if (ws && ws.readyState === WebSocket.OPEN) { console.log('⚠️ WebSocket déjà connecté'); return; } 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é'); // Authentification selon la documentation ws.send(JSON.stringify({ event: 'auth', args: [credentials.token] })); }); ws.on('message', async (data) => { try { const message = JSON.parse(data.toString()); // Gérer l'événement d'authentification réussie if (message.event === 'auth success') { console.log('✅ Authentification WebSocket réussie'); // S'abonner aux logs de la console ws.send(JSON.stringify({ event: 'send logs', args: [null] })); // Démarrer le heartbeat (toutes les 30 secondes) if (heartbeatInterval) clearInterval(heartbeatInterval); heartbeatInterval = setInterval(() => { if (ws && ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify({ event: 'send heartbeat', args: [] })); } }, 30000); } // Gérer les logs de la console if (message.event === 'console output') { const log = message.args[0]; const linkData = parseLogMessage(log); if (linkData) { const playerData = await getSteamIdFromPlayerName(linkData.playerName); if (playerData) { await handleLinkCommand(linkData.playerName, playerData, linkData.code); await checkAndManageWebSocket(); } else { console.log(`❌ Impossible de trouver le Steam ID pour ${linkData.playerName}`); } } } // Gérer les événements de statut if (message.event === 'status') { console.log('📊 Statut du serveur:', message.args[0]); } } catch (error) { // Message non-JSON ou erreur de parsing, on ignore console.error('Erreur parsing message WebSocket:', error); } }); ws.on('error', (error) => { console.error('❌ Erreur WebSocket:', error.message); }); ws.on('close', (code, reason) => { console.log(`⚠️ WebSocket Pterodactyl déconnecté (Code: ${code})`); ws = null; // Arrêter le heartbeat if (heartbeatInterval) { clearInterval(heartbeatInterval); heartbeatInterval = null; } // Ne pas reconnecter automatiquement, laisser le checkInterval gérer ça }); } catch (error) { console.error('Erreur lors de la connexion WebSocket:', error); } }; 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 s'il faut connecter/déconnecter checkInterval = setInterval(checkAndManageWebSocket, 30000); }; const stopConsoleMonitoring = () => { if (heartbeatInterval) { clearInterval(heartbeatInterval); heartbeatInterval = null; } if (checkInterval) { clearInterval(checkInterval); checkInterval = null; } if (reconnectTimeout) { clearTimeout(reconnectTimeout); reconnectTimeout = null; } if (ws) { ws.close(); ws = null; } isMonitoring = false; console.log('🔌 Surveillance de la console arrêtée'); }; module.exports = { startConsoleMonitoring, stopConsoleMonitoring };