Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
3
Help us understand the problem. What are the problem?

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

プログラミング初心者がLineBotをつくってみた

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

コロナ禍でのデリバリー需要の増加

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

事故の傾向

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

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

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

事前準備

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

共通テンプレート

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

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を利用しています。

日の出を調べる

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,
        });
    }
};

ちょっと工夫

このコードを見て、

1)`lat=34.6959484&lng=135.4927352'`の緯度と経度変えれば行けるんじゃないか? 2)`日の出`の部分は全部`日の入り`に変えちゃえ! 3)`const utc_time = res.data.results.sunrise;`のsunriseを`sunset`に変えたら行けるんじゃないか?

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

日の入りを調べる

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
無事にできました!!

②今日の天気が分かるLineBot

こちらの記事を参考に作成しました。
1時間でLINE BOTを作るハンズオン (資料+レポート) in Node学園祭2017 #nodefest
APIはこちらです
https://weather.tsukumijima.net/

天気を調べる

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,
    });
  }

つまづいたところ

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

そして、結果は・・・
天気.PNG
ばっちりできました!

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

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

ngrokを立ち上げるたびに、URLが変わるので、webhookは毎回更新する!!

ということです。
オウム返しが返ってこないとか、上手く動いてない・・・となったら、まずはここを確認して見てください。

まとめ

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

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
3
Help us understand the problem. What are the problem?