discord-nestjs
discord-nestjs copied to clipboard
๐พ NestJS package for discord.js
A progressive Node.js framework for building efficient and scalable server-side applications, heavily inspired by Angular.
๐จ๐ปโ๐ป Installation
$ npm install @discord-nestjs/core discord.js
Or via yarn
$ yarn add @discord-nestjs/core discord.js
๐งพ Description
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
โ Answers on questions
How to migrate from v2 to v3
Click to expand
Modules
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.
Guards, pipes and filters
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 guard -
registerPipeGlobally()
- use for register global pipe -
registerFilterGlobally()
- 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 {}
Collectors
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');
}
}
Providers by glob pattern
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 {}
The bot starts up, but the slash commands and events do not work
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