This commit is contained in:
Arthur Puechberty
2026-01-18 15:08:55 +01:00
parent 6de9768e3f
commit 3f1f3ba40d
25 changed files with 1994 additions and 3 deletions
+10 -2
View File
@@ -1,8 +1,16 @@
const db = require("./db"); const db = require("./db");
const { Client, GatewayIntentBits, Events } = require("discord.js"); const { Client, GatewayIntentBits, Events, Partials } = require("discord.js");
const client = new Client({ intents: Object.values(GatewayIntentBits) }); const client = new Client({
intents: Object.values(GatewayIntentBits),
partials: [
Partials.Message,
Partials.Channel,
Partials.GuildMember,
Partials.User
]
});
require("./loader/events.js")(client); require("./loader/events.js")(client);
require("./loader/commands.js")(client); require("./loader/commands.js")(client);
+22
View File
@@ -214,6 +214,28 @@ db.exec(`
CREATE INDEX IF NOT EXISTS idx_user_activity_stats_date ON user_activity_stats(guild_id, user_id, date); CREATE INDEX IF NOT EXISTS idx_user_activity_stats_date ON user_activity_stats(guild_id, user_id, date);
CREATE TABLE IF NOT EXISTS logs_config (
guild_id TEXT PRIMARY KEY,
enabled INTEGER NOT NULL DEFAULT 0,
category_id TEXT,
moderation_enabled INTEGER NOT NULL DEFAULT 0,
moderation_channel_id TEXT,
voice_enabled INTEGER NOT NULL DEFAULT 0,
voice_channel_id TEXT,
messages_enabled INTEGER NOT NULL DEFAULT 0,
messages_channel_id TEXT,
members_enabled INTEGER NOT NULL DEFAULT 0,
members_channel_id TEXT,
channels_enabled INTEGER NOT NULL DEFAULT 0,
channels_channel_id TEXT,
roles_enabled INTEGER NOT NULL DEFAULT 0,
roles_channel_id TEXT,
invites_enabled INTEGER NOT NULL DEFAULT 0,
invites_channel_id TEXT,
server_enabled INTEGER NOT NULL DEFAULT 0,
server_channel_id TEXT
);
CREATE TABLE IF NOT EXISTS scheduled_messages ( CREATE TABLE IF NOT EXISTS scheduled_messages (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
guild_id TEXT NOT NULL, guild_id TEXT NOT NULL,
+53
View File
@@ -0,0 +1,53 @@
const { AuditLogEvent, ChannelType } = require('discord.js');
const { sendLog } = require('../fonctions/sendLog');
const CHANNEL_TYPE_NAMES = {
[ChannelType.GuildText]: 'Salon textuel',
[ChannelType.GuildVoice]: 'Salon vocal',
[ChannelType.GuildCategory]: 'Catégorie',
[ChannelType.GuildAnnouncement]: 'Salon d\'annonces',
[ChannelType.GuildStageVoice]: 'Salon de conférence',
[ChannelType.GuildForum]: 'Forum',
[ChannelType.GuildMedia]: 'Salon média'
};
module.exports = {
name: 'channelCreate',
async execute(client, channel) {
if (!channel.guild) return;
let executor = null;
try {
const auditLogs = await channel.guild.fetchAuditLogs({
type: AuditLogEvent.ChannelCreate,
limit: 1
});
const createLog = auditLogs.entries.first();
if (createLog && createLog.target.id === channel.id && (Date.now() - createLog.createdTimestamp) < 5000) {
executor = createLog.executor;
}
} catch (err) {
console.error('Erreur récupération audit logs channel create:', err);
}
const typeName = CHANNEL_TYPE_NAMES[channel.type] || 'Salon';
const fields = [
{ name: '📁 Nom', value: channel.name, inline: true },
{ name: '🏷️ Type', value: typeName, inline: true }
];
if (channel.parent) {
fields.push({ name: '📂 Catégorie', value: channel.parent.name, inline: true });
}
await sendLog(client, channel.guild.id, 'channels', {
action: 'create',
title: '✅ Salon créé',
description: `Le salon ${channel} a été créé.`,
fields: fields,
executor: executor
});
}
};
+54
View File
@@ -0,0 +1,54 @@
const { AuditLogEvent, ChannelType } = require('discord.js');
const { sendLog } = require('../fonctions/sendLog');
const CHANNEL_TYPE_NAMES = {
[ChannelType.GuildText]: 'Salon textuel',
[ChannelType.GuildVoice]: 'Salon vocal',
[ChannelType.GuildCategory]: 'Catégorie',
[ChannelType.GuildAnnouncement]: 'Salon d\'annonces',
[ChannelType.GuildStageVoice]: 'Salon de conférence',
[ChannelType.GuildForum]: 'Forum',
[ChannelType.GuildMedia]: 'Salon média'
};
module.exports = {
name: 'channelDelete',
async execute(client, channel) {
if (!channel.guild) return;
let executor = null;
try {
const auditLogs = await channel.guild.fetchAuditLogs({
type: AuditLogEvent.ChannelDelete,
limit: 1
});
const deleteLog = auditLogs.entries.first();
if (deleteLog && deleteLog.target.id === channel.id && (Date.now() - deleteLog.createdTimestamp) < 5000) {
executor = deleteLog.executor;
}
} catch (err) {
console.error('Erreur récupération audit logs channel delete:', err);
}
const typeName = CHANNEL_TYPE_NAMES[channel.type] || 'Salon';
const fields = [
{ name: '📁 Nom', value: channel.name, inline: true },
{ name: '🏷️ Type', value: typeName, inline: true },
{ name: '🆔 ID', value: channel.id, inline: true }
];
if (channel.parent) {
fields.push({ name: '📂 Catégorie', value: channel.parent.name, inline: true });
}
await sendLog(client, channel.guild.id, 'channels', {
action: 'delete',
title: '🗑️ Salon supprimé',
description: `Le salon **#${channel.name}** a été supprimé.`,
fields: fields,
executor: executor
});
}
};
+77
View File
@@ -0,0 +1,77 @@
const { AuditLogEvent } = require('discord.js');
const { sendLog } = require('../fonctions/sendLog');
module.exports = {
name: 'channelUpdate',
async execute(client, oldChannel, newChannel) {
if (!newChannel.guild) return;
const changes = [];
// Changement de nom
if (oldChannel.name !== newChannel.name) {
changes.push({ name: '📝 Nom', value: `\`${oldChannel.name}\`\`${newChannel.name}\``, inline: false });
}
// Changement de topic (description)
if (oldChannel.topic !== newChannel.topic) {
const oldTopic = oldChannel.topic || '*Aucun*';
const newTopic = newChannel.topic || '*Aucun*';
changes.push({ name: '📄 Description', value: `${oldTopic.substring(0, 100)}${newTopic.substring(0, 100)}`, inline: false });
}
// Changement de catégorie
if (oldChannel.parentId !== newChannel.parentId) {
const oldParent = oldChannel.parent?.name || '*Aucune*';
const newParent = newChannel.parent?.name || '*Aucune*';
changes.push({ name: '📂 Catégorie', value: `${oldParent}${newParent}`, inline: false });
}
// Changement de slowmode
if (oldChannel.rateLimitPerUser !== newChannel.rateLimitPerUser) {
const formatSlowmode = (seconds) => {
if (seconds === 0) return 'Désactivé';
if (seconds < 60) return `${seconds}s`;
if (seconds < 3600) return `${Math.floor(seconds / 60)}m`;
return `${Math.floor(seconds / 3600)}h`;
};
changes.push({
name: '🐌 Slowmode',
value: `${formatSlowmode(oldChannel.rateLimitPerUser)}${formatSlowmode(newChannel.rateLimitPerUser)}`,
inline: true
});
}
// Changement NSFW
if (oldChannel.nsfw !== newChannel.nsfw) {
changes.push({ name: '🔞 NSFW', value: `${oldChannel.nsfw ? 'Oui' : 'Non'}${newChannel.nsfw ? 'Oui' : 'Non'}`, inline: true });
}
// Si aucun changement détecté, ignorer
if (changes.length === 0) return;
let executor = null;
try {
const auditLogs = await newChannel.guild.fetchAuditLogs({
type: AuditLogEvent.ChannelUpdate,
limit: 1
});
const updateLog = auditLogs.entries.first();
if (updateLog && updateLog.target.id === newChannel.id && (Date.now() - updateLog.createdTimestamp) < 5000) {
executor = updateLog.executor;
}
} catch (err) {
console.error('Erreur récupération audit logs channel update:', err);
}
await sendLog(client, newChannel.guild.id, 'channels', {
action: 'update',
title: '✏️ Salon modifié',
description: `Le salon ${newChannel} a été modifié.`,
fields: changes,
executor: executor
});
}
};
+41
View File
@@ -0,0 +1,41 @@
const { AuditLogEvent } = require('discord.js');
const { sendLog } = require('../fonctions/sendLog');
module.exports = {
name: 'guildBanAdd',
async execute(client, ban) {
const { guild, user } = ban;
// Essayer de récupérer l'exécuteur depuis les audit logs
let executor = null;
let reason = 'Aucune raison spécifiée';
try {
const auditLogs = await guild.fetchAuditLogs({
type: AuditLogEvent.MemberBan,
limit: 1
});
const banLog = auditLogs.entries.first();
if (banLog && banLog.target.id === user.id && (Date.now() - banLog.createdTimestamp) < 5000) {
executor = banLog.executor;
reason = banLog.reason || reason;
}
} catch (err) {
console.error('Erreur récupération audit logs ban:', err);
}
await sendLog(client, guild.id, 'moderation', {
action: 'ban',
title: '🔨 Membre banni',
description: `**${user.tag}** a été banni du serveur.`,
fields: [
{ name: '👤 Utilisateur', value: `${user} (${user.id})`, inline: true },
{ name: '📝 Raison', value: reason, inline: false }
],
thumbnail: user.displayAvatarURL({ size: 128 }),
user: user,
executor: executor
});
}
};
+38
View File
@@ -0,0 +1,38 @@
const { AuditLogEvent } = require('discord.js');
const { sendLog } = require('../fonctions/sendLog');
module.exports = {
name: 'guildBanRemove',
async execute(client, ban) {
const { guild, user } = ban;
// Essayer de récupérer l'exécuteur depuis les audit logs
let executor = null;
try {
const auditLogs = await guild.fetchAuditLogs({
type: AuditLogEvent.MemberBanRemove,
limit: 1
});
const unbanLog = auditLogs.entries.first();
if (unbanLog && unbanLog.target.id === user.id && (Date.now() - unbanLog.createdTimestamp) < 5000) {
executor = unbanLog.executor;
}
} catch (err) {
console.error('Erreur récupération audit logs unban:', err);
}
await sendLog(client, guild.id, 'moderation', {
action: 'unban',
title: '🔓 Membre débanni',
description: `**${user.tag}** a été débanni du serveur.`,
fields: [
{ name: '👤 Utilisateur', value: `${user} (${user.id})`, inline: true }
],
thumbnail: user.displayAvatarURL({ size: 128 }),
user: user,
executor: executor
});
}
};
+26
View File
@@ -1,9 +1,33 @@
const { Events, EmbedBuilder } = require("discord.js"); const { Events, EmbedBuilder } = require("discord.js");
const db = require("../db"); const db = require("../db");
const { sendLog } = require("../fonctions/sendLog");
module.exports = { module.exports = {
name: Events.GuildMemberAdd, name: Events.GuildMemberAdd,
async execute(client, member) { async execute(client, member) {
// ===== LOG MEMBRE REJOINT =====
const accountAge = Math.floor((Date.now() - member.user.createdTimestamp) / (1000 * 60 * 60 * 24));
const accountAgeStr = accountAge < 1 ? 'Moins d\'un jour' :
accountAge < 7 ? `${accountAge} jours ⚠️` :
accountAge < 30 ? `${accountAge} jours` :
accountAge < 365 ? `${Math.floor(accountAge / 30)} mois` :
`${Math.floor(accountAge / 365)} ans`;
await sendLog(client, member.guild.id, 'members', {
action: 'join',
title: '📥 Membre rejoint',
description: `**${member.user.tag}** a rejoint le serveur.`,
fields: [
{ name: '👤 Membre', value: `${member} (${member.user.tag})`, inline: true },
{ name: '📊 Membres', value: `${member.guild.memberCount}`, inline: true },
{ name: '📅 Compte créé', value: `<t:${Math.floor(member.user.createdTimestamp / 1000)}:R>`, inline: true },
{ name: '⏳ Âge du compte', value: accountAgeStr, inline: true }
],
thumbnail: member.user.displayAvatarURL({ size: 128 }),
user: member.user
});
// ===== MESSAGE DE BIENVENUE =====
db.get( db.get(
"SELECT enabled, channel_id, message FROM welcome_config WHERE guild_id = ?", "SELECT enabled, channel_id, message FROM welcome_config WHERE guild_id = ?",
[member.guild.id], [member.guild.id],
@@ -31,6 +55,8 @@ module.exports = {
} }
} }
); );
// ===== AUTOROLE =====
db.get( db.get(
"SELECT enabled, role_id FROM autorole_newuser_config WHERE guild_id = ?", "SELECT enabled, role_id FROM autorole_newuser_config WHERE guild_id = ?",
[member.guild.id], [member.guild.id],
+57 -1
View File
@@ -1,9 +1,65 @@
const { Events, EmbedBuilder } = require("discord.js"); const { Events, EmbedBuilder, AuditLogEvent } = require("discord.js");
const db = require("../db"); const db = require("../db");
const { sendLog } = require("../fonctions/sendLog");
module.exports = { module.exports = {
name: Events.GuildMemberRemove, name: Events.GuildMemberRemove,
async execute(client, member) { async execute(client, member) {
// ===== VÉRIFIER SI C'EST UN KICK =====
let wasKicked = false;
let kickExecutor = null;
let kickReason = null;
try {
const auditLogs = await member.guild.fetchAuditLogs({
type: AuditLogEvent.MemberKick,
limit: 1
});
const kickLog = auditLogs.entries.first();
if (kickLog && kickLog.target.id === member.id && (Date.now() - kickLog.createdTimestamp) < 5000) {
wasKicked = true;
kickExecutor = kickLog.executor;
kickReason = kickLog.reason || 'Aucune raison spécifiée';
}
} catch (err) {
// Pas de permission audit logs
}
if (wasKicked) {
// ===== LOG KICK =====
await sendLog(client, member.guild.id, 'moderation', {
action: 'kick',
title: '👢 Membre expulsé',
description: `**${member.user.tag}** a été expulsé du serveur.`,
fields: [
{ name: '👤 Membre', value: `${member.user} (${member.user.tag})`, inline: true },
{ name: '📝 Raison', value: kickReason, inline: false }
],
thumbnail: member.user.displayAvatarURL({ size: 128 }),
user: member.user,
executor: kickExecutor
});
} else {
// ===== LOG MEMBRE PARTI =====
const joinedAt = member.joinedTimestamp ?
`<t:${Math.floor(member.joinedTimestamp / 1000)}:R>` : 'Inconnu';
await sendLog(client, member.guild.id, 'members', {
action: 'leave',
title: '📤 Membre parti',
description: `**${member.user.tag}** a quitté le serveur.`,
fields: [
{ name: '👤 Membre', value: `${member.user} (${member.user.tag})`, inline: true },
{ name: '📊 Membres', value: `${member.guild.memberCount}`, inline: true },
{ name: '📅 Avait rejoint', value: joinedAt, inline: true }
],
thumbnail: member.user.displayAvatarURL({ size: 128 }),
user: member.user
});
}
// ===== MESSAGE D'AU REVOIR =====
db.get( db.get(
"SELECT enabled, channel_id, message FROM goodbye_config WHERE guild_id = ?", "SELECT enabled, channel_id, message FROM goodbye_config WHERE guild_id = ?",
[member.guild.id], [member.guild.id],
+180
View File
@@ -0,0 +1,180 @@
const { AuditLogEvent } = require('discord.js');
const { sendLog } = require('../fonctions/sendLog');
module.exports = {
name: 'guildMemberUpdate',
async execute(client, oldMember, newMember) {
const guild = newMember.guild;
// Vérifier les changements de rôles
const oldRoles = oldMember.roles.cache;
const newRoles = newMember.roles.cache;
const addedRoles = newRoles.filter(role => !oldRoles.has(role.id));
const removedRoles = oldRoles.filter(role => !newRoles.has(role.id));
// Log des rôles ajoutés
if (addedRoles.size > 0) {
let executor = null;
try {
const auditLogs = await guild.fetchAuditLogs({
type: AuditLogEvent.MemberRoleUpdate,
limit: 1
});
const roleLog = auditLogs.entries.first();
if (roleLog && roleLog.target.id === newMember.id && (Date.now() - roleLog.createdTimestamp) < 5000) {
executor = roleLog.executor;
}
} catch (err) {
console.error('Erreur récupération audit logs role:', err);
}
await sendLog(client, guild.id, 'members', {
action: 'add',
title: '✅ Rôle(s) ajouté(s)',
description: `**${newMember.user.tag}** a reçu de nouveaux rôles.`,
fields: [
{ name: '👤 Membre', value: `${newMember} (${newMember.user.tag})`, inline: true },
{ name: '🎭 Rôle(s)', value: addedRoles.map(r => r.toString()).join(', '), inline: false }
],
thumbnail: newMember.user.displayAvatarURL({ size: 128 }),
user: newMember.user,
executor: executor
});
}
// Log des rôles retirés
if (removedRoles.size > 0) {
let executor = null;
try {
const auditLogs = await guild.fetchAuditLogs({
type: AuditLogEvent.MemberRoleUpdate,
limit: 1
});
const roleLog = auditLogs.entries.first();
if (roleLog && roleLog.target.id === newMember.id && (Date.now() - roleLog.createdTimestamp) < 5000) {
executor = roleLog.executor;
}
} catch (err) {
console.error('Erreur récupération audit logs role:', err);
}
await sendLog(client, guild.id, 'members', {
action: 'remove',
title: '❌ Rôle(s) retiré(s)',
description: `**${newMember.user.tag}** a perdu des rôles.`,
fields: [
{ name: '👤 Membre', value: `${newMember} (${newMember.user.tag})`, inline: true },
{ name: '🎭 Rôle(s)', value: removedRoles.map(r => r.toString()).join(', '), inline: false }
],
thumbnail: newMember.user.displayAvatarURL({ size: 128 }),
user: newMember.user,
executor: executor
});
}
// Vérifier les changements de pseudo
if (oldMember.nickname !== newMember.nickname) {
let executor = null;
try {
const auditLogs = await guild.fetchAuditLogs({
type: AuditLogEvent.MemberUpdate,
limit: 1
});
const nicknameLog = auditLogs.entries.first();
if (nicknameLog && nicknameLog.target.id === newMember.id && (Date.now() - nicknameLog.createdTimestamp) < 5000) {
executor = nicknameLog.executor;
}
} catch (err) {
console.error('Erreur récupération audit logs nickname:', err);
}
await sendLog(client, guild.id, 'members', {
action: 'change',
title: '📝 Pseudo modifié',
fields: [
{ name: '👤 Membre', value: `${newMember} (${newMember.user.tag})`, inline: true },
{ name: '📝 Ancien pseudo', value: oldMember.nickname || '*Aucun*', inline: true },
{ name: '📝 Nouveau pseudo', value: newMember.nickname || '*Aucun*', inline: true }
],
thumbnail: newMember.user.displayAvatarURL({ size: 128 }),
user: newMember.user,
executor: executor
});
}
// Vérifier les timeouts
const oldTimeout = oldMember.communicationDisabledUntil;
const newTimeout = newMember.communicationDisabledUntil;
if (!oldTimeout && newTimeout) {
// Membre mis en timeout
let executor = null;
let reason = 'Aucune raison spécifiée';
try {
const auditLogs = await guild.fetchAuditLogs({
type: AuditLogEvent.MemberUpdate,
limit: 1
});
const timeoutLog = auditLogs.entries.first();
if (timeoutLog && timeoutLog.target.id === newMember.id && (Date.now() - timeoutLog.createdTimestamp) < 5000) {
executor = timeoutLog.executor;
reason = timeoutLog.reason || reason;
}
} catch (err) {
console.error('Erreur récupération audit logs timeout:', err);
}
await sendLog(client, guild.id, 'moderation', {
action: 'timeout',
title: '⏰ Membre mis en timeout',
description: `**${newMember.user.tag}** a été mis en timeout.`,
fields: [
{ name: '👤 Membre', value: `${newMember} (${newMember.user.tag})`, inline: true },
{ name: '⏱️ Expire', value: `<t:${Math.floor(newTimeout.getTime() / 1000)}:R>`, inline: true },
{ name: '📝 Raison', value: reason, inline: false }
],
thumbnail: newMember.user.displayAvatarURL({ size: 128 }),
user: newMember.user,
executor: executor
});
} else if (oldTimeout && !newTimeout) {
// Timeout retiré
let executor = null;
try {
const auditLogs = await guild.fetchAuditLogs({
type: AuditLogEvent.MemberUpdate,
limit: 1
});
const timeoutLog = auditLogs.entries.first();
if (timeoutLog && timeoutLog.target.id === newMember.id && (Date.now() - timeoutLog.createdTimestamp) < 5000) {
executor = timeoutLog.executor;
}
} catch (err) {
console.error('Erreur récupération audit logs untimeout:', err);
}
await sendLog(client, guild.id, 'moderation', {
action: 'untimeout',
title: '✅ Timeout retiré',
description: `Le timeout de **${newMember.user.tag}** a été retiré.`,
fields: [
{ name: '👤 Membre', value: `${newMember} (${newMember.user.tag})`, inline: true }
],
thumbnail: newMember.user.displayAvatarURL({ size: 128 }),
user: newMember.user,
executor: executor
});
}
}
};
+101
View File
@@ -0,0 +1,101 @@
const { AuditLogEvent } = require('discord.js');
const { sendLog } = require('../fonctions/sendLog');
module.exports = {
name: 'guildUpdate',
async execute(client, oldGuild, newGuild) {
const changes = [];
// Changement de nom
if (oldGuild.name !== newGuild.name) {
changes.push({ name: '📝 Nom', value: `\`${oldGuild.name}\`\`${newGuild.name}\``, inline: false });
}
// Changement d'icône
if (oldGuild.icon !== newGuild.icon) {
changes.push({ name: '🖼️ Icône', value: 'L\'icône du serveur a été modifiée', inline: false });
}
// Changement de bannière
if (oldGuild.banner !== newGuild.banner) {
changes.push({ name: '🎨 Bannière', value: 'La bannière du serveur a été modifiée', inline: false });
}
// Changement de description
if (oldGuild.description !== newGuild.description) {
const oldDesc = oldGuild.description || '*Aucune*';
const newDesc = newGuild.description || '*Aucune*';
changes.push({ name: '📄 Description', value: `${oldDesc.substring(0, 100)}${newDesc.substring(0, 100)}`, inline: false });
}
// Changement de région/locale
if (oldGuild.preferredLocale !== newGuild.preferredLocale) {
changes.push({ name: '🌍 Langue', value: `${oldGuild.preferredLocale}${newGuild.preferredLocale}`, inline: true });
}
// Changement du salon AFK
if (oldGuild.afkChannelId !== newGuild.afkChannelId) {
const oldChannel = oldGuild.afkChannel?.name || '*Aucun*';
const newChannel = newGuild.afkChannel?.name || '*Aucun*';
changes.push({ name: '💤 Salon AFK', value: `${oldChannel}${newChannel}`, inline: true });
}
// Changement du timeout AFK
if (oldGuild.afkTimeout !== newGuild.afkTimeout) {
const formatTimeout = (seconds) => `${Math.floor(seconds / 60)} minutes`;
changes.push({ name: '⏰ Timeout AFK', value: `${formatTimeout(oldGuild.afkTimeout)}${formatTimeout(newGuild.afkTimeout)}`, inline: true });
}
// Changement du niveau de vérification
if (oldGuild.verificationLevel !== newGuild.verificationLevel) {
const levels = ['Aucun', 'Faible', 'Moyen', 'Élevé', 'Très élevé'];
changes.push({ name: '🛡️ Niveau de vérification', value: `${levels[oldGuild.verificationLevel]}${levels[newGuild.verificationLevel]}`, inline: true });
}
// Changement du filtre de contenu explicite
if (oldGuild.explicitContentFilter !== newGuild.explicitContentFilter) {
const filters = ['Désactivé', 'Membres sans rôle', 'Tous les membres'];
changes.push({ name: '🔞 Filtre de contenu', value: `${filters[oldGuild.explicitContentFilter]}${filters[newGuild.explicitContentFilter]}`, inline: true });
}
// Changement du salon système
if (oldGuild.systemChannelId !== newGuild.systemChannelId) {
const oldChannel = oldGuild.systemChannel?.name || '*Aucun*';
const newChannel = newGuild.systemChannel?.name || '*Aucun*';
changes.push({ name: '📢 Salon système', value: `#${oldChannel} → #${newChannel}`, inline: true });
}
// Changement du propriétaire
if (oldGuild.ownerId !== newGuild.ownerId) {
changes.push({ name: '👑 Propriétaire', value: `<@${oldGuild.ownerId}> → <@${newGuild.ownerId}>`, inline: false });
}
// Si aucun changement détecté, ignorer
if (changes.length === 0) return;
let executor = null;
try {
const auditLogs = await newGuild.fetchAuditLogs({
type: AuditLogEvent.GuildUpdate,
limit: 1
});
const updateLog = auditLogs.entries.first();
if (updateLog && (Date.now() - updateLog.createdTimestamp) < 5000) {
executor = updateLog.executor;
}
} catch (err) {
console.error('Erreur récupération audit logs guild update:', err);
}
await sendLog(client, newGuild.id, 'server', {
action: 'update',
title: '⚙️ Serveur modifié',
description: `Les paramètres du serveur ont été modifiés.`,
fields: changes,
thumbnail: newGuild.iconURL({ size: 128 }),
executor: executor
});
}
};
+41
View File
@@ -0,0 +1,41 @@
const { sendLog } = require('../fonctions/sendLog');
module.exports = {
name: 'inviteCreate',
async execute(client, invite) {
if (!invite.guild) return;
const fields = [
{ name: '🔗 Code', value: invite.code, inline: true },
{ name: '📁 Salon', value: invite.channel ? `${invite.channel} (#${invite.channel.name})` : 'Inconnu', inline: true }
];
if (invite.maxUses) {
fields.push({ name: '🔢 Utilisations max', value: invite.maxUses.toString(), inline: true });
}
if (invite.maxAge) {
const hours = Math.floor(invite.maxAge / 3600);
const minutes = Math.floor((invite.maxAge % 3600) / 60);
let expiration = '';
if (hours > 0) expiration += `${hours}h `;
if (minutes > 0) expiration += `${minutes}m`;
if (!expiration) expiration = 'Jamais';
fields.push({ name: '⏰ Expire dans', value: expiration, inline: true });
} else {
fields.push({ name: '⏰ Expiration', value: 'Jamais', inline: true });
}
if (invite.temporary) {
fields.push({ name: '⏳ Temporaire', value: 'Oui (membres expulsés s\'ils quittent)', inline: true });
}
await sendLog(client, invite.guild.id, 'invites', {
action: 'create',
title: '🔗 Invitation créée',
description: `Une nouvelle invitation a été créée: **discord.gg/${invite.code}**`,
fields: fields,
executor: invite.inviter
});
}
};
+42
View File
@@ -0,0 +1,42 @@
const { AuditLogEvent } = require('discord.js');
const { sendLog } = require('../fonctions/sendLog');
module.exports = {
name: 'inviteDelete',
async execute(client, invite) {
if (!invite.guild) return;
let executor = null;
try {
const auditLogs = await invite.guild.fetchAuditLogs({
type: AuditLogEvent.InviteDelete,
limit: 1
});
const deleteLog = auditLogs.entries.first();
if (deleteLog && deleteLog.target.code === invite.code && (Date.now() - deleteLog.createdTimestamp) < 5000) {
executor = deleteLog.executor;
}
} catch (err) {
console.error('Erreur récupération audit logs invite delete:', err);
}
const fields = [
{ name: '🔗 Code', value: invite.code, inline: true },
{ name: '📁 Salon', value: invite.channel ? `#${invite.channel.name}` : 'Inconnu', inline: true }
];
if (invite.uses !== null && invite.uses !== undefined) {
fields.push({ name: '📊 Utilisations', value: invite.uses.toString(), inline: true });
}
await sendLog(client, invite.guild.id, 'invites', {
action: 'delete',
title: '🗑️ Invitation supprimée',
description: `L'invitation **discord.gg/${invite.code}** a été supprimée.`,
fields: fields,
executor: executor
});
}
};
+38
View File
@@ -0,0 +1,38 @@
const { sendLog } = require('../fonctions/sendLog');
module.exports = {
name: 'messageDelete',
async execute(client, message) {
// Ignorer les messages du bot et les messages système
if (!message.guild) return;
if (message.author?.bot) return;
if (!message.content && message.attachments.size === 0 && message.embeds.length === 0) return;
const fields = [
{ name: '👤 Auteur', value: message.author ? `${message.author} (${message.author.tag})` : 'Inconnu', inline: true },
{ name: '📁 Salon', value: `${message.channel} (#${message.channel.name})`, inline: true }
];
// Ajouter le contenu du message s'il existe
if (message.content) {
const content = message.content.length > 1024
? message.content.substring(0, 1021) + '...'
: message.content;
fields.push({ name: '💬 Contenu', value: content, inline: false });
}
// Ajouter les pièces jointes
if (message.attachments.size > 0) {
const attachments = message.attachments.map(a => `[${a.name}](${a.url})`).join('\n');
fields.push({ name: '📎 Pièces jointes', value: attachments.substring(0, 1024), inline: false });
}
await sendLog(client, message.guild.id, 'messages', {
action: 'delete',
title: '🗑️ Message supprimé',
fields: fields,
thumbnail: message.author?.displayAvatarURL({ size: 128 }),
user: message.author
});
}
};
+29
View File
@@ -0,0 +1,29 @@
const { sendLog } = require('../fonctions/sendLog');
module.exports = {
name: 'messageDeleteBulk',
async execute(client, messages, channel) {
if (!channel.guild) return;
const messageList = messages.map(m => {
const author = m.author ? m.author.tag : 'Inconnu';
const content = m.content
? (m.content.length > 50 ? m.content.substring(0, 47) + '...' : m.content)
: '*Pas de contenu*';
return `**${author}**: ${content}`;
}).slice(0, 10).join('\n');
const additionalCount = messages.size > 10 ? `\n... et ${messages.size - 10} autres messages` : '';
await sendLog(client, channel.guild.id, 'messages', {
action: 'delete',
title: '🗑️ Suppression en masse',
description: `**${messages.size}** messages ont été supprimés dans ${channel}.`,
fields: [
{ name: '📁 Salon', value: `${channel} (#${channel.name})`, inline: true },
{ name: '📊 Nombre', value: `${messages.size} messages`, inline: true },
{ name: '📝 Aperçu', value: (messageList + additionalCount).substring(0, 1024) || '*Aucun aperçu*', inline: false }
]
});
}
};
+38
View File
@@ -0,0 +1,38 @@
const { sendLog } = require('../fonctions/sendLog');
module.exports = {
name: 'messageUpdate',
async execute(client, oldMessage, newMessage) {
// Ignorer si pas de guild ou si c'est un bot
if (!newMessage.guild) return;
if (newMessage.author?.bot) return;
// Ignorer si le contenu n'a pas changé (peut être un embed qui se charge)
if (oldMessage.content === newMessage.content) return;
// Ignorer les messages vides
if (!oldMessage.content && !newMessage.content) return;
const oldContent = oldMessage.content
? (oldMessage.content.length > 1024 ? oldMessage.content.substring(0, 1021) + '...' : oldMessage.content)
: '*Message vide ou non caché*';
const newContent = newMessage.content
? (newMessage.content.length > 1024 ? newMessage.content.substring(0, 1021) + '...' : newMessage.content)
: '*Message vide*';
await sendLog(client, newMessage.guild.id, 'messages', {
action: 'edit',
title: '✏️ Message modifié',
description: `[Aller au message](${newMessage.url})`,
fields: [
{ name: '👤 Auteur', value: `${newMessage.author} (${newMessage.author.tag})`, inline: true },
{ name: '📁 Salon', value: `${newMessage.channel} (#${newMessage.channel.name})`, inline: true },
{ name: '📝 Avant', value: oldContent, inline: false },
{ name: '📝 Après', value: newContent, inline: false }
],
thumbnail: newMessage.author?.displayAvatarURL({ size: 128 }),
user: newMessage.author
});
}
};
+35
View File
@@ -0,0 +1,35 @@
const { AuditLogEvent } = require('discord.js');
const { sendLog } = require('../fonctions/sendLog');
module.exports = {
name: 'roleCreate',
async execute(client, role) {
let executor = null;
try {
const auditLogs = await role.guild.fetchAuditLogs({
type: AuditLogEvent.RoleCreate,
limit: 1
});
const createLog = auditLogs.entries.first();
if (createLog && createLog.target.id === role.id && (Date.now() - createLog.createdTimestamp) < 5000) {
executor = createLog.executor;
}
} catch (err) {
console.error('Erreur récupération audit logs role create:', err);
}
await sendLog(client, role.guild.id, 'roles', {
action: 'create',
title: '✅ Rôle créé',
description: `Le rôle ${role} a été créé.`,
fields: [
{ name: '🎭 Nom', value: role.name, inline: true },
{ name: '🎨 Couleur', value: role.hexColor || '#000000', inline: true },
{ name: '🆔 ID', value: role.id, inline: true }
],
executor: executor
});
}
};
+35
View File
@@ -0,0 +1,35 @@
const { AuditLogEvent } = require('discord.js');
const { sendLog } = require('../fonctions/sendLog');
module.exports = {
name: 'roleDelete',
async execute(client, role) {
let executor = null;
try {
const auditLogs = await role.guild.fetchAuditLogs({
type: AuditLogEvent.RoleDelete,
limit: 1
});
const deleteLog = auditLogs.entries.first();
if (deleteLog && deleteLog.target.id === role.id && (Date.now() - deleteLog.createdTimestamp) < 5000) {
executor = deleteLog.executor;
}
} catch (err) {
console.error('Erreur récupération audit logs role delete:', err);
}
await sendLog(client, role.guild.id, 'roles', {
action: 'delete',
title: '🗑️ Rôle supprimé',
description: `Le rôle **@${role.name}** a été supprimé.`,
fields: [
{ name: '🎭 Nom', value: role.name, inline: true },
{ name: '🎨 Couleur', value: role.hexColor || '#000000', inline: true },
{ name: '🆔 ID', value: role.id, inline: true }
],
executor: executor
});
}
};
+72
View File
@@ -0,0 +1,72 @@
const { AuditLogEvent } = require('discord.js');
const { sendLog } = require('../fonctions/sendLog');
module.exports = {
name: 'roleUpdate',
async execute(client, oldRole, newRole) {
const changes = [];
// Changement de nom
if (oldRole.name !== newRole.name) {
changes.push({ name: '📝 Nom', value: `\`${oldRole.name}\`\`${newRole.name}\``, inline: false });
}
// Changement de couleur
if (oldRole.hexColor !== newRole.hexColor) {
changes.push({ name: '🎨 Couleur', value: `${oldRole.hexColor}${newRole.hexColor}`, inline: true });
}
// Changement hoisted (affiché séparément)
if (oldRole.hoist !== newRole.hoist) {
changes.push({ name: '📊 Affiché séparément', value: `${oldRole.hoist ? 'Oui' : 'Non'}${newRole.hoist ? 'Oui' : 'Non'}`, inline: true });
}
// Changement mentionnable
if (oldRole.mentionable !== newRole.mentionable) {
changes.push({ name: '🔔 Mentionnable', value: `${oldRole.mentionable ? 'Oui' : 'Non'}${newRole.mentionable ? 'Oui' : 'Non'}`, inline: true });
}
// Changement de permissions
if (oldRole.permissions.bitfield !== newRole.permissions.bitfield) {
const oldPerms = oldRole.permissions.toArray();
const newPerms = newRole.permissions.toArray();
const addedPerms = newPerms.filter(p => !oldPerms.includes(p));
const removedPerms = oldPerms.filter(p => !newPerms.includes(p));
if (addedPerms.length > 0) {
changes.push({ name: '✅ Permissions ajoutées', value: addedPerms.slice(0, 10).join(', ') + (addedPerms.length > 10 ? '...' : ''), inline: false });
}
if (removedPerms.length > 0) {
changes.push({ name: '❌ Permissions retirées', value: removedPerms.slice(0, 10).join(', ') + (removedPerms.length > 10 ? '...' : ''), inline: false });
}
}
// Si aucun changement détecté, ignorer
if (changes.length === 0) return;
let executor = null;
try {
const auditLogs = await newRole.guild.fetchAuditLogs({
type: AuditLogEvent.RoleUpdate,
limit: 1
});
const updateLog = auditLogs.entries.first();
if (updateLog && updateLog.target.id === newRole.id && (Date.now() - updateLog.createdTimestamp) < 5000) {
executor = updateLog.executor;
}
} catch (err) {
console.error('Erreur récupération audit logs role update:', err);
}
await sendLog(client, newRole.guild.id, 'roles', {
action: 'update',
title: '✏️ Rôle modifié',
description: `Le rôle ${newRole} a été modifié.`,
fields: changes,
executor: executor
});
}
};
+87
View File
@@ -1,5 +1,6 @@
const { Events, ChannelType, PermissionFlagsBits } = require("discord.js"); const { Events, ChannelType, PermissionFlagsBits } = require("discord.js");
const db = require("../db"); const db = require("../db");
const { sendLog } = require("../fonctions/sendLog");
// Store voice join times and intervals for economy // Store voice join times and intervals for economy
const voiceJoinTimes = new Map(); // guildId_oderId -> timestamp const voiceJoinTimes = new Map(); // guildId_oderId -> timestamp
@@ -11,6 +12,9 @@ module.exports = {
// ===== PRIVATE ROOM (TEMP VOICE CHANNELS) ===== // ===== PRIVATE ROOM (TEMP VOICE CHANNELS) =====
await handlePrivateRoom(client, oldState, newState); await handlePrivateRoom(client, oldState, newState);
// ===== VOICE LOGS =====
await handleVoiceLogs(client, oldState, newState);
if (newState.member.user.bot) return; if (newState.member.user.bot) return;
const guildId = newState.guild.id; const guildId = newState.guild.id;
@@ -272,3 +276,86 @@ async function trackVoiceTime(guildId, userId, oldState, newState) {
// No action needed, session continues // No action needed, session continues
} }
} }
// ===== VOICE LOGS =====
async function handleVoiceLogs(client, oldState, newState) {
// Ignorer les bots
if (newState.member?.user?.bot) return;
const guild = newState.guild;
const member = newState.member;
if (!member) return;
// Utilisateur rejoint un salon vocal
if (newState.channelId && !oldState.channelId) {
await sendLog(client, guild.id, 'voice', {
action: 'join',
title: '🔊 Connexion vocale',
description: `**${member.user.tag}** a rejoint un salon vocal.`,
fields: [
{ name: '👤 Membre', value: `${member} (${member.user.tag})`, inline: true },
{ name: '🔊 Salon', value: `${newState.channel}`, inline: true }
],
thumbnail: member.user.displayAvatarURL({ size: 128 }),
user: member.user
});
}
// Utilisateur quitte un salon vocal
else if (!newState.channelId && oldState.channelId) {
await sendLog(client, guild.id, 'voice', {
action: 'leave',
title: '🔇 Déconnexion vocale',
description: `**${member.user.tag}** a quitté un salon vocal.`,
fields: [
{ name: '👤 Membre', value: `${member} (${member.user.tag})`, inline: true },
{ name: '🔊 Salon', value: `${oldState.channel?.name || 'Inconnu'}`, inline: true }
],
thumbnail: member.user.displayAvatarURL({ size: 128 }),
user: member.user
});
}
// Utilisateur change de salon
else if (newState.channelId && oldState.channelId && newState.channelId !== oldState.channelId) {
await sendLog(client, guild.id, 'voice', {
action: 'move',
title: '🔀 Changement de salon',
description: `**${member.user.tag}** a changé de salon vocal.`,
fields: [
{ name: '👤 Membre', value: `${member} (${member.user.tag})`, inline: true },
{ name: '📤 Ancien salon', value: `${oldState.channel?.name || 'Inconnu'}`, inline: true },
{ name: '📥 Nouveau salon', value: `${newState.channel}`, inline: true }
],
thumbnail: member.user.displayAvatarURL({ size: 128 }),
user: member.user
});
}
// Vérifier les changements de mute/deafen serveur
if (oldState.serverMute !== newState.serverMute) {
await sendLog(client, guild.id, 'voice', {
action: newState.serverMute ? 'timeout' : 'untimeout',
title: newState.serverMute ? '🔇 Mute serveur activé' : '🔊 Mute serveur désactivé',
description: `**${member.user.tag}** a été ${newState.serverMute ? 'muté' : 'démuté'} par le serveur.`,
fields: [
{ name: '👤 Membre', value: `${member} (${member.user.tag})`, inline: true },
{ name: '🔊 Salon', value: newState.channel ? `${newState.channel}` : 'N/A', inline: true }
],
thumbnail: member.user.displayAvatarURL({ size: 128 }),
user: member.user
});
}
if (oldState.serverDeaf !== newState.serverDeaf) {
await sendLog(client, guild.id, 'voice', {
action: newState.serverDeaf ? 'timeout' : 'untimeout',
title: newState.serverDeaf ? '🔇 Sourd serveur activé' : '🔊 Sourd serveur désactivé',
description: `**${member.user.tag}** a été rendu ${newState.serverDeaf ? 'sourd' : 'non-sourd'} par le serveur.`,
fields: [
{ name: '👤 Membre', value: `${member} (${member.user.tag})`, inline: true },
{ name: '🔊 Salon', value: newState.channel ? `${newState.channel}` : 'N/A', inline: true }
],
thumbnail: member.user.displayAvatarURL({ size: 128 }),
user: member.user
});
}
}
+162
View File
@@ -0,0 +1,162 @@
const { EmbedBuilder } = require('discord.js');
const db = require('../db');
/**
* Couleurs pour les différents types d'actions
*/
const COLORS = {
// Actions positives (vert)
create: 0x57F287,
join: 0x57F287,
add: 0x57F287,
unban: 0x57F287,
untimeout: 0x57F287,
// Actions neutres (bleu)
update: 0x5865F2,
edit: 0x5865F2,
move: 0x5865F2,
change: 0x5865F2,
// Actions négatives (rouge)
delete: 0xED4245,
leave: 0xED4245,
remove: 0xED4245,
ban: 0xED4245,
kick: 0xED4245,
timeout: 0xED4245,
// Avertissements (orange)
warn: 0xF0B232,
warning: 0xF0B232
};
/**
* Envoie un log dans le salon approprié
* @param {Client} client - Le client Discord
* @param {string} guildId - L'ID du serveur
* @param {string} logType - Le type de log (moderation, voice, messages, members, channels, roles, invites, server)
* @param {object} options - Les options de l'embed
* @param {string} options.action - L'action effectuée (create, delete, update, join, leave, etc.)
* @param {string} options.title - Le titre de l'embed
* @param {string} [options.description] - La description de l'embed
* @param {Array} [options.fields] - Les champs de l'embed
* @param {string} [options.thumbnail] - URL de la miniature
* @param {string} [options.image] - URL de l'image
* @param {User|GuildMember} [options.user] - L'utilisateur concerné
* @param {User|GuildMember} [options.executor] - L'utilisateur qui a effectué l'action
*/
async function sendLog(client, guildId, logType, options) {
try {
// Récupérer la config des logs
const config = await db.getAsync(
"SELECT * FROM logs_config WHERE guild_id = ?",
[guildId]
);
// Vérifier si les logs sont activés
if (!config || !config.enabled) return;
// Vérifier si ce type de log est activé
const typeEnabledField = `${logType}_enabled`;
const typeChannelField = `${logType}_channel_id`;
if (!config[typeEnabledField]) return;
const channelId = config[typeChannelField];
if (!channelId) return;
// Récupérer le salon
const guild = client.guilds.cache.get(guildId);
if (!guild) return;
const channel = guild.channels.cache.get(channelId);
if (!channel) return;
// Déterminer la couleur
const color = COLORS[options.action] || 0x5865F2;
// Créer l'embed
const embed = new EmbedBuilder()
.setColor(color)
.setTitle(options.title)
.setTimestamp();
if (options.description) {
embed.setDescription(options.description);
}
if (options.fields && options.fields.length > 0) {
embed.addFields(options.fields);
}
if (options.thumbnail) {
embed.setThumbnail(options.thumbnail);
}
if (options.image) {
embed.setImage(options.image);
}
// Ajouter l'utilisateur concerné dans le footer si disponible
if (options.user) {
const user = options.user.user || options.user;
embed.setFooter({
text: `ID: ${user.id}`,
iconURL: user.displayAvatarURL({ size: 32 })
});
}
// Ajouter l'exécuteur si disponible
if (options.executor) {
const executor = options.executor.user || options.executor;
embed.addFields({
name: '👤 Exécuté par',
value: `${executor} (${executor.tag || executor.username})`,
inline: true
});
}
// Envoyer le log
await channel.send({ embeds: [embed] });
} catch (err) {
console.error(`Erreur envoi log ${logType}:`, err);
}
}
/**
* Récupère la config des logs pour un serveur
* @param {string} guildId - L'ID du serveur
* @returns {Promise<object|null>}
*/
async function getLogsConfig(guildId) {
try {
return await db.getAsync(
"SELECT * FROM logs_config WHERE guild_id = ?",
[guildId]
);
} catch (err) {
console.error('Erreur récupération config logs:', err);
return null;
}
}
/**
* Vérifie si un type de log est activé
* @param {string} guildId - L'ID du serveur
* @param {string} logType - Le type de log
* @returns {Promise<boolean>}
*/
async function isLogEnabled(guildId, logType) {
const config = await getLogsConfig(guildId);
if (!config || !config.enabled) return false;
return !!config[`${logType}_enabled`];
}
module.exports = {
sendLog,
getLogsConfig,
isLogEnabled,
COLORS
};
+131
View File
@@ -1035,3 +1035,134 @@ body {
object-fit: cover; object-fit: cover;
display: none; display: none;
} }
/* ===== Logs System ===== */
.logs-types-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: var(--spacing-md);
}
.log-type-card {
background: var(--bg-dark);
border: 2px solid var(--border-color);
border-radius: var(--radius-md);
padding: var(--spacing-md);
transition: all 0.2s ease;
cursor: pointer;
}
.log-type-card:hover {
border-color: var(--primary-color);
background: rgba(88, 101, 242, 0.05);
}
.log-type-card.active {
border-color: var(--primary-color);
background: rgba(88, 101, 242, 0.1);
}
.log-type-checkbox {
display: flex;
align-items: flex-start;
gap: var(--spacing-sm);
cursor: pointer;
}
.log-type-checkbox input[type="checkbox"] {
margin-top: 4px;
width: 18px;
height: 18px;
accent-color: var(--primary-color);
}
.log-type-icon {
font-size: 1.5rem;
line-height: 1;
}
.log-type-info {
flex: 1;
}
.log-type-name {
font-weight: 600;
color: var(--text-primary);
margin-bottom: 2px;
}
.log-type-desc {
font-size: 0.85rem;
color: var(--text-muted);
}
.log-type-status {
font-size: 1rem;
margin-left: auto;
}
.logs-channels-preview {
background: var(--bg-dark);
border-radius: var(--radius-md);
padding: var(--spacing-md);
}
.log-channel-item {
display: flex;
align-items: center;
gap: var(--spacing-sm);
padding: var(--spacing-sm) 0;
border-bottom: 1px solid var(--border-color);
}
.log-channel-item:last-child {
border-bottom: none;
}
.log-channel-icon {
color: var(--text-muted);
font-weight: 500;
}
.log-channel-name {
flex: 1;
font-family: monospace;
color: var(--text-primary);
}
.log-channel-status {
font-size: 0.85rem;
padding: 2px 8px;
border-radius: var(--radius-sm);
}
.log-channel-status.created {
background: rgba(87, 242, 135, 0.15);
color: #57F287;
}
.log-channel-status.pending {
background: rgba(240, 178, 50, 0.15);
color: #F0B232;
}
.btn-group {
display: flex;
gap: var(--spacing-sm);
}
.btn-danger {
background: var(--error-color);
color: white;
border: none;
padding: var(--spacing-sm) var(--spacing-md);
border-radius: var(--radius-sm);
cursor: pointer;
font-weight: 500;
transition: all 0.2s ease;
}
.btn-danger:hover {
background: #c0392b;
}
+65
View File
@@ -74,6 +74,10 @@
<span class="nav-item-icon">🤖</span> <span class="nav-item-icon">🤖</span>
Apparence du bot Apparence du bot
</a> </a>
<a class="nav-item" data-section="logs">
<span class="nav-item-icon">📜</span>
Logs
</a>
</div> </div>
</nav> </nav>
@@ -1113,6 +1117,66 @@
</div> </div>
</section> </section>
<!-- Section: Logs -->
<section class="config-section" id="section-logs">
<div class="config-card">
<div class="config-card-header">
<div class="config-card-title">
<span class="icon">📜</span>
<h3>Système de Logs</h3>
</div>
<label class="toggle-switch">
<input type="checkbox" id="logs-enabled">
<span class="toggle-slider"></span>
</label>
</div>
<div class="config-card-body">
<p class="text-muted" style="margin-bottom: 1.5rem;">
Activez les logs pour suivre toutes les actions sur votre serveur. Le bot créera automatiquement les salons de logs dans une catégorie dédiée.
</p>
<!-- Catégorie existante ou nouvelle -->
<div class="form-group">
<label class="form-label">📁 Catégorie pour les logs</label>
<select class="form-select" id="logs-category">
<option value="">📜 Créer une nouvelle catégorie "LOGS"</option>
</select>
<small class="text-muted">Sélectionnez une catégorie existante ou laissez vide pour en créer une nouvelle automatiquement.</small>
</div>
<!-- Types de logs -->
<div class="form-group">
<label class="form-label">🔧 Types de logs à activer</label>
<p class="text-muted" style="margin-bottom: 1rem;">Sélectionnez les types de logs que vous souhaitez activer. Un salon sera créé pour chaque type activé.</p>
<div class="logs-types-grid" id="logs-types-container">
<!-- Généré dynamiquement par JS -->
</div>
</div>
<!-- Aperçu des salons -->
<div class="form-group" id="logs-preview-container" style="display: none;">
<label class="form-label">👁️ Salons de logs</label>
<div class="logs-channels-preview" id="logs-channels-preview">
<!-- Généré dynamiquement -->
</div>
</div>
</div>
<div class="config-card-footer">
<div id="status-logs-form" class="status-message"></div>
<div class="btn-group">
<button type="button" class="btn btn-danger" id="logs-delete-btn" style="display: none;">
🗑️ Supprimer tous les salons
</button>
<button type="button" class="btn btn-primary" id="logs-save-btn">
💾 Sauvegarder
</button>
</div>
</div>
</div>
</section>
</div> </div>
</main> </main>
@@ -1133,5 +1197,6 @@
<script src="/guild/scheduledMessagesForm.js"></script> <script src="/guild/scheduledMessagesForm.js"></script>
<script src="/guild/sendMessageForm.js"></script> <script src="/guild/sendMessageForm.js"></script>
<script src="/guild/botAppearanceForm.js"></script> <script src="/guild/botAppearanceForm.js"></script>
<script src="/guild/logsForm.js"></script>
</body> </body>
</html> </html>
+244
View File
@@ -0,0 +1,244 @@
// ===== LOGS FORM =====
(function() {
const logsEnabled = document.getElementById('logs-enabled');
const logsCategory = document.getElementById('logs-category');
const logsTypesContainer = document.getElementById('logs-types-container');
const logsPreviewContainer = document.getElementById('logs-preview-container');
const logsChannelsPreview = document.getElementById('logs-channels-preview');
const logsSaveBtn = document.getElementById('logs-save-btn');
const logsDeleteBtn = document.getElementById('logs-delete-btn');
const statusLogsForm = document.getElementById('status-logs-form');
let logTypes = [];
let currentConfig = null;
// Charger la config des logs
async function loadLogsConfig() {
try {
const res = await fetch(`/api/bot/get-logs-config/${guildId}`);
const data = await res.json();
if (data.success) {
logTypes = data.logTypes || [];
currentConfig = data.config || {};
// Remplir le select des catégories
logsCategory.innerHTML = '<option value="">📜 Créer une nouvelle catégorie "LOGS"</option>';
(data.categories || []).forEach(cat => {
const option = document.createElement('option');
option.value = cat.id;
option.textContent = cat.name;
if (currentConfig.category_id === cat.id) {
option.selected = true;
}
logsCategory.appendChild(option);
});
// Activer/désactiver le toggle
logsEnabled.checked = !!currentConfig.enabled;
// Générer les checkboxes pour les types de logs
renderLogTypes();
// Mettre à jour l'aperçu
updatePreview();
// Afficher le bouton supprimer si des salons existent
updateDeleteButton();
}
} catch (err) {
console.error('Erreur chargement config logs:', err);
}
}
// Générer les checkboxes des types de logs
function renderLogTypes() {
logsTypesContainer.innerHTML = '';
logTypes.forEach(logType => {
const isEnabled = currentConfig[`${logType.key}_enabled`];
const channelId = currentConfig[`${logType.key}_channel_id`];
const div = document.createElement('div');
div.className = 'log-type-card' + (isEnabled ? ' active' : '');
div.innerHTML = `
<label class="log-type-checkbox">
<input type="checkbox" name="log-type" value="${logType.key}" ${isEnabled ? 'checked' : ''}>
<span class="log-type-icon">${logType.name.split(' ')[0]}</span>
<div class="log-type-info">
<div class="log-type-name">${logType.name.substring(logType.name.indexOf(' ') + 1)}</div>
<div class="log-type-desc">${logType.description}</div>
</div>
${channelId ? `<span class="log-type-status">✅</span>` : ''}
</label>
`;
const checkbox = div.querySelector('input[type="checkbox"]');
checkbox.addEventListener('change', () => {
div.classList.toggle('active', checkbox.checked);
updatePreview();
});
logsTypesContainer.appendChild(div);
});
}
// Mettre à jour l'aperçu des salons
function updatePreview() {
const checkedTypes = [...document.querySelectorAll('input[name="log-type"]:checked')]
.map(cb => cb.value);
if (checkedTypes.length === 0 || !logsEnabled.checked) {
logsPreviewContainer.style.display = 'none';
return;
}
logsPreviewContainer.style.display = 'block';
logsChannelsPreview.innerHTML = '';
checkedTypes.forEach(key => {
const logType = logTypes.find(lt => lt.key === key);
if (!logType) return;
const channelId = currentConfig[`${key}_channel_id`];
const div = document.createElement('div');
div.className = 'log-channel-item';
div.innerHTML = `
<span class="log-channel-icon">#</span>
<span class="log-channel-name">${logType.channelName}</span>
<span class="log-channel-status ${channelId ? 'created' : 'pending'}">
${channelId ? '✅ Créé' : '⏳ Sera créé'}
</span>
`;
logsChannelsPreview.appendChild(div);
});
}
// Mettre à jour le bouton supprimer
function updateDeleteButton() {
const hasChannels = logTypes.some(lt => currentConfig[`${lt.key}_channel_id`]);
logsDeleteBtn.style.display = hasChannels ? 'inline-flex' : 'none';
}
// Sauvegarder la config
logsSaveBtn.addEventListener('click', async () => {
const enabledLogs = [...document.querySelectorAll('input[name="log-type"]:checked')]
.map(cb => cb.value);
logsSaveBtn.disabled = true;
logsSaveBtn.textContent = '⏳ Sauvegarde...';
statusLogsForm.textContent = '';
statusLogsForm.className = 'status-message';
try {
const res = await fetch('/api/bot/save-logs-config', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
guildId,
enabled: logsEnabled.checked,
categoryId: logsCategory.value || null,
enabledLogs
})
});
const data = await res.json();
if (data.success) {
statusLogsForm.textContent = '✅ Configuration sauvegardée !';
statusLogsForm.className = 'status-message success';
// Mettre à jour la config locale
if (data.categoryId) {
currentConfig.category_id = data.categoryId;
}
if (data.channels) {
for (const [key, channelId] of Object.entries(data.channels)) {
currentConfig[`${key}_channel_id`] = channelId;
if (enabledLogs.includes(key)) {
currentConfig[`${key}_enabled`] = 1;
}
}
}
// Rafraîchir l'affichage
renderLogTypes();
updatePreview();
updateDeleteButton();
// Recharger les catégories
await loadLogsConfig();
} else {
statusLogsForm.textContent = '❌ ' + (data.error || 'Erreur lors de la sauvegarde');
statusLogsForm.className = 'status-message error';
}
} catch (err) {
console.error('Erreur sauvegarde logs:', err);
statusLogsForm.textContent = '❌ Erreur de connexion';
statusLogsForm.className = 'status-message error';
}
logsSaveBtn.disabled = false;
logsSaveBtn.textContent = '💾 Sauvegarder';
});
// Supprimer tous les salons
logsDeleteBtn.addEventListener('click', async () => {
if (!confirm('⚠️ Êtes-vous sûr de vouloir supprimer tous les salons de logs ? Cette action est irréversible.')) {
return;
}
logsDeleteBtn.disabled = true;
logsDeleteBtn.textContent = '⏳ Suppression...';
statusLogsForm.textContent = '';
try {
const res = await fetch('/api/bot/delete-logs-channels', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ guildId })
});
const data = await res.json();
if (data.success) {
statusLogsForm.textContent = '✅ Tous les salons de logs ont été supprimés.';
statusLogsForm.className = 'status-message success';
// Reset la config locale
currentConfig = { enabled: false };
logsEnabled.checked = false;
// Décocher tous les types
document.querySelectorAll('input[name="log-type"]').forEach(cb => {
cb.checked = false;
cb.closest('.log-type-card')?.classList.remove('active');
});
// Rafraîchir
renderLogTypes();
updatePreview();
updateDeleteButton();
} else {
statusLogsForm.textContent = '❌ ' + (data.error || 'Erreur lors de la suppression');
statusLogsForm.className = 'status-message error';
}
} catch (err) {
console.error('Erreur suppression logs:', err);
statusLogsForm.textContent = '❌ Erreur de connexion';
statusLogsForm.className = 'status-message error';
}
logsDeleteBtn.disabled = false;
logsDeleteBtn.textContent = '🗑️ Supprimer tous les salons';
});
// Events toggle
logsEnabled.addEventListener('change', updatePreview);
// Charger au démarrage
window.addEventListener('guildLoaded', loadLogsConfig);
if (typeof guildId !== 'undefined' && guildId) {
loadLogsConfig();
}
})();
+316
View File
@@ -1281,5 +1281,321 @@ module.exports = (app, db, client) => {
} }
}); });
// ===== LOGS SYSTEM =====
// Types de logs disponibles
const LOG_TYPES = [
{ key: 'moderation', name: '📋 Modération', channelName: '📋・moderation-logs', description: 'Bans, kicks, timeouts, warns' },
{ key: 'voice', name: '🔊 Vocal', channelName: '🔊・voice-logs', description: 'Connexions/déconnexions vocales' },
{ key: 'messages', name: '💬 Messages', channelName: '💬・messages-logs', description: 'Messages édités/supprimés' },
{ key: 'members', name: '👥 Membres', channelName: '👥・members-logs', description: 'Arrivées/départs, rôles, pseudos' },
{ key: 'channels', name: '📁 Salons', channelName: '📁・channels-logs', description: 'Création/suppression de salons' },
{ key: 'roles', name: '🎭 Rôles', channelName: '🎭・roles-logs', description: 'Création/modification de rôles' },
{ key: 'invites', name: '🔗 Invitations', channelName: '🔗・invites-logs', description: 'Création/utilisation d\'invitations' },
{ key: 'server', name: '⚙️ Serveur', channelName: '⚙️・server-logs', description: 'Modifications du serveur' }
];
// Obtenir la config des logs
router.get("/bot/get-logs-config/:guildId", async (req, res) => {
const { guildId } = req.params;
if (!req.session.guilds) {
return res.status(401).json({ success: false, error: "Non connecté" });
}
try {
const row = await db.getAsync(
"SELECT * FROM logs_config WHERE guild_id = ?",
[guildId]
);
const guild = client.guilds.cache.get(guildId);
const categories = guild ? guild.channels.cache
.filter(c => c.type === 4)
.map(c => ({ id: c.id, name: c.name })) : [];
res.json({
success: true,
config: row || { enabled: false },
categories,
logTypes: LOG_TYPES
});
} catch (err) {
console.error("Erreur get logs config:", err);
res.status(500).json({ success: false, error: err.message });
}
});
// Sauvegarder la config des logs ET créer les salons
router.post("/bot/save-logs-config", express.json(), async (req, res) => {
const { guildId, enabled, categoryId, enabledLogs } = req.body;
if (!req.session.guilds) {
return res.status(401).json({ success: false, error: "Non connecté" });
}
const isAdmin = req.session.guilds.find(
g => g.id === guildId && (BigInt(g.permissions) & 0x8n) === 0x8n
);
if (!isAdmin) {
return res.status(403).json({ success: false, error: "Permission refusée" });
}
try {
const guild = client.guilds.cache.get(guildId);
if (!guild) {
return res.status(404).json({ success: false, error: "Serveur non trouvé" });
}
// Récupérer l'ancienne config
let oldConfig = await db.getAsync("SELECT * FROM logs_config WHERE guild_id = ?", [guildId]);
// Préparer les nouvelles valeurs
const newConfig = {
guild_id: guildId,
enabled: enabled ? 1 : 0,
category_id: categoryId || null,
moderation_enabled: 0, moderation_channel_id: oldConfig?.moderation_channel_id || null,
voice_enabled: 0, voice_channel_id: oldConfig?.voice_channel_id || null,
messages_enabled: 0, messages_channel_id: oldConfig?.messages_channel_id || null,
members_enabled: 0, members_channel_id: oldConfig?.members_channel_id || null,
channels_enabled: 0, channels_channel_id: oldConfig?.channels_channel_id || null,
roles_enabled: 0, roles_channel_id: oldConfig?.roles_channel_id || null,
invites_enabled: 0, invites_channel_id: oldConfig?.invites_channel_id || null,
server_enabled: 0, server_channel_id: oldConfig?.server_channel_id || null
};
// Créer/récupérer la catégorie si pas déjà sélectionnée
let category;
if (categoryId) {
category = guild.channels.cache.get(categoryId);
} else if (enabled && enabledLogs && enabledLogs.length > 0) {
// Créer une nouvelle catégorie pour les logs
category = await guild.channels.create({
name: '📜 LOGS',
type: 4, // CategoryChannel
permissionOverwrites: [
{
id: guild.id,
deny: ['ViewChannel']
},
{
id: client.user.id,
allow: ['ViewChannel', 'SendMessages', 'EmbedLinks']
}
]
});
newConfig.category_id = category.id;
}
// Pour chaque type de log activé, créer le salon si nécessaire
if (enabled && enabledLogs && category) {
for (const logKey of enabledLogs) {
const logType = LOG_TYPES.find(lt => lt.key === logKey);
if (!logType) continue;
const enabledField = `${logKey}_enabled`;
const channelField = `${logKey}_channel_id`;
newConfig[enabledField] = 1;
// Vérifier si le salon existe déjà
let existingChannel = newConfig[channelField] ?
guild.channels.cache.get(newConfig[channelField]) : null;
if (!existingChannel) {
// Créer le salon
const newChannel = await guild.channels.create({
name: logType.channelName,
type: 0, // TextChannel
parent: category.id,
permissionOverwrites: [
{
id: guild.id,
deny: ['ViewChannel']
},
{
id: client.user.id,
allow: ['ViewChannel', 'SendMessages', 'EmbedLinks', 'AttachFiles']
}
]
});
newConfig[channelField] = newChannel.id;
}
}
}
// Désactiver les logs non sélectionnés (mais garder les salons)
if (enabledLogs) {
for (const logType of LOG_TYPES) {
if (!enabledLogs.includes(logType.key)) {
newConfig[`${logType.key}_enabled`] = 0;
}
}
}
// Sauvegarder en base
await new Promise((resolve, reject) => {
db.run(`
INSERT INTO logs_config (
guild_id, enabled, category_id,
moderation_enabled, moderation_channel_id,
voice_enabled, voice_channel_id,
messages_enabled, messages_channel_id,
members_enabled, members_channel_id,
channels_enabled, channels_channel_id,
roles_enabled, roles_channel_id,
invites_enabled, invites_channel_id,
server_enabled, server_channel_id
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(guild_id) DO UPDATE SET
enabled = ?, category_id = ?,
moderation_enabled = ?, moderation_channel_id = ?,
voice_enabled = ?, voice_channel_id = ?,
messages_enabled = ?, messages_channel_id = ?,
members_enabled = ?, members_channel_id = ?,
channels_enabled = ?, channels_channel_id = ?,
roles_enabled = ?, roles_channel_id = ?,
invites_enabled = ?, invites_channel_id = ?,
server_enabled = ?, server_channel_id = ?
`, [
newConfig.guild_id, newConfig.enabled, newConfig.category_id,
newConfig.moderation_enabled, newConfig.moderation_channel_id,
newConfig.voice_enabled, newConfig.voice_channel_id,
newConfig.messages_enabled, newConfig.messages_channel_id,
newConfig.members_enabled, newConfig.members_channel_id,
newConfig.channels_enabled, newConfig.channels_channel_id,
newConfig.roles_enabled, newConfig.roles_channel_id,
newConfig.invites_enabled, newConfig.invites_channel_id,
newConfig.server_enabled, newConfig.server_channel_id,
// ON CONFLICT values
newConfig.enabled, newConfig.category_id,
newConfig.moderation_enabled, newConfig.moderation_channel_id,
newConfig.voice_enabled, newConfig.voice_channel_id,
newConfig.messages_enabled, newConfig.messages_channel_id,
newConfig.members_enabled, newConfig.members_channel_id,
newConfig.channels_enabled, newConfig.channels_channel_id,
newConfig.roles_enabled, newConfig.roles_channel_id,
newConfig.invites_enabled, newConfig.invites_channel_id,
newConfig.server_enabled, newConfig.server_channel_id
], function(err) {
if (err) reject(err);
else resolve();
});
});
res.json({
success: true,
categoryId: newConfig.category_id,
channels: {
moderation: newConfig.moderation_channel_id,
voice: newConfig.voice_channel_id,
messages: newConfig.messages_channel_id,
members: newConfig.members_channel_id,
channels: newConfig.channels_channel_id,
roles: newConfig.roles_channel_id,
invites: newConfig.invites_channel_id,
server: newConfig.server_channel_id
}
});
} catch (err) {
console.error("Erreur save logs config:", err);
res.status(500).json({ success: false, error: err.message });
}
});
// Supprimer tous les salons de logs
router.post("/bot/delete-logs-channels", express.json(), async (req, res) => {
const { guildId } = req.body;
if (!req.session.guilds) {
return res.status(401).json({ success: false, error: "Non connecté" });
}
const isAdmin = req.session.guilds.find(
g => g.id === guildId && (BigInt(g.permissions) & 0x8n) === 0x8n
);
if (!isAdmin) {
return res.status(403).json({ success: false, error: "Permission refusée" });
}
try {
const guild = client.guilds.cache.get(guildId);
if (!guild) {
return res.status(404).json({ success: false, error: "Serveur non trouvé" });
}
const config = await db.getAsync("SELECT * FROM logs_config WHERE guild_id = ?", [guildId]);
if (!config) {
return res.json({ success: true });
}
// Supprimer tous les salons de logs
const channelIds = [
config.moderation_channel_id,
config.voice_channel_id,
config.messages_channel_id,
config.members_channel_id,
config.channels_channel_id,
config.roles_channel_id,
config.invites_channel_id,
config.server_channel_id
].filter(Boolean);
for (const channelId of channelIds) {
const channel = guild.channels.cache.get(channelId);
if (channel) {
try {
await channel.delete("Suppression du système de logs");
} catch (e) {
console.error(`Erreur suppression salon ${channelId}:`, e.message);
}
}
}
// Supprimer la catégorie si elle a été créée par le bot
if (config.category_id) {
const category = guild.channels.cache.get(config.category_id);
if (category && category.children.cache.size === 0) {
try {
await category.delete("Suppression du système de logs");
} catch (e) {
console.error(`Erreur suppression catégorie:`, e.message);
}
}
}
// Reset la config en base
await new Promise((resolve, reject) => {
db.run(`
UPDATE logs_config SET
enabled = 0, category_id = NULL,
moderation_enabled = 0, moderation_channel_id = NULL,
voice_enabled = 0, voice_channel_id = NULL,
messages_enabled = 0, messages_channel_id = NULL,
members_enabled = 0, members_channel_id = NULL,
channels_enabled = 0, channels_channel_id = NULL,
roles_enabled = 0, roles_channel_id = NULL,
invites_enabled = 0, invites_channel_id = NULL,
server_enabled = 0, server_channel_id = NULL
WHERE guild_id = ?
`, [guildId], function(err) {
if (err) reject(err);
else resolve();
});
});
res.json({ success: true });
} catch (err) {
console.error("Erreur delete logs channels:", err);
res.status(500).json({ success: false, error: err.message });
}
});
app.use("/api", router); app.use("/api", router);
}; };