緊急地震速報をGoogle Homeでアナウンスしてみる（with Slack通知）

# はじめに

社内のコミュニケーションスペースにGoogle Homeが！その日は突然やってきました。が、ブームはすぐに過ぎ去り、話しかける人はごくわずかに...。後輩から「何かやらないと、使われなくなっちゃいますね」と、何故か圧力をかけられたので、緊急地震速報を流してみることにしました。

# 処理フロー

緊急地震速報は、[緊急地震速報Bot](https://twitter.com/eewbot/)から取得することにしました。[フォーマット](http://d.hatena.ne.jp/Magi/20110403)も公開いただいているので、通知処理を自由に構築できます（ありがとうございます）。

また、どうせならSlackへも通知させよう、ということで以下のような処理フローにしました。

![ProcessFlow.png](https://qiita-image-store.s3.amazonaws.com/0/60193/3cd79a2a-8382-a45f-c371-1f7f151b4cf5.png)

HubotでTwitterのタイムラインを監視し、推定M5.8以上、もしくは震度4以上の場合に通知させることにします。

前提として、Hubotが準備（インストール）できているものとします。Slackとは連携していなくてOKです（え？[^1]）。

[^1]: Slack通知のためではなく、Twitterのタイムライン監視目的でHubotを利用しました。将来Twitter Botとしても利用できそうですし...。

# どうやったか

## 準備

まず、Hubotに監視させるTwitterアカウントを準備し、[緊急地震速報Bot](https://twitter.com/eewbot/)をフォローしておきます。また、Twitter APIを利用するため、[Twitter Apps](https://apps.twitter.com/)からTwitterアプリを作成（Create New App）し、API KeyやAccess Tokenなどを入手しておきます。

## Hubotスクリプト

### Twitterの監視

Twitterの監視には、[hubot-twitter-userstream](https://github.com/hoo89/hubot-twitter-userstream)を利用しました。

```
$ npm install --save hubot-twitter-userstream
```

Hubotスクリプト（内部の詳細は次項以降に書きます）を作成し、

```coffee:hubot/scripts/eew-notifier.coffee
module.exports = (robot) ->
  robot.hear /(.*)/i, (msg) ->
    # (1) 緊急地震速報を取得
    ...
    # (2) 閾値判定（推定M5.8以上、もしくは震度4以上）
    ...
    # (3) Slackへ通知
    ...
    # (4) Google Homeで読み上げ
    ...
```

TwitterアプリのAPI KeyやAccess Tokenなどを環境変数に

```sh
$ export HUBOT_TWITTER_KEY="key"
$ export HUBOT_TWITTER_SECRET="secret"
$ export HUBOT_TWITTER_TOKEN="token"
$ export HUBOT_TWITTER_TOKEN_SECRET="secret"
```

のように設定した状態で

```
$ ./bin/hubot -a twitter-userstream
```

と実行すると、TwitterのタイムラインがHubotへ流れてきます。あとはこれを、煮るなり焼くなり...、です。

以下、Hubotスクリプトの詳細になります。

### 緊急地震速報を取得

```coffee:hubot/scripts/eew-notifier.coffee
    # (1) 緊急地震速報を取得
    if msg.message.user.name != 'eewbot'
      return

    vals = msg.message.text.split(',')
    if vals.length != 15
      return

    type      = vals[0]  # 電文の種別
    sign      = vals[1]  # 訓練識別符
    announce  = vals[2]  # 発表時刻
    status    = vals[3]  # 発表状況
    number    = vals[4]  # 電文番号 地震IDごとに00-99
    id        = vals[5]  # 地震ID
    time      = vals[6]  # 地震発生時刻
    latitude  = vals[7]  # 震源の北緯
    longitude = vals[8]  # 震源の東経
    place     = vals[9]  # 震央地名
    depth     = vals[10] # 震源の深さ
    magnitude = vals[11] # マグニチュード
    intensity = vals[12] # 最大震度
    flag      = vals[13] # 震源の海陸判定 0：陸 1：海
    alert     = vals[14] # 警報の有無 0：無 1：有

    if sign == '01' # 00：通常 01：訓練
      return

    if status == '0' # 0：通常
      caption = "■■■EEW(第#{number}報)■■■"
    else if status == '8' or status == '9' # 8,9：最終報
      caption = '■■■EEW(最終報)■■■'
    else # 7：キャンセルを誤って発表したとき（地震ID以降の項目は空）
      return

    overview = "#{place}で地震 最大震度 #{intensity}(推定)"

    canceled = false 

    if type == '35' # 35：最大震度のみ Mの推定なし
      details = "[詳細] #{time}発生 深さ#{depth}km"
    else if type == '36' or type == '37' # 36、37：最大震度 Mの推定あり
      details = "[詳細] #{time}発生 M#{magnitude} 深さ#{depth}km"
    else if type == '39' # 39：キャンセル
      caption = '□□□EEW(キャンセル報)□□□'
      canceled = true
    else
      return

    post = caption
    post += '\n' + overview + '\n' + details if !canceled
```

※「発表状況（status）」が7のデータを見つけられなかったので、とりあえず何もしないようにしました。

### 閾値判定（推定M5.8以上、もしくは震度4以上）

```coffee:hubot/scripts/eew-notifier.coffee
    # (2) 閾値判定（推定M5.8以上、もしくは震度4以上）
    filtered = true ;

    result = intensity.match(/[0-9]+[\.]?[0-9]*/)
    if result? and parseFloat( result[0] ) >= 4.0 # 震度4以上
      filtered = false ;
    if type == '36' or type == '37'
      if parseFloat( magnitude ) >= 5.8 # 推定M5.8以上
        filtered = false ;
    if status == '0'
      if number != '1' and number != '5' # 第1、5、最終報
        filtered = true ;

    if !canceled and filtered
      return
```

※通知の総数を減らすため、「第1、5、最終、キャンセル報のみ」という条件も追加しました。

### Slackへ通知

```coffee:hubot/scripts/eew-notifier.coffee
    # (3) Slackへ通知
    data =
      channel: '#xxxxx'
      username: 'eewbot'
      text: post

    msg.http('https://hooks.slack.com/services/Txxxxxxxx/xxxxxxxxx/xxxxxxxxxxxxxxxxxxxxxxxx')
      .post(JSON.stringify(data)) (err, res, body) ->
        if err or res.statusCode != 200
          console.log 'Unknown error.'
```

※Slackへの通知には、Incoming WebHooksを利用しました。Webhook URLが必要になりますので、取得については[SlackのWebhook URL取得手順](https://qiita.com/vmmhypervisor/items/18c99624a84df8b31008)などをご参照ください。

※実際のSlackへの通知内容はこんな感じになりました。

![Slack.png](https://qiita-image-store.s3.amazonaws.com/0/60193/6e8ed929-d046-0771-8096-0e940b528a6b.png)

### Google Homeで読み上げ

```coffee:hubot/scripts/eew-notifier.coffee
    # (4) Google Homeで読み上げ
    @exec = require('child_process').exec
    command = 'sh /home/xxxxx/announce/index.sh '
    if !canceled
      command += '緊急地震速報、' + overview.replace(' ', '、').replace(/\(推定\)/, '') + '、注意してください'
    else
      command += '先ほどの緊急地震速報はキャンセルされました'
    @exec command, (error, stdout, stderr) ->
      if error
        console.log error
      else if stdout
        console.log stdout
```

※シェルスクリプト経由で実行される別プロジェクトで、Google Home（[google-home-notifier](https://www.npmjs.com/package/google-home-notifier)）での読み上げを行っています。これは、[以前の記事](https://qiita.com/hideo-works/items/db25a748889d47d5eff2)で[紹介した処理](https://qiita.com/hideo-works/items/db25a748889d47d5eff2#google-home-notifier)と同じですので、実装はそちらをご参照ください。

※実際の読み上げ内容は「緊急地震速報、東海道南方沖で地震、最大震度 3、注意してください」といった感じになります。

# おわりに

「デスクはSlack、コミュニケーションスペースはGoogle Homeでカバー。カンペキ！」と作ってみたのですが、通知（読み上げ）の応答性に不安があり、実用に耐えない可能性が高そうです（え？）。「ケータイで十分なので、こんなのいらないっス」とか言われてしまう前に、Google Homeの活用方法、もう一度考えないと...。