Phoscur / discord-nestjs

πŸ‘Ύ NestJS package for discord.js

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Nest Logo

A progressive Node.js framework for building efficient and scalable server-side applications, heavily inspired by Angular.

Package License

πŸ‘¨πŸ»β€πŸ’» 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.

❓ 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.

  1. Incoming request
  2. Globally bound middleware
  3. Global guards
  4. Controller guards
  5. Route guards
  6. Global pipes
  7. Controller pipes
  8. Route pipes
  9. Method handler
  10. Exception filters (route, then controller, then global). Apply from end to beginning.
  11. 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

About

πŸ‘Ύ NestJS package for discord.js

License:MIT License


Languages

Language:TypeScript 99.6%Language:JavaScript 0.3%Language:Shell 0.1%