Help us understand the problem. What is going on with this article?

AWS Lambdaを使用したLINEbotの作成

More than 1 year has passed since last update.

①LINE botの作成

LINE botを作成するにはLINE Developersへの登録が必要です。
公式サイトにて登録を完了すると下記のような画面になります。ここから3つの作業を行います。
line_1.png

① 新規プロバイダの作成

  • 画面左側メニューから、新規プロバイダの作成を選択し、プロバイダを作成します。

② 新規チャネルの作成

  • 作成したプロバイダをクリックし、新しくチャネルを作成します。(使用するAPIはMessaging API)

③ 外部連携の設定

  • ChannelSecretの取得

    • Lambdaとの連携に必要なので、ChannelSecretをメモしておきます。 line_3.png
  • アクセストークン・Webhook送信の利用許可設定

    • 今回の連携ではWebhook送信を利用ため、Webhook送信を「利用する」に設定します。
    • アクセストークンもメモしておきます。 line_2.png

②Lambdaで動かすコードの作成

LINEで受けとったデータをAWS Lambdaで処理するためのコードを記述していきます。

作成したコード

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
  • Pythonのインストールが終わったら、pipでAWS CLIをインストールします。

    • $ pip install awscli

AWS SAMの使い方

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が生成される(はず)
$ 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 プロファイル名

◆参考ページ

nttcommunications
NTTコミュニケーションズは、お客さまのデジタルトランスフォーメーション実現に貢献する「DX Enabler™」として、ICTの活用によるお客さまの経営課題の解決やスマートな社会の実現に取り組みます。
https://www.ntt.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした