AWS Lambdaを使ってサーバレスでクイズができるLINE Botをつくった

# 今回つくったもの

LINEのMessaging APIを使ってクイズというか英熟語の問題を出すbotを作成しました。
タイトルにもあるようにLambdaを使ってますので、サーバレスでつくってます。
（LINE BotのAPIは当初アクセスできるIPアドレスをホワイトリストとして登録する必要があったのですが、それがオプション扱いになったので、Lambdaが使えてます）

![line.gif](https://qiita-image-store.s3.amazonaws.com/0/6808/f32b538a-70bd-3259-fc06-9df7bac7054d.gif)


AWS Lambdaを使ったLINE BOTを作ってみた系の記事いっぱいあるのですが、今のMessaging API発表前に作成した記事が多いので、参考にする場合は注意してください。

# このBotの機能

- スタートで開始
- templateメッセージで問題を送信
- 正解、間違いを判定

ソースコードはこちら
https://github.com/ykyk1218/linebot_quize
（問題もソースコードべた書きだったり、結果判定処理がなかったりで作り途中感は満載）

# 準備
 
- LINEビジネスアカウントを用意
- Messaging API（Developer Trial）を申し込む
- Lambda Functionの作成

LINEビジネスアカウントの作成からMessaging APIの申し込みまでは下記ブログが参考になります。
http://www.kazuweb.asia/aws/lambda/chatbot


## LINEビジネスアカウントを用意

こちらのURLから作成します。
https://business.line.me/ja/

## Messaging API

最初は混乱したんですが、LINE Bot APIが新しくなり、この辺の登録方法も若干変わってきてます。

Messaging APIは通常のMessaging APIと別に**Developer Trial**があります。
開発でとりあえず準備する場合はこちらを選択したほうがよいです。

Developer Trialは登録できる友達の数に制限（50人まで）がありますが、開発する上では、そんなに問題ないでしょう。

LINE Business Centerから
「サービス」 > 「Messaging API」でDeveloper Trialを選択します。

![messaging.png](https://qiita-image-store.s3.amazonaws.com/0/6808/33c02079-4e1b-2a57-f868-f4bf223c97ae.png)



Developer Trialの申し込みが完了したらLINE@Managerの管理画面にいきます。
![line_account.png](https://qiita-image-store.s3.amazonaws.com/0/6808/41c074c0-8341-f4fa-54c9-5d9133079204.png)



LINE@MANAGERで
**「アカウント設定」> 「Bot設定」**からAPIを利用するを選択します。

APIの利用を選択したら、同じく「アカウント設定」> 「Bot設定」から**自動応答メッセージ「利用しない」**にしておきます。
これをしておかないとLINE@Managerで設定した応答メッセージが勝手に返信されてしまいます。

![LINE _MANAGER.png](https://qiita-image-store.s3.amazonaws.com/0/6808/28facfa7-631e-9113-76ab-48eb5f575845.png)


## Lambda Functionの作成
AWSのコンソールからLambdaを新規に作成して、Lambdaの**triggerとしてAPI Gatewayを選択**します。

作成したAPI GatewayのURLをMessaging APIのLINE Developers管理画面でwebhook URLとして登録します。

LINE Developer管理画面へいき
![line_account2.png](https://qiita-image-store.s3.amazonaws.com/0/6808/f2c01d88-5e55-0a94-f7e9-959bbf9e0a4a.png)

webhook urlをセット
![line_dev.png](https://qiita-image-store.s3.amazonaws.com/0/6808/7c706453-71ff-f465-aa0a-5e2d325afb91.png)


# コードを書く

準備ができたらコードを書きます。
今回Lambdaではnode.jsを選択してます。

処理的に難しいところは特にないですが、コードのピックアップをしていきます:point_up:

## コードを分割する

準備ができたところで、ここからゴリゴリコードを書いていくわけですが、今回作成するぐらいのものになると1ファイルで管理するにはつらいので、ファイルを分割したいです。

ファイルを分割したいばあいはブラウザ上でコードは書かずに、zipファイルをアップロードする形式にします。
今回は

- index.js（最初に処理をうけるところ）
- quize.js（出題する問題を決めたり、正解・不正解を判定したり）
- lineBot.js（Messaging APIに関連する処理もろもろ）

の3つにファイルをわけました。

## ログを見る

エラーの内容を確認するにはCloudWatchから確認できます。

**CloudWatch > ログ**
<img width="733" alt="sample.png" src="https://qiita-image-store.s3.amazonaws.com/0/6808/ef07d784-eddb-963f-1855-8b722aa90c6c.png">


## メッセージタイプによって処理をわける

通常のテキストが送られた場合はイベントタイプが`message`に
選択肢から選んだ場合は`postback`になります。

これにより処理をわけるようにします。

```
exports.handler = (event, context, callback) => {
  if (event["events"][0]["type"] == "message") {
    //通常メッセージ

  }else if (event["events"][0]["type"] == "postback") {
    //何かしらのボタンを押した
  }
}
```

## 選択肢付のメッセージ（template message）を送信する

下記のようなものを送りたい場合

<img width="425" alt="スクリーンショット 2016-12-05 1.37.45.png" src="https://qiita-image-store.s3.amazonaws.com/0/6808/e1590810-bfd6-1674-d411-661ef99cedd7.png">

JSONでPOSTするデータ

```
{
  "type": "template",
  "altText": "これはサンプルです",
  "template": {
    "type": "buttons",
      "title": "with an eye to〜",
      "text": "選択してください", 
      "actions": ＜アクションボタン＞
    }
}
```

アクションボタンは下記の形

```
[
 { type: "postback", label: "〜をよく見ながら", data: "1-0"},
 { type: "postback", label: "〜を横目に", data: "2-0"},
 { type: "postback", label: "〜と目が合う", data: "3-0"},
 { type: "postback", label: "〜の目的で", data: "4-1" },
]
```

dataのところがボタンを押したときにpostbackイベントで送信される値になるので、ここで正解が押されたか、不正解が押されたかの判定ができるように値をもっています。
（↑の例の場合だとハイフン区切りで2つ目の数字に1が入っていれば正解、0だと不正解）

postbackイベントのときのwebhookで取得できる値は下記の通り

```json
{
  "replyToken": "hogehoge",
  "type": "postback",
  "timestamp": 1462629479859,
  "source": {
    "type": "user",
    "userId": "fugafuga"
  },
  "postback": {
    "data": "4-1"
  }
}
```

一応気をつけるポイントとしては、、、

- アクションボタンは最大４つまで
- labelは文字数が20文字まで

この辺間違うと**Bad Request**となりHTTP Statusが400番で帰ってきます。
labelの最大文字数に気づかずはまった:sweat_smile:

詳しくは
https://devdocs.line.me/ja/

## メッセージの投稿

今までは

- Channel ID
- Channel Secret
- MID
をヘッダーに設定してメッセージをPOST

```
Content-Type: application/json
X-Line-ChannelID: {$channel_id}
X-Line-ChannelSecret: {$channel_secret}
X-Line-Trusted-User-With-ACL: {$mid}
```

新しいMessaging APIでは
Authorizationに**Channel Access Token**を設定してPOST

```
var postData = JSON.stringify(replyMessage)

var headers = {
  'Content-Type' : 'application/json; charset=UTF-8',
  'Authorization' : `Bearer {${process.env.CHANNEL_ACCESS_TOKEN}}`
}
var options = {
  host: 'api.line.me',
  path: '/v2/bot/message/reply',
  headers: headers,
  method: 'POST'
}

// APIリクエスト
var req = https.request(options, function(res){
        console.log('statusCode:', res.statusCode);

  res.on('request_body', function (chunk) {

  });
  req.on('error', function(err) {
       console.log('ERROR: ' + err.message);
  });
});
    
req.write(postData);
req.end();

```

```
var headers = {
  'Content-Type' : 'application/json; charset=UTF-8',
  'Authorization' : `Bearer {${process.env.CHANNEL_ACCESS_TOKEN}}`
}
```
この部分で環境変数に設定した値を使うようにしてます。
Lambdaの環境変数はawsコンソール上から設定できます。




## 面倒なところの解消方法

### ローカルで動作確認
いちいちzipファイルをアップロードしなくても多少の動作確認ができます。
具体的には下記のようなコードを用意。

```driver.js
var event = {
    "events": [
        {
            "type": "message",
            "replyToken": "<返信用のトークン>",
            "source": {
                "userId": "<ユーザーID",
                "type": "user"
            },
            "timestamp": <タイプスタンプ>,
            "message": {
                "type": "text",
                "id": "<メッセージID>",
                "text": "<LINEに投稿されたテストしたいメッセージ>"
            }
        }
    ]
}

var context = {
    invokeid: 'invokeid',
    done: function(err,message){
        return;
    }
}

var lambda = require("./index.js");
lambda.handler(event,context, function(){ return "hoge"});
```

テストしたいメッセージだけちゃんと準備しておけば、あとは
`node <ファイル名>`実行してテストできます。

とはいえreplyでのLINEへの投稿はできないので、最低限のところだけになりますが。

## 簡単アップロード

ブラウザ上でコードを書かずzipでアップロードする形になるので、アップロードが少々面倒...

そこでもう少し簡単にしたいです。

Lambdaのデプロイ周りのツール系は結構色々ありそうな雰囲気でしたが、あんまり大規模すぎると逆に設定が面倒になるのでただzipに圧縮してアップロードだけをしてくれるのが欲しい、と思っていたところ`aws-node-util`という良さげなライブラリがありました。

製作者
http://takamints.hatenablog.jp/entry/2016/03/09/launcher-script-for-the-aws-lambda-function#setup

これを使うことで、コマンド1発でサクッとファイルをアップロードできます。