From df4996fa2ac3bd4cc6de46e1a75b8054e3eff778 Mon Sep 17 00:00:00 2001 From: PineaFan Date: Sun, 1 Jan 2023 14:20:06 +0000 Subject: [PATCH] Many changes, including: command registration works (now actually calls the command functions), fixed emoji embeds, started fixing some commands, database no longer uses proxy --- SELF_HOSTING.md | 2 + src/commands/nucleus/_meta.ts | 2 +- src/commands/verify.ts | 1 - src/utils/client.ts | 16 ++--- .../commandRegistration/getFilesInFolder.ts | 4 +- src/utils/commandRegistration/register.ts | 58 +++++++------------ .../slashCommandBuilder.ts | 20 +++++-- src/utils/database.ts | 22 +------ src/utils/defaultEmbeds.ts | 3 +- src/utils/generateEmojiEmbed.ts | 13 +++-- src/utils/getEmojiByName.ts | 5 +- 11 files changed, 63 insertions(+), 83 deletions(-) diff --git a/SELF_HOSTING.md b/SELF_HOSTING.md index ae05a90..4f01364 100644 --- a/SELF_HOSTING.md +++ b/SELF_HOSTING.md @@ -33,6 +33,8 @@ Alternatively, you can run `Installer.js` to generate it for you. "managementGuildID": "your-management-guild-id-here", "developmentGuildID": "your-development-guild-id-here", "enableDevelopment": true, + "commandsFolder": "your-compiled-commands-folder, e.g. dist/commands", + "eventsFolder": "your-compiled-events-folder, e.g. dist/events", "owners": [ "your-discord-id", ], diff --git a/src/commands/nucleus/_meta.ts b/src/commands/nucleus/_meta.ts index 751feca..d66b5d2 100644 --- a/src/commands/nucleus/_meta.ts +++ b/src/commands/nucleus/_meta.ts @@ -3,6 +3,6 @@ import { command } from "../../utils/commandRegistration/slashCommandBuilder.js" const name = "nucleus"; const description = "Commands relating to Nucleus itself"; -const subcommand = await command(name, description, `settings/logs`) +const subcommand = await command(name, description, `nucleus`) export { name, description, subcommand as command }; diff --git a/src/commands/verify.ts b/src/commands/verify.ts index bf6a306..b9556a6 100644 --- a/src/commands/verify.ts +++ b/src/commands/verify.ts @@ -5,7 +5,6 @@ import verify from "../reflex/verify.js"; const command = new SlashCommandBuilder().setName("verify").setDescription("Get verified in the server"); const callback = async (interaction: CommandInteraction): Promise => { - interaction.reply("boo") verify(interaction); }; diff --git a/src/utils/client.ts b/src/utils/client.ts index 6aa9b43..61cf3bf 100644 --- a/src/utils/client.ts +++ b/src/utils/client.ts @@ -22,14 +22,14 @@ class NucleusClient extends Client { premium: Premium; eventScheduler: EventScheduler; }; - // commands: Record Discord.SlashCommandBuilder) | - // Discord.SlashCommandSubcommandBuilder | ((builder: Discord.SlashCommandSubcommandBuilder) => Discord.SlashCommandSubcommandBuilder) | Discord.SlashCommandSubcommandGroupBuilder | ((builder: Discord.SlashCommandSubcommandGroupBuilder) => Discord.SlashCommandSubcommandGroupBuilder), - // callback: (interaction: Interaction) => Promise, - // check: (interaction: Interaction) => Promise | boolean - // }> = {}; - commands: Discord.Collection = new Discord.Collection(); + commands: Record Discord.SlashCommandBuilder) | + Discord.SlashCommandSubcommandBuilder | ((builder: Discord.SlashCommandSubcommandBuilder) => Discord.SlashCommandSubcommandBuilder) | Discord.SlashCommandSubcommandGroupBuilder | ((builder: Discord.SlashCommandSubcommandGroupBuilder) => Discord.SlashCommandSubcommandGroupBuilder), + callback: (interaction: Interaction) => Promise, + check: (interaction: Interaction) => Promise | boolean + }> = {}; + // commands: Discord.Collection = 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 d8a1298..875e0b0 100644 --- a/src/utils/commandRegistration/getFilesInFolder.ts +++ b/src/utils/commandRegistration/getFilesInFolder.ts @@ -12,11 +12,11 @@ export default async function getSubcommandsInFolder(path: string, indent: strin try { if (file.isDirectory()) { // Get the _meta.ts file - subcommandGroups.push((await import(`../../../${path}/${file.name}/_meta.js`)).command); + subcommandGroups.push(await import(`../../../${path}/${file.name}/_meta.js`)); } 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); + subcommands.push(await import(`../../../${path}/${file.name}`)); } } catch (e) { console.error(`│ ${indent}│ └─ Error loading ${file.name}: ${e}`); diff --git a/src/utils/commandRegistration/register.ts b/src/utils/commandRegistration/register.ts index a734921..1b55496 100644 --- a/src/utils/commandRegistration/register.ts +++ b/src/utils/commandRegistration/register.ts @@ -2,6 +2,7 @@ import { Interaction, SlashCommandBuilder } from 'discord.js'; import config from "../../config/main.json" assert { type: "json" }; import client from "../client.js"; import fs from "fs"; +import Discord from "discord.js"; const colours = { @@ -29,7 +30,7 @@ async function registerCommands() { 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]); + client.commands["commands/" + fetched.command.name] = fetched; } i++; console.log(`${last.replace("└", " ").replace("├", "│")} └─ ${colours.green}Loaded ${file.name} [${i} / ${files.length}]${colours.none}`) @@ -47,12 +48,13 @@ async function registerCommands() { console.log(`Processed ${commands.length} commands, registering...`) + const updateCommands = process.argv.includes("--update-commands"); if (developmentMode) { const guild = await client.guilds.fetch(config.developmentGuildID); - guild.commands.set(processed); + if (updateCommands) guild.commands.set(processed); console.log(`Commands registered in ${guild.name}`) } else { - client.application!.commands.set(processed); + if (updateCommands) client.application!.commands.set(processed); console.log(`Commands registered globally`) } @@ -86,50 +88,30 @@ async function registerEvents() { async function registerCommandHandler() { client.on("interactionCreate", async (interaction: Interaction) => { - if (!interaction.isCommand()) return; + if (!interaction.isChatInputCommand()) 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 fullCommandName = "commands/" + commandName + (subcommandGroupName ? `/${subcommandGroupName}` : "") + (subcommandName ? `/${subcommandName}` : ""); - const command = this.commands.get(fullCommandName); - if (!command) return; + const command = client.commands[fullCommandName]; + const callback = command?.callback; + const check = command?.check; - const sendErrorMessage = async (error: Error) => { - if (this.listenerCount("commandError")) { - return this.emit("commandError", interaction, error); + if (!callback) return; + if (check) { + let result; + try { + result = await check(interaction); + } catch (e) { + console.log(e); + result = false; } - 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; + if (!result) return; } + callback(interaction); }); } diff --git a/src/utils/commandRegistration/slashCommandBuilder.ts b/src/utils/commandRegistration/slashCommandBuilder.ts index c7ac55f..76ecabe 100644 --- a/src/utils/commandRegistration/slashCommandBuilder.ts +++ b/src/utils/commandRegistration/slashCommandBuilder.ts @@ -2,6 +2,8 @@ import type { SlashCommandSubcommandGroupBuilder } from "@discordjs/builders"; import type { SlashCommandBuilder } from "discord.js"; import config from "../../config/main.json" assert { type: "json" }; import getSubcommandsInFolder from "./getFilesInFolder.js"; +import client from "../client.js"; +import Discord from "discord.js"; const colours = { @@ -12,6 +14,7 @@ const colours = { export async function group(name: string, description: string, path: string) { + // If the name of the command does not match the path (e.g. attachment.ts has /attachments), use commandString 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}`) @@ -21,14 +24,16 @@ export async function group(name: string, description: string, path: string) { .setDescription(description) for (const subcommand of fetched.subcommands) { - subcommandGroup.addSubcommand(subcommand); + subcommandGroup.addSubcommand(subcommand.command); }; return subcommandGroup; }; } -export async function command(name: string, description: string, path: string) { +export async function command(name: string, description: string, path: string, commandString: string | undefined = undefined) { + // If the name of the command does not match the path (e.g. attachment.ts has /attachments), use commandString + commandString = "commands/" + (commandString ?? path); const fetched = await getSubcommandsInFolder(config.commandsFolder + "/" + path); 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) => { @@ -36,10 +41,17 @@ export async function command(name: string, description: string, path: string) { command.setDescription(description) for (const subcommand of fetched.subcommands) { - command.addSubcommand(subcommand); + let fetchedCommand; + if (subcommand.command instanceof Function) { + fetchedCommand = subcommand.command(new Discord.SlashCommandSubcommandBuilder()); + } else { + fetchedCommand = subcommand.command; + } + client.commands[commandString! + "/" + fetchedCommand.name] = subcommand + command.addSubcommand(fetchedCommand); } for (const group of fetched.subcommandGroups) { - command.addSubcommandGroup(group); + command.addSubcommandGroup(group.command); }; return command; }; diff --git a/src/utils/database.ts b/src/utils/database.ts index 2c299d0..2ae6af9 100644 --- a/src/utils/database.ts +++ b/src/utils/database.ts @@ -1,30 +1,11 @@ import type Discord from "discord.js"; import { Collection, MongoClient } from "mongodb"; -// @ts-expect-error -import structuredClone from "@ungap/structured-clone"; import config from "../config/main.json" assert { type: "json" }; const mongoClient = new MongoClient(config.mongoUrl); await mongoClient.connect(); const database = mongoClient.db("Nucleus"); -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export const Entry = (data: any) => { - data = data ?? {}; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - data.getKey = (key: any) => data[key]; - return { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - get(target: Record, prop: string, receiver: any) { - let dataToReturn = data[prop]; - if (dataToReturn === null) return Reflect.get(target, prop, receiver); - if (typeof dataToReturn === "object" && !Array.isArray(dataToReturn)) - dataToReturn = new Proxy(Reflect.get(target, prop, receiver), Entry(dataToReturn)); - return dataToReturn ?? Reflect.get(target, prop, receiver); - } - }; -}; - export class Guilds { guilds: Collection; defaultData: GuildConfig | null; @@ -42,7 +23,7 @@ export class Guilds { async read(guild: string): Promise { const entry = await this.guilds.findOne({ id: guild }); - return new Proxy(structuredClone(this.defaultData), Entry(entry)) as unknown as GuildConfig; + return Object.assign({}, this.defaultData, entry); } async write(guild: string, set: object | null, unset: string[] | string = []) { @@ -263,6 +244,7 @@ export interface GuildConfig { }; }; verify: { + enabled: boolean; role: string | null; }; tickets: { diff --git a/src/utils/defaultEmbeds.ts b/src/utils/defaultEmbeds.ts index 2200a5e..2334504 100644 --- a/src/utils/defaultEmbeds.ts +++ b/src/utils/defaultEmbeds.ts @@ -1,5 +1,6 @@ import EmojiEmbed from "./generateEmojiEmbed.js"; +import getEmojiByName from "./getEmojiByName.js"; export const LoadingEmbed = [ - new EmojiEmbed().setTitle("Loading").setDescription("One moment...").setStatus("Danger").setEmoji("NUCLEUS.LOADING") + new EmojiEmbed().setDescription(`${getEmojiByName("NUCLEUS.LOADING")} One moment...`).setStatus("Danger") ]; diff --git a/src/utils/generateEmojiEmbed.ts b/src/utils/generateEmojiEmbed.ts index a7926df..e1b481a 100644 --- a/src/utils/generateEmojiEmbed.ts +++ b/src/utils/generateEmojiEmbed.ts @@ -1,4 +1,4 @@ -import { EmbedBuilder } from "discord.js"; +import { EmbedBuilder } from "@discordjs/builders"; import getEmojiByName from "./getEmojiByName.js"; const colors = { @@ -11,19 +11,19 @@ class EmojiEmbed extends EmbedBuilder { _title = ""; _emoji: string | null = null; - // @ts-expect-error - // This *is* meant to be an accessor rather than a property - override get title() { - if (!this._emoji) return this._title; - return `${getEmojiByName(this._emoji)} ${this._title}`; + _generateTitle() { + if (this._emoji) { return `${getEmojiByName(this._emoji)} ${this._title}`; } + return this._title; } override setTitle(title: string) { this._title = title; + super.setTitle(this._generateTitle()); return this; } setEmoji(emoji: string) { this._emoji = emoji; + super.setTitle(this._generateTitle()); return this; } setStatus(color: "Danger" | "Warning" | "Success") { @@ -32,4 +32,5 @@ class EmojiEmbed extends EmbedBuilder { } } + export default EmojiEmbed; diff --git a/src/utils/getEmojiByName.ts b/src/utils/getEmojiByName.ts index f953a4f..3fa2b53 100644 --- a/src/utils/getEmojiByName.ts +++ b/src/utils/getEmojiByName.ts @@ -4,7 +4,8 @@ interface EmojisIndex { [key: string]: string | EmojisIndex | EmojisIndex[]; } -function getEmojiByName(name: string, format?: string): string { +function getEmojiByName(name: string | null, format?: string): string { + if (!name) return ""; const parts = name.split("."); let id: string | EmojisIndex | EmojisIndex[] | undefined = emojis; for (const part of parts) { @@ -25,7 +26,7 @@ function getEmojiByName(name: string, format?: string): string { return id.toString(); } if (id === undefined) { - return ""; + return ""; } else if (id.toString().startsWith("a")) { return ``; }