GASを使ってslackユーザーデータを取得する(1000件以上対応)

お久しぶりです。カインです。
転職して1年が経過し、色々なGASやSlackアプリを作ったのでぼちぼち記事にしてきたいと思います。

Slackのユーザー情報の呼び出しAPIusers.list)、何かと便利ですよね。
最初はユーザー情報が欲しい時はusers.list APIで毎回呼び出して使用していましたが、実行時間を削減したりスプレッドシートで活用したく、取って来たデータをJSONスプレッドシートに保存して使っています。

今回は、users.listの呼び出し、JSONファイルの保存、スプレッドシートへの書き込みの3つ記事に分けて紹介していきます。

この記事で紹介すること

  • slack API users.list の使い方

slack API users.listの使い方

slackアプリの作成・権限設定

まずはslackアプリを作成し、users.listに必要な権限設定を行います。

アプリの作成

slack左上のメニュー>設定と管理>アプリを管理する

ビルドをクリック

Create New Appをクリック

From scratchをクリック

App Nameに任意の名前を入れて、Pick a workspace to develop your app inにインストール先のワークスペースを選択します。

ここでusers.listに必要な権限を確認します。
https://api.slack.com/methods/users.listを開き、

ここを確認すると、users:read が必要なようです。

が…!これには罠があって、読み進めていくと、

メールアドレスを取得するにはusers:read.emailも必要とのこと!
Required scopesのところに書いておいて欲しいですねぇ…。

権限の設定

OAuth & Permissionsをクリック

少し下がったところにあるScopes>Bot Token Scopesの下にあるAdd an OAuth Scopeをクリック

users:readを探してクリック

users:read.emailを探してクリック

OAuth & Permissionsのページ上部にあるInstall to Workspaceをクリック

内容を確認して、許可するをクリック

これでslackアプリの準備は完了です。
後ほどGASで使用する情報を確認しておきます。

OAuth & Permissionsのページ上部にBot User OAuth Token(xoxb-から始まるもの)があるので控えておきます。

GASの作成・実行

新規プロジェクトの作成

任意のGoogleドライブのフォルダに新規プロジェクトを作成します。

適当にファイル名を付けます

コード作成の前に

ここでusers.listをもう一度確認します。

Argumentsのところで、tokenのみRequiredが付いているので、users.listtokenだけあれば実行は可能です。

ただし、読み進めていると、

一度に1000件までしか取得できないようで、1000件を超える場合はcursorが必要になるようです。
弊社は1000ユーザーを超えているので、cursorも含めたコードを作成します。

処理の流れ

  1. 空のcursorと配列を定義
  2. users.listを呼び出し、配列にuser情報を格納
  3. responseのnext_cursorに値があれば、再度users.listを実行(1000件を超えている場合next_cursorに値が入る)
  4. next_cursorが空であれば繰り返しを終了

トークンの設定

コード内に直接トークンを記載するのはリスクがあるため、スクリプトプロパティ内に設定します。

歯車アイコンをクリック

ページ下部のスクリプト プロパティを追加をクリック

プロパティは任意ですが、bot_tokenとしておきます。
値には、slackアプリの設定で控えた、Bot User OAuth Tokenを貼り付けて保存します。

コードの構成

APIリエクストを複数繰り返す(場合もある)ので、メインの関数とAPIリクエストの関数を分けて作成します。

  • Main
    • 実行用の関数です。
  • callUsersList
    • APIリクエスト用の関数です。
    • Mainから呼び出して使用します。

コード

function Main() {
  let cursor = "";
  let users_data = [];

  // users.listは1度で1000件までしか取得できないので、1000件を超える場合(next_cursorに値がある場合)繰り返す
  do {
    let response = callUsersList(cursor);
    cursor = response["response_metadata"]["next_cursor"]; // こちらの説明は後述
    users_data.push(...response["members"]); // こちらの説明は後述
  } while (cursor != ""); // next_cursorに値が無い場合=全て取得し終わった場合、ループを終了する

  // ユーザーデータをJSON文字列に変換
  const users_data_str = JSON.stringify(users_data);

  Logger.log(users_data_str);
}


function callUsersList(cursor) {
  // スクリプトプロパティに設定したbot_tokenを呼び出す
  const token = PropertiesService.getScriptProperties().getProperty("bot_token")

  const url = "https://slack.com/api/users.list" //APIのURL

  // payloadを設定
  const payload = {
    "token": token,
    "cursor": cursor
  }

  // optionsを設定
  const options = {
    "method": "GET",
    "payload": payload,
    "headers": {
      "contentType": "x-www-form-urlencoded",
    }
  }

  // APIリクエストの結果をresponseに格納
  const response = UrlFetchApp.fetch(url, options);

  // responseの文字列をJSON解析しオブジェクト化して返す
  return JSON.parse(response);
}

一部解説

next_cursorの取り出し

users.listのresponseデータ(以下参照)の中に以下の部分があります。

"response_metadata": {
        "next_cursor": "dXNlcjpVMEc5V0ZYTlo="
    }

この場合は、response_metadata.next_cursorに値があるので、
do~while内の

    cursor = response["response_metadata"]["next_cursor"];

この部分でcursorにdXNlcjpVMEc5V0ZYTlo=が入ります。
cursor=""ではないため、cursorを引数としてcallUsersListに渡して再度APIを呼び出します。

続きが無い場合、response_metadata.next_cursorは

	"response_metadata": {
		"next_cursor": ""
	}

と返ってくるため、cursorには""が入り、do~whileループから抜けます。

メンバー情報の取り出し・格納

do~while内に以下の部分があります。

    users_data.push(...response["members"]);

user_dataは最初は空の配列で、1回APIを呼び出す毎に取得したユーザーデータ(members)を格納しています。
response["members"]の前にある...は、簡単に言うと配列の[]を外すような処理をします。

サンプル
test_dataがresponse["members"]と思ってください。

function sample() {
  let arr1 = [];
  let arr2 = [];

  const test_data = [
    { "name": "a" },
    { "name": "b" },
    { "name": "c" }
  ];

  arr1.push(test_data);
  arr2.push(...test_data);

  Logger.log(arr1);
  Logger.log(arr2);
}

実行結果

(参考)responseデータ(JSON)

users.listから引用

{
    "ok": true,
    "members": [
        {
            "id": "W012A3CDE",
            "team_id": "T012AB3C4",
            "name": "spengler",
            "deleted": false,
            "color": "9f69e7",
            "real_name": "spengler",
            "tz": "America/Los_Angeles",
            "tz_label": "Pacific Daylight Time",
            "tz_offset": -25200,
            "profile": {
                "avatar_hash": "ge3b51ca72de",
                "status_text": "Print is dead",
                "status_emoji": ":books:",
                "real_name": "Egon Spengler",
                "display_name": "spengler",
                "real_name_normalized": "Egon Spengler",
                "display_name_normalized": "spengler",
                "email": "spengler@ghostbusters.example.com",
                "image_24": "https://.../avatar/e3b51ca72dee4ef87916ae2b9240df50.jpg",
                "image_32": "https://.../avatar/e3b51ca72dee4ef87916ae2b9240df50.jpg",
                "image_48": "https://.../avatar/e3b51ca72dee4ef87916ae2b9240df50.jpg",
                "image_72": "https://.../avatar/e3b51ca72dee4ef87916ae2b9240df50.jpg",
                "image_192": "https://.../avatar/e3b51ca72dee4ef87916ae2b9240df50.jpg",
                "image_512": "https://.../avatar/e3b51ca72dee4ef87916ae2b9240df50.jpg",
                "team": "T012AB3C4"
            },
            "is_admin": true,
            "is_owner": false,
            "is_primary_owner": false,
            "is_restricted": false,
            "is_ultra_restricted": false,
            "is_bot": false,
            "updated": 1502138686,
            "is_app_user": false,
            "has_2fa": false
        },
        {
            "id": "W07QCRPA4",
            "team_id": "T0G9PQBBK",
            "name": "glinda",
            "deleted": false,
            "color": "9f69e7",
            "real_name": "Glinda Southgood",
            "tz": "America/Los_Angeles",
            "tz_label": "Pacific Daylight Time",
            "tz_offset": -25200,
            "profile": {
                "avatar_hash": "8fbdd10b41c6",
                "image_24": "https://a.slack-edge.com...png",
                "image_32": "https://a.slack-edge.com...png",
                "image_48": "https://a.slack-edge.com...png",
                "image_72": "https://a.slack-edge.com...png",
                "image_192": "https://a.slack-edge.com...png",
                "image_512": "https://a.slack-edge.com...png",
                "image_1024": "https://a.slack-edge.com...png",
                "image_original": "https://a.slack-edge.com...png",
                "first_name": "Glinda",
                "last_name": "Southgood",
                "title": "Glinda the Good",
                "phone": "",
                "skype": "",
                "real_name": "Glinda Southgood",
                "real_name_normalized": "Glinda Southgood",
                "display_name": "Glinda the Fairly Good",
                "display_name_normalized": "Glinda the Fairly Good",
                "email": "glenda@south.oz.coven"
            },
            "is_admin": true,
            "is_owner": false,
            "is_primary_owner": false,
            "is_restricted": false,
            "is_ultra_restricted": false,
            "is_bot": false,
            "updated": 1480527098,
            "has_2fa": false
        }
    ],
    "cache_ts": 1498777272,
    "response_metadata": {
        "next_cursor": "dXNlcjpVMEc5V0ZYTlo="
    }
}

実行

保存してターゲットをMainにして実行ボタンを押します。

実行結果

なが~~いJSON文字列が表示されれば成功です!

まとめ

ここまで随分長かった気がしますが(記事書き始めて2時間)取って来たデータを活用することが目的なので、まだスタートラインに立ったに過ぎません。
次回の記事では、取得したデータをJSONファイルとしてGoogleドライブに格納したり、それを読み込んだり、スプレッドシートに書き込むやり方を紹介します。

続き