{
    "componentChunkName": "component---src-templates-post-js",
    "path": "/wai-bu-sababu-yao-slackci-shi-dai-puratutohuomuderandamumensiyonbotwozuo-tutahua/",
    "result": {"data":{"ghostPost":{"id":"Ghost__Post__69e85feb781e37000184e7d7","title":"外部サーバー不要！Slack次世代プラットフォームでランダムメンションBotを作った話","slug":"wai-bu-sababu-yao-slackci-shi-dai-puratutohuomuderandamumensiyonbotwozuo-tutahua","featured":false,"feature_image":null,"excerpt":" Slack Platform @Bot メンション ▼ Trigger Event: AppMentioned ▼ Workflow \n入力を受け渡すパイプライン ▼ Function コマンドパース・選出ロジック ▼ Datastore ▼ members メンバー管理 config \n選出モード設定 round_robin_state ラウンドロビン状態 ※ 外部サーバー・DB不要。すべてSlack内で完結","custom_excerpt":null,"visibility":"public","created_at_pretty":"22 April, 2026","published_at_pretty":"29 July, 2026","updated_at_pretty":"29 July, 2026","created_at":"2026-04-22T14:43:07.000+09:00","published_at":"2026-07-29T14:52:28.000+09:00","updated_at":"2026-07-29T14:52:28.000+09:00","meta_title":null,"meta_description":null,"og_description":null,"og_image":null,"og_title":null,"twitter_description":null,"twitter_image":null,"twitter_title":null,"authors":[{"name":"Takashi Uchida","slug":"takashi","bio":null,"profile_image":null,"twitter":null,"facebook":null,"website":null}],"primary_author":{"name":"Takashi Uchida","slug":"takashi","bio":null,"profile_image":null,"twitter":null,"facebook":null,"website":null},"primary_tag":null,"tags":[],"plaintext":" Slack Platform @Bot メンション ▼ Trigger Event: AppMentioned ▼ Workflow \n入力を受け渡すパイプライン ▼ Function コマンドパース・選出ロジック ▼ Datastore ▼ members メンバー管理 config \n選出モード設定 round_robin_state ラウンドロビン状態 ※ 外部サーバー・DB不要。すべてSlack内で完結","html":"<!--kg-card-begin: markdown--><h2 id=\"%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB\">はじめに</h2>\n<p>「このタスク誰がやる？」——チームのSlackチャンネルで、ちょっとしたタスクの担当者をランダムに決めたい場面。既存のslackの機能だとタイミングによって偏りが出てしまっていたり、自分にメンションが飛んできたりするケースがあったので、ラウンドロビン形式でもメンションできるBotが欲しくなりました。</p>\n<p>今回、Slackの次世代プラットフォーム（Slack Hosted）を使って、<strong>外部サーバーもAWSアカウントも不要</strong>なランダムメンションBotを作りました。この記事では、Slack Hostedの特徴と、実際の開発体験を紹介します。</p>\n<p>実際のBot<br>\n<img src=\"https://ghost.tech.anti-pattern.co.jp/content/images/2026/04/----------0008-04-24-14.11.41.png\" alt=\"----------0008-04-24-14.11.41\" loading=\"lazy\"></p>\n<h2 id=\"slack-hosted%E3%81%A8%E3%81%AF\">Slack Hostedとは</h2>\n<p>Slack Hostedは、Slackが提供するアプリケーション実行基盤です。従来のSlack Botとの大きな違いは以下の通りです。</p>\n<table>\n<thead>\n<tr>\n<th></th>\n<th>従来のSlack Bot</th>\n<th>Slack Hosted</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>実行環境</td>\n<td>自前のサーバー / AWS Lambda等</td>\n<td>Slackが提供（Deno Runtime）</td>\n</tr>\n<tr>\n<td>データベース</td>\n<td>自前で用意（DynamoDB等）</td>\n<td>Slack Datastore</td>\n</tr>\n<tr>\n<td>言語</td>\n<td>任意</td>\n<td>TypeScript（Deno）</td>\n</tr>\n<tr>\n<td>インフラコスト</td>\n<td>あり</td>\n<td>有料プランに含まれる</td>\n</tr>\n<tr>\n<td>デプロイ</td>\n<td>自前で構築</td>\n<td><code>slack deploy</code> 一発</td>\n</tr>\n</tbody>\n</table>\n<p>つまり、<strong>Slackの有料プランさえあれば、追加のインフラなしでBotを動かせる</strong>のが最大の魅力です。</p>\n<h2 id=\"%E4%BD%9C%E3%81%A3%E3%81%9F%E3%82%82%E3%81%AE%EF%BC%9Arandom-mention-bot\">作ったもの：Random Mention Bot</h2>\n<p>Botにメンションすると、登録メンバーからランダムに1人を選んでメンションするBotです。</p>\n<h3 id=\"%E4%B8%BB%E3%81%AA%E6%A9%9F%E8%83%BD\">主な機能</h3>\n<ul>\n<li><strong>ランダム選出</strong> — <code>@Bot</code> でメンバーから1人を選出</li>\n<li><strong>ラウンドロビン選出</strong> — 順番に選出するモードも搭載</li>\n<li><strong>メンバー管理</strong> — <code>@Bot add @ユーザー</code> / <code>@Bot remove @ユーザー</code> で追加・削除</li>\n<li><strong>グループ機能</strong> — 用途別にメンバーグループを分けられる</li>\n<li><strong>ユーザーグループ同期</strong> — Slackのユーザーグループからメンバーを一括インポート</li>\n<li><strong>自分除外</strong> — メンションした本人は候補から自動的に除外</li>\n</ul>\n<h3 id=\"%E4%BD%BF%E3%81%84%E6%96%B9\">使い方</h3>\n<pre><code>@Bot add @田中        → メンバー追加\n@Bot add @佐藤        → メンバー追加\n@Bot                  → ランダムに1人選出（自分は除外）\n@Bot mode round_robin → ラウンドロビンに切替（グループ単位）\n@Bot list             → メンバー一覧を表示\n@Bot help             → コマンド一覧を表示\n</code></pre>\n<h2 id=\"%E3%82%A2%E3%83%BC%E3%82%AD%E3%83%86%E3%82%AF%E3%83%81%E3%83%A3\">アーキテクチャ</h2>\n<!--kg-card-end: markdown--><!--kg-card-begin: html--><!DOCTYPE html>\n<html lang=\"ja\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Architecture</title>\n  <style>\n    body {\n      font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Hiragino Sans\", \"Noto Sans CJK JP\", sans-serif;\n      background: #f8f9fa;\n      display: flex;\n      justify-content: center;\n      padding: 40px 16px;\n    }\n    .arch {\n      border: 2px solid #611f69;\n      border-radius: 12px;\n      padding: 40px 32px 24px;\n      background: #faf5fb;\n      position: relative;\n      max-width: 480px;\n      width: 100%;\n    }\n    .arch-label {\n      position: absolute;\n      top: -13px;\n      left: 20px;\n      background: #611f69;\n      color: #fff;\n      font-size: 0.85em;\n      font-weight: 600;\n      padding: 3px 16px;\n      border-radius: 4px;\n    }\n    .flow {\n      display: flex;\n      flex-direction: column;\n      align-items: center;\n      gap: 0;\n    }\n    .node {\n      background: #fff;\n      border: 2px solid #611f69;\n      border-radius: 10px;\n      padding: 12px 32px;\n      text-align: center;\n      width: 300px;\n    }\n    .node .title { font-weight: 700; font-size: 0.95em; }\n    .node .desc { font-size: 0.8em; color: #666; margin-top: 2px; }\n    .arrow {\n      color: #611f69;\n      font-size: 1.2em;\n      line-height: 1;\n      padding: 6px 0;\n    }\n    .db {\n      background: #fff;\n      border: 2px solid #611f69;\n      border-radius: 50% / 25%;\n      padding: 12px 32px;\n      text-align: center;\n      width: 220px;\n    }\n    .db .title { font-weight: 700; font-size: 0.95em; }\n    .stores {\n      display: flex;\n      justify-content: center;\n      gap: 10px;\n      margin-top: 6px;\n      flex-wrap: wrap;\n    }\n    .store {\n      background: #fff;\n      border: 1.5px solid #999;\n      border-radius: 6px;\n      padding: 8px 14px;\n      text-align: center;\n      font-size: 0.82em;\n    }\n    .store .name { font-weight: 600; }\n    .store .sdesc { color: #888; font-size: 0.9em; }\n    .user { text-align: center; font-size: 0.95em; font-weight: 600; }\n    .note {\n      text-align: center;\n      font-size: 0.78em;\n      color: #888;\n      margin-top: 20px;\n    }\n  </style>\n</head>\n<body>\n<div class=\"arch\">\n  <span class=\"arch-label\">Slack Platform</span>\n  <div class=\"flow\">\n    <div class=\"user\"><code>@Bot</code> メンション</div>\n    <div class=\"arrow\">▼</div>\n    <div class=\"node\">\n      <div class=\"title\">Trigger</div>\n      <div class=\"desc\">Event: AppMentioned</div>\n    </div>\n    <div class=\"arrow\">▼</div>\n    <div class=\"node\">\n      <div class=\"title\">Workflow</div>\n      <div class=\"desc\">入力を受け渡すパイプライン</div>\n    </div>\n    <div class=\"arrow\">▼</div>\n    <div class=\"node\">\n      <div class=\"title\">Function</div>\n      <div class=\"desc\">コマンドパース・選出ロジック</div>\n    </div>\n    <div class=\"arrow\">▼</div>\n    <div class=\"db\">\n      <div class=\"title\">Datastore</div>\n    </div>\n    <div class=\"arrow\">▼</div>\n    <div class=\"stores\">\n      <div class=\"store\">\n        <div class=\"name\">members</div>\n        <div class=\"sdesc\">メンバー管理</div>\n      </div>\n      <div class=\"store\">\n        <div class=\"name\">config</div>\n        <div class=\"sdesc\">選出モード設定</div>\n      </div>\n      <div class=\"store\">\n        <div class=\"name\">round_robin_state</div>\n        <div class=\"sdesc\">ラウンドロビン状態</div>\n      </div>\n    </div>\n  </div>\n  <div class=\"note\">※ 外部サーバー・DB不要。すべてSlack内で完結</div>\n</div>\n</body>\n</html>\n<!--kg-card-end: html--><!--kg-card-begin: markdown--><h3 id=\"trigger-%E2%86%92-workflow-%E2%86%92-function-%E3%81%AE3%E5%B1%A4%E6%A7%8B%E9%80%A0\">Trigger → Workflow → Function の3層構造</h3>\n<p>Slack Hostedのアプリは、<strong>Trigger</strong>・<strong>Workflow</strong>・<strong>Function</strong> の3つの要素で構成されます。</p>\n<p><strong>Trigger（トリガー）</strong> はイベントの起点です。今回はBotへのメンションをイベントトリガーで検知しています。</p>\n<pre><code class=\"language-typescript\">// triggers/mention_trigger.ts\nconst mentionTrigger: Trigger&lt;typeof RandomPickWorkflow.definition&gt; = {\n  type: TriggerTypes.Event,\n  name: &quot;Random pick on mention&quot;,\n  workflow: `#/workflows/${RandomPickWorkflow.definition.callback_id}`,\n  event: {\n    event_type: TriggerEventTypes.AppMentioned,\n    all_resources: true,\n  },\n  inputs: {\n    channel_id: { value: TriggerContextData.Event.AppMentioned.channel_id },\n    user_id: { value: TriggerContextData.Event.AppMentioned.user_id },\n    message_ts: { value: TriggerContextData.Event.AppMentioned.message_ts },\n    text: { value: TriggerContextData.Event.AppMentioned.text },\n  },\n};\n</code></pre>\n<p><strong>Workflow（ワークフロー）</strong> はTriggerから受け取った入力をFunctionに渡すパイプラインです。</p>\n<pre><code class=\"language-typescript\">// workflows/random_pick_workflow.ts\nexport const RandomPickWorkflow = DefineWorkflow({\n  callback_id: &quot;random_pick_workflow&quot;,\n  title: &quot;Random Pick&quot;,\n  input_parameters: {\n    properties: {\n      channel_id: { type: Schema.slack.types.channel_id },\n      user_id: { type: Schema.slack.types.user_id },\n      message_ts: { type: Schema.types.string },\n      text: { type: Schema.types.string },\n    },\n    required: [&quot;channel_id&quot;, &quot;user_id&quot;, &quot;message_ts&quot;],\n  },\n});\n\nRandomPickWorkflow.addStep(RandomPickFunction, {\n  channel_id: RandomPickWorkflow.inputs.channel_id,\n  user_id: RandomPickWorkflow.inputs.user_id,\n  message_ts: RandomPickWorkflow.inputs.message_ts,\n  text: RandomPickWorkflow.inputs.text,\n});\n</code></pre>\n<p><strong>Function（ファンクション）</strong> が実際のビジネスロジックです。メンションのテキストをパースして、コマンドに応じた処理を実行します。</p>\n<h3 id=\"slack-datastore%EF%BC%9A%E3%82%B5%E3%83%BC%E3%83%90%E3%83%BC%E3%83%AC%E3%82%B9%E3%81%AA%E3%83%87%E3%83%BC%E3%82%BF%E6%B0%B8%E7%B6%9A%E5%8C%96\">Slack Datastore：サーバーレスなデータ永続化</h3>\n<p>外部DBを用意せずに、Slack Datastoreでデータを永続化できます。定義はシンプルです。</p>\n<pre><code class=\"language-typescript\">// datastores/members.ts\nexport const MembersDatastore = DefineDatastore({\n  name: &quot;members&quot;,\n  primary_key: &quot;id&quot;,\n  attributes: {\n    id: { type: Schema.types.string },       // &quot;group_name:user_id&quot;\n    group_name: { type: Schema.types.string },\n    user_id: { type: Schema.types.string },\n  },\n});\n</code></pre>\n<p>操作もSDK経由で直感的に行えます。</p>\n<pre><code class=\"language-typescript\">// データの書き込み\nawait client.apps.datastore.put({\n  datastore: MembersDatastore.name,\n  item: { id: `${group}:${userId}`, group_name: group, user_id: userId },\n});\n\n// データの検索\nawait client.apps.datastore.query({\n  datastore: MembersDatastore.name,\n  expression: &quot;#g = :g&quot;,\n  expression_attributes: { &quot;#g&quot;: &quot;group_name&quot; },\n  expression_values: { &quot;:g&quot;: group },\n});\n</code></pre>\n<p>DynamoDBライクなクエリ構文で、フィルタリングも可能です。</p>\n<h2 id=\"%E9%96%8B%E7%99%BA%E4%BD%93%E9%A8%93\">開発体験</h2>\n<h3 id=\"%E3%83%AD%E3%83%BC%E3%82%AB%E3%83%AB%E9%96%8B%E7%99%BA%E3%81%8C%E5%BF%AB%E9%81%A9\">ローカル開発が快適</h3>\n<p><code>slack run</code> コマンドで、ローカルのコードをそのままSlackワークスペースで動かせます。コードを変更するとホットリロードされるため、開発サイクルが非常に速いです。</p>\n<pre><code class=\"language-bash\">slack run\n</code></pre>\n<h3 id=\"%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4%E3%81%AF%E4%B8%80%E8%A1%8C\">デプロイは一行</h3>\n<p>本番デプロイも <code>slack deploy</code> だけ。CI/CDパイプラインの構築も不要です。</p>\n<pre><code class=\"language-bash\">slack deploy\n</code></pre>\n<h3 id=\"manifestts%E3%81%A7%E5%85%A8%E4%BD%93%E3%82%92%E5%AE%A3%E8%A8%80%E7%9A%84%E3%81%AB%E7%AE%A1%E7%90%86\">manifest.tsで全体を宣言的に管理</h3>\n<p>アプリの構成（Function、Workflow、Datastore、Bot権限）はすべて <code>manifest.ts</code> に宣言的に記述します。</p>\n<pre><code class=\"language-typescript\">export default Manifest({\n  name: &quot;Random Mention Bot&quot;,\n  description: &quot;Botにメンションするとランダムに1人を選出してメンションします&quot;,\n  functions: [RandomPickFunction, AddMemberFunction, ...],\n  workflows: [RandomPickWorkflow, AddMemberWorkflow, ...],\n  datastores: [MembersDatastore, ChannelConfigDatastore, RoundRobinStateDatastore],\n  botScopes: [\n    &quot;commands&quot;, &quot;app_mentions:read&quot;, &quot;chat:write&quot;,\n    &quot;datastore:read&quot;, &quot;datastore:write&quot;, &quot;usergroups:read&quot;,\n  ],\n});\n</code></pre>\n<p>必要な権限スコープもここで管理するため、設定の見通しが良いです。</p>\n<h2 id=\"%E3%83%87%E3%82%A3%E3%83%AC%E3%82%AF%E3%83%88%E3%83%AA%E6%A7%8B%E6%88%90\">ディレクトリ構成</h2>\n<pre><code>├── manifest.ts              # App設定（宣言的）\n├── datastores/\n│   ├── members.ts           # メンバーデータストア\n│   ├── channel_config.ts    # グループごとのモード設定\n│   └── round_robin_state.ts # ラウンドロビン状態管理\n├── functions/\n│   ├── random_pick.ts       # メインロジック（コマンドパース＋選出）\n│   ├── add_member.ts        # メンバー追加\n│   ├── remove_member.ts     # メンバー削除\n│   ├── list_members.ts      # メンバー一覧\n│   ├── sync_usergroup.ts    # ユーザーグループ同期\n│   └── set_mode.ts          # 選出モード切替\n├── workflows/               # Trigger → Function のパイプライン\n├── triggers/                # イベント検知（メンション等）\n└── slack.json               # Slack CLI設定\n</code></pre>\n<h2 id=\"slack-hosted%E3%81%AE%E8%89%AF%E3%81%84%E3%81%A8%E3%81%93%E3%82%8D%E3%83%BB%E6%B3%A8%E6%84%8F%E7%82%B9\">Slack Hostedの良いところ・注意点</h2>\n<h3 id=\"%E8%89%AF%E3%81%84%E3%81%A8%E3%81%93%E3%82%8D\">良いところ</h3>\n<ul>\n<li><strong>インフラ管理ゼロ</strong> — サーバー、DB、デプロイパイプラインすべて不要</li>\n<li><strong>コスト追加なし</strong> — Slack有料プランに含まれている</li>\n<li><strong>型安全</strong> — TypeScript + Deno SDKで型の恩恵を受けられる</li>\n<li><strong>ローカル開発が快適</strong> — <code>slack run</code> でホットリロード付き開発</li>\n<li><strong>宣言的な構成管理</strong> — manifest.tsで全体像が把握しやすい</li>\n</ul>\n<h3 id=\"%E6%B3%A8%E6%84%8F%E7%82%B9\">注意点</h3>\n<ul>\n<li><strong>Slack有料プランが必須</strong>（Pro / Business+ / Enterprise Grid）</li>\n<li><strong>Denoランタイム限定</strong> — Node.jsのnpmパッケージはそのまま使えない場合がある</li>\n<li><strong>Datastoreの制約</strong> — RDBのような複雑なクエリやJOINはできない</li>\n<li><strong>実行時間制限</strong> — 長時間実行するバッチ処理には向かない</li>\n</ul>\n<h2 id=\"%E3%81%BE%E3%81%A8%E3%82%81\">まとめ</h2>\n<p>Slack Hostedは、「ちょっとしたBot」を作るのに最適なプラットフォームです。</p>\n<ul>\n<li>外部インフラの構築・運用が不要</li>\n<li>TypeScriptで型安全に開発できる</li>\n<li>Datastoreでデータ永続化も完結</li>\n<li><code>slack deploy</code> でデプロイ完了</li>\n</ul>\n<p>今回のRandom Mention Botのように、チーム内のちょっとした課題を解決するBotを、最小限の手間で作って運用できるのがSlack Hostedの魅力です。</p>\n<p>「サーバーを立てるほどでもないけど、Botがあると便利なんだよな」——そんな場面にぜひ試してみてください。</p>\n<!--kg-card-end: markdown-->","url":"https://ghost.tech.anti-pattern.co.jp/wai-bu-sababu-yao-slackci-shi-dai-puratutohuomuderandamumensiyonbotwozuo-tutahua/","canonical_url":null,"uuid":"41024272-38b2-4970-99b1-5fe8e17e1023","page":null,"codeinjection_foot":null,"codeinjection_head":null,"codeinjection_styles":null,"comment_id":"69e85feb781e37000184e7d7","reading_time":5}},"pageContext":{"slug":"wai-bu-sababu-yao-slackci-shi-dai-puratutohuomuderandamumensiyonbotwozuo-tutahua"}},
    "staticQueryHashes": ["176528973","2358152166","2561578252","2731221146","4145280475"]}