はじめに
Slack-botを開発していると,テストチャンネルがBotからのメッセージで溢れたり,またはボットのメッセージが作成した本人でも権限がなくて消せなかったりする際にAPI経由で消すと楽だったのでメモとして残しておきたいと思います.
(入退室管理のボットを作った程度の初心者です...)
また,アプリの作成や必要な権限などについては明記してませんが,適宜APIのリンクから確認していただければと思います.
さらに,APIを呼ぶときのPythonのコードも記載するのですが,今回はいつもお世話になっているPythonのBoltのWebClient経由で呼んでいます.結局はSlack-SDKのWebClientで送ってるのでBoltを使ってないときはSDKのWebClinetやAPIに直接POSTする方法などに置き換えてもらえたら幸いです.
実行環境
Python: 3.9.5 (>=3.6)
必要なモジュール: slack_bolt
Botのメッセージを削除する
まずBotが送信したメッセージを削除するときはchat.deleteメソッドを使います.
必要なのはBotのトークン(xoxb-xxxx-xxxx-xxxx
)とチャンネルのID,メッセージのタイムスタンプです.この3点がわかればいいので,一番簡単な方法としてはメッセージのリンク(https://hogehoge.slack.com/archives/<channnelID>/p<timestamp>
)をコピーしてそのリンクを元にリクエストを投げるという形です.
タイムスタンプは後ろから6桁目にピリオドを付与したものを用います.例えば1234567891011120
の場合は1234567891.011120
をリクエストで使います.
試す方法はchat.deleteメソッドのテスターに記述するのが手っ取り早いですが,個人的にチャンネルIDやタイムスタンプを手作業で抜き出すのが面倒だったので以下のようなPythonスクリプトを使っています.(リンクを直接指定したいときとかは結構便利かもしれません)
import os, sys
from slack_bolt import App
args = sys.argv
try:
url = args[1]
# スレッド時のクエリを削除
if url.find("?") != -1:
url = url[:url.find("?")]
split_url = url.split("/")
channel_id = split_url[-2]
timestamp_orginal = split_url[-1].lstrip("p")
timestamp = "{0}.{1}".format(timestamp[:-6], timestamp[-6:])
except Exception as e:
print("ERROR")
print(e)
sys.exit(1)
app = App(token=os.environ["SLACK_BOT_TOKEN"])
res = app.client.chat_delete(
channel=channel_id,
ts=timestamp,
)
print(res)
# $ python3 deleteMessage.py
# $ https://hogehoge.slack.com/archives/<channnelID>/p<timestamp>
# >> {'ok': True, 'channel': '<channnelID>', 'ts': '<dotted timestamp>'}
直近に送信したBOTのメッセージを削除する
リンクをコピーしてPythonスクリプトを実行するというのは特定のメッセージを削除するときに有用です.しかし,それ以外のときは楽なようで全く楽じゃなくて,例えば「直近から3件削除したい」みたいなときにいちいちリンクをコピーして実行を繰り返すのは流石に手間です.そこで,チャンネルのメッセージIDすら自動で取得して消せたら楽なのにと思いAPIを探しました.
ここで使うのは,conversations.historyメソッドとconversations.repliesメソッドの2つです.
後者は,ボットが生成したスレッド(ボットのみが返信をしているスレッド)を削除するときに使うのですが,以下のような流れで作成されているときを想定しています.
例.入退室管理ボット
(チャンネルへ投稿されたメッセージ...)
|
部屋解錠メッセージ
| |--- メッセージ1 [入室] Aさん
| |--- メッセージ2 [退室] Aさん
| |--- メッセージ3 部屋施錠メッセージ
|
部屋施錠メッセージ(reply_broadcast)
また,conversations.historyメソッドとconversations.repliesメソッドを呼び出す上で最低限必要なのはボットのトークンとチャンネルのIDだけです.これらのメソッドを使って取得したメッセージに含まれるタイムスタンプを元にchat.deleteメソッドにリクエストを送りメッセージを削除します.
通常のメッセージのみを削除する
ここでの通常のメッセージとは,上記の例における部屋解錠メッセージ
や部屋施錠メッセージ(reply_broadcast)
などのチャンネルに送信されたメッセージを指します.スレッドが生えている場合,通常メッセージを削除してもそのスレッドへの返信までは削除されず残ります.
conversations.historyメソッドを使えばチャンネルに送信されたメッセージの履歴を取得できます.これを利用して最新のメッセージのタイムスタンプを取得することでchat.deleteメソッドにリクエストを送ります.
特に,conversations.historyメソッドのAPIに記載されている通り,取得するメッセージの件数(limit)はデフォルトで100になっているので,これを1に設定することで最新のメッセージのみを取得します.
取得したメッセージのJSONの中にあるts
キーがメッセージのタイムスタンプなので,これを用いてchat.deleteメソッドにリクエストを投げます.
conversations.historyメソッドに関する余談
limitを1に設定することで最新を取得すると記載しましたが,limitをどの値に設定したとしてもmessagesというキーの配列の中にメッセージが格納されているので必須というわけではありません.しかし,今回最新のメッセージ以外はすべて不要なので1に設定することをお勧めします.
また,APIを見るとlatestやoldestというオプションがあります,これをうまく使えばより柔軟な操作ができます.
スレッドのメッセージを削除する
スレッドを削除する場合は,はじめに記載した通り,通常メッセージを削除するだけではスレッドに生えたリプライまでは削除されません.
そこで.conversations.repliesメソッドを用いて親のメッセージを起点としてスレッドに対して返信されたメッセージのタイムスタンプを取得し,それらのタイムスタンプを使ってchat.deleteメソッドを呼び出すというアプローチをとります.そうすることで,スレッドに関係したメッセージも含めて丸ごと削除できます.
ここでスレッドに関係したメッセージというのは,スレッドの親となるメッセージ,スレッドに返信されたメッセージ,スレッドに返信しかつチャンネルにも送信されたメッセージ(reply_broadcast=True
として送信したメッセージ)を指します.
流れとしては以下のような手順です.
- conversations.historyメソッドを使って最新のメッセージを取得する
- スレッドに関係したメッセージかどうか判断する
- スレッドに関係ないならばメッセージのタイムスタンプを使ってchat.deleteメソッドにリクエストを投げる
- スレッドに関係したメッセージならばconversations.repliesメソッドを用いてスレッドに関係したすべてのメッセージを取得する - スレッドに関係したすべてのメッセージそれぞれのタイムスタンプを使ってchat.deleteメソッドにリクエストを投げる
スレッドに関係したメッセージかどうかを判断するためには,今回はreply_count
キーとsubtype
キーに注目しました.reply_count
キーはスレッドが生えている親のメッセージに対して付与されているキーで,subtype
キーはreply_broadcast=True
として送信されたメッセージに対して付与されるキーです.これらがメッセージの情報に含まれている場合にはスレッドに関係したメッセージとして判断します.
上記の二つの削除をまとめたPythonコード
import os, time
from slack_bolt import App
channel_id = os.environ["SLACK_CHANNEL_ID"]
app = App(token=os.environ["SLACK_BOT_TOKEN"])
def deleteMessage(channel, ts):
return app.client.chat_delete(
channel=channel_id,
ts=ts,
)
def deleteThreadMessage(channel, thread_ts):
# https://api.slack.com/methods/conversations.replies
messages = app.client.conversations_replies(
channel=channel_id,
ts = thread_ts,
)["messages"]
responses = list()
# 後ろから順番に削除していく
for message in reversed(messages):
responses.append(deleteMessage(
channel,
message["ts"],
))
time.sleep(0.5) # 一応待ちます.
return responses
def run():
# https://api.slack.com/methods/conversations.history
result = app.client.conversations_history(channel=channel_id, limit=1)
message = result["messages"][0]
# スレッドに関係したメッセージかどうか
if "reply_count" in message.keys() or "subtype" in message.keys():
# thread_tsが親のタイムスタンプ
return deleteThreadMessage(channel_id, message["thread_ts"])
# それ以外は通常メッセージなのでそのまま消す
ts = message["ts"]
return [deleteMessage(channel_id, ts)]
if __name__ == "__main__":
responses = run()
for res in responses:
print(res)
# スレッドの場合
# $ python3 deleteMessageNow.py
# >> {'ok': True, 'channel': '<channnelID>', 'ts': '<dotted timestamp3>'}
# >> {'ok': True, 'channel': '<channnelID>', 'ts': '<dotted timestamp2>'}
# >> {'ok': True, 'channel': '<channnelID>', 'ts': '<dotted timestamp1>'}
シェルスクリプトと上記のPythonコードを組み合わせる
直近から3個削除したいときにdeleteMessageNow.py
を3回実行するのは面倒ですので,シェルスクリプトと組み合わせることでとっても楽に消せるようになります.
#!/bin/sh
echo "直近何回分消しますか?":
read num
echo "OK! running deleteMessageNow.py [$num]"
# 入力された回数だけloopを回す
for i in `seq 1 $num`
do
python3 deleteMessageNow.py
done
参考URL
slack-api
- chat.delete
- conversations.history
- conversations.replies
Qiita記事
- [Slack]自作したBotのメッセージを削除する
- Slack APIのchannels.historyが使えなくなる対策
その他
- Bolt for Python
- Bolt for Python (Github)
- Python Slack SDK