Many changes, including: command registration works (now actually calls the command functions), fixed emoji embeds, started fixing some commands, database no longer uses proxy

pull/17/head
PineaFan 3 years ago
parent 752af46e5e
commit df4996fa2a
No known key found for this signature in database
GPG Key ID: 0AEF25BAA0FB1C74

@ -33,6 +33,8 @@ Alternatively, you can run `Installer.js` to generate it for you.
"managementGuildID": "your-management-guild-id-here", "managementGuildID": "your-management-guild-id-here",
"developmentGuildID": "your-development-guild-id-here", "developmentGuildID": "your-development-guild-id-here",
"enableDevelopment": true, "enableDevelopment": true,
"commandsFolder": "your-compiled-commands-folder, e.g. dist/commands",
"eventsFolder": "your-compiled-events-folder, e.g. dist/events",
"owners": [ "owners": [
"your-discord-id", "your-discord-id",
], ],

@ -3,6 +3,6 @@ import { command } from "../../utils/commandRegistration/slashCommandBuilder.js"
const name = "nucleus"; const name = "nucleus";
const description = "Commands relating to Nucleus itself"; 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 }; export { name, description, subcommand as command };

@ -5,7 +5,6 @@ import verify from "../reflex/verify.js";
const command = new SlashCommandBuilder().setName("verify").setDescription("Get verified in the server"); const command = new SlashCommandBuilder().setName("verify").setDescription("Get verified in the server");
const callback = async (interaction: CommandInteraction): Promise<void> => { const callback = async (interaction: CommandInteraction): Promise<void> => {
interaction.reply("boo")
verify(interaction); verify(interaction);
}; };

@ -22,14 +22,14 @@ class NucleusClient extends Client {
premium: Premium; premium: Premium;
eventScheduler: EventScheduler; eventScheduler: EventScheduler;
}; };
// commands: Record<string, { commands: Record<string, {
// command: Discord.SlashCommandBuilder | command: Discord.SlashCommandBuilder |
// ((builder: Discord.SlashCommandBuilder) => Discord.SlashCommandBuilder) | ((builder: Discord.SlashCommandBuilder) => Discord.SlashCommandBuilder) |
// Discord.SlashCommandSubcommandBuilder | ((builder: Discord.SlashCommandSubcommandBuilder) => Discord.SlashCommandSubcommandBuilder) | Discord.SlashCommandSubcommandGroupBuilder | ((builder: Discord.SlashCommandSubcommandGroupBuilder) => Discord.SlashCommandSubcommandGroupBuilder), Discord.SlashCommandSubcommandBuilder | ((builder: Discord.SlashCommandSubcommandBuilder) => Discord.SlashCommandSubcommandBuilder) | Discord.SlashCommandSubcommandGroupBuilder | ((builder: Discord.SlashCommandSubcommandGroupBuilder) => Discord.SlashCommandSubcommandGroupBuilder),
// callback: (interaction: Interaction) => Promise<void>, callback: (interaction: Interaction) => Promise<void>,
// check: (interaction: Interaction) => Promise<boolean> | boolean check: (interaction: Interaction) => Promise<boolean> | boolean
// }> = {}; }> = {};
commands: Discord.Collection<string, [Function, Function]> = new Discord.Collection(); // commands: Discord.Collection<string, [Function, Function]> = new Discord.Collection();
constructor(database: typeof NucleusClient.prototype.database) { constructor(database: typeof NucleusClient.prototype.database) {
super({ intents: 32767 }); super({ intents: 32767 });

@ -12,11 +12,11 @@ export default async function getSubcommandsInFolder(path: string, indent: strin
try { try {
if (file.isDirectory()) { if (file.isDirectory()) {
// Get the _meta.ts file // 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")) { } else if (file.name.endsWith(".js")) {
// If its a file // If its a file
console.log(`${indent}├─ Loading subcommand ${file.name}`) console.log(`${indent}├─ Loading subcommand ${file.name}`)
subcommands.push((await import(`../../../${path}/${file.name}`)).command); subcommands.push(await import(`../../../${path}/${file.name}`));
} }
} catch (e) { } catch (e) {
console.error(`${indent}│ └─ Error loading ${file.name}: ${e}`); console.error(`${indent}│ └─ Error loading ${file.name}: ${e}`);

@ -2,6 +2,7 @@ import { Interaction, SlashCommandBuilder } from 'discord.js';
import config from "../../config/main.json" assert { type: "json" }; import config from "../../config/main.json" assert { type: "json" };
import client from "../client.js"; import client from "../client.js";
import fs from "fs"; import fs from "fs";
import Discord from "discord.js";
const colours = { const colours = {
@ -29,7 +30,7 @@ async function registerCommands() {
console.log(`${last}${colours.yellow}Loading command ${file.name}${colours.none}`) console.log(`${last}${colours.yellow}Loading command ${file.name}${colours.none}`)
const fetched = (await import(`../../../${config.commandsFolder}/${file.name}`)); const fetched = (await import(`../../../${config.commandsFolder}/${file.name}`));
commands.push(fetched.command); commands.push(fetched.command);
client.commands.set(fetched.command.name, [fetched.check, fetched.callback]); client.commands["commands/" + fetched.command.name] = fetched;
} }
i++; i++;
console.log(`${last.replace("└", " ").replace("├", "│")} └─ ${colours.green}Loaded ${file.name} [${i} / ${files.length}]${colours.none}`) 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...`) console.log(`Processed ${commands.length} commands, registering...`)
const updateCommands = process.argv.includes("--update-commands");
if (developmentMode) { if (developmentMode) {
const guild = await client.guilds.fetch(config.developmentGuildID); 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}`) console.log(`Commands registered in ${guild.name}`)
} else { } else {
client.application!.commands.set(processed); if (updateCommands) client.application!.commands.set(processed);
console.log(`Commands registered globally`) console.log(`Commands registered globally`)
} }
@ -86,50 +88,30 @@ async function registerEvents() {
async function registerCommandHandler() { async function registerCommandHandler() {
client.on("interactionCreate", async (interaction: Interaction) => { client.on("interactionCreate", async (interaction: Interaction) => {
if (!interaction.isCommand()) return; if (!interaction.isChatInputCommand()) return;
const commandName = interaction.commandName; const commandName = interaction.commandName;
const subcommandGroupName = interaction.options.getSubcommandGroup(false); const subcommandGroupName = interaction.options.getSubcommandGroup(false);
const subcommandName = interaction.options.getSubcommand(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); const command = client.commands[fullCommandName];
if (!command) return; const callback = command?.callback;
const check = command?.check;
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});
}
if (!callback) return;
if (check) {
let result;
try { try {
let hasPermission = await command.check(interaction); result = await check(interaction);
} catch (e) {
if (!hasPermission) { console.log(e);
sendErrorMessage(new CheckFailedError("You don't have permission to run this command")); result = false;
return;
}
} catch (error) {
sendErrorMessage(error);
return;
} }
try { if (!result) return;
await command.callback(interaction);
} catch (error) {
this._error(error);
sendErrorMessage(error);
return;
} }
callback(interaction);
}); });
} }

@ -2,6 +2,8 @@ import type { SlashCommandSubcommandGroupBuilder } from "@discordjs/builders";
import type { SlashCommandBuilder } from "discord.js"; import type { SlashCommandBuilder } from "discord.js";
import config from "../../config/main.json" assert { type: "json" }; import config from "../../config/main.json" assert { type: "json" };
import getSubcommandsInFolder from "./getFilesInFolder.js"; import getSubcommandsInFolder from "./getFilesInFolder.js";
import client from "../client.js";
import Discord from "discord.js";
const colours = { const colours = {
@ -12,6 +14,7 @@ const colours = {
export async function group(name: string, description: string, path: string) { 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}`) console.log(`│ ├─ Loading group ${name}`)
const fetched = await getSubcommandsInFolder(config.commandsFolder + "/" + path, "│ ") 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}`) 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) .setDescription(description)
for (const subcommand of fetched.subcommands) { for (const subcommand of fetched.subcommands) {
subcommandGroup.addSubcommand(subcommand); subcommandGroup.addSubcommand(subcommand.command);
}; };
return subcommandGroup; 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); 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}`) 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) => { return (command: SlashCommandBuilder) => {
@ -36,10 +41,17 @@ export async function command(name: string, description: string, path: string) {
command.setDescription(description) command.setDescription(description)
for (const subcommand of fetched.subcommands) { 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) { for (const group of fetched.subcommandGroups) {
command.addSubcommandGroup(group); command.addSubcommandGroup(group.command);
}; };
return command; return command;
}; };

@ -1,30 +1,11 @@
import type Discord from "discord.js"; import type Discord from "discord.js";
import { Collection, MongoClient } from "mongodb"; import { Collection, MongoClient } from "mongodb";
// @ts-expect-error
import structuredClone from "@ungap/structured-clone";
import config from "../config/main.json" assert { type: "json" }; import config from "../config/main.json" assert { type: "json" };
const mongoClient = new MongoClient(config.mongoUrl); const mongoClient = new MongoClient(config.mongoUrl);
await mongoClient.connect(); await mongoClient.connect();
const database = mongoClient.db("Nucleus"); 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<string, any>, 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 { export class Guilds {
guilds: Collection<GuildConfig>; guilds: Collection<GuildConfig>;
defaultData: GuildConfig | null; defaultData: GuildConfig | null;
@ -42,7 +23,7 @@ export class Guilds {
async read(guild: string): Promise<GuildConfig> { async read(guild: string): Promise<GuildConfig> {
const entry = await this.guilds.findOne({ id: guild }); 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 = []) { async write(guild: string, set: object | null, unset: string[] | string = []) {
@ -263,6 +244,7 @@ export interface GuildConfig {
}; };
}; };
verify: { verify: {
enabled: boolean;
role: string | null; role: string | null;
}; };
tickets: { tickets: {

@ -1,5 +1,6 @@
import EmojiEmbed from "./generateEmojiEmbed.js"; import EmojiEmbed from "./generateEmojiEmbed.js";
import getEmojiByName from "./getEmojiByName.js";
export const LoadingEmbed = [ 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")
]; ];

@ -1,4 +1,4 @@
import { EmbedBuilder } from "discord.js"; import { EmbedBuilder } from "@discordjs/builders";
import getEmojiByName from "./getEmojiByName.js"; import getEmojiByName from "./getEmojiByName.js";
const colors = { const colors = {
@ -11,19 +11,19 @@ class EmojiEmbed extends EmbedBuilder {
_title = ""; _title = "";
_emoji: string | null = null; _emoji: string | null = null;
// @ts-expect-error _generateTitle() {
// This *is* meant to be an accessor rather than a property if (this._emoji) { return `${getEmojiByName(this._emoji)} ${this._title}`; }
override get title() { return this._title;
if (!this._emoji) return this._title;
return `${getEmojiByName(this._emoji)} ${this._title}`;
} }
override setTitle(title: string) { override setTitle(title: string) {
this._title = title; this._title = title;
super.setTitle(this._generateTitle());
return this; return this;
} }
setEmoji(emoji: string) { setEmoji(emoji: string) {
this._emoji = emoji; this._emoji = emoji;
super.setTitle(this._generateTitle());
return this; return this;
} }
setStatus(color: "Danger" | "Warning" | "Success") { setStatus(color: "Danger" | "Warning" | "Success") {
@ -32,4 +32,5 @@ class EmojiEmbed extends EmbedBuilder {
} }
} }
export default EmojiEmbed; export default EmojiEmbed;

@ -4,7 +4,8 @@ interface EmojisIndex {
[key: string]: string | EmojisIndex | 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("."); const parts = name.split(".");
let id: string | EmojisIndex | EmojisIndex[] | undefined = emojis; let id: string | EmojisIndex | EmojisIndex[] | undefined = emojis;
for (const part of parts) { for (const part of parts) {
@ -25,7 +26,7 @@ function getEmojiByName(name: string, format?: string): string {
return id.toString(); return id.toString();
} }
if (id === undefined) { if (id === undefined) {
return "<a:_:946346549271732234>"; return "";
} else if (id.toString().startsWith("a")) { } else if (id.toString().startsWith("a")) {
return `<a:_:${id.toString().slice(1, id.toString().length)}>`; return `<a:_:${id.toString().slice(1, id.toString().length)}>`;
} }

Loading…
Cancel
Save