Command registration (for mini)
diff --git a/src/utils/client.ts b/src/utils/client.ts
index 7d608c7..6aa9b43 100644
--- a/src/utils/client.ts
+++ b/src/utils/client.ts
@@ -1,14 +1,16 @@
-import { Client } from 'discord.js';
+import Discord, { Client, Interaction } from 'discord.js';
import { Logger } from "../utils/log.js";
import Memory from "../utils/memory.js";
import type { VerifySchema } from "../reflex/verify.js";
import { Guilds, History, ModNotes, Premium } from "../utils/database.js";
import EventScheduler from "../utils/eventScheduler.js";
import type { RoleMenuSchema } from "../actions/roleMenu.js";
+import config from "../config/main.json" assert { type: "json" };
class NucleusClient extends Client {
logger = Logger;
+ config: typeof config = config;
verify: Record<string, VerifySchema> = {};
roleMenu: Record<string, RoleMenuSchema> = {};
memory: Memory = new Memory() as Memory;
@@ -20,6 +22,14 @@
premium: Premium;
eventScheduler: EventScheduler;
};
+ // commands: Record<string, {
+ // command: Discord.SlashCommandBuilder |
+ // ((builder: Discord.SlashCommandBuilder) => Discord.SlashCommandBuilder) |
+ // Discord.SlashCommandSubcommandBuilder | ((builder: Discord.SlashCommandSubcommandBuilder) => Discord.SlashCommandSubcommandBuilder) | Discord.SlashCommandSubcommandGroupBuilder | ((builder: Discord.SlashCommandSubcommandGroupBuilder) => Discord.SlashCommandSubcommandGroupBuilder),
+ // callback: (interaction: Interaction) => Promise<void>,
+ // check: (interaction: Interaction) => Promise<boolean> | boolean
+ // }> = {};
+ commands: Discord.Collection<string, [Function, Function]> = new Discord.Collection();
constructor(database: typeof NucleusClient.prototype.database) {
super({ intents: 32767 });
diff --git a/src/utils/commandRegistration/getFilesInFolder.ts b/src/utils/commandRegistration/getFilesInFolder.ts
index a669065..d8a1298 100644
--- a/src/utils/commandRegistration/getFilesInFolder.ts
+++ b/src/utils/commandRegistration/getFilesInFolder.ts
@@ -1,23 +1,27 @@
import fs from "fs";
-export default async function getSubcommandsInFolder(path: string) {
+export default async function getSubcommandsInFolder(path: string, indent: string = "") {
const files = fs.readdirSync(path, { withFileTypes: true }).filter(
file => !file.name.endsWith(".ts") && !file.name.endsWith(".map")
);
const subcommands = [];
const subcommandGroups = [];
+ let errors = 0;
for (const file of files) {
if (file.name === "_meta.js") continue;
- // If its a folder
- if (file.isDirectory()) {
- // Get the _meta.ts file
- console.log(`│ ├─ Loading subcommand group ${file.name}}`)
- subcommandGroups.push((await import(`../../../${path}/${file.name}/_meta.js`)).command);
- } else if (file.name.endsWith(".js")) {
- // If its a file
- console.log(`│ ├─ Loading subcommand ${file.name}}`)
- subcommands.push((await import(`../../../${path}/${file.name}`)).command);
+ try {
+ if (file.isDirectory()) {
+ // Get the _meta.ts file
+ subcommandGroups.push((await import(`../../../${path}/${file.name}/_meta.js`)).command);
+ } else if (file.name.endsWith(".js")) {
+ // If its a file
+ console.log(`│ ${indent}├─ Loading subcommand ${file.name}`)
+ subcommands.push((await import(`../../../${path}/${file.name}`)).command);
+ }
+ } catch (e) {
+ console.error(`│ ${indent}│ └─ Error loading ${file.name}: ${e}`);
+ errors++;
}
}
- return {subcommands, subcommandGroups};
+ return {subcommands, subcommandGroups, errors};
}
diff --git a/src/utils/commandRegistration/register.ts b/src/utils/commandRegistration/register.ts
index af0b5fc..a734921 100644
--- a/src/utils/commandRegistration/register.ts
+++ b/src/utils/commandRegistration/register.ts
@@ -1,9 +1,16 @@
-import { SlashCommandBuilder } from 'discord.js';
+import { Interaction, SlashCommandBuilder } from 'discord.js';
import config from "../../config/main.json" assert { type: "json" };
import client from "../client.js";
import fs from "fs";
+const colours = {
+ red: "\x1b[31m",
+ green: "\x1b[32m",
+ yellow: "\x1b[33m",
+ none: "\x1b[0m"
+}
+
async function registerCommands() {
const developmentMode = config.enableDevelopment;
const commands = [];
@@ -14,15 +21,18 @@
console.log(`Registering ${files.length} commands`)
let i = 0;
for (const file of files) {
- // Box drawing characters: | └ ─ ┌ ┐ ┘ ┬ ┤ ├ ┴ ┼
- console.log(`├─ ${file.name}`)
+ const last = i === files.length - 1 ? "└" : "├";
if (file.isDirectory()) {
+ console.log(`${last}─ ${colours.yellow}Loading subcommands of ${file.name}${colours.none}`)
commands.push((await import(`../../../${config.commandsFolder}/${file.name}/_meta.js`)).command);
} else if (file.name.endsWith(".js")) {
- commands.push((await import(`../../../${config.commandsFolder}/${file.name}`)).command);
+ console.log(`${last}─ ${colours.yellow}Loading command ${file.name}${colours.none}`)
+ const fetched = (await import(`../../../${config.commandsFolder}/${file.name}`));
+ commands.push(fetched.command);
+ client.commands.set(fetched.command.name, [fetched.check, fetched.callback]);
}
i++;
- console.log(`├─ Loaded ${file.name} [${i} / ${files.length}]`)
+ console.log(`${last.replace("└", " ").replace("├", "│")} └─ ${colours.green}Loaded ${file.name} [${i} / ${files.length}]${colours.none}`)
}
console.log(`Loaded ${commands.length} commands, processing...`)
const processed = []
@@ -39,11 +49,9 @@
if (developmentMode) {
const guild = await client.guilds.fetch(config.developmentGuildID);
- guild.commands.set([])
guild.commands.set(processed);
console.log(`Commands registered in ${guild.name}`)
} else {
- client.application!.commands.set([])
client.application!.commands.set(processed);
console.log(`Commands registered globally`)
}
@@ -51,11 +59,83 @@
};
async function registerEvents() {
- // pass
+ console.log("Reading events")
+ const files = fs.readdirSync(config.eventsFolder, { withFileTypes: true }).filter(
+ file => !file.name.endsWith(".ts") && !file.name.endsWith(".map")
+ );
+ console.log(`Registering ${files.length} events`)
+ let i = 0;
+ let errors = 0;
+ for (const file of files) {
+ const last = i === files.length - 1 ? "└" : "├";
+ i++;
+ try {
+ console.log(`${last}─ ${colours.yellow}Loading event ${file.name}${colours.none}`)
+ const event = (await import(`../../../${config.eventsFolder}/${file.name}`));
+
+ client.on(event.event, event.callback.bind(null, client));
+
+ console.log(`${last.replace("└", " ").replace("├", "│")} └─ ${colours.green}Loaded ${file.name} [${i} / ${files.length}]${colours.none}`)
+ } catch (e) {
+ errors++;
+ console.log(`${last.replace("└", " ").replace("├", "│")} └─ ${colours.red}Failed to load ${file.name} [${i} / ${files.length}]${colours.none}`)
+ }
+ }
+ console.log(`Loaded ${files.length - errors} events (${errors} failed)`)
};
+async function registerCommandHandler() {
+ client.on("interactionCreate", async (interaction: Interaction) => {
+ if (!interaction.isCommand()) return;
+
+ const commandName = interaction.commandName;
+ const subcommandGroupName = interaction.options.getSubcommandGroup(false);
+ const subcommandName = interaction.options.getSubcommand(false);
+
+ let fullCommandName = commandName + (subcommandGroupName ? ` ${subcommandGroupName}` : "") + (subcommandName ? ` ${subcommandName}` : "");
+
+ const command = this.commands.get(fullCommandName);
+ if (!command) return;
+
+ const sendErrorMessage = async (error: Error) => {
+ if (this.listenerCount("commandError")) {
+ return this.emit("commandError", interaction, error);
+ }
+ let method = (!interaction.deferred && !interaction.replied) ? interaction.reply.bind(interaction) : interaction.followUp.bind(interaction);
+ await method({
+ embeds: [
+ new Embed()
+ .setColor(0xff0000)
+ .setTitle("I couldn't run that command")
+ .setDescription(error.message ?? error.toString())
+ ]
+ , ephemeral: true});
+ }
+
+ try {
+ let hasPermission = await command.check(interaction);
+
+ if (!hasPermission) {
+ sendErrorMessage(new CheckFailedError("You don't have permission to run this command"));
+ return;
+ }
+ } catch (error) {
+ sendErrorMessage(error);
+ return;
+ }
+ try {
+ await command.callback(interaction);
+ } catch (error) {
+ this._error(error);
+ sendErrorMessage(error);
+ return;
+ }
+ });
+}
+
export default async function register() {
- console.log("> Registering commands")
await registerCommands();
+ await registerCommandHandler();
await registerEvents();
+ console.log(`${colours.green}Registered commands and events${colours.none}`)
};
diff --git a/src/utils/commandRegistration/slashCommandBuilder.ts b/src/utils/commandRegistration/slashCommandBuilder.ts
index 719855f..c7ac55f 100644
--- a/src/utils/commandRegistration/slashCommandBuilder.ts
+++ b/src/utils/commandRegistration/slashCommandBuilder.ts
@@ -4,8 +4,17 @@
import getSubcommandsInFolder from "./getFilesInFolder.js";
+const colours = {
+ red: "\x1b[31m",
+ green: "\x1b[32m",
+ none: "\x1b[0m"
+}
+
+
export async function group(name: string, description: string, path: string) {
- const fetched = await getSubcommandsInFolder(config.commandsFolder + "/" + path)
+ console.log(`│ ├─ Loading group ${name}`)
+ const fetched = await getSubcommandsInFolder(config.commandsFolder + "/" + path, "│ ")
+ console.log(`│ │ └─ ${fetched.errors ? colours.red : colours.green}Loaded ${fetched.subcommands.length} subcommands for ${name} (${fetched.errors} failed)${colours.none}`)
return (subcommandGroup: SlashCommandSubcommandGroupBuilder) => {
subcommandGroup
.setName(name)
@@ -21,7 +30,7 @@
export async function command(name: string, description: string, path: string) {
const fetched = await getSubcommandsInFolder(config.commandsFolder + "/" + path);
- console.log(`│ ├─ Loaded ${fetched.subcommands.length} subcommands and ${fetched.subcommandGroups.length} subcommand groups for ${name}`)
+ console.log(`│ ├─ ${fetched.errors ? colours.red : colours.green}Loaded ${fetched.subcommands.length} subcommands and ${fetched.subcommandGroups.length} subcommand groups for ${name} (${fetched.errors} failed)${colours.none}`)
return (command: SlashCommandBuilder) => {
command.setName(name)
command.setDescription(description)