やりたいこと
Slackメッセージの三点メニューから取得できるリンクのみを使って、任意のタイミングでSlack APIを使ってそのメッセージに返信したい。
How to retrieve Slack messages via API identified by permalink? - Stack Overflow
この記事を参考に、今回の記事を作成しました。
一部JavaScriptを使っていますが、メッセージリンク(URL)の加工をしているだけなので、ほかの言語でも同じロジックを組めば再現できると思います。
使用するメッセージリンクのサンプル
Slackのリンクは下記の構成であることを想定しています。もし、違ったパラメータ等を含んでいるときはうまく調整しながら参考にしてください。
👇親スレッドのリンクをコピーしたとき
https://<サブドメイン>.slack.com/archives/<Conversation ID>/p<ドットを除いたメッセージの作成日時のタイムスタンプ>
👇親スレッドに返信されたメッセージのリンクをコピーしたとき
https://<サブドメイン>.slack.com/archives/<Conversation ID>/p<ドットを除いたメッセージの作成日時のタイムスタンプ>?thread_ts=<スレッドのメッセージの作成日時のタイムスタンプ ドット付き>&cid=<Conversation ID>
上記どちらのリンクを使った場合でも、返信は親スレッドに対して行う想定でロジックを組み立てていきます。
名称 | 説明 |
---|---|
サブドメイン | slackワークスペース名 |
Conversation ID/チャンネルID | メッセージが存在するチャンネルのID。チャンネルIDと呼ぶほうが多い? |
ドットを除いたメッセージの作成日時のタイムスタンプ | メッセージが作成されたときの小数点以下第6位まであるUNIX時刻からドットを除いた値 例:1701232196111111の場合、2023年11月29日13時29分56.111111秒 Slack APIドキュメントではドットが付いているUNIX時刻の値を thread_ts と呼んでおり、メッセージのIDのようなもののようです。 |
事前準備(Slackアプリ作成)
こちらの記事を参考にSlackアプリケーションを作成します。
Slackアプリケーションにアプリ作成者の権限を委任しておくことで、あとの作業で行うメッセージへの返信ができるようになると思います。
写真ではほかにも権限つけていますが、今回の用途では chat:write
を「User Token Scopes」に指定します。(ほかの4つは不要です)
Install to Workspace
ボタンを最後に押して、許可しておきます。
最後に「OAuth Tokens for Your Workspace」から「User OAuth Token」の値をコピーしておきます。
メッセージに返信する(JavaScript)
下記 extractChannelIDThreadTS
関数を参考に、リンクからチャンネルIDとメッセージの作成日時(UNIX時刻)を取得します。
この2つの値があればメッセージに返信することができます。
/**
* extractChannelIDThreadTS関数
* SlackメッセージのURL(パーマリンク)から、チャンネルIDとメッセージのts(タイムスタンプ)を取得します。
*
* @param {string} permaLink slackメッセージのURL
* @returns {channelId, threadTS}
*/
function extractChannelIDThreadTS(permaLink) {
// trueの場合、permaLinkがスレッドの親のURLではなく、スレッドに返信したメッセージのURLだと判断する
const isIncludesthread_ts = permaLink.includes('thread_ts')
// URLをスラッシュで分解
const splitPermaLink = permaLink.substring(8).split('/')
// チャンネルID
const channelId = splitPermaLink[2]
// 返信するスレッドの親メッセージのts_thread
let threadTS = null
if (isIncludesthread_ts) {
const indexOfthread_ts = permaLink.indexOf('thread_ts=') + 10
const thread_ts_length = 17
threadTS = permaLink.substring(indexOfthread_ts, indexOfthread_ts + thread_ts_length)
} else {
// 先頭'p'の1文字を切り抜き、UNIX時刻になるようドット'.'を入れる
threadTS = splitPermaLink[3].substring(1).substring(0, 10) + '.' + splitPermaLink[3].substring(1).substring(10)
}
return {channelId, threadTS}
}
// 以下例
const slackPermaLink = `https://<サブドメイン>.slack.com/archives/<Conversation ID>/p<ドットを除いたメッセージの作成日時のタイムスタンプ>`
const {channelId, threadTS} = extractChannelIDThreadTS(slackPermaLink)
console.log(channelId, threadTS)
// C05PB1R852L 1701336881.449019
次に replySlackMessage
関数を使ってSlack APIでメッセージに返信します。
headers
の Authorizatin
に直接トークンを書いていますが、ここは環境変数で置き換えてください。 xoxb
から始まるトークンはボットトークンです。
/**
* replySlackMessage関数
* Slackメッセージに返信します。
*
* @param {string} channel 返信するメッセージがある場所のチャンネルID
* @param {string} thread_ts メッセージのts(UNIXタイムスタンプ)。小数点以下第6位まで必要。
* @param {string} text 返信するテキスト
*/
async function replySlackMessage(channel, thread_ts, text) {
const slackReqUrl = `https://slack.com/api/chat.postMessage`
const resSlack = await fetch(slackReqUrl, {
method: 'POST',
body: JSON.stringify({channel, thread_ts, text}),
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer xoxb-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
}
})
if (!resSlack.ok) {
alert('Slackスレッドの返信に失敗しました。')
}
}
// 以下例
const replyMessage = '<@usercode>\n今日のTODO\n<https://www.google.co.jp/|Google トップ>'
replySlackMessage(channelId, threadTS, replyMessage)
上記のマークダウンに近いかたちで replyMessage
を使うと
下記の見た目で返信されます。
@usercode
今日のTODO
Google トップ
以上で終わりです。
別言語でも同じロジックを組みさえすれば、同じことができると思います。
追記(フロントエンドからの送信時のCORSエラー)
フロントエンドから、Slack APIをたたくとCORSエラーになってしまうため、リクエストの Content-Type
を application/x-www-form-urlencoded; charset=utf-8
にするとうまくいきました。
なので replySlackMessage
関数は下記に変更しました。
/**
* replySlackMessage関数
* Slackメッセージに返信します。
*
* @param {string} channel 返信するメッセージがある場所のチャンネルID
* @param {string} thread_ts メッセージのts(UNIXタイムスタンプ)。小数点以下第6位まで必要。
* @param {string} text 返信するテキスト
*/
async function replySlackMessage(channel, thread_ts, text) {
const slackReqUrl = `https://slack.com/api/chat.postMessage`
const body = `token=${トークンの値そのまま}&channel=${channel}&thread_ts=${thread_ts}&text=${text}`
const resSlack = await fetch(slackReqUrl, {
method: 'POST',
body,
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8',
}
})
if (!resSlack.ok) {
alert('Slackプロジェクトスレッドへの返信に失敗しました。')
}
}
こちらの記事を参考にさせていただきました。