Hubot × Raspberry Pi2で電車の遅延情報をSlackに流す

# 背景
我が家にRaspberry Pi 2がサーバとして置いてありました。しかし、特に何も仕事をしていなかったのと、仕事でSlackを使うようになったこともあり、Botとして仕事をさせようと思いつきました。そのときの記事は下記。

- [キーボードもマウスも使わずにsshでRaspberry Pi Model Bを使えるようにした for Mac OSX] (http://seiya-orz.hatenablog.com/entry/2016/03/16/114303)
- [Raspberry Piを自宅Webサーバー化するための設定](http://seiya-orz.hatenablog.com/entry/2016/03/17/111715)
- [Raspberry Pi × hubot × Slackでbot運用](http://seiya-orz.hatenablog.com/entry/2016/06/25/214014)

前回までは、ただ家賃振り込んだかを聞いてくれる機能しかなく、活用しきれていない感がありました。そこで、電車が遅延している時にSlackで教えてくれると便利じゃないかと思い、実際に作ってみました。

完成イメージはこんな感じ。
<img width="668" alt="スクリーンショット 2016-08-12 10.50.03.png" src="https://qiita-image-store.s3.amazonaws.com/0/74853/56576ba2-9431-25cb-7588-df7e3facb198.png">
Botが電車の遅延を検知したら、あるチャンネルに投げてくれるイメージ。また、遅延について知りたいときは、Botに聞くと答えてくれると尚良ですね。

# 実装アイデア
電車遅延の情報をどこから入手してくるかの候補は2つありました。ちなみに僕は通勤に京王線しか使っていませんので、ひとまず京王線で話を進めます。

1. 公式サイトをスクレイピング
1. 公式TwitterのTweet

1だともし路線を増やす際の手間が大きそうです。2は、Tweetの内容を処理するだけで良いので汎用性も高く、楽そうですね。
[京王電鉄運行情報のアカウント](https://twitter.com/keiodentetsu)
他の路線も結構やっているみたいです、使ってませんけど。

ということで、実装は以下の流れでやればできそう。

1. 5分毎にTweetを取得する。
2. 新たなTweetがあった場合、内容を判断する。同時にTweet情報を保持しておく。
3. 遅延していた場合、Slackに投げる。

TwitterAPIで他人のタイムラインの取得方法は以下URL。
https://dev.twitter.com/rest/reference/get/statuses/user_timeline

ちなみに、15分間で300回まではリクエストできそう、たぶん。

# 実装
Raspiで実現していますが、実装的にはLinuxサーバと何ら変わらず自由です。HubotはNode.jsを利用しているので、Node.jsでコーディングします。CoffeeScriptでも書けるんですが、僕はCoffeeScript嫌いなので使いません。

## 実装コード

```js
//   Description:    京王線の遅れ取得
//
//   Commands:   電車
//
//   Author:     Seiya Mogami


var twitter = require('twitter');
var fs = require('fs');
var cronJob = require('cron').CronJob;

// 認証
// 自分のを入れてね
var api = new twitter({
    consumer_key: '',
    consumer_secret: '',
    access_token_key: '',
    access_token_secret: ''
});

// スクリーン名
var name = 'keiodentetsu';

// 電車が遅延している場合は遅延情報を投稿し、遅延していない場合は何もしない
function notifyIfTrainDelay(sname, robot) {
    fs.readFile('./' + sname + '.json', 'utf-8', function(err,jsonString){
        if(err){
            return robot.messageRoom('error', err);
        }else{
            if(!jsonString || jsonString == ""){
                var data = {
                    id_str: '1',
                    text: 'まだツイートが取得されていません。5分少々お待ち下さい。'
                };
                fs.writeFile(sname + '.json', JSON.stringify(data));
            }else{
                var json = JSON.parse(jsonString);
                api.get('statuses/user_timeline', { screen_name: sname, since_id: json.id_str }, function (error, tweets, response) {
                    if (error) {
                        console.log(error);
                        return robot.messageRoom('error', err);
                    } else {
                        if (tweets.length > 0) {
                            // 最新Tweetをjsonファイルとして保持しておく
                            fs.writeFile(sname + '.json', JSON.stringify(tweets[0]));
                            for (var i = 0; i < tweets.length; i++) {
                                var text = tweets[i].text;
                                if (text.match(/京王線/) && (text.match(/遅れ/) || text.match(/見合せ/))) {
                                    // 遅延情報がある場合
                                    return robot.messageRoom('notification', "<!everyone>: 電車遅れてるみたいやで〜\n```\n" + text + "\n```");
                                }
                            }
                            return;
                        } else {
                            console.log('no tweet found');
                            return;
                        }
                    }
                });
            }
        }
    });
}

function getTrainInfo(sname) {
    var json = JSON.parse(fs.readFileSync('./' + sname + '.json', 'utf-8'));
    if(json){
        return json.text;
    }else{
        return 'まだツイートが取得されていません。5分少々お待ち下さい。';
    }
}

// slackの動作部分
module.exports = function (robot) {
    // 秒 分 時 日 月 年
    new cronJob('00 00,05,10,15,20,25,30,35,40,45,50,55 * * * *', function () {
        notifyIfTrainDelay(name, robot);
    }).start();

    robot.respond(/電車/, function (msg) {
        var message = getTrainInfo(name);
        return msg.send("今の京王線はこんな感じやな〜\n```\n" + message + "\n```");
    });
};
```

## 解説
読んでいただければわかるんですが、簡単に解説します。

### 必要なPackageのインストール
```
npm install --save twitter cron
```

### Tweetの処理
スクリーン名とTweetIDを元にTweetを取得します。since_idは指定したID以降のTweetしか取得しないようになるオプションです。よって、どこかしらに最新Tweetを保存しておく必要があるのですが、今回は、簡単のために取得したJSON形式のTweetをそのままJSONファイルにして保存し、必要に応じて読み込むことにしました。

JSON形式のTweetは簡単に言うと、以下の様な形です。今回使っているものだけ。

```json
{
  "id_str": "Tweetに割り当てられたID（数字）を文字列にしたもの",
  "text": "Tweetの本文"
}
```

idとid_strがあるのですが、違いはIntかStringかの違いです。ただ、idのほうの桁数が半端ないので、処理系統によっては丸め込みが発生してしまう可能性があります。ですので、id_strを活用することをおすすめします。

あとは、正規表現使ってtextを処理すればよいのですが、勉強不足ですごい雑な感じで書いてしまっていますね。勉強します。この部分は路線によって書かれ方が違いそうな気がします。各自適当に合わせてください。

Tweetを取得するapi.getは非同期で動作しているので、処理の内部でSlackに投稿するようにしています。

### cronで定期実行
cronJobのインスタンスを生成して定期的にTweetを取得しています。今回は5分おきにしていますが、深夜帯は取得しないようにするのが本当は理想的ですね。ただ、書くのが面倒だったので今回はしていませんが。

# まとめ
いろいろハマったりしましたが、まあなんとか作れました。3時間くらいですかね。今回でTwitter活用のノウハウがたまったので、Tweetに応じていろいろBotにさせることができそうです。

HerokuとかAWSとかを使ってBotをたてることもできるようなので、しかも簡単にできるので皆さんも是非。