A progressive Node.js framework for building efficient and scalable server-side applications, heavily inspired by Angular.
$ npm install @discord-nestjs/core discord.jsOr via yarn
$ yarn add @discord-nestjs/core discord.jsNestJS package for discord.js
This monorepo consists of several packages.
- @discord-nestjs/core - Main package containing decorators, basic types and module declaration.
- @discord-nestjs/common - Contains optional common templates. For example TransformPipe or ValidationPipe.
- @discord-nestjs/schematics - Provides cli to create a bot template.
- Samples
- @sample/command - Bot example with slash commands
- @sample/command-by-glob - Bot example with slash commands by glob pattern
- @sample/command-by-http-request - Bot example with register slash commands by http request
- @sample/sub-command - Bot example with slash sub-commands and sub-groups
- @sample/validation - Bot example with slash commands validation
- @sample/event - Bot example with events
- @sample/dependency-injection - Bot example with dependency injection
- @sample/reaction-collector - Bot example with reaction collector
- @sample/message-collector - Bot example with message collector
- @sample/interaction-collector - Bot example with interaction collector
- @sample/prefix-command - Bot example with prefix command
- @sample/modals - Bot example with modals
Click to expand
For ease of understanding, move your bot declarations to the root module(AppModule).
/* app.module.ts */
import { DiscordModule } from '@discord-nestjs/core';
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { GatewayIntentBits } from 'discord.js';
@Module({
imports: [
ConfigModule.forRoot(),
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'),
removeCommandsBefore: true,
},
],
}),
inject: [ConfigService],
}),
],
})
export class AppModule {}Bot components(such as the slash command class or gateways) no longer related with DiscordModule. Absolutely all providers
are searched globally through all modules. If you need to inject Discord client, you can only do this if you have
exported providers from DiscordModule. The DiscordModule is not global, so a new forFeature function has been added.
/* bot.module.ts */
import { DiscordModule } from '@discord-nestjs/core';
import { Module } from '@nestjs/common';
import { BotGatewaty } from './bot.gateway';
@Module({
imports: [DiscordModule.forFeature()],
providers: [BotGatewaty],
})
export class BotModule {}/* bot.gateway.ts */
import { InjectDiscordClient, Once } from '@discord-nestjs/core';
import { Injectable, Logger } from '@nestjs/common';
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!`);
}
}So the extraProviders option is no longer needed.
The Request lifecycle has also been reworked. Now he repeats it like in NestJS.
- Incoming request
- Globally bound middleware
- Global guards
- Controller guards
- Route guards
- Global pipes
- Controller pipes
- Route pipes
- Method handler
- Exception filters (route, then controller, then global). Apply from end to beginning.
- Response
Removed options responsible for adding global guards, pipes and filters. Instead, add providers to the AppModule like so:
registerGuardGlobally()- use for register global guardregisterPipeGlobally()- use for register global piperegisterFilterGlobally()- use for register global guard
The functions generate an always unique id, so each provider will be registered.
/* app.module.ts */
import { DiscordModule, registerGuardGlobally, registerFilterGlobally } from '@discord-nestjs/core';
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { GatewayIntentBits } from 'discord.js';
import { MyGlobalGuard } from './my-global-guard';
import { MySecondGlobalGuard } from './my-second-global-guard';
import { MyGlobalFilter } from './my-global-filter';
@Module({
imports: [
ConfigModule.forRoot(),
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'),
removeCommandsBefore: true,
},
],
}),
inject: [ConfigService],
}),
],
providers: [
{
provide: registerGuardGlobally(),
useClass: MyGlobalGuard,
},
{
provide: registerGuardGlobally(),
useClass: MySecondGlobalGuard,
},
{
provide: registerFilterGlobally(),
useClass: MyGlobalFilter,
},
],
})
export class AppModule {}If you are using InjectCollector decorator, add scope: Scope.REQUEST.
/* 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 }) // <--- here
@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');
}
}Previously, you could use the commands option, which allowed you to search files by glob pattern. All this functionality
was moved to a separate library https://github.com/fjodor-rybakov/nestjs-dynamic-providers.
Mark the BotModule with the @InjectDynamicProviders decorator.
/* bot.module.ts */
import { DiscordModule } from '@discord-nestjs/core';
import { Module } from '@nestjs/common';
import { InjectDynamicProviders } from 'nestjs-dynamic-providers';
@InjectDynamicProviders('**/*.command.js')
@Module({
imports: [DiscordModule.forFeature()],
})
export class BotModule {}Also add the resolveDynamicProviders() function before creating the Nest application for add metadata for each module.
/* main.ts */
import { AppModule } from './app.module';
import { NestFactory } from '@nestjs/core';
import { resolveDynamicProviders } from 'nestjs-dynamic-providers';
async function bootstrap() {
await resolveDynamicProviders();
await NestFactory.createApplicationContext(AppModule);
}
bootstrap();By default, classes are searched for that are marked with @Injectable() decorator. To override you need to pass filterPredicate as parameters to @InjectDynamicProviders().
Example with filter for `@Command` decorator only
/* bot.module.ts */
import { COMMAND_DECORATOR, DiscordModule } from '@discord-nestjs/core';
import { Module } from '@nestjs/common';
import { InjectDynamicProviders, IsObject } from 'nestjs-dynamic-providers';
@InjectDynamicProviders({
pattern: '**/*.command.js',
filterPredicate: (type) =>
IsObject(type) && Reflect.hasMetadata(COMMAND_DECORATOR, type.prototype),
})
@Module({
imports: [DiscordModule.forFeature()],
})
export class BotModule {}Click to expand
Check your intent is passed to the discordClientOptions of the module. More info
I created DTO and added TransformPipe, but when I receive response to the command, the DTO fields are missing
Click to expand
Set useDefineForClassFields to true in your tsconfig.json.
Also check that the Palyoad and UsePipes decorators are imported from @discord-nestjs/core.
Any questions or suggestions? Join Discord https://discord.gg/kv89Q2dXSR