CDKでSlack botを作ろう

どうも！@ufoo68です。今回２回目の投稿になります。@keigo1450さんよりバトンパスをもらっての投稿となります。前日のものはかなり実用的で新卒の私はすごく勉強になりました。
そんな私の方は、また作ってみた系になります。

# はじめに
この記事が投稿されている頃、[今頃私はラスベガスにいる](http://reinvent.awseventsjapan.com/)ことでしょう。ということでAWS関係のことを書こうと思います(そのために[ソリューションアーキテクト-アソシエイト](https://aws.amazon.com/jp/certification/certified-solutions-architect-associate/)とりました)。
あとこの記事は、自分がreinvent中にQiitaを書くというチャレンジで社内のアドベントカレンダーに登録しました。作ったものですが、Slack上で以下のコマンド

```
/set_money hoge
```

```
/check_money
```

を叩いてカジノの勝ち金を見れるSlackBotを作りました。本当はカジノの戦略考えるBotとか考えましたが、時間と私の知識力により断念。。。

# [CDK](https://docs.aws.amazon.com/cdk/latest/guide/home.html)とは
AWS CDKとは、ざっくりと説明すると、AWSのリソース関係のアーキテクチャの構築とか、デプロイとかをプログラム言語で記述できる便利なツールです。詳しい理解は[このチュートリアル](https://docs.aws.amazon.com/cdk/latest/guide/home.html)を進めるとわかってくるかと思います。

また、この[CDKを使ったIoT的なSlackBotを作る記事](https://dev.classmethod.jp/cloud/aws/aws-cdk-slack-slash-commands-aircon-on/)はすでに公開されています。今回はそれをベースに簡単なおうむ返しSlackBotを作ってみました。
ソースは[ここ](https://github.com/ufoo68/cdk-slack-bot)で公開しております。

# 実装
まずは以下のコマンドでテンプレートを準備します

```bash
cdk init cdk-slack-bot --language typed
```

あとはサンプルコードに以下を付け足したり修正したりします。

```typescript:cdk-slack-bot/lib/cdk-slack-bot-stack.ts
import cdk = require('@aws-cdk/core')
import lambda = require('@aws-cdk/aws-lambda')
import apigw = require('@aws-cdk/aws-apigateway')
import dynamodb = require('@aws-cdk/aws-dynamodb')

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

    const table = new dynamodb.Table(this, 'Money', {
      partitionKey: { name: 'path', type: dynamodb.AttributeType.STRING }
    })

    const bot = new lambda.Function(this, 'BotHandler', {
      runtime: lambda.Runtime.NODEJS_8_10,
      code: lambda.Code.asset('lambda'),
      handler: 'bot.handler',
      environment: {
        HITS_TABLE_NAME: table.tableName
      }
    })

    table.grantReadWriteData(bot);

    new apigw.LambdaRestApi(this, 'Endpoint', {
      handler: bot
    })

  }
}
```


```typescript:cdk-slack-bot/lambda/bot.ts
import * as QueryString from 'querystring'
import { DynamoDB } from 'aws-sdk'

exports.handler = async function (event: any) {
    const query = QueryString.parse(event.body)
    const dynamo = new DynamoDB()
    switch (query.command) {
        case '/set_money':
            if (query.user_name !== 'myname') return {
                statusCode: 200,
                headers: { "Content-Type": "text/plain" },
                body: 'You are not me'
            }
            await dynamo.updateItem({
                TableName: process.env.HITS_TABLE_NAME as string,
                Key: { path: { S: 'money' } },
                UpdateExpression: 'SET money = :num',
                ExpressionAttributeValues: { ':num': { N: query.text as string } }
            }).promise()
            return {
                statusCode: 200,
                headers: { "Content-Type": "text/plain" },
                body: 'Set your total money'
            }
        case '/check_money':
            const money = await dynamo.getItem({
                TableName: process.env.HITS_TABLE_NAME as string,
                Key: { path: { S: 'money' } }
            }).promise()
            return {
                statusCode: 200,
                headers: { "Content-Type": "text/plain" },
                body: `勝ち分は、${money.Item!.money.N}ドルです。`
            }
        default:
            return {
                statusCode: 200,
                headers: { "Content-Type": "text/plain" },
                body: 'invalid command'
            }
    }
}
```

使ったのは`lambda`, `dynamodb`, `apigateway`の3つになります。かなり雑な実装ですが、一応私しか`set_money`できないようにガードをかけました（簡単にハックされるとは思いますが）。

# さいごに
一応このBotは社内Slackのワークスペースにこっそり入れたので`check_money`で私の勝ち分を確認することができます。まあ、今の所マイナスですが。。。
さて、明日も@keigo1450さんの投稿です。お楽しみに！