これは何?
以下の記事を参考にLINE BOTを作ってみたので、手を加えてみたところ・詰まったところなどを記します。
基本的にはこの記事の通りで、少しだけ変えたりしました。
ありがとうございます。楽しく作れました。
1時間でLINE BOTを作るハンズオン (資料+レポート) in Node学園祭2017 #nodefest
一番最後にコードをそのまま載せてます。
作ったBOT
-
天気を答える
「今日の天気」「明日の天気」「明後日の天気」のワードで、ライブドアの天気予報APIから情報を取得して返してくれる。 -
気温を答える
「今日の気温」「明日の気温」「明後日の気温」のワードで、ライブドアの天気予報APIから情報を取得して返してくれる。 -
イメージ

構成要素
- LINE Developer
- Node.js
- Now(PaaS)
- API(Weather Hacks > お天気Webサービス)
やったこと
だいたいの手順
BOTとトークできるようになるまでは冒頭で引用させていただいた記事のまんまです。
1時間でLINE BOTを作るハンズオン (資料+レポート) in Node学園祭2017 #nodefest
この通りにやれば、Nowにデプロイできる環境になっていると思います。
手を加えてみたところ・詰まったところ
BOTを1:1のトークだけでなくルーム、グループにも対応させたい
-
困ったこと
例えば友達とのトークルームで「今日の天気」と言った場合、BOTはそのトークルームではなく、「発言者に対して1:1で」メッセージを送ってくる。 -
原因
BOTはメッセージの「発言者(userId
)」を宛先としてリプライのメッセージを送っている。 -
対応
発信元がユーザーの場合はユーザー、トークルームの場合はトークルーム、グループの場合はグループにリプライするようにした。
具体的にはリクエストの中のsource.type.
以下に発信元を表す情報が入っているので、こういう感じで送り返すべき相手を判別することにした。
let tempId = ''
if (event.source.type === 'user') {
tempId = event.source.userId;
} else if(event.source.type === 'room') {
tempId = event.source.roomId;
} else if(event.source.type === 'group'){
tempId = event.source.groupId;
}
evene.source.type === user
ならuserId
が、room
ならroomId
が、group
ならgroupId
が設定されてくる。
天気予報APIから返ってきたJSONでnullが入ってる時がある
-
困ったこと
「今日の気温」は正しく返ってきても、「明日の気温」が返ってこなかったりする。(日による) -
原因
APIから返却されるJSONの気温データがnull
だったりすることがある。
API(Weather Hacks > お天気Webサービス)の下の方にJSONのサンプルを載せてもらえているのですが、forecasts[0].temperature.max
は設定されているのに対し、forecasts[0].temperature.min
はnull
になっています。
更なる原因はともかく、気温は場合によってはデータが無い時があるようです。その時は当然、欲しかったcelsius
は取得できません。 -
対応
素直にnullチェック
した。(もっと楽にできそうですが…)
if(item.forecasts[2].temperature.max == null){
client.pushMessage(tempId, {
type: 'text',
text: "最高気温のデータが無いみたいです",
});
} else {
client.pushMessage(tempId, {
type: 'text',
text: "最高" + item.forecasts[2].temperature.max.celsius + "度",
});
}
Nowでnowした後の公開URLを固定する
-
困ったこと
Nowではデプロイ(now
コマンド)する度に一意の公開URLが生成されるので、LINE Developer側で設定し直すのが面倒。 -
対応
now.json
をserver.js
と同じ階層に作成し、alias
を設定し、now
でデプロイした後にnow alias
すれば、公開URLを一意にできる。
上の画像ではmatch3-bot2-pdaxurecfi.now.sh
がデプロイ先だが、now alias
でhttps://match3-bot2.now.sh
を公開URLとして使えるようになる。
LINE側にはこのURLを設定してやれば、後は修正してデプロイしてもURLは変更しなくてもOKになる。
所感
-
LINE Developer
簡単に作れるので、もう一つくらいBOT作ってみたい。 -
Now
デプロイ早くて使いやすい。フリープランでも十分いろいろできそう。VSCodeからコマンドでサクッと済むので良いです。
コード
最後に、全くリファクタリングしてませんがコードをそのまま載せておきます。
- now.json
- package.json
- server.js
now.json
{
"version": 1,
"alias": "match3-bot2"
}
package.json
{
"name": "match3-bot2",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "node server.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@line/bot-sdk": "^6.4.0",
"axios": "^0.18.0",
"express": "^4.16.4"
}
}
server.js
'use strict';
const express = require('express');
const line = require('@line/bot-sdk');
const axios = require('axios');
const PORT = process.env.PORT || 3000;
const config = {
channelSecret: 'ここには自身のmassaging APIのものを入れる',
channelAccessToken: 'ここには自身のmassaging APIのものを入れる'
};
const app = express();
app.post('/webhook', line.middleware(config), (req, res) => {
console.log(req.body.events);
Promise
.all(req.body.events.map(handleEvent))
.then((result) => res.json(result));
});
const client = new line.Client(config);
function handleEvent(event) {
if (event.type !== 'message' || event.message.type !== 'text') {
return Promise.resolve(null);
}
let tempId = ''
if (event.source.type === 'user') {
tempId = event.source.userId;
} else if(event.source.type === 'room') {
tempId = event.source.roomId;
} else if(event.source.type === 'group'){
tempId = event.source.groupId;
}
let mes = ''
if(event.message.text === '今日の天気'){
mes = '待っててね';
getTodayForecast(tempId);
}else if(event.message.text === '明日の天気'){
mes = '待っててね';
getTommorowForecast(tempId);
}else if(event.message.text === '明後日の天気'){
mes = '待っててね';
getDayAfterTommorowForecast(tempId);
}else if(event.message.text === '今日の気温'){
mes = '待っててね';
getTodayTemperature(tempId);
}
else if(event.message.text === '明日の気温'){
mes = '待っててね';
getTommorowTemperature(tempId);
}else if(event.message.text === '明後日の気温'){
mes = '待っててね';
getDayAfterTommorowTemperature(tempId);
}
return client.replyMessage(event.replyToken, {
type: 'text',
text: mes
});
}
const getTodayForecast = async (tempId) => {
const res = await axios.get('http://weather.livedoor.com/forecast/webservice/json/v1?city=130010');
const item = res.data;
await
client.pushMessage(tempId, {
type: 'text',
text: item.title,
});
client.pushMessage(tempId, {
type: 'text',
text: item.forecasts[0].telop,
});
}
const getTommorowForecast = async (tempId) => {
const res = await axios.get('http://weather.livedoor.com/forecast/webservice/json/v1?city=130010');
const item = res.data;
await
client.pushMessage(tempId, {
type: 'text',
text: item.title,
});
client.pushMessage(tempId, {
type: 'text',
text: item.forecasts[1].telop,
});
}
const getDayAfterTommorowForecast = async (tempId) => {
const res = await axios.get('http://weather.livedoor.com/forecast/webservice/json/v1?city=130010');
const item = res.data;
await
client.pushMessage(tempId, {
type: 'text',
text: item.title,
});
client.pushMessage(tempId, {
type: 'text',
text: item.forecasts[2].telop,
});
}
const getTodayTemperature = async (tempId) => {
const res = await axios.get('http://weather.livedoor.com/forecast/webservice/json/v1?city=130010');
const item = res.data;
if(item.forecasts[0].temperature.max == null){
client.pushMessage(tempId, {
type: 'text',
text: "最高気温のデータが無いみたいです",
});
} else {
client.pushMessage(tempId, {
type: 'text',
text: "最高" + item.forecasts[0].temperature.max.celsius + "度",
});
}
if(item.forecasts[0].temperature.min == null){
client.pushMessage(tempId, {
type: 'text',
text: "最低気温のデータが無いみたいです",
});
} else {
client.pushMessage(tempId, {
type: 'text',
text: "最低" + item.forecasts[0].temperature.min.celsius + "度",
});
}
}
const getTommorowTemperature = async (tempId) => {
const res = await axios.get('http://weather.livedoor.com/forecast/webservice/json/v1?city=130010');
const item = res.data;
if(item.forecasts[1].temperature.max == null){
client.pushMessage(tempId, {
type: 'text',
text: "最高気温のデータが無いみたいです",
});
} else {
client.pushMessage(tempId, {
type: 'text',
text: "最高" + item.forecasts[1].temperature.max.celsius + "度",
});
}
if(item.forecasts[1].temperature.min == null){
client.pushMessage(tempId, {
type: 'text',
text: "最低気温のデータが無いみたいです",
});
} else {
client.pushMessage(tempId, {
type: 'text',
text: "最低" + item.forecasts[1].temperature.min.celsius + "度",
});
}
}
const getDayAfterTommorowTemperature = async (tempId) => {
const res = await axios.get('http://weather.livedoor.com/forecast/webservice/json/v1?city=130010');
const item = res.data;
if(item.forecasts[2].temperature.max == null){
client.pushMessage(tempId, {
type: 'text',
text: "最高気温のデータが無いみたいです",
});
} else {
client.pushMessage(tempId, {
type: 'text',
text: "最高" + item.forecasts[2].temperature.max.celsius + "度",
});
}
if(item.forecasts[2].temperature.min == null){
client.pushMessage(tempId, {
type: 'text',
text: "最低気温のデータが無いみたいです",
});
} else {
client.pushMessage(tempId, {
type: 'text',
text: "最低" + item.forecasts[2].temperature.min.celsius + "度",
});
}
}
app.listen(PORT);
console.log(`Server running at ${PORT}`);