Skip to content

Commit b6e00d0

Browse files
committed
feat: argument handling
1 parent f050a8b commit b6e00d0

File tree

3 files changed

+145
-79
lines changed

3 files changed

+145
-79
lines changed

src/commands.ts

Lines changed: 8 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,20 @@ import {
99
type Harmonix,
1010
type HarmonixCommand,
1111
type HarmonixCommandInput,
12-
type CommandArg,
13-
type CommandExecuteOptions,
14-
ArgumentResolver
12+
type CommandArg
1513
} from './types'
1614
import { SlashCommandBuilder } from 'discord.js'
1715

1816
export const resolveHarmonixCommand = (
1917
cmd: HarmonixCommandInput,
2018
harmonixOptions: Harmonix['options']
21-
): HarmonixCommand<boolean> => {
19+
): HarmonixCommand<boolean, CommandArg[]> => {
2220
if (typeof cmd === 'string') {
2321
const _jiti = jiti(harmonixOptions.rootDir, {
2422
interopDefault: true
2523
})
2624
const _cmdPath = _jiti.resolve(cmd)
27-
const command = _jiti(_cmdPath) as HarmonixCommand<boolean>
25+
const command = _jiti(_cmdPath) as HarmonixCommand<boolean, CommandArg[]>
2826
const options: CommandOptions = {
2927
name: command.options.name || filename(_cmdPath).split('.')[0],
3028
category: command.options.category || filename(dirname(_cmdPath)),
@@ -38,14 +36,14 @@ export const resolveHarmonixCommand = (
3836
}
3937
}
4038

41-
export const defineCommand = <Slash extends boolean>(
42-
options: CommandOptions & { slash?: Slash },
43-
execute: CommandExecute<Slash>
44-
): HarmonixCommand<Slash> => {
39+
export const defineCommand = <Slash extends boolean, Args extends CommandArg[]>(
40+
options: CommandOptions & { slash?: Slash; args?: Args },
41+
execute: CommandExecute<Slash, Args>
42+
): HarmonixCommand<Slash, Args> => {
4543
return { options, execute }
4644
}
4745

48-
export const toJSON = (cmd: HarmonixCommand<true>) => {
46+
export const toJSON = (cmd: HarmonixCommand<true, CommandArg[]>) => {
4947
const builder = new SlashCommandBuilder()
5048
.setName(cmd.options.name!)
5149
.setDescription(cmd.options.description || 'No description provided')
@@ -101,14 +99,6 @@ export const toJSON = (cmd: HarmonixCommand<true>) => {
10199
.setRequired(arg.required!)
102100
)
103101
break
104-
case CommandArgType.Mentionable:
105-
builder.addMentionableOption((opt) =>
106-
opt
107-
.setName(arg.name)
108-
.setDescription(arg.description)
109-
.setRequired(arg.required!)
110-
)
111-
break
112102
case CommandArgType.Number:
113103
builder.addNumberOption((opt) =>
114104
opt
@@ -117,14 +107,6 @@ export const toJSON = (cmd: HarmonixCommand<true>) => {
117107
.setRequired(arg.required!)
118108
)
119109
break
120-
case CommandArgType.Attachment:
121-
builder.addAttachmentOption((opt) =>
122-
opt
123-
.setName(arg.name)
124-
.setDescription(arg.description)
125-
.setRequired(arg.required!)
126-
)
127-
break
128110
}
129111
}
130112
}
@@ -147,21 +129,3 @@ export const defineArgument = <
147129
required: options.required ?? true
148130
} as CommandArg
149131
}
150-
151-
export const useArguments = (
152-
options: CommandExecuteOptions
153-
): ArgumentResolver => {
154-
if (options.slash) {
155-
return {
156-
get: (name: string) => {
157-
return options.params.get(name)
158-
}
159-
}
160-
} else {
161-
return {
162-
get: (name: string) => {
163-
return options.params[name] || undefined
164-
}
165-
}
166-
}
167-
}

src/discord.ts

Lines changed: 121 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
1-
import { Client, REST, Routes, Events } from 'discord.js'
2-
import type { Harmonix, HarmonixCommand, HarmonixEvent } from './types'
1+
import {
2+
Client,
3+
REST,
4+
Routes,
5+
Events,
6+
type User,
7+
ApplicationCommandOptionType
8+
} from 'discord.js'
9+
import {
10+
type CommandArg,
11+
CommandArgType,
12+
type Harmonix,
13+
type HarmonixCommand,
14+
type HarmonixEvent,
15+
type MessageOrInteraction
16+
} from './types'
317
import 'dotenv/config'
418
import { toJSON } from './commands'
519

@@ -13,48 +27,64 @@ export const initCient = (harmonixOptions: Harmonix['options']) => {
1327

1428
export const registerCommands = (
1529
harmonix: Harmonix,
16-
commands: HarmonixCommand<false>[]
30+
commands: HarmonixCommand<false, CommandArg[]>[]
1731
) => {
18-
harmonix.client?.on(Events.MessageCreate, (message) => {
32+
harmonix.client?.on(Events.MessageCreate, async (message) => {
1933
if (message.author.bot) return
2034
const prefix = harmonix.options.defaultPrefix
2135
const args = message.content.slice(prefix.length).trim().split(/ +/)
2236
const command = args.shift()?.toLowerCase()
2337

2438
if (!command) return
2539
const cmd = commands.find((cmd) => cmd.options.name === command)
26-
27-
const params = Object.fromEntries(
28-
cmd?.options.args?.map((arg, i) => [arg.name, args[i]]) || []
40+
const resolvedArgs = await Promise.all(
41+
(cmd?.options.args || []).map(async (arg, i) => [
42+
arg.name,
43+
await resolveArgument(message, arg.type, args[i])
44+
])
2945
)
46+
const fullArgs = Object.fromEntries(resolvedArgs)
47+
3048
if (!cmd || cmd.options.slash) return
31-
cmd.execute(harmonix.client!, message, { slash: false, params: params })
49+
cmd.execute(harmonix.client!, message, { slash: false, args: fullArgs })
3250
})
3351
}
3452

3553
export const registerSlashCommands = async (
3654
harmonix: Harmonix,
37-
commands: HarmonixCommand<true>[]
55+
commands: HarmonixCommand<true, CommandArg[]>[]
3856
) => {
3957
if (commands.length === 0) return
40-
const rest = new REST().setToken(process.env.HARMONIX_CLIENT_TOKEN || '')
58+
const rest = new REST().setToken(process.env.HARMONIX_CLIENT_TOKEN!)
4159

4260
await rest.put(
4361
Routes.applicationCommands(
44-
harmonix.options.clientId || process.env.HARMONIX_CLIENT_ID || ''
62+
harmonix.options.clientId || process.env.HARMONIX_CLIENT_ID!
4563
),
4664
{ body: commands.map((cmd) => toJSON(cmd)) }
4765
)
48-
harmonix.client?.on('interactionCreate', (interaction) => {
66+
harmonix.client?.on('interactionCreate', async (interaction) => {
4967
if (!interaction.isChatInputCommand()) return
5068
const cmd = commands.find(
5169
(cmd) => cmd.options.name === interaction.commandName
5270
)
5371

5472
if (!cmd || !cmd.options.slash) return
73+
const resolvedArgs = await Promise.all(
74+
interaction.options.data.map(async (opt) => [
75+
opt.name,
76+
await resolveArgument(
77+
interaction,
78+
commandArgType(opt.type)!,
79+
String(opt.value)
80+
)
81+
])
82+
)
83+
const fullArgs = Object.fromEntries(resolvedArgs)
84+
5585
cmd.execute(harmonix.client!, interaction, {
5686
slash: true,
57-
params: interaction.options
87+
args: fullArgs
5888
})
5989
})
6090
}
@@ -68,3 +98,81 @@ export const registerEvents = (harmonix: Harmonix, events: HarmonixEvent[]) => {
6898
}
6999
}
70100
}
101+
102+
export const resolveArgument = async (
103+
entity: MessageOrInteraction,
104+
type: CommandArgType,
105+
value: string
106+
) => {
107+
switch (type) {
108+
case CommandArgType.String:
109+
return value
110+
case CommandArgType.Integer:
111+
return parseInt(value)
112+
case CommandArgType.Boolean:
113+
return value === 'true'
114+
case CommandArgType.User:
115+
const user = await resolveUser(entity, value)
116+
117+
return user
118+
case CommandArgType.Channel:
119+
const channel = await resolveChannel(entity, value)
120+
121+
return channel
122+
case CommandArgType.Role:
123+
const role = await resolveRole(entity, value)
124+
125+
return role
126+
case CommandArgType.Number:
127+
return parseFloat(value)
128+
}
129+
}
130+
131+
const resolveUser = async (
132+
entity: MessageOrInteraction,
133+
value: string
134+
): Promise<User | undefined> => {
135+
return entity.guild?.members.cache.find(
136+
(member) =>
137+
member.user.username === value ||
138+
member.nickname === value ||
139+
member.id === value ||
140+
value == `<@${member.id}>` ||
141+
value == `<@!${member.id}>`
142+
)?.user
143+
}
144+
145+
const resolveChannel = async (entity: MessageOrInteraction, value: string) => {
146+
return entity.guild?.channels.cache.find(
147+
(channel) =>
148+
channel.name === value ||
149+
channel.id === value ||
150+
value == `<#${channel.id}>`
151+
)
152+
}
153+
154+
const resolveRole = async (entity: MessageOrInteraction, value: string) => {
155+
return entity.guild?.roles.cache.find(
156+
(role) =>
157+
role.name === value || role.id === value || value == `<@&${role.id}>`
158+
)
159+
}
160+
161+
const commandArgType = (type: ApplicationCommandOptionType) => {
162+
switch (type) {
163+
case ApplicationCommandOptionType.String:
164+
return CommandArgType.String
165+
case ApplicationCommandOptionType.Integer:
166+
return CommandArgType.Integer
167+
case ApplicationCommandOptionType.Boolean:
168+
return CommandArgType.Boolean
169+
case ApplicationCommandOptionType.User:
170+
return CommandArgType.User
171+
case ApplicationCommandOptionType.Channel:
172+
return CommandArgType.Channel
173+
case ApplicationCommandOptionType.Role:
174+
return CommandArgType.Role
175+
case ApplicationCommandOptionType.Number:
176+
return CommandArgType.Number
177+
}
178+
}

src/types/commands.ts

Lines changed: 16 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {
1+
import type {
22
Client,
33
Message,
44
ChatInputCommandInteraction,
@@ -12,9 +12,7 @@ export enum CommandArgType {
1212
User = 'User',
1313
Channel = 'Channel',
1414
Role = 'Role',
15-
Mentionable = 'Mentionable',
16-
Number = 'Number',
17-
Attachment = 'Attachment'
15+
Number = 'Number'
1816
}
1917

2018
export interface HarmonixCommandArgType {
@@ -46,28 +44,19 @@ export type ExecuteArgument<Slash extends boolean> = Slash extends true
4644
? Message
4745
: MessageOrInteraction
4846

49-
export interface CommandExecuteOptions {
47+
export type CommandExecuteOptions<Args extends CommandArg[]> = {
5048
slash: boolean
51-
params: any
49+
args: {
50+
[K in Args[number]['name']]: any
51+
}
5252
}
5353

54-
export interface ArgumentResolver {
55-
get: <T>(name: string) => T
56-
}
57-
58-
export type CommandExecute<Slash extends boolean> = (
54+
export type CommandExecute<Slash extends boolean, Args extends CommandArg[]> = (
5955
client: Client,
6056
entity: ExecuteArgument<Slash>,
61-
options: CommandExecuteOptions
57+
options: CommandExecuteOptions<Args>
6258
) => void
6359

64-
export interface CommandContext {
65-
name: string
66-
category: string
67-
slash: boolean
68-
execute: CommandExecute<boolean>
69-
}
70-
7160
export interface CommandOptions {
7261
name?: string
7362
description?: string
@@ -82,9 +71,14 @@ export interface CommandOptions {
8271
cooldown?: number
8372
}
8473

85-
export type HarmonixCommandInput = string | HarmonixCommand<boolean>
74+
export type HarmonixCommandInput =
75+
| string
76+
| HarmonixCommand<boolean, CommandArg[]>
8677

87-
export interface HarmonixCommand<Slash extends boolean> {
78+
export interface HarmonixCommand<
79+
Slash extends boolean,
80+
Args extends CommandArg[]
81+
> {
8882
options: CommandOptions
89-
execute: CommandExecute<Slash>
83+
execute: CommandExecute<Slash, Args>
9084
}

0 commit comments

Comments
 (0)