14
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

NECソリューションイノベータAdvent Calendar 2021

Day 21

aws-cdkを使って恋人(?)をLINE Botでつくってみた

Last updated at Posted at 2021-12-20

はじめに

みなさんいかがお過ごしでしょうか。早いもので今年もクリスマスが近づいていますね。
もしかすると1人でお家で凍えて寂しい思いをされている方がいるかもしれません。
そんな方のために恋人っぽく返事をくれるBotを作ってみました。

散々擦られたネタですが、最近はLINE Botに触れる機会が多くあり
aws cdkを使って構築してみたりもしたのでまとめてみることにしました。

構成

AWS上にaws-cdk(typescript)を使用して構築しています。
簡単な定型文の応答では面白くないので、Amazon Comprehendを使ってBotに送信されたメッセージの感情分析を行って、その内容に合わせて返答を行うようにしています。

image.png

LINEアカウントの準備

LINE Developersへ登録してLINEの公式アカウントを作成します。
以下の公式ページの手順に従って作成しましょう。

ソースコード上にチャネルアクセストークンとチャネルシークレットを指定する必要があります。
以下のページの取得手順を参考に、それらの値をメモしておきましょう。

また、アカウント作成時点ではLINE Official Account上で自動応答メッセージが有効になっていると思います。
そのままだとなにかメッセージを送るたびに意図しないメッセージが返却されてしまう状態になってしまうので、オフに設定しておきましょう。

image.png

環境構築

環境構築をしていきます。
筆者はcloud9上で作業を行ってます。
構築した際のNodejsとnpmのバージョンは以下となります。

  • Nodejs : v14.18.2
  • npm : 6.14.15

aws-cdk install

cdkをインストールしましょう。
cdkとはこちらで説明されているように、AWS上のリソースをJavascriptなどのプログラム言語で定義することができるフレームワークになります。

構築手順に関してはこちらの公式手順が参考になると思います。
作業時点でのaws-cdkのバージョンは1.136.0でした。

  • aws-cdkのインストール
npm install -g aws-cdk
cdk --version
  • プロジェクトの作成
mkdir line-bot
cd line-bot
cdk init sample-app --language typescript
  • ライブラリインストール
npm i @aws-cdk/aws-apigateway @aws-cdk/aws-lambda-nodejs @line/bot-sdk

作業時点でのインストールしたライブラリのバージョンは以下となります。
cdkで利用するライブラリはcdkのバージョンと同一にそろえておかないとエラーが発生しがちなので注意しましょう。

  • @aws-cdk/aws-apigateway : 1.136.0
  • @aws-cdk/aws-lambda-nodejs : 1.136.0
  • @line/bot-sdk : 7.4.0

ディレクトリ構成

上述した手順より、テンプレートファイル群が生成されたと思います。
最終的には以下のようにファイルを配置します。

├── bin
│   └── line-bot.ts
├── lambda
│   └── app.ts
├── lib
│   └── line-bot-stack.ts
├── test
│   └── line-bot.test.ts
├── cdk.json
├── jest.config.js
├── package-lock.json
├── package.json
└── tsconfig.json

API Gateway及びLambdaを定義

lib/line-bot-stack.tsへ以下のソースコードを記述します。
API GatewayとLambdaを定義し、Comprehendの実行権限を付与したロールをLambdaへ紐付けています。
LINE_TOKEN及びLINE_CHANNEL_SECRETにはLINE Developersコンソール上で取得したチャンネルアクセストークン及びチャネルシークレットをそれぞれ指定してください。

lib/line-bot-stack.ts
import * as cdk from '@aws-cdk/core';
import * as apigateway from "@aws-cdk/aws-apigateway";
import * as iam from "@aws-cdk/aws-iam";
import { NodejsFunction } from '@aws-cdk/aws-lambda-nodejs';

export class LineBotStack extends cdk.Stack {
  constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // Role
    const lineBotRole = new iam.Role(this, 'lineBotRole', {
      assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
      managedPolicies: [
        iam.ManagedPolicy.fromAwsManagedPolicyName(
          'service-role/AWSLambdaBasicExecutionRole'
        ),
        iam.ManagedPolicy.fromAwsManagedPolicyName('ComprehendFullAccess'),
      ],
    });

    // Lambda
    const lineBotFunction = new NodejsFunction(this, 'lineBotFunction', {
      entry: 'lambda/app.ts',
      handler: 'handler',
      role: lineBotRole,
      timeout: cdk.Duration.seconds(30),
      environment: {
        // line messaging api
        LINE_TOKEN: 'XXXX',
        LINE_CHANNEL_SECRET: 'XXXX',
      },
    });

    // APIGateway
    const apiRoot = new apigateway.LambdaRestApi(this, 'line-sample', {
      handler: lineBotFunction
    });
  }
}

Lambdaの実装(Botの応答部分)

lambda/app.ts
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
import * as line from '@line/bot-sdk'
import * as aws from "aws-sdk";

const client = new line.Client({
  channelAccessToken: process.env.LINE_TOKEN,
  channelSecret: process.env.LINE_CHANNEL_SECRET
})

// for signature line header
const crypto = require('crypto')

export async function handler(event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> {
  console.log("processing line bot api...")
  // x-line-signature request headerを検証する
  // https://developers.line.biz/ja/reference/messaging-api/#signature-validation
  const signature = crypto.createHmac('SHA256', process.env.LINE_CHANNEL_SECRET).update(event.body).digest('base64');
  if (signature != event.headers['x-line-signature']) {
    console.error("fail signature, unauthorized.")
    return {
      statusCode:401,
      body: {
        message: "Unauthorized"
      }
    }
  }
  const requestBody: line.WebhookRequestBody = JSON.parse(event.body!)
  // 返信する
  for (let event of requestBody.events) {
    // メッセージのみ返信する
    if ( event.type == 'message') {
      // テキストのときのみ返信する
      if ( event.message.text !== undefined ) {
        // comprehends
        const resultComp = await comprehend(event.message.text)
        // メッセージ選択
        const returnText = createReturnText(resultComp)
        if (!returnText) {
          break
        }
        // replyMessage
        await client.replyMessage(event.replyToken, {
          type: "text",
          text: returnText
        })
        
      }
    } else {
      console.log("done. this request's type is not message.")
    }
  }
  return {
    statusCode: 200,
    headers: { "Content-Type": "text/plain" }
  }
}

/**
 * comprehendを使用してメッセージを分析する
 */
 async function comprehend(text: string) {
  const compClient = new aws.Comprehend();
  const request = {
    LanguageCode: "ja",
    Text: text,
  };
  try {
    const result = await compClient.detectSentiment(request).promise();
    console.log(result)
    return result.Sentiment
  } catch (err) {
    console.error("Comprehend Error: " + err)
    return false
  }
 }
 
 /**
  * comprehendの結果より応答メッセージを生成
  */
function createReturnText(sentiment: string) {
  let message: string, ary: string[]
  if ( sentiment == 'POSITIVE' ) {
    ary = ['すっごい!','さすがだねぇ','いいね!'];
    message = ary[Math.floor(Math.random() * ary.length)];
  } else if (sentiment == 'NEGATIVE') {
    ary = ['それはやばいね・・・', 'どんまい!', 'ひどいね、、'];
    message = ary[Math.floor(Math.random() * ary.length)];
  } else if (sentiment == 'NEUTRAL') {
    ary = ['うぇ〜い', 'うんうん', 'そうだね〜'];
    message = ary[Math.floor(Math.random() * ary.length)];
  } else {
    message = "ふーん"
  }
  return message
}

デプロイ

以下のコマンドを実行し、デプロイを実行します。
デプロイが完了するとAPI Gatewayのエンドポイントが表示されるので、その値をコピーしておいてください。

cdk deploy

LINE上でのWebhook設定

以下の公式ページを参考に、コピーしておいたAPI GatewayのエンドポイントをWebhook URLへ設定しましょう。
これにより、作成したLINEアカウントに対するメッセージ受信時などのイベント情報がWebhook先のURLへ通知されるようになります。

動作確認

ではさっそく会話してみましょう!!
image.png

ちゃんと動いてくれてますね!

まとめ

aws-cdkを使って、AWS上にComprehendを利用したLINE Botを作ってみました。
文脈から感情分析して応答してくれるので、それなりに動いてくれているように思えます。
ただ、恋人と言えるかどうかは微妙なところですね。。
時間があれば固有表現抽出とかやってもっと高度な応答ができるようにしていきたいです!

14
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?