Botkit + ImgurでプルリクにLGTMする


こんばんは，うどん大 情報系 院2年の[@hico_horiuchi](https://twitter.com/hico_horiuchi)です．
この記事は[ChatOps Advent Calendar 2015](http://qiita.com/advent-calendar/2015/chatops)の17日目です．

本当は「Hubot + ImgurでプルリクにLGTMする」だったのですが，[@jacopen](https://twitter.com/jacopen)大先輩に
**「セカホリ、botkit試してー」**
と言われたので，急遽Botkitで頑張ることにしました．

## Botkit?

昨日のSlack Platformの発表の中で，新しいBot Frameworkの **「Botkit」** が紹介されました．

  - [howdyai/botkit](https://github.com/howdyai/botkit)
  - [The Slack Platform Launch | Several People Are Typing](http://slackhq.com/post/134878632730/launch-platform)
  - [Slackがプラットフォーム化を宣言。「Slack App Directory」と対応Bot開発用の「Botkit」発表。アプリ開発支援に約100億円の資金準備も － Publickey](http://www.publickey1.jp/blog/15/slack_app_directory.html)

[Hubot](https://hubot.github.com/)と同じく，Node.js上で動作するようです．
READMEを読んで感じた特徴は，以下のような感じです．

### メッセージの受信方法の多様性

  - [Receiving Messages](https://github.com/howdyai/botkit#receiving-messages)

Hubotは `robot.hear` と `robot.respond` の2種類だけでした．
BotkitではSlackに特化して，5種類準備されているようです．

  - `message_received` : 全てのメッセージ( `robot.hear` 相当)
  - `ambient` : メンション以外のメッセージ
  - `direct_mention` : `@bot hello` のように名前で始まるメンション
  - `mention` : `hello @bot` のように名前を含むメッセージ
  - `direct_message` : 1対1のDirect Messages

更に `controller.hears('hello', ['direct_message', 'mention',])` のように複数指定できる．
(ただ，Botとのインタフェースを増やし過ぎるのは良くなさそう．)

### 対話機能

  - [Multi-message Replies to Incoming Messages](https://github.com/howdyai/botkit#multi-message-replies-to-incoming-messages)

`bot.startConversation` を使うと，Yes/Noなどの対話を簡単に実装できるみたい．
Yes/Noの判定は標準で用意されてるけど，自分でも `'done'` などを指定できる．

  - `bot.utterances.yes` : yes, yeah, yup, ok, sure
  - `bot.utterances.no` : no, nah, nope

「プルリクマージする?」「デプロイする?」みたいな場面で使えそうですね．

## Imgurから画像を取得してLGTM

ここからが本題です．
突然ですが，みなさんLGTMしてますか?

> LGTMとは、Looks good to meの略です。
> GitHubのPull Requestなど、コードやデザインのレビューをした際などに「いいね!」の意味合いでつかわれます。

今やってる開発系のバイトではプルリク駆動開発をしていて，LGTMし合っています．
ただ，LGTM用の画像を探してきて，コメントして…って面倒ですよね．
そこで，Botを使ってSlackからLGTMできるようにしてみました．

今回は，お気に入りのLGTM画像をImgurのアルバムにしてみました．
ここからランダムに画像を取得して，LGTMコメントに使います．
(一緒にバイトしてる同級生の影響で，俺妹のGIFが多いですね．)

  - [LGTM - Album on Imgur](http://imgur.com/a/ReqhU)

最初に，SlackとImgurでAPIトークンを取得しておきます．
こちらは，Qiitaに投稿されている記事を参考にします．

  - [Slackの新しいフレームワークBotkitを試してみる - Qiita](http://qiita.com/icb54615/items/af08862dfaefbf2bbcbe)
  - [最強の画像アップロードサイト「Imgur.com」のAPIを利用する（匿名画像アップロード編) - Qiita](http://qiita.com/AKB428/items/a5f68a3288cc596975ae)

次に，適当な作業ディレクトリを作って，必要なライブラリをインストールします．

```
$ mkdir ~/botkit
$ cd ~/botkit
$ npm install --save botkit request node-github 
```

さて，少し長いですが，Botkitのコードはこのようになります．
Imgur APIの呼出には[request](https://github.com/request/request)を，GitHub APIの呼出には[node-github](https://github.com/mikedeboer/node-github)を利用しました．
関数の階層が深くなるのが嫌だったので，以前覚えたコールバック関数を積んでいく形にしました．

  - [HubotでSlackのChannelとTrelloのBoardを連携 - Qiita](http://qiita.com/hiconyan/items/94941517b4df774bda4b)

```javascript:~/botkit/bot.json
var SLACK_TOKEN = 'xoxb-0123456789abcdefghijklmnopqrstuvwxyz';
var IMGUR_ALBUM_ID = 'ABCDE';
var IMGUR_CLIENT_ID = '0123456789abcde';
var GITHUB_ACCESS_TOKEN = '0123456789abcdefghijklmnopqrstuvwxyz0123';

var botkit = require('botkit');
var request = require('request');
var githubAPI = require('node-github');

var controller = botkit.slackbot({
  debug: false
});

controller.spawn({
  token: SLACK_TOKEN
}).startRTM();

var getAccountImages = function(bot, message, args) {
  request.get({
    url: 'https://api.imgur.com/3/album/' + IMGUR_ALBUM_ID + '/images',
    headers: {
      'Authorization': 'Client-ID ' + IMGUR_CLIENT_ID
    }
  }, function(error, response, body) {
    if (error || response.statusCode !== 200) {
      return bot.reply(message, 'Faild to request of Imgur API.');
    }

    var images = JSON.parse(body).data;

    if (images.length === 0) {
      return bot.reply(message, 'No images are uploaded to Imgur album.');
    }

    args.image = images[Math.random() * images.length | 0];
    args.callbacks.shift()(bot, message, args);
  });
};

var issuesCreateComment = function(bot, message, args) {
  github = new githubAPI({
    version: '3.0.0'
  });

  github.authenticate({
    type: 'oauth',
    token: GITHUB_ACCESS_TOKEN
  });

  github.issues.createComment({
    user: args.user,
    repo: args.repo,
    number: Number(args.id),
    body: '![' + args.image.id + '](' + args.image.link + ')'
  }, function(error, _) {
    if (error) {
      bot.reply(message, 'Failed to request of GitHub API.');
    }
  });
};

controller.hears('^lgtm +(.+)\/(.+) +([0-9]+)$', 'direct_mention', function(bot, message) {
  var matches = message.text.match(/^lgtm +(.+)\/(.+) +([0-9]+)$/i);

  getAccountImages(bot, message, {
    callbacks: [issuesCreateComment],
    user: matches[1],
    repo: matches[2],
    id: matches[3]
  });
});
```

ちなみに，元になったHubotでの実装もあります．

  - [huboco/lgtm.coffee at master · hico-horiuchi/huboco](https://github.com/hico-horiuchi/huboco/blob/master/scripts/lgtm.coffee)

Botkitでは正規表現に `¥s` とか `¥S` が使えないっぽい?
(Hubotでは `/lgtm\s+(\S+)\/(\S+)\s+([0-9]+)$/i` にしてました．)
あと，正規表現のマッチ部分を `message.match[1]` とかで取れないのも不便です．

## 実際に使ってみる

では，実際にBotkitを起動してみましょう．

```
$ node bot.js 
** No persistent storage method specified! Data may be lost when process shuts down.
** Setting up custom handlers for processing Slack messages
** API CALL: https://slack.com/api/rtm.start
** BOT ID:  bot  ...attempting to connect to RTM!
```

コマンドのフォーマットは `@bot: lgtm <user>/<repo> <id>` です．
(イカに深い意味はありません．)

![botkit_ika_1.gif](https://qiita-image-store.s3.amazonaws.com/0/58742/6588310c-10c3-f607-3358-f03cebc7c3cb.gif "botkit_ika_1.gif")

APIトークンをタイポしてたりすると，ちゃんとエラーを返してくれます．
(これは `bot.log` にも出した方が良さそう．)

![botkit_ika_2.png](https://qiita-image-store.s3.amazonaws.com/0/58742/01df55bf-e881-7166-c36b-987948e10b31.png "botkit_ika_2.png")

## まとめ

今回は，Botkitの紹介と，ImgurとGitHubを組み合わせたサンプルを書いてみました．
Hubotと比較して，Slackに特化して，多機能になっている感じです．
移植も，インタフェース部分を変えるだけなので，比較的に出来そう…?

ただ，やっぱりHubotの方が知見や資産も多くて良いなと思います．
(あと，CoffeeScriptで書けないし…ファイル分割もどうやるんだろう．)

Slack + Botkitでどんなことが出来るようになるのか，今後が楽しみですね!

## Hubot×ChatOps勉強会

僕は生粋のうどん県民ですが，実は大阪や神戸で「Hubot×ChatOps勉強会」をやってます．

  - [Hubot×ChatOps勉強会 - connpass](http://hubot-chatops.connpass.com)

来年の4月から就職して上京するので，その前にもう1度開催したいと思っています．
そこで，大阪で「会場提供しても良いよ」という方居ましたら，連絡頂けると嬉しいです!
また，Botkitも含め，HubotやChatOpsについて発表して頂ける方も募集中です!