概要
- AWS LambdaとDynamoDBを使ってLINE Botを作ったので、その備忘録の1つです。
- この記事では、LINE Botの自動返信メソッドをLambdaで作る手順を説明します。
環境
macOS: 13.5.1
Homebrew: 4.1.4
Node.js: v18.14.1
aws-cli: aws-cli/2.13.7 Python/3.11.4 Darwin/22.6.0 source/arm64 prompt/off
前提条件
- AWS CLIのダウンロード・インストール済み
- CLI操作用のIAMユーザーを作成済み。
- IAMユーザーの作成方法は 以前書いた別の記事(CLI&コンソール作業用のAWS IAMユーザーを作成する) を参照のこと。
サマリー(全体の流れ)
- DynamoDBの作成
- Lambda用のIAMロールの作成
- コンソールでLambdaの作成
- Lambda環境変数の設定
- Lambdaのコードの実装・デプロイ
- API Gatewayの設定
DynamoDBの作成
- AWSへログインし、DynamoDB -> テーブル -> テーブルの作成 をクリック
- テーブル名を入力
- 今回はLINEのユーザー情報を格納するテーブルなので
linebot_user_sample
とします。
- 今回はLINEのユーザー情報を格納するテーブルなので
- パーティションキーを入力
- LINE Botを友達登録しているユーザーのユーザーIDを格納するので
user_id
とします。
- LINE Botを友達登録しているユーザーのユーザーIDを格納するので
- テーブル設定は デフォルト設定 とします。
- テーブルの作成 をクリックし、テーブル作成します。
- 作成されたら、テーブル名をクリックし、ARNをメモっておきます。
- あとで、IAMロールの作成時に使います。
Lambda用のIAMロールの作成
- まず最初に、先ほど作成したDynamoDBのテーブルに対する読み書き権限のポリシーを作成します。
- IAM -> ポリシー -> 『ポリシーを作成』をクリック
-
JSON
をクリックし、下記のjsonを入力し次へ
をクリック。-
"Resource"
は、先ほど作成したテーブルのarnや、自分のアカウント番号を入力してください。
-
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"dynamodb:BatchGetItem",
"dynamodb:GetItem",
"dynamodb:Query",
"dynamodb:Scan",
"dynamodb:BatchWriteItem",
"dynamodb:PutItem",
"dynamodb:UpdateItem"
],
"Resource": "arn:aws:dynamodb:ap-northeast-1:999999999999:table/linebot_user_sample"
},
{
"Effect": "Allow",
"Action": [
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:ap-northeast-1:999999999999:*"
},
{
"Effect": "Allow",
"Action": "logs:CreateLogGroup",
"Resource": "*"
}
]
}
- 任意のポリシー名を入力します。今回は
MyPolicy_dynamodb_read_write_linebot_sample
とします。- 説明は空欄でも構いません。
-
ポリシーの作成
をクリックして、作成します。
- 一覧を見ると、無事、ポリシーが作成されました。
- 次に、LambdaにアタッチするIAMロールの作成をします。
- IAM -> ロール ->
ロールを作成
をクリック。
- 信頼されたエンティティタイプ ->
AWS のサービス
, ユースケース ->Lambda
を選択し次へ
をクリック。
- 先ほど作成したDynamoDBへの書き込みを許可するロール
MyPolicy_dynamodb_read_write_linebot_sample
のレ点チェックをつけます。- これにより、特定のテーブルに対してのみ、読み書き権限が付与されます。
- 次に
AWSLambdaBasicExecutionRole
のレ点チェックをつけ、次へ
をクリックします。
- 任意のロール名を入力し、
ロールを作成
をクリックします。- 今回のロール名は
MyLambdaRole_linebot_sample_dynamodb_read_write
とします。
- 今回のロール名は
- これでIAMロールの作成は完了です。
コンソールでLambdaの作成
- Lambda -> 関数 ->
関数の作成
をクリック
- 以下の設定にします。
- 分類
- 一から作成
- 関数名
- なんでもOK(今回は
linebot_auto_reply_sample
)
- なんでもOK(今回は
- ランタイム
- Node.js 18.x
- アーキテクチャ
- arm64
- 実行ロール
- 既存のロールを使用する
- 既存のロール
- 先ほど作成したロール(
MyLambdaRole_linebot_sample_dynamodb_read_write
)
- 先ほど作成したロール(
- 分類
-
関数の作成
をクリック。
- Lambda関数が作成されました。
Lambda環境変数の設定
- シークレットなどを環境変数に登録します。
- 設定 -> 環境変数 ->
編集
をクリック
- 3つの環境変数を登録します。
- ACCESS_TOKEN : LINE Botのチャネルアクセストークン
- DYNAMODB_TABLE_USER : 先ほど作ったDynamoDBのテーブル名
- LINE_CHANNEL_SECRET : LINE Botのチャネルシークレット
- 環境変数の追加 -> コピペ ->
保存
をクリック。
- これで環境変数の登録完了です。
Lambdaのコードの実装・デプロイ
- VSCodeを起動し、作業ディレクトリへ移動。
cd hogetaro/my_lambda_project
- npm初期化を実行
- 途中の選択肢は、全てデフォルト設定でOKです。(Enter連打でOK)
my_lambda_project/
npm init
- LINE Bot SDKをインストール
my_lambda_project/
npm install @line/bot-sdk
- DynamoDBへの読み書きを実行するためにAWS SDKをインストール。
my_lambda_project/
npm install aws-sdk
- 自動zip化&自動デプロイするために、package.jsonを編集します。
-
"scripts"
に以下を追記します。- arnの箇所には、先ほど作成したLambda関数のarnを記載してください。
"deploy": "aws lambda update-function-code --function-name arn:aws:lambda:ap-northeast-1:999999999999:function:linebot_auto_reply_sample --zip-file fileb://Lambda-Deployment.zip",
"predeploy": "zip -r Lambda-Deployment.zip * -x *.zip *.json *.log"
-
解説
- "deproy"
-
Lambda-Deployment.zip
というファイルをarn:aws:lambda:ap-northeast-1:999999999999:function:linebot_auto_reply_sample
の関数へアップロードする。
-
- "predeploy"
- "deploy"が実行される前に
Lambda-Deployment.zip
というzipファイルを作成する。ただし、.zip, .json, .log の拡張子ファイルはzip化しない。(=index.jsとnode_modulesがzip化されます)
- "deploy"が実行される前に
- "deproy"
-
修正後のコードは以下の通りです。
my_lambda_project/package.json
{
"name": "my_lambda_project",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"deploy": "aws lambda update-function-code --function-name arn:aws:lambda:ap-northeast-1:999999999999:function:linebot_auto_reply_sample --zip-file fileb://Lambda-Deployment.zip",
"predeploy": "zip -r Lambda-Deployment.zip * -x *.zip *.json *.log"
},
"author": "",
"license": "ISC",
"dependencies": {
"@line/bot-sdk": "^7.5.2",
"aws-sdk": "^2.1443.0"
}
}
- 次に自動でデプロイができるか確認してみます。
- index.jsを作成します。
my_lambda_project/index.js
// deployテスト
export const handler = async (event) => {
// TODO implement
const response = {
statusCode: 200,
body: JSON.stringify('Hello from Lambda!'),
};
return response;
};
- AWS CLI用のIAMユーザーを作業ディレクトリで設定します。
- 関連記事も参照のこと。
-
dev_cli
は適切なユーザー名に修正のこと。
my_lambda_project/
export AWS_DEFAULT_PROFILE=dev_cli
- デプロイコマンドを実行
- zip化も行われます。
my_lambda_project/
npm run deploy
- デプロイできているかは、Lambdaのコンソールを確認すると分かります。
-
最終更新
の時間やコードソース
のコメントで、デプロイできていることがわかります。
- LINE Bot自動返信機能を実装するために、index.jsを修正します。
my_lambda_project/index.js
const line = require('@line/bot-sdk');
const AWS = require('aws-sdk');
/*
* セキュリティ関係の処理
*/
// LINEからのリクエストかどうかをチェックする
const checkRequestFromLine = (event) => {
const signature = event.headers["x-line-signature"];
const body = event.body;
const channelSecret = process.env.LINE_CHANNEL_SECRET;
// 署名とbodyをログに出力
console.log("signature -> ", signature);
console.log("body -> ", JSON.stringify(body));
// 署名検証
const isValid = line.validateSignature(body, channelSecret, signature);
return isValid;
};
/*
* DynamoDB関係の処理
*/
// ユーザーの投稿テキストを更新する
const updateUserText = async(userId, userText) => {
const docClient = new AWS.DynamoDB.DocumentClient();
// 更新するパラメータを設定
const params = {
TableName: process.env.DYNAMODB_TABLE_USER,
Key: {
user_id: userId
},
UpdateExpression: "set user_text = :user_text",
ExpressionAttributeValues: {
":user_text": userText,
}
};
// DBへ書き込み
docClient.update(params, function(err, data) {
if(err) {
console.log("updateUserText() -> error -> ", err);
} else {
console.log("updateUserText()成功 -> data -> ", data);
}
});
};
/*
* LINE WebHookからのイベント処理メソッド
*/
// textMessageイベントの処理
const textMessageEventReply = async(event) => {
// 送信先のLINE Bot
const client = new line.Client({
channelAccessToken: process.env.ACCESS_TOKEN
});
// リプライトークン
const replyToken = event.replyToken;
// リプライコンテンツ
let replyContents = null;
// テキストメッセージの場合
if (event.message.type === 'text') {
// ユーザーの投稿テキスト
const userText = event.message.text;
console.log("userText: "+userText);
replyContents = {
'type': 'text',
'text': "あなたの入力した言葉は" + userText + "ですね"
};
// ユーザーの投稿テキストを更新する
const userId = event.source.userId;
await updateUserText(userId, userText);
} else {
const replyText = "ごめんなさい!その種類の投稿には返信できないんです";
replyContents = {
'type': 'text',
'text': replyText
};
}
// クライアントへメッセージ送信
try {
await client.replyMessage(replyToken, replyContents);
} catch(err) {
console.log("error -> ", err);
}
};
// postBackイベントの処理
const postBackEventReply = async(event) => {
// 送信先のLINE Bot
const client = new line.Client({
channelAccessToken: process.env.ACCESS_TOKEN
});
// postBackイベント内容
const postBackEventData = event.postback.data;
console.log("postBackEventData: "+postBackEventData);
// 返信用トークン
const replyToken = event.replyToken;
// リッチメニュー切り替えの場合は返信しない
if(postBackEventData.includes("richmenu-changed-to")) {
console.log("リッチメニュー切り替えイベント")
return
}
const replyContents = {
'type': 'text',
'text': postBackEventData+" のボタンをタップしましたね!"
};
// クライアントへ返信
try {
await client.replyMessage(replyToken, replyContents);
} catch(err) {
console.log("error -> ", err);
}
};
// followイベントの処理
const followEventReply = async (event) => {
// followerのuserId
const userId = event.source.userId;
console.log("userId " + userId + " が友達登録しました");
const docClient = new AWS.DynamoDB.DocumentClient();
// is_followをtrueに更新する
const params = {
TableName: process.env.DYNAMODB_TABLE_USER,
Key: {
user_id: userId
},
UpdateExpression: "set is_follow = :is_follow",
ExpressionAttributeValues: {
":is_follow": true
}
};
try {
// DBへ書き込み
const data = await new Promise((resolve, reject) => {
docClient.update(params, function(err, responseData) {
if (err) {
reject(err);
} else {
resolve(responseData);
}
});
});
console.log("followEventReply()成功 -> data -> ", data);
} catch (err) {
console.log("followEventReply() -> error -> ", err);
}
}
// unfollowイベントの処理
const unfollowEventReply = async (event) => {
// unfollowのuserId
const userId = event.source.userId;
console.log("userId " + userId + " が友達登録解除しました");
const docClient = new AWS.DynamoDB.DocumentClient();
// is_followをfalseに更新する
const params = {
TableName: process.env.DYNAMODB_TABLE_USER,
Key: {
user_id: userId
},
UpdateExpression: "set is_follow = :is_follow",
ExpressionAttributeValues: {
":is_follow": false
}
};
try {
// DBへ書き込み
const data = await new Promise((resolve, reject) => {
docClient.update(params, function(err, responseData) {
if (err) {
reject(err);
} else {
resolve(responseData);
}
});
});
console.log("unfollowEventReply()成功 -> data -> ", data);
} catch (err) {
console.log("unfollowEventReply() -> error -> ", err);
}
}
/*
* LINEからのリクエストを処理する
*/
exports.handler = async (event) => {
console.log('event -> ', event);
// LINEの疎通確認リクエストの場合は、ステータス200を返す
// リクエストbodyは {
// "destination": "xxxxxxxxxx",
// "events": []
// }
const requestBody = JSON.parse(event.body);
if (requestBody.events.length === 0) {
console.log("LINEの疎通確認リクエストのため、ステータス200を返します");
return {
statusCode: 200,
body: JSON.stringify({
message: 'LINEの疎通確認リクエストのため、ステータス200を返します',
}),
headers: {
'Content-Type': 'application/json',
},
};
}
// LINEからのリクエストかどうかをチェックする
if(!checkRequestFromLine(event)) {
console.log("LINEからのリクエストではないので、処理を終了します");
return;
}
const eventsArray = requestBody.events;
console.log("eventsArray -> ", eventsArray);
// 補足説明: events配列は、イベントによっては複数格納されることがあるので、for文で順番に処理する
for (const eventItem of eventsArray) {
// userIdの取得
const userId = eventItem.source.userId;
console.log("userId -> ", userId);
// textMessageイベントの処理
if (eventItem.type === "message") {
await textMessageEventReply(eventItem);
}
// postBackイベントの処理
if (eventItem.type === "postback") {
await postBackEventReply(eventItem);
}
// followイベントの処理
if (eventItem.type === "follow") {
await followEventReply(eventItem);
}
// unfollowイベントの処理
if (eventItem.type === "unfollow") {
await unfollowEventReply(eventItem);
}
}
};
-
npm run deploy
でデプロイを忘れずに。
API Gatewayの設定
- 以前書いた別の記事(LINE BotのWebHookのためのAPI Gateway作成方法) を参照して、設定してください。
LINE Botの動作確認
- 実際にLINE Botにコメントを送信したり、リッチメニューをタップして動作確認をしてみます。
- 問題なく動作しました。
- 以上で『LINE Botの自動返信メソッドをLambdaで作る』手順の完了です。