ハトネコエ Web がくしゅうちょう

プログラミングとかAndroid

DiscordのbotをRubyで簡単に作成したのでコード晒します

同僚と松屋で食べてたら盛り上がって、 Discord の部屋を立てました。
Slackと違ってアプリ連携できないよな〜と思ってたら bot は作れると知ったので「よっしゃ! 作るしかない!」Ruby で作りました。

1. 完成形

こんなものができました。

f:id:nekonenene:20180719041905p:plain

f:id:nekonenene:20180719042000p:plain

2. 必要なもの

  • Ruby (今回は v2.4.2 および v2.5.1 で動作確認しました)
  • discordrb (Discord bot 用のRubyライブラリ)
  • VPSなどのサーバー、もしくは常時稼働させているご自宅のPC
  • それなりの権限を持っているDiscordサーバー

VPSに関しては、GCPの無料枠でGCEを使うのがいいかもしれません。
botなのでメモリもディスク容量も必要ありませんので。

参考 : GCP(Google Cloud Platform)での無料GCE(Google Compute Engine)インスタンス作成

3. 各プログラミング言語におけるライブラリ

Discord の bot は便利なことにいろんな言語で作れます。
ライブラリが充実しているので、得意な言語で書くことが出来ます。

今回は Ruby での作り方を書きますが、各プログラミング言語におけるライブラリと参考になる記事を紹介します。
以下で書いたもの以外にも、PHP, Rust, Swift, Haskell などのライブラリもあるようです。(参考 : https://discordapi.com/unofficial/libs.html

3-1. Python

気のせいかもしれないですが、Pythonbot を作っている記事を多く見かけます。
言語処理などライブラリも充実しているので、あれこれ作るには向いているのでしょうね。

ライブラリ
記事

3-2. Node.js (JavaScript)

Pythonに次いで記事が多いように感じました。
Glitchで無料で動かす手法、とても良さそうです。

ライブラリ
記事

3-3. Java

上手くやればKotlinでも書けるそうなのでチャレンジしてみてはいかがでしょう。

ライブラリ
記事

3-4. C#

C# はWebのイメージがないのでライブラリが2つもあることに驚きました。
よく考えるとゲーム制作者とC#はフレンズさんなので、Discordに関わる人が書きやすい言語である可能性がありますね。

ライブラリ
記事

3-5. Go (golang)

最近人気のgolangことGo言語ですが、なぜか日本語記事が少ないです。
ただ、こういう文字列処理を多く扱うWebアプリケーションはGo言語より他の高級言語が向いているとも言えるでしょうね。

ライブラリ
記事

4. コード公開

作ったコードがこちらです!
解説しやすいよう、実際よりかはいくらか削ってあります。(特に munou_message メソッドは長いのでガッツリ削りました)

また、API Keyなどの秘密情報は環境変数としました。
実際に使う際は、 export HOGEHOGE_KEY='secret_value_is_here' のようにコンソールに打って値を設定するなり、
コードに値を直接記入するなり、 dotenv を活用するなりしてください。

5. 解説

はじめに断っておくと、例外処理はけっこう甘いです。
例外を吐くと bot が何も返さないという結果が起こるだけで、プロセスが落ちたりするわけではないのでゆるくやってます。
ですが、本来はメソッドの仕様上 nil が返ってきてはいけない箇所があったりします。

28〜30行目: CommandBot クラスの作成

@bot = Discordrb::Commands::CommandBot.new(client_id: BOT_CLIENT_ID, token: BOT_TOKEN, prefix: "/")

BOT_CLIENT_ID, BOT_TOKEN に基づいて CommandBot クラスを作成します。
この client id および bot token の値についてはこちらの記事を参考にしてみてください。
今回の制作にあたってとても参考になったサイト様です。

DiscordのBotを作ってみよう! | 東京工業大学デジタル創作同好会traP

ここで、 client id と bot token の組み合わせが誤っていると、 @bot.run のときの接続で
「Authentication failed.」のエラーがコンソールに出力されます。

今回 "/" を指定している prefix は、
コマンドの先頭につける文字です。スラッシュ / だとDiscord側で用意されているコマンドとかぶるおそれがあるので、
!: などを使用するのが安心かもしれません。

68〜76行目: お天気案内( message, command メソッド)

### 天気 ###
@bot.message(contains: /天気は?$/) do |event|
  return if event.message.mentions.count > 0
  event.respond(weather_message)
end

@bot.command :weather do |event|
  event.respond(weather_message)
end

下の @bot.command から説明します。

先に説明した prefix の話はこの command メソッドが関連します。
prefix では "/" を指定しておりましたので、ユーザーがチャット欄に /weather と打つと反応してくれます。

f:id:nekonenene:20180719033130p:plain

つまり、prefixを ! としているのであれば、ユーザーは /weather でなく !weather と打たなければ bot が反応してくれないわけです。

次に @bot.message の方を説明します。

message メソッドは、ユーザーが何の気なしに発言したメッセージに勝手に反応します。
contains: /天気は?$/正規表現で拾うようにしています。

なお、この場合は contains: /天気は?$/ というように正規表現せずとも end_with: "天気は?" と定義しても同じになります。
オプションについて詳しくはこちらをご覧ください。 https://www.rubydoc.info/gems/discordrb/Discordrb/EventContainer#message-instance_method

これだけだと 、あとで説明するメンションにおいても、「天気は?」が含まれるメッセージに反応するようにしていますので、 「@kizuna-bot 明日の天気は?」という質問に 2回botが反応してしまう現象が発生してしまいます。ですので、

return if event.message.mentions.count > 0

を入れて、誰かへのメンションであるメッセージに関しては無視するようにしています。

本当はここで nil を返すのは良くなくて、
本来は Discordrb::Events::MessageEvent などの Event クラスが返り値となるべき箇所ですので、
https://github.com/meew0/discordrb/blob/8b93549e9bf45accd0c49401830ef3af87364071/lib/discordrb/bot.rb#L1227 でエラーが起こり discordrb 側で例外処理されています。

42〜55行目: メンションへの反応( mention メソッド)

### メンションに反応 ###
@bot.mention do |event|
  mention_users = event.message.mentions
  message = event.content

  # 不要な文字列を除去
  message.delete!("\s")
  mention_users.each{ |user|
    message.slice!("<@#{user.id}>")
  }

  reply = munou_message(message: message, event: event)
  event.respond(reply) unless reply.nil?
end

mention メソッドはこのbotに対するリプライを誰かが飛ばしたときに反応するものです。

あとの munou_message メソッドで扱いやすいよう、
「@kizuna-bot 明日の天気は?」のメンション部分を削って「明日の天気は?」を message 変数に代入するよう調整しています。

メンションは、Discord内の処理としては <@ユーザーID> という文字列の形式をとっていますので、その形を slice! メソッドを使用することで削り取っています。
なお、「#general」などのチャンネルリンクも同様で <#チャンネルID> という文字列を内部的には保持しています。

整形したら、 message と event を渡してあげて、
248行目以降の munou_message メソッドで、返すメッセージを選んでくれています。

この munou_message メソッドの語源は「人工無脳」からです。
決められたメッセージに反応するbotを俗に人工無脳と呼びます。
(Siriは人工知能なんかじゃなく人工無脳なんじゃないか、と言われたりもしますね)

294〜295行目など: そしてスタートだ!

kizuna_bot = KizunaBot.new
kizuna_bot.start

KizunaBotクラスを作り start メソッドを呼び出しています。

クラスが作られた時点で initialize メソッドが走り CommandBot クラスが作成され、
start メソッドにより、コンソールにbotの invite URL を表示させたあと、
settings メソッドで先ほどのコマンドやメンションなどの際のbotの反応を定義させ、
最後に run メソッドを呼び出すことで WebSocket による通信を開始しています。
参考 : https://github.com/meew0/discordrb/blob/8b93549e9bf45accd0c49401830ef3af87364071/lib/discordrb/gateway.rb#L504-L529

6. コンソールからの実行

この kizuna_bot_for_blog.rb の実行方法について説明します。

RubyをインストールしてあるPCで

gem install discordrb

と打ち、依存ライブラリをまずインストールします。

kizuna_bot_for_blog.rb をダウンロードして適当な場所に置いたあと、

BOT_CLIENT_ID = ENV["BOT_CLIENT_ID"].freeze
BOT_TOKEN = ENV["BOT_TOKEN"].freeze

の部分に従って、環境変数の設定をするか、もしくはこの部分のコードを直接書き換えます。

コンソールで環境変数の設定をするなら

export BOT_CLIENT_ID="1234567890"; export BOT_TOKEN="secret.token"

のように打ち込みます。
RSS2JSON_API_KEY, RECRUIT_API_KEY については、各サイトでAPI Keyを取得してくる必要があるのでここでは説明を省きます)

最後に、コンソールで kizuna_bot_for_blog.rb があるディレクトリにて

ruby kizuna_bot_for_blog.rb

と実行するだけです。けっこうシンプルです。

7. Heroku での実行

クレジットカードを持っているなら 2. で書いたとおり、GCPの無料枠でGCEを使うのが一番気軽な選択肢かと思いますが、 「24時間起動していたいけどクレジットカードも持ってないしパソコンを立ち上げっぱなしもつらい〜」という方は Heroku を利用する手段があります。

無料プランだと月に550時間(クレジットカード登録をすると1000時間になる)しか動かせないので、
使われる頻度の高いbotだと厳しいですが、botがあまり使われないようであれば、
Herokuは、30分使用されないと勝手にスリープ状態に入り、スリープ期間中は使用時間としてカウントされませんので、1ヶ月でなんとか550時間を超えないで済むかもしれません。

この550時間は、動かしているプロセスの合計時間としての制限ですので、
複数の bot を動かしたい場合は簡単に制限を超えてしまうので注意しましょう。

Heroku で実行するには Gemfile , Gemfile.lockProcfile が必要になります。
説明すると長くなりますので、詳細はいろいろググってみてください。
(ぱっとググってみた感じだと、あまり網羅的な記事が見つかりませんでした……)

github.com

今回は私のほうで Heroku で実行できるようなファイルセットにしたものを用意しました。

README に使い方を記していますので、使ってみたい方はお読みください。

8. おしまい

というわけで、大変長くなりましたが、Ruby で Discord の bot を作る方法でした。
初期セットアップの部分の記事はいくらか見ましたので(お世話になりました、ありがとうございます)、
この記事ではもう一歩踏み込んだ実用としての部分のコードをお見せしました。

Ruby は便利メソッドが大変多いので、botを手軽に作りたいときに選ぶプログラミング言語としてはなかなか向いているのではないでしょうか。

ところで今回の KizunaBot 、Kizuna という名前で Kizuna AI ちゃんの方を連想されたかたが多そうですが、
VOICEROID であり VOCALOID でもある 紲星あかり (きずなあかり)ちゃんの名前からとっていました。

かわいいですよね。

Kizuna AI + ミライアカリ みたいな名前してるので、人と話してると VTuber の名前だと勘違いされがちです。

発売が最近なのでまだ知名度ないですが、素直な(声の)子なのでがんばってほしいです。

VOICEROID2 紲星あかり|ダウンロード版

VOICEROID2 紲星あかり|ダウンロード版

かわいかろう?

いや、でも VOICEROID で言うと私はきりたんの方が欲しいんですよ。
あのダウナーで、かつリアルなしゃべり。きりたそ〜欲しい〜。

VOICEROID+ 東北きりたん EX ダウンロード版|ダウンロード版

VOICEROID+ 東北きりたん EX ダウンロード版|ダウンロード版

以上です。