Slack 次世代プラットフォーム機能を少しずつ試す - Hello World 編

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

[Slack の次世代プラットフォーム機能を少しずつ試しながら、ゆっくりと理解していくシリーズ](https://qiita.com/seratch/items/b1449132add003e61de6)を始めようと思います。「次世代プラットフォーム機能って何？」という方は、以下の記事で詳しく解説しましたので、まずはそちらをお読みください。

https://qiita.com/seratch/items/ecc16b845483c9d6f9e1

この記事では、最もシンプルな形で Hello World を試していきながら、このプラットフォームの基礎を理解していきましょう。

### 前提条件

初めて試される方は、[こちらの記事](https://qiita.com/seratch/items/ecc16b845483c9d6f9e1)の冒頭の注意点をお読みください。有料プランの Slack ワークスペースで、その管理者の方が機能を有効にしている必要があります。

それがすでに完了していれば、開発者の方は Slack CLI をインストールして、対象の Slack ワークスペースでの認証を完了する必要があります。詳しくは以下のページを参照ください。

https://api.slack.com/future/quickstart

この記事では、ここまでは完了している前提で進めます。

### ブランクプロジェクトを作成

新しいプロジェクトを始めるには Slack CLI の `slack create` を実行します。

```bash
$ slack create
? Select a template to build from:

> Hello World
  A simple workflow that sends a greeting

  Scaffolded project
  A solid foundational project that uses a Slack datastore

  Blank project
  A, well.. blank project

  To see all available samples, visit github.com/slack-samples.
```

実行すると「Hello World」というテンプレートが存在しています。これも良いサンプルプロジェクトなのですが、リンクトリガーを使って起動して、標準ファンクションの入力モーダルを起動、その入力値を使ってメッセージをチャンネルに投稿、とさまざまな要素が出てくる例となっています。

この記事では少しずつ理解していくために「Blank project」を選択して、ゼロからやっていきましょう。

プロジェクトが作成されたら、とりあえず `slack run` で起動してみましょう。これをすることで、このアプリの開発版がワークスペースにインストールされます。

```bash
$ cd dreamy-gazelle-453
$ slack run
? Choose a workspace  seratch  T03E94MJU
   App is not installed to this workspace

Updating dev app install for workspace "Acme Corp"

⚠️  Outgoing domains
   No allowed outgoing domains are configured
   If your function makes network requests, you will need to allow the outgoing domains
   Learn more about upcoming changes to outgoing domains: https://api.slack.com/future/changelog
✨  seratch of Acme Corp
Connected, awaiting events
```

`Connected, awaiting events` というメッセージが表示されて、そこで止まったらとりあえずは成功です。Ctrl + C で停止しても構いません。

次の手順からいよいよ設定を追加していきましょう。開発には [Deno プラグイン](https://marketplace.visualstudio.com/items?itemName=denoland.vscode-deno)をインストールした [VS Code](https://code.visualstudio.com/) で開発するのがおすすめです。

### Incoming Webhooks で Hello World してみよう

まずは Incoming Webhooks を使ってワークフローを起動してみましょう。手順は以下の通りです。

* `hello_world.ts` を新しく作成する
* チャンネル ID を入手して `hello_world.ts` を一行だけ書き換える
* ワークフローを `manifest.ts` に追加する
* `slack run` か `slack install` してワークフロー設定を反映する
* `slack triggers create --trigger-def ./hello_world.ts` でトリガーを作成する

となります。

より詳細な手順を以下にまとめます。

#### `hello_world.ts` を新しく作成する

新しく `hello_world.ts` というファイルを作って、以下の内容をそのまま貼り付けてください。

```typescript
// -------------------------
// ワークフロー定義
// -------------------------
import { DefineWorkflow, Schema } from "deno-slack-sdk/mod.ts";

// この定義オブジェクトを manifest.ts で参照するのを忘れずに！
export const workflow = DefineWorkflow({
  callback_id: "hello-world-workflow",
  title: "Hello World Workflow",
  input_parameters: {
    properties: {},
    required: [],
  },
});

// 標準ファンクションを使ってメッセージを投稿
// channel_id は Slack UI 上から入手してください
workflow.addStep(Schema.slack.functions.SendMessage, {
  channel_id: "C03E94MKS", // TODO: 変更する
  message: "Hello World!",
});

// -------------------------
// トリガー定義
// -------------------------
import { Trigger } from "deno-slack-api/types.ts";

// Incoming Webhooks によるトリガー
const trigger: Trigger<typeof workflow.definition> = {
  type: "webhook",
  name: "Hello World Trigger",
  // 上記のワークフローを参照する必要がある
  workflow: `#/workflows/${workflow.definition.callback_id}`,
  inputs: {},
};

// トリガーの作成には `slack triggers create --trigger-def [ファイルパス]` を実行する
// Trigger 形の定義オブジェクトを export default さえしていれば
// そのソースファイルを使用できる
export default trigger;
```

このファイルは、二つのことをやっています。

* このアプリに紐づくワークフローを新しく定義する
* そのワークフローを起動するためのトリガーを一つ定義する

ただ、これらをコードに書いただけではまだ設定は有効になりません。以下のステップで実際に使えるようにしていきます。

#### チャンネル ID を入手して `hello_world.ts` を一行だけ書き換える

先ほど作成した `hello_world.ts` のコードのうち、

>channel_id: "C03E94MKS", // TODO: 変更する

という部分を変更します。

**なお、この記事のサンプルの実行には、パブリックチャンネルを使ってください。**

channel_id は Slack の UI 上でチャンネル名をクリックして表示されるモーダルの最下部にコピペできるように表示されている C 始まりの文字列を設定してください。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19163/bf6df9a2-8ca7-6c43-578c-65debcfdbea9.png" width=300 />

あるいは、そのチャンネルの任意のメッセージのメニューから Copy link して `https://my.slack.com/archives/..` の次に表示されている階層をコピーする、でも OK です。

その文字列で上記のコードを書き換えてください。

#### ワークフローを manifest.ts に追加する

以下のように、定義したワークフローを Manifest オブジェクトに設定してください。

```typescript
import { Manifest } from "deno-slack-sdk/mod.ts";
// 先ほど作ったワークフローを import する
import { workflow as HelloWorld } from "./hello_world.ts";

export default Manifest({
  name: "dreamy-gazelle-453",
  description: "Hello World",
  icon: "assets/default_new_app_icon.png",
  functions: [],
  // 元々から配列だったここに import したワークフローを追加
  workflows: [HelloWorld],
  outgoingDomains: [],
  botScopes: ["commands", "chat:write", "chat:write.public"],
});
```

#### `slack run` か `slack install` してワークフロー設定を反映する

この状態で `slack run` コマンドを実行してください。これをすることで今作ったワークフローの設定内容が Slack のクラウド環境に反映されます。一旦 Ctrl + C で止めても大丈夫です。

・・というか、別のターミナルウィンドウを開いて、そこで `slack run` を起動しっぱなしでもよいです。

#### `slack triggers create --trigger-def ./hello_world.ts` でトリガーを作成する

上記ワークフローが反映されたので、そのワークフローのトリガーを作成できるようになります。もし上の手順をスキップするとワークフローが見つからないという旨のエラーになるので注意してください。

作成するときに選択肢が二つ出てくるかと思いますが、下の方の `(dev)` という表記がついている方を選択してください。

```bash
$ slack triggers create --trigger-def ./hello_world.ts
? Choose an app  [Use arrows to move, type to filter]
   seratch  T03E94MJU
   App is not installed to this workspace

>  seratch (dev)  T03E94MJU
   dreamy-gazelle-453 (dev) A04DHV08MPF
```

成功すると以下のような表示となります。

```bash
$ slack triggers create --trigger-def ./hello_world.ts
? Choose an app  seratch (dev)  T03E94MJU
   dreamy-gazelle-453 (dev) A04DHV08MPF

⚡ Trigger created
   Trigger ID:   Ft04DLR5XXXX
   Trigger Type: webhook
   Trigger Name: Hello World Trigger
   Webhook URL:  https://hooks.slack.com/triggers/T11111/22222/xxxxx
```

この `https://hooks.slack.com/triggers/T11111/22222/xxxxx` という URL を使うとワークフローが起動できるようになっています。以下の `curl` コマンドでリクエストを送ってみましょう。

```bash
curl -XPOST https://hooks.slack.com/triggers/T11111/22222/xxxxx
```

`{"ok":true}` と表示されれば成功です。Slack ワークスペースの指定したチャンネルを見てみてください。

以下のように「Hello World!」とメッセージが投稿されていれば成功です。おめでとうございます :tada: 

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19163/211e350d-b9e1-3551-026d-63dddeca25ad.png" width=300 />

### で、何が起きたのか？

とりあえず手順に従って手を動かしたら、ワークフローが動きました。これによって何が起きたのかを少し整理しておきましょう。

* `slack create` で `dreamy-gazelle-453` というアプリを作成した（この時点では雛形ができただけ）
* `slack run` コマンドで起動したら、選択した Slack ワークスペース内に開発版のアプリがインストールされた（ただし、Blank project なので、まだ何の機能も持たない）
* `hello_world.ts` と `manifest.ts` を書き換えて `slack run` を実行することで、Slack のホスティング環境に反映された（ただし、この時点では起動する手段がない）
* このワークフローを起動するためのトリガーを `slack triggers create` コマンドで作成した
* 作成したトリガーの Webhook URL にリクエストを送ったらワークフローが実行された

という流れでした。

ワークフローを実行したときに `slack run` で起動していなかった人も多かったのではないでしょうか？**それでも「Hello World!」というメッセージが送信されたはずです。**

それはなぜかというと、**ワークフローの実行エンジン、標準ファンクションの部分は手元のプロセスではなく、全てホスティング環境側で実行されているからです。** このワークフローは標準ファンクションの SendMessage しか使っていないため、ローカルから接続していなくても最後に反映された設定で動作し続けるというわけです。

次回の記事ではカスタムのファンクションを実装する例も紹介していきますが、それを開発版アプリで実行する場合は `slack run` で手元のプロセスも起動している必要があります。

### リンクトリガーでワークフローを実行する

もう一つトリガーを作ってみましょう。次はリンクトリガーと呼ばれる、**Slack 内でユーザーがクリックして起動するタイプのトリガー**です。

先ほどのコードを以下のように書き換えます。

```typescript
// -------------------------
// ワークフロー定義
// -------------------------
import { DefineWorkflow, Schema } from "deno-slack-sdk/mod.ts";

// この定義オブジェクトを manifest.ts で参照するのを忘れずに！
export const workflow = DefineWorkflow({
  callback_id: "hello-world-workflow",
  title: "Hello World Workflow",
  input_parameters: {
    properties: {
      // リンクトリガーから受け取るチャンネル ID
      channel_id: { type: Schema.slack.types.channel_id },
    },
    required: ["channel_id"],
  },
});

// 標準ファンクションを使ってメッセージを投稿
workflow.addStep(Schema.slack.functions.SendMessage, {
  // ワークフロー全体のトリガーからの入力を指定
  // リンクトリガーの場合はそれを起動したチャンネルの ID が渡される
  channel_id: workflow.inputs.channel_id,
  message: "Hello World!",
});

// -------------------------
// トリガー定義
// -------------------------
import { Trigger } from "deno-slack-api/types.ts";

// リンクトリガー
const trigger: Trigger<typeof workflow.definition> = {
  type: "shortcut",
  name: "Hello World Trigger",
  workflow: `#/workflows/${workflow.definition.callback_id}`,
  inputs: {
    // クリックしたチャンネルの ID が設定される
    channel_id: { value: "{{data.channel_id}}" },
  },
};

// トリガーの作成には `slack triggers create --trigger-def [ファイルパス]` を実行する
// Trigger 形の定義オブジェクトを export default さえしていれば
// そのソースファイルを使用できる
export default trigger;
```

先ほどと同様に `slack run` でホスティング環境に設定を反映します（起動しっぱなしでも OK です）。

そして `slack triggers create --trigger-def hello_world.ts` で、新しくリンクトリガーを作成しましょう。

```bash
$ slack triggers create --trigger-def hello_world.ts
? Choose an app  seratch (dev)  T03E94MJU
   dreamy-gazelle-453 (dev) A04DHV08MPF

⚡ Trigger created
   Trigger ID:   Ft04DEBXXXX
   Trigger Type: shortcut
   Trigger Name: Hello World Trigger
   URL: https://slack.com/shortcuts/Ft04DEBXXXXX/YYYY
```

この `https://slack.com/shortcuts/Ft04DEBXXXXX/YYYY` という URL をワークフローを起動したいチャンネルにメッセージで投稿するか、チャンネルのブックマークに追加してください。そこから起動すると同様に Hello World! というメッセージが投稿されるはずです。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19163/e736f119-4eac-4a5b-a3be-aaf5ec89615e.png" width=500 />

### まとめ

この記事では以下のことをハンズオンで学びました。

* Blank project からワークフローを作成して実行できるようにする
* Incoming Webhooks トリガーを利用する
* リンクトリガーを利用する

できるだけシンプルにするために無理やり `hello_world.ts` と `manifest.ts` という二つのファイルだけを触る手順で説明しましたが、自前のファンクションを定義したり、トリガーやワークフローを複数定義するようになると、このやり方ではとっちらかってしまいます。

なので、`slack create` で生成される他のテンプレートが行っているように、以下のディレクトリ構成で整理して、相対パスでファンクションやワークフローを import することを推奨しています。

```bash
$ tree
.
├── deno.jsonc
├── functions
│   ├── sample_function.ts
│   └── sample_function_test.ts
├── import_map.json
├── manifest.ts
├── slack.json
├── triggers
│   └── sample_trigger.ts
└── workflows
    └── sample_workflow.ts
```

実際のプロジェクトでは、この方式に素直に従うようにしてみてください。

このシリーズはしばらく続きます。次回からは、自前のファンクションを開発したり、そのテストを書いたり、他の機能も少しずつ試していきましょう。

早くもっと完全なアプリの例を見たい！という方は、以下の記事からリンクされているものを色々見てみてください。

https://qiita.com/seratch/items/621f7b29e897f17e82a6

それでは！