💭

Slack BotをBoltで開発した

2021/09/17に公開

Slack上で動くBotを開発するのにBoltを使った。

https://slack.dev/bolt-js/ja-jp/tutorial/getting-started

Githubのレポジトリにサンプルコードがある。

https://github.com/slackapi/bolt-js/tree/main/examples

どのようにBotは動いているか

SlackにBotを待機させて、あるチャンネル(チャンネルAとする)で特定のメッセージを打つとチャンネルを生成したり、参加したり、ボットがメッセージの一部を改編して投稿したりするものを作りたかった。

イベント発生時に、Slackサーバーからペイロードを受け取るには、イベントのサブスクリプション の設定が必要らしい。

Slack ワークスペースで発生するイベント (メッセージが投稿されたときや、メッセージに対するリアクションが投稿されたときなど) をリッスンするには、Events API を使用してイベントタイプに登録します。

https://api.slack.com/apis/connections/events-api

Slackの管理画面からEvent Subscriptions へ行き、Enable Events の設定をオンにする。そこにRequest URLを登録する。

Slackはイベントに対応するHTTP POSTリクエストをこのRequest URLエンドポイントに送信する。

ローカル開発しながらこれをどうやって用意するかというとngrokというツールが便利でこれを使う。

https://ngrok.com/

簡単にいうと、ローカルPC上で稼働しているネットワーク(TCP)サービスを外部公開できるサービスです。例えば、ローカルPCのWebサーバを外部公開することができます。

https://qiita.com/mininobu/items/b45dbc70faedf30f484e

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/understanding-oauth-scopes-bot

参考

使いたいWeb APIを調べてSlackのスコープの設定をする

https://slack.dev/bolt-js/ja-jp/concepts#web-api

SlackのWeb APIを利用してSlackの操作をする場合、どのAPIを使うのか調べる。

例えば、どこかのチャンネルにメッセージをポストする場合、以下のAPIを使う。

https://api.slack.com/methods/chat.postMessage

このサイトで見るポイントとしては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に同一の文章が二回投函されることになる。

解決方法は以下にまとまっている。

https://dev.classmethod.jp/articles/slack-resend-matome/

個人的には「そもそも再送を無視する方法」が採用しやすかった。

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

https://slack.dev/bolt-js/ja-jp/deployments/aws-lambda

実行環境としては別に自分でExpressのフレームワークでラップしても良さそうなので

  • AWS Elastic Beanstalk

でも良さそうだし

  • GCP Cloud Functions

でも良さそう。

Boltの参考

https://qiita.com/seratch/items/3e44f58a7c8ab41f21f0

https://qiita.com/takiguchi-yu/items/2447f7811290e6e70291

https://techblog.roxx.co.jp/entry/2021/05/31/211939

Discussion