雑談APIを何個か試してみた（Hubot + Chatwork + UserLocal, CotoGoto, Docomo）

# はじめに
ChatOpsの手始めとして、ChatBotで会話APIを実装してみました。
・UserLocal 人工知能ボットAPI
・CotoGoto::Noby API
・Docomo 雑談会話API
ChatworkでもSlackでも手順は同じで、hubotのオプションでどちらかを選べばよいだけです。

# 事前準備
## Chat側
- Chatwork  
 bot用のユーザを新規作成し、API申請をする。
 APIキーとルームIDを取得
- Slack登録  
 Apps & IntegrationでhubotをインストールしてAPIキーを取得

## 会話API側
- UserLocal  
 無料登録してAPI Key取得
- CotoGoto  
 無料登録してAPI Key取得
- Docomo  
 色々な機能があるので、最低限の会話APIであれば即API Key発行されますが、  
 追加の機能によってはサイトの審査や法人登録が必要です。

# サーバ設定(EC2で構築)
## モジュール
```
// 最新のNode.jsを入れるためにNVMから
$ git clone https://github.com/creationix/nvm.git ~/.nvm
$ source ~/.nvm/nvm.sh
$ vi .bash_profile
  # nvm
    if [[ -s ~/.nvm/nvm.sh ]] ; then
      source ~/.nvm/nvm.sh ;
    fi
$ nvm ls-remote
$ nvm install 7.4.0
$ nvm use v7.4.0
$ node -v

$ sudo yum install -y npm redis
$ sudo npm install -g hubot coffee-script yo generator-hubot

$ cd /var/www/bot
$ mkdir hubot-cw
$ cd hubot-cw
$ yo hubot
  全てEnterでOK
$ npm install hubot-chatwork --save
$ npm install forever request --save
```

## ./scripts以下へ新規作成
- hubot.coffee

```
module.exports = (robot) ->
    status  = {}

    robot.respond /(.*)/i, (res) ->
        message = res.match[1]
        if message.length == 0
            res.reply "What?"

    lunch = ['コンビニ', '中華', 'イタリアン', '和食', 'カレー', '昨日と同じ']
    robot.respond /お昼/i, (res) ->
        res.reply res.random lunch

    robot.respond /site (.*)/i, (msg) ->
      url = msg.match[1]
      options =
        url: url
        timeout: 2000
        headers: {'user-agent': 'node title fetcher'}

      request = require 'request'
      cheerio = require 'cheerio'

      request options, (error, response, body) ->
        $ = cheerio.load body
        title = $('title').text().replace(/\n/g, '')
        msg.reply(title)
```
- api_userlocal.coffee

```
module.exports = (robot) ->
    robot.respond /ul (.*)/i, (res) ->
        url = "https://chatbot-api.userlocal.jp/api/chat"
        api_key = process.env.HUBOT_UL_API_KEY
        message = res.match[1]
        params = {
          "message": message,
          "key": api_key
        }
        robot.http(url).query(params).get() (err, response, body) ->
          return response.send "Encountered an error :( #{err}" if err
          body = JSON.parse(body)
          res.reply "#{body.result}"
```
- api_cotogoto.coffee

```
module.exports = (robot) ->
      robot.respond /cg (.*)/i, (res) ->
          url = "https://www.cotogoto.ai/webapi/noby.json"
          api_key = process.env.HUBOT_CG_API_KEY
          message = res.match[1]
          params = {
            "text": message,
            "app_key": api_key
          }
          robot.http(url).query(params).get() (err, response, body) ->
            return response.send "Encountered an error :( #{err}" if err
            body = JSON.parse(body)
            res.reply "#{body.text}"
```
- api_docomo.coffee

```
getTimeDiffAsMinutes = (old_msec) ->
  now = new Date()
  old = new Date(old_msec)
  diff_msec = now.getTime() - old.getTime()
  diff_minutes = parseInt( diff_msec / (60*1000), 10 )
  return diff_minutes

module.exports = (robot) ->
  robot.respond /d (.*)/i, (msg) ->
    HUBOT_D_API_KEY = process.env.HUBOT_D_API_KEY
    message = msg.match[1]
    return unless HUBOT_D_API_KEY && message

    ## ContextIDを読み込む
    KEY_DOCOMO_CONTEXT = 'docomo-talk-context'
    context = robot.brain.get KEY_DOCOMO_CONTEXT || ''

    ## 前回会話してからの経過時間調べる
    KEY_DOCOMO_CONTEXT_TTL = 'docomo-talk-context-ttl'
    TTL_MINUTES = 20
    old_msec = robot.brain.get KEY_DOCOMO_CONTEXT_TTL
    diff_minutes = getTimeDiffAsMinutes old_msec

    ## 前回会話してから一定時間経っていたらコンテキストを破棄
    if diff_minutes > TTL_MINUTES
      context = ''

    url = 'https://api.apigw.smt.docomo.ne.jp/dialogue/v1/dialogue?APIKEY=' + HUBOT_D_API_KEY
    user_name = msg.message.user.name

    request = require('request');
    request.post
      url: url
      json:
        utt: message
        nickname: user_name if user_name
        context: context if context
      , (err, response, body) ->
        ## ContextIDの保存
        robot.brain.set KEY_DOCOMO_CONTEXT, body.context

        ## 会話発生時間の保存
        now_msec = new Date().getTime()
        robot.brain.set KEY_DOCOMO_CONTEXT_TTL, now_msec

        msg.send body.utt
```

## 起動設定（常駐化、環境変数設定）
- ./bin/hubotを編集

```
#!/bin/sh

set -e

ARG1=$1

export PATH="node_modules/.bin:node_modules/hubot/node_modules/.bin:$PATH"
export HUBOT_SLACK_TOKEN=xoxb-xxx
export HUBOT_CHATWORK_TOKEN="xxx"
export HUBOT_CHATWORK_ROOMS="xxx"
export HUBOT_CHATWORK_API_RATE="900" #時間あたりのポーリング回数(4秒に1回)
export HUBOT_UL_API_KEY=xxx
export HUBOT_CG_API_KEY=xxx
export HUBOT_D_API_KEY=xxx

#exec node_modules/.bin/hubot -a chatwork -n mohi

start() {
        forever start -c coffee node_modules/.bin/hubot -a chatwork -n mohi
}
stop () {
        forever stop -c coffee node_modules/.bin/hubot
}
status() {
        forever list
}
restart() {
        forever restartall
}

case "$ARG1" in
        "stop" )
                stop
                ;;
        "restart" )
                restart
                ;;
        "start" )
                start
                ;;
        "status" )
                status
                ;;
esac
```
- 起動

```
$ bin/hubot start
```

# 最後に
## 使い方
「mohi APIオプション 会話内容」
・Coto Goto APIの場合（mohi cg 会話内容）
　　下記を聞くと教えてくれます。
　　　・ニュース
　　　・おみくじ引く
　　　・○○の天気
　　　・○○の意味を知りたい
・User Localの場合（mohi ul 会話内容）
・Docomo雑談APIの場合（mohi d 会話内容）
## 今後
今後はDocomoのAPIや他社も利用して、画像認識や形態素解析も実装したあと、
独自で情報を取ってくるものを作りたいと思ってます。