Rustで作るdiscord bot入門編 (serenity使用)
はじめに
この記事は,Rust 製 discord bot 用ライブラリserenity
についてとてもざっくり解説するものです.公式 GitHub の example を元に構成しています.紹介しきれていない機能がたくさんあるので,気になった方は参考の公式ドキュメントをご覧ください.
本記事での目標
- serenity を用いて簡単な Discord Bot を作成する
今回は,コマンドをメッセージの判定ベースではなく,関数として実装していきます.いくつか理由はありますが,一番の理由は見通しがよくなるからです.メッセージの判定ベースでのやりかたは,serenity/main.rs at current · serenity-rs/serenityをご覧ください.
本記事の対象読者
- Rust を用いて Discord Bot を開発したい人
- Discord Bot を Python などから Rust に移行したい人
- Rapptz/discord.py: An API wrapper for Discord written in Python.は開発終了になりました(2021/9/28)
- Rust を使って実際にモノを作ってみたい人
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
をクリックします.
Bot の名前を入力し,Bot を作成します.
左上のハンバーガーメニューをクリックし,Bot のページに遷移します.
Add Bot をクリックします.
これで Bot が作成されました.Copy
を押すと Bot のトークンがクリップボードにコピーされます.これは後で使います.
Bot の招待リンクを作成ます.OAuth のページを開きます.
下にスクロールし,bot にチェックを入れます.更に下に招待リンクが作成されました.
これをブラウザに貼り付けて 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 ファイルを操作するためのクレートです.
クレートのインポート
説明のためには,それぞれ必要な場所に書いたほうがわかりやすいですが,面倒なのでまとめています.(気が向いたら分けます)
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
コマンドを実装します.
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
を書いて公開します.
pub mod neko;
コマンドを読み込みます.
use commands::{neko::*};
#[group]
#[description("汎用コマンド")]
#[summary("一般")]
#[commands(neko)]
struct General;
help コマンド
ユーザ定義の help コマンドです.~help
(~
はコマンドプレフィックス) と入力したときに送信されるメッセージです.さらに細かくカスタマイズできます.
#[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
というクレートを使用しています.
#[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 のメインループになります.ここでは,実際に動かすクライアントを作成します.
#[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 にあります.
動作例
おわりに
今回のプログラムはt4t5u0/DiscordBotWithRustにあります.個人的には,discord.py と書き方が似ているのでとても書きやすかったです.まだまだ追えていないコンフィグや機能がたくさんありますが,知見がたまったら何かの形で共有したいと思います.-> serenity チートシート(予定) にちまちまと書き溜めています.参考にしてみてください.
公式のサンプルはserenity/examples at current · serenity-rs/serenityになります.参考になるかもしれません.
Discussion