概要
社内で「rubotyってメッセージの文頭にbot名ないと反応してくれないんですか?」
と聞かれたので、これはOSSコミットチャンスか!?
とテンション上がって色々しらべたときのお話。
調査
ドキュメントを読む
ドキュメントを一通り眺めてみる。
それっぽい言葉は無い。
きっと対応していないのだろう。
あんまり用途無いしな。
よ〜し、ちゃんと読んで、きれいに実装するぞ〜!!
そういう気持ちでコードリーディングを開始する。
コードリーディング開始
adapter
何度かコードを読んで、大体仕組みは把握している。
仕組みに興味がある人はtbpgrさんの、このあたりの投稿を読まれると良いだろう。
メッセージ周りは、adapterを読めば良いのだろう。
adapterは、chat toolとrubotyをつなぐためのプラグインである。
今回は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の中身は処理される)
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である。
# 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にあるようだ。
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を読む。
def on(pattern, options = {})
actions << Action.new(pattern, options)
end
lib/ruboty/action.rb
もう何度目のcallだろうか。
そういう気持ちでcallメソッドを読んでいく。
ふむふむ。pattern_withで引っかかったやつは実行されるようだ。
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?
え、その正規表現は.....
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)
うむ....既に機能としてありました。さすがです。
ありがたく使わせていただこうと思います。