Slack の CLI と無料サンドボックス環境でサクサク快適開発

こんにちは、Slack の[公式 SDK 開発](https://github.com/slackapi)と日本の Developer Relations を担当している瀬良 (@seratch) と申します :wave:

## サンドボックス環境が使えるようになりました

米国時間 3/6 にサンフランシスコで開催された TrailblazerDX にて、Slack の新しい開発者向けサポート機能が発表されました。

https://slack.com/intl/ja-jp/blog/developers/developer-program-launch-jp

Bolt for Python / JavaScript でのカスタムファンクションなどのトピックもあるのですが、この記事ではこれまでよりもはるかに簡単な取得・管理が可能となった Enterprise Grid の**サンドボックス環境を使ったローカル開発の方法**について紹介したいと思います。

## 何が嬉しいの？

今までの Slack アプリ開発は、最初に以下のような手順が必要でした：

* https://api.slack.com/apps にアクセスして、アプリの設定
* Slack ワークスペースに自分が開発しているアプリをインストール
* その結果として取得したアクセストークンを環境変数として指定
* ようやくアプリを起動

しかし、今回利用可能となった Slack CLI と新しいサンドボックス環境の組み合わせを使うと非常に簡単で・・・実は **`slack run` で起動するだけ**です。



・
・・
・・・

:thinking: **「ん、Slack CLI って[あの Deno で開発するやつ](https://qiita.com/seratch/items/ecc16b845483c9d6f9e1)でしか使えないんじゃないの？」**

・・・
・・
・


そう思っている方も多いのではないかと思います。

はい、確かに以前は有料プランのワークスペースに接続する必要があり、初期は Deno アプリ専用ツールとして提供されていましたが、今後は Bolt を使った Slack アプリ開発でも利用可能となりました！

それでは、利用するための最小限の手順を紹介していきます。

## Slack の開発者ポータルでサンドボックスを作る

まずは開発者ポータルにアクセスします。

https://api.slack.com/developer-program

まだ登録されていない方は、はじめにアカウントを作成してください。

サンドボックスのプロビジョニングには、有料プランのワークスペースまたはクレジットカード登録が必要とはなりますが、**普通にサンドボックスを作成したり、アプリの動作確認をする分には費用はかかりません**。

ただし、ワークフローのカスタムファンクション実行については、本番の環境と同様に一定の無料枠を超過した分はワークフローの実行回数に基づいた従量課金となります。詳しくは[こちらのヘルプページ](https://slack.com/intl/ja-jp/help/articles/15363357403411)をご確認ください。

アカウントをセットアップできたら、早速サンドボックスを作ってみてください。うまくいったら、以下の画面のようになっているはずです。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19163/6b37719d-b44f-6f4d-4be8-924fb8e73165.png" width=400 />

### Enterprise Grid って何？

Enterprise Grid に馴染みのない方も多いかと思いますので、Sandbox Org の「Org」について少しだけ補足しておきます。

多くの方が馴染みのある Enterprise Grid 以外のプランではワークスペースを一つだけ利用できるわけですが、Enterprise Grid ではその上位に「オーガナイゼーション」という概念があり、その下にいくつでもワークスペースをつくることができるという違いがあります。

今回提供されるサンドボックス環境は Grid なので、まず Org を作ってその下にワークスペースを作っていくと流れになるというわけです。とりあえずは「そういうものなんだな」というくらいの理解で大丈夫です。流れに従って、手順を進めていってください。

なお、Enterprise Grid の一般的な説明については、以下のヘルプページや公式のガイド等を参考にしてください。

https://slack.com/intl/ja-jp/help/articles/115005481226

## Slack CLI のセットアップ

サンドボックス環境をプロビジョニングできたら、次は Slack CLI をインストールしてください。各プラットフォームのインストールの手順は以下のページを参考にしてください。`slack login` する前までやれば OK です。

https://api.slack.com/automation/cli/install

`slack` コマンドのインストールが終わったら、ターミナル上で `slack login` を実行します。以下のような画面表示になると思います。

```
$ slack login

📋 Run the following slash command in any Slack channel or DM
   This will open a modal with user permissions for you to approve
   Once approved, a challege code will be generated in Slack

/slackauthticket MGIwNTczNzktYWQ5Y***********************************

? Enter challenge code
```

`/slackauthticket MGIwN....` のところをコピーして、先ほど作ったサンドボックス環境の Slack ワークスペースのテキスト入力エリアで実行します。

以下のようなモーダルダイアログが表示され、

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19163/012e4417-ae84-1ee3-f1dd-ccd9f3553e71.png" width=400 />

「Confirm」ボタンを押すと、以下のように Challenge Code が表示されるので、

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19163/2971188d-5c1e-812c-2fbc-eb5083e2c070.png" width=400 />

その文字列をコピーしてターミナル側で入力すれば完了です。

この連携の結果は `$HOME/.slack/credentials.json` というファイルに保存されています。認証を取り消したい場合は `slack logout` で対象を選択してログアウトすることもできます。

それでは、さっそく開発を始めましょう！

## Python アプリを作る

まず、最もシンプルな Python アプリから接続してみましょう。

### Python 環境とライブラリのインストール

やり方は Poetry でも何でも OK ですが、Python 環境をセットアップし、必要な依存ライブラリ（二つだけです！）をインストールしておきます。

```bash
# やり方は Poetry でも何でも OK ですが、Python 環境をセットアップします
python3 -m venv .venv
source .venv/bin/activate

# 必要な依存ライブラリはとりあえずこれだけ
pip install slack-bolt slack-cli-hooks
```

### CLI との連携に必要なファイルを置く

Slack CLI は、Python の場合には少なくとも以下のファイルが存在していることを期待します。

|ファイル名|説明|
|-|-|
|`slack.json`|CLI がローカルで起動するために必要な設定|
|`manifest.json`|アプリの設定（`slack run` 実行中は自動で反映）|
|`app.py`|実行するアプリコード（ファイル名の変更も可能）|

`slack.json` は、以下の内容をそのまま保存してください。

```json
{
  "hooks": {
    "get-hooks": "python3 -m slack_cli_hooks.hooks.get_hooks"
  }
}
```

次に `manifest.json` ですが、これは必要に応じて変更するファイルです。今回のデモでは、まずはそのまま貼り付けてください。


```json
{
  "display_information": {
    "name": "Hello",
    "description": "You can update this later",
    "background_color": "#4361ba",
    "long_description": ""
  },
  "features": {
    "app_home": {
      "home_tab_enabled": false,
      "messages_tab_enabled": false,
      "messages_tab_read_only_enabled": true
    },
    "bot_user": {
      "display_name": "Hello Bot",
      "always_online": true
    },
    "slash_commands": [
      {
        "command": "/hello-sandbox",
        "description": "Say hello to sandbox",
        "usage_hint": "",
        "should_escape": true
      }
    ]
  },
  "oauth_config": {
    "scopes": {
      "bot": ["commands"]
    }
  },
  "settings": {
    "interactivity": { "is_enabled": true },
    "org_deploy_enabled": true,
    "socket_mode_enabled": true
  },
  "outgoing_domains": []
}
```

最後に `app.py` を以下の内容で置きます。


```python
import os
import logging
from slack_bolt import App
from slack_bolt.adapter.socket_mode import SocketModeHandler

logging.basicConfig(level=logging.DEBUG)

app = App(token=os.environ["SLACK_BOT_TOKEN"])

# ここにコードを追記していくこと


if __name__ == "__main__":
    SocketModeHandler(app, os.environ["SLACK_APP_TOKEN"]).start()
```

### 早速開発開始

一旦、準備完了です。`slack run` でアプリを立ち上げてみてください。特にエラーらしき出力が出なければ大丈夫です。

この段階で、ワークスペース側では既に `/hello-sandbox` というスラッシュコマンドが使えるようになっているはずです。実行してみてください。

UI 上でエラーになりますが、ターミナル側では、以下のようなワーニングが表示されているはずです。これは Bolt for Python が出力してくれる実装例のサジェスチョンです。

```
WARNING:slack_bolt.App:Unhandled request ({'type': None, 'command': '/hello-sandbox'})
---
[Suggestion] You can handle this type of event with the following listener function:

@app.command("/hello-sandbox")
def handle_some_command(ack, body, logger):
    ack()
    logger.info(body)
```

アプリから何も応答がないのは面白くないので、`ack()` に "Hello!" を渡すコードを追加してみましょう。`if __name__ == "__main__":` よりも前の行に追記してください。

```python
@app.command("/hello-sandbox")
def handle_some_command(ack, body, logger):
    ack("Hello!")
    logger.info(body)
```

この記事投稿時点で Python の CLI サポートは、コード変更の自動リロードに対応していません。コードを変更したときは `slack run` を立ち上げ直してください。

毎回ワークスペースを選ぶのが煩わしいと思いますので `slack run -a A06P0E7LY48` のように表示される App ID を指定するようにすると少し楽かもしれません。

新しくなったコードでは、UI 上で実行エラーにならず、bot が「Hello!」と応答してくれるはずです :tada:

## Node.js アプリを作る

同様に Node.js でも同じことをやってみましょう。`manifest.json` は変更する必要ありません。`slack.json` を以下のように変更します。

```json
{
  "hooks": {
    "get-hooks": "npx -q --no-install -p @slack/cli-hooks slack-cli-get-hooks",
    "start": "npm start"
  }
}
```

新しく `package.json` と `app.js` を追加します。

```
npm init -y
npm i @slack/bolt
npm i --save-dev @slack/cli-hooks nodemon
```

さらに `package.json` の "scripts" を以下のように変更します。`npm start` 以外にしたい場合は `slack.json` と揃える形で命名してください。

```
  "scripts": {
    "start": "nodemon --watch '*.js' --exec node app.js"
  },
```

最後に `app.js` を以下の内容で保存します。

```javascript
const { App, LogLevel } = require('@slack/bolt');

const app = new App({
  socketMode: true,
  token: process.env.SLACK_BOT_TOKEN,
  appToken: process.env.SLACK_APP_TOKEN,
  logLevel: LogLevel.DEBUG,
});

app.command("/hello-sandbox", async ({ ack }) => {
  await ack("Hello!");
});

;(async () => {
  await app.start();
  console.log("⚡️ Bolt app is running!");
})();
```

同じように `slack run` で起動してみてください。うまく動いているでしょうか？

こちらの例では nodemon でファイル変更を自動検知するので、再起動せずにサクサク開発することができるはずです。

## TypeScript の Node.js アプリを作る

最後に Node.js アプリを TypeScript で開発できるようにしてみましょう。

まず `tsconfig.json` を配置します。以下の内容はあくまで一例ですので、細かい点はお好みで大丈夫です。

```json
{
  "compilerOptions": {
    "target": "es2022",
    "module": "commonjs",
    "lib": ["es2022", "dom"],
    "outDir": "lib",
    "strict": true,
    "esModuleInterop": true,
    "resolveJsonModule": true
  }
}
```

次に追加の依存ライブラリをインストールします。

```
npm i --save-dev typescript ts-node @types/node
```

そして `package.json` に先ほど設定したコマンドを `"start": "nodemon --watch 'src/**/*.ts' --exec \"ts-node\" src/app.ts"` と書き換えてください。

最後に `src/app.ts` を以下の内容で保存します。

```typescript
import { App, LogLevel } from "@slack/bolt";

const app = new App({
  socketMode: true,
  token: process.env.SLACK_BOT_TOKEN,
  appToken: process.env.SLACK_APP_TOKEN,
  logLevel: LogLevel.DEBUG,
});

app.command("/hello-sandbox", async ({ ack }) => {
  await ack("Hello!");
});

;(async () => {
  await app.start();
  console.log("⚡️ Bolt app is running!");
})();
```

正しく設定できていれば、同じように動作するはずです。コードを変更してそれが反映されることも確認してみてください。

## CLI を使うと嬉しいこと

Slack CLI を使っていて本当に嬉しいことは、ここからです。

もう https://api.slack.com/apps にアクセスして、アプリ設定を変更して・・インストールし直して・・という手間が必要なくなります。

`manifest.json` を以下の内容で書き換えて保存してみましょう。

```json
{
  "display_information": {
    "name": "Hello",
    "description": "You can update this later",
    "background_color": "#4361ba",
    "long_description": ""
  },
  "features": {
    "app_home": {
      "home_tab_enabled": false,
      "messages_tab_enabled": false,
      "messages_tab_read_only_enabled": true
    },
    "bot_user": {
      "display_name": "Hello Bot",
      "always_online": true
    }
  },
  "oauth_config": {
    "scopes": {
      "bot": ["commands", "chat:write", "app_mentions:read"]
    }
  },
  "settings": {
    "interactivity": { "is_enabled": true },
    "event_subscriptions": {
      "bot_events": ["app_mention"]
    },
    "org_deploy_enabled": true,
    "socket_mode_enabled": true
  },
  "outgoing_domains": []
}
```

ファイルを保存すると Slack CLI によってその変更が Slack のサーバーサイドで管理するメタデータにも自動的に反映されます。

先ほどまで使っていたスラッシュコマンドはワークスペースからなくなり、代わりにこのアプリはボットをメンションするイベントを受信できるようになりました。

先ほど追加したソースファイルの `src/app.ts` に以下のコードを追加してください。これも nodemon によって自動で反映されるはずです。

```typescript
app.event("app_mention", async ({ say }) => {
  await say("Hi!");
});
```

動作確認のために `@Hello Bot (local)` をチャンネルに招待してメンションしてみてください。「Hi!」という返事が返ってきたら成功です :tada:

## 終わりに

想像以上に手軽に始められて、サクサク開発できるということがお分かりいただけたはずです。

本番もソケットモードで良いなら Dockerfile を用意してそれでデプロイできるようにするのがよいと思います（その場合 nodemon などは要らないですが）。ソケットモードについては以下の記事を参考にしてみてください：

https://qiita.com/seratch/items/1a460c08c3e245b56441

`manifest.json` の設定方法については、以下のページを参考にしてください。なお、YAML 形式だと CLI との連携では読み込まれないので、必ず JSON 形式で指定してください。

https://api.slack.com/reference/manifests

「Node.js や Python 以外では使えないのかな？」という疑問をお持ちの方もいると思います。すでに技術的には可能ではあるのですが、動作させるには・・・もう少し手間がかかります。これについてはまた別の記事で紹介できればと思います。

それでは！