Skip to content

Commit 86fed21

Browse files
committed
feat: enhance reply function to support reactive options
1 parent ab81d28 commit 86fed21

File tree

2 files changed

+99
-55
lines changed

2 files changed

+99
-55
lines changed

src/runtime/server/utils/reply.ts

Lines changed: 96 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,119 @@
1-
import type { BaseMessageOptions, InteractionEditReplyOptions, InteractionReplyOptions } from 'discord.js'
1+
import type { BaseMessageOptions, CommandInteraction, InteractionCallbackResponse, InteractionEditReplyOptions, InteractionReplyOptions, InteractionResponse, Message } from 'discord.js'
2+
import type { MaybeRef } from 'vue'
23
import type { SlashCommandCustomReturnHandler } from '../../../types'
34
import { MessageFlags } from 'discord.js'
5+
import { isRef, reactive, watch } from 'vue'
46

57
type OmitPreservingCallSignature<T, K extends keyof T = keyof T>
68
= Omit<T, K> & (T extends (...args: infer R) => infer S ? (...args: R) => S : unknown)
79

810
type File = NonNullable<BaseMessageOptions['files']>[number]
911

1012
type ReplyFunction = SlashCommandCustomReturnHandler & {
11-
(text?: string, options?: InteractionReplyOptions): SlashCommandCustomReturnHandler
12-
edit: EditReplyFunction
13-
ephemeral: OmitPreservingCallSignature<ReplyFunction, 'edit' | 'ephemeral'>
14-
flags: (flags: InteractionReplyOptions['flags']) => OmitPreservingCallSignature<ReplyFunction, 'edit'>
15-
file: (file: File) => OmitPreservingCallSignature<ReplyFunction, 'edit'>
16-
files: (files: File[]) => OmitPreservingCallSignature<ReplyFunction, 'edit'>
17-
send: ReplyFunction
18-
}
13+
(text?: MaybeRef<string>, options?: InteractionReplyOptions):
14+
SlashCommandCustomReturnHandler & {
15+
[Symbol.iterator]: () => {
16+
next: (...args: ReadonlyArray<any>) => IteratorResult<
17+
ReplyFunction,
18+
Message | InteractionResponse | InteractionCallbackResponse
19+
>
20+
}
21+
}
22+
23+
[Symbol.iterator]: () => {
24+
next: (...args: ReadonlyArray<any>) => IteratorResult<
25+
never,
26+
Promise<Message | InteractionResponse | InteractionCallbackResponse>
27+
>
28+
}
1929

20-
type EditReplyFunction = SlashCommandCustomReturnHandler & {
21-
(text?: string, options?: InteractionEditReplyOptions): SlashCommandCustomReturnHandler
22-
flags: (flags: InteractionEditReplyOptions['flags']) => EditReplyFunction
23-
file: (file: File) => EditReplyFunction
24-
files: (files: File[]) => EditReplyFunction
25-
send: EditReplyFunction
30+
ephemeral: OmitPreservingCallSignature<ReplyFunction, 'ephemeral'>
31+
flags: (flags: MaybeRef<InteractionReplyOptions['flags']>) => ReplyFunction
32+
file: (file: MaybeRef<File>) => ReplyFunction
33+
files: (files: MaybeRef<File[]>) => ReplyFunction
34+
send: ReplyFunction
2635
}
2736

2837
function createReplyFunction(
29-
{ editReply, ...defaultOptions }: (
30-
| InteractionReplyOptions
31-
| InteractionEditReplyOptions
32-
) & { editReply?: boolean } = {},
38+
{ ...defaultOptions }: (InteractionReplyOptions) = {},
3339
): ReplyFunction {
40+
type Msg = Message | InteractionCallbackResponse | InteractionResponse
41+
3442
const reply = ((...args) => {
35-
if (typeof args[0] === 'string' || typeof args[0] === 'undefined') {
43+
if (typeof args[0] === 'string' || typeof args[0] === 'undefined' || isRef(args[0]) || typeof args[0] === 'function') {
3644
const [text, options] = args
37-
return async (interaction) => {
38-
if (editReply || interaction.deferred || interaction.replied) {
39-
await interaction.editReply({
40-
content: text,
41-
...(defaultOptions as InteractionEditReplyOptions),
42-
...(options as InteractionEditReplyOptions),
43-
})
44-
}
45-
else {
46-
await interaction.reply({
47-
content: text,
48-
...(defaultOptions as InteractionReplyOptions),
49-
...(options as InteractionReplyOptions),
50-
})
51-
}
45+
let message: Promise<Msg>
46+
47+
const replyOptions = reactive({
48+
content: text,
49+
...defaultOptions,
50+
...options,
51+
}) as InteractionReplyOptions
52+
53+
const handler = (interaction: CommandInteraction) => {
54+
const method = interaction.replied
55+
? 'followUp'
56+
: interaction.deferred ? 'editReply' : 'reply'
57+
message = interaction[method](
58+
replyOptions as InteractionReplyOptions & InteractionEditReplyOptions,
59+
).catch(err => err)
60+
return message
5261
}
62+
63+
Object.defineProperty(handler, Symbol.iterator, {
64+
value: () => ({
65+
next: () => (
66+
message !== undefined
67+
? {
68+
done: true,
69+
value: message,
70+
}
71+
: {
72+
done: false,
73+
value: handler,
74+
}
75+
),
76+
}),
77+
})
78+
79+
watch(replyOptions, async (options) => {
80+
const msg = await message
81+
if ('edit' in msg)
82+
msg.edit(options as InteractionEditReplyOptions)
83+
else
84+
msg.resource?.message?.edit(options as InteractionEditReplyOptions)
85+
}, { deep: true, flush: 'sync' })
86+
87+
return handler
5388
}
5489
else {
5590
const interaction = args[0]
56-
if (editReply || interaction.deferred || interaction.replied) {
57-
return interaction.editReply({
58-
...(defaultOptions as InteractionEditReplyOptions),
59-
}).then(() => undefined)
60-
}
61-
else {
62-
return interaction.reply({
63-
...(defaultOptions as InteractionReplyOptions),
64-
}).then(() => undefined)
91+
const method = interaction.replied
92+
? 'followUp'
93+
: interaction.deferred ? 'editReply' : 'reply'
94+
const options = reactive(defaultOptions) as InteractionReplyOptions
95+
const messagePromise = interaction[method](options as InteractionReplyOptions & InteractionEditReplyOptions)
96+
.catch(err => err)
97+
98+
watch(options, async (opts) => {
99+
const message = await messagePromise
100+
if (!message)
101+
return
102+
if ('edit' in message)
103+
message.edit(opts as InteractionEditReplyOptions)
104+
else
105+
message.resource?.message?.edit(opts as InteractionEditReplyOptions)
106+
}, { deep: true, flush: 'sync' })
107+
108+
return {
109+
[Symbol.iterator]: () => ({
110+
next: () => (
111+
{
112+
done: true,
113+
value: messagePromise,
114+
}
115+
),
116+
}),
65117
}
66118
}
67119
}) as ReplyFunction
@@ -92,14 +144,6 @@ function createReplyFunction(
92144
})
93145
},
94146
},
95-
edit: {
96-
get() {
97-
return createReplyFunction({
98-
...defaultOptions,
99-
editReply: true,
100-
})
101-
},
102-
},
103147
file: {
104148
get() {
105149
return (file: File) => createReplyFunction({

src/types.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { DiscordClient } from '#build/types/nitro-imports'
22
import type { ChatInputCommandInteraction, ClientOptions } from 'discord.js'
33
import type { ListenOptions } from 'listhen'
4+
import type { MaybeRef } from 'vue'
45
import type { IntegerOption, NumberOption, StringOption } from './runtime/server/utils/describeOption'
56
import { ApplicationCommandOptionType } from 'discord.js'
67

@@ -136,14 +137,13 @@ export type SlashCommandOption
136137
// | SlashCommandOptionBase
137138

138139
export type SlashCommandCustomReturnHandler
139-
= (this: DiscordClient, interaction: ChatInputCommandInteraction, client: DiscordClient) => SlashCommandReturnType
140+
= (this: DiscordClient, interaction: ChatInputCommandInteraction, client: DiscordClient) => unknown
140141

141142
export type SlashCommandReturnType
142143
= | void
143-
| string
144+
| MaybeRef<string>
144145
| SlashCommandCustomReturnHandler
145146
| Promise<SlashCommandReturnType>
146-
// TODO: type TNext
147147
| Generator<SlashCommandReturnType>
148148
| AsyncGenerator<SlashCommandReturnType>
149149

0 commit comments

Comments
 (0)