Skip to content

Commit 7c74fd2

Browse files
authored
Merge pull request #40 from underctrl-io/buttonkit-feat
Add `onEnd` to ButtonKit
2 parents b9dac70 + 7260c7c commit 7c74fd2

File tree

5 files changed

+82
-41
lines changed

5 files changed

+82
-41
lines changed

apps/docs/pages/guide/buttonkit.mdx

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ ButtonKit is an enhanced version of the native Discord.js [`ButtonBuilder`](http
66

77
It is not recommended to use this to listen for button clicks forever since it creates collectors. For that purpose, it's recommended to use a regular "interactionCreate" event listener.
88

9-
## Usage
9+
## Handle button clicks
1010

1111
<Tabs items={['CommonJS', 'ESM', 'TypeScript']}>
1212
<Tabs.Tab>
@@ -98,7 +98,7 @@ In the above example, you may notice how similar `ButtonKit` is to the native Di
9898

9999
Here's an empty `onClick` method without any arguments:
100100

101-
```js
101+
```js copy
102102
const myButton = new ButtonKit()
103103
.setCustomId('custom_button')
104104
.setLabel('Click me!')
@@ -109,15 +109,15 @@ myButton.onClick();
109109

110110
The first argument required by this function is your handler function which will acknowledge button clicks (interactions) handled by ButtonKit. You can handle them like so:
111111

112-
```js
112+
```js copy
113113
myButton.onClick((buttonInteraction) => {
114114
buttonInteraction.reply('You clicked a button!');
115115
});
116116
```
117117

118-
However, the code above won't actually work since ButtonKit doesn't have any idea where it's supposed to specifically listen button clicks from. To fix that, you need to pass in a second argument which houses all the collector options. The `message` property is the message that ButtonKit will use to listen for button clicks.
118+
However, the code above won't actually work since ButtonKit doesn't have any idea where it's supposed to specifically listen button clicks from. To fix that, you need to pass in a second argument, also known as your [options](/guide/buttonkit#buttonkit-onclick-options) which houses all the collector configuration. The `message` property is the message that ButtonKit will use to listen for button clicks.
119119

120-
```js
120+
```js copy
121121
const row = new ActionRowBuilder().addComponents(myButton);
122122
const message = await channel.send({ components: [row] });
123123

@@ -131,7 +131,7 @@ myButton.onClick(
131131

132132
This also works with interaction replies. Just ensure you pass `fetchReply` alongside your components:
133133

134-
```js
134+
```js copy
135135
const row = new ActionRowBuilder().addComponents(myButton);
136136
const message = await interaction.reply({ components: [row], fetchReply: true });
137137

@@ -143,7 +143,7 @@ myButton.onClick(
143143
);
144144
```
145145

146-
## ButtonKit options
146+
## ButtonKit `onClick` options
147147

148148
### `message`
149149

@@ -154,6 +154,7 @@ The message object that ButtonKit uses to listen for button clicks (interactions
154154
### `time` (optional)
155155

156156
- Type: `number`
157+
- Default: `86400000`
157158

158159
The duration (in ms) the collector should run for and listen for button clicks.
159160

@@ -166,3 +167,36 @@ Whether or not the collector should automatically reset the timer when a button
166167
### Additional optional options
167168

168169
- Type: [`InteractionCollectorOptions`](https://old.discordjs.dev/#/docs/discord.js/main/typedef/InteractionCollectorOptions)
170+
171+
## Handle collector end
172+
173+
<Callout type="warning">
174+
This feature is currently only available in the [development
175+
version](/guide/installation#development-version)
176+
</Callout>
177+
178+
When setting up an `onClick()` method using ButtonKit, you may also want to run some code after the collector ends running. The default timeout is 1 day, but you can modify this in [onClickOptions#time](/guide/buttonkit#time-optional). To handle when the collector ends, you can setup an `onEnd()` method like this:
179+
180+
```js copy {16-21}
181+
const myButton = new ButtonKit()
182+
.setCustomId('custom_button')
183+
.setLabel('Click me!')
184+
.setStyle(ButtonStyle.Primary);
185+
186+
const row = new ActionRowBuilder().addComponents(myButton);
187+
const message = await interaction.reply({ components: [row], fetchReply: true });
188+
189+
myButton
190+
.onClick(
191+
(buttonInteraction) => {
192+
buttonInteraction.reply('You clicked a button!');
193+
},
194+
{ message },
195+
)
196+
.onEnd(() => {
197+
console.log('Button collector ended.');
198+
199+
myButton.setDisabled(true);
200+
message.edit({ components: [row] });
201+
});
202+
```

packages/commandkit/src/components/ButtonKit.ts

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@ import {
1515
* If the first argument is null, it means that the interaction collector has been destroyed.
1616
*/
1717
export type CommandKitButtonBuilderInteractionCollectorDispatch = (
18-
interaction: ButtonInteraction | null,
18+
interaction: ButtonInteraction,
1919
) => Awaitable<void>;
2020

21+
export type CommandKitButtonBuilderOnEnd = () => Awaitable<void>;
22+
2123
export type CommandKitButtonBuilderInteractionCollectorDispatchContextData = {
2224
/**
2325
* The message to listen for button interactions on.
@@ -35,6 +37,7 @@ export type CommandKitButtonBuilderInteractionCollectorDispatchContextData = {
3537

3638
export class ButtonKit extends ButtonBuilder {
3739
#onClickHandler: CommandKitButtonBuilderInteractionCollectorDispatch | null = null;
40+
#onEndHandler: CommandKitButtonBuilderOnEnd | null = null;
3841
#contextData: CommandKitButtonBuilderInteractionCollectorDispatchContextData | null = null;
3942
#collector: InteractionCollector<ButtonInteraction> | null = null;
4043

@@ -63,29 +66,38 @@ export class ButtonKit extends ButtonBuilder {
6366
* button.onClick(null);
6467
* ```
6568
*/
66-
public onClick(handler: null): this;
6769
public onClick(
6870
handler: CommandKitButtonBuilderInteractionCollectorDispatch,
69-
data: CommandKitButtonBuilderInteractionCollectorDispatchContextData,
70-
): this;
71-
public onClick(
72-
handler: CommandKitButtonBuilderInteractionCollectorDispatch | null,
7371
data?: CommandKitButtonBuilderInteractionCollectorDispatchContextData,
7472
): this {
7573
if (this.data.style === ButtonStyle.Link) {
7674
throw new TypeError('Cannot setup "onClick" handler on link buttons.');
7775
}
7876

77+
if (!handler) {
78+
throw new TypeError('Cannot setup "onClick" without a handler function parameter.');
79+
}
80+
7981
this.#destroyCollector();
8082

8183
this.#onClickHandler = handler;
82-
if (handler && data) this.#contextData = data;
84+
if (data) this.#contextData = data;
8385

8486
this.#setupInteractionCollector();
8587

8688
return this;
8789
}
8890

91+
public onEnd(handler: CommandKitButtonBuilderOnEnd): this {
92+
if (!handler) {
93+
throw new TypeError('Cannot setup "onEnd" without a handler function parameter.');
94+
}
95+
96+
this.#onEndHandler = handler;
97+
98+
return this;
99+
}
100+
89101
#setupInteractionCollector() {
90102
if (!this.#contextData || !this.#onClickHandler) return;
91103

@@ -133,11 +145,11 @@ export class ButtonKit extends ButtonBuilder {
133145

134146
this.#collector.on('end', () => {
135147
this.#destroyCollector();
148+
this.#onEndHandler?.();
136149
});
137150
}
138151

139152
#destroyCollector() {
140-
this.#onClickHandler?.(null);
141153
this.#collector?.stop('end');
142154
this.#collector?.removeAllListeners();
143155
this.#collector = null;

packages/commandkit/tests/src/commands/misc/ping.ts

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ const tests = Array.from({ length: 10 }, (_, i) => ({
2828

2929
export async function autocomplete({ interaction }: AutocompleteProps) {
3030
const arg = interaction.options.getString('test', false);
31+
console.log(arg);
3132
if (!arg) return interaction.respond(tests);
3233

3334
const filtered = tests.filter((test) => test.name.toLowerCase().includes(arg.toLowerCase()));
@@ -51,30 +52,26 @@ export async function run({ interaction, client }: SlashCommandProps) {
5152
fetchReply: true,
5253
});
5354

54-
button.onClick(
55-
(inter) => {
56-
if (!inter) {
57-
button.setDisabled(true);
58-
message.edit({
59-
components: [row],
60-
});
61-
return;
62-
}
55+
button
56+
.onClick(
57+
(inter) => {
58+
console.log('onClick called');
6359

64-
inter.reply({
65-
content: 'You clicked the ping button!',
66-
ephemeral: true,
67-
});
68-
},
69-
{ message, time: 10_000, autoReset: true },
70-
);
60+
inter.reply({
61+
content: 'You clicked the ping button!',
62+
ephemeral: true,
63+
});
64+
},
65+
{ message, time: 10_000, autoReset: true },
66+
)
67+
.onEnd(() => {
68+
console.log('onEnd called');
7169

72-
// interaction.reply(`:ping_pong: Pong! \`${client.ws.ping}ms\``);
70+
button.setDisabled(true);
71+
message.edit({ components: [row] });
72+
});
7373
}
7474

7575
export const options: CommandOptions = {
7676
devOnly: true,
77-
78-
// Test deprecation warning.
79-
guildOnly: true,
8077
};

packages/commandkit/tests/src/index.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import { CommandKit } from '../../src/index';
22
import { Client } from 'discord.js';
33

4-
require('dotenv/config');
5-
64
const client = new Client({
75
intents: ['Guilds', 'GuildMembers', 'GuildMessages', 'MessageContent'],
86
});

packages/commandkit/tests/src/validations/devOnly.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import type { ValidationProps } from '../../../src';
22

33
export default function ({ interaction, commandObj, handler }: ValidationProps) {
44
if (interaction.isAutocomplete()) return;
5-
if (commandObj.data.name === 'ping') {
6-
interaction.reply('blocked...');
7-
return true;
8-
}
5+
// if (commandObj.data.name === 'ping') {
6+
// interaction.reply('blocked...');
7+
// return true;
8+
// }
99
}

0 commit comments

Comments
 (0)