Quantcast
Channel: Node.jsタグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 8945

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

$
0
0
LINE MessageAPIを使って天気予報・ファッションレコメンドアプリを作ってみました。 完成形としては以下の通りです。 以前Laravelでも同様のアプリを作成しました。 そちらの模様はこちらからどうぞ。 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に入力します。 .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サーバーの実行 おまじないだけで片付けるのもアレなので公式サイトを貼っておきます。 公式サイトが神なのでこれを見れば簡単に作れると思います。 また、今回デプロイ先で使う、Glitchでscriptsにstartがないといけないのでこの辺りで追加しておきましょう。(読み方は知らん。グリッチでしょうかね。) 開発効率を上げるために、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つ使って開発していきます。 こんな感じです。 コマンドはこの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ディレクトリに関数に切り分けて作成していきます。 ①「今日の洋服は?」というメッセージを受け取る 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キーを発行してください。 発行できたらこの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」をクリックします。 ここには、GithubのURLを貼り付けます。 ちょっと待つとこのように読み込まれます。 便利なのは全てのファイルが確認できるところです。 HerokuなどはどちらかというとCUIであり、GUIのGlitchは直感的に操作できてすごく良かったです。 最後に.envに値を入力します。 ここまで行えばデプロイは成功です! ちなみに URL変えたいときはここをいじってください ShareボタンをクリックすればURLがLive siteに書いているよ Webhookの設定を変更 これで完成です! 最後に Node.js便利だなぁ。 今後は、TypeScriptを使ったパターンやAWSへのデプロイなどを記事にしてみます。

Viewing all articles
Browse latest Browse all 8945

Trending Articles