diff --git a/src/Interactions/context.ts b/src/Interactions/context.ts index d0a6a58..cde89cb 100644 --- a/src/Interactions/context.ts +++ b/src/Interactions/context.ts @@ -43,12 +43,9 @@ export const ModalSubmitData = GenericTag< export interface DiscordFocusedOption { readonly _: unique symbol } -export interface FocusedOptionContext { - readonly focusedOption: Discord.ApplicationCommandInteractionDataOption -} export const FocusedOptionContext = GenericTag< DiscordFocusedOption, - FocusedOptionContext + Discord.ApplicationCommandInteractionDataOption >("dfx/Interactions/FocusedOptionContext") export interface DiscordSubCommand { @@ -78,7 +75,7 @@ export const resolved = ( export const focusedOptionValue = Effect.map( FocusedOptionContext, - _ => _.focusedOption.value ?? "", + _ => _.value ?? "", ) export class SubCommandNotFound extends TypeIdError( diff --git a/src/Interactions/definitions.ts b/src/Interactions/definitions.ts index 904716d..1693251 100644 --- a/src/Interactions/definitions.ts +++ b/src/Interactions/definitions.ts @@ -30,8 +30,7 @@ export class GlobalApplicationCommand { export const global = < R, E, - const A extends - DeepReadonlyObject, + const A extends Discord.CreateGlobalApplicationCommandParams, >( command: A, handle: CommandHandler, @@ -52,8 +51,7 @@ export class GuildApplicationCommand { export const guild = < R, E, - const A extends - DeepReadonlyObject, + const A extends Discord.CreateGuildApplicationCommandParams, >( command: A, handle: CommandHandler, @@ -66,36 +64,36 @@ export const guild = < export class MessageComponent { readonly _tag = "MessageComponent" constructor( - readonly predicate: (customId: string) => Effect.Effect, + readonly predicate: (customId: string) => boolean, readonly handle: Effect.Effect, ) {} } -export const messageComponent = ( - pred: (customId: string) => Effect.Effect, - handle: CommandHandler, +export const messageComponent = ( + pred: (customId: string) => boolean, + handle: CommandHandler, ) => new MessageComponent< - Exclude, - E1 | E2 - >(pred as any, handle as any) + Exclude, + E + >(pred, handle as any) export class ModalSubmit { readonly _tag = "ModalSubmit" constructor( - readonly predicate: (customId: string) => Effect.Effect, + readonly predicate: (customId: string) => boolean, readonly handle: Effect.Effect, ) {} } -export const modalSubmit = ( - pred: (customId: string) => Effect.Effect, - handle: Effect.Effect, +export const modalSubmit = ( + pred: (customId: string) => boolean, + handle: Effect.Effect, ) => new ModalSubmit< - Exclude, - E1 | E2 - >(pred as any, handle as any) + Exclude, + E + >(pred, handle as any) export class Autocomplete { readonly _tag = "Autocomplete" @@ -103,42 +101,30 @@ export class Autocomplete { readonly predicate: ( data: Discord.ApplicationCommandDatum, focusedOption: Discord.ApplicationCommandInteractionDataOption, - ) => Effect.Effect, + ) => boolean, readonly handle: Effect.Effect, ) {} } -export const autocomplete = ( +export const autocomplete = ( pred: ( data: Discord.ApplicationCommandDatum, focusedOption: Discord.ApplicationCommandInteractionDataOption, - ) => Effect.Effect, - handle: Effect.Effect, + ) => boolean, + handle: Effect.Effect, ) => new Autocomplete< Exclude< - R1 | R2, + R, | DiscordInteraction | DiscordApplicationCommand | DiscordFocusedOption | Scope >, - E1 | E2 - >(pred as any, handle as any) + E + >(pred, handle as any) // ==== Command handler helpers -type DeepReadonly = - T extends Array - ? ReadonlyArray> - : T extends Function - ? T - : T extends object - ? DeepReadonlyObject - : T -type DeepReadonlyObject = { - readonly [P in keyof T]: DeepReadonly -} - export type CommandHandler = | Effect.Effect | CommandHandlerFn diff --git a/src/Interactions/handlers.ts b/src/Interactions/handlers.ts index c23208f..59a8434 100644 --- a/src/Interactions/handlers.ts +++ b/src/Interactions/handlers.ts @@ -1,5 +1,4 @@ import type * as Chunk from "effect/Chunk" -import * as Option from "effect/Option" import * as Effect from "effect/Effect" import * as IxHelpers from "dfx/Helpers/interactions" import * as Ctx from "dfx/Interactions/context" @@ -40,91 +39,66 @@ export const handlers = ( splitDefinitions(flattened) return { - [Discord.InteractionType.PING]: () => + [Discord.InteractionType.PING]: _ => Effect.succeed({ type: Discord.InteractionCallbackType.PONG, } as any), [Discord.InteractionType.APPLICATION_COMMAND]: i => { const data = i.data as Discord.ApplicationCommandDatum - - return Option.match(Option.fromNullable(Commands[data.name]), { - onNone: () => Effect.fail(new DefinitionNotFound(i)), - onSome: command => - Effect.provideService( - command.handle(i), - Ctx.ApplicationCommand, - data, - ) as Handler, E, B>, - }) + const command = Commands[data.name] + if (command === undefined) { + return Effect.fail(new DefinitionNotFound(i)) + } + return Effect.provideService( + command.handle(i), + Ctx.ApplicationCommand, + data, + ) as Handler, E, B> }, [Discord.InteractionType.MODAL_SUBMIT]: i => { const data = i.data as Discord.ModalSubmitDatum - - return Effect.findFirst(ModalSubmit, _ => - _.predicate(data.custom_id), - ).pipe( - Effect.flatMap( - Option.match({ - onNone: () => Effect.fail(new DefinitionNotFound(i)), - onSome: match => - Effect.provideService( - match.handle(i), - Ctx.ModalSubmitData, - data, - ) as Handler, - }), - ), + const match = ModalSubmit.find(_ => _.predicate(data.custom_id)) + if (match === undefined) { + return Effect.fail(new DefinitionNotFound(i)) + } + return Effect.provideService( + match.handle(i), + Ctx.ModalSubmitData, + data, ) as Handler, E, B> }, [Discord.InteractionType.MESSAGE_COMPONENT]: i => { const data = i.data as Discord.MessageComponentDatum - - return Effect.findFirst(MessageComponent, _ => - _.predicate(data.custom_id), - ).pipe( - Effect.flatMap( - Option.match({ - onNone: () => Effect.fail(new DefinitionNotFound(i)), - onSome: match => - Effect.provideService( - match.handle(i), - Ctx.MessageComponentData, - data, - ) as Handler, - }), - ), + const match = MessageComponent.find(_ => _.predicate(data.custom_id)) + if (match === undefined) { + return Effect.fail(new DefinitionNotFound(i)) + } + return Effect.provideService( + match.handle(i), + Ctx.MessageComponentData, + data, ) as Handler, E, B> }, [Discord.InteractionType.APPLICATION_COMMAND_AUTOCOMPLETE]: i => { const data = i.data as Discord.ApplicationCommandDatum - - return Option.match(IxHelpers.focusedOption(data), { - onNone: () => Effect.fail(new DefinitionNotFound(i)), - onSome: focusedOption => - Effect.findFirst(Autocomplete, _ => - _.predicate(data, focusedOption), - ).pipe( - Effect.flatMap( - Option.match({ - onNone: () => Effect.fail(new DefinitionNotFound(i)), - onSome: match => - Effect.provideService( - match.handle(i), - Ctx.ApplicationCommand, - data, - ).pipe( - Effect.provideService(Ctx.FocusedOptionContext, { - focusedOption, - }), - ) as Handler, - }), - ), - ), - }) as Handler, E, B> + const option = IxHelpers.focusedOption(data) + if (option._tag === "None") { + return Effect.fail(new DefinitionNotFound(i)) + } + const match = Autocomplete.find(_ => _.predicate(data, option.value)) + if (match === undefined) { + return Effect.fail(new DefinitionNotFound(i)) + } + return match + .handle(i) + .pipe( + Effect.provideService(Ctx.ApplicationCommand, data), + Effect.provideService(Ctx.FocusedOptionContext, option.value), + ) as Handler, E, B> }, } } diff --git a/src/Interactions/index.ts b/src/Interactions/index.ts index 25c0812..0f88b9b 100644 --- a/src/Interactions/index.ts +++ b/src/Interactions/index.ts @@ -1,4 +1,3 @@ -import * as Effect from "effect/Effect" import type * as Discord from "dfx/types" export { response } from "dfx/Helpers/interactions" @@ -14,14 +13,13 @@ export { } from "dfx/Interactions/definitions" // Filters -export const id = (query: string) => (customId: string) => - Effect.succeed(query === customId) +export const id = (query: string) => (customId: string) => query === customId export const idStartsWith = (query: string) => (customId: string) => - Effect.succeed(customId.startsWith(query)) + customId.startsWith(query) export const idRegex = (query: RegExp) => (customId: string) => - Effect.succeed(query.test(customId)) + query.test(customId) export const option = (command: string, optionName: string) => @@ -32,7 +30,7 @@ export const option = "name" >, ) => - Effect.succeed(data.name === command && focusedOption.name === optionName) + data.name === command && focusedOption.name === optionName export const optionOnly = (optionName: string) => @@ -43,4 +41,4 @@ export const optionOnly = "name" >, ) => - Effect.succeed(focusedOption.name === optionName) + focusedOption.name === optionName diff --git a/src/Interactions/utils.ts b/src/Interactions/utils.ts index 12350df..7d5c282 100644 --- a/src/Interactions/utils.ts +++ b/src/Interactions/utils.ts @@ -3,6 +3,7 @@ import * as Effect from "effect/Effect" import * as Ctx from "dfx/Interactions/context" import type * as D from "dfx/Interactions/definitions" import type * as Discord from "dfx/types" +import * as ReadonlyArray from "effect/ReadonlyArray" export type DefinitionFlattened = D.InteractionDefinition extends infer D @@ -38,60 +39,66 @@ export const flattenDefinitions = ( _: Discord.InteractionResponse, ) => Effect.Effect, ) => - Chunk.map(definitions, ([definition, transform]) => ({ - ...definition, - handle: (i: Discord.Interaction) => - Effect.scoped( - Effect.isEffect(definition.handle) - ? transform( - Effect.flatMap(definition.handle, _ => handleResponse(i, _)), + ReadonlyArray.map( + Chunk.toReadonlyArray(definitions), + ([definition, transform]) => ({ + ...definition, + handle: Effect.isEffect(definition.handle) + ? (i: Discord.Interaction) => + Effect.scoped( + transform( + Effect.flatMap( + definition.handle as Effect.Effect, + _ => handleResponse(i, _), + ), + ), ) - : transform( - Effect.flatMap(definition.handle(context), _ => - handleResponse(i, _), + : (i: Discord.Interaction) => + Effect.scoped( + transform( + Effect.flatMap( + ( + definition.handle as ( + _: any, + ) => Effect.Effect + )(context), + _ => handleResponse(i, _), + ), ), ), - ), - })) + }), + ) export const splitDefinitions = ( - definitions: Chunk.Chunk>, + definitions: ReadonlyArray>, ) => { - const grouped = Chunk.reduce( + const grouped = ReadonlyArray.reduce( definitions, { - Autocomplete: Chunk.empty(), - GlobalApplicationCommand: Chunk.empty(), - GuildApplicationCommand: Chunk.empty(), - MessageComponent: Chunk.empty(), - ModalSubmit: Chunk.empty(), + Autocomplete: [], + GlobalApplicationCommand: [], + GuildApplicationCommand: [], + MessageComponent: [], + ModalSubmit: [], + Commands: {}, } as { - [K in D.InteractionDefinition["_tag"]]: Chunk.Chunk< + [K in D.InteractionDefinition["_tag"]]: Array< Extract, { _tag: K }> > + } & { + readonly Commands: Record> + }, + (acc, d) => { + acc[d._tag].push(d as any) + if ( + d._tag === "GlobalApplicationCommand" || + d._tag === "GuildApplicationCommand" + ) { + acc.Commands[d.command.name] = d as any + } + return acc }, - (acc, d) => ({ - ...acc, - [d._tag]: Chunk.append(acc[d._tag] as Chunk.Chunk, d), - }), - ) - - const Commands = Chunk.appendAll( - grouped.GlobalApplicationCommand, - grouped.GuildApplicationCommand, - ).pipe( - Chunk.reduce( - {} as Record>, - (acc, d) => - ({ - ...acc, - [d.command.name]: d, - }) as any, - ), ) - return { - ...grouped, - Commands, - } + return grouped }