CRBT-Team/Purplet

View on GitHub
packages/purplet/src/hooks/command-slash.ts

Summary

Maintainability
A
1 hr
Test Coverage
import type { LocalizationMap } from 'purplet/types';
import { ApplicationCommandType } from 'purplet/types';
import { $appCommand } from './command';
import { $applicationCommands, $interaction } from '../lib/hook-core';
import { $merge } from '../lib/hook-merge';
import type { OptionBuilder, OptionBuilderToPurpletResolvedObject } from '../lib/OptionBuilder';
import { getOptionBuilderAutocompleteHandlers } from '../lib/OptionBuilder';
import type { SlashCommandInteraction } from '../structures';
import { AutocompleteInteraction } from '../structures';
import { camelChoiceToSnake } from '../utils/case';
import { toJSONValue } from '../utils/json';
import type { CommandPermissionsInput } from '../utils/permissions';
import { resolveCommandPermissions } from '../utils/permissions';

export interface SlashCommandData<T> extends CommandPermissionsInput {
  name: string;
  nameLocalizations?: LocalizationMap;
  description: string;
  descriptionLocalizations?: LocalizationMap;
  options?: OptionBuilder<T>;
  handle(this: SlashCommandInteraction, options: OptionBuilderToPurpletResolvedObject<T>): void;
}

/**
 * Registers a slash command. Use spaces in the name to specify subcommands. Fully detailed usage
 * guide at https://purplet.js.org/docs/slash-commands.
 */
export function $slashCommand<T>(options: SlashCommandData<T>) {
  const commandOptions = toJSONValue(options.options ?? []);
  const autocompleteHandlers = getOptionBuilderAutocompleteHandlers(options.options);

  return $merge([
    $appCommand({
      command: {
        type: ApplicationCommandType.ChatInput,
        name: options.name,
        name_localizations: options.nameLocalizations,
        description: options.description,
        description_localizations: options.descriptionLocalizations,
        ...resolveCommandPermissions(options),
        options: commandOptions,
      },
      handle(this: SlashCommandInteraction) {
        const resolvedOptions = Object.fromEntries(
          commandOptions.map(option => [option.name, this.getResolvedOption(option.name)])
        ) as unknown as OptionBuilderToPurpletResolvedObject<T>;

        options.handle.call(this, resolvedOptions);
      },
    }),
    Object.keys(autocompleteHandlers ?? {}).length > 0 &&
      $interaction(async i => {
        if (
          AutocompleteInteraction.is(i) &&
          i.fullCommandName === options.name &&
          i.commandType === ApplicationCommandType.ChatInput
        ) {
          const resolvedOptions = Object.fromEntries(
            commandOptions.map(option => [option.name, (i.getOption(option.name) as any)?.value])
          ) as unknown as T;

          i.showAutocompleteResponse(
            (
              await (autocompleteHandlers as any)[i.focusedOption.name].call(i, resolvedOptions)
            ).map(camelChoiceToSnake)
          );
        }
      }),
  ]);
}

export interface SlashCommandGroupData extends CommandPermissionsInput {
  name: string;
  nameLocalizations?: LocalizationMap;
  description: string;
  descriptionLocalizations?: LocalizationMap;
}

/**
 * Defines slash command group metadata. Rationale and usage is described at
 * https://purplet.js.org/docs/slash-commands/#subcommands.
 */
export function $slashCommandGroup(data: SlashCommandGroupData) {
  return $applicationCommands([
    {
      isSlashCommandGroup: true,
      name: data.name,
      name_localizations: data.nameLocalizations,
      description: data.description,
      description_localizations: data.descriptionLocalizations,
      ...resolveCommandPermissions(data),
    },
  ]);
}