meocord 2.1.1 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AUTHOR.md +2 -3
- package/README.md +289 -41
- package/dist/cjs/_shared/controller.decorator-MUHA_A3z.cjs +529 -0
- package/dist/cjs/core/index.cjs +288 -85
- package/dist/cjs/decorator/index.cjs +5 -1
- package/dist/cjs/enum/index.cjs +17 -4
- package/dist/cjs/testing/index.cjs +88 -4
- package/dist/esm/bin/builder-template/builder/primary-entry-point.builder.template +26 -0
- package/dist/esm/bin/builder-template/controller/autocomplete.controller.template +16 -0
- package/dist/esm/bin/builder-template/controller/channel-select-menu.controller.template +12 -0
- package/dist/esm/bin/builder-template/controller/context-menu.controller.template +2 -2
- package/dist/esm/bin/builder-template/controller/mentionable-select-menu.controller.template +12 -0
- package/dist/esm/bin/builder-template/controller/modal-submit.controller.template +1 -1
- package/dist/esm/bin/builder-template/controller/primary-entry-point.controller.template +12 -0
- package/dist/esm/bin/builder-template/controller/role-select-menu.controller.template +12 -0
- package/dist/esm/bin/builder-template/controller/slash.controller.template +1 -1
- package/dist/esm/bin/builder-template/controller/user-select-menu.controller.template +12 -0
- package/dist/esm/bin/generator.js +4 -9
- package/dist/esm/bin/helper/controller-generator.helper.js +24 -7
- package/dist/esm/core/meocord.app.js +289 -85
- package/dist/esm/decorator/controller.decorator.js +157 -24
- package/dist/esm/decorator/index.js +1 -1
- package/dist/esm/enum/controller.enum.js +23 -4
- package/dist/esm/testing/mock-interaction.js +89 -5
- package/dist/esm/util/interaction.util.js +174 -0
- package/dist/types/controller.enum-DYfhYaat.d.ts +36 -0
- package/dist/types/core/index.d.ts +94 -0
- package/dist/types/decorator/index.d.ts +74 -41
- package/dist/types/enum/index.d.ts +1 -1
- package/dist/types/interface/index.d.ts +87 -3
- package/dist/types/testing/index.d.ts +3 -21
- package/package.json +2 -2
- package/dist/cjs/_shared/controller.decorator-DX5lFlPZ.cjs +0 -216
- package/dist/types/controller.enum-QA-IuReF.d.ts +0 -18
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ApplicationCommandType,
|
|
3
|
+
ApplicationIntegrationType,
|
|
4
|
+
EntryPointCommandHandlerType,
|
|
5
|
+
InteractionContextType,
|
|
6
|
+
} from 'discord.js'
|
|
7
|
+
import { CommandBuilder } from 'meocord/decorator'
|
|
8
|
+
import { CommandType } from 'meocord/enum'
|
|
9
|
+
|
|
10
|
+
// Entry point commands have no builder class in @discordjs/builders, so the raw REST
|
|
11
|
+
// body is returned instead — MeoCord registers it as-is. `AppHandler` is what makes
|
|
12
|
+
// Discord send the interaction here; `DiscordLaunchActivity` opens the activity without
|
|
13
|
+
// ever reaching the bot.
|
|
14
|
+
@CommandBuilder(CommandType.PRIMARY_ENTRY_POINT)
|
|
15
|
+
export class SampleCommandBuilder {
|
|
16
|
+
build() {
|
|
17
|
+
return {
|
|
18
|
+
type: ApplicationCommandType.PrimaryEntryPoint as const,
|
|
19
|
+
name: 'sample-entry-point',
|
|
20
|
+
description: 'Launch the activity',
|
|
21
|
+
handler: EntryPointCommandHandlerType.AppHandler,
|
|
22
|
+
integration_types: [ApplicationIntegrationType.GuildInstall],
|
|
23
|
+
contexts: [InteractionContextType.Guild],
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { AutocompleteInteraction } from 'discord.js'
|
|
2
|
+
import { Controller, Autocomplete } from 'meocord/decorator'
|
|
3
|
+
|
|
4
|
+
@Controller()
|
|
5
|
+
export class {{className}}AutocompleteController {
|
|
6
|
+
|
|
7
|
+
// The option this completes must be declared with `.setAutocomplete(true)` on the
|
|
8
|
+
// slash command builder, otherwise Discord never sends the interaction.
|
|
9
|
+
@Autocomplete('sample-slash', 'query')
|
|
10
|
+
async completeQuery(interaction: AutocompleteInteraction) {
|
|
11
|
+
const { value } = interaction.options.getFocused(true)
|
|
12
|
+
const choices = ['alpha', 'beta', 'gamma'].filter(choice => choice.startsWith(value))
|
|
13
|
+
|
|
14
|
+
await interaction.respond(choices.map(choice => ({ name: choice, value: choice })))
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ChannelSelectMenuInteraction } from 'discord.js'
|
|
2
|
+
import { Controller, Command } from 'meocord/decorator'
|
|
3
|
+
import { CommandType } from 'meocord/enum'
|
|
4
|
+
|
|
5
|
+
@Controller()
|
|
6
|
+
export class {{className}}ChannelSelectMenuController {
|
|
7
|
+
|
|
8
|
+
@Command('channel-select-menu', CommandType.CHANNEL_SELECT_MENU)
|
|
9
|
+
async handleChannelSelectMenu(interaction: ChannelSelectMenuInteraction) {
|
|
10
|
+
await interaction.reply(`Selected: ${interaction.channels.map(channel => channel.toString()).join(', ')}`)
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { UserContextMenuCommandInteraction, MessageContextMenuCommandInteraction } from 'discord.js'
|
|
2
|
-
import { Controller, Command
|
|
3
|
-
import { SampleCommandBuilder } from '
|
|
2
|
+
import { Controller, Command } from 'meocord/decorator'
|
|
3
|
+
import { SampleCommandBuilder } from '{{builderImportPath}}'
|
|
4
4
|
|
|
5
5
|
@Controller()
|
|
6
6
|
export class {{className}}ContextMenuController {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { MentionableSelectMenuInteraction } from 'discord.js'
|
|
2
|
+
import { Controller, Command } from 'meocord/decorator'
|
|
3
|
+
import { CommandType } from 'meocord/enum'
|
|
4
|
+
|
|
5
|
+
@Controller()
|
|
6
|
+
export class {{className}}MentionableSelectMenuController {
|
|
7
|
+
|
|
8
|
+
@Command('mentionable-select-menu', CommandType.MENTIONABLE_SELECT_MENU)
|
|
9
|
+
async handleMentionableSelectMenu(interaction: MentionableSelectMenuInteraction) {
|
|
10
|
+
await interaction.reply(`Selected ${interaction.users.size} user(s) and ${interaction.roles.size} role(s).`)
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -3,7 +3,7 @@ import { Controller, Command } from 'meocord/decorator'
|
|
|
3
3
|
import { CommandType } from 'meocord/enum'
|
|
4
4
|
|
|
5
5
|
@Controller()
|
|
6
|
-
export class {{className}}
|
|
6
|
+
export class {{className}}ModalSubmitController {
|
|
7
7
|
|
|
8
8
|
@Command('submit-modal', CommandType.MODAL_SUBMIT)
|
|
9
9
|
async handleModal(interaction: ModalSubmitInteraction) {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { PrimaryEntryPointCommandInteraction } from 'discord.js'
|
|
2
|
+
import { Controller, Command } from 'meocord/decorator'
|
|
3
|
+
import { SampleCommandBuilder } from '{{builderImportPath}}'
|
|
4
|
+
|
|
5
|
+
@Controller()
|
|
6
|
+
export class {{className}}PrimaryEntryPointController {
|
|
7
|
+
|
|
8
|
+
@Command('sample-entry-point', SampleCommandBuilder)
|
|
9
|
+
async handleEntryPoint(interaction: PrimaryEntryPointCommandInteraction) {
|
|
10
|
+
await interaction.reply('Launching the activity.')
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { RoleSelectMenuInteraction } from 'discord.js'
|
|
2
|
+
import { Controller, Command } from 'meocord/decorator'
|
|
3
|
+
import { CommandType } from 'meocord/enum'
|
|
4
|
+
|
|
5
|
+
@Controller()
|
|
6
|
+
export class {{className}}RoleSelectMenuController {
|
|
7
|
+
|
|
8
|
+
@Command('role-select-menu', CommandType.ROLE_SELECT_MENU)
|
|
9
|
+
async handleRoleSelectMenu(interaction: RoleSelectMenuInteraction) {
|
|
10
|
+
await interaction.reply(`Selected: ${interaction.roles.map(role => role.name).join(', ')}`)
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ChatInputCommandInteraction } from 'discord.js'
|
|
2
2
|
import { Controller, Command } from 'meocord/decorator'
|
|
3
|
-
import { SampleCommandBuilder } from '
|
|
3
|
+
import { SampleCommandBuilder } from '{{builderImportPath}}'
|
|
4
4
|
|
|
5
5
|
@Controller()
|
|
6
6
|
export class {{className}}SlashController {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { UserSelectMenuInteraction } from 'discord.js'
|
|
2
|
+
import { Controller, Command } from 'meocord/decorator'
|
|
3
|
+
import { CommandType } from 'meocord/enum'
|
|
4
|
+
|
|
5
|
+
@Controller()
|
|
6
|
+
export class {{className}}UserSelectMenuController {
|
|
7
|
+
|
|
8
|
+
@Command('user-select-menu', CommandType.USER_SELECT_MENU)
|
|
9
|
+
async handleUserSelectMenu(interaction: UserSelectMenuInteraction) {
|
|
10
|
+
await interaction.reply(`Selected: ${interaction.users.map(user => user.username).join(', ')}`)
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Argument } from 'commander';
|
|
2
|
+
import { ControllerType } from '../enum/controller.enum.js';
|
|
2
3
|
import { ControllerGeneratorHelper } from './helper/controller-generator.helper.js';
|
|
3
4
|
import { Logger } from '../common/logger.js';
|
|
4
5
|
import '../common/theme.js';
|
|
@@ -9,15 +10,9 @@ import wait from '../util/wait.util.js';
|
|
|
9
10
|
class GeneratorCLI {
|
|
10
11
|
register(program) {
|
|
11
12
|
const generatorCommand = program.command('generate').alias('g').description('Generate components');
|
|
12
|
-
generatorCommand.command('controller').alias('co').description('Generate a controller component').addArgument(
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
'message',
|
|
16
|
-
'modal-submit',
|
|
17
|
-
'reaction',
|
|
18
|
-
'select-menu',
|
|
19
|
-
'slash'
|
|
20
|
-
])).addArgument(new Argument('<name>', 'Name of the controller')).action(async (type, name)=>{
|
|
13
|
+
generatorCommand.command('controller').alias('co').description('Generate a controller component').addArgument(// Derived from the enum rather than listed by hand, so a new controller kind
|
|
14
|
+
// cannot be added to the framework and stay unreachable from the CLI.
|
|
15
|
+
new Argument('<type>', 'Type of the controller (e.g., button, context-menu, etc.)').choices(Object.values(ControllerType))).addArgument(new Argument('<name>', 'Name of the controller')).action(async (type, name)=>{
|
|
21
16
|
await this.handleGenerateComponent({
|
|
22
17
|
component: 'controller',
|
|
23
18
|
type: type,
|
|
@@ -14,7 +14,7 @@ class ControllerGeneratorHelper {
|
|
|
14
14
|
*/ generateController(args, type) {
|
|
15
15
|
const { parts, kebabCaseName, className } = validateAndFormatName(args.controllerName);
|
|
16
16
|
const controllerDir = path.join(process.cwd(), 'src', 'controllers', type, ...parts);
|
|
17
|
-
const template = this.buildControllerTemplate(className, type);
|
|
17
|
+
const template = this.buildControllerTemplate(className, type, parts);
|
|
18
18
|
this.generateControllerStructure(controllerDir, kebabCaseName, className, type, template);
|
|
19
19
|
}
|
|
20
20
|
/**
|
|
@@ -23,8 +23,8 @@ class ControllerGeneratorHelper {
|
|
|
23
23
|
* @param type - The type of the controller, defined in the `ControllerType` enum.
|
|
24
24
|
* @returns The populated template string for the controller.
|
|
25
25
|
* @throws Will throw an error if the controller type is unsupported.
|
|
26
|
-
*/ buildControllerTemplate(className, type) {
|
|
27
|
-
const templateConfig = this.getTemplateConfig(type, className);
|
|
26
|
+
*/ buildControllerTemplate(className, type, parts = []) {
|
|
27
|
+
const templateConfig = this.getTemplateConfig(type, className, parts);
|
|
28
28
|
if (!templateConfig) {
|
|
29
29
|
throw new Error(`Unsupported controller type: ${type}`);
|
|
30
30
|
}
|
|
@@ -35,22 +35,38 @@ class ControllerGeneratorHelper {
|
|
|
35
35
|
* @param type - The type of the controller, defined in the `ControllerType` enum.
|
|
36
36
|
* @param className - The name of the controller class.
|
|
37
37
|
* @returns An object containing the template path and variables, or `undefined` if not found.
|
|
38
|
-
*/ getTemplateConfig(type, className) {
|
|
38
|
+
*/ getTemplateConfig(type, className, parts = []) {
|
|
39
39
|
const baseDir = path.resolve(__dirname$1, '..', 'builder-template', 'controller');
|
|
40
40
|
const templates = {
|
|
41
41
|
[ControllerType.BUTTON]: 'button.controller.template',
|
|
42
42
|
[ControllerType.MODAL_SUBMIT]: 'modal-submit.controller.template',
|
|
43
43
|
[ControllerType.SELECT_MENU]: 'select-menu.controller.template',
|
|
44
|
+
[ControllerType.USER_SELECT_MENU]: 'user-select-menu.controller.template',
|
|
45
|
+
[ControllerType.ROLE_SELECT_MENU]: 'role-select-menu.controller.template',
|
|
46
|
+
[ControllerType.MENTIONABLE_SELECT_MENU]: 'mentionable-select-menu.controller.template',
|
|
47
|
+
[ControllerType.CHANNEL_SELECT_MENU]: 'channel-select-menu.controller.template',
|
|
44
48
|
[ControllerType.REACTION]: 'reaction.controller.template',
|
|
45
49
|
[ControllerType.MESSAGE]: 'message.controller.template',
|
|
46
50
|
[ControllerType.CONTEXT_MENU]: 'context-menu.controller.template',
|
|
47
|
-
[ControllerType.SLASH]: 'slash.controller.template'
|
|
51
|
+
[ControllerType.SLASH]: 'slash.controller.template',
|
|
52
|
+
[ControllerType.AUTOCOMPLETE]: 'autocomplete.controller.template',
|
|
53
|
+
[ControllerType.PRIMARY_ENTRY_POINT]: 'primary-entry-point.controller.template'
|
|
48
54
|
};
|
|
49
55
|
const template = templates[type] ? path.resolve(baseDir, templates[type]) : undefined;
|
|
56
|
+
// The builder is written beside the controller, so a nested controller name moves
|
|
57
|
+
// it too. Hard-coding `@src/controllers/<type>/builders/...` only ever pointed at
|
|
58
|
+
// the top-level one, which for a nested name does not exist.
|
|
59
|
+
const builderImportPath = [
|
|
60
|
+
'@src/controllers',
|
|
61
|
+
type,
|
|
62
|
+
...parts,
|
|
63
|
+
'builders/sample.builder'
|
|
64
|
+
].join('/');
|
|
50
65
|
return template ? {
|
|
51
66
|
template,
|
|
52
67
|
variables: {
|
|
53
|
-
className
|
|
68
|
+
className,
|
|
69
|
+
builderImportPath
|
|
54
70
|
}
|
|
55
71
|
} : undefined;
|
|
56
72
|
}
|
|
@@ -99,7 +115,8 @@ class ControllerGeneratorHelper {
|
|
|
99
115
|
const baseDir = path.resolve(__dirname$1, '..', 'builder-template', 'builder');
|
|
100
116
|
const templates = {
|
|
101
117
|
[ControllerType.CONTEXT_MENU]: 'context-menu.builder.template',
|
|
102
|
-
[ControllerType.SLASH]: 'slash.builder.template'
|
|
118
|
+
[ControllerType.SLASH]: 'slash.builder.template',
|
|
119
|
+
[ControllerType.PRIMARY_ENTRY_POINT]: 'primary-entry-point.builder.template'
|
|
103
120
|
};
|
|
104
121
|
const template = templates[type] ? path.resolve(baseDir, templates[type]) : undefined;
|
|
105
122
|
return template ? {
|