diff --git a/package.json b/package.json index a24e9c8..d355beb 100644 --- a/package.json +++ b/package.json @@ -1,17 +1,17 @@ { - "name": "butlerbotng", + "name": "butlerbot", "version": "1.0.0", "main": "dist/index.js", "scripts": { "start": "node dist/index.js", "build": "tsup src/index.ts --minify", - "register-slash-commands": "tsx src/register-slash-commands.ts", + "register-slash-commands": "tsx src/registerSlashCommands.ts", "dev": "tsx watch src/index.ts", "test": "echo \"Error: no test specified\" && exit 1" }, "repository": { "type": "git", - "url": "https://git.3t.network/terriblecodeclub/butlerbotng.git" + "url": "https://git.3t.network/terriblecodeclub/butlerbot.git" }, "author": "Butlersaurus", "license": "ISC", @@ -23,13 +23,13 @@ "dotenv": "^16.4.5", "glob": "^11.0.0", "mongodb": "^6.8.1", - "prettier": "^3.3.3", "replicate": "^0.32.1" }, "devDependencies": { "@types/glob": "^8.1.0", "@types/node": "^20.4.0", "eslint": "^9.9.1", + "prettier": "^3.3.3", "ts-node": "^10.9.1", "tsup": "^8.2.4", "tsx": "^4.19.1", diff --git a/src/commands.ts b/src/commands.ts deleted file mode 100644 index c8bd982..0000000 --- a/src/commands.ts +++ /dev/null @@ -1,33 +0,0 @@ -import * as birthday from './commands/birthday'; -import * as corrupt from './commands/corrupt'; -import * as countdown from './commands/countdown'; -import * as eyecandy from './commands/eyecandy'; -import * as game from './commands/game'; -import * as image from './commands/image'; -import * as imdb from './commands/imdb'; -import * as kanye from './commands/kanye'; -import * as magicEightBall from './commands/magic8Ball'; -import * as payday from './commands/payday'; -import * as plant from './commands/plant'; -import * as reminder from './commands/reminder'; -import * as servertime from './commands/servertime'; -import * as taylor from './commands/taylor'; -import * as twentyTwenty from './commands/twentyTwenty'; - -export const commands = { - birthday, - corrupt, - countdown, - eyecandy, - game, - image, - imdb, - kanye, - magicEightBall, - payday, - plant, - reminder, - servertime, - taylor, - twentyTwenty, -}; diff --git a/src/config.ts b/src/config.ts index 0072cdc..f8b63dc 100644 --- a/src/config.ts +++ b/src/config.ts @@ -10,9 +10,10 @@ interface Config { omdbApiKey: string; replicateApiKey: string; mongodbUri: string; + registerSlashCommands?: boolean; } -const getEnv = (key: string, required = true): string | undefined => { +const getEnv = (key: string, required = true): string | boolean | undefined => { const value = process.env[key]; if (!value && required) { throw new Error(`${key} is not set in the environment variables.`); @@ -28,6 +29,7 @@ const config: Config = { omdbApiKey: getEnv('OMDB_API_KEY') as string, replicateApiKey: getEnv('REPLICATE_API_KEY') as string, mongodbUri: getEnv('MONGODB_URI') as string, + registerSlashCommands: getEnv('REGISTER_SLASH_COMMANDS', false) as boolean, }; export default config; diff --git a/src/index.ts b/src/index.ts index da30c7a..b26eef3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,25 +6,50 @@ import { GatewayIntentBits, Interaction, } from 'discord.js'; -import { commands } from './commands'; +import { getCommands, registerSlashCommands } from './utils/commands'; +import { Command } from './utils/types'; +import config from './config'; // Define an extended version of the Client interface to include commands interface ExtendedClient extends Client { - commands: Collection; + commands: Collection; } const client: ExtendedClient = new Client({ intents: [GatewayIntentBits.Guilds], }) as ExtendedClient; +// Add the commands to the client +client.commands = getCommands(); + +// If REGISTER_SLASH_COMMANDS is set to true, register the commands. +if (config.registerSlashCommands) { + registerSlashCommands(client.commands) + .then(() => { + console.log('Successfully registered slash commands'); + }) + .catch((error) => { + console.error('Failed to register slash commands:', error); + process.exit(1); + }); +} + // Register the event listener for command interactions. client.on(Events.InteractionCreate, async (interaction: Interaction) => { if (!interaction.isChatInputCommand()) return; - const { commandName } = interaction; + const command = client.commands.get(interaction.commandName); - if (commands[commandName as keyof typeof commands]) { - commands[commandName as keyof typeof commands].execute(interaction); + if (command) { + try { + await command.execute(interaction); + } catch (error) { + console.error(`Error executing ${interaction.commandName}:`, error); + await interaction.reply({ + content: 'There was an error executing that command!', + ephemeral: true, + }); + } } }); diff --git a/src/register-slash-commands.ts b/src/register-slash-commands.ts deleted file mode 100644 index 5ba8a45..0000000 --- a/src/register-slash-commands.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { REST, Routes } from 'discord.js'; -import { commands } from './commands'; -import config from './config'; - -// Register all slash commands, globally across all Guilds. -const commandData = Object.values(commands).map( - (command) => command.data.toJSON() as any -); - -const rest = new REST().setToken(config.discordApiKey); - -try { - console.log('Started refreshing application (/) commands.'); - - // Refresh all slash commands globally. - rest.put(Routes.applicationCommands(config.discordApplicationId), { - body: commandData, - }); - - // Exit the process. - process.exit(); -} catch (error) { - // And of course, make sure you catch and log any errors! - console.error(error); -} diff --git a/src/registerSlashCommands.ts b/src/registerSlashCommands.ts new file mode 100644 index 0000000..5431c68 --- /dev/null +++ b/src/registerSlashCommands.ts @@ -0,0 +1,12 @@ +import { getCommands, registerSlashCommands } from './utils/commands'; + +const commands = getCommands(); + +registerSlashCommands(commands) + .then(() => { + console.log('Successfully registered slash commands'); + }) + .catch((error) => { + console.error('Failed to register slash commands:', error); + process.exit(1); + }); diff --git a/src/utils/commands.ts b/src/utils/commands.ts new file mode 100644 index 0000000..e785281 --- /dev/null +++ b/src/utils/commands.ts @@ -0,0 +1,58 @@ +import { Collection, REST, Routes } from 'discord.js'; +import * as birthday from '../commands/birthday'; +import * as corrupt from '../commands/corrupt'; +import * as countdown from '../commands/countdown'; +import * as eyecandy from '../commands/eyecandy'; +import * as game from '../commands/game'; +import * as image from '../commands/image'; +import * as imdb from '../commands/imdb'; +import * as kanye from '../commands/kanye'; +import * as magicEightBall from '../commands/magic8Ball'; +import * as payday from '../commands/payday'; +import * as plant from '../commands/plant'; +import * as reminder from '../commands/reminder'; +import * as servertime from '../commands/servertime'; +import * as taylor from '../commands/taylor'; +import * as twentyTwenty from '../commands/twentyTwenty'; +import config from '../config'; +import { Command } from './types'; + +/** + * Get all commands as a collection. + * @returns A collection of commands. + */ +export function getCommands(): Collection { + const commands = new Collection(); + + commands.set(birthday.data.name, birthday); + commands.set(corrupt.data.name, corrupt); + commands.set(countdown.data.name, countdown); + commands.set(eyecandy.data.name, eyecandy); + commands.set(game.data.name, game); + commands.set(image.data.name, image); + commands.set(imdb.data.name, imdb); + commands.set(kanye.data.name, kanye); + commands.set(magicEightBall.data.name, magicEightBall); + commands.set(payday.data.name, payday); + commands.set(plant.data.name, plant); + commands.set(reminder.data.name, reminder); + commands.set(servertime.data.name, servertime); + commands.set(taylor.data.name, taylor); + commands.set(twentyTwenty.data.name, twentyTwenty); + + return commands; +} + +/** + * Register all slash commands globally across all Guilds. + * @param commands A collection of commands to register. + */ +export async function registerSlashCommands( + commands: Collection +) { + const commandData = commands.map((command) => command.data.toJSON()); + const rest = new REST({ version: '10' }).setToken(config.discordApiKey); + await rest.put(Routes.applicationCommands(config.discordApplicationId), { + body: commandData, + }); +} diff --git a/src/utils/types.ts b/src/utils/types.ts new file mode 100644 index 0000000..348210e --- /dev/null +++ b/src/utils/types.ts @@ -0,0 +1,18 @@ +import { + SlashCommandBuilder, + SlashCommandSubcommandsOnlyBuilder, + SlashCommandOptionsOnlyBuilder, + ChatInputCommandInteraction, +} from 'discord.js'; + +// Create a generic type to cover all relevant SlashCommandBuilder types +export type CommandBuilder = + | SlashCommandBuilder + | SlashCommandSubcommandsOnlyBuilder + | SlashCommandOptionsOnlyBuilder; + +// Define the Command interface +export interface Command { + data: CommandBuilder; // Use the generic CommandBuilder type + execute: (interaction: ChatInputCommandInteraction) => Promise; +}