LINE BOT作ってNowで動かした時のメモ

# これは何？

以下の記事を参考にLINE BOTを作ってみたので、**手を加えてみたところ・詰まったところ**などを記します。

基本的にはこの記事の通りで、少しだけ変えたりしました。
ありがとうございます。楽しく作れました。
[1時間でLINE BOTを作るハンズオン (資料+レポート) in Node学園祭2017 #nodefest](https://qiita.com/n0bisuke/items/ceaa09ef8898bee8369d)

一番最後にコードをそのまま載せてます。

# 作ったBOT

* 天気を答える
「今日の天気」「明日の天気」「明後日の天気」のワードで、ライブドアの天気予報APIから情報を取得して返してくれる。

* 気温を答える
「今日の気温」「明日の気温」「明後日の気温」のワードで、ライブドアの天気予報APIから情報を取得して返してくれる。

* イメージ
<img width="375" alt="写真 2019-01-04 23 35 34.png" src="https://qiita-image-store.s3.amazonaws.com/0/145628/81825e8a-391e-84f0-a10a-18974d2a8435.png">

# 構成要素

* LINE Developer
* Node.js
* Now(PaaS)
* [API(Weather Hacks > お天気Webサービス)](http://weather.livedoor.com/weather_hacks/webservice)

# やったこと

## だいたいの手順

BOTとトークできるようになるまでは冒頭で引用させていただいた記事のまんまです。
[1時間でLINE BOTを作るハンズオン (資料+レポート) in Node学園祭2017 #nodefest](https://qiita.com/n0bisuke/items/ceaa09ef8898bee8369d)

この通りにやれば、Nowにデプロイできる環境になっていると思います。

## 手を加えてみたところ・詰まったところ

### BOTを1：1のトークだけでなくルーム、グループにも対応させたい

* 困ったこと
例えば友達とのトークルームで「今日の天気」と言った場合、BOTはそのトークルームではなく、「**発言者に対して1:1で**」メッセージを送ってくる。

* 原因
BOTはメッセージの「発言者(``userId``)」を宛先としてリプライのメッセージを送っている。

* 対応
発信元がユーザーの場合はユーザー、トークルームの場合はトークルーム、グループの場合はグループにリプライするようにした。
具体的にはリクエストの中の``source.type.``以下に発信元を表す情報が入っているので、こういう感じで送り返すべき相手を判別することにした。

~~~server.jsより抜粋
  let tempId = ''
  if (event.source.type === 'user') {
    tempId = event.source.userId;
  } else if(event.source.type === 'room') {
    tempId = event.source.roomId;
  } else if(event.source.type === 'group'){
    tempId = event.source.groupId;
  }
~~~

``evene.source.type === user``なら``userId``が、``room``なら``roomId``が、``group``なら``groupId``が設定されてくる。

### 天気予報APIから返ってきたJSONでnullが入ってる時がある

* 困ったこと
「今日の気温」は正しく返ってきても、「明日の気温」が返ってこなかったりする。(日による)

* 原因
APIから返却されるJSONの気温データが``null``だったりすることがある。
[API(Weather Hacks > お天気Webサービス)](http://weather.livedoor.com/weather_hacks/webservice)の下の方にJSONのサンプルを載せてもらえているのですが、``forecasts[0].temperature.max``は設定されているのに対し、``forecasts[0].temperature.min``は``null``になっています。
更なる原因はともかく、気温は**場合によってはデータが無い時がある**ようです。その時は当然、欲しかった``celsius``は取得できません。

* 対応
素直に``nullチェック``した。(もっと楽にできそうですが…)

~~~server.jsより抜粋
  if(item.forecasts[2].temperature.max == null){
    client.pushMessage(tempId, {
      type: 'text',
      text: "最高気温のデータが無いみたいです",
    });
  } else {
    client.pushMessage(tempId, {
      type: 'text',
      text: "最高" + item.forecasts[2].temperature.max.celsius + "度",
    });
  }
~~~

### Nowでnowした後の公開URLを固定する

* 困ったこと
Nowではデプロイ(``now``コマンド)する度に一意の公開URLが生成されるので、LINE Developer側で設定し直すのが面倒。

* 対応
``now.json``を``server.js``と同じ階層に作成し、``alias``を設定し、``now``でデプロイした後に``now alias``すれば、公開URLを一意にできる。

![キャプチャ.PNG](https://qiita-image-store.s3.amazonaws.com/0/145628/6229340b-52f1-5c3b-c150-878595c6b192.png)
上の画像では``match3-bot2-pdaxurecfi.now.sh``がデプロイ先だが、``now alias``で``https://match3-bot2.now.sh``を公開URLとして使えるようになる。
LINE側にはこのURLを設定してやれば、後は修正してデプロイしてもURLは変更しなくてもOKになる。
![キャプチャ.PNG](https://qiita-image-store.s3.amazonaws.com/0/145628/cb1ba6ab-0182-12c5-84f7-0f8c1084a4d8.png)

## 所感

* LINE Developer
簡単に作れるので、もう一つくらいBOT作ってみたい。

* Now
デプロイ早くて使いやすい。フリープランでも十分いろいろできそう。VSCodeからコマンドでサクッと済むので良いです。

## コード

最後に、全くリファクタリングしてませんがコードをそのまま載せておきます。

* now.json
* package.json
* server.js

### now.json

~~~now.json
{
    "version": 1,
    "alias": "match3-bot2"
}
~~~

### package.json

~~~package.json
{
  "name": "match3-bot2",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "node server.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@line/bot-sdk": "^6.4.0",
    "axios": "^0.18.0",
    "express": "^4.16.4"
  }
}

~~~

### server.js

~~~server.js
'use strict';

const express = require('express');
const line = require('@line/bot-sdk');
const axios = require('axios');
const PORT = process.env.PORT || 3000;

const config = {
  channelSecret: 'ここには自身のmassaging APIのものを入れる',
  channelAccessToken: 'ここには自身のmassaging APIのものを入れる'
};

const app = express();

app.post('/webhook', line.middleware(config), (req, res) => {
    console.log(req.body.events);
    Promise
      .all(req.body.events.map(handleEvent))
      .then((result) => res.json(result));
});

const client = new line.Client(config);

function handleEvent(event) {
  if (event.type !== 'message' || event.message.type !== 'text') {
    return Promise.resolve(null);
  }

  let tempId = ''
  if (event.source.type === 'user') {
    tempId = event.source.userId;
  } else if(event.source.type === 'room') {
    tempId = event.source.roomId;
  } else if(event.source.type === 'group'){
    tempId = event.source.groupId;
  }

  let mes = ''
  if(event.message.text === '今日の天気'){
    mes = '待っててね';
    getTodayForecast(tempId);
  }else if(event.message.text === '明日の天気'){
    mes = '待っててね';
    getTommorowForecast(tempId);
  }else if(event.message.text === '明後日の天気'){
    mes = '待っててね';
    getDayAfterTommorowForecast(tempId);
  }else if(event.message.text === '今日の気温'){
    mes = '待っててね';
    getTodayTemperature(tempId);
  }
  else if(event.message.text === '明日の気温'){
    mes = '待っててね';
    getTommorowTemperature(tempId);
  }else if(event.message.text === '明後日の気温'){
    mes = '待っててね';
    getDayAfterTommorowTemperature(tempId);
  }

  return client.replyMessage(event.replyToken, {
    type: 'text',
    text: mes
  });
}

const getTodayForecast = async (tempId) => {
    const res = await axios.get('http://weather.livedoor.com/forecast/webservice/json/v1?city=130010');
    const item = res.data;
    await 
    client.pushMessage(tempId, {
      type: 'text',
      text: item.title,
    });
    client.pushMessage(tempId, {
      type: 'text',
      text: item.forecasts[0].telop,
    });
}

const getTommorowForecast = async (tempId) => {
  const res = await axios.get('http://weather.livedoor.com/forecast/webservice/json/v1?city=130010');
  const item = res.data;
  await 
  client.pushMessage(tempId, {
    type: 'text',
    text: item.title,
  });
  client.pushMessage(tempId, {
    type: 'text',
    text: item.forecasts[1].telop,
  });
}

const getDayAfterTommorowForecast = async (tempId) => {
  const res = await axios.get('http://weather.livedoor.com/forecast/webservice/json/v1?city=130010');
  const item = res.data;
  await 
  client.pushMessage(tempId, {
    type: 'text',
    text: item.title,
  });
  client.pushMessage(tempId, {
    type: 'text',
    text: item.forecasts[2].telop,
  });
}

const getTodayTemperature = async (tempId) => {
  const res = await axios.get('http://weather.livedoor.com/forecast/webservice/json/v1?city=130010');
  const item = res.data;

  if(item.forecasts[0].temperature.max == null){
    client.pushMessage(tempId, {
      type: 'text',
      text: "最高気温のデータが無いみたいです",
    });
  } else {
    client.pushMessage(tempId, {
      type: 'text',
      text: "最高" + item.forecasts[0].temperature.max.celsius + "度",
    });
  }

  if(item.forecasts[0].temperature.min == null){
    client.pushMessage(tempId, {
      type: 'text',
      text: "最低気温のデータが無いみたいです",
    });
  } else {
    client.pushMessage(tempId, {
      type: 'text',
      text: "最低" + item.forecasts[0].temperature.min.celsius + "度",
    });
  }
}

const getTommorowTemperature = async (tempId) => {
  const res = await axios.get('http://weather.livedoor.com/forecast/webservice/json/v1?city=130010');
  const item = res.data;

  if(item.forecasts[1].temperature.max == null){
    client.pushMessage(tempId, {
      type: 'text',
      text: "最高気温のデータが無いみたいです",
    });
  } else {
    client.pushMessage(tempId, {
      type: 'text',
      text: "最高" + item.forecasts[1].temperature.max.celsius + "度",
    });
  }

  if(item.forecasts[1].temperature.min == null){
    client.pushMessage(tempId, {
      type: 'text',
      text: "最低気温のデータが無いみたいです",
    });
  } else {
    client.pushMessage(tempId, {
      type: 'text',
      text: "最低" + item.forecasts[1].temperature.min.celsius + "度",
    });
  }
}

const getDayAfterTommorowTemperature = async (tempId) => {
  const res = await axios.get('http://weather.livedoor.com/forecast/webservice/json/v1?city=130010');
  const item = res.data;

  if(item.forecasts[2].temperature.max == null){
    client.pushMessage(tempId, {
      type: 'text',
      text: "最高気温のデータが無いみたいです",
    });
  } else {
    client.pushMessage(tempId, {
      type: 'text',
      text: "最高" + item.forecasts[2].temperature.max.celsius + "度",
    });
  }

  if(item.forecasts[2].temperature.min == null){
    client.pushMessage(tempId, {
      type: 'text',
      text: "最低気温のデータが無いみたいです",
    });
  } else {
    client.pushMessage(tempId, {
      type: 'text',
      text: "最低" + item.forecasts[2].temperature.min.celsius + "度",
    });
  }
}

app.listen(PORT);
console.log(`Server running at ${PORT}`);
~~~