※本記事の内容は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);
}
}
}