LINE MessageAPIを使って天気予報・ファッションレコメンドアプリを作ってみました。
完成形としては以下の通りです。
以前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は使わないぞと心に決めました。
ということで本題に入っていきましょう。
どのようなアプリか
皆さんは、今日の気温を聞いて、**「快適に過ごすために今日のファッションをこうしよう」**ってパッと思いつきますか?
私は、最高気温、最低気温を聞いてもそれがどのくらい暑いのか、寒いのかがピンと来ず、洋服のチョイスを外したことがしばしばあります。
こんな思いを2度としないために今回このアプリを作りました。
Laravelで同様のアプリケーションを作成していたので所要時間としては、4時間程度です。
ほぼほぼ難しいところもないので、初心者でも簡単に作成できるかと思います。
アプリの流れ
アプリの流れは大まかに以下の4つのステップで成り立っています。
・①クライアントが現在地を送る
・②OpenWeatherから天気予報を取得
・③データの整形
・④クライアントに送る
GitHub
完成形のコードは以下となります。
では実際に作成していきましょう!
LINE Developers
にアカウントを作成する
LINE Developersにアクセスして、「ログイン」ボタンをクリックしてください。
その後諸々入力してもらったら以下のように作成できるかと思います。
注意事項としては、今回Messaging API
となるので、チャネルの種類を間違えた方は修正してください。
チャネルシークレットとチャネルアクセストークンが必要になるのでこの2つを発行します。
ではこの2つを.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
も作成しておきましょう。
node_modules
package-lock.json
.env
https://localhost:3000
にアクセスするとhello world
が表示
APIサーバーが正しく動くか検証のため一応作っておきましょう。
'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サーバーの実行
おまじないだけで片付けるのもアレなので公式サイトを貼っておきます。
公式サイトが神なのでこれを見れば簡単に作れると思います。
また、今回デプロイ先で使う、Glitch
でscripts
にstartがないといけないのでこの辺りで追加しておきましょう。(読み方は知らん。グリッチでしょうかね。)
開発効率を上げるために、nodemon
も使えるようにしておきます。
{
"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つ使って開発していきます。
こんな感じです。
コマンドはこの2つです。
$ npm run dev
$ ssh -R 80:localhost:3000 localhost.run
どうでもいいですが最近ターミナルをおしゃれにしました!
この記事がとても参考になりました。
Webhook URLの登録
localhost.run
で作成したhttpsのURLをコピーしてください。
私の場合は以下のURLです。
これをLINE DevelopersのWebhookに設定します。
これで初期設定は完了です。
ここからの流れはこのような感じです。
①「今日の洋服は?」というメッセージを受け取る
②「今日の洋服は?」を受け取ったら、位置情報メッセージを送る
③「今日の洋服は?」以外を受け取ったら、「そのメッセージには対応していません」と送る
④「位置情報メッセージ」を受け取る
⑤「位置情報メッセージ」を受け取ったら、緯度と経度を使って天気予報を取得する
⑥「位置情報メッセージ」を受け取ったら、天気予報メッセージを送る
では作っていきましょう!
またこれら全てのコードをapi/index.js
に書くとコードが肥大化し可読性が落ちます。
なのでCommon
ディレクトリに関数に切り分けて作成していきます。
①「今日の洋服は?」というメッセージを受け取る
'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');
});
exports.SendMessage = (client, event) => {
const text = event.message.text;
const replyToken = event.replyToken;
if (text === '今日の洋服は?') {
// ここで位置情報メッセージを送る
} else {
// ここで「そのメッセージには対応していません」と送る
}
};
一見、**「なんだこれ・・普通に難しそうじゃねーか!」**と思うかもしれませんが、ほぼ公式サイトをコピペです。
公式サイトが優秀すぎました。
console.log(req.body.events)
でデバッグすればこれだけで簡単に作れると思います。
②「今日の洋服は?」を受け取ったら、位置情報メッセージを送る
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/',
},
],
},
};
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作成に関しては公式サイトを参考にしましょう。
③「今日の洋服は?」以外を受け取ったら、「そのメッセージには対応していません」と送る
exports.Template = () => {
return {
type: 'text',
text: 'ごめんなさい、このメッセージは対応していません。',
};
};
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作成に関しては公式サイトを参考にしましょう。
④「位置情報メッセージ」を受け取る
'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');
});
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キーを発行してください。
発行できたらこのAPIを.env
に保存します。
# OpenWeather(https://home.openweathermap.org/api_keys)
WEATHER_API = "a11b22c33d44e55f66g77"
あとは関数内で.env
を取得するだけです。
②経度、③緯度
これら2つは、eventから取得できます。
ということで作っていきましょう。
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
取得した天気予報のデータの整形を行う。
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のテンプレートを作成する。
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,
},
},
},
};
};
⑥「位置情報メッセージ」を受け取ったら、天気予報メッセージを送る
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」**をクリックします。
ちょっと待つとこのように読み込まれます。
便利なのは全てのファイルが確認できるところです。
Heroku
などはどちらかというとCUIであり、GUIのGlitch
は直感的に操作できてすごく良かったです。
最後に.env
に値を入力します。
ここまで行えばデプロイは成功です!
ちなみに
URL変えたいときはここをいじってください
Share
ボタンをクリックすればURLがLive site
に書いているよ
Webhookの設定を変更
これで完成です!
最後に
Node.js便利だなぁ。
今後は、TypeScriptを使ったパターンやAWSへのデプロイなどを記事にしてみます。