Skip to content

Commit

Permalink
simplify and improve command and event registration
Browse files Browse the repository at this point in the history
  • Loading branch information
adamhl8 committed Dec 14, 2022
1 parent 5c1ac2d commit 755ed59
Show file tree
Hide file tree
Showing 11 changed files with 121 additions and 150 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "discord-bot-shared",
"version": "0.7.0",
"version": "0.8.0",
"type": "module",
"description": "Modules for creating discord bots.",
"repository": "github:adamhl8/discord-bot-shared",
Expand All @@ -25,7 +25,7 @@
"discord.js": "^14.7.1"
},
"devDependencies": {
"@types/node": "^18.11.13",
"@types/node": "^18.11.15",
"@typescript-eslint/eslint-plugin": "^5.46.1",
"@typescript-eslint/parser": "^5.46.1",
"eslint": "^8.29.0",
Expand Down
44 changes: 0 additions & 44 deletions src/commands.ts

This file was deleted.

13 changes: 0 additions & 13 deletions src/events.ts

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { ChatInputCommandInteraction, Client } from "discord.js"
import { Command, CommandsCollection } from "./commands.js"
import { Command, Commands } from "../register-commands.js"

type InteractionCheck = (interaction: ChatInputCommandInteraction) => Promise<boolean | void>

function registerInteractionCreate(bot: Client, commands: CommandsCollection, interactionCheck?: InteractionCheck) {
function registerInteractionCreate(bot: Client, commands: Commands, interactionCheck?: InteractionCheck) {
bot.on("interactionCreate", async (interaction) => {
if (!interaction.isChatInputCommand()) return

const command = commands.get(interaction.commandName)
const command = commands[interaction.commandName]
if (!command) return interactionReply(interaction, "Unable to get command.")

if (!(await checkRoles(command, interaction)))
Expand Down
2 changes: 1 addition & 1 deletion src/ready.ts → src/events/ready.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Client } from "discord.js"

function registerReady(bot: Client) {
bot.once("ready", () => {
console.log("I am ready!")
console.log("Client is ready.")
})
}

Expand Down
37 changes: 0 additions & 37 deletions src/guild-cache.ts

This file was deleted.

50 changes: 50 additions & 0 deletions src/guild.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { ChannelType, Guild as djsGuild, NonThreadGuildBasedChannel } from "discord.js"

type NonThreadGuildBasedChannelType =
| ChannelType.GuildText
| ChannelType.GuildVoice
| ChannelType.GuildNews
| ChannelType.GuildStageVoice
| ChannelType.GuildCategory

class Guild {
readonly id: string
readonly guild: djsGuild

constructor(id: string, guild: djsGuild) {
this.id = id
this.guild = guild
}

get members() {
return this.guild.members.fetch()
}

get channels() {
return this.guild.channels.fetch()
}

get roles() {
return this.guild.roles.fetch()
}

get emojis() {
return this.guild.emojis.fetch()
}

async getChannel<T extends NonThreadGuildBasedChannel>(
channelNameOrId: string,
channelType: NonThreadGuildBasedChannelType,
): Promise<T | undefined> {
const channels = await this.channels

let channel: NonThreadGuildBasedChannel | undefined | null
channel = channels.find((channel) => (channel ? channel.name === channelNameOrId : false))
if (channel) return channel.type === channelType ? (channel as T) : undefined

channel = channels.get(channelNameOrId)
if (channel) return channel.type === channelType ? (channel as T) : undefined
}
}

export default Guild
41 changes: 24 additions & 17 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,37 @@
import { Client, ClientOptions } from "discord.js"
import registerCommands from "./commands.js"
import registerEvents from "./events.js"
import { setBot } from "./guild-cache.js"
import registerInteractionCreate, { InteractionCheck } from "./interaction-create.js"
import registerReady from "./ready.js"
import { Client, ClientOptions, Collection } from "discord.js"
import registerInteractionCreate, { InteractionCheck } from "./events/interaction-create.js"
import registerReady from "./events/ready.js"
import Guild from "./guild.js"
import registerCommands, { Commands } from "./register-commands.js"
import registerEvents, { Events } from "./register-events.js"

const botToken = process.env.BOT_TOKEN || ""
const clientId = process.env.CLIENT_ID || ""
const guildId = process.env.GUILD_ID || ""

async function login(botIntents: ClientOptions, projectMetaURL: string, interactionCheck?: InteractionCheck): Promise<Client> {
async function login(botIntents: ClientOptions, commands: Commands, events: Events, interactionCheck?: InteractionCheck) {
console.log("Logging in...")

const bot = new Client(botIntents)
const commands = await registerCommands(botToken, clientId, projectMetaURL, guildId)
await registerCommands(botToken, clientId, commands)
registerEvents(bot, events)

// Register default/built-in events
registerReady(bot)
registerInteractionCreate(bot, commands, interactionCheck)
void registerEvents(projectMetaURL)

setBot(bot)
await bot.login(botToken)
console.log("Logged in.")

const partialGuilds = await bot.guilds.fetch()
const guildPromises = partialGuilds.map((guildPartial) => guildPartial.fetch())
const guilds = await Promise.all(guildPromises)
const GuildCollection = new Collection<string, Guild>()
for (const guild of guilds) GuildCollection.set(guild.id, new Guild(guild.id, guild))

void bot.login(botToken)
return bot
return { GuildCollection, bot }
}

export default login
export { Command } from "./commands.js"
export { default as getGuildCache } from "./guild-cache.js"
export { InteractionCheck } from "./interaction-create.js"
export { getChannel, isCategoryChannel, isTextChannel, throwError } from "./util.js"
export { InteractionCheck } from "./events/interaction-create.js"
export { Command } from "./register-commands.js"
export { isCategoryChannel, isTextChannel, throwError } from "./util.js"
24 changes: 24 additions & 0 deletions src/register-commands.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { REST } from "@discordjs/rest"
import { Routes } from "discord-api-types/v10"
import { ChatInputCommandInteraction, SlashCommandBuilder } from "discord.js"

interface Command {
requiredRoles?: string[]
command: SlashCommandBuilder
run: (interaction: ChatInputCommandInteraction) => void | Promise<void>
}

interface Commands {
[name: string]: Command
}

async function registerCommands(botToken: string, clientId: string, commands: Commands) {
const commandData = Object.values(commands).map((command) => command.command.toJSON())

const rest = new REST().setToken(botToken)
await rest.put(Routes.applicationCommands(clientId), { body: commandData })
console.log("Registered (/) commands.")
}

export default registerCommands
export { Command, Commands }
15 changes: 15 additions & 0 deletions src/register-events.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Client } from "discord.js"

type Event = (bot: Client) => void

interface Events {
[name: string]: Event
}

function registerEvents(bot: Client, events: Events) {
for (const registerEvent of Object.values(events)) registerEvent(bot)
console.log("Registered events.")
}

export default registerEvents
export { Event, Events }
35 changes: 2 additions & 33 deletions src/util.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,4 @@
import {
APIPartialChannel,
BaseChannel,
CategoryChannel,
ChannelType,
GuildResolvable,
NonThreadGuildBasedChannel,
TextChannel,
} from "discord.js"
import getGuildCache from "./guild-cache.js"

type NonThreadGuildBasedChannelType =
| ChannelType.GuildText
| ChannelType.GuildVoice
| ChannelType.GuildNews
| ChannelType.GuildStageVoice
| ChannelType.GuildCategory

async function getChannel<T extends NonThreadGuildBasedChannel>(
channelNameOrId: string,
channelType: NonThreadGuildBasedChannelType,
guildResolvable: GuildResolvable,
): Promise<T | undefined> {
const { channels } = (await getGuildCache(guildResolvable)) || throwError("Unable to get guild cache.")

let channel: NonThreadGuildBasedChannel | undefined | null
channel = channels.find((channel) => (channel ? channel.name === channelNameOrId : false))
if (channel) return channel.type === channelType ? (channel as T) : undefined

channel = channels.get(channelNameOrId)
if (channel) return channel.type === channelType ? (channel as T) : undefined
}
import { APIPartialChannel, BaseChannel, CategoryChannel, ChannelType, TextChannel } from "discord.js"

function isTextChannel(channel: BaseChannel | APIPartialChannel): channel is TextChannel {
return channel.type === ChannelType.GuildText
Expand All @@ -43,4 +12,4 @@ function throwError(error: string): never {
throw new Error(error)
}

export { getChannel, isTextChannel, isCategoryChannel, throwError }
export { isTextChannel, isCategoryChannel, throwError }

0 comments on commit 755ed59

Please sign in to comment.