1. Overview
1.概述
Discord4J is an open-source Java library that can primarily be used to quickly access the Discord Bot API. It heavily integrates with Project Reactor to provide a completely non-blocking reactive API.
Discord4J是一个开源的Java库,主要可用于快速访问Discord Bot API。它与Project Reactor严重集成,以提供一个完全非阻塞的反应式 API。
We’ll use Discord4J in this tutorial to create a simple Discord bot capable of responding to a predefined command. We’ll build the bot on top of Spring Boot to demonstrate how easy it would be to scale our bot across many other features enabled by Spring Boot.
在本教程中,我们将使用Discord4J来创建一个简单的Discord机器人,能够对预定义的命令做出反应。我们将在Spring Boot的基础上建立机器人,以证明在Spring Boot支持的许多其他功能中扩展我们的机器人是多么容易。
When we’re finished, this bot will be able to listen for a command called “!todo” and will print out a statically defined to-do list.
当我们完成后,这个机器人将能够监听一个名为”!todo “的命令,并打印出一个静态定义的待办事项列表。
2. Create a Discord Application
2.创建一个 Discord 应用程序
For our bot to receive updates from Discord and post responses in channels, we’ll need to create a Discord Application in the Discord Developer Portal and set it up to be a bot. This is a simple process. Since Discord allows the creation of multiple applications or bots under a single developer account, feel free to try this multiple times with different settings.
为了使我们的机器人能够接收来自 Discord 的更新并在频道中发布回复,我们需要在 Discord 开发者门户中创建一个 Discord 应用程序,并将其设置为一个机器人。这是一个简单的过程。由于Discord允许在一个开发者账户下创建多个应用程序或机器人,因此,请随时用不同的设置多次尝试。
Here are the steps to create a new application:
以下是创建一个新应用程序的步骤。
- Log in to the Discord Developer Portal
- In the Applications tab, click “New Application”
- Enter a name for our bot and click “Create”
- Upload an App Icon and a description and click “Save Changes”
Now that an application exists, we simply need to add bot functionality to it. This will generate the bot token that Discord4J requires.
现在,一个应用程序已经存在,我们只需要向它添加机器人功能。这将产生Discord4J所需的机器人令牌。
Here are the steps to transform an application into a bot:
以下是将一个应用程序转化为机器人的步骤。
- In the Applications tab, select our application (if it is not already selected).
- In the Bot tab, click “Add Bot” and confirm that we want to do it.
Now that our application has become a real bot, copy the token so that we can add it to our application properties. Be careful not to share this token publicly since someone else would be able to execute malicious code while impersonating our bot.
现在,我们的应用程序已经成为一个真正的机器人,复制该令牌,以便我们可以将其添加到我们的应用程序属性中。注意不要公开分享这个令牌,因为其他人将能够在冒充我们的机器人时执行恶意代码。
We’re now ready to write some code!
我们现在准备好写一些代码了!
3. Create a Spring Boot App
3.创建一个Spring Boot应用程序
After constructing a new Spring Boot app, we need to be sure to include the Discord4J core dependency:
在构建新的Spring Boot应用程序后,我们需要确保包括Discord4J core依赖性。
<dependency>
<groupId>com.discord4j</groupId>
<artifactId>discord4j-core</artifactId>
<version>3.1.1</version>
</dependency>
Discord4J works by initializing a GatewayDiscordClient with the bot token we created earlier. This client object allows us to register event listeners and configure many things, but at a bare minimum, we must at least call the login() method. This will display our bot as being online.
Discord4J的工作方式是用我们之前创建的机器人令牌初始化一个GatewayDiscordClient。这个客户端对象允许我们注册事件监听器并配置许多东西,但最低限度,我们必须至少调用login()/em>方法。这将显示我们的机器人是在线的。
First, let’s add our bot token to our application.yml file:
首先,让我们在application.yml文件中加入我们的机器人令牌。
token: 'our-token-here'
Next, let’s inject it into a @Configuration class where we can instantiate our GatewayDiscordClient:
接下来,让我们把它注入一个@Configuration类,在那里我们可以实例化我们的GatewayDiscordClient。
@Configuration
public class BotConfiguration {
@Value("${token}")
private String token;
@Bean
public GatewayDiscordClient gatewayDiscordClient() {
return DiscordClientBuilder.create(token)
.build()
.login()
.block();
}
}
At this point, our bot would be seen as online, but it doesn’t do anything yet. Let’s add some functionality.
在这一点上,我们的机器人将被视为在线,但它还没有做任何事情。让我们添加一些功能。
4. Add Event Listeners
4.添加事件监听器
The most common feature of a chatbot is the command. This is an abstraction seen in CLIs where a user types some text to trigger certain functions. We can achieve this in our Discord bot by listening for new messages that users send and replying with intelligent responses when appropriate.
聊天机器人最常见的特征是命令。这是在CLI中看到的一个抽象概念,用户输入一些文本来触发某些功能。在我们的Discord机器人中,我们可以通过监听用户发送的新消息并在适当的时候用智能回复来实现这一点。
There are many types of events for which we can listen. However, registering a listener is the same for all of them, so let’s first create an interface for all of our event listeners:
我们可以监听的事件有很多类型。然而,注册一个监听器对所有的事件都是一样的,所以让我们首先为我们所有的事件监听器创建一个接口。
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();
}
}
Now we can implement this interface for as many discord4j.core.event.domain.Event extensions as we want.
现在我们可以为任意多的discord4j.core.event.domain.Event扩展实现这个接口。
Before we implement our first event listener, let’s modify our client @Bean configuration to expect a list of EventListener so that it can register every one found in the Spring ApplicationContext:
在我们实现第一个事件监听器之前,让我们修改我们的客户端@Bean配置,以期待一个EventListener的列表,这样它就可以注册在Spring ApplicationContext中发现的每一个监听器。
@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;
}
Now, all we have to do to register event listeners is to implement our interface and annotate it with Spring’s @Component-based stereotype annotations. The registration will now happen automatically for us!
现在,我们注册事件监听器所要做的就是实现我们的接口,并用Spring的@Component-based stereotype annotations来注释它。
We could have chosen to register each event separately and explicitly. However, it is generally better to take a more modular approach for better code scalability.
我们可以选择单独和明确地注册每个事件。然而,一般来说,为了提高代码的可扩展性,采取更加模块化的方法会更好。
Our event listener setup is now complete, but the bot still doesn’t do anything yet, so let’s add some events to listen to.
我们的事件监听器设置现在已经完成,但机器人仍然没有做任何事情,所以让我们添加一些事件来监听。
4.1. Command Processing
4.1.命令处理
To receive a user’s command, we can listen to two different event types: MessageCreateEvent for new messages and MessageUpdateEvent for updated messages. We may only want to listen for new messages, but as a learning opportunity, let’s assume we want to support both kinds of events for our bot. This will provide an extra layer of robustness that our users may appreciate.
为了接收用户的命令,我们可以监听两种不同的事件类型。MessageCreateEvent用于新消息,MessageUpdateEvent用于更新消息。我们可能只想监听新消息,但作为一个学习的机会,让我们假设我们想为我们的机器人支持两种事件。这将提供一个额外的稳健性,我们的用户可能会喜欢。
Both event objects contain all the relevant information about each event. In particular, we’re interested in the message contents, the author of the message, and the channel it was posted to. Luckily, all of these data points live in the Message object that both of these event types provide.
这两个事件对象都包含关于每个事件的所有相关信息。特别是,我们对消息的内容、消息的作者以及它被发布到的频道感兴趣。幸运的是,所有这些数据点都存在于这两个事件类型所提供的Message对象中。
Once we have the Message, we can check the author to make sure it is not a bot, we can check the message contents to make sure it matches our command, and we can use the message’s channel to send a response.
一旦我们有了Message,我们可以检查作者以确保它不是一个机器人,我们可以检查消息内容以确保它符合我们的命令,我们可以使用消息的通道来发送一个回应。
Since we can fully operate from both events through their Message objects, let’s put all downstream logic into a common location so that both event listeners can use it:
既然我们可以通过这两个事件的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();
}
}
A lot is going on here, but this is the most basic form of a command and response. This approach uses a reactive functional design, but it is possible to write this in a more traditional imperative way using block().
这里有很多事情要做,但这是命令和响应的最基本形式。这种方法使用了反应式功能设计,但也可以使用block()以更传统的命令式方式来写这个。
Scaling across multiple bot commands, invoking different services or data repositories, or even using Discord roles as authorization for certain commands are common parts of a good bot command architecture. Since our listeners are Spring-managed @Services, we could easily inject other Spring-managed beans to take care of those tasks. However, we won’t tackle any of that in this article.
在多个僵尸指令中进行扩展,调用不同的服务或数据存储库,甚至使用Discord角色作为某些指令的授权,这些都是良好的僵尸指令架构的常见部分。由于我们的监听器是Spring管理的@Services,我们可以很容易地注入其他Spring管理的bean来处理这些任务。然而,我们不会在本文中解决这些问题。
4.2. EventListener<MessageCreateEvent>
4.2.EventListener<MessageCreateEvent>
To receive new messages from a user, we must listen to the MessageCreateEvent. Since the command processing logic already lives in MessageListener, we can extend it to inherit that functionality. Also, we need to implement our EventListener interface to comply with our registration design:
为了接收来自用户的新消息,我们必须监听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());
}
}
Through inheritance, the message is passed off to our processCommand() method where all verification and responses occur.
通过继承,消息被传递给我们的processCommand()方法,所有的验证和响应都发生在这里。
At this point, our bot will receive and respond to the “!todo” command. However, if a user corrects their mistyped command, the bot would not respond. Let’s support this use case with another event listener.
在这一点上,我们的机器人将接收并响应”!todo “命令。然而,如果用户纠正了他们打错的命令,机器人就不会做出反应。让我们用另一个事件监听器来支持这个用例。
4.3. EventListener<MessageUpdateEvent>
4.3.EventListener<MessageUpdateEvent>
The MessageUpdateEvent is emitted when a user edits a message. We can listen for this event to recognize commands, much like how we listen for the MessageCreateEvent.
MessageUpdateEvent是在用户编辑信息时发出的。我们可以监听这个事件来识别命令,就像我们监听MessageCreateEvent一样。
For our purposes, we only care about this event if the message contents were changed. We can ignore other instances of this event. Fortunately, we can use the isContentChanged() method to filter out such instances:
为了我们的目的,我们只关心这个事件,如果消息内容被改变。我们可以忽略这个事件的其他实例。幸运的是,我们可以使用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);
}
}
In this case, since getMessage() returns Mono<Message> instead of a raw Message, we need to use flatMap() to send it to our superclass.
在这种情况下,由于getMessage()返回Mono<Message>而不是原始的Message,我们需要使用flatMap()来将其发送到我们的超类。
5. Test Bot in Discord
5.在Discord中测试机器人
Now that we have a functioning Discord bot, we can invite it to a Discord server and test it.
现在我们有了一个正常运作的Discord机器人,我们可以邀请它到一个Discord服务器上进行测试。
To create an invite link, we must specify which permissions the bot requires to function properly. A popular third-party Discord Permissions Calculator is often used to generate an invite link with the needed permissions. Although it’s not recommended for production, we can simply choose “Administrator” for testing purposes and not worry about the other permissions. Simply supply the Client ID for our bot (found in the Discord Developer Portal) and use the generated link to invite our bot to a server.
要创建一个邀请链接,我们必须指定机器人需要哪些权限才能正常运行。一个流行的第三方Discord权限计算器通常被用来生成具有所需权限的邀请链接。虽然不建议用于生产,但我们可以简单地选择 “管理员 “来进行测试,而不必担心其他权限。只需提供我们的机器人的客户端 ID(可在 Discord 开发人员门户中找到),并使用生成的链接来邀请我们的机器人进入服务器。
If we do not grant Administrator permissions to the bot, we might need to tweak channel permissions so that the bot can read and write in a channel.
如果我们不授予机器人管理员权限,我们可能需要调整通道权限,使机器人可以在一个通道中读写。
The bot now responds to the message “!todo” and when a message is edited to say “!todo”:
机器人现在对信息”!todo “做出反应,当信息被编辑为”!todo “时,机器人也会做出反应。
6. Overview
6.概述
This tutorial described all the necessary steps for creating a Discord bot using the Discord4J library and Spring Boot. Finally, it described how to set up a basic scalable command and response structure for the bot.
本教程描述了使用Discord4J库和Spring Boot创建一个Discord机器人的所有必要步骤。最后,它描述了如何为机器人建立一个基本的可扩展命令和响应结构。
For a complete and working bot, view the source code over on GitHub. A valid bot token is required to run it.
对于一个完整的、可运行的机器人,请查看GitHub上的源代码。运行它需要一个有效的机器人令牌。