Skip to content

コマンドとイベント

ワンだふる (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_addedPOST /slack/events (Events API) で届く。index.ts が署名検証 → 即 200 (3 秒 ack) → ctx.waitUntil で以降を非同期実行する。event:{event_id} キーの KV 記録 (TTL 1 時間) で Slack リトライによる二重通知を抑止する。

通知処理本体 (application/notify-reaction.ts):

  1. event.item.type !== "message" のリアクションは無視 (presentation で早期 return)。
  2. TeamConfig をロード。
  3. findBinding(config, event.reaction) でリアクションに対応する ReactionBinding を探す。なければ 何もせず終了
  4. conversations.info でチャンネル情報 (name + isPrivate) を取得。プライベートチャンネルなら何もせず終了 — プライバシー保護の不変条件。
  5. workspaceDomain が未キャッシュなら team.info で取得し保存。
  6. ユーザー情報 (users.info) を取得。
  7. メッセージリンクを構築し、通知文を そのバインディングの channelchat.postMessage で送信。

不変条件: プライベートチャンネルでのリアクションは絶対に通知しない。SlackGateway.fetchChannelInfo{ name, isPrivate } を返す。isPrivate チェックを外す改修は禁止。

通知本文のフォーマット

{userName} さんが {#channel} で :{reaction}: しました
{messageLink}
  • userNamedisplayName → realName → name → <@id> の順にフォールバック。
  • メッセージリンクは https://{workspaceDomain}.slack.com/archives/{channelId}/p{messageTsの.を除いたもの}
  • workspaceDomain 取得失敗時はリンク行を省略する。
  • unfurl_links: true でリンクをアンファール表示する。