Slack BotをBoltで開発した
Slack上で動くBotを開発するのにBoltを使った。
Githubのレポジトリにサンプルコードがある。
どのようにBotは動いているか
SlackにBotを待機させて、あるチャンネル(チャンネルAとする)で特定のメッセージを打つとチャンネルを生成したり、参加したり、ボットがメッセージの一部を改編して投稿したりするものを作りたかった。
イベント発生時に、Slackサーバーからペイロードを受け取るには、イベントのサブスクリプション の設定が必要らしい。
Slack ワークスペースで発生するイベント (メッセージが投稿されたときや、メッセージに対するリアクションが投稿されたときなど) をリッスンするには、Events API を使用してイベントタイプに登録します。
Slackの管理画面からEvent Subscriptions へ行き、Enable Events の設定をオンにする。そこにRequest URL
を登録する。
Slackはイベントに対応するHTTP POSTリクエストをこのRequest URL
エンドポイントに送信する。
ローカル開発しながらこれをどうやって用意するかというとngrok
というツールが便利でこれを使う。
簡単にいうと、ローカルPC上で稼働しているネットワーク(TCP)サービスを外部公開できるサービスです。例えば、ローカルPCのWebサーバを外部公開することができます。
ngrokで発行したURLは、管理画面からRequest URLとして登録する。するとローカル環境でport 3000で動作しているプロセスを外部公開できるので、Slackワークスペースでのイベントをローカルのプロセスでキャッチできるようになる。
まとめ
SlackワークスペースにBotを常駐させる。Botはイベントの発生まで待機。イベントが発生すると、Request URL
にSlackサーバーからペイロードを送信する。ペイロードを受け取るのばBotのサーバーで、プログラムが実行される。プログラム中からSlackワークスペースに何か操作を加えるには、Slack Web APIを使う。Slack Web APIには一つ一つスコープがあり、管理画面から登録する必要がある。
スコープは何か
スコープは、Web API メソッドの呼び出しや Events API のイベントの受信といった Slack での機能を実行する権限をアプリに付与するために使用します。ユーザーがアプリのインストールフローに従って進むと、ユーザーはアプリがリクエストするスコープへのアクセスを許可する必要があります。
以前は、Slack はさまざまな権限と機能を要求するボットスコープを提供していました。つまりアプリが、必要としない権限をリクエストすることがよくありました。その結果、ユーザーはプライバシーとセキュリティ上の懸念からアプリをインストールしたくないと考えるようになりました。このため現在では、Slack ではアプリがその動作に必要な小さなスコープのみを選択できるようになりました。これにより、以前よりもユーザーがアプリをインストールするようになります。
参考
- https://api.slack.com/lang/ja-jp/hello-world-bolt
- https://api.slack.com/lang/ja-jp/understanding-oauth-scopes-bot
使いたいWeb APIを調べてSlackのスコープの設定をする
SlackのWeb APIを利用してSlackの操作をする場合、どのAPIを使うのか調べる。
例えば、どこかのチャンネルにメッセージをポストする場合、以下のAPIを使う。
このサイトで見るポイントとしてはRequired scopes
のところ。
Slackの管理画面上でBot TokensとUser Tokensを登録しないと、そのSlackチームに対してBotに認可が下りてない状態で操作できない。
再送が起こる
Slackチャンネルに常駐して動作させる場合Events APIを使うことになる。
const { App } = require('@slack/bolt');
const app = new App({
token: process.env.SLACK_BOT_TOKEN,
signingSecret: process.env.SLACK_SIGNING_SECRET
});
// Listens to incoming messages that contain "hello"
app.message('hello', async ({ message, say }) => {
// say() sends a message to the channel where the event was triggered
await say(`Hey there <@${message.user}>!`);
});
(async () => {
// Start your app
await app.start(process.env.PORT || 3000);
console.log('⚡️ Bolt app is running!');
})();
hello
と打ち込むと実行される部分を以下のように変更する。
app.message('hello', async ({ message, say }) => {
// say() sends a message to the channel where the event was triggered
const result = await getResult();
const result2 = await getResult2();
await say(`Hey there <@${message.user}>!. ${result2.text}`);
});
このコードが3秒以上実行終了までにかかると、メッセージが再送されてしまう。Slackに同一の文章が二回投函されることになる。
解決方法は以下にまとまっている。
個人的には「そもそも再送を無視する方法」が採用しやすかった。
const slackToken = process.env.SLACK_TOKEN
exports.index = async (event, context) => {
const body = JSON.parse(event.body);
if (!(body.token === slackToken)) return {"statusCode": 401, "body": "Missing Token"}
// 再送かをチェック
if (event.headers['X-Slack-Retry-Num']) {
return { statusCode: 200, body: JSON.stringify({ message: "No need to resend" }) };
}
// メインの処理をしていく
...
}
Deploy
公式には以下の2つの方法のdeploy方法がのっている。
- Heroku
- AWS Lambda
実行環境としては別に自分でExpressのフレームワークでラップしても良さそうなので
- AWS Elastic Beanstalk
でも良さそうだし
- GCP Cloud Functions
でも良さそう。
Boltの参考
Discussion