事故を起こさないために、日の入りと天気を教えてくれるLineBotをつくってみた

#プログラミング初心者がLineBotをつくってみた
飲食店で働く私が、学んだプログラミングをもとに、LineBotを作成してみました。
こんな風に考えたらできた！というところや、つまづいた箇所もまとめたので、
そこも含めて、読んでいただければと思います。



#コロナ禍でのデリバリー需要の増加
コロナ禍になり、UberEatsや出前館などのデリバリーをよく見るようになりました。
飲食店からすると、この委託配送ってすごく手数料が取られて、大変なんです。
ということで、自分たちで運んじゃえ！と思い、
私の働くお店でもデリバリーを始めることになりました。

#事故の傾向
そうしていく中で、手探りの中でやっていたのですが、
悲しいことに、事故が頻発してしまいました。

事故が起きやすい原因の傾向が、
**・夕暮れ時でライトを点けずに、車に見落とされる**
**・雨の日にスリップして転倒する**
の２つがあります。

なので、事故を未然に防ぐために、
**①日の入り時刻が分かるLineBot**
**②今日の天気が分かるLineBot**
をつくろうと思いました。

#事前準備
まずは、LineBotのアカウントをつくったりと準備が必要なのですが、
今日は割愛します。
[1時間でLINE BOTを作るハンズオン (資料+レポート) in Node学園祭2017 #nodefest](https://qiita.com/n0bisuke/items/ceaa09ef8898bee8369d)
こちらの記事の作ってみよう！のところが分かりやすいので、参考にご準備ください。

#共通テンプレート
下記のコードをもとに、サンプル関数の部分を変えて、作成していきました。

```JS:server.js
'use strict';

// ########################################
//               初期設定など
// ########################################

// パッケージを使用します
const express = require('express');
const line = require('@line/bot-sdk');
const axios = require('axios');

// ローカル（自分のPC）でサーバーを公開するときのポート番号です
const PORT = process.env.PORT || 3000;

// Messaging APIで利用するクレデンシャル（秘匿情報）です。
const config = {
    channelSecret: '作成したBotのチャネルシークレット',
    channelAccessToken: '作成したBotのチャネルアクセストークン'
};



// ########## ▼▼▼ サンプル関数 ▼▼▼ ##########
（この行をサンプル関数丸ごと全部と置き換えてね）
// ########## ▲▲▲ サンプル関数 ▲▲▲ ##########



// ########################################
//  LINEサーバーからのWebhookデータを処理する部分
// ########################################

// LINE SDKを初期化します
const client = new line.Client(config);

// LINEサーバーからWebhookがあると「サーバー部分」から以下の "handleEvent" という関数が呼び出されます
async function handleEvent(event) {
    // 受信したWebhookが「テキストメッセージ以外」であればnullを返すことで無視します
    if (event.type !== 'message' || event.message.type !== 'text') {
        return Promise.resolve(null);
    }
    // サンプル関数を実行します
    return sampleFunction(event);
}



// ########################################
//          Expressによるサーバー部分
// ########################################

// expressを初期化します
const app = express();

// HTTP POSTによって '/webhook' のパスにアクセスがあったら、POSTされた内容に応じて様々な処理をします
app.post('/webhook', line.middleware(config), (req, res) => {
  
  // 検証ボタンをクリックしたときに飛んできたWebhookを受信したときのみ以下のif文内を実行
  if (req.body.events.length === 0) {
    res.send('Hello LINE BOT! (HTTP POST)'); // LINEサーバーに返答します（なくてもよい）
    console.log('検証イベントを受信しました！'); // ターミナルに表示します
    return; // これより下は実行されません
  } else {
    // 通常のメッセージなど … Webhookの中身を確認用にターミナルに表示します
    console.log('受信しました:', req.body.events);
  }

  // あらかじめ宣言しておいた "handleEvent" 関数にWebhookの中身を渡して処理してもらい、
  // 関数から戻ってきたデータをそのままLINEサーバーに「レスポンス」として返します
  Promise.all(req.body.events.map(handleEvent)).then((result) => res.json(result));
});

// 最初に決めたポート番号でサーバーをPC内だけに公開します
// （環境によってはローカルネットワーク内にも公開されます）
app.listen(PORT);
console.log(`ポート${PORT}番でExpressサーバーを実行中です…`);
```

#①日の入り時刻が分かるLineBot
最初に教えてもらったコードが大阪府北区の日の出を調べるコードでした。
[Sunset and sunrise times API](https://sunrise-sunset.org/api)を利用しています。
###日の出を調べる
```JS:server2.js
const sampleFunction = async (event) => {
    // ユーザーメッセージが「日の出」か「日の入り」かどうか
    if (event.message.text !== '今日の日の出は？') {
        return client.replyMessage(event.replyToken, {
            type: 'text',
            text: '「今日の日の出は？」と話しかけてね'
        });
    } else {
        // 「リプライ」を使って先に返事しておきます
        await client.replyMessage(event.replyToken, {
            type: 'text',
            text: '調べています……'
        });
        
        let pushText = '';
        try {
            // axiosで日の出日の入り時刻のAPIを叩きます（少し時間がかかる・ブロッキングする）
            const res = await axios.get('https://api.sunrise-sunset.org/json?lat=34.6959484&lng=135.4927352');
            // 取得できるのはUTCなので日本時間（+9時間）になおす
            const utc_time = res.data.results.sunrise;
            // '時', '分', '秒 PM' に分割する
            const tm_split = utc_time.split(':');
            // '時' を9時間進めて12時間戻す（13時を過ぎないようにする）
            const jp_hour = Number(tm_split[0]) + 9 - 12;
            // '秒 PM' を '秒' だけにする
            const sec = tm_split[2].split(' ')[0];
            // 再構成する
            const time_string = `${jp_hour}時${tm_split[1]}分${sec}秒`;
            pushText = `今日の大阪市北区付近の日の出は${time_string}です！`;
        } catch (error) {
            pushText = '検索中にエラーが発生しました。ごめんね。';
            // APIからエラーが返ってきたらターミナルに表示する
            console.error(error);
        }

        // 「プッシュ」で後からユーザーに通知します
        return client.pushMessage(event.source.userId, {
            type: 'text',
            text: pushText,
        });
    }
};
```
###ちょっと工夫
このコードを見て、

:::note info
１）`lat=34.6959484&lng=135.4927352'`の緯度と経度変えれば行けるんじゃないか？
２）`日の出`の部分は全部`日の入り`に変えちゃえ！
３）`const utc_time = res.data.results.sunrise;`のsunriseを`sunset`に変えたら行けるんじゃないか？
:::

と思い、書き換えたコードがこちら！

###日の入りを調べる
```JS:server3.js
const sampleFunction = async (event) => {
    // ユーザーメッセージが「日の出」か「日の入り」かどうか
    if (event.message.text !== '今日の日の入りは？') {
        return client.replyMessage(event.replyToken, {
            type: 'text',
            text: '「今日の日の入りは？」と話しかけてね'
        });
    } else {
        // 「リプライ」を使って先に返事しておきます
        await client.replyMessage(event.replyToken, {
            type: 'text',
            text: '調べています……'
        });
        
        let pushText = '';
        try {
            // axiosで日の出日の入り時刻のAPIを叩きます（少し時間がかかる・ブロッキングする）
            const res = await axios.get('https://api.sunrise-sunset.org/json?lat=35.536325&lng=139.635307');
            // 取得できるのはUTCなので日本時間（+9時間）になおす
            const utc_time = res.data.results.sunset;
            // '時', '分', '秒 PM' に分割する
            const tm_split = utc_time.split(':');
            // '時' を9時間進めて12時間戻す（13時を過ぎないようにする）
            const jp_hour = Number(tm_split[0]) + 9 - 12;
            // '秒 PM' を '秒' だけにする
            const sec = tm_split[2].split(' ')[0];
            // 再構成する
            const time_string = `${jp_hour}時${tm_split[1]}分${sec}秒`;
            pushText = `今日の綱島付近の日の入りは${time_string}です！ライトをつけてくださいね！`;
        } catch (error) {
            pushText = '検索中にエラーが発生しました。ごめんね。';
            // APIからエラーが返ってきたらターミナルに表示する
            console.error(error);
        }

        // 「プッシュ」で後からユーザーに通知します
        return client.pushMessage(event.source.userId, {
            type: 'text',
            text: pushText,
        });
    }
};
```
結果は・・・
![日の入り.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2052662/150ee109-8a02-eed2-5f11-edf924465459.png)
無事にできました！！

#②今日の天気が分かるLineBot
こちらの記事を参考に作成しました。
[1時間でLINE BOTを作るハンズオン (資料+レポート) in Node学園祭2017 #nodefest](https://qiita.com/n0bisuke/items/ceaa09ef8898bee8369d)
APIはこちらです
https://weather.tsukumijima.net/

###天気を調べる

```JS:server4.js
const sampleFunction = async (event) => {
    if (event.message.text !== '今日の天気は？') {
        return client.replyMessage(event.replyToken, {
            type: 'text',
            text: '「今日の天気は？」と話しかけてね'
      });
    }
  
    let replyText = '';
    replyText = 'ちょっと待ってね'; //"ちょっと待ってね"ってメッセージだけ先に処理
    await client.replyMessage(event.replyToken, {
        type: 'text',
        text: replyText
    });
  
    //axiosを使って天気APIにアクセス
    const CITY_ID = `140010`; //取得したい地域のIDを指定
        const URL = `https://weather.tsukumijima.net/api/forecast?city=${CITY_ID}`;
    const res = await axios.get(URL);
    const pushText = res.data.description.text;
    return client.pushMessage(event.source.userId, {
        type: 'text',
        text: pushText,
    });
  }
```
###つまづいたところ

:::note warn
１）`CITY_ID`を変えておらず、神奈川県の天気にできなかった。
２）参考記事では最初が`async function handleEvent(event) {`になっていたが、
　　共通テンプレートでは最後に`return sampleFunction(event);`でサンプル関数を実行しているので、
　　`const sampleFunction = async (event) => {`で始める必要があった。
:::

そして、結果は・・・
![天気.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2052662/1ca64637-ca3b-c2ce-0137-4d4daedecb8b.png)
ばっちりできました！

#全体を通してつまづいたところ

初心者の私が全体を通してつまづいたところが、

:::note warn
<font color="IndianRed"></font>ngrokを立ち上げるたびに、URLが変わるので、webhookは毎回更新する！！
:::
ということです。
オウム返しが返ってこないとか、上手く動いてない･･･となったら、まずはここを確認して見てください。

#まとめ
同じBot内で日の入りと天気の両方が聞けたらよかったのですが、
結果はバラバラのBotでないと動かない形になりました。
また、位置情報を送ったら、そこから日の入りと天気が返ってくるというところまでできたら、
どんなお店でも使えるので最高だなと思いました。
（まだまだそこまでは行きつかない現実・・・）
でも、自分が調べたいことに対して、しっかりとBotで返ってくるので、自分の中では大満足です◎
これからも頑張って、勉強するぞー！！