[Rust] Discordのbotを作ってみる [Serenity]

2022.09.30

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

Introduction

私はクラスメソッドで、10年以上リモートワークで作業しているのですが、
メンバーとのやり取りはほとんどSlackでやっています。
しかし、仕事以外ではここ数年、Discordを使う機会が増えてきました。

どうせならbotでもつくってみたいなーと思っていたところ、よい記事があったので、
これを参考にDiscordのシンプルなbotをRustで作成してみます。

Discord?

Discordは、オープンなコミュニティの色合いが強いチャットサービスです。
テキストチャットや通話、ビデオミーティングなどの機能をもってます。
そして、botとはDiscordの拡張機能みたいなものです。
Discordサーバに任意のbotを招待することで、そのbotの機能が使えるようになります。
ちょっと前に話題になった、画像生成AIのMidjourneyもDiscordのbotとして提供されてました。

Environment

  • OS :  MacOS 12.4
  • Rust : 1.62.1

※ M1 Mac(2020)にて動作確認

Setup Discord bot

Discordアカウントの登録

Discordのアカウントがない人は、ここで必要事項を記述して
アカウントを登録しておきましょう。
ログインしたら画面左のサーバ追加ボタンから、
bot動作確認用の適当なサーバを作成しておきます。

botを登録する

まずはbotをDiscordに登録します。
ここへアクセスし、New Applicationボタンを押して
新しいアプリケーションを作成します。

名前を適当に決め、チェックボックスにチェックをつけてCreateボタンを押す。  

botのアイコンや情報について設定します。
ここでApplication IDが表示されているので、覚えておきましょう。

サイドバーのメニューから「Bot」を選択します。
ここでは表示名を決めることができます。
また、後で使用するのでReset Tokenボタンを押してトークンを覚えておきます。

そして、bot送られたメッセージから情報を取得するために、
画面下へスクロールし、「MESSAGE CONTENT INTENT」にチェックをつけ、
「Save Changes」ボタンを押して設定を保存します。

次に、下記URLをブラウザでアクセスし、botを認証します。
<Application ID>のところはさきほどコピーしたApplication IDに置き換えてください。

https://discord.com/oauth2/authorize?client_id=<Application ID>&scope=bot&permissions=8

下記画面が表示されるので、作成したサーバーを選択して「はい」を押します。
これでbotが作成されました。

bot追加のメッセージが表示され、画面右に作成したbotが出てきてます。
(オフラインですが)

bot実装前の準備

画面下の歯車アイコンから詳細設定を選択し、「開発者モード」をオンにしておきます。

↑をしておくと、サーバのコンテキストメニューからIDをコピーできたり、bot開発に役立つ機能が追加されます。

Create bot with Rust

botの登録ができたので、実装していきます。
まずは必要なツールやライブラリをインストールします。

cargo-shuttleのインストールとプロジェクト作成

cargo-shuttleはサーバレスWebプラットフォームであるshuttle上に
Webアプリケーションをデプロイするためのコマンドラインツールです。
cargo installでインストールします。

% cargo install cargo-shuttle

Serenity は、Discordのbot用ライブラリです。
下記のようにcargo-shuttleを使って、shuttleへデプロイできる
Serenityを使ったプロジェクトを作成できます。

% cargo shuttle init --serenity

コード実装

まずはプロジェクトのルートにSecrets.tomlファイルを作成し、
↓のように記述します。

DISCORD_TOKEN="<Reset Tokenで取得したトークン>"
DISCORD_GUILD_ID="<コンテキストメニュー→サーバIDをコピー でコピーしたID>"

src/lib.rsを、下記のように修正します。

use log::info;
use serenity::async_trait;

use serenity::model::gateway::Ready;
use serenity::model::prelude::*;
use serenity::prelude::*;
use serenity::model::prelude::interaction::Interaction::ApplicationCommand;
use serenity::model::interactions::*;
use shuttle_service::error::CustomError;
use shuttle_service::SecretStore;
use sqlx::PgPool;

struct Bot;

#[async_trait]
impl EventHandler for Bot {
    async fn ready(&self, ctx: Context, ready: Ready) {
        info!("{} is connected!", ready.user.name);

        let guild_id = GuildId(<DISCORD_GUILD_ID>);

                //コマンドを登録
        let commands = GuildId::set_application_commands(&guild_id, &ctx.http, |commands| {
            commands.create_application_command(|command| {
                command.name("hello").description("Say hello")
            }).create_application_command(|command| {
                command.name("bye").description("Say Good Bye")
            })
        })
        .await
        .unwrap();

        info!("{:#?}", commands);
    }

    async fn interaction_create(&self,ctx: Context,interaction: serenity::model::interactions::Interaction) {
        if let ApplicationCommand(command) = interaction {
            let response_content = match command.data.name.as_str() {
                "hello" => "hello!!".to_owned(),
                "bye" => "good bye!!".to_owned(),
                command => unreachable!("Unknown command: {}", command),
            };

            let create_interaction_response =
                command.create_interaction_response(&ctx.http, |response| {
                    response
                        .kind(InteractionResponseType::ChannelMessageWithSource)
                        .interaction_response_data(|message| message.content(response_content))
                });

            if let Err(why) = create_interaction_response.await {
                eprintln!("Cannot respond to slash command: {}", why);
            }
        }
    }
}

・・・

ready関数にhelloとbyeというコマンドを登録し、
interaction_create関数(登録したコマンドを使ったときのhook)で
コマンドに対するレスポンスを実装します。
それぞれコマンドを受け取ったら、適当なメッセージを返すだけの簡単なものです。

コードが修正できたらrunコマンドを実行してテストしてみます。

% cargo shuttle run
    Finished dev [unoptimized + debuginfo] target(s) in 2.44s
    DB ready can be reached at postgres://postgres:postgres@localhost:24660/postgres

    Starting discord-bot on http://127.0.0.1:8000
    DB ready can be reached at postgres://postgres:postgres@localhost:24660/postgres

botがオンラインになりました。

/hello,/byeと登録したコマンドを実行すると、botがメッセージを返してくれます。

なお、ローカルテストでなくshuttleにデプロイしたいならdeployコマンドを実行します。

% cargo shuttle deploy

Summary

今回はRustでDiscordのシンプルなbotを実装して
動作確認してみました。

参考にしたここでは、
このあとさらにいろいろなAPIを使ってbotを実装しているので、
興味のあるかたはチェックしてみてください。

References