LINE Botの通知イベントをAWS DynamoDBに保存する

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/15c8df20-5752-c5e4-8521-dfd56780240a.png)

LINE APIでメッセージを送信するためには、送信先のグループIDや、お友達のユーザーIDが必要ですが、一般アカウントの場合は、IDを調べるAPIがありません（グループメンバーのユーザーIDを取得するには、認証済みアカウントまたはプレミアムアカウントが必要）。

その代わり、フォローや、グループ参加のタイミングでWebhookが飛んでくるので、通知されるイベントオブジェクトに含まれるIDを覚えておくことで取り回しができます。

LINE APIのWebhookイベントで人の出入りで発生するイベントです。

- フォローイベント
- フォロー解除イベント
- 参加イベント
- 退出イベント
- メンバー参加イベント
- メンバー退出イベント

参考: [Messaging APIリファレンス - LINE Developers](https://developers.line.biz/ja/reference/messaging-api/)

IDをDB化しておけば良さそうなので、WebhookイベントのタイミングでDynamoDBに保存する方法を試してみます。

#前準備
LINE Developerアカウントのセットアップ方法、AWS Lambdaの使用方法は前回の記事を参考にしてください。

- [Serveoを使ってLINE botサンプルをローカル環境で試す](https://qiita.com/yuppejp/items/fac63d043ee59e278427)
- [LINE Botの通知イベントをAWS Lambdaで受け取る](https://qiita.com/yuppejp/items/fac63d043ee59e278427)

まずは、Webhookでどんな構造のイベントオブジェクトが送られているかをみるために、イベントをまるごとDynamoDBに保存してみます。

#DynamoDBのテーブルを作成する
AWS DynamoDBダッシュボードで「テーブルの作成」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/a8604004-5d19-74e0-08ef-6d92a1daecc1.png)
設定例：

|項目|設定値|
|---|---|
|テーブル名|LineEvent|
|プライマリキー|id|
|その他|デフォルト|

#IAMでロールにDynamoDBのアクセス権を与える
AWSのIdentity and Access Management (IAM)ダッシュボードでLambdaのロールを開いて、インラインポリシーを追加します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/7e087ef4-c190-edf0-9dbc-a5f1e4b5fe5f.png)

サービスに「DynamoDB」を指定し、とりあえずすべてのリソースを指定します。実際には対象のDynamoDBのARNを個別に指定したほうが良いと思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/2a88af8d-2fa9-3fc1-0d7b-b0f81e1b1a20.png)

ポリシー名を設定し、ポリシーを作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/887b8a96-f13d-8837-989c-a80ce2b75a6f.png)

そうすると、Lambda関数のDesignerにDyanamoDBが表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/1bd6c559-ede8-744f-61a1-d7359357b7cb.png)

#Lambda関数からDynamoDBへ書き込む
DBの準備ができたので、LINE BotのWebhookイベントオブジェクトをまるごとDynamoDBに書き込んでみます。事前にスキーマ定義をしなくても、プライマリーキーさえあれば、あとは好きな項目を入れられます。

```JavaScript:index.js
'use strict';
const AWS = require("aws-sdk");

// event handler
exports.handler = function handleEvent(event, context) {

  const docClient = new AWS.DynamoDB.DocumentClient();
  var params = {
    "TableName": "LineEvent",
    "Item": {
      "id": event.requestContext.requestId,
      "event": event,
      "body": JSON.parse(event.body),
    }
  };
  docClient.put(params, function (err, data) {
    if (err) {
      console.error('Unable to put item. Error JSON:', JSON.stringify(err, null, 2));
    } else {
      console.log('DeleteItem succeeded:', JSON.stringify(data, null, 2));
    }
  });
};
```

コードをLambda関数に保存したら、LINEアプリからbotをフォローしたり、メッセージを送信してみます。DynamoDBダッシュボードのテーブルの「項目」を表示すると、レコードが追加されていることがわかります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/587d53ce-ff08-3091-c31e-a921e05ffd7a.png)

ためしに、「follow」イベント開くと、各項目がツリーで表示されます。めっちゃ便利。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/4d3c02b3-b89e-2338-ea01-206337fafdd2.png)

#イベントに応じてテーブルを更新する
様子がわかったので、ユーザーDBとメッセージDBを作ってみます。

##テーブルの作成
これまでと同様の手順で、DynamoDBダッシュボードで新規にテーブルを2つ作成します。

|テーブル名|プライマリキー|プライマリキーの属性|
|---|---|---|
|LineUser|userId|文字列|
|LineMessage|messageId|文字列|

##Lambda関数のコード
follow, unfollow, messageイベントに対応してDynamoDBのテーブルにレコードを追加・削除するサンプルコードです。

```JavaScript:index.js
'use strict';
const line = require('@line/bot-sdk');
const AWS = require("aws-sdk");

// create LINE SDK config from env variables
const config = {
    channelAccessToken: process.env.CHANNEL_ACCESS_TOKEN,
    channelSecret: process.env.CHANNEL_SECRET,
};

// event handler
exports.handler = function handleEvent(event, context) {
    const body = JSON.parse(event.body);
    const eventType = body.events[0].type;
    const userId = body.events[0].source.userId;
    const timestamp = body.events[0].timestamp;
    const replyToken = body.events[0].replyToken;

    const docClient = new AWS.DynamoDB.DocumentClient();

    let params = {};
    switch (eventType) {
        case "follow":
            params = {
                "TableName": "LineUser",
                "Item": {
                    "userId": userId,
                    "timestamp": timestamp
                }
            };
            docClient.put(params, function (err, data) {
                if (err) {
                    console.error("Unable to add item. Error JSON:", JSON.stringify(err, null, 2));
                } else {
                    console.log("Added item:", JSON.stringify(data, null, 2));
                }
            });
            break;

        case "unfollow":
            params = {
                "TableName": "LineUser",
                "Key": {
                    "userId": userId
                }
            };
            docClient.delete(params, function (err, data) {
                if (err) {
                    console.error("Unable to delete item. Error JSON:", JSON.stringify(err, null, 2));
                } else {
                    console.log("DeleteItem succeeded:", JSON.stringify(data, null, 2));
                }
            });
            break;

        case "message":
            const messageId = body.events[0].message.id;
            const messageType = body.events[0].message.type;
            const messageText = body.events[0].message.text;
            params = {
                "TableName": "LineMessage",
                "Item": {
                    "messageId": messageId,
                    "userId": userId,
                    "text": messageText,
                    "timestamp": timestamp
                }
            };
            docClient.put(params, function (err, data) {
                if (err) {
                    console.error("Unable to add item. Error JSON:", JSON.stringify(err, null, 2));
                } else {
                    console.log("Added item:", JSON.stringify(data, null, 2));
                }
                
                // おうむ返し
                return lineEcho(context, messageType, messageText, replyToken);
            });
            break;
    }
};

function lineEcho(context, type, text, replyToken) {
    // ignore non-text-message event
    if (type != 'text' || replyToken == '00000000000000000000000000000000') {
        const lambdaResponse = {
            statusCode: 200,
            headers: { "X-Line-Status": "OK" },
            body: '{"result":"connect check"}'
        };
        return context.succeed(lambdaResponse);
    }

    // create a echoing text message
    const echo = { type: 'text', text: text };

    // create LINE SDK client
    const client = new line.Client(config);

    // use reply API
    return client.replyMessage(replyToken, echo);
}
```

#さいごに
これでユーザーIDのDB化ができたので、LINE botアプリの取り回しが楽になると思います。
今後は、Android/iOSアプリからDynamoDBの内容を取り出すために、Web API化してみたいと思います。

#参考
- [Amazon DynamoDB開発ガイド - AWS ドキュメント](
https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/GettingStarted.NodeJs.01.html)
- [Messaging APIリファレンス - LINE Developers](https://developers.line.biz/ja/reference/messaging-api/)
- [【詳解】JavascriptでDynamoDBを操作する - Qiita](https://qiita.com/Fujimon_fn/items/66be7b807a8329496899)
- [Webhookイベントを使ってメッセージ送信用ユーザーIDのDBを作る #LINE_API - DevelopersIO](
https://dev.classmethod.jp/etc/create-user-db-using-line-webhook-events/)