diff --git a/packages/koishi-core/src/app.ts b/packages/koishi-core/src/app.ts index 19294f73e3..5e4da49664 100644 --- a/packages/koishi-core/src/app.ts +++ b/packages/koishi-core/src/app.ts @@ -275,7 +275,9 @@ export class App extends Context { private _preprocess = async (meta: Meta<'message'>, next: NextFunction) => { // strip prefix let capture: RegExpMatchArray - let atMe = false, nickname = '', prefix: string = null + let atMe = false + let nickname = '' + let prefix: string = null let message = simplify(meta.message.trim()) let parsedArgv: ParsedCommandLine @@ -356,12 +358,15 @@ export class App extends Context { } // execute command - if (parsedArgv) return parsedArgv.command.execute(parsedArgv, next) + if (parsedArgv && !parsedArgv.command.getConfig('disable', meta)) { + return parsedArgv.command.execute(parsedArgv, next) + } // show suggestions const target = message.split(/\s/, 1)[0].toLowerCase() - if (!target || !capture) return next() + if (!target || !capture || parsedArgv) return next() + const executableMap = new Map() return showSuggestions({ target, meta, @@ -371,18 +376,27 @@ export class App extends Context { items: Object.keys(this._commandMap), coefficient: this.options.similarityCoefficient, command: suggestion => this._commandMap[suggestion], + disable: (name) => { + const command = this._commandMap[name] + let disabled = executableMap.get(command) + if (disabled === undefined) { + disabled = !!command.getConfig('disable', meta) + executableMap.set(command, disabled) + } + return disabled + }, execute: async (suggestion, meta, next) => { const newMessage = suggestion + message.slice(target.length) - const parsedArgv = this.parseCommandLine(newMessage, meta) - return parsedArgv.command.execute(parsedArgv, next) + const argv = this.parseCommandLine(newMessage, meta) + return argv.command.execute(argv, next) }, }) } parseCommandLine (message: string, meta: Meta<'message'>): ParsedCommandLine { - const name = message.split(/\s/, 1)[0].toLowerCase() - const command = this.getCommand(name, meta) - if (command) { + const name = message.split(/\s/, 1)[0] + const command = this._getCommandByRawName(name) + if (command?.context.match(meta)) { const result = command.parse(message.slice(name.length).trimStart()) return { meta, command, ...result } } @@ -391,7 +405,9 @@ export class App extends Context { executeCommandLine (message: string, meta: Meta<'message'>, next: NextFunction = noop) { if (!('$ctxType' in meta)) this.server.parseMeta(meta) const argv = this.parseCommandLine(message, meta) - if (argv) return argv.command.execute(argv, next) + if (argv && !argv.command.getConfig('disable', meta)) { + return argv.command.execute(argv, next) + } return next() } diff --git a/packages/koishi-core/src/context.ts b/packages/koishi-core/src/context.ts index 7707aabb12..5eb111508d 100644 --- a/packages/koishi-core/src/context.ts +++ b/packages/koishi-core/src/context.ts @@ -234,7 +234,7 @@ export class Context { return parent } - private _getCommandByRawName (name: string) { + protected _getCommandByRawName (name: string) { const index = name.lastIndexOf('/') return this.app._commandMap[name.slice(index + 1).toLowerCase()] } diff --git a/packages/koishi-core/src/utils.ts b/packages/koishi-core/src/utils.ts index 39fa16b576..7e4446b4d6 100644 --- a/packages/koishi-core/src/utils.ts +++ b/packages/koishi-core/src/utils.ts @@ -56,17 +56,18 @@ interface SuggestOptions { prefix: string suffix: string coefficient?: number + disable?: (name: string) => boolean command: Command | ((suggestion: string) => Command) execute: (suggestion: string, meta: Meta<'message'>, next: NextFunction) => any } -function findSimilar (target: string, coefficient: number) { - return (name: string) => name.length > 2 && leven(name, target) <= name.length * coefficient -} - export function showSuggestions (options: SuggestOptions): Promise { - const { target, items, meta, next, prefix, suffix, execute, coefficient = 0.4 } = options - const suggestions = items.filter(findSimilar(target, coefficient)) + const { target, items, meta, next, prefix, suffix, execute, disable, coefficient = 0.4 } = options + const suggestions = items.filter((name) => { + return name.length > 2 + && leven(name, target) <= name.length * coefficient + && !disable?.(name) + }) if (!suggestions.length) return next() return next(async () => { diff --git a/packages/koishi-core/tests/suggestion.spec.ts b/packages/koishi-core/tests/suggestion.spec.ts index d71ad9bb1f..5ea7809499 100644 --- a/packages/koishi-core/tests/suggestion.spec.ts +++ b/packages/koishi-core/tests/suggestion.spec.ts @@ -12,8 +12,9 @@ describe('Command Suggestions', () => { .action(({ meta }, bar) => { return meta.$send('foo' + bar) }) - + app.command('fooo', { checkUnknown: true, checkRequired: true }) + .alias('bool') .option('-t, --text ') .action(({ meta, options }) => { return meta.$send('fooo' + options.text) @@ -69,7 +70,7 @@ describe('Command Suggestions', () => { test('multiple suggestions', async () => { await session1.shouldHaveReply('fool bar', [ messages.COMMAND_SUGGESTION_PREFIX, - format(messages.SUGGESTION_TEXT, '“foo”或“fooo”'), + format(messages.SUGGESTION_TEXT, '“foo”或“fooo”或“bool”'), ].join('')) await session1.shouldHaveNoReply(' ') })