Google Group のメンバーを Google Chat の参加者にする
はじめに
Google Workspace を利用していると、プロジェクトや部署ごとに Google Group(メーリングリスト)と Google Chat のスペースを運用することがよくあります。
しかし、「メーリングリストに新しいメンバーが追加されたのに、Chat スペースへの招待を忘れてしまった…」といった経験はありませんか?
本記事では、このGoogle Group のメンバーと Google Chat の参加者の同期(招待) を、Google Apps Script (GAS) を使って自動化する方法を紹介します。
GASとAdvanced Serviceの活用
この自動化を実現するために、以下の2つのGoogle Workspace Advanced Serviceを利用します。
Admin Directory Service (Directory API): Google Group のメンバーリストを取得するために使用します。
Chat Advanced Service (Chat API): Google Chat のスペース情報取得と、メンバーを追加するために使用します。
これらのサービスを利用するには、GASのスクリプトエディタで事前に有効化しておく必要があります。
事前準備:Advanced Serviceの有効化
- GASのスクリプトエディタを開く
- 左側のメニューから「サービス」⚙️(歯車のアイコン)を選択
- 「Admin Directory API」と「Google Chat API」を探し、それぞれ「追加」ボタンを押して有効にする
なお、 Admin Directory API の実行には、Google Workspace の管理者権限が必要です。 また、 Google Cloud の Project を作成し、Google Workspace の管理権限を付与しておいてください。
スプレッドシートの準備
GASは、連携対象とする Google Group と Google Chat スペースの情報をスプレッドシートから読み込みます。
以下の構造で、記事に添付されているコードに合わせたシートを作成してください。
| 列 | 変数名 | 内容 | 例 |
|---|---|---|---|
| A列 | COLUMN_STATUS | 状態 (有効 の行のみ処理) | 有効 or 無効 or エラー |
| B列 | COLUMN_MAILING_LIST | Google Group のメールアドレス | google-group@example.com |
| C列 | COLUMN_SPACE_ID | Google Chat のスペースID | exampleSpaceId |
| D列 | COLUMN_SPACE_NAME | スペース名 (GASが自動で書き込み) | プロジェクトA チーム |
スペースIDの見つけ方 Google Chat の URL (https://chat.google.com/room/exampleSpaceId) の /room/ の後ろにある英数字がスペースIDです。
GASコード
1. スプレッドシートの設定とカスタムメニュー
onOpen() 関数により、スプレッドシートを開いたときに「🤖 Chat連携処理」というカスタムメニューが作成され、そこからメインの処理 processSpaceInvitations を手動で実行できます。
// スプレッドシートの設定
const SHEET_NAME = '設定';
const COLUMN_STATUS = 0; // A列: 状態
const COLUMN_MAILING_LIST = 1; // B列: メーリングリスト
const COLUMN_SPACE_ID = 2; // C列: スペースID
/**
* スプレッドシートを開いたときにカスタムメニューを作成する
*/
function onOpen() {
SpreadsheetApp.getUi()
.createMenu('🤖 Chat連携処理')
.addItem(
'参加者追加を実行(管理者以外実行不可)',
'processSpaceInvitations',
)
.addToUi();
}
2. メインの処理 (processSpaceInvitations)
この関数が各行をループし、設定されたメーリングリストとスペースIDの組み合わせに対して処理を実行します。
/**
* メインの処理を実行する関数
*/
function processSpaceInvitations() {
// ... シート取得とエラーチェック ...
// ヘッダー行をスキップ (1行目から開始)
for (let i = 1; i < values.length; i++) {
// ... 行の値を取得 ...
// 処理対象は「有効」の行のみ
if (status !== '有効') {
Logger.log(
`LOG: ${rowNum}行目: 状態が「${status}」のためスキップします。`,
);
continue;
}
try {
// 1. Google ChatのスペースIDからスペース名を取得し、D列に記載
const spaceName = getSpaceNameAndLog(spaceId, rowNum); // ... D列に書き込む処理 ...
// メーリングリストの参加者を取得する処理
const members = getGroupMembersAndLog(mailingList, rowNum); // 3. メーリングリストの参加者をスペースに追加する処理
if (members.length > 0) {
addMembersToSpaceAndLog(spaceId, members, rowNum);
} // ... 正常完了ログ ...
} catch (e) {
// エラーが発生した場合は「状態」を「エラー」に変更
sheet.getRange(rowNum, COLUMN_STATUS + 1).setValue('エラー');
}
}
Logger.log('--- 全ての処理が終了しました ---');
}
// ...
3. Google Group メンバーの取得 (getGroupMembersAndLog)
ここでAdmin Directory Serviceを使用し、Google Group のメンバー(member.type === 'USER' のみ)のメールアドレスを取得します。
// ...
/**
* Googleグループ(メーリングリスト)の参加者を取得する
*/
function getGroupMembersAndLog(mailingList, rowNum) {
// ... 空欄チェック ...
const members = [];
try {
// Admin Directory Service を使用してグループメンバーを取得 (管理者権限が必要)
let pageToken;
do {
const response = AdminDirectory.Members.list(mailingList, {
pageToken: pageToken,
}); // ... ユーザーのみを抽出してmembers配列に追加 ...
pageToken = response.nextPageToken;
} while (pageToken);
Logger.log(
`LOG: ${rowNum}行目: ... ${members.length}名のメンバーを取得しました。`,
);
return members;
} catch (e) {
// ... エラー処理 ...
}
}
// ...
4. Chat スペースへのメンバー追加 (addMembersToSpaceAndLog)
取得したメンバーを Google Chat スペースに招待します。ここでChat Advanced Serviceを使用します。
重要なポイント:type: "HUMAN" の明記
Chat API でユーザーを招待する際、memberResource の中に type: "HUMAN" を含めることで、「実行者(GASの実行ユーザー)」としてメンバーを招待するよう明示しています。これにより、Chat Appとしてではなく、管理者としてユーザーを招待する動作となり、正常に処理が進みます。
// ...
/**
* メンバーリストをGoogle Chatのスペースに追加する
*/
function addMembersToSpaceAndLog(spaceId, members, rowNum) {
// ...
members.forEach((email) => {
try {
// ユーザーのリソース名を作成 (users/user@example.com の形式)
const userResourceName = `users/${email}`; // 💡 決定的な修正: 実行者がChat Appではなく人間(管理者)であることを明示
const memberResource = {
member: {
name: userResourceName,
type: 'HUMAN',
},
}; // Chat Advanced Service を使用してメンバーを追加 (招待)
Chat.Spaces.Members.create(memberResource, spaceResourceName);
Logger.log(
`LOG: ${rowNum}行目: メンバー ${email} の追加に成功しました。`,
);
} catch (e) {
// 既にメンバーである場合もここで捕捉
Logger.log(
`WARN: ${rowNum}行目: メンバー ${email} の追加に失敗しました。詳細: ${e.message}`,
);
failCount++;
}
}); // ...
}
最終的なコード
// スプレッドシートの設定
const SHEET_NAME = '設定';
const COLUMN_STATUS = 0; // A列: 状態
const COLUMN_MAILING_LIST = 1; // B列: メーリングリスト
const COLUMN_SPACE_ID = 2; // C列: スペースID
const COLUMN_SPACE_NAME = 3; // D列: スペース名
/**
* スプレッドシートを開いたときにカスタムメニューを作成する
*/
function onOpen() {
SpreadsheetApp.getUi()
.createMenu('🤖 Chat連携処理') // メニュー名
.addItem(
'参加者追加を実行(管理者以外実行不可)',
'processSpaceInvitations',
) // 項目名と実行関数
.addToUi();
}
/**
* メインの処理を実行する関数
*/
function processSpaceInvitations() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheetByName(SHEET_NAME);
if (!sheet) {
Logger.log(
'FATAL ERROR: 指定されたシート名が見つかりません。シート名を確認してください。',
);
SpreadsheetApp.getUi().alert('エラー: シートが見つかりません。');
return;
}
// データ範囲を取得 (ヘッダー行を除く2行目から)
const dataRange = sheet.getDataRange();
const values = dataRange.getValues();
// ヘッダー行をスキップ (1行目から開始)
for (let i = 1; i < values.length; i++) {
const row = values[i];
const rowNum = i + 1; // スプレッドシートの行番号 (2, 3, 4...)
// スプレッドシートの値
const status = row[COLUMN_STATUS].toString().trim();
const mailingList = row[COLUMN_MAILING_LIST].toString().trim();
const spaceId = row[COLUMN_SPACE_ID].toString().trim();
// 処理対象は「有効」の行のみ
if (status !== '有効') {
Logger.log(
`LOG: ${rowNum}行目: 状態が「${status}」のためスキップします。`,
);
continue;
}
Logger.log(
`--- ${rowNum}行目の処理を開始: ML: ${mailingList}, SpaceID: ${spaceId} ---`,
);
try {
// 1. Google ChatのスペースIDからスペース名を取得し、D列に記載
const spaceName = getSpaceNameAndLog(spaceId, rowNum);
if (spaceName) {
// D列 (COLUMN_SPACE_NAME + 1) にスペース名を書き込む
sheet.getRange(rowNum, COLUMN_SPACE_NAME + 1).setValue(spaceName);
}
// 2. メーリングリストの参加者を取得する処理
const members = getGroupMembersAndLog(mailingList, rowNum);
// 3. メーリングリストの参加者をスペースに追加する処理
if (members.length > 0) {
addMembersToSpaceAndLog(spaceId, members, rowNum);
} else {
Logger.log(
`LOG: ${rowNum}行目: メーリングリスト「${mailingList}」にメンバーがいません。`,
);
}
Logger.log(`LOG: ${rowNum}行目: 全ての処理が正常に完了しました。`);
} catch (e) {
// エラーが発生した場合は「状態」を「エラー」に変更し、ログに出力
const errorMessage = `ERROR: ${rowNum}行目: 処理中にエラーが発生しました。詳細: ${e.message}`;
Logger.log(errorMessage);
sheet.getRange(rowNum, COLUMN_STATUS + 1).setValue('エラー');
}
}
Logger.log('--- 全ての処理が終了しました ---');
}
// ------------------------------------
// --- 処理ごとの関数定義 ---
// ------------------------------------
/**
* Google ChatのスペースIDからスペース名を取得し、スプレッドシートに書き込む
* @param {string} spaceId - Google ChatのスペースID (例: AAQATO2pTpI)
* @param {number} rowNum - ログ出力用の行番号
* @returns {string | null} 取得したスペース名、または取得できなかった場合はnull
*/
function getSpaceNameAndLog(spaceId, rowNum) {
if (!spaceId) {
Logger.log(
`WARN: ${rowNum}行目: スペースIDが空欄のため、スペース名の取得をスキップします。`,
);
return null;
}
try {
const spaceResourceName = `spaces/${spaceId}`;
// Chat Advanced Service を使用してスペース情報を取得
const space = Chat.Spaces.get(spaceResourceName);
const spaceName = space.displayName;
Logger.log(`LOG: ${rowNum}行目: スペース名を取得しました: ${spaceName}`);
return spaceName;
} catch (e) {
// Chat APIのエラーが発生した場合
Logger.log(
`ERROR: ${rowNum}行目: スペース名の取得に失敗しました。スペースIDが正しいか確認してください。詳細: ${e.message}`,
);
throw new Error(`スペース名取得エラー: ${e.message}`); // 上位に投げて「エラー」ステータスにする
}
}
/**
* Googleグループ(メーリングリスト)の参加者を取得する
* @param {string} mailingList - Googleグループのメールアドレス
* @param {number} rowNum - ログ出力用の行番号
* @returns {string[]} グループメンバーのメールアドレスの配列
*/
function getGroupMembersAndLog(mailingList, rowNum) {
if (!mailingList) {
Logger.log(
`WARN: ${rowNum}行目: メーリングリストが空欄のため、メンバーの取得をスキップします。`,
);
return [];
}
const members = [];
try {
// Admin Directory Service を使用してグループメンバーを取得 (管理者権限が必要)
let pageToken;
do {
const response = AdminDirectory.Members.list(mailingList, {
pageToken: pageToken,
});
if (response.members) {
response.members.forEach((member) => {
// ユーザー(USER)のみを対象とする
if (member.type === 'USER' && member.email) {
members.push(member.email);
}
});
}
pageToken = response.nextPageToken;
} while (pageToken);
Logger.log(
`LOG: ${rowNum}行目: メーリングリスト「${mailingList}」から${members.length}名のメンバーを取得しました。`,
);
return members;
} catch (e) {
Logger.log(
`ERROR: ${rowNum}行目: メーリングリストのメンバー取得に失敗しました。アドレスや権限を確認してください。詳細: ${e.message}`,
);
throw new Error(`MLメンバー取得エラー: ${e.message}`); // 上位に投げて「エラー」ステータスにする
}
}
/**
* メンバーリストをGoogle Chatのスペースに追加する
* @param {string} spaceId - Google ChatのスペースID
* @param {string[]} members - 追加するメンバーのメールアドレスの配列
* @param {number} rowNum - ログ出力用の行番号
*/
function addMembersToSpaceAndLog(spaceId, members, rowNum) {
const spaceResourceName = `spaces/${spaceId}`;
let successCount = 0;
let failCount = 0;
Logger.log(
`LOG: ${rowNum}行目: スペース ${spaceId} にメンバーを追加します。`,
);
members.forEach((email) => {
try {
// ユーザーのリソース名を作成 (users/user@example.com の形式)
const userResourceName = `users/${email}`;
// Chat API V1 の Memberships.create に渡すリソースボディを定義
const memberResource = {
member: {
name: userResourceName,
// 💡 決定的な修正: 実行者がChat Appではなく人間(管理者)であることを明示
type: 'HUMAN',
},
};
// Chat Advanced Service を使用してスペースにメンバーを追加 (招待)
Chat.Spaces.Members.create(memberResource, spaceResourceName);
Logger.log(
`LOG: ${rowNum}行目: メンバー ${email} の追加に成功しました。`,
);
successCount++;
} catch (e) {
// 既にメンバーである場合(409 CONFLICT)もここで捕捉
// エラーメッセージをログに残し、警告とする
Logger.log(
`WARN: ${rowNum}行目: メンバー ${email} の追加に失敗しました。詳細: ${e.message}`,
);
failCount++;
}
});
Logger.log(
`LOG: ${rowNum}行目: メンバー追加処理が完了しました。成功: ${successCount}名、失敗: ${failCount}名。`,
);
// メンバー追加に失敗があった場合は、処理全体をエラーとして上位に投げる
if (failCount > 0) {
throw new Error(
`メンバー追加中に${failCount}件の失敗が発生しました。ログを確認してください。`,
);
}
}
実行方法
- スプレッドシートを開く: スクリプトをバインドしたスプレッドシートをリロードまたは開きます
- カスタムメニューの実行: スプレッドシート上部のメニューバーに新しく追加された「🤖 Chat連携処理」をクリックし、「参加者追加を実行(管理者以外実行不可)」を選択します
- 承認: 初回実行時、Admin Directory ServiceやChat Advanced Serviceの利用について管理者権限での承認が求められますので、画面の指示に従い承認してください
- ログの確認: 処理の成否はGASの実行ログで確認できます。エラーが発生した場合は、スプレッドシートの A列「状態」が「エラー」に変わるので、ログと合わせて原因を特定してください
まとめ
本記事で紹介した GAS スクリプトにより、Google Group のメンバー情報を活用し、Google Chat スペースの参加者を半自動的に最新の状態に保つことができるようになりました。
特に Admin Directory Service を利用する点や、Chat API へのリクエストで type: "HUMAN" を指定する点など、高度な機能を利用する際のポイントを押さえることで、強力な自動化を実現できます。