Firebase と Boltで勉強会用のSlack Botを作ったのでチュートリアル形式でまとめます。
無料版Slackで頑張っている全国の勉強会運営者の助けになれば..!!
~~※ 現状のBoltのFaaSでの利用にはいくつかの課題があるようです。v2では改善されるようなので、その際にはv2版で記事更新します。~~~
2020/3/30にリリースされました。記事の内容もv2で対応しています。
作る物
- Slackでスラッシュコマンドを打つと、モーダルが開く
- フォームを入力し投稿ボタンを押すと、入力内容をフォーマットして投稿する
という簡単なBotです。
もくもく回の最初に「今日やることを投稿する」際に使うことをイメージしています。
処理は単純なので、いろいろ応用は効きそうです。
(有料版Slackならワークフローで一瞬で出来そうなのですが、自分の勉強会のSlackにはそんな便利な物はなく。。)
これから説明するコードは全て以下GitHubのリポジトリにあります。
Slackの設定
まずSlack API側の設定です。Botアプリの作成から権限の追加などを行います。
アプリの作成
以下リンクからCreate New Appを押してSlackボットを作成します。
任意のアプリ名、ワークスペースを入力してCreate Appを押してください。

Scopeの設定
サイドバーのOAuth & PermissionsのScopesよりアプリがWorkspaceでどのような操作を行えるのかの権限を設定します。
Bot Token Scopesのプルダウンから以下を設定します。
-
chat:write
アプリのメッセージ投稿を許可する -
commands
slashコマンドの読み取りを許可する -
users:read
ユーザー情報の読み取りを許可する

Workspaceへの登録
同じくサイドバーのOAuth & Permissionsの一番上、Install App to Workspace を押してアプリをWorkspaceに追加してください。
先ほど設定したScopeについて確認が出るので許可を押してください。

Tokenのメモ
アプリをWorkspaceに追加後、OAuth & Permissionsに表示されるBot User Access Token と、 Basic InformationのApp Credentialsに表示されるVerification Tokenをメモします。
次項でFunctions上のBolt Appの初期化で利用します。
(OAuth & Permissions > OAuth Tokens & Redirect URLs の Bot User Access Token)
(Basic Information > App CredentialsのSigning Secret)
Firebase の設定
次にFirebaseで実際にアプリのコードを書いてきます。
プロジェクトの作成
以下からFirebaseのプロジェクトを作成します。
作成後プランをBlazeに変更してください。Slack APIとの外部通信が発生するためです。
(無料プランはFunctions上でFirebaseサービス以外との通信ができない)
開発環境の構築
任意のディレクトリでFirebaseの開発環境を構築します。
# FirebaseのCLIが入っていない場合は事前に以下を実行してください
# npm i -g firebase-tools
firebase init
featureの選択ではCloud Functionsを選び、その他は任意の設定をしてください。
(サンプルコードはTypeScriptで書いています)

init後、functionsディレクトリが作成されます。
これからのコードは全てそちらを起点とします。
cd functions
Boltのインストール
BoltはNode.js環境で動く SlackアプリのSDKです。
こちらに日本語ドキュメントもあります。
https://slack.dev/bolt/ja-jp/tutorial/getting-started
functionsディレクトリで以下を実行してBoltをインストールしましょう。
npm i @slack/bolt
SlackのTokenを環境変数に設定
Slackアプリの初期化で利用するためSlackの設定でメモした Signing Secret
、Bot User Access Token
,を環境変数に設定します
firebase functions:config:set slack.secret=xxxxxxxxxxxxxx
firebase functions:config:set slack.token=xxxxxxxxxxxxx
Appの作成
functionsのsrcにslackディレクトリを作りapp.ts
を作成します
mkdir src/slack
touch src/slack/app.ts
app.ts上でslackアプリを作成します。
前項で設定した環境変数をfunctions.config()
で取得してAppの初期化をしています。
useMokumokuCommand
の部分は次に説明するmokumoku.tsで定義します。
この状態で、Receiverをfunctionsに渡すとfunctionsのURL/events
でアプリにアクセスできます。
この初期化部分の実装はこちらの記事 を参考にさせて頂きました。
import * as functions from "firebase-functions";
import { App, ExpressReceiver } from "@slack/bolt";
import { useMokumokuCommand } from "./commands/mokumoku";
const config = functions.config();
export const expressReceiver = new ExpressReceiver({
signingSecret: config.slack.secret,
endpoints: "/events",
processBeforeResponse: true
});
const app = new App({
receiver: expressReceiver,
token: config.slack.token
});
useMokumokuCommand(app);
mokumokuコマンドの作成
次に実際に処理をするmokumoku.tsを作っていきます。
mkdir src/slack/commands
touch src/slack/commands/mokumoku.ts
コードの完成形はこちらです。
-
/mokumoku
コマンドでモーダルを開く - フォーム内容のPOSTを受けてフォーマットしたメッセージを送る
という処理を行っています。
import { App } from "@slack/bolt";
const VIEW_ID = "dialog_1";
type User = {
real_name: string;
profile: {
image_192: string;
};
};
const createMessageBlock = (
username: string,
userIcon: string,
profile: string,
todo: string
) => {
return [
{
type: "context",
elements: [
{
type: "mrkdwn",
text: `posted by *${username}*`
}
]
},
{
type: "divider"
},
{
type: "section",
text: {
type: "mrkdwn",
text: `:memo: *自己紹介*\n${profile}\n\n\n:books: *今日やること*\n${todo}`
},
accessory: {
type: "image",
image_url: userIcon,
alt_text: "user thumbnail"
}
},
{
type: "divider"
}
];
};
export const useMokumokuCommand = (app: App) => {
app.command("/mokumoku", async ({ ack, body, context, command }) => {
await ack();
try {
await app.client.views.open({
token: context.botToken,
trigger_id: body.trigger_id,
view: {
type: "modal",
callback_id: VIEW_ID,
title: {
type: "plain_text",
text: "今日のもくもく"
},
blocks: [
{
type: "input",
block_id: "profile_block",
label: {
type: "plain_text",
text: "自己紹介"
},
element: {
type: "plain_text_input",
action_id: "profile_input",
multiline: true
}
},
{
type: "input",
block_id: "todo_block",
label: {
type: "plain_text",
text: "今日やること"
},
element: {
type: "plain_text_input",
action_id: "todo_input",
multiline: true
}
}
],
private_metadata: command.channel_id,
submit: {
type: "plain_text",
text: "投稿"
}
}
});
} catch (error) {
console.error(error);
}
});
app.view(VIEW_ID, async ({ ack, view, context, body }) => {
await ack();
const values = view.state.values;
const channelId = view.private_metadata;
const profile = values.profile_block.profile_input.value;
const todo = values.todo_block.todo_input.value;
try {
// get user info
const { user } = await app.client.users.info({
token: context.botToken,
user: body.user.id
});
// post chanel
await app.client.chat.postMessage({
token: context.botToken,
channel: channelId,
text: "",
blocks: createMessageBlock(
(user as User).real_name,
(user as User).profile.image_192,
profile,
todo
)
});
} catch (error) {
console.error("post message error", error);
}
});
};
簡単に解説です。
app.command()
でリッスンするスラッシュコマンドの登録と、実行時の処理を登録しています。第一引数に登録したいコマンドを、第二引数にコールバックで処理を書いていきます。
app.command("/mokumoku", async ({ ack, body, context, command }) => { ... }
app.view()
でモーダルの投稿結果を受け取る処理を登録しています。第一引数にapp.command内のモーダル表示処理app.client.views.openで設定したcallback_idを設定しモーダルとの接続を、第二引数のコールバックでメッセージの送信処理を書いています。
app.view(VIEW_ID, async ({ ack, view, context, body }) => { ... }
Functionsの設定
アクセスポイントとなるFunctionsを設定します。
http.onRequestにそのままexpressReceiver.appを渡せばOKです。とても簡単ですね。
import * as functions from "firebase-functions";
import { expressReceiver } from "./slack/app";
export const slack = functions.https.onRequest(expressReceiver.app);
デプロイ
以下コマンドでアプリをFunctionsにデプロイします。
npm run build
npm run deploy
deployコマンド実行後表示されるFunctionsへのアクセスポイントのURLをメモしておいてください。
次項でSlackアプリとの連携設定で使います。
Slackアプリとの連携
最後に前項でデプロイしたFunctionsとslackアプリを連携します。
スラッシュコマンドの登録
https://api.slack.com/apps から作成したSlackアプリを選びサイドバーのSlash CommandsからCreate New Commandでコマンドを追加します。
Requst URLには先ほどメモしたFunctionへのアクセスポイントのURL/eventsを入力してください。

Interactive Components の設定
モーダルからの投稿に対応するため Interactive Components を有効化にします
サイドバーのInteractive ComponentsからチェックボタンをOnにしてRequest URLに先ほどと同様のFunctionへのアクセスポイントのURL/eventを入力して保存します。
これを設定することでモーダルの投稿ボタン押下後、内容がFunctionsにPOSTされます。

※ 私はこれを最初設定せず、「なぜかモーダルひらくものの投稿できないな〜」と半日潰しました。。最終的に以下記事で紹介されていたSlackコミュニティ公式グループで質問してInteractive Componentについて教えてもらい解決しました。暖かいコミュニティに感謝
https://qiita.com/girlie_mac/items/93538f9a69eb4015f951#comment-a983ea977482e78f209c
動作確認
コマンドを使いたいチャネルにBotを招待して、実際にSlack上でコマンドを叩いてみましょう。
無事動けば完成です
あとアプリの名前や、アイコンはSlackアプリの設定画面のBasic InformationのDisplay Informationで、お好みに設定してください。

終わりに
以上、 「Cloud Functions for Firebase 🔥 + Bolt ⚡️で勉強会用のSlack Botを作ってみた」でした。
初めてのSlack Botでまだまだ分からないところばかりなので間違いなどあれば気軽にコメントで指摘お願いします。
今後もいろいろ機能を追加して勉強会のSlack Botを育てて行きたいです。
参考
以下記事大変参考にさせて頂きました! 良記事ありがとうございます。