①LINE botの作成
LINE botを作成するにはLINE Developersへの登録が必要です。
公式サイトにて登録を完了すると下記のような画面になります。ここから3つの作業を行います。
① 新規プロバイダの作成
- 画面左側メニューから、新規プロバイダの作成を選択し、プロバイダを作成します。
② 新規チャネルの作成
- 作成したプロバイダをクリックし、新しくチャネルを作成します。(使用するAPIはMessaging API)
③ 外部連携の設定
-
ChannelSecretの取得
-
アクセストークン・Webhook送信の利用許可設定
②Lambdaで動かすコードの作成
LINEで受けとったデータをAWS Lambdaで処理するためのコードを記述していきます。
作成したコード
- LINEとの受け答え部分のwebhook.jsに記述し、一部の関数はutils.jsに分離させています。
- ※コードはおじさんLINEごっこBOTを作ってみた - PPT Developers' Blogを参考にさせていただきました。
- 環境はNode.js 6以上を想定しています。
webhook.js
const utils = require('utils');
const line = require('@line/bot-sdk');
//const request = require('request-promise');
const Line = new line.Client({
channelAccessToken: process.env.LINE_ACCESS_TOKEN,
channelSecret: process.env.LINE_CHANNEL_SECRET,
});
const AWS = require('aws-sdk');
exports.handler = (event, context, callback) => {
try {
let body = null;
if (typeof event.body === "string" || Buffer.isBuffer(event.body)) {
body = utils.validateSignature(
event.body,
process.env.LINE_CHANNEL_SECRET,
event.headers['X-Line-Signature']
);
}
if (body === null) {
throw new Error('body parsing failed');
}
body.events.forEach((webhookData) => {
const replyToken = webhookData.replyToken;
const msgEvtType = webhookData.type;
const timeStamp = webhookData.timestamp;
const userId = webhookData.source.userId;
switch (msgEvtType) {
case 'follow': // 友達追加時
Line.getProfile(userId).then((profileData) => {
const displayName = profileData.displayName;
const replyMessage = utils.welcomeMessage(displayName);
Line.replyMessage(replyToken, { type: 'text', text: replyMessage });
});
break;
case 'unfollow': // ブロック時
break;
case 'message': // メッセージ送信時
Line.getProfile(userId).then((profileData) => {
const displayName = profileData.displayName;
const messageType = webhookData.message.type;
let replyMessage = "";
switch (messageType) {
// switch文で送信メッセージの種類ごとにBOTの処理を分けられる
case 'text': // テキストメッセージ
replyMessage = webhookData.message.text;
break;
case 'image': // 画像
replyMessage = '画像ですね。';
Line.replyMessage(replyToken, { type: 'text', text: replyMessage });
break;
case 'video': // 動画
replyMessage = '動画ですね。';
Line.replyMessage(replyToken, { type: 'text', text: replyMessage });
break;
case 'audio': // 音声
replyMessage = '音声ですね。';
Line.replyMessage(replyToken, { type: 'text', text: replyMessage });
break;
case 'file': // ファイル
replyMessage = 'ファイルですね。';
Line.replyMessage(replyToken, { type: 'text', text: replyMessage });
break;
case 'location': // 位置情報
replyMessage = '位置情報ですね。';
Line.replyMessage(replyToken, { type: 'text', text: replyMessage });
break;
case 'sticker': // スタンプ
replyMessage = 'スタンプですね。';
Line.replyMessage(replyToken, { type: 'text', text: replyMessage });
break;
}
});
break;
case 'postback': // ポストバックイベント受信時
Line.getProfile(userId).then((profileData) => {
const displayName = profileData.displayName;
const replyToken = webhookData.replyToken;
const pbData = JSON.parse(webhookData.postback.data);
let replyMessage = "";
const pbType = pbData.PBType;
switch (pbType) {
case 'SurveyAnswer': // サーベイ回答依頼に対する顧客からの回答
switch (pbData.Satisfy) {
case 'Yes':
replyMessage = pbData.Customer + '様。ご満足いただきありがとうございました。';
Line.replyMessage(replyToken, { type: 'text', text: replyMessage });
break;
case 'No':
replyMessage = pbData.Customer + '様。ご不満の点について今後改善して参ります。';
Line.replyMessage(replyToken, { type: 'text', text: replyMessage });
break;
}
break;
}
});
break;
}
});
} catch (e) {
// console.log('throw exception:');
throw e;
}
// console.log('Return: 200');
callback(null, { statusCode: 200 });
};
utils.js
const emojione = require('emojione');
const JSONParseError = require('@line/bot-sdk').JSONParseError;
const validateSignature = require('@line/bot-sdk').validateSignature;
const SignatureValidationFailed = require('@line/bot-sdk').SignatureValidationFailed;
// X-Line-Signatureリクエストヘッダーに含まれる署名を検証
module.exports.validateSignature = function (body, secret, signature) {
if (!validateSignature(body, secret, signature)) {
throw new SignatureValidationFailed("signature validation failed", signature)
}
const strBody = Buffer.isBuffer(body) ? body.toString() : body;
try {
return JSON.parse(strBody);
} catch (err) {
throw new JSONParseError(err.message, strBody);
}
};
// 返答メッセージに入っているプレースホルダを任意の文字列に変換
module.exports.convertPlaceholders = function (message, placeholderString, replaceValue) {
const placeholder = `<${placeholderString}>`;
const convertedMessage = message.replace(new RegExp(placeholder, 'g'), replaceValue);
return convertedMessage;
};
// 友達追加時メッセージ
module.exports.welcomeMessage = function (displayName) {
return emojione.shortnameToUnicode("はじめまして、${displayName}さん:exclamation::smiley:");
};
上記のコードについて
- 以下の項目については個別のパラメータを設定してください。
- LINEのアクセストークン
- LINEのChannelSecret
- 呼び出すAmazon Connectの設定(インスタンスID、リージョン、コンタクトフローID、取得した電話番号)
- Amazon Connect Salesforce lambda packageをAPI GWを介してAPI化した際のURL
- 手配する修理サービスの電話番号
③デプロイ
- デプロイはSAMを利用します。AWS CLIを使用するので設定が必要です。
AWS CLI
- コマンドベースでAWSの設定を行うために、AWS CLIを使用します。Pythonベースのためpipでインストールします。
- WindowsであればMSI形式のインストーラも利用可
CLIの設定方法
-
Python3をインストールします。(公式サイト)
- Pathを通した後、コマンドプロンプトからPython、pipが使えるかバージョン表示のコマンドで確認
$ python -V
$ pip -V
- Pathを通した後、コマンドプロンプトからPython、pipが使えるかバージョン表示のコマンドで確認
-
Pythonのインストールが終わったら、pipでAWS CLIをインストールします。
$ pip install awscli
AWS SAMの使い方
-
以下のサイトを参考にさせていただきました。
-
以下の作業が必要です。
- 中間生成物を置くためのS3の準備
- Lambdaに入れたいコードの準備
- SAM使用のためのテンプレートの作成
- パッケージング(S3に中間生成物が置かれる)
- パッケージングされたものをAWSにデプロイ
1. S3バケットの作成
- AWS CLIを使用して下記のコマンドを打ち込みます。
$ aws s3 mb s3://入れるバケット名 --profile CLIのプロファイル名
-
$ aws s3 ls --profile CLIのプロファイル名
で作成を確認
2. コードの準備
- デプロイするコード(webhook.js等)、外部モジュールをLambda関数ごとに1つのzip形式ファイルとしてまとめます。
- 外部モジュールはnpmコマンドであらかじめローカルにインストールしておき、node_modules以下のファイルもzipにまとめます。
3. テンプレートの作成
- デプロイ情報をYAML形式で作成します。
template.yaml
# バージョンの設定
AWSTemplateFormatVersion: '2010-09-09'
# 説明文
Description: Create Lambda function by using AWS SAM.
# Lambdaをサーバレスにする
Transform: AWS::Serverless-2016-10-31
# 複数の設定を行う際に共通する設定はGlobalsに記載
Globals:
Function:
# 使用言語・バージョンを記載
Runtime: nodejs8.10
# タイムアウト時間を秒で記載
Timeout: 60
# メモリサイズの記載
MemorySize: 256
Environment:
Variables:
# 変数を作成し、中にデータを入力
HOGEHOGE: 36
HUGAHUAG: abc
Resources:
# 作成するLambda関数・ロールの名前を記載
LineChatWebhookReceiverSample:
# 上記が何者なのか種類を記載 ロールの場合はAWS::IAM::Role
Type: AWS::Serverless::Function
# これ以降はプロパティ
Properties:
# !GetAttとすることでこのファイル内で作成したロールを指定することができます。
Role: !GetAtt LineChatIamRoleSample.Arn
# 上記で記載したZIPファイルの相対座標を記載しています。(このファイルから見た)
CodeUri: 'functions/webhook.zip'
# このハンドラ名の前半がファイル名ドットの後の公判が呼び出す関数名となってます。
# ZIPしない場合にはfunctions/webhook.handler等で対応
Handler: webhook.handler
Events:
# Amazon API GWを同時に作る場合に記載
Api:
Type: Api
Properties:
Path: /webhooks/line
Method: post
# 同様にログプロセッサーを使用する場合に記載
LogsProcessor:
Type: CloudWatchLogs
Properties:
# !Refとしてこのファイル下部で作成したものを選択する
LogGroupName: !Ref LineChatLogGroupSample
LineChatLogGroupSample:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub /aws/lambda/${LineChatWebhookReceiverSample}
RetentionInDays: 14
# 上記で作成した関数に付与するロールの作成
LineChatIamRoleSample:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: "sts:AssumeRole"
Policies:
-
PolicyName: "LineChat-lambda-sample"
PolicyDocument:
Version: "2012-10-17"
Statement:
-
Effect: "Allow"
Action: "cloudwatch:*"
Resource: "*"
-
Effect: "Allow"
Action: "s3:*"
Resource: "*"
-
Effect: "Allow"
Action: "logs:*"
Resource: "*"
-
Effect: "Allow"
Action: "dynamodb:*"
Resource: "*"
-
Effect: "Allow"
Action: "connect:*"
Resource: "*"
4. パッケージングとデプロイ
- テンプレートとコードは以下のディレクトリ構成にします。
./
|-- ./functions
| |--webhook.zip
|-- template.yaml
- 下記コマンドを実行し、パッケージングとデプロイを行います。
- 作成したYAML形式のテンプレート(template.yaml)、S3バケット、スタック名を指定します。
-
aws cloudformation package
でパッケージングすることによりpackaged-template.yamlが生成される(はず)
-
- 作成したYAML形式のテンプレート(template.yaml)、S3バケット、スタック名を指定します。
$ aws cloudformation package --template-file template.yaml --s3-bucket S3バケット名 --output-template-file packaged-template.yaml --profile プロファイル名
$ aws cloudformation deploy --template-file packaged-template.yaml --stack-name スタック名 --capabilities CAPABILITY_IAM --profile プロファイル名