hubot-slack アダプタ v2 から v3 へ

もう日も変わろうという時間になっても Hubot Advent Calendar 2014 の13日目が埋まっていないようなので急遽飛び込んでみます。うーん、ちっともそれ向きの話ではないかも。

hubot の Slack アダプタ がバージョン 2 から 3 (これを書いている時点では 3.1.0)になりました。これに伴って Slack 側の Hubot Integration も様変わりしています。

試しに触ってみて、気づいた点をメモしておきます。

大きく変わったこと

Slack 側の Hubot Integration

発行される token の形式が変わりました。v2 から v3 に変更すると以前のものは使えず、改めてもうひとつ Hubot の integration を追加する必要があります(以前のものはあとで不要になれば削除)。

hubot の名前はここで設定します。

チャンネルは、はじめは #general にのみ参加している状態になります(後述)。

以前にはあった Hubot URL の欄はなくなりました。自前ホストの Hubot と Slack を連携させる場合につまづく点だったのですが、なくなりました。ということは、Hubot を動かすサーバーはたとえ NAT 越しの LAN の中でもよくなりました。テストのとき便利です。

環境変数はひとつ

Hubot 側で設定する環境変数は、上記の token の HUBOT_SLACK_TOKEN のみになりました。HUBOT_SLACK_TEAM は不要となり(たぶん token にその情報が含まれるのでしょう)、HUBOT_SLACK_BOTNAME も不要になりました(上記のように Slack 側で設定)。

名簿に現れるようになった

以前は名簿に現れず隠れメンバーのようなものでしたが、v3 ではまるで一般メンバーと同等の扱いです。従ってその名前をちゃんと設定しておく必要があり、それが Hubot Integration で行うものです。

一般メンバーに対して行うように、ダイレクトメッセージを送ることができます。名簿で hubot の名前をクリックするとその画面になりますが、ここではいつものように hubot ping と bot 名を前置せず、単に ping と入力するとちょうどいいという形になります。

参加チャンネル

はじめ、hubot は #general のみに参加している状態です。ほかのチャンネルに招待( /invite @hubot )すれば、そこに参加させることができます。プライベートグループにも参加させることができます。

これまで使っていたスクリプトが動かなくなるなどの影響について

今後更新されるかもしれませんので、あくまでこれを書いている時点の問題です。

【追記】2014年12月17日現在の最新版 3.2.0 で、下記のアットマーク問題とURL問題は解決されているようです。したがって以下のパッチは不要です。【追記終わり】

robot.messageRoom での room 名

これまでのバージョンでは Readme にも

Slack API uses channel ID’s by default, which uses computer-friendly alphanumeric ID. To use the pretty names, prefix it with a hash.
  robot.respond /hello$/i, (msg) ->
    robot.messageRoom '#general', 'hello there'

と書かれており、room 名にハッシュ(#)を付けることになっていました。

v3 ではちょうど逆になって、ハッシュ(#)を付けないようになりました。これが付いていると正しく動作しません。

たとえば現時点の hubot-rss-reader が引っかかりました。

アットマーク付きbot名に反応しない

これまでは、bot 名の前にアットマーク(@)があってもなくても、また後ろにコロン(:)またはカンマ(,)があってもなくてもよかった、つまり

hubot ping
hubot: ping
@hubot ping
@hubot: ping

はいずれもhubotへの命令として有効だったのですが、v3になってなぜか@付きが無効になっています。不具合だと思われますので、早晩解決するでしょう。

対策

プルリクエストされているパッチに、さらにコロンとカンマのところを修正して適用してみました(下記)。

URLをパラメーターにすると括弧がついてしまう

たとえば hubot-rss-reader

hubot add http://example.com/index.rdf

みたいにして使うのですが、自動的に hubot add 〈http://example.com/index.rdf〉 のように山括弧( 〈 と 〉 )が付加されたものが bot に渡されるため、スクリプトがこれを URL と認識しない、ということになってしまいます。

v2 にはこれに対する処理があったのですが、v3ではすっかりなくなっています。不具合だと思うのですが、開発者は割と悠長な構えに見えます。

対策

v2 にあった処理を持ってきて適用してみました。なにか不都合があるのかなあ。

--- slack.coffee.orig 2014-12-13 17:53:24.290393342 +0900
+++ slack.coffee 2014-12-13 19:42:08.788464223 +0900
@@ -8,6 +8,7 @@
constructor: (robot) ->
@robot = robot
+ @botUserID = null
run: ->
# Take our options from the environment, and set otherwise suitable defaults
@@ -44,6 +45,13 @@
loggedIn: (self, team) =>
@robot.logger.info "Logged in as #{self.name} of #{team.name}, but not yet connected"
+ # Go through the list of known users and find the name that matches ours
+ for id, user of @client.users
+ if user.is_bot and user.name == self.name
+ @botUserID = id
+ break
+ @robot.logger.info "Bot's Slack user ID is #{@botUserID}"
+
# Provide our name to Hubot
@robot.name = self.name
@@ -103,6 +111,11 @@
# If this is a DM, pretend it was addressed to us
if msg.getChannelType() == 'DM'
txt = "#{@robot.name} #{txt}"
+ # Or, if we were @-mentioned
+ else if matches = txt.match(/<@([^>]+)>[:,]?\s?(.*)/)
+ [userID, someText] = matches[1..2]
+ if @botUserID and (userID == @botUserID)
+ txt = "#{@robot.name} #{someText}"
@receive new TextMessage user, txt, msg.ts
view raw atmark.patch hosted with ❤ by GitHub
--- slack.coffee.orig 2014-12-13 17:53:24.290393342 +0900
+++ slack.coffee 2014-12-13 17:53:51.421673532 +0900
@@ -61,6 +61,24 @@
@client.removeListener 'close', @.close
@client.removeListener 'message', @.message
+ ###################################################################
+ # HTML helpers.
+ ###################################################################
+ unescapeHtml: (string) ->
+ try
+ string
+ # Unescape entities
+ .replace(/&amp;/g, '&')
+ .replace(/&lt;/g, '<')
+ .replace(/&gt;/g, '>')
+
+ # Convert markup into plain url string.
+ .replace(/<((\bhttps?)[^|]+)(\|(.*))+>/g, '$1')
+ .replace(/<((\bhttps?)(.*))?>/g, '$1')
+ catch e
+ @robot.logger.error "Failed to unescape HTML: #{e}"
+ return ''
+
message: (msg) =>
return if msg.hidden
return if not msg.text and not msg.attachments
@@ -96,7 +114,7 @@
else
# Build message text to respond to, including all attachments
- txt = msg.getBody()
+ txt = @unescapeHtml msg.getBody()
@robot.logger.debug "Received message: '#{txt}' in channel: #{channel.name}, from: #{user.name}"

attachment が使えなくなった

この機能を使っていないので詳しいことは述べません。さっそく別のスクリプト hubot-slack-attachment ができているようです。

Hubot で docomo 「雑談対話」API を使う — “敬称”を Hubot コマンドに

これは Hubot Advent Calendar 2014 の 5 日目の記事です。が、その流れにうまく乗れているのかいないのか。1週間ほど前に自分の書いた「Hubot ping の日本語化」に引き続き、Hubot を日本語で楽しく使おうという話です。Advent Calendar の前日(4日)の記事の最後のほうでも、メッセージを日本語化するとより親しみがわく(あるいは腹が立つ)というお話でしたね。

さてさて、存在確認(ping)以外にもう少し会話的なものを楽しみたいのですが、大量の質問と答を仕込むのは労力の割に楽しくなさそうだし、人口無能みたいなものを組み込むのはこちらの技量からして難しいし……と考えあぐねていたところに、docomo Developer support 提供のAPI「雑談対話」というものを見つけました。しかもちょうど開発用APIキー有効期間の制限が廃止され、特に本申請をせずともよくなりました。

hubot-docomo-dialogue をつくった」を参考にしました。そこでは、すべての発言に反応するよう

...
  robot.hear /.*/, (res) ->
...

となっていました。しかし、これではまじめな話をしているところに割り込んできてうっとうしいので robot.respond にしたいところです。さりとて、コマンドを日本語化したとしても

user> わぷー 雑談 今日は寒かったね。

というのも無粋というか興ざめです(「わぷー」というのは bot の名前です。hubot の起動時に bin/hubot --name wapuu --alias わぷー として起動して、日本語名を与えています)。

“敬称”をコマンドに

ちょっと思案して、敬称の「さん」をコマンドにしてみました。

...
  robot.respond /さん(?:、)?\s*(.*)/, (res) ->
...

使用する時は、bot名とコマンドのあいだの空白は実はなくても大丈夫なので、

user> わぷーさん、今日は寒かったね。

と、ずいぶん自然な文体にすることができます。「わぷー」がbot名、「さん、」がコマンド、「今日は寒かったね。」が処理に渡されるパラメータと解釈されます。

Hubot のほかのちゃんとしたコマンドのときは呼び捨て、雑談したいときは「さん」付け、と使い分けることになります。

Wikipedia「敬称」にあるものを適当に詰め込み、読点をつけてもつけなくてもいい(ただし呼び捨てに読点を付けると雑談になる)ようにしました。

雑談対話 API

雑談対話と知識Q&A雑談対話 API には“会話の継続”というものがあります。いったん言葉(リクエスト)を投げかけ、返答に含まれる「コンテキストID」を次のリクエストにつければ継続的な対話になるというものです。この機能も組み込むことにしました。これでしりとりもできるようになりました(「わぷー、りんご」みたいにいちいち呼びかけなければなりませんが)。そして、2分あいだをあけると継続としないことにしました。

できた script は

です。

試してみましたが、しりとりは確かに上手にできます。しかし継続対話は期待ほどではなく、的外れなものになりがちでした。API の向こう側の対話能力は今後バージョンアップされるのでしょうか。

同様の docomo API 「知識Q&A」を利用するための docomo-knowledge.coffee も作ってみました。

こちらも試してみましたが、うーん、あんまり実用的な答は返ってきませんね。

user> わぷー教えて、ロスはいま何時?
wapuu> ロサンゼルスの今の時刻は15時35分です。

がちょっと便利なくらいです。

Hubot ping の日本語化 — 「いるのか?」の正規表現

Hubot の ping コマンド

hubot_wapuuSlack というコミュニケーションツールを利用することになって、そこでこれは便利かもということで Hubot をいじってみています。

現時点での安定版 Debian Wheezy に Hubot をインストールするにはどうすればいいんだとか、Slack に連携させるときに Heroku を使う方法ならすぐに見つかるが、自前ホストのHubotとSlackを連携させるにはどうするんだとか、いくつか引っかかりながらも、ここにリンクした情報が既にあったのでうまくいきました。

この bot がちゃんと作動しているかどうか反応を見るのに、ping というコマンドがはじめからサンプルとして付属しています。bot の名前に ping と付けて問いかけると、PONG と答えるというものです。

user> hubot ping
hubot> PONG

さて、「HubotのScriptを翻訳するとかわいい」にありますように、日本語化すると愛着がわきます。ping の Script はとても簡単で、肝は

  robot.respond /PING$/i, (msg) ->
    msg.send "PONG"

です。使用目的はとにかく存在確認ですから、日本語にすると

  robot.respond /いるのか\?$/i, (msg) ->
    msg.send "はい、ここにいます!"

です。

また、Hubot を起動するときに bot 自身の名前と別名を付けることができます。そこでこれを日本語で

bin/hubot --name wapuu --alias わぷー

と付けてやると、ますます親しみがわきます。これで応答は

user> わぷー いるのか?
wapuu> はい、ここにいます!

となりました。

「いるのか?」のバリエーションの正規表現

このままですと、script の質問文のところは正規表現で書けるのに、ただ「いるのか?」に正確にマッチしないと返答しないということになっています。

「いるのか?」の分解存在確認の日本文「いるのか?」を考察してみます。これを4つ(疑問符を入れると5つ)に分けて、それぞれのバリエーションとその組み合わせを思いつくまま挙げてみました(図)。ありえない組み合わせ—たとえば、2段目が「ります」のとき、その前に「い」はありえない—の制限を付け加えながら、正規表現にして書いてみると、次のようになりました。

/(((い|ゐ|居)(て?))(?!り)|(お|を|居)|((い|居)(て?)は)(?!ま))((る|ん(?=の))|((り?)ます)(?!ん))((の?ん?)(です)?|(んだ)(?!か))?(か(い?な?|よ|ね)?|(よ?)(ね|な))?\s?(\?|?)/

これで、質問文は「いる?」から「いてはりますのんかいな?」(いや、これが現実にありうる表現かどうか確証は持てないのですが)まで対応できます。

ping の目的は存在確認なので、日本語では「生きてる?」とも言いそうです。1段目の前に「生きて」を付けて、元の1段目を必須から任意に変更すればよさそうです。さらに、「調子どう?」「元気?」というフレーズもよく使いそうですね。これはもうそのままということにします。だんだん手抜きになってきました。

結局、日本語版 ping はこうなりました。

  robot.respond /(((い|ゐ|居)(て?))(?!り)|(お|を|居)|((い|居)(て?)は)(?!ま))((る|ん(?=の))|((り?)ます)(?!ん))((の?ん?)(です)?|(んだ)(?!か))?(か(い?な?|よ|ね)?|(よ?)(ね|な))?\s?(\?|?)/i, (msg) ->
    msg.send "はい、ここにいます!"

  robot.respond /(い|生|活)きて(((い|ゐ|居)(て?))(?!り)|(お|を|居)|)?((る|ん(?=の))|((り?)ます)(?!ん))((の?ん?)(です)?|(んだ)(?!か))?(か(い?な?|よ|ね)?|(よ?)(ね|な))?/i, (msg) ->
    msg.send "はい、ここにいます。"

  robot.respond /(調子どう|元気)/i, (msg) ->
    msg.send "はい、元気です。"

wapuu_pingちゃんと数えていませんが、これで「いるのか?」を意味する数十種類の表現に答えることができるようになりました。

このWapuu bot は、WordPress の日本語コミュニティがこれから活用していこうとしている Slack にいます。WordPress 本体の開発元でこの Slack を使っていくことになったことに触発され、そこに収まりきれない話題—日本語圏特有の話題、日本語で話したほうが楽な話題など—を扱っていこうという場所です(たぶん)。単に「WordPressの使い方」を尋ねるような場所ではありませんが、何かちょっとでも手助けしたい(日本語圏で最も求められているのはたぶん翻訳です。本体のみならず多くのドキュメントやニュースなどが翻訳されるのを待っています。もちろんほかにも何かがあります。たとえばこの記事のように、どうでもいいような冗談を仕込むとかもきっと)と思っている方があればぜひ参加してみてください。参加資格などなく、参加方法の手続きを踏めさえすれば誰でも参加できます。Wapuu bot はそこで待っています。