feat(bot): boilerplate for adding interaction commands and event listeners
This commit is contained in:
parent
55c6a7125a
commit
cf8713c1bb
@ -1,23 +1,22 @@
|
||||
import { Client, GatewayIntentBits, ActivityType } from "discord.js";
|
||||
import { Client, GatewayIntentBits } from "discord.js";
|
||||
import EventHandler from "@bot/handlers/events";
|
||||
import InteractionHandler from "@bot/handlers/interactions";
|
||||
import { triggerTask } from "@bot/task";
|
||||
|
||||
export const client = new Client({
|
||||
intents: [
|
||||
GatewayIntentBits.Guilds,
|
||||
GatewayIntentBits.GuildMembers,
|
||||
GatewayIntentBits.GuildWebhooks
|
||||
]
|
||||
})
|
||||
export default class DiscordBot extends Client {
|
||||
constructor() {
|
||||
super({ intents: [
|
||||
GatewayIntentBits.Guilds,
|
||||
GatewayIntentBits.GuildMembers,
|
||||
GatewayIntentBits.MessageContent,
|
||||
GatewayIntentBits.GuildWebhooks, // May not need?
|
||||
] });
|
||||
|
||||
client.on("ready", () => {
|
||||
if (!client.user) {
|
||||
throw Error("Client is null");
|
||||
this.login(process.env.BOT_TOKEN);
|
||||
}
|
||||
|
||||
setInterval(() => triggerTask(client), 5000);
|
||||
public events = new EventHandler(this);
|
||||
public interactions = new InteractionHandler(this);
|
||||
}
|
||||
|
||||
client.user.setActivity("new sources", {type: ActivityType.Watching});
|
||||
console.log(`Discord Bot ${client.user.displayName} is online!`)
|
||||
});
|
||||
|
||||
client.login(process.env.BOT_TOKEN);
|
||||
export const client = new DiscordBot();
|
||||
|
15
src/bot/components/event.ts
Normal file
15
src/bot/components/event.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import DiscordBot from "@bot/bot";
|
||||
|
||||
export default class Event {
|
||||
readonly client: DiscordBot;
|
||||
name!: string;
|
||||
once!: boolean;
|
||||
|
||||
constructor(client: DiscordBot) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
execute(..._args: unknown[]) {
|
||||
throw new Error("No execute override");
|
||||
}
|
||||
}
|
32
src/bot/components/interaction.ts
Normal file
32
src/bot/components/interaction.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import { RESTPostAPIApplicationCommandsJSONBody, SlashCommandBuilder } from "discord.js";
|
||||
import DiscordBot from "@bot/bot";
|
||||
|
||||
export default class Interaction {
|
||||
readonly client: DiscordBot;
|
||||
name!: string;
|
||||
description: string = "No description";
|
||||
options: any[] = [];
|
||||
dmPermission!: boolean;
|
||||
|
||||
constructor(client: DiscordBot) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
execute(..._args: unknown[]) {
|
||||
throw new Error("No execute override");
|
||||
}
|
||||
|
||||
toJSON(): RESTPostAPIApplicationCommandsJSONBody {
|
||||
const command = new SlashCommandBuilder();
|
||||
|
||||
command.setName(this.name);
|
||||
command.setDescription(this.description);
|
||||
command.setDMPermission(this.dmPermission); // TODO: deprecated - replace
|
||||
|
||||
for (const option of this.options) {
|
||||
command.options.push(option);
|
||||
}
|
||||
|
||||
return command.toJSON();
|
||||
}
|
||||
}
|
14
src/bot/events/interactionCreate.ts
Normal file
14
src/bot/events/interactionCreate.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { Interaction } from "discord.js";
|
||||
import Event from "@bot/components/event";
|
||||
|
||||
export default class interactionCreate extends Event {
|
||||
name = "interactionCreate";
|
||||
|
||||
async execute(interaction: Interaction) {
|
||||
if (!interaction.isChatInputCommand()) return;
|
||||
|
||||
await interaction.deferReply();
|
||||
const command = this.client.interactions.get(interaction.commandName);
|
||||
return command!.execute(interaction);
|
||||
}
|
||||
}
|
19
src/bot/events/ready.ts
Normal file
19
src/bot/events/ready.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { ActivityType } from "discord.js";
|
||||
import Event from "@bot/components/event";
|
||||
import DiscordBot from "@bot/bot";
|
||||
|
||||
export default class Ready extends Event {
|
||||
name = "ready";
|
||||
once = true;
|
||||
|
||||
async execute(client: DiscordBot): Promise<void> {
|
||||
if (!client.user) {
|
||||
throw new Error("Discord client is not truthy");
|
||||
}
|
||||
|
||||
await client.interactions.deploy();
|
||||
|
||||
client.user.setActivity("new sources", {type: ActivityType.Watching});
|
||||
console.log(`Discord Bot ${client.user.displayName} is online!`);
|
||||
}
|
||||
}
|
31
src/bot/handlers/events.ts
Normal file
31
src/bot/handlers/events.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import { join } from "path";
|
||||
import { Collection } from "discord.js";
|
||||
import getAllFiles from "@bot/utils/getAllFiles";
|
||||
import Event from "@bot/components/event";
|
||||
import DiscordBot from "@bot/bot";
|
||||
|
||||
export default class EventHandler extends Collection<string, Event> {
|
||||
readonly client: DiscordBot;
|
||||
|
||||
constructor(client: DiscordBot) {
|
||||
super();
|
||||
this.client = client
|
||||
this.init();
|
||||
}
|
||||
|
||||
private async init() {
|
||||
const eventsDirectory = join(__dirname, "../events");
|
||||
const modules = getAllFiles(eventsDirectory);
|
||||
|
||||
for (const module of modules) {
|
||||
const eventClass = ((r) => r.default || r)(require(module));
|
||||
const event: Event = new eventClass(this.client);
|
||||
this.set(event.name, event);
|
||||
|
||||
this.client[event.once ? "once" : "on"](
|
||||
event.name,
|
||||
(...args: unknown[]) => event.execute(...args)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
38
src/bot/handlers/interactions.ts
Normal file
38
src/bot/handlers/interactions.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import { join } from "path";
|
||||
import { Collection, REST, Routes } from "discord.js";
|
||||
import getAllFiles from "@bot/utils/getAllFiles";
|
||||
import Interaction from "@bot/components/interaction";
|
||||
import DiscordBot from "@bot/bot";
|
||||
|
||||
export default class InteractionHandler extends Collection<string, Interaction> {
|
||||
readonly client: DiscordBot;
|
||||
|
||||
constructor(client: DiscordBot) {
|
||||
super();
|
||||
this.client = client;
|
||||
this.init();
|
||||
}
|
||||
|
||||
private async init() {
|
||||
const interactionsDirectory = join(__dirname, "../interactions");
|
||||
const modules = getAllFiles(interactionsDirectory);
|
||||
|
||||
for (const module of modules) {
|
||||
const interactionClass = ((r) => r.default || r)(require(module))
|
||||
const interaction: Interaction = new interactionClass(this.client);
|
||||
this.set(interaction.name, interaction);
|
||||
}
|
||||
}
|
||||
|
||||
async deploy() {
|
||||
const interactions = this.map(inter => inter.toJSON())
|
||||
const rest = new REST({ version: "10" }).setToken(process.env.BOT_TOKEN!);
|
||||
|
||||
for (const [_, guild] of this.client.guilds.cache) {
|
||||
rest.put(
|
||||
Routes.applicationGuildCommands(process.env.CLIENT_ID!, guild.id),
|
||||
{ body: interactions }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
20
src/bot/interactions/ping.ts
Normal file
20
src/bot/interactions/ping.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { CommandInteraction, EmbedBuilder } from "discord.js";
|
||||
import Interaction from "@bot/components/interaction";
|
||||
|
||||
export default class Ping extends Interaction {
|
||||
name = "ping";
|
||||
description = "Measure the bot's ability to respond.";
|
||||
|
||||
async execute(interaction: CommandInteraction) {
|
||||
const execTime = Math.abs(Date.now() - interaction.createdTimestamp);
|
||||
const apiLatency = Math.floor(this.client.ws.ping);
|
||||
|
||||
const embed = new EmbedBuilder();
|
||||
embed.addFields([
|
||||
{ name: "Command Time", value: `${execTime}ms` },
|
||||
{ name: "Discord API Latency", value: `${apiLatency}ms` }
|
||||
])
|
||||
|
||||
return interaction.editReply({ embeds: [embed] });
|
||||
}
|
||||
}
|
17
src/bot/interactions/trigger.ts
Normal file
17
src/bot/interactions/trigger.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { CommandInteraction } from "discord.js";
|
||||
import Interaction from "@bot/components/interaction";
|
||||
import { triggerTask } from "@bot/task";
|
||||
|
||||
|
||||
export default class Trigger extends Interaction {
|
||||
name = "trigger";
|
||||
description = "Perform a single process of the feeds."
|
||||
|
||||
async execute(interaction: CommandInteraction) {
|
||||
await triggerTask(this.client);
|
||||
const execTime = Math.abs(Date.now() - interaction.createdTimestamp);
|
||||
return interaction.editReply({
|
||||
content: `Completed in \`${execTime}ms\``
|
||||
});
|
||||
}
|
||||
}
|
22
src/bot/utils/getAllFiles.ts
Normal file
22
src/bot/utils/getAllFiles.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import fs from "fs";
|
||||
import { join } from "path";
|
||||
|
||||
const getAllFiles = (directoryPath: string, existingResult?: string[]) => {
|
||||
const fileNames = fs.readdirSync(directoryPath);
|
||||
|
||||
let result: string[] = existingResult ?? [];
|
||||
|
||||
for (const fileName of fileNames) {
|
||||
if (!(fileName.endsWith(".ts") || fileName.endsWith(".js"))) continue;
|
||||
|
||||
const fullPath = join(directoryPath, fileName);
|
||||
|
||||
fs.statSync(fullPath).isDirectory()
|
||||
? result = getAllFiles(fullPath, result)
|
||||
: result.push(fullPath);
|
||||
}
|
||||
|
||||
return result
|
||||
};
|
||||
|
||||
export default getAllFiles;
|
Loading…
x
Reference in New Issue
Block a user