diff --git a/README.md b/README.md index 07b9bb6..d2b6057 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ BOT_TOKEN=VOTRE_TOKEN_BOT SESSION_SECRET=un_secret_aleatoire_pour_les_sessions DB_PATH=database.sqlite OWNER=VOTRE_ID_UTILISATEUR +URL=https://ton-domaine.com ``` 4. Lancer le serveur : diff --git a/app/commands/avatar.js b/app/commands/avatar.js new file mode 100644 index 0000000..e1e1f16 --- /dev/null +++ b/app/commands/avatar.js @@ -0,0 +1,99 @@ +const addCommand = require("../fonctions/addCommand"); +const { EmbedBuilder } = require("discord.js"); + +module.exports = addCommand({ + name: "avatar", + description: "Affiche l'avatar d'un utilisateur.", + aliases: ["av", "pp", "pdp", "pfp"], + permissions: [], + botOwnerOnly: false, + dm: false, + scope: "global", + + slashOptions: [ + { + type: "USER", + name: "utilisateur", + description: "L'utilisateur dont vous voulez voir l'avatar", + required: false, + }, + ], + + executePrefix: async (client, message, args) => { + const user = message.mentions.users.first() || (args[0] ? await client.users.fetch(args[0]).catch(() => null) : null) || message.author; + const member = message.guild.members.cache.get(user.id); + await sendAvatarEmbed(message, user, member, message.author); + }, + + executeSlash: async (client, interaction) => { + const user = interaction.options.getUser("utilisateur") || interaction.user; + const member = interaction.guild.members.cache.get(user.id); + await sendAvatarEmbedSlash(interaction, user, member); + }, +}); + +async function sendAvatarEmbed(message, user, member, requestedBy) { + const globalAvatar = user.displayAvatarURL({ dynamic: true, size: 4096 }); + const serverAvatar = member?.displayAvatarURL({ dynamic: true, size: 4096 }); + + const embed = new EmbedBuilder() + .setColor(0x5865F2) + .setTitle(`🖼️ Avatar de ${user.username}`) + .setImage(globalAvatar) + .addFields( + { name: "🔗 Liens", value: `[PNG](${user.displayAvatarURL({ extension: "png", size: 4096 })}) • [JPG](${user.displayAvatarURL({ extension: "jpg", size: 4096 })}) • [WEBP](${user.displayAvatarURL({ extension: "webp", size: 4096 })})${user.avatar?.startsWith("a_") ? ` • [GIF](${user.displayAvatarURL({ extension: "gif", size: 4096 })})` : ""}`, inline: false } + ) + .setFooter({ text: `Demandé par ${requestedBy.username}`, iconURL: requestedBy.displayAvatarURL({ dynamic: true }) }) + .setTimestamp(); + + if (serverAvatar && serverAvatar !== globalAvatar) { + embed.setDescription("**Avatar global** (avatar de serveur ci-dessous)"); + } + + await message.reply({ embeds: [embed] }); + + if (serverAvatar && serverAvatar !== globalAvatar) { + const serverEmbed = new EmbedBuilder() + .setColor(0x5865F2) + .setTitle(`🖼️ Avatar serveur de ${user.username}`) + .setImage(serverAvatar) + .addFields( + { name: "🔗 Liens", value: `[PNG](${member.displayAvatarURL({ extension: "png", size: 4096 })}) • [JPG](${member.displayAvatarURL({ extension: "jpg", size: 4096 })}) • [WEBP](${member.displayAvatarURL({ extension: "webp", size: 4096 })})${member.avatar?.startsWith("a_") ? ` • [GIF](${member.displayAvatarURL({ extension: "gif", size: 4096 })})` : ""}`, inline: false } + ); + + await message.channel.send({ embeds: [serverEmbed] }); + } +} + +async function sendAvatarEmbedSlash(interaction, user, member) { + const globalAvatar = user.displayAvatarURL({ dynamic: true, size: 4096 }); + const serverAvatar = member?.displayAvatarURL({ dynamic: true, size: 4096 }); + + const embed = new EmbedBuilder() + .setColor(0x5865F2) + .setTitle(`🖼️ Avatar de ${user.username}`) + .setImage(globalAvatar) + .addFields( + { name: "🔗 Liens", value: `[PNG](${user.displayAvatarURL({ extension: "png", size: 4096 })}) • [JPG](${user.displayAvatarURL({ extension: "jpg", size: 4096 })}) • [WEBP](${user.displayAvatarURL({ extension: "webp", size: 4096 })})${user.avatar?.startsWith("a_") ? ` • [GIF](${user.displayAvatarURL({ extension: "gif", size: 4096 })})` : ""}`, inline: false } + ) + .setFooter({ text: `Demandé par ${interaction.user.username}`, iconURL: interaction.user.displayAvatarURL({ dynamic: true }) }) + .setTimestamp(); + + if (serverAvatar && serverAvatar !== globalAvatar) { + embed.setDescription("**Avatar global** (avatar de serveur ci-dessous)"); + } + + await interaction.reply({ embeds: [embed] }); + + if (serverAvatar && serverAvatar !== globalAvatar) { + const serverEmbed = new EmbedBuilder() + .setColor(0x5865F2) + .setTitle(`🖼️ Avatar serveur de ${user.username}`) + .setImage(serverAvatar) + .addFields( + { name: "🔗 Liens", value: `[PNG](${member.displayAvatarURL({ extension: "png", size: 4096 })}) • [JPG](${member.displayAvatarURL({ extension: "jpg", size: 4096 })}) • [WEBP](${member.displayAvatarURL({ extension: "webp", size: 4096 })})${member.avatar?.startsWith("a_") ? ` • [GIF](${member.displayAvatarURL({ extension: "gif", size: 4096 })})` : ""}`, inline: false } + ); + + await interaction.followUp({ embeds: [serverEmbed] }); + } +} diff --git a/app/commands/ban.js b/app/commands/ban.js new file mode 100644 index 0000000..de2e4f7 --- /dev/null +++ b/app/commands/ban.js @@ -0,0 +1,118 @@ +const addCommand = require("../fonctions/addCommand"); +const { EmbedBuilder, PermissionFlagsBits } = require("discord.js"); + +module.exports = addCommand({ + name: "ban", + description: "Bannit un membre du serveur.", + aliases: ["bannir"], + permissions: [PermissionFlagsBits.BanMembers], + botOwnerOnly: false, + dm: false, + scope: "global", + + slashOptions: [ + { + type: "USER", + name: "utilisateur", + description: "L'utilisateur à bannir", + required: true, + }, + { + type: "STRING", + name: "raison", + description: "La raison du bannissement", + required: false, + }, + { + type: "INTEGER", + name: "supprimer_messages", + description: "Nombre de jours de messages à supprimer (0-7)", + required: false, + }, + ], + + executePrefix: async (client, message, args) => { + const user = message.mentions.users.first() || (args[0] ? await client.users.fetch(args[0]).catch(() => null) : null); + if (!user) { + return message.reply({ embeds: [new EmbedBuilder().setColor(0xED4245).setDescription("❌ Veuillez mentionner un utilisateur à bannir.")] }); + } + + const reason = args.slice(1).join(" ") || "Aucune raison fournie"; + const member = message.guild.members.cache.get(user.id); + + const error = validateBan(member, message.member, message.guild, user); + if (error) return message.reply({ embeds: [error] }); + + await executeBan(user, reason, 0, message.author, message.guild, message, member); + }, + + executeSlash: async (client, interaction) => { + const user = interaction.options.getUser("utilisateur"); + const reason = interaction.options.getString("raison") || "Aucune raison fournie"; + const deleteMessageDays = interaction.options.getInteger("supprimer_messages") || 0; + const member = interaction.guild.members.cache.get(user.id); + + const error = validateBan(member, interaction.member, interaction.guild, user); + if (error) return interaction.reply({ embeds: [error], ephemeral: true }); + + await executeBan(user, reason, deleteMessageDays, interaction.user, interaction.guild, interaction, member); + }, +}); + +function validateBan(member, executor, guild, user) { + if (user.id === executor.id) { + return new EmbedBuilder().setColor(0xED4245).setDescription("❌ Vous ne pouvez pas vous bannir vous-même."); + } + if (user.id === guild.ownerId) { + return new EmbedBuilder().setColor(0xED4245).setDescription("❌ Vous ne pouvez pas bannir le propriétaire du serveur."); + } + if (member) { + if (!member.bannable) { + return new EmbedBuilder().setColor(0xED4245).setDescription("❌ Je ne peux pas bannir cet utilisateur. Vérifiez que mon rôle est au-dessus du sien."); + } + if (executor.roles.highest.position <= member.roles.highest.position && executor.id !== guild.ownerId) { + return new EmbedBuilder().setColor(0xED4245).setDescription("❌ Vous ne pouvez pas bannir quelqu'un avec un rôle égal ou supérieur au vôtre."); + } + } + return null; +} + +async function executeBan(user, reason, deleteMessageDays, moderator, guild, context, member) { + try { + try { + const dmEmbed = new EmbedBuilder() + .setColor(0xED4245) + .setTitle(`🔨 Vous avez été banni de ${guild.name}`) + .addFields( + { name: "📋 Raison", value: reason }, + { name: "👮 Modérateur", value: moderator.tag } + ) + .setTimestamp(); + await user.send({ embeds: [dmEmbed] }); + } catch (e) {} + + await guild.members.ban(user, { + reason: `${reason} | Par ${moderator.tag}`, + deleteMessageSeconds: Math.min(Math.max(deleteMessageDays, 0), 7) * 24 * 60 * 60 + }); + + const embed = new EmbedBuilder() + .setColor(0xED4245) + .setTitle("🔨 Membre banni") + .setThumbnail(user.displayAvatarURL({ dynamic: true })) + .addFields( + { name: "👤 Utilisateur", value: `${user.tag} (${user.id})`, inline: true }, + { name: "👮 Modérateur", value: moderator.tag, inline: true }, + { name: "📋 Raison", value: reason, inline: false }, + { name: "🗑️ Messages supprimés", value: `${deleteMessageDays} jour(s)`, inline: true } + ) + .setFooter({ text: `Banni par ${moderator.username}`, iconURL: moderator.displayAvatarURL({ dynamic: true }) }) + .setTimestamp(); + + await context.reply({ embeds: [embed] }); + } catch (error) { + console.error(error); + const errorEmbed = new EmbedBuilder().setColor(0xED4245).setDescription("❌ Une erreur est survenue lors du bannissement."); + await context.reply({ embeds: [errorEmbed], ephemeral: true }); + } +} diff --git a/app/commands/banner.js b/app/commands/banner.js new file mode 100644 index 0000000..b86b617 --- /dev/null +++ b/app/commands/banner.js @@ -0,0 +1,69 @@ +const addCommand = require("../fonctions/addCommand"); +const { EmbedBuilder } = require("discord.js"); + +module.exports = addCommand({ + name: "banner", + description: "Affiche la bannière d'un utilisateur.", + aliases: ["banniere"], + permissions: [], + botOwnerOnly: false, + dm: false, + scope: "global", + + slashOptions: [ + { + type: "USER", + name: "utilisateur", + description: "L'utilisateur dont vous voulez voir la bannière", + required: false, + }, + ], + + executePrefix: async (client, message, args) => { + const user = message.mentions.users.first() || (args[0] ? await client.users.fetch(args[0]).catch(() => null) : null) || message.author; + const fetchedUser = await user.fetch(); + + if (!fetchedUser.banner) { + const embed = new EmbedBuilder() + .setColor(0xED4245) + .setDescription(`❌ **${user.username}** n'a pas de bannière.`) + .setFooter({ text: `Demandé par ${message.author.username}`, iconURL: message.author.displayAvatarURL({ dynamic: true }) }); + + return message.reply({ embeds: [embed] }); + } + + const embed = createBannerEmbed(fetchedUser, user, message.author); + await message.reply({ embeds: [embed] }); + }, + + executeSlash: async (client, interaction) => { + const user = interaction.options.getUser("utilisateur") || interaction.user; + const fetchedUser = await user.fetch(); + + if (!fetchedUser.banner) { + const embed = new EmbedBuilder() + .setColor(0xED4245) + .setDescription(`❌ **${user.username}** n'a pas de bannière.`) + .setFooter({ text: `Demandé par ${interaction.user.username}`, iconURL: interaction.user.displayAvatarURL({ dynamic: true }) }); + + return interaction.reply({ embeds: [embed], ephemeral: true }); + } + + const embed = createBannerEmbed(fetchedUser, user, interaction.user); + await interaction.reply({ embeds: [embed] }); + }, +}); + +function createBannerEmbed(fetchedUser, user, requestedBy) { + const bannerUrl = fetchedUser.bannerURL({ dynamic: true, size: 4096 }); + + return new EmbedBuilder() + .setColor(fetchedUser.accentColor || 0x5865F2) + .setTitle(`🎨 Bannière de ${user.username}`) + .setImage(bannerUrl) + .addFields( + { name: "🔗 Liens", value: `[PNG](${fetchedUser.bannerURL({ extension: "png", size: 4096 })}) • [JPG](${fetchedUser.bannerURL({ extension: "jpg", size: 4096 })}) • [WEBP](${fetchedUser.bannerURL({ extension: "webp", size: 4096 })})${fetchedUser.banner?.startsWith("a_") ? ` • [GIF](${fetchedUser.bannerURL({ extension: "gif", size: 4096 })})` : ""}`, inline: false } + ) + .setFooter({ text: `Demandé par ${requestedBy.username}`, iconURL: requestedBy.displayAvatarURL({ dynamic: true }) }) + .setTimestamp(); +} diff --git a/app/commands/botinfo.js b/app/commands/botinfo.js new file mode 100644 index 0000000..0308260 --- /dev/null +++ b/app/commands/botinfo.js @@ -0,0 +1,59 @@ +const addCommand = require("../fonctions/addCommand"); +const { EmbedBuilder } = require("discord.js"); +const os = require("os"); + +module.exports = addCommand({ + name: "botinfo", + description: "Affiche les informations du bot.", + aliases: ["bot", "bi", "stats"], + permissions: [], + botOwnerOnly: false, + dm: true, + scope: "global", + + slashOptions: [], + + executePrefix: async (client, message, args) => { + const embed = createBotInfoEmbed(client, message.author); + await message.reply({ embeds: [embed] }); + }, + + executeSlash: async (client, interaction) => { + const embed = createBotInfoEmbed(client, interaction.user); + await interaction.reply({ embeds: [embed] }); + }, +}); + +function createBotInfoEmbed(client, user) { + const uptime = process.uptime(); + const days = Math.floor(uptime / 86400); + const hours = Math.floor((uptime % 86400) / 3600); + const minutes = Math.floor((uptime % 3600) / 60); + const seconds = Math.floor(uptime % 60); + const uptimeString = `${days}j ${hours}h ${minutes}m ${seconds}s`; + + const memUsage = process.memoryUsage(); + const memUsed = (memUsage.heapUsed / 1024 / 1024).toFixed(2); + const memTotal = (memUsage.heapTotal / 1024 / 1024).toFixed(2); + + return new EmbedBuilder() + .setColor(0x5865F2) + .setTitle(`🤖 Informations sur ${client.user.username}`) + .setThumbnail(client.user.displayAvatarURL({ dynamic: true, size: 256 })) + .addFields( + { name: "📛 Nom", value: client.user.tag, inline: true }, + { name: "🆔 ID", value: client.user.id, inline: true }, + { name: "📅 Créé le", value: ``, inline: true }, + { name: "⏱️ Uptime", value: uptimeString, inline: true }, + { name: "🏓 Latence", value: `${client.ws.ping}ms`, inline: true }, + { name: "💾 Mémoire", value: `${memUsed} / ${memTotal} MB`, inline: true }, + { name: "🖥️ Serveurs", value: `${client.guilds.cache.size}`, inline: true }, + { name: "👥 Utilisateurs", value: `${client.users.cache.size}`, inline: true }, + { name: "📺 Salons", value: `${client.channels.cache.size}`, inline: true }, + { name: "📚 Node.js", value: process.version, inline: true }, + { name: "📦 Discord.js", value: require("discord.js").version, inline: true }, + { name: "💻 OS", value: `${os.type()} ${os.release()}`, inline: true } + ) + .setFooter({ text: `Demandé par ${user.username}`, iconURL: user.displayAvatarURL({ dynamic: true }) }) + .setTimestamp(); +} diff --git a/app/commands/clear.js b/app/commands/clear.js new file mode 100644 index 0000000..185a9ed --- /dev/null +++ b/app/commands/clear.js @@ -0,0 +1,143 @@ +const addCommand = require("../fonctions/addCommand"); +const { EmbedBuilder, PermissionFlagsBits } = require("discord.js"); + +module.exports = addCommand({ + name: "clear", + description: "Supprime un nombre de messages dans le salon.", + aliases: ["purge", "supprimer", "delete"], + permissions: [PermissionFlagsBits.ManageMessages], + botOwnerOnly: false, + dm: false, + scope: "global", + + slashOptions: [ + { + type: "INTEGER", + name: "nombre", + description: "Le nombre de messages à supprimer (1-100)", + required: true, + }, + { + type: "USER", + name: "utilisateur", + description: "Supprimer uniquement les messages de cet utilisateur", + required: false, + }, + ], + + executePrefix: async (client, message, args) => { + const amount = parseInt(args[0]); + if (!amount || amount < 1 || amount > 100) { + return message.reply({ embeds: [new EmbedBuilder().setColor(0xED4245).setDescription("❌ Veuillez spécifier un nombre entre 1 et 100.")] }); + } + + const targetUser = message.mentions.users.first(); + + // Supprimer le message de commande + await message.delete().catch(() => {}); + + await executeClear(message.channel, amount, targetUser, message.author); + }, + + executeSlash: async (client, interaction) => { + const amount = interaction.options.getInteger("nombre"); + const targetUser = interaction.options.getUser("utilisateur"); + + await interaction.deferReply({ ephemeral: true }); + await executeClearSlash(interaction, amount, targetUser); + }, +}); + +async function executeClear(channel, amount, targetUser, moderator) { + try { + let messages = await channel.messages.fetch({ limit: 100 }); + + if (targetUser) { + messages = messages.filter(m => m.author.id === targetUser.id); + } + + const twoWeeksAgo = Date.now() - 14 * 24 * 60 * 60 * 1000; + messages = messages.filter(m => m.createdTimestamp > twoWeeksAgo); + + const messagesToDelete = [...messages.values()].slice(0, amount); + + if (messagesToDelete.length === 0) { + const errorEmbed = new EmbedBuilder() + .setColor(0xED4245) + .setDescription("❌ Aucun message à supprimer."); + const errorMsg = await channel.send({ embeds: [errorEmbed] }); + setTimeout(() => errorMsg.delete().catch(() => {}), 5000); + return; + } + + const deleted = await channel.bulkDelete(messagesToDelete, true); + + const embed = new EmbedBuilder() + .setColor(0x57F287) + .setDescription(`🗑️ **${deleted.size}** message(s) supprimé(s) par ${moderator}.`); + + if (targetUser) { + embed.addFields({ name: "👤 Utilisateur ciblé", value: targetUser.tag, inline: true }); + } + + const confirmMsg = await channel.send({ embeds: [embed] }); + setTimeout(() => confirmMsg.delete().catch(() => {}), 5000); + } catch (error) { + console.error(error); + const errorEmbed = new EmbedBuilder().setColor(0xED4245).setDescription("❌ Une erreur est survenue lors de la suppression."); + const errorMsg = await channel.send({ embeds: [errorEmbed] }); + setTimeout(() => errorMsg.delete().catch(() => {}), 5000); + } +} + +async function executeClearSlash(interaction, amount, targetUser) { + try { + let messages = await interaction.channel.messages.fetch({ limit: 100 }); + + if (targetUser) { + messages = messages.filter(m => m.author.id === targetUser.id); + } + + const twoWeeksAgo = Date.now() - 14 * 24 * 60 * 60 * 1000; + messages = messages.filter(m => m.createdTimestamp > twoWeeksAgo); + + const messagesToDelete = [...messages.values()].slice(0, amount); + + if (messagesToDelete.length === 0) { + return interaction.editReply({ + embeds: [new EmbedBuilder().setColor(0xED4245).setDescription("❌ Aucun message à supprimer (les messages de plus de 14 jours ne peuvent pas être supprimés en masse).")] + }); + } + + const deleted = await interaction.channel.bulkDelete(messagesToDelete, true); + + const embed = new EmbedBuilder() + .setColor(0x57F287) + .setTitle("🗑️ Messages supprimés") + .setDescription(`**${deleted.size}** message(s) supprimé(s) avec succès.`) + .addFields( + { name: "📺 Salon", value: `<#${interaction.channel.id}>`, inline: true }, + { name: "👮 Modérateur", value: interaction.user.tag, inline: true } + ) + .setFooter({ text: `Supprimé par ${interaction.user.username}`, iconURL: interaction.user.displayAvatarURL({ dynamic: true }) }) + .setTimestamp(); + + if (targetUser) { + embed.addFields({ name: "👤 Utilisateur ciblé", value: targetUser.tag, inline: true }); + } + + await interaction.editReply({ embeds: [embed] }); + + const confirmEmbed = new EmbedBuilder() + .setColor(0x57F287) + .setDescription(`🗑️ **${deleted.size}** message(s) supprimé(s) par ${interaction.user}.`); + + const confirmMsg = await interaction.channel.send({ embeds: [confirmEmbed] }); + setTimeout(() => confirmMsg.delete().catch(() => {}), 5000); + } catch (error) { + console.error(error); + await interaction.editReply({ + embeds: [new EmbedBuilder().setColor(0xED4245).setDescription("❌ Une erreur est survenue lors de la suppression des messages.")] + }); + } +} diff --git a/app/commands/coinflip.js b/app/commands/coinflip.js new file mode 100644 index 0000000..8af988d --- /dev/null +++ b/app/commands/coinflip.js @@ -0,0 +1,36 @@ +const addCommand = require("../fonctions/addCommand"); +const { EmbedBuilder } = require("discord.js"); + +module.exports = addCommand({ + name: "coinflip", + description: "Lance une pièce (pile ou face).", + aliases: ["cf", "flip", "piece"], + permissions: [], + botOwnerOnly: false, + dm: true, + scope: "global", + + slashOptions: [], + + executePrefix: async (client, message, args) => { + const embed = createCoinflipEmbed(message.author); + await message.reply({ embeds: [embed] }); + }, + + executeSlash: async (client, interaction) => { + const embed = createCoinflipEmbed(interaction.user); + await interaction.reply({ embeds: [embed] }); + }, +}); + +function createCoinflipEmbed(user) { + const result = Math.random() < 0.5 ? "pile" : "face"; + const emoji = result === "pile" ? "🪙" : "💿"; + + return new EmbedBuilder() + .setColor(result === "pile" ? 0xFEE75C : 0x5865F2) + .setTitle(`${emoji} Coinflip`) + .setDescription(`La pièce est tombée sur **${result.toUpperCase()}** !`) + .setFooter({ text: `Lancé par ${user.username}`, iconURL: user.displayAvatarURL({ dynamic: true }) }) + .setTimestamp(); +} diff --git a/app/commands/kick.js b/app/commands/kick.js new file mode 100644 index 0000000..4079423 --- /dev/null +++ b/app/commands/kick.js @@ -0,0 +1,108 @@ +const addCommand = require("../fonctions/addCommand"); +const { EmbedBuilder, PermissionFlagsBits } = require("discord.js"); + +module.exports = addCommand({ + name: "kick", + description: "Expulse un membre du serveur.", + aliases: ["expulser"], + permissions: [PermissionFlagsBits.KickMembers], + botOwnerOnly: false, + dm: false, + scope: "global", + + slashOptions: [ + { + type: "USER", + name: "utilisateur", + description: "L'utilisateur à expulser", + required: true, + }, + { + type: "STRING", + name: "raison", + description: "La raison de l'expulsion", + required: false, + }, + ], + + executePrefix: async (client, message, args) => { + const user = message.mentions.users.first() || (args[0] ? await client.users.fetch(args[0]).catch(() => null) : null); + if (!user) { + return message.reply({ embeds: [new EmbedBuilder().setColor(0xED4245).setDescription("❌ Veuillez mentionner un utilisateur à expulser.")] }); + } + + const reason = args.slice(1).join(" ") || "Aucune raison fournie"; + const member = message.guild.members.cache.get(user.id); + + const error = validateKick(member, message.member, message.guild, user); + if (error) return message.reply({ embeds: [error] }); + + await executeKick(member, user, reason, message.author, message.guild, message); + }, + + executeSlash: async (client, interaction) => { + const user = interaction.options.getUser("utilisateur"); + const reason = interaction.options.getString("raison") || "Aucune raison fournie"; + const member = interaction.guild.members.cache.get(user.id); + + const error = validateKick(member, interaction.member, interaction.guild, user); + if (error) return interaction.reply({ embeds: [error], ephemeral: true }); + + await executeKick(member, user, reason, interaction.user, interaction.guild, interaction); + }, +}); + +function validateKick(member, executor, guild, user) { + if (!member) { + return new EmbedBuilder().setColor(0xED4245).setDescription("❌ Cet utilisateur n'est pas sur ce serveur."); + } + if (member.id === executor.id) { + return new EmbedBuilder().setColor(0xED4245).setDescription("❌ Vous ne pouvez pas vous expulser vous-même."); + } + if (member.id === guild.ownerId) { + return new EmbedBuilder().setColor(0xED4245).setDescription("❌ Vous ne pouvez pas expulser le propriétaire du serveur."); + } + if (!member.kickable) { + return new EmbedBuilder().setColor(0xED4245).setDescription("❌ Je ne peux pas expulser cet utilisateur. Vérifiez que mon rôle est au-dessus du sien."); + } + if (executor.roles.highest.position <= member.roles.highest.position && executor.id !== guild.ownerId) { + return new EmbedBuilder().setColor(0xED4245).setDescription("❌ Vous ne pouvez pas expulser quelqu'un avec un rôle égal ou supérieur au vôtre."); + } + return null; +} + +async function executeKick(member, user, reason, moderator, guild, context) { + try { + try { + const dmEmbed = new EmbedBuilder() + .setColor(0xFEE75C) + .setTitle(`🚪 Vous avez été expulsé de ${guild.name}`) + .addFields( + { name: "📋 Raison", value: reason }, + { name: "👮 Modérateur", value: moderator.tag } + ) + .setTimestamp(); + await user.send({ embeds: [dmEmbed] }); + } catch (e) {} + + await member.kick(reason); + + const embed = new EmbedBuilder() + .setColor(0x57F287) + .setTitle("🚪 Membre expulsé") + .setThumbnail(user.displayAvatarURL({ dynamic: true })) + .addFields( + { name: "👤 Utilisateur", value: `${user.tag} (${user.id})`, inline: true }, + { name: "👮 Modérateur", value: moderator.tag, inline: true }, + { name: "📋 Raison", value: reason, inline: false } + ) + .setFooter({ text: `Expulsé par ${moderator.username}`, iconURL: moderator.displayAvatarURL({ dynamic: true }) }) + .setTimestamp(); + + await context.reply({ embeds: [embed] }); + } catch (error) { + console.error(error); + const errorEmbed = new EmbedBuilder().setColor(0xED4245).setDescription("❌ Une erreur est survenue lors de l'expulsion."); + await context.reply({ embeds: [errorEmbed], ephemeral: true }); + } +} diff --git a/app/commands/panel.js b/app/commands/panel.js new file mode 100644 index 0000000..ea718f4 --- /dev/null +++ b/app/commands/panel.js @@ -0,0 +1,54 @@ +const addCommand = require("../fonctions/addCommand"); +const { EmbedBuilder, ButtonBuilder, ButtonStyle, ActionRowBuilder } = require("discord.js"); + +module.exports = addCommand({ + name: "panel", + description: "Affiche le lien vers le dashboard du serveur.", + aliases: ["dashboard", "dash", "web"], + permissions: [], + botOwnerOnly: false, + dm: false, + scope: "global", + + slashOptions: [], + + executePrefix: async (client, message, args) => { + const embed = createPanelEmbed(message.guild, message.author, client); + const row = createPanelButton(message.guild.id); + await message.reply({ embeds: [embed], components: [row] }); + }, + + executeSlash: async (client, interaction) => { + const embed = createPanelEmbed(interaction.guild, interaction.user, client); + const row = createPanelButton(interaction.guild.id); + await interaction.reply({ embeds: [embed], components: [row] }); + }, +}); + +function createPanelEmbed(guild, user, client) { + const dashboardUrl = `${process.env.URL}/guild/${guild.id}`; + + return new EmbedBuilder() + .setColor(0x5865F2) + .setTitle("🎛️ Panel de configuration") + .setDescription(`Accédez au dashboard pour configurer **${guild.name}** !`) + .setThumbnail(guild.iconURL({ dynamic: true, size: 256 })) + .addFields( + { name: "🔗 Lien direct", value: dashboardUrl, inline: false }, + { name: "⚙️ Fonctionnalités", value: "• Système de niveaux\n• Économie\n• Messages de bienvenue/au revoir\n• Auto-rôles\n• Et plus encore !", inline: false } + ) + .setFooter({ text: `Demandé par ${user.username}`, iconURL: user.displayAvatarURL({ dynamic: true }) }) + .setTimestamp(); +} + +function createPanelButton(guildId) { + const dashboardUrl = `${process.env.URL}/guild/${guildId}`; + + const button = new ButtonBuilder() + .setLabel("Ouvrir le Dashboard") + .setStyle(ButtonStyle.Link) + .setURL(dashboardUrl) + .setEmoji("🌐"); + + return new ActionRowBuilder().addComponents(button); +} diff --git a/app/commands/serverinfo.js b/app/commands/serverinfo.js new file mode 100644 index 0000000..7c79750 --- /dev/null +++ b/app/commands/serverinfo.js @@ -0,0 +1,72 @@ +const addCommand = require("../fonctions/addCommand"); +const { EmbedBuilder, ChannelType } = require("discord.js"); + +module.exports = addCommand({ + name: "serverinfo", + description: "Affiche les informations du serveur.", + aliases: ["server", "si", "serveur"], + permissions: [], + botOwnerOnly: false, + dm: false, + scope: "global", + + slashOptions: [], + + executePrefix: async (client, message, args) => { + const embed = await createServerInfoEmbed(message.guild, message.author); + await message.reply({ embeds: [embed] }); + }, + + executeSlash: async (client, interaction) => { + const embed = await createServerInfoEmbed(interaction.guild, interaction.user); + await interaction.reply({ embeds: [embed] }); + }, +}); + +async function createServerInfoEmbed(guild, user) { + await guild.members.fetch(); + + const textChannels = guild.channels.cache.filter(c => c.type === ChannelType.GuildText).size; + const voiceChannels = guild.channels.cache.filter(c => c.type === ChannelType.GuildVoice).size; + const categories = guild.channels.cache.filter(c => c.type === ChannelType.GuildCategory).size; + + const onlineMembers = guild.members.cache.filter(m => m.presence?.status !== "offline").size; + const botCount = guild.members.cache.filter(m => m.user.bot).size; + const humanCount = guild.memberCount - botCount; + + const verificationLevels = { + 0: "Aucune", + 1: "Faible", + 2: "Moyenne", + 3: "Haute", + 4: "Très haute" + }; + + const boostLevel = guild.premiumTier ? `Niveau ${guild.premiumTier}` : "Aucun"; + + const embed = new EmbedBuilder() + .setColor(0x5865F2) + .setTitle(`📊 Informations sur ${guild.name}`) + .setThumbnail(guild.iconURL({ dynamic: true, size: 256 })) + .addFields( + { name: "📛 Nom", value: guild.name, inline: true }, + { name: "🆔 ID", value: guild.id, inline: true }, + { name: "👑 Propriétaire", value: `<@${guild.ownerId}>`, inline: true }, + { name: "📅 Créé le", value: ``, inline: true }, + { name: "🔒 Vérification", value: verificationLevels[guild.verificationLevel], inline: true }, + { name: "🚀 Boost", value: `${boostLevel} (${guild.premiumSubscriptionCount || 0} boosts)`, inline: true }, + { name: "👥 Membres", value: `Total: ${guild.memberCount}\n👤 Humains: ${humanCount}\n🤖 Bots: ${botCount}\n🟢 En ligne: ${onlineMembers}`, inline: true }, + { name: "📺 Salons", value: `💬 Textuels: ${textChannels}\n🔊 Vocaux: ${voiceChannels}\n📁 Catégories: ${categories}`, inline: true }, + { name: "🎭 Rôles", value: `${guild.roles.cache.size}`, inline: true }, + { name: "😀 Emojis", value: `${guild.emojis.cache.size}`, inline: true }, + { name: "🎨 Stickers", value: `${guild.stickers.cache.size}`, inline: true } + ) + .setFooter({ text: `Demandé par ${user.username}`, iconURL: user.displayAvatarURL({ dynamic: true }) }) + .setTimestamp(); + + if (guild.bannerURL()) { + embed.setImage(guild.bannerURL({ size: 512 })); + } + + return embed; +} diff --git a/app/commands/timeout.js b/app/commands/timeout.js new file mode 100644 index 0000000..8f854b8 --- /dev/null +++ b/app/commands/timeout.js @@ -0,0 +1,157 @@ +const addCommand = require("../fonctions/addCommand"); +const { EmbedBuilder, PermissionFlagsBits } = require("discord.js"); + +module.exports = addCommand({ + name: "timeout", + description: "Met un membre en timeout (exclusion temporaire).", + aliases: ["mute", "to"], + permissions: [PermissionFlagsBits.ModerateMembers], + botOwnerOnly: false, + dm: false, + scope: "global", + + slashOptions: [ + { + type: "USER", + name: "utilisateur", + description: "L'utilisateur à mettre en timeout", + required: true, + }, + { + type: "STRING", + name: "durée", + description: "La durée du timeout (ex: 10m, 1h, 1d, 1w)", + required: true, + }, + { + type: "STRING", + name: "raison", + description: "La raison du timeout", + required: false, + }, + ], + + executePrefix: async (client, message, args) => { + const user = message.mentions.users.first() || (args[0] ? await client.users.fetch(args[0]).catch(() => null) : null); + if (!user) { + return message.reply({ embeds: [new EmbedBuilder().setColor(0xED4245).setDescription("❌ Veuillez mentionner un utilisateur.")] }); + } + + const durationStr = args[1]; + if (!durationStr) { + return message.reply({ embeds: [new EmbedBuilder().setColor(0xED4245).setDescription("❌ Veuillez spécifier une durée (ex: 10m, 1h, 1d).")] }); + } + + const reason = args.slice(2).join(" ") || "Aucune raison fournie"; + const member = message.guild.members.cache.get(user.id); + + await executeTimeout(member, user, durationStr, reason, message.member, message.author, message.guild, message); + }, + + executeSlash: async (client, interaction) => { + const user = interaction.options.getUser("utilisateur"); + const durationStr = interaction.options.getString("durée"); + const reason = interaction.options.getString("raison") || "Aucune raison fournie"; + const member = interaction.guild.members.cache.get(user.id); + + await executeTimeout(member, user, durationStr, reason, interaction.member, interaction.user, interaction.guild, interaction); + }, +}); + +function parseDuration(durationStr) { + const durationRegex = /^(\d+)(s|m|h|d|w)$/; + const match = durationStr.match(durationRegex); + + if (!match) return null; + + const amount = parseInt(match[1]); + const unit = match[2]; + + const multipliers = { + s: 1000, + m: 60 * 1000, + h: 60 * 60 * 1000, + d: 24 * 60 * 60 * 1000, + w: 7 * 24 * 60 * 60 * 1000 + }; + + return { ms: amount * multipliers[unit], amount, unit }; +} + +async function executeTimeout(member, user, durationStr, reason, executor, moderator, guild, context) { + const duration = parseDuration(durationStr); + const maxTimeout = 28 * 24 * 60 * 60 * 1000; + + const sendError = async (msg) => { + const embed = new EmbedBuilder().setColor(0xED4245).setDescription(msg); + await context.reply({ embeds: [embed], ephemeral: true }); + }; + + if (!duration) { + return sendError("❌ Format de durée invalide. Utilisez: `10s`, `10m`, `1h`, `1d`, `1w`"); + } + + if (duration.ms > maxTimeout) { + return sendError("❌ La durée maximale du timeout est de 28 jours."); + } + + if (!member) { + return sendError("❌ Cet utilisateur n'est pas sur ce serveur."); + } + + if (member.id === executor.id) { + return sendError("❌ Vous ne pouvez pas vous mettre en timeout vous-même."); + } + + if (member.id === guild.ownerId) { + return sendError("❌ Vous ne pouvez pas mettre le propriétaire en timeout."); + } + + if (!member.moderatable) { + return sendError("❌ Je ne peux pas mettre cet utilisateur en timeout. Vérifiez que mon rôle est au-dessus du sien."); + } + + if (executor.roles.highest.position <= member.roles.highest.position && executor.id !== guild.ownerId) { + return sendError("❌ Vous ne pouvez pas mettre en timeout quelqu'un avec un rôle égal ou supérieur au vôtre."); + } + + try { + try { + const dmEmbed = new EmbedBuilder() + .setColor(0xFEE75C) + .setTitle(`⏰ Vous avez été mis en timeout sur ${guild.name}`) + .addFields( + { name: "⏱️ Durée", value: durationStr }, + { name: "📋 Raison", value: reason }, + { name: "👮 Modérateur", value: moderator.tag } + ) + .setTimestamp(); + await user.send({ embeds: [dmEmbed] }); + } catch (e) {} + + await member.timeout(duration.ms, `${reason} | Par ${moderator.tag}`); + + const unitNames = { s: "seconde(s)", m: "minute(s)", h: "heure(s)", d: "jour(s)", w: "semaine(s)" }; + const endTimestamp = Math.floor((Date.now() + duration.ms) / 1000); + + const embed = new EmbedBuilder() + .setColor(0xFEE75C) + .setTitle("⏰ Membre mis en timeout") + .setThumbnail(user.displayAvatarURL({ dynamic: true })) + .addFields( + { name: "👤 Utilisateur", value: `${user.tag} (${user.id})`, inline: true }, + { name: "👮 Modérateur", value: moderator.tag, inline: true }, + { name: "⏱️ Durée", value: `${duration.amount} ${unitNames[duration.unit]}`, inline: true }, + { name: "🕐 Expire", value: `\n()`, inline: false }, + { name: "📋 Raison", value: reason, inline: false } + ) + .setFooter({ text: `Timeout par ${moderator.username}`, iconURL: moderator.displayAvatarURL({ dynamic: true }) }) + .setTimestamp(); + + await context.reply({ embeds: [embed] }); + } catch (error) { + console.error(error); + const errorEmbed = new EmbedBuilder().setColor(0xED4245).setDescription("❌ Une erreur est survenue lors du timeout."); + await context.reply({ embeds: [errorEmbed], ephemeral: true }); + } +} diff --git a/app/commands/untimeout.js b/app/commands/untimeout.js new file mode 100644 index 0000000..9506c8b --- /dev/null +++ b/app/commands/untimeout.js @@ -0,0 +1,100 @@ +const addCommand = require("../fonctions/addCommand"); +const { EmbedBuilder, PermissionFlagsBits } = require("discord.js"); + +module.exports = addCommand({ + name: "untimeout", + description: "Retire le timeout d'un membre.", + aliases: ["unmute", "uto"], + permissions: [PermissionFlagsBits.ModerateMembers], + botOwnerOnly: false, + dm: false, + scope: "global", + + slashOptions: [ + { + type: "USER", + name: "utilisateur", + description: "L'utilisateur dont vous voulez retirer le timeout", + required: true, + }, + { + type: "STRING", + name: "raison", + description: "La raison du retrait du timeout", + required: false, + }, + ], + + executePrefix: async (client, message, args) => { + const user = message.mentions.users.first() || (args[0] ? await client.users.fetch(args[0]).catch(() => null) : null); + if (!user) { + return message.reply({ embeds: [new EmbedBuilder().setColor(0xED4245).setDescription("❌ Veuillez mentionner un utilisateur.")] }); + } + + const reason = args.slice(1).join(" ") || "Aucune raison fournie"; + const member = message.guild.members.cache.get(user.id); + + await executeUntimeout(member, user, reason, message.author, message.guild, message); + }, + + executeSlash: async (client, interaction) => { + const user = interaction.options.getUser("utilisateur"); + const reason = interaction.options.getString("raison") || "Aucune raison fournie"; + const member = interaction.guild.members.cache.get(user.id); + + await executeUntimeout(member, user, reason, interaction.user, interaction.guild, interaction); + }, +}); + +async function executeUntimeout(member, user, reason, moderator, guild, context) { + const sendError = async (msg) => { + const embed = new EmbedBuilder().setColor(0xED4245).setDescription(msg); + await context.reply({ embeds: [embed], ephemeral: true }); + }; + + if (!member) { + return sendError("❌ Cet utilisateur n'est pas sur ce serveur."); + } + + if (!member.isCommunicationDisabled()) { + return sendError("❌ Cet utilisateur n'est pas en timeout."); + } + + if (!member.moderatable) { + return sendError("❌ Je ne peux pas modifier le timeout de cet utilisateur."); + } + + try { + await member.timeout(null, `${reason} | Par ${moderator.tag}`); + + try { + const dmEmbed = new EmbedBuilder() + .setColor(0x57F287) + .setTitle(`✅ Votre timeout a été retiré sur ${guild.name}`) + .addFields( + { name: "📋 Raison", value: reason }, + { name: "👮 Modérateur", value: moderator.tag } + ) + .setTimestamp(); + await user.send({ embeds: [dmEmbed] }); + } catch (e) {} + + const embed = new EmbedBuilder() + .setColor(0x57F287) + .setTitle("✅ Timeout retiré") + .setThumbnail(user.displayAvatarURL({ dynamic: true })) + .addFields( + { name: "👤 Utilisateur", value: `${user.tag} (${user.id})`, inline: true }, + { name: "👮 Modérateur", value: moderator.tag, inline: true }, + { name: "📋 Raison", value: reason, inline: false } + ) + .setFooter({ text: `Untimeout par ${moderator.username}`, iconURL: moderator.displayAvatarURL({ dynamic: true }) }) + .setTimestamp(); + + await context.reply({ embeds: [embed] }); + } catch (error) { + console.error(error); + const errorEmbed = new EmbedBuilder().setColor(0xED4245).setDescription("❌ Une erreur est survenue lors du retrait du timeout."); + await context.reply({ embeds: [errorEmbed], ephemeral: true }); + } +} diff --git a/app/commands/uptime.js b/app/commands/uptime.js new file mode 100644 index 0000000..bda7e73 --- /dev/null +++ b/app/commands/uptime.js @@ -0,0 +1,44 @@ +const addCommand = require("../fonctions/addCommand"); +const { EmbedBuilder } = require("discord.js"); + +module.exports = addCommand({ + name: "uptime", + description: "Affiche depuis combien de temps le bot est en ligne.", + aliases: ["up", "online"], + permissions: [], + botOwnerOnly: false, + dm: true, + scope: "global", + + slashOptions: [], + + executePrefix: async (client, message, args) => { + const embed = createUptimeEmbed(message.author); + await message.reply({ embeds: [embed] }); + }, + + executeSlash: async (client, interaction) => { + const embed = createUptimeEmbed(interaction.user); + await interaction.reply({ embeds: [embed] }); + }, +}); + +function createUptimeEmbed(user) { + const uptime = process.uptime(); + const days = Math.floor(uptime / 86400); + const hours = Math.floor((uptime % 86400) / 3600); + const minutes = Math.floor((uptime % 3600) / 60); + const seconds = Math.floor(uptime % 60); + + const startTimestamp = Math.floor((Date.now() - uptime * 1000) / 1000); + + return new EmbedBuilder() + .setColor(0x57F287) + .setTitle("⏱️ Uptime") + .setDescription(`Le bot est en ligne depuis **${days}** jours, **${hours}** heures, **${minutes}** minutes et **${seconds}** secondes.`) + .addFields( + { name: "🚀 Démarré", value: `\n()`, inline: true } + ) + .setFooter({ text: `Demandé par ${user.username}`, iconURL: user.displayAvatarURL({ dynamic: true }) }) + .setTimestamp(); +} diff --git a/app/commands/userinfo.js b/app/commands/userinfo.js new file mode 100644 index 0000000..280b2c7 --- /dev/null +++ b/app/commands/userinfo.js @@ -0,0 +1,95 @@ +const addCommand = require("../fonctions/addCommand"); +const { EmbedBuilder } = require("discord.js"); + +module.exports = addCommand({ + name: "userinfo", + description: "Affiche les informations d'un utilisateur.", + aliases: ["user", "ui", "whois", "membre"], + permissions: [], + botOwnerOnly: false, + dm: false, + scope: "global", + + slashOptions: [ + { + type: "USER", + name: "utilisateur", + description: "L'utilisateur dont vous voulez voir les informations", + required: false, + }, + ], + + executePrefix: async (client, message, args) => { + const user = message.mentions.users.first() || (args[0] ? await client.users.fetch(args[0]).catch(() => null) : null) || message.author; + const member = message.guild.members.cache.get(user.id); + const embed = createUserInfoEmbed(user, member, message.guild, message.author); + await message.reply({ embeds: [embed] }); + }, + + executeSlash: async (client, interaction) => { + const user = interaction.options.getUser("utilisateur") || interaction.user; + const member = interaction.guild.members.cache.get(user.id); + const embed = createUserInfoEmbed(user, member, interaction.guild, interaction.user); + await interaction.reply({ embeds: [embed] }); + }, +}); + +function createUserInfoEmbed(user, member, guild, requestedBy) { + const flags = user.flags?.toArray() || []; + const badges = { + ActiveDeveloper: "👨‍💻 Développeur Actif", + BugHunterLevel1: "🐛 Bug Hunter Niveau 1", + BugHunterLevel2: "🐛 Bug Hunter Niveau 2", + CertifiedModerator: "🛡️ Modérateur Certifié", + HypeSquadOnlineHouse1: "🏠 HypeSquad Bravery", + HypeSquadOnlineHouse2: "🏠 HypeSquad Brilliance", + HypeSquadOnlineHouse3: "🏠 HypeSquad Balance", + Hypesquad: "🎉 HypeSquad Events", + Partner: "👥 Partenaire Discord", + PremiumEarlySupporter: "💎 Early Supporter", + Staff: "⚙️ Staff Discord", + VerifiedBot: "✅ Bot Vérifié", + VerifiedDeveloper: "🔧 Développeur Vérifié" + }; + + const userBadges = flags.map(flag => badges[flag] || flag).join("\n") || "Aucun"; + + const status = { + online: "🟢 En ligne", + idle: "🟡 Inactif", + dnd: "🔴 Ne pas déranger", + offline: "⚫ Hors ligne" + }; + + const embed = new EmbedBuilder() + .setColor(member?.displayHexColor || 0x5865F2) + .setTitle(`👤 Informations sur ${user.username}`) + .setThumbnail(user.displayAvatarURL({ dynamic: true, size: 256 })) + .addFields( + { name: "📛 Nom", value: user.tag, inline: true }, + { name: "🆔 ID", value: user.id, inline: true }, + { name: "🤖 Bot", value: user.bot ? "Oui" : "Non", inline: true }, + { name: "📅 Compte créé le", value: `\n()`, inline: true } + ); + + if (member) { + embed.addFields( + { name: "📥 A rejoint le", value: `\n()`, inline: true }, + { name: "📊 Statut", value: status[member.presence?.status || "offline"], inline: true }, + { name: "🎨 Couleur", value: member.displayHexColor, inline: true }, + { name: `🎭 Rôles (${member.roles.cache.size - 1})`, value: member.roles.cache.filter(r => r.id !== guild.id).map(r => r.toString()).slice(0, 10).join(", ") || "Aucun", inline: false } + ); + + if (member.premiumSinceTimestamp) { + embed.addFields({ name: "🚀 Booster depuis", value: ``, inline: true }); + } + } + + embed.addFields({ name: "🏅 Badges", value: userBadges, inline: false }); + + embed + .setFooter({ text: `Demandé par ${requestedBy.username}`, iconURL: requestedBy.displayAvatarURL({ dynamic: true }) }) + .setTimestamp(); + + return embed; +}