Skip to content

Commit 650ec76

Browse files
swiizyydevramsean0vladfrangufavna
authored
feat(templates): add interaction handler templates (#216)
Co-authored-by: Sean Outram <outramsean0@gmail.com> Co-authored-by: Vlad Frangu <kingdgrizzle@gmail.com> Co-authored-by: Jeroen Claassens <jeroen.claassens@live.nl> Co-authored-by: Jeroen Claassens <support@favware.tech>
1 parent afa4afb commit 650ec76

15 files changed

+299
-4
lines changed

src/commands/generate.ts

+14-2
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,20 @@ async function fetchConfig() {
5050
* @returns A string with a hint for the user
5151
*/
5252
function parseCommonHints(component: string): string {
53-
if (component.toLowerCase() === 'command' || component.toLowerCase() === 'commands') {
54-
return `\nHint: You wrote "${component}", instead of "messagecommand", "slashcommand", or "contextmenucommand"`;
53+
const newLine = '\n';
54+
const lowerCaseComponent = component.toLowerCase();
55+
56+
if (lowerCaseComponent === 'command' || lowerCaseComponent === 'commands') {
57+
return `${newLine}Hint: You wrote "${component}", instead of "messagecommand", "slashcommand", or "contextmenucommand"`;
58+
}
59+
60+
if (
61+
lowerCaseComponent === 'interaction-handler' ||
62+
lowerCaseComponent === 'interaction-handlers' ||
63+
lowerCaseComponent === 'interactionhandler' ||
64+
lowerCaseComponent === 'interactionhandlers'
65+
) {
66+
return `${newLine}Hint: You wrote "${component}", instead of "buttoninteractionhandler", "autocompleteinteractionhandler", "modalinteractionhandler", or "selectmenuinteractionhandler"`;
5567
}
5668

5769
return '';

src/commands/init.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ export default async () => {
2323
arguments: response.arguments,
2424
commands: response.commands,
2525
listeners: response.listeners,
26-
preconditions: response.preconditions
26+
preconditions: response.preconditions,
27+
'interaction-handlers': response['interaction-handlers']
2728
},
2829
customFileTemplates: {
2930
enabled: response.cftEnabled,

src/prompts/PromptInit.ts

+7
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,12 @@ export const PromptInit = [
4949
message: 'Where do you store your preconditions? (do not include the base)',
5050
initial: 'preconditions'
5151
},
52+
{
53+
type: 'text',
54+
name: 'interaction-handlers',
55+
message: 'Where do you store your interaction handlers? (do not include the base)',
56+
initial: 'interaction-handlers'
57+
},
5258
{
5359
type: 'confirm',
5460
name: 'cftEnabled',
@@ -70,5 +76,6 @@ export type PromptInitObjectKeys =
7076
| 'listeners'
7177
| 'arguments'
7278
| 'preconditions'
79+
| 'interaction-handlers'
7380
| 'cftEnabled'
7481
| 'cftLocation';

templates/.sapphirerc.json.sapphire

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"commands": "commands",
77
"listeners": "listeners",
88
"preconditions": "preconditions",
9+
"interaction-handlers": "interaction-handlers",
910
"routes": "routes"
1011
},
1112
"customFileTemplates": {

templates/.sapphirerc.yml.sapphire

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ locations:
55
commands: commands
66
listeners: listeners
77
preconditions: preconditions
8+
interaction-handlers: interaction-handlers
89
routes: routes
910
customFileTemplates:
1011
enabled: false
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
{ "category": "interaction-handlers" }
2+
---
3+
const { InteractionHandler, InteractionHandlerTypes } = require('@sapphire/framework');
4+
const { InteractionHandler, InteractionHandlerTypes } = require('@sapphire/framework');
5+
6+
class AutocompleteHandler extends InteractionHandler {
7+
/**
8+
* @param {InteractionHandler.Context} context
9+
* @param {InteractionHandler.Options} options
10+
*/
11+
constructor(context, options) {
12+
super(context, {
13+
...options,
14+
interactionHandlerType: InteractionHandlerTypes.Autocomplete
15+
});
16+
}
17+
18+
/**
19+
* @param {import('discord.js').AutocompleteInteraction} interaction
20+
* @param {import('discord.js').ApplicationCommandOptionChoiceData[]} result
21+
*/
22+
async run(interaction, result) {
23+
return interaction.respond(result);
24+
}
25+
26+
/**
27+
* @param {import('discord.js').AutocompleteInteraction} interaction
28+
*/
29+
async parse(interaction) {
30+
// Only run this interaction for the command with ID '1000000000000000000'
31+
if (interaction.commandId !== '1000000000000000000') return this.none();
32+
// Get the focussed (current) option
33+
const focusedOption = interaction.options.getFocused(true);
34+
// Ensure that the option name is one that can be autocompleted, or return none if not.
35+
switch (focusedOption.name) {
36+
case 'search': {
37+
// Search your API or similar. This is example code!
38+
const searchResult = await myApi.searchForSomething(focusedOption.value);
39+
// Map the search results to the structure required for Autocomplete
40+
return this.some(searchResult.map((match) => ({ name: match.name, value: match.key })));
41+
}
42+
default:
43+
return this.none();
44+
}
45+
}
46+
}
47+
48+
module.exports = {
49+
AutocompleteHandler
50+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{ "category": "interaction-handlers" }
2+
---
3+
import { ApplyOptions } from '@sapphire/decorators';
4+
import { InteractionHandler, InteractionHandlerTypes } from '@sapphire/framework';
5+
import { AutocompleteInteraction, type ApplicationCommandOptionChoiceData } from 'discord.js';
6+
7+
@ApplyOptions<InteractionHandler.Options>({
8+
interactionHandlerType: InteractionHandlerTypes.Autocomplete
9+
})
10+
export class AutocompleteHandler extends InteractionHandler {
11+
public override async run(interaction: AutocompleteInteraction, result: ApplicationCommandOptionChoiceData[]) {
12+
return interaction.respond(result);
13+
}
14+
15+
public override async parse(interaction: AutocompleteInteraction) {
16+
// Only run this interaction for the command with ID '1000000000000000000'
17+
if (interaction.commandId !== '1000000000000000000') return this.none();
18+
// Get the focussed (current) option
19+
const focusedOption = interaction.options.getFocused(true);
20+
// Ensure that the option name is one that can be autocompleted, or return none if not.
21+
switch (focusedOption.name) {
22+
case 'search': {
23+
// Search your API or similar. This is example code!
24+
const searchResult = await myApi.searchForSomething(focusedOption.value);
25+
// Map the search results to the structure required for Autocomplete
26+
return this.some(searchResult.map((match) => ({ name: match.name, value: match.key })));
27+
}
28+
default:
29+
return this.none();
30+
}
31+
}
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
{ "category": "interaction-handlers" }
2+
---
3+
const { InteractionHandler, InteractionHandlerTypes } = require('@sapphire/framework');
4+
5+
class ButtonHandler extends InteractionHandler {
6+
/**
7+
* @param {InteractionHandler.Context} context
8+
* @param {InteractionHandler.Options} options
9+
*/
10+
constructor(context, options) {
11+
super(context, {
12+
...options,
13+
interactionHandlerType: InteractionHandlerTypes.Button
14+
});
15+
}
16+
17+
/**
18+
* @param {import('discord.js').ButtonInteraction} interaction
19+
*/
20+
async run(interaction) {
21+
await interaction.reply({
22+
content: 'Hello from a button interaction handler!',
23+
// Let's make it so only the person who pressed the button can see this message!
24+
ephemeral: true
25+
});
26+
}
27+
28+
/**
29+
* @param {import('discord.js').ButtonInteraction} interaction
30+
*/
31+
parse(interaction) {
32+
if (interaction.customId !== 'my-awesome-button') return this.none();
33+
return this.some();
34+
}
35+
}
36+
37+
module.exports = {
38+
ButtonHandler
39+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{ "category": "interaction-handlers" }
2+
---
3+
import { ApplyOptions } from '@sapphire/decorators';
4+
import { InteractionHandler, InteractionHandlerTypes } from '@sapphire/framework';
5+
import type { ButtonInteraction } from 'discord.js';
6+
7+
@ApplyOptions<InteractionHandler.Options>({
8+
interactionHandlerType: InteractionHandlerTypes.Button
9+
})
10+
export class ButtonHandler extends InteractionHandler {
11+
public async run(interaction: ButtonInteraction) {
12+
await interaction.reply({
13+
content: 'Hello from a button interaction handler!',
14+
// Let's make it so only the person who pressed the button can see this message!
15+
ephemeral: true
16+
});
17+
}
18+
19+
public override parse(interaction: ButtonInteraction) {
20+
if (interaction.customId !== 'my-awesome-button') return this.none();
21+
22+
return this.some();
23+
}
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
{ "category": "interaction-handlers" }
2+
---
3+
const { InteractionHandler, InteractionHandlerTypes } = require('@sapphire/framework');
4+
5+
class ModalHandler extends InteractionHandler {
6+
/**
7+
* @param {InteractionHandler.Context} context
8+
* @param {InteractionHandler.Options} options
9+
*/
10+
constructor(context, options) {
11+
super(context, {
12+
...options,
13+
interactionHandlerType: InteractionHandlerTypes.ModalSubmit
14+
});
15+
}
16+
17+
/**
18+
* @param {import('discord.js').ModalSubmitInteraction} interaction
19+
*/
20+
async run(interaction) {
21+
await interaction.reply({
22+
content: 'Thank you for submitting the form!',
23+
ephemeral: true
24+
});
25+
}
26+
27+
/**
28+
* @param {import('discord.js').ModalSubmitInteraction} interaction
29+
*/
30+
parse(interaction) {
31+
if (interaction.customId !== 'hello-popup') return this.none();
32+
33+
return this.some();
34+
}
35+
}
36+
37+
module.exports = {
38+
ModalHandler
39+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{ "category": "interaction-handlers" }
2+
---
3+
import { ApplyOptions } from '@sapphire/decorators';
4+
import { InteractionHandler, InteractionHandlerTypes } from '@sapphire/framework';
5+
import type { ModalSubmitInteraction } from 'discord.js';
6+
7+
@ApplyOptions<InteractionHandler.Options>({
8+
interactionHandlerType: InteractionHandlerTypes.ModalSubmit
9+
})
10+
export class ModalHandler extends InteractionHandler {
11+
public async run(interaction: ModalSubmitInteraction) {
12+
await interaction.reply({
13+
content: 'Thank you for submitting the form!',
14+
ephemeral: true
15+
});
16+
}
17+
18+
public override parse(interaction: ModalSubmitInteraction) {
19+
if (interaction.customId !== 'hello-popup') return this.none();
20+
21+
return this.some();
22+
}
23+
}

templates/components/route.js.sapphire

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{ "category": "routes" }
22
---
33
const { methods, Route } = require('@sapphire/plugin-api');
4+
45
class UserRoute extends Route {
56
/**
67
* @param {Route.Context} context
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
{ "category": "interaction-handlers" }
2+
---
3+
const { InteractionHandler, InteractionHandlerTypes } = require('@sapphire/framework');
4+
5+
class MenuHandler extends InteractionHandler {
6+
/**
7+
* @param {InteractionHandler.Context} context
8+
* @param {InteractionHandler.Options} options
9+
*/
10+
constructor(context, options) {
11+
super(context, {
12+
...options,
13+
interactionHandlerType: InteractionHandlerTypes.SelectMenu
14+
});
15+
}
16+
17+
/**
18+
* @param {import('discord.js').StringSelectMenuInteraction} interaction
19+
*/
20+
async run(interaction) {
21+
await interaction.reply({
22+
// Remember how we can have multiple values? Let's get the first one!
23+
content: `You selected: ${interaction.values[0]}`
24+
});
25+
}
26+
27+
/**
28+
* @param {import('discord.js').StringSelectMenuInteraction} interaction
29+
*/
30+
parse(interaction) {
31+
if (interaction.customId !== 'my-echo-select') return this.none();
32+
33+
return this.some();
34+
}
35+
}
36+
37+
module.exports = {
38+
MenuHandler
39+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{ "category": "interaction-handlers" }
2+
---
3+
import { ApplyOptions } from '@sapphire/decorators';
4+
import { InteractionHandler, InteractionHandlerTypes } from '@sapphire/framework';
5+
import type { StringSelectMenuInteraction } from 'discord.js';
6+
7+
@ApplyOptions<InteractionHandler.Options>({
8+
interactionHandlerType: InteractionHandlerTypes.SelectMenu
9+
})
10+
export class MenuHandler extends InteractionHandler {
11+
public override async run(interaction: StringSelectMenuInteraction) {
12+
await interaction.reply({
13+
// Remember how we can have multiple values? Let's get the first one!
14+
content: `You selected: ${interaction.values[0]}`
15+
});
16+
}
17+
18+
public override parse(interaction: StringSelectMenuInteraction) {
19+
if (interaction.customId !== 'my-echo-select') return this.none();
20+
21+
return this.some();
22+
}
23+
}

templates/schemas/.sapphirerc.scheme.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,14 @@
2727
"preconditions": {
2828
"type": "string"
2929
},
30+
"interaction-handlers": {
31+
"type": "string"
32+
},
3033
"routes": {
3134
"type": "string"
3235
}
3336
},
34-
"required": ["base", "arguments", "commands", "listeners", "preconditions"]
37+
"required": ["base", "arguments", "commands", "listeners", "preconditions", "interaction-handlers"]
3538
},
3639
"customFileTemplates": {
3740
"description": "Settings about custom component (piece) templates",

0 commit comments

Comments
 (0)