all repos — stealth-developers @ 3cce197b87dde34e731a376deec555215cbd0bb1

bot: button equivalents for all appropriate implemented commands
vi did:web:vt3e.cat
Sat, 27 Jun 2026 02:18:22 +0100
commit

3cce197b87dde34e731a376deec555215cbd0bb1

parent

2c1d94cc8c22d2f411f9b6449232111101a7113c

M .zed/settings.json.zed/settings.json

@@ -1,10 +1,11 @@

{ + "language_servers": ["vtsls", "oxlint", "oxfmt"], "lsp": { "oxlint": { "initialization_options": { "settings": { "configPath": null, - "run": "onType", + "run": "onSave", "disableNestedConfig": false, "fixKind": "safe_fix", "unusedDisableDirectives": "deny",

@@ -15,7 +16,7 @@ "oxfmt": {

"initialization_options": { "settings": { "fmt.configPath": null, - "run": "onSave", + "run": "onType", }, }, },
M apps/bot/package.jsonapps/bot/package.json

@@ -7,23 +7,23 @@ "dev": "bun src/index.ts | pino-pretty",

"run": "bun src/index.ts" }, "dependencies": { - "@purrkit/router": "^1.1.0", - "@sapphire/decorators": "^6.1.1", + "@purrkit/router": "^1.3.0", + "@sapphire/decorators": "^6.2.0", "@sapphire/discord.js-utilities": "7.3.2", - "@sapphire/framework": "^5.3.2", - "@sapphire/plugin-logger": "^4.0.2", + "@sapphire/framework": "^5.5.0", + "@sapphire/plugin-logger": "^4.1.0", "@sapphire/plugin-subcommands": "^7.0.1", - "@sapphire/utilities": "^3.18.1", + "@sapphire/utilities": "^3.18.2", "@stealth-developers/config": "workspace:*", "@stealth-developers/db": "workspace:*", - "discord.js": "^14.17.3", + "discord.js": "^14.26.4", "pino": "^10.3.1", "pino-pretty": "^13.1.3" }, "devDependencies": { "@sapphire/cli": "^1.9.3", - "@types/node": "^22.10.7", - "@types/ws": "^8.5.13", + "@types/node": "^22.20.0", + "@types/ws": "^8.18.1", "typescript": "~5.4.5" } }
M apps/bot/src/feats/tickets/README.mdapps/bot/src/feats/tickets/README.md

@@ -4,9 +4,9 @@ - [x] for

- [x] view [ticketId] - [ ] actions - [x] close + - [x] reopen - [x] claim - [x] unclaim - - [x] reopen - [x] comment [ticketId?] - [x] edit [ticketId?] - [ ] clean - /ticket delete
M apps/bot/src/feats/tickets/actions.tsapps/bot/src/feats/tickets/actions.ts

@@ -123,8 +123,8 @@ }),

) .addActionRowComponents( new ActionRowBuilder<ButtonBuilder>().addComponents( - TicketButtonPresets.claim(publicId), - TicketButtonPresets.close(publicId), + TicketButtonPresets.claimTicketButton(publicId), + TicketButtonPresets.closeTicketButton(publicId), ), ), );

@@ -160,7 +160,7 @@ }),

) .addActionRowComponents( new ActionRowBuilder<ButtonBuilder>().addComponents( - TicketButtonPresets.upload(publicId), + TicketButtonPresets.uploadLink(publicId), ), ), );

@@ -246,10 +246,12 @@

container.addTextDisplayComponents(text(`-# ${ticket.id}`)); const actionRow = new ActionRowBuilder<ButtonBuilder>(); - if (limitedDetails) actionRow.addComponents(TicketButtonPresets.thank(ticket.id)); + if (limitedDetails) actionRow.addComponents(TicketButtonPresets.thankButton(ticket.id)); if (!limitedDetails) - actionRow.addComponents(TicketButtonPresets.comments(ticket.id, ticket.comments.length)); - actionRow.addComponents(TicketButtonPresets.transcript(ticket.id)); + actionRow.addComponents( + TicketButtonPresets.viewCommentsButton(ticket.id, ticket.comments.length), + ); + actionRow.addComponents(TicketButtonPresets.transcriptsLink(ticket.id)); container.addActionRowComponents(actionRow); return container;

@@ -302,8 +304,16 @@ } else {

channel.edit({ name: channelName(result.localId, result.status), }); + + const actionRow = new ActionRowBuilder<ButtonBuilder>().addComponents( + TicketButtonPresets.reopenTicketButton(ticketId), + TicketButtonPresets.cleanTicketButton(ticketId), + ); + messageChannel(channel, { content: `This ticket has been closed by <@${user.discordId}>.`, + components: [actionRow], + flags: ["SuppressNotifications"], }); }

@@ -312,7 +322,7 @@ if (!botActor) throw new PublicError("Failed to close ticket: Bot actor not found in database");

const subject = await client.users.fetch(dbSubject.discordId); - const container = await getTicketContainer(result, true); + const container = getTicketContainer(result, true); const messageRes = await Result.fromAsync( messageUser(subject, {

@@ -341,7 +351,7 @@ const container = getTicketContainer(result, false);

const message = await Result.fromAsync( messageChannel(logChannel, { components: [container], - flags: ["IsComponentsV2"], + flags: ["SuppressNotifications", "IsComponentsV2"], }), );

@@ -375,9 +385,15 @@ lastClaimantActivityAt: new Date(),

}); if (ticketChannel) { + const actionRow = new ActionRowBuilder<ButtonBuilder>().addComponents( + TicketButtonPresets.claimTicketButton(ticket.id), + TicketButtonPresets.closeTicketButton(ticket.id), + ); + messageChannel(ticketChannel, { content: `<@${actor.discordId}> reopened this ticket.`, flags: ["SuppressNotifications"], + components: [actionRow], }); if (ticketChannel.type === ChannelType.GuildText) {

@@ -416,9 +432,14 @@ lastClaimantActivityAt: new Date(),

}); if (ticketChannel) { + const actionRow = new ActionRowBuilder<ButtonBuilder>().addComponents( + TicketButtonPresets.unclaimTicketButton(ticket.id), + ); + messageChannel(ticketChannel, { content: `<@${actor.discordId}> has claimed this ticket, they will be with you shortly.`, flags: ["SuppressNotifications"], + components: [actionRow], }); if (ticketChannel.type === ChannelType.GuildText) {

@@ -464,9 +485,13 @@ lastClaimantActivityAt: null,

}); if (ticketChannel) { + const actionRow = new ActionRowBuilder<ButtonBuilder>().addComponents( + TicketButtonPresets.claimTicketButton(ticket.id), + ); messageChannel(ticketChannel, { content: `<@${actor.discordId}> unclaimed this ticket, someone else will be with you shortly.`, flags: ["SuppressNotifications"], + components: [actionRow], }); if (ticketChannel.type === ChannelType.GuildText) {
M apps/bot/src/feats/tickets/buttons.tsapps/bot/src/feats/tickets/buttons.ts

@@ -3,10 +3,40 @@ import { Result } from "@sapphire/framework";

import { option } from "@purrkit/router"; import { kitten } from "@/client"; -import { errorMessage, PublicError } from "@/lib"; +import { errorMessage, handleError, PublicError } from "@/lib"; import * as actions from "./actions"; +import { useGetTicketData } from "@/middleware"; +import { TicketModalPresets } from "./modals"; -export const closeTicketButton = kitten.button("b-close-ticket", { +export const newTicketButton = kitten.button("b-open-ticket", { + run: async (interaction) => { + if (!interaction.guild) + return errorMessage(interaction, "You must run this command in a guild"); + + const func = actions.createTicket(interaction.member ?? interaction.user, interaction.guild); + + const ticketResult = await Result.fromAsync(async () => func); + if (ticketResult.isErr()) { + const error = ticketResult.unwrapErr(); + + if (error instanceof PublicError) { + return errorMessage(interaction, error.message); + } else { + console.error(error); + return errorMessage(interaction, "An unexpected error occurred while creating the ticket."); + } + } + + const { channelId } = ticketResult.unwrap(); + return interaction.reply({ + content: `Opened ticket <#${channelId}>.`, + flags: ["Ephemeral"], + }); + }, +}); + +// +export const closeTicketButton = useGetTicketData.button("b-close-ticket", { options: { ticketId: option.string({ required: true }), },

@@ -24,119 +54,222 @@ await interaction.showModal(modal);

}, }); -export const deleteTicketButton = kitten.button("delete-ticket", { +export const reopenTicketButton = useGetTicketData.button("b-reopen-ticket", { options: { ticketId: option.string({ required: true }), }, - async run() {}, + async run(interaction, _, { actor, ticket, ticketChannel, guild }) { + if (!ticket || !ticketChannel || !actor || !guild) + return errorMessage(interaction, "Failed to find ticket"); + + const result = await Result.fromAsync( + actions.reopenTicket({ ticket, ticketChannel, actor, guild }), + ); + if (result.isErr()) return handleError(interaction, result.unwrapErr()); + + await interaction.reply({ + content: `Ticket has been reopened.`, + flags: ["Ephemeral"], + }); + }, }); -export const claimTicketButton = kitten.button("claim-ticket", { +// +export const claimTicketButton = useGetTicketData.button("b-claim-ticket", { options: { ticketId: option.string({ required: true }), }, - async run() {}, + async run(interaction, _, ctx) { + const { actor, guild, ticket, ticketChannel, user } = ctx; + if (!actor || !guild || !ticket || !ticketChannel || !user) { + await errorMessage(interaction, "Invalid ticket"); + return; + } + + const result = await Result.fromAsync( + actions.claimTicket({ ticket, ticketChannel, actor, guild }), + ); + if (result.isErr()) return handleError(interaction, result.unwrapErr()); + + await interaction.reply({ + content: "Ticket claimed!", + flags: ["Ephemeral"], + }); + }, }); -export const unclaimTicketButton = kitten.button("unclaim-ticket", { +export const unclaimTicketButton = useGetTicketData.button("b-unclaim-ticket", { options: { ticketId: option.string({ required: true }), }, - async run() {}, + async run(interaction, _, { actor, ticket, ticketChannel, guild }) { + if (!ticket || !ticketChannel || !actor || !guild) + return errorMessage(interaction, "Failed to find ticket"); + + const result = await Result.fromAsync( + actions.unclaimTicket({ ticket, ticketChannel, actor, guild }), + ); + if (result.isErr()) return handleError(interaction, result.unwrapErr()); + + await interaction.reply({ + content: `You have unclaimed ticket #${ticket.localId}.`, + flags: ["Ephemeral"], + }); + }, }); -export const thankButton = kitten.button("thank", { +// +export const commentOnTicketButton = useGetTicketData.button("b-comment-ticket", { options: { ticketId: option.string({ required: true }), }, - async run() {}, + async run(interaction, _, { actor, ticket, ticketChannel, guild }) { + if (!ticket || !ticketChannel || !actor || !guild) + return errorMessage(interaction, "Failed to find ticket"); + + if (!actor.moderator) + return errorMessage(interaction, "You must be a moderator to use this command"); + + await interaction.showModal(TicketModalPresets.comment(ticket.id)); + }, }); -export const commentsButton = kitten.button("comments", { +export const viewCommentsButton = kitten.button("b-comment-ticket", { options: { ticketId: option.string({ required: true }), }, async run() {}, }); -export const newTicketButton = kitten.button("open-ticket", { - run: async (interaction) => { - if (!interaction.guild) - return errorMessage(interaction, "You must run this command in a guild"); +// +export const editTicketButton = useGetTicketData.button("b-edit-ticket", { + options: { + ticketId: option.string({ required: true }), + }, + async run(interaction, _, { actor, ticket }) { + if (!actor) { + return errorMessage(interaction, "This command can only be used in a server."); + } else if (!actor.moderator) { + return errorMessage(interaction, "You do not have permission to use this command."); + } else if (!ticket) { + return errorMessage(interaction, "Ticket not found."); + } - const func = actions.createTicket(interaction.member ?? interaction.user, interaction.guild); + await interaction.showModal( + TicketModalPresets.edit(ticket.id, { + public: ticket.closeReason, + private: ticket.privateReason, + }), + ); + }, +}); - const ticketResult = await Result.fromAsync(async () => func); - if (ticketResult.isErr()) { - const error = ticketResult.unwrapErr(); +export const cleanTicketButton = kitten.button("b-delete-ticket", { + options: { + ticketId: option.string({ required: true }), + }, + async run() {}, +}); - if (error instanceof PublicError) { - return errorMessage(interaction, error.message); - } else { - console.error(error); - return errorMessage(interaction, "An unexpected error occurred while creating the ticket."); - } - } - - const { channelId } = ticketResult.unwrap(); - return interaction.reply({ - content: `Opened ticket <#${channelId}>.`, - flags: ["Ephemeral"], - }); +export const thankButton = kitten.button("b-thank-ticket", { + options: { + ticketId: option.string({ required: true }), }, + async run() {}, }); -export const TicketButtons = [ +export const TicketButtonsMap = { newTicketButton, closeTicketButton, - deleteTicketButton, + reopenTicketButton, claimTicketButton, + unclaimTicketButton, + commentOnTicketButton, + viewCommentsButton, + editTicketButton, + cleanTicketButton, thankButton, - commentsButton, -]; +} as const; +export const TicketButtons = Object.values(TicketButtonsMap); +export type TicketButtonKeys = keyof typeof TicketButtonsMap | "transcriptsLink" | "uploadLink"; + +export const TicketButtonPresets: Record<TicketButtonKeys, (...args: any[]) => ButtonBuilder> = { + newTicketButton: () => + new ButtonBuilder() + .setLabel("Open a Ticket") + .setStyle(ButtonStyle.Success) + .setCustomId(newTicketButton.id()), -export const TicketButtonPresets = { - close: (id: string) => + // + closeTicketButton: (id: string) => new ButtonBuilder() .setCustomId(closeTicketButton.id({ ticketId: id })) .setLabel("Close Ticket") .setStyle(ButtonStyle.Danger), - delete: (id: string) => + reopenTicketButton: (id: string) => new ButtonBuilder() - .setCustomId(deleteTicketButton.id({ ticketId: id })) - .setLabel("Delete Ticket") - .setStyle(ButtonStyle.Danger), - claim: (id: string) => + .setCustomId(reopenTicketButton.id({ ticketId: id })) + .setLabel("Reopen Ticket") + .setStyle(ButtonStyle.Primary), + + // + claimTicketButton: (id: string) => new ButtonBuilder() .setCustomId(claimTicketButton.id({ ticketId: id })) .setLabel("Claim Ticket") .setStyle(ButtonStyle.Primary), - thank: (id: string) => + unclaimTicketButton: (id: string) => + new ButtonBuilder() + .setCustomId(unclaimTicketButton.id({ ticketId: id })) + .setLabel("Unclaim Ticket") + .setStyle(ButtonStyle.Secondary), + + // + commentOnTicketButton: (id: string) => new ButtonBuilder() - .setCustomId(thankButton.id({ ticketId: id })) - .setLabel("Thank your moderator") - .setEmoji("💖") - .setStyle(ButtonStyle.Success), - comments: (id: string, quantity: number) => + .setCustomId(commentOnTicketButton.id({ ticketId: id })) + .setLabel(`Add Comment`) + .setStyle(ButtonStyle.Secondary), + + viewCommentsButton: (id: string, quantity: number) => new ButtonBuilder() - .setCustomId(commentsButton.id({ ticketId: id })) + .setCustomId(viewCommentsButton.id({ ticketId: id })) .setLabel(`View Comments (≥${quantity})`) .setStyle(ButtonStyle.Secondary), - transcript: (id: string) => + + // + editTicketButton: (id: string) => + new ButtonBuilder() + .setCustomId(editTicketButton.id({ ticketId: id })) + .setLabel("Edit Ticket") + .setStyle(ButtonStyle.Primary), + + // + cleanTicketButton: (id: string) => + new ButtonBuilder() + .setCustomId(cleanTicketButton.id({ ticketId: id })) + .setLabel("Delete Channel") + .setStyle(ButtonStyle.Danger), + + // + transcriptsLink: (id: string) => new ButtonBuilder() .setLabel("View on Web") .setURL(`https://tickets.vt3e.cat/tickets/${id}`) .setStyle(ButtonStyle.Link), - create: () => - new ButtonBuilder() - .setLabel("Open a Ticket") - .setStyle(ButtonStyle.Success) - .setCustomId(newTicketButton.id()), - upload: (id: string) => + + uploadLink: (id: string) => new ButtonBuilder() .setLabel("Upload a video") .setStyle(ButtonStyle.Link) .setURL(`https://tickets.vt3e.cat/upload/${id}`), + + thankButton: (id: string) => + new ButtonBuilder() + .setCustomId(thankButton.id({ ticketId: id })) + .setLabel("Thank your moderator") + .setEmoji("💖") + .setStyle(ButtonStyle.Success), }; export default TicketButtonPresets;
M apps/bot/src/feats/tickets/commands.tsapps/bot/src/feats/tickets/commands.ts

@@ -1,37 +1,16 @@

import { option } from "@purrkit/router"; import { Result } from "@sapphire/result"; import { ButtonBuilder, ContainerBuilder, TextDisplayBuilder, ActionRowBuilder } from "discord.js"; -import { getTicketByChannelId, getTicketByGuild, getTicketByUser } from "@stealth-developers/db"; +import { getTicketByGuild, getTicketByUser } from "@stealth-developers/db"; import { errorMessage, getTextChannel, handleError, logger, PublicError } from "@/lib"; import { hasAccessToTicket } from "@/lib/tickets"; -import { useGetData } from "@/middleware"; +import { useGetTicketData } from "@/middleware"; import { TicketButtonPresets } from "./buttons"; import * as actions from "./actions"; import { manageConfig } from "./config"; import { TicketModalPresets } from "./modals"; - -const useGetTicketData = useGetData.use(async (interaction, { guild }) => { - if (interaction.isChatInputCommand()) { - const ticketId = interaction.options.getNumber("ticket", false); - if (ticketId && guild) { - const ticket = await getTicketByGuild(guild.id, ticketId); - if (!ticket) return {}; - return { ticket }; - } - } - - if (!interaction.channel) return {}; - - const ticket = await getTicketByChannelId(interaction.channel.id); - if (!ticket) return {}; - - if (!ticket.channelId) return { ticket }; - const ticketChannel = await getTextChannel(ticket.channelId, true); - - return { ticket, ticketChannel }; -}); const ticketCommand = useGetTicketData.command("ticket", { description: "Ticket-related commands",

@@ -368,7 +347,9 @@

const container = new ContainerBuilder() .addTextDisplayComponents(new TextDisplayBuilder().setContent(promptText)) .addActionRowComponents( - new ActionRowBuilder<ButtonBuilder>().addComponents(TicketButtonPresets.create()), + new ActionRowBuilder<ButtonBuilder>().addComponents( + TicketButtonPresets.newTicketButton(), + ), ); await channel.send({ flags: ["IsComponentsV2"], components: [container] });
M apps/bot/src/lib/error.tsapps/bot/src/lib/error.ts

@@ -35,6 +35,7 @@ interaction: Interaction,

error: unknown, fallbackMessage?: string, ) { + console.log("error", error); const context = getInteractionContext(interaction); if (error instanceof PublicError) return await errorMessage(interaction, error.message);
M apps/bot/src/lib/interactions.tsapps/bot/src/lib/interactions.ts

@@ -4,7 +4,11 @@ type Flags = InteractionReplyOptions["flags"];

export function errorMessage(interaction: BaseInteraction, message: string, ephemeral = true) { const flags: Flags = ephemeral ? ["Ephemeral"] : []; - if (!interaction.isCommand()) return; + if (interaction.isButton()) { + return interaction.reply({ content: message, flags }); + } else if (!interaction.isCommand()) { + return; + } if (interaction.deferred) return interaction.editReply({ content: message }); if (!interaction.replied) return interaction.reply({ content: message, flags });
M apps/bot/src/middleware/index.tsapps/bot/src/middleware/index.ts

@@ -1,7 +1,12 @@

import { HaltExecution } from "@purrkit/router"; import { kitten } from "@/client"; -import { upsertGuild } from "@stealth-developers/db"; -import { upsertUser } from "@/lib"; +import { + getTicketByChannelId, + getTicketByGuild, + getTicketById, + upsertGuild, +} from "@stealth-developers/db"; +import { getTextChannel, upsertUser } from "@/lib"; const base = kitten.builder();

@@ -36,3 +41,41 @@ throw new HaltExecution("Failed to find your guild in the database. Please try again.");

return { guild }; }); + +export const useGetTicketData = useGetData.use(async (interaction, { guild }) => { + if (interaction.isChatInputCommand()) { + const ticketId = interaction.options.getNumber("ticket", false); + if (ticketId && guild) { + const ticket = await getTicketByGuild(guild.id, ticketId); + if (!ticket) return {}; + return { ticket }; + } + } + + if (interaction.isButton()) { + const customId = interaction.customId; + if (customId.includes("ticket:")) { + const ticketId = customId.split(":")[1]; + if (ticketId) { + const ticket = await getTicketById(ticketId); + if (!ticket) return {}; + + if (!ticket.channelId) return { ticket }; + const ticketChannel = await getTextChannel(ticket.channelId, true); + if (!ticketChannel) return { ticket }; + + return { ticket, ticketChannel }; + } + } + } + + if (!interaction.channel) return {}; + + const ticket = await getTicketByChannelId(interaction.channel.id); + if (!ticket) return {}; + + if (!ticket.channelId) return { ticket }; + const ticketChannel = await getTextChannel(ticket.channelId, true); + + return { ticket, ticketChannel }; +});
M bun.lockbun.lock

@@ -320,7 +320,7 @@ "@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="],

"@types/bun": ["@types/bun@1.3.14", "", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="], - "@types/node": ["@types/node@22.19.19", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew=="], + "@types/node": ["@types/node@22.20.0", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-QWlFW2wf3nTjC13/DqRnBpR4ZO36VJH/JVBkA/vcnmbTBNQIlnObqyqZE1tUR7+Ni23Lda8R1BxMfbXRpCUx5g=="], "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="],

@@ -354,7 +354,7 @@ "drizzle-kit": ["drizzle-kit@1.0.0-rc.3", "", { "dependencies": { "@drizzle-team/brocli": "^0.11.0", "@js-temporal/polyfill": "^0.5.1", "esbuild": "^0.25.10", "get-tsconfig": "^4.13.6", "jiti": "^2.6.1" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-lwGIi6GTlEYzrtac1Szecns+zrAxjaOoNysfBCKRQOXQE7nMJ8IU2I2S6ek3FHEm1N30dQb5YrTdqV7zNH6n7A=="],

"drizzle-orm": ["drizzle-orm@1.0.0-rc.3", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@effect/sql-pg": ">=4.0.0-beta.58 || >=4.0.0", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@sinclair/typebox": ">=0.34.8", "@sqlitecloud/drivers": ">=1.0.653", "@tidbcloud/serverless": "*", "@tursodatabase/database": ">=0.2.1", "@tursodatabase/database-common": ">=0.2.1", "@tursodatabase/database-wasm": ">=0.2.1", "@types/better-sqlite3": "*", "@types/mssql": "^9.1.4", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "arktype": ">=2.0.0", "better-sqlite3": ">=9.3.0", "bun-types": "*", "effect": ">=4.0.0-beta.58 || >=4.0.0", "expo-sqlite": ">=14.0.0", "mssql": "^11.0.1", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5", "typebox": ">=1.0.0", "valibot": ">=1.0.0-beta.7", "zod": "^3.25.0 || ^4.0.0" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@effect/sql-pg", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@sinclair/typebox", "@sqlitecloud/drivers", "@tidbcloud/serverless", "@tursodatabase/database", "@tursodatabase/database-common", "@tursodatabase/database-wasm", "@types/better-sqlite3", "@types/mssql", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "arktype", "better-sqlite3", "bun-types", "effect", "expo-sqlite", "mssql", "mysql2", "pg", "postgres", "sql.js", "sqlite3", "typebox", "valibot", "zod"] }, "sha512-akZOa5UxapFbdBG8IDkfBRpSZJpMHaOJtGgp7oi1oHaiU8S3KN92waHo2l5aRuv1D9tMGYpv3BQFOsiGcNjLTQ=="], - "elysia": ["elysia@1.4.28", "", { "dependencies": { "cookie": "^1.1.1", "exact-mirror": "^0.2.7", "fast-decode-uri-component": "^1.0.1", "memoirist": "^0.4.0" }, "peerDependencies": { "@sinclair/typebox": ">= 0.34.0 < 1", "@types/bun": ">= 1.2.0", "file-type": ">= 20.0.0", "openapi-types": ">= 12.0.0", "typescript": ">= 5.0.0" }, "optionalPeers": ["@types/bun", "typescript"] }, "sha512-Vrx8sBnvq8squS/3yNBzR1jBXI+SgmnmvwawPjNuEHndUe5l1jV2Gp6JJ4ulDkEB8On6bWmmuyPpA+bq4t+WYg=="], + "elysia": ["elysia@1.4.29", "", { "dependencies": { "cookie": "^1.1.1", "exact-mirror": "^0.2.7", "fast-decode-uri-component": "^1.0.1", "memoirist": "^0.4.0" }, "peerDependencies": { "@sinclair/typebox": ">= 0.34.0 < 1", "@types/bun": ">= 1.2.0", "file-type": ">= 20.0.0", "openapi-types": ">= 12.0.0", "typescript": ">= 5.0.0" }, "optionalPeers": ["@types/bun", "typescript"] }, "sha512-GwMRGGwSdjfPt+w3LA0fqTuYJtS8uVRJicvoar98/HrO5qdFKDc9CwjIb6Kja+v39lkY+58hr2JvdR9jQzlUuA=="], "end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="],

@@ -527,6 +527,8 @@

"@sapphire/framework/@sapphire/discord.js-utilities": ["@sapphire/discord.js-utilities@7.3.3", "", { "dependencies": { "@sapphire/discord-utilities": "^3.5.0", "@sapphire/duration": "^1.2.0", "@sapphire/utilities": "^3.18.2", "tslib": "^2.8.1" } }, "sha512-WDj+zjWgNCUSvzYDD0wY3TVeTUseHq0Nhk0wVWxSDjY8z2gFEVcpY7wF8/fbTDWP44LUG5sUQ4haIrIj2OjmkQ=="], "@sapphire/pieces/@discordjs/collection": ["@discordjs/collection@2.1.1", "", {}, "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg=="], + + "@stealth-developers/bot/@purrkit/router": ["@purrkit/router@1.3.0", "", { "peerDependencies": { "discord.js": "^14.26.4", "typescript": "^5" } }, "sha512-exP3od2+Stw86Z8sA/8Oetk/u/cNNU7zu2K64eOavC1qpqZZ1+x/++egLXZ1PZnDy9+QSQrv1/lL6TkUyqGXqQ=="], "@stealth-developers/bot/typescript": ["typescript@5.4.5", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ=="],