diff --git a/src/handler/events/interactionCreate.ts b/src/handler/events/interactionCreate.ts index 25ab48b..2d25e3f 100644 --- a/src/handler/events/interactionCreate.ts +++ b/src/handler/events/interactionCreate.ts @@ -12,7 +12,7 @@ import { match } from 'ts-pattern'; import { SernError } from '../structures/errors'; import Context from '../structures/context'; import { controller } from '../sern'; -import type { Module } from '../structures/module'; +import type { AutocompleteCommand, Module, SlashCommand } from '../structures/module'; import { isButton, isChatInputCommand, @@ -24,6 +24,7 @@ import { import { filterCorrectModule } from './observableHandling'; import { CommandType } from '../structures/enums'; import type { Result } from 'ts-results'; +import type { AutocompleteInteraction } from 'discord.js'; function applicationCommandHandler(mod: Module | undefined, interaction: CommandInteraction) { const mod$ = (cmdTy: T) => of(mod).pipe(filterCorrectModule(cmdTy)); @@ -126,21 +127,53 @@ function messageComponentInteractionHandler( .otherwise(() => throwError(() => SernError.NotSupportedInteraction)); } -function modalHandler(modul : Module|undefined, ctx: ModalSubmitInteraction) { - return of(modul).pipe( - filterCorrectModule(CommandType.Modal), - concatMap(mod => { - return of(mod.onEvent?.map(e => e.execute([ctx], controller)) ?? []).pipe( - map(res => ({ - mod, - res, - execute() { - return mod.execute(ctx); - }, - })), - ); - }) - ) +function modalHandler(modul: Module | undefined, ctx: ModalSubmitInteraction) { + return of(modul).pipe( + filterCorrectModule(CommandType.Modal), + concatMap(mod => { + return of(mod.onEvent?.map(e => e.execute([ctx], controller)) ?? []).pipe( + map(res => ({ + mod, + res, + execute() { + return mod.execute(ctx); + }, + })), + ); + }), + ); +} + +function autoCmpHandler(mod: Module | undefined, interaction: AutocompleteInteraction) { + return of(mod).pipe( + filterCorrectModule(CommandType.Slash), + concatMap(mod => { + const choice = interaction.options.getFocused(true); + const selectedOption = mod.options?.find( + o => o.autocomplete && o.command.name === choice.name, + ); + if (selectedOption !== undefined && selectedOption.autocomplete) { + return of( + selectedOption.command.onEvent?.map(e => + e.execute([interaction], controller), + ) ?? [], + ).pipe( + map(res => ({ + mod, + res, + execute() { + return selectedOption.command.execute(interaction); + }, + })), + ); + } + return throwError( + () => + SernError.NotSupportedInteraction + + ` There is probably no autocomplete tag for this option`, + ); + }), + ); } export function onInteractionCreate(wrapper: Wrapper) { @@ -171,6 +204,7 @@ export function onInteractionCreate(wrapper: Wrapper) { } if (interaction.isAutocomplete()) { const modul = Files.ApplicationCommands[1].get(interaction.commandName); + return autoCmpHandler(modul, interaction); } return of(); }), diff --git a/src/handler/plugins/plugin.ts b/src/handler/plugins/plugin.ts index 282e30f..39752e2 100644 --- a/src/handler/plugins/plugin.ts +++ b/src/handler/plugins/plugin.ts @@ -14,6 +14,7 @@ import type { Awaitable, Client } from 'discord.js'; import type { Err, Ok, Result } from 'ts-results'; import type { Module, Override } from '../..'; +import { CommandType } from '../..'; import type { BaseModule, ModuleDefs } from '../structures/module'; import type { PluginType } from '../structures/enums'; import type { ValueOf } from 'ts-pattern/dist/types/helpers'; @@ -68,8 +69,10 @@ type ModuleNoPlugins = ValueOf<{ //TODO: I WANT BETTER TYPINGS AHHHHHHHHHHHHHHH export function sernModule(plugins: CommandPlugin[], mod: ModuleNoPlugins): Module { - return { - plugins, - ...mod, - }; + if (mod.type !== CommandType.Autocomplete) + return { + plugins, + ...mod, + }; + else return mod; } diff --git a/src/handler/structures/enums.ts b/src/handler/structures/enums.ts index d81d7cb..9aefe37 100644 --- a/src/handler/structures/enums.ts +++ b/src/handler/structures/enums.ts @@ -2,13 +2,14 @@ * @enum { number }; */ enum CommandType { - Text = 0b0000001, - Slash = 0b0000010, - MenuUser = 0b0000100, - MenuMsg = 0b0001000, - Button = 0b0010000, - MenuSelect = 0b0100000, - Modal = 0b1000000, + Text = 0b00000001, + Slash = 0b00000010, + MenuUser = 0b00000100, + MenuMsg = 0b00001000, + Button = 0b00010000, + MenuSelect = 0b00100000, + Modal = 0b01000000, + Autocomplete = 0b10000000, Both = 0b0000011, } diff --git a/src/handler/structures/module.ts b/src/handler/structures/module.ts index 2a82695..d4a4c23 100644 --- a/src/handler/structures/module.ts +++ b/src/handler/structures/module.ts @@ -1,4 +1,5 @@ import type { + ApplicationCommandAutocompleteOption, ApplicationCommandOptionData, Awaitable, ButtonInteraction, @@ -11,6 +12,8 @@ import type { Args, Override } from '../../types/handler'; import type { CommandPlugin, EventPlugin } from '../plugins/plugin'; import type Context from './context'; import { CommandType, PluginType } from './enums'; +import type { AutocompleteInteraction } from 'discord.js'; +import type { ApplicationCommandOptionType } from 'discord.js'; export interface BaseModule { type: CommandType | PluginType; @@ -36,7 +39,7 @@ export type SlashCommand = Override< type: CommandType.Slash; onEvent?: EventPlugin[]; plugins?: CommandPlugin[]; - options?: ApplicationCommandOptionData[]; + options?: OptionsData[]; } >; @@ -47,7 +50,7 @@ export type BothCommand = Override< onEvent?: EventPlugin[]; plugins?: CommandPlugin[]; alias?: string[]; - options?: ApplicationCommandOptionData[]; + options?: OptionsData[]; } >; @@ -94,12 +97,24 @@ export type SelectMenuCommand = Override< export type ModalSubmitCommand = Override< BaseModule, { - type : CommandType.Modal; + type: CommandType.Modal; onEvent?: EventPlugin[]; plugins?: CommandPlugin[]; - execute : (ctx: ModalSubmitInteraction) => Awaitable; + execute: (ctx: ModalSubmitInteraction) => Awaitable; } +>; +// Autocomplete commands are a little different +// They can't have command plugins as they are +// in conjunction with chat input commands +// TODO: possibly in future, allow Autocmp commands in separate files? +export type AutocompleteCommand = Override< + BaseModule, + { + type: CommandType.Autocomplete; + onEvent?: EventPlugin[]; + execute: (ctx: AutocompleteInteraction) => Awaitable; + } >; export type Module = @@ -110,7 +125,8 @@ export type Module = | ContextMenuMsg | ButtonCommand | SelectMenuCommand - | ModalSubmitCommand; + | ModalSubmitCommand + | AutocompleteCommand; //https://stackoverflow.com/questions/64092736/alternative-to-switch-statement-for-typescript-discriminated-union // Explicit Module Definitions for mapping @@ -122,5 +138,18 @@ export type ModuleDefs = { [CommandType.MenuUser]: ContextMenuUser; [CommandType.Button]: ButtonCommand; [CommandType.MenuSelect]: SelectMenuCommand; - [CommandType.Modal] : ModalSubmitCommand; + [CommandType.Modal]: ModalSubmitCommand; + [CommandType.Autocomplete]: AutocompleteCommand; }; + +type OptionsData = + | Exclude + | { + required?: boolean; + autocomplete: true; + type: + | ApplicationCommandOptionType.String + | ApplicationCommandOptionType.Number + | ApplicationCommandOptionType.Integer; + command: AutocompleteCommand; + };