見出し画像

【GAS】Slackのチャンネルリストを書き出すコード2通りとページ送りのメモ

概要

GASで、APIを利用してSlackのチャンネル情報を取得し、指定した情報をアクティブシートに書き出す関数を2通り書いたのでメモしてみました。記事の最後に、ページを巡回しながらfetchしていく考え方についてもメモしています。

実行結果

実行結果はこのような感じ。

Slackチャンネルリストをシートに出力したところ

特徴

チャンネル数が多くても、全ページを巡回し、すべてのチャンネルのリストを取得していくコードです。

前提

slackのBotへの権限付与やトークンの取得は済んでいるという前提です。
まだの場合は、slack APIのリファレンス、下記などを参考に設定が必要です。

コード

同じ動作をするコードを、以下の2通りで書いています。
1)一体型のコード
Botに権限を付与し、APIからトークンを取得し、
このトークンをコードにベタ書きすれば、コピペで動きます
(2)関数切り分け型のコード
(1)の関数を機能ごとに切り分けたもの。
トークンを事前にプロパティストアに保存する必要あり。
ひと手間かかりますが、より安全なコード。

(1) 一体型のコード

/** Slackのチャンネル情報の一部を取得してアクティブシートに書き出す関数 
 *事前にslackのボットに権限を付与して、トークンを入手しておく必要あり
 *'ここにトークンをベタ書きで入力'の部分にトークンを入力しておく必要あり
 */
function setSlackChannelInfoInSheet() {

  const channelInfoArr = [];
  let cursor; //whileの外で宣言だけして未定義undefinedにしておく(空文字にしないこと!そうするとwhileが最初からfalseになって実行されなくなってしまうので)
  while (cursor !== '') { //最終頁ではnext_cursorが空文字列となることを利用したwhile文で全ページを巡回
    const token = 'ここにトークンをベタ書きで入力'; // propertyストアの使い方が分かっている方はそれを使用
    const url = 'https://www.slack.com/api/conversations.list';

    const options =
    {
      method: "get",
      contentType: "application/x-www-form-urlencoded",
      headers: { "Authorization": "Bearer " + token },
      "payload": {
        "token": token,
        "limit": 200, 
     //1ページ当たりのlimitの設定上限は1000だがメソッドによっては上限まで取れない可能性。
         //slack推奨は1ページあたり100-200件(https://api.slack.com/docs/pagination)
        "cursor": cursor
   }
    }
  
  //fetchメソッドによる情報取得
  const response = UrlFetchApp.fetch(url, options);
  const conversationsListObj = JSON.parse(response);

  //シートのヘッダーとなるレコードを配列に追加
  channelInfoArr.push(['チャンネル名', 'チャンネルID', 'チャンネル概要','アーカイブ済'])
  //取得したチャンネルごとに、チャンネル名、チャンネルID、チャンネル概要、アーカイブ済かどうかのレコードを配列に追加
  conversationsListObj.channels.forEach(channel => channelInfoArr.push([channel.name, channel.id, channel.purpose.value, channel.is_archived]));
  //次のページをリクエストするためのnext_cursorをレスポンスから抽出してcursorに代入(これがoptionsに入るため、次のページが取れる)
  cursor = conversationsListObj.response_metadata.next_cursor;
}

//アクティブシートに書き出し
const sheet = SpreadsheetApp.getActiveSheet();
sheet.clear()
  .getRange(1, 1, channelInfoArr.length, channelInfoArr[0].length)
  .setValues(channelInfoArr);
}

(2) 関数切り分け型のコード

/** 
 *Slackのチャンネル情報の一部を取得してアクティブシートに書き出す関数
 *事前にslackのボットに権限を付与して、トークンを入手しておく必要あり
 *SLACK_BOT_USER_OAUTH_TOKENをプロパティストアに保存しておく必要あり
 *NOTE:
 *最終頁ではnext_cursorが空文字列となる。
 *slackの推奨は1ページあたり100-200件の取得
 *1ページあたりの最大は1000件だがこれはメソッドに依存し
 *1000件指定してもその通りの実行が確保されるわけではない
 *上記2点の出典:https://api.slack.com/docs/pagination
 *youtube APIではページトークンが最終頁でundefinedになるが、slackでは空文字なので
 *cursorを宣言するときは空文字で初期化せず、未定義にしておく(さもないとwhile文は一切実行されない)
 */
function setSlackChannelInfoInSheet() { // 実行関数
  const channelInfoArr = [];
  let cursor;
  while (cursor !== '') {
    const obj = fetchConversationsListObj(cursor);
    extractChannelInfoAndPushIntoArr(obj, channelInfoArr);
    cursor = getCursor(obj);
  }

  setArrInSheet(channelInfoArr, 'slackChannelList')
}

/**
 * SlackのConversationsListをオブジェクトにして取得する関数
NOTE:slackの推奨は1ページあたり100-200件の取得(https://api.slack.com/docs/pagination)
 * @param {string} cursor - whileの1周目ではundefinedが渡され、以降のは前頁で取得したnext_cursorが渡される
 * @return {Object} fetchしたresponseをparseしたオブジェクト
 */
function fetchConversationsListObj(cursor) {
  const token = PropertiesService.getUserProperties().getProperty('SLACK_BOT_USER_OAUTH_TOKEN');
  const url = 'https://www.slack.com/api/conversations.list';

  const options =
  {
    method: "get",
    contentType: "application/x-www-form-urlencoded",
    headers: { "Authorization": "Bearer " + token },
    "payload": {
      "token": token,
      "limit": 200, 
      "cursor": cursor 
    }
  }

  const response = UrlFetchApp.fetch(url, options);
  const conversationsListObj = JSON.parse(response);
  return conversationsListObj;
}

/** 
 * fetchのレスポンスのオブジェクトから情報を抽出して配列に追加
 * @param {Object} obj – fetchしたレスポンスのオブジェクト
 * @param {Array} arr – 指定した情報を保存していく配列
 */
function extractChannelInfoAndPushIntoArr(obj, arr) {
  arr.push(['チャンネル名', 'チャンネルID', 'チャンネル概要','アーカイブ済']) //シートのヘッダとなるレコードを配列の1要素目に入れる
  obj.channels.forEach(channel => arr.push([channel.name, channel.id, channel.purpose.value, channel.is_archived]));
}

/** 
 * fetchのレスポンスのオブジェクトからnext_cursorを取り出して返す関数
 * @param {Object}} obj – fetchしたレスポンスのオブジェクト
 * @return {string} 次のページをfetchするためのnext_cursor(レスポンスオブジェクトに含まれる)
 */
function getCursor(obj) {
  const cursor = obj.response_metadata.next_cursor;
  return cursor;
}

/** 
 * 受け取った配列をアクティブシートに書き出す関数 
 * @param {Array}} arr – レスポンスから取得した情報を持つ二次元配列
 * @param {string} sheetname - 出力先シートのシート名
 *
 */
function setArrInSheet(arr) {
  const sheet = SpreadsheetApp.getActiveSheet();
  sheet.clear()
    .getRange(1, 1, arr.length, arr[0].length)
    .setValues(arr);
}

参考URL

Slack APIのconversations.listメソッド

conversations.list API method

[補足]APIのレスポンスのページを進んでいくコードについて

次のページ、次のページと自動的にリクエストするコードを書く時、私は以下のようにしています。

 (1)まず、APIリファレンスを読み、次のページを示すプロパティの名前、型などを調べる
(今回の場合、上記参考URLで、プロパティ名はnext_cursor、型はstring、最終頁では空文字となる、と書かれています。最終頁はAPIによっては空文字ではなくundefinedだったり、と仕様が異なるので、使いたいAPIのリファレンスを都度読みます。APIによっては、cursorをoptions内で指定するのではなく、urlに埋め込むコードを書かなければならない場合もあるかも)

(2)レスポンスオブジェクトをログ出力して、(1)で調べたプロパティがオブジェクトの階層構造のどこにあるかを確認する*(リファレンスでも確認できることが多いですが、念のため)

(3)このプロパティを代入していく変数を宣言する。宣言する場所は、while文やfor文など、繰り返しfetchしてチャンネルを取得するブロックの外(前)。(ブロック内で宣言すると、次のループで新たにfetchする前に情報が失われてしまうので)

(4)このプロパティを(ドット記法などで)取り出して、(3)で宣言した変数に代入する。代入する場所は、fetchを行うwhile文やfor文の中。(ループごとに取得する情報なので)

*今回、next_cursorがレスポンスオブジェクトのどの階層にあるかをログ出力して確認するには、上のコードで"limit":200と書いているところを、"limit":3などのように(テスト用に一時的に)少なく書き換えてからconsole.log(conversationsListObj)をするとよさそうに思います。
1ページあたり1000件といった状態でログ出力すると、出力の後半が省略されてしまい、階層構造の全体像が見られない可能性があるからです。


この記事が気に入ったらサポートをしてみませんか?