Merge pull request #136 from MierenManz/main
Argument parsing for commands
This commit is contained in:
commit
60e7c75113
5 changed files with 264 additions and 5 deletions
1
mod.ts
1
mod.ts
|
@ -191,3 +191,4 @@ export {
|
||||||
isVoiceChannel,
|
isVoiceChannel,
|
||||||
default as getChannelByType
|
default as getChannelByType
|
||||||
} from './src/utils/channel.ts'
|
} from './src/utils/channel.ts'
|
||||||
|
export * from "./src/utils/command.ts"
|
|
@ -9,6 +9,7 @@ import {
|
||||||
CommandsManager,
|
CommandsManager,
|
||||||
parseCommand
|
parseCommand
|
||||||
} from './command.ts'
|
} from './command.ts'
|
||||||
|
import { parseArgs } from '../utils/command.ts'
|
||||||
import { Extension, ExtensionsManager } from './extension.ts'
|
import { Extension, ExtensionsManager } from './extension.ts'
|
||||||
|
|
||||||
type PrefixReturnType = string | string[] | Promise<string | string[]>
|
type PrefixReturnType = string | string[] | Promise<string | string[]>
|
||||||
|
@ -239,7 +240,7 @@ export class CommandClient extends Client implements CommandClientOptions {
|
||||||
client: this,
|
client: this,
|
||||||
name: parsed.name,
|
name: parsed.name,
|
||||||
prefix,
|
prefix,
|
||||||
args: parsed.args,
|
args: parseArgs(command.args, parsed.args),
|
||||||
argString: parsed.argString,
|
argString: parsed.argString,
|
||||||
message: msg,
|
message: msg,
|
||||||
author: msg.author,
|
author: msg.author,
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { Collection } from '../utils/collection.ts'
|
||||||
import type { CommandClient } from './client.ts'
|
import type { CommandClient } from './client.ts'
|
||||||
import type { Extension } from './extension.ts'
|
import type { Extension } from './extension.ts'
|
||||||
import { join, walk } from '../../deps.ts'
|
import { join, walk } from '../../deps.ts'
|
||||||
|
import type { Args } from '../utils/command.ts'
|
||||||
export interface CommandContext {
|
export interface CommandContext {
|
||||||
/** The Client object */
|
/** The Client object */
|
||||||
client: CommandClient
|
client: CommandClient
|
||||||
|
@ -23,7 +23,7 @@ export interface CommandContext {
|
||||||
/** Name of Command which was used */
|
/** Name of Command which was used */
|
||||||
name: string
|
name: string
|
||||||
/** Array of Arguments used with Command */
|
/** Array of Arguments used with Command */
|
||||||
args: string[]
|
args: Record<string, unknown> | null
|
||||||
/** Complete Raw String of Arguments */
|
/** Complete Raw String of Arguments */
|
||||||
argString: string
|
argString: string
|
||||||
/** Guild which the command has called */
|
/** Guild which the command has called */
|
||||||
|
@ -46,7 +46,7 @@ export interface CommandOptions {
|
||||||
/** Usage Example of Command, only Arguments (without Prefix and Name) */
|
/** Usage Example of Command, only Arguments (without Prefix and Name) */
|
||||||
examples?: string | string[]
|
examples?: string | string[]
|
||||||
/** Does the Command take Arguments? Maybe number of required arguments? Or list of arguments? */
|
/** Does the Command take Arguments? Maybe number of required arguments? Or list of arguments? */
|
||||||
args?: number | boolean | string[]
|
args?: Args[]
|
||||||
/** Permissions(s) required by both User and Bot in order to use Command */
|
/** Permissions(s) required by both User and Bot in order to use Command */
|
||||||
permissions?: string | string[]
|
permissions?: string | string[]
|
||||||
/** Permission(s) required for using Command */
|
/** Permission(s) required for using Command */
|
||||||
|
@ -81,7 +81,7 @@ export class Command implements CommandOptions {
|
||||||
extension?: Extension
|
extension?: Extension
|
||||||
usage?: string | string[]
|
usage?: string | string[]
|
||||||
examples?: string | string[]
|
examples?: string | string[]
|
||||||
args?: number | boolean | string[]
|
args?: Args[]
|
||||||
permissions?: string | string[]
|
permissions?: string | string[]
|
||||||
userPermissions?: string | string[]
|
userPermissions?: string | string[]
|
||||||
botPermissions?: string | string[]
|
botPermissions?: string | string[]
|
||||||
|
|
107
src/utils/command.ts
Normal file
107
src/utils/command.ts
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
interface MentionToRegex {
|
||||||
|
[key: string]: RegExp
|
||||||
|
mentionUser: RegExp
|
||||||
|
mentionRole: RegExp
|
||||||
|
mentionChannel: RegExp
|
||||||
|
}
|
||||||
|
|
||||||
|
const mentionToRegex: MentionToRegex = {
|
||||||
|
mentionUser: /<@!?(\d{17,19})>/,
|
||||||
|
mentionRole: /<@&(\d{17,19})>/,
|
||||||
|
mentionChannel: /<#(\d{17,19})>/
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CommandArgumentMatchTypes =
|
||||||
|
| 'flag'
|
||||||
|
| 'mentionUser'
|
||||||
|
| 'mentionRole'
|
||||||
|
| 'mentionChannel'
|
||||||
|
| 'content'
|
||||||
|
| 'rest'
|
||||||
|
|
||||||
|
export interface Args<T = unknown> {
|
||||||
|
name: string
|
||||||
|
match: CommandArgumentMatchTypes
|
||||||
|
defaultValue?: T
|
||||||
|
flag?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseArgs(
|
||||||
|
commandArgs: Args[] | undefined,
|
||||||
|
messageArgs: string[]
|
||||||
|
): Record<string, unknown> | null {
|
||||||
|
if (commandArgs === undefined) return null
|
||||||
|
|
||||||
|
const messageArgsNullableCopy: Array<string | null> = [...messageArgs]
|
||||||
|
const args: Record<string, unknown> = {}
|
||||||
|
|
||||||
|
for (const entry of commandArgs) {
|
||||||
|
switch (entry.match) {
|
||||||
|
case 'flag':
|
||||||
|
parseFlags(args, entry, messageArgsNullableCopy)
|
||||||
|
break
|
||||||
|
case 'mentionUser':
|
||||||
|
case 'mentionRole':
|
||||||
|
case 'mentionChannel':
|
||||||
|
parseMention(args, entry, messageArgsNullableCopy)
|
||||||
|
break
|
||||||
|
case 'content':
|
||||||
|
parseContent(args, entry, messageArgs)
|
||||||
|
break
|
||||||
|
case 'rest':
|
||||||
|
parseRest(args, entry, messageArgsNullableCopy)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return args
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseFlags(
|
||||||
|
args: Record<string, unknown>,
|
||||||
|
entry: Args,
|
||||||
|
argsNullable: Array<string | null>
|
||||||
|
): void {
|
||||||
|
for (let i = 0; i < argsNullable.length; i++) {
|
||||||
|
if (entry.flag === argsNullable[i]) {
|
||||||
|
argsNullable[i] = null
|
||||||
|
args[entry.name] = true
|
||||||
|
break
|
||||||
|
} else args[entry.name] = entry.defaultValue ?? false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseMention(
|
||||||
|
args: Record<string, unknown>,
|
||||||
|
entry: Args,
|
||||||
|
argsNullable: Array<string | null>
|
||||||
|
): void {
|
||||||
|
const regex = mentionToRegex[entry.match]
|
||||||
|
const index = argsNullable.findIndex(
|
||||||
|
(x) => typeof x === 'string' && regex.test(x)
|
||||||
|
)
|
||||||
|
const regexMatches = regex.exec(argsNullable[index]!)
|
||||||
|
args[entry.name] =
|
||||||
|
regexMatches !== null
|
||||||
|
? regexMatches[0].replace(regex, '$1')
|
||||||
|
: entry.defaultValue
|
||||||
|
argsNullable[index] = null
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseContent(
|
||||||
|
args: Record<string, unknown>,
|
||||||
|
entry: Args,
|
||||||
|
argsNonNullable: Array<string | null>
|
||||||
|
): void {
|
||||||
|
args[entry.name] =
|
||||||
|
argsNonNullable.length > 0 ? argsNonNullable : entry.defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseRest(
|
||||||
|
args: Record<string, unknown>,
|
||||||
|
entry: Args,
|
||||||
|
argsNullable: Array<string | null>
|
||||||
|
): void {
|
||||||
|
const restValues = argsNullable.filter((x) => typeof x === 'string')
|
||||||
|
args[entry.name] =
|
||||||
|
restValues !== null ? restValues?.join(' ') : entry.defaultValue
|
||||||
|
}
|
150
test/argsparser_test.ts
Normal file
150
test/argsparser_test.ts
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
import { Args, parseArgs } from '../src/utils/command.ts'
|
||||||
|
import {
|
||||||
|
assertEquals,
|
||||||
|
assertNotEquals
|
||||||
|
} from 'https://deno.land/std@0.95.0/testing/asserts.ts'
|
||||||
|
|
||||||
|
const commandArgs: Args[] = [
|
||||||
|
{
|
||||||
|
name: 'originalMessage',
|
||||||
|
match: 'content'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'permaban',
|
||||||
|
match: 'flag',
|
||||||
|
flag: '--permanent',
|
||||||
|
defaultValue: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'user',
|
||||||
|
match: 'mentionUser'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'reason',
|
||||||
|
match: 'rest',
|
||||||
|
defaultValue: 'ree'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const messageArgs1: string[] = [
|
||||||
|
'<@!708544768342229012>',
|
||||||
|
'--permanent',
|
||||||
|
'bye',
|
||||||
|
'bye',
|
||||||
|
'Skyler'
|
||||||
|
]
|
||||||
|
const expectedResult1 = {
|
||||||
|
originalMessage: [
|
||||||
|
'<@!708544768342229012>',
|
||||||
|
'--permanent',
|
||||||
|
'bye',
|
||||||
|
'bye',
|
||||||
|
'Skyler'
|
||||||
|
],
|
||||||
|
permaban: true,
|
||||||
|
user: '708544768342229012',
|
||||||
|
reason: 'bye bye Skyler'
|
||||||
|
}
|
||||||
|
|
||||||
|
Deno.test({
|
||||||
|
only: false,
|
||||||
|
name: 'parse command arguments 1 (assertEquals)',
|
||||||
|
fn: () => {
|
||||||
|
const result = parseArgs(commandArgs, messageArgs1)
|
||||||
|
assertEquals(result, expectedResult1)
|
||||||
|
},
|
||||||
|
sanitizeOps: true,
|
||||||
|
sanitizeResources: true,
|
||||||
|
sanitizeExit: true
|
||||||
|
})
|
||||||
|
|
||||||
|
const messageArgs2: string[] = [
|
||||||
|
'<@!708544768342229012>',
|
||||||
|
'bye',
|
||||||
|
'bye',
|
||||||
|
'Skyler'
|
||||||
|
]
|
||||||
|
const expectedResult2 = {
|
||||||
|
originalMessage: ['<@!708544768342229012>', 'bye', 'bye', 'Skyler'],
|
||||||
|
permaban: true,
|
||||||
|
user: '708544768342229012',
|
||||||
|
reason: 'bye bye Skyler'
|
||||||
|
}
|
||||||
|
|
||||||
|
Deno.test({
|
||||||
|
name: 'parse command arguments 2 (assertEquals)',
|
||||||
|
fn: () => {
|
||||||
|
const result = parseArgs(commandArgs, messageArgs2)
|
||||||
|
assertEquals(result, expectedResult2)
|
||||||
|
},
|
||||||
|
sanitizeOps: true,
|
||||||
|
sanitizeResources: true,
|
||||||
|
sanitizeExit: true
|
||||||
|
})
|
||||||
|
|
||||||
|
const messageArgs3: string[] = [
|
||||||
|
'<@!708544768342229012>',
|
||||||
|
'bye',
|
||||||
|
'bye',
|
||||||
|
'Skyler'
|
||||||
|
]
|
||||||
|
const expectedResult3 = {
|
||||||
|
permaban: false,
|
||||||
|
user: '708544768342229012',
|
||||||
|
reason: 'bye bye Skyler'
|
||||||
|
}
|
||||||
|
|
||||||
|
Deno.test({
|
||||||
|
name: 'parse command arguments default value (assertNotEquals)',
|
||||||
|
fn: () => {
|
||||||
|
const result = parseArgs(commandArgs, messageArgs3)
|
||||||
|
assertNotEquals(result, expectedResult3)
|
||||||
|
},
|
||||||
|
sanitizeOps: true,
|
||||||
|
sanitizeResources: true,
|
||||||
|
sanitizeExit: true
|
||||||
|
})
|
||||||
|
|
||||||
|
const commandArgs2: Args[] = [
|
||||||
|
{
|
||||||
|
name: 'user',
|
||||||
|
match: 'mentionUser'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'channel',
|
||||||
|
match: 'mentionChannel'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'role',
|
||||||
|
match: 'mentionRole'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'reason',
|
||||||
|
match: 'rest',
|
||||||
|
defaultValue: 'ree'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const messageArgs4: string[] = [
|
||||||
|
'<@!708544768342229012>',
|
||||||
|
'bye',
|
||||||
|
'<#783319033730564098>',
|
||||||
|
'<@&836715188690092032>'
|
||||||
|
]
|
||||||
|
const expectedResult4 = {
|
||||||
|
channel: '783319033730564098',
|
||||||
|
role: '836715188690092032',
|
||||||
|
user: '708544768342229012',
|
||||||
|
reason: 'bye'
|
||||||
|
}
|
||||||
|
|
||||||
|
Deno.test({
|
||||||
|
name: 'parse command arguments mentions (assertEquals)',
|
||||||
|
fn: () => {
|
||||||
|
const result = parseArgs(commandArgs2, messageArgs4)
|
||||||
|
assertEquals(result, expectedResult4)
|
||||||
|
},
|
||||||
|
sanitizeOps: true,
|
||||||
|
sanitizeResources: true,
|
||||||
|
sanitizeExit: true
|
||||||
|
})
|
Loading…
Reference in a new issue