Създаване на Discord Bot с Discord4J + Spring Boot

1. Общ преглед

Discord4J е Java библиотека с отворен код, която може предимно да се използва за бърз достъп до API на Discord Bot. Той се интегрира силно с Project Reactor, за да осигури напълно неблокиращ реактивен API.

Ще използваме Discord4J в този урок, за да създадем прост бот на Discord, способен да отговори на предварително дефинирана команда. Ще изградим бота върху Spring Boot, за да демонстрираме колко лесно би било да мащабираме нашия бот в много други функции, разрешени от Spring Boot.

Когато приключим, този бот ще може да слуша команда, наречена "! Todo", и ще отпечата статично дефиниран списък със задачи.

2. Създайте приложение Discord

За да получи нашият бот актуализации от Discord и да публикува отговори в канали, ще трябва да създадем приложение Discord в портала за разработчици на Discord и да го настроим да бъде бот. Това е прост процес. Тъй като Discord позволява създаването на множество приложения или ботове под един акаунт на програмист, не се колебайте да опитате това няколко пъти с различни настройки.

Ето стъпките за създаване на ново приложение:

  • Влезте в портала за разработчици на Discord
  • В раздела Приложения щракнете върху „Ново приложение“
  • Въведете име за нашия бот и щракнете върху „Създаване“
  • Качете икона на приложение и описание и кликнете върху „Запазване на промените“

Сега, когато приложението съществува, ние просто трябва да добавим бот функционалност към него. Това ще генерира бот токена, който Discord4J изисква.

Ето стъпките за трансформиране на приложение в бот:

  • В раздела Приложения изберете нашето приложение (ако още не е избрано).
  • В раздела Бот кликнете върху „Добавяне на бот“ и потвърдете, че искаме да го направим.

Сега, когато нашето приложение се превърна в истински бот, копирайте маркера, за да можем да го добавим към свойствата на приложението си. Внимавайте да не споделяте този токен публично, тъй като някой друг би могъл да изпълни злонамерен код, докато се представя за нашия бот.

Вече сме готови да напишем код!

3. Създайте приложение Spring Spring Boot

След конструирането на ново приложение Spring Boot, трябва да сме сигурни, че включваме основната зависимост на Discord4J:

 com.discord4j discord4j-core 3.1.1 

Discord4J работи, като инициализира GatewayDiscordClient с бот токена, който създадохме по-рано. Този клиентски обект ни позволява да регистрираме слушатели на събития и да конфигурираме много неща, но като минимум трябва поне да извикаме метода login () . Това ще покаже нашия бот като онлайн.

Първо, нека добавим нашия бот маркер към нашия файл 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 { 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(); } }

Сега можем да приложим този интерфейс за толкова разширения на discord4j.core.event.domain.Event, колкото искаме.

Преди да приложим първия си слушател на събития, нека модифицираме нашата клиентска конфигурация @Bean , за да очакваме списък на EventListener, така че да може да регистрира всеки, намерен в 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; }
    

Сега всичко, което трябва да направим, за да регистрираме слушатели на събития, е да внедрим нашия интерфейс и да го анотираме със стереотипните анотации, базирани на Spring @Component . Регистрацията вече ще се извърши автоматично за нас!

Можехме да изберем да регистрираме всяко събитие отделно и изрично. По принцип обаче е по-добре да се възприеме по-модулен подход за по-добра мащабируемост на кода.

Настройката ни за слушател на събития вече е завършена, но ботът все още не прави нищо, така че нека добавим някои събития за слушане.

4.1. Обработка на команди

За да получим потребителска команда, можем да слушаме два различни типа събития: MessageCreateEvent за нови съобщения и MessageUpdateEvent за актуализирани съобщения. Може да искаме само да слушаме нови съобщения, но като възможност за учене, нека приемем, че искаме да поддържаме и двата вида събития за нашия бот. Това ще осигури допълнителен слой стабилност, който нашите потребители могат да оценят.

И двата обекта на събитието съдържат цялата подходяща информация за всяко събитие. По-специално, ние се интересуваме от съдържанието на съобщението, автора на съобщението и канала, в който е публикувано. За щастие всички тези точки от данни живеят в обекта 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.

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

Този урок описва всички необходими стъпки за създаване на Discord бот с помощта на библиотеката Discord4J и Spring Boot. И накрая, той описва как да настроите основна мащабируема структура на команди и отговори за бота.

За пълен и работещ бот прегледайте изходния код на GitHub. За да го стартирате, е необходим валиден бот маркер.