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.
Files changed (34) hide show
  1. package/AUTHOR.md +2 -3
  2. package/README.md +289 -41
  3. package/dist/cjs/_shared/controller.decorator-MUHA_A3z.cjs +529 -0
  4. package/dist/cjs/core/index.cjs +288 -85
  5. package/dist/cjs/decorator/index.cjs +5 -1
  6. package/dist/cjs/enum/index.cjs +17 -4
  7. package/dist/cjs/testing/index.cjs +88 -4
  8. package/dist/esm/bin/builder-template/builder/primary-entry-point.builder.template +26 -0
  9. package/dist/esm/bin/builder-template/controller/autocomplete.controller.template +16 -0
  10. package/dist/esm/bin/builder-template/controller/channel-select-menu.controller.template +12 -0
  11. package/dist/esm/bin/builder-template/controller/context-menu.controller.template +2 -2
  12. package/dist/esm/bin/builder-template/controller/mentionable-select-menu.controller.template +12 -0
  13. package/dist/esm/bin/builder-template/controller/modal-submit.controller.template +1 -1
  14. package/dist/esm/bin/builder-template/controller/primary-entry-point.controller.template +12 -0
  15. package/dist/esm/bin/builder-template/controller/role-select-menu.controller.template +12 -0
  16. package/dist/esm/bin/builder-template/controller/slash.controller.template +1 -1
  17. package/dist/esm/bin/builder-template/controller/user-select-menu.controller.template +12 -0
  18. package/dist/esm/bin/generator.js +4 -9
  19. package/dist/esm/bin/helper/controller-generator.helper.js +24 -7
  20. package/dist/esm/core/meocord.app.js +289 -85
  21. package/dist/esm/decorator/controller.decorator.js +157 -24
  22. package/dist/esm/decorator/index.js +1 -1
  23. package/dist/esm/enum/controller.enum.js +23 -4
  24. package/dist/esm/testing/mock-interaction.js +89 -5
  25. package/dist/esm/util/interaction.util.js +174 -0
  26. package/dist/types/controller.enum-DYfhYaat.d.ts +36 -0
  27. package/dist/types/core/index.d.ts +94 -0
  28. package/dist/types/decorator/index.d.ts +74 -41
  29. package/dist/types/enum/index.d.ts +1 -1
  30. package/dist/types/interface/index.d.ts +87 -3
  31. package/dist/types/testing/index.d.ts +3 -21
  32. package/package.json +2 -2
  33. package/dist/cjs/_shared/controller.decorator-DX5lFlPZ.cjs +0 -216
  34. 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, CommandType } from 'meocord/decorator'
3
- import { SampleCommandBuilder } from '@src/controllers/context-menu/builders/sample.builder'
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}}ModalController {
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 '@src/controllers/slash/builders/sample.builder'
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(new Argument('<type>', 'Type of the controller (e.g., button, context-menu, etc.)').choices([
13
- 'button',
14
- 'context-menu',
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 ? {