346 lines
12 KiB
JavaScript
346 lines
12 KiB
JavaScript
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] <LouisMazin> !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 };
|