slack で bot を動かしていて 「 別の bot のメッセージに反応して何かする bot 」 を書くユースケースがあります *1
例えば こんな メッセージを出す bot がいるとします *2

上記の メッセージ から UUID ( OpenStack の Server UUID ) を取り出して、別の bot で何か処理をしたい場合を考えましょう
text
を抜き出して正規表現で UUID のマッチを試みる
- attachments や blocks の Hash/Array をイテレートして UUID を取り出す
といったアプローチでメッセージを扱うかと思います。一種のスクレイピングですね
非構造化されたデータを扱う問題
非構造化されたデータ を扱うのは何かと面倒で変更にも弱いですね
元のメッセージのフォーマットが変わる ( text
の内容が変わる, attachments や blocks の構造が変わる ) と bot を再実装する必要が出そうです 。人間向けの view
に依存してるコードは実装が複雑になったり、メンテナンス性が下がります。スクレイピングのコードを書いたことのある人はよくわかるかと思います。
bot が bot のメッセージを扱う場合に、構造化されたデータ ( Hash, Array ) で扱えると楽だよなぁと.... 長らく思っていたのでした。
で、改めて Slack のドキュメントを調べてみたら、メッセージに metadata
として Hash 構造のデータを付けられるのを この度 知ったのでした。
api.slack.com
今年の4月頃に出ていたのかな? *3
下記のように metadata
に event_type
と event_payload
を入れてメッセージを POST できる!
{
"channel": "C23456",
"text": "New teammate @Billy just joined",
"metadata": {
"event_type": "new_teammate",
"event_payload": {
"id": "TK-2132",
"summary": "New teammate has been added to the channel",
"description": "@Billy is a new teammate and needs to be added to the neccesary channels",
"priority": "HIGH",
"resource_ type": "TASK"
}
}
}
別の bot のメッセージに反応して何かする bot を作る際、metadata を参照すると構造化されたデータで扱いやすそう (ただし メッセージを出す bot で metadata を付与してないといけないけど ... )
- metadata 付きのメッセージを通知する Ruby アプリ
- metadata 付きのメッセージに反応する Python Bolt アプリ
を例に作ってみました

↑ のようなメッセージを POST する単純なコードです。metadata も付けています。
#!/usr/bin/env ruby
require 'slack-ruby-client'
require 'dotenv'
Dotenv.load
params = {
channel: '#dev',
username: 'live-migration-notifier',
text: ":arrow_forward: live-migrationが開始されました",
blocks: [
{
type: :section,
text: {
type: :plain_text,
text: ":arrow_forward: live-migrationが開始されました",
emoji: true
}
},
{
type: :divider,
},
{
type: :context,
elements: [
{
type: :mrkdwn,
text: ":desktop_computer: *example.com*\n:pencil2: 7ef9111c-0000-0000-0000-1717950e45cf",
verbatim: true
}
]
},
{
type: :context,
elements: [
{
type: :plain_text,
text: ":outbox_tray: host000",
emoji: true
},
{
type: :plain_text,
text: ":inbox_tray: host001",
emoji: true
}
]
},
{
type: :divider,
},
{
type: :context,
elements: [
{
type: :plain_text,
text: "req-b23c7b63-0000-0000-0000-00000000",
emoji: false
},
{
type: :plain_text,
text: "by instance-migrator",
emoji: false
}
]
}
]
}
metadata = {
event_type: "live_migration_started",
event_payload: {
source_host: "host000",
dest_host: "host001",
server: {
name: "example.com",
uuid: "7ef9111c-0000-0000-0000-1717950e45cf",
},
}
}
params[:metadata] = metadata.to_json
client = Slack::Web::Client.new(token: ENV['SLACK_API_TOKEN'])
client.chat_postMessage(params)
先のメッセージに反応する Python Bolt アプリです。
"""_summary_
"""
import os
import dotenv
import slack_bolt
from slack_bolt.adapter.socket_mode import SocketModeHandler
dotenv.load_dotenv()
app = slack_bolt.App(token=os.environ["SLACK_BOT_TOKEN"])
@app.event("message")
def handle_message(event, say):
metadata = event.get("metadata")
if metadata is None:
return
if metadata["event_type"] == "live_migration_started":
payload = metadata["event_payload"]
print(payload)
server_name = payload["server"]["name"]
server_uuid = payload["server"]["uuid"]
say("{0} {1} started live-migration".format(server_name, server_uuid))
if __name__ == "__main__":
handler = SocketModeHandler(app, os.environ["SLACK_APP_TOKEN"])
handler.start()
動作の例
二つの bot が連携して動作している例です

Python Bolt アプリでは、以下のように metadata を受け取っています
$ poetry run python bot.py
⚡️ Bolt app is running!
{'source_host': 'host000', 'dest_host': 'host001', 'server': {'name': 'example.com', 'uuid': '7ef9111c-0000-0000-0000-1717950e45cf'}}
意図した通りにデータを扱うことができています。
Proof Of Concent なコードなので、特に利用価値がある実装ではありません。 metadata
を通して依存関係を持っている、ってのがポイントですね。
感想
- Emoji で Reaction 付けたら、メッセージの metadata ( event_type, event_payload ) を取り出して何か操作するってコード書けそう
- OSS なんかで汎用的に使う bot の場合、
event_type
の命名はどう扱うのがいいのだろうか?
- あまり単純な名前をつけると、別の bot と衝突するだろう
- namespace の規約があるといいようには思う
そもそもの話
- live-migration-notifier の通知を別チャンネルにも転送したいユースケースがあった
- live-migration-notifier に機能を追加するよりも、別の bot で転送機能実装したらいいかと思って 「 別の bot のメッセージに反応して何かする bot 」を書いていた