🌟

GASを使ってドメイン内のすべてのGoogleグループ設定を出力する

2023/04/30に公開

定期的にGASでいろんなコードを書いているのですが、私の中でも書き方の流行り廃りが結構あったりします。
ドメイン内のすべてのGoogleグループ設定を出力する というコードを最近書いたので、どういうコードを書いたのかを解説とセットで残しておこうかなと。

別にGoogleグループの設定なんて出力しないって方も、よりよいGASのコードを書きたいって方はぜひ見てってほしいです。(たぶん)参考になるところがあるはずです。

ちなみにGoogleグループ設定ってなに?そんなの気にしたことがない。って方は下記の吉田さんのnoteをみるとよいです。
Googleグループの設定と管理|吉田航|note

本記事で作るもの

こんな感じでサクッとGWS内の全てのグループ設定が出力できます。

事前準備

適当にスプレッドシートを作ってご自身のGWSのドメイン名のシートを作っといてください。

GASを作る

コード

下記のGASコードをコピペしてください。

const ACTIVE_USER_EMAIL = Session.getActiveUser().getEmail();
const DOMAIN_NAME = ACTIVE_USER_EMAIL.substring(ACTIVE_USER_EMAIL.indexOf('@') + 1);

const SHEET_HEADERS = [
  'email',
  'whoCanJoin',
  'whoCanViewMembership',
  'whoCanViewGroup',
  'whoCanInvite',
  'whoCanAdd',
  'allowExternalMembers',
  'whoCanPostMessage',
  'allowWebPosting',
  'primaryLanguage',
  'maxMessageBytes',
  'isArchived',
  'archiveOnly',
  'messageModerationLevel',
  'spamModerationLevel',
  'replyTo',
  'includeCustomFooter',
  'customFooterText',
  'sendMessageDenyNotification',
  'defaultMessageDenyNotificationText',
  'showInGroupDirectory',
  'allowGoogleCommunication',
  'membersCanPostAsTheGroup',
  'messageDisplayFont',
  'includeInGlobalAddressList',
  'whoCanLeaveGroup',
  'whoCanContactOwner',
  'whoCanAddReferences',
  'whoCanAssignTopics',
  'whoCanUnassignTopic',
  'whoCanTakeTopics',
  'whoCanMarkDuplicate',
  'whoCanMarkNoResponseNeeded',
  'whoCanMarkFavoriteReplyOnAnyTopic',
  'whoCanMarkFavoriteReplyOnOwnTopic',
  'whoCanUnmarkFavoriteReplyOnAnyTopic',
  'whoCanEnterFreeFormTags',
  'whoCanModifyTagsAndCategories',
  'favoriteRepliesOnTop',
  'whoCanApproveMembers',
  'whoCanBanUsers',
  'whoCanModifyMembers',
  'whoCanApproveMessages',
  'whoCanDeleteAnyPost',
  'whoCanDeleteTopics',
  'whoCanLockTopics',
  'whoCanMoveTopicsIn',
  'whoCanMoveTopicsOut',
  'whoCanPostAnnouncements',
  'whoCanHideAbuse',
  'whoCanMakeTopicsSticky',
  'whoCanModerateMembers',
  'whoCanModerateContent',
  'whoCanAssistContent',
  'customRolesEnabledForSettingsToBeMerged',
  'enableCollaborativeInbox',
  'whoCanDiscoverGroup',
  'defaultSender',
];

function onOpen() {
  SpreadsheetApp.getUi().createMenu('Googleグループ設定出力')
    .addItem('実行', 'writeGroupsSettingsList')
    .addToUi();
}

function writeGroupsSettingsList() {
  const sheet = SheetsService.getGroupsSheet({sheetName: DOMAIN_NAME});
  SheetsService.clearSheet({sheet, headers: SHEET_HEADERS});

  const allGroupsSettings = GroupService.listGoogleGroupSettings();

  const rowValues = allGroupsSettings.map((gs) => {
    const row = [];
    SHEET_HEADERS.forEach(title => {
      row.push(gs[title] || '');
    })
    return row;
  });

  const startRow = 2;
  sheet.getRange(startRow, 1, rowValues.length, SHEET_HEADERS.length).setValues(rowValues);
}

class GroupService {
  static listGoogleGroupSettings() {
    const allGroupsSettings = [];
    let pageToken = '';
    let page;
    do {
      console.log('DOMAIN_NAME', DOMAIN_NAME);
      // @see https://developers.google.com/apps-script/advanced/admin-sdk-directory
      page = AdminDirectory.Groups.list({
        domain: DOMAIN_NAME,
        maxResults: 200,
        pageToken,
      });

      page.groups.forEach((g) => {
        console.log('groupKey', g.email);
        // https://developers.google.com/apps-script/advanced/admin-sdk-groups-settings
        // https://developers.google.com/admin-sdk/groups-settings/v1/reference/groups
        const groupSettings = AdminGroupsSettings.Groups.get(g.email);
        allGroupsSettings.push(groupSettings);
      });

      pageToken = page.nextPageToken;
    } while (pageToken);

    return allGroupsSettings;
  }
}

class SheetsService {
  static clearSheet({sheet, headers}) {
    sheet.clearContents();
    sheet.getRange(1, 1, 1, headers.length).setValues([headers]);
  };
  
  static getGroupsSheet({sheetName}) {
    return SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName);
  }
}

サービスを有効化

下記の2つのサービスを追加してください。

  • AdminDirectory
  • AdminGroupsSettings

コードとサービスの有効化が終わると下記のような状態になります。

実行

スプレッドシートを再読込するとメニューに Googleグループ設定出力 がでているので、特権ユーザーで実行すればグループ設定が出力されます。
※ 最初の1回だけ認可だけが走るので、2回実行が必要になります

UIの調整

列や行の固定、交互の背景色等を設定してUIを調整すると良いでしょう。

実装時のポイント

実装時のポイントをいくつか解説します。

実行ユーザーをうまくつかってドメインを取る

const ACTIVE_USER_EMAIL = Session.getActiveUser().getEmail();
const DOMAIN_NAME = ACTIVE_USER_EMAIL.substring(ACTIVE_USER_EMAIL.indexOf('@') + 1);

Session.getActiveUser().getEmail() でGASの実行ユーザーのメールアドレスが取れます。
複数GWS契約していても対応できますし、コードの可搬性を考えて、ドメインはベタ書きせずに動的にとると良いでしょう。

Web上では Session.getActiveUser().getUserLoginId() を使ってメールアドレスを取得するコードが散見されますが、サポート終了していますので使わないように。
https://developers.google.com/apps-script/reference/base/user?hl=ja#getuserloginid

APIのキー名を利用してシートのヘッダーを定義

(こだわりがなければ)シートのヘッダーはGroups Settings APIで取得できる項目をそのまま使いましょう。
そうすることで、 group['key'] = 'value'; のように何度も値を入れる必要がなくなります。

const SHEET_HEADERS = [
  'email',
  'whoCanJoin',
...

ヘッダーの値を定義しておくことで、再実行時必要なシートのクリア処理で値を全部消す→ヘッダー行を設定、の流れをきれいに作れます。
また、シートのクリアを clearContents() にしているのでシートの書式設定は残るようにしてあります。交互の背景色や、条件付き書式を作っても再実行でそのまま残せます。

class SheetsService {
  static clearSheet({sheet, headers}) {
    sheet.clearContents();
    sheet.getRange(1, 1, 1, titles.length).setValues([headers]);
  };
...

Classを使う

GroupService, SheetsService のようにClassを使っています。これはいろいろな理由があるのですが、特に重要な2点だけ解説しておきます。

1つめはファイル分割をしやすくする(依存関係を明確にする)ためです。
現状は大したコード量じゃないため1ファイルで書いていますが、コードが増えてくると可読性が下がっていきます。のちのちファイルを分けることを考慮して事前にClassを切って下地を作っておく、ということです。

2つめはグローバル定義された関数をへらすためです。
下記画像のように、手動実行時の関数リストはグローバルで定義された関数を引っ張ってきます。
直接実行する必要がない関数はClassを使って隠蔽しておくと、手動実行時にでてきませんし、トリガー設定時の候補にもでてきません。不要なものは隠すことでよりよいコードになります。

ちなみにFunction名の最後に _ をつけることでも隠すことができます。
classを作らずに隠したい場合は使うと良いと思います。

API実行はリファレンスのURLをコメントで残す

何をコメントに残すかは悩ましいですが、とりあえずコメントに絶対に残すべきなのはリファレンスのURLです。
@see とかもつけてもいいかもしれません。

  // https://developers.google.com/apps-script/advanced/admin-sdk-groups-settings
  // https://developers.google.com/admin-sdk/groups-settings/v1/reference/groups
  const groupSettings = AdminGroupsSettings.Groups.get(g.email);

Discussion