diff --git a/src/base/classes/Command.ts b/src/base/classes/Command.ts new file mode 100644 index 0000000..17b5a1a --- /dev/null +++ b/src/base/classes/Command.ts @@ -0,0 +1,32 @@ +import { ChatInputCommandInteraction, AutocompleteInteraction } from "discord.js"; +import { Category } from "../enums/Category"; +import {ICommand} from "../interfaces/ICommand"; +import {CustomClient} from "./CustomClient"; +import {ICommandOptions} from "../interfaces/ICommandOptions"; + +export class Command implements ICommand { + client: CustomClient; + name: string; + description: string; + category: Category; + options: object; + default_member_permissions: bigint; + dm_permissions: boolean; + cooldown: number; + + constructor(client: CustomClient, options: ICommandOptions) { + this.client = client; + this.name = options.name; + this.description = options.description; + this.category = options.category; + this.options = options.options; + this.default_member_permissions = options.default_member_permissions; + this.dm_permissions = options.dm_permissions; + this.cooldown = options.cooldown; + } + + execute(interaction: ChatInputCommandInteraction): void { + } + autocomplete(interaction: AutocompleteInteraction): void { + } +} \ No newline at end of file diff --git a/src/base/classes/CustomClient.ts b/src/base/classes/CustomClient.ts index 0c85bb2..6e47339 100644 --- a/src/base/classes/CustomClient.ts +++ b/src/base/classes/CustomClient.ts @@ -1,18 +1,29 @@ -import IConfig from "../interfaces/IConfig"; -import ICustomClient from "../interfaces/ICustomClient"; -import {Client} from "discord.js"; -import Handler from "./Handler"; +import {IConfig} from "../interfaces/IConfig"; +import {ICustomClient} from "../interfaces/ICustomClient"; +import {Client, Collection, GatewayIntentBits} from "discord.js"; +import {Handler} from "./Handler"; +import {Command} from "./Command"; +import {SubCommand} from "./SubCommand"; -export default class CustomClient extends Client implements ICustomClient { +export class CustomClient extends Client implements ICustomClient { config: IConfig; handler: Handler; + commands: Collection; + subCommands: Collection; + cooldowns: Collection>; + constructor() { super({ - intents: [], + intents: Object.keys(GatewayIntentBits).map((a: string) => { + return GatewayIntentBits[a as keyof typeof GatewayIntentBits]; + }), }) this.config = require(`${process.cwd()}/data/config.json`); this.handler = new Handler(this); + this.commands = new Collection(); + this.subCommands = new Collection(); + this.cooldowns = new Collection(); } async init(): Promise { await this.LoadHandlers(); @@ -21,6 +32,7 @@ export default class CustomClient extends Client implements ICustomClient { async LoadHandlers(): Promise { await this.handler.loadEvents(); + await this.handler.loadCommands(); } } \ No newline at end of file diff --git a/src/base/classes/Event.ts b/src/base/classes/Event.ts index f0695dd..c6260a1 100644 --- a/src/base/classes/Event.ts +++ b/src/base/classes/Event.ts @@ -1,11 +1,11 @@ -import { Events } from "discord.js"; -import IEvent from "../interfaces/IEvent"; -import CustomClient from "./CustomClient"; -import IEventOptions from "../interfaces/IEventOptions"; +import {ClientEvents} from "discord.js"; +import {IEvent} from "../interfaces/IEvent"; +import {CustomClient} from "./CustomClient"; +import {IEventOptions} from "../interfaces/IEventOptions"; -export default class Event implements IEvent { +export class Event implements IEvent { client: CustomClient; - name: Events; + name: keyof ClientEvents; description: string; once: boolean; diff --git a/src/base/classes/Handler.ts b/src/base/classes/Handler.ts index 10adb91..6255131 100644 --- a/src/base/classes/Handler.ts +++ b/src/base/classes/Handler.ts @@ -1,10 +1,12 @@ -import IHandler from "../interfaces/IHandler"; +import {IHandler} from "../interfaces/IHandler"; import path from "node:path"; import {glob} from "glob"; -import CustomClient from "./CustomClient"; -import Event from "./Event"; +import {CustomClient} from "./CustomClient"; +import {Event} from "./Event"; +import {Command} from "./Command"; +import {SubCommand} from "./SubCommand"; -export default class Handler implements IHandler { +export class Handler implements IHandler { client: CustomClient; constructor(client: CustomClient) { this.client = client; @@ -13,18 +15,28 @@ export default class Handler implements IHandler { const files = (await glob(`dist/events/**/*.js`)).map(filePath => path.resolve(filePath)); files.map(async (file: string) => { - const event : Event = new (await import(file)).default(this.client); + let evt = await import(file); + let EventClass: any; + for (const key in evt) { + if (typeof evt[key] === 'function') { + EventClass = evt[key]; + break; + } + } + + if (!EventClass) { + return delete require.cache[require.resolve(file)] && console.log(`Event ${file.split('/').pop()} n'a pas de classe`); + } + + const event : Event = new EventClass(this.client); if (!event.name) return delete require.cache[require.resolve(file)] && console.log(`Event ${file.split('/').pop()} n'a pas de nom`); const execute = (...args: any[]) => event.execute(...args); - if (event.once) { - // @ts-ignore this.client.once(event.name, execute); } else { - // @ts-ignore this.client.on(event.name, execute); } @@ -34,4 +46,39 @@ export default class Handler implements IHandler { }); } + + async loadCommands(): Promise { + const files = (await glob(`dist/commands/**/*.js`)).map(filePath => path.resolve(filePath)); + + files.map(async (file: string) => { + let evt = await import(file); + let EventClass: any; + for (const key in evt) { + if (typeof evt[key] === 'function') { + EventClass = evt[key]; + break; + } + } + + if (!EventClass) { + return delete require.cache[require.resolve(file)] && console.log(`Event ${file.split('/').pop()} n'a pas de classe`); + } + + const command : Command|SubCommand = new EventClass(this.client); + + if (!command.name) + return delete require.cache[require.resolve(file)] && console.log(`Event ${file.split('/').pop()} n'a pas de nom`); + + if (file.split('/').pop()?.split(".")[2]) { + console.log(`Sous Commande ${file.split('/').pop()} chargé`); + this.client.subCommands.set(command.name, command as SubCommand); + } else { + console.log(`Commande ${file.split('/').pop()} chargé`); + this.client.commands.set(command.name, command as Command); + } + + return delete require.cache[require.resolve(file)]; + }); + } + } \ No newline at end of file diff --git a/src/base/classes/SubCommand.ts b/src/base/classes/SubCommand.ts new file mode 100644 index 0000000..0696664 --- /dev/null +++ b/src/base/classes/SubCommand.ts @@ -0,0 +1,18 @@ +import { ChatInputCommandInteraction } from "discord.js"; +import {ISubCommand} from "../interfaces/ISubCommand"; +import {CustomClient} from "./CustomClient"; +import {ISubCommandOptions} from "../interfaces/ISubCommandOptions"; + +export class SubCommand implements ISubCommand { + client: CustomClient; + name: string; + + constructor(client: CustomClient,options: ISubCommandOptions) { + this.client = client; + this.name = options.name; + } + + execute(interaction: ChatInputCommandInteraction): void { + } + +} \ No newline at end of file diff --git a/src/base/enums/Category.ts b/src/base/enums/Category.ts new file mode 100644 index 0000000..fea873b --- /dev/null +++ b/src/base/enums/Category.ts @@ -0,0 +1,4 @@ +export enum Category { + UTILITIES = 'Utilitaires', +} + diff --git a/src/base/interfaces/ICommand.ts b/src/base/interfaces/ICommand.ts new file mode 100644 index 0000000..bfb0c4f --- /dev/null +++ b/src/base/interfaces/ICommand.ts @@ -0,0 +1,17 @@ +import {CustomClient} from "../classes/CustomClient"; +import {AutocompleteInteraction, ChatInputCommandInteraction} from "discord.js"; +import {Category} from "../enums/Category"; + +export interface ICommand { + client: CustomClient; + name: string; + description: string; + category: Category; + options: object; + default_member_permissions: bigint; + dm_permissions: boolean; + cooldown: number; + + execute(interaction: ChatInputCommandInteraction): void; + autocomplete(interaction: AutocompleteInteraction): void; +} \ No newline at end of file diff --git a/src/base/interfaces/ICommandOptions.ts b/src/base/interfaces/ICommandOptions.ts new file mode 100644 index 0000000..a991d7a --- /dev/null +++ b/src/base/interfaces/ICommandOptions.ts @@ -0,0 +1,11 @@ +import {Category} from "../enums/Category"; + +export interface ICommandOptions { + name: string; + description: string; + category: Category; + options: object; + default_member_permissions: bigint; + dm_permissions: boolean; + cooldown: number; +} \ No newline at end of file diff --git a/src/base/interfaces/IConfig.ts b/src/base/interfaces/IConfig.ts index c4a1977..91abc2b 100644 --- a/src/base/interfaces/IConfig.ts +++ b/src/base/interfaces/IConfig.ts @@ -1,3 +1,3 @@ -export default interface IConfig { +export interface IConfig { token: string; } \ No newline at end of file diff --git a/src/base/interfaces/ICustomClient.ts b/src/base/interfaces/ICustomClient.ts index 0ee782c..8d8063d 100644 --- a/src/base/interfaces/ICustomClient.ts +++ b/src/base/interfaces/ICustomClient.ts @@ -1,7 +1,14 @@ -import IConfig from "./IConfig"; +import {IConfig} from "./IConfig"; +import {Command} from "../classes/Command"; +import {Collection} from "discord.js"; +import {SubCommand} from "../classes/SubCommand"; -export default interface ICustomClient { +export interface ICustomClient { config: IConfig; + commands: Collection; + subCommands: Collection; + cooldowns: Collection>; + init(): void; LoadHandlers(): void; } \ No newline at end of file diff --git a/src/base/interfaces/IEvent.ts b/src/base/interfaces/IEvent.ts index e8f8717..f9e8a15 100644 --- a/src/base/interfaces/IEvent.ts +++ b/src/base/interfaces/IEvent.ts @@ -1,9 +1,9 @@ -import CustomClient from "../classes/CustomClient"; -import {Events} from "discord.js"; +import {CustomClient} from "../classes/CustomClient"; +import {ClientEvents} from "discord.js"; -export default interface IEvent { +export interface IEvent { client: CustomClient; - name: Events; + name: keyof ClientEvents; description: string; once: boolean; diff --git a/src/base/interfaces/IEventOptions.ts b/src/base/interfaces/IEventOptions.ts index c6cf583..6f8e96e 100644 --- a/src/base/interfaces/IEventOptions.ts +++ b/src/base/interfaces/IEventOptions.ts @@ -1,7 +1,7 @@ -import {Events} from "discord.js"; +import {ClientEvents} from "discord.js"; -export default interface IEventOptions { - name: Events; +export interface IEventOptions { + name: keyof ClientEvents; description: string; once: boolean; } \ No newline at end of file diff --git a/src/base/interfaces/IHandler.ts b/src/base/interfaces/IHandler.ts index a488665..a250f45 100644 --- a/src/base/interfaces/IHandler.ts +++ b/src/base/interfaces/IHandler.ts @@ -1,3 +1,4 @@ -export default interface IHandler { +export interface IHandler { loadEvents(): void; + loadCommands(): void; } \ No newline at end of file diff --git a/src/base/interfaces/ISubCommand.ts b/src/base/interfaces/ISubCommand.ts new file mode 100644 index 0000000..81c4ca8 --- /dev/null +++ b/src/base/interfaces/ISubCommand.ts @@ -0,0 +1,9 @@ +import {CustomClient} from "../classes/CustomClient"; +import {ChatInputCommandInteraction} from "discord.js"; + +export interface ISubCommand { + client: CustomClient; + name: string; + + execute(interaction: ChatInputCommandInteraction): void; +} \ No newline at end of file diff --git a/src/base/interfaces/ISubCommandOptions.ts b/src/base/interfaces/ISubCommandOptions.ts new file mode 100644 index 0000000..8b858f8 --- /dev/null +++ b/src/base/interfaces/ISubCommandOptions.ts @@ -0,0 +1,3 @@ +export interface ISubCommandOptions { + name: string; +} \ No newline at end of file diff --git a/src/events/client/Ready.ts b/src/events/client/Ready.ts index 5949f6d..60fee13 100644 --- a/src/events/client/Ready.ts +++ b/src/events/client/Ready.ts @@ -1,7 +1,7 @@ -import Event from '../../base/classes/Event'; -import CustomClient from "../../base/classes/CustomClient"; +import {Event} from '../../base/classes/Event'; +import {CustomClient} from "../../base/classes/CustomClient"; import {Events} from "discord.js"; -export default class Ready extends Event { +export class Ready extends Event { constructor(client: CustomClient) { super(client, { name: Events.ClientReady, diff --git a/src/events/guild/CommandHandler.ts b/src/events/guild/CommandHandler.ts new file mode 100644 index 0000000..9d38a10 --- /dev/null +++ b/src/events/guild/CommandHandler.ts @@ -0,0 +1,44 @@ +import {ChatInputCommandInteraction, Collection, Events} from "discord.js"; +import {CustomClient} from "../../base/classes/CustomClient"; +import {Event} from "../../base/classes/Event"; +import {Command} from "../../base/classes/Command"; + +export class CommandHandler extends Event { + constructor(client: CustomClient) { + super(client, { + name: Events.InteractionCreate, + once: false, + description: 'Event se déclenchant lorsqu\'une interaction est créée' + }); + } + + async execute(interaction: ChatInputCommandInteraction): Promise { + if (!interaction.isChatInputCommand()) return; + + const command: Command = this.client.commands.get(interaction.commandName) as Command; + + if (!command) { + await interaction.reply({content: 'Commande inconnue', ephemeral: true}) + this.client.commands.delete(interaction.commandName); + return; + } + const {cooldowns} = this.client; + + if (!cooldowns.has(command.name)) { + cooldowns.set(command.name, new Collection()); + } + + const now = Date.now(); + const timestamps = cooldowns.get(command.name)!; + const cooldownAmount = (command.cooldown || 3) * 1000; + + if (timestamps.has(interaction.user.id) && now < (timestamps.get(interaction.user.id) || 0) + cooldownAmount) { + await interaction.reply({ + content: `Veuillez patienter ${((timestamps.get(interaction.user.id) || 0) + cooldownAmount - now) / 1000} secondes avant de réutiliser la commande \`${command.name}\`` + }) + return; + } + + command.execute(interaction); + } +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 75dfa4e..4ea143d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,2 +1,2 @@ -import CustomClient from "./base/classes/CustomClient"; +import {CustomClient} from "./base/classes/CustomClient"; (async () => await new CustomClient().init())(); \ No newline at end of file