diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..03b63c4 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,19 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Debug Bot", + "skipFiles": ["/**"], + "program": "${workspaceFolder}/src/index.ts", + "preLaunchTask": "npm: build", + "sourceMaps": true, + "outFiles": ["${workspaceFolder}/out/**/*.js"], + "cwd": "${workspaceFolder}/out" + } + ] +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..17a0de6 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,24 @@ +{ + // For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558 + "version": "2.0.0", + "tasks": [ + { + "type": "npm", + "script": "start", + "isBackground": true, + "problemMatcher": { + "owner": "typescript", + "pattern": "$tsc", + "background": { + "activeOnStart": true, + "beginsPattern": { + "regexp": "(.*?)" + }, + "endsPattern": { + "regexp": "Build complete" + } + } + } + } + ] +} diff --git a/package.json b/package.json index 589125e..c518244 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "start": "npm run build && cd out && node index.js", "build": "tsc", "prebuild": "rimraf ./out", + "postbuild": "echo ✅ Build complete", "lint": "eslint ./src/**" }, "keywords": [], diff --git a/src/modules/commands/dm.ts b/src/modules/commands/dm.ts index 145d60d..0e78630 100644 --- a/src/modules/commands/dm.ts +++ b/src/modules/commands/dm.ts @@ -1,38 +1,26 @@ -import { SlashCommandBuilder, type ChatInputCommandInteraction } from "discord.js"; -import { type Command } from "../../types/Command"; -import type CrystalClient from "../../types/CrystalClient"; +import { SlashCommandBuilder, type ChatInputCommandInteraction } from "discord.js" +import { type Command } from "../../types/Command" +import type CrystalClient from "../../types/CrystalClient" module.exports = { - name: 'dm', + name: "dm", data: new SlashCommandBuilder() .setName("dm") .setDescription("Send a direct message to someone using the bot.") - .addUserOption((option) => - option - .setName('user') - .setDescription('The user you want to message.') - .setRequired(true)) - .addStringOption((option) => - option - .setName('message') - .setDescription("If you want to send a message type it here.") - .setRequired(false)) - .addStringOption((option) => - option - .setName('emojis') - .setDescription('The emoji(s) you want to react with.') - .setRequired(false)) - .addAttachmentOption((option) => - option - .setName('attachment') - .setDescription('The attachment you want to send.') - .setRequired(false)) - .addStringOption((option) => - option - .setName('message_id') - .setDescription('The message id you want to use.') - .setRequired(false)), - execute: async function (interaction: ChatInputCommandInteraction, client: CrystalClient): Promise { - - } + .addUserOption(option => + option.setName("user").setDescription("The user you want to message.").setRequired(true) + ) + .addStringOption(option => + option.setName("message").setDescription("If you want to send a message type it here.").setRequired(false) + ) + .addStringOption(option => + option.setName("emojis").setDescription("The emoji(s) you want to react with.").setRequired(false) + ) + .addAttachmentOption(option => + option.setName("attachment").setDescription("The attachment you want to send.").setRequired(false) + ) + .addStringOption(option => + option.setName("message_id").setDescription("The message id you want to use.").setRequired(false) + ), + execute: async function (interaction: ChatInputCommandInteraction, client: CrystalClient): Promise {}, } satisfies Command diff --git a/src/modules/commands/hello.ts b/src/modules/commands/hello.ts index 5256064..78592e7 100644 --- a/src/modules/commands/hello.ts +++ b/src/modules/commands/hello.ts @@ -1,14 +1,13 @@ import { SlashCommandBuilder, type ChatInputCommandInteraction } from "discord.js" import { type Command } from "../../types/Command" -import type CrystalClient from "../../types/CrystalClient" module.exports = { name: "hello", data: new SlashCommandBuilder().setName("hello").setDescription("Say hello!"), - execute: async function (interaction: ChatInputCommandInteraction, client: CrystalClient) { - void await interaction.reply({ + execute: async function (interaction: ChatInputCommandInteraction) { + void (await interaction.reply({ content: "Hey there!", ephemeral: false, - }) + })) }, } satisfies Command diff --git a/src/modules/commands/message.ts b/src/modules/commands/message.ts index 2ca2cce..abec89a 100644 --- a/src/modules/commands/message.ts +++ b/src/modules/commands/message.ts @@ -1,41 +1,37 @@ -import { SlashCommandBuilder, type ChatInputCommandInteraction, type TextChannel } from "discord.js"; -import { type Command } from "../../types/Command"; -import type CrystalClient from "../../types/CrystalClient"; +import { SlashCommandBuilder, type ChatInputCommandInteraction, type TextChannel } from "discord.js" +import { type Command } from "../../types/Command" +import type CrystalClient from "../../types/CrystalClient" module.exports = { - name: 'message', + name: "message", data: new SlashCommandBuilder() .setName("message") .setDescription("Send a message using the bot.") - .addChannelOption((option) => - option - .setName('channel') - .setDescription('The channel to send the message to.') - .setRequired(true)) - .addStringOption((option) => - option - .setName('message') - .setDescription('The message you want to send.') - .setRequired(true)), + .addChannelOption(option => + option.setName("channel").setDescription("The channel to send the message to.").setRequired(true) + ) + .addStringOption(option => + option.setName("message").setDescription("The message you want to send.").setRequired(true) + ), execute: async function (interaction: ChatInputCommandInteraction, client: CrystalClient) { - const textChannel = interaction.options.getChannel('channel') as TextChannel - const botMessage = interaction.options.getString('message') + const textChannel = interaction.options.getChannel("channel") as TextChannel + const botMessage = interaction.options.getString("message") try { if (botMessage !== null) { - void await textChannel.send(botMessage) - void await interaction.reply({ + void (await textChannel.send(botMessage)) + void (await interaction.reply({ content: "Your message was sent.", - ephemeral: true - }) + ephemeral: true, + })) } else { - void await interaction.reply({ + void (await interaction.reply({ content: "Your message could not get sent.", - ephemeral: true - }) + ephemeral: true, + })) } } catch (error) { console.error(error) } - } + }, } satisfies Command diff --git a/src/modules/commands/pin.ts b/src/modules/commands/pin.ts index 2e61b6c..1d78292 100644 --- a/src/modules/commands/pin.ts +++ b/src/modules/commands/pin.ts @@ -1,57 +1,56 @@ -import { SlashCommandBuilder, type ChatInputCommandInteraction, type TextChannel } from "discord.js"; -import { type Command } from "../../types/Command"; -import type CrystalClient from "../../types/CrystalClient"; +import { SlashCommandBuilder, type ChatInputCommandInteraction, type TextChannel } from "discord.js" +import { type Command } from "../../types/Command" +import type CrystalClient from "../../types/CrystalClient" module.exports = { - name: 'pin', + name: "pin", data: new SlashCommandBuilder() .setName("pin") .setDescription("pin a message using the bot.") - .addChannelOption((option) => + .addChannelOption(option => + option.setName("channel").setDescription("The channel you want to pin a message in.").setRequired(true) + ) + .addStringOption(option => option - .setName('channel') - .setDescription('The channel you want to pin a message in.') - .setRequired(true)) - .addStringOption((option) => - option - .setName('message_id') - .setDescription('The message you want to pin to using a message id.') - .setRequired(true)), + .setName("message_id") + .setDescription("The message you want to pin to using a message id.") + .setRequired(true) + ), execute: async function (interaction: ChatInputCommandInteraction, client: CrystalClient) { - const textChannel = interaction.options.getChannel('channel') as TextChannel - const messageId = interaction.options.getString('message_id') + const textChannel = interaction.options.getChannel("channel") as TextChannel + const messageId = interaction.options.getString("message_id") try { if (messageId !== null) { const targetMessage = await textChannel.messages.fetch(messageId) - void await targetMessage.pin() - void await interaction.reply({ + void (await targetMessage.pin()) + void (await interaction.reply({ content: "Successfully pinned the message.", - ephemeral: true - }) + ephemeral: true, + })) } else { - void await interaction.reply({ + void (await interaction.reply({ content: "Unsuccessfully pinned the message.", - ephemeral: true - }) + ephemeral: true, + })) } } catch (error: any) { switch (true) { - case error.message.includes('Unknown Message'): - void await interaction.reply({ + case error.message.includes("Unknown Message"): + void (await interaction.reply({ content: "Unsuccessfully pinned the message: Invalid message id.", - ephemeral: true - }) + ephemeral: true, + })) break default: - void await interaction.reply({ + void (await interaction.reply({ content: "Unsuccessfully pinned the message.", - ephemeral: true - }) + ephemeral: true, + })) console.error(error) } } - } + }, } satisfies Command diff --git a/src/modules/commands/react.ts b/src/modules/commands/react.ts index b35a491..b375f85 100644 --- a/src/modules/commands/react.ts +++ b/src/modules/commands/react.ts @@ -1,73 +1,70 @@ -import { SlashCommandBuilder, type ChatInputCommandInteraction, type TextChannel } from "discord.js"; -import { type Command } from "../../types/Command"; -import type CrystalClient from "../../types/CrystalClient"; +import { SlashCommandBuilder, type ChatInputCommandInteraction, type TextChannel } from "discord.js" +import { type Command } from "../../types/Command" +import type CrystalClient from "../../types/CrystalClient" module.exports = { - name: 'react', + name: "react", data: new SlashCommandBuilder() .setName("react") .setDescription("React to a message using the bot.") - .addChannelOption((option) => + .addChannelOption(option => + option.setName("channel").setDescription("The channel you want to react to.").setRequired(true) + ) + .addStringOption(option => option - .setName('channel') - .setDescription('The channel you want to react to.') - .setRequired(true)) - .addStringOption((option) => - option - .setName('message_id') - .setDescription('The message you want to react to using a message id.') - .setRequired(true)) - .addStringOption((option) => - option - .setName('emojis') - .setDescription('The emoji(s) you want to react with.') - .setRequired(true)), + .setName("message_id") + .setDescription("The message you want to react to using a message id.") + .setRequired(true) + ) + .addStringOption(option => + option.setName("emojis").setDescription("The emoji(s) you want to react with.").setRequired(true) + ), execute: async function (interaction: ChatInputCommandInteraction, client: CrystalClient) { - const textChannel = interaction.options.getChannel('channel') as TextChannel - const messageId = interaction.options.getString('message_id') - const emojis = interaction.options.getString('emojis') + const textChannel = interaction.options.getChannel("channel") as TextChannel + const messageId = interaction.options.getString("message_id") + const emojis = interaction.options.getString("emojis") try { - if ((emojis !== null) && (messageId !== null)) { + if (emojis !== null && messageId !== null) { const targetMessage = await textChannel.messages.fetch(messageId) - const emojiArray = emojis.split(' ') + const emojiArray = emojis.split(" ") for (const emoji of emojiArray) { - void await targetMessage.react(emoji.trim()) + void (await targetMessage.react(emoji.trim())) } - void await interaction.reply({ + void (await interaction.reply({ content: "The reaction was successful.", - ephemeral: true - }) + ephemeral: true, + })) } else { - void await interaction.reply({ + void (await interaction.reply({ content: "The reaction failed.", - ephemeral: true - }) + ephemeral: true, + })) } } catch (error: any) { switch (true) { - case error.message.includes('Unknown Message'): - void await interaction.reply({ + case error.message.includes("Unknown Message"): + void (await interaction.reply({ content: "The reaction failed: Invalid message id.", - ephemeral: true - }) + ephemeral: true, + })) break - case error.message.includes('Unknown Emoji'): - void await interaction.reply({ + case error.message.includes("Unknown Emoji"): + void (await interaction.reply({ content: "The reaction failed: Invalid emoji(s).", - ephemeral: true - }) + ephemeral: true, + })) break default: - void await interaction.reply({ + void (await interaction.reply({ content: "The reaction failed.", - ephemeral: true - }) + ephemeral: true, + })) console.error(error) } } - } + }, } satisfies Command diff --git a/src/modules/commands/reload.ts b/src/modules/commands/reload.ts index 264b310..b009c0a 100644 --- a/src/modules/commands/reload.ts +++ b/src/modules/commands/reload.ts @@ -1,17 +1,23 @@ import { SlashCommandBuilder, type ChatInputCommandInteraction } from "discord.js" import { type Command } from "../../types/Command" -import type CrystalClient from "../../types/CrystalClient" import { loadEvents } from "../utils/eventHandler" +import type CrystalClient from "../../types/CrystalClient" +import { loadCommands } from "../utils/commandHandler" module.exports = { name: "reload", - data: new SlashCommandBuilder() - .setName("reload") - .setDescription("reload the command, channels or events."), + data: new SlashCommandBuilder().setName("reload").setDescription("reload the command, channels or events."), execute: async function (interaction: ChatInputCommandInteraction, client: CrystalClient) { - void await interaction.deferReply({ephemeral: true}) - void await interaction.editReply({content: "Completed the reload."}) - + console.log(` + _____ _ _ _ + | __ |___| |___ ___ _| |_|___ ___ + | -| -_| | . | .'| . | | | . | + |__|__|___|_|___|__,|___|_|_|_|_ | + |___| + `) + void (await interaction.deferReply({ ephemeral: true })) void loadEvents(client) + void loadCommands(client) + void (await interaction.editReply({ content: "Completed the reload." })) }, } satisfies Command diff --git a/src/modules/commands/reply.ts b/src/modules/commands/reply.ts index 0c1935d..1c8cd2c 100644 --- a/src/modules/commands/reply.ts +++ b/src/modules/commands/reply.ts @@ -1,49 +1,46 @@ -import { SlashCommandBuilder, type ChatInputCommandInteraction, type TextChannel } from "discord.js"; -import { type Command } from "../../types/Command"; -import type CrystalClient from "../../types/CrystalClient"; +import { SlashCommandBuilder, type ChatInputCommandInteraction, type TextChannel } from "discord.js" +import { type Command } from "../../types/Command" +import type CrystalClient from "../../types/CrystalClient" module.exports = { - name: 'reply', + name: "reply", data: new SlashCommandBuilder() .setName("reply") .setDescription("Reply to a message using the bot.") - .addChannelOption((option) => + .addChannelOption(option => + option.setName("channel").setDescription("The channel to send the reply to.").setRequired(true) + ) + .addStringOption(option => option - .setName('channel') - .setDescription('The channel to send the reply to.') - .setRequired(true)) - .addStringOption((option) => - option - .setName('message_id') - .setDescription('The message you want to reply to using a message id.') - .setRequired(true)) - .addStringOption((option) => - option - .setName('message') - .setDescription('The message you want to send.') - .setRequired(true)), + .setName("message_id") + .setDescription("The message you want to reply to using a message id.") + .setRequired(true) + ) + .addStringOption(option => + option.setName("message").setDescription("The message you want to send.").setRequired(true) + ), execute: async function (interaction: ChatInputCommandInteraction, client: CrystalClient) { - const textChannel = interaction.options.getChannel('channel') as TextChannel - const messageId = interaction.options.getString('message_id') - const botMessage = interaction.options.getString('message') + const textChannel = interaction.options.getChannel("channel") as TextChannel + const messageId = interaction.options.getString("message_id") + const botMessage = interaction.options.getString("message") try { - if ((botMessage !== null) && (messageId !== null)) { - const targetMessage = await textChannel.messages.fetch(messageId); + if (botMessage !== null && messageId !== null) { + const targetMessage = await textChannel.messages.fetch(messageId) - void await targetMessage.reply(botMessage) - void await interaction.reply({ + void (await targetMessage.reply(botMessage)) + void (await interaction.reply({ content: "Your message was sent.", - ephemeral: true - }) + ephemeral: true, + })) } else { - void await interaction.reply({ + void (await interaction.reply({ content: "Your message could not get sent.", - ephemeral: true - }) + ephemeral: true, + })) } } catch (error) { console.error(error) } - } + }, } satisfies Command diff --git a/src/modules/config.ts b/src/modules/config.ts index 03230c2..09d787f 100644 --- a/src/modules/config.ts +++ b/src/modules/config.ts @@ -6,7 +6,6 @@ export function getConfig(): Config { return { token: `${process.env.TOKEN}`, - prefix: `${process.env.PREFIX}`, admins: process.env.ADMINS != null ? process.env.ADMINS.split(" ") : [], shards: process.env.SHARDS != null ? parseInt(process.env.SHARDS) : 1, postgresConfig: { diff --git a/src/modules/events/interactionCreate.ts b/src/modules/events/interactionCreate.ts index b2136bd..4db756e 100644 --- a/src/modules/events/interactionCreate.ts +++ b/src/modules/events/interactionCreate.ts @@ -6,11 +6,11 @@ module.exports = { name: "interactionCreate", once: false, rest: false, - execute: async function (interaction: ChatInputCommandInteraction, client: CrystalClient) { + execute: async function (interaction: ChatInputCommandInteraction) { if (!interaction.isChatInputCommand()) return const command = CrystalClient.commands.get(interaction.commandName) if (command === null || command === undefined) await interaction.reply({ content: "This command does not exist", ephemeral: true }) - command?.execute(interaction, client) + void command?.execute(interaction, CrystalClient.getClient()) }, } satisfies Event diff --git a/src/modules/events/ready.ts b/src/modules/events/ready.ts index 9d16571..329fd6d 100644 --- a/src/modules/events/ready.ts +++ b/src/modules/events/ready.ts @@ -1,16 +1,16 @@ -import { type Client } from "discord.js" import { type Event } from "../../types/Event" import { loadCommands } from "../utils/commandHandler" import { getChannels } from "../utils/channelRegistrar" +import CrystalClient from "../../types/CrystalClient" module.exports = { name: "ready", once: false, rest: false, - execute: async function (client: Client): Promise { - await loadCommands(client) - await getChannels(client) + execute: async function (client: CrystalClient): Promise { + await loadCommands(CrystalClient.getClient()) + await getChannels(CrystalClient.getClient()) - console.log(`Logged in as ${client.user.tag}`) + console.log(`Logged in as ${client.user?.tag}`) }, } satisfies Event diff --git a/src/types/Config.ts b/src/types/Config.ts index 59af1a9..44fcc93 100644 --- a/src/types/Config.ts +++ b/src/types/Config.ts @@ -1,6 +1,5 @@ export type Config = { token: string - prefix: string admins: string[] shards: number postgresConfig: PostgresConfig diff --git a/src/types/CrystalClient.ts b/src/types/CrystalClient.ts index ed1357e..f05d71b 100644 --- a/src/types/CrystalClient.ts +++ b/src/types/CrystalClient.ts @@ -7,13 +7,19 @@ export default class CrystalClient extends Client { static events: Collection void> static guilds: string[] static channels: GuildBasedChannel[] = [] + private static client: CrystalClient constructor(options: ClientOptions) { super(options) + CrystalClient.client = this CrystalClient.commands = new Collection() CrystalClient.events = new Collection() - void loadEvents(this) + void loadEvents(CrystalClient.client) + } + + static getClient(): CrystalClient { + return CrystalClient.client } } diff --git a/tsconfig.json b/tsconfig.json index 8bb75e9..624ab24 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -47,7 +47,7 @@ // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ // "declarationMap": true, /* Create sourcemaps for d.ts files. */ // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ - // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + "sourceMap": true /* Create source map files for emitted JavaScript files. */, // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ "outDir": "./out" /* Specify an output folder for all emitted files. */,