Help us understand the problem. What is going on with this article?

AWS 公式提供のaws-serverless-express を使ってLINE Bot 開発環境を作ってみた

More than 1 year has passed since last update.

はじめに

昨年末に初めてAWS Lambda 環境でLINE Bot を開発しました
簡単なBot を作るだけであれば、Lambda を素で使っても問題ありませんが、LINE Login など他のWEB サービスからの呼び出しを受け付けて動くような少し複雑なBot を作ろうと思うと、ちょっと辛くなってきます。

Lambda 環境でNode.js のexpress のようなフレームワークが使えないかと思って検索していると、こちらの記事を見つけました。

記事によると、Lambda でexpress が使えるコードがAWS 公式で(!)提供されているとドンピシャの内容でしたので、そうならば使ってみるしかありません。

以下、Mac OSX 環境で構築・開発したものとなります。

AWS 環境の準備

AWS 公式コードの取得

GitHub で公開されているAWS の公式コードを取得して、開発用コードにします。

$ git clone https://github.com/awslabs/aws-serverless-express.git
$ cp -r ./aws-serverless-express/example ~/aws/samplebot-express
$ cd ~/aws/samplebot-express

アプリ設定

公式コードのREADME.md の手順に従って開発するアプリを設定します。
事前にS3のバケット(コードのデプロイ用)作成と、AWS CLI をインストールしておく必要があります。

$ cd ~/aws/samplebot-express
$ npm run config -- --account-id="<accountID>" --bucket-name="<bucketName>"  --region="<region>" --function-name="<functionName>"
パラメータ 意味
accountID AWS のアカウントIDです。AWS コンソールのアカウント画面で確認できます。
bucketName S3 のバケット名です。このバケットはコードのデプロイだけに使われます。
region 作成するLambda アプリ が稼働するリージョン。東京リージョンならap-northeast-1 ですね。
functionName 作成するLambda アプリ名です。
$ cd ~/aws/samplebot-express
$ npm run config -- --account-id="xxxxxxxxxxx" --bucket-name="samplebot-express-bucket"  --region="ap-northeast-1" --function-name="samplebot-express"

AWS へのアプリ登録

このコマンドだけでLambda やAPIGateway の登録ができてしまいます。
いや~楽チンですね。

$ npm run setup

コマンド実行後、CloudFormation 画面で、該当のCloudStack のStatus が”CREATE_COMPLETE”になれば登録完了です。

動作確認

これでサンプルアプリが動く状態になったので、さっそく動作確認してみましょう。
該当のCloudStack のOutputs にある「ApiUrl」のURL にアクセスします。
CloudStack のOutputs にある「ApiUrl」

ちゃんと動いてますね〜!
RESTful API な感じで動いています。
AWSServerlessExpress.png

LINE アカウントの準備

AWS の方がひととおり動いたので、次はLINE Bot を動かすために必要なLINE アカウントを取得・設定します。

アカウント準備

LINE Bot を動かすためにはLINE Business アカウント取得などをしておく必要があります。
アカウント準備のやり方もこの記事に書こうかと思ったのですが、こちらの記事が分かりやすかったので、こちらを参照して設定していただければよいです。

"Channel Secret"と"Channel Access Token"の設定

LINE Bot 実行に必要な"Channel Secret"と"Channel Access Token"を取得して、Lambda の環境変数に設定しておきます。

"Channel Secret"と"Channel Access Token"の取得

"Channel Secret"と"Channel Access Token"は、LINE developers の画面から取得できます。
Channel SecretとChannel Access Tokenを取得
"Channel Secret"は画面上部にある「SHOW」ボタンを押して表示されたものを、"Channel Access Token"は画面下部の「ISSUE」ボタンを押して発行されたものを控えておきます。

Lambda アプリに環境変数の設定する

Lambda アプリの環境変数に先ほど取得した"Channel Secret"と"Channel Access Token"を設定します。
Lambda コンソールの”Code”欄下部に環境変数を設定する箇所があるのでここに追記して、画面上部の「Save」ボタンを押して設定を反映します。
(「Save」ボタンを押さずに後述の”メッセージ送信元の検証”で引っかかったのはナイショです ;-))
Lambda コンソール
それぞれ、以下の変数名で設定しました。

  • Channel Secret:LINE_CHANNEL_SECRET
  • Channel Access Token:LINE_CHANNEL_ACCESS_TOKEN

Webhook URL の設定

LINE でBot 宛にメッセージが来た場合や、「お友だち追加」時に実行されるWebhook URL を設定します。
AWS へのLambda アプリ登録時にCloudFormation で構築されたAPI Gateway のURL をLINE developers サイトに登録します。
今回はAPI Gateway へ構築済みURL 配下の"webhook/"パスがLINE から実行されるように設定します。

さきほど"Channel Secret"などを取得したLINE developers サイトの画面から「EDIT」ボタンを押してWebhook URL を入力します。
Webhook URL を入力

Bot アプリの開発

LINE アカウントの準備ができたので、次はLambda 上で動かすBot アプリを開発します。
今回は送信されたテキストメッセージの語尾に”でんがな!”を付けて返すだけのBot を作ります。

node.js のバージョン

node.js のバージョンはLambda と同じバージョン(v4.3.2)にしておきます。

node.jsのバージョン
$ node -v
v4.3.2

LINE Messaging API 用SDK のインストール

node.js 用のLINE Messaging API 公式SDK は残念ながら提供されていないため、今回は非公式SDK 「node-line-bot-api」を使っていきます。
node-line-bot-api」はnpm でインストールできます。

$ npm install --save node-line-bot-api
node-line-bot-api@0.1.2 node_modules/node-line-bot-api
└── superagent@2.3.0 (methods@1.1.2, component-emitter@1.2.1, cookiejar@2.1.0, extend@3.0.0, mime@1.3.4, formidable@1.1.1, qs@6.3.0, debug@2.6.0, readable-stream@2.2.2, form-data@1.0.0-rc4)

LINE Messaging API 用SDK の初期化

ここからコードを書いていきます。
app.js の冒頭でSDK「node-line-bot-api」をrequire して、SDK の初期化時にLINE アカウントを設定した際に取得した"Channel Secret"と"Channel Access Token"を渡します。

app.js(一部)
'use strict'
const express = require('express');
const line = require('node-line-bot-api');
const bodyParser = require('body-parser');
const cors = require('cors');
const awsServerlessExpressMiddleware = require('aws-serverless-express/middleware');
const app = express();

// LINE BOT SDK 初期化
line.init({
  accessToken: process.env.LINE_CHANNEL_ACCESS_TOKEN,
  channelSecret: process.env.LINE_CHANNEL_SECRET
});
…(後略)

なお、Lambda に設定した環境変数は「process.env.${変数名}」で取得できます。
各種API のトークン値などを環境変数に設定しておけば開発用/本番用も簡単に切り分けられますね。

LINE メッセージ送信元の検証

LINE Messaging API の公式ページにもあるとおり、Webhook 受信後に送信元が正しいかを検証する必要があります。
本来はWebhook リクエストヘッダにある署名「X-Line-Signature」値と、リクエストボディとHMAC-SHA256アルゴリズムなどを基に計算した値とを比較して、正しい送信元であるかを検証する必要がありますが、こちらは今回使うSDK「node-line-bot-api」に検証機能が実装されているので、こちらを使います。

app.js(一部)
…(前略)
// 送信元の検証(X-Line-Signature 署名検証)にrawBodyが必要
// @see https://devdocs.line.me/ja/?shell#webhooks
app.use(bodyParser.json({
    verify(req, res, buf) {
        req.rawBody = buf;
    }
}));

app.post('/webhook/', 
         line.validator.validateSignature(),    // <-- 送信元の検証(X-Line-Signature 署名検証)
         function(req, res) {
…(後略)

LINE メッセージ受信時の動作を作る

AWS 公式コードのapp.js にはサンプルコードが残っているので、今回使わない箇所は削除して、LINE webhook 用のPOST リクエスト受信部分を作ります。

app.js(一部)
…(前略)
/**
* LINE からのWebhook 受信時に呼ばれる
* LINE developers の「Webhook URL」に設定したURL
* URL:https://xxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/prod/webhook/
*
* @see https://devdocs.line.me/ja/#webhook-event-object
*/
app.post('/webhook/', line.validator.validateSignature(), function(req, res) {
    console.log('LINE message Webhook called!!');

    // 受信したメッセージイベントを処理
    const promises = req.body.events.map(function(event) {
        let promise = null;
        if ('message' === event.type) {
            promise = handleMessageEvent(event);
        }
        return promise;
    });

    // メッセージごとの処理はPromiseで実行
    Promise
        .all(promises)
        .then(function(value) {
            // 処理が全て正常終了すれば、HTTP STATUS CODE 200を返す
            return res.json({success: true}); })
        .catch(function(error) {
            // 異常終了があれば、HTTP STATUS CODE 500を返す
            console.log('Error!: ', error);
            return res.status(500).json({});
        });
});

/**
* メッセージ受信時に呼ばれる
* @param event  {JSON}  Message Event
* @return   Promise(Reply message)
*
* @see https://devdocs.line.me/ja/#webhook-event-object
* @see https://devdocs.line.me/ja/#reply-message
*/
function handleMessageEvent(event)
{
    console.log('handleMessageEvent function called!!');
    const messageType = event.message.type;

    let promise = null;
    if ('text' === messageType) {
        // テキストメッセージであれば、メッセージの語尾に”でんがな!”だけ付けて返す
        promise = line.client.replyMessage({
            replyToken: event.replyToken,
            messages: [{
                type: 'text',
                text: event.message.text + 'でんがな!'
            }]
        });
    } else if ('location' === messageType || 'sticker' === messageType) {
        // 位置情報とスタンプ(公式スタンプのみ)は、そのまま返す
        promise = line.client.replyMessage({
            replyToken: event.replyToken,
            messages: [event.message]
        });
    } else {
        // それ以外は、適当なメッセージを返す
        promise = line.client.replyMessage({
            replyToken: event.replyToken,
            messages: [{
                type: 'text',
                text: 'それはあかんでぇ…'
            }]
        });
    }
    return promise;
}
…(後略)

AWS へのアップロード

簡単ですがBot アプリをつくったのでAWS へアップロードします。
素のままアップロードする場合、アプリリソースをZip ファイルに固めてアップロードする必要がありますが、今回の場合はたったひとつのコマンドでアップロード出来るようにpackage.json で設定されています。
素晴らしいですねぇ。

$ npm run package-upload-update-function

いざ実行!

これでひととおりできたので実行してみましょう!
LINE アプリでBot を友だち追加して、Bot とのトーク画面でメッセージを送るだけです。

動きました!
SampleBot01.png
テキストメッセージの場合は”でんがな!”が末尾について、位置情報とスタンプの場合はそのまま返信できていますね。

SampleBot02.png
画像の場合は適当なテキストメッセージを返信できています :-)

こんな感じで簡単にサーバーレスなLINE Bot を開発できます。素晴らしく簡単ですねぇ。

その他ハマったこと

この公式コードを使ってアプリを作っていると幾つかハマったことがあったので載せておきます。

アプリにファイルを追加する場合

AWSへのアップロードするタスクがpackage.json で設定されていますが、サンプルで準備されているファイル/ディレクトリしか記載されていないので、新たにjs ファイルを追加した場合はpackage.json の「package-function」(Zip ファイルを作る)タスクに追記する必要があります。

package.json(一部)
// 変更前
// "package-function": "zip -q -r lambda-function.zip lambda.js app.js index.html node_module",
// 
// 変更後 末尾に「lib」ディレクトリを追加
"package-function": "zip -q -r lambda-function.zip lambda.js app.js index.html node_modules lib",

AWS 公式コードを使って複数のアプリを作成する場合

複数のexpress アプリを登録する場合、そのままでは複数登録できないので、次の設定をアプリごとに変えておきます。

package.json

ファイルの8行目付近にある"cloudFormationStackName" の値を変えておく必要があります。
"cloudFormationStackName" がアカウント内で重複すると登録できません。

package.json(一部)
 {
   "name": "aws-serverless-express-example",
   "version": "1.1.0",
   "description": "Example application for running a Node Express app on AWS Lambda using Amazon API Gateway.",
   "main": "lambda.js",
   "config": {
     "s3BucketName": "samplebot-express-bucket",
     "cloudFormationStackName": "AwsServerlessExpressStackSampleBot", // <-- ここ
     "region": "ap-northeast-1"
   }

simple-proxy-api.yaml

ファイルの4行目付近にある "title" を変えておくとAPI Gateway のリソース名称が変わるので区別がつきやすいです。

simple-proxy-api.yaml(一部)
 ---
 swagger: 2.0
 info:
   title: SampleBotExpressApi // <-- ここ
 basePath: /prod

おわりに

こんなにも簡単にWeb アプリを作ることが出来るなんて、ほんと良い世の中になりましたねぇ(遠い目

ちなみに、今回紹介した環境を使って「ホテルBot」というLINE Bot を開発しています。
別の記事にも掲載しましたが、Bot とのトークで”札幌で明日から2泊、一万円以下の禁煙ルームで朝食あり” のようにテキストメッセージで条件指定するだけで、条件にあったホテルの検索結果を返してくれ、そのまま予約までできるLINE Botです。
出張が多く頻繁にホテル予約をするので、これからも使い勝手がよくなるように改良していきたいと思っています。
私のように出張族の方や、旅行好きな方は是非お試しください。

LINE アプリで下のQRコードを読み取って、Bot を友だち追加すればすぐに使えます。

このページをスマートフォンで見ている場合は、下のボタンをタップしてLINE アプリで「友だち追加」してください。
友だち追加

それでは皆さんもよいLINE Bot 開発を!

参考記事など

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away