Node.js(Express)で作ったTwitterボットとダイレクトメッセージで雑談する


「都会にはこんなに人がいるのにぼくは独りぼっちだ…」というときのために、Twitterボットにダイレクトメッセージ機能をつけ、雑談できるようにしてみました。

Twitterボットの雛形は、@hkusuさんの[Node.js(Express) と Heroku で Twitter ボットを作る - Qiita](http://qiita.com/hkusu/items/75404aefdb5f89be6b6e)という記事を参考に作りました。

### 環境
 * OS : Windows 10
 * Node.js : 0.12.7
 * Express : 4.13.3

### Twitterアプリケーションのセットアップ
まずは[Twitter Application Management](https://apps.twitter.com/)でボットのセットアップを行い、各種トークンを取得します。

ボットにダイレクトメッセージを行わせたい場合、Permissionsに`Read, Write and Access direct messages`を設定しておく必要があります。

![access.png](https://qiita-image-store.s3.amazonaws.com/0/64812/79d888b8-7f1d-0eb2-ab40-dd903fca5ee3.png)

もしセットアップ済みのボットでPermissions設定を変更した場合、Access TokenとAccess Token Secretを再生成する必要があるので注意してください。（古いTokenのままだとDMが送受信できません。軽くハマりました。。）

### TwitterのStreaming API
[参考元の記事](http://qiita.com/hkusu/items/75404aefdb5f89be6b6e)と同じように、Twitter用ライブラリとして[twit](https://github.com/ttezel/twit)を使用します。`options`はExpressアプリに設定しておいた環境変数が取れるようになっているので、ここから各種Tokenを取得してtwitに渡します。

twitからTwitterのStreaming APIからのイベントを受信するために`stream`を取得します。

```js:index.js
var app = require('../app')
var Twit = require('twit')

var options = app.get('options')
var twitter = new Twit( {
  consumer_key: options.key,
  consumer_secret: options.secret,
  access_token: options.token,
  access_token_secret: options.token_secret
})

// イベント受信用のstream取得
var stream = twitter.stream('user')
```
ここで指定している`user`はStreamingの種類（endpoint）で、各ユーザ向けイベントを取得するのに使います。他に`public`と`site`があります。

### フォロー返し
ダイレクトメッセージのやりとりは、相互フォロー状態が前提条件です。

※2016/1/18追記
いつの間にか、相互フォローがなくてもダイレクトメッセージのやり取りができるようになっているようですが、記事はそのままにしておきます。

自分のTwitterアカウントとボットのアカウントを手動で相互フォローにしてもよいのですが、せっかくなのでボットにフォロー返し機能を付けることにしました。

フォローは`follow`イベントで検知します。
フォローの実行は[`friendships/create`のPOST](https://dev.twitter.com/rest/reference/post/friendships/create)ですね。

```js:index.js
// フォローイベントのハンドリング
stream.on('follow', function(data) {
  var param = { user_id: data.source.id_str }

  // 自分発のフォローは処理しない
  if (data.source.id_str === options.id) return

  // フォローされた相手をフォローする
  twitter.post('friendships/create', param, function(err, data, resp){})
})
```

`follow`イベントは「自分がフォローされた」場合も、「自分がフォローした」場合も発火してしまうので、素朴に作ると「自分による自分のフォロー」をしようとし、無限ループな予感がします（やってみると途中で止まるんですけど）。

いい感じにフィルタリングしてから受信できればいいのですが方法が見つからないので、受信後にフォローの実行元IDが自分かどうかでガードするようにしてみました。（コード中の`option.id`は、環境変数で与えたTwitterボット用のIDです）

### ダイレクトメッセージ受信＋エコー返信
とりあえずダイレクトメッセージを受信したら、そのままオウム返しする機能を付けてみます。

ダイレクトメッセージを送受信は`direct_message`イベントで検知し、送信の実行は[`direct_messages/new`のPOST](https://dev.twitter.com/rest/reference/post/direct_messages/new)で行います。

```js:index.js
// ダイレクトメッセージハンドリング
stream.on('direct_message', function(data) {
  var message = data.direct_message

  // 自分が送信したダイレクトメッセージは処理しない
  if (message.sender_id_str === options.id) return

  // エコーで返信
  var reply = { user_id: message.sender_id_str, text: message.text }
  twitter.post('direct_messages/new', reply, function(err, data, resp) {})
})
```
フォローの時と同じように、送信元が自分かどうかで返信処理に進むかどうか判定させています。

これで以下のようにやりとりできます。
右側が人間、左側がボットです。
![echo.png](https://qiita-image-store.s3.amazonaws.com/0/64812/877790fc-e37b-bbb7-6380-c71789376958.png)

### 雑談で返信
対話は、[ドコモの雑談対話API](https://dev.smt.docomo.ne.jp/?p=docs.api.page&api_name=dialogue&p_name=api_reference)というのを使うとお手軽に実現できます。使用時はTwitter APIと同じようにアプリケーション登録し、Tokenを取得する必要があります。

APIを叩くのを楽にするために、Requestをインストールしておきます。
```
npm install request --save
```

次にエコー返信していたコードを書き換えます。
Requestを使って雑談対話APIに会話内容をPOSTし、返ってきた回答をダイレクトメッセージに受け渡しています。

```js:index.js
var request = require('request').defaults({strictSSL: false})

// docomo雑談対話API
var api = "https://api.apigw.smt.docomo.ne.jp/dialogue/v1/dialogue?APIKEY="
var token = options.dialog_token

// context保存用
var status = {}

// ダイレクトメッセージハンドリング
stream.on('direct_message', function(data) {
  var message = data.direct_message

  // 自分が送信したダイレクトメッセージは処理しない
  if (message.sender_id_str === options.id) return

  // 雑談対話API呼び出し
  status.utt = message.text
  status.nickname = message.sender_screen_name
  var param = { body: JSON.stringify(status) }
  request.post(api + token, param, function(err, res, data) {
    body = JSON.parse(data)

    // 雑談対話の文脈保存
    status.context = body.context
    status.mode = body.mode

    // 雑談対話APIで得られた回答内容でダイレクトメッセージに返信
    var reply = { user_id: message.sender_id_str, text: body.utt }
    twitter.post('direct_messages/new', reply, function(err, data, res) {})
  })
})
```
コード中の`status`は、雑談時の文脈(context)を保存するのに使っています。同じ文脈で会話すると、応答が前回までの対話を考慮したものになるようです。それほど精度の高いものではなさそうですが…

本来は話しかけられたID全ての分をデータストアに保存する必要があると思いますが、とりあえずはメモリ上に1つだけに保存するように作ってみました。

これでボットに話しかけると、それっぽい回答が返ってくるようになりました。
![dialog.png](https://qiita-image-store.s3.amazonaws.com/0/64812/a1beebf3-94cf-9854-42fc-941b3679c213.png)

### ローカル実行時の環境変数埋め込み
参考にしたTwitterボットの作成記事では、ローカル環境での起動のためにシェルスクリプト`local_run.sh`を作成していました。

私はWindows環境で開発していますので、以下のようなスクリプト`local_run.cmd`を作成しました。

```bat:local_run.cmd
rem Twitterボットのトークン
set BOT_TWITTER_KEY=XXXX
set BOT_TWITTER_SECRET=XXXX
set BOT_TWITTER_TOKEN=XXXX
set BOT_TWITTER_TOKEN_SECRET=XXXX

rem TwitterボットのアカウントのID
set BOT_TWITTER_ID=XXXX

rem 雑談API用トークン
set BOT_DOCOMO_TOKEN=XXXX

npm start
```

### 後記
雑談対話API、結構面白いです。ついつい長話してしまう。超時間の無駄だけど…

[ボットフレームワークであるHubotに組み込んで、チャットルーム内でしゃべらせる](http://fromatom.hatenablog.com/entry/2014/12/07/010447)なんて使い方をしてる方もいますし、それ以外にも色々使い道ありそうですね。キュレーション自動化とかできそう。

### 参考資料
 * [Documentation | Twitter Developers](https://dev.twitter.com/overview/documentation)
 * [ドコモの雑談対話API](https://dev.smt.docomo.ne.jp/?p=docs.api.page&api_name=dialogue&p_name=api_reference)
 * [twit - npm](https://www.npmjs.com/package/twit)
 * [Node.js(Express) と Heroku で Twitter ボットを作る - Qiita](http://qiita.com/hkusu/items/75404aefdb5f89be6b6e)
 * [DOCOMOの雑談対話APIを使ってHubotと雑談する。 - 文字っぽいの。](http://fromatom.hatenablog.com/entry/2014/12/07/010447)