LoginSignup
3
2

More than 1 year has passed since last update.

Google Apps ScriptでGoogle Groupsのメンバーを更新する

Last updated at Posted at 2021-12-26

※本記事の内容は2021年12月26日のものです.

目標

Google Groupsのメンバーの追加や削除を,ブラウザのUIを経由せずに,Google Apps Script (GAS)を用いて行う.

環境

Google Workspace for Nonprofits

準備

Workspaceの管理者として: https://admin.google.com/ のセキュリティ > APIの制御 > ドメインで所有する内部アプリを信頼する にチェックを入れる.(※)

Drive SDK API access を有効にする.サイドメニュー > アプリ > Google Workspace > ドライブとドキュメント > 機能とアプリケーション > Drive SDK (※)

上記2つの(※)は多分必要と思われますが,検証していません.

Google Driveの適当なフォルダにスプレッドシートを作成する.スクリプト実行の土台として使用する.また作成したスプレッドシートに記入された情報に基づいてメンバーの管理を行う.

スプレッドシートの 拡張機能 > Apps Script を選択してスクリプトエディタを起動する.

左側のサービスの横の+をクリックすると,サービスを追加するダイアログが開く.Admin Directory APIを追加する.

機能ごとの動作確認

Google Groupsに対する操作方法と,Google Sheetsに対する操作方法の,両方を確認する.

GAS一般

ログの出力

各種の実行ログの出力はconsole.log()で行える.

Sheets

全シート名の取得

スプレッドシート内にある全てのシート名を得る.

function GetAllSheetNames() {
  const sheets = SpreadsheetApp.getActiveSpreadsheet().getSheets();

  for (let sheet of sheets) {
    console.log(sheet.getName())
  }
}

全シート名を取得し,シートに出力

全シート名を,あるシート(ここではシート名print)に出力する.

function OutputAllSheetNames() {
  const sheets = SpreadsheetApp.getActiveSpreadsheet().getSheets();

  let values = [];
  for (let sheet of sheets) {
    let rowData = [];
    rowData.push(sheet.getName());
    values.push(rowData);
  }

  const sheetPrint = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('print');
  sheetPrint.clear();
  sheetPrint.getRange(1, 1, values.length, 1).setValues(values);  
}

全データの取得

あるシート(ここではシート名new)に書かれている全データを取得する.

function GetAllData() {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('new');
  const values = sheet.getDataRange().getValues(); // getDataRange()でデータの存在範囲が得られる.
  const data = values.flat().sort(); // 配列の配列を1次元にし,ソートする.

  console.log(data);
}

getValues()で得られるvaluesは配列の配列である.
このサンプルではメソッドflat()によって1次元化した後,メソッドsort()によってソートしているが,これは筆者の都合である(シートの1列目にメンバーのメールアドレスが記載されていると想定しているため).

参考: 【GAS・スプレッドシート】範囲・セルの値を取得する方法|選択中や指定した範囲の値

Groups

全グループ情報の取得

あるドメイン(ここではexample.org)に属する全てのグループの情報を得る.

const domainName = 'example.org'; // 環境に合わせて変更

function GetAllGroupNames() {
  const groupsList = AdminDirectory.Groups.list({
    domain: domainName,
    maxResults: 200
  });

  for (let group of groupsList.groups) {
    console.log(group);
  }
}

メンバー一覧の取得(200名以下)

あるグループ(ここではtest@example.org)のメンバー一覧を得る(200名以下の場合).

function GetGroupMember()
{
  const groupEmail = 'test@example.org';
  const members = AdminDirectory.Members.list(groupEmail).members;

  console.log(members);
}

メンバー一覧を得るだけなら,グループそのものを直接扱わなくてもできる.

メンバー一覧の取得(200名超)

同時に取得できる人数が最大200名という制約があるため,200名を超える場合には複数回実行して結果を統合する必要がある.

function GetOver200Members() {
  const groupEmail = 'test@example.org';
  let pageToken;
  let page;
  let members = [];
  do {
    page = AdminDirectory.Members.list(groupEmail, {
      maxResults: 200,
      pageToken: pageToken
    });
    members = [...members, ...page.members];
    pageToken = page.nextPageToken;
  } while (pageToken);

  console.log(members);
}

Admin SDK Directory Service のList all usersやList all groupsのソースコードを参考にし,対象をメンバーとするように改変した.nextPageTokenがある間は続きがあるため実行を繰り返す.

結果の統合にはスプレッド演算子(...)を使用した.

参考: JSのスプレッド構文を理解する

メンバーの追加

あるグループにメンバー(メールアドレス: test-user@example.com)を追加する.

function AddMember()
{
  const groupEmail = 'test@example.org';
  const newMember = {
    email: 'test-user@example.com',
    role: 'MEMBER'
  };
  AdminDirectory.Members.insert(newMember, groupEmail); // 既に登録されていたら fatal error でストップする.
}

試した限りではinsert()の第1引数に直接メールアドレスを指定することはできない.

メンバーの削除

あるグループからメンバー(メールアドレス: test-user@example.com)を削除する.

function DeleteMember()
{
  const groupEmail = 'test@example.org';
  const memberEmail = 'test-user@example.com';
  AdminDirectory.Members.remove(groupEmail, memberEmail); // 存在していなければ fatal error でストップする.
}

削除の場合は追加と異なりremove()の第2引数には削除したいメンバーのメールアドレスを直接指定する.

実際に作成したスクリプトの例

(1)グループtest@example.orgのメンバーを,(2)シートnewに記載されているメールアドレスに更新するスクリプトを作成した.具体的には以下に示す操作を行う.

  • (1)と(2)の両方に記載がある場合は,そのまま何もしない.
  • (1)のみに記載がある場合は,削除する.
  • (2)のみに記載がある場合は,新規登録する.

実行方法: スクリプトエディタで実行する関数としてUpdateMemberListを選択し,実行をクリックする.

各メールアドレスに対する操作をシートactionに出力するようになっているため,あらかじめシートactionを用意しておく.シートactionの内容は実行のたびにクリアされる.

// 対象グループのメールアドレス
const TARGET_GROUP = 'test@example.org';

// メンバーのメールアドレス一覧が登録されているシート名
const NEW_EMAIL_SHEET = 'new';

// 各メールアドレスに対する操作を記録するシート名
const ACTION_SHEET = 'action';

// 追加のみ実行するか
const IS_ADD_ONLY = true; // false だと追加と削除を実行

// 実際に実行するか
const IS_EXEC = false; // true だと実行

// グループ名(メールアドレス)を指定すると,登録されているメンバーを返す.
// 200名を超えていてもOK

function GetMembers(group) {
  let pageToken;
  let page;
  let members = [];
  do {
    page = AdminDirectory.Members.list(group, {
      maxResults: 200,
      pageToken: pageToken
    });
    members = [...members, ...page.members]; // ... はスプレッド演算子で配列の内容を展開する
    pageToken = page.nextPageToken; // 次のページトークンを得る
  } while (pageToken);

  return members;
}

// 以下が実行するべき関数

function UpdateMemberList() {
  // 現在グループに登録されているメンバの情報
  const members = GetMembers(TARGET_GROUP);

  // シート new に記載されている登録者リスト
  const sheetNew = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(NEW_EMAIL_SHEET);
  // 配列の配列は扱いにくいので,flat()で1次元にする.
  const newEmailsUnchecked = sheetNew.getDataRange().getValues().flat();

  // 新しい登録者には,重複がある可能性があるため,確認する.
  let newEmails = [];
  for (let email of newEmailsUnchecked) {
    if (newEmails.includes(email)) { // 既にある
      console.log('duplicated:', email);
      continue;
    }
    newEmails.push(email);
  }

  // 相互にメールアドレスのチェック

  // 現在のメンバー members について for をまわす.
  // 何もしないリスト keepEmails
  // 削除するリスト deleteEmails
  // 両方を合わせたリスト registeredEmails
  let keepEmails = [];
  let deleteEmails = [];
  let registeredEmails = [];
  for (let member of members) {
    const email = member.email;
    if (newEmails.includes(email)) { // 配列のメソッド includes() で存在チェック
      keepEmails.push(email);
    } else {
      deleteEmails.push(email);
    }
    // チェック用にメールアドレスのリストも作る
    registeredEmails.push(email);
  }

  // 次に,newEmails について for をまわす.registeredEmails に入っているかチェックする.
  // 何もしないリスト keepEmailsNew
  // 登録するリスト addEmails
  let keepEmailsNew = [];
  let addEmails = [];
  for (let email of newEmails) {
    if (registeredEmails.includes(email)) {
      keepEmailsNew.push(email);
    } else {
      addEmails.push(email);
    }
  }

  // 整合性を確認できるように,結果の個数をconsoleに表示する.
  console.log('KEEP:', keepEmails.length);
  console.log('DELETE:', deleteEmails.length);
  console.log('ALL:', registeredEmails.length);
  console.log('KEEP(NEW):', keepEmailsNew.length);
  console.log('ADD:', addEmails.length);
  console.log('ALL(NEW):', newEmails.length);

  // チェックと,実行内容がみやすいように,ソートしておく.
  keepEmails.sort();
  keepEmailsNew.sort();
  deleteEmails.sort();
  addEmails.sort();

  // キープするメールアドレスは同じになっているはずなので,チェックする.
  if (keepEmails.length != keepEmailsNew.length) {
    console.log('keepEmails.length != keepEmailsNew.length');
  }
  for (let i = 0; i < keepEmails.length; i++) {
    if (keepEmails[i] != keepEmailsNew[i]) {
      console.log('keepEmails != keepEmailsNew', keepEmails[i], keepEmailsNew[i]);
    }
  }

  // 各メールアドレスに対する処理内容をシート action に出力する.
  let values = []; // 出力用空配列
  values.push(['メールアドレス', '操作']);
  for (let email of keepEmails) {
    let rowData = [];
    rowData.push(email);
    rowData.push('KEEP');
    values.push(rowData);
  }
  for (let email of deleteEmails) {
    let rowData = [];
    rowData.push(email);
    rowData.push('DELETE');
    values.push(rowData);
  }
  for (let email of addEmails) {
    let rowData = [];
    rowData.push(email);
    rowData.push('ADD');
    values.push(rowData);
  }
  const sheetAction = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(ACTION_SHEET);
  sheetAction.clear();
  sheetAction.getRange(1, 1, values.length, 2).setValues(values);

  // いざ,操作を実行する!!!!!
  if (IS_EXEC) {
    // deleteEMails のエントリを削除する
    if (!IS_ADD_ONLY) {
      for (let email of deleteEmails) {
        AdminDirectory.Members.remove(TARGET_GROUP, email);
      }
    }
    // addEMails のエントリを追加する
    for (let email of addEmails) {
      let member = {
        email: email,
        role: 'MEMBER'
      };
      AdminDirectory.Members.insert(member, TARGET_GROUP);
    }
  }
}

参考

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2