【今日から始めるAWS】Lambdaを使ってLINEのbotをつくる

#はじめに
30代未経験からエンジニアをめざして勉強中のYNと申します。お読みいただきありがとうございます。
少し前に作ったLINEのbotをつくりました。
下記参考記事をそのままコピーした内容になってしまったのですが、学習ログとして投稿させていただきました。

* [LambdaではじめてのLINE Botを作る](https://dev.classmethod.jp/articles/lambda-line-bot-tutorial/#toc-2)
* [AWS Lambdaを使ってLINEBotを作ってみよう！](https://qiita.com/shinbunbun_/items/ae09364504002d0c25f1)

#今回やったこと
下記のように、こちらが送ったテキストメッセージをそのまま返答してくれる、オウム返しbotを作ります。
<img width="558" alt="スクリーンショット 2020-08-08 11.16.49.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/572098/7dcfc966-61f2-da8f-81f9-46bd3eb96e2b.png">

下記のサーバレス構造を構築します。（[こちらの記事](https://qiita.com/shinbunbun_/items/ae09364504002d0c25f1)から図を拝借させていただきました。）
<img width="723" alt="スクリーンショット 2020-08-08 11.09.26.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/572098/8997d572-bd6e-9513-2114-912b6eaf5aa5.png">

#手順

* 事前準備
* `Lambda`の設定
* API-gatewayの設定
* LINE webhookの設定
* LINEチャンネルの設定

#事前準備

* LINEデベロッパー登録


#Lambdaの設定

* **1. ローカルPCでlambda関数のフォルダを作成**

```
$ cd ~
$ mkdir line-bot
$ cd line-bot
```

* **2. index.jsを作成**

```
$ npm install @line/bot-sdk
```

```javascript:line-bot/index.js
"use strict";
const line = require("@line/bot-sdk");
const client = new line.Client({ channelAccessToken: process.env.ACCESSTOKEN });
// ①SDKをインポート

const crypto = require("crypto");

exports.handler = function (event, context) {
  let body = JSON.parse(event.body);
  let signature = crypto
    .createHmac("sha256", process.env.CHANNELSECRET)
    .update(event.body)
    .digest("base64");
  let checkHeader = (event.headers || {})["X-Line-Signature"];

  if (signature === checkHeader) {
  // ②cryptoを使ってユーザーからのメッセージの署名を検証する


    if (body.events[0].replyToken === "00000000000000000000000000000000") {   
      let lambdaResponse = {
        statusCode: 200,
        headers: { "X-Line-Status": "OK" },
        body: '{"result":"connect check"}',
      };
      context.succeed(lambdaResponse);
      // ③接続確認エラーを確認する。
      
    } else {
      let text = body.events[0].message.text;
      const message = {
        type: "text",
        text,
      };
      client
        .replyMessage(body.events[0].replyToken, message)
        .then((response) => {
          let lambdaResponse = {
            statusCode: 200,
            headers: { "X-Line-Status": "OK" },
            body: '{"result":"completed"}',
          };
          context.succeed(lambdaResponse);
        })
        .catch((err) => console.log(err));
      // ④リクエストとして受け取ったテキストをそのまま返す

    }
  } else {
    console.log("署名認証エラー");
  }
};

```


下記、index.jsにおける処理を解説します。

`①SDKをインポートする`

>```javascript: 
const line = require("@line/bot-sdk");
const client = new line.Client({ channelAccessToken: process.env.ACCESSTOKEN });
```
[githubのexample](https://github.com/line/line-bot-sdk-nodejs/blob/master/examples/echo-bot/index.js)でSDKの使い方が参照できます。



`②cryptoを使ってユーザーからのメッセージの署名を検証する`

>```javascript: 
>const crypto = require("crypto");
exports.handler = function (event, context) {
  let body = JSON.parse(event.body);
  let signature = crypto
    .createHmac("sha256", process.env.CHANNELSECRET)
    .update(event.body)
    .digest("base64");
  let checkHeader = (event.headers || {})["X-Line-Signature"];
  if (signature === checkHeader) {
    // ここでlambdaの処理を記載
  }
```
[cryptoはnode.jsに標準で組み込まれている暗号化に関するライブラリで](https://nodejs.org/en/knowledge/cryptography/how-to-use-crypto-module/)、botにおける実装の詳細については[LINE公式ドキュメント](https://developers.line.biz/ja/reference/messaging-api/#signature-validation)に書いてあります。
（暗号化については[こちらの記事](https://qiita.com/asksaito/items/1793b8d8b3069b0b8d68)が分かりやすかったです）

>ここでは、下記２点が一致するかを検証しています。

>* `signature`: ユーザーから送られてきたリクエスト(`event.body`)を、秘密鍵(`CHANNELSECRET`)を使って暗号化したもの

>* `event.headers["X-Line-Signature"]`：リクエストヘッダーに含まれる署名



`③接続確認の場合の処理を実装する`
>botが接続確認を行う場合、`replyToken="000..."`のリクエストが来ます。

>```javascript: 
let body = JSON.parse(event.body);
if (body.events[0].replyToken === "00000000000000000000000000000000"){
      let lambdaResponse = {
        statusCode: 200,
        headers: { "X-Line-Status": "OK" },
        body: '{"result":"connect check"}',
      };
      context.succeed(lambdaResponse);
      }
```


`④リクエストとして受け取ったテキストをそのまま返す`
>ユーザーから送られてきたリクエストの中身（イベントオブジェクト）は[LINE公式ドキュメント](https://developers.line.biz/ja/reference/messaging-api/#webhook-event-objects)にまとめられています。
リクエストに含まれるメッセージがテキストの場合、`body.events[0].message.text`に含まれます。
また、`client.replyMessage(replyToken, message)`のメソッドを使うことで、ユーザーにメッセージを返信することが出来ます。

>```javascript: 
let text = body.events[0].message.text;
   const message = {
     type: "text",
     text,
   };
   client
     .replyMessage(body.events[0].replyToken, message)
     .then((response) => {
       let lambdaResponse = {
         statusCode: 200,
         headers: { "X-Line-Status": "OK" },
         body: '{"result":"completed"}',
       };
       context.succeed(lambdaResponse);
     })
```

* **3. index.jsとnode_modulesを圧縮しLambdaにアップロードする**

<img width="1567" alt="スクリーンショット 2020-08-08 17.59.05.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/572098/292c91d4-358c-780a-ecaa-8e922b04e653.png">

* **4. アクセストークン（ACCESSTOKEN）とChannelSecret（CHANNELSECRET）を環境変数に登録する**

<img width="1536" alt="スクリーンショット 2020-08-08 18.04.54.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/572098/29ad3238-e144-65a2-9ea1-a0d849376f78.png">



#API-gatewayの設定

* 1 POSTメソッドを作成する
<img width="1655" alt="スクリーンショット 2020-08-08 18.13.44.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/572098/61e5e6d3-905b-f6d8-e632-fde7c607190a.png">

* 2 リクエストヘッダーによる認証を追加する
<img width="1651" alt="スクリーンショット 2020-08-08 18.15.21.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/572098/94c95d94-3270-57a0-f500-1b46165b9e62.png">

* 3 統合リクエストに作成した`Lambda`関数を設定する
<img width="1410" alt="スクリーンショット 2020-08-08 18.18.58.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/572098/ccd52ee3-c2d6-a33f-509d-5f1db169103b.png">

* 4 APIをデプロイ
<img width="1448" alt="スクリーンショット 2020-08-08 19.24.39.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/572098/cc99fcc9-d8fd-83b5-e7ab-d2d8036ccdb6.png">


# LINE webhookの設定
LINEアプリを通してユーザーから受け取ったリクエストを`Lambda`へ送るwebhookの設定をします。
作成したAPIのエンドポイントを、LINEコンソールのwebhook設定に反映します。
<img width="1561" alt="スクリーンショット 2020-08-12 16.18.07.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/572098/a14985ea-aafa-288f-3947-b7230d61eefa.png">

<img width="1176" alt="スクリーンショット 2020-08-12 16.23.46.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/572098/315b1a73-cbf9-ca6c-8bbc-76c7ab10c853.png">


# LINEチャンネルの設定

`LINE Official Account Manager`からユーザーからのメッセージに対するあいさつや応答の設定をすることができます。
<img width="1664" alt="スクリーンショット 2020-08-08 19.29.07.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/572098/35824f5f-01be-b05d-26c1-3dc4d615be7f.png">


#最後に
`Lambda`を使うことで、簡単にLINEのbotを作ることができます。
初学者でも1時間弱でつくることができました。
機械学習や外部のAPIを組み合わせれば、面白いことができそうです。
ご覧いただきありがとうございました。