Erstellen eines Discord Bot mit Discord4J + Spring Boot

1. Übersicht

Discord4J ist eine Open-Source-Java-Bibliothek, mit der hauptsächlich schnell auf die Discord Bot-API zugegriffen werden kann. Es ist stark in Project Reactor integriert, um eine vollständig nicht blockierende reaktive API bereitzustellen.

In diesem Tutorial verwenden wir Discord4J, um einen einfachen Discord-Bot zu erstellen, der auf einen vordefinierten Befehl reagieren kann. Wir werden den Bot auf Spring Boot aufbauen, um zu demonstrieren, wie einfach es wäre, unseren Bot auf viele andere von Spring Boot aktivierte Funktionen zu skalieren.

Wenn wir fertig sind, kann dieser Bot auf einen Befehl namens "! Todo" warten und eine statisch definierte Aufgabenliste ausdrucken.

2. Erstellen Sie eine Discord-Anwendung

Damit unser Bot Updates von Discord erhält und Antworten in Kanälen veröffentlicht, müssen wir eine Discord-Anwendung im Discord Developer Portal erstellen und als Bot einrichten. Dies ist ein einfacher Vorgang. Da Discord die Erstellung mehrerer Anwendungen oder Bots unter einem einzigen Entwicklerkonto ermöglicht, können Sie dies mehrmals mit unterschiedlichen Einstellungen versuchen.

Hier sind die Schritte zum Erstellen einer neuen Anwendung:

  • Melden Sie sich beim Discord Developer Portal an
  • Klicken Sie auf der Registerkarte Anwendungen auf "Neue Anwendung".
  • Geben Sie einen Namen für unseren Bot ein und klicken Sie auf "Erstellen".
  • Laden Sie ein App-Symbol und eine Beschreibung hoch und klicken Sie auf "Änderungen speichern".

Nachdem eine Anwendung vorhanden ist, müssen wir lediglich noch Bot-Funktionen hinzufügen. Dadurch wird das für Discord4J erforderliche Bot-Token generiert.

Hier sind die Schritte, um eine Anwendung in einen Bot umzuwandeln:

  • Wählen Sie auf der Registerkarte Anwendungen unsere Anwendung aus (falls diese noch nicht ausgewählt ist).
  • Klicken Sie auf der Registerkarte "Bot" auf "Bot hinzufügen" und bestätigen Sie, dass wir dies tun möchten.

Nachdem unsere Anwendung zu einem echten Bot geworden ist, kopieren Sie das Token, damit wir es unseren Anwendungseigenschaften hinzufügen können. Achten Sie darauf, dieses Token nicht öffentlich zu teilen, da jemand anderes schädlichen Code ausführen kann, während er sich als unser Bot ausgibt.

Wir sind jetzt bereit, Code zu schreiben!

3. Erstellen Sie eine Spring Boot App

Nach dem Erstellen einer neuen Spring Boot-App müssen wir sicherstellen, dass die Discord4J-Kernabhängigkeit enthalten ist:

 com.discord4j discord4j-core 3.1.1 

Discord4J initialisiert einen GatewayDiscordClient mit dem zuvor erstellten Bot-Token. Mit diesem Client-Objekt können wir Ereignis-Listener registrieren und viele Dinge konfigurieren. Zumindest müssen wir jedoch die Methode login () aufrufen . Dadurch wird unser Bot als online angezeigt.

Fügen wir zunächst unser Bot-Token zu unserer Datei application.yml hinzu :

token: 'our-token-here'

Als nächstes fügen wir es in eine @ Configuration- Klasse ein, in der wir unseren GatewayDiscordClient instanziieren können :

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

Zu diesem Zeitpunkt wird unser Bot als online angesehen, aber er macht noch nichts. Fügen wir einige Funktionen hinzu.

4. Fügen Sie Ereignis-Listener hinzu

Das häufigste Merkmal eines Chatbots ist der Befehl. Dies ist eine Abstraktion in CLIs, in denen ein Benutzer Text eingibt, um bestimmte Funktionen auszulösen. Dies können wir in unserem Discord-Bot erreichen, indem wir auf neue Nachrichten warten, die Benutzer senden, und gegebenenfalls mit intelligenten Antworten antworten.

Es gibt viele Arten von Ereignissen, auf die wir hören können. Die Registrierung eines Listeners ist jedoch für alle gleich. Erstellen wir daher zunächst eine Schnittstelle für alle unsere Event-Listener:

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

Jetzt können wir diese Schnittstelle für so viele discord4j.core.event.domain.Event- Erweiterungen implementieren, wie wir möchten.

Bevor wir unseren ersten Ereignis-Listener implementieren, ändern wir unsere Client @ Bean- Konfiguration so, dass eine Liste von EventListener erwartet wird , damit jeder im Spring ApplicationContext gefundene registriert werden kann :

@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; }
    

Jetzt müssen wir nur noch unsere Schnittstelle implementieren und mit Spring's @Component- basierten Stereotyp-Annotationen versehen, um Ereignis-Listener zu registrieren . Die Registrierung erfolgt nun automatisch für uns!

Wir hätten uns dafür entscheiden können, jede Veranstaltung einzeln und explizit zu registrieren. Im Allgemeinen ist es jedoch besser, einen modulareren Ansatz zu wählen, um die Skalierbarkeit des Codes zu verbessern.

Unser Event-Listener-Setup ist jetzt abgeschlossen, aber der Bot tut noch nichts. Fügen Sie also einige Events hinzu, die Sie anhören möchten.

4.1. Befehlsverarbeitung

Um den Befehl eines Benutzers zu erhalten, können wir zwei verschiedene Ereignistypen abhören : MessageCreateEvent für neue Nachrichten und MessageUpdateEvent für aktualisierte Nachrichten. Wir möchten vielleicht nur auf neue Nachrichten warten, aber als Lernmöglichkeit nehmen wir an, dass wir beide Arten von Ereignissen für unseren Bot unterstützen möchten. Dies bietet eine zusätzliche Robustheitsebene, die unsere Benutzer möglicherweise zu schätzen wissen.

Beide Ereignisobjekte enthalten alle relevanten Informationen zu jedem Ereignis. Insbesondere interessieren uns der Inhalt der Nachricht, der Autor der Nachricht und der Kanal, auf dem sie gepostet wurde. Glücklicherweise befinden sich alle diese Datenpunkte im Nachrichtenobjekt , das diese beiden Ereignistypen bereitstellen.

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

In diesem Tutorial wurden alle erforderlichen Schritte zum Erstellen eines Discord-Bots mithilfe der Discord4J-Bibliothek und von Spring Boot beschrieben. Schließlich wurde beschrieben, wie eine grundlegende skalierbare Befehls- und Antwortstruktur für den Bot eingerichtet wird.

Für einen vollständigen und funktionierenden Bot sehen Sie sich den Quellcode auf GitHub an. Ein gültiges Bot-Token ist erforderlich, um es auszuführen.