From 1b0315f8748e381e6a241ba73980417eb7a3726b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tina=C3=ABl=20Devresse?= <32441291+HunteRoi@users.noreply.github.com> Date: Fri, 20 Sep 2024 00:49:22 +0200 Subject: [PATCH] General cleanup & refactor (#106) * refactor: avoid high cognitive complexity * fix(interactionCreate): bug due to already replied interaction * fix(interactionCreate): sometimes, the reply is not existing so it needs to be sent * fix(events): apply sonarcloud fixes --- Database.Dockerfile | 2 +- Dockerfile | 13 +-- src/commands/announce.ts | 2 +- src/commands/help.ts | 64 +++++++----- src/commands/pinmsg.ts | 87 ++++++++++------- src/config.ts | 2 +- src/datadrop.ts | 3 +- src/events/interactionCreate.ts | 168 ++++++++++++++++++++------------ src/events/messageCreate.ts | 93 +++++++++++------- src/events/ready.ts | 7 +- 10 files changed, 268 insertions(+), 173 deletions(-) diff --git a/Database.Dockerfile b/Database.Dockerfile index 69269c0..f4653ee 100644 --- a/Database.Dockerfile +++ b/Database.Dockerfile @@ -3,7 +3,7 @@ FROM postgres:15.1 ARG POSTGRES_DB # Install pg_cron extension -RUN apt-get update && apt-get install -y postgresql-15-cron +RUN apt-get update && apt-get install -y postgresql-15-cron && apt-get clean # Add pg_cron to shared_preload_libraries RUN echo "shared_preload_libraries='pg_cron'" >> /usr/share/postgresql/postgresql.conf.sample diff --git a/Dockerfile b/Dockerfile index adcfb8f..218f1c3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,8 @@ -FROM node:lts-alpine as BUILD +FROM node:lts-alpine AS BUILD WORKDIR /app -RUN apk add zip +RUN apk --no-cache add zip COPY . . RUN yarn install --frozen-lockfile @@ -12,16 +12,13 @@ RUN yarn install --production RUN zip -r app.zip ./node_modules ./build ./yarn.lock ./.env # ------------------------------------------------------------ -FROM node:lts-alpine as APP +FROM node:lts-alpine AS APP WORKDIR /app -RUN apk add unzip +RUN apk --no-cache add unzip COPY --from=BUILD /app/app.zip . -RUN unzip app.zip -RUN rm app.zip -RUN mv ./build/* . -RUN rm -rf ./build +RUN unzip app.zip && rm app.zip && mv ./build/* . && rm -rf ./build CMD ["sh", "-c", "sleep 2 && node ./index.js"] diff --git a/src/commands/announce.ts b/src/commands/announce.ts index a6076cd..2831fe3 100644 --- a/src/commands/announce.ts +++ b/src/commands/announce.ts @@ -39,7 +39,7 @@ export default { max: 1, time: 30000 }); - const confirmation = collectedMessages && collectedMessages.first(); + const confirmation = collectedMessages?.first(); if (confirmation && confirmation.content.toLowerCase() === 'yes') { const annoncesChannel = await message.guild.channels.fetch(announce.channelid); if (!annoncesChannel) return; diff --git a/src/commands/help.ts b/src/commands/help.ts index 04abd25..245a8f1 100644 --- a/src/commands/help.ts +++ b/src/commands/help.ts @@ -1,11 +1,7 @@ -import { ChannelType, ColorResolvable, EmbedBuilder, Message } from 'discord.js'; +import { Collection, ColorResolvable, EmbedBuilder, Message } from 'discord.js'; import { DatadropClient } from '../datadrop.js'; -function buildEmbed(title: string, color: ColorResolvable, description: string): EmbedBuilder { - return new EmbedBuilder().setTitle(title).setColor(color).setDescription(description); -} - export default { name: 'help', description: @@ -16,36 +12,22 @@ export default { execute: async (client: DatadropClient, message: Message, args: string[]) => { const { prefix } = client.config; const { commands } = client; - const data: string[] = []; let embed: EmbedBuilder; if (!args.length) { - // lister les commandes - data.push(`Utilisez \`${prefix}${module.exports.name} [commande]\` pour lire le message d'aide sur une commande spécifique.\n`); - data.push('Commandes disponibles :'); - data.push(`- ${commands.map((command) => command.name).join('\n- ')}`); - embed = buildEmbed('Liste des commandes', 'Random', data.join('\n')); - } - else { - // afficher le message d'aide de la commande args[0] + embed = listAvailableCommands(prefix, commands); + } else { const name = args[0].toLowerCase(); - const command = - commands.get(name) || - commands.find((c) => c.aliases && c.aliases.includes(name)); + const command = commands.get(name) || commands.find((c) => c.aliases?.includes(name)); if (!command) { - if (message.channel.isSendable()) + if (message.channel.isSendable()) { await message.channel.send("Ce n'est pas une commande valide."); + } return; } - data.push(`**Nom:** ${command.name}`); - if (command.aliases) data.push(`**Alias:** ${command.aliases.join(', ')}`); - if (command.description) data.push(`**Description:** ${command.description}`); - if (command.usage) data.push(`**Usage:** \`${prefix}${command.name} ${command.usage}\``); - data.push(`**Cooldown:** ${command.cooldown || 3} seconde(s)`); - - embed = buildEmbed(`Aide pour '${command.name}'`, 'Random', data.join('\n')); + embed = buildCommandUsage(prefix, command); } try { @@ -57,3 +39,35 @@ export default { } } }; + +function buildEmbed(title: string, color: ColorResolvable, description: string): EmbedBuilder { + return new EmbedBuilder().setTitle(title).setColor(color).setDescription(description); +} + +function listAvailableCommands(prefix: string | undefined, commands: Collection): EmbedBuilder { + const data: string[] = []; + + data.push(`Utilisez \`${prefix}${module.exports.name} [commande]\` pour lire le message d'aide sur une commande spécifique.\n`); + data.push('Commandes disponibles :'); + data.push(`- ${commands.map((command) => command.name).join('\n- ')}`); + + return buildEmbed('Liste des commandes', 'Random', data.join('\n')); +} + +function buildCommandUsage(prefix: string | undefined, command: any): EmbedBuilder { + const data: string[] = []; + + data.push(`**Nom:** ${command.name}`); + if (command.aliases) { + data.push(`**Alias:** ${command.aliases.join(', ')}`); + } + if (command.description) { + data.push(`**Description:** ${command.description}`); + } + if (command.usage) { + data.push(`**Usage:** \`${prefix}${command.name} ${command.usage}\``); + } + data.push(`**Cooldown:** ${command.cooldown || 3} seconde(s)`); + + return buildEmbed(`Aide pour '${command.name}'`, 'Random', data.join('\n')); +} diff --git a/src/commands/pinmsg.ts b/src/commands/pinmsg.ts index 0117575..5e48695 100644 --- a/src/commands/pinmsg.ts +++ b/src/commands/pinmsg.ts @@ -1,4 +1,4 @@ -import { Message, MessageResolvable } from 'discord.js'; +import { Message, MessageReference, MessageResolvable } from 'discord.js'; import { DatadropClient } from '../datadrop.js'; @@ -12,46 +12,67 @@ export default { async execute(client: DatadropClient, message: Message, args: string[]) { if (!message.guild || !message.member || !message.reference) return; - const reference = message.reference; const { communitymanagerRoleid, adminRoleid, delegatesRoleid, professorRoleid } = client.config; const verboseIsActive = args && args.length >= 1 && (args[0] === '--verbose' || args[0] === '-v'); - const hasAnyRequiredRole = [communitymanagerRoleid, adminRoleid, delegatesRoleid, professorRoleid].some(r => message.member!.roles.cache.has(r)); - if (!hasAnyRequiredRole) { - if (verboseIsActive && message.channel.isSendable()) await message.channel.send(`❌ **Oups!** - Tu n'es pas membre d'un des rôles nécessaires et n'es donc pas éligible à cette commande.`); - else await message.react('❌'); + const hasRequiredRoles = await isAuthorized(message, [communitymanagerRoleid, adminRoleid, delegatesRoleid, professorRoleid], client, verboseIsActive); + if (!hasRequiredRoles) return; - client.logger.info(`Le membre <${message.member.displayName}> (${message.member.id}) a tenté d'épingler/désépingler le message <${reference.messageId}> mais n'a pas les droits nécessaires.`); - return; + const referencedMessage = await getMessage(message, client, verboseIsActive); + if (!referencedMessage) return; + + if (referencedMessage.pinned) { + await referencedMessage.unpin(); + await replyOnAction(message, '✅', 'Message désépinglé!', verboseIsActive); + } else { + await referencedMessage.pin(); + await replyOnAction(message, '✅', 'Message épinglé!', verboseIsActive); } + client.logger.info(`Le membre <${message.member.displayName}> (${message.member.id}) a épinglé/désépinglé le message <${referencedMessage.id}>.`); + } +}; - if (!reference || reference.channelId != message.channel.id) { - if (verboseIsActive && message.channel.isSendable()) await message.channel.send('❌ **Oups!** - Pas de référence. Peut-être avez-vous oublié de sélectionner le message à (dés)épingler en y répondant? (cfr )'); - else await message.react('❌'); +async function replyOnAction(message: Message, emoji: string, content: string, verboseIsActive?: boolean): Promise { + if (verboseIsActive && message.channel.isSendable()) { + await message.channel.send(`${emoji} ${content}`); + } else { + await message.react(emoji); + } +} - client.logger.info(`Le membre <${message.member.displayName}> (${message.member.id}) a tenté d'épingler/désépingler un message sans le référencer.`); - return; - } +async function isAuthorized(message: Message, requiredRoles: string[], client: DatadropClient, verboseIsActive?: boolean): Promise { + if (!requiredRoles.some(r => message.member!.roles.cache.has(r))) { + await replyOnAction(message, '❌', '**Oups!** - Tu n\'es pas membre d\'un des rôles nécessaires et n\'es donc pas éligible à cette commande.', verboseIsActive); - const channel = message.channel; - const parentMessage = await channel.messages.fetch(reference.messageId as MessageResolvable); - if (!parentMessage) { - if (verboseIsActive && message.channel.isSendable()) await message.channel.send('❌ **Oups!** - Message non trouvé. Peut-être a-t-il été supprimé?'); - else await message.react('❌'); + client.logger.info(`Le membre <${message.member?.displayName}> (${message.member?.id}) a tenté d'épingler/désépingler un message mais n'a pas les droits nécessaires.`); + return false; + } + return true; +} - client.logger.info(`Le membre <${message.member.displayName}> (${message.member.id}) a tenté d'épingler/désépingler un message non-trouvé.`); - return; - } +async function getReference(message: Message, client: DatadropClient, verboseIsActive?: boolean): Promise { + const reference = message.reference; + if (!reference || reference.channelId != message.channel.id) { + await replyOnAction(message, '❌', '**Oups!** - Pas de référence. Peut-être avez-vous oublié de sélectionner le message à (dés)épingler en y répondant? (cfr )', verboseIsActive); - if (parentMessage.pinned) { - await parentMessage.unpin(); - if (verboseIsActive && message.channel.isSendable()) await message.channel.send('✅ Message désépinglé!'); - else await message.react('✅'); - } else { - await parentMessage.pin(); - if (verboseIsActive && message.channel.isSendable()) await message.channel.send('✅ Message épinglé!'); - else await message.react('✅'); - } - client.logger.info(`Le membre <${message.member.displayName}> (${message.member.id}) a épinglé/désépinglé le message <${parentMessage.id}>.`); + client.logger.info(`Le membre <${message.member?.displayName}> (${message.member?.id}) a tenté d'épingler/désépingler un message sans le référencer.`); + return null; } -}; + return reference; +} + +async function getMessage(message: Message, client: DatadropClient, verboseIsActive?: boolean): Promise { + const reference = await getReference(message, client, verboseIsActive); + if (!reference) return null; + + const channel = message.channel; + const referencedMessage = await channel.messages.fetch(reference.messageId as MessageResolvable); + if (!referencedMessage) { + await replyOnAction(message, '❌', '**Oups!** - Message non trouvé. Peut-être a-t-il été supprimé?', verboseIsActive); + + client.logger.info(`Le membre <${message.member?.displayName}> (${message.member?.id}) a tenté d'épingler/désépingler un message non-trouvé.`); + return null; + } + + return referencedMessage; +} diff --git a/src/config.ts b/src/config.ts index a3171ba..9d1b35b 100644 --- a/src/config.ts +++ b/src/config.ts @@ -42,7 +42,7 @@ export async function readConfig(): Promise { const json = JSON.parse(process.env.CONFIG ?? '{}'); for (const prop in json) { - if (prop.match(/regex/i)) { + if (/regex/i.exec(prop)) { json[prop] = new RegExp(json[prop]); } } diff --git a/src/datadrop.ts b/src/datadrop.ts index d18c6f1..86188de 100644 --- a/src/datadrop.ts +++ b/src/datadrop.ts @@ -131,7 +131,8 @@ export class DatadropClient extends Client { .map(role => role as Role) .filter(requiredRole => !!requiredRole); - this.logger.info(`Le rôle ${role.name} (<${role.id}>) n'a pas pu être donné à <${member.user.tag}> parce que tous les rôles requis ne sont pas assignés à ce membre: ${requiredRolesMissing.map(role => `${role.name} (<${role.id}>)`).join(', ')}.`); + const roleNames = requiredRolesMissing.map(role => `${role.name} (<${role.id}>)`).join(', '); + this.logger.info(`Le rôle ${role.name} (<${role.id}>) n'a pas pu être donné à <${member.user.tag}> parce que tous les rôles requis ne sont pas assignés à ce membre: ${roleNames}.`); await interaction.editReply(`Tu ne peux pas t'assigner le rôle ${role}! Tu dois d'abord avoir les rôles suivants: ${requiredRolesMissing.join(', ')}.`); }); this.selfRoleManager.on(SelfRoleManagerEvents.maxRolesReach, async (member: GuildMember, interaction: StringSelectMenuInteraction | ButtonInteraction, currentRolesNumber: number, maxRolesNumber: number, role: Role) => { diff --git a/src/events/interactionCreate.ts b/src/events/interactionCreate.ts index 6aafa8a..1c24af2 100644 --- a/src/events/interactionCreate.ts +++ b/src/events/interactionCreate.ts @@ -1,84 +1,122 @@ -import { ActionRowBuilder, ButtonBuilder, ButtonStyle, Interaction, italic, ModalBuilder, TextInputBuilder, TextInputStyle } from 'discord.js'; +import { ActionRowBuilder, ButtonBuilder, ButtonInteraction, ButtonStyle, Interaction, italic, ModalBuilder, ModalSubmitInteraction, RepliableInteraction, TextInputBuilder, TextInputStyle } from 'discord.js'; import { DatadropClient } from 'src/datadrop.js'; export default async function interactionCreate(client: DatadropClient, interaction: Interaction) { - const user = interaction.user; - if (interaction.isButton() && interaction.customId.startsWith('la') && interaction.customId.includes(user.id)) { - const userFromDatabase = await client.database.read(user.id); - if (userFromDatabase?.activatedCode) { - await interaction.reply({ ephemeral: true, content: 'Tu as déjà lié ton compte Hénallux avec ton compte Discord!' }); - return; - } + if (isVerificationButton(interaction)) { + if (await isAlreadyVerified(client, interaction)) return; + + await showVerificationModal(interaction); + } + else if (isVerificationModalSubmission(interaction)) { + await interaction.deferReply({ ephemeral: true }); + + if (await isAlreadyVerified(client, interaction)) return; - const modal = new ModalBuilder().setTitle('Lier son compte'); - const input = new TextInputBuilder(); + let content: string; switch (interaction.customId) { - case `lae${user.id}`: { - modal.setCustomId(`lacm${user.id}`); - input.setLabel('Email Hénallux') - .setPlaceholder('********@henallux.be') - .setRequired(true) - .setMinLength(20) - .setStyle(TextInputStyle.Short) - .setCustomId('email'); + case `lacm${interaction.user.id}`: { + content = await getEmailAndSendCode(interaction, client); + await showVerificationButton(interaction, `${content}\n${italic("D'ailleurs, l'email peut potentiellement se retrouver dans tes spams!")}`, client); break; } - case `lacb${user.id}`: { - modal.setCustomId(`lav${user.id}`); - input.setLabel('Code de vérification') - .setPlaceholder('******') - .setRequired(true) - .setMinLength(6) - .setMaxLength(6) - .setStyle(TextInputStyle.Short) - .setCustomId('code'); + case `lav${interaction.user.id}`: { + content = await verifyCode(interaction, client); + await interaction.editReply({ content }); break; } } - const inputComponent = new ActionRowBuilder().addComponents(input); - modal.addComponents(inputComponent); - await interaction.showModal(modal); } - else if (interaction.isModalSubmit() && interaction.customId.includes(user.id)) { - await interaction.deferReply({ ephemeral: true }); + else if (isUnhandledInteraction(interaction) && interaction.isRepliable()) { + await interaction.reply({ ephemeral: true, content: "Ce message ne t'était assurément pas destiné!" }); + } +} - const userFromDatabase = await client.database.read(user.id); - if (userFromDatabase?.activatedCode) { - await interaction.editReply({ content: 'Tu as déjà lié ton compte Hénallux avec ton compte Discord!' }); - return; +async function isAlreadyVerified(client: DatadropClient, interaction: Interaction) { + const userFromDatabase = await client.database.read(interaction.user.id); + if (userFromDatabase?.activatedCode) { + if (interaction.isRepliable()) { + const replyOptions = { + content: 'Tu as déjà lié ton compte Hénallux avec ton compte Discord!', + ephemeral: true + }; + if (interaction.replied) { + await interaction.editReply(replyOptions); + } + else { + await interaction.reply(replyOptions); + } } - switch (interaction.customId) { - case `lacm${user.id}`: { - const regex = /(etu\d{5,}|mdp[a-z]{5,})@henallux\.be/; - const email = interaction.fields.getTextInputValue('email'); - if (!regex.test(email)) { - await interaction.editReply({ content: "L'adresse email fournie n'est pas valide!\nSi tu es un.e étudiant.e, elle doit correspondre à etuXXXXX@henallux.be.\nSi tu es un.e professeur.e, elle doit correspondre à mdpXXXXX@henallux.be." }); - break; - } + return true; + } + return false; +} - const result = await client.verificationManager.sendCode(user.id, { to: email, guildId: interaction.guildId }); +function isVerificationButton(interaction: Interaction): interaction is ButtonInteraction { + return interaction.isButton() && interaction.customId.startsWith('la') && interaction.customId.includes(interaction.user.id); +} - const linkAccountButton = new ButtonBuilder() - .setLabel('Vérifier son code') - .setEmoji('🎰') - .setStyle(ButtonStyle.Primary) - .setCustomId(`lacb${user.id}`) - .setDisabled(result === client.errorMessage || result.endsWith(client.activeAccountMessage)); - const buttonComponent = new ActionRowBuilder().addComponents(linkAccountButton); - await interaction.editReply({ content: `${result}\n${italic("D'ailleurs, l'email peut potentiellement se retrouver dans tes spams!")}`, components: [buttonComponent] }); - break; - } - case `lav${user.id}`: { - const code = interaction.fields.getTextInputValue('code'); - const result = await client.verificationManager.verifyCode(user.id, code); - await interaction.editReply({ content: result }); - break; - } - } +async function showVerificationModal(interaction: ButtonInteraction) { + const modal = new ModalBuilder().setTitle('Lier son compte'); + const input = new TextInputBuilder(); + switch (interaction.customId) { + case `lae${interaction.user.id}`: + modal.setCustomId(`lacm${interaction.user.id}`); + input.setLabel('Email Hénallux') + .setPlaceholder('********@henallux.be') + .setRequired(true) + .setMinLength(20) + .setStyle(TextInputStyle.Short) + .setCustomId('email'); + break; + case `lacb${interaction.user.id}`: + modal.setCustomId(`lav${interaction.user.id}`); + input.setLabel('Code de vérification') + .setPlaceholder('******') + .setRequired(true) + .setMinLength(6) + .setMaxLength(6) + .setStyle(TextInputStyle.Short) + .setCustomId('code'); + break; } - else if ((interaction.isButton() || interaction.isModalSubmit() || interaction.isStringSelectMenu()) && !interaction.customId.startsWith('sr-') && !interaction.customId.includes(user.id)) { - await interaction.reply({ ephemeral: true, content: "Ce message ne t'était assurément pas destiné!" }); + const inputComponent = new ActionRowBuilder().addComponents(input); + modal.addComponents(inputComponent); + await interaction.showModal(modal); +} + +function isVerificationModalSubmission(interaction: Interaction): interaction is ModalSubmitInteraction { + return interaction.isModalSubmit() && interaction.customId.includes(interaction.user.id); +} + +async function verifyCode(interaction: ModalSubmitInteraction, client: DatadropClient) { + const code = interaction.fields.getTextInputValue('code'); + return await client.verificationManager.verifyCode(interaction.user.id, code); +} + +async function getEmailAndSendCode(interaction: ModalSubmitInteraction, client: DatadropClient) { + const regex = /(etu\d{5,}|mdp[a-z]{5,})@henallux\.be/; + const email = interaction.fields.getTextInputValue('email'); + if (!regex.test(email)) { + return "L'adresse email fournie n'est pas valide!\nSi tu es un.e étudiant.e, elle doit correspondre à etuXXXXX@henallux.be.\nSi tu es un.e professeur.e, elle doit correspondre à mdpXXXXX@henallux.be."; } -}; + + return await client.verificationManager.sendCode(interaction.user.id, { to: email, guildId: interaction.guildId }); +} + +async function showVerificationButton(interaction: RepliableInteraction, content: string, client: DatadropClient) { + const linkAccountButton = new ButtonBuilder() + .setLabel('Vérifier son code') + .setEmoji('🎰') + .setStyle(ButtonStyle.Primary) + .setCustomId(`lacb${interaction.user.id}`) + .setDisabled(content.includes(client.errorMessage) || content.includes(client.activeAccountMessage)); + const buttonComponent = new ActionRowBuilder().addComponents(linkAccountButton); + await interaction.editReply({ content, components: [buttonComponent] }); +} + +function isUnhandledInteraction(interaction: Interaction) { + return (interaction.isButton() || interaction.isModalSubmit() || interaction.isStringSelectMenu()) + && !interaction.customId.startsWith('sr-') && !interaction.customId.includes(interaction.user.id) +} diff --git a/src/events/messageCreate.ts b/src/events/messageCreate.ts index 1d81989..acc791c 100644 --- a/src/events/messageCreate.ts +++ b/src/events/messageCreate.ts @@ -1,58 +1,81 @@ import { ChannelType, Message } from 'discord.js'; - import { DatadropClient } from '../datadrop.js'; const escapeRegex = (str: string | null | undefined) => str?.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); export default async function messageCreate(client: DatadropClient, message: Message) { - const { - prefix, - communitymanagerRoleid, - adminRoleid, - ownerIds, - } = client.config; - if (message.author.bot) return; const lowerCasedContent = message.content.toLowerCase(); - const prefixRegex = new RegExp(`^(<@!?${client.user!.id}>|${escapeRegex(prefix)})\\s*`); - if (!prefixRegex.test(lowerCasedContent.split(' ').join(''))) return; + const prefixRegex = new RegExp(`^(<@!?${client.user!.id}>|${escapeRegex(client.config.prefix)})\\s*`); + if (!isCommand(lowerCasedContent, prefixRegex)) return; + + const commandName = getCommandNameFromMessage(message.content, prefixRegex); + const command = getCommand(client, commandName); + if (!command) return; + + logCommandUsage(client, message, command); + + if (!isCommandAllowed(client, message, command)) return; + + try { + executeCommand(client, message, command); + } catch (err) { + handleCommandError(client, message); + } +} + +function isCommand(content: string, prefixRegex: RegExp): boolean { + return prefixRegex.test(content.split(' ').join('')); +} - const matches = message.content.match(prefixRegex); - if (!matches) return; +function getCommandNameFromMessage(content: string, prefixRegex: RegExp): string { + const matches = prefixRegex.exec(content); + if (!matches) return ''; const [, matchedPrefix] = matches; + const args = content.slice(matchedPrefix.length).trim().split(/ +/g) ?? []; + if (!args || args.length === 0) return ''; + return args.shift()!.toLowerCase(); +} - const args = message.content.slice(matchedPrefix.length).trim().split(/ +/g) ?? []; - if (!args || args.length === 0) return; +function getCommand(client: DatadropClient, commandName: string) { + return client.commands.get(commandName) || client.commands.find((cmd) => cmd.aliases?.includes(commandName)); +} - const commandName = args.shift()!.toLowerCase(); - const command = client.commands.get(commandName) || client.commands.find((cmd) => cmd.aliases && cmd.aliases.includes(commandName)); - if (!command) return; +function logCommandUsage(client: DatadropClient, message: Message, command: any) { + const channelInfo = message.channel.type === ChannelType.GuildText ? `dans #${message.channel.name} (${message.channel.id})` : 'en DM'; + client.logger.info(`${message.author.tag} (${message.author.id}) a utilisé '${command.name}' ${channelInfo}`); +} + +function isCommandAllowed(client: DatadropClient, message: Message, command: any): boolean { + const { ownerIds, communitymanagerRoleid, adminRoleid } = client.config; - client.logger.info(`${message.author.tag} (${message.author.id}) a utilisé '${command.name}' ${message.channel.type === ChannelType.GuildText ? `dans #${message.channel.name} (${message.channel.id})` : 'en DM'}`); + const isAuthorized = ownerIds.includes(message.author.id) || message.member!.roles.cache.get(communitymanagerRoleid) || message.member!.roles.cache.get(adminRoleid); + if ((command.adminOnly || command.ownerOnly) && !isAuthorized) return false; if (command.guildOnly && message.channel.type !== ChannelType.GuildText) { - return message.reply("Je ne peux pas exécuter cette commande en dehors d'une guilde!"); + message.reply("Je ne peux pas exécuter cette commande en dehors d'une guilde!"); + return false; } - if (command.args && !args.length) { - let reply = `Vous n'avez pas donné d'arguments, ${message.author}!`; - + if (command.args && !message.content.includes(' ')) { + const reply = `Vous n'avez pas donné d'arguments, ${message.author}!`; if (command.usage) { - reply += `\nL'utilisation correcte de cette commande est : \`${matchedPrefix}${command.name} ${command.usage}\``; + message.reply(`${reply}\nL'utilisation correcte de cette commande est : \`${message.content} ${command.usage}\``); + } else { + message.reply(reply); } - - return message.reply(reply); + return false; } - const isAuthorized = ownerIds.includes(message.author.id) || message.member!.roles.cache.get(communitymanagerRoleid) || message.member!.roles.cache.get(adminRoleid); - if ((command.adminOnly || command.ownerOnly) && !isAuthorized) return; + return true; +} - try { - command.execute(client, message, args); - } - catch (err) { - client.logger.error(err as string); - message.reply(":x: **Oups!** - Une erreur est apparue en essayant cette commande. Reporte-le à un membre du Staff s'il te plaît!"); - } -}; +function executeCommand(client: DatadropClient, message: Message, command: any) { + command.execute(client, message, message.content.split(' ').slice(1)); +} + +function handleCommandError(client: DatadropClient, message: Message) { + client.logger.error('An error occurred while executing the command'); + message.reply(":x: **Oups!** - Une erreur est apparue en essayant cette commande. Reporte-le à un membre du Staff s'il te plaît!"); +} diff --git a/src/events/ready.ts b/src/events/ready.ts index f1ba153..a73bfac 100644 --- a/src/events/ready.ts +++ b/src/events/ready.ts @@ -18,12 +18,13 @@ export default async function ready(client: DatadropClient) { async function registerRolesChannels(client: DatadropClient, config: Configuration): Promise { const { rolesChannelid, first, second, third, alumni, tutor, announce } = config; - const format = (rte: RoleToEmojiData) => - `${rte.emoji} - ${rte.role instanceof Role ? rte.role : roleMention(rte.role)}${rte.smallNote ? ` (${rte.smallNote})` : ''}`; const message = { options: { sendAsEmbed: true, - format, + format: (rte: RoleToEmojiData) => { + const initialFormatedRte = `${rte.emoji} - ${rte.role instanceof Role ? rte.role : roleMention(rte.role)}`; + return rte.smallNote ? `${initialFormatedRte} (${rte.smallNote})` : initialFormatedRte; + }, descriptionPrefix: bold( 'Utilisez les boutons suivants pour vous attribuer/retirer le rôle souhaité!' )