ヽ(´・肉・`)ノログ

How do we fighting without fighting?

Rubotyを読む

Elixir で bot を作ろうと考えた.

そこで

r7kamura/ruboty の最新 v1.2.2 を読み,run させたときにどうやって plugin と連携しているのか調べた.

Ruboty | 基本構成図解 も理解の助けになった. 概要を知るのにこの絵を眺めると登場人物を把握しやすい.

コマンド実行

ruboty コマンドを実行すると bin/ruboty

Ruboty::CommandBuilder.new(ARGV).build.call

が呼ばれる.

ruboty/command_builder.rb には

def initialize(arguments = ARGV)
  @arguments = arguments
end

def build
  command_class.new(options)
end

def command_class
  case
  when options[:generate]
    Commands::Generate
  when options[:help]
    Commands::Help
  else
    Commands::Run
  end
end

があるので,何も指定していない今回は Commands::Run が返される.つまり Commands::Run.new(options).call だ.

ruboty/run.rb

module Ruboty
  module Commands
    class Run < Base
      def call
        Robot.new(options).run
      end
    end
  end
end

継承元の Baseruboty/base.rb の通り options を保持しているだけだ.

Robot.new(options).runRobot インスタンスを作って run させていることがわかる.

Robot

ruboty/robot.rb の run

def run
  dotenv
  bundle
  setup
  remember
  handle
  adapt
end
dotenv
Dotenv から環境変数を読み込む
bundle
Bundler から gem を読み込む
setup
ファイルを load する.Ruboty | Ruboty を魔改造してみる のように,オプションで渡したファイル名の plugin を読み込ませることができる
remember
Brains::Base.find_class.new で Brain を作っている
handle
Ruboty.handlers.map { |handler_class| handler_class.new(self) } で Handle を作っている
adapt
AdapterBuilder.new(self).build.run で Adapter を動かしている

Brain

ruboty/base.rb

Brain についてはあまり書くことがない.

ruboty/robot_spec.rb にあるような robot.brain.data["a"] といった形で状態を扱えるようだ.

Brain は 1 つの Robot インスタンスに 1 つしか持てないようになっている.

Handler

ruboty/base.rb

1 つの Handler は,複数の Action を持つことができ,Action は Handler.on メソッドで登録する. 複数の Action を登録する場合は,ruboty-alias/alias.rb のように複数回 Handler.on を呼び出す.

on(
  /alias (?<from>.+?) -> (?<to>.+)\z/m,
  description: "Create alias message",
  name: "create",
)

on(
  /list alias\z/,
  description: "List alias",
  name: "list",
)

on(
  /delete alias (?<from>.+)\z/m,
  description: "Delete alias",
  name: "delete",
)

on(
  //,
  description: "Resolve alias if registered",
  name: "resolve",
  hidden: true,
)

ruboty/action.rb

Action には,パターンと,パターンに合致したときに呼び出す Handelr のメソッド名が設定される.

Handler が call されたときは,登録してある Action を順番に call していく.

Action が call されると,渡された message に対してパターンが合致するか試し,合致したら name で Handler のメソッドを呼ぶ.

大抵の場合は message.reply で Adapter へと表示してもらいたい内容を返す.

Message は ruboty/message.rb に定義がある.内容と,メタデータ(どこから来たか,マッチした内容は何かなど)によって構成されている.

Adapter

Adapter も 1 つの Robot インスタンスに 1 つしか持てないようになっている.

ruboty/base.rb

run したら,自分でループしながら動作しつづけ,入力があれば,引数にメタ情報と入力内容をつけて robot.receive を呼びだす.

出力は Handler で message.reply が呼ばれ,その中で robot.say が呼ばれ,その中で adapter.say が呼ばれて返ってくる.

まとめ

登場人物はおおまかに

  1. 本体(Robot)
  2. 外界との接続(Adapter)
  3. 来たメッセージに,どんな種類のとき,どう反応するか(Handler)
  4. (おまけ)状態を Robot に持っておく(Brain)

の 4 つ.

1 つの Robot は,1 つの Adapter,1 つの Brain,複数の Handler を持つ.

入力があると Adapter, Robot, Handler の層の間を Message が流れていく.

Handle が反応して,出力したい場合は Message の reply を呼び出す.すると Robot, Adapter の順に呼び出されて出力を行える.

Ruboty を使ったことがなく,Ruboty の Plugin についての知識もなかったのだが 3 - 4 時間くらいでこの記事を書きながらだいたい読めた.

とてもわかりやすい作りになっていた.すばらしい.

このエントリーをはてなブックマークに追加