素人が2020年までの1ヶ月でLINE BOTに挑戦する毎日note. 【Day 24:ここまでに作ったサンプルコードの解説 part 2】


こんにちは!
12月1日から2019年残り1ヶ月でスケジュール調整BOTの開発に挑戦している"くろ"です。

BOTの概要:Day 2の記事

BOTの仕様:機能一覧やフローチャート Day 10の記事

今日はDay17で作ったサンプルコード(herokuにデプロイしているやつ)の解説のpart 2です。

index.jsというファイルですね。

const express = require('express')
const path = require('path')
const PORT = process.env.PORT || 5000
const line = require("@line/bot-sdk");


const config = {
 channelAccessToken: process.env.ACCESS_TOKEN,
 channelSecret: process.env.SECRET_KEY
};
const client = new line.Client(config); // 追加


express()
 .use(express.static(path.join(__dirname, 'public')))
 .set('views', path.join(__dirname, 'views'))
 .set('view engine', 'ejs')
 .get('/', (req, res) => res.render('pages/index'))
 .get('/g/', (req, res) => res.json({method: "こんにちは、getさん"})) // 追加
 .post('/p/', (req, res) => res.json({method: "こんにちは、postさん"})) // 追加
 .post("/hook/", line.middleware(config), (req, res) => lineBot(req, res)) // 変更、middlewareを追加
 .listen(PORT, () => console.log(`Listening on ${ PORT }`))

 // 追加
function lineBot(req, res) {
 res.status(200).end(); //変更
 const events = req.body.events;
 const promises = [];
 for (let i = 0, l = events.length; i < l; i++) {
   const ev = events[i];
   promises.push(
     echoman(ev)
   );
 }
 Promise.all(promises).then(console.log("pass")); // 変更
}

// 追加
async function echoman(ev) {
 const pro =  await client.getProfile(ev.source.userId);
 return client.replyMessage(ev.replyToken, {
   type: "text",
   text: `${pro.displayName}さん、今「${ev.message.text}」って言いました?`
 })
}

ちなみに、昨日の記事ではこの辺の解説をしていましたが、今日インプットを進めていて頭の中がスッキリしたので、昨日の記事に追記しています。

ここにも書いておきます。

 .get('/', (req, res) => res.render('pages/index'))
 .get('/g/', (req, res) => res.json({method: "こんにちは、getさん"})) // 追加
 .post('/p/', (req, res) => res.json({method: "こんにちは、postさん"})) // 追加

めちゃくちゃ簡単に言うと、こいつらは

http://hogehoge.com/YYYというURLにアクセスした時にする処理を書いています。YYYに応じたリクエストに対するレスポンスを記述している場所です。

つまり、

'/'はYYYがなにもない時、つまりhttp://hogehoge.comにアクセスした時

'g'はYYYがgの時、つまりhttp://hogehoge.com/gにアクセスした時

'p'はYYYがgの時、つまりhttp://hogehoge.com/pにアクセスした時

にそれぞれ設定された関数を呼び出して処理を行うという記述です。

ここを読んでいて理解できました。

ここでやっと、こういうURLをぐるぐる回していけば、アプリケーションになりそうだなと思いました。

3段落目の最後から2番目の行


 .post("/hook/", line.middleware(config), (req, res) => lineBot(req, res)) // 変更、middlewareを追加

ここもpostメソッドで、hookというパスを要求して、その後に、line.middleware(config)という関数と、 (req, res) => lineBot(req, res))という関数を実行しています。

LINEBOTでユーザーがメッセージを送った時に飛んでくるHTTPプロトコルを利用した情報(webhook)をPOSTメソッドを使って取得した上で、その処理をしている所だと思います。

今のオウム返しBOTの根幹ですね。

line.middleware(config)という関数は送られてきたwebhookが、ちゃんと該当のLINEBOTから飛んできているのか?を暗号キーとかを使って認証している部分です。

configは2段落目で定義しています。

const config = {
 channelAccessToken: process.env.ACCESS_TOKEN,
 channelSecret: process.env.SECRET_KEY
};
const client = new line.Client(config); // 追加

1~4行目で、configという文字列にアクセストークンを格納しています。

5行目で、configというアクセストークンが入った箱をnew line.Client()という関数に渡して実行するという処理をclientという文字列に格納しています。これは5段落目のBOTがメッセージを返したりする時に使っていました。

4段落目はBOTのメインの関数です。

function lineBot(req, res) {
 res.status(200).end(); //変更
 const events = req.body.events;
 const promises = [];
 for (let i = 0, l = events.length; i < l; i++) {
   const ev = events[i];
   promises.push(
     echoman(ev)
   );
 }
 Promise.all(promises).then(console.log("pass")); // 変更
}

関数を宣言しているのが1行目です。

2行目は下記の記事に書いているように、すぐに応答を返さないと他の処理が詰まってしまうために、処理を始めるまえに200番を返しているのだと思います。

イベントが発生するとWebhookを使って通知されます。応答できるイベントには応答トークンが発行されます。応答トークンは一定の期間が経過すると無効になるため、メッセージを受信したらすぐに応答を返す必要があります。応答トークンは1回のみ使用できます。

ボットサーバーにWebhookから送信されるHTTP POSTリクエストには、ステータスコード200を返す必要があります。

https://qiita.com/TakuTaku04/items/cb71f10669a9e9cbf71b#%E3%81%95%E3%81%A3%E3%81%A8200%E7%95%AA%E3%82%92%E8%BF%94%E3%81%99

3行目はeventsにリクエストのwebhookのボディを配列として格納しているっぽい。

5~10行目は実際に行う処理を書いていて、events.lengthはevents配列の要素の数を表していて、配列を最初から最後まで順番に読み込んでねというループ処理の中に、promises.push(echoman(ev)という関数が入っています。

echoman(ev)は5段落目で定義されている関数です。

promisesはこちらに記載がありましたが、結構難しいことを書いているので、echomanを非同期処理(順番にやらずに準備ができ次第処理を実行する)でやるために書いていると思っておけば大丈夫だと思います。

promisesをつけないと、ループ処理をしている中で、BOTの返答をechoman(ev)が処理しているのを待たないと次に進めないから大変ってことかなと。

最後の行は多分処理が終わったよっていうのをconsole.logで出していると思います。

5段落目はオウム返しBOTの関数です。

async function echoman(ev) {
 const pro =  await client.getProfile(ev.source.userId);
 return client.replyMessage(ev.replyToken, {
   type: "text",
   text: `${pro.displayName}さん、今「${ev.message.text}」って言いました?`
 })
}

2行目はevents内のuserIDを取得しています。

3~5行目でリプライメッセージを送ってます。

client.replyMessage('<replyToken>', message)という形で、第一引数にリプライトークンを入れて、第ニ引数にメッセージの内容を入れれば送れます。

以上がHeroku+Express+node.jsでのオウム返しBOTのサンプルの解説でした。

よろしければサポートお願いします! 頂いたサポートはクリエイター活動に活用させて頂きます。