Slack Bolt for Pythonを使ってのbot構築。ローカル開発からHerokuデプロイまで

Slackは多用しているものの、botkit以来すっかりbot開発している時間がなかったため時間が空いてしまったのですが、python使って作ろう！と決めて初学者が躓く最初の最初についての解説。

Boltはチュートリアルもしっかりしているし、それに沿ってやっていけばやれるはずではあるのですが、そもそもの書き方がよくわかっていないとかbotってなんだっけ？という場合にお作法が分からずに躓く人がいるのではないでしょうか。まぁ私のことなのですけど。ということで、デプロイして動く所まではたどり着いたので初学者向けの解説です。あんまり日本語コンテンツがないのもハードルを上げていますね。

##前提条件
Python3.6以上が必要なので、そちらのインストールは済ませておきましょう。
当方の環境はMacです。

##セットアップ手順
基本的にはこちらにかかれている通りです。シンプル。
[Bolt for python](https://github.com/slackapi/bolt-python)
[チュートリアルはこちら](https://api.slack.com/start/building/bolt-python)

##slackでアプリを作る
チュートリアルのGetting startedからスタートして下さい。
##スコープを設定する
スコープの設定が必要です。アプリのサイドバーにあるOAuth & Permissionsを開いてScopes欄から設定しましょう。
何がしたいのかによって必要なスコープが変わるのですが、ひとまずDMでのやり取りで開発を進めたいということであれば
im:read
im:write
の２つを入れておきましょう。
chat:write
chat:write.public
を入れておけば公開チャネルに書き込みが出来ます。
files:write
を入れておくと、ファイルのアップロードが可能になります。
app_mentions:read
を入れておくと、メンションメッセージが読み取れます。
スコープの設定をしておかないと、アクションをした時にデータが降ってこないので対応が取れないということになります。

##ひとまずアプリをインストール
アプリ画面のInstall Appにいってインストールしましょう。するとbotのTokenが取得できます。
![Slack_API__Applications___Leave_a_Nest_Co__ltd__Slack.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22294/78240597-01d2-ec17-6400-6198212d355c.png)
OAuth & Permissions画面にもTokenは表示されています。

## アプリのホームを有効化
これはやってもやらなくてもどちらでも良いです。お好みで。

ここまで来たらSlackの画面を離れて開発環境を開きましょう。
##ターミナルからpythonの仮想環境を作ってboltをインストールする

```
# Python 3.6+ required
python -m venv .venv
source .venv/bin/activate

pip install -U pip
pip install slack_bolt
```
適当なフォルダに移動してpythonの仮想環境を作りましょう。アクティベートしたらboltをインストールします。

##環境変数を設定する
仮想環境内で使う環境変数を設定しておきましょう。

```
export SLACK_BOT_TOKEN=xoxb-your-token
export SLACK_SIGNING_SECRET=your-signing-secret
```
SLACK_BOT_TOKENはインストール画面もしくはOAuth & Permissions画面に表示されています。
SLACK_SIGNING_SECRETは一番上のBasic InformationのApp Credentials画面に伏せ字になって表示されています。

##ngrokでローカルとの通信を確立する
[ngrok](https://ngrok.com/)は、ローカルPCの開発環境とSlack間の通信を接続してくれます。以前開発した時はそんなのなかったのでherokuにいちいちアップロードして確認していましたが、便利な時代がやってきました。素晴らしいアプリケーションです。
Slackbotの開発はその性質上、Slackで行われたアクションをBolt側で受け取る必要があります。ローカルPCで開発を行う場合、これを受け取るのが面倒です。そのためにngrokを使います。
ngrokをインストールして立ち上げておくと、接続用のURLをngrokが発行してくれます。こんな感じです。
まずはngrokを立ち上げます。

```
ngrok http 3000
```
するとこんな感じでURLが発行されます。
![dev_—_ngrok_http_3000_—_192×57_と_george_—_dataloader_command_—_java_◂_dataloader_command_—_80×24_と_Slack___editors___Leave_a_Nest_Co__ltd_.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22294/f15440e1-7d77-2544-4dc3-39e1bdfbf0f6.png)
ここに書かれているURLをSlackアプリのエンドポイントに設定しておくと、localhostの3000ポートに通信が届きます。素晴らしい技術！！

##boltを立ち上げる
app.pyというファイルを作って以下のコードを貼り付けます。

```
import os
# Use the package we installed
from slack_bolt import App

# Initializes your app with your bot token and signing secret
app = App(
    token=os.environ.get("SLACK_BOT_TOKEN"),
    signing_secret=os.environ.get("SLACK_SIGNING_SECRET")
)

# Start your app
if __name__ == "__main__":
    app.start(port=int(os.environ.get("PORT", 3000)))
```

保存したらアプリを立ち上げましょう。

```
python3 app.py
```

これでboltは動きます。なんて簡単なんだ！

##Event Subscriptionsを設定する
botにイベントを読み取らせる場合に必要な権限です。まずはEnable EventsをONにしましょう。

![Slack_API__Applications___Leave_a_Nest_Co__ltd__Slack.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22294/1b9f2a32-41ed-8977-a486-fe5ea2536c15.png)

すると、Request URLという欄が出てきますので、こちらに先程取得したngrokのURLを入れるという形になります。
先程app.pyを立ち上げてあるので3000番ポートで受付が可能です。URLを入れればVerifiedになるでしょう。

##追加機能を付与していく
私がアホだったのですが、処理コードを書く場所は、以下の部分です。



```
import os
# Use the package we installed
from slack_bolt import App

# Initializes your app with your bot token and signing secret
app = App(
    token=os.environ.get("SLACK_BOT_TOKEN"),
    signing_secret=os.environ.get("SLACK_SIGNING_SECRET")
)

"""
ここに書きます
"""


# Start your app
if __name__ == "__main__":
    app.start(port=int(os.environ.get("PORT", 3000)))
```

全部の処理を書いて、最後にアプリがスタートする形なんですよね。
最初、app.startの下に処理を書いて全く処理されずに焦りました。そりゃそうだ。


ということで、[チュートリアルのResponding to events](https://api.slack.com/start/building/bolt-python#listening)に書いてあるコードをマージしたapp.pyはこちらです。

```app.py
import os
# Use the package we installed
from slack_bolt import App

# Initializes your app with your bot token and signing secret
app = App(
    token=os.environ.get("SLACK_BOT_TOKEN"),
    signing_secret=os.environ.get("SLACK_SIGNING_SECRET")
)

@app.event("app_home_opened")
def update_home_tab(client, event, logger):
  try:
    # views.publish is the method that your app uses to push a view to the Home tab
    client.views_publish(
      # the user that opened your app's app home
      user_id=event["user"],
      # the view object that appears in the app home
      view={
        "type": "home",
        "callback_id": "home_view",

        # body of the view
        "blocks": [
          {
            "type": "section",
            "text": {
              "type": "mrkdwn",
              "text": "*Welcome to your _App's Home_* :tada:"
            }
          },
          {
            "type": "divider"
          },
          {
            "type": "section",
            "text": {
              "type": "mrkdwn",
              "text": "This button won't do much for now but you can set up a listener for it using the `actions()` method and passing its unique `action_id`. See an example in the `examples` folder within your Bolt app."
            }
          },
          {
            "type": "actions",
            "elements": [
              {
                "type": "button",
                "text": {
                  "type": "plain_text",
                  "text": "Click me!"
                }
              }
            ]
          }
        ]
      }
    )
  
  except Exception as e:
    logger.error(f"Error opening modal: {e}")

# Start your app
if __name__ == "__main__":
    app.start(port=int(os.environ.get("PORT", 3000)))

```

保存したら、一度走ってるapp.py を⌘cでキャンセルしてもう一度走らせて下さい。

```
python3 app.py
```

これで、アプリのホーム画面を開くとこんな感じになっているはずです。
![Slack___Nest_Analytics___Leave_a_Nest_Co__ltd_.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22294/05437148-4acd-9f6a-36e7-0904a61b8c37.png)

ということでひとまずは出来ました！あとは処理を諸々書いていって、ご所望のbotを作りましょう。

##Herokuにデプロイする
いつまでもローカル環境で動かしておくという訳にはいかないはずです。本番運用が視野に入ってきたら、どこかのサーバにデプロイしてbotを動かし続けましょう。今回はHerokuにデプロイします。
[Heroku用のサンプルはこちらにあります](https://github.com/slackapi/bolt-python/tree/main/examples/heroku)。

先程作ったapp.pyをコピーしてmain.pyファイルを作りましょう。
Procfileとrequirements.txtが必須です。フォルダに追加して下さい。
Procfileに以下の記載が確認できると思います

```Procfile
web: gunicorn --bind :$PORT --workers 1 --threads 2 --timeout 0 main:flask_app
```

main:flask_appのmainがファイル名の指定。他の名前が良い場合、例えばapp.pyを使いたい場合は、app:flask_app と記載すれば動きます。

こだわりがない場合はそのまま入れておけば良いです。
開発中に色んなものをpipでインストールして使うと思うのですが、その場合はrequrements.txtに追記する必要があります。
[私の場合はこちらを参考にして](https://takazawa.github.io/hobby/pipreqs/)作りました。

フォルダに
main.py
Procfile
requrements.txt
が入ったら、以下のようにコマンドを打っていきます。
アプリ名部分には小文字でアプリ名を入力して作って下さい。そうすると、.gitのURLが返ってきます。

```
# heroku login
# heroku create アプリ名
# git remote add heroku https://git.heroku.com/xxx.git

# export SLACK_BOT_TOKEN=xxx
# export SLACK_SIGNING_SECRET=xxx
# heroku config:set SLACK_BOT_TOKEN=$SLACK_BOT_TOKEN
# heroku config:set SLACK_SIGNING_SECRET=$SLACK_SIGNING_SECRET
# git checkout -b main
# git add .
# git commit -m'Initial commit for my awesome Slack app'
# git push heroku main
```

pushすることでHeroku上にデプロイされます。
最後にSlackのEvent Subscriptions画面を表示して、ngrokのURLをHerokuのURLに変更しましょう。
変更後に無事に動けばデプロイ完了です。おめでとう！

##本番環境を見据えた開発環境の設定
HerokuにはflaskというWebアプリケーションフレームワークを使って稼働させています。ローカル環境もそれに合わせてしまいましょう。
[flask用のexampleはこちらです](https://github.com/seratch/slack_learning_app_ja/blob/2c24fb4f5e17b958056d2d1dc950bfd53bbaa98e/flask_app.py#L35-L38)


```main.py
import logging
import os

from slack_bolt import App

from app.listeners import register_listeners

# デフォルトではローカルファイルに state の情報やインストール情報を書きます
# 必要に応じて別の実装に差し替えてください（Amazon S3, RDB に対応しています）
app = App()
register_listeners(app)

from flask import Flask, request
from slack_bolt.adapter.flask import SlackRequestHandler

flask_app = Flask(__name__)
handler = SlackRequestHandler(app)


"""
ここに処理書く
"""


# Only for local debug
if __name__ == "__main__":
    logging.basicConfig(format="%(asctime)s %(message)s", level=logging.DEBUG)
    flask_app.run(debug=True, host="0.0.0.0", port=int(os.environ.get("PORT", 3000)))
```

こうやって書いておくことで、python main.py として立ち上げておくとflaskが立ち上がります。
先程までのように、処理を書き換えた場合に一度pythonをquiteしてまた立ち上げ直す必要がなくなるので作業効率が上がります。オススメ。(実行前にpip install flaskするのを忘れずに)
Herokuログイン時にはif __name__ == "__main__"は呼ばれずProcfileで指定したGunicornが処理してくれます。


##小技
slackからのメッセージは全てjsonで返ってきます。
import json
をしてから以下のようにすると、ターミナルに整形されたJSONが表示されて便利です。

``` botメンション時にjsonを整形して出力する.py
def handle_mention(body, say, logger):
    print(json.dumps(body, indent=2))
```
*注意：メンション時なのでEvent Subscriptionsのスコープにapp_mentionをつけて下さい。
もちろん他の応答時でも同様にすれば見やすくなると思います。