/** * Gestion de l'XP vocal * Gain d'XP toutes les 10 minutes en vocal (5-25 XP) * - Timer ne démarre QUE si au moins 2 personnes dans le salon * - Timer s'arrête si l'utilisateur est mute/deafen * - totalVoiceTime est incrémenté chaque minute */ const { Events, EmbedBuilder } = require('discord.js'); const db = require('../functions/database/db.js'); const { addXP, getXPMultiplier, getXPProgress } = require('../functions/xp/xp.js'); const { colors } = require('../utils/constants'); // Map const voiceUsers = new Map(); function canGainXP(member) { if (!member || !member.voice.channelId) return false; const voiceChannel = member.voice.channel; if (!voiceChannel) return false; // Compter les humains const membersInChannel = voiceChannel.members.filter(m => !m.user.bot).size; // Conditions : Pas tout seul, pas mute, pas deaf const isAlone = membersInChannel < 2; const isMuted = member.voice.serverMute || member.voice.selfMute; const isDeafened = member.voice.serverDeaf || member.voice.selfDeaf; return !isAlone && !isMuted && !isDeafened; } async function startTimers(key, member, guildId, userId) { // Si déjà des timers, on ne fait rien if (voiceUsers.has(key)) return; // 1. Timer XP (10 min) const xpInterval = setInterval(async () => { try { // Re-vérification dynamique const currentMember = await member.guild.members.fetch(userId).catch(() => null); if (!currentMember || !canGainXP(currentMember)) return; const multiplier = getXPMultiplier(currentMember, 'voice'); const xpGained = Math.floor(Math.random() * 21) + 5; // 5-25 XP const result = await addXP(userId, guildId, xpGained, 'voice', multiplier); if (result && result.levelUp) { const progress = getXPProgress(result.newXP, result.newLevel); const embed = new EmbedBuilder() .setTitle('🎉 Level Up !') .setDescription(`Félicitations ${currentMember.toString()} ! Tu as atteint le niveau **${result.newLevel}** en vocal !`) .setColor(colors.success) .addFields( { name: '📊 XP', value: `${result.newXP} XP`, inline: true }, { name: '⭐ Niveau', value: `${result.newLevel}`, inline: true }, { name: '📈 Progression', value: `${progress.current}/${progress.needed} XP (${progress.percentage}%)`, inline: true } ) .setThumbnail(currentMember.user.displayAvatarURL({ dynamic: true })) .setTimestamp(); const systemChannel = member.guild.systemChannel; if (systemChannel) await systemChannel.send({ embeds: [embed] }).catch(() => null); } } catch (err) { console.error('Erreur Timer XP:', err); } }, 10 * 60 * 1000); // 2. Timer Temps (1 min) const timeInterval = setInterval(async () => { try { const currentMember = await member.guild.members.fetch(userId).catch(() => null); if (currentMember && canGainXP(currentMember)) { await db.query('UPDATE user_xp SET totalVoiceTime = totalVoiceTime + 1 WHERE userId = ? AND guildId = ?', [userId, guildId]); } } catch (err) { console.error('Erreur Timer Temps:', err); } }, 60 * 1000); voiceUsers.set(key, { xpInterval, timeInterval }); // Mise à jour lastVoiceJoin await db.query('UPDATE user_xp SET lastVoiceJoin = ? WHERE userId = ? AND guildId = ?', [Date.now(), userId, guildId]).catch(() => {}); } function stopTimers(key) { const userData = voiceUsers.get(key); if (userData) { clearInterval(userData.xpInterval); clearInterval(userData.timeInterval); voiceUsers.delete(key); } } async function updateState(member) { if (!member || member.user.bot) return; const guildId = member.guild.id; const userId = member.id; const key = `${guildId}_${userId}`; if (canGainXP(member)) { await startTimers(key, member, guildId, userId); } else { stopTimers(key); } } async function checkChannel(channel) { if (!channel) return; // Vérifier tous les membres du salon car l'arrivée/départ de quelqu'un change le statut "isAlone" for (const [id, member] of channel.members) { await updateState(member); } } module.exports = { name: Events.VoiceStateUpdate, async execute(oldState, newState) { try { // Cas 1: Join if (!oldState.channelId && newState.channelId) { await checkChannel(newState.channel); } // Cas 2: Leave else if (oldState.channelId && !newState.channelId) { const member = oldState.member; const key = `${member.guild.id}_${member.id}`; stopTimers(key); // Arrêt immédiat pour celui qui part await checkChannel(oldState.channel); // Vérif pour ceux qui restent } // Cas 3: Move else if (oldState.channelId && newState.channelId && oldState.channelId !== newState.channelId) { await checkChannel(oldState.channel); await checkChannel(newState.channel); } // Cas 4: Mute/Deaf update (Même channel) else if (oldState.channelId === newState.channelId) { await updateState(newState.member); } } catch (err) { console.error('Erreur VoiceStateUpdate:', err); } }, };