slack-sdk を使おう
「Python で Slack API や Webhook を扱うなら公式 SDK(slack-sdk/slack-bolt)を使おう」シリーズの記事です。公式 SDK についての概要、他にも記事がリンクされていますので、こちらのエントリーページにもアクセスしてみてください。
この記事では slack-sdk
PyPI パッケージを使って Slack にファイルをアップロードするコーディングについて解説していきます。
files.upload API を使うだけ
と言っても、実は files.upload
という API の使い方を理解するだけですので、とても簡単です。英語のみとなりますが、API ドキュメントは以下の URL です。
この記事では Python SDK でどのようにこの API を使うかをコード例とともに紹介していきます。
Slack アプリ設定をつくってインストールする
App Manifest を使ってアプリ設定をつくる
こちらの URL からアプリを新しく作ってください。
この URL には以下の App Manifest が埋め込まれており、ワークスペースを選ぶだけでアプリ設定を完了することができます。
_metadata:
major_version: 1
minor_version: 1
display_information:
name: File Writer App
features:
bot_user:
display_name: File Writer Bot
oauth_config:
scopes:
bot:
# チャンネルにメッセージを投稿するのに必要
- chat:write
# ファイルのアップロードだけなら不要、ここでは files.info / files.list を使うため
- files:read
# ファイルをアップロードするために必要
- files:write
Slack ワークスペースにインストールする
アプリ設定ができたら、そのまま画面にある「Install to Workspace」ボタンから Slack ワークスペースにインストールします。
いつものアプリインストール確認画面で「Allow」をクリックします。
インストールが終わったら、左のメニューから Settings > Install App に移動して、アクセストークンを取得してください。ご覧の通り、ここはこの記事時点で少しわかりにくくなっていますが、この辺の導線は、今後のアプリ設定画面のアップデートで改善されるかと思います。
このトークンの値を環境変数 SLACK_BOT_TOKEN
に設定しましょう。これで Slack アプリ設定画面での準備は完了です。
export SLACK_BOT_TOKEN=xoxb-11111111111-2222222222-dssdfswsddjfh34sfdsfs
Python コードでファイルをアップロードする
プロジェクトの新規作成
まず使用する Python のバージョンが 3.6 以上であるかを確認してください。
最近では、システム標準の python3
や pip3
などのコマンドも 3.6 以上のバージョンだとは思いますが、常に最新のバージョン(この記事投稿時点で 3.10 です)を使用するために pyenv などのツールを使って Python のランタイムを管理することをお勧めします。
その 3.6 以上の Python で、以下のような依存ライブラリを解決したまっさらな環境を作ります。
echo 'slack-sdk>=3.12,<4' > requirements.txt
python3 -m venv .venv
source .venv/bin/activate
pip install -U pip
pip install -r requirements.txt
この状態で REPL を使ってもよいですが、以下ではスクリプトとして実行できるコード例を示していきます。
ただファイルをアップロードしてみる
まずはじめに以下のどのコードでも共通の部分です。以下では繰り返しませんが、スクリプトの最初には必ず含めるようにしてください。
import logging, os
# デバッグレベルのログを出力します
# 実際に使うスクリプト・アプリではデフォルトの INFO レベルにして
# 必要なログは自分で追加のログを出力するようにしましょう
logging.basicConfig(level=logging.DEBUG)
# Web API クライアントを初期化します
from slack_sdk import WebClient
# export SLACK_BOT_TOKEN= のように環境変数があらかじめ設定されている前提です
# トークンを直接コードに埋め込むことは避けましょう
client = WebClient(os.environ["SLACK_BOT_TOKEN"])
それではまずはトークンが正しいかを確認します。
# トークンが正しいか確認します
auth_test = client.auth_test()
bot_user_id = auth_test["user_id"]
print(f"App's bot user: {bot_user_id}")
以下のような出力になるでしょう。
>>> auth_test = client.auth_test()
DEBUG:slack_sdk.web.base_client:Sending a request - url: https://www.slack.com/api/auth.test, query_params: {}, body_params: {}, files: {}, json_body: None, headers: {}
DEBUG:slack_sdk.web.base_client:Received the following response - status: 200, headers: {}, body: {"ok":true,"url":"https:\/\/example.slack.com\/","team":"Acme Corp","user":"file_writer_bot","team_id":"T1234567890","user_id":"U02PY3HA48G","bot_id":"B02P8CPE143","is_enterprise_install":false}
>>> bot_user_id = auth_test["user_id"]
>>> print(f"App's bot user: {bot_user_id}")
App's bot user: U02PY3HA48G
問題ないようです。bot user のユーザー ID を出力していますが、これからアップロードするファイルはすべてこのユーザーがアップロードしたもの(この bot user の持ち物)として扱われます。
まだ何もしていないので、実行しても意味がないですが、このユーザーがアップロードしたファイル一覧を確認するには以下のようなコードを実行します。後ほどファイルをアップロードしたら、再度実行してみましょう。
>>> files = client.files_list(user=bot_user_id)
DEBUG:slack_sdk.web.base_client:Sending a request - url: https://www.slack.com/api/files.list, query_params: {}, body_params: {'user': 'U02PY3HA48G'}, files: {}, json_body: None, headers: {}
DEBUG:slack_sdk.web.base_client:Received the following response - status: 200, headers: {}, body: {"ok":true,"files":[],"paging":{"count":100,"total":0,"page":1,"pages":0}}
それでは、いよいよファイルをアップロードしてみます。まずは content
パラメーターを使ってテキストデータをファイルとしてアップロードします。
new_file = client.files_upload(
title="My Test Text File",
filename="test.txt",
content="Hi there! This is a text file!",
)
REPL で実行してみると、どうやら成功したようです。
>>> new_file = client.files_upload(
... title="My Test Text File",
... filename="test.txt",
... content="Hi there! This is a text file!",
... )
DEBUG:slack_sdk.web.base_client:Sending a request - url: https://www.slack.com/api/files.upload, query_params: {}, body_params: {'filename': 'test.txt', 'title': 'My Test Text File', 'content': 'Hi there! This is a text file!'}, files: {}, json_body: None, headers: {}
DEBUG:slack_sdk.web.base_client:Received the following response - status: 200, headers: {}, body: {"ok":true,"file":{"id":"F02P5J88137","created":1638414790,"timestamp":1638414790,"name":"test.txt","title":"My Test Text File","mimetype":"text\/plain","filetype":"text","pretty_type":"Plain Text","user":"U02PY3HA48G","editable":true,"size":30,"mode":"snippet","is_external":false,"external_type":"","is_public":false,"public_url_shared":false,"display_as_bot":false,"username":"","url_private":"https:\/\/files.slack.com\/files-pri\/T03E94MJU-F02P5J88137\/test.txt","url_private_download":"https:\/\/files.slack.com\/files-pri\/T03E94MJU-F02P5J88137\/download\/test.txt","permalink":"https:\/\/seratch.slack.com\/files\/U02PY3HA48G\/F02P5J88137\/test.txt","permalink_public":"https:\/\/slack-files.com\/T03E94MJU-F02P5J88137-e3fda671e9","edit_link":"https:\/\/seratch.slack.com\/files\/U02PY3HA48G\/F02P5J88137\/test.txt\/edit","preview":"Hi there! This is a text file!","preview_highlight":"<div class=\"CodeMirror cm-s-default CodeMirrorServer\" oncopy=\"if(event.clipboardData){event.clipboardData.setData('text\/plain',window.getSelection().toString().replace(\/\\u200b\/g,''));event.preventDefault();event.stopPropagation();}\">\n<div class=\"CodeMirror-code\">\n<div><pre>Hi there! This is a text file!<\/pre><\/div>\n<\/div>\n<\/div>\n","lines":1,"lines_more":0,"preview_is_truncated":false,"comments_count":0,"is_starred":false,"shares":{},"channels":[],"groups":[],"ims":[],"has_rich_preview":false}}
files.list
API で確認してみても、ファイルは確かにアップロードされています(files.list
のデータ反映は少しラグがある場合もあります)。
>>> files = client.files_list(user=bot_user_id)
DEBUG:slack_sdk.web.base_client:Sending a request - url: https://www.slack.com/api/files.list, query_params: {}, body_params: {'user': 'U02PY3HA48G'}, files: {}, json_body: None, headers: {}
DEBUG:slack_sdk.web.base_client:Received the following response - status: 200, headers: {}, body: {"ok":true,"files":[{"id":"F02P5J88137","created":1638414790,"timestamp":1638414790,"name":"test.txt","title":"My Test Text File","mimetype":"text\/plain","filetype":"text","pretty_type":"Plain Text","user":"U02PY3HA48G","editable":true,"size":30,"mode":"snippet","is_external":false,"external_type":"","is_public":false,"public_url_shared":false,"display_as_bot":false,"username":"","url_private":"https:\/\/files.slack.com\/files-pri\/T03E94MJU-F02P5J88137\/test.txt","url_private_download":"https:\/\/files.slack.com\/files-pri\/T03E94MJU-F02P5J88137\/download\/test.txt","permalink":"https:\/\/seratch.slack.com\/files\/U02PY3HA48G\/F02P5J88137\/test.txt","permalink_public":"https:\/\/slack-files.com\/T03E94MJU-F02P5J88137-e3fda671e9","edit_link":"https:\/\/seratch.slack.com\/files\/U02PY3HA48G\/F02P5J88137\/test.txt\/edit","preview":"Hi there! This is a text file!","preview_highlight":"<div class=\"CodeMirror cm-s-default CodeMirrorServer\" oncopy=\"if(event.clipboardData){event.clipboardData.setData('text\/plain',window.getSelection().toString().replace(\/\\u200b\/g,''));event.preventDefault();event.stopPropagation();}\">\n<div class=\"CodeMirror-code\">\n<div><pre>Hi there! This is a text file!<\/pre><\/div>\n<\/div>\n<\/div>\n","lines":1,"lines_more":0,"preview_is_truncated":false,"channels":[],"groups":[],"ims":[],"comments_count":0}],"paging":{"count":100,"total":1,"page":1,"pages":1}}
しかし、Slack ワークスペースを見ても何も起きていませんね?これはどういうことでしょうか?
ファイルをチャンネルで共有する
この時点では、ファイルは確かに Slack にアップロードされたのですが、まだこのアプリの bot user 以外には見えない状態になっています。
API を使って bot user にこのファイルを他のユーザーと共有させましょう。そのために chat.postMessage
API を使ってメッセージを投稿します。
#random
チャンネルを使うことにしましょう。以下のように File Writer Bot をメンションして #random
チャンネルに invite してください。
そして、以下のようにファイルの URL を取得したら、それを含めるメッセージを投稿してください。
file_url = new_file.get("file").get("permalink")
new_message = client.chat_postMessage(
channel="#random",
text=f"Here is the file: {file_url}",
)
するとこのようにファイルが表示されるようになりました。
ファイルアップロード時にチャンネルを指定する
しかし、毎回このようなことをするのは手間ですね。これを一括でやるなら、以下のように channels
パラメーターを設定します。ファイルアップロードをアプリからやるのは、このやり方が一般的です。
なお channels
の値は、以下のコード例のように一つの str を渡しても str の配列で複数のチャンネル ID を渡してもどちらでも問題ありません。また、ここでは簡便化のために #random
を指定していますが、一般的にはチャンネル ID を指定することを推奨しています(チャンネル名はいつでも変更される可能性があるため)。
upload_and_then_share_file = client.files_upload(
channels="#random", # 複数のチャンネルを指定することも可能
title="Test text data",
filename="test.txt",
content="Hi there! This is a text file!",
initial_comment="Here is the file:",
)
これを実行すると、URL をリンクを含めないこと以外は全く同じようにファイルがチャンネルで共有されます。
ローカルのファイルをアップロードする
それでは、ローカルにあるファイルをアップロードしてみましょう。手元に適当な画像ファイルなどがあればそれを使っても構いませんが、ここではコピペして実行できるように引き続きテキストファイルを使って説明します。ターミナルから以下を実行して test.txt
というファイルを作ってください。
echo 'Hi there! This is a text file!' > test.txt
そして、そのディレクトリで以下の Python コードを実行します。file
パラメーターでファイルのパスを指定します。
upload_text_file = client.files_upload(
channels="#random",
title="Test text data",
file="./test.txt",
initial_comment="Here is the file:",
)
そうすると全く同じようにファイルがアップロードされていることがわかります。
files.upload
API では、ここで紹介したもの以外にもいくつかパラメーターがサポートされていますので、色々と試してみてください。
(おまけ)ファイルを削除する
おまけでファイルを削除する方法などを紹介してみようと思います。
今 3 つのファイルを上のコードでアップロードしています。
file_ids = []
# Python SDK は for での自動ページネイションに対応しています
for page in client.files_list(user=bot_user_id):
for file in page.get("files", []):
file_ids.append(file["id"])
print(file_ids)
上記のコードを実行すると 3 つのファイル ID を確認できます。
>>> file_ids
['F02P5J88137', 'F02P8H5BN9G', 'F02P5K7T8SZ']
これらを全て削除してみましょう。
for page in client.files_list(user=bot_user_id):
for file in page.get("files", []):
client.files_delete(file=file["id"])
削除すると files
の配列はすぐに空になるはずです。paging
の方の数字は少しラグがある場合もあります。
Slack ワークスペースの UI 上でも以下のようにファイルが削除された旨の表示となりました。
まとめ
この記事では Slack API を使ってファイルをアップロードし、チャンネルで共有する手順について、公式 Python SDK のコード例とともに紹介しました。Python 以外でもやることは基本的に同じです。まず Python でサッと試してから実際の言語のコードに落としていくのもよいかもしれません。
それでは