この記事の目的
Slack AI が発表され,今後も利用が普及する見込みであることから,我が家にも Slack を導入する機運が高まりました.
Slack をフリープランでもフル活用するために今後拡張していけそうな機能を調べたので,備忘録のためにこの記事にまとめます.
こちらの記事では Slack API 公式ドキュメント に準じて動作確認を行っています.
こちらの記事では無料で活用できる範囲の検証しか行っていません.
よって以下は取り扱っていません.
- Workflows
- Next generation Slack platform
Slack App は 10 個までの制限があることも念頭に置く必要があるかもしれません.
基本的に https://your-workspace.slack.com/apps から作業を行います.
自前の Slack Apps を利用した拡張
API を使うにあたって,自前の Slack App を作成してワークスペースにインストールする必要があります.
まず,自前の Slack App を作成する手順を説明します.
- https://your-workspace.slack.com/apps の右上にある Build をクリックして遷移した先から Create an App をクリック
- From scratch を選ぶ
- App Name に適当なアプリ名を入力し,アプリを利用するワークスペースを指定したら Create App を実行
- Basic Information が表示されたら Install your app から ワークスペースにインストール
- インストールが完了したら OAuth & Permissions からトークン (
xoxb-
はBot用,xoxp-
はユーザ用) が発行されていることを確認
作成した App に対して適切な権限やスコープを管理する1必要があります.
デフォルトではどのチャンネルにも接続はできません.
ここでは公式ドキュメント2に従って,OAuth & Permissions > Scopes から Bot 用とユーザ用に channels:read
, channels:history
, chat:write
をつけておきます.
トークンを利用してパブリックなチャンネルの情報を取得 (api/conversations.list
3) できるか試すことで,アプリがワークスペースにインストールできたことを確認できます.
以下では特にチャンネル名とチャンネル ID を返してもらうようにしています.
curl -X POST 'https://slack.com/api/conversations.list' \
-H "Authorization: Bearer xoxp-xxxxxxxxxxxxxxxxxxxx" \
| jq '[.channels[].name]','[.channels[].id]'
特定のチャンネル・チャットにメッセージを送る
api/chat.postMessage
cURL を使ってユーザの代わりにメッセージを送る場合の例を以下に示します.
"channel"
の値はチャンネル名でもチャンネル ID でも OK です.
curl -X POST 'https://slack.com/api/chat.postMessage' \
-H "Content-Type: application/json" \
-H "Authorization: Bearer xoxp-xxxxxxxxxxxxxxxxxxxx" \
-d @- << EOF
{
"channel": "CHANNEL-NAME-OR-ID",
"text": "Hello world :tada:"
}
EOF
Bot で送る場合は予めチャンネルに App を登録しておく必要があります.
Add an App が完了したら改めてPOST
を送ることで投稿が完了し,App 名の Bot ユーザからのチャット投稿が確認できます.
curl -X POST 'https://slack.com/api/chat.postMessage' \
-H "Content-Type: application/json" \
-H "Authorization: Bearer xoxb-xxxxxxxxxxxxxxxxxxxx" \
-d @- << EOF
{
"channel": "CHANNEL-NAME-OR-ID",
"text": "Hello world from Bot :tada:"
}
EOF
スレッド返信するには ts
値が必要です.
api/chat.postMessage
でメッセージを送った場合はレスポンスに ts
値を持っているため,例えばあるサービスから順番に情報が送られる場合は ts
値を保持しておけば以下の POST
でスレッド返信できます.
curl -X POST 'https://slack.com/api/chat.postMessage' \
-H "Content-Type: application/json" \
-H "Authorization: Bearer xoxp-xxxxxxxxxxxxxxxxxxxx" \
-d @- << EOF
{
"channel": "CHANNEL-NAME-OR-ID",
"thread_ts": "PARENT_MESSAGE_TS",
"text": "Hello again! :tada:"
}
EOF
ts
値がわからないがスレッド返信したい場合は,特定のチャンネルでメッセージを検索する を参照して検索してから実行するとよいかもしれません.
その他,一時的なメッセージを送る場合は chat.postEphemeral
4,時間指定でメッセージを予約投稿する場合は chat.scheduleMessage
5 を利用することができます.
Incoming Webhooks
Incoming Webhooks を有効化してメッセージを投稿する方法もあります.
この場合はチャンネルごとに宛先 URL を発行して POST
します.
curl -X POST 'https://hooks.slack.com/services/xxxxxxxxxxxxxxxxxxxx' \
-H "Content-Type: application/json" \
-d @- << EOF
{
"text": "Hello world :tada:"
}
EOF
api/chat.postMessage
では App に設定した権限やスコープに準じて,プライベートなチャンネルに投稿はできませんでした.
一方で Incoming Webhooks を有効化する際にプライベートチャンネルを指定した場合は,プライベートチャンネルでも指定 URL を使えば投稿できてしまうため,ユースケースに合せて api/chat.postMessage
と選択する必要がありそうです.
また,Incoming Webhooks のレスポンスには ts
値は含みません.
特定のチャンネルでメッセージを検索する
api/conversations.history
会話の履歴を取得する場合は api/conversations.history
を利用します.
ここで "channel" はチャンネル ID のみ指定可能で,チャンネル名を指定するとエラーになります.
curl -X POST 'https://slack.com/api/conversations.history' \
-H "Content-Type: application/json" \
-H "Authorization: Bearer xoxp-xxxxxxxxxxxxxxxxxxxx" \
-d @- << EOF
{
"channel": "CHANNEL-ID",
"limit": 1
}
EOF
limit
を使って最新のチャットからいくつ取得するかを決めることができます.デフォルトは 100 です.
このレスポンスから text
を対象にチャットの文字検索も可能であり,ts
値の特定も一応可能です.
特定のチャンネルからいろいろなサービスの処理を発生させる
Slash Commands
スラッシュコマンドを使うと,メッセージ作成ボックスからアプリを呼び出すことができます.
呼び出されたアプリからは,処理が行われた結果を Slack に返答するようにします.
スラッシュコマンドの後ろには任意の引数をつけることができます.
引数はコマンドの実行により送られるリクエスト JSON 中の text
データに含まれており,適当にパースして処理に反映できます.
一方で,Slack がスラッシュコマンドを受信してから 3 秒以内に連携先アプリより HTTP 200 OK
が返ってこなかった場合は failed with the error "operation_timeout"
となります6.
時間がかかる処理については仕様上相性が悪く,何らかの方法で非同期的に処理できるように実装しなければなりません.
ここでは例として Google Apps Script (GAS) で WorldTimeAPI を叩くアプリを作成し,Slack のメッセージ作成ボックスから /time
コマンドを打ち込むことで WorldTimeAPI の datetime
を取得します.
また,引数には任意の timezones7 を付与できるものとし,引数がない場合は Asia/Tokyo
で問い合わせを行うものとします.
リクエスト元の検証には Basic Information
> App Credentials
> Verification Token
を参照して token
を利用します.
function doPost(e) {
const url = "http://worldtimeapi.org/api/timezone/";
const token = e.parameter.token;
const timezone = e.parameters.text[0] || "Asia/Tokyo";
// Verification
if (token === "xxxxxxxxxxxxxxxxxxxx") {
// Access WorldTimeAPI
const res = UrlFetchApp.fetch(url + timezone);
const jsonRes = JSON.parse(res);
return ContentService.createTextOutput(JSON.stringify(jsonRes.datetime));
} else {
throw new Error("Invalid request.");
}
}
注意
-
token
情報はe.parameter
,引数の情報はe.parameters
から取得しています.e.parameters
内の要素は全てリストなので注意してください. - 上記では
token
をハードコーディングしていますが,実装する際はハードコーディングしない工夫をしてください.
Event API
イベント API を使うと,チャンネルの設定に変更が行われたりメッセージが送られた (=トリガーとして設定したあるイベントが発生した) 場合に,行われた操作の情報を指定サーバーに送ることができます.
スラッシュコマンドと異なり毎回 Slack にメッセージを送り返す仕様ではないため,応答を Slack に返したい場合は 特定のチャンネル・チャットにメッセージを送る を参照して応答できるようにしてあげる必要があります.
また,引数を渡して処理を分岐させることもできません.
ただし,例えばチャンネルへの投稿をトリガとした (message.channels
8) 場合に event.blocks.elements.elements.text
に投稿したチャットの内容を含むため,うまく扱えばスラッシュコマンドのように引数として扱えるかもしれませんが,管理はかなり煩雑になると思われます.
接続先に指定できるサーバーの数も1つの Slack App につき 1 つまでのため,無料の範囲で量産するのも難しそうです.
- あるチャンネルで投稿される全ての投稿を監視する
- 特定の用途にのみ用いるチャンネルで処理を組み込む
といった実装の仕方が主なユースケースかもしれません.
Slack からの情報を受信する側は,接続検証のためにハンドシェイクを実装してあげる必要があります9.
ここでは,トリガ発火によって Slack からサーバに POST されるデータをそのまま Slack の別のチャネルに送り返す実装をしてみます.
検証に使われる token
は,スラッシュコマンドと同様に Basic Information
> App Credentials
> Verification Token
を参照します.
function doPost(e) {
const jsonReq = JSON.parse(e.postData.contents);
// Verification
if (jsonReq.token == "xxxxxxxxxxxxxxxxxxxx") {
// Post a chat
const strReq = JSON.stringify(jsonReq);
const jsonText = {"text": strReq};
const jsonPayload = JSON.stringify(jsonText);
UrlFetchApp.fetch("https://hooks.slack.com/services/xxxxxxxxxxxxxxxxxxxx", {
"method": "post",
"contentType": "application/json",
"payload": jsonPayload
});
// Handshake
return ContentService.createTextOutput(jsonReq.challenge);
} else {
throw new Error("Invalid request.");
}
}
注意
-
UrlFetchApp.fetch
の宛先に Slack Apps がイベントをリッスンしているチャネルを指定してしまうと無限ループが起きるので注意してください. - 上記では
token
とWebhook URL
をハードコーディングしていますが,実装する際はハードコーディングしない工夫をしてください. - 上記のまま実装すると
token
等も含まれて投稿されるため,動作確認を行うチャンネルにはご注意ください.
今回トリガとなるイベントには message.channels
8 を利用して,Slack App を適用したチャンネルに投稿があった場合に稼働するようにします.
Interactivity & Shortcuts
より高級なメッセージ投稿を行うことで,双方向的なやり取りを実装できます.
また自作アプリとの連携によって新しいモーダルを実装したり10,App Home に追加機能を持たせたり11することができます.
作りたいメッセージ/モーダル/App Home のイメージは,Block Kit Builder を用いることで簡単に膨らませることができます.
Slack 自体は適切に作成された blocks
要素を受信して高級な表示を行うだけですが,Block Kit Builder では左バーからボタンをクリックしたり,レビュー中の要素をドラッグ&ドロップしたりすることで簡単に blocks
要素を自動生成することができます.
Desktop/Mobile を切り替えれば,PC/スマホでどう見えるのかチェックしながら作成を行えます.
また,作ったメッセージやモーダルを Slack 上で確認したい場合は Send to Slack
をクリックすることでテスト送信もできます.
ここでは Block Kit Builder で作成した blocks
要素をイベント API で設定した自動応答に含める実装をしてみます.
詳しい手順は Event API を参照してください.
Builder で作成した blocks
{
"blocks": [
{
"type": "image",
"title": {
"type": "plain_text",
"text": "I Need a Marg",
"emoji": true
},
"image_url": "https://assets3.thrillist.com/v1/image/1682388/size/tl-horizontal_main.jpg",
"alt_text": "marg"
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "This is a section block with an accessory image."
},
"accessory": {
"type": "image",
"image_url": "https://pbs.twimg.com/profile_images/625633822235693056/lNGUneLX_400x400.jpg",
"alt_text": "cute cat"
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "Test block with users select"
},
"accessory": {
"type": "users_select",
"placeholder": {
"type": "plain_text",
"text": "Select a user",
"emoji": true
},
"action_id": "users_select-action"
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "Section block with a timepicker"
},
"accessory": {
"type": "timepicker",
"initial_time": "13:37",
"placeholder": {
"type": "plain_text",
"text": "Select time",
"emoji": true
},
"action_id": "timepicker-action"
}
},
{
"type": "actions",
"elements": [
{
"type": "button",
"text": {
"type": "plain_text",
"text": "Click Me",
"emoji": true
},
"value": "click_me_123",
"action_id": "actionId-0"
}
]
},
{
"type": "divider"
}
]
}
function doPost(e) {
const jsonReq = JSON.parse(e.postData.contents);
// Verification
if (jsonReq.token == "xxxxxxxxxxxxxxxxxxxx") {
// Post a great chat
const jsonBlocks = {
"blocks": [
{
"type": "image",
"title": {
"type": "plain_text",
"text": "I Need a Marg",
"emoji": true
},
"image_url": "https://assets3.thrillist.com/v1/image/1682388/size/tl-horizontal_main.jpg",
"alt_text": "marg"
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "This is a section block with an accessory image."
},
"accessory": {
"type": "image",
"image_url": "https://pbs.twimg.com/profile_images/625633822235693056/lNGUneLX_400x400.jpg",
"alt_text": "cute cat"
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "Test block with users select"
},
"accessory": {
"type": "users_select",
"placeholder": {
"type": "plain_text",
"text": "Select a user",
"emoji": true
},
"action_id": "users_select-action"
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "Section block with a timepicker"
},
"accessory": {
"type": "timepicker",
"initial_time": "13:37",
"placeholder": {
"type": "plain_text",
"text": "Select time",
"emoji": true
},
"action_id": "timepicker-action"
}
},
{
"type": "actions",
"elements": [
{
"type": "button",
"text": {
"type": "plain_text",
"text": "Click Me",
"emoji": true
},
"value": "click_me_123",
"action_id": "actionId-0"
}
]
},
{
"type": "divider"
}
]
};
const jsonPayload = JSON.stringify(jsonBlocks);
UrlFetchApp.fetch("https://hooks.slack.com/services/xxxxxxxxxxxxxxxxxxxx", {
"method": "post",
"contentType": "application/json",
"payload": jsonPayload
});
// Handshake
return ContentService.createTextOutput(jsonReq.challenge);
} else {
throw new Error("Invalid request.");
}
}
以上で Block Kit で作成した高級なメッセージが自動投稿されるようになりますが,更にアクションを追加して機能を拡張します.
Slack App では,ショートカットやモーダル,ボタンやメニューなどでアクションが Slack 上で行われた際に処理を行うサーバーを1つだけ指定することができます12.
ここでは GAS を使って先ほど自動送信されたメッセージの "type": "button"
要素に対するアクションを処理するサーバーも新たに作成します.
function doPost(e) {
const jsonReq = JSON.parse(e.parameter.payload);
// Verification
if (jsonReq.token == "xxxxxxxxxxxxxxxxxxxx") {
// Post a chat
const ts = jsonReq.message.ts;
// const webhookURL = jsonReq.response_url;
const webhookURL = "https://hooks.slack.com/services/xxxxxxxxxxxxxxxxxxxx";
const action_id = jsonReq.actions[0].action_id;
const action_value = jsonReq.actions[0].value;
if (action_id == "actionId-0") {
const message = action_value + "が送信されました";
const jsonText = {
"text": message,
"thread_ts": ts
}
const jsonPayload = JSON.stringify(jsonText);
UrlFetchApp.fetch(webhookURL, {
"method": "post",
"contentType": "application/json",
"payload": jsonPayload
});
}
} else {
throw new Error("Invalid request.");
}
}
注意
- イベント API では
e.postData.contents
を使っていましたが,Interactivity ではe.parameter.payload
を使います. -
const webhookURL = jsonReq.response_url;
とすると,アクションを発生させたメッセージを上書きすることができます. -
jsonReq.actions[0]
のようにactions
はリストなので注意してください.送られてくる リクエストの中身を確認しながら実装するのが良いと思います.
既存の Slack Apps を利用した拡張
独自の Slack App を利用した API の活用方法を先に書きましたが,基本的には既存の Slack Apps を優先的に使うことが推奨されます.
個人的に使いたいかも?と感じたものについて調べてみたのでこちらにまとめます.
Slack Developer Tools
Block Kit Builder からモーダルの見た目を確認するために必要になります.
その他にも /sdt whoami
コマンドでワークスペース ID やユーザ ID を調査したり,/sdt docs
で API ドキュメント13を Slack 上で参照することができます.
RSS
言わずと知れた Slack へのニュース配信設定です.
App 追加後,受信したいチャンネルで /feed subscribe
するだけで設定が完了します.
様々なサイトを登録できますが,登録しすぎると埋もれて読めなくなるだけだったりするので適当に数を絞ったり配信するチャンネルを変えたりするのが良いと思います.
また,日経電子版のトップニュースは NIKKEI for Slack を使うとよいと思います.
subscribe 用のコマンドを残しておきます.
# 日本のニュース系
## NHKニュース - 主要ニュース
/feed subscribe https://www.nhk.or.jp/rss/news/cat0.xml
## Nikkei Asia
/feed subscribe https://asia.nikkei.com/rss/feed/nar?_gl=1*17haoij*_ga*MTQ3MzYyOTE4OS4xNjkxMDM0MTg1*_ga_5H36ZEETNT*MTcwMTIyNTAwMS4xLjEuMTcwMTIyNTExNi4xOC4wLjA.
## 日テレニュース
/feed subscribe https://news.ntv.co.jp/rss/index.rdf
# 世界のニュース系
## BBC - Top Stories
/feed subscribe https://feeds.bbci.co.uk/news/rss.xml
## CNN - Top Stories
/feed subscribe http://rss.cnn.com/rss/edition.rss
## CNBC - Top News
/feed subscribe https://search.cnbc.com/rs/search/combinedcms/view.xml?partnerId=wrss01&id=1
# 趣味・興味系
## Publickey1
/feed subscribe https://www.publickey1.jp/atom.xml
## ITmedia
/feed subscribe https://rss.itmedia.co.jp/rss/2.0/topstory.xml
## Gigazine
/feed subscribe https://gigazine.net/news/rss_2.0/
GitHub
GitHub と Slack を簡単に連携でき,リポジトリの更新を通知したりログを残すことができます.
詳しい機能や設定手順はこちらの記事が参考になります.
GitLab
gitlab.com を利用している場合は,こちらの App を追加することで簡単に連携することができます.
self-managed な GitLab と連携するには 16.2 移行のバージョンを利用していてかつ Slack と疎通できる環境にある条件があり,自分で manifest から Slack App を作成14しなければ使えません.15
※ 一応,Slack 非公式な App で self-managed な GitLab と連携する方法もあるみたいですが,自分で作成した方が良いと思います...16
こちらでは,公式ドキュメントに沿って 16.5.0-ee.0
の GitLab との連携手順を示します.
GitLab の Admin Area
のリンクから App を作成することで manifest を利用した自前の Slack App を簡単に作成できます.
- GitLab の
Admin Area
でSetting
>General
>GitLab for Slack App
を開きます.Create Slack App
を押します.Slack API のページに移動しますが,URL を見ると manifest が送られていることが確認できます.
- Slack API のページから
Create an App
を押し,利用する Slack ワークスペースを指定して App を作成します.作成後,ワークスペースにインストールします. - GitLab から送られた manifest を確認・編集したい場合は,App の
App Manifest
から行えます. - GitLab に戻り
Enable GitLab for Slack app
にチェック後,Slack App の App Credentials の値を書き写してSave changes
を行うことで設定完了です.
個人的には GitLab を閉じた環境で利用しているためうまく使えそうにないですが...
Axe AI
OpenAI の GPT-3.5 および GPT-4 で構築された自然言語チャットボットです.
1 日に 10 回の質問までは無料で利用できます.
連続で投げた質問は関連性を保持して会話として返答してくれるみたいです.
また,日本語で聞いた場合は日本語でも返答してくれるみたいです.
Slack と ChatGPT の連携は Open AI API と 特定のチャンネルからいろいろなサービスの処理を発生させる を参照して自分で実装してもよいのですが,自前で "operation_timeout"
の対応をしないといけないのが面倒で,かつ 1 日に何回も利用するわけでもなかったので検討しました.
会話したデータは 1 年間保持され,削除依頼をすることもできるようですが,個人情報を流さないように十分に気を付ける必要があります.
Open Poll
Slack で投票を簡単に行うことができます.
/poll "What's you favourite color ?" "Red" "Green" "Blue" "Yellow"
Workspace に設定する拡張
Slackbot
Slackbot は Slack App ではありませんが Slack を機能拡張する1つの方法となり得る感じたのでこちらに記載します.
アイディアはこちらの記事に基づきます.
Slackbot の設定は https://your-workspace.slack.com.slack.com/customize/slackbot から行います.
個人情報や機密情報を覚えさせることは推奨できませんが,ランダムな応答を得たり備忘録を残すには役立つと感じました.
所感
長くなってしまいましたが,Slack には制限がありつつも様々な方法でチャットツール統合・拡張を行う手段があることがわかりました.
一方,スラッシュコマンド と Interactivity & Shortcuts でリクエストに含まれるデータの構造が異なるなど多少の癖があることもわかりました.
また,閉じた環境で使っている self-managed なサーバとの連携は難しいと感じました.
これからどう応用できるか考えてみたいと思います.