Creando un Discord Bot con Discord4J + Spring Boot

1. Información general

Discord4J es una biblioteca Java de código abierto que se puede utilizar principalmente para acceder rápidamente a la API de Discord Bot. Se integra en gran medida con Project Reactor para proporcionar una API reactiva completamente sin bloqueo.

Usaremos Discord4J en este tutorial para crear un bot de Discord simple capaz de responder a un comando predefinido. Construiremos el bot sobre Spring Boot para demostrar lo fácil que sería escalar nuestro bot a través de muchas otras características habilitadas por Spring Boot.

Cuando hayamos terminado, este bot podrá escuchar un comando llamado "! Todo" e imprimirá una lista de tareas pendientes definida estáticamente.

2. Crea una aplicación Discord

Para que nuestro bot reciba actualizaciones de Discord y publique respuestas en los canales, necesitaremos crear una Aplicación de Discord en el Portal de Desarrolladores de Discord y configurarla para que sea un bot. Este es un proceso simple. Dado que Discord permite la creación de múltiples aplicaciones o bots bajo una sola cuenta de desarrollador, no dude en probar esto varias veces con diferentes configuraciones.

Estos son los pasos para crear una nueva aplicación:

  • Inicie sesión en el portal para desarrolladores de Discord
  • En la pestaña Aplicaciones, haga clic en "Nueva aplicación"
  • Ingrese un nombre para nuestro bot y haga clic en "Crear"
  • Sube un ícono de aplicación y una descripción y haz clic en "Guardar cambios".

Ahora que existe una aplicación, simplemente necesitamos agregarle funcionalidad de bot. Esto generará el token de bot que requiere Discord4J.

Estos son los pasos para transformar una aplicación en un bot:

  • En la pestaña Aplicaciones, seleccione nuestra aplicación (si aún no está seleccionada).
  • En la pestaña Bot, haga clic en "Agregar Bot" y confirme que queremos hacerlo.

Ahora que nuestra aplicación se ha convertido en un bot real, copie el token para que podamos agregarlo a las propiedades de nuestra aplicación. Tenga cuidado de no compartir este token públicamente, ya que otra persona podría ejecutar código malicioso mientras se hace pasar por nuestro bot.

¡Ahora estamos listos para escribir código!

3. Cree una aplicación Spring Boot

Después de construir una nueva aplicación Spring Boot, debemos asegurarnos de incluir la dependencia principal de Discord4J:

 com.discord4j discord4j-core 3.1.1 

Discord4J funciona inicializando un GatewayDiscordClient con el token de bot que creamos anteriormente. Este objeto de cliente nos permite registrar oyentes de eventos y configurar muchas cosas, pero como mínimo, debemos llamar al método login () . Esto mostrará nuestro bot como en línea.

Primero, agreguemos nuestro token de bot a nuestro archivo application.yml :

token: 'our-token-here'

A continuación, inyectémoslo en una clase @Configuration donde podemos crear una instancia de nuestro GatewayDiscordClient :

@Configuration public class BotConfiguration { @Value("${token}") private String token; @Bean public GatewayDiscordClient gatewayDiscordClient() { return DiscordClientBuilder.create(token) .build() .login() .block(); } }

En este punto, nuestro bot se vería en línea, pero aún no hace nada. Agreguemos alguna funcionalidad.

4. Agregar oyentes de eventos

La característica más común de un chatbot es el comando. Esta es una abstracción que se ve en las CLI donde un usuario escribe un texto para activar ciertas funciones. Podemos lograr esto en nuestro bot de Discord escuchando los nuevos mensajes que envían los usuarios y respondiendo con respuestas inteligentes cuando sea apropiado.

Hay muchos tipos de eventos que podemos escuchar. Sin embargo, registrar un oyente es el mismo para todos, así que primero creemos una interfaz para todos nuestros oyentes de eventos:

import discord4j.core.event.domain.Event; public interface EventListener { Logger LOG = LoggerFactory.getLogger(EventListener.class); Class getEventType(); Mono execute(T event); default Mono handleError(Throwable error) { LOG.error("Unable to process " + getEventType().getSimpleName(), error); return Mono.empty(); } }

Ahora podemos implementar esta interfaz para tantas extensiones discord4j.core.event.domain.Event como queramos.

Antes de implementar nuestro primer detector de eventos, modifiquemos la configuración de nuestro cliente @Bean para esperar una lista de EventListener para que pueda registrar todos los que se encuentran en Spring ApplicationContext :

@Bean public  GatewayDiscordClient gatewayDiscordClient(List
    
      eventListeners) { GatewayDiscordClient client = DiscordClientBuilder.create(token) .build() .login() .block(); for(EventListener listener : eventListeners) { client.on(listener.getEventType()) .flatMap(listener::execute) .onErrorResume(listener::handleError) .subscribe(); } return client; }
    

Ahora, todo lo que tenemos que hacer para registrar los oyentes de eventos es implementar nuestra interfaz y anotarla con las anotaciones de estereotipo basadas en @Component de Spring . ¡El registro ahora ocurrirá automáticamente para nosotros!

Podríamos haber optado por registrar cada evento de forma separada y explícita. Sin embargo, generalmente es mejor adoptar un enfoque más modular para una mejor escalabilidad del código.

La configuración de nuestro detector de eventos ahora está completa, pero el bot todavía no hace nada, así que agreguemos algunos eventos para escuchar.

4.1. Procesamiento de comandos

Para recibir el comando de un usuario, podemos escuchar dos tipos de eventos diferentes: MessageCreateEvent para mensajes nuevos y MessageUpdateEvent para mensajes actualizados. Es posible que solo queramos escuchar nuevos mensajes, pero como una oportunidad de aprendizaje, supongamos que queremos apoyar ambos tipos de eventos para nuestro bot. Esto proporcionará una capa adicional de robustez que nuestros usuarios pueden apreciar.

Ambos objetos de evento contienen toda la información relevante sobre cada evento. En particular, nos interesa el contenido del mensaje, el autor del mensaje y el canal en el que se publicó. Afortunadamente, todos estos puntos de datos viven en el objeto Mensaje que proporcionan estos dos tipos de eventos.

Una vez que tenemos el mensaje , podemos verificar el autor para asegurarnos de que no sea un bot, podemos verificar el contenido del mensaje para asegurarnos de que coincida con nuestro comando, y podemos usar el canal del mensaje para enviar una respuesta.

Dado que podemos operar completamente desde ambos eventos a través de sus objetos Message , coloquemos toda la lógica descendente en una ubicación común para que ambos detectores de eventos puedan usarla:

import discord4j.core.object.entity.Message; public abstract class MessageListener { public Mono 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(); } }

Están sucediendo muchas cosas aquí, pero esta es la forma más básica de comando y respuesta. Este enfoque usa un diseño funcional reactivo, pero es posible escribir esto de una manera imperativa más tradicional usando 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.

4.2. EventListener

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:

@Service public class MessageCreateListener extends MessageListener implements EventListener { @Override public Class getEventType() { return MessageCreateEvent.class; } @Override public Mono execute(MessageCreateEvent event) { return processCommand(event.getMessage()); } }

Through inheritance, the message is passed off to our processCommand() method where all verification and responses occur.

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.

4.3. EventListener

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.

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:

@Service public class MessageUpdateListener extends MessageListener implements EventListener { @Override public Class getEventType() { return MessageUpdateEvent.class; } @Override public Mono execute(MessageUpdateEvent event) { return Mono.just(event) .filter(MessageUpdateEvent::isContentChanged) .flatMap(MessageUpdateEvent::getMessage) .flatMap(super::processCommand); } }

In this case, since getMessage() returns Mono instead of a raw Message, we need to use flatMap() to send it to our superclass.

5. Test Bot in Discord

Now that we have a functioning Discord bot, we can invite it to a Discord server and test it.

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.

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”:

6. Overview

Este tutorial describe todos los pasos necesarios para crear un bot de Discord usando la biblioteca Discord4J y Spring Boot. Finalmente, describió cómo configurar un comando escalable básico y una estructura de respuesta para el bot.

Para obtener un bot completo y funcional, vea el código fuente en GitHub. Se requiere un token de bot válido para ejecutarlo.