···11+import {
22+ ChatInputCommandInteraction,
33+ Client,
44+ CommandInteraction,
55+ SlashCommandBuilder,
66+} from "discord.js";
77+import type { customClient } from "../..";
88+99+export const data = new SlashCommandBuilder()
1010+ .setName("ping")
1111+ .setDescription("Replies with pong!");
1212+export const execute = async (
1313+ interaction: ChatInputCommandInteraction & { client: customClient }
1414+) => {
1515+ await interaction.reply("Pong!");
1616+};
+92-1
src/index.ts
···11-console.log("Hello via Bun!");11+import {
22+ ChatInputCommandInteraction,
33+ Client,
44+ Collection,
55+ Events,
66+ GatewayIntentBits,
77+ MessageFlags,
88+ type Interaction,
99+} from "discord.js";
1010+import { Glob } from "bun";
1111+1212+const token = process.env.token;
1313+if (!token) throw new Error("Token required. Please fill in TOKEN in .env");
1414+console.log("Token Valid!");
1515+1616+// client typing
1717+export type customClient = Client & {
1818+ // add command typing
1919+ commands: Collection<
2020+ string,
2121+ {
2222+ data: { name: string };
2323+ execute: (
2424+ interaction: ChatInputCommandInteraction & { client: customClient }
2525+ ) => Promise<void>;
2626+ }
2727+ >;
2828+};
2929+// new client
3030+const client: customClient = new Client({
3131+ intents: [GatewayIntentBits.Guilds],
3232+ // as customclient as i cannot add .commands till this is done
3333+}) as customClient;
3434+3535+// setup commands
3636+client.commands = new Collection();
3737+const commandGlob = new Glob("**/*.ts");
3838+for await (const file of commandGlob.scan("./src/commands")) {
3939+ const command = await import("./commands/" + file);
4040+ // check command contains all required properties
4141+ if (
4242+ "data" in command &&
4343+ "execute" in command &&
4444+ typeof command.data === "object" &&
4545+ command.data !== null &&
4646+ "name" in command.data &&
4747+ typeof command.data.name === "string"
4848+ ) {
4949+ client.commands.set(command.data.name, command);
5050+ console.log("[loaded]", file);
5151+ } else {
5252+ // log missing features
5353+ console.log(`[WARNING] ${file} is not a valid command!`, command);
5454+ }
5555+}
5656+5757+// when client is ready do this once
5858+client.once(Events.ClientReady, (ready) => {
5959+ console.log(`Ready! Logged in as ${ready.user.tag}`);
6060+});
6161+6262+// _interaction so we can cast types properly
6363+// we need access to client.commands
6464+// we could just do it as a global but this makes it go wherever the command goes soooo
6565+client.on(Events.InteractionCreate, async (_interaction) => {
6666+ const interaction = _interaction as Interaction & { client: customClient };
6767+ if (!interaction.isChatInputCommand()) return;
6868+ const command = interaction.client.commands.get(interaction.commandName);
6969+ if (!command) {
7070+ console.error(`No command ${interaction.commandName}`);
7171+ return;
7272+ }
7373+7474+ try {
7575+ await command.execute(interaction);
7676+ } catch (e) {
7777+ console.error(e);
7878+ if (interaction.replied || interaction.deferred) {
7979+ await interaction.followUp({
8080+ content: "There was an error while executing this command!",
8181+ flags: MessageFlags.Ephemeral,
8282+ });
8383+ } else {
8484+ await interaction.reply({
8585+ content: "There was an error while executing this command!",
8686+ flags: MessageFlags.Ephemeral,
8787+ });
8888+ }
8989+ }
9090+});
9191+9292+client.login(token);
+54
src/sync.js
···11+const { REST, Routes } = require("discord.js");
22+const { client: clientId, guild: guildId, token } = process.env;
33+const fs = require("node:fs");
44+const path = require("node:path");
55+66+const commands = [];
77+// Grab all the command folders from the commands directory you created earlier
88+const foldersPath = path.join(__dirname, "commands");
99+const commandFolders = fs.readdirSync(foldersPath);
1010+1111+for (const folder of commandFolders) {
1212+ // Grab all the command files from the commands directory you created earlier
1313+ const commandsPath = path.join(foldersPath, folder);
1414+ const commandFiles = fs
1515+ .readdirSync(commandsPath)
1616+ .filter((file) => file.endsWith(".ts") || file.endsWith(".js"));
1717+ // Grab the SlashCommandBuilder#toJSON() output of each command's data for deployment
1818+ for (const file of commandFiles) {
1919+ const filePath = path.join(commandsPath, file);
2020+ const command = require(filePath);
2121+ if ("data" in command && "execute" in command) {
2222+ commands.push(command.data.toJSON());
2323+ } else {
2424+ console.log(
2525+ `[WARNING] The command at ${filePath} is missing a required "data" or "execute" property.`
2626+ );
2727+ }
2828+ }
2929+}
3030+3131+// Construct and prepare an instance of the REST module
3232+const rest = new REST().setToken(token);
3333+3434+// and deploy your commands!
3535+(async () => {
3636+ try {
3737+ console.log(
3838+ `Started refreshing ${commands.length} application (/) commands.`
3939+ );
4040+4141+ // The put method is used to fully refresh all commands in the guild with the current set
4242+ const data = await rest.put(Routes.applicationGuildCommands(clientId, guildId), {
4343+ body: commands,
4444+ });
4545+4646+ console.log(
4747+ `Successfully reloaded ${data.length} application (/) commands.`
4848+ );
4949+ } catch (error) {
5050+ // And of course, make sure you catch and log any errors!
5151+ console.error(error);
5252+ process.stdout.write(JSON.stringify(error) + "\n");
5353+ }
5454+})();