DiscordのBotを作る #2
— development — 8 min read
ボイスチャンネルへの参加を通知する
DiscordのBotを作る #1の続きです.
コード書く
めでたく起動したのであとはWikiとかドキュメント参照してコード書きましょう.
イベントハンドラを追加するにはフィルタ属性のリストとブロックと共にそれぞれのメソッドを呼び出せばいいので,(Wikiより)
require 'discordrb'
bot = Discordrb::Commands::CommandBot.new token: 'B0T.T0KEN.here', client_id: 'BOT.CLIENT_ID.here', prefix: '!'
bot.message(with_text: "Hey Bot!") do |event|    event.respond "Hi, #{event.user.name}!"end
bot.runこんな感じで記述すると,"Hey Bot!"というテキストに対して"Hi, {ユーザ名}"と返すようになります.(exampleでは与えてませんでしたが,botインスタンス生成時にCLIENT_IDも与えときましょう.)
最初はいまいち何してるかわかりませんでした.ライブラリ読んでみると,
messageメソッドはDiscordrb::Commands::CommandBotのスーパークラスDiscordrb::BotがincludeしているDiscordrb::EventContainerモジュールで定義されていて,フィルタ属性とブロックを渡してイベントを登録してるのがわかります.Rubyはメソッド呼び出しの時に引数と一緒にブロック渡せるんですね.discordrb/container.rb
コマンドの定義ならこんな感じ
bot.command :hello do |event|  event.respond "Hello, #{event.user.name}"endボイスチャンネルへの参加をテキストチャンネルへ通知する
Discordは同じボイスチャンネルに参加していない限り誰かが新しく参加してきても何の通知もありません.ので新しくユーザがボイスチャンネルに参加した時,テキストチャンネルで通知するようにします.暇なときしれっとボイスチャンネルに接続しとけば誰かかまってくれるかもしれません.|ω' ) ヌッ
ボイスチャンネルへの参加は#voice_state_updateで取れるので,
bot.voice_state_update do |event|
endこのブロックを記述する.誰かがボイスチャンネルに接続した時発火するイベントはこれ
Class: Discordrb::Events::VoiceStateUpdateEvent
参加時のみ通知したいのでold_channelがnilのときのみ#send_messageします.
if event.old_channel.nil?  # text notification  text = "#{event.user.name} joined #{event.channel.name}"  event.bot.send_message(default_text_channel, text)endメッセージを送るテキストチャンネルIDが必要なので事前に取得しとかなきゃならないですね.デフォルトで存在する"general"があればそこに,なければ最初に取得できたチャンネルに.Discordはチャンネル一つもない状況が作れるみたいなのでテキストチャンネルがなければ警告くらいだしとけばいいかな.(自分の作ったもので初めて例外処理書いた気がします...)
begin  default_text_channel = nil  event.server.channels.each do |channel|    if channel.type == 0      default_text_channel ||= channel.id      default_text_channel = channel.id if channel.name == 'general'    end  end  exit unless default_text_channelrescue SystemExit => err  puts "[WARN] There is no text channel."end最後に,Botアカウントには反応したくないのでuser.bot_accountがfalseのときのみ処理するとして,こんな感じになりました.
bot.voice_state_update do |event|  if !event.user.bot_account    # get default text channel    begin      default_text_channel = nil      event.server.channels.each do |channel|        if channel.type == 0          default_text_channel ||= channel.id          default_text_channel = channel.id if channel.name == 'general'        end      end      exit unless default_text_channel    rescue SystemExit => err      puts "[WARN] There is no text channel."    end
    # notify only when joining any channel    if event.old_channel.nil?      # text notification      text = "#{event.user.name} joined #{event.channel.name}"      event.bot.send_message(default_text_channel, text)    end  endend
bot_accountの判定部分,早期returnで返してしまいたかったんですが,エラー吐いてしまうので断念.無駄にネストが深い.というかRubyはreturn使うの邪道なんでしょうか.まだよくわかってない.
チャンネルへの参加を通知. 誰か相手して('ω')!!!
 
    
モジュールを使う
大まかな機能毎にコード管理したかったのでmodule使ってコード分割します.
ディレクトリ構成は大体こんな感じ(GitHubでRubyのリポジトリ漁ってパクり参考にしました.)
$ tree srcsrc├── bot.rb└── modules    ├── commands    │   └── general.rb    ├── events    │   ├── join_announcer.rb    │   └── mention.rb    └── helpers        └── soundfile.rb
4 directories, 5 files例えば,general.rbにはコマンド類を記述したいので,CommandContainerをextendしたモジュール作って内部に#command使ってコマンドを定義しときます. #voice_state_updateの処理はコマンドではなくイベントが発生したら呼ぶ処理なのでjoin_announcer.rbとしてモジュール化しときます.
require 'discordrb'
module Bot::Commands  module General    extend Discordrb::Commands::CommandContainer    command :hello do |event|      event.respond "Hello, #{event.user.name}"    end  endendこれをbot.rb内でCommandBotのインスタンスBOTにincludeする.bot.rbはモジュールの読み込みとBOT.runのみ.
def self.load_modules(cls, path)  new_module = Module.new  const_set(cls.to_sym, new_module)  Dir["src/modules/#{path}/*rb"].each { |file| load file }  new_module.constants.each do |mod|    BOT.include! new_module.const_get(mod)  endend
load_modules(:Commands, 'commands')load_modules(:Events, 'events')なるほど(˘ω˘)
#load_modulesは指定したシンボルと読み込んだモジュールを記述したファイルから新しくモジュール作ってBOTにincludeしてるのかな.#include!が調べてもいまいちわからないけど恐らくそんな感じ.
とにかくこれで今まで通り動作して機能追加もしやすくなりました.
まとめ
取り敢えずボイスチャンネルへの参加をテキストチャンネルに通知するDiscord Botが作れました.
なんだかんだMac上で動いたBotを契約したVPS上で動くようにするための環境構築に一番時間がかかった気がします.(Discordで音声を扱う場合はさらに必要なパッケージがいくつかあって手間取る)
この環境構築の手順もそのうち自分のためにもまとめときたいですね.
あと,ボイスチャンネルに参加したユーザ名を音声通知する機能も実装したんですが,ボイスチャンネルに人がいなくなったとき謎の警告を吐くのが解決してない&記事が長くなりすぎるのでまた今度まとめます.(GitHub上のリポジトリは音声通知まで実装してます.)
Bot作成,ちょっと書けばすぐ動くし簡単に機能追加できそうなのでおすすめです.たのしい.昔PHP向けのライブラリ使って作ろうとしたときはリファレンス禄に読めなくてやめちゃったんですが今やってみると割とサクサク読めるようになって若干の成長を感じる.あと,人の実装みて理解しようとすると学ぶことが多くて結構勉強になったのでよかったですね.