Update Bot (j'ai plus le repo sur GitHub)
Qui c'est la conne qui a delete le repo sur GitHub? C'EST MOIIIII
This commit is contained in:
@@ -1,25 +1,118 @@
|
||||
const { Events, MessageFlags } = require('discord.js');
|
||||
const { Events, ModalBuilder, TextInputBuilder, TextInputStyle, ActionRowBuilder, MessageFlags } = require('discord.js');
|
||||
const chalk = require('chalk');
|
||||
|
||||
module.exports = {
|
||||
name: Events.InteractionCreate,
|
||||
async execute(interaction) {
|
||||
if (!interaction.isChatInputCommand()) return;
|
||||
|
||||
const command = interaction.client.commands.get(interaction.commandName);
|
||||
|
||||
if (!command) {
|
||||
console.error(`No command matching ${interaction.commandName} was found.`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// --- 1. GESTION DES MODALS (Soumission) ---
|
||||
if (interaction.isModalSubmit()) {
|
||||
const ticketModule = require('../commands/ticket/ticket.js');
|
||||
|
||||
// 1.A - Création de Ticket (Support, Plainte, etc.)
|
||||
if (interaction.customId.startsWith('ticket_create_modal_')) {
|
||||
// On récupère le type depuis l'ID (ex: ticket_create_modal_Support)
|
||||
const type = interaction.customId.replace('ticket_create_modal_', '');
|
||||
await ticketModule.handleCreate(interaction, type);
|
||||
return;
|
||||
}
|
||||
|
||||
// 1.B - Candidature Spécifique
|
||||
if (interaction.customId === 'candidature_modal') {
|
||||
await ticketModule.handleCandidatureModalSubmit(interaction);
|
||||
return;
|
||||
}
|
||||
|
||||
// 1.C - Raison Fermeture
|
||||
if (interaction.customId === 'ticket_close_reason_modal') {
|
||||
await ticketModule.handleCloseModal(interaction);
|
||||
return;
|
||||
}
|
||||
|
||||
// 1.D - Raison Réouverture
|
||||
if (interaction.customId === 'ticket_reopen_modal') {
|
||||
await ticketModule.handleReopenModal(interaction);
|
||||
return;
|
||||
}
|
||||
|
||||
// 1.E - Raison Suppression
|
||||
if (interaction.customId === 'ticket_delete_modal') {
|
||||
await ticketModule.handleDeleteModal(interaction);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// --- 2. GESTION DES BOUTONS (Ouverture des Modals) ---
|
||||
if (interaction.isButton()) {
|
||||
const ticketModule = require('../commands/ticket/ticket.js');
|
||||
|
||||
// 2.A - Bouton Création : On ouvre un MODAL pour tout le monde
|
||||
if (interaction.customId.startsWith('ticket_create_')) {
|
||||
|
||||
// Cas Spécial : Candidature (Déjà géré avec ses questions spécifiques)
|
||||
if (interaction.customId === 'ticket_create_candidature') {
|
||||
const questions = require('../commands/ticket/ticket.js').CANDIDATURE_QUESTIONS;
|
||||
const modal = new ModalBuilder().setCustomId('candidature_modal').setTitle('Recrutement Staff');
|
||||
for(let i=0; i<Math.min(questions.length,5); i++) {
|
||||
const input = new TextInputBuilder().setCustomId(`cand_q${i+1}`).setLabel(questions[i].substring(0,45)).setStyle(TextInputStyle.Paragraph).setRequired(true);
|
||||
modal.addComponents(new ActionRowBuilder().addComponents(input));
|
||||
}
|
||||
await interaction.showModal(modal);
|
||||
return;
|
||||
}
|
||||
|
||||
// Cas Général (Support, Plainte, etc.): Modal simple "Sujet"
|
||||
// On extrait le type du bouton (ex: ticket_create_support -> support)
|
||||
let typeKey = interaction.customId.replace('ticket_create_', '');
|
||||
// On remet en joli format (support -> Support, plainte_staff -> Plainte Staff)
|
||||
const typeMapping = {
|
||||
'support': 'Support',
|
||||
'plainte': 'Plainte',
|
||||
'plainte_staff': 'Plainte Staff',
|
||||
'probleme_technique': 'Problème Technique'
|
||||
};
|
||||
const prettyType = typeMapping[typeKey] || 'Support';
|
||||
|
||||
// On passe le type dans l'ID du modal pour le récupérer au submit
|
||||
const modal = new ModalBuilder()
|
||||
.setCustomId(`ticket_create_modal_${prettyType}`)
|
||||
.setTitle(`Nouveau ticket : ${prettyType}`);
|
||||
|
||||
const subjectInput = new TextInputBuilder()
|
||||
.setCustomId('ticket_subject')
|
||||
.setLabel("Sujet / Raison de la demande")
|
||||
.setStyle(TextInputStyle.Paragraph)
|
||||
.setRequired(true)
|
||||
.setMaxLength(1000)
|
||||
.setPlaceholder("Décrivez brièvement votre problème...");
|
||||
|
||||
modal.addComponents(new ActionRowBuilder().addComponents(subjectInput));
|
||||
await interaction.showModal(modal);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2.B - Autres actions (Fermer, Réouvrir, etc.)
|
||||
if (interaction.customId.startsWith('ticket_close_')) return await ticketModule.handleClose(interaction);
|
||||
if (interaction.customId.startsWith('ticket_delete_')) return await ticketModule.handleDelete(interaction);
|
||||
if (interaction.customId.startsWith('ticket_reopen_')) return await ticketModule.handleReopen(interaction);
|
||||
if (interaction.customId.startsWith('ticket_claim_')) return await ticketModule.handleClaim(interaction);
|
||||
}
|
||||
|
||||
// --- 3. COMMANDES SLASH ---
|
||||
if (!interaction.isChatInputCommand()) return;
|
||||
const command = interaction.client.commands.get(interaction.commandName);
|
||||
if (!command) {
|
||||
console.error(chalk.red(`Aucune commande correspondant à ${interaction.commandName} n'a été trouvée.`));
|
||||
return;
|
||||
}
|
||||
await command.execute(interaction);
|
||||
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
console.error(chalk.red(`Erreur lors de l'exécution d'une interaction :`), error);
|
||||
if (interaction.replied || interaction.deferred) {
|
||||
await interaction.followUp({ content: 'There was an error while executing this command!', flags: MessageFlags.Ephemeral });
|
||||
await interaction.followUp({ content: 'Une erreur est survenue lors de l\'exécution de cette interaction !', flags: MessageFlags.Ephemeral });
|
||||
} else {
|
||||
await interaction.reply({ content: 'There was an error while executing this command!', flags: MessageFlags.Ephemeral });
|
||||
await interaction.reply({ content: 'Une erreur est survenue lors de l\'exécution de cette interaction !', flags: MessageFlags.Ephemeral });
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
162
events/messageCreate.js
Normal file
162
events/messageCreate.js
Normal file
@@ -0,0 +1,162 @@
|
||||
const { Events, EmbedBuilder } = require('discord.js');
|
||||
const db = require('../functions/database/db.js');
|
||||
const { addXP, isChannelExcluded, getUserXP, getXPMultiplier, getXPProgress } = require('../functions/xp/xp.js');
|
||||
const { colors } = require('../utils/constants');
|
||||
|
||||
// --- FONCTIONS UTILITAIRES ---
|
||||
|
||||
async function detectBump(message) {
|
||||
const bumpBots = ['302050872383242240']; // Disboard bot ID
|
||||
if (!message.author.bot || !bumpBots.includes(message.author.id)) return null;
|
||||
|
||||
const content = message.content?.toLowerCase() || '';
|
||||
const embed = message.embeds[0];
|
||||
if (!embed) return null;
|
||||
|
||||
const embedDescription = embed.description?.toLowerCase() || '';
|
||||
const embedTitle = embed.title?.toLowerCase() || '';
|
||||
const bumpKeywords = ['bump done', 'bump réussi', 'bump réalisé', 'bump effectué', 'serv bump', 'server bump'];
|
||||
|
||||
const isBumpMessage = bumpKeywords.some(keyword =>
|
||||
embedDescription.includes(keyword) ||
|
||||
embedTitle.includes(keyword) ||
|
||||
content.includes(keyword)
|
||||
);
|
||||
|
||||
if (!isBumpMessage) return null;
|
||||
|
||||
// Tentative de récupération de l'ID utilisateur
|
||||
if (message.mentions.users.size > 0) return message.mentions.users.first().id;
|
||||
|
||||
const userIdMatch = embedDescription.match(/<@!?(\d+)>/);
|
||||
if (userIdMatch) return userIdMatch[1];
|
||||
|
||||
if (embed.footer) {
|
||||
const footerMatch = embed.footer.text?.match(/<@!?(\d+)>/);
|
||||
if (footerMatch) return footerMatch[1];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function detectWelcomeMessage(message) {
|
||||
if (message.author.bot) return false;
|
||||
const welcomeKeywords = ['bienvenue', 'welcome', 'bvn', 'salut', 'hey', 'bonjour'];
|
||||
const content = message.content.toLowerCase();
|
||||
|
||||
return welcomeKeywords.some(k => content.includes(k)) &&
|
||||
message.mentions.users.size > 0 &&
|
||||
message.content.length < 100;
|
||||
}
|
||||
|
||||
async function handleLevelUp(message, result, member) {
|
||||
if (!result || !result.levelUp) return;
|
||||
|
||||
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}** !`)
|
||||
.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();
|
||||
|
||||
await message.channel.send({ embeds: [embed] });
|
||||
}
|
||||
|
||||
// --- LOGIQUE PRINCIPALE ---
|
||||
|
||||
module.exports = {
|
||||
name: Events.MessageCreate,
|
||||
async execute(message) {
|
||||
if (!message.channel.isTextBased() || message.channel.isDMBased()) return;
|
||||
|
||||
try {
|
||||
// 1. GESTION DES BUMPS
|
||||
const bumpedUserId = await detectBump(message);
|
||||
if (bumpedUserId) {
|
||||
try {
|
||||
await db.query('INSERT INTO bumps (userId, guildId, bumpTime, reminderSent) VALUES (?, ?, ?, ?)', [bumpedUserId, message.guild.id, Date.now(), false]);
|
||||
|
||||
const member = await message.guild.members.fetch(bumpedUserId).catch(() => null);
|
||||
if (member) {
|
||||
const xpGained = Math.floor(Math.random() * 51) + 50; // 50-100 XP
|
||||
const result = await addXP(bumpedUserId, message.guild.id, xpGained, 'bump', getXPMultiplier(member));
|
||||
await handleLevelUp(message, result, member);
|
||||
}
|
||||
} catch (err) { console.error('Erreur bump:', err); }
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. IGNORER LES BOTS (Sauf pour les bumps traités au-dessus)
|
||||
if (message.author.bot) return;
|
||||
|
||||
// 3. GESTION DES TICKETS
|
||||
const [tickets] = await db.query('SELECT * FROM tickets WHERE channelId = ? AND status = ?', [message.channel.id, 'Ouvert']);
|
||||
if (tickets.length > 0) {
|
||||
const ticket = tickets[0];
|
||||
const attachments = message.attachments.size > 0 ? message.attachments.map(a => a.url).join(', ') : null;
|
||||
|
||||
const maxTag = 100;
|
||||
const maxText = 65535;
|
||||
const safeUserTag = (message.author.tag && message.author.tag.length > maxTag) ? message.author.tag.substring(0, maxTag) : message.author.tag;
|
||||
const safeContent = (message.content && message.content.length > maxText) ? message.content.substring(0, maxText) : message.content;
|
||||
const safeAttachments = (attachments && attachments.length > maxText) ? attachments.substring(0, maxText) : attachments;
|
||||
|
||||
await db.query(
|
||||
`INSERT INTO ticket_messages (ticketId, messageId, userId, userTag, content, attachments, timestamp) VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
||||
[ticket.ticketId, message.id, message.author.id, safeUserTag, safeContent || null, safeAttachments, message.createdTimestamp]
|
||||
);
|
||||
return; // Pas d'XP dans les tickets
|
||||
}
|
||||
|
||||
// 4. SYSTÈME D'XP (Messages normaux)
|
||||
const excluded = await isChannelExcluded(message.channel.id, message.guild.id);
|
||||
if (!excluded) {
|
||||
const userXP = await getUserXP(message.author.id, message.guild.id);
|
||||
const now = Date.now();
|
||||
const cooldown = 15000; // 15s
|
||||
|
||||
let lastMessageTime = parseInt(userXP.lastMessageTime || 0, 10);
|
||||
// Reset si date invalide
|
||||
if (lastMessageTime > now || lastMessageTime < (now - 31536000000)) lastMessageTime = 0;
|
||||
|
||||
if (lastMessageTime === 0 || (now - lastMessageTime) >= cooldown) {
|
||||
const member = await message.guild.members.fetch(message.author.id).catch(() => null);
|
||||
const xpGained = Math.floor(Math.random() * 11) + 15; // 15-25 XP
|
||||
|
||||
const result = await addXP(message.author.id, message.guild.id, xpGained, 'message', member ? getXPMultiplier(member) : 1.0);
|
||||
if (member) await handleLevelUp(message, result, member);
|
||||
|
||||
// Mise à jour stats + timestamp
|
||||
await db.query('UPDATE user_xp SET lastMessageTime = ?, totalMessages = totalMessages + 1 WHERE userId = ? AND guildId = ?', [now, message.author.id, message.guild.id]);
|
||||
} else {
|
||||
// Juste incrémenter le compteur de messages (Cooldown actif)
|
||||
await db.query('UPDATE user_xp SET totalMessages = totalMessages + 1 WHERE userId = ? AND guildId = ?', [message.author.id, message.guild.id]);
|
||||
}
|
||||
}
|
||||
|
||||
// 5. MESSAGES DE BIENVENUE
|
||||
if (detectWelcomeMessage(message)) {
|
||||
try {
|
||||
await message.react('👋').catch(() => null);
|
||||
await message.react('🎉').catch(() => null);
|
||||
|
||||
const member = await message.guild.members.fetch(message.author.id).catch(() => null);
|
||||
if (member) {
|
||||
const xpGained = Math.floor(Math.random() * 21) + 20; // 20-40 XP
|
||||
const result = await addXP(message.author.id, message.guild.id, xpGained, 'welcome', getXPMultiplier(member));
|
||||
await handleLevelUp(message, result, member);
|
||||
}
|
||||
} catch (err) { console.error('Erreur bienvenue:', err); }
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
console.error('Erreur globale messageCreate:', err);
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -1,9 +1,15 @@
|
||||
const { Events } = require('discord.js');
|
||||
const { Events, ActivityType } = require('discord.js');
|
||||
const chalk = require('chalk');
|
||||
|
||||
module.exports = {
|
||||
name: Events.ClientReady,
|
||||
once: true,
|
||||
execute(client) {
|
||||
console.log(`Ready! Connectée en tant que ${client.user.tag}`);
|
||||
},
|
||||
name: Events.ClientReady,
|
||||
once: true,
|
||||
execute(client) {
|
||||
console.log(chalk.green(`✅ Connectée en tant que ${client.user.tag}`));
|
||||
console.log(chalk.cyan(`📊 ${client.guilds.cache.size} serveur(s) | ${client.users.cache.size} utilisateur(s)`));
|
||||
console.log(chalk.blue(`🔧 ${client.commands.size} commande(s) chargée(s)`));
|
||||
|
||||
// Statut du bot
|
||||
client.user.setActivity('France Femboy', { type: ActivityType.Watching });
|
||||
},
|
||||
};
|
||||
143
events/voiceStateUpdate.js
Normal file
143
events/voiceStateUpdate.js
Normal file
@@ -0,0 +1,143 @@
|
||||
/**
|
||||
* 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<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);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user