Update Bot

This commit is contained in:
2026-03-15 11:58:43 +01:00
parent b67c111ffc
commit cd99275933
560 changed files with 23173 additions and 55113 deletions

View File

@@ -1,143 +1,234 @@
/**
* 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 { Events } = require('discord.js');
const db = require('../functions/database/db.js');
const { addXP, getXPMultiplier, getXPProgress } = require('../functions/xp/xp.js');
const { colors } = require('../utils/constants');
const { addXP, getUserXP, getXPMultiplier } = require('../functions/xp/xp.js');
// Map<guildId_userId, { xpInterval, timeInterval }>
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);
}
}
// Stocker les utilisateurs en vocal par serveur
const voiceUsers = new Map(); // Map<guildId_userId, { joinTime, interval }>
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);
}
},
};
name: Events.VoiceStateUpdate,
async execute(oldState, newState) {
const userId = newState.member?.id || oldState.member?.id;
const guildId = newState.guild?.id || oldState.guild?.id;
if (!userId || !guildId) return;
// Ignorer les bots
if (newState.member?.user.bot || oldState.member?.user.bot) return;
const key = `${guildId}_${userId}`;
// Utilisateur rejoint un canal vocal
if (!oldState.channelId && newState.channelId) {
// L'utilisateur a rejoint un canal vocal
const joinTime = Date.now();
// Mettre à jour lastVoiceJoin dans la DB
await db.query(
'UPDATE user_xp SET lastVoiceJoin = ? WHERE userId = ? AND guildId = ?',
[joinTime, userId, guildId]
).catch(() => {
// L'utilisateur n'existe pas encore, on le créera lors du premier gain d'XP
});
// Créer un interval pour gagner de l'XP toutes les 10 minutes
const interval = setInterval(async () => {
try {
// Récupérer le serveur et le membre
const guild = newState.guild || oldState.guild;
if (!guild) {
clearInterval(interval);
voiceUsers.delete(key);
return;
}
const member = await guild.members.fetch(userId).catch(() => null);
if (!member || !member.voice.channelId) {
// L'utilisateur n'est plus en vocal, arrêter l'interval
clearInterval(interval);
voiceUsers.delete(key);
return;
}
// Vérifier si l'utilisateur est seul (anti-farming ultra strict)
// Ne pas donner d'XP si l'utilisateur est seul dans le salon (peu importe s'il est muet ou non)
const voiceChannel = member.voice.channel;
if (voiceChannel) {
// Compter le nombre de membres dans le salon (sans les bots)
const membersInChannel = voiceChannel.members.filter(m => !m.user.bot).size;
const isAlone = membersInChannel === 1; // Seul dans le salon
// Si l'utilisateur est seul, ne pas donner d'XP (anti-farming ultra strict)
if (isAlone) {
// Ne pas donner d'XP mais continuer le timer (il pourra gagner de l'XP si quelqu'un le rejoint)
return;
}
}
// Récupérer le multiplicateur (avec source='voice' pour le bonus vocal)
const multiplier = getXPMultiplier(member, 'voice');
// Gain d'XP vocal : 5-25 XP toutes les 10 minutes
const xpGained = Math.floor(Math.random() * 21) + 5; // 5-25 XP
// Ajouter l'XP
const result = await addXP(userId, guildId, xpGained, 'voice', multiplier);
if (result) {
// Mettre à jour totalVoiceTime (en minutes)
await db.query(
'UPDATE user_xp SET totalVoiceTime = totalVoiceTime + 10 WHERE userId = ? AND guildId = ?',
[userId, guildId]
);
// Si niveau supérieur, envoyer un message (optionnel, on peut le désactiver pour éviter le spam)
if (result.levelUp) {
const { EmbedBuilder } = require('discord.js');
const { colors } = require('../utils/constants');
const { getXPProgress } = require('../functions/xp/xp.js');
const progress = getXPProgress(result.newXP, result.newLevel);
const embed = new EmbedBuilder()
.setTitle('🎉 Level Up !')
.setDescription(`Félicitations ${member.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(member.user.displayAvatarURL({ dynamic: true }))
.setTimestamp();
// Envoyer dans le canal système (si disponible)
const systemChannel = guild.systemChannel;
if (systemChannel) {
await systemChannel.send({ embeds: [embed] }).catch(() => null);
}
}
}
} catch (err) {
console.error('Erreur lors du gain d\'XP vocal:', err);
}
}, 10 * 60 * 1000); // 10 minutes
voiceUsers.set(key, { joinTime, interval });
}
// Utilisateur quitte un canal vocal
if (oldState.channelId && !newState.channelId) {
// L'utilisateur a quitté le canal vocal
const userData = voiceUsers.get(key);
if (userData) {
clearInterval(userData.interval);
voiceUsers.delete(key);
// Calculer le temps passé en vocal et mettre à jour totalVoiceTime
const timeSpent = Math.floor((Date.now() - userData.joinTime) / 1000 / 60); // en minutes
if (timeSpent > 0) {
await db.query(
'UPDATE user_xp SET totalVoiceTime = totalVoiceTime + ? WHERE userId = ? AND guildId = ?',
[timeSpent, userId, guildId]
).catch(() => null);
}
}
}
// Utilisateur change de canal vocal
if (oldState.channelId && newState.channelId && oldState.channelId !== newState.channelId) {
// L'utilisateur a changé de canal vocal
// On récupère les données existantes et on met à jour le joinTime
const userData = voiceUsers.get(key);
if (userData) {
// Mettre à jour le joinTime pour le nouveau canal
userData.joinTime = Date.now();
// L'interval continue, pas besoin de le redémarrer
} else {
// Si pour une raison quelconque les données n'existent pas, créer un nouvel interval
// (ce cas ne devrait normalement pas arriver)
const joinTime = Date.now();
await db.query(
'UPDATE user_xp SET lastVoiceJoin = ? WHERE userId = ? AND guildId = ?',
[joinTime, userId, guildId]
).catch(() => null);
const interval = setInterval(async () => {
try {
const guild = newState.guild || oldState.guild;
if (!guild) {
clearInterval(interval);
voiceUsers.delete(key);
return;
}
const member = await guild.members.fetch(userId).catch(() => null);
if (!member || !member.voice.channelId) {
clearInterval(interval);
voiceUsers.delete(key);
return;
}
// Vérifier si l'utilisateur est seul (anti-farming ultra strict)
const voiceChannel = member.voice.channel;
if (voiceChannel) {
const membersInChannel = voiceChannel.members.filter(m => !m.user.bot).size;
const isAlone = membersInChannel === 1;
// Si l'utilisateur est seul, ne pas donner d'XP
if (isAlone) {
return;
}
}
const multiplier = getXPMultiplier(member, 'voice');
const xpGained = Math.floor(Math.random() * 21) + 5;
const result = await addXP(userId, guildId, xpGained, 'voice', multiplier);
if (result) {
await db.query(
'UPDATE user_xp SET totalVoiceTime = totalVoiceTime + 10 WHERE userId = ? AND guildId = ?',
[userId, guildId]
);
if (result.levelUp) {
const { EmbedBuilder } = require('discord.js');
const { colors } = require('../utils/constants');
const { getXPProgress } = require('../functions/xp/xp.js');
const progress = getXPProgress(result.newXP, result.newLevel);
const embed = new EmbedBuilder()
.setTitle('🎉 Level Up !')
.setDescription(`Félicitations ${member.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(member.user.displayAvatarURL({ dynamic: true }))
.setTimestamp();
const systemChannel = guild.systemChannel;
if (systemChannel) {
await systemChannel.send({ embeds: [embed] }).catch(() => null);
}
}
}
} catch (err) {
console.error('Erreur lors du gain d\'XP vocal:', err);
}
}, 10 * 60 * 1000);
voiceUsers.set(key, { joinTime, interval });
}
}
},
};