はじめに
Optunaを用いたコーヒーのハンドドリップ最適化をしようとしていて(下記参照)、 moblog.hatenablog.jp いまSlackBotに落とし込んだところ (下記参照)。 moblog.hatenablog.jp
その環境構築とかをまとめる。
システムの概要
システムの概要は過去記事と、以下画像を参照のこと。
Botを動かすとしてSlackのApplication Clientから仲介されたリクエストを受け付けるサーバが必要となるが、
のどちらかが想定できる。今回は自身の適当なサーバをNgrokで安全に公開した。
環境構築
以下のパッケージやソフトウェアのインストール、認証情報の配置は、SlackBotを動作させるサーバーで行う(起動し続けるならラップトップでもいい)。
SlackBotの準備 (Workspaceでの設定)
SlackAppでのbotの作成が必要で、https://api.slack.com/apps から適当に作っておいてWorkspaceに追加しておこう(Webで探せばやり方は見つかる)。
スコープの設定
Botが何をどこまでできるかの範囲(スコープ)を決める。今回は
の2つのみで十分 (コマンドとか設定していない)。
SlackBotの設定テスト
以下のリクエストでメッセージを送れるかどうかのテストができる (SlackBotとしてSlackのサーバにPOSTを送ってWorkspaceへのポストの権限があるかどうかを確認)。
$ curl -X POST -F channel=[channel ID] -F text="Slack投稿テスト" https://slack.com/api/ch at.postMessage -H "Authorization: Bearer xoxb-[hogehoge]"
xoxb-[hogehoge]
の部分は、https://api.slack.com/apps/ のOAuthのタブから OAuth Token がわかる。
[Channel ID]
はBot自身のものか、どこかのChannelにInviteしてそこのChannel IDを使用。
成功したらJsonで"ok":true
が返って来て、次のような投稿が飛んでくる。
$ curl -X POST -F channel=[channel ID] -F text="Slack投稿テスト" https://slack.com/api/chat.postMessage -H "Authorization: Bearer xoxb-***hogehoge***" {"ok":true,"channel":"[channel ID]","ts":"1687491751.172649","message":{"bot_id":"B05DYD5V3EG","type":"message","text":"Slack\u6295\u7a3f\u30c6\u30b9\u30c8","user":"U05DT2A1WAH","ts":"1687491751.172649","app_id":"A01TM7Q7YP8","blocks":[{"type":"rich_text","block_id":"Mfi3k","elements":[{"type":"rich_text_section","elements":[{"type":"text","text":"Slack\u6295\u7a3f\u30c6\u30b9\u30c8"}]}]}],"team":"TBHPDDE7N","bot_profile":{"id":"B05DYD5V3EG","app_id":"A01TM7Q7YP8","name":"Human in the Coffee Loop","icons":{"image_36":"https:\/\/a.slack-edge.com\/80588\/img\/plugins\/app\/bot_36.png","image_48":"https:\/\/a.slack-edge.com\/80588\/img\/plugins\/app\/bot_48.png","image_72":"https:\/\/a.slack-edge.com\/80588\/img\/plugins\/app\/service_72.png"},"deleted":false,"updated":1687487127,"team_id":"TBHPDDE7N"}}}
以下のようにScopeが足りないぞと言われたらスコープを追加しよう *1 。
$ curl -X POST -F channel=[channel ID] -F text="Slack投稿テスト" https://slack.com/api/ch at.postMessage -H "Authorization: Bearer xoxb-[hogehoge]" {"ok":false,"error":"missing_scope","needed":"chat:write:bot","provided":"app_mentions:read,bookmarks:read,commands"}
"ok":false,"error":"missing_scope","needed":"chat:write:bot"
Scopeを変更したらReinstallが必要。← スコープを変更すると以下のように reinstall your appしろと言われる。
「⚠️You’ve changed the permission scopes your app uses. Please reinstall your app for these changes to take effect (and if your app is listed in the Slack App Directory, you’ll need to resubmit it as well」
ngrokでローカルホストとngrokのグローバルIPをマッピング
ngrokを起動して得られるngrokのIPからlocalhost:5000へとポートフォワーディングするのは以下のコマンドをたたくだけ。
ngrok http 5000
出力された Forwarding にGlobal IPが記されている。
ngrok (Ctrl+C to quit) Send your ngrok traffic logs to Datadog: https://ngrok.com/blog-post/datadog-logs Session Status online Session Expires 1 hour, 56 minutes Terms of Service https://ngrok.com/tos Version 3.3.1 Region Europe (eu) Latency 37ms Web Interface http://127.0.0.1:4040 **Forwarding https://6828-193-167-228-172.eu.ngrok.io -> http://localhost:5000** Connections ttl opn rt1 rt5 p50 p90 0 0 0.00 0.00 0.00 0.00
アカウントを登録していないとSessionは2時間で切れてしまうようだが、Freeアカウントを登録してからセッションを開始すると(ngrok実行環境のconfigファイルをいじる必要がある←アカウントページのコマンドを打つだけでいい)、Session Expiresが消えて時間制限がなくなる(?)。(ほんとに?すごい)
ngrok (Ctrl+C to quit) Send your ngrok traffic logs to Datadog: https://ngrok.com/blog-post/datadog-logs Session Status online Account hogehoge@gmail.com (Plan: Free) Version 3.3.1 Region Europe (eu) Latency 37ms Web Interface http://127.0.0.1:4040 Forwarding https://34ee-193-167-228-172.ngrok-free.app -> http://localhost:5000 Connections ttl opn rt1 rt5 p50 p90 0 0 0.00 0.00 0.00 0.00
↑ アカウント登録後に要求したセッション。Session Expiresがなくなっている(無限なのか?すごすぎ)。URLは発行ごとに変わってしまうので、サーバ落とした後とかには再発行してSlackAppのRequest URLに変更したURLを再登録する必要がある。Upgradeすると固定ドメインが作れる/設定できるらしい(1月10USD程度)。
以下のように--domain
オプションで指定できる様子。[hogehoge].ngrok-free.app
はngrok
で予約されたサブドメイン。
ngrok http --domain=[hogehoge].ngrok-free.app 5000
SlackAppのEventへ登録
Request URLにアプリのURL (ngrokで発行されたものか、レンタルサーバの公開URL) を登録する ([url]/slack/events
)。
Subscribe to bot eventsで、受け付けるイベントの種類を登録しておく *2。
app_mention
イベントはDMのメッセージで発生しないらしい。massage.im
イベントがDM専用のイベントっぽい。
Messages sent to your app in direct message conversations are not dispatched via
app_mention
, including messages sent from other apps, regardless of whether your app is explicitly mentioned or otherwise. Subscribe to[message.im](https://api.slack.com/events/message.im)
events to receive messages directed to your bot user in direct message conversations.
Google Spreadsheet の用意
Google Sheet をデータベースとして使用しているので、APIを取得するのと権限の設定をしなければいけない。これが一番面倒くさい。
簡単に書き出すと
- Google Cloud Platform (GCP) 上でプロジェクトの作成
- Google Spreadsheets APIとGoogle Drive APIの有効化
- IAMからサービスアカウントの作成
- サービスアカウントに対する秘密キーの作成
- Sheetを作成して、サービスアカウントに編集権限を追加する
- 秘密キーとシートのIDをサーバに配置する
が必要となる。
1—5は「PythonでGoogle Sheetを編集 GCP API」とか検索するといくらでも出てくるので各自やってもらいたい(ここが面倒すぎて他人におすすめできていない)。
以下のようにワークブックへ接続しているので、実行ディレクトリの
./data/sheet/human-in-the-coffee-loop-d4b97ca67511.json
にサービスアカウントの秘密キー./data/sheet/sheet_id.dat
にspreadsheetのsheet id
を保存する。
# サービスアカウントの認証 dir_base = './data/sheet/' gc = gspread.service_account(os.path.join(dir_base, 'human-in-the-coffee-loop-d4b97ca67511.json')) # ワークブックへの接続 with open(os.path.join(dir_base, 'sheet_id.dat')) as f: sheet_id = f.readlines() workbook = gc.open_by_key(sheet_id[0])
Botの動作環境
インストール
以下が必要パッケージ (Dockerやらvenvで環境は分けよう)。
pip install flask slack slackclient slack_sdk slackeventsapi python-dotenv pip install gspread optuna # for other than slackbot
slack_sdk
とslack
がどっちなのかはしらん (どっちかが統合された?)。python-dotenv
はやっぱり OAuth token 等を扱うので必須。
以下から Human in the Coffee Loop のプロジェクトをCloneする。
git clone [git@github.com](mailto:git@github.com):indiaki1o/Human-in-the-Coffee-Loop.git
SlackBotの認証情報の設置
dotenvでBotの OAuth token と Signing secret を管理しているので、プロジェクト直下に.env
ファイルを作成し、以下のように保存しておく。
SLACK_BOT_OAUTH_TOKEN=xoxb-[OAuth token] SLACK_SIGNING_SECRET=[Signing secret]
実行・運用
SlackBotを動作させるコンピュータ上で Human_in_the_Coffee_Loop_SlackApp.py
を実行すると、 * Running on http://127.0.0.1:5000
と表示されるようにFlaskサーバがLocalhostで動作するので、そのポート番号をngrokにフォワードしてもらう。
$ python ./Human_in_the_Coffee_Loop_SlackApp.py DEBUG:slack_sdk.web.legacy_base_client:Sending a request - url: https://www.slack.com/api/auth.test, query_params: {}, body_params: {}, files: {}, json_body: None, headers: {'Content-Type': 'application/x-www-form-urlencoded', 'Authorization': '(redacted)', 'User-Agent': 'Python/3.10.11 slackclient/3.21.3 Windows/10'} * Serving Flask app 'Human_in_the_Coffee_Loop_SlackApp' * Debug mode: on INFO:werkzeug:WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. * Running on http://127.0.0.1:5000 INFO:werkzeug:Press CTRL+C to quit INFO:werkzeug: * Restarting with stat INFO:werkzeug: * Debugger PIN: XXX-XXX-XXX
ngrok http 5000 &
実際はSSHセッションが切れるとFlaskサーバもngrokもKillされてしまうので、nohup
を用い、殺されないように実行しよう (ngrokがすでにフォワードしているなら再度起動する必要はない)。
nohup python ./Human_in_the_Coffee_Loop_SlackApp.py & nohup ngrok http 5000 &
ログはloggingパッケージを使用しておりstderrに出力されるため(デフォルトの設定から変えていない)、ログを取りたければリダイレクトをすること (以下のコマンドだと主に stderr.log
へ出力されるはず)。
$ nohup python ./Human_in_the_Coffee_Loop_SlackApp.py 1>> stdout.log 2>> stderr.log &
ここまで来たら、BotにDMを送ればレシピを送ってくれるはず。
おわりに
環境構築長すぎ面倒すぎる。
*1:chat:write:bot と書いているが、やろうとしていることがbotのDMでのchat.postMessage なので、chat:writeと、もしかしたらchat:write.customize の追加が必要?
*2:イベントの種類はこちらを参考に:https://api.slack.com/events/app_mention