【Bolt for Python】ソケットモードでSlack Appを作ってみた

# はじめに
Boltフレームワークを使って、Slackアプリを作成しました。
Slackワークフローに独自のステップを追加できるようになっているというのを知りまして、
「Boltフレームワークを使うと超簡単だよ！」と公式ドキュメントに書かれていましたので、それを試してみます。

Bolt公式で日本語ドキュメントがあり、かなり詳しく書かれているので、基本的にはこれに沿って作るだけです。

https://slack.dev/bolt-python/ja-jp/tutorial/getting-started

また、アプリの機能としては、以前書いた記事↓に似ていますが、
「Slackのワークフローでフォームに入力させ、その値を使ってClickUpでタスクを登録する」というものにします。
以前書いた記事では、ワークフローごとにGASを作る必要があってかなり面倒だった（その分、データ構造を好きにできるという利点はありますが）ので、
Slackのワークフローを作るだけで、ClickUpタスク作成の設定ができたら便利じゃん？という発想です。

https://qiita.com/sallytollies/items/288938c52519e65a9c8b

# 手順
## 1.Bolt入門ガイドに沿ってアプリを作成
[Bolt入門ガイド](https://slack.dev/bolt-python/ja-jp/tutorial/getting-started)の手順に従って、アプリを新規作成。
本当にすぐできます。私はSlackApp側の準備は前もってやっておいたので、本当に一瞬でできました。

## 2.ワークフローステップの箇所を記述
今回はサクッとやりたいので、1ファイルに全部の機能をまとめて記述してしまうことにします。

公式ドキュメントのワークフローステップサンプルが元々タスク登録をやるようなステップだったので、ほとんどそのままです。
clickupのlistidを可変にできるようにしたかったので、そこだけ追加しています。

```python:app.py    
# WorkflowStepの中身

def edit(ack, step, configure):
    ack()

    blocks = [
        {
            "type": "input",
            "block_id": "task_name_input",
            "element": {
                "type": "plain_text_input",
                "action_id": "name",
                "placeholder": {"type": "plain_text", "text": "前ステップのフォームで入力させる変数を設定。"},
            },
            "label": {"type": "plain_text", "text": "ClickUpのTask name"},
        },
        {
            "type": "input",
            "block_id": "task_description_input",
            "element": {
                "type": "plain_text_input",
                "action_id": "description",
                "placeholder": {"type": "plain_text", "text": "前ステップのフォームで入力させる変数を設定。"},
            },
            "label": {"type": "plain_text", "text": "ClickUpのTask description"},
        },
        {
            "type": "input",
            "block_id": "listid_input",
            "element": {
                "type": "plain_text_input",
                "action_id": "listid",
                "placeholder": {"type": "plain_text", "text": "タスクを登録させたいClickUpのリストIDを入力。（8ケタの数字）"},
            },
            "label": {"type": "plain_text", "text": "タスクを登録させたいClickUpのList ID"},
        },
    ]
    configure(blocks=blocks)


def save(ack, view, update):
    ack()

    values = view["state"]["values"]
    task_name = values["task_name_input"]["name"]
    task_description = values["task_description_input"]["description"]
    clickup_listid= values['listid_input']['listid']

    inputs = {
        "task_name": {"value": task_name["value"]},
        "task_description": {"value": task_description["value"]},
        "clickup_listid": {"value": clickup_listid["value"]}
    }
    
    outputs = [
        {
            "type": "text",
            "name": "task_name",
            "label": "Task name",
        },
        {
            "type": "text",
            "name": "task_description",
            "label": "Task description",
        },
        {
            "type": "text",
            "name": "clickup_listid",
            "label": "Clickup ListID",
        }
    ]
    update(inputs=inputs, outputs=outputs)


def execute(step, complete, fail):
    inputs = step["inputs"]
    # すべての処理が成功した場合
    outputs = {
        "task_name": inputs["task_name"]["value"],
        "task_description": inputs["task_description"]["value"],
        "clickup_listid": inputs["clickup_listid"]["value"],
    }
    complete(outputs=outputs)

    # 失敗した処理がある場合
    error = {"message": "Just testing step failure!"}
    fail(error=error)

# WorkflowStep の新しいインスタンスを作成する
ws = WorkflowStep(
    callback_id="clickup_task_create",
    edit=edit,
    save=save,
    execute=execute,
)
# ワークフローステップを渡してリスナーを設定する
app.step(ws)
```

## 3.API叩くところを記述する

ClickUpAPIの実行をexcuteのところに差し込んでみます。

outputs オブジェクトに設定しておくと、ワークフローの後続のステップで使用できる変数になるようす。
今回の例のままではあんまり利用イメージ湧かないですが、一応、そのまま設定しておきます。

```python:app.py
def execute(step, complete, fail):
    inputs = step["inputs"]
    # すべての処理が成功した場合
    # clickup連携定義
    url = ' https://api.clickup.com/api/v2/list/' + inputs["clickup_listid"]["value"] + '/task'
    headers = {'content-type': 'application/json', 'Authorization': clickuptoken }
    payload = {"name": inputs["task_name"]["value"],
               "description": inputs["task_description"]["value"]}

    requests.post(url, data=json.dumps(payload), headers=headers,verify=False)
```

## 4.実行

以下のコマンドを叩いて、実行してみます。

```terminal
python3 app.py
```

Bolt is workingを確認して、Slackを見てみると…

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/666457/04a01b51-f15c-e3b2-67b9-c231ab1152ed.png)


ワークフローステップが選べるようになっています！
これを使ってワークフローを作り、入力してみると、無事にClickUpに登録されました。

ソケットモード、すぐに実環境で確認できて、細かいこと考えなくていいので本当に楽です。

# デプロイ
引き続き、細かいことは考えたくなかったので、Herokuでデプロイすることにします。
Socketmodeに対応してくれているので、普通にデプロイすれば動くようです。ラクです。

## Herokuでdeploy

1. 作成したアプリをGitHubリポジトリに上げる
1. Herokuでのbuild用にファイルを追加
1. HerokuでNew appを作成
1. GitHubリポジトリをConnectする
1. Settings->Config Varsで`SLACK_BOT_TOKEN`などの変数を設定
1. Build packsでpythonを指定
1. デプロイ！
1. Resourcesでサーバを起動するのを忘れずに（一度忘れてしまって、アレ！？となりました。注意）

### Herokuでのbuild用にファイルを追加

軽くつまづきました。メモ。

Procfileに、workerであることを明示してあげつつ、起動時にアプリが実行するコマンドを記載。

```text:Procfile
worker: python3 app.py
```

requirements.txtに外部ライブラリを明示。

```text:requirements.txt
slack_bolt
requests
```

以上です。

とにかくお手軽にできるので、SlackAppのハードルが下がりますね。
WorkflowStepを作れるのも色々使い道がありそう。