[AWS] LINEWORKSでリマインダBOTを作ってみた

Advent Calendar7日目です。

今回は、AWS環境でリマインダBOTを作成してみました。
Cloudformationのテンプレートも掲載しますので、興味がある方はご覧ください。

Lambdaでの実装の記事は [こちら](https://qiita.com/peyryo/items/ff5a2a693d47ce13fa18)

## 完成したBOT
BOTのやりとりはこんな感じです。
![iOS の画像.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/504215/afb367bc-bc86-dbba-4aed-89eba7869a70.png)


以下の機能があります。

- イベント登録機能
    - イベントのタイトル、時間を入力することでリマインドしたいイベントを登録できます。
    - 「イベント登録」ボタンを押すことで登録が開始します。

- イベント参照機能
    - 登録したイベントを参照することができます。
    - 「イベント参照」ボタンを押すことでイベントを参照することができます。

- イベント通知機能
    - 登録したイベントの日時が近づくとBOTが通知してくれます。

今回は、BOTにトーク固定メニューを用いてボタンを配置しました。


## 全体構成
![overview.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/504215/faad98ca-08f7-6e20-554a-464f81787383.png)

BOTのバックエンドの構築はAWSを利用しました。
今回はなるべく手軽に作るために、サーバレスで構築してみました。

BOTとのやり取りは API Gateway と Lambda が担い、トークの状態管理に DynamoDB を使用しています。

LINEWORKS -> AWS へのメッセージは、API Gateway経由でlambdaで処理されます。
そのため、LINEWORKSのBOTのcallback URLには、API GatewayのURLを設定してあります。

AWS->LINEWORKS へのメッセージ通知は、SQS を介して送信用のLambdaが行います。
メッセージ送信に必要なアクセストークンおよび認証キーは、S3 で管理してあります。

リマインドの通知は、CloudWatch Event を利用して Lambda を定期的に起動し、
DynamoDB内にあるイベントをポーリングすることで実現しています。

# テンプレート
AWSのリソース作成には、 Cloudformation を用いています。
今回は、Lambda と API Gateway を使用しているため、記述が容易な AWS SAM を利用しました。

各種リソースの名称と設定値は適当に設定してありますので、もし使用する場合は適宜変えてください。

template.yaml

```yaml
AWSTemplateFormatVersion: 2010-09-09
Transform:
- AWS::Serverless-2016-10-31

# テンプレートのパラメータに LINEWORKS の BOT に必要な認証情報を渡すように設定
Parameters:
  BotNo:
    Description: LINEWORKS bot number
    Type: String
  ApiId:
    Description: LINEWORKS api id
    Type: String
  ServerListId:
    Description: LINEWORKS server list id
    Type: String
  ServerApiConsumerKey:
    Description: LINEWORKS server api consumer key
    Type: String

# 全Lambda関数に適用されるプロパティ
Globals:
  Function:
    AutoPublishAlias: live
    Timeout: 10
    # Lambdaの関数に適用されるプロパティ
    Environment:
      Variables:
        BOT_NO: !Ref BotNo
        API_ID: !Ref ApiId
        SERVER_LIST_ID: !Ref ServerListId 
        SERVER_API_CONSUMER_KEY: !Ref ServerApiConsumerKey

Resources:

  # AWS -> LINEWORKSへの通知を担うLambdaの関数
  SendLambda:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: 'send-lineworks-message'
      Handler: index.handler
      Runtime: python3.7
      CodeUri: lambda/send-message
      Role:
        Fn::GetAtt:
        - LambdaExecutionRole
        - Arn
      Events:
        # SQS のトリガーをここで定義
        SQS1:
          Type: SQS
          Properties:
            Queue:
              Fn::GetAtt:
                - MessageQueue
                - Arn
            BatchSize: 10

  # LINEWORKS -> AWS の受信処理を担うLambdaの関数
  RecieveMessage:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: 'recieve-lineworks-message'
      Handler: index.handler
      Runtime: python3.7
      CodeUri: lambda/recieve-message
      Role:
        Fn::GetAtt:
        - LambdaExecutionRole
        - Arn
      Events:
        PostEvent:
          Type: Api
          Properties:
            Path: /callback
            Method: post

  # DynamoDBから登録されているEventを取得するLambda関数
  GetEvents:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: 'get-events'
      Handler: index.handler
      Runtime: python3.7
      CodeUri: lambda/get-events
      Role:
        Fn::GetAtt:
        - LambdaExecutionRole
        - Arn
      Events:
        # CloudWatch Eventをここで定義
        Schedule:
          Type: Schedule
          Properties:
            # ポーリング間隔は5分
            Schedule: rate(5 minutes)

  # Lambda関数 の権限（甘め）
  # とりあえず、全Lambda関数に適用
  LambdaExecutionRole:
    Description: Creating service role in IAM for AWS Lambda
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub 'LineWorksLambdaExecution'
      AssumeRolePolicyDocument:
        Statement:
        - Effect: Allow
          Principal:
            Service: [lambda.amazonaws.com]
          Action: sts:AssumeRole
      Path: /
      ManagedPolicyArns:
        - !Sub 'arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'
      Policies:
        - 
         PolicyName: lineworks-lambda-execution-role
         PolicyDocument:
           Version: "2012-10-17"
           Statement:
              - 
                Effect: "Allow"
                Action: "sqs:*"
                Resource: "*"
              - 
                Effect: "Allow"
                Action: "dynamodb:*"
                Resource: "*"

  # DynamoDBの定義
  LineWorksDB:
    Type: AWS::DynamoDB::Table
    Properties:
      AttributeDefinitions: 
        - 
          AttributeName: "Hash"
          AttributeType: "S"
        - 
          AttributeName: "Range"
          AttributeType: "S"
      KeySchema: 
        - 
          AttributeName: "Hash"
          KeyType: "HASH"
        - 
          AttributeName: "Range"
          KeyType: "RANGE"
      ProvisionedThroughput:
        ReadCapacityUnits: "1"
        WriteCapacityUnits: "1"
      TableName: lineworks-sample-table
      # 「ExpireTime」をTTLに設定
      # TTLを設定することでアイテムの自動削除が可能
      TimeToLiveSpecification:
        AttributeName: ExpireTime
        Enabled: true
      Tags:
        - Key: key
          Value: value

  # SQSの定義
  MessageQueue:
    Type: 'AWS::SQS::Queue'
    Properties:
      QueueName: lineworks-message-queue
```

デプロイする際は、以下のスクリプトを用いました。
スタック名も適当です。

build.sh

```bash
### ここは適宜それぞれの環境に合わせて変更
BOT_NO="xxx"
API_ID="yyy"
SERVER_LIST_ID="zzz"
SERVER_API_CONSUMER_KEY="aaa"
DEPLOY_S3_BUCKET = "bbb"
###

aws cloudformation package --template template.yml --s3-bucket ${DEPLOY_S3_BUCKET} --output-template template-export.yml

aws cloudformation deploy \
    --template-file template-export.yml \
    --stack-name lineworks-sample-stack \
    --capabilities CAPABILITY_NAMED_IAM \
    --parameter-overrides BotNo=${BOT_NO} ApiId=${API_ID} ServerListId=${SERVER_LIST_ID} ServerApiConsumerKey=${SERVER_API_CONSUMER_KEY}

```

## まとめ

AWSでサーバレスな環境でリマインダBOTを作成してみました。

次回は、Lambda関数の実装について紹介したいと思います。
[実装編のリンク先](https://qiita.com/peyryo/items/ff5a2a693d47ce13fa18)