diff --git a/packages/discord/src/command/abstract-command.listener.ts b/packages/discord/src/command/abstract-command.listener.ts index 64a460791..7e05d5569 100644 --- a/packages/discord/src/command/abstract-command.listener.ts +++ b/packages/discord/src/command/abstract-command.listener.ts @@ -8,6 +8,7 @@ import * as Sentry from "@sentry/node"; import { + type APIApplicationCommandOptionChoice, type APIUser, ApplicationCommandOptionType, GatewayDispatchEvents, @@ -42,6 +43,10 @@ export interface ExecuteCommandOptions { message?: IMessage | Message; } +const MAX_AUTOCOMPLETE_CHOICES = 25; +const MAX_CHOICE_NAME_LENGTH = 100; +const MAX_STRING_CHOICE_VALUE_LENGTH = 100; + export abstract class AbstractCommandListener { protected hooks: Map; protected readonly logger = new Logger("CommandListener"); @@ -262,10 +267,40 @@ export abstract class AbstractCommandListener { return { type: InteractionResponseType.ApplicationCommandAutocompleteResult, - data: { choices: response }, + data: { choices: this.sanitizeAutocompleteChoices(response) }, }; } + private sanitizeAutocompleteChoices( + choices: APIApplicationCommandOptionChoice[] = [] + ): APIApplicationCommandOptionChoice[] { + return choices + .flatMap((choice) => { + const valueName = + choice.value === undefined || choice.value === null ? + "" : + String(choice.value); + const name = (choice.name || valueName).trim(); + + if (!name) return []; + + if ( + typeof choice.value === "string" && + (choice.value.trim().length === 0 || + choice.value.length > MAX_STRING_CHOICE_VALUE_LENGTH) + ) + return []; + + return [ + { + ...choice, + name: name.slice(0, MAX_CHOICE_NAME_LENGTH), + }, + ]; + }) + .slice(0, MAX_AUTOCOMPLETE_CHOICES); + } + private async onInteraction(interaction: Interaction): Promise { if (interaction.isCommandInteraction()) { this.onCommand(interaction);