RubyでSlackのボットを書くには、slack-ruby-client gemやruboty gemなどを使うのが一般的だと思います。 しかし個人的には、Slackボット程度でgemを使うのは好みでないので、なるべく素のRubyだけで書くようにしています。 その方法をまとめておきます。
Slack appを登録する
まず、https://api.slack.com/appsで"Create New App"して、適当に設定をします。
次のYAMLを"App Manifest"に貼ってSave Changesすると一気に設定できます。
display_information: name: Sample Slack App features: bot_user: display_name: Sample Slack App always_online: true oauth_config: scopes: bot: - app_mentions:read - chat:write settings: event_subscriptions: request_url: https://example.com/ bot_events: - app_mention org_deploy_enabled: false socket_mode_enabled: false token_rotation_enabled: false
かんたんに説明すると、メンションされたときに通知を受け取る(app_mentions:read Scopeとapp_mention Event Subscription)、チャンネルで発言する(chat:write Scope)、という設定です。
なお、メンションではない発言も全部受け取りたかったらchannels:history Scopeとmessage.channels Event Subscriptionを足すとよいです。
ボットからSlackにメッセージを送る
chat.postMessage APIを叩くだけです。 標準ライブラリだけで簡単にできます。
require "net/http" require "json" TOKEN = "xoxb-..." # Bot User OAuth Token を埋める CHANNEL = "CXXXXXXXX" # Channel ID を埋める resp = Net::HTTP.post_form( URI.parse("https://slack.com/api/chat.postMessage"), { token: TOKEN, channel: CHANNEL, text: "Hello", } ) json = JSON.parse(resp.body, symbolize_names: true) pp json[:ok] #=> true on success, false on failure
必要な設定は2つです。
- Slack appの設定画面の"OAuth & Permissions"からBot User OAuth Tokenをコピーして、
TOKEN
に入れる - 発言したいSlackチャンネルの"View channel details"の最下部にあるChannel IDをコピーして、
CHANNEL
にいれる
そうしてコードを実行すれば、発言できるはずです。
ここでは単純に"Hello"というテキストを送っていますが、text: "Hello"
の代わりに次のようなものを書けば、Slackのmarkdown風のマークアップができます。
{ blocks: [ { type: "section", text: { type: "mrkdwn", text: "*Hello* `world`" } } ] }
このあたりについて詳しくはReference: blocksをご参照ください。 Block Kit Builderでインタラクティブに構築することもできるようです。
Slackからイベント通知を受け取る
Slackから「メンションされた」や「誰かが発言した」などのイベントの通知を受け取るには、Events APIを使います。
2024年現在、Events APIには2種類の通信方法があります。
- publicなHTTPサーバを立てて、HTTPリクエストとして通知を受け取る
- WebSocketでSlackに接続し、プッシュ通知を受け取る(Socket Mode)
両方かんたんに説明します。
HTTPサーバを立てる方法
sinatra gemでHTTPサーバを書きます。gemですが、sinatraは自分の心の許容リストに入っているのでOKとしています。
require "sinatra" require "json" require "openssl" SIGNING_SECRET = "..." # Signing Secret で埋める def verify_signature(timestamp, body, sig_actual) msg = ["v0", timestamp, body].join(":") sig_expected = "v0=" + OpenSSL::HMAC::hexdigest(OpenSSL::Digest::SHA256.new, SIGNING_SECRET, msg) OpenSSL.secure_compare(sig_actual, sig_expected) end post "/" do body = request.body.read # Slack からの POST であることを検証する halt 401, "{}" unless verify_signature( request.env["HTTP_X_SLACK_REQUEST_TIMESTAMP"], body, request.env["HTTP_X_SLACK_SIGNATURE"] ) json = JSON.parse(body, symbolize_names: true) case json[:type] when "url_verification" # Slack に URL を登録するときのイベント、challenge をそのまま返せば良い json[:challenge] when "event_callback" event = json[:event] case event[:type] when "app_mention" # メンションされた p event[:text] end "" else "" end end
必要な設定は1つだけです。
- Slack appの設定画面の"Basic Information"の"App Credentials"からSigning Secret(hexで32桁)をコピーして、
SIGNING_SECRET
にいれる
このサーバをインターネットからアクセスできるところで実行します。 実験ではngrokなど使うとよいかもしれません。
$ ruby ~/bot-server.rb ... == Sinatra (v4.0.0) has taken the stage on 4567 for development with backup from WEBrick [20XX-XX-XX XX:XX:XX] INFO WEBrick::HTTPServer#start: pid=XXXXXX port=4567
そして、"Event Subscriptions"のRequest URLで、立てたサーバのURLを入力します。 うまく行けば"Verified"となります。
Slackでボットに対して@Sample Slack Bot Hello
などとメンションしてみましょう。
うまく行っていれば、HTTPサーバの方に発言内容が出ているはずです。
"<@XXXXXXXXXXX> Hello" XXX.XXX.XXX.XXX - - [XX/XXX/20XX:XX:XX:XX +0900] "POST / HTTP/1.1" 200 - 0.0064
Socket Modeを使う方法
Socket Modeは、RubyからWebSocketでSlackに接続してプッシュ通知を受け取る方法です。 publicなHTTPサーバを用意しなくてよいので、運用は手軽かもしれません。
残念ながら、RubyでgemなしでWebSocketクライアントを使うのは大変です。いろいろ調べましたが、満足できる方法はみつけられませんでした。
調べたこと(クリックしたら詳細表示)
WebSocketプロトコルを扱うwebsocket gemはよくできていて、依存もゼロなので好みです。
ただ、実際に通信するgemとなると、eventmachineだったりfaradayだったり、巨大なgemに依存しがちです。
その点websocket-client-simple gemは、その手のものに依存しないクライアントというコンセプトは好きなのですが、細かいところで気になることが多かったです *1 。これを好みに合わせて直すくらいなら、Slackボットに特化したかんたんなものを自作するかって気分になりました。
net/httpがWebSocketをサポートしてくれたらいいのになあ。
ということで、あきらめてwebsocket gemのみに依存するslack_socket_mode_botというgemを作りました。それを使う例だけ示しておきます。
require "slack_socket_mode_bot" SLACK_BOT_TOKEN = "xoxb-..." SLACK_APP_TOKEN = "xapp-..." bot = SlackSocketModeBot.new(token: SLACK_BOT_TOKEN, app_token: SLACK_APP_TOKEN) do |data| if data[:type] == "events_api" && data[:payload][:event][:type] == "app_mention" event = data[:payload][:event] p event[:text] end end bot.run
必要な設定は3つです。
- Slack appの設定画面の"Socket Mode"でEnable Socket Modeを有効にする
- Slack appの設定画面の"OAuth & Permissions"からBot User OAuth Tokenをコピーして、
SLACK_BOT_TOKEN
に入れる(`xoxb-で始まるもの) - Slack appの設定画面の"Basic Information"の"App-Level Tokens"を作って、
SLACK_APP_TOKEN
にいれる(xapp-
で始まるもの)
あとは、このコードを実行するとSlackと通信し始めます。
$ ruby bot.rb
Slackでボットに対して@Sample Slack Bot Hello
などとメンションしてみましょう。
うまく行っていれば、発言内容が通知され、p
で出力されるはずです。
"<@XXXXXXXXXXX> Hello"
ちなみに、Socket ModeのWebSocketはあくまでイベント通知を受け取るためだけのものであり、これを経由してchat.postMessage
などのAPIを呼ぶことはできません。
slack_socket_mode_botでは、SlackSocketModeBot#call(method, data)
というAPIを呼ぶ方法もおまけで付けておきました。
まとめ
なるべくgemに頼らずにRubyでSlackのボットを書く方法を説明しました。 ここで述べた方法は、Ruby開発者のSlack workspaceで動くボットたちで長期間運用しています。
もちろんslack-ruby-client gemなどを使うのがかんたんだと思うし、抵抗がないならそれが賢いと思います。 ただ、slack-ruby-client gemはSlack社謹製ではないので、たとえばまだSocket Modeに対応していないようです。
なぜgemに頼らないかというと、依存を減らしたいという個人的な好み(いわゆるNot Invented Here症候群かも)が最大の理由ですが、Slackはわりと頻繁にAPIを仕様変更するので、多少面倒でもSlack公式のAPIを直接叩いておくほうが長期的にはメンテナンス性が高いのでは? と思ったり思わなかったり。
変更履歴
- 06/24 22:00
OpenSSL.secure_compare
やhalt 401, "{}"
を使うようにした(thanks @sora_h)