[GitHub API & Webhook + hubot]社内で実際にいい反響を得ているチャットボットの機能

サイバー・バズの@kashira2339です。

[チャットボット Advent Calendar 2016](http://qiita.com/advent-calendar/2016/chatbot)の6日目の記事となります。


弊社では[Slack](https://slack.com/)を使っており、そこでは何体かの[hubot](https://hubot.github.com/)が動いています。

---

# 作って役立ってるもの

話は結論から。

## 1. hubotで現在のマイルストーン問い合わせする機能

<img width="533" alt="Slack_-_Cyber_Buzz.png" src="https://qiita-image-store.s3.amazonaws.com/0/36105/06efc03d-5e7a-34d4-e8d0-411154c99e52.png">

モザイクがかかっているが、汚いものではない。

```
hubot {repository} (ms|milestones) now
```

弊社のSlackで動作しているhubotは、このように話しかけると
**現在のマイルストーン**及び**そのマイルストーンに紐づくIssue**を出力してくれる。

### なんでBotで？

1. でしゃばらないテキストインターフェースでもいい
2. みんなにコピペして共有する手間がはぶける

弊社にはリリース前にチャットなどに**リリースノート**を貼るという文化があった。
*「これ今日リリースするものなので確認お願いしまーす」*

とかを、GitHubのmilestonesから拾い上げてきて共有することが面倒臭かった。
欲しい形でまとまってないし。


チャットで表示されれば自然とみんな見てくれてるし、
適度に出力が整っていればいいということでbotで採用。
通知したいチャンネルで``hubot xxxx ms now``って発言すれば済むので楽チンなのだ。

### 簡単な実装説明

```bash
# hubot + GitHubなら、githubotがいい
npm install --save githubot
```

```coffeescript
github = require './github'

module.exports = (robot) ->
  # 反応させるパターンを決めて、
  robot.respond /(.+) (milestones|ms) now/i, (msg) ->
    repo = msg.match[1]
    github.milestones { repo: repo }, (response) ->
        # マイルストーンいろいろしたり、
        github.issues { repo: repo, milestone: マイルストーン番号 }, (response) ->
            # Issueをいろいろして、
            msg.send "なんかメッセージ"
```

マイルストーンと紐づくIssueの取得に必須なのはこれくらい。
あとは、お好みで並び替えたり絞り込んだりして、msg.sendに渡してしまえばチャットルームでボットがしゃべる。

## 2. GitHubのメンション・アサインを読み取り、SlackにDMを送る

<img width="529" alt="スクリーンショット_0028-12-05_午後8_02_01.png" src="https://qiita-image-store.s3.amazonaws.com/0/36105/f7264cb0-5eab-d327-e394-c572d24ca5ff.png">

このBotからのメッセージは、GitHubのIssueやPullRequestのコメント上で
メンションが検出されたユーザー向けのDMとなっているため、全員が使うチャットの画面を汚すことがない。
また、DMなので運用担当者であっても非常に気づいてくれやすく、
Issueをベースにしたコミュニケーションが円滑になった。
（通知がきてたあのIssueの件だけど...とか）

今ではこのBotからDMが飛ばないと苦情がくるほどである。

### なんでBotで？

1. Botが「話しかけてくる」という魅力

正直この機能は、GitHubのWebHookさえ待ち構えられれば（ようは、Webサーバーがあれば）それで十分なのである。

だが、Botはチャットへのもう一人の参加者なのだ。
単なる監視システムからのNotifycationなどとは愛情の深さが違うのである。


| botme | AWS CloudWatch|
|:-----------|:------|
|<img width="278" alt="botme.png" src="https://qiita-image-store.s3.amazonaws.com/0/36105/eb127dd6-e147-f151-303e-db59b4b09a5d.png"> |<img width="336" alt="Slack-cwatch.png" src="https://qiita-image-store.s3.amazonaws.com/0/36105/bc13265e-cb79-9a36-76b9-e539a8c5734a.png"> |

モザイクが悪い演出をしているが、これがDMでくると思うと非常に愛らしくはないだろうか？
「すぐに確認しなきゃ！」という気持ちになる。

### 簡単な実装説明

```coffeescript


# GitHub Webhookのヘッダに含まれるシグネチャの検証
isValidSign = (sign, body) ->
    secret = process.env.HUBOT_GITHUB_WEBHOOK_SECRET
    hmac = crypto.createHmac 'sha1', secret
    hmac.update JSON.stringify(body), 'utf-8'
    hash = hmac.digest 'hex'
    generated_signature = "sha1=#{hash}"


module.exports = (robot) ->
  # hubotは標準でExpressに依存しているのでルーティング簡単
  robot.router.post '/github/webhook', (req, res) ->
    # イベントの型取って、シグネチャも検証する
    type = req.get 'X-Github-Event'
    sign = req.get 'X-Hub-Signature'

    unless isValidSign sign, req.body
      res.status(401).send 'unauthorized'
      return
        
    # req.bodyがwebhook本体なのでいろいろして
    res.status(200).send 'ok' # 200返しとく
        if !!robot.adapter.client
          userId = robot.adapter.client.getUserByName('kashira2339')?.id
          return unless userId?
          robot.adapter.client.openDM userId, (data) ->
            # robot.emit関数でattachment（見た目が綺麗なメッセージ）が送れる！
            robot.emit 'slack.attachment',
              content:
                pretext: 'タイトルの上のプレーンテキスト'
                text: '本文'
                fallback: '通知時の文言'
                title: 'タイトル' 
                title_link: 'タイトルクリック時の飛び先'
                mrkdwn_in: ['markdownを適用するプロパティ(textとか)']
                color: '色(16進数)'
                channel: param['kashira2339'] # ユーザ名
        else
          robot.send {room: '#general'}, "めっせーじ"
```

基本的にはこのページ(GitHub Webhooks)とにらめっこしながら作る。
https://developer.github.com/webhooks/

イベントに対応したペイロードに含まれる文字列などからusernameを抽出したら、
あとはそのusernameをSlackの名前と照らし合わせれば、該当ユーザにDMを送れる。

セキュリティのため、シグネチャの検証はしておいた方がいい。

# 背景

サイバー・バズはもともと他のプロジェクト管理ツール
（某〜らぼさんの出している某acklogというツール）を使っていたのだが、
ある機会にこれをGitHubのIssue駆動に乗り換えた。
自分の所属チームでは[ZenHub](https://www.zenhub.com/)も併用している。
もはやこれなしで仕事するにはストレスが溜まるほどだ。


ただし、乗り換えには不満がつきもので
今までできたことが1つできなくなるだけで、とても不便に感じるものである。
開発チームはGitHub運用に概ね賛成だったものの、運用チームは移行のメリットをそこまで享受できなかったり、英語を嫌ったりと難色を示していた。

中でも、移行期間中に上がった大きな不満は以下の2つのことであった。

- 「リリースノートが見づらい」
- 「通知が来てるかわかりにくい」

Slackには、GitHubのIntegrationが標準で用意されており、それ単体でもかなり使える。
GitHub自体にもNotificationsページが用意されている。
しかし、このIntegrationやGitHubの機能だけでは上記の課題を満足に解決することはできず、
より強力なソリューションを求めることとなった。

それで実装に至ったのが、上記２つの機能なのである。


---
※ ここからはうわごと


# チャットボットの年

2016年はチャットボットが話題となった年だった。

というより、チャットというプラットフォームでできることへの注目が集まり、*高度な技術を持つ人*や*好奇心旺盛な人*、*お金が大好きな人*が次々とチャットの世界へ足を踏み入れた。。

[FacebookのMessenger Platform BETA](https://messengerplatform.fb.com/)
[LINE Messaging API](https://business.line.me/ja/services/bot)

こういったAPIも大手SNSから公開され、
チャットボットへの関心は以前の比にならないくらい高まった。

Google Trendsで「チャットボット」で調べてみても、その推移は一目瞭然だ。
![スクリーンショット 0028-12-05 7.26.25.png](https://qiita-image-store.s3.amazonaws.com/0/36105/9059594d-8acb-caa1-77f7-f353d038c5c6.png)



## 疑問

昨今ではあらゆることがチャットでできるようになったおかげで、
チャットプラットフォーム側でもあらゆる機能が実装されている。

- お問い合わせフォーム
- EC
- 決済
- 予約

また、もっと身近なところでは、チャットボットで以下をつくってみた〜なんて記事も見かける。

- 今日の天気
- 雑談
- リマインダー

ただ、これだけ多くのことができるようになったからこそ
**本当にチャットボットに必要な機能ってなんなんだ…？**
そのように感じるようになった。

## チャットボットに求められていること

- **ボットが一人の参加者としてエビデンスを残すこと**
- **誰にでも馴染みのある抽象化されたインタフェースを提供すること**

チャットという場は、プロジェクトの関係者やチームメンバーが参加している。
つまり、チャット上での発言はメンバー全員の目に触れる。
テキストボックスとチャットルーム（チャンネル）は参加者に共通のインターフェースを提供する。

チャットはログが残る。
ログは、会話の流れとともに記録される。
前後のやり取りや背景が把握しやすくなる。

GitHub社などでは、チャットプラットフォーム上でビルド・テスト・デプロイのコマンドを書き込むことでシステムの運用を図っているそうだ。これをChatOpsと読んだりする。

これらはチャット、そしてチャットボットというインタフェースが
意図せずとも**記録**と**制約**を作り上げているからこそ効果を発揮する。

運用の記録を取ることで再利用可能な価値を残し、
個人の差異を吸収することで安定した運用を叶える。

これらの一つの解決策がチャット+チャットボットなのではないだろうか。


---
# おわりに

チャットボットは、その取り組みやすさやカジュアルさも魅力。

プログラミング初心者の方もぜひ肩肘はらず、チャレンジしてみてください。

[チャットボット Advent Calendar 2016](http://qiita.com/advent-calendar/2016/chatbot)の6日目はここまで。

７日目は@yukihirai0505さんより、「GASとSlackではじめるチャットボット〜初心者プログラマ向け〜」です。

ではまた。