2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

#Slack Bolt for python で複数Slackアプリを1つのBoltでホスティングしてコスト削減してみた

Last updated at Posted at 2023-04-24

これまで社内用アプリ、社外でもインストールできる公開用アプリと色々なアプリをSlackが提供するフレームワークである、Bolt for Pythonおよび、Pythonフレームワークの一つであるFlaskを使って作ってきました。
社内的な事情で、というかboltが出てきた当初はHerokuに簡単にデプロイする仕組みがあったりしたので、そのままHeroku上にアプリを走らせているのですが、アプリによっては活動時間が大したことなかったりしてちょっともったいないよなという状態になったりしていました。
そもそも負荷が高くないようなアプリであれば、一つのHeroku環境で処理してしまいたいなと思ったことはありませんか。僕はありました。
ということで、いつものSlack瀬良さんにヒアリングしてみたり、そもそもFlaskの書き方がよくわかってなかったなということもあってそのあたりのコードの補完をChatGPTにお願いするなどして完成させたので共有していきたいと思います。

まず、パスの定義の仕方です

bolt.py
@bolt.route("/<app_id>/slack/events", methods=["POST"])
def slack_events(app_id):
    if app_id not in handlers:
        return "Invalid app ID", 404
    return handlers[app_id].handle(request)

@bolt.route("/<app_id>/slack/install", methods=["GET"])
def install(app_id):
    if app_id not in handlers:
        return "Invalid app ID", 404
    return handlers[app_id].handle(request)

@bolt.route("/<app_id>/slack/oauth_redirect", methods=["GET"])
def oauth_redirect(app_id):
    if app_id not in handlers:
        return "Invalid app ID", 404
    return handlers[app_id].handle(request)

こんな形にしておきます。
通常であれば
/slack/events
でイベントAPIを受け取るのですが、これを
/app_id/slack/events
というパスで受け取るようにします。

app_id部分は任意の文字列で、Slackアプリのマニフェスト部分をそのような形式にしておきます。

app_manifest.json
    "settings": {
        "event_subscriptions": {
            "request_url": "https://任意のドメイン/app_id(任意のパス)/slack/events",

こうやってマニフェストのrequest_urlのドメインは同じ、パスをアプリ毎に変更するという形でどのアプリからの入力なのかを判定していく事になります。

同様に、redirect_urlsも変更して下さい

app_manifest.json
    "oauth_config": {
        "redirect_urls": [
            "https://任意のドメイン/app_id(任意のパス)/slack/oauth_redirect"
        ],

その他、パス設定がある場合は、同じように変更しておきます。

secret関連の書き方

環境変数を変数に格納しておきます

bolt.py
client_id_app1, \
client_secret_app1, \
signing_secret_appp1, \
client_id_app2, \
client_secret_app2, \
signing_secret_app2, \
      = (
    os.environ["SLACK_CLIENT_ID_APP1"],
    os.environ["SLACK_CLIENT_SECRET_APP1"],
    os.environ["SLACK_SIGNING_SECRET_APP1"],
    os.environ["SLACK_CLIENT_ID_APP2"],
    os.environ["SLACK_CLIENT_SECRET_APP2"],
    os.environ["SLACK_SIGNING_SECRET_APP2"],
)

installation_store_app1 = SQLAlchemyInstallationStore(
    client_id=client_id_app1, engine=engine, logger=logger,
)
installation_store_app2 = SQLAlchemyInstallationStore(
    client_id=client_id_app2, engine=engine, logger=logger,
)
oauth_state_store = SQLAlchemyOAuthStateStore(
    expiration_seconds=120, engine=engine, logger=logger,
)

パスはリスト化しておきましょう

後で使います

bolt.py
app_path_list = [
    "app1",
    "app2",
]

APPの生成

install_pathとredirect_uri_pathを設定すると、カスタムパスとして使えるようになります。

bolt.py
def get_bolt_app(app_path):
    install_path = "/" + app_path + "/slack/install"
    redirect_uri_path = "/" + app_path + "/slack/oauth_redirect"
    signing_secret = globals()[f"signing_secret_{app_path}"]
    installation_store = globals()[f"installation_store_{app_path}"]
    client_id = globals()[f"client_id_{app_path}"]
    client_secret = globals()[f"client_secret_{app_path}"]
    return App(
        logger=logger,
        signing_secret=signing_secret,
        installation_store=installation_store,
        client=WebClient(retry_handlers = retry_handlers),
        oauth_settings=OAuthSettings(
            client_id=client_id,
            client_secret=client_secret,
            state_store=oauth_state_store,
            scopes=os.environ.get("SLACK_SCOPES"),
            user_scopes=os.environ.get("SLACK_USER_SCOPES"),
            callback_options=callback_options,
            install_path=install_path,
            redirect_uri_path=redirect_uri_path
        ),
    )

apps = []
for path in app_path_list:
    apps.append(get_bolt_app(path))

各種イベントの呼び出し方

bolt.py

def handle_message_events(client, body, event, logger, context):
    # ここにメッセージイベントの処理を書く

handlers_list = []
for app in apps:
    handler = SlackRequestHandler(app) #ハンドラーの定義をする
    handlers_list.append(handler)
    app.event("message")(handle_message_events)
    #ここからしたにすべてのイベントを書き連ねる

handlerの渡し方

bolt.py
handlers = {}
app_count = 0
for path in app_path_list:
    handlers[path] = handlers_list[app_count]
    app_count = app_count + 1

ここで書いたhandlersを最初に書いたパスの定義部分で参照しています

まとめ

こんな形にしておけば、アプリを追加するときは以下の3つをやるだけで追加が可能です

  • SECRET関連情報を追加する
  • installation_store の設定を追加する
  • app_path_listにパスを追加する

ということで、複数のSlackアプリを、一つのBoltアプリでホスティングする為の書き方でした。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?