add structure for simplifie creation of global command

This commit is contained in:
Arthur Puechberty
2026-01-17 15:13:22 +01:00
parent 08647924e3
commit d5f0f4c30b
11 changed files with 593 additions and 17 deletions
+6 -5
View File
@@ -19,13 +19,14 @@ cd mon-bot-discord
3. Créer un fichier `.env` :
```env
TOKEN=VOTRE_TOKEN_BOT
CLIENT_ID=VOTRE_CLIENT_ID
CLIENT_SECRET=VOTRE_CLIENT_SECRET
REDIRECT_URI=http://localhost:3000/auth/discord/callback
SESSION_SECRET=un_secret_aleatoire
CLIENT_ID=VOTRE_BOT_CLIENT_ID
CLIENT_SECRET=VOTRE_BOT_CLIENT_SECRET
REDIRECT_URI=https://your_domaine.com/auth/discord/callback
PORT=3000
BOT_TOKEN=VOTRE_TOKEN_BOT
SESSION_SECRET=un_secret_aleatoire_pour_les_sessions
DB_PATH=database.sqlite
OWNER=VOTRE_ID_UTILISATEUR
```
4. Lancer le serveur :
+3 -4
View File
@@ -7,6 +7,9 @@ const e = require('express');
const client = new Client({ intents: Object.values(GatewayIntentBits) });
require("./loader/events.js")(client);
require("./loader/commands.js")(client);
client.once(Events.ClientReady, async () => {
console.log(`Bot connecté en tant que ${client.user.tag}`);
await loadSlashCommands(client);
@@ -17,10 +20,6 @@ client.once(Events.ClientReady, async () => {
client.on(Events.InteractionCreate, async interaction => {
if (!interaction.isChatInputCommand()) return;
if (interaction.commandName === 'ping') {
await interaction.reply('Pong!');
}
else if (interaction.commandName === 'level') {
const guildId = interaction.guild.id;
const userId = interaction.user.id;
+170
View File
@@ -0,0 +1,170 @@
const addCommand = require("../fonctions/addCommand");
const {
SlashCommandBuilder,
ButtonStyle,
ButtonBuilder,
ActionRowBuilder,
EmbedBuilder,
} = require("discord.js");
module.exports = addCommand(
(this.name = "ping"),
(this.description = "Cette commande permet de vérifier la latence du bot."),
(this.aliases = ["latency", "lag", "responseTime"]),
(this.permissions = []),
(this.botOwnerOnly = false),
(this.dm = true),
(this.executePrefix = async (client, message, args) => {
const pingBtn = new ButtonBuilder()
.setCustomId("pingBtn")
.setLabel("🔄")
.setStyle(ButtonStyle.Primary);
const row = new ActionRowBuilder().addComponents(pingBtn);
const embed = new EmbedBuilder()
.setTitle("Pong !")
.setDescription(`La latence du bot est de \`${client.ws.ping}\`ms.`)
.setColor("#0099FF")
.setTimestamp()
.setFooter({
text: `Demandé par ${message.author.tag}`,
iconURL: message.author.displayAvatarURL(),
});
const sendMessage = await message.reply({
embeds: [embed],
components: [row],
});
const filter = (i) => i.customId === "pingBtn";
const collector = sendMessage.createMessageComponentCollector({
filter,
time: 120000,
});
collector.on("collect", async (i) => {
if (i.user.id !== message.author.id)
return i.reply({
content: "Vous n'êtes pas l'auteur du message.",
ephemeral: true,
});
const embed = new EmbedBuilder()
.setTitle("Pong !")
.setDescription(`La latence du bot est de \`${client.ws.ping}\`ms.`)
.setColor("#0099FF")
.setTimestamp()
.setFooter({
text: `Demandé par ${message.author.tag}`,
iconURL: message.author.displayAvatarURL(),
});
sendMessage.edit({ embeds: [embed], components: [row] });
i.reply({ content: "La latence a été rafraichie.", ephemeral: true });
});
collector.on("end", async () => {
const embed = new EmbedBuilder()
.setTitle("Pong !")
.setDescription(`La latence du bot est de \`${client.ws.ping}\`ms.`)
.setColor("#0099FF")
.setTimestamp()
.setFooter({
text: `Demandé par ${message.author.tag}`,
iconURL: message.author.displayAvatarURL(),
});
sendMessage.edit({ embeds: [embed], components: [] });
});
}),
(this.executeSlash = async (client, interaction) => {
const pingBtn = new ButtonBuilder()
.setCustomId("pingBtn")
.setLabel("🔄")
.setStyle(ButtonStyle.Primary);
const row = new ActionRowBuilder().addComponents(pingBtn);
const embed = new EmbedBuilder()
.setTitle("Pong !")
.setDescription(`La latence du bot est de \`${client.ws.ping}\`ms.`)
.setColor("#0099FF")
.setTimestamp()
.setFooter({
text: `Demandé par ${interaction.user.tag}`,
iconURL: interaction.user.displayAvatarURL(),
});
if (interaction.options.getBoolean("actualiser") === false) {
const sendMessage = await interaction.reply({
embeds: [embed],
components: [row],
});
const filter = (i) => i.customId === "pingBtn";
const collector = sendMessage.createMessageComponentCollector({
filter,
time: 120000,
});
collector.on("collect", async (i) => {
if (i.user.id !== interaction.user.id)
return i.reply({
content: "Vous n'êtes pas l'auteur du message.",
ephemeral: true,
});
const embed = new EmbedBuilder()
.setTitle("Pong !")
.setDescription(`La latence du bot est de \`${client.ws.ping}\`ms.`)
.setColor("#0099FF")
.setTimestamp()
.setFooter({
text: `Demandé par ${interaction.user.tag}`,
iconURL: interaction.user.displayAvatarURL(),
});
sendMessage.edit({ embeds: [embed], components: [row] });
i.reply({ content: "La latence a été rafraichie.", ephemeral: true });
});
collector.on("end", async () => {
const embed = new EmbedBuilder()
.setTitle("Pong !")
.setDescription(`La latence du bot est de \`${client.ws.ping}\`ms.`)
.setColor("#0099FF")
.setTimestamp()
.setFooter({
text: `Demandé par ${interaction.user.tag}`,
iconURL: interaction.user.displayAvatarURL(),
});
sendMessage.edit({ embeds: [embed], components: [] });
});
} else {
const sendMessage = await interaction.reply({ embeds: [embed] });
const interval = setInterval(() => {
const embed = new EmbedBuilder()
.setTitle("Pong !")
.setDescription(`La latence du bot est de \`${client.ws.ping}\`ms.`)
.setColor("#0099FF")
.setTimestamp()
.setFooter({
text: `Demandé par ${interaction.user.tag}`,
iconURL: interaction.user.displayAvatarURL(),
});
sendMessage.edit({ embeds: [embed] });
}, 10000);
setTimeout(() => {
clearInterval(interval);
}, 120000);
}
}),
(this.slashOptions = new SlashCommandBuilder().addBooleanOption((option) =>
option
.setName("actualiser")
.setDescription(
"Actualiser automatiquement la latence du bot toute les 10 secondes pandant 2 minutes.",
)
.setRequired(false),
)),
);
+6
View File
@@ -69,6 +69,12 @@ db.exec(`
last_xp_message_timestamp INTEGER,
PRIMARY KEY (guild_id, user_id)
);
CREATE TABLE IF NOT EXISTS prefix (
guildId TEXT NOT NULL,
prefix TEXT NOT NULL DEFAULT '!',
PRIMARY KEY (guildId)
);
`);
module.exports = db;
+64
View File
@@ -0,0 +1,64 @@
const getPrefix = require("../../fonctions/getPrefix");
module.exports = {
name: "messageCreate",
async execute(client, message) {
if (!message || !message.author) return;
if (message.author.bot) return;
if (!message.content) return;
let prefix;
if (message.channel.type === 1) {
prefix = await getPrefix(message.channel.id);
} else {
prefix = await getPrefix(message.guild.id);
}
if (!message.content.startsWith(prefix)) return;
const args = message.content.slice(prefix.length).trim().split(/ +/);
const commandName = args.shift().toLowerCase();
const command =
client.commands.get(commandName) ||
client.commands.find(
(cmd) => cmd.aliases && cmd.aliases.includes(commandName),
);
if (!command) return;
if (command.dm !== true && message.channel.type === 1)
return message
.reply({
content: "Cette commande ne peut pas être utilisée en message privé.",
})
.then((msg) => setTimeout(() => msg.delete(), 5000));
if (process.env.OWNER && !process.env.OWNER === message.author.id) {
if (command.botOwnerOnly)
return message
.reply({
content: "Cette commande est réservée au propriétaire du bot.",
})
.then((msg) => setTimeout(() => msg.delete(), 5000));
if (
command.permissions &&
message.channel.type !== 1 &&
!command.permissions.every((permission) =>
message.member.permissions.has(permission),
)
)
return message
.reply({
content: "Vous n'avez pas la permission d'utiliser cette commande.",
})
.then((msg) => setTimeout(() => msg.delete(), 5000));
}
try {
command.executePrefix(client, message, args);
console.log(`[CMD - PREFIX] ${message.author.tag} | ${commandName}`);
} catch (error) {
console.error(
`Erreur lors de l'exécution de la commande '${commandName}':`,
error,
);
}
},
};
+46
View File
@@ -0,0 +1,46 @@
module.exports = {
name: "interactionCreate",
async execute(client, interaction) {
if (!interaction.isCommand()) return;
const command = client.commands.get(interaction.commandName);
if (!command) return;
if (command.dm !== true && interaction.channel.type === 1)
return interaction.reply({
content: "Cette commande ne peut pas être utilisée en message privé.",
ephemeral: true,
});
if (process.env.OWNER && !process.env.OWNER === interaction.user.id) {
if (command.botOwnerOnly)
return interaction.reply({
content: "Cette commande est réservée au propriétaire du bot.",
ephemeral: true,
});
if (
command.permissions &&
interaction.channel.type !== 1 &&
!command.permissions.every((permission) =>
interaction.member.permissions.has(permission),
)
)
return interaction.reply({
content: "Vous n'avez pas la permission d'utiliser cette commande.",
ephemeral: true,
});
}
try {
command.executeSlash(client, interaction);
console.log(
`[CMD - SLASH] ${interaction.user.tag} | ${interaction.commandName}`,
);
} catch (error) {
console.error(
`Erreur lors de l'exécution de la commande slash '${interaction.commandName}':`,
error,
);
}
},
};
+172
View File
@@ -0,0 +1,172 @@
const { SlashCommandBuilder, PermissionsBitField } = require("discord.js");
/**
* Ajoute une nouvelle commande au bot.
*
* @param {string} name - Le nom de la commande.
* @param {string} description - La description de la commande.
* @param {Array<string>} aliases - Les alias de la commande.
* @param {Array<PermissionsBitField>} permissions - Les permissions nécessaires pour exécuter la commande.
* @param {boolean} botOwnerOnly - Si la commande est réservée au propriétaire du bot.
* @param {boolean} dm - Si la commande peut être exécutée en message privé.
* @param {Function} executePrefix - La fonction à exécuter avec un préfixe.
* @param {Function} executeSlash - La fonction à exécuter comme commande slash.
* @param {Array<Object>} slashOptions - Les options pour la commande slash.
*
* @returns {void} Ne retourne rien.
*/
function addCommand(
name,
description,
aliases,
permissions,
botOwnerOnly,
dm,
executePrefix,
executeSlash,
slashOptions,
) {
if (!name) return console.error("Le nom de la commande est requis.");
name = name.toString();
name = name.toLowerCase();
name = name.replace(/ /g, "_");
if (!description)
return console.error("La description de la commande est requise.");
description = description.toString();
if (!aliases) aliases = [];
if (!Array.isArray(aliases)) aliases = [aliases];
aliases = aliases.map((alias) => alias.toString());
if (!permissions) permissions = [];
if (!Array.isArray(permissions)) permissions = [permissions];
if (!botOwnerOnly) botOwnerOnly = false;
botOwnerOnly = Boolean(botOwnerOnly);
if (!dm) dm = false;
dm = Boolean(dm);
if (!executePrefix)
return console.error("La fonction executePrefix est requise.");
if (!executeSlash)
return console.error("La fonction executeSlash est requise.");
if (
typeof executePrefix !== "function" ||
executePrefix.constructor.name !== "AsyncFunction"
) {
return console.error(
"La fonction executePrefix doit être une fonction asynchrone.",
);
}
if (
typeof executeSlash !== "function" ||
executeSlash.constructor.name !== "AsyncFunction"
) {
return console.error(
"La fonction executeSlash doit être une fonction asynchrone.",
);
}
const executePrefixParams = executePrefix
.toString()
.match(/\(([^)]+)\)/)[1]
.split(",")
.map((param) => param.trim());
if (
executePrefixParams.length !== 3 ||
executePrefixParams[0] !== "client" ||
executePrefixParams[1] !== "message" ||
executePrefixParams[2] !== "args"
) {
return console.error(
'La fonction executePrefix doit avoir les paramètres "client", "message" et "args".',
);
}
const executeSlashParams = executeSlash
.toString()
.match(/\(([^)]+)\)/)[1]
.split(",")
.map((param) => param.trim());
if (
executeSlashParams.length !== 2 ||
executeSlashParams[0] !== "client" ||
executeSlashParams[1] !== "interaction"
) {
return console.error(
'La fonction executeSlash doit avoir les paramètres "client" et "interaction".',
);
}
const command = {
name,
description,
aliases,
permissions,
botOwnerOnly,
dm,
executePrefix,
executeSlash,
};
let default_member_permissions;
if (command.permissions.length === 0) {
default_member_permissions = null;
} else {
default_member_permissions = new PermissionsBitField();
command.permissions.forEach(
(permission) => (default_member_permissions += BigInt(permission)),
);
}
command.data = new SlashCommandBuilder()
.setName(command.name)
.setDescription(command.description)
.setDMPermission(command.dm)
.setDefaultMemberPermissions(default_member_permissions);
for (const key in command.data) {
if (command.data.hasOwnProperty(key)) {
const value = command.data[key];
if (value !== undefined && key !== "options") {
slashOptions[key] = value;
}
}
}
command.data = slashOptions;
let utilisation = "";
command.data.options.forEach((option) => {
let optionUsage = "";
if (option.choices) {
optionUsage = option.required
? `<${option.choices.map((choice) => choice.name).join("|")}>`
: `[${option.choices.map((choice) => choice.name).join("|")}]`;
} else {
if (option.type === 3) {
optionUsage = option.required ? `<${option.name}>` : `[${option.name}]`;
} else if (option.type === 4) {
optionUsage = option.required ? `<${option.name}>` : `[${option.name}]`;
} else if (option.type === 5) {
optionUsage = option.required ? `<True|False>` : `[True|False]`;
} else if (option.type === 6) {
optionUsage = option.required ? `<@member>` : `[@member]`;
} else if (option.type === 7) {
optionUsage = option.required ? `<#channel>` : `[#channel]`;
} else if (option.type === 8) {
optionUsage = option.required ? `<@role>` : `[@role]`;
} else if (option.type === 9) {
optionUsage = option.required ? `<@mention>` : `[@mention]`;
} else if (option.type === 10) {
optionUsage = option.required ? `<${option.name}>` : `[${option.name}]`;
} else if (option.type === 11) {
optionUsage = option.required ? `<${option.name}>` : `[${option.name}]`;
}
}
utilisation += ` ${optionUsage}`;
});
utilisation = utilisation.trim();
command.utilisation = utilisation;
return command;
}
module.exports = addCommand;
+16
View File
@@ -0,0 +1,16 @@
const db = require("../db.js");
module.exports = async function getPrefix(guildId) {
const prefix = await new Promise((resolve, reject) => {
db.get(
`SELECT prefix FROM prefix WHERE guildId = ?`,
[guildId],
(err, row) => {
if (err) reject(err);
resolve(row);
},
);
});
return prefix ? prefix.prefix : "!";
};
+59
View File
@@ -0,0 +1,59 @@
const fs = require("fs");
const path = require("path");
const { Collection } = require("discord.js");
module.exports = (client) => {
client.commands = new Collection();
function loadCommandsFromDirectory(directory) {
fs.readdir(directory, (err, files) => {
if (err) {
console.error("Erreur lors de la lecture du dossier:", err);
return;
}
files.forEach((file) => {
const filePath = path.join(directory, file);
fs.stat(filePath, (err, stats) => {
if (err) {
console.error(
"Erreur lors de la récupération des informations du fichier:",
err,
);
return;
}
if (stats.isDirectory()) {
loadCommandsFromDirectory(filePath);
} else if (stats.isFile() && file.endsWith(".js")) {
try {
const command = require(filePath);
const commandName = command.name || file.split(".")[0];
if (!command.category) {
const parentDir = path.basename(path.dirname(filePath));
command.category =
parentDir === "commands" ? "🌟・Other" : parentDir;
}
if (!command.dm) command.dm = false;
if (!command.botOwnerOnly) command.botOwnerOnly = false;
if (!command.permissions) command.permissions = [];
if (!command.aliases) command.aliases = [];
if (!command.description)
command.description = "Aucune description.";
client.commands.set(commandName, command);
delete require.cache[require.resolve(filePath)];
} catch (error) {
console.error(
`Erreur lors du chargement de la commande '${file}':`,
error,
);
}
}
});
});
});
}
loadCommandsFromDirectory(path.join(__dirname, "..", "commands"));
};
+45
View File
@@ -0,0 +1,45 @@
const fs = require("fs");
const path = require("path");
module.exports = (client) => {
function loadEventsFromDirectory(directory) {
fs.readdir(directory, (err, files) => {
if (err) {
console.error("Erreur lors de la lecture du dossier:", err);
return;
}
files.forEach((file) => {
const filePath = path.join(directory, file);
fs.stat(filePath, (err, stats) => {
if (err) {
console.error(
"Erreur lors de la récupération des informations du fichier:",
err,
);
return;
}
if (stats.isDirectory()) {
loadEventsFromDirectory(filePath);
} else if (stats.isFile() && file.endsWith(".js")) {
try {
const event = require(filePath);
let eventName = event.name || file.split(".")[0];
client.on(eventName, event.execute.bind(null, client));
delete require.cache[require.resolve(filePath)];
} catch (error) {
console.error(
`Erreur lors du chargement de l'événement '${file}':`,
error,
);
}
}
});
});
});
}
loadEventsFromDirectory(path.join(__dirname, "..", "events"));
};
+6 -8
View File
@@ -13,17 +13,15 @@ module.exports = async (client, guildId = null) => {
========================= */
if (!guildId) {
const globalCommands = [
{
name: 'ping',
description: 'Replies with Pong!',
},
];
const globalCommands = [];
client.commands.forEach((command) => {
globalCommands.push(command.data.toJSON());
});
try {
console.log('Refreshing GLOBAL slash commands...');
await rest.put(
Routes.applicationCommands(CLIENT_ID),
Routes.applicationCommands(client.user.id),
{ body: globalCommands }
);
console.log('Global slash commands loaded');
@@ -69,7 +67,7 @@ module.exports = async (client, guildId = null) => {
try {
await rest.put(
Routes.applicationGuildCommands(CLIENT_ID, guild.id),
Routes.applicationGuildCommands(client.user.id, guild.id),
{ body: guildCommands }
);
console.log(`Guild commands updated for ${guild.name}`);