外部サーバー不要!Slack次世代プラットフォームでランダムメンションBotを作った話

はじめに

「このタスク誰がやる?」——チームのSlackチャンネルで、ちょっとしたタスクの担当者をランダムに決めたい場面。既存のslackの機能だとタイミングによって偏りが出てしまっていたり、自分にメンションが飛んできたりするケースがあったので、ラウンドロビン形式でもメンションできるBotが欲しくなりました。

今回、Slackの次世代プラットフォーム(Slack Hosted)を使って、外部サーバーもAWSアカウントも不要なランダムメンションBotを作りました。この記事では、Slack Hostedの特徴と、実際の開発体験を紹介します。

実際のBot
----------0008-04-24-14.11.41

Slack Hostedとは

Slack Hostedは、Slackが提供するアプリケーション実行基盤です。従来のSlack Botとの大きな違いは以下の通りです。

従来のSlack Bot Slack Hosted
実行環境 自前のサーバー / AWS Lambda等 Slackが提供(Deno Runtime)
データベース 自前で用意(DynamoDB等) Slack Datastore
言語 任意 TypeScript(Deno)
インフラコスト あり 有料プランに含まれる
デプロイ 自前で構築 slack deploy 一発

つまり、Slackの有料プランさえあれば、追加のインフラなしでBotを動かせるのが最大の魅力です。

作ったもの:Random Mention Bot

Botにメンションすると、登録メンバーからランダムに1人を選んでメンションするBotです。

主な機能

  • ランダム選出@Bot でメンバーから1人を選出
  • ラウンドロビン選出 — 順番に選出するモードも搭載
  • メンバー管理@Bot add @ユーザー / @Bot remove @ユーザー で追加・削除
  • グループ機能 — 用途別にメンバーグループを分けられる
  • ユーザーグループ同期 — Slackのユーザーグループからメンバーを一括インポート
  • 自分除外 — メンションした本人は候補から自動的に除外

使い方

@Bot add @田中        → メンバー追加
@Bot add @佐藤        → メンバー追加
@Bot                  → ランダムに1人選出(自分は除外)
@Bot mode round_robin → ラウンドロビンに切替(グループ単位)
@Bot list             → メンバー一覧を表示
@Bot help             → コマンド一覧を表示

アーキテクチャ

Architecture
Slack Platform
@Bot メンション
Trigger
Event: AppMentioned
Workflow
入力を受け渡すパイプライン
Function
コマンドパース・選出ロジック
Datastore
members
メンバー管理
config
選出モード設定
round_robin_state
ラウンドロビン状態
※ 外部サーバー・DB不要。すべてSlack内で完結

Trigger → Workflow → Function の3層構造

Slack Hostedのアプリは、TriggerWorkflowFunction の3つの要素で構成されます。

Trigger(トリガー) はイベントの起点です。今回はBotへのメンションをイベントトリガーで検知しています。

// triggers/mention_trigger.ts
const mentionTrigger: Trigger<typeof RandomPickWorkflow.definition> = {
  type: TriggerTypes.Event,
  name: "Random pick on mention",
  workflow: `#/workflows/${RandomPickWorkflow.definition.callback_id}`,
  event: {
    event_type: TriggerEventTypes.AppMentioned,
    all_resources: true,
  },
  inputs: {
    channel_id: { value: TriggerContextData.Event.AppMentioned.channel_id },
    user_id: { value: TriggerContextData.Event.AppMentioned.user_id },
    message_ts: { value: TriggerContextData.Event.AppMentioned.message_ts },
    text: { value: TriggerContextData.Event.AppMentioned.text },
  },
};

Workflow(ワークフロー) はTriggerから受け取った入力をFunctionに渡すパイプラインです。

// workflows/random_pick_workflow.ts
export const RandomPickWorkflow = DefineWorkflow({
  callback_id: "random_pick_workflow",
  title: "Random Pick",
  input_parameters: {
    properties: {
      channel_id: { type: Schema.slack.types.channel_id },
      user_id: { type: Schema.slack.types.user_id },
      message_ts: { type: Schema.types.string },
      text: { type: Schema.types.string },
    },
    required: ["channel_id", "user_id", "message_ts"],
  },
});

RandomPickWorkflow.addStep(RandomPickFunction, {
  channel_id: RandomPickWorkflow.inputs.channel_id,
  user_id: RandomPickWorkflow.inputs.user_id,
  message_ts: RandomPickWorkflow.inputs.message_ts,
  text: RandomPickWorkflow.inputs.text,
});

Function(ファンクション) が実際のビジネスロジックです。メンションのテキストをパースして、コマンドに応じた処理を実行します。

Slack Datastore:サーバーレスなデータ永続化

外部DBを用意せずに、Slack Datastoreでデータを永続化できます。定義はシンプルです。

// datastores/members.ts
export const MembersDatastore = DefineDatastore({
  name: "members",
  primary_key: "id",
  attributes: {
    id: { type: Schema.types.string },       // "group_name:user_id"
    group_name: { type: Schema.types.string },
    user_id: { type: Schema.types.string },
  },
});

操作もSDK経由で直感的に行えます。

// データの書き込み
await client.apps.datastore.put({
  datastore: MembersDatastore.name,
  item: { id: `${group}:${userId}`, group_name: group, user_id: userId },
});

// データの検索
await client.apps.datastore.query({
  datastore: MembersDatastore.name,
  expression: "#g = :g",
  expression_attributes: { "#g": "group_name" },
  expression_values: { ":g": group },
});

DynamoDBライクなクエリ構文で、フィルタリングも可能です。

開発体験

ローカル開発が快適

slack run コマンドで、ローカルのコードをそのままSlackワークスペースで動かせます。コードを変更するとホットリロードされるため、開発サイクルが非常に速いです。

slack run

デプロイは一行

本番デプロイも slack deploy だけ。CI/CDパイプラインの構築も不要です。

slack deploy

manifest.tsで全体を宣言的に管理

アプリの構成(Function、Workflow、Datastore、Bot権限)はすべて manifest.ts に宣言的に記述します。

export default Manifest({
  name: "Random Mention Bot",
  description: "Botにメンションするとランダムに1人を選出してメンションします",
  functions: [RandomPickFunction, AddMemberFunction, ...],
  workflows: [RandomPickWorkflow, AddMemberWorkflow, ...],
  datastores: [MembersDatastore, ChannelConfigDatastore, RoundRobinStateDatastore],
  botScopes: [
    "commands", "app_mentions:read", "chat:write",
    "datastore:read", "datastore:write", "usergroups:read",
  ],
});

必要な権限スコープもここで管理するため、設定の見通しが良いです。

ディレクトリ構成

├── manifest.ts              # App設定(宣言的)
├── datastores/
│   ├── members.ts           # メンバーデータストア
│   ├── channel_config.ts    # グループごとのモード設定
│   └── round_robin_state.ts # ラウンドロビン状態管理
├── functions/
│   ├── random_pick.ts       # メインロジック(コマンドパース+選出)
│   ├── add_member.ts        # メンバー追加
│   ├── remove_member.ts     # メンバー削除
│   ├── list_members.ts      # メンバー一覧
│   ├── sync_usergroup.ts    # ユーザーグループ同期
│   └── set_mode.ts          # 選出モード切替
├── workflows/               # Trigger → Function のパイプライン
├── triggers/                # イベント検知(メンション等)
└── slack.json               # Slack CLI設定

Slack Hostedの良いところ・注意点

良いところ

  • インフラ管理ゼロ — サーバー、DB、デプロイパイプラインすべて不要
  • コスト追加なし — Slack有料プランに含まれている
  • 型安全 — TypeScript + Deno SDKで型の恩恵を受けられる
  • ローカル開発が快適slack run でホットリロード付き開発
  • 宣言的な構成管理 — manifest.tsで全体像が把握しやすい

注意点

  • Slack有料プランが必須(Pro / Business+ / Enterprise Grid)
  • Denoランタイム限定 — Node.jsのnpmパッケージはそのまま使えない場合がある
  • Datastoreの制約 — RDBのような複雑なクエリやJOINはできない
  • 実行時間制限 — 長時間実行するバッチ処理には向かない

まとめ

Slack Hostedは、「ちょっとしたBot」を作るのに最適なプラットフォームです。

  • 外部インフラの構築・運用が不要
  • TypeScriptで型安全に開発できる
  • Datastoreでデータ永続化も完結
  • slack deploy でデプロイ完了

今回のRandom Mention Botのように、チーム内のちょっとした課題を解決するBotを、最小限の手間で作って運用できるのがSlack Hostedの魅力です。

「サーバーを立てるほどでもないけど、Botがあると便利なんだよな」——そんな場面にぜひ試してみてください。