hubot-ircではmsg.replyのリプライ先が変わるので注意
hubot-ircを使い、こういうコードで一定時間後に後からユーザに通知しようとしてたところ、
replyしてるのに発言元とは別のチャットに送信してしまうという問題が起きました。
robot.respond /進捗 start/i, (msg) ->
setTimeout(->
msg.reply "進捗どうですか?"
return
, 30 * 60 * 1000)
msg.send "進捗 start"
何度か意図的に起こしてみたところ、どうやらmsg.replyの送信先は、
そのユーザがreplyする時に最後に発言したチャットに対して行われるらしく、
メッセージが作られた後に別のチャットに発言した場合、そちらに送られてしまうようです。
そのため、以下のように送信先を待避することで回避できます。
robot.respond /進捗 start/i, (msg) ->
user = msg.message.user.name
room = msg.message.user.room
setTimeout(->
robot.send {room: room}, "#{user} 進捗どうですか?"
return
, 30 * 60 * 1000)
msg.send "進捗 start"
原因となるコードを捜す
というのは振る舞いから推測したものなので、実際にコードを追ってみます。
hubot-irc.reply
まずはhubot-ircのmsg.replyの中を見ます。
https://github.com/nandub/hubot-irc/blob/master/src/irc.coffee#L78
reply: (envelope, strings...) ->
for str in strings
@send envelope.user, "#{envelope.user.name}: #{str}"
メッセージをユーザ宛に変換し、sendメソッドにenvelope.userと一緒に渡しています。
hubot-irc.send
次にsendメソッドの中身を見ます。
https://github.com/nandub/hubot-irc/blob/522c50166f15e57ada0c10f181d4de26b4349717/src/irc.coffee#L16
send: (envelope, strings...) ->
# Use @notice if SEND_NOTICE_MODE is set
return @notice envelope, strings if process.env.HUBOT_IRC_SEND_NOTICE_MODE?
target = @_getTargetFromEnvelope envelope
unless target
return logger.error "ERROR: Not sure who to send to. envelope=", envelope
for str in strings
@bot.say target, str
envelope
(replyメソッドのenvelope.user)から_getTargetFromEnvelope
でターゲットを取り出し、
@bot.say
で発言をしています。
このとき、@botはnodeのircパッケージのオブジェクトで、
sayメソッドはtargetに対してメッセージを送るようになっています。
そのため、このtargetに設定される返信先が変更される可能性が高そうです。
hubt-irc._getTargetFromEnvelope
https://github.com/nandub/hubot-irc/blob/master/src/irc.coffee#L336
_getTargetFromEnvelope: (envelope) ->
user = null
room = null
target = null
# as of hubot 2.4.2, the first param to send() is an object with 'user'
# and 'room' data inside. detect the old style here.
if envelope.reply_to
user = envelope
else
# expand envelope
user = envelope.user
room = envelope.room
if user
# most common case - we're replying to a user in a room
if user.room
target = user.room
# reply directly
else if user.name
target = user.name
# replying to pm
else if user.reply_to
target = user.reply_to
# allows user to be an id string
else if user.search?(/@/) != -1
target = user
else if room
# this will happen if someone uses robot.messageRoom(jid, ...)
target = room
target
ユーザの情報を使ってリプライ先を決定しているようです。
この中で、envelope.roomが存在すれば、それをtargetとして設定するようになっています。
なお、このメソッド内のenvelopeは、replyメソッドのenvelope.userを差しています。
そのため、次はreplyメソッドのenvelope.user.roomが書き換わるかどうかを調べていきます。
replyメソッドの呼ばれ方
コールスタックをさかのぼっていくと、以下のメソッドが順に呼ばれていました。
https://github.com/github/hubot/blob/39681ea35de6375154748418e11f533ef51c3340/src/listener.coffee#L22
https://github.com/github/hubot/blob/39681ea35de6375154748418e11f533ef51c3340/src/robot.coffee#L192
https://github.com/github/hubot/blob/39681ea35de6375154748418e11f533ef51c3340/src/adapter.coffee#L65
https://github.com/nandub/hubot-irc/blob/522c50166f15e57ada0c10f181d4de26b4349717/src/irc.coffee#L237
この中で、最後のhubt-ircの以下の部分で、nodeのircがメッセージを受信した場合に、
メッセージから特定されたuserオブジェクトが、replyメソッドのenvelope.userになっていました。
bot.addListener 'message', (from, to, message) ->
if options.nick.toLowerCase() == to.toLowerCase()
# this is a private message, let the 'pm' listener handle it
return
if from in options.ignoreUsers
logger.info('Ignoring user: %s', from)
# we'll ignore this message if it's from someone we want to ignore
return
logger.debug "From #{from} to #{to}: #{message}"
user = self.createUser to, from
if user.room
logger.info "#{to} <#{from}> #{message}"
else
unless message.indexOf(to) == 0
message = "#{to}: #{message}"
logger.debug "msg <#{from}> #{message}"
self.receive new TextMessage(user, message)
userオブジェクトは、受け取ったメッセージからcreateUserを使って求めているようです
hubot-irc.createUser
createUser: (channel, from) ->
user = @getUserFromId from
user.name = from
if channel.match(/^[&#]/)
user.room = channel
else
user.room = null
user
ここで、user.roomに最新のメッセージが送られたchannelをセットしています。
そのため、getUserFromIdが返すuserオブジェクトがメッセージごとに共通なら、
replyメソッドに渡された後に別のメッセージによって書き換わる可能性があると言えます。
hubot.getUserFromId
getUserFromIdはhubot.getUserFromIdを呼び出しているだけでした。
(HubotのAPIが変わったため、一手間挟んでいる)
https://github.com/github/hubot/blob/39681ea35de6375154748418e11f533ef51c3340/src/brain.coffee#L103
userForId: (id, options) ->
user = @data.users[id]
unless user
user = new User id, options
@data.users[id] = user
if options and options.room and (!user.room or user.room isnt options.room)
user = new User id, options
@data.users[id] = user
user
@data内に保存されているユーザを帰しています。
そのため、各メッセージ毎に共通のuserオブジェクトが返されます。
hubot-irc.createUser内でこのオブジェクトのroomを書き換えているため、
ユーザが発言を行うと、robot.replyの時に使用するuserオブジェクトが変更され、
慈顔をおいて発言しようとすると別のチャットに発言していたようです。