よく風邪を引くのでちょうどいい布団を教えてくれるLINE BOT作った

## 風邪引きたくない

最近すっかり秋になり、気温の変化が激しいですね。
僕はまだタオルケットで寝ているのですが、朝起きると凍えるような寒さでよく風邪を引いてしまいます。

今の時期はどんな布団で寝るといいのかググってみると、[東京西川さんのホームページ](https://www.nishikawasangyo.co.jp/column/bedding/20180211180000/)に情報が載っていました。
室温が 20℃前後の時は、真綿掛布団をかぶって寝るとちょうど良いそうです。

せっかくなので通年で使えるよう、東京の夜～明け方の気温を取得し、ちょうどいい布団を教えてくれる LINE BOT を作りたいと思います。

## 使用イメージ

![593153202.580649.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90087/160fb215-df7d-1953-4c93-f297b55639dc.gif)


## システム構成図

システム構成は以下の通り。

![architecture.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90087/2faa7a76-7bd1-faaf-cc08-a8c73ca1bbd9.png)

- アーキテクチャ：API Gateway + Lambda（Node.js）
- 天気 API：[OpenWeatherMap API](https://openweathermap.org/)
  - 夜 9 時～翌朝 6 時までの気温を取得します。
  - 使い方は[https://qiita.com/nownabe/items/aeac1ce0977be963a740](https://qiita.com/nownabe/items/aeac1ce0977be963a740)を参考にしました。

## 作り方

### LINE BOT

主に下記の記事を参考にさせてもらいました。
- https://qiita.com/n0bisuke/items/ceaa09ef8898bee8369d
- https://dev.classmethod.jp/etc/lambda-line-bot-tutorial/

### ソースコードの書き換え(2019/10/21更新)

Lambda 関数のソースは以下のようになっています。
これを`$ tsc`でコンパイルすると動くはず。

環境変数は以下の 3 つを Lambda のコンソールで追加してください。

- LINE_CHANNEL_SECRET：BOT のチャネルシークレット（LINE Developers コンソールから取得）
- LINE_CHANNEL_ACCESS_TOKEN：BOT のアクセストークン（LINE Developers コンソールから取得）
- WEATHER_API_KEY：OpenWeatherAPI のキー（OpenWeatherMap ログイン後に取得）

```typescript
const line = require('@line/bot-sdk');
const client = new line.Client({
  channelSecret: process.env.LINE_CHANNEL_SECRET,
  channelAccessToken: process.env.LINE_CHANNEL_ACCESS_TOKEN,
});
const axios = require('axios');
const dayjs = require('dayjs');

const weatherApiConfig = {
  key: process.env.WEATHER_API_KEY,
};

interface PeriodInterface {
  begin: string;
  end: string;
}

interface WeatherInfoInterface {
  min: number;
  max: number;
  humidity: number;
}

interface BedclothesInterface {
  best: string;
}

exports.handler = async (event: any) => {
  const body = JSON.parse(event.body);
  console.log('event', JSON.stringify(event));
  console.log('body', JSON.stringify(body));

  if (body.events[0].replyToken === '00000000000000000000000000000000' && body.events[1].replyToken === 'ffffffffffffffffffffffffffffffff') {
    console.log('Developerコンソールからの疎通確認');
    return {
      statusCode: 200,
    };
  }

  try {
    const result = await Promise.all(body.events.map(handleEvent));
    console.log('result', JSON.stringify(result));

    return {
      statusCode: 200,
    };
  } catch (e) {
    console.error(JSON.stringify(e));

    return {
      statusCode: 500,
    };
  }
};

const handleEvent = async (event: any) => {
  if (event.type !== 'message' || event.message.type !== 'text') {
    return Promise.resolve(null);
  }

  const weathers = await axios.get(`${process.env.WEATHER_API_BASE_URL}/forecast?q=tokyo&units=metric&lang=ja&APPID=${weatherApiConfig.key}`);
  console.log(weathers.data.list);

  const { begin, end } = getPeriod();
  console.log({ begin, end });

  // 取得した天気リストの中から、夜9時～翌6時の情報のみに絞る
  const nightWeathers = weathers.data.list.filter((weather: any) => {
    return begin < weather.dt && weather.dt < end;
  });
  console.log({ nightWeathers: JSON.stringify(nightWeathers) });

  const weather = selectOne(nightWeathers);
  console.log(weather);

  const bestBedclothes = getBestBedClothes(weather);
  console.log({ bestBedclothes });

  return client.replyMessage(event.replyToken, {
    type: 'text',
    text: `今夜は、${bestBedclothes.best}がおすすめです。\n\n睡眠気温\n最高気温：${Math.round(weather.max * 10) / 10}℃\n最低気温：${Math.round(weather.min * 10) / 10}℃\n湿度　　：${weather.humidity}%`,
  });
};

// 9時～翌6時のデータを取得するための、UNIX時間を計算する
const getPeriod = (): PeriodInterface => {
  const today = dayjs();
  const begin = today.hour(20).endOf('hour').unix();
  const end = today.add(1, 'day').hour(6).startOf('hour').unix();

  return {
    begin,
    end,
  };
};

// 取得した天気リストの中から、最高気温、最低気温、最高湿度を選ぶ
const selectOne = (weathers: any): WeatherInfoInterface => {
  let min = 100;
  let max = -20;
  let humidity = 0;

  for (const nightWeather of weathers) {
    min = min < nightWeather.main.temp ? min : nightWeather.main.temp;
    max = max > nightWeather.main.temp ? max : nightWeather.main.temp;
    humidity = humidity > nightWeather.main.humidity ? humidity : nightWeather.main.humidity;
  }

  return { min, max, humidity };
};

// 気温を基に、最適な布団を選択する
const getBestBedClothes = (weather: WeatherInfoInterface): BedclothesInterface => {
  let best = 'タオルケット';
  if (weather.min < 10) {
    best = '羽毛布団 + 毛布';
  } else if (weather.min < 15) {
    best = '羽毛布団';
  } else if (weather.min < 20) {
    best = '薄手の掛け布団';
  }

  return {
    best,
  };
};

```

## 数日使ってみて

初日は暖かく、タオルケットをオススメされました。2 日目は少し寒かったので薄手の掛布団でした。
朝起きてみて、ちょうどいい感じだったので意外と使えるかも。

![try1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90087/f350828b-db42-9c8f-d244-3391046300af.jpeg)

![try2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90087/8fa5deab-f650-c1cf-3d84-d47de2b54551.jpeg)

ぱっと使ってみて改善したいところをいくつかピックアップしました。
そのうちアップデートするかも。

- 返事が文字だけだと寂しいので、イラストとか入れたい
- 前日の気温と比較して、暖かくなるのか寒くなるのかメッセージを追加したい
- プル型だと見なくなるので、定時にプッシュ送信したい
- 機械感が強いので、キャラを設定してみる
- [寝床内気象](https://www.nishikawasangyo.co.jp/company/laboratory/kankyo/)なるパラメータがあるらしい。  
  これはさすがに天気 API での取得は無理なので、LINE Things を使って布団内の気温・湿度を取得したい  
　→温度だけですが測定してみました。「[快適な睡眠を目指して寝床内気象を測定する #sleeptech](https://qiita.com/tmisuo0423/items/8e3bba55c3242ba02cc7)」(2019/10/22 更新)

使ってくれる方がいれば、ぜひフィードバックください（LINE で友達登録してね）。
![qr.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90087/3f71050e-0fc5-8261-11c7-bb6b69fc18aa.png)

## 更新履歴
- 2019/10/19 LINEBOT にリッチメニューを追加しました。
- 2019/10/21 ソースコードを TypeScript 化＆リファクタしました。
- 2019/10/22 寝床内気象の温度取得記事を書きました。リンク追加。