slack_boltを使ってSocket Modeで通信するslack botを作る(2021.7.11)

Summary

ユーザーの投稿やコマンドに反応するSlack botを作成した。
Socket Modeを使うと、外部のpublic HTTP endpointを契約することなく、slack_boltモジュールを用いて記述したPythonのAppでの通信を実現できる。

webには古い情報が多く、古いやり方では最新のslack APIでは動作しなくなっている。日本語の情報も結構あって使いやすそうに見えたslackbotモジュールは2019年12月のSlack APIの仕様変更でちゃんと動作しない。Slack APIのTutorialsのリンクにslackclientというのが紹介されていたが、実際にはそちらも開発を終了していて、現在はslack_sdkというのが後継のようだが、低レベルAPIっぽい。slack_boltというのがslackbotに近い感じ。
実際にはslack_boltはslack_sdkを呼び出すラッパーとして実装されているようだ。
ということで、bolt for pythonを用いた実装にチャレンジ!

AppとSlackサーバとの通信

2019年12月にSlackの通信仕様が変更され、現在ではSlackからHTTPSPOSTメッセージを受け取れるPublic URLを持ったサーバがBot appとの間を取り持つ必要があるようだ。このモードではサーバを契約する必要があるのと、HTTPサーバが公開されていないといけないのがやりにくい。 そのような懸念がある場合に、boltでも使えるSocket Modeというのがあるらしい。

まとめると、botの実体のクライアントAppがslackから通知を受け取るためには、Slack Events APIを使う接続方法と、Socket Modeを使う接続方法の2つが用意されている。本検討では、後者のSocket Modeでの実装を試みた。

Appの設定

slack apiのYour Appsページでslack appの新規作成を行い、必要な設定をしてtokenなどの情報を入手する必要がある。

手順は以下のとおり

  1. ボットを設置したいSlack Workspaceとそのchannelを作成する
  2. https://api.slack.com/にWorkspaceのアカウントでログイン
  3. https://api.slack.com/appsで「Create New App」
  4. tokenの生成(メモしておく)やmanifestoの設定(users.readも設定)をしておく
    (Socket Modeの許可とApp-level tokenの生成も忘れずに)
  5. Slack Workspaceを指定してAppをインストール
  6. tokenの再発行や設定の追加は「OAuth & Permissions」の「scopes」などから行う
  7. pythonからAppを起動

1. Slack Appの新規作成

https://api.slack.com/appsで「Create New App」をクリック。

Create New App

From an app manifestを選択して次に進む。

Create New App

Workspaceを選択
Create New App

新規作成でManifestをコピー&ペーストするだけで既存アプリの基本設定を引き継ぐことができる。
Create New App

とりあえず使いそうなものは全部ONにしたManifestの例(manifest.yml)はこちら
_metadata:
  major_version: 1
  minor_version: 1
display_information:
  name: HelloBot2000
  description: An example app
  background_color: "#da3a79"
  long_description: An example app that can display actionable notifications in
    Slack. App contains all the necessary scopes to find a channel for
    publishing, and to publish a message. Enables Interactivity features to
    handle use of the interactive elements of the notification. Used with
    tutorials on api.slack.com/tutorials.
features:
  app_home:
    home_tab_enabled: false
    messages_tab_enabled: true
    messages_tab_read_only_enabled: true
  bot_user:
    display_name: HelloBot2000
    always_online: true
  shortcuts:
    - name: HelloBot2000-shortcut
      type: global
      callback_id: HelloBot2000-shortcut
      description: HelloBot2000のグローバルショートカット
oauth_config:
  redirect_urls:
    - https://example.com/slack/auth
  scopes:
    bot:
      - app_mentions:read
      - channels:history
      - channels:read
      - chat:write
      - chat:write.customize
      - commands
      - files:write
      - groups:history
      - im:history
      - im:read
      - im:write
      - reactions:read
      - reactions:write
      - users:read
      - users:write
      - files:read
settings:
  event_subscriptions:
    bot_events:
      - app_mention
      - message.channels
      - message.groups
      - message.im
      - reaction_added
  interactivity:
    is_enabled: true
    request_url: https://example.com/slack/message_action
  org_deploy_enabled: false
  socket_mode_enabled: true

動作中のAppの設定をExportしたManifesto.ymlをまるごとコピペしても、なんだか怒られる。

Set Socket Mode

赤丸に×がついているエラー部分をみながら適当に解消して、あとは設定ページから変更する。

Set Socket Mode

2. Socket Modeの設定

Connect using Socket ModeのトグルスイッチをONにする。

Set Socket Mode

Interactivity & ShortcutsもONになる

Set Socket Mode

Shortcutsイベントも受け取るならON(Manifestで最初に指定しておけば自動で設定される)

Set Socket Mode

Botにイベントに応じた動作をさせるならイベントの受信を有効にする必要がある

Set Socket Mode

イベントのトグルスイッチをONにしたところ

Set Socket Mode

App IDやSecretはBasic Informationで確認できる

Set Socket Mode

Bot User OAuth TokenはInstall Appまたは

Set Socket Mode

OAuth & Permissionsで確認可能

Set Socket Mode

Manifestで指定したScopeを編集できる

Set Socket Mode

Shortcutsの設定確認

Set Socket Mode

Restrict API Token UsageにAllowed IP Address Rangesを設定すると、Socket Modeで通信を行うAppの送信元IPアドレスを制限できる。

Set Socket Mode

Botの設定

BotをWorkspaceにinstallする

Python環境の構築

pyenvとvirtualenvでバージョンの設定を行う

専用のディレクトリを掘って、そこにpyenvで環境を作りlocalに設定する。

$ mkdir test_bolt
$ cd test_bolt
$ pyenv versions
$ pyenv virtualenv 3.9.4 3.9.4_test_bolt
$ pyenv local 3.9.4_test_bolt

slack_boltのinstall

依存関係によりslack_boltをpip installすると、slack_sdkもinstallされるようだ。

$ python -m pip install --upgrade pip
$ pip install slack_bolt

Tokenの設定

Signing Secretと Signing Secretは、Your Appからbotを選択し、Basic Informationのページに、App IDやClient IDなどの情報と一緒に表示されている。Showボタンを押して表示する。 Bot User OAuth TokenはYour Appの左に表示されているSettingsのメニューからInstall AppのリンクをクリックするとInstall App Settingsで表示できる。

たとえば、以下のようなファイルにtokenを設定しておく。 slack_tokens.sh

#!/bin/zsh

export SLACK_SIGNING_SECRET='<your-signing-secret>'
export SLACK_BOT_TOKEN='xoxb-<your-bot-token>'
export SLACK_APP_TOKEN='xapp-<your-app-token>'

以下を実行すると当該shellでtokenがexportされている。

chmod a+x ./slack_tokens.sh
source ./slack_tokens.sh
export -p

Simple Socket Mode App

Slack Workspaceのchannelに常駐して、イベントに反応するbotのサンプルを作成した。 @app.event()や@app.message()でイベントをフックし、ハンドラを起動する。

@app.message()と@app.event(“message”)は、どちらか先に該当した方が実行され、それ以降のハンドラは無視されます。なので、@app.event(“message”)より後ろに@app.message()を書いても実行されないようだ。

@app.event(“app_mention”)は、@app.message()や@app.event(“message”)があっても、それとは独立のイベントとして処理される。

実行例はこちら
results

上記の処理をしたプログラムはこちら
(bolt_simpleSocketModeApp.py)

プログラムの詳細
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# =======================================================
#  slack_boltでpythonからslackにメッセージを返信
#
#  bolt_simpleSocketModeApp.py
#  coded by Noboru Harada (noboru@ieee.org)
#
#  Changes:
#  2021/07/11: First version
# =======================================================

import logging
import os
from slack_sdk import WebClient
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"])

# respond mentions to this bot
@app.event("app_mention")
def handle_mention_events(body, say, logger):
    print("### BODY ### -----------------------------------")
    print(body)
    print("### SAY ### -----------------------------------")
    print(say)
    print("### LOGGER ### -----------------------------------")
    print(logger)
    print("### Send say ### -----------------------------------")
    say(f"In 'app_mention'\nこんにちは <@{body['event']['user']}> さん!")
    print("### end handler ### -----------------------------------")

@app.message("こんにちは")
def handle_messge(message, say):
    print("### MESSAGE ### -----------------------------------")
    print(message)
    print("### SAY ### -----------------------------------")
    print(say)
    print("### Send say ### -----------------------------------")
    say(f"In 'こんにちは' こんにちは <@{message['user']}> さん!")
    print("### end handler ### -----------------------------------")

# deafault handler for 'message' event
@app.event("message")
def handle_messge_events(body, say, logger):
    print("### BODY ### -----------------------------------")
    print(body)
    print("### SAY ### -----------------------------------")
    print(say)
    print("### LOGGER ### -----------------------------------")
    print(logger)
    print("### Send say ### -----------------------------------")
    say(f"In 'message'\nこんにちは <@{body['event']['user']}> さん!")
    print("### end handler ### -----------------------------------")


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

参照

定時にslack: /remind me say @hellobot2000 capture every weekday at 22:05 (https://slack.com/intl/en-in/blog/productivity/never-forget-the-little-things-with-remind)

Python Slack SDK

使えなくなったモジュール

古い情報が平気でapi.slack.comからリンクされているので注意!!

Back to Index