slackユーザーデータをJSONファイルとしてGoogleドライブに保存する

前回の記事の続きです。

前回:
GASを使ってslackユーザーデータを取得する(1000件以上対応) - シンプルに暮らしたい情シスのブログ
次回:


今回はAPIリクエストの結果(ユーザーデータ)をJSONファイルとしてGoogleドライブに保存するGASを作っていきます。

JSONファイルとして保存するメリット

リクエスト回数の節約

APIには「1時間に〇回まで」や、「1分間に〇回まで」といった制限が設けられていることが多いです。
今回使ったslack API(users.list)の場合、Rate limitsを見ると Tier2となっています。

Tier2のところをクリックすると、飛べるRate Limitsの解説ページではこのように記載があり、どうやら1分間に20回程度は行えるようです。

1000件を超えるユーザー数の場合は、1度の取得で2回以上のリクエストを行うため、実行回数によってはオーバーしてしまう可能性があります。
そこで1日に1回更新する程度で良いようなデータは、JSONファイルとして保存し、users.list APIを使う代わりにJSONファイルを読み込めば、APIの実行制限を超えるようなケースは避けられます。

呼び出し速度の向上

APIは便利かつ最新の情報を取得できますが、slackのサーバーへリクエストし、応答を待つ時間がかかります。
JSONファイルを読み込む場合もそれなりの時間はかかりますが、APIリクエストよりは早くなります。

事前準備

保存先フォルダ―IDのスクリプトプロパティ設定

今回は保存先フォルダ―のIDが必要となるため、スクリプトプロパティにセットしておきます。

まずはフォルダーを作成します(任意)

フォルダを開き、URLの folders/ より後ろの部分をコピーします。
(フォルダを右クリックしてリンクを取得からでもいけます)

GASを開いて歯車アイコン>スクリプトプロパティを編集

スクリプトプロパティを追加

プロパティ名を適当に決めて、値に先ほどコピーした文字を貼り付け、保存します。

これで準備は完了です。

コード

JSONファイルを作成するコード

まずは単純にJSONファイルを作成するコードです。
新規作成と既存ファイルの更新ではそれぞれ処理が異なるため、別々に書いていきます。

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

  // ユーザーデータ(仮)
  const users_data = [
    { "name": "hogehoge" },
    { "name": "hagehage" }
  ];

  // 保存先のフォルダー情報を取得
  const folder = DriveApp.getFolderById(folder_id);

  // Blobオブジェクトを作成
  const content_type = "application/json";
  const file_name = "users_list_response.json";
  const blob = Utilities.newBlob("", content_type, file_name);
  blob.setDataFromString(JSON.stringify(users_data),"UTF-8"); // Blobオブジェクトにデータをセット

  // Blobオブジェクトをファイル化し、保存先フォルダーへ保存
  folder.createFile(blob);
}

これを実行すると、JSONファイルが作成されました。

中身を見てみると、

[{"name":"hogehoge"},{"name":"hagehage"}]

がちゃんと入っています。

ちなみに

  blob.setDataFromString(JSON.stringify(users_data),"UTF-8");

この部分でusers_dataをJSON.stringifyで囲っていますが、これをしない場合(データがJSONオブジェクトの場合)このようになってしまいます。

JSON.stringifyの処理をすることでJSONオブジェクトを文字列に変換しています。

JSONファイルを更新するコード

先ほど作成したファイルのIDを取得します。

リンクをコピー

取得したリンクの赤線部分がファイルIDです。

後ほどファイルIDを自動的に取得する処理を設けるので、
ここでははスクリプトプロパティには入れず、コード内に直接記載します。

function updateJson() {
  // 更新対象のファイルID
  const file_id = "xxxxxxxxxxxxxxxxxxxxxxxx";
  // 更新対象ファイルを取得
  const file = DriveApp.getFileById(file_id);

  // 更新するユーザーデータ(仮)
  const users_data = [
    { "name": "sato" },
    { "name": "suzuki" }
  ];

  // 更新対象ファイルへデータを上書きする
  file.setContent(JSON.stringify(users_data));
}

これを実行すると、先ほどのjsonファイルの中身が更新されました。

createJsonとupdateJsonの2つのコードで運用する場合、

  1. 初回はcreateJsonを実行
  2. 2回目以降はfile_idを設定し、updateJsonを実行

というちょっとイケてない運用になります。
1つのコードで作成・更新をしたいので、

  1. フォルダ内から指定の特定のファイル名のファイルを探す
  2. 無ければ作成
  3. あれば更新

という処理にしたいです。

保存先フォルダ内からファイルを探してIDを取得するコード

まずは保存先のフォルダから、特定のファイル名のファイルを探し、IDを取得します。

function searchFileId() {
  // スクリプトプロパティに設定したfolder_idを呼び出す
  const folder_id = PropertiesService.getScriptProperties().getProperty("folder_id");
  // 保存先のフォルダー情報を取得
  const folder = DriveApp.getFolderById(folder_id);
  // フォルダー内のファイル一覧を取得
  const files = folder.getFiles();
  // 探したいファイル名
  const file_name = "users_list_response.json";
  // ファイルIDを格納する変数
  let file_id;

  // ファイル一覧から1件ずつ探す
  while (files.hasNext()) {
    const file = files.next();
    // ファイルが見つかった時の処理
    if (file.getName() == file_name) {
      file_id = file.getId();
      break; // ファイルが見つかった時点でループ終了
    }
  }

  Logger.log(file_id);
}

実行すると上手く取得できました。

ファイルが見つからない場合はfile_idがnullになります。(変数宣言時の値になる)


JSONファイルの作成・更新を一括で行うコード

上記3つのコードを組み合わせて、作成・更新を1つのコードにまとめます。
良い関数名が思い浮かばなかったのでChatGPTに考えてもらいました。
(ちなみにcreateUpdateJsonあたりにしようと思ってました)

折角なので作った関数を少し変えて、引数を渡すことで同じ記述を避けるように改修します。

function createOrUpdateJson() {
  // スクリプトプロパティに設定したfolder_idを呼び出す
  const folder_id = PropertiesService.getScriptProperties().getProperty("folder_id");
  // 検索や作成に使うファイル名
  const file_name = "users_list_response.json";

  // ユーザーデータ(仮)
  const users_data = [
    { "name": "hogehoge" },
    { "name": "hagehage" }
  ];

  // フォルダ内にある対象のJSONファイルのIDを取得する
  const file_id = searchFileId(folder_id, file_name);

  if (file_id == null) {
    createJson(folder_id, file_name, users_data);
    Logger.log("ファイルが存在しないため作成");
  } else {
    updateJson(file_id, users_data);
    Logger.log("ファイルが存在するため更新");
  }
}

function createJson(folder_id, file_name, users_data) {
  // 保存先のフォルダー情報を取得します
  const folder = DriveApp.getFolderById(folder_id);

  // Blobオブジェクトを作成
  const content_type = "application/json";
  const blob = Utilities.newBlob("", content_type, file_name);
  blob.setDataFromString(JSON.stringify(users_data), "UTF-8"); // Blobオブジェクトにデータをセット

  // Blobオブジェクトをファイル化し、保存先フォルダーへ保存
  folder.createFile(blob);
}

function updateJson(file_id,users_data) {
  // 更新対象ファイルを取得
  const file = DriveApp.getFileById(file_id);

  // 更新対象ファイルへデータを上書きする
  file.setContent(JSON.stringify(users_data));
}

function searchFileId(folder_id, file_name) {
  // 保存先のフォルダー情報を取得
  const folder = DriveApp.getFolderById(folder_id);
  // フォルダー内のファイル一覧を取得
  const files = folder.getFiles();
  // ファイルIDを格納する変数
  let file_id;

  // ファイル一覧から1件ずつ探す
  while (files.hasNext()) {
    const file = files.next();
    // ファイルが見つかった時の処理
    if (file.getName() == file_name) {
      file_id = file.getId();
      break; // ファイルが見つかった時点でループ終了
    }
  }

  return file_id;
}

一度フォルダ内を空にし、createOrUpdateJsonを実行してみます。

対象のファイルが無いのでcreateJsonが実行されていますね。

続いて2回目の実行

今度はファイルが存在しているので、上手くupdateJsonが実行されています。

slackユーザーデータをJSONファイルとしてGoogleドライブに保存するコード(全体)

前回作ったコードと組み合わせて、

  1. slackユーザーデータの取得
  2. JSONファイルの作成or保存

を一括で行う形にします。

createOrUpdateJsonにusers_dataを引数で渡す形にして使いまわします。

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

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

  createOrUpdateJson(users_data);
}


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);
}

function createOrUpdateJson(users_data) {
  // スクリプトプロパティに設定したfolder_idを呼び出す
  const folder_id = PropertiesService.getScriptProperties().getProperty("folder_id");
  // 検索や作成に使うファイル名
  const file_name = "users_list_response.json";

  // フォルダ内にある対象のJSONファイルのIDを取得する
  const file_id = searchFileId(folder_id, file_name);

  if (file_id == null) {
    createJson(folder_id, file_name, users_data);
  } else {
    updateJson(file_id, users_data);
  }
}

function createJson(folder_id, file_name, users_data) {
  // 保存先のフォルダー情報を取得します
  const folder = DriveApp.getFolderById(folder_id);

  // Blobオブジェクトを作成
  const content_type = "application/json";
  const blob = Utilities.newBlob("", content_type, file_name);
  blob.setDataFromString(JSON.stringify(users_data), "UTF-8"); // Blobオブジェクトにデータをセット

  // Blobオブジェクトをファイル化し、保存先フォルダーへ保存
  folder.createFile(blob);
}

function updateJson(file_id,users_data) {
  // 更新対象ファイルを取得
  const file = DriveApp.getFileById(file_id);

  // 更新対象ファイルへデータを上書きする
  file.setContent(JSON.stringify(users_data));
}

function searchFileId(folder_id, file_name) {
  // 保存先のフォルダー情報を取得
  const folder = DriveApp.getFolderById(folder_id);
  // フォルダー内のファイル一覧を取得
  const files = folder.getFiles();
  // ファイルIDを格納する変数
  let file_id;

  // ファイル一覧から1件ずつ探す
  while (files.hasNext()) {
    const file = files.next();
    // ファイルが見つかった時の処理
    if (file.getName() == file_name) {
      file_id = file.getId();
      break; // ファイルが見つかった時点でループ終了
    }
  }

  return file_id;
}

Mainを実行すると、作成・更新されたJSONの中身が、前回の実行結果と同じになりました!

このMainを時間トリガーで実行することで、最新のユーザーデータ(JSON)を更新し続けることができます。

おまけ(トリガー設定)

時間実行トリガーの設定方法です。

GASの左メニューの時計アイコンをクリック

トリガーを追加

設定項目 内容
実行する関数を選択 Main
イベントのソースを選択 時間主導型
時間ベースのトリガーのタイプを選択 お好み
時間の間隔を選択(時間) お好み

上記の設定をして保存します。

このスクショの設定の場合は6時間ごとに実行されます。

※トリガー設定時の注意
トリガーは、トリガーを作成したアカウントに依存します。
作成したアカウントが退職などで削除された場合、実行されなくなるため、削除の可能性が無いアカウントでトリガーを作成することを推奨します。

まとめ

今回のコードでユーザーデータをJSONファイルで保存することができました。
次回は保存したJSONファイルを読み込み、活用するやり方をご紹介します。

前回:
GASを使ってslackユーザーデータを取得する(1000件以上対応) - シンプルに暮らしたい情シスのブログ
次回:

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ドライブに格納したり、それを読み込んだり、スプレッドシートに書き込むやり方を紹介します。

続き

スプレッドシートのデータを連想配列化する話

ご無沙汰しております。
相変わらず定期的な更新が続かないカインです。

実は4月に転職しまして、新しいことに挑戦している毎日です。
ブログネタも沢山出来たので少しずつアウトプットしていきたいと思います!

今回はスプレッドシートのデータをGASで取得し、連想配列化する話です。
以前は連想配列とはなんぞやといった感じでしたが、理解が進むにつれてその便利さに魅了されつつあります。
データを弄るのにとても便利なのでかなり汎用的に使えて重宝しています。

機能


データサンプルはこちらから一部引用

↑このようなデータを↓のような連想配列にします。
JSON文字列化しています。

[
    {
        "entrydate": "2020-04-27T15:00:00.000Z",
        "userid": "OD77412",
        "name": "オソミハ",
        "seibetsu": "men",
        "age": 32,
        "totalmoney": 52000,
        "birthday": "1988-03-04T15:00:00.000Z"
    },
    {
        "entrydate": "2019-10-26T15:00:00.000Z",
        "userid": "QS19455",
        "name": "トムアシテキ",
        "seibetsu": "men",
        "age": 70,
        "totalmoney": 59000,
        "birthday": "1949-10-12T15:00:00.000Z"
    },
    {
        "entrydate": "2019-11-30T15:00:00.000Z",
        "userid": "HY60274",
        "name": "オンウ",
        "seibetsu": "men",
        "age": 54,
        "totalmoney": 71000,
        "birthday": "1966-02-02T15:00:00.000Z"
    },
    {
        "entrydate": "2020-07-17T15:00:00.000Z",
        "userid": "TA49387",
        "name": "レコスソニ",
        "seibetsu": "men",
        "age": 80,
        "totalmoney": 63000,
        "birthday": "1940-05-28T15:00:00.000Z"
    },
    {
        "entrydate": "2019-09-01T15:00:00.000Z",
        "userid": "PK56576",
        "name": "コミトハム",
        "seibetsu": "men",
        "age": 81,
        "totalmoney": 87000,
        "birthday": "1939-07-23T15:00:00.000Z"
    }
]

※注
日付はJSON.stringifyを使用した際にタイムゾーン情報が失われてUTCに置き換わっています。
以下のコードをJSON変換前に記述しておくことで無理やり回避できます。

Date.prototype.toJSON = function () { return this.getFullYear() + '/' + ('0' + (this.getMonth() + 1)).slice(-2) + '/' + ('0' + this.getDate()).slice(-2) + ' ' + ('0' + this.getHours()).slice(-2) + ':' + ('0' + this.getMinutes()).slice(-2) + ':' + ('0' + this.getSeconds()).slice(-2); }

コード全体

// こちらを実行
function sample() {
  const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  // const spreadsheet = SpreadsheetApp.openById("スプレッドシートのID") //スタンドアロン型の場合はこちらを使用
  const sheet = spreadsheet.getSheetByName("抽出したいシート名");

  const sheet_data = changeAssociativeArray(sheet);

  Logger.log(JSON.stringify(sheet_data)); // 生成した連想配列をログに表示
}

// sheet情報を引数で渡すと連想配列が返される関数
function changeAssociativeArray(sheet){
  // スプレッドシートから値を取得し、二次元配列へ格納
  let data_array = sheet.getDataRange().getValues();

  // 項目名と値を分離
  let keys = data_array[0]; //項目名を取得しkeysに格納
  data_array.shift(); // 項目名をdata_arrayから削除

  // 二次元配列を連想配列に変換
  let new_data_array = data_array.map(function(values){
    let hash = {};
    values.map((value, index)=>hash[keys[index]] = value);
    return hash;
  });

  return new_data_array;
}

連想配列化して何が嬉しいか

一番のメリットはデータの扱いやすさです。
mapやfilter、forEach等の各種配列用の関数も使用できますし、データを項目名で取得できるので可読性が段違いです。

生成した連想配列(sheet_data)から名前(name)の一覧を取り出す例

  for (let i = 0; i < sheet_data.length; i++) {
    Logger.log(sheet_data[i]["name"]);
  }


配列用の関数(map、filter、forEach等)も使用でき、項目名の指定もドットで区切るだけでも可能なので、コードをより簡潔に書くこともできます。
ただし、項目名が日本語や一部記号を含む場合は ["項目名"] で指定する必要があります。

※↑のfor文と同じ内容を簡潔に書いた場合

  sheet_data.forEach((value)=>Logger.log(value.name));

まとめ

このGASはスプレッドシートのデータを取得して活用するケースが増えたので多用しています。
課題や応用もいくつかあるので後日記事にします!

slackのAPI chat.postMessage で投稿したメッセージのURLを取得するGAS

※記事投稿後にもっと簡単なやり方があるよ!と教えていただき修正しました(*´▽`人)
(team.infoを使用→chat.getPermalinkを使用)


ご無沙汰しております。
なかなか書くネタが見つからないカインです。

slackのAPI chat.postMessage で投稿したメッセージのURLを取得するスクリプトを作ったので備忘録的に書いていきます。

処理の流れ

  1. chat.postMessage APIでslackへメッセージを投稿
  2. 投稿結果からチャンネルIDタイムスタンプを取得
  3. chat.getPermalink APIでチャンネルIDとタイムスタンプからURLを取得

使用するAPI

  • chat.postMessage
    • メッセージ投稿に使用します。
  • chat.getPermalink
    • チャンネルIDとタイムスタンプからURLを取得するために使用します。

準備

まずはslackアプリとGASを用意します。
GAS作成参考
アプリ作成参考

slackアプリの設定

権限の設定

メニューのOAuth & Permissionsを選択します。

f:id:simple-josys:20220118212943p:plain

Add an OAuth Scopeをクリックして、

  • chat:write

の権限をBotに追加します。

f:id:simple-josys:20220118232540p:plain

権限追加後は、Workspaceへインストールしましょう。

f:id:simple-josys:20220118213256p:plain

インストールしたら、Bot User OAuth Tokenを控えます。

f:id:simple-josys:20220118214105p:plain

また、slackのチャンネルにアプリを追加しておきます。

f:id:simple-josys:20220118220109p:plain

コード全体

GASにコードをコピペします。
botトーク投稿先のチャンネルIDは書き換えてください。

function getSlackUrl(){
  const bot_token = "xoxb-*******************************"; //slackアプリ OAuth & PermissionsのBot User OAuth Access Token
  
  //メッセージを投稿
  const post_channel = "*********"; //投稿先のチャンネルID
  var message_data = "投稿するメッセージの内容(適当)";
  var created_message = createMessage(message_data,bot_token,post_channel); //createMessage関数を呼び出してAPIに必要な情報を整理
  var response = UrlFetchApp.fetch("https://slack.com/api/chat.postMessage", created_message); //メッセージを投稿
  
  //responseをcreateSlackUrl関数へ渡してURLを生成
  var slack_url = createSlackUrl(response,bot_token);
  
  //確認用に生成したURLを投稿してみる
  var message_data = slack_url;
  var created_message = createMessage(message_data,bot_token,post_channel); 
  var response = UrlFetchApp.fetch("https://slack.com/api/chat.postMessage", created_message);
}

//----------------------------------------postMessage作成
function createMessage(message_data,bot_token,channel_id){
  var payload = {
    "token" : bot_token,
    "channel" : channel_id,
    "text" : message_data
  }
  var options = {
    "method" : "POST",
    "payload" : payload,
    "headers": {
      "contentType": "x-www-form-urlencoded",
    }
  }
  return options;
}

//----------------------------------------createSlackUrl
function createSlackUrl(response_pm,bot_token){
  var r_pm = JSON.parse(response_pm); //JSONを解析
  var channel = r_pm.channel; //チャンネルIDを抽出
  var ts = r_pm.ts; //タイムスタンプを抽出
  
  var payload = {
    "token" : bot_token,
    "channel" : channel,
    "message_ts" : ts
  }
  var options = {
    "method" : "GET",
    "payload" : payload,
    "headers": {
      "contentType": "x-www-form-urlencoded",
    }
  }
  
  //チャンネルIDとタイムスタンプを元に、chat.getPermalink APIでURLを取得する
  var response_ti = UrlFetchApp.fetch("https://slack.com/api/chat.getPermalink", options);
  var slack_url = JSON.parse(response_ti).permalink; //URLを抽出
  
  return slack_url;
}

chat.postMessageのresponseの中身

※一部 *** でボカしています。
channeltsを取り出して使用します。

{
  "ok": true,
  "channel": "C0*******A8",
  "ts": "1642******.****00",
  "message": {
    "bot_id": "B02******NL",
    "type": "message",
    "text": "投稿するメッセージの内容(適当)",
    "user": "U02******HY",
    "ts": "1642******.****00",
    "team": "TR*****F3",
    "bot_profile": {
      "id": "B02******NL",
      "app_id": "A02******CC",
      "name": "get_slack_url",
      "icons": {
        "image_36": "https://a.slack-edge.com/80588/img/plugins/app/bot_36.png",
        "image_48": "https://a.slack-edge.com/80588/img/plugins/app/bot_48.png",
        "image_72": "https://a.slack-edge.com/80588/img/plugins/app/service_72.png"
      },
      "deleted": false,
      "updated": 1642509170,
      "team_id": "TR*****F3"
    }
  }
}

chat.getPermalinkのresponseの中身

permalinkを取り出して使用します。

{
  "ok": true,
  "permalink": "https://s*****t.slack.com/archives/C0*******A8/p1642******.****00",
  "channel": "C0*******A8"
}

functionの説明

getSlackUrl

実行用

createMessage

chat.postMessageメソッドに必要な中身を作る処理

createSlackUrl

chat.postMessageのsuccess responseの中身からURLを作成する処理

実行テスト

function getSlackUrlを実行

f:id:simple-josys:20220118225016p:plain

メッセージが投稿された後、生成したURLが投稿されました!

終わりに

難しいことは特にしていませんが、今日までpostMessageで投稿したものをOutgoing Webhookで拾ってURLを生成するというアホなことをやっていました。
Outgoing Webhookはプライベートチャンネルでは使えませんが、今回作ったものはプライベートチャンネルでも使えますし、何より仕組みそのものがスッキリしました!

メールアドレスでslackのユーザーIDを取得するGAS

約1年ぶりの更新です(;^ω^)
滞ってるにもかかわらず、毎日10人前後に見ていただけているようで恐縮です!
ありがとうございます!!

この記事はGASとslackでヘルプデスクフォームを作った話(Googleフォーム版 準備編)に関連した記事です。

メールアドレスを元にユーザー情報を取得する便利なAPIを見つけたのでご紹介です!

コード全体

bot作成やslackとGASの連携等は準備編コード編をご参照ください。

//検索したいメールアドレスと、botトークンを渡す
function findUserByEmails(email,bot_token){

  var url="https://slack.com/api/users.lookupByEmail" //APIのURL
  var payload = {
    "token" : bot_token, //botトークン ※記事上部の別記事参照
    "email" : email //検索したいメールアドレス
  }
  var options = {
    "method" : "GET",
    "payload" : payload,
    "headers": {
      "contentType": "x-www-form-urlencoded",
    }
  }
  
  var json_data = UrlFetchApp.fetch(url, options); //APIリクエスト実行と結果の格納
  json_data = JSON.parse(json_data) //結果はJSONデータで返されるのでデコード
  
  if (json_data["ok"]){ //boolean型でtrue or falseが格納されています
    var user_id = String(json_data["user"]["id"]) //trueの場合返答されたデータからユーザーIDを抽出
    } else {
      var user_id = "null" //falseの場合null(文字列)を格納
      }

  return user_id //ユーザーID(or null)を返却
}

使用API

users.lookupByEmail
メールアドレスを元にユーザー情報をリクエストするAPIです。

メールアドレスの以外にも

  • 表示名
  • 氏名
  • アイコンのURL
  • 役割・担当

等々

プロフィールで閲覧できるものは一通り取得できるようです。
(APIのページの「Typical success response」に詳細が書いてあります)

今まで使っていた手法

  1. users.listを使って、slack上のユーザーを全て取得
  2. 1件ずつメールアドレスが一致するかチェック
  3. 一致する場合ユーザーIDを格納する
  4. 一致しない場合null(文字列)を返す

いつからかユーザーが存在するにも関わらずユーザーIDが見つけられないというトラブルが起き始めたので対策を調べていたところ、users.lookupByEmailを見つけました。

素人考えですが、users.listで取得した結果を見ると4000文字程度あったのでどこかで文字数上限に引っかかったのかなーと考えてます。

今まで使っていた手法のコード(供養)

function findUserByEmails(email,bot_token){
  var url="https://slack.com/api/users.list"
  var payload = {
    "token" : bot_token,
  }
  var options = {
    "method" : "GET",
    "payload" : payload,
    "headers": {
      "contentType": "x-www-form-urlencoded",
    }
  }
  var json_data = UrlFetchApp.fetch(url, options);
  json_data = JSON.parse(json_data)
  for(i in json_data["members"]){
    if(String(json_data["members"][i]["profile"]["email"])==email){
      var user_id =String(json_data["members"][i]["id"])
      }
  }
  
  if(user_id == null){
    user_id = "null"
  }

  return user_id
}