Création d'un bot Discord avec Discord4J + Spring Boot

1. Vue d'ensemble

Discord4J est une bibliothèque Java open-source qui peut principalement être utilisée pour accéder rapidement à l'API Discord Bot. Il s'intègre fortement à Project Reactor pour fournir une API réactive totalement non bloquante.

Nous utiliserons Discord4J dans ce didacticiel pour créer un robot Discord simple capable de répondre à une commande prédéfinie. Nous allons construire le bot au-dessus de Spring Boot pour montrer à quel point il serait facile de faire évoluer notre bot sur de nombreuses autres fonctionnalités activées par Spring Boot.

Lorsque nous aurons terminé, ce bot pourra écouter une commande appelée «! Todo» et affichera une liste de tâches définie statiquement.

2. Créez une application Discord

Pour que notre bot reçoive des mises à jour de Discord et publie des réponses dans les canaux, nous devons créer une application Discord dans le portail des développeurs Discord et la configurer pour qu'elle devienne un robot. C'est un processus simple. Étant donné que Discord permet la création de plusieurs applications ou bots sous un seul compte de développeur, n'hésitez pas à l'essayer plusieurs fois avec des paramètres différents.

Voici les étapes pour créer une nouvelle application:

  • Connectez-vous au portail des développeurs Discord
  • Dans l'onglet Applications, cliquez sur «Nouvelle application»
  • Entrez un nom pour notre bot et cliquez sur "Créer"
  • Téléchargez une icône d'application et une description, puis cliquez sur "Enregistrer les modifications"

Maintenant qu'une application existe, nous devons simplement lui ajouter des fonctionnalités de bot. Cela générera le jeton de bot dont Discord4J a besoin.

Voici les étapes pour transformer une application en bot:

  • Dans l'onglet Applications, sélectionnez notre application (si elle n'est pas déjà sélectionnée).
  • Dans l'onglet Bot, cliquez sur «Ajouter un robot» et confirmez que nous voulons le faire.

Maintenant que notre application est devenue un véritable bot, copiez le jeton afin que nous puissions l'ajouter à nos propriétés d'application. Veillez à ne pas partager ce jeton publiquement car quelqu'un d'autre pourrait exécuter du code malveillant tout en usurpant l'identité de notre bot.

Nous sommes maintenant prêts à écrire du code!

3. Créez une application Spring Boot

Après avoir construit une nouvelle application Spring Boot, nous devons nous assurer d'inclure la dépendance principale Discord4J:

 com.discord4j discord4j-core 3.1.1 

Discord4J fonctionne en initialisant un GatewayDiscordClient avec le jeton de bot que nous avons créé précédemment. Cet objet client nous permet d'enregistrer des écouteurs d'événements et de configurer beaucoup de choses, mais au minimum, nous devons au moins appeler la méthode login () . Cela affichera notre bot comme étant en ligne.

Tout d'abord, ajoutons notre jeton de bot à notre fichier application.yml :

token: 'our-token-here'

Ensuite, injectons-le dans une classe @Configuration où nous pouvons instancier notre GatewayDiscordClient :

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

À ce stade, notre bot serait considéré comme en ligne, mais il ne fait encore rien. Ajoutons quelques fonctionnalités.

4. Ajouter des écouteurs d'événements

La fonction la plus courante d'un chatbot est la commande. Il s'agit d'une abstraction vue dans les CLI où un utilisateur tape du texte pour déclencher certaines fonctions. Nous pouvons y parvenir dans notre bot Discord en écoutant les nouveaux messages que les utilisateurs envoient et en répondant avec des réponses intelligentes le cas échéant.

Il existe de nombreux types d'événements que nous pouvons écouter. Cependant, l'enregistrement d'un auditeur est le même pour tous, alors créons d'abord une interface pour tous nos écouteurs d'événements:

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(); } }

Nous pouvons maintenant implémenter cette interface pour autant d' extensions discord4j.core.event.domain.Event que nous le souhaitons.

Avant d'implémenter notre premier écouteur d'événements, modifions notre configuration client @Bean pour attendre une liste d' EventListener afin qu'il puisse enregistrer tous ceux trouvés dans 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; }
    

Maintenant, tout ce que nous avons à faire pour enregistrer les écouteurs d'événements est d'implémenter notre interface et de l'annoter avec les annotations de stéréotypes basées sur @Component de Spring . L'inscription se fera désormais automatiquement pour nous!

Nous aurions pu choisir d'enregistrer chaque événement séparément et explicitement. Cependant, il est généralement préférable d'adopter une approche plus modulaire pour une meilleure évolutivité du code.

La configuration de notre écouteur d'événements est maintenant terminée, mais le bot ne fait toujours rien, ajoutons donc quelques événements à écouter.

4.1. Traitement des commandes

Pour recevoir la commande d'un utilisateur, nous pouvons écouter deux types d'événements différents: MessageCreateEvent pour les nouveaux messages et MessageUpdateEvent pour les messages mis à jour. Nous voulons peut-être uniquement écouter les nouveaux messages, mais en tant qu'opportunité d'apprentissage, supposons que nous souhaitons prendre en charge les deux types d'événements pour notre bot. Cela fournira une couche supplémentaire de robustesse que nos utilisateurs peuvent apprécier.

Les deux objets événement contiennent toutes les informations pertinentes sur chaque événement. En particulier, nous nous intéressons au contenu du message, à l'auteur du message et au canal sur lequel il a été publié. Heureusement, tous ces points de données vivent dans l' objet Message fourni par ces deux types d'événements.

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.

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:

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(); } }

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().

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

Ce didacticiel décrit toutes les étapes nécessaires pour créer un bot Discord à l'aide de la bibliothèque Discord4J et de Spring Boot. Enfin, il a décrit comment configurer une structure de commande et de réponse évolutive de base pour le bot.

Pour un bot complet et fonctionnel, affichez le code source sur GitHub. Un jeton de bot valide est requis pour l'exécuter.