This commit is contained in:
2026-03-15 12:22:42 +01:00
parent cd99275933
commit 311ba5e7f3
558 changed files with 55182 additions and 22981 deletions

View File

@@ -4,85 +4,53 @@
*/
const chalk = require('chalk');
const { EmbedBuilder } = require('discord.js');
const { colors } = require('../../utils/constants');
module.exports = (client, db) => {
setInterval(async () => {
try {
const now = Date.now();
// Récupérer les bumps qui ont besoin d'un reminder (2h après, avec une fenêtre de 1 minute)
const twoHours = 2 * 60 * 60 * 1000;
const oneMinute = 60 * 1000;
const [bumps] = await db.query(
'SELECT * FROM bumps WHERE reminderSent = ? AND bumpTime <= ? AND bumpTime >= ?',
[false, now - twoHours + oneMinute, now - twoHours - oneMinute]
);
if (!bumps.length) return;
for (const bump of bumps) {
try {
const guild = client.guilds.cache.get(bump.guildId);
if (!guild) {
console.warn(chalk.yellow(`⚠️ Impossible d'envoyer le reminder de bump: le bot n'est pas dans la guild ${bump.guildId}`));
continue;
}
const member = await guild.members.fetch(bump.userId).catch(() => null);
if (!member) {
console.warn(chalk.yellow(`⚠️ Utilisateur ${bump.userId} introuvable dans la guild ${guild.id}`));
continue;
}
// Trouver le canal de bump (ou utiliser le canal système)
// On cherche dans les canaux de bump configurés, sinon on utilise le canal système
const [bumpChannels] = await db.query(
'SELECT * FROM bump_channels WHERE guildId = ?',
[bump.guildId]
);
let channel = null;
if (bumpChannels.length > 0) {
// Utiliser le premier canal de bump configuré
channel = guild.channels.cache.get(bumpChannels[0].channelId);
}
// Si pas de canal de bump configuré, utiliser le canal système
if (!channel) {
channel = guild.systemChannel;
}
if (!channel) {
console.warn(chalk.yellow(`⚠️ Aucun canal disponible pour envoyer le reminder de bump dans la guild ${guild.id}`));
continue;
}
const { EmbedBuilder } = require('discord.js');
const { colors } = require('../../utils/constants');
const reminderEmbed = new EmbedBuilder()
.setTitle('⏰ Rappel de Bump')
.setDescription(`${member.toString()}, tu peux maintenant refaire un bump !`)
.setColor(colors.info)
.setFooter({ text: 'Utilise /bump (Disboard) pour bump le serveur • /bumptime pour vérifier' })
.setTimestamp();
await channel.send({ embeds: [reminderEmbed] });
// Marquer le reminder comme envoyé
await db.query(
'UPDATE bumps SET reminderSent = ? WHERE id = ?',
[true, bump.id]
);
console.log(chalk.green(`✅ Reminder de bump envoyé à ${member.user.tag} dans la guild ${guild.name}`));
} catch (err) {
console.error(chalk.red(`❌ Erreur lors de l'envoi du reminder de bump ${bump.id}:`), err);
}
}
} catch (err) {
console.error(chalk.red('❌ Erreur lors de la vérification des reminders de bump:'), err);
}
}, 60 * 1000); // Vérifier toutes les minutes
};
setInterval(async () => {
try {
const now = Date.now();
const twoHours = 2 * 60 * 60 * 1000;
// Récupérer tous les bumps de plus de 2h qui n'ont pas encore eu de rappel
const [bumps] = await db.query(
'SELECT * FROM bumps WHERE reminderSent = ? AND bumpTime <= ?',
[false, now - twoHours]
);
if (!bumps.length) return;
for (const bump of bumps) {
try {
const guild = client.guilds.cache.get(bump.guildId);
if (!guild) continue;
const member = await guild.members.fetch(bump.userId).catch(() => null);
if (!member) continue;
const [bumpChannels] = await db.query('SELECT * FROM bump_channels WHERE guildId = ?', [bump.guildId]);
let channel = bumpChannels.length > 0 ? guild.channels.cache.get(bumpChannels[0].channelId) : guild.systemChannel;
if (!channel) continue;
const reminderEmbed = new EmbedBuilder()
.setTitle('⏰ Rappel de Bump')
.setDescription(`${member.toString()}, tu peux maintenant refaire un bump !`)
.setColor(colors.info)
.setFooter({ text: 'Utilise /bump (Disboard) pour bump le serveur • /bumptime pour vérifier' })
.setTimestamp();
await channel.send({ embeds: [reminderEmbed] });
await db.query('UPDATE bumps SET reminderSent = ? WHERE id = ?', [true, bump.id]);
console.log(chalk.green(`✅ Reminder de bump envoyé à ${member.user.tag} dans la guild ${guild.name}`));
} catch (err) {
console.error(chalk.red(`❌ Erreur reminder bump ${bump.id}:`), err);
}
}
} catch (err) {
console.error(chalk.red('❌ Erreur vérification reminders bump:'), err);
}
}, 60 * 1000);
};

View File

@@ -1,10 +1,10 @@
const mysql = require('mysql2/promise');
const pool = mysql.createPool({
host: process.env.DB_HOST || '192.168.1.6',
user: process.env.DB_USER || 'bot',
password: process.env.DB_PASSWORD || 'NxKr63LJB65pHv%t7E$JqgxKRsZMw%VIHEZAjq%^O0KYKjW#cRc^ebIH@%S9kaTh*GIg^D3ai4KBjMeXEh6xwv#9afQIR2$!2UB8C3ToXjnYFmzR%$lfpshnf8g@8229',
database: process.env.DB_NAME || 'bot',
host: process.env.DB_HOST || '192.168.1.6',
user: process.env.DB_USER || 'bot',
password: process.env.DB_PASSWORD || 'NxKr63LJB65pHv%t7E$JqgxKRsZMw%VIHEZAjq%^O0KYKjW#cRc^ebIH@%S9kaTh*GIg^D3ai4KBjMeXEh6xwv#9afQIR2$!2UB8C3ToXjnYFmzR%$lfpshnf8g@8229',
database: process.env.DB_NAME || 'bot',
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0
@@ -13,28 +13,20 @@ const pool = mysql.createPool({
async function initDB() {
const conn = await pool.getConnection();
try {
// Tables de modération
await conn.query(`
CREATE TABLE IF NOT EXISTS bans (
userId VARCHAR(32),
guildId VARCHAR(32),
userId VARCHAR(32),
guildId VARCHAR(32),
reason TEXT,
modId VARCHAR(32),
timestamp BIGINT,
type VARCHAR(20) NOT NULL,
unbanDate BIGINT,
PRIMARY KEY (userId, guildId)
)
unbanDate BIGINT,
PRIMARY KEY (userId, guildId)
)
`);
// Ajouter la colonne guildId si elle n'existe pas (migration)
try {
await conn.query('ALTER TABLE bans ADD COLUMN guildId VARCHAR(32)');
await conn.query('ALTER TABLE bans DROP PRIMARY KEY');
await conn.query('ALTER TABLE bans ADD PRIMARY KEY (userId, guildId)');
} catch {
// La colonne existe déjà, on continue
}
await conn.query(`
CREATE TABLE IF NOT EXISTS logs (
id INT AUTO_INCREMENT PRIMARY KEY,
@@ -42,231 +34,203 @@ async function initDB() {
userTag VARCHAR(100),
modId VARCHAR(32),
modTag VARCHAR(100),
action VARCHAR(50),
action VARCHAR(50),
reason TEXT,
type ENUM('Permanent','Temporary') DEFAULT 'Permanent',
guildId VARCHAR(32),
timestamp BIGINT
)
`);
// Ajouter la colonne guildId si elle n'existe pas (migration)
try {
await conn.query('ALTER TABLE logs ADD COLUMN guildId VARCHAR(32)');
} catch {
// La colonne existe déjà, on continue
}
// Agrandir la colonne action si elle est trop petite (migration)
try {
await conn.query('ALTER TABLE logs MODIFY COLUMN action VARCHAR(50)');
} catch {
// La colonne est déjà de la bonne taille ou n'existe pas, on continue
}
// Table pour les mutes
await conn.query(`
CREATE TABLE IF NOT EXISTS mutes (
userId VARCHAR(32),
guildId VARCHAR(32),
reason TEXT,
modId VARCHAR(32),
modTag VARCHAR(100),
timestamp BIGINT,
unmuteDate BIGINT,
type VARCHAR(20) NOT NULL,
PRIMARY KEY (userId, guildId)
)
`);
// Table pour les messages de ban personnalisés
await conn.query(`
CREATE TABLE IF NOT EXISTS staffbanmessages (
userId VARCHAR(32) PRIMARY KEY,
userTag VARCHAR(100),
message TEXT,
guildId VARCHAR(32),
timestamp BIGINT
)
)
`);
// Table pour les tickets
await conn.query(`
CREATE TABLE IF NOT EXISTS tickets (
id INT AUTO_INCREMENT PRIMARY KEY,
ticketId VARCHAR(20) UNIQUE NOT NULL,
channelId VARCHAR(32) UNIQUE NOT NULL,
userId VARCHAR(32) NOT NULL,
userTag VARCHAR(100),
guildId VARCHAR(32) NOT NULL,
type ENUM('Support', 'Plainte', 'Plainte Staff', 'Candidature', 'Problème Technique') NOT NULL,
status ENUM('Ouvert', 'Fermé', 'En attente', 'Supprimé') DEFAULT 'Ouvert',
createdAt BIGINT NOT NULL,
closedAt BIGINT,
closedBy VARCHAR(32),
transcript TEXT,
transcriptPath VARCHAR(255),
INDEX idx_userId (userId),
INDEX idx_guildId (guildId),
INDEX idx_status (status)
)
`);
await conn.query(`
CREATE TABLE IF NOT EXISTS mutes (
userId VARCHAR(32),
guildId VARCHAR(32),
reason TEXT,
modId VARCHAR(32),
modTag VARCHAR(100),
timestamp BIGINT,
unmuteDate BIGINT,
type VARCHAR(20) NOT NULL,
PRIMARY KEY (userId, guildId)
)
`);
// Ajouter la colonne transcriptPath si elle n'existe pas (migration)
try {
await conn.query('ALTER TABLE tickets ADD COLUMN transcriptPath VARCHAR(255)');
} catch {
// La colonne existe déjà, on continue
}
await conn.query(`
CREATE TABLE IF NOT EXISTS staffbanmessages (
userId VARCHAR(32) PRIMARY KEY,
userTag VARCHAR(100),
message TEXT,
timestamp BIGINT
)
`);
// Ajouter la colonne claimedBy si elle n'existe pas (migration)
try {
await conn.query('ALTER TABLE tickets ADD COLUMN claimedBy VARCHAR(32)');
} catch {
// La colonne existe déjà, on continue
}
// Tables de tickets
await conn.query(`
CREATE TABLE IF NOT EXISTS tickets (
id INT AUTO_INCREMENT PRIMARY KEY,
ticketId VARCHAR(20) UNIQUE NOT NULL,
channelId VARCHAR(32) UNIQUE NOT NULL,
userId VARCHAR(32) NOT NULL,
userTag VARCHAR(100),
guildId VARCHAR(32) NOT NULL,
type ENUM('Support', 'Plainte', 'Plainte Staff', 'Candidature', 'Problème Technique') NOT NULL,
status ENUM('Ouvert', 'Fermé', 'En attente', 'Supprimé') DEFAULT 'Ouvert',
createdAt BIGINT NOT NULL,
closedAt BIGINT,
closedBy VARCHAR(32),
transcript TEXT,
transcriptPath VARCHAR(255),
claimedBy VARCHAR(32),
claimedAt BIGINT,
claimedByTag VARCHAR(100),
INDEX idx_userId (userId),
INDEX idx_guildId (guildId),
INDEX idx_status (status),
INDEX idx_claimedBy (claimedBy)
)
`);
// Ajouter la colonne claimedAt si elle n'existe pas (migration)
try {
await conn.query('ALTER TABLE tickets ADD COLUMN claimedAt BIGINT');
} catch {
// La colonne existe déjà, on continue
}
await conn.query(`
CREATE TABLE IF NOT EXISTS ticket_messages (
id INT AUTO_INCREMENT PRIMARY KEY,
ticketId VARCHAR(20) NOT NULL,
messageId VARCHAR(32) NOT NULL,
userId VARCHAR(32) NOT NULL,
userTag VARCHAR(100),
content TEXT,
attachments TEXT,
timestamp BIGINT NOT NULL,
INDEX idx_ticketId (ticketId),
INDEX idx_messageId (messageId)
)
`);
// Ajouter la colonne claimedByTag si elle n'existe pas (migration)
try {
await conn.query('ALTER TABLE tickets ADD COLUMN claimedByTag VARCHAR(100)');
} catch {
// La colonne existe déjà, on continue
}
await conn.query(`
CREATE TABLE IF NOT EXISTS candidature_responses (
id INT AUTO_INCREMENT PRIMARY KEY,
ticketId VARCHAR(20) NOT NULL,
questionNumber INT NOT NULL,
question TEXT NOT NULL,
response TEXT,
timestamp BIGINT NOT NULL,
INDEX idx_ticketId (ticketId)
)
`);
// Ajouter l'index pour claimedBy si elle n'existe pas (migration)
try {
await conn.query('CREATE INDEX idx_claimedBy ON tickets(claimedBy)');
} catch {
// L'index existe déjà, on continue
}
// Tables XP
await conn.query(`
CREATE TABLE IF NOT EXISTS user_xp (
userId VARCHAR(32) NOT NULL,
guildId VARCHAR(32) NOT NULL,
xp INT DEFAULT 0,
level INT DEFAULT 0,
lastMessageTime BIGINT DEFAULT 0,
totalMessages INT DEFAULT 0,
totalVoiceTime INT DEFAULT 0,
lastVoiceJoin BIGINT DEFAULT 0,
lastBumpTime BIGINT DEFAULT 0,
PRIMARY KEY (userId, guildId),
INDEX idx_guildId (guildId),
INDEX idx_xp (xp),
INDEX idx_level (level)
)
`);
// Table pour les messages des tickets (pour transcription)
await conn.query(`
CREATE TABLE IF NOT EXISTS ticket_messages (
id INT AUTO_INCREMENT PRIMARY KEY,
ticketId VARCHAR(20) NOT NULL,
messageId VARCHAR(32) NOT NULL,
userId VARCHAR(32) NOT NULL,
userTag VARCHAR(100),
content TEXT,
attachments TEXT,
timestamp BIGINT NOT NULL,
INDEX idx_ticketId (ticketId),
INDEX idx_messageId (messageId)
)
`);
await conn.query(`
CREATE TABLE IF NOT EXISTS xp_excluded_channels (
channelId VARCHAR(32) NOT NULL,
guildId VARCHAR(32) NOT NULL,
reason VARCHAR(255),
addedBy VARCHAR(32),
addedAt BIGINT,
PRIMARY KEY (channelId, guildId),
INDEX idx_guildId (guildId)
)
`);
// Table pour les réponses aux questions de candidature
await conn.query(`
CREATE TABLE IF NOT EXISTS candidature_responses (
id INT AUTO_INCREMENT PRIMARY KEY,
ticketId VARCHAR(20) NOT NULL,
questionNumber INT NOT NULL,
question TEXT NOT NULL,
response TEXT,
timestamp BIGINT NOT NULL,
INDEX idx_ticketId (ticketId)
)
`);
await conn.query(`
CREATE TABLE IF NOT EXISTS xp_logs (
id INT AUTO_INCREMENT PRIMARY KEY,
userId VARCHAR(32) NOT NULL,
guildId VARCHAR(32) NOT NULL,
xpGained INT NOT NULL,
source VARCHAR(50) NOT NULL,
multiplier DECIMAL(3,2) DEFAULT 1.00,
timestamp BIGINT NOT NULL,
INDEX idx_userId (userId),
INDEX idx_guildId (guildId),
INDEX idx_timestamp (timestamp)
)
`);
// Table pour les niveaux/XP des utilisateurs
await conn.query(`
CREATE TABLE IF NOT EXISTS user_xp (
userId VARCHAR(32) NOT NULL,
guildId VARCHAR(32) NOT NULL,
xp INT DEFAULT 0,
level INT DEFAULT 0,
lastMessageTime BIGINT DEFAULT 0,
totalMessages INT DEFAULT 0,
totalVoiceTime INT DEFAULT 0,
lastVoiceJoin BIGINT DEFAULT 0,
lastBumpTime BIGINT DEFAULT 0,
PRIMARY KEY (userId, guildId),
INDEX idx_guildId (guildId),
INDEX idx_xp (xp),
INDEX idx_level (level)
)
`);
// Tables Bump
await conn.query(`
CREATE TABLE IF NOT EXISTS bumps (
id INT AUTO_INCREMENT PRIMARY KEY,
userId VARCHAR(32) NOT NULL,
guildId VARCHAR(32) NOT NULL,
bumpTime BIGINT NOT NULL,
reminderSent BOOLEAN DEFAULT FALSE,
INDEX idx_userId (userId),
INDEX idx_guildId (guildId),
INDEX idx_bumpTime (bumpTime)
)
`);
// Table pour les salons exclus de l'XP
await conn.query(`
CREATE TABLE IF NOT EXISTS xp_excluded_channels (
channelId VARCHAR(32) NOT NULL,
guildId VARCHAR(32) NOT NULL,
reason VARCHAR(255),
addedBy VARCHAR(32),
addedAt BIGINT,
PRIMARY KEY (channelId, guildId),
INDEX idx_guildId (guildId)
)
`);
await conn.query(`
CREATE TABLE IF NOT EXISTS bump_channels (
channelId VARCHAR(32) NOT NULL,
guildId VARCHAR(32) NOT NULL,
bumpBotId VARCHAR(32),
addedBy VARCHAR(32),
addedAt BIGINT,
PRIMARY KEY (channelId, guildId),
INDEX idx_guildId (guildId)
)
`);
// Table pour les logs de gain d'XP (optionnel, pour debugging)
await conn.query(`
CREATE TABLE IF NOT EXISTS xp_logs (
id INT AUTO_INCREMENT PRIMARY KEY,
userId VARCHAR(32) NOT NULL,
guildId VARCHAR(32) NOT NULL,
xpGained INT NOT NULL,
source VARCHAR(50) NOT NULL,
multiplier DECIMAL(3,2) DEFAULT 1.00,
timestamp BIGINT NOT NULL,
INDEX idx_userId (userId),
INDEX idx_guildId (guildId),
INDEX idx_timestamp (timestamp)
)
`);
// Tables Profils
await conn.query(`
CREATE TABLE IF NOT EXISTS user_profiles (
userId VARCHAR(32) NOT NULL,
guildId VARCHAR(32) NOT NULL,
signature TEXT,
birthday DATE,
color VARCHAR(7),
gender VARCHAR(50),
location VARCHAR(100),
updatedAt BIGINT,
PRIMARY KEY (userId, guildId),
INDEX idx_guildId (guildId)
)
`);
// Table pour stocker les bumps (pour reminder et XP)
await conn.query(`
CREATE TABLE IF NOT EXISTS bumps (
id INT AUTO_INCREMENT PRIMARY KEY,
userId VARCHAR(32) NOT NULL,
guildId VARCHAR(32) NOT NULL,
bumpTime BIGINT NOT NULL,
reminderSent BOOLEAN DEFAULT FALSE,
INDEX idx_userId (userId),
INDEX idx_guildId (guildId),
INDEX idx_bumpTime (bumpTime)
)
`);
// Migrations (pour compatibilité existante)
// On utilise la syntaxe standard sans IF NOT EXISTS pour compatibilité maximale
try {
await conn.query('ALTER TABLE bans ADD COLUMN guildId VARCHAR(32)');
await conn.query('ALTER TABLE bans DROP PRIMARY KEY');
await conn.query('ALTER TABLE bans ADD PRIMARY KEY (userId, guildId)');
} catch {}
try { await conn.query('ALTER TABLE logs ADD COLUMN guildId VARCHAR(32)'); } catch {}
try { await conn.query('ALTER TABLE logs MODIFY COLUMN action VARCHAR(50)'); } catch {}
try { await conn.query('ALTER TABLE tickets ADD COLUMN transcriptPath VARCHAR(255)'); } catch {}
try { await conn.query('ALTER TABLE tickets ADD COLUMN claimedBy VARCHAR(32)'); } catch {}
try { await conn.query('ALTER TABLE tickets ADD COLUMN claimedAt BIGINT'); } catch {}
try { await conn.query('ALTER TABLE tickets ADD COLUMN claimedByTag VARCHAR(100)'); } catch {}
// Migrations Profils (Sans IF NOT EXISTS)
try { await conn.query('ALTER TABLE user_profiles ADD COLUMN color VARCHAR(7)'); } catch {}
try { await conn.query('ALTER TABLE user_profiles ADD COLUMN gender VARCHAR(50)'); } catch {}
try { await conn.query('ALTER TABLE user_profiles ADD COLUMN location VARCHAR(100)'); } catch {}
// Table pour stocker les salons de bump (configuration)
await conn.query(`
CREATE TABLE IF NOT EXISTS bump_channels (
channelId VARCHAR(32) NOT NULL,
guildId VARCHAR(32) NOT NULL,
bumpBotId VARCHAR(32),
addedBy VARCHAR(32),
addedAt BIGINT,
PRIMARY KEY (channelId, guildId),
INDEX idx_guildId (guildId)
)
`);
// Table pour les profils utilisateurs (signatures, etc.)
await conn.query(`
CREATE TABLE IF NOT EXISTS user_profiles (
userId VARCHAR(32) NOT NULL,
guildId VARCHAR(32) NOT NULL,
signature TEXT,
birthday DATE,
updatedAt BIGINT,
PRIMARY KEY (userId, guildId),
INDEX idx_guildId (guildId)
)
`);
} finally {
conn.release();
}
} finally {
conn.release();
}
}
initDB().catch(console.error);
module.exports = pool;
module.exports = pool;

View File

@@ -1,43 +1,41 @@
const chalk = require('chalk');
module.exports = (client, db) => {
setInterval(async () => {
try {
const now = Date.now();
const [expiredBans] = await db.query(
'SELECT * FROM bans WHERE type = ? AND unbanDate <= ?',
['Temporary', now]
);
setInterval(async () => {
try {
const now = Date.now();
const [expiredBans] = await db.query(
'SELECT * FROM bans WHERE type = ? AND unbanDate <= ?',
['Temporary', now]
);
if (!expiredBans.length) return;
if (!expiredBans.length) return;
for (const ban of expiredBans) {
const guild = client.guilds.cache.get(ban.guildId);
if (!guild) {
console.warn(chalk.yellow(`⚠️ Impossible de débannir ${ban.userId}: le bot n'est pas dans la guild ${ban.guildId}`));
continue;
}
for (const ban of expiredBans) {
const guild = client.guilds.cache.get(ban.guildId);
if (!guild) {
console.warn(chalk.yellow(`⚠️ Impossible de débannir ${ban.userId}: le bot n'est pas dans la guild ${ban.guildId}`));
continue;
}
try {
await guild.members.unban(ban.userId, 'Bannissement temporaire expiré');
console.log(chalk.green(`✅ Auto-débanni ${ban.userId} de la guild ${guild.name} (${guild.id})`));
} catch (err) {
console.error(chalk.red(`❌ Échec du débannissement de ${ban.userId} de la guild ${guild.id}:`), err.message);
}
try {
await guild.members.unban(ban.userId, 'Bannissement temporaire expiré');
console.log(chalk.green(`✅ Auto-débanni ${ban.userId} de la guild ${guild.name} (${guild.id})`));
} catch (err) {
console.error(chalk.red(`❌ Échec du débannissement de ${ban.userId} de la guild ${guild.id}:`), err.message);
}
// Supprime uniquement le ban correspondant à cette guild
await db.query('DELETE FROM bans WHERE userId = ? AND guildId = ?', [ban.userId, ban.guildId]);
await db.query('DELETE FROM bans WHERE userId = ? AND guildId = ?', [ban.userId, ban.guildId]);
// Log automatique
await db.query(
`INSERT INTO logs (userId, userTag, modId, modTag, action, reason, type, timestamp, guildId)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
[ban.userId, null, client.user.id, client.user.tag, 'Débannissement automatique', 'Bannissement temporaire expiré', 'Temporary', Date.now(), ban.guildId]
);
}
await db.query(
`INSERT INTO logs (userId, userTag, modId, modTag, action, reason, type, timestamp, guildId)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
[ban.userId, null, client.user.id, client.user.tag, 'Débannissement automatique', 'Bannissement temporaire expiré', 'Temporary', Date.now(), ban.guildId]
);
}
} catch (err) {
console.error(chalk.red('❌ Erreur lors de l\'auto-débannissement:'), err);
}
}, 60_000);
};
} catch (err) {
console.error(chalk.red('❌ Erreur lors de l\'auto-débannissement:'), err);
}
}, 60_000);
};

View File

@@ -1,51 +1,47 @@
const chalk = require('chalk');
module.exports = (client, db) => {
setInterval(async () => {
try {
const now = Date.now();
const [expiredMutes] = await db.query(
'SELECT * FROM mutes WHERE type = ? AND unmuteDate <= ?',
['Temporary', now]
);
setInterval(async () => {
try {
const now = Date.now();
const [expiredMutes] = await db.query(
'SELECT * FROM mutes WHERE type = ? AND unmuteDate <= ?',
['Temporary', now]
);
if (!expiredMutes.length) return;
if (!expiredMutes.length) return;
for (const mute of expiredMutes) {
const guild = client.guilds.cache.get(mute.guildId);
if (!guild) {
console.warn(chalk.yellow(`⚠️ Impossible de démuter ${mute.userId}: le bot n'est pas dans la guild ${mute.guildId}`));
continue;
}
for (const mute of expiredMutes) {
const guild = client.guilds.cache.get(mute.guildId);
if (!guild) {
console.warn(chalk.yellow(`⚠️ Impossible de démuter ${mute.userId}: le bot n'est pas dans la guild ${mute.guildId}`));
continue;
}
const member = await guild.members.fetch(mute.userId).catch(() => null);
if (!member) {
// L'utilisateur n'est plus sur le serveur, on supprime quand même le mute
await db.query('DELETE FROM mutes WHERE userId = ? AND guildId = ?', [mute.userId, mute.guildId]);
continue;
}
const member = await guild.members.fetch(mute.userId).catch(() => null);
if (!member) {
await db.query('DELETE FROM mutes WHERE userId = ? AND guildId = ?', [mute.userId, mute.guildId]);
continue;
}
try {
await member.timeout(null, 'Mute temporaire expiré');
console.log(chalk.green(`✅ Auto-démuté ${mute.userId} de la guild ${guild.name} (${guild.id})`));
} catch (err) {
console.error(chalk.red(`❌ Échec du démute de ${mute.userId} de la guild ${guild.id}:`), err.message);
}
try {
await member.timeout(null, 'Mute temporaire expiré');
console.log(chalk.green(`✅ Auto-démuté ${mute.userId} de la guild ${guild.name} (${guild.id})`));
} catch (err) {
console.error(chalk.red(`❌ Échec du démute de ${mute.userId} de la guild ${guild.id}:`), err.message);
}
// Supprime le mute de la DB
await db.query('DELETE FROM mutes WHERE userId = ? AND guildId = ?', [mute.userId, mute.guildId]);
await db.query('DELETE FROM mutes WHERE userId = ? AND guildId = ?', [mute.userId, mute.guildId]);
// Log automatique
await db.query(
`INSERT INTO logs (userId, userTag, modId, modTag, action, reason, type, timestamp, guildId)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
[mute.userId, null, client.user.id, client.user.tag, 'Unmute automatique', 'Mute temporaire expiré', 'Temporary', Date.now(), mute.guildId]
);
}
} catch (err) {
console.error(chalk.red('❌ Erreur lors de l\'auto-unmute:'), err);
}
}, 60_000);
};
await db.query(
`INSERT INTO logs (userId, userTag, modId, modTag, action, reason, type, timestamp, guildId)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
[mute.userId, null, client.user.id, client.user.tag, 'Unmute automatique', 'Mute temporaire expiré', 'Temporary', Date.now(), mute.guildId]
);
}
} catch (err) {
console.error(chalk.red('❌ Erreur lors de l\'auto-unmute:'), err);
}
}, 60_000);
};

View File

@@ -5,217 +5,173 @@
const db = require('../database/db.js');
// Formule de calcul de l'XP nécessaire pour atteindre un niveau
// XP total nécessaire = 5 * (level^2) + (50 * level) + 100
// Exemples :
// - Niveau 1 : 5 * 1^2 + 50 * 1 + 100 = 155 XP total
// - Niveau 2 : 5 * 2^2 + 50 * 2 + 100 = 220 XP total
// - Niveau 3 : 5 * 3^2 + 50 * 3 + 100 = 295 XP total
function getXPForLevel(level) {
if (level <= 0) return 0;
return Math.floor(5 * Math.pow(level, 2) + 50 * level + 100);
if (level <= 0) return 0;
return Math.floor(5 * Math.pow(level, 2) + 50 * level + 100);
}
// Calculer le niveau à partir de l'XP total
function getLevelFromXP(xp) {
if (xp < 0) return 0;
let level = 0;
let requiredXP = getXPForLevel(level + 1);
// Trouver le niveau en vérifiant l'XP nécessaire
while (xp >= requiredXP) {
level++;
requiredXP = getXPForLevel(level + 1);
}
return level;
if (xp < 0) return 0;
let level = 0;
let requiredXP = getXPForLevel(level + 1);
while (xp >= requiredXP) {
level++;
requiredXP = getXPForLevel(level + 1);
}
return level;
}
// Calculer l'XP nécessaire pour le prochain niveau
function getXPForNextLevel(level) {
return getXPForLevel(level + 1);
return getXPForLevel(level + 1);
}
// Calculer l'XP progress dans le niveau actuel
function getXPProgress(xp, level) {
// XP nécessaire pour atteindre le niveau actuel
const xpForCurrentLevel = getXPForLevel(level);
// XP nécessaire pour atteindre le prochain niveau
const xpForNextLevel = getXPForLevel(level + 1);
// XP dans le niveau actuel (différence entre l'XP total et l'XP du niveau actuel)
const xpInLevel = Math.max(0, xp - xpForCurrentLevel);
// XP nécessaire pour passer au niveau suivant
const xpNeeded = xpForNextLevel - xpForCurrentLevel;
// Éviter la division par zéro
if (xpNeeded <= 0) {
return {
current: xpInLevel,
needed: 100,
percentage: 0
};
}
const percentage = Math.max(0, Math.min(100, Math.floor((xpInLevel / xpNeeded) * 100)));
return {
current: xpInLevel,
needed: xpNeeded,
percentage: percentage
};
const xpForCurrentLevel = getXPForLevel(level);
const xpForNextLevel = getXPForLevel(level + 1);
const xpInLevel = Math.max(0, xp - xpForCurrentLevel);
const xpNeeded = xpForNextLevel - xpForCurrentLevel;
if (xpNeeded <= 0) {
return { current: xpInLevel, needed: 100, percentage: 0 };
}
const percentage = Math.max(0, Math.min(100, Math.floor((xpInLevel / xpNeeded) * 100)));
return { current: xpInLevel, needed: xpNeeded, percentage };
}
// Obtenir ou créer un utilisateur dans la table XP
async function getUserXP(userId, guildId) {
try {
const [rows] = await db.query(
'SELECT * FROM user_xp WHERE userId = ? AND guildId = ?',
[userId, guildId]
);
if (rows.length === 0) {
// Créer l'utilisateur avec 0 XP
await db.query(
'INSERT INTO user_xp (userId, guildId, xp, level) VALUES (?, ?, 0, 0)',
[userId, guildId]
);
return {
userId,
guildId,
xp: 0,
level: 0,
lastMessageTime: 0,
totalMessages: 0,
totalVoiceTime: 0,
lastVoiceJoin: 0,
lastBumpTime: 0
};
}
return rows[0];
} catch (err) {
console.error('Erreur lors de la récupération de l\'XP:', err);
return null;
}
try {
const [rows] = await db.query(
'SELECT * FROM user_xp WHERE userId = ? AND guildId = ?',
[userId, guildId]
);
if (rows.length === 0) {
await db.query(
'INSERT INTO user_xp (userId, guildId, xp, level) VALUES (?, ?, 0, 0)',
[userId, guildId]
);
return {
userId, guildId, xp: 0, level: 0,
lastMessageTime: 0, totalMessages: 0,
totalVoiceTime: 0, lastVoiceJoin: 0, lastBumpTime: 0
};
}
return rows[0];
} catch (err) {
console.error('Erreur getUserXP:', err);
return null;
}
}
// Ajouter de l'XP à un utilisateur
async function addXP(userId, guildId, xpGained, source = 'message', multiplier = 1.0) {
try {
const userXP = await getUserXP(userId, guildId);
if (!userXP) return null;
const finalXP = Math.floor(xpGained * multiplier);
const newXP = userXP.xp + finalXP;
const newLevel = getLevelFromXP(newXP);
// Mettre à jour l'XP et le niveau
await db.query(
'UPDATE user_xp SET xp = ?, level = ? WHERE userId = ? AND guildId = ?',
[newXP, newLevel, userId, guildId]
);
// Logger le gain d'XP (optionnel, pour debugging)
try {
await db.query(
'INSERT INTO xp_logs (userId, guildId, xpGained, source, multiplier, timestamp) VALUES (?, ?, ?, ?, ?, ?)',
[userId, guildId, finalXP, source, multiplier, Date.now()]
);
} catch (logErr) {
// Ignorer les erreurs de log
}
return {
oldXP: userXP.xp,
newXP,
oldLevel: userXP.level,
newLevel,
xpGained: finalXP,
levelUp: newLevel > userXP.level
};
} catch (err) {
console.error('Erreur lors de l\'ajout d\'XP:', err);
return null;
}
try {
const userXP = await getUserXP(userId, guildId);
if (!userXP) return null;
const finalXP = Math.floor(xpGained * multiplier);
const newXP = userXP.xp + finalXP;
const newLevel = getLevelFromXP(newXP);
await db.query(
'UPDATE user_xp SET xp = ?, level = ? WHERE userId = ? AND guildId = ?',
[newXP, newLevel, userId, guildId]
);
try {
await db.query(
'INSERT INTO xp_logs (userId, guildId, xpGained, source, multiplier, timestamp) VALUES (?, ?, ?, ?, ?, ?)',
[userId, guildId, finalXP, source, multiplier, Date.now()]
);
} catch {}
return {
oldXP: userXP.xp,
newXP,
oldLevel: userXP.level,
newLevel,
xpGained: finalXP,
levelUp: newLevel > userXP.level
};
} catch (err) {
console.error('Erreur addXP:', err);
return null;
}
}
// Vérifier si un salon est exclus de l'XP
async function isChannelExcluded(channelId, guildId) {
try {
const [rows] = await db.query(
'SELECT * FROM xp_excluded_channels WHERE channelId = ? AND guildId = ?',
[channelId, guildId]
);
return rows.length > 0;
} catch (err) {
console.error('Erreur lors de la vérification de l\'exclusion:', err);
return false;
}
try {
const [rows] = await db.query(
'SELECT * FROM xp_excluded_channels WHERE channelId = ? AND guildId = ?',
[channelId, guildId]
);
return rows.length > 0;
} catch (err) {
console.error('Erreur isChannelExcluded:', err);
return false;
}
}
// Obtenir le multiplicateur XP d'un utilisateur (booster, vocal, etc.)
// Obtenir le multiplicateur XP d'un utilisateur
function getXPMultiplier(member, source = 'message') {
let multiplier = 1.0;
// Vérifier si l'utilisateur est booster (1.2x)
if (member.premiumSince) {
multiplier *= 1.2;
}
// Multiplicateur vocal : 1.0x après 5 minutes de vocal (selon Issue #11)
// Note: Cette fonction est appelée toutes les 10 minutes, donc on applique déjà le bonus
if (source === 'voice') {
// Le bonus vocal est déjà appliqué car on gagne de l'XP toutes les 10 minutes
// Donc on considère qu'on a déjà passé plus de 5 minutes
multiplier *= 1.0; // Pas de changement, mais on pourrait ajouter un bonus ici
}
return multiplier;
let multiplier = 1.0;
if (member.premiumSince) multiplier *= 1.2;
return multiplier;
}
// Obtenir le classement des utilisateurs par XP
async function getLeaderboard(guildId, limit = 10) {
try {
const [rows] = await db.query(
'SELECT * FROM user_xp WHERE guildId = ? ORDER BY xp DESC LIMIT ?',
[guildId, limit]
);
return rows;
} catch (err) {
console.error('Erreur lors de la récupération du classement:', err);
return [];
}
try {
const [rows] = await db.query(
'SELECT * FROM user_xp WHERE guildId = ? ORDER BY xp DESC LIMIT ?',
[guildId, limit]
);
return rows;
} catch (err) {
console.error('Erreur getLeaderboard:', err);
return [];
}
}
// Obtenir la position d'un utilisateur dans le classement
async function getUserRank(userId, guildId) {
try {
const userXP = await getUserXP(userId, guildId);
if (!userXP) return 0;
const [rows] = await db.query(
'SELECT COUNT(*) as count FROM user_xp WHERE guildId = ? AND xp > ?',
[guildId, userXP.xp]
);
return (rows[0]?.count || 0) + 1;
} catch (err) {
console.error('Erreur lors de la récupération du rang:', err);
return 0;
}
try {
const userXP = await getUserXP(userId, guildId);
if (!userXP) return 0;
const [rows] = await db.query(
'SELECT COUNT(*) as count FROM user_xp WHERE guildId = ? AND xp > ?',
[guildId, userXP.xp]
);
return (rows[0]?.count || 0) + 1;
} catch (err) {
console.error('Erreur getUserRank:', err);
return 0;
}
}
module.exports = {
getXPForLevel,
getLevelFromXP,
getXPForNextLevel,
getXPProgress,
getUserXP,
addXP,
isChannelExcluded,
getXPMultiplier,
getLeaderboard,
getUserRank,
};
getXPForLevel,
getLevelFromXP,
getXPForNextLevel,
getXPProgress,
getUserXP,
addXP,
isChannelExcluded,
getXPMultiplier,
getLeaderboard,
getUserRank,
};