support setting integration contexts & types
vi did:web:vt3e.cat
Fri, 12 Jun 2026 04:23:25 +0100
4 files changed,
164 insertions(+),
22 deletions(-)
A
.oxfmtrc.json
@@ -0,0 +1,5 @@
+{ + "$schema": "./node_modules/oxfmt/configuration_schema.json", + "ignorePatterns": [], + "useTabs": true +}
A
.oxlintrc.json
@@ -0,0 +1,15 @@
+{ + "$schema": "./node_modules/oxlint/configuration_schema.json", + "plugins": [ + "typescript", + "unicorn", + "oxc" + ], + "categories": { + "correctness": "error" + }, + "rules": {}, + "env": { + "builtin": true + } +}
M
pkgs/core/src/commands.ts
→
pkgs/core/src/commands.ts
@@ -6,6 +6,31 @@ MessageContextMenuCommandInteraction,
} from "discord.js"; import type { Option, InferOptions } from "./options"; +export const INTEGRATION_TYPES = { + GUILD_INSTALL: 0, + USER_INSTALL: 1, +} as const; +export const INTERACTION_CONTEXTS = { + GUILD: 0, + BOT_DM: 1, + PRIVATE_CHANNEL: 2, +} as const; + +export const INTEGRATION_TYPE_PRESETS = { + ALL: Object.values(INTEGRATION_TYPES), + GUILD_ONLY: [INTEGRATION_TYPES.GUILD_INSTALL], + USER_ONLY: [INTEGRATION_TYPES.USER_INSTALL], +}; +export const INTEGRATION_CONTEXT_PRESETS = { + EVERYWHERE: Object.values(INTERACTION_CONTEXTS), + GUILDS_ONLY: [INTERACTION_CONTEXTS.GUILD], + DMS_ONLY: [INTERACTION_CONTEXTS.BOT_DM, INTERACTION_CONTEXTS.PRIVATE_CHANNEL], +}; + +export type IntegrationType = (typeof INTEGRATION_TYPES)[keyof typeof INTEGRATION_TYPES]; +export type InteractionContextType = + (typeof INTERACTION_CONTEXTS)[keyof typeof INTERACTION_CONTEXTS]; + export class Subcommand { constructor( public name: string,@@ -50,6 +75,10 @@ constructor(
public name: string, public description: string, public middlewares: Function[] = [], + public config?: { + integrationTypes?: IntegrationType[]; + contexts?: InteractionContextType[]; + }, ) {} subcommand<O extends Record<string, Option<any, any>>>(@@ -86,6 +115,8 @@ public name: string,
public config: { description: string; options?: Record<string, Option<any, any>>; + integrationTypes?: IntegrationType[]; + contexts?: InteractionContextType[]; run: Function; }, public middlewares: Function[] = [],@@ -98,6 +129,10 @@ public name: string,
public type: "user" | "message", public run: Function, public middlewares: Function[] = [], + public config?: { + integrationTypes?: IntegrationType[]; + contexts?: InteractionContextType[]; + }, ) {} }@@ -115,6 +150,8 @@ name: string,
config: { description: string; options?: O; + integrationTypes?: IntegrationType[]; + contexts?: InteractionContextType[]; run: ( interaction: ChatInputCommandInteraction, args: InferOptions<O>,@@ -123,26 +160,63 @@ ) => Promise<void> | void;
}, ): Command; - command(name: string, config: { description: string }): SubcommandGroupBuilder<Ctx>; + command( + name: string, + config: { + description: string; + integrationTypes?: IntegrationType[]; + contexts?: InteractionContextType[]; + }, + ): SubcommandGroupBuilder<Ctx>; command(name: string, config: any): any { if (config.run) { return new Command(name, config, this.middlewares); } - return new SubcommandGroupBuilder<Ctx>(name, config.description, this.middlewares); + return new SubcommandGroupBuilder<Ctx>(name, config.description, this.middlewares, { + integrationTypes: config.integrationTypes, + contexts: config.contexts, + }); } userContextMenu( name: string, - run: (interaction: UserContextMenuCommandInteraction, ctx: Ctx) => Promise<void> | void, + runOrConfig: + | ((interaction: UserContextMenuCommandInteraction, ctx: Ctx) => Promise<void> | void) + | { + integrationTypes?: IntegrationType[]; + contexts?: InteractionContextType[]; + run: (interaction: UserContextMenuCommandInteraction, ctx: Ctx) => Promise<void> | void; + }, ): ContextMenuCommand { - return new ContextMenuCommand(name, "user", run, this.middlewares); + if (typeof runOrConfig === "function") + return new ContextMenuCommand(name, "user", runOrConfig, this.middlewares); + + return new ContextMenuCommand(name, "user", runOrConfig.run, this.middlewares, { + integrationTypes: runOrConfig.integrationTypes, + contexts: runOrConfig.contexts, + }); } messageContextMenu( name: string, - run: (interaction: MessageContextMenuCommandInteraction, ctx: Ctx) => Promise<void> | void, + runOrConfig: + | ((interaction: MessageContextMenuCommandInteraction, ctx: Ctx) => Promise<void> | void) + | { + integrationTypes?: IntegrationType[]; + contexts?: InteractionContextType[]; + run: ( + interaction: MessageContextMenuCommandInteraction, + ctx: Ctx, + ) => Promise<void> | void; + }, ): ContextMenuCommand { - return new ContextMenuCommand(name, "message", run, this.middlewares); + if (typeof runOrConfig === "function") + return new ContextMenuCommand(name, "message", runOrConfig, this.middlewares); + + return new ContextMenuCommand(name, "message", runOrConfig.run, this.middlewares, { + integrationTypes: runOrConfig.integrationTypes, + contexts: runOrConfig.contexts, + }); } }
M
pkgs/core/src/kitten.ts
→
pkgs/core/src/kitten.ts
@@ -10,13 +10,21 @@ type CommandInteraction,
type UserContextMenuCommandInteraction, type MessageContextMenuCommandInteraction, BaseInteraction, + type RESTPostAPIApplicationCommandsJSONBody, ApplicationCommandType, } from "discord.js"; import { HaltExecution } from "./errors"; import { type Option, type InferOptions, OPTION_TYPES } from "./options"; import { KittenComponent, type KittenComponentInteraction } from "./components"; -import { Command, CommandBuilder, SubcommandGroupBuilder, ContextMenuCommand } from "./commands"; +import { + Command, + CommandBuilder, + SubcommandGroupBuilder, + ContextMenuCommand, + type IntegrationType, + type InteractionContextType, +} from "./commands"; import { baseLogger, type KittenLogger } from "./logger"; export interface KittenOptions {@@ -47,6 +55,8 @@ name: string,
config: { description: string; options?: O; + integrationTypes?: IntegrationType[]; + contexts?: InteractionContextType[]; run: ( interaction: ChatInputCommandInteraction, args: InferOptions<O>,@@ -54,7 +64,14 @@ ) => Promise<void> | void;
}, ): Command; - command(name: string, config: { description: string }): SubcommandGroupBuilder<{}>; + command( + name: string, + config: { + description: string; + integrationTypes?: IntegrationType[]; + contexts?: InteractionContextType[]; + }, + ): SubcommandGroupBuilder<{}>; command(name: string, config: any): any { return this.builder().command(name, config);@@ -62,16 +79,28 @@ }
userContextMenu( name: string, - run: (interaction: UserContextMenuCommandInteraction) => Promise<void> | void, + runOrConfig: + | ((interaction: UserContextMenuCommandInteraction) => Promise<void> | void) + | { + integrationTypes?: IntegrationType[]; + contexts?: InteractionContextType[]; + run: (interaction: UserContextMenuCommandInteraction) => Promise<void> | void; + }, ): ContextMenuCommand { - return this.builder().userContextMenu(name, run); + return this.builder().userContextMenu(name, runOrConfig as any); } messageContextMenu( name: string, - run: (interaction: MessageContextMenuCommandInteraction) => Promise<void> | void, + runOrConfig: + | ((interaction: MessageContextMenuCommandInteraction) => Promise<void> | void) + | { + integrationTypes?: IntegrationType[]; + contexts?: InteractionContextType[]; + run: (interaction: MessageContextMenuCommandInteraction) => Promise<void> | void; + }, ): ContextMenuCommand { - return this.builder().messageContextMenu(name, run); + return this.builder().messageContextMenu(name, runOrConfig as any); } button<O extends Record<string, Option<any, any>>>(@@ -215,7 +244,6 @@ } else {
await run(interaction, context); } } catch (err) { - // TODO)) custom error handlers if (err instanceof HaltExecution) return; this.logger.error(`unhandled error executing command ${interaction.commandName}:`, {@@ -318,7 +346,7 @@ return parsed;
} async sync({ guildId }: { guildId?: string } = {}) { - const payloads: any[] = []; + const payloads: RESTPostAPIApplicationCommandsJSONBody[] = []; // chat input commands for (const cmd of this.commands.values()) {@@ -342,35 +370,55 @@ options: this.transformOptionsForDiscord(sub.config.options),
})), })); - payloads.push({ + const payload: RESTPostAPIApplicationCommandsJSONBody = { type: ApplicationCommandType.ChatInput, name: cmd.name, description: cmd.description, options: [...subcommandOptions, ...groupOptions], - }); + }; + + if (cmd.config?.integrationTypes) payload.integration_types = cmd.config.integrationTypes; + if (cmd.config?.contexts) payload.contexts = cmd.config.contexts; + + payloads.push(payload); } else { - payloads.push({ + const payload: RESTPostAPIApplicationCommandsJSONBody = { type: ApplicationCommandType.ChatInput, name: cmd.name, description: cmd.config.description, options: this.transformOptionsForDiscord(cmd.config.options), - }); + }; + + if (cmd.config.integrationTypes) payload.integration_types = cmd.config.integrationTypes; + if (cmd.config.contexts) payload.contexts = cmd.config.contexts; + + payloads.push(payload); } } // context menus for (const cmd of this.userContextMenus.values()) { - payloads.push({ + const payload: RESTPostAPIApplicationCommandsJSONBody = { type: ApplicationCommandType.User, name: cmd.name, - }); + }; + + if (cmd.config?.integrationTypes) payload.integration_types = cmd.config.integrationTypes; + if (cmd.config?.contexts) payload.contexts = cmd.config.contexts; + + payloads.push(payload); } for (const cmd of this.messageContextMenus.values()) { - payloads.push({ + const payload: RESTPostAPIApplicationCommandsJSONBody = { type: ApplicationCommandType.Message, name: cmd.name, - }); + }; + + if (cmd.config?.integrationTypes) payload.integration_types = cmd.config.integrationTypes; + if (cmd.config?.contexts) payload.contexts = cmd.config.contexts; + + payloads.push(payload); } if (!this.client.application) {