1. 概要

Discord4J は、主に Discord BotAPIにすばやくアクセスするために使用できるオープンソースのJavaライブラリです。 Project Reactor と緊密に統合して、完全にノンブロッキングのリアクティブAPIを提供します。

このチュートリアルではDiscord4Jを使用して、事前定義されたコマンドに応答できる単純なDiscordボットを作成します。 Spring Bootの上にボットを構築して、Spring Bootによって有効化された他の多くの機能にわたってボットをスケーリングすることがいかに簡単であるかを示します。

終了すると、このボットは「!todo」というコマンドをリッスンし、静的に定義されたToDoリストを出力できるようになります。

2. Discordアプリケーションを作成する

ボットがDiscordから更新を受信し、チャネルに応答を投稿するには、Discord開発者ポータルでDiscordアプリケーションを作成し、ボットとして設定する必要があります。 これは簡単なプロセスです。 Discordでは、単一の開発者アカウントで複数のアプリケーションまたはボットを作成できるため、さまざまな設定でこれを複数回試してみてください。

新しいアプリケーションを作成する手順は次のとおりです。

  • Discord開発者ポータルにログインします
  • [アプリケーション]タブで、[新しいアプリケーション]をクリックします
  • ボットの名前を入力し、「作成」をクリックします
  • アプリのアイコンと説明をアップロードし、[変更を保存]をクリックします

アプリケーションが存在するので、ボット機能をアプリケーションに追加するだけです。 これにより、Discord4Jが必要とするボットトークンが生成されます。

アプリケーションをボットに変換する手順は次のとおりです。

  • [アプリケーション]タブで、アプリケーションを選択します(まだ選択されていない場合)。
  • [ボット]タブで、[ボットの追加]をクリックし、実行することを確認します。

アプリケーションが実際のボットになったので、トークンをコピーして、アプリケーションのプロパティに追加できるようにします。 ボットになりすましているときに他の誰かが悪意のあるコードを実行する可能性があるため、このトークンを公に共有しないように注意してください。

これで、コードを作成する準備が整いました。

3. SpringBootアプリを作成する

新しいSpringBootアプリを作成した後、Discord4Jコアの依存関係を必ず含める必要があります。

<dependency>
    <groupId>com.discord4j</groupId>
    <artifactId>discord4j-core</artifactId>
    <version>3.1.1</version>
</dependency>

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. イベントリスナーを追加する

チャットボットの最も一般的な機能はコマンドです。 これは、 CLIs に見られる抽象化であり、ユーザーが特定の機能をトリガーするためにテキストを入力します。 これは、Discordボットで、ユーザーが送信する新しいメッセージをリッスンし、必要に応じてインテリジェントな応答で返信することで実現できます。

私たちが聞くことができるイベントには多くの種類があります。 ただし、リスナーの登録はすべてのリスナーで同じであるため、最初にすべてのイベントリスナーのインターフェイスを作成しましょう。

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

これで、このインターフェイスを必要な数のdiscord4j.core.event.domain.Event拡張機能に実装できます。

最初のイベントリスナーを実装する前に、クライアント @Bean 構成を変更して、 EventListener のリストを期待し、 SpringApplicationContextで見つかったすべてのものを登録できるようにします。 ]:

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

これで、イベントリスナーを登録するために必要なのは、インターフェイスを実装し、Springの@Componentベースのステレオタイプアノテーションでアノテーションを付けることだけです。登録が自動的に行われます。

各イベントを個別に明示的に登録することを選択することもできます。 ただし、一般的には、コードのスケーラビリティを向上させるために、よりモジュール化されたアプローチを採用することをお勧めします。

これでイベントリスナーのセットアップは完了しましたが、ボットはまだ何も実行していないので、リッスンするイベントをいくつか追加しましょう。

4.1. コマンド処理

ユーザーのコマンドを受信するために、2つの異なるイベントタイプをリッスンできます。新しいメッセージの場合は MessageCreateEvent 、更新されたメッセージの場合はMessageUpdateEventです。 新しいメッセージだけを聞きたいと思うかもしれませんが、学習の機会として、ボットの両方の種類のイベントをサポートしたいとします。 これにより、ユーザーが高く評価できる堅牢性の追加レイヤーが提供されます。

両方のイベントオブジェクトには、各イベントに関連するすべての情報が含まれています。 特に、メッセージの内容、メッセージの作成者、およびメッセージが投稿されたチャネルに関心があります。幸い、これらのデータポイントはすべてメッセージオブジェクトに存在します。これらのイベントタイプの両方が提供します。

メッセージを取得したら、作成者をチェックしてボットではないことを確認し、メッセージの内容をチェックしてコマンドと一致していることを確認し、メッセージのチャネルを使用してメッセージを送信できます。応答。

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

ここでは多くのことが行われていますが、これはコマンドと応答の最も基本的な形式です。 このアプローチでは、リアクティブ機能設計を使用しますが、 block()を使用して、より従来の命令型の方法でこれを記述することができます。

複数のボットコマンド間でのスケーリング、さまざまなサービスやデータリポジトリの呼び出し、または特定のコマンドの承認としてのDiscordロールの使用は、優れたボットコマンドアーキテクチャの一般的な部分です。 リスナーはSpring管理の@Serviceであるため、他のSpring管理のBeanを簡単に挿入して、これらのタスクを処理できます。 ただし、この記事ではそのいずれにも取り組みません。

4.2. EventListener

ユーザーから新しいメッセージを受信するには、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());
    }
}

継承を通じて、メッセージは processCommand()メソッドに渡され、そこですべての検証と応答が行われます。

この時点で、ボットは「!todo」コマンドを受信して応答します。 ただし、ユーザーが誤って入力したコマンドを修正した場合、ボットは応答しません。 このユースケースを別のイベントリスナーでサポートしましょう。

4.3. EventListener

MessageUpdateEvent は、ユーザーがメッセージを編集したときに発行されます。 MessageCreateEvent をリッスンするのと同じように、このイベントをリッスンしてコマンドを認識することができます。

私たちの目的では、メッセージの内容が変更された場合にのみ、このイベントを考慮します。 このイベントの他のインスタンスは無視できます。 幸い、 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);
    }
}

この場合、 getMessage() 戻り値単核症生の代わりにメッセージ 、使用する必要があります flatMap() それを私たちのスーパークラスに送るために。

5. 不和のボットをテストする

機能しているDiscordボットができたので、Discordサーバーに招待してテストできます。

招待リンクを作成するには、ボットが正しく機能するために必要な権限を指定する必要があります。 人気のあるサードパーティのDiscordPermissionsCalculator は、必要な権限を持つ招待リンクを生成するためによく使用されます。 本番環境にはお勧めしませんが、テスト目的で「管理者」を選択するだけで、他の権限について心配する必要はありません。ボット(Discord開発者ポータルにあります)のクライアントIDを指定し、ボットをサーバーに招待するためのリンクを生成しました。

ボットに管理者権限を付与しない場合、ボットがチャネルの読み取りと書き込みを行えるように、チャネル権限を微調整する必要がある場合があります。

ボットはメッセージ「!todo」に応答し、メッセージが編集されて「!todo」と表示されるようになりました。

6. 概要

このチュートリアルでは、Discord4JライブラリとSpringBootを使用してDiscordボットを作成するために必要なすべての手順について説明しました。 最後に、ボットの基本的なスケーラブルなコマンドと応答の構造を設定する方法について説明しました。

完全で機能するボットについては、GitHubでソースコードをご覧ください。 実行するには有効なボットトークンが必要です。