👀

Slackを手軽に監視したい(Event APIをつかってみたよ)

2022/12/20に公開

@tosqです こんにちはこんにちは!!
Aidemyでえんじにゃーしてます
https://hrmos.co/pages/aidemy/jobs

What

我々はSlackに取り憑かれている。
故にSlackでなにが起こっているのか、知りたいのである。

ということで、Slackを手軽に監視できるアプリをつくっていきます

TL;DR

  • BoltでFirebase Functionsにbotを作成する
  • emojiの追加やチャンネル作成を検知して自動投稿できるようにする

背景

元々Real Time Messaging APIでemojiの追加やチャンネルの作成などを検知して自動投稿するbotがありましたが、現在新しくつくるアプリでは利用できません
また、WebSocketサーバが必要であり、サーバ稼働コストもかかってしまうのでCloud Functions + Boltへの移行を行いました

手順

bot作成

  • https://api.slack.com/apps [Create New App] でbot(App)を作成する
  • [Install your app] からWorkspaceにインストールします

firebase cliのインストール

ログイン
$ firebase login
プロジェクト一覧参照
$ firebase projects:list
利用するプロジェクト指定
$ firebase use slack-bot

firebaseにSlack Appの設定をする

  • 登録したアプリの [OAuth & Permissions] から Signing Secret と Bot User OAuth Token を取得してfirebaseのconfigに登録する
$ firebase functions:config:set slack.signing_secret=xoxb-xxxxxxxxxx-xxxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxx
$ firebase functions:config:set slack.bot_token=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

アプリの作成

$ git clone git@github.com:tosaka-n/cloud-function-slack-bolt.git

https://github.com/tosaka-n/cloud-function-slack-bolt

  • 上記のレポジトリでは、複数のアプリをまとめて管理できるようにindex.jsでエンドポイントを集約しています
  • src/index.jsに設定してあるexportsがエンドポイントになります

ex)

index.js
exports.slack = functions.https.onRequest(slack.app);
slack.js
const expressReceiver = new ExpressReceiver({
  signingSecret: config.slack.signing_secret,
  endpoints: '/',
  processBeforeResponse: true
});
// 中略
exports.app = expressReceiver.app;

の場合は https://xxx.cloudfunctions.net/slack になります

デプロイ

$npm run deploy

or

$ firebase deploy --only functions

でデプロイできます

エンドポイントの登録

  • Slackに作成したアプリの [Add features and functionality] → [Event Subscriptions] から登録します
  • 下部の緑[Save Changes]をクリックしないと保存されないので注意

注記

  • cf) https://api.slack.com/events/url_verification
  • 上記によると challenge パラメータを返す必要があるかもしれません
    • 実装時に必要だったか失念したため、必要そうであれば challenge の値をそのまま返すように追記してください

アプリのpermission設定

  • Slack Appの [Subscribe to bot events] から指定します
  • 特に message.channels を指定しないと投稿に反応できないので必ず登録してください

ここまでで、下準備が完了です
適当なchannelに作成したアプリをiviteして 「hello」 と投稿するとbotが 「hello」 を返してくれるはずです

監視について

  • 利用可能なEventについては下記にまとまっています
  • 利用したいEventについて app.event で受け取ることでSlackに起こったできごとを監視することができます

サンプル

emojiの追加/削除

app.event('emoji_changed', async ({ event, client }) => {
  if (event.subtype == "add") {
    await client.chat.postMessage({
      channel,
      text: `\`:${event.name}:\` が追加されました`,
      attachments: [
        {
          pretext: `:${event.name}:`,
          image_url: event.value
        }
      ]
    });
  } else if (event.subtype == "remove") {
    await Promise.all(event.names.map(name => client.chat.postMessage({ channel, text: `${name}が消えました` })));
  }
});

チャンネル作成

app.event("channel_created", async ({ event, client }) => {
  const user = await client.users.info({user: event.channel.creator});
  await client.chat.postMessage({ channel, text: `チャンネル <#${event.channel.id}|${event.channel.name}> が作成されました` });
});

ユーザの追加

  • ユーザにはworkflow/bot/userの3種類あるため、分岐します
app.event("team_join", async ({ event, client }) => {
  const userInfo = await client.users.info({ user: event.user.id });
  if (userInfo.user.is_bot) {
    if (userInfo.user.is_workflow_bot) {
      await client.chat.postMessage({ channel, text: `${userInfo.user.real_name}というワークフローが作成されました` });
    } else {
      await client.chat.postMessage({ channel, text: `${userInfo.user.name}が追加されました` });
    }
  } else {
    await client.chat.postMessage({ channel, text: `${userInfo.user.name}[${userInfo.user.profile.last_name} ${userInfo.user.profile.first_name}]さんが参加しました` });
  }
});

備考

  • Real Time Messaging APIでは非公式ながら下記のEventも流れてきていましたが、Event APIには存在しないため、移行の際に元々のコードから削除しました
    • apps_installed: botの作成
    • channel_updated: Public channelのprivate化

終わりに

Aidemyではエンジニアを募集してます!!!!!! (エンジニア以外も)
こちらから
https://hrmos.co/pages/aidemy/jobs

それではみなさん、よいSlack Lifeを〜

Aidemy Tech Blog

Discussion