diff --git a/commands/utility/diagnostique-ws.js b/commands/utility/diagnostique-ws.js new file mode 100644 index 0000000..47fcf60 --- /dev/null +++ b/commands/utility/diagnostique-ws.js @@ -0,0 +1,88 @@ +const { SlashCommandBuilder, EmbedBuilder, MessageFlags } = require('discord.js'); +const { getWebSocketStatus, forceWebSocketReconnect } = require('../../consoleMonitor.js'); + +module.exports = { + data: new SlashCommandBuilder() + .setName('diagnostique-ws') + .setDescription('Vérifier l\'état du WebSocket et forcer une reconnexion si nécessaire') + .addBooleanOption(option => + option.setName('reconnect') + .setDescription('Forcer une reconnexion du WebSocket') + .setRequired(false) + ), + + async execute(interaction) { + try { + const forceReconnect = interaction.options.getBoolean('reconnect') || false; + + if (forceReconnect) { + await interaction.reply({ + content: '🔄 Reconnexion forcée du WebSocket en cours...', + flags: MessageFlags.Ephemeral + }); + + await forceWebSocketReconnect(); + + // Attendre un peu pour laisser le temps de se reconnecter + await new Promise(resolve => setTimeout(resolve, 3000)); + } + + const status = getWebSocketStatus(); + + const embed = new EmbedBuilder() + .setColor(status.hasWebSocket && status.wsState === 'OPEN' ? 0x00FF00 : 0xFF0000) + .setTitle('🔌 Diagnostic WebSocket') + .addFields( + { name: '📊 Monitoring Actif', value: status.isMonitoring ? '✅ Oui' : '❌ Non', inline: true }, + { name: '🔗 WebSocket Présent', value: status.hasWebSocket ? '✅ Oui' : '❌ Non', inline: true }, + { name: '📡 État Connexion', value: status.wsState || 'N/A', inline: true }, + { name: '🔄 En cours de connexion', value: status.isConnecting ? '⏳ Oui' : '✅ Non', inline: true }, + { name: '💓 Heartbeat Actif', value: status.hasHeartbeat ? '✅ Oui' : '❌ Non', inline: true }, + { name: '🔍 Vérification Auto', value: status.hasCheckInterval ? '✅ Oui' : '❌ Non', inline: true }, + { name: '⏱️ Reconnexion Planifiée', value: status.hasPendingReconnect ? '⏳ Oui' : '❌ Non', inline: true }, + { name: '🔁 Délai Reconnexion', value: `${Math.round(status.reconnectDelayMs / 1000)}s`, inline: true }, + { name: '🔄 Refresh Credentials', value: status.hasRefreshInterval ? '✅ Oui' : '❌ Non', inline: true } + ) + .setTimestamp(); + + if (status.connectionTimestamp) { + embed.addFields({ name: '🕐 Dernière Connexion', value: status.connectionTimestamp, inline: false }); + } + + if (status.monitoringStartTimestamp) { + embed.addFields({ name: '🚀 Démarrage Monitoring', value: status.monitoringStartTimestamp, inline: false }); + } + + // Recommandations + let recommendations = ''; + if (!status.isMonitoring) { + recommendations += '⚠️ Le monitoring n\'est pas actif. Redémarrer le bot.\n'; + } + if (!status.hasWebSocket || status.wsState !== 'OPEN') { + recommendations += '⚠️ Le WebSocket n\'est pas connecté. Utilisez `/diagnostique-ws reconnect:True` pour forcer une reconnexion.\n'; + } + if (status.hasPendingReconnect) { + recommendations += `ℹ️ Une reconnexion est prévue dans ${Math.round(status.reconnectDelayMs / 1000)}s.\n`; + } + + if (recommendations) { + embed.addFields({ name: '💡 Recommandations', value: recommendations, inline: false }); + } else { + embed.addFields({ name: '✅ Statut', value: 'Tout fonctionne normalement !', inline: false }); + } + + if (forceReconnect) { + await interaction.editReply({ content: null, embeds: [embed], flags: MessageFlags.Ephemeral }); + } else { + await interaction.reply({ embeds: [embed], flags: MessageFlags.Ephemeral }); + } + + } catch (error) { + console.error('[DIAGNOSTIQUE-WS] Erreur:', error); + await interaction.reply({ + content: '❌ Erreur lors du diagnostic', + flags: MessageFlags.Ephemeral + }).catch(() => {}); + } + }, +}; diff --git a/consoleMonitor.js b/consoleMonitor.js index 452f874..c089e04 100644 --- a/consoleMonitor.js +++ b/consoleMonitor.js @@ -260,39 +260,43 @@ const scheduleReconnect = () => { if (reconnectTimeout) clearTimeout(reconnectTimeout); const delay = reconnectDelayMs; reconnectDelayMs = Math.min(reconnectDelayMs * 2, RECONNECT_DELAY_MAX_MS); - console.log(`🔄 Reconnexion WebSocket dans ${Math.round(delay / 1000)}s...`); + console.log(`🔄 [WEBSOCKET] Reconnexion WebSocket programmée dans ${Math.round(delay / 1000)}s... (Monitoring actif: ${isMonitoring})`); reconnectTimeout = setTimeout(() => { + console.log('🔄 [WEBSOCKET] Tentative de reconnexion maintenant...'); checkAndManageWebSocket(); }, delay); }; 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'); + console.log('⚠️ [WEBSOCKET] Déjà connecté ou en cours de connexion (état:', ws.readyState, ')'); return; } if (isConnecting) { - console.log('⚠️ Connexion WebSocket déjà en cours'); + console.log('⚠️ [WEBSOCKET] Connexion déjà en cours, abandon de cette tentative'); return; } isConnecting = true; connectionTimestamp = Date.now(); - console.log(`📅 Timestamp de connexion: ${new Date(connectionTimestamp).toISOString()}`); + console.log(`📅 [WEBSOCKET] Timestamp de connexion: ${new Date(connectionTimestamp).toISOString()}`); try { // Toujours rafraîchir les credentials pour éviter les tokens expirés après redémarrage serveur + console.log('🔑 [WEBSOCKET] Récupération de nouveaux credentials...'); const credentials = await getWebSocketCredentials(pterodactylToken, serverId); + console.log('✅ [WEBSOCKET] Credentials récupérés avec succès'); ws = new WebSocket(credentials.socket, { origin: process.env.PTERODACTYL_API_URL }); ws.on('open', () => { - console.log('✅ WebSocket Pterodactyl connecté'); + console.log('✅ [WEBSOCKET] WebSocket Pterodactyl connecté avec succès'); isConnecting = false; resetReconnectBackoff(); + console.log('🔐 [WEBSOCKET] Envoi de l\'authentification...'); ws.send(JSON.stringify({ event: 'auth', args: [credentials.token] @@ -304,7 +308,8 @@ const connectWebSocket = async (pterodactylToken, serverId) => { const message = JSON.parse(data.toString()); if (message.event === 'auth success') { - console.log('✅ Authentification WebSocket réussie'); + console.log('✅ [WEBSOCKET] Authentification WebSocket réussie'); + console.log('📡 [WEBSOCKET] Demande de réception des logs console...'); ws.send(JSON.stringify({ event: 'send logs', @@ -317,6 +322,7 @@ const connectWebSocket = async (pterodactylToken, serverId) => { ws.send(JSON.stringify({ event: 'send heartbeat', args: [] })); } }, 30000); + console.log('💓 [WEBSOCKET] Heartbeat configuré (30s)'); } if (message.event === 'console output') { const log = message.args[0]; @@ -340,6 +346,11 @@ const connectWebSocket = async (pterodactylToken, serverId) => { } } + // Détecter si c'est un message de chat + if (log.includes('[CHAT]')) { + console.log(`💬 [CONSOLE] Message de chat détecté: ${log.substring(0, 100)}`); + } + // Transférer le message de chat vers Discord via le bridge console.log(`📋 [CONSOLE] Traitement du log pour le bridge...`); try { @@ -400,12 +411,15 @@ const connectWebSocket = async (pterodactylToken, serverId) => { }); ws.on('error', (error) => { - console.error('❌ Erreur WebSocket:', error.message); + console.error('❌ [WEBSOCKET] Erreur WebSocket:', error.message); + console.error('❌ [WEBSOCKET] Code erreur:', error.code || 'N/A'); isConnecting = false; }); ws.on('close', (code, reason) => { - console.log(`⚠️ WebSocket Pterodactyl déconnecté (Code: ${code})`); + const reasonStr = reason ? reason.toString() : 'Aucune raison fournie'; + console.log(`⚠️ [WEBSOCKET] WebSocket Pterodactyl déconnecté (Code: ${code}, Raison: ${reasonStr})`); + console.log(`🔍 [WEBSOCKET] État actuel: isMonitoring=${isMonitoring}, isConnecting=${isConnecting}`); ws = null; isConnecting = false; connectionTimestamp = null; @@ -413,18 +427,28 @@ const connectWebSocket = async (pterodactylToken, serverId) => { if (heartbeatInterval) { clearInterval(heartbeatInterval); heartbeatInterval = null; + console.log('💔 [WEBSOCKET] Heartbeat arrêté'); } // Planifier une reconnexion avec backoff (utile lors des redémarrages quotidiens du serveur) - scheduleReconnect(); + if (isMonitoring) { + scheduleReconnect(); + } else { + console.log('⚠️ [WEBSOCKET] Monitoring désactivé, pas de reconnexion'); + } }); } catch (error) { - console.error('Erreur lors de la connexion WebSocket:', error); + console.error('❌ [WEBSOCKET] Erreur lors de la connexion WebSocket:', error.message); + console.error('❌ [WEBSOCKET] Stack:', error.stack); isConnecting = false; connectionTimestamp = null; // Échec immédiat -> planifier reconnexion avec backoff - scheduleReconnect(); + if (isMonitoring) { + scheduleReconnect(); + } else { + console.log('⚠️ [WEBSOCKET] Monitoring désactivé, pas de reconnexion'); + } } }; @@ -528,4 +552,29 @@ const forceWebSocketReconnect = async () => { await forceReconnectToRefreshCredentials(); }; -module.exports = { startConsoleMonitoring, stopConsoleMonitoring, forceWebSocketReconnect }; +// Fonction pour obtenir l'état du WebSocket (pour diagnostic) +const getWebSocketStatus = () => { + const wsStates = { + [WebSocket.CONNECTING]: 'CONNECTING', + [WebSocket.OPEN]: 'OPEN', + [WebSocket.CLOSING]: 'CLOSING', + [WebSocket.CLOSED]: 'CLOSED' + }; + + return { + isMonitoring, + isConnecting, + hasWebSocket: !!ws, + wsState: ws ? wsStates[ws.readyState] : 'N/A', + wsStateRaw: ws ? ws.readyState : null, + connectionTimestamp: connectionTimestamp ? new Date(connectionTimestamp).toISOString() : null, + monitoringStartTimestamp: monitoringStartTimestamp ? new Date(monitoringStartTimestamp).toISOString() : null, + reconnectDelayMs, + hasHeartbeat: !!heartbeatInterval, + hasCheckInterval: !!checkInterval, + hasRefreshInterval: !!refreshCredentialsInterval, + hasPendingReconnect: !!reconnectTimeout + }; +}; + +module.exports = { startConsoleMonitoring, stopConsoleMonitoring, forceWebSocketReconnect, getWebSocketStatus }; diff --git a/palworld-bridge.js b/palworld-bridge.js index b8c4eea..1f8a1c5 100644 --- a/palworld-bridge.js +++ b/palworld-bridge.js @@ -9,7 +9,7 @@ const initPalworldBridge = (client, channelId) => { bridgeClient = client; bridgeChannelId = channelId; - console.log(`🌉 Pont Palworld-Discord initialisé (Salon: ${channelId})`); + console.log(`🌉 [BRIDGE] Pont Palworld-Discord initialisé (Salon: ${channelId})`); // Écouter les messages du salon Discord client.on('messageCreate', async (message) => { @@ -23,6 +23,7 @@ const initPalworldBridge = (client, channelId) => { try { // Récupérer le pseudo Palworld lié + console.log(`🔍 [BRIDGE] Recherche de liaison pour ${message.author.tag} (ID: ${message.author.id})`); const userLink = await getUserLink(message.author.id); console.log(`🔍 [BRIDGE] Liaison trouvée pour ${message.author.tag}:`, userLink);