diff --git a/.gitignore b/.gitignore index dbc0617..501f7b1 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ node_modules dist .turbo .env -!.env.example \ No newline at end of file +.env.* +!.env.example diff --git a/apps/typicalbot/.env.example b/apps/typicalbot/.env.example new file mode 100644 index 0000000..0fe35bd --- /dev/null +++ b/apps/typicalbot/.env.example @@ -0,0 +1,2 @@ +DISCORD_APPLICATION_ID= +DISCORD_TOKEN= diff --git a/apps/typicalbot/package.json b/apps/typicalbot/package.json new file mode 100644 index 0000000..7227def --- /dev/null +++ b/apps/typicalbot/package.json @@ -0,0 +1,24 @@ +{ + "name": "@trpkit/typicalbot", + "version": "0.1.0", + "private": true, + "scripts": { + "build": "tsc && tsc-alias", + "cmd:register": "node --env-file=.env dist/register.js", + "cmd:unregister": "node --env-file=.env dist/unregister.js", + "dev": "pnpm build && node --env-file=.env.dev dist/bot.js", + "dev:cmds": "pnpm build && node --env-file=.env.dev dist/register.js", + "start": "node --env-file=.env dist/index.js" + }, + "dependencies": { + "discord.js": "14.19.2", + "fast-glob": "3.3.3", + "tslib": "2.8.1", + "zod": "3.24.3" + }, + "devDependencies": { + "@types/node": "22.15.3", + "tsc-alias": "1.8.15", + "typescript": "5.8.3" + } +} diff --git a/apps/typicalbot/src/bot.ts b/apps/typicalbot/src/bot.ts new file mode 100644 index 0000000..0df20e2 --- /dev/null +++ b/apps/typicalbot/src/bot.ts @@ -0,0 +1,24 @@ +import { Client, GatewayIntentBits } from "discord.js"; +import { env } from "./env"; +import { loadEvents, registerEvents } from "./lib/EventHandler"; + +const client = new Client({ + intents: [ + GatewayIntentBits.Guilds, + GatewayIntentBits.GuildMembers, + GatewayIntentBits.GuildMessages, + GatewayIntentBits.MessageContent, + ], +}); + +const events = loadEvents(); +registerEvents(client, events); + +(async () => { + try { + await client.login(env.DISCORD_TOKEN); + } catch (e) { + console.error(e); + await client.destroy(); + } +})(); diff --git a/apps/typicalbot/src/commands/moderation/ban.ts b/apps/typicalbot/src/commands/moderation/ban.ts new file mode 100644 index 0000000..9ad4787 --- /dev/null +++ b/apps/typicalbot/src/commands/moderation/ban.ts @@ -0,0 +1,66 @@ +import { createCommand } from "@/lib/CommandHandler"; +import { ApplicationCommandType, MessageFlags } from "discord.js"; + +export default createCommand({ + metadata: { + options: { + name: "ban", + description: "Ban a user from the server", + type: ApplicationCommandType.ChatInput, + options: [ + { + name: "user", + description: "The user to ban", + type: 6, + required: true, + }, + { + name: "reason", + description: "The reason for the ban", + type: 3, + required: false, + }, + ], + }, + }, + chatInput: async (client, interaction) => { + if (!interaction.guild) { + await interaction.reply({ + content: "This command can only be used in a server.", + flags: [MessageFlags.Ephemeral], + }); + return; + } + + const user = interaction.options.getUser("user", true); + const reason = interaction.options.getString("reason") || "No reason provided"; + const member = interaction.guild.members.cache.get(user.id); + + if (!member) { + await interaction.reply({ + content: "That user is not in this server.", + flags: [MessageFlags.Ephemeral], + }); + return; + } + + if (!member.bannable) { + await interaction.reply({ + content: "I cannot ban that user.", + flags: [MessageFlags.Ephemeral], + }); + return; + } + + try { + await member.ban({ reason }); + await interaction.reply(`Banned ${user.tag} for: ${reason}`); + } catch (error) { + console.error(error); + await interaction.reply({ + content: "There was an error trying to ban that user.", + flags: [MessageFlags.Ephemeral], + }); + } + }, +}); diff --git a/apps/typicalbot/src/commands/moderation/kick.ts b/apps/typicalbot/src/commands/moderation/kick.ts new file mode 100644 index 0000000..2fcb2e0 --- /dev/null +++ b/apps/typicalbot/src/commands/moderation/kick.ts @@ -0,0 +1,66 @@ +import { createCommand } from "@/lib/CommandHandler"; +import { ApplicationCommandType, MessageFlags } from "discord.js"; + +export default createCommand({ + metadata: { + options: { + name: "kick", + description: "Kick a user from the server", + type: ApplicationCommandType.ChatInput, + options: [ + { + name: "user", + description: "The user to kick", + type: 6, + required: true, + }, + { + name: "reason", + description: "The reason for the kick", + type: 3, + required: false, + }, + ], + }, + }, + chatInput: async (client, interaction) => { + if (!interaction.guild) { + await interaction.reply({ + content: "This command can only be used in a server.", + flags: [MessageFlags.Ephemeral], + }); + return; + } + + const user = interaction.options.getUser("user", true); + const reason = interaction.options.getString("reason") || "No reason provided"; + const member = interaction.guild.members.cache.get(user.id); + + if (!member) { + await interaction.reply({ + content: "That user is not in this server.", + flags: [MessageFlags.Ephemeral], + }); + return; + } + + if (!member.kickable) { + await interaction.reply({ + content: "I cannot kick that user.", + flags: [MessageFlags.Ephemeral], + }); + return; + } + + try { + await member.kick(reason); + await interaction.reply(`Kicked ${user.tag} for: ${reason}`); + } catch (error) { + console.error(error); + await interaction.reply({ + content: "There was an error trying to kick that user.", + flags: [MessageFlags.Ephemeral], + }); + } + }, +}); diff --git a/apps/typicalbot/src/commands/moderation/timeout.ts b/apps/typicalbot/src/commands/moderation/timeout.ts new file mode 100644 index 0000000..9b2d940 --- /dev/null +++ b/apps/typicalbot/src/commands/moderation/timeout.ts @@ -0,0 +1,73 @@ +import { createCommand } from "@/lib/CommandHandler"; +import { ApplicationCommandType, MessageFlags } from "discord.js"; + +export default createCommand({ + metadata: { + options: { + name: "timeout", + description: "Timeout a user in the server", + type: ApplicationCommandType.ChatInput, + options: [ + { + name: "user", + description: "The user to timeout", + type: 6, + required: true, + }, + { + name: "duration", + description: "The duration of the timeout in minutes", + type: 4, + required: true, + }, + { + name: "reason", + description: "The reason for the timeout", + type: 3, + required: false, + }, + ], + }, + }, + chatInput: async (client, interaction) => { + if (!interaction.guild) { + await interaction.reply({ + content: "This command can only be used in a server.", + flags: [MessageFlags.Ephemeral], + }); + return; + } + + const user = interaction.options.getUser("user", true); + const duration = interaction.options.getInteger("duration", true); + const reason = interaction.options.getString("reason") || "No reason provided"; + const member = interaction.guild.members.cache.get(user.id); + + if (!member) { + await interaction.reply({ + content: "That user is not in this server.", + flags: [MessageFlags.Ephemeral], + }); + return; + } + + if (!member.moderatable) { + await interaction.reply({ + content: "I cannot timeout that user.", + flags: [MessageFlags.Ephemeral], + }); + return; + } + + try { + await member.timeout(duration * 60 * 1000, reason); + await interaction.reply(`Timed out ${user.tag} for ${duration} minutes. Reason: ${reason}`); + } catch (error) { + console.error(error); + await interaction.reply({ + content: "There was an error trying to timeout that user.", + flags: [MessageFlags.Ephemeral], + }); + } + }, +}); diff --git a/apps/typicalbot/src/commands/moderation/unban.ts b/apps/typicalbot/src/commands/moderation/unban.ts new file mode 100644 index 0000000..7a73f9a --- /dev/null +++ b/apps/typicalbot/src/commands/moderation/unban.ts @@ -0,0 +1,52 @@ +import { createCommand } from "@/lib/CommandHandler"; +import { ApplicationCommandType, MessageFlags } from "discord.js"; + +export default createCommand({ + metadata: { + options: { + name: "unban", + description: "Unban a user from the server", + type: ApplicationCommandType.ChatInput, + options: [ + { + name: "user", + description: "The user ID to unban", + type: 3, + required: true, + }, + ], + }, + }, + chatInput: async (client, interaction) => { + if (!interaction.guild) { + await interaction.reply({ + content: "This command can only be used in a server.", + flags: [MessageFlags.Ephemeral], + }); + return; + } + + const userId = interaction.options.getString("user", true); + const bans = await interaction.guild.bans.fetch(); + const bannedUser = bans.find((ban) => ban.user.id === userId); + + if (!bannedUser) { + await interaction.reply({ + content: "That user is not banned from this server.", + flags: [MessageFlags.Ephemeral], + }); + return; + } + + try { + await interaction.guild.members.unban(userId); + await interaction.reply(`Unbanned ${bannedUser.user.tag}`); + } catch (error) { + console.error(error); + await interaction.reply({ + content: "There was an error trying to unban that user.", + flags: [MessageFlags.Ephemeral], + }); + } + }, +}); diff --git a/apps/typicalbot/src/commands/system/ping.ts b/apps/typicalbot/src/commands/system/ping.ts new file mode 100644 index 0000000..6bb8db4 --- /dev/null +++ b/apps/typicalbot/src/commands/system/ping.ts @@ -0,0 +1,22 @@ +import { createCommand } from "@/lib/CommandHandler"; +import { ApplicationCommandType, MessageFlags } from "discord.js"; + +export default createCommand({ + metadata: { + options: { + name: "ping", + description: "Healthcheck", + type: ApplicationCommandType.ChatInput, + }, + }, + chatInput: async (client, interaction) => { + const sent = await interaction.deferReply({ + flags: [MessageFlags.Ephemeral], + withResponse: true, + }); + + await interaction.editReply({ + content: `Pong! Heartbeat: ${interaction.client.ws.ping.toFixed(0)}ms | Roundtrip: ${sent.interaction.createdTimestamp - interaction.createdTimestamp}ms`, + }); + }, +}); diff --git a/apps/typicalbot/src/env.ts b/apps/typicalbot/src/env.ts new file mode 100644 index 0000000..40e3584 --- /dev/null +++ b/apps/typicalbot/src/env.ts @@ -0,0 +1,27 @@ +import { z } from "zod"; + +const envSchema = z.object({ + NODE_ENV: z.enum(["development", "production", "test"]).default("development"), + + // Discord + DISCORD_APPLICATION_ID: z.string(), + DISCORD_TOKEN: z.string(), +}); + +const parsed = envSchema.safeParse(process.env); + +if (!parsed.success) { + console.error( + `Missing or invalid environment variable${parsed.error.errors.length > 1 ? "s" : ""}: +${parsed.error.errors.map((error) => ` ${error.path}: ${error.message}`).join("\n")}` + ); + process.exit(1); +} + +const secretEnvs: Array = ["DISCORD_TOKEN"]; + +for (const secretEnv of secretEnvs) { + delete process.env[secretEnv]; +} + +export const env = Object.freeze(parsed.data); diff --git a/apps/typicalbot/src/events/chatCommands.ts b/apps/typicalbot/src/events/chatCommands.ts new file mode 100644 index 0000000..a8bc042 --- /dev/null +++ b/apps/typicalbot/src/events/chatCommands.ts @@ -0,0 +1,33 @@ +import { loadCommands } from "@/lib/CommandHandler"; +import { createEvent } from "@/lib/EventHandler"; +import { ApplicationCommandType } from "discord.js"; + +const coll = loadCommands(); + +export default createEvent({ + metadata: { + event: "interactionCreate", + }, + handler: async (client, interaction) => { + if (!interaction.isCommand() && !interaction.isAutocomplete()) return; + + const command = coll.get(interaction.commandName.toLowerCase()); + if (!command) return; + + switch (interaction.commandType) { + case ApplicationCommandType.ChatInput: { + const args = Object.fromEntries( + interaction.options.data.map((option) => [option.name, option.value]) + ); + + if (interaction.isAutocomplete() && command.autoComplete) { + await command.autoComplete(client, interaction, args); + } else if (interaction.isCommand()) { + await command.chatInput(client, interaction, args); + } + + break; + } + } + }, +}); diff --git a/apps/typicalbot/src/events/startup.ts b/apps/typicalbot/src/events/startup.ts new file mode 100644 index 0000000..f65b666 --- /dev/null +++ b/apps/typicalbot/src/events/startup.ts @@ -0,0 +1,10 @@ +import { createEvent } from "@/lib/EventHandler"; + +export default createEvent({ + metadata: { + event: "ready", + }, + handler: async (client, readyClient) => { + console.log(`Ready! Logged in as ${readyClient.user.tag}.`); + }, +}); diff --git a/apps/typicalbot/src/index.ts b/apps/typicalbot/src/index.ts new file mode 100644 index 0000000..4de93da --- /dev/null +++ b/apps/typicalbot/src/index.ts @@ -0,0 +1,8 @@ +import { env } from "@/env"; +import { ShardingManager } from "discord.js"; + +const manager = new ShardingManager("./dist/bot.js", { token: env.DISCORD_TOKEN, totalShards: 30 }); + +manager.on("shardCreate", (shard) => console.log(`Launched shard ${shard.id}.`)); + +manager.spawn(); diff --git a/apps/typicalbot/src/lib/CommandHandler.ts b/apps/typicalbot/src/lib/CommandHandler.ts new file mode 100644 index 0000000..6849dc6 --- /dev/null +++ b/apps/typicalbot/src/lib/CommandHandler.ts @@ -0,0 +1,149 @@ +import { + type ApplicationCommand, + type ApplicationCommandData, + type AutocompleteInteraction, + type ChatInputCommandInteraction, + type Client, + Collection, + REST, + Routes, +} from "discord.js"; +import fg from "fast-glob"; + +type CommandMetadata = { + options: ApplicationCommandData; + enabled?: boolean; +}; + +type CommandHandler = { + metadata: CommandMetadata; + chatInput: ( + client: C, + interaction: ChatInputCommandInteraction, + args: Record + ) => Promise | unknown; + autoComplete?: ( + client: C, + interaction: AutocompleteInteraction, + args: Record + ) => Promise | unknown; +}; + +export function createCommand(config: CommandHandler) { + return config; +} + +// biome-ignore lint/suspicious/noExplicitAny: can safely ignore +class CommandCollection extends Collection> { + // biome-ignore lint/suspicious/noExplicitAny: can safely ignore + add(command: CommandHandler) { + this.set(command.metadata.options.name.toLowerCase(), command); + } +} + +export function loadCommands() { + const coll = new CommandCollection(); + + const files = fg.sync("../commands/**/*.js", { cwd: __dirname }); + + for (const file of files) { + const mod = require(file); + const { metadata, chatInput } = mod.default || {}; + + if (!metadata?.options || !chatInput) { + console.warn(`Skipping file: ${file} (missing metadata or chatInput)`); + continue; + } + + const isEnabled = metadata.enabled !== false; + + if (!isEnabled) { + console.log(`Command '${metadata.options.name}' in file '${file}' is disabled.`); + continue; + } + + coll.add(mod.default); + console.log(`Registered command '${metadata.options.name}' from file: ${file}`); + } + + return coll; +} + +export async function registerGlobalCommands( + token: string, + applicationId: string, + commands: ApplicationCommandData[] +) { + const rest = new REST().setToken(token); + + try { + await rest.put(Routes.applicationCommands(applicationId), { + body: commands, + }); + console.log(`Successfully registered ${commands.length} global commands.`); + } catch (e) { + console.error(e); + process.exit(1); + } +} + +export async function registerGuildCommands( + token: string, + applicationId: string, + guildId: string, + commands: ApplicationCommandData[] +) { + const rest = new REST().setToken(token); + + try { + await rest.put(Routes.applicationGuildCommands(applicationId, guildId), { + body: commands, + }); + console.log(`Successfully registered ${commands.length} guild commands.`); + } catch (e) { + console.error(e); + process.exit(1); + } +} + +export async function unregisterGlobalCommands(token: string, applicationId: string) { + const rest = new REST().setToken(token); + + try { + const commands = (await rest.get( + Routes.applicationCommands(applicationId) + )) as ApplicationCommand[]; + + for (const command of commands) { + await rest.delete(Routes.applicationCommand(applicationId, command.id)); + } + + console.log(`Successfully deleted ${commands.length} global commands.`); + } catch (e) { + console.error(e); + process.exit(1); + } +} + +export async function unregisterGuildCommands( + token: string, + applicationId: string, + guildId: string +) { + const rest = new REST().setToken(token); + + try { + const commands = (await rest.get( + Routes.applicationGuildCommands(applicationId, guildId) + )) as ApplicationCommand[]; + + for (const command of commands) { + await rest.delete(Routes.applicationCommand(applicationId, command.id)); + } + + console.log(`Successfully deleted ${commands.length} guild commands.`); + } catch (e) { + console.error(e); + process.exit(1); + } +} diff --git a/apps/typicalbot/src/lib/EventHandler.ts b/apps/typicalbot/src/lib/EventHandler.ts new file mode 100644 index 0000000..6428c07 --- /dev/null +++ b/apps/typicalbot/src/lib/EventHandler.ts @@ -0,0 +1,69 @@ +import { type Client, type ClientEvents, Collection } from "discord.js"; +import fg from "fast-glob"; + +type EventMetadata = { + event: E; + enabled?: boolean; +}; + +type EventHandler = ( + client: C, + ...args: ClientEvents[E] +) => Promise | unknown; + +export function createEvent(config: { + metadata: EventMetadata; + handler: EventHandler; +}) { + return config; +} + +// biome-ignore lint/suspicious/noExplicitAny: can safely ignore +class EventCollection extends Collection[]> { + // biome-ignore lint/suspicious/noExplicitAny: can safely ignore + add(event: keyof ClientEvents, handler: EventHandler) { + const item = this.get(event) || []; + item.push(handler); + this.set(event, item); + } +} + +export function registerEvents(client: C, collection: EventCollection) { + collection.forEach((handlers, event) => { + const method = event === "ready" ? "once" : "on"; + + client[method](event, (...args) => { + for (const handler of handlers) { + handler(client, ...args); + } + }); + }); +} + +export function loadEvents() { + const coll = new EventCollection(); + + const files = fg.sync("../events/**/*.js", { cwd: __dirname }); + + for (const file of files) { + const mod = require(file); + const { metadata, handler } = mod.default || {}; + + if (!metadata?.event || !handler) { + console.warn(`Skipping file: ${file} (missing metadata or handler)`); + continue; + } + + const isEnabled = metadata.enabled !== false; + + if (!isEnabled) { + console.log(`Event '${metadata.event}' in file '${file}' is disabled.`); + continue; + } + + coll.add(metadata.event as keyof ClientEvents, handler); + console.log(`Registered event '${metadata.event}' from file: ${file}`); + } + + return coll; +} diff --git a/apps/typicalbot/src/register.ts b/apps/typicalbot/src/register.ts new file mode 100644 index 0000000..d1eb926 --- /dev/null +++ b/apps/typicalbot/src/register.ts @@ -0,0 +1,12 @@ +import { env } from "@/env"; +import { loadCommands, registerGlobalCommands } from "@/lib/CommandHandler"; + +(async () => { + try { + const commands = loadCommands(); + const options = [...commands.values()].map((cmd) => cmd.metadata.options); + await registerGlobalCommands(env.DISCORD_TOKEN, env.DISCORD_APPLICATION_ID, options); + } catch { + process.exit(1); + } +})(); diff --git a/apps/typicalbot/src/unregister.ts b/apps/typicalbot/src/unregister.ts new file mode 100644 index 0000000..5a18cd9 --- /dev/null +++ b/apps/typicalbot/src/unregister.ts @@ -0,0 +1,10 @@ +import { env } from "@/env"; +import { unregisterGlobalCommands } from "@/lib/CommandHandler"; + +(async () => { + try { + await unregisterGlobalCommands(env.DISCORD_TOKEN, env.DISCORD_APPLICATION_ID); + } catch { + process.exit(1); + } +})(); diff --git a/apps/typicalbot/tsconfig.json b/apps/typicalbot/tsconfig.json new file mode 100644 index 0000000..9af8584 --- /dev/null +++ b/apps/typicalbot/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "commonjs", + "lib": ["ES2022"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": false, + "outDir": "dist", + "rootDir": "src", + "baseUrl": ".", + "paths": { + "@/*": ["src/*"] + } + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/package.json b/package.json index c09903b..2906349 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "scripts": { "build": "turbo run build", "dev:idlebiz": "turbo run dev -F=@trpkit/idlebiz...", + "dev:typicalbot": "turbo run dev -F=@trpkit/typicalbot...", "format": "biome check --write .", "lint-staged": "lint-staged", "preinstall": "npx only-allow pnpm", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b76ecef..d8651cf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,7 +13,7 @@ importers: version: 1.9.4 '@commitlint/cli': specifier: ^19.8.0 - version: 19.8.0(@types/node@22.15.2)(typescript@5.8.3) + version: 19.8.0(@types/node@22.15.3)(typescript@5.8.3) '@commitlint/config-conventional': specifier: ^19.8.0 version: 19.8.0 @@ -55,6 +55,31 @@ importers: specifier: ^5.8.3 version: 5.8.3 + apps/typicalbot: + dependencies: + discord.js: + specifier: 14.19.2 + version: 14.19.2 + fast-glob: + specifier: 3.3.3 + version: 3.3.3 + tslib: + specifier: 2.8.1 + version: 2.8.1 + zod: + specifier: 3.24.3 + version: 3.24.3 + devDependencies: + '@types/node': + specifier: 22.15.3 + version: 22.15.3 + tsc-alias: + specifier: 1.8.15 + version: 1.8.15 + typescript: + specifier: 5.8.3 + version: 5.8.3 + packages: '@babel/code-frame@7.26.2': @@ -252,6 +277,9 @@ packages: '@types/node@22.15.2': resolution: {integrity: sha512-uKXqKN9beGoMdBfcaTY1ecwz6ctxuJAcUlwE55938g0ZJ8lRxwAZqRz2AJ4pzpt5dHdTPMB863UZ0ESiFUcP7A==} + '@types/node@22.15.3': + resolution: {integrity: sha512-lX7HFZeHf4QG/J7tBZqrCAXwz9J5RD56Y6MpP0eJkka8p+K0RY/yBTW7CYFJ4VGCclxqOLKmiGP5juQc6MKgcw==} + '@types/webidl-conversions@7.0.3': resolution: {integrity: sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==} @@ -427,6 +455,10 @@ packages: resolution: {integrity: sha512-r5jsPyaeoCrRGbdse4vQNbHAsoc2zuueyiTFJ2Ce7BiaJak9OldzKZWaWGwKdCFDH3zXlthU1hHXkx1EswKZCA==} engines: {node: '>=18'} + discord.js@14.19.2: + resolution: {integrity: sha512-L/ivhVefzzRcChHJSaGYsgA4Uqx6or2sst5JZ/ft9OBwrj8OJIzrrcutlkHnm/hlI0Hrm3es62TRVksU8VUqrg==} + engines: {node: '>=18'} + dot-prop@5.3.0: resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} engines: {node: '>=8'} @@ -1134,11 +1166,11 @@ snapshots: '@biomejs/cli-win32-x64@1.9.4': optional: true - '@commitlint/cli@19.8.0(@types/node@22.15.2)(typescript@5.8.3)': + '@commitlint/cli@19.8.0(@types/node@22.15.3)(typescript@5.8.3)': dependencies: '@commitlint/format': 19.8.0 '@commitlint/lint': 19.8.0 - '@commitlint/load': 19.8.0(@types/node@22.15.2)(typescript@5.8.3) + '@commitlint/load': 19.8.0(@types/node@22.15.3)(typescript@5.8.3) '@commitlint/read': 19.8.0 '@commitlint/types': 19.8.0 tinyexec: 0.3.2 @@ -1185,7 +1217,7 @@ snapshots: '@commitlint/rules': 19.8.0 '@commitlint/types': 19.8.0 - '@commitlint/load@19.8.0(@types/node@22.15.2)(typescript@5.8.3)': + '@commitlint/load@19.8.0(@types/node@22.15.3)(typescript@5.8.3)': dependencies: '@commitlint/config-validator': 19.8.0 '@commitlint/execute-rule': 19.8.0 @@ -1193,7 +1225,7 @@ snapshots: '@commitlint/types': 19.8.0 chalk: 5.4.1 cosmiconfig: 9.0.0(typescript@5.8.3) - cosmiconfig-typescript-loader: 6.1.0(@types/node@22.15.2)(cosmiconfig@9.0.0(typescript@5.8.3))(typescript@5.8.3) + cosmiconfig-typescript-loader: 6.1.0(@types/node@22.15.3)(cosmiconfig@9.0.0(typescript@5.8.3))(typescript@5.8.3) lodash.isplainobject: 4.0.6 lodash.merge: 4.6.2 lodash.uniq: 4.5.0 @@ -1327,12 +1359,16 @@ snapshots: '@types/conventional-commits-parser@5.0.1': dependencies: - '@types/node': 22.15.2 + '@types/node': 22.15.3 '@types/node@22.15.2': dependencies: undici-types: 6.21.0 + '@types/node@22.15.3': + dependencies: + undici-types: 6.21.0 + '@types/webidl-conversions@7.0.3': {} '@types/whatwg-url@11.0.5': @@ -1341,7 +1377,7 @@ snapshots: '@types/ws@8.18.1': dependencies: - '@types/node': 22.15.2 + '@types/node': 22.15.3 '@vladfrangu/async_event_emitter@2.4.6': {} @@ -1459,9 +1495,9 @@ snapshots: meow: 12.1.1 split2: 4.2.0 - cosmiconfig-typescript-loader@6.1.0(@types/node@22.15.2)(cosmiconfig@9.0.0(typescript@5.8.3))(typescript@5.8.3): + cosmiconfig-typescript-loader@6.1.0(@types/node@22.15.3)(cosmiconfig@9.0.0(typescript@5.8.3))(typescript@5.8.3): dependencies: - '@types/node': 22.15.2 + '@types/node': 22.15.3 cosmiconfig: 9.0.0(typescript@5.8.3) jiti: 2.4.2 typescript: 5.8.3 @@ -1512,6 +1548,25 @@ snapshots: - bufferutil - utf-8-validate + discord.js@14.19.2: + dependencies: + '@discordjs/builders': 1.11.1 + '@discordjs/collection': 1.5.3 + '@discordjs/formatters': 0.6.1 + '@discordjs/rest': 2.5.0 + '@discordjs/util': 1.1.1 + '@discordjs/ws': 1.2.2 + '@sapphire/snowflake': 3.5.3 + discord-api-types: 0.38.1 + fast-deep-equal: 3.1.3 + lodash.snakecase: 4.1.1 + magic-bytes.js: 1.12.0 + tslib: 2.8.1 + undici: 6.21.1 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + dot-prop@5.3.0: dependencies: is-obj: 2.0.0