コマンドとイベント
ワンだふる (apps/suteki-bot) のスラッシュコマンドと Slack イベントの処理。
スラッシュコマンド
リアクションと通知チャンネルを 1:1 のペア (ReactionBinding) として管理する。同じ emoji を再登録するとチャンネルが上書きされる。
| コマンド | 機能 | 例 |
|---|---|---|
/reaction-track <emoji> #channel | リアクションと通知先チャンネルのペアを登録 (既存 emoji は上書き) | /reaction-track :white_check_mark: #all-announce |
/reaction-untrack <emoji> | リアクションの通知設定を解除 | /reaction-untrack done |
/reaction-list | 現在の (emoji → channel) ペア一覧を表示 | — |
コマンドは POST /slack/commands (form-encoded) で届き、payload の command フィールドでディスパッチする。処理は KV の読み書き程度なので 3 秒以内に同期処理し、HTTP レスポンス (JSON) をそのままコマンド応答にする。
応答はすべて in_channel (公開) で返す。チャンネル指定は #name / <#Cxxxx|name> メンション / 生文字列のいずれも受け付け、チャンネルメンションは Cxxxx の ID に正規化する。
「複数パターンのトラッキング」は
/reaction-trackを異なる emoji で複数回呼ぶことで実現する。emoji が一意キーで、1 emoji あたり 1 channel しか持てない。
イベントハンドリング
reaction_added は POST /slack/events (Events API) で届く。index.ts が署名検証 → 即 200 (3 秒 ack) → ctx.waitUntil で以降を非同期実行する。event:{event_id} キーの KV 記録 (TTL 1 時間) で Slack リトライによる二重通知を抑止する。
通知処理本体 (application/notify-reaction.ts):
event.item.type !== "message"のリアクションは無視 (presentation で早期 return)。TeamConfigをロード。findBinding(config, event.reaction)でリアクションに対応するReactionBindingを探す。なければ 何もせず終了。conversations.infoでチャンネル情報 (name+isPrivate) を取得。プライベートチャンネルなら何もせず終了 — プライバシー保護の不変条件。workspaceDomainが未キャッシュならteam.infoで取得し保存。- ユーザー情報 (
users.info) を取得。 - メッセージリンクを構築し、通知文を そのバインディングの
channelにchat.postMessageで送信。
不変条件: プライベートチャンネルでのリアクションは絶対に通知しない。
SlackGateway.fetchChannelInfoは{ name, isPrivate }を返す。isPrivateチェックを外す改修は禁止。
通知本文のフォーマット
{userName} さんが {#channel} で :{reaction}: しました
{messageLink}userNameはdisplayName → realName → name → <@id>の順にフォールバック。- メッセージリンクは
https://{workspaceDomain}.slack.com/archives/{channelId}/p{messageTsの.を除いたもの}。 workspaceDomain取得失敗時はリンク行を省略する。unfurl_links: trueでリンクをアンファール表示する。