A progressive Node.js framework for building efficient and scalable server-side applications, heavily inspired by Angular.
$ npm install @discord-nestjs/core discord.js
Or via yarn
$ yarn add @discord-nestjs/core discord.js
NestJS 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