ねぎ嫌い

思いついたことをてきとうに。

Slack Botの実装をbolt x typescript x cloud functionsで実現する

背景

  • Slack上の特定のコマンドやアクションに引っ掛けて、チャンネル作成や通知をしたい
  • 特にチャンネル作成の権限がシステム管理者や特定の役割を持った人に集中している場合に、彼らを通さずに実現したい

TL;DR

  • boltに関しては日本語訳あるので、公式を読む
  • Slackのどの権限が必要なのかはSlack APIを読む
  • Cloud Functions (本文ではFirebase Functionsを利用) の構成はFirebaseの公式を読む

前提条件

  • 自分が自由にできるSlackのworkspaceを用意する (持っていなければ、公式を読もう)
  • 自分が自由にできるGCPのプロジェクトを用意する
  • Node.js (v12以上) がインストールされている (インストールされていなければ 公式の手順に従ってインストールする)

概要

SlackのBoltフレームワークと、Firebase SDKを用いて簡単にSlack Botの実装をする
今回は、Botにメンション付けたらチャットにメッセージを返してくれる簡易的な、何の役にも立たないものを実装する。
やるべきことは大きく分けて3ステップ

  • プロジェクトのセットアップ
  • 実装
  • Slack Botのセットアップ

ステップ

プロジェクトのセットアップ

firebase sdkのインストール

> npm i -g firebase-sdk
> fireabse --version

firebase コマンドが使えていればOK。
続いてFirebase Functionsのプロジェクトを開始する
いくつか質問があるので回答する

> firebase init


# まずはFunctionsを作成したいので、functionsにカーソルを合わせてスペースで選択

? Which Firebase CLI features do you want to set up for this folder? Press Space to select features, then Enter to confirm your choices. (Press <space> to select, <a> to toggle all, <i> to invert selection)
 ◯ Database: Configure Firebase Realtime Database and deploy rules
 ◯ Firestore: Deploy rules and create indexes for Firestore
❯◯ Functions: Configure and deploy Cloud Functions
 ◯ Hosting: Configure and deploy Firebase Hosting sites
 ◯ Storage: Deploy Cloud Storage security rules
 ◯ Emulators: Set up local emulators for Firebase features
 ◯ Remote Config: Get, deploy, and rollback configurations for Remote Config

# 利用するGCP (Firebase) のプロジェクトを選択
# 持っていればexisting project, なければ新しく作る

First, let's associate this project directory with a Firebase project.
You can create multiple project aliases by running firebase use --add, 
but for now we'll just set up a default project.

? Please select an option: (Use arrow keys)
❯ Use an existing project 
  Create a new project 
  Add Firebase to an existing Google Cloud Platform project 
  Don't set up a default project 

# 型の恩恵を受けたいのでTypescriptを選択する

A functions directory will be created in your project with a Node.js
package pre-configured. Functions can be deployed with firebase deploy.

? What language would you like to use to write Cloud Functions? (Use arrow keys)
❯ JavaScript 
  TypeScript 

# あとは適当で良い。GoogleのESLintは厳し目なので合わない人は選択しないほうが良い

続いて、利用するフレームワーク群のインストールをしておく。
ソースコードはfunctions/以下のディレクトリに集約されるので、どこでnpm スクリプトを動かすかは意識しておく。

> cd functions/
# bolt本体のインストール
> npm i @slack/bolt 

# 型定義をインストール
> npm i -D @types/node 

バックエンドの実装

構成は以下

.
├── jest.config.ts
├── package-lock.json
├── package.json
├── src
│   ├── index.ts
│   ├── slack
│   │   ├── bot.ts
├── tsconfig.json

まずはbot本体の実装から

今回は「メンションが飛んできたら」をトリガーとしたいので、app_mentionのイベントに引っ掛ける形で起動する。
どのイベントに引っ掛けるべきか、は公式 に記載がある。

また、後述の手順で取得する接続情報はFirebaseの環境情報から取得する。
Secrets Manager使ってもいいけど200msくらいオーバーヘッドあるので、、、

import {
  AllMiddlewareArgs,
  App,
  ExpressReceiver,
  SlackEventMiddlewareArgs,
} from "@slack/bolt";

// 接続情報はアプリに対して払いだされるので複数のbotにできるような構造体にしておくと拡張性あって良いかも
// その場合はexpressを利用してルーティングを設定しないとダメ。
type Config = {
  slack: {
    bot: {
      secret: string,
      token: string
    }
  }
}

const config: Config = functions.config() as Config;
const {secret, token} = config.slack.bot;

const expressReceiver = new ExpressReceiver({
  signingSecret: secret,
  endpoints: "/events",
  processBeforeResponse: true,
});

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

type AppMentionContext = SlackEventMiddlewareArgs<"app_mention"> &
  AllMiddlewareArgs;

app.event("app_mention", async (ctx: AppMentionContext) => {
  const { event, say } = ctx;
  const message = event.text;
  await say(`message: ${message}`);
});

あとはfunctionsが認識できるようにexportしてあげる

import * as functions from "firebase-functions";
import bot from "./slack/bot";

// `slackbot`がエンドポイントになる。(https://xxxx/slackbot)
exports.slackbot = functions.https.onRequest(bot.app);

接続情報がまだないのでデプロイはしない

Slackの設定

Slack API にアクセスし、Create a custom Appからアプリを登録する。 特に凝ったものを作らないので From scratch を選択する。
アプリの名前を適当に入力し、ワークスペースに自分がどうにでもできるものを指定する。

f:id:anizozina:20210607083036p:plain f:id:anizozina:20210607090034j:plain ※ちょっとエッチな名前つけちゃったので隠した

アプリのセッティングページに遷移するので、Botの実装に利用するSigning Secretの値だけすぐに使えるようにコピーしておく。

f:id:anizozina:20210607083923j:plain

続いて、OAuthトークンの生成を行う。
botが利用する機能に応じてスコープを設定してあげる必要がある。
スコープはScopes and permissionに記載されているので、読んで選択する。

今回はbotのメンションをトリガーとして実行され、チャンネルにチャットを書き込むので以下の権限を設定する

  • chat:write
  • app_mentions:read

f:id:anizozina:20210607090207p:plain

Install Appからインストールを許可するとOAuth Tokenが発行されるので、コピーして持っておく。

ソースコードをデプロイしないと次に進めないので、先にコンソールで

  • Firebase環境情報の設定
  • デプロイ

を実施する。

# Firebase環境情報の設定
# 要: 想定したプロジェクトがデプロイ先に指定されているかの確認

> firebase functions:config:set slack.bot.secret="Signing Secretの値"
> firebase functions:config:set slack.bot.token="OAuth tokenの値"

# Deploy
> firebase deploy --only functions:bot

デプロイ後に生成されるfunctionのエンドポイントをコピーする(https://us-central1-$project.cloudfunctions.net/bot みたいな形式)

Slack側の設定に戻り、Event Subscriptionsの画面でイベントを有効化し、Request URLに↑のURL+ /slack/events を設定する。
うまく設定できていればVerified!のchipが付いてくれる。

f:id:anizozina:20210607085310j:plain

少し下のほうにある Subscribe to bot events の欄で、OAuthトークン発行時に選択したイベントのうちトリガーになるほう(app_mention)を指定する。 f:id:anizozina:20210607085532p:plain

以上。動作させるにはチャットにアプリをインストールする必要があるのでそこだけ最後やってもらえれば。

最後に

細かいところは全然キャッチアップしていないけれど、ふわっとした理解ですぐ作れるのはありがたい。
誤りがあったり補足があったら指摘していただけると助かります mm