hubot-lineという Hubot のアダプタを作りました。これを使うと、HubotというボットフレームワークからLINEへスタンプやイメージ、メッセージなどの送受信が簡単にできます。何らかの機能を持ったボットを提供したいときにメインロジックだけを開発するだけで済むので、楽ができますね。
しかし、マニュアルが長くなってしまったので、読むのも面倒だけど使ってみたい僕のような人向けにサンプルボットも作りました。
さらに、こんなサンプルひとつ渡されても、コード読み解くのめんどいという僕のような人向けに、このサンプルボットを元に使い方を解説します。
なお、LINE BOT APIとHubot、Herokuの知識をある程度お持ちの方向けの解説になります。有用な記事がいくつもありますし、そちらを参考にしてください。そこまでは面倒すぎて面倒みきりません。ということで早速。
ボットのロジックは ./scripts/line.coffee
に全て書いてあります。
インポート
まず、先頭でスクリプトから使用するオブジェクトのインポートが必要です。
{LineRawMessageListener, LineImageListener, LineVideoListener, LineAudioListener, LineLocationListener,
LineStickerListener, LineContactListener, LineRawOperationListener, LineFriendListener, LineBlockListener,
LineTextAction, LineImageAction, LineVideoAction, LineAudioAction, LineLocationAction, LineStickerAction
} = require 'hubot-line'
メッセージを受け取る
スタンプ
サンプルボットはスタンプを受け取ったとき、同じスタンプを返すという動作をします。次のコードがそれをしています。
robot.listeners.push new LineStickerListener robot, (() -> true), (res) ->
res.emote new LineStickerAction res.message.STKID, res.message.STKPKGID
LineStickerListener
を全スタンプを受け取るリスナーとして登録します。スタンプを受け取ったら、そのパラメーターSTKID
とSTKPKGID
からLineStickerAction
を作ってemote
すると、同じスタンプを返信します。
もし、受信するスタンプを限定したかったら、matcherの(() -> true)
をカスタマイズしてください。((msg) -> msg.STKID is 1)
みたいな感じです。
イメージ
次に、サンプルボットはイメージを受け取ったとき、同じイメージを送り返すという動作をします。次のコードがそれをしています。
robot.listeners.push new LineImageListener robot, (() -> true), (res) ->
res.message.content (content) ->
robot.brain.set res.message.id, new Buffer(content, 'binary').toString('base64')
originalContentUrl = "#{contentEndpoint}?id=#{res.message.id}"
res.message.previewContent (previewContent) ->
robot.brain.set "p#{res.message.id}", new Buffer(previewContent, 'binary').toString('base64')
previewImageUrl = "#{contentEndpoint}?id=p#{res.message.id}"
res.emote new LineImageAction originalContentUrl, previewImageUrl
スタンプと違うのは、イメージは受信したときにデータが含まれておらず、別にダウンロードしてくる必要があるということです。res.message.content (content) ->
でイメージデータを、res.message.previewContent (previewContent) ->
でプレビューデータをダウンロードできます。
イメージを送信するときも同様で、APIでデータ自体は送信せず、originalContentUrl
やpreviewImageUrl
のようにURLを渡します。LINEプラットフォームはそのURLからデータをダウンロードするという方式になっています。
Herokuにはファイルを置けないため、サンプルではデータをBase64エンコードしてRedisに突っ込んでいます。そして以下のコードで、LINEプラットフォームにデータを提供しています。
robot.router.get "/download", (req, res) =>
content = robot.brain.get req.query.id
robot.brain.remove req.query.id
res.set('Content-Type', 'binary')
res.send new Buffer(content, 'base64')
Redisの容量制限もあるので、データをダウンロードさせたら、すぐにデータを消しています。それでも、キャパオーバーなど心配ですし、このような処理のままリリースしたりせず、適切なストレージを用意しましょう。これはサンプルだから許されるいい加減さです。
また、プレビューをオウム返ししていますが、送信時の方がイメージサイズの上限が厳しいようで、ダウンロードに失敗しています。240x240にリサイズしましょう。これはサンプルだからry
ビデオ
次に、サンプルボットはビデオを受け取ったとき、サンプルボットのリポジトリに含まれるビデオを送り返すという動作をします。次のコードがそれをしています。
robot.listeners.push new LineVideoListener robot, (() -> true), (res) ->
res.message.content (content) ->
originalContentUrl = "https://github.com/umakoz/hubot-line-example/raw/master/content/video.mp4"
res.message.previewContent (previewContent) ->
previewImageUrl = "https://github.com/umakoz/hubot-line-example/raw/master/content/video.jpg"
res.emote new LineVideoAction originalContentUrl, previewImageUrl
やってることはほとんどイメージと同じですね。しかし何故かRedisに突っ込むパターンでは動作しなかったので、諦めました。
音声
サンプルボットは音声を受け取ったとき、サンプルボットのリポジトリに含まれる音声の先頭1秒を送り返すという動作をします。次のコードがそれをしています。
robot.listeners.push new LineAudioListener robot, (() -> true), (res) ->
res.message.content (content) ->
robot.brain.set res.message.id, new Buffer(content, 'binary').toString('base64')
originalContentUrl = "#{contentEndpoint}?id=#{res.message.id}"
res.emote new LineAudioAction originalContentUrl, 1000
先頭1秒になってるのは、音声データから秒数を取得するのが面倒だったためです。
位置情報
サンプルボットは位置情報を受け取ったとき、住所と緯度、経度を次の通り返信しています。
robot.listeners.push new LineLocationListener robot, (() -> true), (res) ->
res.emote new LineLocationAction res.message.address, res.message.latitude, res.message.longitude
連絡先
サンプルボットは連絡先を受け取ったとき、以下の通り、MIDと名前を返信しています。テキストを返すときはemote
ではなくsend
を使います。
robot.listeners.push new LineContactListener robot, (() -> true), (res) ->
res.send "got a contact. mid: #{res.message.mid} displayName: #{res.message.displayName}"
オペレーションを受け取る
友達登録
サンプルボットは新しい友達が増えた(もしくはブロック解除された)という通知を受け取ったとき、次の通りMIDを返信しています。
robot.listeners.push new LineFriendListener robot, (() -> true), (res) ->
res.send "be a friend. mid: #{res.message.mid}"
ブロック
サンプルボットはブロックされた(もしくは友達が減った)という通知を受け取ったとき、何もしません。ブロックされてるので、どうせ何も送れませんからね。
robot.listeners.push new LineBlockListener robot, (() -> true), (res) ->
テキストについては、次の通りHubotの通常のやり方で大丈夫です。ただ、サンプルボットは名無しで作ってますので、名前は入れなくて結構です。badger
と送ると、Badgers?...
と返してくれますね。
robot.respond /badger/i, (res) ->
res.send "Badgers? BADGERS? WE DON'T NEED NO STINKIN BADGERS"
そのほかメッセージをまとめて受け取るためのLineRawMessageListener
と、オペレーションをまとめて受け取るためのLineRawOperationListener
が用意されていますので、使ってみてください。
一応、サンプルボットはHeroku へデプロイするという想定で書いていますが、サーバーと node.js、固定グローバルIP、Redisがあれば動作するはずです。
解説は以上です。
あとがき
最後に、僕は基本iOSプログラマーなので、CoffeeScriptは全然詳しくありません。Hubotと他のアダプタのコードから学んだ知識しかないので、プルリクは大歓迎です。
あと、英語を勉強しているので頑張ってドキュメント書いたけど、途中で諦めて残念な感じになっているので、そっちの指摘も大歓迎です。
ということで、型安全な世界に戻ります。Swift書こう。