🦄

Firebase + Spreadsheet で Slack Bot を作ったら社内用語辞典の運用が3倍楽しくなった話

2022/01/24に公開
12

最近作った Slack Bot が好評だったのでまとめてみました!
どこの Slack ワークスペースでも導入できるように詳細に設定方法も記載しています。

🛠 作ったもの

tell-me-bot(社内では tell-me-paccho)という、社内用語辞典をいい感じに管理してくれる Slack Bot を作りました。

社内ではもともと Spreadsheet で社内用語を管理していたのですが、メンテナンスする人が限られ、あまり積極的には利用されていない状況でした。

そんな時に@しかじろうさんのこちらの記事を発見して、これはおもしろいアイデアだと思い、Firebase + Bolt(TypeScript)にて作ってみました(アイデアをくれた@しかじろうさんに感謝🙏)。元記事の機能を参考に+αの機能も色々実装しています。

https://twitter.com/KawamataRyo/status/1480732294134796288

構成

Cloud Functions for Firebase で Slack アプリのフレームワークである Bolt.js を動かし、Slack の Event API や Action API のリクエストに応じて社内用語辞典の Spreadsheet を操作しています。
また、後述する曖昧検索の実現のために、Fuse.js を内部的に利用しています。
Spreadsheet との通信は Cloud Functions のサービスアカウトを利用して googlea-api-nodejs-client を使っています。

DB として Firestore ではなく Spreadsheet を使った理由はこちらです。

  • もともと社内用語辞典が Spreadsheet で管理されていたのでそれをそのまま使える
  • エンジニア以外でも誰でも手軽に辞書データを直接追加・編集・削除を出来る(重要!
  • Slack のインタフェースで編集・削除の機能を実装するのが面倒

実装も全て GitHub で公開しています(⭐を貰えると泣いて喜びます)。
https://github.com/kawamataryo/tell-me-bot

💡 機能詳細

tell-me-bot の機能を紹介します。

用語の検索

完全一致する用語を tell-me-bot にメンションした場合は、即結果が表示されます。もし説明が間違っていた場合は、メッセージ記載の Spreadsheet のリンクから編集できます。

曖昧検索への対応

もし、完全一致はしないけれども似たような単語がある場合は、候補を表示します。候補のボタンを押すと、その用語の説明を答えてくれます。

内部的には Fuse.js の Fuzzy Search を利用しています。
これがこのボットの一番の売りの機能です。曖昧な問いかけからも正確な情報を取得できます。

https://github.com/krisk/fuse

用語の追加

もし曖昧検索の結果が間違っていたり、検索に該当する用語がない場合は、Slack のモーダルから用語を新規に追加することもできます(Spreadsheet を直接編集することでも可能です)。
手軽に追加できるので、気づいたときにどんどん用語を増やすことができます。

質問チャネルへの代理質問(Optional)

用語を新規に登録しようにも、用語の意味がわからない場合が多いですよね。その場合は、社内の質問チャネルに tell-me-bot が代理質問する機能もあります。
自分の所属する LAPRAS社 では、#ask-anything という以下の理念で運用されているチャネルがあるのでそこに質問を投げるようにしています。

#ask-anything
LAPRASに関するあらゆることについて、誰でもなんでもいつでも何回でも質問してよいチャンネル。大事なことは、質問されたことにLAPRASの誰かがかならず反応すること。反応は早ければ早いほどよい(回答が得られなくても仕方ない。反応があることがだいじ)。

実際に以下のように投げられて、都度専門知識をもつメンバーが用語を登録してくれてとても助かっています。

質問したチャネル ask-anythingチャネル

💬 社内の声

会社の Slack でも嬉しい声を頂いたので一部紹介します!
やはり使って感想をもらえるのは開発者として一番嬉しいですね😆

社内の声

実際に社内用語辞典は急速に充実していっています。
個人開発のアプリを気軽に紹介できて、ポジティブなフィードバックを貰える社内環境はとても良いなーと思っています。

👨‍💻 設定方法

基本的にコードは全て公開しているので、以下手順でどの Slack にも導入できるはずです。

事前準備

事前に以下を準備します。

  • Firebase CLI
    • 任意のシェルでセットアップしておいてください
  • 空の Firebase プロジェクト
    • Blaze プラン(従量課金プラン)にプラン変更してください
  • 任意の Spreadsheet
    • 1 枚目のシートの A1 に用語、B1 に説明というヘッダーを追加しておいてください
    • Spreadsheet の ID をメモしておいてください(参考)

Slack APPの作成(1)

Slack の Your Apps にアクセスして Create New App を押します。

https://api.slack.com/apps

選択後表示されるモーダルで From app manifest を選択します。

次にアプリを追加する Slack ワークスペースを選択します。

次に manifest の入力欄が表示されるので以下を入力します。

display_information:
  name: tell-me-bot
  description: tell-me-bot
  background_color: "#323336"
features:
  bot_user:
    display_name: tell-me-bot
    always_online: true
oauth_config:
  scopes:
    bot:
      - app_mentions:read
      - chat:write
      - im:history
      - im:read
      - im:write
      - users:read
      - channels:read
settings:
  event_subscriptions:
    request_url: https://example.com
    bot_events:
      - app_mention
      - message.im
  interactivity:
    is_enabled: true
    request_url: https://example.com
  org_deploy_enabled: false
  socket_mode_enabled: false
  is_hosted: false
  token_rotation_enabled: false

最後に確認が表示されるので、create ボタンを押せばアプリが作成されます。

アプリが作成されたら以下 2 の値をメモしておきます。

  • Basic Information > App Credentials の Signin Secret
  • OAuth & Permission > OAuth Tokens for Your Workspace の Bot User OAuth Token


この値は次の Firebase の設定で使います。

また、App Home > Show Tabs から Message Tab の設定を ON にしてAllow users to send Slash commands and messages from the messages tabにチェックしてください。これを設定することで、tell-me-bot との DM でも社内用語辞典の操作が行えるようになります。

Firebase プロジェクトのデプロイ

tell-me-bot のリポジトリを任意のディレクトリでクローンします。

$ git clone https://github.com/kawamataryo/tell-me-bot
$ cd tell-me-bot

最初に Firebase CLI でプロジェクトの設定を行います。
プロジェクトの ID は firebase projects:list で確認できます。

$ firebase use <事前準備で作成したプロジェクトのID>

そして Firebase の環境変数を設定します。
ここで今まででメモしてきた値を設定します。

# Slack APPの作成でメモしたBot User OAuth Tokenの値
$ firebase functions:config:set slack.bot_token="xxxx"

# Slack APPの作成でメモしたSignin Secretの値
$ firebase functions:config:set slack.signin_secret="xxxx"

# 質問チャネルがある場合は、質問チャネルのIDを指定。ない場合は空文字を指定。
$ firebase functions:config:set slack.ask_channel_id=""

# 事前準備で用意したSpreadsheetのID
$ firebase functions:config:set sheet.id="xxxx"

次に functions ディレクトリの依存モジュールを install します。

$ npm --prefix ./functions i

最後にデプロイです。

$ npm --prefix ./functions run deploy

完了後のログで functions の URL が確認できるのでメモします。

GCPの設定

GCP のダッシュボードにアクセスし、Firebase で作成したプロジェクトを指定してください。

https://console.cloud.google.com/apis/dashboard?hl=ja

そしてメニューから Google Sheets API を開き有効化します。
これは tell-me-bot の内部で Spreadsheet の操作に Google Sheet API を利用しているためです。

次に、メニューからサービスアカウントを開きます。
2 つのサービスアカウントが作成されていると思うので、App Engine default service accountの方のメールアドレスをメモします。

最後に、事前準備で作成した Spreadsheet を開き、先程メモしたアカウントに対して編集権限を設定します。

これで GCP 側の準備は完了です。

Slack APPの設定(2)

Slack APP に戻り、App Manifest を開きます。
そこで、manifest の設定で以前example.comとしていた箇所を、先程メモした fucntions の デプロイURL/events を記載します。必ず後ろに/eventsをつけてください。

そして Save Change を押します。ここで上部に Verify Error と出る場合は functions の環境変数の設定か URL が間違っている可能性があるので確認してください。functions の log を見るとわかりやすいです。

最後に Slack APP を指定のワークスペースにインストールします。

Install App から Install Workspace を実行してください。

動作確認

インストールした Slack Work スペースの任意のチャネルに tell-me-bot を追加してメンションしてみてください。

bot が答えてくれれば全て完了です。もしエラーが出る場合は Firebase のログを確認してください。

おわりに

個人的にもとても便利で、どの会社にもあって良いなと思ってます。使ってもらえると嬉しいです!
設定方法に詰まりましたら、気軽に Twitter DM か記事コメントで連絡ください。ベストエフォートでサポートします!

https://twitter.com/KawamataRyo

Discussion

しかじろうしかじろう

僕のやつより100万倍ちゃんとしてる!!!!!!!!!!

ryo_kawamataryo_kawamata

うおお!しかじろうさん!!
ありがとうございます!!
しかじろうさんの記事ありきで開発スタートしたので本当に感謝です🙏
めっちゃ良いアイディアだと思いました!

ge44_maxge44_max

記事を参考に導入させていただきました。
大変便利で感謝しております。

実用しておりまして、1点困ったことが起きましたのでご相談です。
もしよろしければ回答いただけますと幸いです。

鍵付きのプライベートチャネルでもtell-me-botを使えるようにしたいです。

鍵付きのプライベートチャネルでもほぼ問題なく利用できますが、
「曖昧検索への対応」の候補をクリックした時だけエラーメッセージが表示されます。

@tell-me-bot あああ
→候補が出る
→どれか押下する
→エラーメッセージ(ごめーん..なんかエラーっぽい..。 Slack管理者に確認してね)

※通常のパブリックチャネルでは問題ありません。

もしエラーが出る場合は Firebase のログを確認してください。

確認してみました。

[
  Error: An API error occurred: missing_scope
      at platformErrorFromResult (/workspace/node_modules/@slack/web-api/dist/errors.js:51:33)
      at WebClient.apiCall (/workspace/node_modules/@slack/web-api/dist/WebClient.js:167:56)
      at processTicksAndRejections (node:internal/process/task_queues:96:5)
      at async Object.exports.fetchChannelName (/workspace/lib/lib/utils.js:23:25)
      at async /workspace/lib/v1/bolt/actions/useSearchAction.js:39:36
      at async Array.<anonymous> (/workspace/node_modules/@slack/bolt/dist/middleware/builtin.js:158:9)
      at async Array.onlyActions (/workspace/node_modules/@slack/bolt/dist/middleware/builtin.js:15:5) {
    code: 'slack_webapi_platform_error',
    data: {
      ok: false,
      error: 'missing_scope',
      needed: 'groups:read',
      provided: 'app_mentions:read,chat:write,im:history,im:read,im:write,users:read,channels:read',
      response_metadata: [Object]
    }
  }
]

何かの権限が足りない、と言われているのはわかりましたが、
(非エンジニアのおじさんでは)具体的にどこを修正すればいいのか検討がつかず、
こちらにコメントさせていただきました。

ryo_kawamataryo_kawamata

使ってもらえて嬉しいです!

おっしゃるとおり権限が足りないようです!
エラーメッセージに以下のような記載があるので、

      error: 'missing_scope',
      needed: 'groups:read',

groups:read' を App ManifestのScopes/bot 以下の部分に追記してください!
変更後、おそらくSlackアプリの再インストールを促されるので、そちらもお願いします。

wabisabiwabisabi

社内言語の共通化を現在行なっているのですが、こちら大変参考になり、導入を検討しています。
slackbotの通知のテキスト内容を変更したいのですが、こちらforkしてテキストなどの改変を加えてもよろしいでしょうか?

rerrer

記事を参考に導入を検討しているのですが、エラーが出て導入ができないです;;
Error: Error occurred while parsing your function triggers.

TypeError: Cannot read properties of undefined (reading 'key')
at Object.<anonymous> (/Users/kensuke/Downloads/bot/tell-me-bot-main/functions/lib/v1/bolt/app.js:73:53)
at Module._compile (node:internal/modules/cjs/loader:1105:14)
at Object.Module._extensions..js (node:internal/modules/cjs/loader:1159:10)
at Module.load (node:internal/modules/cjs/loader:981:32)
at Function.Module._load (node:internal/modules/cjs/loader:822:12)
at Module.require (node:internal/modules/cjs/loader:1005:19)
at require (node:internal/modules/cjs/helpers:102:18)
at Object.<anonymous> (/Users/kensuke/Downloads/bot/tell-me-bot-main/functions/lib/v1/index.js:23:27)
at Module._compile (node:internal/modules/cjs/loader:1105:14)
at Object.Module._extensions..js (node:internal/modules/cjs/loader:1159:10)

具体的にどこを修正すればいいのかわからず、
こちらにコメントさせていただきました。
よろしくお願い致します

rerrer

ありがとうございます!!
無事デプロイできたので、試したのですが、同じようなログが出でしまいました😅
何度もすみません
UnknownError: Cannot read properties of undefined (reading 'key')
at asCodedError (/workspace/node_modules/@slack/bolt/dist/errors.js:35:12)
at App.handleError (/workspace/node_modules/@slack/bolt/dist/App.js:613:57)
at App.processEvent (/workspace/node_modules/@slack/bolt/dist/App.js:598:25)
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at async ExpressReceiver.requestHandler (/workspace/node_modules/@slack/bolt/dist/receivers/ExpressReceiver.js:239:13) {
code: 'slack_bolt_unknown_error',
original: TypeError: Cannot read properties of undefined (reading 'key')
at /workspace/lib/v1/bolt/events/useMentionEvent.js:35:27
at /workspace/node_modules/@slack/bolt/dist/App.js:575:33
at invokeMiddleware (/workspace/node_modules/@slack/bolt/dist/middleware/process.js:20:16)
at next (/workspace/node_modules/@slack/bolt/dist/middleware/process.js:13:29)
at Array.<anonymous> (/workspace/node_modules/@slack/bolt/dist/middleware/builtin.js:233:15)
at invokeMiddleware (/workspace/node_modules/@slack/bolt/dist/middleware/process.js:12:53)
at next (/workspace/node_modules/@slack/bolt/dist/middleware/process.js:13:29)
at Array.onlyEvents (/workspace/node_modules/@slack/bolt/dist/middleware/builtin.js:63:11)
at invokeMiddleware (/workspace/node_modules/@slack/bolt/dist/middleware/process.js:12:53)
at processMiddleware (/workspace/node_modules/@slack/bolt/dist/middleware/process.js:22:12)
}

rerrer

何度も質問して申し訳ないのですが
consoleのログで、エラーが起きてるのですが、2時間悩んでいまして、質問させていただきました
よろしくお願い致します
なお、環境変数が登録できないのですが、対象をお教えいただきたいです
Please deploy your functions for the change to take effect by running firebase deploy --only functions

 GaxiosError: Requested entity was not found.
      at Gaxios._request (/workspace/node_modules/gaxios/build/src/gaxios.js:129:23)
      at processTicksAndRejections (node:internal/process/task_queues:96:5)
      at async Compute.requestAsync (/workspace/node_modules/google-auth-library/build/src/auth/oauth2client.js:368:18)
      at async SpreadsheetClient.getValues (/workspace/lib/lib/spreadsheetClient.js:18:21)
      at async /workspace/lib/v1/bolt/events/useMentionEvent.js:52:33
      at async Array.<anonymous> (/workspace/node_modules/@slack/bolt/dist/middleware/builtin.js:233:9)
      at async Array.onlyEvents (/workspace/node_modules/@slack/bolt/dist/middleware/builtin.js:63:5) {
    response: {
      config: [Object],
      data: [Object],
      headers: [Object],
      status: 404,
      statusText: 'Not Found',
      request: [Object]
    },
    config: {
      url: 'https://sheets.googleapis.com/v4/spreadsheets/%E2%80%9C1Dfz3bQXeSOd408vHPRPSA7-2T_bSJqHUjqgd-Hkk5A8%E2%80%9D/values/A2%3AB',
      method: 'GET',
      userAgentDirectives: [Array],
      paramsSerializer: [Function (anonymous)],
      headers: [Object],
      params: {},
      validateStatus: [Function (anonymous)],
      retry: true,
      responseType: 'json',
      retryConfig: [Object]
    },
    code: 404,
    errors: [ [Object] ]
  }
]
ryo_kawamataryo_kawamata

エラーの件は、スプレッドシートのURLが間違っているか、GCPのサービスアカウントに編集権限がないのだと思います。

記事のGCPの設定の欄をご確認ください。

なお、環境変数が登録できないのですが、対象をお教えいただきたいです

これはどの環境変数の件を指しているのでしょうか?