🕌

ブラボーな Slack bot をつくる

2022/12/20に公開

はじめに

サッカーワールドカップ、非常に盛り上がりましたね!
日本がまさかドイツとスペインに勝てるとは...
(開幕前は予選敗退すると思っていました、すみませんでした。。)
さて、今回はそんな盛り上がったワールドカップに乗じてブラボーな Slack bot を作成しました。

完成イメージ

bravo.png
いくつか簡単なパターンに合わせてブラボーと返事してくれます。

  • メンションのみ
  • ブラボーとほめてほしい人を指定
    • 1人
    • 複数人
  • インタビュー
    • 「今の心境をお聞かせください」
    • 「まずは今の気持ちいかがですか」

前提

以下の環境で作成しました。

  • WSL2 (Ubuntu20.04)
  • AWS CLI 2.9.8
  • SAM CLI version 1.66.0
  • Node.js v18.12.1
  • yarn v1.22.1

AWS CLI と SAM CLI のインストールはこちらを参考にしてます。
https://qiita.com/hf7777hi/items/6d2b093d6ed7271cf81b
また、AWS CLI を使うためには認証情報等の設定が事前に必要です。
公式ドキュメントを参考にして設定してください。
https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-configure-quickstart.html

作成の流れ

大まかに以下の流れで作成していきます。

  1. hello world を返す API の作成
  2. Slack チャンネル へ hello world と返信する bot の作成
  3. ブラボーな botに修正

hello world を返す API の作成

SAM プロジェクト作成

任意のディレクトリで sam initを実行してください。
そうすると対話式にテンプレートの選択ができます。
今回は API を実行すると「hello world」と返してくれるテンプレートを選択します。
また、最近 TypeScript が使えるようになったらしく、TypeScript を選択してみました。

sam init ログ
$sam init
    
You can preselect a particular runtime or package type when using the `sam init` experience.
Call `sam init --help` to learn more.

Which template source would you like to use?
	1 - AWS Quick Start Templates
	2 - Custom Template Location
Choice: 1

Choose an AWS Quick Start application template
	1 - Hello World Example
	2 - Multi-step workflow
	3 - Serverless API
	4 - Scheduled task
	5 - Standalone function
	6 - Data processing
	7 - Infrastructure event management
	8 - Serverless Connector Hello World Example
	9 - Multi-step workflow with Connectors
	10 - Lambda EFS example
	11 - Machine Learning
Template: 1   

Use the most popular runtime and package type? (Python and zip) [y/N]: N

Which runtime would you like to use?
	1 - aot.dotnet7 (provided.al2)
	2 - dotnet6
	3 - dotnet5.0
	4 - dotnetcore3.1
	5 - go1.x
	6 - go (provided.al2)
	7 - graalvm.java11 (provided.al2)
	8 - graalvm.java17 (provided.al2)
	9 - java11
	10 - java8.al2
	11 - java8
	12 - nodejs18.x
	13 - nodejs16.x
	14 - nodejs14.x
	15 - nodejs12.x
	16 - python3.9
	17 - python3.8
	18 - python3.7
	19 - ruby2.7
	20 - rust (provided.al2)
Runtime: 12

What package type would you like to use?
	1 - Zip
	2 - Image
Package type: 1

Based on your selections, the only dependency manager available is npm.
We will proceed copying the template using npm.

Select your starter template
	1 - Hello World Example
	2 - Hello World Example TypeScript
Template: 2

Would you like to enable X-Ray tracing on the function(s) in your application?  [y/N]: N

Project name [sam-app]: slack-bot

Cloning from https://github.com/aws/aws-sam-cli-app-templates (process may take a moment)

    -----------------------
    Generating application:
    -----------------------
    Name: slack-bot
    Runtime: nodejs18.x
    Architectures: x86_64
    Dependency Manager: npm
    Application Template: hello-world-typescript
    Output Directory: .
    
    Next steps can be found in the README file at ./slack-bot/README.md
        
  
    Commands you can use next
    =========================
    [*] Create pipeline: cd slack-bot && sam pipeline init --bootstrap
    [*] Validate SAM template: cd slack-bot && sam validate
    [*] Test Function in the Cloud: cd slack-bot && sam sync --stack-name {stack-name} --watch

sam init 実行後は以下のようなディレクトリ構成になっているはずです。

$ tree -a -I ".aws-sam|.git|node_modules|coverage"
.
├── .gitignore
├── README.md
├── events
│   └── event.json
├── hello-world
│   ├── .eslintignore
│   ├── .eslintrc.js
│   ├── .npmignore
│   ├── .prettierrc.js
│   ├── app.ts
│   ├── jest.config.ts
│   ├── package.json
│   ├── tests
│   │   └── unit
│   │       └── test-handler.test.ts
│   └── tsconfig.json
└── template.yaml

ビルド

そのままビルドすると以下のようなエラーが出て失敗します。

$ sam build
(省略)
Build Failed
Error: NodejsNpmEsbuildBuilder:EsbuildBundle - Esbuild Failed: cannot find esbuild

そのため、package.json を修正します。

hello-world/package.json
   "devDependencies": {
     "@types/aws-lambda": "^8.10.92",
     "@types/node": "^18.11.4",
     "@typescript-eslint/eslint-plugin": "^5.10.2",
     "@typescript-eslint/parser": "^5.10.2",
-    "esbuild": "^0.14.14",
     "esbuild-jest": "^0.5.0",
     "eslint": "^8.8.0",
     "eslint-config-prettier": "^8.3.0",
     "eslint-plugin-prettier": "^4.0.0",
     "jest": "^29.2.1",
     "prettier": "^2.5.1",
     "ts-node": "^10.9.1",
     "typescript": "^4.8.4"
  },
+  "dependencies": {
+    "esbuild": "^0.14.14"
+   }
 }

その後 yarn を実行します。
npm を使用する方は適宜読み替えてください。

$ cd hello-world
$ yarn

ルートディレクトリに戻って sam build します。

sam build ログ
$ cd ../
$ sam build
Your template contains a resource with logical ID "ServerlessRestApi", which is a reserved logical ID in AWS SAM. It could result in unexpected behaviors and is not recommended.
Building codeuri: ~/develop/slack-bot/hello-world runtime: nodejs18.x metadata: {'BuildMethod': 'esbuild', 'BuildProperties': {'Minify': True, 'Target': 'es2020', 'Sourcemap': True, 'EntryPoints': ['app.ts']}} architecture: x86_64 functions: HelloWorldFunction
Running NodejsNpmEsbuildBuilder:CopySource
Running NodejsNpmEsbuildBuilder:NpmInstall
Running NodejsNpmEsbuildBuilder:EsbuildBundle

Sourcemap set without --enable-source-maps, adding --enable-source-maps to function HelloWorldFunction NODE_OPTIONS

You are using source maps, note that this comes with a performance hit! Set Sourcemap to false and remove NODE_OPTIONS: --enable-source-maps to disable source maps.


Build Succeeded

Built Artifacts  : .aws-sam/build
Built Template   : .aws-sam/build/template.yaml

Commands you can use next
=========================
[*] Validate SAM template: sam validate
[*] Invoke Function: sam local invoke
[*] Test Function in the Cloud: sam sync --stack-name {{stack-name}} --watch
[*] Deploy: sam deploy --guided

デプロイ

以下のような .toml ファイルを用意します。

samconfig.toml
version = 0.1
[default]
[default.deploy]
[default.deploy.parameters]
stack_name = "slack-bot"
s3_prefix = "slack-bot"
region = "ap-northeast-1"
confirm_changeset = true
capabilities = "CAPABILITY_IAM"
disable_rollback = true
image_repositories = []

sam deploy でデプロイをします。
--guided オプションを付けると対話式で必要な項目を入力していくことになります。
初回のデプロイ時はこのオプションを付けると良いでしょう。

sam deploy ログ
$ sam deploy --guided

Configuring SAM deploy
======================

        Looking for config file [samconfig.toml] :  Found
        Reading default arguments  :  Success

        Setting default arguments for 'sam deploy'
        =========================================
        Stack Name [slack-bot]: 
        AWS Region [ap-northeast-1]: 
        #Shows you resources changes to be deployed and require a 'Y' to initiate deploy
        Confirm changes before deploy [Y/n]: Y
        #SAM needs permission to be able to create roles to connect to the resources in your template
        Allow SAM CLI IAM role creation [Y/n]: Y
        #Preserves the state of previously provisioned resources when an operation fails
        Disable rollback [Y/n]: n
        HelloWorldFunction may not have authorization defined, Is this okay? [y/N]: y
        Save arguments to configuration file [Y/n]: Y
        SAM configuration file [samconfig.toml]: 
        SAM configuration environment [default]: 

        Looking for resources needed for deployment:
         Managed S3 bucket: aws-sam-cli-managed-default-samclisourcebucket-********
         A different default S3 bucket can be set in samconfig.toml

        Saved arguments to config file
        Running 'sam deploy' for future deployments will use the parameters saved above.
        The above parameters can be changed by modifying samconfig.toml
        Learn more about samconfig.toml syntax at 
        https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-config.html

Uploading to slack-bot/b70fecc7502d3de3fe519cdc978318ad  1518 / 1518  (100.00%)

        Deploying with following values
        ===============================
        Stack name                   : slack-bot
        Region                       : ap-northeast-1
        Confirm changeset            : True
        Disable rollback             : False
        Deployment s3 bucket         : aws-sam-cli-managed-default-samclisourcebucket-********
        Capabilities                 : ["CAPABILITY_IAM"]
        Parameter overrides          : {}
        Signing Profiles             : {}

Initiating deployment
=====================
Uploading to slack-bot/************.template  1428 / 1428  (100.00%)

Waiting for changeset to be created..
CloudFormation stack changeset
---------------------------------------------------------------------------------------------------------------------------------------------------------
Operation                              LogicalResourceId                      ResourceType                           Replacement                          
---------------------------------------------------------------------------------------------------------------------------------------------------------
+ Add                                  HelloWorldFunctionHelloWorldPermissi   AWS::Lambda::Permission                N/A                                  
                                       onProd                                                                                                             
+ Add                                  HelloWorldFunctionRole                 AWS::IAM::Role                         N/A                                  
+ Add                                  HelloWorldFunction                     AWS::Lambda::Function                  N/A                                  
+ Add                                  ServerlessRestApiDeploymentd4d193690   AWS::ApiGateway::Deployment            N/A                                  
                                       c                                                                                                                  
+ Add                                  ServerlessRestApiProdStage             AWS::ApiGateway::Stage                 N/A                                  
+ Add                                  ServerlessRestApi                      AWS::ApiGateway::RestApi               N/A                                  
---------------------------------------------------------------------------------------------------------------------------------------------------------

Changeset created successfully. arn:aws:cloudformation:ap-northeast-1:************:changeSet/samcli-deploy1234567890/********-****-****-****-************


Previewing CloudFormation changeset before deployment
======================================================
Deploy this changeset? [y/N]: y

2022-12-19 22:29:10 - Waiting for stack create/update to complete

CloudFormation events from stack operations (refresh every 0.5 seconds)
---------------------------------------------------------------------------------------------------------------------------------------------------------
ResourceStatus                         ResourceType                           LogicalResourceId                      ResourceStatusReason                 
---------------------------------------------------------------------------------------------------------------------------------------------------------
CREATE_IN_PROGRESS                     AWS::CloudFormation::Stack             slack-bot                              User Initiated                       
CREATE_IN_PROGRESS                     AWS::IAM::Role                         HelloWorldFunctionRole                 -                                    
CREATE_IN_PROGRESS                     AWS::IAM::Role                         HelloWorldFunctionRole                 Resource creation Initiated          
CREATE_COMPLETE                        AWS::IAM::Role                         HelloWorldFunctionRole                 -                                    
CREATE_IN_PROGRESS                     AWS::Lambda::Function                  HelloWorldFunction                     -                                    
CREATE_IN_PROGRESS                     AWS::Lambda::Function                  HelloWorldFunction                     Resource creation Initiated          
CREATE_COMPLETE                        AWS::Lambda::Function                  HelloWorldFunction                     -                                    
CREATE_IN_PROGRESS                     AWS::ApiGateway::RestApi               ServerlessRestApi                      -                                    
CREATE_IN_PROGRESS                     AWS::ApiGateway::RestApi               ServerlessRestApi                      Resource creation Initiated          
CREATE_COMPLETE                        AWS::ApiGateway::RestApi               ServerlessRestApi                      -                                    
CREATE_IN_PROGRESS                     AWS::Lambda::Permission                HelloWorldFunctionHelloWorldPermissi   -                                    
                                                                              onProd                                                                      
CREATE_IN_PROGRESS                     AWS::ApiGateway::Deployment            ServerlessRestApiDeploymentd4d193690   -                                    
                                                                              c                                                                           
CREATE_IN_PROGRESS                     AWS::Lambda::Permission                HelloWorldFunctionHelloWorldPermissi   Resource creation Initiated          
                                                                              onProd                                                                      
CREATE_IN_PROGRESS                     AWS::ApiGateway::Deployment            ServerlessRestApiDeploymentd4d193690   Resource creation Initiated          
                                                                              c                                                                           
CREATE_COMPLETE                        AWS::ApiGateway::Deployment            ServerlessRestApiDeploymentd4d193690   -                                    
                                                                              c                                                                           
CREATE_IN_PROGRESS                     AWS::ApiGateway::Stage                 ServerlessRestApiProdStage             -                                    
CREATE_IN_PROGRESS                     AWS::ApiGateway::Stage                 ServerlessRestApiProdStage             Resource creation Initiated          
CREATE_COMPLETE                        AWS::ApiGateway::Stage                 ServerlessRestApiProdStage             -                                    
CREATE_COMPLETE                        AWS::Lambda::Permission                HelloWorldFunctionHelloWorldPermissi   -                                    
                                                                              onProd                                                                      
CREATE_COMPLETE                        AWS::CloudFormation::Stack             slack-bot                              -                                    
---------------------------------------------------------------------------------------------------------------------------------------------------------
CloudFormation outputs from deployed stack
----------------------------------------------------------------------------------------------------------------------------------------------------------
Outputs                                                                                                                                                  
----------------------------------------------------------------------------------------------------------------------------------------------------------
Key                 HelloWorldFunctionIamRole                                                                                                            
Description         Implicit IAM Role created for Hello World function                                                                                   
Value               arn:aws:iam::************:role/slack-bot-HelloWorldFunctionRole-PF0VG0WCZJFC                                                         

Key                 HelloWorldApi                                                                                                                        
Description         API Gateway endpoint URL for Prod stage for Hello World function                                                                     
Value               https://************.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/                                                              

Key                 HelloWorldFunction                                                                                                                   
Description         Hello World Lambda Function ARN                                                                                                      
Value               arn:aws:lambda:ap-northeast-1:************:function:slack-bot-HelloWorldFunction-Qtlbtu7fOTYE                                        
----------------------------------------------------------------------------------------------------------------------------------------------------------

Successfully created/updated stack - slack-bot in ap-northeast-1

デプロイが成功すると、AWS に以下のリソースが作成されます。
resources.png

さて、これで API が実行できるようになりました。
出力された API の URL をブラウザなり curl なりで実行してみましょう。

Value               https://************.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/ 

hello world と帰ってきたら成功です。

{"message":"hello world"}

なお、できた S3 のバケット名は控えておいてください。[1] 再デプロイ時に使用します。

Deployment s3 bucket         : aws-sam-cli-managed-default-samclisourcebucket-********
$ SAM_S3_BUCKET="aws-sam-cli-managed-default-samclisourcebucket-********"
$ sam deploy --s3-bucket "${SAM_S3_BUCKET}"

Slack チャンネル へ hello world と返信する bot の作成

Slack App の Event Subscriptions という機能を用いて、bot にメンションしたら特定の URL にリクエストが飛ぶようにします。
まず bot 自体の API の設定とソースコードの修正を行います。

API の 修正

Slack App は POST メソッドでリクエストを送れるようにしないといけません。
本当は新たにエンドポイントを追加するのが良いでしょうが、
今回は横着をして /hello を GET メソッドから POST メソッドで受け付けるようにしました。

template.yaml
Resources:
  HelloWorldFunction:
    Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
    Properties:
      CodeUri: hello-world/
      Handler: app.lambdaHandler
      Runtime: nodejs18.x
      Architectures:
        - x86_64
      Events:
        HelloWorld:
          Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
          Properties:
            Path: /hello
-            Method: get
+            Method: post

Challenge 認証対応

Slack App の Event Subscriptions に設定する URL は Challenge 認証が通らないと使えません。
ただ、対応はいたってシンプルです。
Challenge 認証をすると リクエスト の body から challenge が取れるので、レスポンスとして challenge の値を返すように修正するだけです。
詳細は以下を参照にしてください。
https://dev.classmethod.jp/articles/challenge-about-challenge-request-for-slack/
また、Challenge 認証 は次項のサンプルで合わせて実装しています。

Slack へメッセージ送信

Slack からリクエストを受けたあと、特定のチャンネルにメッセージを送れるようにします。
まず、Slack Web API を使用するために@slack/web-apiをインストールします。

$ cd hello-world
$ yarn add @slack/web-api

次にソースコードを修正します。
以下は chat.postMessage を使用して 特定のチャンネルへメッセージを送信するサンプルです。
token (SLACK_TOKEN) と channnel (SLACK_CHANNEL) を Lambda の環境変数に手で設定しておく前提でコーディングしています。[2]

サンプル
hello-world/slack/message.ts
import { ChatPostMessageResponse, WebClient } from "@slack/web-api";

const token = process.env['SLACK_TOKEN'] || '';
const defaultChannel = process.env['SLACK_CHANNEL'] || '';

type PostMessageRequest = {
    text: string,
    channel?: string,
};

const postMessage = async ({ text, channel = defaultChannel }: PostMessageRequest): Promise<ChatPostMessageResponse> =>
    await new WebClient(token).chat.postMessage({ channel, text });

export { postMessage };
hello-world/app.ts
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';

import { postMessage } from './slack/message';

export const lambdaHandler = async (event: APIGatewayProxyEvent) => {
    try {
	const body = JSON.parse(event.body || '{}');
	if (isChallenge(body)) {
	    return {
		statusCode: 200,
		body: JSON.stringify({
		    challenge: body.challenge,
		}),
	    };
	}
        const response = await postMessage({ text: 'hello world' });
        return {
            statusCode: 200,
            body: JSON.stringify({
                message: response.message,
            }),
        };
    } catch (err) {
      //...
    }
};
const isChallenge = ({ challenge }: { challenge: string }) => !!challenge;

修正が終わったら再デプロイをします。

$ SAM_S3_BUCKET="aws-sam-cli-managed-default-samclisourcebucket-********"
$ sam deploy --s3-bucket "${SAM_S3_BUCKET}"

Slack App の設定

bot の修正 および再デプロイが終わったので、今度は Slack 側の設定をしていきます。
大まかに以下が必要となります。

  • Slack App 作成
    • App の設定 (アイコンや名前等)
    • 権限の設定
      • app_mentions:read
      • chat:write
      • chat:write.public
    • インストール
  • Event Subscriptions の有効化
    • Slack App の 設定画面から API の URL を設定して Verified になることを確認
  • 任意のチャンネルに Slack App を追加

細かい手順はこちらの記事に非常によくまとまっています。参考にしてください。
https://zenn.dev/mokomoka/articles/6d281d27aa344e

ブラボーな botに修正

これまでは hello world しか返さない bot でしたが、ここからもう少し手を加えます。

  1. リクエストからユーザーが bot に送ったメッセージを抽出する
  2. メッセージに応じて返答を変える
    まず、ユーザーが bot に送ったメッセージを抽出しましょう。

メッセージの抽出

これが厄介で、今回もっともハマったところでした。
大きく2点ポイントがあります。

  1. ネストの深さ
    以下はリクエストの body になります。非常にネストが深いことがお分かりいただけるかと思います。

    body
    {
        "token": "************",
        "team_id": "************",
        "api_app_id": "************",
        "event": {
            "client_msg_id": "****************************",
            "type": "app_mention",
            "text": "<@************> abc\\u3042\\u3044\\u3046\\u3048\\u304a123",
            "user": "************",
            "ts": "1671488956.835939",
            "blocks": [
                {
                    "type": "rich_text",
                    "block_id": "=nT",
                    "elements": [
                        {
                            "type": "rich_text_section",
                            "elements": [
                                {
                                    "type": "user",
                                    "user_id": "************"
                                },
                                {
                                    "type": "text",
                                    "text": " abc\\u3042\\u3044\\u3046\\u3048\\u304a123"
                                }
                            ]
                        }
                    ]
                }
            ],
            "team": "************",
            "channel": "************",
            "event_ts": "1671488956.835939"
        },
        "type": "event_callback",
        "event_id": "**************",
        "event_time": 1671488956,
        "authorizations": [
            {
                "enterprise_id": null,
                "team_id": "************",
                "user_id": "************",
                "is_bot": true,
                "is_enterprise_install": false
            }
        ],
        "is_ext_shared_channel": false,
        "event_context": "************************************************************"
    }
    

    この JSON からメッセージを抜き出すためには、例えば TypeScript で以下のように書けます。

    body
      .event?
      .blocks?.[0]?
      .elements?.[0]?
      .elements?
      .map(({ text }: { text: string }) => text)
      .join('')
    

    map の中で text というフィールドのみ抜き出しています。[3]

  2. ユニコード(¥uxxx形式)のアンエスケープ

    メッセージの抽出はこれでOKですが、抜き出した文字列がエスケープされてます。
    さらに厄介にさせているのが、半角文字はエスケープされておらず、エスケープされている文字とそうでない文字が混在しているところです。

    abc\\u3042\\u3044\\u3046\\u3048\\u304a123 // abcあいうえお123
    

    これを自力でまじめに対処するとなると非常に厄介なのですが、先人の知恵をお借りしましょう。
    以下を追加します。

    yarn add unescape-js
    

    ソースコードはこちらです。
    https://github.com/iamakulov/unescape-js

    また、使い方は以下のようになります。

    unescape サンプル
    hello-world/essage_extractor.ts
    import unescapeJs from 'unescape-js';
    
    export const extractMessage = (body: any): string => {
        const message = body.event?.blocks?.[0]?.elements?.[0]?.elements?.map(({ text }: { text: string }) => text).join('') || '';
        return unescapeJs(message);
    };
    
    hello-world/app.ts
    import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
    
    import { postMessage } from './slack/message';
    import { extractMessage } from './message_extractor';
    
    export const lambdaHandler = async (event: APIGatewayProxyEvent): => {
        try {
            const body = JSON.parse(event.body || '{}');
            if (isChallenge(body)) {
                return {
                    statusCode: 200,
                    body: JSON.stringify({
                        challenge: body.challenge,
                    }),
                };
            }
            const message = extractMessage(body);
            console.log({message});
            const response = await postMessage({ text: message });
            return {
                statusCode: 200,
                body: JSON.stringify({
                    message: response.message,
                }),
            };
        } catch (err) {
            // ...
        }
    };
    
    const isChallenge = ({ challenge }: { challenge: string }) => !!challenge;
    

メッセージに応じて返答を変える

メッセージに応じたブラボーのバリエーションを用意します。
もうちょっと調整していい感じにできるかなと思いますが、何パターンか用意できたので妥協しました。

ブラボーのパターン分け
bravo.ts
export const getBravo = (message: string): string => {
    if (!message) {
        return `ブラボォォー!!!`;
    }
    const trimedMessages = message.split(' ').filter(m => !!m);
    if (trimedMessages.length >= 2) {
        return `${trimedMessages.map(m => m + 'も').join('')}みーんなブラボー、すごすぎる。`;
    }
    if (interviewPatterns.vsGermany.some(m => m.match(trimedMessages[0]))) {
        return `みんなブラボー、ブラボォー、、ブラボォォー!!!`;
    }
    if (interviewPatterns.vsSpain.some(m => m.match(trimedMessages[0]))) {
        return `あれ言っていいっすか?小さい声で言うんで、小さい声で\n\nブラボォォー!!!`;
    }
    return `${trimedMessages[0]}、ブラボォォー!!!`;
};

const interviewPatterns = {
    vsGermany: ['今の心境お聞かせください', 'いまの心境お聞かせください'],
    vsSpain: ['まずは今の気持ちいかがですか', 'まずはいまの気持ちいかがですか'],
};

最後に仕上げです。app.ts の postMessage() に渡すテキストを上記で取得したブラボーなメッセージに変えます。

ブラボーなメッセージで返答
app.ts
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';

import { postMessage } from './slack/message';
import { extractMessage } from './message_extractor';
import { getBravo } from './bravo';

export const lambdaHandler = async (event: APIGatewayProxyEvent) => {
    try {
        console.log(event);
        const body = JSON.parse(event.body || '{}');
        if (isChallenge(body)) {
            return {
                statusCode: 200,
                body: JSON.stringify({
                    challenge: body.challenge,
                }),
            };
        }
        const message = extractMessage(body);
        console.log({message});
        const bravo = getBravo(message);
        console.log({bravo});
        const response = await postMessage({ text: bravo });
        return {
            statusCode: 200,
            body: JSON.stringify({
                message: response.message,
            }),
        };
    } catch (err) {
        // ...
    }
};

const isChallenge = ({ challenge }: { challenge: string }) => !!challenge;

こちらをデプロイすれば bot の完成です🎉

おわりに

流行り(?)に乗っかった bot をネタとして作成しました。
この bot を通して、Slack からの Event を受け取り、Slack にメッセージを投げる方法が分かりました。
発展させていくと、例えば勤怠管理や辞書登録をする実用的なアプリも作成できるかと思います。
次回はこの bot をもう少しエンハンスしてより実用的なアプリの作成にトライしていきたいです。

脚注
  1. 実は samconfig.toml でS3のバケット名を指定できます。今回はセキュリティ上バケット名を晒したくないためあえて samconfig.toml に記載していません。ソース管理することも考えると面倒ですがデプロイ時にバケットを指定したほうが良いかと思います。 ↩︎

  2. AWS のパラメータストアやシークレットマネージャーから token や チャンネルを取ってくるようにした方がベターかもしれません。 ↩︎

  3. 正確には text というフィールドがない場合に undefined が返ってきます。後続の join メソッドで undefined が空文字に変換されています。 ↩︎

Discussion