【GAS×ZOOM API】ミーティングの参加者をリアルタイムで取得する方法

こんにちは！GoogleAppsScriptを使った業務効率化に夢中の初心者プログラマー、太郎です。

今日はGoogleAppsScript(GAS)でZOOMミーティングに参加している人のリストを、リアルタイムで取得する方法について書いていきたいと思います。

なお、ミーティングの参加者一覧を取得するメソッドは、**『Business or a higher plan』限定**です。無料アカウントでは使用できないのでご注意ください。
また、参加者一覧を取得するには、JWT Tokenと該当するミーティングを作成したユーザーのuserIDが必要です。それらの取得方法に関しては、以下の2つの記事で紹介しておりますので、参照いただければと思います。
* [【GAS×ZOOM API】初学者にも優しくJWT認証の方法を解説してみる](https://qiita.com/coticoticotty/items/56ff2a3324fe408b06ca)
* [【GAS×ZOOM API】ミーティングを作成する方法](https://qiita.com/coticoticotty/items/a167b2c6db56dd42a7f7)

ミーティングの参加者一覧を取得するための方法は2段階に分かれます。
一つ目が、該当のミーティングのIDを取得すること。二つ目がそのミーティングの参加者一覧を取得すること、となります。順番に見ていきましょう。
##参加者を取得したいミーティングのIDを取得する
まずは、該当のミーティングのIDを取得する方法になります。以下の様に記述します。

```javascript
function getMeetingID() {
  const userId = '*******'; // 取得したのユーザーIDを入力
  const JWTToken = '*****'; // 作成したJWT Tokenを入力
  const meetingName = '*******'; // 参加者を取得したいミーティングの名前を入力
  const request = UrlFetchApp.fetch(
    `https://api.zoom.us/v2/users/${userId}/meetings`,
    {
      method: 'GET',
      contentType: 'application/json',
      headers: { Authorization: `Bearer ${JWTToken}` },
    }
  );
  const meetingData = JSON.parse(request.getContentText()); // JSON形式で取得したミーティングデータをJavaScriptオブジェクトに変換
  // for文で繰り返し処理して該当のミーティングIDを取得します。
  for (i = 0; meetingData.meetings.length; i++) {
    if (meetingData.meetings[i].topic === meetingName) {
      return meetingData.meetings[i].uuid;
    }
  }
}
```
以下、解説していきます。

```javascript
 const request = UrlFetchApp.fetch(
    `https://api.zoom.us/v2/users/${userId}/meetings`,
    {
      method: 'GET',
      contentType: 'application/json',
      headers: { Authorization: `Bearer ${JWTToken}` },
    }
```
この部分でミーティング一覧を取得しています。このあたりの詳しい説明は、前回も書いたので割愛します。返り値はこんな感じです。([公式ドキュメント](https://marketplace.zoom.us/docs/api-reference/zoom-api/dashboards/dashboardmeetings)より)

```javascript
{
  "from": "string [date]",
  "to": "string [date]",
  "page_count": "integer",
  "page_size": "integer",
  "total_records": "integer",
  "next_page_token": "string",
  "meetings": [
    {
      "uuid": "string [uuid]",
      "id": "integer",
      "topic": "string",
      "host": "string",
      "email": "string",
      "user_type": "string",
      "start_time": "string [date-time]",
      "end_time": "string [date-time]",
      "duration": "string",
      "participants": "integer",
      "has_pstn": "boolean",
      "has_voip": "boolean",
      "has_3rd_party_audio": "boolean",
      "has_video": "boolean",
      "has_screen_share": "boolean",
      "has_recording": "boolean",
      "has_sip": "boolean"
    }
  ]
}
```
取得したいミーティングIDは、idかuuidです。
基本的にどっちでも良さそうなんですが、idだと制限される機能がありそうなのでuuidで取得しておいた方が良さそうな感じです。
なお、meetingの情報は配列の中に格納されているのですが、meetingが複数ある場合がほとんどだと思います。

```javascript
meetings: [{meeting1}, {meeting2}, {meeting3},・・・]
```
こんな形ですね。なので、複数あるミーティングデータから該当のIDをミーティング名から取得してあげます。そのための記述が以下ですね。

```javascript
  for (i = 0; meetingData.meetings.length; i++) {
    if (meetingData.meetings[i].topic === meetingName) {
      return meetingData.meetings[i].uuid;
    }
  }
```
たぶんfor文以外にもっと上手な書き方があると思うのですが、初心者にはこれが手一杯です💦

ただ、ミーティングの数が30以上ある場合、上記のやり方だと上手く取得できません。nextpagetokenというパラメータを使用する必要があります。
そちらも加味してコードを書くとこんな感じです。

```javascript
function getMeetingID() {
  const userId = '*******'; // 取得したのユーザーIDを入力
  const JWTToken = '*****'; // 作成したJWT Tokenを入力
  const meetingName = '*******'; // 参加者を取得したいミーティングの名前を入力  let pageToken = '';
  do {
    const request = UrlFetchApp.fetch(
      `https://api.zoom.us/v2/users/${getZoomUserId()}/meetings/?next_page_token=${pageToken}`,
      {
        method: 'GET',
        contentType: 'application/json',
        headers: { Authorization: `Bearer ${getZoomAccessToken()}` },
      }
    );
    const meetingData = JSON.parse(request.getContentText());
    for (i = 0; meetingData.meetings.length; i++) {
      if (meetingData.meetings[i].topic === meetingName) {
        return meetingData.meetings[i].uuid;
      }
    }
    pageToken = meetingData.next_page_token;
  } while (pageToken);
}
```
next_page_tokenという値を取得して、それをURLにパラメータとして加えてあげる必要があるんですね。
ページトークンの使い方に関しては、以下の記事にまとめてあるのでこちらを参照ください。
[【GAS】GoogleClassroomの取得上限以上のリストを取り出す方法(nextPageToken)](https://qiita.com/coticoticotty/items/17638685b8969b974adb)

また、GETメソッドにパラメータを渡す方法は、以下の記事に詳しく書いてあるので、知りたい方はご参照ください。
[HTTPとPOSTとGET](https://qiita.com/Sekky0905/items/dff3d0da059d6f5bfabf)

さて、準備が出来たので、いよいよミーティングの参加者一覧を取得していきましょう！
##参加者一覧をリアルタイムで取得する
今回も先にコードを書きますね。使用するメソッドは[List Meeting Participants](https://marketplace.zoom.us/docs/api-reference/zoom-api/dashboards/dashboardmeetingparticipants)です。こんな形になります。

```javascript
function getParticipants() {
  const JWTToken = '*****'; // 作成したJWT Tokenを入力
  const meetingId = getMeetingID(); //先ほどの関数を使ってミーティングID取得
  let pageToken = "";
  let participantList = [];

  do {
  const requestUrl = `https://api.zoom.us/v2/metrics/meetings/${meetingId}/participants/?type=live&next_page_token=${pageToken}`;
    const request = UrlFetchApp.fetch(
      requestUrl,
      {
        method: 'GET',
        contentType: 'application/json',
        headers: { Authorization: `Bearer ${JWTToken}` },
      }
    );
    const meetingData = JSON.parse(request.getContentText());
    const participantsData = meetingData.participants;
    for (i = 0; i < participantsData.length; i++) {
    const participantName = participantsData[i].user_name;
    const participantMail = participantsData[i].email;
    participantList.push([studentName, studentMail]);
    pageToken = meetingData.next_page_token;
    }
  } while (pageToken);
  return participantList;
}
```
以下詳しく見ていきます。なお、ZOOMの参加者はたいてい30人を超えるため、今回もnextPageTokenを使用しています。

```javascript
const requestUrl = `https://api.zoom.us/v2/metrics/meetings/${meetingId}/participants/?type=live&next_page_token=${pageToken}`;
const request = UrlFetchApp.fetch(
  requestUrl,
  {
    method: 'GET',
    contentType: 'application/json',
    headers: { Authorization: `Bearer ${JWTToken}` },
  }
);
```
こちらの部分で、ZOOMにGETリクエストを行っています。URLに含めるパラメーターは先ほど取得したmeetingIdと30件以上のデータ取得に必要なpageTokenです。
また、ヘッダーに、JWTTokenも渡します。

この結果、帰ってくるデータが、以下のような感じ。

```javascript
{
  "page_count": 1,
  "page_size": 30,
  "total_records": 2,
  "next_page_token": "",
  "participants": [
    {
      "id": "d52f19c548b88490b5d16fcbd38",
      "user_id": "32dsfsd4g5gd",
      "user_name": "dojo",
      "device": "WIN",
      "ip_address": "127.0.0.1",
      "location": "New York",
      "network_type": "Wired",
      "microphone": "Plantronics BT600",
      "camera": "FaceTime HD Camera	",
      "speaker": "Plantronics BT600",
      "data_center": "SC",
      "connection_type": "P2P",
      "join_time": "2019-09-07T13:15:02.837Z",
      "leave_time": "2019-09-07T13:15:09.837Z",
      "share_application": false,
      "share_desktop": true,
      "share_whiteboard": true,
      "recording": false,
      "pc_name": "dojo's pc",
      "domain": "Dojo-workspace",
      "mac_addr": " 00:0a:95:9d:68:16",
      "harddisk_id": "sed proident in",
      "version": "4.4.55383.0716",
      "leave_reason": "Dojo left the meeting.<br>Reason: Host ended the meeting."
    },
    {
      "id": "z8aaaaaaCfp8uQ",
      "user_id": "1670000000",
      "user_name": "Rea",
      "device": "Android",
      "ip_address": "120.000.000",
      "location": "San Jose (US)",
      "network_type": "Wifi",
      "data_center": "SC",
      "connection_type": "UDP",
      "join_time": "2019-08-02T15:31:48Z",
      "leave_time": "2019-08-02T16:04:12Z",
      "share_application": false,
      "share_desktop": false,
      "share_whiteboard": false,
      "recording": false,
      "pc_name": "Rea's PC",
      "domain": "Rea-workspace",
      "mac_addr": "",
      "harddisk_id": "",
      "version": "4.4.55383.0716",
      "leave_reason": "Rea left the meeting.<br>Reason: Host closed the meeting."
    }
  ]
}
```
**うわっ！ながっ！！！！**っと思って読むのを辞めないでもう少しお付き合いください>_<

先ほど取得したmeeting情報と同様で、欲しいデータはparticipantsの中に配列として格納されています。

```javascript
"participants": [{participant1}, {participant2}, {participant3},・・・]
```
そのため、キーをparticipantsで指定してあげれば、参加者一覧のデータが取れるのですが、ここで注意なのが、**得られるデータがJSON形式**であることです！JSONとは、javascriptのオブジェクトのような恰好してるけど、実はString型のデータです。
ものすごくざっくり翻訳してみると、、、💦

まあ要するに、javascriptで読み込める形になっていないので、parseすることで、javascriptでも使えるデータ型にしてあげないといけない、ということです。

```javascript
const meetingData = JSON.parse(request.getContentText());
const participantsData = meetingData.participants;
```
そんなわけで、取得したJSON形式のデータをjavascriptのデータ型に変換し、participantのデータを配列として取得しているのがこちらの部分です。

```javascript
for (i = 0; i < participantsData.length; i++) {
  const participantName = participantsData[i].user_name;
  const participantMail = participantsData[i].email;
  participantList.push([studentName, studentMail]);
  pageToken = meetingData.next_page_token;
}
```
その後、配列として取得したユーザーのデータを一つ一つ取り出し、user_nameとemailを取得。それらを配列に格納していきます。次の30件を読み込むための、pageTokenの設定も忘れずに！

以上でZOOMの参加者を取得することができます！

## 取得できるデータの注意点
そんなわけで、ZOOMの参加者をミーティング開催中に取得する方法は以上で終わりなのですが、取得できるデータにいくつか注意点があります。
一つ目、得られるデータは、そのMTGに現在参加している人、および、**参加したけど今は退出している人**の一覧であること。

ZOOMのレコードをエクセルファイルで出力した経験はありますか？
今回得られるデータは、まさにあのファイルをMTGの途中で取得する、ようなイメージです。
今MTGに参加している人も、すでに退出してしまった人も、一緒くたに表示されます。
なので、MTGに参加したが、今は退出している生徒を把握したい場合、leave_timeを取得して、その時間に応じて、何かしらの処理を実行する必要でてきます。私の場合はその必要がなかったので、そこまで実装することはなかったのですが、やりたい人はぜひトライしてみてください。
leavetimeを取得して、現在時刻と比べてフィルターかければ出来るはずです。

二つ目は、入室するたびにデータが残ることです。つまり、入退室を3回繰り返した場合、得られるデータにも3回名前が載ることになります。
そのため、その参加者一番最初に入室した時刻を知りたい場合、もう一工夫してあげる必要があるんですね。

注意点としては以上です！
コロナウィルスの影響で、ZOOMを使ってセミナーをしたり、授業を行ったりする機会は増えたと思います。
そんなときに、このAPI使えると非常に便利ですよ～～～！
ぜひ試してみてください^ ^

今後も、初心者なりに探求してみたことをどんどん発信していきたいと思います！