-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor(messageCreate): move commands handling into specific file
- Loading branch information
Tinaël Devresse
committed
Oct 23, 2024
1 parent
1b0315f
commit 8c92451
Showing
13 changed files
with
141 additions
and
86 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,81 +1,13 @@ | ||
import { ChannelType, Message } from 'discord.js'; | ||
import { DatadropClient } from '../datadrop.js'; | ||
import { Message } from 'discord.js'; | ||
|
||
const escapeRegex = (str: string | null | undefined) => str?.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); | ||
import { DatadropClient } from '../datadrop.js'; | ||
import { CommandHandler } from '../services/CommandHandler.js'; | ||
|
||
export default async function messageCreate(client: DatadropClient, message: Message) { | ||
if (message.author.bot) return; | ||
|
||
const lowerCasedContent = message.content.toLowerCase(); | ||
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('')); | ||
} | ||
|
||
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(); | ||
} | ||
if (message.author.bot) return; | ||
|
||
function getCommand(client: DatadropClient, commandName: string) { | ||
return client.commands.get(commandName) || client.commands.find((cmd) => cmd.aliases?.includes(commandName)); | ||
} | ||
|
||
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; | ||
|
||
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) { | ||
message.reply("Je ne peux pas exécuter cette commande en dehors d'une guilde!"); | ||
return false; | ||
} | ||
|
||
if (command.args && !message.content.includes(' ')) { | ||
const reply = `Vous n'avez pas donné d'arguments, ${message.author}!`; | ||
if (command.usage) { | ||
message.reply(`${reply}\nL'utilisation correcte de cette commande est : \`${message.content} ${command.usage}\``); | ||
} else { | ||
message.reply(reply); | ||
const commandHandler = new CommandHandler(client); | ||
if (commandHandler.shouldExecute(message.content.toLowerCase())) { | ||
await commandHandler.execute(message); | ||
} | ||
return false; | ||
} | ||
|
||
return true; | ||
} | ||
|
||
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!"); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import { Message } from 'discord.js'; | ||
|
||
import { DatadropClient } from '../datadrop.js'; | ||
|
||
export interface Command { | ||
name: string; | ||
aliases?: string[]; | ||
args?: boolean; | ||
description: string; | ||
usage?: string; | ||
guildOnly?: boolean; | ||
ownerOnly?: boolean; | ||
|
||
execute(client: DatadropClient, message: Message, args: string[]): Promise<void>; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
import { ChannelType, Message } from 'discord.js'; | ||
|
||
import { DatadropClient } from '../datadrop.js'; | ||
import { Command } from '../models/Command.js'; | ||
|
||
const escapeRegex = (str: string | null | undefined) => str?.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); | ||
|
||
type AuthorizationResponse = { | ||
error?: string; | ||
}; | ||
|
||
export class CommandHandler { | ||
#prefixRegex: RegExp; | ||
#client: DatadropClient; | ||
|
||
constructor(client: DatadropClient) { | ||
this.#client = client; | ||
this.#prefixRegex = new RegExp(`^(<@!?${client.user!.id}>|${escapeRegex(client.config.prefix)})\\s*`); | ||
} | ||
|
||
shouldExecute(content: string): boolean { | ||
return this.#prefixRegex.test(content.split(' ').join('')); | ||
} | ||
|
||
async execute(message: Message): Promise<void> { | ||
const args = this.#getArgs(message.content); | ||
const command = this.#inferCommandFromArgs(args); | ||
if (!command) { | ||
await message.reply(":x: **Oups!** - Cette commande n'existe pas. Utilisez la commande `help` pour voir la liste des commandes disponibles."); | ||
return; | ||
} | ||
|
||
const authorizationResponse = this.#checkAuthorization(message, command); | ||
this.#logUsage(message, command, !!authorizationResponse.error); | ||
|
||
if (authorizationResponse.error) { | ||
await message.reply(authorizationResponse.error); | ||
return; | ||
} | ||
|
||
try { | ||
await command.execute(this.#client, message, args); | ||
} catch (err) { | ||
this.#client.logger.error(`Une erreur est survenue lors de l'exécution de la commande "${command.name}": ${err}`); | ||
await message.reply(":x: **Oups!** - Une erreur est apparue en essayant cette commande. Reporte-le à un membre du Staff s'il te plaît!"); | ||
} | ||
} | ||
|
||
#getArgs(content: string): string[] { | ||
const matches = this.#prefixRegex.exec(content); | ||
if (!matches) return []; | ||
const [, matchedPrefix] = matches; | ||
return content.slice(matchedPrefix.length).trim().split(/ +/g) ?? []; | ||
} | ||
|
||
#inferCommandFromArgs(args: string[]): Command | undefined { | ||
if (!args || args.length === 0) return; | ||
const commandName = args.shift()!.toLowerCase(); | ||
|
||
return this.#client.commands.get(commandName) || this.#client.commands.find((cmd) => cmd.aliases?.includes(commandName)); | ||
} | ||
|
||
/** | ||
* Side-effect function to log the usage of the command. | ||
* @param message The message | ||
* @param command The command | ||
* @param isAuthorized Whether the user responsible of the message is authorized to use the command or not. | ||
*/ | ||
#logUsage(message: Message, command: any, isAuthorized: boolean): void { | ||
const channelInfo = message.channel.type === ChannelType.GuildText ? `dans #${message.channel.name} (${message.channel.id})` : 'en DM'; | ||
this.#client.logger.info(`${message.author.tag} (${message.author.id}) a utilisé '${command.name}' ${channelInfo} ${isAuthorized ? 'avec' : 'sans'} autorisation`); | ||
} | ||
|
||
#checkAuthorization(message: Message, command: any): AuthorizationResponse { | ||
const { ownerIds, communitymanagerRoleid, adminRoleid } = this.#client.config; | ||
|
||
const canBypassAuthorization = ownerIds.includes(message.author.id) | ||
|| message.member!.roles.cache.get(communitymanagerRoleid) | ||
|| message.member!.roles.cache.get(adminRoleid); | ||
if ((command.adminOnly || command.ownerOnly) && !canBypassAuthorization) { | ||
return { error: ":x: **Oups!** - Cette commande est réservée à un nombre limité de personnes dont vous ne faites pas partie." }; | ||
} | ||
|
||
if (command.guildOnly && message.channel.type !== ChannelType.GuildText) { | ||
return { error: ":x: **Oups!** - Je ne peux pas exécuter cette commande en dehors d'une guilde!" }; | ||
} | ||
|
||
if (command.args && !message.content.slice(command.name.length).startsWith(' ')) { | ||
const reply = `:x: **Oups!** - Vous n'avez pas donné d'arguments, ${message.author}!`; | ||
return { | ||
error: command.usage | ||
? `${reply}\nL'utilisation correcte de cette commande est : \`${message.content} ${command.usage}\`` | ||
: reply | ||
}; | ||
} | ||
|
||
return {}; | ||
} | ||
} |