3
0

More than 1 year has passed since last update.

生まれたばかりのGodと友だちになりました。(・´з`・)<バイクに乗る前に話しかけてね!

Last updated at Posted at 2021-10-21

バイク(特に愛車)に乗るときは、天気予報や最高最低気温等、私はとっても気になってしまうのです。
ツーリングルートや食べ物等、他にも調べたいことがたくさんあり、調べる時間は楽しい:heart:ですが、簡素化できることは簡素化させたいですよね:sparkles:
必要な情報を調べて返答してくれる、会話もできて暇つぶしにも付き合ってくれて、しかもつぶやきまでしてくれる。
神様(=God)のようなLINE Botを生み出して友だちになろうと思います!

命名 God(・´з`・)ちゃん

Botという言葉を知らない人に「え?LINE God?」と言われたのが発端です。気に入りました。
この顔文字も最近のお気に入りなので使うことにします。
「LINE God」に変更しようとしたところLINE Developersから「LINEと入る文字はNGです。」とはじかれたので、適当に「API Tha★God」にしてしまいました。1回変更すると1週間後まで変更できないとのこと:sweat:
仕方がないので、このまま動かすことにします。

God(・´з`・)ちゃん 生まれておいで~

God(・´з`・)ちゃん誕生~そして友だちへ~

はい。誕生しました!友だちになりました!
生まれたばかりで知っている言葉も少ないので「今日」「明日」「明後日」にだけ反応して神のみぞ知る情報(天気予報)とひとりごとをつぶやいてくれます。
(言葉は話せるけど文字は難しくてあまり読めない感じ)
読めない言葉が来たら知ってる言葉で送るようお願いされます。

処理コード

「全体の処理コード」は長いので折りたたみます。
「各処理ごとのコード」で細かく見ていきます。

全体の処理コード

長いので折りたたみます。

今回は「初期設定」と「LINEサーバーからのWebhookデータを処理する部分」は公開されているものを使いましたので、その間の部分を考えて実装しました。
'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: '発行されたチャネルシークレット',
  channelAccessToken: '発行されたチャネルアクセストークン'
};

// ########## ▼▼▼ サンプル関数 ▼▼▼ ##########
const sampleFunction = async (event) => {
  // ユーザーメッセージが「今日の天気は?」かどうか
  if (event.message.text !== '今日' && event.message.text !== '明日' && event.message.text !== '明後日') {
    return client.replyMessage(event.replyToken, {
        type: 'text',
        text: '(・´з`・)<神のみぞ知る情報を教えちゃうよ。\n「今日」「明日」「明後日」って送ってみてね。'
    });  
  } else {
      // 「リプライ」を使って先に返事しておきます
      await client.replyMessage(event.replyToken, {
          type: 'text',
          text: '(・´з`・)<ちょっと待つんだよ。'
      });

      let today = new Date();     //今日   
      let dataflg = 0;

      switch (event.message.text) {
        case '明日':
          today.setDate(today.getDate() + 1);       //明日
          dataflg = 1;
          break;
        case '明後日':
          today.setDate(today.getDate() + 2);       //明後日   
          dataflg = 2;
          break;
      }

      //計算後の日付を代入
      let dataYear = today.getFullYear();
      let dataMonth = today.getMonth()+1;
      let dataDay = today.getDate();

      //表示用のメッセージの代入用に宣言し、''を代入する(この後結果によって中身が変わるため先にここで宣言する)
      let pushText = '';
      let pushImage = '';

      try {
          // axiosで天気予報APIを叩きます(少し時間がかかる・ブロッキングする)
      // axiosで天気予報APIを叩きます(少し時間がかかる・ブロッキングする)
      const res = await axios.get('https://weather.tsukumijima.net/api/forecast/city/140020');
      // 天気情報を取得
      const today_weather = res.data.forecasts[dataflg].telop;  //天気予報
      let today_T00_06 = res.data.forecasts[dataflg].chanceOfRain.T00_06.substring(0,1);  //降水確率0-6時 %数字のみ取得
      let today_T06_12 = res.data.forecasts[dataflg].chanceOfRain.T06_12.substring(0,1);  //降水確率6-12時 %数字のみ取得
      let today_T12_18 = res.data.forecasts[dataflg].chanceOfRain.T12_18.substring(0,1);  //降水確率11-18時 %数字のみ取得
      let today_T18_24 = res.data.forecasts[dataflg].chanceOfRain.T18_24.substring(0,1);  //降水確率18-24時 %数字のみ取得
      let today_wind = res.data.forecasts[dataflg].detail.wind;  //風の強さ
      let today_max = res.data.forecasts[dataflg].temperature.max.celsius;  //最高気温
      let today_min = res.data.forecasts[dataflg].temperature.min.celsius;  //最低気温
      let rainMsg = '';  //降水確率によってメッセージを変更するため、先にメッセージ代入用を宣言
      let maxMsg = '';  //最高気温によってメッセージを変更するため、先にメッセージ代入用を宣言

      //降水確率によってメッセージを変更する
      if (today_T00_06 !== '-') {
        today_T00_06 = today_T00_06 *10;  //降水確率0-6時 *10
      }
      if (today_T06_12 !== '-') {
        today_T06_12 = today_T06_12 *10;  //降水確率6-12時 *10
      }
      if (today_T12_18 !== '-') {
        today_T12_18 = today_T12_18 *10;  //降水確率11-18時 *10
      }
      if (today_T18_24 !== '-') {
        today_T18_24 = today_T18_24 *10;  //降水確率18-24時 *10
      }
      if (today_T00_06 > 39 || today_T06_12 > 39 || today_T12_18 > 39 || today_T18_24 > 39) {
        rainMsg = '降水確率が40%以上の時間帯があるけどバイク乗るの?\n車体が濡れたらキチンと拭くんだよ?';
      } else {
        if (today_T00_06 < 10 || today_T06_12 < 10 || today_T12_18 < 10 || today_T18_24 > 10) {
          rainMsg = '雨が降る心配は無いみたい!\nバイク日和だね!気をつけて★';
        } else {
          rainMsg = '念のため雨具を用意してね。\n無事に走れますように♪';
        }
      }

      if (today_wind === null) {    //nullだったらメッセージ変更
        today_wind = 'どれくらいの強さになるか未定だよ';
      }
      if (today_max === null){   //nullだったら最高気温へ'--'を代入
        today_max = '-';
        maxMsg = '';
      } else {
        if (today_max < 21 ){    //nullじゃなかったら温度メッセージを変更するを代入
          maxMsg = '防寒具必須。特に手袋は重装備に!ホッカイロもあると良いかもよ。';
        } else if (today_max > 27) {
          maxMsg = '暑いけどバイクに乗るなら長袖着てね?そんで水分補給もわすれずにね!';
        } else if (today_max < 27 && today_max > 21) {
          maxMsg = '寒暖差に備えて着替え持っていくといいよ~。ちなみにサングラスってかっこいいよね。';
        } else if (today_max = '--') {
          maxMsg = '・・・着替えは持つべし・・・バイクってイイよね。'
        }
      }
      if (today_min === null ) {
        today_min = '-';
      }
        //いらすとやのバイク画像を配列登録する(今後は取得しに行く方式にしたい)
        const bikeUrl = ['画像URLベタ打ち1','画像URLベタ打ち2','画像URLベタ打ち3','画像URLベタ打ち4'];
          pushImage = (bikeUrl[Math.floor(Math.random() * bikeUrl.length)]);  //上の配列からランダムに配列番号を返す

          //返信用のテキストを作成
          pushText =  dataYear + '/' + dataMonth + '/' + dataDay +'の神奈川県小田原周辺の天気は' + today_weather + 'です。\n'
                   + '最高気温:' + today_max + '\n'
                   + '最低気温:' + today_min + '\n'
                   + '降水確率\n'
                   + '  :0時~6時  ' + today_T00_06 +'\n'
                   + '  :6時~12時  ' + today_T06_12 +'\n'
                   + '  :12時~18時  ' + today_T12_18 +'\n'
                   + '  :18時~24時  ' + today_T18_24 + '\n'
                   + '風:' + today_wind +'\n\n'
                   + '神様のひとりごと\n' + '(・´з`・)<' + rainMsg + '\n' + maxMsg + '\n安全第一!';

      } catch (error) {
          pushText = '時間をおいてもう一回やってみて!';
          pushImage = 'エラー画像URL';
          // APIからエラーが返ってきたらターミナルに表示する
          console.error(error);
      }
      // 「プッシュ」でユーザーに通知します
      await client.pushMessage(event.source.userId, {
          type: 'image',
          originalContentUrl: pushImage,
          previewImageUrl: pushImage,
      });
      // 「プッシュ」で後からユーザーに通知します
      return client.pushMessage(event.source.userId, {
          type: 'text',
          text: pushText,
      });
  }
};

// ########## ▲▲▲ サンプル関数 ▲▲▲ ##########

// ########################################
//  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サーバーを実行中です…`);

各処理ごとのコード

取得したデータから条件分岐させ、天気とバイクに関することを「神様のひとりごと」として表示します。
取得する天気データは時間帯によって取得できないデータがあるようで、nullだったり--だったりしますのでそれを回避しています。

日付取得

天気予報を返すときに日付を言ってほしいので、「今日」「明日」「明後日」どの言葉が入ってきたかSwitch文を使って分岐させ日付を取得します。
JSONデータから必要なデータのインデックス番号を代入します。
初めてSwitchi文を使ってみましたが、If文より読みやすいと感じました。

      let today = new Date();     //今日   
      let dataflg = 0;

      switch (event.message.text) {
        case '明日':
          today.setDate(today.getDate() + 1);       //明日
          dataflg = 1;
          break;
        case '明後日':
          today.setDate(today.getDate() + 2);       //明後日   
          dataflg = 2;
          break;
      }

      //計算後の日付を代入
      let dataYear = today.getFullYear();
      let dataMonth = today.getMonth()+1;
      let dataDay = today.getDate();

降水確率

6時間ごとに降水確率のデータが取れるので、その降水確率の%によって「神様のひとりごと」を変更しています。
JSONデータには「**%」と格納されるので、%を除く数字部分を持ってきます。

      let today_T00_06 = res.data.forecasts[dataflg].chanceOfRain.T00_06.substring(0,1);  //降水確率0-6時 %数字のみ取得
      let today_T06_12 = res.data.forecasts[dataflg].chanceOfRain.T06_12.substring(0,1);  //降水確率6-12時 %数字のみ取得
      let today_T12_18 = res.data.forecasts[dataflg].chanceOfRain.T12_18.substring(0,1);  //降水確率11-18時 %数字のみ取得
      let today_T18_24 = res.data.forecasts[dataflg].chanceOfRain.T18_24.substring(0,1);  //降水確率18-24時 %数字のみ取得

降水確率が高い時間帯があると、バイクに本当に乗るのか?聞いてくれます。
降水確率が低いと雨の心配はない等のコメントをしてくれます。

      //降水確率によってメッセージを変更する
      if (today_T00_06 !== '-') {
        today_T00_06 = today_T00_06 *10;  //降水確率0-6時 *10
      }
      if (today_T06_12 !== '-') {
        today_T06_12 = today_T06_12 *10;  //降水確率6-12時 *10
      }
      if (today_T12_18 !== '-') {
        today_T12_18 = today_T12_18 *10;  //降水確率11-18時 *10
      }
      if (today_T18_24 !== '-') {
        today_T18_24 = today_T18_24 *10;  //降水確率18-24時 *10
      }
      if (today_T00_06 > 39 || today_T06_12 > 39 || today_T12_18 > 39 || today_T18_24 > 39) {
        rainMsg = '降水確率が40%以上の時間帯があるけどバイク乗るの?\n車体が濡れたらキチンと拭くんだよ?';
      } else {
        if (today_T00_06 < 10 || today_T06_12 < 10 || today_T12_18 < 10 || today_T18_24 > 10) {
          rainMsg = '雨が降る心配は無いみたい!\nバイク日和だね!気をつけて★';
        } else {
          rainMsg = '念のため雨具を用意してね。\n無事に走れますように♪';
        }
      }

JSONデータの風の情報がnullとして格納されることがあるため、null表示を回避しています。
God(・´з`・)が風の強さをまだ決めていないかのような返答にしています。
「キミが本当に決めているのかい?」と言いたくなります:smirk:

     if (today_wind === null) {    //nullだったらメッセージ変更
        today_wind = 'どれくらいの強さになるか未定だよ';
      }

最高気温

JSONデータの最高気温の情報がnullとして格納されることがあるため、null表示を回避しています。
友だちからのメッセージにnullなんて入っていたら機械っぽくてげんなりしちゃいますよね。
実際は機械なんですが:expressionless:
温度によって「神様のひとりごと」が変わります。優しいGodに育っておくれ。


      if (today_max === null){   //nullだったら最高気温へ'--'を代入
        today_max = '-';
        maxMsg = '';
      } else {
        if (today_max < 21 ){    //nullじゃなかったら温度メッセージを変更するを代入
          maxMsg = '防寒具必須。特に手袋は重装備に!ホッカイロもあると良いかもよ。';
        } else if (today_max > 27) {
          maxMsg = '暑いけどバイクに乗るなら長袖着てね?そんで水分補給もわすれずにね!';
        } else if (today_max < 27 && today_max > 21) {
          maxMsg = '寒暖差に備えて着替え持っていくといいよ~。ちなみにサングラスってかっこいいよね。';
        } else if (today_max = '--') {
          maxMsg = '・・・着替えは持つべし・・・バイクってイイよね。'
        }
      }

最低気温

JSONデータの最低気温の情報がnullとして格納されることがあるため、null表示を回避しています。

      if (today_min === null ) {
        today_min = '-';

LINEメッセージ内容

APIを使って取得してきたデータとそのデータを元に条件分岐して設定したメッセージと画像を表示します。
会話したくて仕方がないのかな?ひとりごと多めに育ってます。(・´з`・)

          //返信用のテキストを作成
          pushText =  dataYear + '/' + dataMonth + '/' + dataDay +'の神奈川県小田原周辺の天気は' + today_weather + 'です。\n'
                   + '最高気温:' + today_max + '\n'
                   + '最低気温:' + today_min + '\n'
                   + '降水確率\n'
                   + '  :0時~6時  ' + today_T00_06 +'\n'
                   + '  :6時~12時  ' + today_T06_12 +'\n'
                   + '  :12時~18時  ' + today_T12_18 +'\n'
                   + '  :18時~24時  ' + today_T18_24 + '\n'
                   + '風:' + today_wind +'\n\n'
                   + '神様のひとりごと\n' + '(・´з`・)<' + rainMsg + '\n' + maxMsg + '\n安全第一!';

画像

画像URL配列登録

いらすとやフリー素材画像のバイク関連の画像URLを配列に格納します。
画像を取得できるAPIを使ってバイク関連の画像を持ってきたかったのですが、バイクをキーワードにすると、前回前々回で、欲しいデータが取れない可能性が高いことを学んだので直接URLを配列に格納しました。
バイク画像で検索すると「バイク事故」「バイク転倒」「自転車」等、友だちから送られてきたらショッキングな画像や関係ない画像が来るのは問題なので、本当に必要な画像を表示するように制御したいです。

      //いらすとやのバイク画像を配列登録する(今後は取得しに行く方式にしたい)
      const bikeUrl = ['画像URLベタ打ち1','画像URLベタ打ち2','画像URLベタ打ち3','画像URLベタ打ち4'];
          pushImage = (bikeUrl[Math.floor(Math.random() * bikeUrl.length)]);  //上の配列からランダムに配列番号を返す

画像投稿

type: 'text'を設定するとURLをそのまま送ることになりますので、画像を送りたいときは type: 'image'を設定することでをLINEに画像を投稿することができます。
God(・´з`・)ちゃんからスタンプが送られてきたみたいで楽しい☆

      // 「プッシュ」でユーザーに通知します
      await client.pushMessage(event.source.userId, {
          type: 'image',
          originalContentUrl: pushImage,
          previewImageUrl: pushImage,

心残り

暇つぶし機能としてAdvice Slip JSON APIを使ってランダムにアドバイスを返してくれるという機能を作っていましたが、単体で動かすと動くのに組み合わせると動かない。時間もない。ということで今回は腹をくくって削除しました:disappointed:
if文と{}がよく分かんないことになってるのが原因なんだとは思うのですが・・・

God(・´з`・)ちゃんが返してくれたアドバイス画像 和訳アカウントで翻訳した画像
一言コメントRe.jpg 一言コメントEn.jpg

God(・´з`・)ちゃんに覚えてもらいたい機能

God(・´з`・)ちゃんの教育方針を考えるということですね。
つまりは:fire:リベンジ:fire:ということ!

①日の出&日の入時刻 ★使用予定API:Sunset and sunrise times API
時刻によってコメントを返す
・例)「5:17くらいに日が出そう。その前に出るならライト点灯してね。」
・例)「18:09ごろから暗くなるから早めにライト点灯して早く帰ってね。」
②目的地の設定
目的地を設定することで以下やり取りができる
・「目的地の天気」も返してくれる。
・「オススメ度が高いお土産」を表示して「このお土産買ってきて」等とお願いされる。
・「オススメ度が高い飲食店」を表示して「ここで○○食べたら感想教えて」等とお願いされる。
③会話の充実
会話もできるようにしたい。
そして、暇つぶしもできると良い。 ★使用予定API:Advice Slip JSON APIThe Bored API
④バイク画像の自動取得

最後に

長くなるとif文の{}がどこでつながってるのかわからなくなってくるのが苦労しました。
Visual Studio Codeではわかるようになっていますが、この{}たちはここであっているのか?わからなくなってきたので、長くなってしまうときにコードを飛ばすことができると良いなと思いました。
JavaScriptを初めて2週間。まだまだ勉強!ということでこれからも頑張ります。

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0