20
23

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【Node.js】天気予報アプリをLINE MessageAPIで作ってみた!

Last updated at Posted at 2021-06-14

LINE MessageAPIを使って天気予報・ファッションレコメンドアプリを作ってみました。
完成形としては以下の通りです。
名称未設定.png

以前Laravelでも同様のアプリを作成しました。
そちらの模様はこちらからどうぞ。

追記(2021/06/21)

Node.jsとTypeScriptでも作ってみました。
LINEのパッケージも型定義されているので、TypeScriptで作った方がミスなく作れるのでこちらもよかったら見ていってください!

追記(2021/06/22)

AWSでデプロイしました。
AWS勉強中の方はこちらの記事もどうぞ!

追記(2021/07/14)

AWSのLambdaを使ってサーバレスアプリケーションとしてデプロイしました。
インフラ初心者の方に関しては、上記のEC2を使った構成よりも簡単なのでぜひチャレンジしてみてください。

追記(2021/06/24)

新たに、現在地から美味しいお店を探すアプリをLINE Messaging APIで作ってみました!
こちらでは、Google Maps APIを使用しています。
ぜひこちらの記事もどうぞ!

LaravelとNode.jsの比較

圧倒的にNode.jsで作る方が簡単でした。
Flex Messageを作成する時はJSONデータを扱います。
正式名称をJavaScript Object Notationというだけあって、
JavaScriptとの相性の良さが半端なかったです。

なので作成し終わった後は、Laravelで作るなんて酔狂だなと感じるほどでした。

LaravelとNuxt.jsでLIFFアプリを作ろうと思っていましたが、
これを作った後はLaravelは使わないぞと心に決めました。

ということで本題に入っていきましょう。

どのようなアプリか

皆さんは、今日の気温を聞いて、**「快適に過ごすために今日のファッションをこうしよう」**ってパッと思いつきますか?

私は、最高気温、最低気温を聞いてもそれがどのくらい暑いのか、寒いのかがピンと来ず、洋服のチョイスを外したことがしばしばあります。

image.png

こんな思いを2度としないために今回このアプリを作りました。

Laravelで同様のアプリケーションを作成していたので所要時間としては、4時間程度です。
ほぼほぼ難しいところもないので、初心者でも簡単に作成できるかと思います。

アプリの流れ

アプリの流れは大まかに以下の4つのステップで成り立っています。

・①クライアントが現在地を送る
・②OpenWeatherから天気予報を取得
・③データの整形
・④クライアントに送る

GitHub

完成形のコードは以下となります。

では実際に作成していきましょう!

LINE Developersにアカウントを作成する

LINE Developersにアクセスして、「ログイン」ボタンをクリックしてください。

その後諸々入力してもらったら以下のように作成できるかと思います。
注意事項としては、今回Messaging APIとなるので、チャネルの種類を間違えた方は修正してください。

スクリーンショット 2021-05-29 16.13.47.png

チャネルシークレットチャネルアクセストークンが必要になるのでこの2つを発行します。

スクリーンショット 2021-05-29 16.16.20.png

スクリーンショット 2021-05-29 16.17.51.png

ではこの2つを.envに入力します。

.env
LINE_CHANNEL_SECRET=abcdefg123456
LINE_CHANNEL_ACCESS_TOKEN=HogeHogeHoge123456789HogeHogeHoge

package.jsonの作成

以下のコマンドを入力してください。
これで、package.jsonの作成が完了します。

ターミナル
$ npm init -y

必要なパッケージのインストール

今回使用するパッケージは以下の5つです。
・@line/bot-sdk
・express
・dotenv
・axios
・nodemon

以下のコマンドを入力してください。
これで全てのパッケージがインストールされます。

ターミナル
$ npm install @line/bot-sdk express dotenv axios nodemon --save

また、この辺りで必要ないディレクトリはGithubにpushしたくないので、.gitignoreも作成しておきましょう。

.gitignore
node_modules
package-lock.json
.env

https://localhost:3000にアクセスするとhello worldが表示

APIサーバーが正しく動くか検証のため一応作っておきましょう。

api/index.js
'use strict';

// Load the package
const line = require('@line/bot-sdk');
const express = require('express');
const axios = require('axios');
require('dotenv').config();

// Read the ports from the process.env file
const PORT = process.env.PORT || 3000;

// Load the access token and channel secret from the .env file
const config = {
  channelAccessToken: process.env.CHANNEL_ACCESS_TOKEN,
  channelSecret: process.env.CHANNEL_SECRET,
};

// Instantiate
const app = express();
const client = new line.Client(config);

// Do routing
// Testing Routing
app.get('/', (req, res) => {
  res.send('Hello World');
});

// Start the server
app.listen(PORT, () => {
  console.log('http://localhost:3000');
});

上記の内容としては、
①必要なパッケージを読み込む
②PORT番号を選択する(デプロイ先でPORT番号が指定されるパターンに備えて一応.envを読み込む形式にしています。)
config の作成(これはおまじないのようなものです)
④インスタンス化を行う。(clientもおまじない)
⑤ルーティングの作成
⑥WEBサーバーの実行

おまじないだけで片付けるのもアレなので公式サイトを貼っておきます。
公式サイトが神なのでこれを見れば簡単に作れると思います。

また、今回デプロイ先で使う、Glitchscriptsstartがないといけないのでこの辺りで追加しておきましょう。(読み方は知らん。グリッチでしょうかね。)

開発効率を上げるために、nodemonも使えるようにしておきます。

package.json
{
  "name": "Weather",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node api/index.js",
    "dev": "nodemon api/index.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@line/bot-sdk": "^7.3.0",
    "axios": "^0.21.1",
    "dotenv": "^10.0.0",
    "express": "^4.17.1",
    "nodemon": "^2.0.7"
  }
}

開発用にhttpsの取得

Laravelでは、Ngrokを採用していました。
今回もNgrokでも良かったのですが、インストールせずに使えるlocalhost.runが便利すぎたのでこちらを採用します。

インストール型の方がセキュリティ面は安全なのでしょうが、今回はあんま気にせず、sshだけでhttpsが使えるlocalhost.runを使いましょう。

ということでここからはターミナル2つ使って開発していきます。
こんな感じです。
スクリーンショット 2021-06-14 20.41.08.png

コマンドはこの2つです。

ターミナル
$ npm run dev
$ ssh -R 80:localhost:3000 localhost.run

どうでもいいですが最近ターミナルをおしゃれにしました!
この記事がとても参考になりました。

Webhook URLの登録

localhost.runで作成したhttpsのURLをコピーしてください。
私の場合は以下のURLです。

これをLINE DevelopersのWebhookに設定します。

スクリーンショット 2021-06-14 20.48.20.png

これで初期設定は完了です。

ここからの流れはこのような感じです。

①「今日の洋服は?」というメッセージを受け取る
②「今日の洋服は?」を受け取ったら、位置情報メッセージを送る
③「今日の洋服は?」以外を受け取ったら、「そのメッセージには対応していません」と送る
④「位置情報メッセージ」を受け取る
⑤「位置情報メッセージ」を受け取ったら、緯度と経度を使って天気予報を取得する
⑥「位置情報メッセージ」を受け取ったら、天気予報メッセージを送る

では作っていきましょう!
またこれら全てのコードをapi/index.jsに書くとコードが肥大化し可読性が落ちます。
なのでCommonディレクトリに関数に切り分けて作成していきます。

①「今日の洋服は?」というメッセージを受け取る

api/index.js
'use strict';

// Load the package
const line = require('@line/bot-sdk');
const express = require('express');
require('dotenv').config();

// Load the module
const ButtonOrErrorMessage = require('./Common/Send/ButtonOrErrorMessage');

// Read the ports from the process.env file
const PORT = process.env.PORT || 3000;

// Load the access token and channel secret from the .env file
const config = {
  channelAccessToken: process.env.CHANNEL_ACCESS_TOKEN,
  channelSecret: process.env.CHANNEL_SECRET,
};

// Instantiate
const app = express();
const client = new line.Client(config);

// Do routing
// Testing Routing
app.get('/', (req, res) => {
  res.send('Hello World');
});

// API Routing
app.post('/api/line/message', line.middleware(config), async (req, res) => {
  const event = req.body.events[0];
  const eventType = event.message.type;

  // ここでメッセージのタイプを分けてメッセージ内容を分岐させます。
  // これはテキストメッセージです
  if (eventType === 'text') {
    await ButtonOrErrorMessage.SendMessage(client, event);
  }
});

// Start the server (Production Environment)
app.listen(PORT, () => {
  console.log('http://localhost:3000');
});
Common/Send/ButtonOrErrorMessage.js
exports.SendMessage = (client, event) => {
  const text = event.message.text;
  const replyToken = event.replyToken;

  if (text === '今日の洋服は?') {
    // ここで位置情報メッセージを送る
  } else {
    // ここで「そのメッセージには対応していません」と送る
  }
};

一見、**「なんだこれ・・普通に難しそうじゃねーか!」**と思うかもしれませんが、ほぼ公式サイトをコピペです。
公式サイトが優秀すぎました。
console.log(req.body.events)でデバッグすればこれだけで簡単に作れると思います。

②「今日の洋服は?」を受け取ったら、位置情報メッセージを送る

Common/Template/ButtonMessageTemplate.js
exports.Template = () => {
  return {
    type: 'template',
    altText: 'This is a buttons template',
    template: {
      type: 'buttons',
      text: '今日はどんな洋服にしようかな',
      actions: [
        {
          type: 'uri',
          label: '現在地を送る',
          uri: 'https://line.me/R/nv/location/',
        },
      ],
    },
  };
Common/Send/ButtonOrErrorMessage.js
const ButtonMessageTemplate = require('../Template/ButtonMessageTemplate');

exports.SendMessage = (client, event) => {
  const text = event.message.text;
  const replyToken = event.replyToken;

  if (text === '今日の洋服は?') {
    client.replyMessage(replyToken, ButtonMessageTemplate.Template());
  } else {
    // ここで「そのメッセージには対応していません」と送る
  }
};

ボタンメッセージのJSON作成に関しては公式サイトを参考にしましょう。

③「今日の洋服は?」以外を受け取ったら、「そのメッセージには対応していません」と送る

Common/Template/ErrorMessageTemplate.js
exports.Template = () => {
  return {
    type: 'text',
    text: 'ごめんなさい、このメッセージは対応していません。',
  };
};
Common/Send/ButtonOrErrorMessage.js
const ButtonMessageTemplate = require('../Template/ButtonMessageTemplate');
const ErrorMessageTemplate = require('../Template/ErrorMessageTemplate');

exports.SendMessage = (client, event) => {
  const text = event.message.text;
  const replyToken = event.replyToken;

  if (text === '今日の洋服は?') {
    client.replyMessage(replyToken, ButtonMessageTemplate.Template());
  } else {
    client.replyMessage(replyToken, ErrorMessageTemplate.Template());
  }
};

テキストメッセージのJSON作成に関しては公式サイトを参考にしましょう。

④「位置情報メッセージ」を受け取る

api/index.js
'use strict';

// Load the package
const line = require('@line/bot-sdk');
const express = require('express');
require('dotenv').config();

// Load the module
const ButtonOrErrorMessage = require('./Common/Send/ButtonOrErrorMessage');
const FlexMessage = require('./Common/Send/FlexMessage');

// Read the ports from the process.env file
const PORT = process.env.PORT || 3000;

// Load the access token and channel secret from the .env file
const config = {
  channelAccessToken: process.env.CHANNEL_ACCESS_TOKEN,
  channelSecret: process.env.CHANNEL_SECRET,
};

// Instantiate
const app = express();
const client = new line.Client(config);

// Do routing
// Testing Routing
app.get('/', (req, res) => {
  res.send('Hello World');
});

// API Routing
app.post('/api/line/message', line.middleware(config), async (req, res) => {
  const event = req.body.events[0];
  const eventType = event.message.type;

  // ここでメッセージのタイプを分けてメッセージ内容を分岐させます。
  // これはテキストメッセージです
  if (eventType === 'text') {
    await ButtonOrErrorMessage.SendMessage(client, event);
  }
  // これは位置情報メッセージです
  if (eventType === 'location') {
    await FlexMessage.SendMessage(client, event);
  }
});

// Start the server (Production Environment)
app.listen(PORT, () => {
  console.log('http://localhost:3000');
});
Common/Send/FlexMessage.js
exports.SendMessage = async (client, event) => {
  try {
    const message = // ここでFlexMessageを作成
    const replyToken = event.replyToken;

    client.replyMessage(replyToken, message);
  } catch (err) {
    console.log(err.response.data);
  }
};

⑤「位置情報メッセージ」を受け取ったら、緯度と経度を使って天気予報を取得する

Flex Messageの作成方法に関してファイル名も出しながら説明します。

【ファイル名】GetWeatherForecast

天気予報を取得します。

まずはOpenWeatherで天気予報を取得するために必要な情報が3つあります。
①API
②経度
③緯度
それではこの3つを取得していきましょう。

①API

以下にアクセスしてください。

アカウントを作成し、APIキーを発行してください。

スクリーンショット 2021-05-29 20.28.58.png

発行できたらこのAPIを.envに保存します。

.env
# OpenWeather(https://home.openweathermap.org/api_keys)
WEATHER_API = "a11b22c33d44e55f66g77"

あとは関数内で.envを取得するだけです。

②経度、③緯度

これら2つは、eventから取得できます。

ということで作っていきましょう。

api/Common/Template/WeatherForecast/GetWeatherForecast.js
exports.getData = async (event) => {
  // Load the package
  const axios = require('axios');

  // Get latitude and longitude
  const latitude = event.message.latitude;
  const longitude = event.message.longitude;

  // OpenWeatherAPI
  const openWeatherAPI = process.env.WEATHER_API;

  // OpenWeatherURL
  const openWeatherURL = `https://api.openweathermap.org/data/2.5/onecall?lat=${latitude}&lon=${longitude}&units=metric&lang=ja&appid=${openWeatherAPI}`;

  const weatherData = await axios.get(openWeatherURL);
  return weatherData;
};

【ファイル名】FormatWeatherForecastData

取得した天気予報のデータの整形を行う。

api/Common/Template/WeatherForecast/FormatWeatherForecastData.js
const GetWeatherForecast = require('./GetWeatherForecast');

exports.formatData = async (event) => {
  // Get the weatherData getData
  const weathers = await GetWeatherForecast.getData(event);

  // Util
  const weather = weathers.data.daily[0];

  // Five required data
  // 1) Today's date
  let today = weather.dt;
  today = new Date(today * 1000);
  today = today.toLocaleDateString('ja-JP');

  // 2) Weather forecast
  const weatherForecast = weather.weather[0].description;

  // 3) Temperature (morning, daytime, evening, night)
  const mornTemperature = weather.feels_like.morn;
  const dayTemperature = weather.feels_like.day;
  const eveTemperature = weather.feels_like.eve;
  const nightTemperature = weather.feels_like.night;

  // Bifurcate your clothing by maximum temperature
  const maximumTemperature = Math.max(
    mornTemperature,
    dayTemperature,
    eveTemperature,
    nightTemperature
  );

  // 4) Fashion Advice
  let fashionAdvice = '';

  // 5) Fashion Image
  let imageURL = '';

  if (maximumTemperature >= 26) {
    fashionAdvice =
      '暑い!半袖が活躍する時期です。少し歩くだけで汗ばむ気温なので半袖1枚で大丈夫です。ハットや日焼け止めなどの対策もしましょう';
    imageURL =
      'https://uploads-ssl.webflow.com/603c87adb15be3cb0b3ed9b5/60aa3c44153071e6df530eb7_71.png';
  } else if (maximumTemperature >= 21) {
    fashionAdvice =
      '半袖と長袖の分かれ目の気温です。日差しのある日は半袖を、曇りや雨で日差しがない日は長袖がおすすめです。この気温では、半袖の上にライトアウターなどを着ていつでも脱げるようにしておくといいですね!';
    imageURL =
      'https://uploads-ssl.webflow.com/603c87adb15be3cb0b3ed9b5/6056e58a5923ad81f73ac747_10.png';
  } else if (maximumTemperature >= 16) {
    fashionAdvice =
      'レイヤードスタイルが楽しめる気温です。ちょっと肌寒いかな?というくらいの過ごしやすい時期なので目一杯ファッションを楽しみましょう!日中と朝晩で気温差が激しいので羽織ものを持つことを前提としたコーディネートがおすすめです。';
    imageURL =
      'https://uploads-ssl.webflow.com/603c87adb15be3cb0b3ed9b5/6087da411a3ce013f3ddcd42_66.png';
  } else if (maximumTemperature >= 12) {
    fashionAdvice =
      'じわじわと寒さを感じる気温です。ライトアウターやニットやパーカーなどが活躍します。この時期は急に暑さをぶり返すことも多いのでこのLINEで毎日天気を確認してくださいね!';
    imageURL =
      'https://uploads-ssl.webflow.com/603c87adb15be3cb0b3ed9b5/6056e498e7d26507413fd853_4.png';
  } else if (maximumTemperature >= 7) {
    fashionAdvice =
      'そろそろ冬本番です。冬服の上にアウターを羽織ってちょうどいいくらいです。ただし室内は暖房が効いていることが多いので脱ぎ着しやすいコーディネートがおすすめです!';
    imageURL =
      'https://uploads-ssl.webflow.com/603c87adb15be3cb0b3ed9b5/6056e4de7156326ff560b1a1_6.png';
  } else {
    fashionAdvice =
      '凍えるほどの寒さです。しっかり厚着して、マフラーや手袋、ニット帽などの冬小物もうまく使って防寒対策をしましょう!';
    imageURL =
      'https://uploads-ssl.webflow.com/603c87adb15be3cb0b3ed9b5/6056ebd3ea0ff76dfc900633_48.png';
  }

  // Make an array of the above required items.
  const weatherArray = {
    today,
    imageURL,
    weatherForecast,
    mornTemperature,
    dayTemperature,
    eveTemperature,
    nightTemperature,
    fashionAdvice,
  };

  return weatherArray;
};

【ファイル名】FlexMessageTemplate

整形したデータを取得して Flex Messageのテンプレートを作成する。

api/Common/Template/WeatherForecast/FlexMessageTemplate.js
const FormatWeatherForecastData = require('./FormatWeatherForecastData');

exports.Template = async (event) => {
  const data = await FormatWeatherForecastData.formatData(event);

  return {
    type: 'flex',
    altText: '天気予報です',
    contents: {
      type: 'bubble',
      header: {
        type: 'box',
        layout: 'vertical',
        contents: [
          {
            type: 'text',
            text: data.today,
            color: '#FFFFFF',
            align: 'center',
            weight: 'bold',
          },
        ],
      },
      hero: {
        type: 'image',
        url: data.imageURL,
        size: 'full',
      },
      body: {
        type: 'box',
        layout: 'vertical',
        contents: [
          {
            type: 'text',
            text: `天気は、「${data.weatherForecast}」です`,
            weight: 'bold',
            align: 'center',
          },
          {
            type: 'text',
            text: '■体感気温',
            margin: 'lg',
          },
          {
            type: 'text',
            text: `朝:${data.mornTemperature}℃`,
            margin: 'sm',
            size: 'sm',
            color: '#C8BD16',
          },
          {
            type: 'text',
            text: `日中:${data.dayTemperature}℃`,
            margin: 'sm',
            size: 'sm',
            color: '#789BC0',
          },
          {
            type: 'text',
            text: `夕方:${data.eveTemperature}℃`,
            margin: 'sm',
            size: 'sm',
            color: '#091C43',
          },
          {
            type: 'text',
            text: `夜:${data.nightTemperature}℃`,
            margin: 'sm',
            size: 'sm',
            color: '#004032',
          },
          {
            type: 'separator',
            margin: 'xl',
          },
          {
            type: 'text',
            text: '■洋服アドバイス',
            margin: 'xl',
          },
          {
            type: 'text',
            text: data.fashionAdvice,
            margin: 'sm',
            wrap: true,
            size: 'xs',
          },
        ],
      },
      styles: {
        header: {
          backgroundColor: '#00B900',
        },
        hero: {
          separator: false,
        },
      },
    },
  };
};

⑥「位置情報メッセージ」を受け取ったら、天気予報メッセージを送る

api/Common/Send/FlexMessage.js
const FlexMessageTemplate = require('../Template/WeatherForecast/FlexMessageTemplate');

exports.SendMessage = async (client, event) => {
  try {
    const message = await FlexMessageTemplate.Template(event);
    const replyToken = event.replyToken;

    client.replyMessage(replyToken, message);
  } catch (err) {
    console.log(err.response.data);
  }
};

これで完成です!
めちゃくちゃ簡単ですね。
curlコマンドなどを叩かずとも簡単に実装できますね。

最後にデプロイをしましょう

前述しましたが今回はデプロイはGlitchを使います。

アカウントは、Githubで作るのがおすすめです。
作成しましたら、プロジェクトを作成します。
**「import from GitHub」**をクリックします。

スクリーンショット 2021-06-14 21.26.24.png

ここには、GithubのURLを貼り付けます。
スクリーンショット 2021-06-14 21.27.50.png

ちょっと待つとこのように読み込まれます。
便利なのは全てのファイルが確認できるところです。
HerokuなどはどちらかというとCUIであり、GUIのGlitchは直感的に操作できてすごく良かったです。

スクリーンショット 2021-06-14 21.28.54.png

最後に.envに値を入力します。

スクリーンショット 2021-06-14 21.31.17.png

ここまで行えばデプロイは成功です!

スクリーンショット 2021-06-14 21.33.06.png

ちなみに

URL変えたいときはここをいじってください

スクリーンショット 2021-06-14 21.33.34.png

ShareボタンをクリックすればURLがLive siteに書いているよ

スクリーンショット 2021-06-14 21.34.22.png

Webhookの設定を変更

スクリーンショット 2021-06-14 21.36.35.png

これで完成です!

最後に

Node.js便利だなぁ。

image.png

今後は、TypeScriptを使ったパターンやAWSへのデプロイなどを記事にしてみます。

20
23
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
20
23

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?