LoginSignup
10
9

More than 5 years have passed since last update.

Rubotyでbot宛てのメッセージじゃなくても反応して欲しくてコードを読んだ話

Last updated at Posted at 2016-10-08

概要

社内で「rubotyってメッセージの文頭にbot名ないと反応してくれないんですか?」
と聞かれたので、これはOSSコミットチャンスか!?
とテンション上がって色々しらべたときのお話。

調査

ドキュメントを読む

ドキュメントを一通り眺めてみる。
それっぽい言葉は無い。
きっと対応していないのだろう。
あんまり用途無いしな。
よ〜し、ちゃんと読んで、きれいに実装するぞ〜!!
そういう気持ちでコードリーディングを開始する。

コードリーディング開始

adapter

何度かコードを読んで、大体仕組みは把握している。
仕組みに興味がある人はtbpgrさんの、このあたりの投稿を読まれると良いだろう。

メッセージ周りは、adapterを読めば良いのだろう。
adapterは、chat toolとrubotyをつなぐためのプラグインである。
今回はshell.rbを読んでいく。

lib/ruboty/adapters/shell.rb
  def listen
    step until stopped?
  rescue Interrupt
    stop
  end

  def step
    case body = read
    when "exit", "quit", nil
      stop
    else
      robot.receive(body: body, source: SOURCE)
    end
  end

ふむ。全てのメッセージをrobot.receiveで受け取っているらしいな。
ということは、メッセージの文頭にbot名無くても大丈夫そう。
次は、robot.rbを読もう。

robot.rb

robot.rbのreceiveを読む。
どうやら、handlersを総なめして、条件にヒットしたものをcallしてるらしい。
ということで、handlerを読もう。
(ちなみに missing: trueのときのみに、handlerの中身は処理される)

lib/ruboty/robot.rb
def receive(attributes)
  message = Message.new(attributes.merge(robot: self))
  unless handlers.inject(false) { |matched, handler| matched | handler.call(message) }
    handlers.each do |handler|
      handler.call(message, missing: true)
    end
  end
end

handlers/base.rb

handlerはrubotyで実行する各種タスクの条件を記載しているclassである。

lib/ruboty/handlers/ping.rb
# pingタスクの例
module Ruboty
  module Handlers
    class Ping < Base
      on(/ping\z/i, name: "ping", description: "Return PONG to PING")

      def ping(message)
        Ruboty::Actions::Ping.new(message).call
      end
    end
  end
end

さて、handlersのcallを読んでいく。
handlers/base.rbにあるようだ。

lib/ruboty/handlers/base.rb
  def call(message, options = {})
    self.class.actions.inject(false) do |matched, action|
      matched | action.call(self, message, options)
    end
  end

ふむ。ここでも別classをcallしているようだ。
どこかで配列actionsに値を代入しているメソッドがあるのだろう。
ということで、actionsに対して値を代入しているメソッドを探す。
どうやら、onメソッドのようだ。Action インスタンスを代入しているらしい。
ということで、次はaction.rbを読む。

lib/ruboty/handlers/base.rb
def on(pattern, options = {})
  actions << Action.new(pattern, options)
end

lib/ruboty/action.rb

もう何度目のcallだろうか。
そういう気持ちでcallメソッドを読んでいく。
ふむふむ。pattern_withで引っかかったやつは実行されるようだ。

lib/ruboty/action.rb
def call(handler, message, options = {})
  if !!options[:missing] == missing? && message.match(pattern_with(handler.robot.name))
    !!handler.send(name, message)
  else
    false
  end
end

そしてなんと....
あれ? all?
え、その正規表現は.....

lib/ruboty/action.rb
def all?
  !!options[:all]
end
# 省略

private

def pattern_with(robot_name)
  if all?
    /\A#{pattern}/
  else
    /#{self.class.prefix_pattern(robot_name)}#{pattern}/
  end
end

結論

handlerのonメソッドに対して、optionでall: trueを入れれば解決。

on(/hear\z/i, name: "hear", description: "", all: true)

うむ....既に機能としてありました。さすがです。
ありがたく使わせていただこうと思います。

10
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
9