250 lines
8.7 KiB
JavaScript
250 lines
8.7 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;
|
|
|
|
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',
|
|
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 || {};
|
|
|
|
// Chercher le joueur par nom
|
|
for (const [steamId, player] of Object.entries(players)) {
|
|
if (player.name === playerName) {
|
|
return steamId;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
} catch (error) {
|
|
console.error('Erreur lors de la récupération du Steam ID:', error.message);
|
|
return null;
|
|
}
|
|
};
|
|
|
|
const handleLinkCommand = async (playerName, steamId, code) => {
|
|
try {
|
|
console.log(`🔗 Tentative de liaison détectée: ${playerName} (${steamId}) avec le code ${code}`);
|
|
|
|
const result = await verifyLinkCode(code, steamId, playerName);
|
|
|
|
if (result.success) {
|
|
console.log(`✅ Liaison réussie pour ${playerName}`);
|
|
|
|
// Envoyer un message de confirmation à l'utilisateur Discord
|
|
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: **${playerName}**\n` +
|
|
`🆔 Steam ID: \`${steamId}\``
|
|
).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 connectWebSocket = async (pterodactylToken, serverId) => {
|
|
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) {
|
|
// Récupérer le Steam ID depuis l'API
|
|
const steamId = await getSteamIdFromPlayerName(linkData.playerName);
|
|
|
|
if (steamId) {
|
|
await handleLinkCommand(linkData.playerName, steamId, linkData.code);
|
|
} else {
|
|
console.log(`❌ Impossible de trouver le Steam ID pour ${linkData.playerName}`);
|
|
console.log(`💡 Le joueur doit être connecté sur le serveur`);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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}, Raison: ${reason})`);
|
|
ws = null;
|
|
|
|
// Arrêter le heartbeat
|
|
if (heartbeatInterval) {
|
|
clearInterval(heartbeatInterval);
|
|
heartbeatInterval = null;
|
|
}
|
|
|
|
// Reconnexion automatique
|
|
if (reconnectTimeout) clearTimeout(reconnectTimeout);
|
|
reconnectTimeout = setTimeout(() => {
|
|
console.log('🔄 Tentative de reconnexion...');
|
|
connectWebSocket(pterodactylToken, serverId);
|
|
}, 10000);
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Erreur lors de la connexion WebSocket:', error);
|
|
|
|
// Réessayer dans 30 secondes
|
|
if (reconnectTimeout) clearTimeout(reconnectTimeout);
|
|
reconnectTimeout = setTimeout(() => {
|
|
console.log('🔄 Tentative de reconnexion...');
|
|
connectWebSocket(pterodactylToken, serverId);
|
|
}, 30000);
|
|
}
|
|
};
|
|
|
|
const startConsoleMonitoring = (discordClient, pterodactylToken) => {
|
|
client = discordClient;
|
|
|
|
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');
|
|
connectWebSocket(pterodactylToken, serverId);
|
|
};
|
|
|
|
const stopConsoleMonitoring = () => {
|
|
if (heartbeatInterval) {
|
|
clearInterval(heartbeatInterval);
|
|
heartbeatInterval = null;
|
|
}
|
|
|
|
if (reconnectTimeout) {
|
|
clearTimeout(reconnectTimeout);
|
|
reconnectTimeout = null;
|
|
}
|
|
|
|
if (ws) {
|
|
ws.close();
|
|
ws = null;
|
|
}
|
|
};
|
|
|
|
module.exports = { startConsoleMonitoring, stopConsoleMonitoring };
|