Google App ScriptでGoogle Drive内の特定のディレクトリ内でファイルの追加 or 更新を定期的にチェックしてメールを飛ばすやつを作ったよって話

# 事の発端

親「Google Driveのフォルダで更新とかファイル追加とかあったときにメールで通知するやつが欲しくて、ググったらいろいろ出てきたんだけど、これコピペで動く？」
私「(読む...)動かなさそう」
親「動くやつ作れる？」
私「まあできるんじゃない？」

ってことがあって作ることになった

# 動作環境

 - Google App Script

# コード

```js
/*
  更新チェックを行ないたいディレクトリのID
  URL:https://drive.google.com/drive/u/0/folders/XXXXXXXXXXXXXXXXXXXXXXX における "XXXXXXXXXXXXXXXXXXXXXXX" の部分
 */
const observedDirID = "ID";
/*
  監視対象ディレクトリのファイルリストを保存するスプレッドシートのID
  URL:https://docs.google.com/spreadsheets/d/XXXXXXXXXXXXXXXXXXXXXXXXXXX/edit における "XXXXXXXXXXXXXXXXXXXXXXXXXXX" の部分
*/
const filelistSheetID = "ID";

/*
  メールの宛先
  カンマで区切って複数設定可能
  e.g. "hoge@gmail.com, fuga@gmail.com, abcd@example.com"
*/
const dstMailAddr = "mail addresses";

//  メールを送信するか true / false
const isMailSend = true;


//  再帰的にディレクトリ内のファイルIDを取得
function getAllFilesId(targetDir) {
  let fileIDs = [];

  let files = targetDir.getFiles();
  while (files.hasNext()) {
    fileIDs.push(files.next().getId());
  }

  let childDirs = targetDir.getFolders();
  while (childDirs.hasNext()) {
    let childDir = childDirs.next();
    fileIDs = fileIDs.concat(getAllFilesId(childDir));
  }
  return fileIDs;
}

//  URLからID取得
function getUIDFromURL(url) {
    const exp = new RegExp("https?://.*?/.*?/.*?/(.*?)(?=/)");
    const uid = url.match(exp)[1];

    return uid;
}
//  ディレクトリ内のファイルの更新確認
function updateCheck() {
  const observedDir = DriveApp.getFolderById(observedDirID);
  let fileData = [];
  const fileIDs = getAllFilesId(observedDir);

  for (let i = 0; i < fileIDs.length; i ++) {
    const file = DriveApp.getFileById(fileIDs[i])
    fileData[i] = {
      name: file.getName(),
      lastUpdate: file.getLastUpdated(),
      url: file.getUrl()
    };
  }

  // スプレッドシートに記載されているフォルダ名と更新日時を取得
  const spreadsheet = SpreadsheetApp.openById(filelistSheetID);
  const sheet = spreadsheet.getActiveSheet();
  const data = sheet.getDataRange().getValues();

  // 取得したデータを連想配列に変換
  let sheetData = [];
  for (let i = 0; i < data.length; i++) {
    sheetData[i] = {
      name: data[i][0],
      lastUpdate: data[i][1],
      url: data[i][2]
    };
  }

  // 実際のフォルダとスプレッドシート情報を比較
  let updateFileList = [];
  for (const fd of fileData) {
    let isBreak = false;

    for(const sd of sheetData) {
      const fileUID = getUIDFromURL(fd.url);
      const sheetUID = getUIDFromURL(sd.url);
      if(fileUID == sheetUID) {
        if(fd.lastUpdate > sd.lastUpdate) {
          Logger.log(fd.name + " is changed");

          updateFileList.push(fd)
        }
        isBreak = true;
        break;
      }
    }
    if(!isBreak){
      Logger.log(fd.name + " is added");
      updateFileList.push(fd);
    } 
  }

  // 新規及び更新された情報をメール送信
  if (updateFileList.length == 0){
    Logger.log("not changed");

    return;
  }else {
    sheet.clearContents();
    
    let fileDataTmp = [];
    for(let i = 0; i < fileData.length; i ++) {
      fileDataTmp[i] = [
        fileData[i].name,
        fileData[i].lastUpdate,
        fileData[i].url
      ];
    }
    sheet.getRange(1, 1, fileData.length, 3).setValues(fileDataTmp);
  }
  
  if (!isMailSend) 
    return;

  let body = "下記のディレクトリで" + updateFileList.length + "個のファイルが追加 / 変更されました。\nURL: " + observedDir.getUrl() + "\n\n";

  //  ファイル名  URL の形式で表示
  for (const fd of updateFileList) {
    const parentDirs = DriveApp.getFileById(getUIDFromURL(fd.url)).getParents()
    const parentDir = parentDirs.next();
    body += "ファイル名:\t" + fd.name + "\nURL:\t\t" + fd.url + "\n親フォルダ:\t" + parentDir.getUrl() + "\n\n";
  }

  Logger.log(body);

  const title = observedDir.getName() + " フォルダが更新されました";
  const options = {
    name: "ファイル更新チェックスクリプト",
    noReply: true
  };
  MailApp.sendEmail(dstMailAddr, title, body, options);
}
```

# 解説(?)

このあたりは各種設定

```js
/*
  更新チェックを行ないたいディレクトリのID
  URL:https://drive.google.com/drive/u/0/folders/XXXXXXXXXXXXXXXXXXXXXXX における "XXXXXXXXXXXXXXXXXXXXXXX" の部分
 */
const observedDirID = "ID";
/*
  監視対象ディレクトリのファイルリストを保存するスプレッドシートのID
  URL:https://docs.google.com/spreadsheets/d/XXXXXXXXXXXXXXXXXXXXXXXXXXX/edit における "XXXXXXXXXXXXXXXXXXXXXXXXXXX" の部分
*/
const filelistSheetID = "ID";

/*
  メールの宛先
  カンマで区切って複数設定可能
  e.g. "hoge@gmail.com, fuga@gmail.com, abcd@example.com"
*/
const dstMailAddr = "mail addresses";

//  メールを送信するか true / false
const isMailSend = true;
```

ディレクトリ内のすべてのファイルのIDを取得する関数とURLからファイルとかのユニークIDを取得する関数

```js
//  再帰的にディレクトリ内のファイルIDを取得
function getAllFilesId(targetDir) {
  let fileIDs = [];

  let files = targetDir.getFiles();
  while (files.hasNext()) {
    fileIDs.push(files.next().getId());
  }

  let childDirs = targetDir.getFolders();
  while (childDirs.hasNext()) {
    let childDir = childDirs.next();
    fileIDs = fileIDs.concat(getAllFilesId(childDir));
  }
  return fileIDs;
}

//  URLからID取得
function getUIDFromURL(url) {
    const exp = new RegExp("https?://.*?/.*?/.*?/(.*?)(?=/)");
    const uid = url.match(exp)[1];

    return uid;
}
```

↑の関数を使ってディレクトリ内のファイルのIDを全取得して、ファイルIDからファイル名、更新日時、ファイルのURLを取得

```js
//  ディレクトリ内のファイルの更新確認
function updateCheck() {
  const observedDir = DriveApp.getFolderById(observedDirID);
  let fileData = [];
  const fileIDs = getAllFilesId(observedDir);

  for (let i = 0; i < fileIDs.length; i ++) {
    const file = DriveApp.getFileById(fileIDs[i])
    fileData[i] = {
      name: file.getName(),
      lastUpdate: file.getLastUpdated(),
      url: file.getUrl()
    };
  }
```

スプレッドシートに格納されてるファイル情報も同じように取得

```js
  // スプレッドシートに記載されているフォルダ名と更新日時を取得
  const spreadsheet = SpreadsheetApp.openById(filelistSheetID);
  const sheet = spreadsheet.getActiveSheet();
  const data = sheet.getDataRange().getValues();

  // 取得したデータを連想配列に変換
  let sheetData = [];
  for (let i = 0; i < data.length; i++) {
    sheetData[i] = {
      name: data[i][0],
      lastUpdate: data[i][1],
      url: data[i][2]
    };
  }
```

現在のファイルの状態とスプレッドシートのデータをforで回して、更新日時が変更されていればリストに追加
現在のファイルにあって、スプレッドシートになければ新規追加なのでリストに追加

```js
  // 実際のフォルダとスプレッドシート情報を比較
  let updateFileList = [];
  for (const fd of fileData) {
    let isBreak = false;

    for(const sd of sheetData) {
      const fileUID = getUIDFromURL(fd.url);
      const sheetUID = getUIDFromURL(sd.url);
      if(fileUID == sheetUID) {
        if(fd.lastUpdate > sd.lastUpdate) {
          Logger.log(fd.name + " is changed");

          updateFileList.push(fd)
        }
        isBreak = true;
        break;
      }
    }
    if(!isBreak){
      Logger.log(fd.name + " is added");
      updateFileList.push(fd);
    } 
  }
```

ファイル更新・追加があればスプレッドシートの情報を更新

```js
  // 新規及び更新された情報をメール送信
  if (updateFileList.length == 0){
    Logger.log("not changed");

    return;
  }else {
    sheet.clearContents();
    
    let fileDataTmp = [];
    for(let i = 0; i < fileData.length; i ++) {
      fileDataTmp[i] = [
        fileData[i].name,
        fileData[i].lastUpdate,
        fileData[i].url
      ];
    }
    sheet.getRange(1, 1, fileData.length, 3).setValues(fileDataTmp);
  }
```

ここらへんはメールを送信するだけ

```js
  if (!isMailSend) 
    return;

  let body = "下記のディレクトリで" + updateFileList.length + "個のファイルが追加 / 変更されました。\nURL: " + observedDir.getUrl() + "\n\n";

  //  ファイル名  URL の形式で表示
  for (const fd of updateFileList) {
    const parentDirs = DriveApp.getFileById(getUIDFromURL(fd.url)).getParents()
    const parentDir = parentDirs.next();
    body += "ファイル名:\t" + fd.name + "\nURL:\t\t" + fd.url + "\n親フォルダ:\t" + parentDir.getUrl() + "\n\n";
  }

  Logger.log(body);

  const title = observedDir.getName() + " フォルダが更新されました";
  const options = {
    name: "ファイル更新チェックスクリプト",
    noReply: true
  };
  MailApp.sendEmail(dstMailAddr, title, body, options);
}
```

# あとがき

 - GAS普段使わからんから調べまくった
 - 実はこれ作ったの半年くらい前