Skip to content

Commit

Permalink
Merge pull request #973 from fjodor-rybakov/feature/v5
Browse files Browse the repository at this point in the history
Feature/v5
  • Loading branch information
fjodor-rybakov authored Jan 29, 2023
2 parents 064ceb2 + 99a8a5c commit 191753d
Show file tree
Hide file tree
Showing 174 changed files with 3,685 additions and 6,696 deletions.
275 changes: 3 additions & 272 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ NestJS package for discord.js

This monorepo consists of several packages.
* [@discord-nestjs/core](https://github.com/fjodor-rybakov/discord-nestjs/tree/master/packages/core) - Main package containing decorators, basic types and module declaration.
* [@discord-nestjs/common](https://github.com/fjodor-rybakov/discord-nestjs/tree/master/packages/common) - Contains optional common templates. For example TransformPipe or ValidationPipe.
* [@discord-nestjs/common](https://github.com/fjodor-rybakov/discord-nestjs/tree/master/packages/common) - Contains optional common templates. For example SlashCommandPipe or ValidationPipe.
* [@discord-nestjs/schematics](https://github.com/fjodor-rybakov/discord-nestjs/tree/master/packages/schematics) - Provides cli to create a bot template.
* Samples
* [@sample/command](https://github.com/fjodor-rybakov/discord-nestjs/tree/master/packages/sample/command) - Bot example with slash commands
Expand All @@ -55,275 +55,6 @@ This monorepo consists of several packages.

## ❓ Answers on questions

### How to migrate from v2 to v3

<details>
<summary>Click to expand</summary>

#### Modules

For ease of understanding, move your bot declarations to the root module(AppModule).

```typescript
/* 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.

```typescript
/* 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 {}
```

```typescript
/* 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.
```typescript
/* 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`.

```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 }) // <--- 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.

```typescript
/* 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.

```typescript
/* 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().
<details>
<summary>Example with filter for `@Command` decorator only</summary>

```typescript
/* 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 {}

```
</details>

</details>

### The bot starts up, but the slash commands and events do not work

<details>
Expand All @@ -333,13 +64,13 @@ Check your intent is passed to the `discordClientOptions` of the module. [More i

</details>

### I created DTO and added `TransformPipe`, but when I receive response to the command, the DTO fields are missing
### I created DTO and added `SlashCommandPipe`, but when I receive response to the command, the DTO fields are missing

<details>
<summary>Click to expand</summary>

Set `useDefineForClassFields` to `true` in your `tsconfig.json`.
Also check that the `Palyoad` and `UsePipes` decorators are imported from `@discord-nestjs/core`.
Also check that the `@CommandOptions` and `@InteractionEvent` decorators are set.

</details>

Expand Down
Loading

0 comments on commit 191753d

Please sign in to comment.