Bolt + lambda を使って Slack に通知メッセージを送る API を作る

この記事は個人ブログと同じ内容です

zenn.dev

 

 

Boltを利用してWebアプリと連携し、Slackワークスペースに所属するユーザーに応じて通知を出し分けるAPIを作ってみたので知見として書きます。

当記事で書くこと

  • Slack Appの設定
  • Bolt + serverless によるSlackBotのAPI実装
    • emailからユーザーとのDMチャンネルを検索
    • 取得したDMチャンネルIDへのメッセージ送信
  • lambdaへのデプロイ

当記事で書かないこと

  • Boltを利用したOAuth周りの認証設定

手順

【SlackApp側の設定】

Slack App 作成 ←のリンクから Slack App を作成する

App Home タブにてアプリの DM に表示するタブを設定

  • "App Display Name" の Edit ボタンから、好きな display name を設定して保存する
  • Messages Tab
    • ここのチェックをtrueにすると Messages Tab でユーザーがメッセージを送信できるようになる Allow users to send Slash commands and messages from the messages tab

OAuth & Permissions タブにて

  • Slack API の利用に必要な以下の権限を設定する
"channels:read",
"chat:write:bot",
"groups:read",
"im:read",
"mpim:read",
"users:read",
"users:read.email"

以下のAPIWorks withに必要なscopeが書いてあります

  • Install to WorkSpace する

【Slack App開発】

サンプルコードは以下のリポジトリで公開しています

まずはサーバーレスアプリケーションを開発、デプロイするためのツールをインストールします

こちらの記事で紹介されている事前準備を行ってください

Lambdaの作成

下記コマンドを実行してNode.js用の作業ディレクトリとLambdaの定義ファイル作成します。

$ serverless create --template aws-nodejs --path myService

以下を実行して初期設定を行います

$ yarn init
myService
├── .npmignore
├── handler.js
├── package.json
└── serverless.yml

以下のコマンドで必要なパッケージをインストールします

$ yarn add @slack/bolt @vendia/serverless-express multiparty
$ yarn add -D serverless serverless-offline

package.jsonに以下のscriptを追記します これでyarn devすることでlocalでデバックできるようになりました

package.json

{
    ...
    "scripts": {
        "dev": "sls offline",
        "deploy": "npx serverless deploy"
    },
}

Serverlessの設定ファイルを以下の内容に変更します

serverless.yml

service: serverless-bolt-js
frameworkVersion: "2"
provider:
  name: aws
  runtime: nodejs12.x
  region: ap-northeast-1
  environment:
    SLACK_SIGNING_SECRET: ${env:SLACK_SIGNING_SECRET}
    SLACK_BOT_TOKEN: ${env:SLACK_BOT_TOKEN}
functions:
  slack:
    handler: app.handler
    events:
      - http:
          method: ANY
          path: /{any+}
useDotenv: true
plugins:
  - serverless-offline
package:
  patterns:
    - '!.git/**'
    - '!README.md'

あわせて環境変数を追加します

.env

SLACK_SIGNING_SECRET="xxx"
SLACK_BOT_TOKEN="xoxb-xxx"

準備ができたら処理を書いていきます

handler.js

const { App, ExpressReceiver } = require("@slack/bolt");
const serverlessExpress = require("@vendia/serverless-express");
const multiparty = require("multiparty");

const accessToken = process.env["SLACK_BOT_TOKEN"];

const expressReceiver = new ExpressReceiver({
  signingSecret: process.env["SLACK_SIGNING_SECRET"],
  processBeforeResponse: true,
});

const app = new App({
  token: accessToken,
  receiver: expressReceiver,
});

// /slack/events/massegesへのpostリクエストのエンドポイント作成
app.receiver.router.post("/slack/events/masseges", async (req, res) => {
  // req から fields を抽出する
  const data = await new Promise((resolve, reject) => {
    const form = new multiparty.Form();
    form.parse(req, (err, fields, files) => {
      resolve(fields);
    });
  });

  // validation
  if (!data.email) {
    res.status(400).send("error: no_email");
    return;
  }
  if (!data.text) {
    res.status(400).send("error: no_text");
    return;
  }

  const userEmailList = data.email.find((_, i) => i === 0).split(",");
  const massege = data.text.find((_, i) => i === 0);

  let userIds = [];
  for (const email of userEmailList) {
    try {
      // reqパラメーターのemilがワークスペースに存在するか確認
      const user = await app.client.users.lookupByEmail({
        token: accessToken,
        email: email,
      });
      if (user) {
        userIds = [...userIds, user.user.id];
      }
    } catch (error) {
      res.status(400).send(`error: user is Not Found. ${email}`);
      return;
    }
  }

  // DMチャンネル一覧を取得
  const conversationsList = await app.client.conversations.list({
    token: accessToken,
    types: "im",
  });
  const channels = conversationsList.channels;

  if (!!userIds.length) {
    // メールアドレスから取得したユーザーの DM チャンネルのみにフィルター
    channels
      .filter((x) => {
        return userIds.some((y) => {
          return y === x.user;
        });
      })
      .forEach((x) => {
        // メッセージ送信
        app.client.chat.postMessage({
          token: accessToken,
          channel: x.id,
          blocks: massege,
        });
      });
  }
  res.status(200).send("success!!");
});

// Handle the Lambda function event
module.exports.handler = serverlessExpress({
  app: expressReceiver.app,
});

デプロイ

以下コマンドでlambdaへデプロイします

yarn deploy

これで完成です!

試してみる

APIエンドポイントに対してcurlでリクエストを送ってみる (email が一致したユーザーは Slack の DM にメッセージが送信される)

aws

通知メッセージを作成する

paramater

  • email : カンマ( , )区切りで通知を送信したいユーザーのメールアドレスを渡す
  • text : メッセージの block を作成しパラメーターに渡す
$ curl --location --request POST 'https://xxxxxxxxxx.amazonaws.com/dev/slack/events/masseges' \
--form 'email="taro@hoge.com,jiro@hoge.com"' \
--form 'text="[
    {
        \"type\": \"section\",
        \"text\": {
            \"type\": \"mrkdwn\",
            \"text\": \"*twitterフォローしてね!*\"
        }
    },
    {
        \"type\": \"section\",
        \"fields\": [
            {
                \"type\": \"mrkdwn\",
                \"text\": \"https://twitter.com/Area029S\"
            }
        ]
    }
]"'

送信できました!

screeen_shot

あとがき

以上でSlackワークスペースに所属するユーザーに応じて通知を出し分けるAPIを作成することができました。 今後はBoltの認証機能を利用したマルチワークスペース対応の実装例を紹介できればと思います。 ちなみにシンプルに通知のみを実行したいのであればBoltを使わないでサービス側で直接SlackAPIを叩いてしまう方が低コストに実現できます。

参考にした記事