Rustでモンハンを楽しくするDiscord Botを作った
まえがき
モンハンってしばらくやってるとやることがなくなりますよね。
そこで、出発するクエスト・使う武器・追加ミッションなどを生成してくれるDiscord Bot、MHR Roulette Bot(以降、本Bot)を作成し、様々な武器を様々な側面から楽しめるようにしてみました。
本BotはRustでserenityというDiscord Botを作るためのライブラリを使ってシュッと作りました。
モンハンには色々な武器や色々な戦い方があるので、楽しみ尽くしたいですよね。
でもTAをやるほどのやる気もない。
そんなときに便利です。
今回作ったもの
- Bot本体
- CIのためのGitHub Action(rustfmt)
- CIのためのGitHub Action(clippy)
- 一番上に貼り付けてあるかっこいいロゴ
出来上がったもの
矢切りだけで戦う方法や、アイテム持ち込み1個で戦う方法を学びましょう。
実際にルーレットを運用した結果、メンバーの爆弾投げスキルが劇的に向上しました。
コマンドについて
Discordのスラッシュコマンドに対応したBotです。
- settings
- members: メンバーの設定
- exclude: 武器・クエスト・モンスターの除外設定
- target: 対象にする武器・クエスト・モンスターの設定
- obliterate: 現在の設定を削除する
- generate
- マストオーダー、オプショナルオーダー、メンバーとその武器を決定・表示する
- statistics
- ハンターがどの武器を何回指定されたかなどをクエリできる
- version
- バージョン情報、対応しているMHRのバージョンなどが見られる
などのコマンドが使えます。
技術的な話
ちょっとRustの話をして、そのあとにDiscord APIの話をして、最後にBotの設計の話をします。
Rustの話
Nightly chnnel を使った開発
本Botの開発にはRustのnightly channelを使用しています。
なので、もし本Botをビルドして使ってみたいという危篤なモンハン廃人がいた場合、nightly channelを使う必要があります。
const_evaluatable_checked
やformat_args_capture
などの気になる機能があり、コンパイラをいじめたい気分になったからです。
2時間開発したら1回ICEする勢いで開発しています。
というかclippyがICEし続けててつらいです。
const_evaluatable_checked
const genericsという機能がRust 1.51.0から導入されたようなきがします。
これ単体だと使いみちが限られていて面白くないのですが、const_evaluatable_checked
を用いれば面白いことができるようになります。
3行ほどコードを書くだけで、const genericsを使った制約条件を作ることができるようになるのです。
pub trait Satisfied {}
/// Condition: constraint for const generics
///
/// This structure is used to constrain the parameters of const generics.
/// You can use it if you want to accept non-empty arrays
/// or arrays with less than 5 elements as arguments.
///
/// # Usage
///
/// ```
/// #![allow(incomplete_features)]
/// #![feature(const_evaluatable_checked)]
/// #![feature(const_generics)]
/// use mhr_roulette::concepts::{Condition, Satisfied};
///
/// fn foo<const Size: usize>(_arr: &[i32; Size])
/// where
/// Condition<{ Size > 0 }>: Satisfied,
/// {
/// // _arr is non-empty array.
/// }
/// ```
pub struct Condition<const B: bool>;
impl Satisfied for Condition<true> {}
実コードでは、Discordでは一回のメッセージで表示できるボタンが5個までとなっているため、間違って5個よりも多いボタンをメッセージに含まないようにするために使ったりしています。
impl Buttons {
pub fn new<const N: usize>(buttons: &[CreateButton; N]) -> Buttons
where
Condition<{ N <= 5 }>: Satisfied,
{
Buttons {
buttons: buttons.to_vec(),
}
}
}
format_args_capture
読んだままの機能。
C#とかTypeScriptにあるかも。
let x = 42;
println!("{x}");
なんかフォーマットも指定できそうな予感もする。
format!("{err:?}")
一度使ってしまうと快適すぎてないとイライラするほど中毒性がある。
C#でわりと昔から使える機能なので、Rustではずっとイライラしていたため僕待望の機能。
Discord API の話
最近のDiscord APIにはInteractionsなるものがあります。
Slash Commands
スラッシュコマンドとは、今までのDiscord Botのコマンドを覚えないとうまく使えない問題等々を解決するための新しい仕組みです。
スラッシュコマンドは/
を入力することでトリガーします。
スラッシュコマンドの一覧を見ることができ、選択したコマンドがどのような引数を持つのかを視覚的に確認する事ができます。
右側にはどのBotのスラッシュコマンドが表示されていて、コマンドはBotごとにまとめて表示されいます、よって同じコマンド名を予約していても安心です。
Botアプリケーションはどこのサーバーでも使えるグローバルスラッシュコマンドとあるサーバー限定のスラッシュコマンドがあります。
グローバルスラッシュコマンドの登録は反映に最大1時間(普通は10分くらいかな?)かかりますが、サーバー限定のスラッシュコマンドは即時反映です。
スラッシュコマンドの仕様は現在以下のようになっていますが、変わるかもしれないので使う場合は一度
を一読することをおすすめします。- 1つのアプリには、ユニークな名前のトップレベルのグローバルコマンドを100個まで設定できます。
- ギルドごとに100個までのギルドコマンドを持つことができます。
- アプリはトップレベルのコマンドに最大25のサブコマンドグループを持つことができます。
- アプリはサブコマンドグループ内に最大25個のサブコマンドを持つことができます。
- コマンドは最大25個のオプションを持つことができます。
- オプションには最大25個の選択肢を設定可能。
- 各コマンドとそのサブコマンド、グループの名前、説明、値のプロパティを合わせて最大4000文字まで入力可能です。
Select Menu
ドロップダウンリストみたいなものを出せる。
これはDiscord APIのなかでも最も最近導入されたもの。
選択肢は最大25個。
選択できる最小/最大の選択肢数を設定できる。
つまり、最低1つは選択しないといけなくて、最大10個まで選択できるドロップダウンリストみたいなものを作ることができる。
設計の話
Botの主なモジュールは以下の5つです:
- data
- model
- parser
- executor
- bot
dataモジュールは武器やモンスターなどをenumにしたものがたくさんあるだけのモジュールです。特に凝ったことはしていないです。
modelモジュールではDiscordから送られてくるイベント通知(Notification)とDiscordに送信する応答(Response)をモデル化したstruct群です。
parserはDiscordから送られてくるイベント通知をパースしてNotificationに変換します。
executorはコマンドの実装です。
Notificationを受け取り、必要ならばデータベースに書き込みを行い、Responseを構築します。
最後にbotモジュールです。
botモジュールはDiscordとの通信を行うイベントハンドラーとストリーミング通信
クラッシュレポート
使ったクレートを紹介するコーナー
Discord Botを作るためのフレームワーク。
本Bot最新のDiscord APIを使うために、gitのcurrentブランチを使っている。
非同期でロギングをするために使ったクレート。
tracingのsubscriberをカスタマイズするユーティリティのクレート。
tracingのwriterをあれこれするためのクレート。
Discussion
うおおおおおお