LoginSignup
9
4

More than 1 year has passed since last update.

続:Slack Bolt for Pythonを使ってのbot構築 アプリ配信に向けた対応

Last updated at Posted at 2020-12-09

Slack___Slack_API__Applications___Leave_a_Nest_Co__ltd_.png
前回はSlack Advent Calendar2020の三日目に書きました。
Slack Bolt for Pythonを使ってのbot構築。ローカル開発からHerokuデプロイまで

本日はここから少し進めて、開発したアプリを複数組織にインストールして使えるようにするためにどうしたら良いのかについて深堀りしていきます。

boltの生成方法が変わる

参考ソースはこちらです

前回書いたコードは以下の通りです。一つの組織でのみ使えれば良い場合はこの形で問題ありません。トークンはBot token です。

app = App(
    token=os.environ.get("SLACK_BOT_TOKEN"),
    signing_secret=os.environ.get("SLACK_SIGNING_SECRET")
)

複数組織へのインストールが必要な場合はこんな感じにします。

app.py
import sqlalchemy
from sqlalchemy.engine import Engine

#ここは使うDBによって変えて下さい
database_url = "sqlite:///slackapp.db"
# database_url = "postgresql://localhost/slackapp"  # pip install psycopg2

logger = logging.getLogger(__name__)
client_id, client_secret, signing_secret = (
    os.environ["SLACK_CLIENT_ID"],
    os.environ["SLACK_CLIENT_SECRET"],
    os.environ["SLACK_SIGNING_SECRET"],
)

engine: Engine = sqlalchemy.create_engine(database_url)
installation_store = SQLAlchemyInstallationStore(
    client_id=client_id, engine=engine, logger=logger,
)
oauth_state_store = SQLAlchemyOAuthStateStore(
    expiration_seconds=120, engine=engine, logger=logger,
)

#slack_installationsテーブルにレコードがなければapp.py起動時にテーブルを初期化
try:
    engine.execute("select count(*) from slack_installations")
except Exception as e:
    installation_store.metadata.create_all(engine)
    oauth_state_store.metadata.create_all(engine)

app = App(
    logger=logger,
    signing_secret=signing_secret,
    installation_store=installation_store,
    oauth_settings=OAuthSettings(
        client_id=client_id,
        client_secret=client_secret,
        state_store=oauth_state_store,
    ),
)

@flask_app.route("/slack/install", methods=["GET"])
def install():
    return handler.handle(request)


@flask_app.route("/slack/oauth_redirect", methods=["GET"])
def oauth_redirect():
    return handler.handle(request)

ということで、よしなにしてくれるようになります。
おなじみの、say(f"Hi Yo, <@{user}>!")
なんかはこのような形で受けておかないと、飛んできたメッセージのteam_idがわからなくなってしまい、sayレスポンスするとchannel_not_foundエラーが出ます。そりゃそうです、初期状態ではtokenが最初のワークスペースのインストール時のものに固定しているのですから。

The server responded with: {'ok': False, 'error': 'channel_not_found'})

渡ってくるデータについて

appmention.py
@app.event("app_mention")
def handle_mention(context,body,say,logger):

こんな感じで、引数を受け取ると思います。個人的には、ここで何が受け取れるのかを知りたかったのですが、ドキュメントにはなっていないようです。

ということで、地道なリサーチが必要ですが、使えそうなものをピックアップしておきます。
Contextとは?こちらを参照下さい。

ということで、contextを受けておけば、以下のデータが取得できます。
tokenなんかはcontextからじゃないと取れなさそうですね。

app_mention_context.py
{
    'team_id': 'xxxxxxx', 
    'user_id': 'xxxxxxx', 
    'channel_id': 'xxxxxxxx', 
    'logger': <Logger app.py (DEBUG)>, 
    'token': 'xoxb-xxxxxxxxxxxxxxx', 
    'client': <slack_sdk.web.client.WebClient object at xxxxxxx>,
    'authorize_result':
        {'enterprise_id': None, 'team_id': 'xxxxxxx', 'bot_user_id': 'xxxxxx', 'bot_id': 'xxxxxx', 'bot_token': 'xoxb-xxxxxxxxxxxxxxx', 'user_id': None, 'user_token': None},
    'bot_id': 'xxxxx',
    'bot_user_id': 'xxxxx',
    'bot_token': 'xoxb-xxxxxxxxxxxxxxx',
    'ack': <slack_bolt.context.ack.ack.Ack object at xxxxx>, 
    'say': <slack_bolt.context.say.say.Say object at xxxxx>, 
    'respond': <slack_bolt.context.respond.respond.Respond object at xxxxx>
}

bodyはこんな感じ。メッセージの中身はbodyを参照しましょう。

app_mention_body.py
{
    'token': 'xxxxxxxxxx', 
    'team_id': 'xxxxxxxxxxxxxxx',
    'api_app_id': 'xxxxxxxxxxxxxxx', 
    'event': 
        {
            'client_msg_id': 'dada87aa-xxxxxxxxxxxxxxx', 
            'type': 'app_mention', 
            'text': '<@xxxxxxx>\xa0メンションメッセージ',
            'user': 'xxxxxxxxxxxxxxx',
            'ts': '1607487863.008600',
            'team': 'xxxxxxxxxxxxxxx',
            'blocks': [{'type': 'rich_text', 'block_id': 'XFYvi', 'elements': [{'type': 'rich_text_section', 'elements': [{'type': 'user', 'user_id': 'xxxxxxxxxxxxxxx'}, {'type': 'text', 'text': '\xa0メンションメッセージ'}]}]}], 
            'channel': 'xxxxxxxxxxxxxxx', 
            'event_ts': '1607487863.008600'
        }, 
    'type': 'event_callback',
    'event_id': 'xxxxxxxxxxxxxxx',
    'event_time': 1607487863, 
    'authorizations': [{'enterprise_id': None, 'team_id': 'xxxxxxxxxxxxxxx', 'user_id': 'xxxxxxxxxxxxxxx', 'is_bot': True, 'is_enterprise_install': False}], 
    'is_ext_shared_channel': False,
    'event_context': '1-app_mention-xxxxxxxxxxxxxxx-xxxxxxxxxxxxxxx'
}

アプリのインストール時にスコープを設定する必要があります。.envに環境変数に入れておきましょう。Herokuデプロイ時は環境変数設定に入れておいて下さい。ここで書いたものは一例です。

env.
export SLACK_SCOPES=app_mentions:read,channels:history,channels:join,channels:read,chat:write,chat:write.public,files:write,im:history,reactions:read,team:read,users:read

これで、インストール時に適切なパーミッションを含んだbotがインストールされます。

まずは、最初にインストールしたワークスペースで動作を確認しましょう。問題なく動いたら、マルチワークスペース対応に進みます。
アプリの配信設定をしましょう。
まずはOAuth&Permissions画面に行ってからRedirect URLsを変更します。

https://xxxxxx.ngrok.io/slack/oauth_redirect

/slack/oauth_redirect
の部分が元のapp.pyにはありませんでした。
これが出来たら、SlackのManage Distributionを開いて書いてある事にチェックを入れていきます。
固有の設定値をハードコーディングしてないか?
通信はhttpsになってるか?
みたいなものをクリアすると、晴れてdistribution可能な状態になります。

Slack___Slack_API__Applications___Leave_a_Nest_Co__ltd_.png

アプリの管理画面の Sharable URL は OAuth の state パラメータがないので Bolt for Python を使っているときはそのまま使えません。アプリのドメインのあとに /slack/install のパスをつけた URL からお望みのワークスペースへとインストールしてください。

あとは、複数ワークスペースでも問題なくbotとのやり取りが出来るかを確認しましょう!

ということで、やり方が分かってしまえば、シンプルにまとまってしまうのですが、理解に至るまでが時間がかかったよというお話でした。参考になれば幸いです!

多大な感謝を @seratch さんに。

関連リンク

9
4
1

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
9
4