Googleドライブ上に「私書箱」を作りませんか

## はじめに

Googleの共有ドライブはとても便利なものです。しかし、共有ドライブではマイドライブ度は違い、ファイルやフォルダを作るときに、ドライブや親フォルダよりも権限を絞ることができません。
全員にそれぞれその人専用の資料を共有したい、でも互いに見れるようにはしたくない...。（人事関連、経理関連の伝達事項など）
そして一人一人にファイルの場所を伝えて回らなくて済むよう、格納されているトップフォルダのurlを共有するだけで済むようにしたい。

## 概要
ということで、マイドライブに「私書箱」を作りました。指定した人と編集者しか中身を見られないフォルダです。
この私書箱フォルダに入れたファイルは、自動的に親フォルダの権限に準じるので管理が楽々。

GASでこちらを実現するコードを組みました。

## 私書箱の作成＆閲覧権限設定

__まずは私書箱たちを格納するトップフォルダを作り、私書箱を使うであろう集団に閲覧権限を付与します。__

これはGoogleDriveの仕様上、必ず一番最初に行う必要があります。上位フォルダの権限を追加してしまうと、下位フォルダに全部権限が付与されてしまうので、超重要ステップです。

以降はGASでコードを書いていきます。

トップフォルダのIDとフォルダそのものを取得します。
```js
const topFolderUrl = {私書箱たちを格納するフォルダのurl}
const topFolderId = topFolderUrl.replace(/.*\/folders\//, "").replace(/\?.*/, "");
const topFolder = DriveApp.getFolderById(topFolderId);
```
私書箱を作っていきます。

```js
const targetFolder = topFolder.createFolder({フォルダの名前});
const targetFolderId = targetFolder.getId();
```

トップフォルダの閲覧権限を持っているメールアドレスを指定し、権限を削除します。

```js
targetFolder.removeViewer({閲覧権限を削除したいメールアドレス});
```

私書箱に任意の編集者と閲覧者を追加します。

```js
// listWriterEmails = 編集権限を持たせたいメールアドレスのリスト
listWriterEmails.forEach(function (email) {
    Drive.Permissions.insert(
        { 'role': 'writer', 'type': 'user', 'value': email },
        targetFolderId,
        { 'sendNotificationEmails': 'false' }
    );
});
```

```js
// listReaderEmails = 閲覧権限を持たせたいメールアドレスのリスト
listReaderEmails.forEach(function (email) {
    // 閲覧者のメールアドレスを追加
    Drive.Permissions.insert(
        { 'role': 'reader', 'type': 'user', 'value': email },
        targetFolderId,
        { 'sendNotificationEmails': 'false' }
    );
});
```

## 追加設定

GoogleDriveでは同名のフォルダが存在し得てしまうので、既にあるかどうか判定する場合はこのようにします

```js
const folderIterator = topFolder.getFoldersByName({フォルダの名前});
if (!folderIterator.hasNext()) {
    // ここに私書箱を作成する処理
}
```

GoogleWorkspace有料プランではGAS実行時間に30分の制限があるので、それを乗り越えるためにトリガーとプロパティをセットします。（無料プランだと制限は6分です。）
作るフォルダ数があまり多くないという場合は必要ないかもしれません。


```js
function makeMailbox() {
    const functionName = 'makeMailbox'
    const startTime = new Date();

    // プロパティに'folderCount'があればその番号から再開、なければ0から
    let startIndex = Number(PropertiesService.getScriptProperties().getProperty('folderCount')) || 0;
    let folderCount = startIndex;

    for (let index = startIndex; index < {作りたいフォルダ名のリスト}.length; index++) {

        // 関数の実行開始から25分経過したらreturnして強制的に実行を中断する
        const currentTime = new Date();
        const seconds = (currentTime - startTime) / 1000; // 経過秒数を計算
        if (seconds > 1500) { 
            // トリガーとプロパティを設定
            PropertiesService.getScriptProperties().setProperty('folderCount', folderCount);
            setTrigger(functionName);
            return;
        }
    
        // ここに{作りたいフォルダ名のリスト}を用いて各私書箱を作成する処理

        folderCount++;
    }

    // すべての私書箱を作成し終えたらトリガーとプロパティを削除
    let triggers = ScriptApp.getProjectTriggers();
        for (let trigger of triggers) {
        ScriptApp.deleteTrigger(trigger);
        };
    PropertiesService.getScriptProperties().deleteProperty('folderCount');
}

function setTrigger(functionName) {
    // 同名のトリガーがすでにある場合はそれを削除
    let triggers = ScriptApp.getProjectTriggers();
    for (let trigger of triggers) {
        if (trigger.getHandlerFunction() == functionName) {
          ScriptApp.deleteTrigger(trigger);
        }
    };

  // 1分後にトリガーが発動するように予約
  ScriptApp.newTrigger(functionName).timeBased().after(60000).create();
}
```

## さいごに
いかがでしたでしょうか。まだまだ改善の余地はあると思いますが、ファイルと適切な名前のフォルダに格納するスクリプトと組み合わせると、非常に便利なものになると思います。
誰かのお役に立てれば幸いです。