🔖

Rustで作るdiscord bot入門編 (serenity使用)

2021/05/19に公開

はじめに

この記事は,Rust 製 discord bot 用ライブラリserenityについてとてもざっくり解説するものです.公式 GitHub の example を元に構成しています.紹介しきれていない機能がたくさんあるので,気になった方は参考の公式ドキュメントをご覧ください.

本記事での目標

  • serenity を用いて簡単な Discord Bot を作成する
    今回は,コマンドをメッセージの判定ベースではなく,関数として実装していきます.いくつか理由はありますが,一番の理由は見通しがよくなるからです.メッセージの判定ベースでのやりかたは,serenity/main.rs at current · serenity-rs/serenityをご覧ください.

本記事の対象読者

Discord とは

グループでの通話やチャットに特化した SNS です.サーバーと呼ばれるグループの中にチャンネルというスペースがあります.チャンネルは複数作ることができて,目的ごとに使い分けることができます.DL はこちらから.Discord | 想像してみよう

Discord bot とは

Discord はサーバーに Bot を追加できます.Bot には様々な機能をもたせることができます.既存のものでは,音楽をかける Bot や,サイコロを振る Bot などがあります.今回は,独自の機能を持った Bot を作っていきます.

Rust とは

一言でいうと,高速かつ安全な素晴らしい言語です.イケメンです.

serenity を選んだ理由

  • 現在(2021 年 5 月)開発が盛んなこと
  • discord.py によく似ていること
    の 2 点です.

本編

ディレクトリ構成

Rust の環境構築は終わっているものとします.終わっていない方は,Install Rust - Rust Programming Languageからお願いします.

まずは以下のコマンドを実行します.

cd 任意のディレクトリ
cargo init discord-bot-rust
cd discord-bot-rust
touch ./config.json

ディレクトリは以下のようになっているはずです.

discord-bot-rust
├── Cargo.toml
├── config.json
├── LICENSE
├── README.md
└── src
   ├── commands
   │  ├── mod.rs
   │  └── neko.rs
   └── main.rs

Discord 側の準備

Bot を動かすために必要なトークンを作成します.まず,Discord Developer Portal — My Applicationsにアクセスします.

右上のNew Applicationをクリックします.
8ae2fa4d70a0bad909a929f196bbdd6a.png (947×224)

Bot の名前を入力し,Bot を作成します.
1ba19d681bbad34af0b6d93419c5df86.png (473×403)

左上のハンバーガーメニューをクリックし,Bot のページに遷移します.
1996537dfdfbd24a36cd6a3e80d08ad5.png (954×501)

Add Bot をクリックします.
c0d594349d6c414fe65a10d86d4ea408.png (947×484)

これで Bot が作成されました.Copyを押すと Bot のトークンがクリップボードにコピーされます.これは後で使います.
e9e29ae38dc4f03ec6545ba4732ce3f3.png (953×587)

Bot の招待リンクを作成ます.OAuth のページを開きます.
f314701c5145c95e3e1ab240fb869040.png (952×478)

下にスクロールし,bot にチェックを入れます.更に下に招待リンクが作成されました.
3bbd95b2bd7bc158aba31e276b781fcb.png (952×926)
これをブラウザに貼り付けて Bot をサーバーに招待しましょう.

Cargo.toml

Cargo.toml を以下のようにします.

[package]
authors = ["yourname"]
edition = "2018"
name = "discord-bot-rust"
version = "0.1.0"

[dependencies]
serde = {version = "1.0", features = ["derive"]}
serde_json = "1.0"

serenity = {version = "0.10.7", features = ["framework", "standard_framework"]}

tokio = {version = "1.6.0", features = ["macros", "rt-multi-thread"]}

dipendencies 項目を付け加えます.これは外部クレート(パッケージ)を使用することを,明示するためのものです.tokioは非同期プログラミング向けのクレートです.serdeは JSON ファイルを操作するためのクレートです.

クレートのインポート

説明のためには,それぞれ必要な場所に書いたほうがわかりやすいですが,面倒なのでまとめています.(気が向いたら分けます)

main.rs
mod commands;

use std::{collections::HashSet, fs::File, io::BufReader, usize};

use serenity::async_trait;
use serenity::framework::standard::{
    help_commands,
    macros::{group, help},
    Args, CommandGroup, CommandResult, HelpOptions,
};
use serenity::framework::StandardFramework;
use serenity::model::{channel::Message, gateway::Ready, id::UserId};
use serenity::prelude::{Client, Context, EventHandler};

use serde::{Deserialize, Serialize};
use serde_json::Result;

use commands::{channels::*, neko::*};

イベントハンドラ

// Handler構造体.取得したいイベントを実装する
struct Handler;

#[async_trait]
impl EventHandler for Handler {
    // Botが起動したときに走る処理
    async fn ready(&self, _: Context, ready: Ready) {
        // デフォルトでC言語のprintfのようなことができる
        println!("{} is connected!", ready.user.name);
    }
}

neko コマンド

commands以下にnekoコマンドを実装します.

commands/neko.rs
use serenity::framework::standard::{macros::command, CommandResult};
use serenity::model::prelude::*;
use serenity::prelude::*;

#[command]
#[description = "猫のように鳴く"]
async fn neko(ctx: &Context, msg: &Message) -> CommandResult {
    // msg.channel_id.say で,channel_id の channel にメッセージを投稿
    msg.channel_id
        .say(&ctx.http, format!("{} にゃーん", msg.author.mention()))
        .await?;
    // CommandResultはResultを継承している
    // `Result?` は正常な値の場合,Resultの中身を返し,エラーの場合は即座にreturnする演算子
    Ok(())
}

mod.rs を書いて公開します.

commands/mod.rs
pub mod neko;

コマンドを読み込みます.

main.rs
use commands::{neko::*};

#[group]
#[description("汎用コマンド")]
#[summary("一般")]
#[commands(neko)]
struct General;

help コマンド

ユーザ定義の help コマンドです.~help(~はコマンドプレフィックス) と入力したときに送信されるメッセージです.さらに細かくカスタマイズできます.

main.rs
#[help] // Helpコマンド
#[individual_command_tip = "これはヘルプコマンド"] // Helpコマンドの説明
#[strikethrough_commands_tip_in_guild = ""] // 使用できないコマンドについての説明を削除
async fn my_help(
    ctx: &Context,
    msg: &Message,
    args: Args,
    help_options: &'static HelpOptions,
    groups: &[&'static CommandGroup],
    owners: HashSet<UserId>,
) -> CommandResult {
    // _ は使用しない返り値を捨てることを明示している
    let _ = help_commands::with_embeds(ctx, msg, args, help_options, groups, owners).await;
    // 空のタプルをreturn(仕様)
    // Rustでは`;`なしの行は値としてreturnすることを表す
    // return Ok(()); と同義
    Ok(())
}

トークン読み込み

config.json に先程作った Bot のトークンを貼り付けましょう."This_is_Token" という部分を置き換えてください.

JSON の読み込みについて詳しくは他の記事を参照ください.serdeというクレートを使用しています.

main.rs
#[derive(Serialize, Deserialize)]
struct Token {
    token: String,
}

//{"token": "This_is_Token"} の形のトークンを取り出す関数
fn get_token(file_name: &str) -> Result<String> {
    let file = File::open(file_name).unwrap();
    let reader = BufReader::new(file);
    let t: Token = serde_json::from_reader(reader).unwrap();
    Ok(t.token)
}

main 関数

Bot のメインループになります.ここでは,実際に動かすクライアントを作成します.

main.rs
#[tokio::main]
async fn main() {
    // Discord Bot Token を設定
    let token = get_token("config.json").expect("Err トークンが見つかりません");
    // コマンド系の設定
    let framework = StandardFramework::new()
        // |c| c はラムダ式
        .configure(|c| c.prefix("~")) // コマンドプレフィックス
        .help(&MY_HELP) // ヘルプコマンドを追加
        .group(&GENERAL_GROUP); // general を追加するには,GENERAL_GROUP とグループ名をすべて大文字にする

    // Botのクライアントを作成
    let mut client = Client::builder(&token)
        .event_handler(Handler) // 取得するイベント
        .framework(framework) // コマンドを登録
        .await
        .expect("Err creating client"); // エラーハンドリング

    // メインループ.Botを起動
    if let Err(why) = client.start().await {
        println!("Client error: {:?}", why);
    }
}

実行

cargo run

を実行すると Bot が起動します.run はプログラムのビルドと実行を同時に行います.

Linux を使っていて,バックグラウンドで実行したい場合は

nohup cargo run &

を実行します.

最終的にリリースビルドを行い,Bot を起動するときは,

cargo run --release

を実行します.--release フラグにより,約 20 倍高速化されます.

全コード

t4t5u0/DiscordBotWithRust にあります.

動作例

74607e4ad2c9c18aa48a4c26c09221b3.png (456×433)

おわりに

今回のプログラムはt4t5u0/DiscordBotWithRustにあります.個人的には,discord.py と書き方が似ているのでとても書きやすかったです.まだまだ追えていないコンフィグや機能がたくさんありますが,知見がたまったら何かの形で共有したいと思います.-> serenity チートシート(予定) にちまちまと書き溜めています.参考にしてみてください.

公式のサンプルはserenity/examples at current · serenity-rs/serenityになります.参考になるかもしれません.

参考

Discussion