BotkitのConversationでプルリクをマージする


こんばんは，うどん大 情報系 院2年の[@hico_horiuchi](https://twitter.com/hico_horiuchi)です．
この記事は[SLP KBIT Advent Calendar 2015](http://www.adventar.org/calendars/797)の22日目です．
([ChatOps Advent Calendar 2015](http://qiita.com/advent-calendar/2015/chatops)の15日目が空いてたので，兼用で登録しました．)

先日の「[Botkit + ImgurでプルリクにLGTMする](http://qiita.com/hiconyan/items/f850a901852f7d6b56df)」に引き続き，Botkitを使ってみます．
SLPでもGitHubとSlackを使っているので，Botを活用できるシーンを考えてみました．
(SlackはTeamが乱立してるので，統合してChannnelでまとめたい感…．)

ちなみに，昨年は「[Hubotで始める簡単Bot開発](http://qiita.com/hiconyan/items/9805657866720dac7acc)」だったので，2年連続でBotネタですね．

## Botkit?

昨日のSlack Platformの発表の中で，新しく紹介されたBot Frameworkです．

  - [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との違いなどは「[Botkit + ImgurでプルリクにLGTMする](http://qiita.com/hiconyan/items/f850a901852f7d6b56df)」を参照して下さい．

## Conversation(対話機能)

Botkitでは，標準で **対話機能** が提供されるようです．
(質問文と，それに続く返答での条件分岐が，対話と定義されてるようです．)

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

`bot.startConversation` を使うと，Yes/Noなどの対話を簡単に実装できるようです．
Yes/Noの判定基準となる単語は，このように定義されていました．

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

今回は対話機能を使って，Botkitにプルリクエストをマージさせます．
利用シーンとしては，こんな感じでしょうか．
(GitHub上でマージしちゃった方が早い気がしなくもない．)

  1. Slackに「プルリクが作成されたよ」とGitHubの通知が飛んでくる．
  2. Slack上でプルリクについて，あーだこーだ議論してみる．
  3. Botkitに「マージして」と言うと，「本当に良い?」と聞いてくる．
  4. 「Yes」と答えるとマージ，「No」と答えると何もしない．

ちなみに，誤爆を防ぐために「Botしかマージする権限を持っていない」という会社もあるようです．
マージをトリガにして，CIを使ってデプロイする所まで自動化していると楽しそうですね．
この辺りの話は，[Rebuild.fm](http://rebuild.fm)で有名な[@naoya_ito](https://twitter.com/naoya_ito)さんが書かれてます．

  - [GitHub 時代のデプロイ戦略 - naoyaのはてなダイアリー](http://d.hatena.ne.jp/naoya/20140502/1399027655)

## Conversationでプルリクをマージ

ここからが本題です．

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

  - [Slackの新しいフレームワークBotkitを試してみる - Qiita](http://qiita.com/icb54615/items/af08862dfaefbf2bbcbe)
  - [githubのAPIトークンを取得する - Qiita](http://qiita.com/rentalname@github/items/9a185d445e45b8b4857c)

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

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

さて，少し長いですが，Botkitのコードはこのようになります．
GitHub APIの呼出には[node-github](https://github.com/mikedeboer/node-github)を利用しました．

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

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

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

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

var pullRequestsMerge = function(bot, message, args) {
  console.log(args);

  github = new githubAPI({
    version: '3.0.0'
  });

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

  github.pullRequests.merge({
    user: args.user,
    repo: args.repo,
    number: Number(args.id)
  }, function(error, _) {
    if (error) {
      bot.botkit.log('Failed to request of GitHub API:', error);
    }
  });
};

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

  bot.startConversation(message, function(err,convo) {
    convo.ask('Are you sure you want to merge?', [{
      pattern: bot.utterances.yes,
      callback: function(response, convo) {
        pullRequestsMerge(bot, response, {
          user: matches[1],
          repo: matches[2],                                                                                             
          id: matches[3]
        });

        convo.next();
      }
    }, {
      pattern: bot.utterances.no,
      callback: function(response, convo) {
        bot.api.reactions.add({
          timestamp: response.ts,
          channel: response.channel,                                                                                    
          name: '-1',
        }, function(error, _) {
          if (error) {
            bot.botkit.log('Failed to add emoji reaction:', error);
          }
	    });

	    convo.next();
      }
    }]);
  });
});
```

## 実際に使ってみる

では，実際に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: merge <user>/<repo> <id>` です．
(イカに深い意味はありません．)

![botkit_ikaring.png](https://qiita-image-store.s3.amazonaws.com/0/58742/5ee52dab-654b-6d07-b930-0328cc7570a2.png "botkit_ikaring.png")

「no」と返すと :-1: されますが，これもBotkitでやってます(後述)．
「yes」と返すと，ちゃんとマージされてることが分かりますね．
(GitHubと連携してることが多いと思うので，敢えてBotkitで結果を返してません．)

## (おまけ) BotkitからAdd Reactionする

BotkitはSlackに特化してるので，Add ReactionなどのAPIも簡単に利用できます．
以下，[howdyai/botkit/bot.js](https://github.com/howdyai/botkit/blob/master/bot.js)より抜粋．

```javascript:bot.js
bot.api.reactions.add({
  timestamp: message.ts,
  channel: message.channel,
  name: 'robot_face',
},function(err,res) {
  if (err) {
    bot.botkit.log("Failed to add emoji reaction :(",err);
  }
});
```

このメソッドは，SlackのAPIをそのまま叩いてる感じですね．
タイムスタンプとChannelで，Add Reactionするメッセージを特定するみたいです．

  - [reactions.add method | Slack](https://api.slack.com/methods/reactions.add)

Conversationだと `callback: function(response, convo) {}` の `response` に返答が入ってるようです．
(「yes」「no」などのメッセージオブジェクト．)
これを指定して `bot.api.reactions.add()` してやると，返答にEmojiを付けられます．

## まとめ

Botkitだと，標準で対話機能が提供されてるので，簡単に実装できました．
SLPでもこんな感じで，SlackとGitHubを連携させて，ChatOpsが流行ればなーと思っています．
(ChatOpsすると自動化が必要になるので，色々便利になったり，運用ミス減らせるんじゃないかな?)

Hubotで同様の機能を，[@bouzuya](https://twitter.com/bouzuya)さんが[hubot-pr](https://github.com/bouzuya/hubot-pr)として作られてます．
こちらはプルリクの一覧表示ができるなど，高機能で良いですね．