🤖

28行でSlackのグループメンションBOTを実装した

2024/04/30に公開

Slackのユーザーグループがゲストには使えなかったのでSlackBOTを作成して再現をしました
Glitchで作ったのでRemixしてご活用いただけるかと思います
ゲストにもグループメンションさせたい人の助けになれば幸いです

とりあえず問題を解決したい方へ

ゲストでも使えるグループメンションBOTの作り方をまずは説明します

GlitchをRemix

Glitchでは他人が作ったソースコードをコピーしてカスタマイズし、ある程度は無料でプログラムを動かせるサービスです
Glitchのアカウントを作成し、私が作ったこのプロジェクトをRemixしてください
※自分のプロジェクトとしてコピーすることをRemixと呼ぶそうです
Remixリンク: https://glitch.com/edit/#!/remix/grey-elfin-ghost

SlackBOTを作成

公式のこの記事の「アプリの設定」セクションと「イベントの設定」セクションを実施してください
なお途中で権限は chat:write に加えて app_mentions:read も設定してください
この時作るボットの名前でグループメンションができるようになります

環境変数を修正

.env ファイルを開き SLACK_SIGNING_SECRETSLACK_BOT_TOKEN を設定してください
SLACK_SIGNING_SECRET はここで取得できます

SLACK_BOT_TOKEN はここで取得できます

メッセージを設定

.env ファイルの MESSAGE にこのBOTが返してほしいメッセージを入力してください
ただメンションをするだけなら <@メンバーID> <@メンバーID> <@メンバーID> という形式の値を設定すれば @Aさん @Bさん @Cさん とメンションを飛ばしてくれます
このメンバーIDはここで取得できます

仕組みの解説

Bolt

SlackのSDK「Bolt」を利用したBOTのGlitchをベースに開発しました
Boltのアプリインスタンスに対して app.use()app.event() などに関数を渡すことで処理を定義できるのはシンプルで使いやすかったです
今回のソースコードは大きく分けて4つの要素で成り立っています

  1. アプリインスタンスの作成
  2. 再送信されたリクエストに対する処理
  3. メンションされたときの処理
  4. アプリの起動

アプリインスタンスの作成

const { App, ExpressReceiver } = require("@slack/bolt");

const app = new App({
  signingSecret: process.env.SLACK_SIGNING_SECRET,
  token: process.env.SLACK_BOT_TOKEN,
  receiver: new ExpressReceiver({
    signingSecret: process.env.SLACK_SIGNING_SECRET,
    customPropertiesExtractor: ({ headers }) => ({ headers }),
  }),
});

SlackのEvent APIはサーバーからのレスポンスが遅いとリトライをしてくれます
一方でGlitchはリクエストが来たタイミングで寝ていたサーバーを起動してからリクエストを処理するため、久々のリクエストに対するレスポンスが遅くなってしまいます
ただしGlitchはサーバー起動後にきちんとリクエストを送ってくれるためSlackからは複数回リクエストが届いてしまいます
これに対応すべくHTTPリクエストのヘッダーを後続の処理で取り扱うためアプリインスタンスの作成時に customPropertiesExtractor でヘッダー情報を後続の処理に渡しています

再送信されたリクエストに対する処理

app.use(async ({ context, next }) => {
  if (!context.headers["x-slack-retry-num"]) await next();
});

HTTPリクエストのヘッダーに x-slack-retry-num が含まれるときはSlackが再送信したリクエストであることを示しています
これがないときだけ後続の処理を実行するようにします

メンションされたときの処理

app.event("app_mention", async ({ event, client }) => {
  if (event.edited) return;
  await client.chat.postMessage({
    channel: event.channel,
    text: process.env.MESSAGE,
    thread_ts: event.ts,
  });
});

メンションを受けたスレッドに環境変数で設定したメッセージを送っています
なお、編集したときにもイベントが発火するので編集した情報がある場合は処理をスキップしています
Boltを使うと短く簡潔にかけていいですね

アプリの起動

(async () => {
  await app.start(process.env.PORT || 3000);
  console.log("⚡️ Bolt app is running!");
})();

app.start() を呼び出すことでサーバーが起動します

株式会社find | 落とし物クラウド

Discussion