import { Attachment } from '../builders'; import { type GuildMemberStructure, type GuildRoleStructure, type InteractionGuildMemberStructure, Transformers, type UserStructure, } from '../client/transformers'; import type { MakeRequired } from '../common'; import { type AllChannels, channelFrom } from '../structures'; import { type APIApplicationCommandInteractionDataOption, type APIAttachment, type APIGuildMember, type APIInteractionDataResolvedChannel, type APIInteractionGuildMember, type APIRole, type APIUser, ApplicationCommandOptionType, } from '../types'; import type { Command, CommandAutocompleteOption, CommandOption, SubCommand } from './applications/chat'; import type { UsingClient } from './applications/shared'; export type ContextOptionsResolved = { members?: Record | APIInteractionGuildMember>; users?: Record; roles?: Record; channels?: Record; attachments?: Record; }; export class OptionResolver { readonly options: OptionResolved[]; public hoistedOptions: OptionResolved[]; private subCommand: string | null = null; private group: string | null = null; constructor( private client: UsingClient, options: APIApplicationCommandInteractionDataOption[], public parent?: Command, public guildId?: string, public resolved?: ContextOptionsResolved, ) { this.hoistedOptions = this.options = options.map(option => this.transformOption(option, resolved)); if (this.hoistedOptions[0]?.type === ApplicationCommandOptionType.Subcommand) { this.subCommand = this.hoistedOptions[0].name; this.hoistedOptions = this.hoistedOptions[0].options ?? []; } if (this.hoistedOptions[0]?.type === ApplicationCommandOptionType.SubcommandGroup) { this.group = this.hoistedOptions[0].name; this.subCommand = this.hoistedOptions[0].options![0]!.name; this.hoistedOptions = this.hoistedOptions[0].options![0].options ?? []; } } get fullCommandName() { return `${this.parent?.name}${ this.group ? ` ${this.group} ${this.subCommand}` : this.subCommand ? ` ${this.subCommand}` : '' }`; } getCommand() { if (this.subCommand) { return (this.parent?.options as SubCommand[] | undefined)?.find( x => (this.group ? x.group === this.group : true) && x.name === this.subCommand, ); } return this.parent; } getAutocompleteValue(): string | undefined { return this.hoistedOptions.find(option => option.focused)?.value as string; } getAutocomplete() { return (this.getCommand()?.options as CommandOption[] | undefined)?.find( option => option.name === this.hoistedOptions.find(x => x.focused)?.name, ) as CommandAutocompleteOption | undefined; } getParent() { return this.parent?.name; } getSubCommand() { return this.subCommand; } getGroup() { return this.group; } get(name: string) { return this.options.find(opt => opt.name === name); } getHoisted(name: string) { return this.hoistedOptions.find(x => x.name === name); } getValue( name: string, ): | AllChannels | Attachment | boolean | number | string | UserStructure | GuildMemberStructure | InteractionGuildMemberStructure | GuildRoleStructure | undefined { const option = this.getHoisted(name); if (!option) { return; } switch (option.type) { case ApplicationCommandOptionType.Attachment: return option.attachment!; case ApplicationCommandOptionType.Boolean: return option.value as boolean; case ApplicationCommandOptionType.Channel: return option.channel!; case ApplicationCommandOptionType.Integer: case ApplicationCommandOptionType.Number: return option.value as number; case ApplicationCommandOptionType.Role: return option.role; case ApplicationCommandOptionType.String: return option.value as string; case ApplicationCommandOptionType.User: return option.member ?? option.user; case ApplicationCommandOptionType.Mentionable: return option.member ?? option.user ?? option.role; default: return; } } private getTypedOption(name: string, allow: ApplicationCommandOptionType[]) { const option = this.getHoisted(name); if (!option) { throw new Error('Bad Option'); } if (!allow.includes(option.type)) { throw new Error('Bad Option'); } return option; } getChannel(name: string, required?: true): AllChannels; getChannel(name: string): AllChannels | undefined { const option = this.getTypedOption(name, [ApplicationCommandOptionType.Channel]); return option.channel; } getString(name: string, required?: true): string; getString(name: string): string | null { const option = this.getTypedOption(name, [ApplicationCommandOptionType.String]); return option.value as string; } transformOption(option: APIApplicationCommandInteractionDataOption, resolved?: ContextOptionsResolved) { const resolve: OptionResolved = { ...option, }; if ('value' in option) { resolve.value = option.value; } if ('options' in option) { resolve.options = option.options?.map(x => this.transformOption(x, resolved)); } if (resolved) { const value = resolve.value as string; const user = resolved.users?.[value]; if (user) { resolve.user = Transformers.User(this.client, user); } const member = resolved.members?.[value]; if (member) { resolve.member = 'permissions' in member ? Transformers.InteractionGuildMember(this.client, member, user!, this.guildId!) : Transformers.GuildMember(this.client, member, user!, this.guildId!); } const channel = resolved.channels?.[value]; if (channel) { resolve.channel = channelFrom(channel, this.client); } const role = resolved.roles?.[value]; if (role) { resolve.role = Transformers.GuildRole(this.client, role, this.guildId!); } const attachment = resolved.attachments?.[value]; if (attachment) { resolve.attachment = attachment instanceof Attachment ? attachment : new Attachment(this.client, attachment); } } return resolve; } } export interface OptionResolved { name: string; type: ApplicationCommandOptionType; value?: string | number | boolean; options?: OptionResolved[]; user?: UserStructure; member?: GuildMemberStructure | InteractionGuildMemberStructure; attachment?: Attachment; channel?: AllChannels; role?: GuildRoleStructure; focused?: boolean; } export type OptionResolvedWithValue = MakeRequired, 'value'> & { type: | ApplicationCommandOptionType.Boolean | ApplicationCommandOptionType.Integer | ApplicationCommandOptionType.Number | ApplicationCommandOptionType.String; }; export type OptionResolvedWithProp = Exclude< OptionResolved, { type: | ApplicationCommandOptionType.Boolean | ApplicationCommandOptionType.Integer | ApplicationCommandOptionType.Number | ApplicationCommandOptionType.String; } >;