From 0b248f16f75c1373284fe960efd1d5ae7f357b76 Mon Sep 17 00:00:00 2001 From: rybak Date: Mon, 30 Jan 2023 08:53:23 +0300 Subject: [PATCH] feat: Command option decorator unnecessary, mark as deprecated --- README.md | 2 +- package.json | 88 +- packages/common/README.md | 7 +- packages/core/README.md | 2286 ++++++++--------- .../options/command-options.contant.ts | 1 - .../options/command-options.decorator.ts | 13 +- .../providers/reflect-metadata.provider.ts | 5 - packages/core/src/services/dto.service.ts | 37 +- .../command-by-glob/src/bot/dto/play.dto.ts | 3 +- .../src/bot/dto/play.dto.ts | 3 +- .../sample/command/src/bot/dto/play.dto.ts | 3 +- .../src/bot/dto/play.dto.ts | 3 +- .../src/bot/dto/play.dto.ts | 3 +- .../sub-command/src/bot/dto/email.dto.ts | 3 +- .../sub-command/src/bot/dto/number.dto.ts | 3 +- .../validation/src/bot/dto/stats.dto.ts | 3 +- .../files/command/src/bot/dto/play.dto.ts | 3 +- 17 files changed, 1232 insertions(+), 1234 deletions(-) delete mode 100644 packages/core/src/decorators/command/options/command-options.contant.ts diff --git a/README.md b/README.md index bbbd9e5b..4e901330 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ Check your intent is passed to the `discordClientOptions` of the module. [More i Click to expand Set `useDefineForClassFields` to `true` in your `tsconfig.json`. -Also check that the `@CommandOptions` and `@InteractionEvent` decorators are set. +Also check that the `@InteractionEvent` decorators are set. diff --git a/package.json b/package.json index 4e1c5c49..8f3a2b43 100644 --- a/package.json +++ b/package.json @@ -1,44 +1,44 @@ -{ - "name": "discord-nestjs", - "private": true, - "workspaces": [ - "packages/*", - "packages/sample/*" - ], - "repository": { - "type": "git", - "url": "https://github.com/fjodor-rybakov/discord-nestjs" - }, - "homepage": "https://github.com/fjodor-rybakov/discord-nestjs#readme", - "scripts": { - "bootstrap": "lerna bootstrap", - "clean": "lerna clean", - "build": "lerna run build --no-private", - "format": "prettier --write \"packages/**/*.ts\" --ignore-path .gitignore", - "prepublish:release": "npm run build", - "postinstall": "npm run build", - "publish:release": "lerna publish -m \"chore: Publish release\" --no-private --conventional-commits --create-release github", - "publish:beta": "lerna publish --canary --preid beta -m \"chore: Publish beta release\" --no-private", - "lint": "eslint \"packages/{common,core}/src/**/*.ts\" --fix", - "prepare": "husky install", - "check-updates": "npx npm-check-updates --deep", - "update": "npx npm-check-updates --deep -u" - }, - "devDependencies": { - "@trivago/prettier-plugin-sort-imports": "4.0.0", - "@typescript-eslint/eslint-plugin": "5.49.0", - "@typescript-eslint/parser": "5.49.0", - "eslint": "8.33.0", - "eslint-config-prettier": "8.6.0", - "eslint-plugin-prettier": "4.2.1", - "husky": "8.0.3", - "lerna": "6.4.1", - "lint-staged": "13.1.0", - "prettier": "2.8.3" - }, - "lint-staged": { - "*.ts": [ - "prettier --write" - ] - } -} +{ + "name": "discord-nestjs", + "private": true, + "workspaces": [ + "packages/*", + "packages/sample/*" + ], + "repository": { + "type": "git", + "url": "https://github.com/fjodor-rybakov/discord-nestjs" + }, + "homepage": "https://github.com/fjodor-rybakov/discord-nestjs#readme", + "scripts": { + "bootstrap": "lerna bootstrap", + "clean": "lerna clean", + "build": "lerna run build --no-private", + "format": "prettier --write \"packages/**/*.ts\" --ignore-path .gitignore", + "prepublish:release": "npm run build", + "postinstall": "npm run build", + "publish:release": "lerna publish -m \"chore: Publish release\" --no-private --conventional-commits --create-release github", + "publish:beta": "lerna publish --canary --preid beta -m \"chore: Publish beta release\" --no-private", + "lint": "eslint \"packages/{common,core}/src/**/*.ts\" --fix", + "prepare": "husky install", + "check-updates": "npx npm-check-updates --deep", + "update": "npx npm-check-updates --deep -u" + }, + "devDependencies": { + "@trivago/prettier-plugin-sort-imports": "4.0.0", + "@typescript-eslint/eslint-plugin": "5.49.0", + "@typescript-eslint/parser": "5.49.0", + "eslint": "8.33.0", + "eslint-config-prettier": "8.6.0", + "eslint-plugin-prettier": "4.2.1", + "husky": "8.0.3", + "lerna": "6.4.1", + "lint-staged": "13.1.0", + "prettier": "2.8.3" + }, + "lint-staged": { + "*.ts": [ + "prettier --write" + ] + } +} diff --git a/packages/common/README.md b/packages/common/README.md index fba12119..c4890695 100644 --- a/packages/common/README.md +++ b/packages/common/README.md @@ -30,9 +30,8 @@ For prefix command use `PrefixCommandPipe`. /* registration-email.dto.ts */ import {City} from '../definitions/city'; -import {Param, ParamType, Choice, CommandOptions} from '@discord-nestjs/core'; +import {Param, ParamType, Choice} from '@discord-nestjs/core'; -@CommandOptions() export class RegistrationEmailDto { @Param({ name: 'email', @@ -85,10 +84,9 @@ Also, suitable for prefix commands. ```typescript /* registration-email.dto.ts */ -import {CommandOptions, Param, ParamType} from '@discord-nestjs/core'; +import {Param, ParamType} from '@discord-nestjs/core'; import {IsEmail, Length, Max, Min} from 'class-validator'; -@CommandOptions() export class RegistrationEmailDto { @IsEmail() @Param({ @@ -166,7 +164,6 @@ class ResidencePlace { import { DiscordIntersectionType } from '@discord-nestjs/common'; // Must have properties name, city and street -@CommandOptions() class Profile extends DiscordIntersectionType(FullName, ResidencePlace) { } ``` diff --git a/packages/core/README.md b/packages/core/README.md index 5026c59b..cb31db05 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -1,1144 +1,1142 @@ -# Core module - -## 🧾 Description - -NestJS package for discord.js - -- [👨🏻‍💻 Installation](#Installation) -- [📑 Overview](#Overview) -- [▶️ Usage](#Usage) - - [ℹ️ Creating slash commands](#Command) - - [ℹ️ UI based commands(Context menu commands)](#UIBasedCommand) - - [ℹ️ Automatic registration of slash commands](#AutoRegCommand) - - [ℹ️ Subscribe to event](#SubToEvent) - - [ℹ️ Prefix commands](#PrefixCommands) - - [ℹ️ ️ Pipes, Guards, Interceptors and Filters](#Consumers) - - [ℹ️ Collectors](#Collectors) - - [ℹ️ Modals](#Modals) -- [🛠️ Exported providers](#Providers) - - [ℹ️ DiscordClientProvider](#DiscordClientProvider) - - [ℹ️ DiscordCommandProvider](#DiscordCommandProvider) - - [ℹ️ ReflectMetadataProvider](#ReflectMetadataProvider) - - [ℹ️ CollectorProvider](#CollectorProvider) -- [🗂 Decorators description](#DecoratorsDescription) - - [ℹ️ @InjectDiscordClient](#InjectDiscordClient) - - [ℹ️ @Command](#Command) - - [ℹ️ @SubCommand](#SubCommand) - - [ℹ️ @On](#On) - - [ℹ️ @Once](#Once) - - [ℹ️ @ArgNum](#ArgNum) - - [ℹ️ @ArgRange](#ArgRange) - - [ℹ️ @InteractionEvent/@IA](#InteractionEvent) - - [ℹ️ @InteractionEvent/@MSG](#MessageEvent) - - [ℹ️ @Param](#Param) - - [ℹ️ @Choice](#Choice) - - [ℹ️ @Channel](#Channel) - - [ℹ️ @InteractionEventCollector](#InteractionEventCollector) - - [ℹ️ @MessageEventCollector](#MessageEventCollector) - - [ℹ️ @ReactionEventCollector](#ReactionEventCollector) - - [ℹ️ @UseCollectors](#UseCollectors) - - [ℹ️ @InjectCollector](#InjectCollector) - - [ℹ️ @InjectCauseEvent](#InjectCauseEvent) - - [ℹ️ @Filter](#Filter) - - [ℹ️ @Field](#Field) - - [ℹ️ @TextInputValue](#TextInputValue) - - - - - -## 👨🏻‍💻 Installation - -```bash -$ npm install @discord-nestjs/core discord.js -``` - -Or via yarn - -```bash -$ yarn add @discord-nestjs/core discord.js -``` - - - - - -## 📑 Overview - -> ⚠️**Before you start using, set `useDefineForClassFields` to `true` in your `tsconfig.json`.** - -The module declaration proceeds in the same way as it is done in NestJS by means -creating a dynamic module through the `forRootAsync` functions. - -- `token` \* - Your discord bot token. You can get [here](https://discord.com/developers/applications) -- `discordClientOptions` \* - Client options from discord.js library -- `registerCommandOptions` - Specific registration of slash commands(If option is not set, global commands will be registered) - - `forGuild` - For which guild to register a slash command - - `trigger` - Used in cases where it is necessary to register commands by event - - `allowFactory` - Based on what criteria will slash commands be registered - - `removeCommandsBefore` - Remove mission commands -- `prefix` - Global command prefix -- `prefixGlobalOptions` - Global options for prefix command -- `webhook` - Connecting with webhook - - `webhookId` \* - Webhook id - - `webhookToken` \* - Webhook token -- `autoLogin` - Calling login function from discord client on application bootstrap -- `failOnLogin` - Throw an exception if login failed - -> ⚠️**Important! For the bot to work correctly, you need to set up intentions in `discordClientOptions` param. [More info](https://discordjs.guide/popular-topics/intents.html#privileged-intents)** - -Below is an example of creating a dynamic module using the `forRootAsync` function - -```typescript -/* app.module.ts */ - -import { DiscordModule } from '@discord-nestjs/core'; -import { Module } from '@nestjs/common'; -import { GatewayIntentBits } from 'discord.js'; - -@Module({ - imports: [ - DiscordModule.forRootAsync({ - useFactory: () => ({ - token: 'your-bot-token', - discordClientOptions: { - intents: [GatewayIntentBits.Guilds], - }, - }), - }), - ], -}) -export class AppModule {} -``` - -Alternatively, you can use the `useClass` syntax - -```typescript -/* app.module.ts */ - -import { Module } from '@nestjs/common'; -import { DiscordConfigService } from './discord-config-service'; -import { DiscordModule } from '@discord-nestjs/core'; - -@Module({ - imports: [ - DiscordModule.forRootAsync({ - useClass: DiscordConfigService, - }), - ], -}) -export class AppModule {} -``` - -You need to implement the `DiscordOptionsFactory` interface - -```typescript -/* discord-config.service.ts */ - -import { Injectable } from '@nestjs/common'; -import { - DiscordModuleOption, - DiscordOptionsFactory, -} from '@discord-nestjs/core'; -import { GatewayIntentBits } from 'discord.js'; - -@Injectable() -export class DiscordConfigService implements DiscordOptionsFactory { - createDiscordOptions(): DiscordModuleOption { - return { - token: 'your-bot-token', - discordClientOptions: { - intents: [GatewayIntentBits.Guilds], - }, - }; - } -} -``` - -If you need to inject exported providers outside the `AppModule`, use the `Discord.forFeature()` import. -For example, you need to get the Discord `Client` in your module. - -```typescript -/* bot.module.ts */ - -import { Module } from '@nestjs/common'; -import { DiscordModule } from '@discord-nestjs/core'; -import { BotGateway } from './bot.gateway' - -@Module({ - imports: [DiscordModule.forFeature()], - providers: [BotGateway] -}) -export class BotModule {} -``` - -```typescript -/* bot.gateway.ts */ - -import { Injectable, Logger } from '@nestjs/common'; -import { Once, InjectDiscordClient } from '@discord-nestjs/core'; -import { Client } from 'discord.js'; - -@Injectable() -export class BotGateway { - private readonly logger = new Logger(BotGateway.name); - - constructor( - @InjectDiscordClient() - private readonly client: Client, - ) {} - - @Once('ready') - onReady() { - this.logger.log(`Bot ${this.client.user.tag} was started!`); - } -} -``` - -## ▶️ Usage - -### ℹ️ Creating slash commands - -> If you install `@angular-devkit/schematics-cli` and [@discord-nestjs/schematics](https://github.com/fjodor-rybakov/discord-nestjs/tree/master/packages/schematics) -> , you can run the follow command to create a slash-command bot template: -> `schematics @discord-nestjs/schematics:application --template slash-command` - -To add a slash command, you need to create a class with `@Command` decorator. -The `@Handler` decorator will point to the command processing method. - -#### 💡 Example - -```typescript -/* playlist.command.ts */ - -import { Command, Handler } from '@discord-nestjs/core'; -import { CommandInteraction } from 'discord.js'; -import { Injectable } from '@nestjs/common'; - -@Command({ - name: 'playlist', - description: 'Get current playlist', -}) -@Injectable() -export class PlaylistCommand { - @Handler() - onPlaylist(interaction: CommandInteraction): string { - return 'List with music...'; - } -} -``` - -If your command accepts parameters, you need to create a class with options. -Mark class with `@CommandOptions()` decorator. - -```typescript -/* registration.dto.ts */ - -import { Param, Choice, ParamType, CommandOptions } from '@discord-nestjs/core'; - -enum City { - Moscow, - 'New York', - Tokyo, -} - -@CommandOptions() -export class RegistrationOptions { - @Param({ description: 'User name', required: true }) - name: string; - - @Param({ description: 'User age', required: true, type: ParamType.INTEGER }) - age: number; - - @Choice(City) - @Param({ description: 'User city', type: ParamType.INTEGER }) - city: City; -} -``` - -* `@Param` decorator defines command parameter. -* `@Choice` decorator marks command parameter as dropdown(**Accepts `enum` or `Map`**). -* `@Channel` decorator marks command parameter as channel select. - -> By default, if `name` is not passed to the decorator parameters, -> then the name of the marked property will be taken. - -> If the command parameter is a `string` or a `boolean`, then it is not necessary -> to pass the type. The type will resolve **automatically**. - -To get object with command option you need add `@InteractionEvent()`/`@IA()` with `SlashCommandPipe`. -`InteractionEvent` will extract the data from event args and `SlashCommandPipe` will convert the data into an object. - -> You can import `SlashCommandPipe` from [common](https://github.com/fjodor-rybakov/discord-nestjs/tree/master/packages/common) package - -#### 💡 Example - -```typescript -/* registration.command.ts */ - -import {Command, InteractionEvent, Handler} from '@discord-nestjs/core'; -import {SlashCommandPipe} from '@discord-nestjs/common'; -import {Injectable} from '@nestjs/common'; - -import {RegistrationOptions} from './registration.dto'; - -@Command({ - name: 'reg', - description: 'User registration', -}) -@Injectable() -export class BaseInfoCommand { - @Handler() - onRegistration(@InteractionEvent(SlashCommandPipe) options: RegistrationOptions): string { - return `User was registered with name: ${options.name}, age ${options.age} and city ${options.city}`; - } -} -``` - -> Also, you can validate the options using `ValidationPipe` from [common](https://github.com/fjodor-rybakov/discord-nestjs/tree/master/packages/common) package. - -Each command must be added to a NestJS module. - -```typescript -/* bot-slash-commands.module.ts */ - -import { PlaylistCommand } from './playlist.command'; -import { BaseInfoCommand } from './registration.command'; -import { Module } from '@nestjs/common'; - -@Module({ - providers: [PlaylistCommand, BaseInfoCommand], -}) -export class BotSlashCommandsModule { -} -``` - -Or you can use https://github.com/fjodor-rybakov/nestjs-dynamic-providers for search files by glob pattern. - -```typescript -/* bot-slash-commands.module.ts */ - -import { DiscordModule } from '@discord-nestjs/core'; -import { Module } from '@nestjs/common'; -import { InjectDynamicProviders } from 'nestjs-dynamic-providers'; - -@InjectDynamicProviders('**/*.command.js') -@Module({}) -export class BotSlashCommandsModule {} -``` - -And add your `BotSlashCommandsModule` to `AppModule`. - -```typescript -/* app.module.ts */ - -import { DiscordModule } from '@discord-nestjs/core'; -import { Module } from '@nestjs/common'; -import { GatewayIntentBits } from 'discord.js'; -import { BotSlashCommandsModule } from './bot-slash-commands.module'; - -@Module({ - imports: [ - DiscordModule.forRootAsync({ - useFactory: () => ({ - token: 'your-bot-token', - discordClientOptions: { - intents: [Intents.FLAGS.GUILDS], - }, - }), - }), - BotSlashCommandsModule, - ], -}) -export class AppModule {} -``` - -If your command is more complex, you can add subgroups of commands or subcommands to it. -To do this, you need to add your subgroups and subcommands to the `include` option. The `include` parameter accepts -list of class types or `UseGroup` function, which in turn accepts group parameters and list of subcommands. - -> ⚠️ **Remember that if you define a subgroups of commands or subcommands, this will automatically mark your main command unusable. -> More details in [Discord API](https://canary.discord.com/developers/docs/interactions/slash-commands#subcommands-and-subcommand-groups)** - -#### 💡 Example - -```typescript -/* registration.command.ts */ - -import { BaseInfoSubCommand } from './sub-commands/base-info-sub-command'; -import { EmailSubCommand } from './sub-commands/email-sub-command'; -import { NumberSubCommand } from './sub-commands/number-sub-command'; -import { Command, UseGroup } from '@discord-nestjs/core'; - -@Command({ - name: 'reg', - description: 'User registration', - include: [ - UseGroup( - { name: 'type', description: 'Registration type' }, - NumberSubCommand, - EmailSubCommand, - ), - BaseInfoSubCommand, - ], -}) -export class RegistrationCommand {} -``` - -Subcommands are declared similarly to commands and implement the same interfaces. -To do this, you need to create a class, mark it with the `SubCommand` decorator and -specify which interface they implement(`DiscordCommand` or `DiscordTransformedCommand`) - -#### 💡 Example - -```typescript -/* email-sub-command.ts */ - -import { - Handler, - IA, - SubCommand, -} from '@discord-nestjs/core'; -import {SlashCommandPipe} from '@discord-nestjs/common'; - -import {EmailDto} from '../../dto/email.dto'; - -@SubCommand({name: 'email', description: 'Register by email'}) -export class EmailSubCommand { - @Handler() - onEmail(@IA(SlashCommandPipe) dto: EmailDto): string { - return `Success register user: ${dto.email}, ${dto.name}, ${dto.age}, ${dto.city}`; - } -} -``` - -```typescript -/* number-sub-command.ts */ - -import { - Handler, - IA, - SubCommand, -} from '@discord-nestjs/core'; -import {SlashCommandPipe} from '@discord-nestjs/common'; - -import {NumberDto} from '../../dto/number.dto'; - -@SubCommand({name: 'number', description: 'Register by phone number'}) -export class NumberSubCommand { - @Handler() - onPhoneNumber(@IA(SlashCommandPipe) dto: NumberDto): string { - return `Success register user: ${dto.phoneNumber}, ${dto.name}, ${dto.age}, ${dto.city}`; - } -} -``` - -```typescript -/* base-info-sub-command.ts */ - -import {Handler, SubCommand} from '@discord-nestjs/core'; -import { - CommandInteraction, - InteractionReplyOptions, - MessageEmbed, -} from 'discord.js'; - -@SubCommand({name: 'base-info', description: 'Base info'}) -export class BaseInfoSubCommand { - @Handler() - onBaseInfo(interaction: CommandInteraction): InteractionReplyOptions { - const {user} = interaction; - - const embed = new MessageEmbed() - .setImage(user.avatarURL()) - .addField('Name', user.username); - - return { - embeds: [embed], - }; - } -} -``` - -All commands and sub-commands must also be added to module providers. - -```typescript -/* bot-slash-commands.module.ts */ - -import { RegistrationCommand } from './registration.command'; -import { BaseInfoSubCommand } from './sub-commands/base-info-sub-command'; -import { EmailSubCommand } from './sub-commands/email-sub-command'; -import { NumberSubCommand } from './sub-commands/number-sub-command'; -import { Module } from '@nestjs/common'; - -@Module({ - providers: [ - RegistrationCommand, - NumberSubCommand, - EmailSubCommand, - BaseInfoSubCommand, - ], -}) -export class BotSlashCommandsModule { -} -``` - -### ℹ️ UI based commands(Context menu commands) - -In addition to slash commands, you can define commands through the context menu. -To do this, you need to explicitly set the command type. (`USER` or `MESSAGE`) - -```typescript -/* playlist.command.ts */ - -import {Command, Handler} from '@discord-nestjs/core'; -import {ContextMenuInteraction} from 'discord.js'; -import {ApplicationCommandTypes} from 'discord.js/typings/enums'; - -@Command({ - name: 'playlist', - type: ApplicationCommandTypes.USER, -}) -export class PlaylistCommand { - @Handler() - onPlaylist(interaction: ContextMenuInteraction): string { - return 'Your playlist...'; - } -} -``` - -### ℹ️ Automatic registration of slash commands - -Commands are registered automatically if you define them in code. The `registerCommandOptions` property responds to the -command registration setting. It works according to the following principle: - -* `registerCommandOptions` - takes an array of objects. - - -* If `registerCommandOptions` option is not specified, global commands will be registered by default - -* If `trigger` used in cases where it is necessary to register commands by event -* If `allowFactory` is specified then commands will be registered by condition from `allowFactory` -* If `forGuild` is specified, then commands for a specific guild will be registered -* If `removeCommandsBefore` is specified, then registered commands that are not in your code will be removed - -The `trigger`, `allowFactory`, `forGuild` and `removeCommandsBefore` options are combined with each other. - -> Global commands, unlike guild commands, are cached and updated once per hour. [More info](https://discordjs.guide/interactions/slash-commands.html#global-commands). - -#### 💡 Example - -```typescript -import { DiscordModule } from '@discord-nestjs/core'; -import { Module } from '@nestjs/common'; -import { ConfigModule, ConfigService } from '@nestjs/config'; -import { GatewayIntentBits, Message } from 'discord.js'; -import { BotSlashCommandsModule } from './bot-slash-commands.module'; - -@Module({ - imports: [ - DiscordModule.forRootAsync({ - imports: [ConfigModule], - useFactory: (configService: ConfigService) => ({ - token: configService.get('TOKEN'), - discordClientOptions: { - intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages], - }, - registerCommandOptions: [ - { - forGuild: configService.get('GUILD_ID_WITH_COMMANDS'), - allowFactory: (message: Message) => - !message.author.bot && message.content === '!deploy', - }, - ], - }), - inject: [ConfigService], - }), - BotSlashCommandsModule - ], -}) -export class BotModule {} -``` - -### ℹ️ Subscribe to event - -Subscription to incoming events ([hint](https://gist.github.com/koad/316b265a91d933fd1b62dddfcc3ff584)) - -Use the `@On` decorator to subscribe to an event. `BotGateway` must be added to module providers. - -#### 💡 Example - -```typescript -/* bot.gateway.ts */ - -import { Injectable, Logger } from '@nestjs/common'; -import { On, Once, InjectDiscordClient } from '@discord-nestjs/core'; -import { Client, Message } from 'discord.js'; - -@Injectable() -export class BotGateway { - private readonly logger = new Logger(BotGateway.name); - - constructor( - @InjectDiscordClient() - private readonly client: Client, - ) {} - - @Once('ready') - onReady() { - this.logger.log(`Bot ${this.client.user.tag} was started!`); - } - - @On('messageCreate') - async onMessage(message: Message): Promise { - if (!message.author.bot) { - await message.reply("I'm watching you"); - } - } -} -``` - -You can also subscribe to an event once using the `@Once` decorator - -#### 💡 Example - -```typescript -/* bot.gateway.ts */ - -import { Injectable, Logger } from '@nestjs/common'; -import { Once } from '@discord-nestjs/core'; -import { Message } from 'discord.js'; - -@Injectable() -export class BotGateway { - private readonly logger = new Logger(BotGateway.name); - - @Once('ready') - onReady() { - this.logger.log('Bot was started!'); - } -} -``` - -### ℹ️ Prefix commands - -> If you install `@angular-devkit/schematics-cli` and [@discord-nestjs/schematics](https://github.com/fjodor-rybakov/discord-nestjs/tree/master/packages/schematics) -> , you can run the follow command to create a prefix-command bot template: -> `schematics @discord-nestjs/schematics:application --template prefix-command` - -To create a command with a prefix from the `messageCreate` event use the `PrefixCommandInterceptor`. -The following code will create a `!start` prefix command. - -#### 💡 Example - -```typescript -/* bot.gateway.ts */ - -import {PrefixCommandInterceptor} from '@discord-nestjs/common'; -import { - InjectDiscordClient, - On, - Once, -} from '@discord-nestjs/core'; -import { Injectable, Logger, UseInterceptors } from '@nestjs/common'; -import { Client } from 'discord.js'; - -import { StartDto } from './dto/start.dto'; - -@Injectable() -export class BotGateway { - private readonly logger = new Logger(BotGateway.name); - - constructor( - @InjectDiscordClient() - private readonly client: Client, - ) {} - - @Once('ready') - onReady(): void { - this.logger.log(`Bot ${this.client.user.tag} was started!`); - } - - @On('messageCreate') - @UseInterceptors(new PrefixCommandInterceptor('start')) - async onMessage(message: Message): Promise { - return 'Message processed successfully'; - } -} -``` - -> You can set the `prefix` globally via setting in the `DiscordModule`. - -You can also generate a DTO class based on incoming message content. - -Create an DTO class. Think of the input string as if it were separated by spaces. -For slicing parameters, the decorators `@ArgNum` and `@ArgRange` are used. - -* `@ArgNum` takes value at array index -* `@ArgRange` is the same as the `slice` function - -#### 💡 Example - -```typescript -/* start.dto.ts */ - -import { ArgNum, ArgRange } from '@discord-nestjs/core'; - -export class StartDto { - @ArgNum(() => ({ position: 0 })) - game: string; - - @ArgRange((last) => ({ formPosition: last + 1 })) - players: string[]; -} -``` - -Then just create a command. To get object with command option you need add `@MessageEvent()`/`@MSG()` with `PrefixCommandPipe`. -`MessageEvent` will extract the data from event args and `PrefixCommandPipe` will convert the data into an object. - -> You can import `PrefixCommandPipe` from [common](https://github.com/fjodor-rybakov/discord-nestjs/tree/master/packages/common) package - -```typescript -/* bot.gateway.ts */ - -import {PrefixCommandInterceptor, PrefixCommandPipe} from '@discord-nestjs/common'; -import { - InjectDiscordClient, - On, - Once, - MessageEvent -} from '@discord-nestjs/core'; -import { Injectable, Logger, UseInterceptors } from '@nestjs/common'; -import { Client } from 'discord.js'; - -import {StartDto} from './dto/start.dto'; - -@Injectable() -export class BotGateway { - private readonly logger = new Logger(BotGateway.name); - - constructor( - @InjectDiscordClient() - private readonly client: Client, - ) { - } - - @Once('ready') - onReady() { - this.logger.log(`Bot ${this.client.user.tag} was started!`); - } - - @PrefixCommand('start') - @UsePipes(PrefixCommandPipe) - async onMessage(@MessageEvent(PrefixCommandPipe) dto: StartDto): Promise { - console.log(dto); - - return 'Message processed successfully'; - } -} -``` - -`BotGateway` must be added to module providers. - -The message `!start warzone misha mark` in the channel should generate -`StartDto { game: 'warzone', players: [ 'misha', 'mark' ] }` DTO. - - -### ℹ️ Pipes, Guards, Interceptors and Filters - -[Pipes](https://docs.nestjs.com/pipes), [Guards](https://docs.nestjs.com/guards), [Interceptors](https://docs.nestjs.com/interceptors) -and [Filter](https://docs.nestjs.com/exception-filters) work the same as Nest. - -The only exception is guard. In NestJS `Guard` consumer throw Forbidden exception, when access denied. -The `discord-nestjs` listeners catch `ForbiddenException` by default. To override this behavior set -`isTrowForbiddenException: true` in `DiscordModule` options. - -### ℹ️ Collectors - -In addition to the standard implementation of `collectors` from `discord.js`, `discord-nestjs` provides their declaration -through decorators. You can create three types of collectors: ReactCollector, MessageCollector and InteractionCollector. - -The first thing you need to do is create a collector class and mark it with either `@MessageEventCollector` or -`@ReactionEventCollector` or `@InteractionEventCollector` with a decorator. For example, let's create a `ReactionCollector`. - -#### 💡 Example - -```typescript -/* appreciated-reaction-collector.ts */ - -import { - Filter, - InjectCollector, - On, - Once, - ReactionEventCollector, -} from '@discord-nestjs/core'; -import { Injectable, Scope } from '@nestjs/common'; -import { MessageReaction, ReactionCollector, User } from 'discord.js'; - -@Injectable({ scope: Scope.REQUEST }) -@ReactionEventCollector({ time: 15000 }) -export class AppreciatedReactionCollector { - constructor( - @InjectCollector() - private readonly collector: ReactionCollector, - ) {} - - @Filter() - isLikeFromAuthor(reaction: MessageReaction, user: User): boolean { - return ( - reaction.emoji.name === '👍' && user.id === reaction.message.author.id - ); - } - - @On('collect') - onCollect(): void { - console.log('collect'); - } - - @Once('end') - onEnd(): void { - console.log('end'); - } -} -``` - -Let me explain in detail what is going on here. - -* We marked the `AppreciatedReactionCollector` class with the `@ReactionEventCollector` decorator and passed collector - options as decorator argument. Think of it like we created `message.createReactionCollector({ time: 15000 });` from - `discord.js` library. -* The `@InjectCollector` injects the value of the collector into the class constructor. -If you use this decorator, you need to add `scope: Scope.REQUEST`. The default is `scope: Scope.DEFAULT`. -* The `@Filter` decorator filters the incoming data into the collector. Treat it like the `filter` option in `createReactionCollector`. -* Decorators `On` and `Once` subscribe to collector events. - -> Filters, guards, interceptors and pipes can be applied to collector events. - -To apply your collector to the message use `@UseCollectors` decorator. - -#### 💡 Example - -```typescript -/* bot.gateway.ts */ - -import { On, Once, UseCollectors } from '@discord-nestjs/core'; -import { CollectorInterceptor } from '@discord-nestjs/common'; -import { Injectable, Logger, UseGuards, UseInterceptors } from '@nestjs/common'; -import { Message } from 'discord.js'; - -import { AppreciatedReactionCollector } from './appreciated-reaction-collector'; -import { MessageFromUserGuard } from './guards/message-from-user.guard'; - -@Injectable() -export class BotGateway { - private readonly logger = new Logger(BotGateway.name); - - @Once('ready') - onReady(): void { - this.logger.log('Bot was started!'); - } - - @On('messageCreate') - @UseGuards(MessageFromUserGuard) - @UseCollectors(AppreciatedReactionCollector) - @UseInterceptors(CollectorInterceptor) - async onMessage(message: Message): Promise { - await message.reply('Start collector'); - } -} -``` - -In order for the collector to be called in the correct order, you need to hang `CollectorInterceptor` interceptor. - -> You can import `CollectorInterceptor` from [common](https://github.com/fjodor-rybakov/discord-nestjs/tree/master/packages/common) package - -If you need to get applied collectors use `@AppliedCollectors` param decorator. - -Other collectors types are created exactly by analogy. They apply to both event handlers and commands. - -For example, below is a sample button creation. - -#### 💡 Example - -```typescript -/* post-interaction-collector.ts */ - -import { - Filter, - InjectCauseEvent, - InteractionEventCollector, - On, -} from '@discord-nestjs/core'; -import { Injectable, Scope } from '@nestjs/common'; -import { ButtonInteraction, ChatInputCommandInteraction } from 'discord.js'; - -@Injectable({ scope: Scope.REQUEST }) -@InteractionEventCollector({ time: 15000 }) -export class PostInteractionCollector { - constructor( - @InjectCauseEvent() - private readonly causeInteraction: ChatInputCommandInteraction, - ) {} - - @Filter() - filter(interaction: ButtonInteraction): boolean { - return this.causeInteraction.id === interaction.message.interaction.id; - } - - @On('collect') - async onCollect(interaction: ButtonInteraction): Promise { - await interaction.update({ - content: 'A button was clicked!', - components: [], - }); - } -} - -``` - -* The `@InjectCauseEvent` decorator allow you get event that created the collector - -```typescript -/* play.command.ts */ - -import { CollectorInterceptor, SlashCommandPipe } from '@discord-nestjs/common'; -import { - AppliedCollectors, - Command, - Handler, - IA, - UseCollectors, -} from '@discord-nestjs/core'; -import { MessageActionRowComponentBuilder } from '@discordjs/builders'; -import { UseInterceptors } from '@nestjs/common'; -import { - ActionRowBuilder, - ButtonBuilder, - ButtonInteraction, - ButtonStyle, - InteractionCollector, - InteractionReplyOptions, -} from 'discord.js'; - -import { PlayDto } from '../dto/play.dto'; -import { PostInteractionCollector } from '../post-interaction-collector'; - -@Command({ - name: 'play', - description: 'Plays a song', -}) -@UseInterceptors(CollectorInterceptor) -@UseCollectors(PostInteractionCollector) -export class PlayCommand { - @Handler() - async onPlayCommand( - @IA(SlashCommandPipe) dto: PlayDto, - @AppliedCollectors(0) collector: InteractionCollector, - ): Promise { - const row = - new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setCustomId('primary') - .setLabel(dto.song) - .setStyle(ButtonStyle.Primary), - ); - - console.log(collector); - - return { - content: 'Click on the button to play the song!', - components: [row], - }; - } -} -``` - -### ℹ️ Modals - -Full example is shown [here](https://github.com/fjodor-rybakov/discord-nestjs/tree/master/packages/sample/modals) - - - - - -## 🛠️ Exported providers - -`DiscordModule` currently exports only three providers. - -### ℹ️ DiscordClientProvider - -Provides the discord client or webhook client. - -### ℹ️ DiscordCommandProvider - -`discord-nestjs` package builds slash command object based on all decorators and DiscordCommandProvider provides it. -For example, this is useful when you need to lazily register commands or register commands for a specific guild. - -### ℹ️ ReflectMetadataProvider - -Provides methods for getting metadata for decorators. - -### ℹ️ CollectorProvider - -Allow you to apply collector event - - - - -## 🗂 Decorators description - -### ℹ️ @InjectDiscordClient - -Inject Discord.js client - -### ℹ️ @Command - -Mark class as command - -#### Params - -- `name` \* - Command name -- `description` \* - Command description -- `include` - Include subgroups and subcommands -- `defaultMemberPermissions` - Set default permission -- `dmPermission` - Has DM permission - -### ℹ️ @SubCommand - -Mark class as subcommand - -#### Params - -- `name` \* - Command name -- `description` \* - Command description - -### ℹ️ @On - -Handle discord and collector events [hint](https://gist.github.com/koad/316b265a91d933fd1b62dddfcc3ff584) - -#### Params - -`event` \* - Name of the event to listen to - -### ℹ️ @Once - -Handle discord and collector events (only once) [hint](https://gist.github.com/koad/316b265a91d933fd1b62dddfcc3ff584) - -### ℹ️ @ArgNum - -Set value by argument number - -#### Params - -- arguments - - `last` - Last index position -- return - - `position` \* - Position index form input - -### ℹ️ @ArgRange - -Set value by argument number - -#### Params - -- arguments - - `last` - Last index position -- return - - `formPosition` \* - Start index position form input - - `toPosition` - Finish index position form input (default last index of input) - -#### Params - -`event` \* - Name of the event to listen to - -### ℹ️ @InteractionEvent/@IA - -Extract interaction event from args - -### ℹ️ @MessageEvent/@MSG - -Extract message event from args - -### ℹ️ @Param - -Sets the command parameter - -#### Params - -- `description` \* - Command description -- `name` - Command name -- `required` - The parameter is required -- `autocomplete` - Send autocomplete interaction(Only for `string`, `number` and `integer`) -- `minValue` - Min value for `number` or `integer` -- `maxValue` - Max value for `number` or `integer` -- `type` - Specifies the type of the parameter - -### ℹ️ @Choice - -Marks command parameter as dropdown. - -#### Params - -(**Accepts `enum` or `Map`**) - -### ℹ️ @Channel - -Marks command parameter as channel select. - -#### Params - -`channelType` - list of channel types - -### ℹ️ @InteractionEventCollector - -Create interaction collector - -#### Params - -See [here](https://discord.js.org/#/docs/main/stable/typedef/MessageComponentCollectorOptions) - -### ℹ️ @MessageEventCollector - -Create message collector - -#### Params - -See [here](https://discord.js.org/#/docs/main/stable/typedef/MessageCollectorOptions) - -### ℹ️ @ReactionEventCollector - -Create reaction collector - - -#### Params - -See [here](https://discord.js.org/#/docs/main/stable/typedef/ReactionCollectorOptions) - -### ℹ️ @UseCollectors - -Apply collector - -#### Params - -- List of collector classes - -### ℹ️ @InjectCollector - -Inject collector in constructor (only in class collector) - -### ℹ️ @InjectCauseEvent - -Inject cause event in constructor (only in class collector) - -### ℹ️ @Filter - -Add filter to collector - -### ℹ️ @Field - -Extract field from modal form - -### ℹ️ @TextInputValue - -Extract text input value from modal form +# Core module + +## 🧾 Description + +NestJS package for discord.js + +- [👨🏻‍💻 Installation](#Installation) +- [📑 Overview](#Overview) +- [▶️ Usage](#Usage) + - [ℹ️ Creating slash commands](#Command) + - [ℹ️ UI based commands(Context menu commands)](#UIBasedCommand) + - [ℹ️ Automatic registration of slash commands](#AutoRegCommand) + - [ℹ️ Subscribe to event](#SubToEvent) + - [ℹ️ Prefix commands](#PrefixCommands) + - [ℹ️ ️ Pipes, Guards, Interceptors and Filters](#Consumers) + - [ℹ️ Collectors](#Collectors) + - [ℹ️ Modals](#Modals) +- [🛠️ Exported providers](#Providers) + - [ℹ️ DiscordClientProvider](#DiscordClientProvider) + - [ℹ️ DiscordCommandProvider](#DiscordCommandProvider) + - [ℹ️ ReflectMetadataProvider](#ReflectMetadataProvider) + - [ℹ️ CollectorProvider](#CollectorProvider) +- [🗂 Decorators description](#DecoratorsDescription) + - [ℹ️ @InjectDiscordClient](#InjectDiscordClient) + - [ℹ️ @Command](#Command) + - [ℹ️ @SubCommand](#SubCommand) + - [ℹ️ @On](#On) + - [ℹ️ @Once](#Once) + - [ℹ️ @ArgNum](#ArgNum) + - [ℹ️ @ArgRange](#ArgRange) + - [ℹ️ @InteractionEvent/@IA](#InteractionEvent) + - [ℹ️ @InteractionEvent/@MSG](#MessageEvent) + - [ℹ️ @Param](#Param) + - [ℹ️ @Choice](#Choice) + - [ℹ️ @Channel](#Channel) + - [ℹ️ @InteractionEventCollector](#InteractionEventCollector) + - [ℹ️ @MessageEventCollector](#MessageEventCollector) + - [ℹ️ @ReactionEventCollector](#ReactionEventCollector) + - [ℹ️ @UseCollectors](#UseCollectors) + - [ℹ️ @InjectCollector](#InjectCollector) + - [ℹ️ @InjectCauseEvent](#InjectCauseEvent) + - [ℹ️ @Filter](#Filter) + - [ℹ️ @Field](#Field) + - [ℹ️ @TextInputValue](#TextInputValue) + + + + + +## 👨🏻‍💻 Installation + +```bash +$ npm install @discord-nestjs/core discord.js +``` + +Or via yarn + +```bash +$ yarn add @discord-nestjs/core discord.js +``` + + + + + +## 📑 Overview + +> ⚠️**Before you start using, set `useDefineForClassFields` to `true` in your `tsconfig.json`.** + +The module declaration proceeds in the same way as it is done in NestJS by means +creating a dynamic module through the `forRootAsync` functions. + +- `token` \* - Your discord bot token. You can get [here](https://discord.com/developers/applications) +- `discordClientOptions` \* - Client options from discord.js library +- `registerCommandOptions` - Specific registration of slash commands(If option is not set, global commands will be registered) + - `forGuild` - For which guild to register a slash command + - `trigger` - Used in cases where it is necessary to register commands by event + - `allowFactory` - Based on what criteria will slash commands be registered + - `removeCommandsBefore` - Remove mission commands +- `prefix` - Global command prefix +- `prefixGlobalOptions` - Global options for prefix command +- `webhook` - Connecting with webhook + - `webhookId` \* - Webhook id + - `webhookToken` \* - Webhook token +- `autoLogin` - Calling login function from discord client on application bootstrap +- `failOnLogin` - Throw an exception if login failed + +> ⚠️**Important! For the bot to work correctly, you need to set up intentions in `discordClientOptions` param. [More info](https://discordjs.guide/popular-topics/intents.html#privileged-intents)** + +Below is an example of creating a dynamic module using the `forRootAsync` function + +```typescript +/* app.module.ts */ + +import { DiscordModule } from '@discord-nestjs/core'; +import { Module } from '@nestjs/common'; +import { GatewayIntentBits } from 'discord.js'; + +@Module({ + imports: [ + DiscordModule.forRootAsync({ + useFactory: () => ({ + token: 'your-bot-token', + discordClientOptions: { + intents: [GatewayIntentBits.Guilds], + }, + }), + }), + ], +}) +export class AppModule {} +``` + +Alternatively, you can use the `useClass` syntax + +```typescript +/* app.module.ts */ + +import { Module } from '@nestjs/common'; +import { DiscordConfigService } from './discord-config-service'; +import { DiscordModule } from '@discord-nestjs/core'; + +@Module({ + imports: [ + DiscordModule.forRootAsync({ + useClass: DiscordConfigService, + }), + ], +}) +export class AppModule {} +``` + +You need to implement the `DiscordOptionsFactory` interface + +```typescript +/* discord-config.service.ts */ + +import { Injectable } from '@nestjs/common'; +import { + DiscordModuleOption, + DiscordOptionsFactory, +} from '@discord-nestjs/core'; +import { GatewayIntentBits } from 'discord.js'; + +@Injectable() +export class DiscordConfigService implements DiscordOptionsFactory { + createDiscordOptions(): DiscordModuleOption { + return { + token: 'your-bot-token', + discordClientOptions: { + intents: [GatewayIntentBits.Guilds], + }, + }; + } +} +``` + +If you need to inject exported providers outside the `AppModule`, use the `Discord.forFeature()` import. +For example, you need to get the Discord `Client` in your module. + +```typescript +/* bot.module.ts */ + +import { Module } from '@nestjs/common'; +import { DiscordModule } from '@discord-nestjs/core'; +import { BotGateway } from './bot.gateway' + +@Module({ + imports: [DiscordModule.forFeature()], + providers: [BotGateway] +}) +export class BotModule {} +``` + +```typescript +/* bot.gateway.ts */ + +import { Injectable, Logger } from '@nestjs/common'; +import { Once, InjectDiscordClient } from '@discord-nestjs/core'; +import { Client } from 'discord.js'; + +@Injectable() +export class BotGateway { + private readonly logger = new Logger(BotGateway.name); + + constructor( + @InjectDiscordClient() + private readonly client: Client, + ) {} + + @Once('ready') + onReady() { + this.logger.log(`Bot ${this.client.user.tag} was started!`); + } +} +``` + +## ▶️ Usage + +### ℹ️ Creating slash commands + +> If you install `@angular-devkit/schematics-cli` and [@discord-nestjs/schematics](https://github.com/fjodor-rybakov/discord-nestjs/tree/master/packages/schematics) +> , you can run the follow command to create a slash-command bot template: +> `schematics @discord-nestjs/schematics:application --template slash-command` + +To add a slash command, you need to create a class with `@Command` decorator. +The `@Handler` decorator will point to the command processing method. + +#### 💡 Example + +```typescript +/* playlist.command.ts */ + +import { Command, Handler } from '@discord-nestjs/core'; +import { CommandInteraction } from 'discord.js'; +import { Injectable } from '@nestjs/common'; + +@Command({ + name: 'playlist', + description: 'Get current playlist', +}) +@Injectable() +export class PlaylistCommand { + @Handler() + onPlaylist(interaction: CommandInteraction): string { + return 'List with music...'; + } +} +``` + +If your command accepts parameters, you need to create a class with options. + +```typescript +/* registration.dto.ts */ + +import { Param, Choice, ParamType } from '@discord-nestjs/core'; + +enum City { + Moscow, + 'New York', + Tokyo, +} + +export class RegistrationDto { + @Param({ description: 'User name', required: true }) + name: string; + + @Param({ description: 'User age', required: true, type: ParamType.INTEGER }) + age: number; + + @Choice(City) + @Param({ description: 'User city', type: ParamType.INTEGER }) + city: City; +} +``` + +* `@Param` decorator defines command parameter. +* `@Choice` decorator marks command parameter as dropdown(**Accepts `enum` or `Map`**). +* `@Channel` decorator marks command parameter as channel select. + +> By default, if `name` is not passed to the decorator parameters, +> then the name of the marked property will be taken. + +> If the command parameter is a `string` or a `boolean`, then it is not necessary +> to pass the type. The type will resolve **automatically**. + +To get object with command option you need add `@InteractionEvent()`/`@IA()` with `SlashCommandPipe`. +`InteractionEvent` will extract the data from event args and `SlashCommandPipe` will convert the data into an object. + +> You can import `SlashCommandPipe` from [common](https://github.com/fjodor-rybakov/discord-nestjs/tree/master/packages/common) package + +#### 💡 Example + +```typescript +/* registration.command.ts */ + +import {Command, InteractionEvent, Handler} from '@discord-nestjs/core'; +import {SlashCommandPipe} from '@discord-nestjs/common'; +import {Injectable} from '@nestjs/common'; + +import {RegistrationDto} from './registration.dto'; + +@Command({ + name: 'reg', + description: 'User registration', +}) +@Injectable() +export class BaseInfoCommand { + @Handler() + onRegistration(@InteractionEvent(SlashCommandPipe) options: RegistrationDto): string { + return `User was registered with name: ${options.name}, age ${options.age} and city ${options.city}`; + } +} +``` + +> Also, you can validate the options using `ValidationPipe` from [common](https://github.com/fjodor-rybakov/discord-nestjs/tree/master/packages/common) package. + +Each command must be added to a NestJS module. + +```typescript +/* bot-slash-commands.module.ts */ + +import { PlaylistCommand } from './playlist.command'; +import { BaseInfoCommand } from './registration.command'; +import { Module } from '@nestjs/common'; + +@Module({ + providers: [PlaylistCommand, BaseInfoCommand], +}) +export class BotSlashCommandsModule { +} +``` + +Or you can use https://github.com/fjodor-rybakov/nestjs-dynamic-providers for search files by glob pattern. + +```typescript +/* bot-slash-commands.module.ts */ + +import { DiscordModule } from '@discord-nestjs/core'; +import { Module } from '@nestjs/common'; +import { InjectDynamicProviders } from 'nestjs-dynamic-providers'; + +@InjectDynamicProviders('**/*.command.js') +@Module({}) +export class BotSlashCommandsModule {} +``` + +And add your `BotSlashCommandsModule` to `AppModule`. + +```typescript +/* app.module.ts */ + +import { DiscordModule } from '@discord-nestjs/core'; +import { Module } from '@nestjs/common'; +import { GatewayIntentBits } from 'discord.js'; +import { BotSlashCommandsModule } from './bot-slash-commands.module'; + +@Module({ + imports: [ + DiscordModule.forRootAsync({ + useFactory: () => ({ + token: 'your-bot-token', + discordClientOptions: { + intents: [Intents.FLAGS.GUILDS], + }, + }), + }), + BotSlashCommandsModule, + ], +}) +export class AppModule {} +``` + +If your command is more complex, you can add subgroups of commands or subcommands to it. +To do this, you need to add your subgroups and subcommands to the `include` option. The `include` parameter accepts +list of class types or `UseGroup` function, which in turn accepts group parameters and list of subcommands. + +> ⚠️ **Remember that if you define a subgroups of commands or subcommands, this will automatically mark your main command unusable. +> More details in [Discord API](https://canary.discord.com/developers/docs/interactions/slash-commands#subcommands-and-subcommand-groups)** + +#### 💡 Example + +```typescript +/* registration.command.ts */ + +import { BaseInfoSubCommand } from './sub-commands/base-info-sub-command'; +import { EmailSubCommand } from './sub-commands/email-sub-command'; +import { NumberSubCommand } from './sub-commands/number-sub-command'; +import { Command, UseGroup } from '@discord-nestjs/core'; + +@Command({ + name: 'reg', + description: 'User registration', + include: [ + UseGroup( + { name: 'type', description: 'Registration type' }, + NumberSubCommand, + EmailSubCommand, + ), + BaseInfoSubCommand, + ], +}) +export class RegistrationCommand {} +``` + +Subcommands are declared similarly to commands and implement the same interfaces. +To do this, you need to create a class, mark it with the `SubCommand` decorator and +specify which interface they implement(`DiscordCommand` or `DiscordTransformedCommand`) + +#### 💡 Example + +```typescript +/* email-sub-command.ts */ + +import { + Handler, + IA, + SubCommand, +} from '@discord-nestjs/core'; +import {SlashCommandPipe} from '@discord-nestjs/common'; + +import {EmailDto} from '../../dto/email.dto'; + +@SubCommand({name: 'email', description: 'Register by email'}) +export class EmailSubCommand { + @Handler() + onEmail(@IA(SlashCommandPipe) dto: EmailDto): string { + return `Success register user: ${dto.email}, ${dto.name}, ${dto.age}, ${dto.city}`; + } +} +``` + +```typescript +/* number-sub-command.ts */ + +import { + Handler, + IA, + SubCommand, +} from '@discord-nestjs/core'; +import {SlashCommandPipe} from '@discord-nestjs/common'; + +import {NumberDto} from '../../dto/number.dto'; + +@SubCommand({name: 'number', description: 'Register by phone number'}) +export class NumberSubCommand { + @Handler() + onPhoneNumber(@IA(SlashCommandPipe) dto: NumberDto): string { + return `Success register user: ${dto.phoneNumber}, ${dto.name}, ${dto.age}, ${dto.city}`; + } +} +``` + +```typescript +/* base-info-sub-command.ts */ + +import {Handler, SubCommand} from '@discord-nestjs/core'; +import { + CommandInteraction, + InteractionReplyOptions, + MessageEmbed, +} from 'discord.js'; + +@SubCommand({name: 'base-info', description: 'Base info'}) +export class BaseInfoSubCommand { + @Handler() + onBaseInfo(interaction: CommandInteraction): InteractionReplyOptions { + const {user} = interaction; + + const embed = new MessageEmbed() + .setImage(user.avatarURL()) + .addField('Name', user.username); + + return { + embeds: [embed], + }; + } +} +``` + +All commands and sub-commands must also be added to module providers. + +```typescript +/* bot-slash-commands.module.ts */ + +import { RegistrationCommand } from './registration.command'; +import { BaseInfoSubCommand } from './sub-commands/base-info-sub-command'; +import { EmailSubCommand } from './sub-commands/email-sub-command'; +import { NumberSubCommand } from './sub-commands/number-sub-command'; +import { Module } from '@nestjs/common'; + +@Module({ + providers: [ + RegistrationCommand, + NumberSubCommand, + EmailSubCommand, + BaseInfoSubCommand, + ], +}) +export class BotSlashCommandsModule { +} +``` + +### ℹ️ UI based commands(Context menu commands) + +In addition to slash commands, you can define commands through the context menu. +To do this, you need to explicitly set the command type. (`USER` or `MESSAGE`) + +```typescript +/* playlist.command.ts */ + +import {Command, Handler} from '@discord-nestjs/core'; +import {ContextMenuInteraction} from 'discord.js'; +import {ApplicationCommandTypes} from 'discord.js/typings/enums'; + +@Command({ + name: 'playlist', + type: ApplicationCommandTypes.USER, +}) +export class PlaylistCommand { + @Handler() + onPlaylist(interaction: ContextMenuInteraction): string { + return 'Your playlist...'; + } +} +``` + +### ℹ️ Automatic registration of slash commands + +Commands are registered automatically if you define them in code. The `registerCommandOptions` property responds to the +command registration setting. It works according to the following principle: + +* `registerCommandOptions` - takes an array of objects. + + +* If `registerCommandOptions` option is not specified, global commands will be registered by default + +* If `trigger` used in cases where it is necessary to register commands by event +* If `allowFactory` is specified then commands will be registered by condition from `allowFactory` +* If `forGuild` is specified, then commands for a specific guild will be registered +* If `removeCommandsBefore` is specified, then registered commands that are not in your code will be removed + +The `trigger`, `allowFactory`, `forGuild` and `removeCommandsBefore` options are combined with each other. + +> Global commands, unlike guild commands, are cached and updated once per hour. [More info](https://discordjs.guide/interactions/slash-commands.html#global-commands). + +#### 💡 Example + +```typescript +import { DiscordModule } from '@discord-nestjs/core'; +import { Module } from '@nestjs/common'; +import { ConfigModule, ConfigService } from '@nestjs/config'; +import { GatewayIntentBits, Message } from 'discord.js'; +import { BotSlashCommandsModule } from './bot-slash-commands.module'; + +@Module({ + imports: [ + DiscordModule.forRootAsync({ + imports: [ConfigModule], + useFactory: (configService: ConfigService) => ({ + token: configService.get('TOKEN'), + discordClientOptions: { + intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages], + }, + registerCommandOptions: [ + { + forGuild: configService.get('GUILD_ID_WITH_COMMANDS'), + allowFactory: (message: Message) => + !message.author.bot && message.content === '!deploy', + }, + ], + }), + inject: [ConfigService], + }), + BotSlashCommandsModule + ], +}) +export class BotModule {} +``` + +### ℹ️ Subscribe to event + +Subscription to incoming events ([hint](https://gist.github.com/koad/316b265a91d933fd1b62dddfcc3ff584)) + +Use the `@On` decorator to subscribe to an event. `BotGateway` must be added to module providers. + +#### 💡 Example + +```typescript +/* bot.gateway.ts */ + +import { Injectable, Logger } from '@nestjs/common'; +import { On, Once, InjectDiscordClient } from '@discord-nestjs/core'; +import { Client, Message } from 'discord.js'; + +@Injectable() +export class BotGateway { + private readonly logger = new Logger(BotGateway.name); + + constructor( + @InjectDiscordClient() + private readonly client: Client, + ) {} + + @Once('ready') + onReady() { + this.logger.log(`Bot ${this.client.user.tag} was started!`); + } + + @On('messageCreate') + async onMessage(message: Message): Promise { + if (!message.author.bot) { + await message.reply("I'm watching you"); + } + } +} +``` + +You can also subscribe to an event once using the `@Once` decorator + +#### 💡 Example + +```typescript +/* bot.gateway.ts */ + +import { Injectable, Logger } from '@nestjs/common'; +import { Once } from '@discord-nestjs/core'; +import { Message } from 'discord.js'; + +@Injectable() +export class BotGateway { + private readonly logger = new Logger(BotGateway.name); + + @Once('ready') + onReady() { + this.logger.log('Bot was started!'); + } +} +``` + +### ℹ️ Prefix commands + +> If you install `@angular-devkit/schematics-cli` and [@discord-nestjs/schematics](https://github.com/fjodor-rybakov/discord-nestjs/tree/master/packages/schematics) +> , you can run the follow command to create a prefix-command bot template: +> `schematics @discord-nestjs/schematics:application --template prefix-command` + +To create a command with a prefix from the `messageCreate` event use the `PrefixCommandInterceptor`. +The following code will create a `!start` prefix command. + +#### 💡 Example + +```typescript +/* bot.gateway.ts */ + +import {PrefixCommandInterceptor} from '@discord-nestjs/common'; +import { + InjectDiscordClient, + On, + Once, +} from '@discord-nestjs/core'; +import { Injectable, Logger, UseInterceptors } from '@nestjs/common'; +import { Client } from 'discord.js'; + +import { StartDto } from './dto/start.dto'; + +@Injectable() +export class BotGateway { + private readonly logger = new Logger(BotGateway.name); + + constructor( + @InjectDiscordClient() + private readonly client: Client, + ) {} + + @Once('ready') + onReady(): void { + this.logger.log(`Bot ${this.client.user.tag} was started!`); + } + + @On('messageCreate') + @UseInterceptors(new PrefixCommandInterceptor('start')) + async onMessage(message: Message): Promise { + return 'Message processed successfully'; + } +} +``` + +> You can set the `prefix` globally via setting in the `DiscordModule`. + +You can also generate a DTO class based on incoming message content. + +Create an DTO class. Think of the input string as if it were separated by spaces. +For slicing parameters, the decorators `@ArgNum` and `@ArgRange` are used. + +* `@ArgNum` takes value at array index +* `@ArgRange` is the same as the `slice` function + +#### 💡 Example + +```typescript +/* start.dto.ts */ + +import { ArgNum, ArgRange } from '@discord-nestjs/core'; + +export class StartDto { + @ArgNum(() => ({ position: 0 })) + game: string; + + @ArgRange((last) => ({ formPosition: last + 1 })) + players: string[]; +} +``` + +Then just create a command. To get object with command option you need add `@MessageEvent()`/`@MSG()` with `PrefixCommandPipe`. +`MessageEvent` will extract the data from event args and `PrefixCommandPipe` will convert the data into an object. + +> You can import `PrefixCommandPipe` from [common](https://github.com/fjodor-rybakov/discord-nestjs/tree/master/packages/common) package + +```typescript +/* bot.gateway.ts */ + +import {PrefixCommandInterceptor, PrefixCommandPipe} from '@discord-nestjs/common'; +import { + InjectDiscordClient, + On, + Once, + MessageEvent +} from '@discord-nestjs/core'; +import { Injectable, Logger, UseInterceptors } from '@nestjs/common'; +import { Client } from 'discord.js'; + +import {StartDto} from './dto/start.dto'; + +@Injectable() +export class BotGateway { + private readonly logger = new Logger(BotGateway.name); + + constructor( + @InjectDiscordClient() + private readonly client: Client, + ) { + } + + @Once('ready') + onReady() { + this.logger.log(`Bot ${this.client.user.tag} was started!`); + } + + @PrefixCommand('start') + @UsePipes(PrefixCommandPipe) + async onMessage(@MessageEvent(PrefixCommandPipe) dto: StartDto): Promise { + console.log(dto); + + return 'Message processed successfully'; + } +} +``` + +`BotGateway` must be added to module providers. + +The message `!start warzone misha mark` in the channel should generate +`StartDto { game: 'warzone', players: [ 'misha', 'mark' ] }` DTO. + + +### ℹ️ Pipes, Guards, Interceptors and Filters + +[Pipes](https://docs.nestjs.com/pipes), [Guards](https://docs.nestjs.com/guards), [Interceptors](https://docs.nestjs.com/interceptors) +and [Filter](https://docs.nestjs.com/exception-filters) work the same as Nest. + +The only exception is guard. In NestJS `Guard` consumer throw Forbidden exception, when access denied. +The `discord-nestjs` listeners catch `ForbiddenException` by default. To override this behavior set +`isTrowForbiddenException: true` in `DiscordModule` options. + +### ℹ️ Collectors + +In addition to the standard implementation of `collectors` from `discord.js`, `discord-nestjs` provides their declaration +through decorators. You can create three types of collectors: ReactCollector, MessageCollector and InteractionCollector. + +The first thing you need to do is create a collector class and mark it with either `@MessageEventCollector` or +`@ReactionEventCollector` or `@InteractionEventCollector` with a decorator. For example, let's create a `ReactionCollector`. + +#### 💡 Example + +```typescript +/* appreciated-reaction-collector.ts */ + +import { + Filter, + InjectCollector, + On, + Once, + ReactionEventCollector, +} from '@discord-nestjs/core'; +import { Injectable, Scope } from '@nestjs/common'; +import { MessageReaction, ReactionCollector, User } from 'discord.js'; + +@Injectable({ scope: Scope.REQUEST }) +@ReactionEventCollector({ time: 15000 }) +export class AppreciatedReactionCollector { + constructor( + @InjectCollector() + private readonly collector: ReactionCollector, + ) {} + + @Filter() + isLikeFromAuthor(reaction: MessageReaction, user: User): boolean { + return ( + reaction.emoji.name === '👍' && user.id === reaction.message.author.id + ); + } + + @On('collect') + onCollect(): void { + console.log('collect'); + } + + @Once('end') + onEnd(): void { + console.log('end'); + } +} +``` + +Let me explain in detail what is going on here. + +* We marked the `AppreciatedReactionCollector` class with the `@ReactionEventCollector` decorator and passed collector + options as decorator argument. Think of it like we created `message.createReactionCollector({ time: 15000 });` from + `discord.js` library. +* The `@InjectCollector` injects the value of the collector into the class constructor. +If you use this decorator, you need to add `scope: Scope.REQUEST`. The default is `scope: Scope.DEFAULT`. +* The `@Filter` decorator filters the incoming data into the collector. Treat it like the `filter` option in `createReactionCollector`. +* Decorators `On` and `Once` subscribe to collector events. + +> Filters, guards, interceptors and pipes can be applied to collector events. + +To apply your collector to the message use `@UseCollectors` decorator. + +#### 💡 Example + +```typescript +/* bot.gateway.ts */ + +import { On, Once, UseCollectors } from '@discord-nestjs/core'; +import { CollectorInterceptor } from '@discord-nestjs/common'; +import { Injectable, Logger, UseGuards, UseInterceptors } from '@nestjs/common'; +import { Message } from 'discord.js'; + +import { AppreciatedReactionCollector } from './appreciated-reaction-collector'; +import { MessageFromUserGuard } from './guards/message-from-user.guard'; + +@Injectable() +export class BotGateway { + private readonly logger = new Logger(BotGateway.name); + + @Once('ready') + onReady(): void { + this.logger.log('Bot was started!'); + } + + @On('messageCreate') + @UseGuards(MessageFromUserGuard) + @UseCollectors(AppreciatedReactionCollector) + @UseInterceptors(CollectorInterceptor) + async onMessage(message: Message): Promise { + await message.reply('Start collector'); + } +} +``` + +In order for the collector to be called in the correct order, you need to hang `CollectorInterceptor` interceptor. + +> You can import `CollectorInterceptor` from [common](https://github.com/fjodor-rybakov/discord-nestjs/tree/master/packages/common) package + +If you need to get applied collectors use `@AppliedCollectors` param decorator. + +Other collectors types are created exactly by analogy. They apply to both event handlers and commands. + +For example, below is a sample button creation. + +#### 💡 Example + +```typescript +/* post-interaction-collector.ts */ + +import { + Filter, + InjectCauseEvent, + InteractionEventCollector, + On, +} from '@discord-nestjs/core'; +import { Injectable, Scope } from '@nestjs/common'; +import { ButtonInteraction, ChatInputCommandInteraction } from 'discord.js'; + +@Injectable({ scope: Scope.REQUEST }) +@InteractionEventCollector({ time: 15000 }) +export class PostInteractionCollector { + constructor( + @InjectCauseEvent() + private readonly causeInteraction: ChatInputCommandInteraction, + ) {} + + @Filter() + filter(interaction: ButtonInteraction): boolean { + return this.causeInteraction.id === interaction.message.interaction.id; + } + + @On('collect') + async onCollect(interaction: ButtonInteraction): Promise { + await interaction.update({ + content: 'A button was clicked!', + components: [], + }); + } +} + +``` + +* The `@InjectCauseEvent` decorator allow you get event that created the collector + +```typescript +/* play.command.ts */ + +import { CollectorInterceptor, SlashCommandPipe } from '@discord-nestjs/common'; +import { + AppliedCollectors, + Command, + Handler, + IA, + UseCollectors, +} from '@discord-nestjs/core'; +import { MessageActionRowComponentBuilder } from '@discordjs/builders'; +import { UseInterceptors } from '@nestjs/common'; +import { + ActionRowBuilder, + ButtonBuilder, + ButtonInteraction, + ButtonStyle, + InteractionCollector, + InteractionReplyOptions, +} from 'discord.js'; + +import { PlayDto } from '../dto/play.dto'; +import { PostInteractionCollector } from '../post-interaction-collector'; + +@Command({ + name: 'play', + description: 'Plays a song', +}) +@UseInterceptors(CollectorInterceptor) +@UseCollectors(PostInteractionCollector) +export class PlayCommand { + @Handler() + async onPlayCommand( + @IA(SlashCommandPipe) dto: PlayDto, + @AppliedCollectors(0) collector: InteractionCollector, + ): Promise { + const row = + new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setCustomId('primary') + .setLabel(dto.song) + .setStyle(ButtonStyle.Primary), + ); + + console.log(collector); + + return { + content: 'Click on the button to play the song!', + components: [row], + }; + } +} +``` + +### ℹ️ Modals + +Full example is shown [here](https://github.com/fjodor-rybakov/discord-nestjs/tree/master/packages/sample/modals) + + + + + +## 🛠️ Exported providers + +`DiscordModule` currently exports only three providers. + +### ℹ️ DiscordClientProvider + +Provides the discord client or webhook client. + +### ℹ️ DiscordCommandProvider + +`discord-nestjs` package builds slash command object based on all decorators and DiscordCommandProvider provides it. +For example, this is useful when you need to lazily register commands or register commands for a specific guild. + +### ℹ️ ReflectMetadataProvider + +Provides methods for getting metadata for decorators. + +### ℹ️ CollectorProvider + +Allow you to apply collector event + + + + +## 🗂 Decorators description + +### ℹ️ @InjectDiscordClient + +Inject Discord.js client + +### ℹ️ @Command + +Mark class as command + +#### Params + +- `name` \* - Command name +- `description` \* - Command description +- `include` - Include subgroups and subcommands +- `defaultMemberPermissions` - Set default permission +- `dmPermission` - Has DM permission + +### ℹ️ @SubCommand + +Mark class as subcommand + +#### Params + +- `name` \* - Command name +- `description` \* - Command description + +### ℹ️ @On + +Handle discord and collector events [hint](https://gist.github.com/koad/316b265a91d933fd1b62dddfcc3ff584) + +#### Params + +`event` \* - Name of the event to listen to + +### ℹ️ @Once + +Handle discord and collector events (only once) [hint](https://gist.github.com/koad/316b265a91d933fd1b62dddfcc3ff584) + +### ℹ️ @ArgNum + +Set value by argument number + +#### Params + +- arguments + - `last` - Last index position +- return + - `position` \* - Position index form input + +### ℹ️ @ArgRange + +Set value by argument number + +#### Params + +- arguments + - `last` - Last index position +- return + - `formPosition` \* - Start index position form input + - `toPosition` - Finish index position form input (default last index of input) + +#### Params + +`event` \* - Name of the event to listen to + +### ℹ️ @InteractionEvent/@IA + +Extract interaction event from args + +### ℹ️ @MessageEvent/@MSG + +Extract message event from args + +### ℹ️ @Param + +Sets the command parameter + +#### Params + +- `description` \* - Command description +- `name` - Command name +- `required` - The parameter is required +- `autocomplete` - Send autocomplete interaction(Only for `string`, `number` and `integer`) +- `minValue` - Min value for `number` or `integer` +- `maxValue` - Max value for `number` or `integer` +- `type` - Specifies the type of the parameter + +### ℹ️ @Choice + +Marks command parameter as dropdown. + +#### Params + +(**Accepts `enum` or `Map`**) + +### ℹ️ @Channel + +Marks command parameter as channel select. + +#### Params + +`channelType` - list of channel types + +### ℹ️ @InteractionEventCollector + +Create interaction collector + +#### Params + +See [here](https://discord.js.org/#/docs/main/stable/typedef/MessageComponentCollectorOptions) + +### ℹ️ @MessageEventCollector + +Create message collector + +#### Params + +See [here](https://discord.js.org/#/docs/main/stable/typedef/MessageCollectorOptions) + +### ℹ️ @ReactionEventCollector + +Create reaction collector + + +#### Params + +See [here](https://discord.js.org/#/docs/main/stable/typedef/ReactionCollectorOptions) + +### ℹ️ @UseCollectors + +Apply collector + +#### Params + +- List of collector classes + +### ℹ️ @InjectCollector + +Inject collector in constructor (only in class collector) + +### ℹ️ @InjectCauseEvent + +Inject cause event in constructor (only in class collector) + +### ℹ️ @Filter + +Add filter to collector + +### ℹ️ @Field + +Extract field from modal form + +### ℹ️ @TextInputValue + +Extract text input value from modal form diff --git a/packages/core/src/decorators/command/options/command-options.contant.ts b/packages/core/src/decorators/command/options/command-options.contant.ts deleted file mode 100644 index 1ee67f62..00000000 --- a/packages/core/src/decorators/command/options/command-options.contant.ts +++ /dev/null @@ -1 +0,0 @@ -export const COMMAND_OPTIONS = '__command_options__'; diff --git a/packages/core/src/decorators/command/options/command-options.decorator.ts b/packages/core/src/decorators/command/options/command-options.decorator.ts index a3b4a29d..b94a95d3 100644 --- a/packages/core/src/decorators/command/options/command-options.decorator.ts +++ b/packages/core/src/decorators/command/options/command-options.decorator.ts @@ -1,9 +1,4 @@ -import { COMMAND_OPTIONS } from './command-options.contant'; - -export function CommandOptions(): ClassDecorator { - return (target: TFunction): TFunction | void => { - Reflect.defineMetadata(COMMAND_OPTIONS, {}, target.prototype); - - return target; - }; -} +/** + * @deprecated will be remove in next release + */ +export function CommandOptions() {} diff --git a/packages/core/src/providers/reflect-metadata.provider.ts b/packages/core/src/providers/reflect-metadata.provider.ts index 5c7303b6..20199b99 100644 --- a/packages/core/src/providers/reflect-metadata.provider.ts +++ b/packages/core/src/providers/reflect-metadata.provider.ts @@ -15,7 +15,6 @@ import { USE_COLLECTORS_METADATA } from '../decorators/collector/use-collectors/ import { ChatInputCommandOptions } from '../decorators/command/chat-input-command-options'; import { COMMAND_DECORATOR } from '../decorators/command/command.constant'; import { HANDLER_DECORATOR } from '../decorators/command/handler/handler.constant'; -import { COMMAND_OPTIONS } from '../decorators/command/options/command-options.contant'; import { OnCollectDecoratorOptions } from '../decorators/event/on-collect-decorator-options'; import { OnDecoratorOptions } from '../decorators/event/on-decorator-options'; import { @@ -59,10 +58,6 @@ export class ReflectMetadataProvider { return Reflect.getMetadata(HANDLER_DECORATOR, instance, methodName); } - getCommandOptionsDecoratorMetadata(classType: Type): Record { - return Reflect.getMetadata(COMMAND_OPTIONS, classType.prototype); - } - getSubCommandDecoratorMetadata( instance: InstanceType, ): SubCommandOptions { diff --git a/packages/core/src/services/dto.service.ts b/packages/core/src/services/dto.service.ts index 83ec0edd..6799728b 100644 --- a/packages/core/src/services/dto.service.ts +++ b/packages/core/src/services/dto.service.ts @@ -49,13 +49,13 @@ export class DtoService { const [commandOption] = await lastValueFrom( from(paramsTypes).pipe( - map((type, index) => ({ - type, - index, - metadata: - this.metadataProvider.getCommandOptionsDecoratorMetadata(type), - })), - filter(({ metadata }) => !!metadata), + map((type) => { + return { + type, + isDto: this.isDto(type), + }; + }), + filter(({ isDto }) => isDto), toArray(), ), ); @@ -102,4 +102,27 @@ export class DtoService { return commandOptions; } + + private isDto(type: Type): boolean { + try { + const instance = new type(); + const allProperties = Object.keys(instance); + + return allProperties.some( + (property) => + !!( + this.metadataProvider.getParamDecoratorMetadata(type, property) || + this.metadataProvider.getArgNumDecoratorMetadata(type, property) || + this.metadataProvider.getArgNumDecoratorMetadata(type, property) || + this.metadataProvider.getFiledDecoratorMetadata(type, property) || + this.metadataProvider.getTextInputValueDecoratorMetadata( + type, + property, + ) + ), + ); + } catch { + return false; + } + } } diff --git a/packages/sample/command-by-glob/src/bot/dto/play.dto.ts b/packages/sample/command-by-glob/src/bot/dto/play.dto.ts index 099851cc..8dc1aa2d 100644 --- a/packages/sample/command-by-glob/src/bot/dto/play.dto.ts +++ b/packages/sample/command-by-glob/src/bot/dto/play.dto.ts @@ -1,7 +1,6 @@ -import { CommandOptions, Param } from '@discord-nestjs/core'; +import { Param } from '@discord-nestjs/core'; import { Transform } from 'class-transformer'; -@CommandOptions() export class PlayDto { @Transform(({ value }) => value.toUpperCase()) @Param({ diff --git a/packages/sample/command-by-http-request/src/bot/dto/play.dto.ts b/packages/sample/command-by-http-request/src/bot/dto/play.dto.ts index 099851cc..8dc1aa2d 100644 --- a/packages/sample/command-by-http-request/src/bot/dto/play.dto.ts +++ b/packages/sample/command-by-http-request/src/bot/dto/play.dto.ts @@ -1,7 +1,6 @@ -import { CommandOptions, Param } from '@discord-nestjs/core'; +import { Param } from '@discord-nestjs/core'; import { Transform } from 'class-transformer'; -@CommandOptions() export class PlayDto { @Transform(({ value }) => value.toUpperCase()) @Param({ diff --git a/packages/sample/command/src/bot/dto/play.dto.ts b/packages/sample/command/src/bot/dto/play.dto.ts index 099851cc..8dc1aa2d 100644 --- a/packages/sample/command/src/bot/dto/play.dto.ts +++ b/packages/sample/command/src/bot/dto/play.dto.ts @@ -1,7 +1,6 @@ -import { CommandOptions, Param } from '@discord-nestjs/core'; +import { Param } from '@discord-nestjs/core'; import { Transform } from 'class-transformer'; -@CommandOptions() export class PlayDto { @Transform(({ value }) => value.toUpperCase()) @Param({ diff --git a/packages/sample/dependency-injection/src/bot/dto/play.dto.ts b/packages/sample/dependency-injection/src/bot/dto/play.dto.ts index 099851cc..8dc1aa2d 100644 --- a/packages/sample/dependency-injection/src/bot/dto/play.dto.ts +++ b/packages/sample/dependency-injection/src/bot/dto/play.dto.ts @@ -1,7 +1,6 @@ -import { CommandOptions, Param } from '@discord-nestjs/core'; +import { Param } from '@discord-nestjs/core'; import { Transform } from 'class-transformer'; -@CommandOptions() export class PlayDto { @Transform(({ value }) => value.toUpperCase()) @Param({ diff --git a/packages/sample/interaction-collector/src/bot/dto/play.dto.ts b/packages/sample/interaction-collector/src/bot/dto/play.dto.ts index ed3dc8bb..73703f91 100644 --- a/packages/sample/interaction-collector/src/bot/dto/play.dto.ts +++ b/packages/sample/interaction-collector/src/bot/dto/play.dto.ts @@ -1,6 +1,5 @@ -import { CommandOptions, Param } from '@discord-nestjs/core'; +import { Param } from '@discord-nestjs/core'; -@CommandOptions() export class PlayDto { @Param({ name: 'song', diff --git a/packages/sample/sub-command/src/bot/dto/email.dto.ts b/packages/sample/sub-command/src/bot/dto/email.dto.ts index c3b8fa97..5d32b0d1 100644 --- a/packages/sample/sub-command/src/bot/dto/email.dto.ts +++ b/packages/sample/sub-command/src/bot/dto/email.dto.ts @@ -1,8 +1,7 @@ -import { Choice, CommandOptions, Param, ParamType } from '@discord-nestjs/core'; +import { Choice, Param, ParamType } from '@discord-nestjs/core'; import { City } from '../definitions/city'; -@CommandOptions() export class EmailDto { @Param({ description: 'Base user email', diff --git a/packages/sample/sub-command/src/bot/dto/number.dto.ts b/packages/sample/sub-command/src/bot/dto/number.dto.ts index baf9525e..1d9ebb03 100644 --- a/packages/sample/sub-command/src/bot/dto/number.dto.ts +++ b/packages/sample/sub-command/src/bot/dto/number.dto.ts @@ -1,8 +1,7 @@ -import { Choice, CommandOptions, Param, ParamType } from '@discord-nestjs/core'; +import { Choice, Param, ParamType } from '@discord-nestjs/core'; import { City } from '../definitions/city'; -@CommandOptions() export class NumberDto { @Param({ name: 'phone-number', diff --git a/packages/sample/validation/src/bot/dto/stats.dto.ts b/packages/sample/validation/src/bot/dto/stats.dto.ts index 4a32bc12..ee25eae3 100644 --- a/packages/sample/validation/src/bot/dto/stats.dto.ts +++ b/packages/sample/validation/src/bot/dto/stats.dto.ts @@ -1,7 +1,6 @@ -import { CommandOptions, Param } from '@discord-nestjs/core'; +import { Param } from '@discord-nestjs/core'; import { IsAlphanumeric, IsPhoneNumber } from 'class-validator'; -@CommandOptions() export class StatsDto { @IsAlphanumeric() @Param({ diff --git a/packages/schematics/src/lib/application/files/command/src/bot/dto/play.dto.ts b/packages/schematics/src/lib/application/files/command/src/bot/dto/play.dto.ts index 099851cc..8dc1aa2d 100644 --- a/packages/schematics/src/lib/application/files/command/src/bot/dto/play.dto.ts +++ b/packages/schematics/src/lib/application/files/command/src/bot/dto/play.dto.ts @@ -1,7 +1,6 @@ -import { CommandOptions, Param } from '@discord-nestjs/core'; +import { Param } from '@discord-nestjs/core'; import { Transform } from 'class-transformer'; -@CommandOptions() export class PlayDto { @Transform(({ value }) => value.toUpperCase()) @Param({