1. 概述

Discord4J 是一个开源的 Java 库,主要用于快速接入 Discord Bot API。它深度集成了 Project Reactor,提供了一个完全非阻塞的响应式 API。

在本教程中,我们将使用 Discord4J 构建一个简单的 Discord 机器人,使其能够响应预定义的指令。我们将基于 Spring Boot 来构建,借此展示如何借助 Spring Boot 的强大生态来轻松扩展机器人的功能。

最终,这个机器人将能够监听名为 !todo 的命令,并输出一个静态定义的待办事项列表。

2. 创建 Discord 应用

为了让我们的机器人能够接收来自 Discord 的事件并发送消息到频道中,我们需要在 Discord 开发者门户 中创建一个 Discord 应用,并将其配置为一个机器人。这个过程非常简单。由于 Discord 允许在单个开发者账号下创建多个应用或机器人,因此可以尝试多次不同配置。

以下是创建新应用的步骤:

  • 登录 Discord Developer Portal
  • 在 Applications 页面中,点击 “New Application”
  • 输入机器人名称并点击 “Create”
  • 可选:上传应用图标和描述,然后点击 “Save Changes”

BaeldungBotApplication

创建好应用之后,我们需要为其添加机器人功能。这一步会生成 Discord4J 所需的机器人 Token。

以下是将应用转换为机器人的步骤:

  • 在 Applications 页面中,选择我们创建的应用(如果尚未选中)
  • 切换到 Bot 标签页,点击 “Add Bot” 并确认操作

BaeldungBotBot

现在我们的应用已经成为一个机器人了,接下来复制 Token,稍后我们会将其添加到应用配置中。⚠️切勿将此 Token 公开,否则他人可能冒充你的机器人执行恶意操作。

现在我们可以开始写代码了!

3. 创建 Spring Boot 应用

新建一个 Spring Boot 项目后,需要添加 Discord4J core 依赖:

<dependency>
    <groupId>com.discord4j</groupId>
    <artifactId>discord4j-core</artifactId>
    <version>3.3.0-RC1</version>
</dependency>

Discord4J 的工作原理是通过我们之前创建的机器人 Token 初始化一个 GatewayDiscordClient。这个客户端对象允许我们注册事件监听器并进行各种配置,但最基础的一步是调用 login() 方法,这将使机器人上线。

首先,我们将 Token 添加到 application.yml 文件中:

token: 'our-token-here'

然后,将其注入到一个 @Configuration 类中,并实例化 GatewayDiscordClient

@Configuration
public class BotConfiguration {

    @Value("${token}")
    private String token;

    @Bean
    public GatewayDiscordClient gatewayDiscordClient() {
        return DiscordClientBuilder.create(token)
          .build()
          .login()
          .block();
    }
}

此时,机器人已经上线,但还不会做任何事情。我们接下来添加一些功能。

4. 添加事件监听器

聊天机器人最常见的功能之一是命令响应。这类似于 CLI 中的命令模式,用户输入特定文本触发某些功能。我们可以通过监听用户发送的消息,并在合适时进行响应来实现这一功能。

Discord 提供了多种类型的事件供我们监听。但注册监听器的方式是一致的,因此我们先创建一个通用的事件监听接口:

import discord4j.core.event.domain.Event;

public interface EventListener<T extends Event> {

    Logger LOG = LoggerFactory.getLogger(EventListener.class);
    
    Class<T> getEventType();
    Mono<Void> execute(T event);
    
    default Mono<Void> handleError(Throwable error) {
        LOG.error("Unable to process " + getEventType().getSimpleName(), error);
        return Mono.empty();
    }
}

现在我们可以为任意的 discord4j.core.event.domain.Event 实现这个接口。

在实现第一个事件监听器之前,我们修改客户端的 @Bean 配置,让它自动注册 Spring 容器中所有实现了 EventListener 的组件:

@Bean
public <T extends Event> GatewayDiscordClient gatewayDiscordClient(List<EventListener<T>> eventListeners) {
    GatewayDiscordClient client = DiscordClientBuilder.create(token)
      .build()
      .login()
      .block();

    for(EventListener<T> listener : eventListeners) {
        client.on(listener.getEventType())
          .flatMap(listener::execute)
          .onErrorResume(listener::handleError)
          .subscribe();
    }

    return client;
}

现在,我们只需实现 EventListener 接口并加上 Spring 的 @Component 或其衍生注解,即可自动注册监听器。

我们本可以选择手动注册每个事件监听器,但模块化方式更利于代码维护和扩展。

事件监听器的基础结构已经搭建完成,但机器人还不能做任何事情,接下来我们添加一些事件监听逻辑。

4.1. 命令处理

要接收用户的命令,我们可以监听两种事件类型:MessageCreateEvent(新消息)和 MessageUpdateEvent(消息更新)。虽然我们可能只关心新消息,但为了演示,我们假设两种事件都需要支持。

这两种事件都包含相关消息内容、作者和频道信息。幸运的是,这些信息都封装在 Message 对象中。

拿到 Message 后,我们可以验证发送者是否为机器人、判断消息内容是否匹配命令,并通过消息所属频道发送响应。

由于两种事件都提供 Message 对象,我们将命令处理逻辑提取到一个公共方法中:

import discord4j.core.object.entity.Message;

public abstract class MessageListener {

    public Mono<Void> processCommand(Message eventMessage) {
        return Mono.just(eventMessage)
          .filter(message -> message.getAuthor().map(user -> !user.isBot()).orElse(false))
          .filter(message -> message.getContent().equalsIgnoreCase("!todo"))
          .flatMap(Message::getChannel)
          .flatMap(channel -> channel.createMessage("Things to do today:\n - write a bot\n - eat lunch\n - play a game"))
          .then();
    }
}

这段代码虽然看起来有点复杂,但其实是最基础的命令响应逻辑。我们使用了 响应式函数式编程 的方式,当然也可以通过调用 block() 以传统命令式方式实现。

在实际项目中,一个良好的命令架构通常包括:

  • 多个命令支持
  • 不同服务或数据源调用
  • 使用 Discord 角色进行权限控制

由于监听器是 Spring 管理的 @Service,我们可以轻松注入其他 Spring Bean 来实现这些功能。不过这些内容不在本文讨论范围。

4.2. EventListener

要监听用户发送的新消息,我们需要监听 MessageCreateEvent。由于命令处理逻辑已经封装在 MessageListener 中,我们只需继承该类并实现 EventListener 接口:

@Service
public class MessageCreateListener extends MessageListener implements EventListener<MessageCreateEvent> {

    @Override
    public Class<MessageCreateEvent> getEventType() {
        return MessageCreateEvent.class;
    }

    @Override
    public Mono<Void> execute(MessageCreateEvent event) {
        return processCommand(event.getMessage());
    }
}

通过继承,消息会被传递给 processCommand() 方法进行验证和响应。

此时,机器人已经可以响应 !todo 命令。但如果用户修改了之前输入错误的消息,机器人不会响应。为此,我们需要添加另一个监听器。

4.3. EventListener

当用户编辑消息时,会触发 MessageUpdateEvent。我们可以通过监听这个事件来识别命令输入。

但我们只关心消息内容发生变化的情况,可以使用 isContentChanged() 方法进行过滤:

@Service
public class MessageUpdateListener extends MessageListener implements EventListener<MessageUpdateEvent> {
    
    @Override
    public Class<MessageUpdateEvent> getEventType() {
        return MessageUpdateEvent.class;
    }

    @Override
    public Mono<Void> execute(MessageUpdateEvent event) {
        return Mono.just(event)
          .filter(MessageUpdateEvent::isContentChanged)
          .flatMap(MessageUpdateEvent::getMessage)
          .flatMap(super::processCommand);
    }
}

由于 getMessage() 返回的是 Mono<Message> 而非原始 Message,我们需要使用 flatMap() 将其传递给父类处理。

5. 测试机器人

现在我们已经构建好了一个功能完整的 Discord 机器人,可以将其邀请到服务器中进行测试。

要生成邀请链接,我们需要指定机器人所需的权限。常用的第三方工具 Discord Permissions Calculator 可以帮助我们生成带权限的邀请链接。⚠️虽然不推荐用于生产环境,但测试时可以选择 “Administrator” 权限以简化配置。

只需提供机器人的 Client ID(在 Discord 开发者门户中可找到),然后使用生成的链接将机器人邀请到 服务器 中。

如果未授予管理员权限,可能需要手动调整频道权限,使机器人可以读取和发送消息。

机器人现在可以响应 !todo 命令,即使该命令是用户编辑后输入的:

BaeldungBotCommand

6. 总结

本教程详细介绍了如何使用 Discord4J 和 Spring Boot 构建一个 Discord 机器人,并搭建了一个可扩展的命令响应结构。

运行该机器人需要有效的机器人 Token。


原始标题:Creating a Discord Bot with Discord4J + Spring Boot | Baeldung