夫の帰宅時間をお知らせしてくれるLINE bot

この記事は、[PORT Advent Calendar 2018](https://qiita.com/advent-calendar/2018/port)の16日目です。
Vue.jsかfirebaseかAMPについて書こうと思いつつ、、夫の帰宅時間連絡の手間を思いついたので、解消するものを考えました。

# 解決したい課題
旦那氏の帰宅時間がわかるようにしたい。私も旦那氏もお腹ペコペコ状態なので、ご飯をすぐ食べられる状態にしたいから。
旦那氏は日によって出向したりするので、毎日同じオフィスから帰宅するわけではありません。
なので、毎日帰宅時間をたずねていました。聞くのも手間だし、調べる方も手間。
# 解決方法
旦那氏「名古屋から乗ったよ」みたいな文章をLINEで送信したら、botが勝手に乗り換え検索してくれて、最寄駅の到着時間を返信してくれたらいいんじゃない？
よくないかもしれないけど、試しに作ってみようのモチベーション。初めてのLINE bot作成です。

# 参考
[LINEのBot開発 超入門（前編） ゼロから応答ができるまで](https://qiita.com/nkjm/items/38808bbc97d6927837cd)
こちらの記事を参考にさせていただきました。
環境Node.jsとHerokuだったので、それに従います。初めてのHeroku。
こちらがHeroku公式。デプロイまではこれも参考に。[Getting Started on Heroku with Node.js](https://devcenter.heroku.com/articles/getting-started-with-nodejs)

# とりあえず参考の通りにした
めっちゃ飛ばしましたけど、、
[LINEのBot開発 超入門（前編） ゼロから応答ができるまで](https://qiita.com/nkjm/items/38808bbc97d6927837cd)さんが超優秀なので、ほとんどその通りにしました。ちょこちょこ詰まりはしたけれど、そこは公式を参考にしつつ、「こんにちは」って送信したら、「これはこれは」って返ってくるようになりました。
<img width="320" alt="IMG_0569.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/48488/41281332-03ee-8680-4699-4573fa156cbb.png">
アイコンはホシガメちゃんにしました。(向き反対の方が喋ってる感でたかもしれない)
「メッセージありがとうございます」みたいな返信はデフォルトメッセージで、LINE Developersの設定から削除することができました。

```index.js
require('dotenv').config()
const express = require('express')
const path = require('path')
const PORT = process.env.PORT || 5000
const line = require('@line/bot-sdk')
const config = {
  channelAccessToken: process.env.LINE_ACCESS_TOKEN,
  channelSecret: process.env.LINE_CHANNEL_SECRET
}
const app = express()
const bot = new line.Client(config)

app.post('/webhook', line.middleware(config), (req, res, next) => {
  res.sendStatus(200)
  let events_processed = []
  req.body.events.forEach((event) => {
    if (event.type == "message" && event.message.type == "text"){
      if (event.message.text == "こんにちは"){
        events_processed.push(bot.replyMessage(event.replyToken, {
          type: "text",
          text: "これはこれは"
        }))
      }
   }
})

app.listen(PORT, () => console.log(`Listening on ${ PORT }`))

```
参考までにコードは確かだいたいこんな感じ。

# Dialogflowにキーワードを登録
ここも詳しくは超入門見ていただければでてきますが、 
[Dialogflow](https://dialogflow.com/)ってサービスにキーワードを登録すると、Node.jsからの判別が簡単になります。
表記揺れ登録で、言語処理が簡単になるって感じ。
私は以下の言葉について登録しました。
## Entities登録
### 帰るって意味を汲み取る(kitaku)
<img width="793" alt="スクリーンショット 2018-12-10 23.59.12.png" src="https://qiita-image-store.s3.amazonaws.com/0/48488/f451c16e-2ec9-a5e6-3cfd-776b65cc83f0.png">

### 乗る駅(ekimei)
`名古屋駅`=>`名古屋`、`名駅`、`なごや`

## Intentsに例文を登録
<img width="711" alt="スクリーンショット 2018-12-11 0.03.12.png" src="https://qiita-image-store.s3.amazonaws.com/0/48488/fec0a3dd-2a60-4490-63b5-68d141a21fd1.png">
例文登録すると、勝手にEntitiesと紐づけてくれます。

## イベント登録
登録した単語が来たらフックになるように`Action and parameters`イベント登録します。
<img width="751" alt="スクリーンショット 2018-12-11 0.10.50.png" src="https://qiita-image-store.s3.amazonaws.com/0/48488/44015159-245d-a3cc-20d0-8962c69fd3c0.png">

Node.jsに接続するのにGoogle Cloud Platformを介したりして、ちょっと面倒ですが、使えるようになります。

```index.js
// 省略
app.post('/webhook', line.middleware(config), (req, res, next) => {
  res.sendStatus(200)
  let events_processed = []
  req.body.events.forEach((event) => {
    if (event.type == "message" && event.message.type == "text"){
      if (event.message.text == "こんにちは"){
        events_processed.push(bot.replyMessage(event.replyToken, {
          type: "text",
          text: "これはこれは"
        }))
      }

      events_processed.push(
        session_client.detectIntent({
          session: session_client.sessionPath(process.env.GOOGLE_PROJECT_ID, event.source.userId),
          queryInput: {
            text: {
              text: event.message.text,
              languageCode: "ja",
            }
          }
        }).then((responses) => {
          if (responses[0].queryResult && responses[0].queryResult.action == "handle-kitaku"){
            let message_text
            let from = responses[0].queryResult.parameters.fields.ekimei.stringValue
            let to = process.env.EKIMEI_TO
            if (from){
              message_text = `ご帰宅ですね！${from}からですね！`
              return bot.replyMessage(event.replyToken, {
                type: "text",
                text: message_text
              });
            } else {
              message_text = `ご帰宅ですね！どこから？`;
              return bot.replyMessage(event.replyToken, {
                type: "text",
                text: message_text
              })
            }
          }
        })
      )
    }
  })
})

```


<img width="360px" src="https://qiita-image-store.s3.amazonaws.com/0/48488/d3a49a89-a7a8-5c66-b919-57b750f25d57.png">

名古屋駅から帰宅するのを察してくれるようになりました。
ここまでは、参考そのままな感じ。

# 乗り換え検索ができるようにならなければならない
旦那氏の帰宅時間をお知らせしてもらいたいので、経路検索のAPIを叩かなければいけません。
今回は[駅すぱあとさんのフリープラン](https://ekiworld.net/service/sier/webservice/free_provision.html)を使用しました。
申し込みが必要でしたが、レスポンスが早く、サクッとアクセスキーが使えるようになりました。
検索結果のURLを返してくれるようになります。
<img width="1062" alt="スクリーンショット 2018-12-11 20.43.50.png" src="https://qiita-image-store.s3.amazonaws.com/0/48488/8b375b79-9d35-71ec-e522-d79354fe59a7.png">
こちらを導入するにあたり、存在する駅名にDialogflowのキーワードを変更しておきました。

Entities　`名古屋駅`=>`名古屋`、`名駅`、`なごや`
にしていましたが、`名古屋駅`だと検索にひっからなかったので、`名古屋`に変更。

# なんだかんだAPI叩けた
```index.js
// events_processed.pushにapiを追加する
      events_processed.push(
        session_client.detectIntent({
          session: session_client.sessionPath(process.env.GOOGLE_PROJECT_ID, event.source.userId),
          queryInput: {
            text: {
              text: event.message.text,
              languageCode: "ja",
            }
          }
        }).then((responses) => {
          if (responses[0].queryResult && responses[0].queryResult.action == "handle-kitaku"){
            let message_text
            let date = moment().format('YYYYMMDD')
            let time = moment().format('HHmm')
            let from = responses[0].queryResult.parameters.fields.ekimei.stringValue
            let to = process.env.EKIMEI_TO
            if (from){
              const url = `https://api.ekispert.jp/v1/json/search/course/light?from=${encodeURIComponent(from)}&to=${encodeURIComponent(to)}&date=${date}&time=${time}&searchType=departure&plane=false&shinkansen=false&limitedExpress=true&redirect=false&contentsMode=sp&key=${process.env.EKISPERT_KEY}`
              axios.get(url).then((res) => {
                message_text = `ご帰宅ですね！${from}からですね！\n${res.data.ResultSet.ResourceURI}`
                return bot.replyMessage(event.replyToken, {
                  type: "text",
                  text: message_text
                })
              }).catch((err) => {
                message_text = `エラー！${err}`
                return bot.replyMessage(event.replyToken, {
                  type: "text",
                  text: message_text
                });
              })
            } else {
              // 略
            }
          }
        })
      )
    }
  })
```
<img width="320" alt="IMG_0570.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/48488/5173b0a2-d618-b137-17e5-262ed4727dcd.png">
やったー！と思ったのもつかの間。

<img width="320px" src="https://qiita-image-store.s3.amazonaws.com/0/48488/b2aac699-7f1a-4049-9c59-6b3b2e2444e7.png">

時間が日本じゃないことは明白。
herokuの時間を日本に変更する必要がありました。
`heroku config:add TZ=Asia/Tokyo --app [app_name]`
[参考にした](https://qiita.com/ikemura23/items/52ab8a5d260c7ee4d42b)

<img width="320px" src="https://qiita-image-store.s3.amazonaws.com/0/48488/7fa4fce1-d214-4eb4-0fe2-e3bb43c27319.png">

これで時間が合いました。

# 感想
帰宅連絡だけで埋め尽くされていたLINEが、botを含めたグループを作成したことで、帰宅用にスレッドが分かれたのが地味に良かったです。
これだと直近の電車しか探せないので、何分前に電車に乗ったのか、何分後に駅につくのかみたいな感じで、検索できるようにしたらいいかなと。
参考を見ると、botの対応ももっとチューニングできるらしいので、そこもやってもいいかもしれません。

<img width="320px" src="https://qiita-image-store.s3.amazonaws.com/0/48488/c473c54d-6138-fe67-a5ae-cb81ea8f6c20.png">
旦那氏はそこそこ喜んでくれました。

---

最後になりますが、[PORT株式会社では自社サービスを支えてくれる優秀なRubyエンジニアを募集しています](https://www.theport.jp/)(Rubyエンジニア以外も)。
もくもく会も行なっていますので、ぜひ一緒にもくもくしましょう！
[PORTもくもく会](https://freestyle-mokumoku.connpass.com/)
(私は残念ながらリモート勢なのでいませんが・・)

[PORT Advent Calendar](https://qiita.com/advent-calendar/2018/port)はまだまだ続きます。