From a359f73fa24127a4964d411c8c1c0dfea5edc0f1 Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Sun, 7 Jan 2024 15:26:08 -0600 Subject: [PATCH] fix: crashing when slash command is used as text command (#349) * progress on fix * fix: ids --- src/core/id.ts | 54 +++++++++++-------- .../structures/services/module-manager.ts | 5 +- src/handlers/dispatchers.ts | 4 +- src/handlers/event-utils.ts | 33 +++++++----- src/handlers/ready-event.ts | 8 ++- test/handlers/id.test.ts | 29 +--------- 6 files changed, 66 insertions(+), 67 deletions(-) diff --git a/src/core/id.ts b/src/core/id.ts index fa4a33ba..28032c54 100644 --- a/src/core/id.ts +++ b/src/core/id.ts @@ -4,20 +4,20 @@ import { CommandType, EventType } from './structures'; /** * Construct unique ID for a given interaction object. * @param event The interaction object for which to create an ID. - * @returns A unique string ID based on the type and properties of the interaction object. + * @returns An array of unique string IDs based on the type and properties of the interaction object. */ export function reconstruct(event: T) { switch (event.type) { case InteractionType.MessageComponent: { - return `${event.customId}_C${event.componentType}`; + return [`${event.customId}_C${event.componentType}`]; } case InteractionType.ApplicationCommand: case InteractionType.ApplicationCommandAutocomplete: { - return `${event.commandName}_A${event.commandType}`; + return [`${event.commandName}_A${event.commandType}`, `${event.commandName}_B`]; } //Modal interactions are classified as components for sern case InteractionType.ModalSubmit: { - return `${event.customId}_C1`; + return [`${event.customId}_M`]; } } } @@ -27,21 +27,20 @@ export function reconstruct(event: T) { */ const appBitField = 0b000000001111; -// Each index represents the exponent of a CommandType. -// Every CommandType is a power of two. -export const CommandTypeDiscordApi = [ - 1, // CommandType.Text - ApplicationCommandType.ChatInput, - ApplicationCommandType.User, - ApplicationCommandType.Message, - ComponentType.Button, - ComponentType.StringSelect, - 1, // CommandType.Modal - ComponentType.UserSelect, - ComponentType.RoleSelect, - ComponentType.MentionableSelect, - ComponentType.ChannelSelect, -]; + +const TypeMap = new Map([ + [CommandType.Text, 0], + [CommandType.Both, 0], + [CommandType.Slash, ApplicationCommandType.ChatInput], + [CommandType.CtxUser, ApplicationCommandType.User], + [CommandType.CtxMsg, ApplicationCommandType.Message], + [CommandType.Button, ComponentType.Button], + [CommandType.Modal, InteractionType.ModalSubmit], + [CommandType.StringSelect, ComponentType.StringSelect], + [CommandType.UserSelect, ComponentType.UserSelect], + [CommandType.MentionableSelect, ComponentType.MentionableSelect], + [CommandType.RoleSelect, ComponentType.RoleSelect], + [CommandType.ChannelSelect, ComponentType.ChannelSelect]]); /* * Generates a number based on CommandType. @@ -49,8 +48,7 @@ export const CommandTypeDiscordApi = [ * TextCommands are 0 as they aren't either or. */ function apiType(t: CommandType | EventType) { - if (t === CommandType.Both || t === CommandType.Modal) return 1; - return CommandTypeDiscordApi[Math.log2(t)]; + return TypeMap.get(t)!; } /* @@ -59,6 +57,18 @@ function apiType(t: CommandType | EventType) { * Then, another number generated by apiType function is appended */ export function create(name: string, type: CommandType | EventType) { + if(type == CommandType.Text) { + return `${name}_T`; + } + if(type == CommandType.Both) { + return `${name}_B`; + } + if(type == CommandType.Modal) { + return `${name}_M`; + } const am = (appBitField & type) !== 0 ? 'A' : 'C'; - return name + '_' + am + apiType(type); + return `${name}_${am}${apiType(type)}` } + + + diff --git a/src/core/structures/services/module-manager.ts b/src/core/structures/services/module-manager.ts index 63154802..356936bf 100644 --- a/src/core/structures/services/module-manager.ts +++ b/src/core/structures/services/module-manager.ts @@ -44,7 +44,10 @@ export class DefaultModuleManager implements ModuleManager { const publishable = 0b000000110; return Promise.all( Array.from(entries) - .filter(([id]) => !(Number.parseInt(id.at(-1)!) & publishable)) + .filter(([id]) => { + const last_entry = id.at(-1); + return last_entry == 'B' || !(publishable & Number.parseInt(last_entry!)); + }) .map(([, path]) => Files.importModule(path)), ); } diff --git a/src/handlers/dispatchers.ts b/src/handlers/dispatchers.ts index ad9549c8..5162e1ee 100644 --- a/src/handlers/dispatchers.ts +++ b/src/handlers/dispatchers.ts @@ -11,7 +11,7 @@ import { import { createResultResolver } from './event-utils'; import { BaseInteraction, Message } from 'discord.js'; import { CommandType, Context } from '../core'; -import type { AnyFunction, Args } from '../types/utility'; +import type { Args } from '../types/utility'; import { inspect } from 'node:util' import type { CommandModule, Module, Processed } from '../types/core-modules'; @@ -77,7 +77,7 @@ export function createDispatcher(payload: { if (isAutocomplete(payload.event)) { const option = treeSearch(payload.event, payload.module.options); assert.ok(option, SernError.NotSupportedInteraction + ` There is no autocomplete tag for ` + inspect(payload.module)); - const { command, name, parent } = option; + const { command } = option; return { ...payload, diff --git a/src/handlers/event-utils.ts b/src/handlers/event-utils.ts index ad75f520..6182aee3 100644 --- a/src/handlers/event-utils.ts +++ b/src/handlers/event-utils.ts @@ -71,18 +71,23 @@ export function createInteractionHandler( return createGenericHandler, void>>( source, async event => { - const fullPath = mg.get(Id.reconstruct(event)); - if(!fullPath) { - return Err.EMPTY + const possibleIds = Id.reconstruct(event); + let fullPaths= possibleIds + .map(id => mg.get(id)) + .filter((id): id is string => id !== undefined); + + if(fullPaths.length == 0) { + return Err.EMPTY; } + const [ path ] = fullPaths; return Files - .defaultModuleLoader>(fullPath) - .then(payload => Ok(createDispatcher({ - module: payload.module, - event, - }))); - }, - ); + .defaultModuleLoader>(path) + .then(payload => Ok(createDispatcher({ + module: payload.module, + event, + }))); + }, +); } export function createMessageHandler( @@ -92,10 +97,12 @@ export function createMessageHandler( ) { return createGenericHandler(source, async event => { const [prefix, ...rest] = fmt(event.content, defaultPrefix); - const fullPath = mg.get(`${prefix}_A1`); - + let fullPath = mg.get(`${prefix}_T`); if(!fullPath) { - return Err('Possibly undefined behavior: could not find a static id to resolve') + fullPath = mg.get(`${prefix}_B`); + if(!fullPath) { + return Err('Possibly undefined behavior: could not find a static id to resolve'); + } } return Files .defaultModuleLoader>(fullPath) diff --git a/src/handlers/ready-event.ts b/src/handlers/ready-event.ts index 9e6753df..b4e811bd 100644 --- a/src/handlers/ready-event.ts +++ b/src/handlers/ready-event.ts @@ -39,8 +39,12 @@ function register>( validModuleType, `Found ${module.name} at ${fullPath}, which does not have a valid type`, ); - if (module.type === CommandType.Both || module.type === CommandType.Text) { - module.alias?.forEach(a => manager.set(`${a}_A1`, fullPath)); + if (module.type === CommandType.Both) { + module.alias?.forEach(a => manager.set(`${a}_B`, fullPath)); + } else { + if(module.type === CommandType.Text){ + module.alias?.forEach(a => manager.set(`${a}_T`, fullPath)); + } } return Result.wrap(() => manager.set(id, fullPath)); } diff --git a/test/handlers/id.test.ts b/test/handlers/id.test.ts index a472742b..d8306900 100644 --- a/test/handlers/id.test.ts +++ b/test/handlers/id.test.ts @@ -2,7 +2,6 @@ import { describe, expect, it, vi } from 'vitest'; import * as Id from '../../src/core/id'; import { faker } from '@faker-js/faker'; import { CommandModule, CommandType, commandModule } from '../../src'; -import { CommandTypeDiscordApi } from '../../src/core/id'; function createRandomCommandModules() { const randomCommandType = [ @@ -41,32 +40,8 @@ describe('id resolution', () => { const metadata = modules.map(createMetadata); metadata.forEach((meta, idx) => { const associatedModule = modules[idx]; - const am = (appBitField & associatedModule.type) !== 0 ? 'A' : 'C'; - let uid = 0; - if ( - associatedModule.type === CommandType.Both || - associatedModule.type === CommandType.Modal - ) { - uid = 1; - } else { - uid = CommandTypeDiscordApi[Math.log2(associatedModule.type)]; - } - expect(meta.id).toBe(associatedModule.name + '_' + am + uid); + const uid = Id.create(associatedModule.name!, associatedModule.type!); + expect(meta.id).toBe(uid); }); }); - - it('maps commands type to discord components or application commands', () => { - expect(CommandTypeDiscordApi[Math.log2(CommandType.Text)]).toBe(1); - - expect(CommandTypeDiscordApi[1]).toBe(1); - expect(CommandTypeDiscordApi[Math.log2(CommandType.CtxUser)]).toBe(2); - expect(CommandTypeDiscordApi[Math.log2(CommandType.CtxMsg)]).toBe(3); - expect(CommandTypeDiscordApi[Math.log2(CommandType.Button)]).toBe(2); - expect(CommandTypeDiscordApi[Math.log2(CommandType.StringSelect)]).toBe(3); - expect(CommandTypeDiscordApi[Math.log2(CommandType.UserSelect)]).toBe(5); - expect(CommandTypeDiscordApi[Math.log2(CommandType.RoleSelect)]).toBe(6); - expect(CommandTypeDiscordApi[Math.log2(CommandType.MentionableSelect)]).toBe(7); - expect(CommandTypeDiscordApi[Math.log2(CommandType.ChannelSelect)]).toBe(8); - expect(CommandTypeDiscordApi[6]).toBe(1); - }); });