【小ネタ】Slack APIで、スレッドのメッセージを取得する

オペレーション部 江口です。

社内でちょっとしたSlack連携をしたいという話があり、少しSlack APIをいじっています。

利用されている方はご存知の通り、現在のSlackにはある話題についてスレッドを作成できます。 今回の検証であるスレッドのメッセージを(イベントをトリガーにして)全て抜き出してみるということをやってみました。 小ネタなんですが、結構調べるのに時間をかけたので自分のメモ代わりに残しておきます。

使用フレームワーク

せっかくなのでSlack APIを手軽に扱えるというSlack社製のフレームワーク、Boltを検証で使ってみていますので、本記事のサンプルはBoltでのものとなります。 Boltについてはこのdevelopers.ioでもすでに紹介記事があるので、そちらをご参照ください。

SlackのBoltフレームワークを使ってWordPressをSlackから検索するBotを作ってみた

また、上の記事でも紹介されていますがSlack公式のチュートリアルが充実していますので、Boltを最初に触る場合にはまずこのチュートリアルを読むと良いでしょう。

Hello World, Bolt! ⚡️ Bolt フレームワークを使って Slack Bot を作ろう

チュートリアルで利用するglitchは軽く試すにはものすごく便利なのでおすすめです。

概要

さて本題ですが、Slack APIでスレッドのメッセージを取得する場合、以下のような流れになります。

  1. 取得したいスレッドのthread_tsを特定する
  2. 取得したthread_tsをキーとして、conversation.repliesでクエリをかけ、結果を取得する

まあこれだけといえばこれだけです。

thread_ts

thread_tsはスレッドごとに一意の値です。tsは「タイムスタンプ」のことで、スレッドの親メッセージのタイムスタンプの値のようです。 ともかく同じスレッドのメッセージであれば同じthread_tsの値を持っています。 また、このthread_tsはそもそもスレッドに紐づいた情報にしかない属性なので、この有無をメッセージがスレッドに属するかの判定に利用することもできます。

conversation.replies

conversation.repliesは、ある会話のリプライを取得するというそのままの機能なAPI関数です。

詳しい利用方法は Slackのリファレンス(https://api.slack.com/methods/conversations.replies)を参照いただくのが早いと思います。

パラメータとしてチャンネルのID、スレッドの親メッセージのタイムスタンプ(=thread_ts)を指定する必要があります。

boltを利用する場合、以下のように記述します。

※大文字になっている部分(SLACK_OAUTH_TOKEN)などはそれぞれ実際の値に置き換える必要があります

const replies = await slackApp.client.conversations.replies({
    token: SLACK_OAUTH_TOKEN, 
    channel: CHANNEL_ID,
    ts: THREAD_TS,
    inclusive: true
});

実際のメッセージは、帰ってくる情報の中のmessagesというキーに配列で格納されます。 ただ、私が検証した限りでは、必ずしもきちんとした順番でメッセージが格納されるわけではないようです。(順番に取得できる場合もありますが、そうでない場合もありました) messagesには実際のメッセージの文字列(text)の他にタイムスタンプ(ts)も含まれるので、順番を揃える場合にはこのtsの値を使ってソートする必要があります。

注意事項:Slack Appのスコープの設定

conversation.repliesをSlack Appで利用する場合、事前にAppのスコープとしてチャンネルの履歴(またはIMの履歴)の読み取りを指定しておく必要があります。 Slack Appの設定から、[OAuth & Permissions]->[Scopes]->[Add an OAuth Scope]でスコープとしてchannnels:history(IMの履歴の取得の場合はim:history)を追加してください。

thread_tsの取得(メッセージイベントを利用する方法)

さて、取得したいスレッドのthread_tsが分かれば良い、ということは分かりました。では特定のスレッドのthread_tsはどうすれば取得できるでしょう。

方法はいくつかありますが、例えばSlack Appのevent subscriptionでメッセージの投稿イベントをSubscribeする(message.imまたはmessage.channels)方法があります。 この場合、メッセージが投稿されるたび、そのメッセージの情報をAppで受け取ることができますが、この情報にthread_tsが含まれます。

Boltでは、message()メソッドでメッセージを受け取ることができます。 この時パラメータとしてマッチする文字列を指定することで、特定のメッセージにのみ反応するようにもできます。例えば以下の例の場合、?(:+1:)が含まれるメッセージが送られてきた場合に処理を行います。

slackApp.message(':+1:', async ({ message, say }) => {
    //実際の処理
}

参考までに、受け取ったスレッドの子メッセージのデータをdumpした情報を以下に載せておきます。 (Boltでメッセージのデータをdumpする方法は、前掲の当ブログのBolt紹介記事で紹介されています。

{ client_msg_id: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
 type: 'message',
 text: 'テスト :+1:',
 user: 'XXXXXXXXX',
 ts: '1571796593.005300',
 team: 'XXXXXXXXX',
 thread_ts: '1571797440.006700',
 parent_user_id: 'UPE2A65J5',
 channel: 'XXXXXXXXX',
 event_ts: '1571796593.005300',
 channel_type: 'channel' }

ちゃんとthread_tsが属性として含まれていますね。また、conversation.repliesで指定が必要なチャンネルIDの情報も含まれています。 受け取ったこのデータを利用すれば、「?を入れたメッセージが含まれるスレッド」の情報を取得することができそうです。

サンプルコード

上記の例の場合の全体のサンプルコードは次のようになります。

const { App } = require('@slack/bolt');
const store = require('./store');

//Slack API用のトークン、SIGNING SECRETの指定
const slackApp = new App({
  signingSecret: process.env.SLACK_SIGNING_SECRET,
  token: process.env.SLACK_BOT_TOKEN
});

//「いいね」の絵文字が含まれるメッセージを受け取った場合の処理
slackApp.message(':+1:', async ({ message, say }) => {
  //「いいね」を受け取った旨の投稿
  say('「いいね」受け取りました!');
  //スレッドだった場合、スレッドのメッセージ履歴を取得し投稿する
  if (message.thread_ts){
    try {
      const replies = await slackApp.client.conversations.replies({
        token: process.env.SLACK_OAUTH_TOKEN,
        channel: message.channel,
        ts: message.thread_ts,
        inclusive: true
      });
      //メッセージのタイムスタンプ順ソート
      replies.messages.sort(function(a,b){a.ts-b.ts})
      //メッセージの投稿
      for (const idx in replies.messages){
        say(`${replies.messages[idx].text}`);
      }
    }
    catch (error) {
      console.error(error);
    }
  //スレッドではない場合、当該メッセージのみを投稿
  }else{
    say(`${message.text}`)
  }
});
  • 元の投稿

  • それを受けてのAppの投稿

終わりに

以上、「特定の絵文字が投稿された場合に、そのスレッドの情報を取得する」という形で、スレッド情報の取得方法の例を紹介しました。

ただ、絵文字をメッセージとして投稿するのではなく、リアクションとして付けた場合に同じような処理をしたい、という風に思われる方もいると思います。 実は私も本来そうしたいのですが、これはSlackのAPIの仕様上少し手間のようです。 (リアクションが追加された際のイベント情報に当該メッセージのthread_tsが含まれないため、これを別の方法で取得する必要があります) これについてはうまく出来ればまた別途共有したいと思います。

というわけで今回は以上です。読んでいただいてありがとうございました。