はじめに
この記事は前回取材してもらった記事 社内部活動でfondeskのカスタマイズ版とSlackチャンネル「TLけいさつ」を作った僕の野望 から技術的な要素をもっと細かく書いたものです
使用技術
- 言語
- NodeJS
- TypeScript
- FrameWork
- PaaS
- DB
なんで作ったか
既に同じような機能をもつ bot はあったけどどれもメンバー表を別で持たないといけなかった なので都度更新しないといけないかったりする億劫さないしは運用作業が必要になっちゃう。。
e.g.
- スプレッドシートを使った場合
- S3
閃き
Slack のプロフィールと同期してくれたらいいのでは? と思い、fondesk がいるチャンネルのメンバーのプロフィールを定期的に同期するハンドラーを書きました(Cron)
実装編
HTTP リクエストを実装
Cron をどのようにしようかと迷ってたんですが、今回 PaaS を GAE にしてることもあって HTTPリクエストを受け取れるエンドポイントを作ることにしました。 Slack Bolt Framework は Express のラッパーなので Express Receiver が使えます 以下のようにしてあっという間に呼び出せます
import { ExpressReceiver } from "@slack/bolt"; const receiver = new ExpressReceiver({ signingSecret: process.env.SLACK_SIGNING_SECRET, }); receiver.app.get(`/register`, (req, res) => { res.sendStatus(200); // チャンネルメンバーを取得 const channel = new Channel(app); // 更新 channel.putChannelMemberFirestore(); });
Cron を実装
GAE の場合は cron.yaml を作成します そして以下のコマンドを打つと cron を設定できます
$ gcloud app deploy app.yml cron.yml
以下は実際に fondeSlack で動かしているものです
cron: - description: upsert slack profile into firestore url: /register schedule: every 1 hours from 9:00 to 18:00 timezone: Asia/Tokyo
ちょっと困ったこと
Cron の schedule ですが微妙にやりたいことができなかったです 例えば弊社では基本的に受電する機会は平日に限られます。 なので平日の営業時間 9:00-18:00 までが受電する時間帯です プロフィールの更新作業もこの時間と同じにしたかったのですが、GAE の Cron ドキュメントを見ると、できないようです
↓ こうしたかった... (平日だけ9:00-18:00に1時間に一回更新)
cron: - description: upsert slack profile into firestore url: /register schedule: every mon tue wed thu fri 1 hours from 9:00 to 18:00 timezone: Asia/Tokyo
参考
プロフィールを DB に保存する
今回、DB はさくっと作りたかったので Firestore を使ってます 構成としては、Slack のプロフィールを DB に保存しておいて、fondesk からメッセージがきたら DB と照らしあわせて、対象のユーザの SlackID でメンションする形です
Slack 命名規則だったり除外したいプロフィール項目だったり
プロフィール項目は特定の項目は除外するようにしてます 弊社でいうと部署ですね
弊社では Slack の本名には命名規則があり、Asato Nago / 名護 朝人 のようにしています
このように会社ごとで命名規則などがあると思うのでその点は調整可能なようにしています (これは config ファイルなどを別で管理する予定)
// 検索対象に含めたくないslackのプロフィール項目名 private IGNORE_CUSTOM_FIELDS_LABEL = ["部署"]; private splitWords = (data: string): string[] => { if (!data) return []; // チームによってSlackプロフィールの設定ルールがあると思うのでここは区切りたい文字で const specialChar = /\/||//; const result = data .split(/\s+/) .filter(item => item.replace(specialChar, "")); console.log({ data }); return result; }; private getCustomFields = (data: fields): string[] => { if (!data) return []; return Object.keys(data).flatMap(element => { const field = data[element]; if (this.IGNORE_CUSTOM_FIELDS_LABEL.includes(field.label)) { return ""; } return this.splitWords(field.value); }); };
実際の Firestore Console
駆け足で作ったのと初めての Firestore なので DB 構成などはもっと良くなると思います 何か tips などあればぜひ教えてほしいです
Event Subscriptions
さて、最後は fondesk からのメッセージから DB に問合せてメンションする処理です 12/2 から fondesk のペリカンアップデートによって Slack の通知方法が変わりました 具体的には 旧い attachment から block kit を使うようになり、とてもみやすい&わかりやすい内容となってました
Before
After
なお、コード側では fondesk のメッセージにのみ反応’するようにしています
app.message(/^(.*)/, async ({ context, message: payload }) => { console.log({ payload }); if ( payload.subtype !== "bot_message" || !payload.blocks || !payload.blocks.length ) { console.log("🥺 not fondesk message"); return; }
誰に対してのメッセージかは正規表現で抜き出し、DB に問合せます
const splitedText = payload.text.match(/あて先:(.+)\s/); const targetMember = splitedText.length > 1 ? splitedText[1] : ""; const hitUser = await getFirestore(targetMember);
あとは、複数人ヒットした場合はメンションを追加したり、また誰も見つからなかった場合のみ @here
をつけるようにしたり、メンションすべき人が見つかった場合はスレッドメッセージにしたりしています
誰も見つからなかったとき
複数人のメンション
さいごに
先人たちに感謝! そしてお気軽に PR やイシューを是非お願いします〜 75asa/fondeSlack