Skip to content

Necord

Necord 是一个强大的模块,它简化了 Discord 机器人开发,并且可以无缝集成进你的 NestJS 应用。

注意

Necord 是第三方包,并不是由 NestJS 核心团队官方维护的。如果你遇到问题,请在其官方仓库中反馈。

安装

首先,你需要安装 Necord 以及它的依赖 Discord.js

bash
$ npm install necord discord.js

用法

要在项目中使用 Necord,请导入 NecordModule 并使用必要选项进行配置。

typescript
// app.module.ts
import { Module } from '@nestjs/common';
import { NecordModule } from 'necord';
import { IntentsBitField } from 'discord.js';
import { AppService } from './app.service';

@Module({
  imports: [
    NecordModule.forRoot({
      token: process.env.DISCORD_TOKEN,
      intents: [IntentsBitField.Flags.Guilds],
      development: [process.env.DISCORD_DEVELOPMENT_GUILD_ID],
    }),
  ],
  providers: [AppService],
})
export class AppModule {}

提示

可用 intents 的完整列表请见这里

完成这一步后,你就可以在 provider 中注入 AppService,轻松注册命令、事件等功能。

typescript
// app.service.ts
import { Injectable, Logger } from '@nestjs/common';
import { Context, On, Once, ContextOf } from 'necord';
import { Client } from 'discord.js';

@Injectable()
export class AppService {
  private readonly logger = new Logger(AppService.name);

  @Once('ready')
  public onReady(@Context() [client]: ContextOf<'ready'>) {
    this.logger.log(`Bot logged in as ${client.user.username}`);
  }

  @On('warn')
  public onWarn(@Context() [message]: ContextOf<'warn'>) {
    this.logger.warn(message);
  }
}

理解上下文

你可能已经注意到上面示例中的 @Context 装饰器。这个装饰器会把事件上下文注入到方法中,从而让你访问各种与事件相关的数据。由于事件类型有很多种,上下文类型会通过 ContextOf<type: string> 自动推导。你可以通过 @Context() 装饰器轻松获取上下文变量,它会为该变量填入与当前事件相关的参数数组。

文本命令

注意

文本命令依赖 message content,而对于已验证机器人以及加入超过 100 个服务器的应用,这项能力正在被弃用。这意味着如果你的机器人无法访问 message content,文本命令将无法工作。关于这一变更的更多信息见这里

下面展示了如何使用 @TextCommand 装饰器创建一个简单的消息命令处理器。

typescript
// app.commands.ts
import { Injectable } from '@nestjs/common';
import { Context, TextCommand, TextCommandContext, Arguments } from 'necord';

@Injectable()
export class AppCommands {
  @TextCommand({
    name: 'ping',
    description: 'Responds with pong!',
  })
  public onPing(
    @Context() [message]: TextCommandContext,
    @Arguments() args: string[],
  ) {
    return message.reply('pong!');
  }
}

应用命令

应用命令为用户在 Discord 客户端内与应用交互提供了原生方式。应用命令有三种类型,可通过不同界面访问:聊天输入命令、消息上下文菜单命令(右键消息)以及用户上下文菜单命令(右键用户)。

Slash 命令

Slash 命令是一种非常适合与用户进行结构化交互的方式。它允许你创建带有精确定义参数和选项的命令,从而显著改善用户体验。

要使用 Necord 定义 Slash 命令,可以使用 SlashCommand 装饰器。

typescript
// app.commands.ts
import { Injectable } from '@nestjs/common';
import { Context, SlashCommand, SlashCommandContext } from 'necord';

@Injectable()
export class AppCommands {
  @SlashCommand({
    name: 'ping',
    description: 'Responds with pong!',
  })
  public async onPing(@Context() [interaction]: SlashCommandContext) {
    return interaction.reply({ content: 'Pong!' });
  }
}

提示

当机器人客户端登录后,它会自动注册所有定义好的命令。请注意,全局命令最多会被缓存一小时。为了避免全局缓存带来的问题,可以利用 Necord 模块中的 development 参数,将命令可见性限制在单个 guild 中。

选项

你可以使用 option 装饰器为 Slash 命令定义参数。下面我们创建一个 TextDto 类来演示:

typescript
// text.dto.ts
import { StringOption } from 'necord';

export class TextDto {
  @StringOption({
    name: 'text',
    description: 'Input your text here',
    required: true,
  })
  text: string;
}

随后就可以在 AppCommands 类中使用这个 DTO:

typescript
// app.commands.ts
import { Injectable } from '@nestjs/common';
import { Context, SlashCommand, Options, SlashCommandContext } from 'necord';
import { TextDto } from './length.dto';

@Injectable()
export class AppCommands {
  @SlashCommand({
    name: 'length',
    description: 'Calculate the length of your text',
  })
  public async onLength(
    @Context() [interaction]: SlashCommandContext,
    @Options() { text }: TextDto,
  ) {
    return interaction.reply({
      content: `The length of your text is: ${text.length}`,
    });
  }
}

完整的内置 option 装饰器列表请参见这份文档

自动补全

要为 Slash 命令实现自动补全功能,你需要创建一个拦截器。这个拦截器会在用户输入自动补全字段时处理请求。

typescript
// cats-autocomplete.interceptor.ts
import { Injectable } from '@nestjs/common';
import { AutocompleteInteraction } from 'discord.js';
import { AutocompleteInterceptor } from 'necord';

@Injectable()
class CatsAutocompleteInterceptor extends AutocompleteInterceptor {
  public transformOptions(interaction: AutocompleteInteraction) {
    const focused = interaction.options.getFocused(true);
    let choices: string[];

    if (focused.name === 'cat') {
      choices = ['Siamese', 'Persian', 'Maine Coon'];
    }

    return interaction.respond(
      choices
        .filter((choice) => choice.startsWith(focused.value.toString()))
        .map((choice) => ({ name: choice, value: choice })),
    );
  }
}

你还需要在 options 类上标记 autocomplete: true

typescript
// cat.dto.ts
import { StringOption } from 'necord';

export class CatDto {
  @StringOption({
    name: 'cat',
    description: 'Choose a cat breed',
    autocomplete: true,
    required: true,
  })
  cat: string;
}

最后,把该拦截器应用到 Slash 命令上:

typescript
// cats.commands.ts
import { Injectable, UseInterceptors } from '@nestjs/common';
import { Context, SlashCommand, Options, SlashCommandContext } from 'necord';
import { CatDto } from '/cat.dto';
import { CatsAutocompleteInterceptor } from './cats-autocomplete.interceptor';

@Injectable()
export class CatsCommands {
  @UseInterceptors(CatsAutocompleteInterceptor)
  @SlashCommand({
    name: 'cat',
    description: 'Retrieve information about a specific cat breed',
  })
  public async onSearch(
    @Context() [interaction]: SlashCommandContext,
    @Options() { cat }: CatDto,
  ) {
    return interaction.reply({
      content: `I found information on the breed of ${cat} cat!`,
    });
  }
}

用户上下文菜单

用户命令会出现在右键点击用户(或触摸长按用户)时弹出的上下文菜单中。这类命令提供了直接面向用户的快捷操作。

typescript
// app.commands.ts
import { Injectable } from '@nestjs/common';
import { Context, UserCommand, UserCommandContext, TargetUser } from 'necord';
import { User } from 'discord.js';

@Injectable()
export class AppCommands {
  @UserCommand({ name: 'Get avatar' })
  public async getUserAvatar(
    @Context() [interaction]: UserCommandContext,
    @TargetUser() user: User,
  ) {
    return interaction.reply({
      embeds: [
        new MessageEmbed()
          .setTitle(`Avatar of ${user.username}`)
          .setImage(user.displayAvatarURL({ size: 4096, dynamic: true })),
      ],
    });
  }
}

消息上下文菜单

消息命令会出现在右键点击消息时的上下文菜单中,可用于执行与消息相关的快捷操作。

typescript
// app.commands.ts
import { Injectable } from '@nestjs/common';
import { Context, MessageCommand, MessageCommandContext, TargetMessage } from 'necord';
import { Message } from 'discord.js';

@Injectable()
export class AppCommands {
  @MessageCommand({ name: 'Copy Message' })
  public async copyMessage(
    @Context() [interaction]: MessageCommandContext,
    @TargetMessage() message: Message,
  ) {
    return interaction.reply({ content: message.content });
  }
}

按钮

Buttons 是可以放进消息中的交互元素。用户点击后,会向你的应用发送一个 interaction

typescript
// app.components.ts
import { Injectable } from '@nestjs/common';
import { Context, Button, ButtonContext } from 'necord';

@Injectable()
export class AppComponents {
  @Button('BUTTON')
  public onButtonClick(@Context() [interaction]: ButtonContext) {
    return interaction.reply({ content: 'Button clicked!' });
  }
}

选择菜单

Select menus 是另一种可出现在消息上的交互组件。它们提供了类似下拉框的 UI,供用户选择选项。

typescript
// app.components.ts
import { Injectable } from '@nestjs/common';
import { Context, StringSelect, StringSelectContext, SelectedStrings } from 'necord';

@Injectable()
export class AppComponents {
  @StringSelect('SELECT_MENU')
  public onSelectMenu(
    @Context() [interaction]: StringSelectContext,
    @SelectedStrings() values: string[],
  ) {
    return interaction.reply({ content: `You selected: ${values.join(', ')}` });
  }
}

完整的内置选择菜单组件列表请访问这里

模态框

模态框(Modals)是弹出式表单,允许用户提交结构化输入。下面展示如何使用 Necord 创建并处理模态框:

typescript
// app.modals.ts
import { Injectable } from '@nestjs/common';
import { Context, Modal, ModalContext } from 'necord';

@Injectable()
export class AppModals {
  @Modal('pizza')
  public onModal(@Context() [interaction]: ModalContext) {
    return interaction.reply({
      content: `Your fav pizza : ${interaction.fields.getTextInputValue('pizza')}`
    });
  }
}

更多信息

更多信息请访问 Necord 官网。

基于 NestJS 官方文档翻译