295 lines
10 KiB
JavaScript
295 lines
10 KiB
JavaScript
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] <LouisMazin> !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',
|
|
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}`);
|
|
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 };
|