CDKでSlack botを作ろう


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

はじめに

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

/set_money hoge
/check_money

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

CDKとは

AWS CDKとは、ざっくりと説明すると、AWSのリソース関係のアーキテクチャの構築とか、デプロイとかをプログラム言語で記述できる便利なツールです。詳しい理解はこのチュートリアルを進めるとわかってくるかと思います。

また、このCDKを使ったIoT的なSlackBotを作る記事はすでに公開されています。今回はそれをベースに簡単なおうむ返しSlackBotを作ってみました。
ソースはここで公開しております。

実装

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

cdk init cdk-slack-bot --language typed

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

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
    })

  }
}
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さんの投稿です。お楽しみに!