mirror of
https://github.com/arthur-pbty/LazyBot.git
synced 2026-06-03 23:23:33 +02:00
add logs
This commit is contained in:
+10
-2
@@ -1,8 +1,16 @@
|
||||
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/commands.js")(client);
|
||||
|
||||
@@ -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 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 (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
guild_id TEXT NOT NULL,
|
||||
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -1,9 +1,33 @@
|
||||
const { Events, EmbedBuilder } = require("discord.js");
|
||||
const db = require("../db");
|
||||
const { sendLog } = require("../fonctions/sendLog");
|
||||
|
||||
module.exports = {
|
||||
name: Events.GuildMemberAdd,
|
||||
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(
|
||||
"SELECT enabled, channel_id, message FROM welcome_config WHERE guild_id = ?",
|
||||
[member.guild.id],
|
||||
@@ -31,6 +55,8 @@ module.exports = {
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// ===== AUTOROLE =====
|
||||
db.get(
|
||||
"SELECT enabled, role_id FROM autorole_newuser_config WHERE guild_id = ?",
|
||||
[member.guild.id],
|
||||
|
||||
@@ -1,9 +1,65 @@
|
||||
const { Events, EmbedBuilder } = require("discord.js");
|
||||
const { Events, EmbedBuilder, AuditLogEvent } = require("discord.js");
|
||||
const db = require("../db");
|
||||
const { sendLog } = require("../fonctions/sendLog");
|
||||
|
||||
module.exports = {
|
||||
name: Events.GuildMemberRemove,
|
||||
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(
|
||||
"SELECT enabled, channel_id, message FROM goodbye_config WHERE guild_id = ?",
|
||||
[member.guild.id],
|
||||
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -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 }
|
||||
]
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -1,5 +1,6 @@
|
||||
const { Events, ChannelType, PermissionFlagsBits } = require("discord.js");
|
||||
const db = require("../db");
|
||||
const { sendLog } = require("../fonctions/sendLog");
|
||||
|
||||
// Store voice join times and intervals for economy
|
||||
const voiceJoinTimes = new Map(); // guildId_oderId -> timestamp
|
||||
@@ -11,6 +12,9 @@ module.exports = {
|
||||
// ===== PRIVATE ROOM (TEMP VOICE CHANNELS) =====
|
||||
await handlePrivateRoom(client, oldState, newState);
|
||||
|
||||
// ===== VOICE LOGS =====
|
||||
await handleVoiceLogs(client, oldState, newState);
|
||||
|
||||
if (newState.member.user.bot) return;
|
||||
|
||||
const guildId = newState.guild.id;
|
||||
@@ -272,3 +276,86 @@ async function trackVoiceTime(guildId, userId, oldState, newState) {
|
||||
// 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
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
};
|
||||
@@ -1035,3 +1035,134 @@ body {
|
||||
object-fit: cover;
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -74,6 +74,10 @@
|
||||
<span class="nav-item-icon">🤖</span>
|
||||
Apparence du bot
|
||||
</a>
|
||||
<a class="nav-item" data-section="logs">
|
||||
<span class="nav-item-icon">📜</span>
|
||||
Logs
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
@@ -1113,6 +1117,66 @@
|
||||
</div>
|
||||
</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>
|
||||
</main>
|
||||
|
||||
@@ -1133,5 +1197,6 @@
|
||||
<script src="/guild/scheduledMessagesForm.js"></script>
|
||||
<script src="/guild/sendMessageForm.js"></script>
|
||||
<script src="/guild/botAppearanceForm.js"></script>
|
||||
<script src="/guild/logsForm.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
})();
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user